From 47cabfb0a9059685b7ab5eee81a7bcca4095cc7b Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Thu, 22 Feb 2024 14:06:45 +1100 Subject: [PATCH] Add example data and test, fix ungrouping, extend docs --- README.md | 8 ++++++ deps.edn | 7 +++-- .../example-paychex-pay-item-details.csv | 28 +++++++++++++++++++ src/import.clj | 24 ++++++++++++++-- test/import_test.clj | 19 +++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 resources/example-paychex-pay-item-details.csv create mode 100644 test/import_test.clj diff --git a/README.md b/README.md index 5cac3b8..1a86921 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Paychex payroll importer for Beancount +The aim of this tool is to automate the monthly task of transcribing payroll CSV +information for Conservancy's 8 (currently) employees into ~ 300 lines of fairly +intricate Beancount bookkeeping data. + ## Usage Run with: @@ -16,6 +20,10 @@ Or use a reduced set of options (missing values will be replaced by "TODO" in th The project is set up for development in Emacs and CIDER-mode. Open a source file and run `cider-jack-in`. +Run tests with: + + clojure -M:test + You can run without building using: clojure -M -m core --csv 2023-12-27_Pay-Item-Details_2023-12-2.csv --date 2023-12-29 --period "December 2023" --total-fees 206.50 diff --git a/deps.edn b/deps.edn index 0fa6d3c..bb96f00 100644 --- a/deps.edn +++ b/deps.edn @@ -1,9 +1,12 @@ -{:deps { +{:paths ["src" "resources"] + :deps { org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/data.csv {:mvn/version "1.0.1"} org.clojure/tools.cli {:mvn/version "1.1.230"}} :aliases {:dev {:extra-deps {lambdaisland/deep-diff2 {:mvn/version "2.10.211"}}} - ;; Run with clj -T:build function-in-build + :test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.87.1366"}} + :main-opts ["-m" "kaocha.runner"]} + ;; Run with clj -T:build function-in-build :build {:deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}} :ns-default build}}} diff --git a/resources/example-paychex-pay-item-details.csv b/resources/example-paychex-pay-item-details.csv new file mode 100644 index 0000000..27e75c8 --- /dev/null +++ b/resources/example-paychex-pay-item-details.csv @@ -0,0 +1,28 @@ +Example Co Inc,"Citizen, Jack A",1,403b EE Pretax,Retirement,,,,,,1000,,,,, +Example Co Inc,"Citizen, Jack A",1,Exp Reimb Non Tax,Reimbursement,,,,,50,,,,,, +Example Co Inc,"Citizen, Jack A",1,Fed Income Tax,Withholding,,,,,,,,470.22,,, +Example Co Inc,"Citizen, Jack A",1,Fed Unemploy,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jack A",1,Medicare,Withholding,,,,,,,,88,,, +Example Co Inc,"Citizen, Jack A",1,Medicare,Liability,,,,,,,,,88,, +Example Co Inc,"Citizen, Jack A",1,Net Pay,Net Pay,,,,,,,,,,,4184.49 +Example Co Inc,"Citizen, Jack A",1,Salary,Earnings,6068.99,,0,,,,,,,, +Example Co Inc,"Citizen, Jack A",1,Social Security,Withholding,,,,,,,,376.28,,, +Example Co Inc,"Citizen, Jack A",1,Social Security,Liability,,,,,,,,,376.28,, +Example Co Inc,"Citizen, Jack A",1,TN Unemploy,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jill B",2,403b EE Pretax,Retirement,,,,,,820,,,,, +Example Co Inc,"Citizen, Jill B",2,Exp Reimb Non Tax,Reimbursement,,,,,50,,,,,, +Example Co Inc,"Citizen, Jill B",2,Fed Income Tax,Withholding,,,,,,,,681.01,,, +Example Co Inc,"Citizen, Jill B",2,Fed Unemploy,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jill B",2,Medicare,Liability,,,,,,,,,99.28,, +Example Co Inc,"Citizen, Jill B",2,Medicare,Withholding,,,,,,,,99.29,,, +Example Co Inc,"Citizen, Jill B",2,Net Pay,Net Pay,,,,,,,,,,,4397.39 +Example Co Inc,"Citizen, Jill B",2,OR Disability PFL,Withholding,,,,,,,,41.08,,, +Example Co Inc,"Citizen, Jill B",2,OR Disability PFL,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jill B",2,OR EE Work Bene,Withholding,,,,,,,,0,,, +Example Co Inc,"Citizen, Jill B",2,OR ER Work Bene,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jill B",2,OR Income Tax,Withholding,,,,,,,,427.8,,, +Example Co Inc,"Citizen, Jill B",2,OR TRANS STT,Withholding,,,,,,,,6.03,,, +Example Co Inc,"Citizen, Jill B",2,OR Unemploy,Liability,,,,,,,,,0,, +Example Co Inc,"Citizen, Jill B",2,Salary,Earnings,6847.12,,0,,,,,,,, +Example Co Inc,"Citizen, Jill B",2,Social Security,Liability,,,,,,,,,424.52,, +Example Co Inc,"Citizen, Jill B",2,Social Security,Withholding,,,,,,,,424.52,,, diff --git a/src/import.clj b/src/import.clj index 07fdc13..d0310dd 100644 --- a/src/import.clj +++ b/src/import.clj @@ -99,6 +99,26 @@ (str/join (for [[k v] meta] (format " %s: \"%s\"\n" (name k) v))))))))) +;; Each of the below functions returns one of the five sections of the required +;; bookkeeping data: +;; +;; * net pay (single transaction) +;; * individual taxes (one transaction for each employee) +;; * employer taxes (single transaction) +;; * fees (single transaction) +;; * retirement (single trasaction) +;; +;; These functions take the input CSV data, pre-formatted and grouped by +;; employee. +;; +;; The output is an intermediate data structure that can then be run through +;; `render-transaction` to produce Beancount transaction text. The data +;; structures can also be programatically compared to a parsed version of the +;; hand-written Beancount transactions for development and testing (see +;; parse.clj). +;; +;; (All functions return a sequence of transactions so we can concatenate them.) + (defn payroll "Return a net pay transaction." [date period receipt-no groups] @@ -201,10 +221,10 @@ (defn employer-taxes "Return an employer taxes transaction." [date period receipt-no groups] - (let [ungrouped (concat (vals groups)) ; Grouping by employee not useful here + (let [ungrouped (apply concat (vals groups)) ; Grouping by employee not useful here liability-lines (filter #(and (= (:type %) "Liability") (not (str/includes? (:category %) "Unemploy"))) ungrouped) - liability-postings (for [{:keys [category amount]} liability-lines] + liability-postings (for [{:keys [name category amount]} liability-lines] {:account "Expenses:Payroll:Tax" :amount amount :currency "USD" diff --git a/test/import_test.clj b/test/import_test.clj new file mode 100644 index 0000000..78e8069 --- /dev/null +++ b/test/import_test.clj @@ -0,0 +1,19 @@ +(ns import-test + (:require [import :as sut] + [clojure.java.io] + [clojure.test :as t])) + +(t/deftest sample-import + (let [grouped-data (import/read-grouped-csv + (clojure.java.io/resource + "example-paychex-pay-item-details.csv")) + imported (concat (import/payroll "2023-12-29" "December 2023" "rt:19462/674660" grouped-data) + (import/individual-taxes "2023-12-29" "December 2023" "rt:19462/674660" "rt:19403/675431" grouped-data) + (import/employer-taxes "2023-12-29" "December 2023" "rt:19462/674660" grouped-data) + (import/payroll-fees "2023-12-29" "December 2023" "rt:19459/675387" "rt:19459/674887" 206.50M grouped-data) + (import/retirement "2024-01-02" "December 2023" "rt:19403/676724" "rt:19403/675431" grouped-data))] + (t/is (= (count imported) 6)))) + +(comment + (t/run-all-tests) +)