Compare commits
10 commits
8029f8b60c
...
ea57f67f7c
Author | SHA1 | Date | |
---|---|---|---|
ea57f67f7c | |||
11626fb3d4 | |||
da22fc9339 | |||
0a5f798aa4 | |||
a1b59bc609 | |||
ebb3cefeb7 | |||
59a15c3d6f | |||
dc5692c718 | |||
85ce83f0b6 | |||
2d65abbd58 |
10 changed files with 412 additions and 232 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
/src/examples.clj
|
||||
/private/
|
||||
/.cpcache/
|
||||
/target/
|
||||
/.nrepl-port
|
||||
|
|
29
README.md
29
README.md
|
@ -4,15 +4,22 @@ 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:
|
||||
See a demo with:
|
||||
|
||||
java -jar import-0.0.8-standalone.jar --csv resources/example-paychex-pay-item-details.csv --date 2023-12-29 --period 'December 2023' --total-fees 206.50 --pay-receipt-no rt:19462/674660 --pay-invoice-no rt:19403/675431 --fees-receipt-no rt:19459/675387 --fees-invoice-no rt:19459/674887 --retirement-receipt-no rt:19403/676724 --retirement-invoice-no rt:19403/675431
|
||||
java -jar import-x.x.x-standalone.jar --demo
|
||||
|
||||
Or use a reduced set of options (missing values will be replaced by "TODO" in the output):
|
||||
Provide your own payroll data with:
|
||||
|
||||
java -jar import-0.0.8-standalone.jar --csv resources/example-paychex-pay-item-details.csv --date 2023-12-29 --period "December 2023" --total-fees 206.50
|
||||
java -jar import-x.x.x-standalone.jar --csv resources/example-paychex-pay-item-details.csv --total-fees 206.50
|
||||
|
||||
In the above, various values such as the date, time period covered and
|
||||
receipt/invoice values will be replace with "TODO" placeholders. You can
|
||||
alternatively provide any/all of these explicitly:
|
||||
|
||||
java -jar import-x.x.x-standalone.jar --csv resources/example-paychex-pay-item-details.csv --date 2023-12-29 --period 'December 2023' --total-fees 206.50 --pay-receipt-no rt:19462/674660 --pay-invoice-no rt:19403/675431 --fees-receipt-no rt:19459/675387 --fees-invoice-no rt:19459/674887 --retirement-receipt-no rt:19403/676724 --retirement-invoice-no rt:19403/675431
|
||||
|
||||
You can test the output in Beancount by adding the following header entries to define the accounts:
|
||||
|
||||
|
@ -32,20 +39,22 @@ Then run Beancount with:
|
|||
|
||||
## Development
|
||||
|
||||
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
|
||||
bin/test
|
||||
|
||||
You can run without building using:
|
||||
|
||||
clojure -M -m core --csv resources/example-paychex-pay-item-details.csv --date 2023-12-29 --period "December 2023" --total-fees 206.50
|
||||
bin/dev --csv resources/example-paychex-pay-item-details.csv --total-fees 206.50
|
||||
|
||||
The project is set up for development in Emacs and CIDER-mode. Open a source
|
||||
file and run `cider-jack-in`.
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
To build, run:
|
||||
|
||||
clj -T:build uber
|
||||
bin/build
|
||||
|
||||
This will output a JAR file like `target/import-x.x.x-standalone.jar`.
|
||||
|
|
2
bin/build
Executable file
2
bin/build
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env sh
|
||||
clojure -T:build uber
|
2
bin/dev
Executable file
2
bin/dev
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env sh
|
||||
clojure -M -m core "$@"
|
2
bin/test
Executable file
2
bin/test
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env sh
|
||||
clojure -M:test "$@"
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
(defn uber [_]
|
||||
(clean nil)
|
||||
(b/copy-dir {:src-dirs ["src"]
|
||||
(b/copy-dir {:src-dirs ["src" "resources"]
|
||||
:target-dir class-dir})
|
||||
(b/copy-dir {:src-dirs ["src" "resources"]
|
||||
:target-dir class-dir})
|
||||
(b/compile-clj {:basis @basis
|
||||
:ns-compile '[core]
|
||||
|
|
2
deps.edn
2
deps.edn
|
@ -1,4 +1,4 @@
|
|||
{:paths ["src" "resources"]
|
||||
{:paths ["src" "resources" "private"] ;; Private is not include in the build
|
||||
:deps {
|
||||
org.clojure/clojure {:mvn/version "1.11.1"}
|
||||
org.clojure/data.csv {:mvn/version "1.0.1"}
|
||||
|
|
72
src/core.clj
72
src/core.clj
|
@ -3,7 +3,9 @@
|
|||
|
||||
(ns core
|
||||
"Beancount importer for Paychex Pay Item Details report."
|
||||
(:require [clojure.string :as str]
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.cli :refer [parse-opts]]
|
||||
[import :as import])
|
||||
(:gen-class))
|
||||
|
@ -20,6 +22,8 @@
|
|||
:default 0M]
|
||||
[nil "--pay-receipt-no REFERENCE" "Payroll receipt number, eg. \"rt:111/222\""
|
||||
:default "TODO-PAY-RECEIPT"]
|
||||
[nil "--pay-invoice-no REFERENCE" "Payroll invoice number, eg. \"rt:111/222\""
|
||||
:default "TODO-PAY-INVOICE"]
|
||||
[nil "--fees-receipt-no REFERENCE" "Paychex fees receipt number, eg. \"rt:111/222\""
|
||||
:default "TODO-FEES-RECEIPT"]
|
||||
[nil "--fees-invoice-no REFERENCE" "Paychex fees invoice number, eg. \"rt:111/222\""
|
||||
|
@ -34,11 +38,23 @@
|
|||
:parse-fn #(str/split % #":")
|
||||
:default {}
|
||||
:assoc-fn (fn [m k [name proj]] (assoc-in m [k name] proj))]
|
||||
[nil "--demo" "Produce demo output based made-up payroll data. Useful for documentation."]
|
||||
["-h" "--help"]])
|
||||
|
||||
(defn unmatched-employees
|
||||
"Identify any mismatches between employees in the pay run and --project employee allocations."
|
||||
[records projects]
|
||||
(set/difference (set (keys projects)) (->> records (map :name) set)))
|
||||
|
||||
(def demo-options
|
||||
{:csv (io/resource "example-paychex-pay-item-details.csv")
|
||||
:date "2024-01-01"
|
||||
:period "January 2024"
|
||||
:total-fees 66.67M})
|
||||
|
||||
(defn -main [& args]
|
||||
(let [{:keys [options errors summary]} (parse-opts args cli-options)
|
||||
{:keys [date period pay-receipt-no total-fees fees-receipt-no fees-invoice-no retirement-receipt-no retirement-invoice-no project]} options]
|
||||
options (if (:demo options) (merge options demo-options) options)]
|
||||
(when (:help options)
|
||||
(println summary)
|
||||
(System/exit 0))
|
||||
|
@ -47,14 +63,24 @@
|
|||
(str "The following errors occurred while parsing your command:\n\n"
|
||||
(str/join \newline errors)))
|
||||
(System/exit 1))
|
||||
(let [data (import/read-csv (:csv options))
|
||||
imported (concat (import/payroll date period pay-receipt-no project data)
|
||||
(import/individual-taxes date period pay-receipt-no retirement-invoice-no project data)
|
||||
(import/employer-taxes date period pay-receipt-no project data)
|
||||
(import/payroll-fees date period fees-receipt-no fees-invoice-no total-fees project data)
|
||||
(import/retirement date period retirement-receipt-no retirement-invoice-no data))]
|
||||
(let [{:keys [date period pay-receipt-no pay-invoice-no total-fees project]} options
|
||||
{:keys [fees-receipt-no fees-invoice-no retirement-receipt-no retirement-invoice-no]} options
|
||||
records (import/read-csv (:csv options))
|
||||
imported (concat (import/net-pay date period pay-invoice-no project records)
|
||||
(import/individual-taxes date period pay-invoice-no retirement-invoice-no project records)
|
||||
(import/employer-taxes date period pay-invoice-no project records)
|
||||
(import/net-pay-ach-debit date period pay-receipt-no pay-invoice-no project records)
|
||||
(import/taxes-ach-debit date period pay-receipt-no pay-invoice-no project records)
|
||||
(import/fees date period fees-receipt-no fees-invoice-no total-fees project records)
|
||||
(import/retirement date period retirement-receipt-no retirement-invoice-no records))
|
||||
unmatched (unmatched-employees records project)]
|
||||
(when-not (empty? unmatched)
|
||||
(println
|
||||
(str "Could not find these employees in the payroll:\n\n"
|
||||
(str/join ", " unmatched)))
|
||||
(System/exit 1))
|
||||
(doseq [i imported]
|
||||
(println (import/render-transaction i))))))
|
||||
(println (import/render-transaction i))))))
|
||||
|
||||
(comment
|
||||
;; Examples to exercise the importer during development.
|
||||
|
@ -65,29 +91,19 @@
|
|||
;; These examples are not included with the code for privacy reasons.
|
||||
(require '[examples])
|
||||
|
||||
(def data (import/read-csv "/home/ben/Downloads/2023-12-27_Pay-Item-Details_2023-12-2.csv"))
|
||||
(def records (import/read-csv "/home/ben/Downloads/2024-01-29_Pay-Item-Details_2024-01.csv"))
|
||||
(def imported
|
||||
(concat (import/payroll "2023-12-29" "December 2023" "rt:19462/674660" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/individual-taxes "2023-12-29" "December 2023" "rt:19462/674660" "rt:19403/675431" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/employer-taxes "2023-12-29" "December 2023" "rt:19462/674660" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/payroll-fees "2023-12-29" "December 2023" "rt:19459/675387" "rt:19459/674887" 206.50M {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/retirement "2024-01-02" "December 2023" "rt:19403/676724" "rt:19403/675431" data)))
|
||||
(concat (import/net-pay "2024-01-31" "January 2024" "rt:19462/685751" {} records)
|
||||
(import/individual-taxes "2024-01-31" "January 2024" "rt:19462/685751" "rt:19403/685602" {} records)
|
||||
(import/employer-taxes "2024-01-31" "January 2024" "rt:19462/685751" {} records)
|
||||
(import/net-pay-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
|
||||
(import/taxes-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
|
||||
(import/fees "2024-01-31" "January 2024" "rt:19459/675387" "rt:19459/674887" 206.50M {} records)
|
||||
(import/retirement "2024-01-31" "January 2024" "rt:19403/685929" "rt:19403/685602" records)))
|
||||
;; Compare hand-written transactions with imported (ignoring ordering).
|
||||
(dd/pretty-print
|
||||
(dd/diff
|
||||
(sort-postings (parse examples/dec-2023))
|
||||
(sort-postings imported)))
|
||||
|
||||
(def data (import/read-csv "/home/ben/Downloads/2024-01-29_Pay-Item-Details_2024-01.csv"))
|
||||
(def imported
|
||||
(concat (import/payroll "2024-01-31" "January 2024" "rt:19462/685751" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/individual-taxes "2024-01-31" "January 2024" "rt:19462/685751" "rt:19403/685602" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/employer-taxes "2024-01-31" "January 2024" "rt:19462/685751" {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/payroll-fees "2024-01-31" "January 2024" "rt:19459/675387" "rt:19459/674887" 206.50M {"Sharp-Sage-A" "Outreachy"} data)
|
||||
(import/retirement "2024-01-31" "January 2024" "rt:19403/685929" "rt:19403/685602" data)))
|
||||
(dd/pretty-print
|
||||
(dd/diff
|
||||
(sort-postings (parse examples/jan-2024))
|
||||
(sort-postings (parse examples/jan-2024-with-liabilities))
|
||||
(sort-postings imported)))
|
||||
|
||||
;; Print out text transactions
|
||||
|
|
363
src/import.clj
363
src/import.clj
|
@ -65,6 +65,19 @@
|
|||
(assoc m :project (get projects name))
|
||||
m))
|
||||
|
||||
(defn total
|
||||
"Sum the provided records."
|
||||
[records]
|
||||
(->> records (map :amount) (reduce + 0M)))
|
||||
|
||||
(defn total-by-type
|
||||
"Sum up the provided records that match this type."
|
||||
[type records]
|
||||
(->> records
|
||||
(filter #(= (:type %) type))
|
||||
(map :amount)
|
||||
(reduce + 0M)))
|
||||
|
||||
(defn- split-fee
|
||||
"Share a total fee into n groups allocating the remainder as evenly as possible.
|
||||
(split-fee 36.02 4) => (9.01M 9.01M 9.00M 9.00M)"
|
||||
|
@ -108,7 +121,7 @@
|
|||
;; without realising that file existed and instead by precisely matching the
|
||||
;; recent manually created payroll transactions).
|
||||
;;
|
||||
;; These functions take the input CSV data, pre-formatted and grouped by
|
||||
;; These functions take the input CSV records, pre-formatted and grouped by
|
||||
;; employee.
|
||||
;;
|
||||
;; The output is an intermediate data structure that can then be run through
|
||||
|
@ -119,191 +132,249 @@
|
|||
;;
|
||||
;; (All functions return a sequence of transactions so we can concatenate them.)
|
||||
|
||||
(defn payroll
|
||||
(defn net-pay
|
||||
"Return a net pay transaction."
|
||||
[date period receipt-no projects data]
|
||||
(let [postings (for [[name records] (group-by :name data)]
|
||||
(let [total-net-pay (->> records
|
||||
(filter #(= (:type %) "Net Pay"))
|
||||
(map :amount)
|
||||
(reduce + 0M))
|
||||
total-reimbursement (->> records
|
||||
(filter #(= (:type %) "Reimbursement"))
|
||||
(map :amount)
|
||||
(reduce + 0M))
|
||||
actual-total-net-pay (- total-net-pay total-reimbursement)]
|
||||
[{:account "Expenses:Payroll:Salary"
|
||||
:amount actual-total-net-pay
|
||||
:currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}
|
||||
{:account "Assets:FR:Check2721"
|
||||
:amount (- actual-total-net-pay)
|
||||
:currency "USD"
|
||||
:meta {:entity name}}
|
||||
{:account "Expenses:Hosting"
|
||||
:amount total-reimbursement
|
||||
:currency "USD"
|
||||
:meta (assoc-project projects name {:entity name :payroll-type "US:Reimbursement"})}
|
||||
{:account "Assets:FR:Check2721"
|
||||
:amount (- total-reimbursement)
|
||||
:currency "USD"
|
||||
:meta (assoc-project projects name {:entity name :tax-implication "Reimbursement"})}]))]
|
||||
[{:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "W2"
|
||||
:payroll-type "US:General"}
|
||||
:postings (apply concat postings)}]))
|
||||
[date period invoice-no projects records]
|
||||
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "W2"
|
||||
:payroll-type "US:General"}
|
||||
:postings []}
|
||||
postings (flatten
|
||||
(for [[name employee-records] (group-by :name records)]
|
||||
(let [total-net-pay (total-by-type "Net Pay" employee-records)
|
||||
total-reimbursement (total-by-type "Reimbursement" employee-records)
|
||||
actual-total-net-pay (- total-net-pay total-reimbursement)]
|
||||
[{:account "Expenses:Payroll:Salary"
|
||||
:amount actual-total-net-pay :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- actual-total-net-pay) :currency "USD"
|
||||
:meta {:entity name}}
|
||||
{:account "Expenses:Hosting"
|
||||
:amount total-reimbursement :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name :payroll-type "US:Reimbursement"})}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- total-reimbursement) :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name :tax-implication "Reimbursement"})}])))]
|
||||
[(assoc template :postings postings)]))
|
||||
|
||||
(defn individual-taxes
|
||||
"Return a transaction of expenses/witholding for each employee."
|
||||
[date period receipt-no invoice-no projects data]
|
||||
(for [[name records] (group-by :name data)]
|
||||
(let [retirement-lines (filter #(= (:type %) "Retirement") records)
|
||||
witholding-lines (filter #(= (:type %) "Withholding") records)
|
||||
[date period pay-invoice-no retirement-invoice-no projects records]
|
||||
(for [[name employee-records] (group-by :name records)]
|
||||
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - %s" period name)
|
||||
:meta (assoc-project
|
||||
projects name
|
||||
{:project "Conservancy"
|
||||
:program "Conservancy:Payroll"
|
||||
:entity name
|
||||
:invoice pay-invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"})
|
||||
:postings []}
|
||||
retirement-records (filter #(= (:type %) "Retirement") employee-records)
|
||||
witholding-records (filter #(= (:type %) "Withholding") employee-records)
|
||||
;; We add these extra disability insurance/asset postings for NY only
|
||||
;; as discussed in beancount/doc/Payroll.md.
|
||||
insurance-lines (filter (fn [{:keys [category type]}]
|
||||
insurance-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Withholding")
|
||||
(str/starts-with? category "NY Disability"))) records)
|
||||
total-retirement (->> retirement-lines
|
||||
(map :amount)
|
||||
(reduce + 0M))
|
||||
retirement-postings (for [{:keys [category amount]} retirement-lines]
|
||||
(str/starts-with? category "NY Disability"))) employee-records)
|
||||
total-retirement (total retirement-records)
|
||||
retirement-postings (for [{:keys [category amount]} retirement-records]
|
||||
(if (= category "403b ER match")
|
||||
{:account "Expenses:Payroll:Salary"
|
||||
:amount amount
|
||||
:currency "USD"
|
||||
:amount amount :currency "USD"
|
||||
:meta {:payroll-type "US:403b:Match"
|
||||
:invoice invoice-no}}
|
||||
:invoice retirement-invoice-no}}
|
||||
{:account "Expenses:Payroll:Salary"
|
||||
:amount amount
|
||||
:currency "USD"
|
||||
:amount amount :currency "USD"
|
||||
:meta {:payroll-type "US:403b:Employee"
|
||||
:invoice invoice-no}}))
|
||||
:invoice retirement-invoice-no}}))
|
||||
liability-postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- total-retirement)
|
||||
:currency "USD"
|
||||
:meta {:invoice invoice-no}}]
|
||||
withholding-postings (for [{:keys [category amount]} witholding-lines]
|
||||
:amount (- total-retirement) :currency "USD"
|
||||
:meta {:invoice retirement-invoice-no}}]
|
||||
withholding-postings (for [{:keys [category amount]} witholding-records]
|
||||
{:account "Expenses:Payroll:Salary"
|
||||
:amount amount
|
||||
:currency "USD"
|
||||
:amount amount :currency "USD"
|
||||
:meta {:payroll-type (cat->payroll-type category)}})
|
||||
withholding-asset-postings [{:account "Assets:FR:Check2721"
|
||||
:amount (- (reduce + 0M (map :amount witholding-lines)))
|
||||
:currency "USD"
|
||||
withholding-asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- (total witholding-records)) :currency "USD"
|
||||
:meta {:tax-implication "W2"}}]
|
||||
insurance-postings (for [{:keys [category amount]} insurance-lines]
|
||||
insurance-postings (for [{:keys [category amount]} insurance-records]
|
||||
{:account "Expenses:Insurance"
|
||||
:amount (- amount)
|
||||
:currency "USD"
|
||||
:amount (- amount) :currency "USD"
|
||||
:meta {:payroll-type (cat->payroll-type category)}})
|
||||
insurance-asset-postings [{:account "Assets:FR:Check2721"
|
||||
:amount (reduce + 0M (map :amount insurance-lines))
|
||||
:currency "USD"}]]
|
||||
{:date date :desc (format "Monthly Payroll - %s - TAXES - %s" period name)
|
||||
:meta (assoc-project projects name {:project "Conservancy"
|
||||
:program "Conservancy:Payroll"
|
||||
:entity name
|
||||
:receipt receipt-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"})
|
||||
:postings (concat
|
||||
retirement-postings
|
||||
liability-postings
|
||||
withholding-postings
|
||||
withholding-asset-postings
|
||||
insurance-postings
|
||||
insurance-asset-postings)})))
|
||||
insurance-asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount (total insurance-records) :currency "USD"}]
|
||||
all-postings (concat
|
||||
retirement-postings
|
||||
liability-postings
|
||||
withholding-postings
|
||||
withholding-asset-postings
|
||||
insurance-postings
|
||||
insurance-asset-postings)]
|
||||
(assoc template :postings all-postings))))
|
||||
|
||||
(defn employer-taxes
|
||||
"Return an employer taxes transaction."
|
||||
[date period receipt-no projects data]
|
||||
(let [liability-lines (filter (fn [{:keys [category type]}]
|
||||
[date period invoice-no projects records]
|
||||
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings []}
|
||||
liability-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Liability")
|
||||
(not (str/includes? category "Unemploy")))) data)
|
||||
liability-postings (for [{:keys [amount name category]} liability-lines]
|
||||
(not (str/includes? category "Unemploy")))) records)
|
||||
liability-postings (for [{:keys [amount name category]} liability-records]
|
||||
{:account "Expenses:Payroll:Tax"
|
||||
:amount amount
|
||||
:currency "USD"
|
||||
:amount amount :currency "USD"
|
||||
;; Use eg. "US:Medicare", not "US:Tax:Medicare" as
|
||||
;; in individual-taxes as confirmed by Rosanne.
|
||||
:meta (assoc-project projects
|
||||
:meta (assoc-project
|
||||
projects
|
||||
name
|
||||
{:entity name
|
||||
:payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})})
|
||||
total-liabilities (->> liability-postings (map :amount) (reduce + 0M))
|
||||
unemploy-lines (filter (fn [{:keys [category type]}]
|
||||
total-liabilities (total liability-records)
|
||||
unemploy-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Liability")
|
||||
(str/includes? category "Unemploy"))) data)
|
||||
unemploy-postings (for [{:keys [amount name category]} unemploy-lines]
|
||||
(str/includes? category "Unemploy"))) records)
|
||||
unemploy-postings (for [{:keys [amount name category]} unemploy-records]
|
||||
{:account "Expenses:Payroll:Tax"
|
||||
:amount amount
|
||||
:currency "USD"
|
||||
:amount amount :currency "USD"
|
||||
:meta (assoc-project projects
|
||||
name
|
||||
{:entity (first (str/split category #" "))
|
||||
:memo name ; distinguishes multiple employees in one state
|
||||
:payroll-type (str "US:" (cat->payroll-type category))})})
|
||||
total-unemploy (->> unemploy-postings (map :amount) (reduce + 0M))
|
||||
asset-postings [{:account "Assets:FR:Check2721"
|
||||
:amount (- (+ total-liabilities total-unemploy))
|
||||
:currency "USD"
|
||||
name
|
||||
{:entity (first (str/split category #" "))
|
||||
:memo name ; distinguishes multiple employees in one state
|
||||
:payroll-type (str "US:" (cat->payroll-type category))})})
|
||||
total-unemploy (total unemploy-records)
|
||||
asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- (+ total-liabilities total-unemploy)) :currency "USD"
|
||||
:meta {:entity "Paychex"
|
||||
:tax-implication "Tax-Payment"}}]]
|
||||
[{:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings (concat liability-postings unemploy-postings asset-postings)}]))
|
||||
:tax-implication "Tax-Payment"}}]
|
||||
all-postings (concat liability-postings unemploy-postings asset-postings)]
|
||||
[(assoc template :postings all-postings)]))
|
||||
|
||||
(defn payroll-fees
|
||||
"Return a payroll fees transaction."
|
||||
[date period receipt-no invoice-no total-fees projects data]
|
||||
(let [employees (distinct (map :name data))
|
||||
(defn fees
|
||||
"Return a transaction paying payroll fees to Paychex payroll fees transaction."
|
||||
[date period receipt-no invoice-no total-fees projects records]
|
||||
(let [template {:date date :payee "Paychex" :desc (format "Monthly Payroll - %s - Fee" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "USA-Corporation"}
|
||||
:postings []}
|
||||
employees (distinct (map :name records))
|
||||
exact-fee-allocation (split-fee total-fees (count employees))
|
||||
employee-fees (map vector employees exact-fee-allocation)
|
||||
expense-postings (for [[name fee] employee-fees]
|
||||
{:account "Expenses:Payroll:Fees"
|
||||
:amount fee
|
||||
:currency "USD"
|
||||
:amount fee :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})})
|
||||
asset-postings [{:account "Assets:FR:Check2721"
|
||||
:amount (- total-fees)
|
||||
:currency "USD"}]]
|
||||
[{:date date :payee "Paychex" :desc (format "Monthly Payroll - %s - Fee" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "USA-Corporation"}
|
||||
:postings (concat expense-postings asset-postings)}]))
|
||||
:amount (- total-fees) :currency "USD"}]
|
||||
all-postings (concat expense-postings asset-postings)]
|
||||
[(assoc template :postings all-postings)]))
|
||||
|
||||
(defn retirement
|
||||
"Return a retirement transaction."
|
||||
[date period receipt-no invoice-no data]
|
||||
(let [liability-postings (for [[name records] (group-by :name data)]
|
||||
(let [total-retirement (->> records
|
||||
(filter #(= (:type %) "Retirement"))
|
||||
(map :amount)
|
||||
(reduce + 0M))]
|
||||
{:account "Liabilities:Payable:Accounts",
|
||||
:amount total-retirement,
|
||||
:currency "USD",
|
||||
"Return a transaction paying off retirement liabilities."
|
||||
[date period receipt-no invoice-no records]
|
||||
(let [template {:date date :desc (format "ASCENSUS TRUST RET PLAN - ACH DEBIT - Vanguard 403(b) - %s" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "Retirement-Pretax"
|
||||
:invoice invoice-no}
|
||||
:postings []}
|
||||
liability-postings (for [[name employee-records] (group-by :name records)]
|
||||
(let [total-retirement (total-by-type "Retirement" employee-records)]
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount total-retirement :currency "USD"
|
||||
:meta {:entity name}}))
|
||||
total-liabilities (->> liability-postings (map :amount) (reduce + 0M))
|
||||
total-liabilities (total liability-postings)
|
||||
asset-postings [{:account "Assets:FR:Check1345"
|
||||
:amount (- total-liabilities)
|
||||
:currency "USD"}]]
|
||||
[{:date date :desc (format "ASCENSUS TRUST RET PLAN - ACH DEBIT - Vanguard 403(b) - %s" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "Retirement-Pretax"
|
||||
:invoice invoice-no}
|
||||
:postings (concat liability-postings asset-postings)}]))
|
||||
:amount (- total-liabilities) :currency "USD"}]
|
||||
all-postings (concat liability-postings asset-postings)]
|
||||
[(assoc template :postings all-postings)]))
|
||||
|
||||
|
||||
(defn net-pay-ach-debit
|
||||
"Return a transaction paying off net pay liabilities"
|
||||
[date period receipt-no invoice-no projects records]
|
||||
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay - ACH debit" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "W2"
|
||||
:payroll-type "US:General"}
|
||||
:postings []}
|
||||
employee-postings (flatten
|
||||
(for [[name employee-records] (group-by :name records)]
|
||||
(let [net-pay (total-by-type "Net Pay" employee-records)
|
||||
reimbursements (total-by-type "Reimbursement" employee-records)]
|
||||
[{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- net-pay reimbursements) :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount reimbursements :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}])))
|
||||
total-net-pay (total-by-type "Net Pay" records)
|
||||
total-reimbursements (total-by-type "Reimbursement" records)
|
||||
total-net-pay-posting {:account "Assets:FR:Check2721"
|
||||
:amount (- (- total-net-pay total-reimbursements)) :currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "W2"}}
|
||||
total-reimbursements-posting {:account "Assets:FR:Check2721"
|
||||
:amount (- total-reimbursements) :currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "Reimbursement"}}
|
||||
all-postings (concat employee-postings [total-net-pay-posting total-reimbursements-posting])]
|
||||
[(assoc template :postings all-postings)]))
|
||||
|
||||
(defn taxes-ach-debit
|
||||
"Return a transaction paying off tax liabilities."
|
||||
[date period receipt-no invoice-no projects records]
|
||||
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - ACH debit" period)
|
||||
:meta {:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt receipt-no
|
||||
:invoice invoice-no
|
||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings []}
|
||||
liability-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Liability")
|
||||
(not (str/includes? category "Unemploy")))) records)
|
||||
total-liabilities (total liability-records)
|
||||
unemploy-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Liability")
|
||||
(str/includes? category "Unemploy"))) records)
|
||||
total-unemploy (total unemploy-records)
|
||||
liability-postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount (+ total-liabilities total-unemploy) :currency "USD"
|
||||
:meta {:entity "Paychex"}}]
|
||||
withholding-liability-postings (flatten
|
||||
(for [[name employee-records] (group-by :name records)]
|
||||
(let [witholding-records (filter #(= (:type %) "Withholding") employee-records)
|
||||
insurance-records (filter (fn [{:keys [category type]}]
|
||||
(and (= type "Withholding")
|
||||
(str/starts-with? category "NY Disability")))
|
||||
employee-records)]
|
||||
[{:account "Liabilities:Payable:Accounts"
|
||||
:amount (total witholding-records) :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount (- (total insurance-records)) :currency "USD"
|
||||
:meta (assoc-project projects name {:entity name})}])))
|
||||
asset-postings [{:account "Assets:FR:Check2721"
|
||||
:amount (- (+ total-liabilities total-unemploy (total withholding-liability-postings))) :currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}}]
|
||||
all-postings (concat withholding-liability-postings liability-postings asset-postings)]
|
||||
[(assoc template :postings all-postings)]))
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
[clojure.string :as str]
|
||||
[clojure.test :as t :refer [deftest is]]))
|
||||
|
||||
(deftest payroll
|
||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/payroll "DATE" "PERIOD" "TODO-PAY-RECEIPT" {} data)
|
||||
(deftest net-pay
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/net-pay "DATE" "PERIOD" "TODO-PAY-INVOICE" {} records)
|
||||
expected '[{:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - Net Pay"
|
||||
:meta
|
||||
{:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-PAY-RECEIPT"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "W2"
|
||||
|
@ -25,7 +25,7 @@
|
|||
:amount 4134.49M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -4134.49M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
|
@ -33,7 +33,7 @@
|
|||
:amount 50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack" :payroll-type "US:Reimbursement"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack" :tax-implication "Reimbursement"}}
|
||||
|
@ -41,7 +41,7 @@
|
|||
:amount 4347.39M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -4347.39M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
|
@ -49,22 +49,22 @@
|
|||
:amount 50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill" :payroll-type "US:Reimbursement"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill" :tax-implication "Reimbursement"}})}]]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest individual-taxes
|
||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/individual-taxes "DATE" "PERIOD" "TODO-PAY-RECEIPT" "TODO-RETIREMENT-INVOICE" {} data)
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/individual-taxes "DATE" "PERIOD" "TODO-PAY-INVOICE" "TODO-RETIREMENT-INVOICE" {} records)
|
||||
expected '({:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - TAXES - Citizen-Jack"
|
||||
:meta
|
||||
{:project "Conservancy"
|
||||
:program "Conservancy:Payroll"
|
||||
:entity "Citizen-Jack"
|
||||
:receipt "TODO-PAY-RECEIPT"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings
|
||||
|
@ -90,18 +90,18 @@
|
|||
:amount 376.28M
|
||||
:currency "USD"
|
||||
:meta {:payroll-type "US:Tax:SocialSecurity"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -934.50M
|
||||
:currency "USD"
|
||||
:meta {:tax-implication "W2"}}
|
||||
{:account "Assets:FR:Check2721" :amount 0M :currency "USD"})}
|
||||
{:account "Liabilities:Payable:Accounts" :amount 0M :currency "USD"})}
|
||||
{:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - TAXES - Citizen-Jill"
|
||||
:meta
|
||||
{:project "Conservancy"
|
||||
:program "Conservancy:Payroll"
|
||||
:entity "Citizen-Jill"
|
||||
:receipt "TODO-PAY-RECEIPT"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings
|
||||
|
@ -143,22 +143,22 @@
|
|||
:amount 424.52M
|
||||
:currency "USD"
|
||||
:meta {:payroll-type "US:Tax:SocialSecurity"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -1679.73M
|
||||
:currency "USD"
|
||||
:meta {:tax-implication "W2"}}
|
||||
{:account "Assets:FR:Check2721" :amount 0M :currency "USD"})})]
|
||||
{:account "Liabilities:Payable:Accounts" :amount 0M :currency "USD"})})]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest employer-taxes
|
||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/employer-taxes "DATE" "PERIOD" "TODO-PAY-RECEIPT" {} data)
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/employer-taxes "DATE" "PERIOD" "TODO-PAY-INVOICE" {} records)
|
||||
expected '[{:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - TAXES - Employer"
|
||||
:meta
|
||||
{:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-PAY-RECEIPT"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||
:postings
|
||||
|
@ -215,15 +215,17 @@
|
|||
{:entity "OR"
|
||||
:memo "Citizen-Jill"
|
||||
:payroll-type "US:OR:Unemployment"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount -988.08M
|
||||
:currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}})}]]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest payroll-fees
|
||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/payroll-fees "DATE" "PERIOD" "TODO-FEES-RECEIPT" "TODO-FEES-INVOICE" 206.51 {} data)
|
||||
;; TODO: Add 2 x ACH credit tests
|
||||
|
||||
(deftest fees
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/fees "DATE" "PERIOD" "TODO-FEES-RECEIPT" "TODO-FEES-INVOICE" 206.51 {} records)
|
||||
expected '[{:date "DATE"
|
||||
:payee "Paychex"
|
||||
:desc "Monthly Payroll - PERIOD - Fee"
|
||||
|
@ -249,8 +251,8 @@
|
|||
(is (= actual expected))))
|
||||
|
||||
(deftest retirement
|
||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/retirement "DATE" "PERIOD" "TODO-RETIREMENT-RECEIPT" "TODO-RETIREMENT-INVOICE" data)
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/retirement "DATE" "PERIOD" "TODO-RETIREMENT-RECEIPT" "TODO-RETIREMENT-INVOICE" records)
|
||||
expected '[{:date "DATE"
|
||||
:desc
|
||||
"ASCENSUS TRUST RET PLAN - ACH DEBIT - Vanguard 403(b) - PERIOD"
|
||||
|
@ -274,28 +276,102 @@
|
|||
{:account "Assets:FR:Check1345" :amount -1820M :currency "USD"})}]]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest net-pay-ach-debit
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/net-pay-ach-debit "DATE" "PERIOD" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
|
||||
expected [{:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - Net Pay - ACH debit"
|
||||
:meta {:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:payroll-type "US:General"
|
||||
:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-PAY-RECEIPT"
|
||||
:tax-implication "W2"}
|
||||
:postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount 4134.49M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 4347.39M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
:amount -8481.88M
|
||||
:currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "W2"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
:amount -100M
|
||||
:currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "Reimbursement"}}]}]]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest taxes-ach-debit
|
||||
(let [records (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||
actual (import/taxes-ach-debit "DATE" "PERIOD" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} records)
|
||||
expected [{:date "DATE"
|
||||
:desc "Monthly Payroll - PERIOD - TAXES - ACH debit"
|
||||
:meta {:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:invoice "TODO-PAY-INVOICE"
|
||||
:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-PAY-RECEIPT"}
|
||||
:postings [{:account "Liabilities:Payable:Accounts"
|
||||
:amount 934.50M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 0M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 1679.73M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 0M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Liabilities:Payable:Accounts"
|
||||
:amount 988.08M
|
||||
:currency "USD"
|
||||
:meta {:entity "Paychex"}}
|
||||
{:account "Assets:FR:Check2721"
|
||||
:amount -3602.31M
|
||||
:currency "USD"
|
||||
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}}]}]]
|
||||
(is (= actual expected))))
|
||||
|
||||
(deftest render-transaction
|
||||
(let [transaction '{:date "DATE"
|
||||
:payee "Paychex"
|
||||
:desc "Monthly Payroll - PERIOD - Fee"
|
||||
:meta
|
||||
{:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-FEES-RECEIPT"
|
||||
:invoice "TODO-FEES-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "USA-Corporation"}
|
||||
:postings
|
||||
({:account "Expenses:Payroll:Fees"
|
||||
:amount 103.26M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Expenses:Payroll:Fees"
|
||||
:amount 103.25M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Assets:FR:Check2721" :amount -206.51M :currency "USD"})}
|
||||
:payee "Paychex"
|
||||
:desc "Monthly Payroll - PERIOD - Fee"
|
||||
:meta
|
||||
{:program "Conservancy:Payroll"
|
||||
:project "Conservancy"
|
||||
:receipt "TODO-FEES-RECEIPT"
|
||||
:invoice "TODO-FEES-INVOICE"
|
||||
:approval
|
||||
"Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||
:tax-implication "USA-Corporation"}
|
||||
:postings
|
||||
({:account "Expenses:Payroll:Fees"
|
||||
:amount 103.26M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jack"}}
|
||||
{:account "Expenses:Payroll:Fees"
|
||||
:amount 103.25M
|
||||
:currency "USD"
|
||||
:meta {:entity "Citizen-Jill"}}
|
||||
{:account "Assets:FR:Check2721" :amount -206.51M :currency "USD"})}
|
||||
actual (import/render-transaction transaction)
|
||||
expected (str/triml "
|
||||
DATE txn \"Paychex\" \"Monthly Payroll - PERIOD - Fee\"
|
||||
|
|
Loading…
Reference in a new issue