Add prototype ACH debit transactions
Also rearranged transaction functions to have the entry template a the top for readability. Added helpers for totalling.
This commit is contained in:
parent
85ce83f0b6
commit
dc5692c718
3 changed files with 221 additions and 128 deletions
39
src/core.clj
39
src/core.clj
|
@ -21,6 +21,8 @@
|
||||||
:default 0M]
|
:default 0M]
|
||||||
[nil "--pay-receipt-no REFERENCE" "Payroll receipt number, eg. \"rt:111/222\""
|
[nil "--pay-receipt-no REFERENCE" "Payroll receipt number, eg. \"rt:111/222\""
|
||||||
:default "TODO-PAY-RECEIPT"]
|
: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\""
|
[nil "--fees-receipt-no REFERENCE" "Paychex fees receipt number, eg. \"rt:111/222\""
|
||||||
:default "TODO-FEES-RECEIPT"]
|
:default "TODO-FEES-RECEIPT"]
|
||||||
[nil "--fees-invoice-no REFERENCE" "Paychex fees invoice number, eg. \"rt:111/222\""
|
[nil "--fees-invoice-no REFERENCE" "Paychex fees invoice number, eg. \"rt:111/222\""
|
||||||
|
@ -44,7 +46,7 @@
|
||||||
|
|
||||||
(defn -main [& args]
|
(defn -main [& args]
|
||||||
(let [{:keys [options errors summary]} (parse-opts args cli-options)
|
(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]
|
{:keys [date period pay-receipt-no pay-invoice-no total-fees fees-receipt-no fees-invoice-no retirement-receipt-no retirement-invoice-no project]} options]
|
||||||
(when (:help options)
|
(when (:help options)
|
||||||
(println summary)
|
(println summary)
|
||||||
(System/exit 0))
|
(System/exit 0))
|
||||||
|
@ -54,10 +56,13 @@
|
||||||
(str/join \newline errors)))
|
(str/join \newline errors)))
|
||||||
(System/exit 1))
|
(System/exit 1))
|
||||||
(let [data (import/read-csv (:csv options))
|
(let [data (import/read-csv (:csv options))
|
||||||
imported (concat (import/payroll date period pay-receipt-no project data)
|
;; TODO: Renamed receipt meta to invoice
|
||||||
|
imported (concat (import/net-pay date period pay-receipt-no project data)
|
||||||
(import/individual-taxes date period pay-receipt-no retirement-invoice-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/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/net-pay-ach-debit date period pay-receipt-no pay-invoice-no {} data)
|
||||||
|
(import/taxes-ach-debit date period pay-receipt-no pay-invoice-no {} data)
|
||||||
|
(import/fees date period fees-receipt-no fees-invoice-no total-fees project data)
|
||||||
(import/retirement date period retirement-receipt-no retirement-invoice-no data))
|
(import/retirement date period retirement-receipt-no retirement-invoice-no data))
|
||||||
unmatched (unmatched-employees data project)]
|
unmatched (unmatched-employees data project)]
|
||||||
(when-not (empty? unmatched)
|
(when-not (empty? unmatched)
|
||||||
|
@ -77,29 +82,19 @@
|
||||||
;; These examples are not included with the code for privacy reasons.
|
;; These examples are not included with the code for privacy reasons.
|
||||||
(require '[examples])
|
(require '[examples])
|
||||||
|
|
||||||
(def data (import/read-csv "/home/ben/Downloads/2023-12-27_Pay-Item-Details_2023-12-2.csv"))
|
(def data (import/read-csv "/home/ben/Downloads/2024-01-29_Pay-Item-Details_2024-01.csv"))
|
||||||
(def imported
|
(def imported
|
||||||
(concat (import/payroll "2023-12-29" "December 2023" "rt:19462/674660" {"Sharp-Sage-A" "Outreachy"} data)
|
(concat (import/net-pay "2024-01-31" "January 2024" "rt:19462/685751" {} data)
|
||||||
(import/individual-taxes "2023-12-29" "December 2023" "rt:19462/674660" "rt:19403/675431" {"Sharp-Sage-A" "Outreachy"} data)
|
(import/individual-taxes "2024-01-31" "January 2024" "rt:19462/685751" "rt:19403/685602" {} data)
|
||||||
(import/employer-taxes "2023-12-29" "December 2023" "rt:19462/674660" {"Sharp-Sage-A" "Outreachy"} data)
|
(import/employer-taxes "2024-01-31" "January 2024" "rt:19462/685751" {} data)
|
||||||
(import/payroll-fees "2023-12-29" "December 2023" "rt:19459/675387" "rt:19459/674887" 206.50M {"Sharp-Sage-A" "Outreachy"} data)
|
(import/net-pay-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} data)
|
||||||
(import/retirement "2024-01-02" "December 2023" "rt:19403/676724" "rt:19403/675431" data)))
|
(import/taxes-ach-debit "2024-01-31" "January 2024" "TODO-PAY-RECEIPT" "TODO-PAY-INVOICE" {} data)
|
||||||
|
(import/fees "2024-01-31" "January 2024" "rt:19459/675387" "rt:19459/674887" 206.50M {} data)
|
||||||
|
(import/retirement "2024-01-31" "January 2024" "rt:19403/685929" "rt:19403/685602" data)))
|
||||||
;; Compare hand-written transactions with imported (ignoring ordering).
|
;; Compare hand-written transactions with imported (ignoring ordering).
|
||||||
(dd/pretty-print
|
(dd/pretty-print
|
||||||
(dd/diff
|
(dd/diff
|
||||||
(sort-postings (parse examples/dec-2023))
|
(sort-postings (parse examples/jan-2024-with-liabilities))
|
||||||
(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 imported)))
|
(sort-postings imported)))
|
||||||
|
|
||||||
;; Print out text transactions
|
;; Print out text transactions
|
||||||
|
|
250
src/import.clj
250
src/import.clj
|
@ -65,6 +65,15 @@
|
||||||
(assoc m :project (get projects name))
|
(assoc m :project (get projects name))
|
||||||
m))
|
m))
|
||||||
|
|
||||||
|
(defn total [records]
|
||||||
|
(->> records (map :amount) (reduce + 0M)))
|
||||||
|
|
||||||
|
(defn total-by-type [type records]
|
||||||
|
(->> records
|
||||||
|
(filter #(= (:type %) type))
|
||||||
|
(map :amount)
|
||||||
|
(reduce + 0M)))
|
||||||
|
|
||||||
(defn- split-fee
|
(defn- split-fee
|
||||||
"Share a total fee into n groups allocating the remainder as evenly as possible.
|
"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)"
|
(split-fee 36.02 4) => (9.01M 9.01M 9.00M 9.00M)"
|
||||||
|
@ -119,24 +128,27 @@
|
||||||
;;
|
;;
|
||||||
;; (All functions return a sequence of transactions so we can concatenate them.)
|
;; (All functions return a sequence of transactions so we can concatenate them.)
|
||||||
|
|
||||||
(defn payroll
|
(defn net-pay
|
||||||
"Return a net pay transaction."
|
"Return a net pay transaction."
|
||||||
[date period receipt-no projects data]
|
[date period receipt-no projects data]
|
||||||
(let [postings (for [[name records] (group-by :name data)]
|
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
|
||||||
(let [total-net-pay (->> records
|
:meta {:program "Conservancy:Payroll"
|
||||||
(filter #(= (:type %) "Net Pay"))
|
:project "Conservancy"
|
||||||
(map :amount)
|
:invoice receipt-no
|
||||||
(reduce + 0M))
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||||
total-reimbursement (->> records
|
:tax-implication "W2"
|
||||||
(filter #(= (:type %) "Reimbursement"))
|
:payroll-type "US:General"}
|
||||||
(map :amount)
|
:postings []}
|
||||||
(reduce + 0M))
|
postings (flatten
|
||||||
|
(for [[name records] (group-by :name data)]
|
||||||
|
(let [total-net-pay (total-by-type "Net Pay" records)
|
||||||
|
total-reimbursement (total-by-type "Reimbursement" records)
|
||||||
actual-total-net-pay (- total-net-pay total-reimbursement)]
|
actual-total-net-pay (- total-net-pay total-reimbursement)]
|
||||||
[{:account "Expenses:Payroll:Salary"
|
[{:account "Expenses:Payroll:Salary"
|
||||||
:amount actual-total-net-pay
|
:amount actual-total-net-pay
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta (assoc-project projects name {:entity name})}
|
:meta (assoc-project projects name {:entity name})}
|
||||||
{:account "Assets:FR:Check2721"
|
{:account "Liabilities:Payable:Accounts"
|
||||||
:amount (- actual-total-net-pay)
|
:amount (- actual-total-net-pay)
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta {:entity name}}
|
:meta {:entity name}}
|
||||||
|
@ -144,33 +156,33 @@
|
||||||
:amount total-reimbursement
|
:amount total-reimbursement
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta (assoc-project projects name {:entity name :payroll-type "US:Reimbursement"})}
|
:meta (assoc-project projects name {:entity name :payroll-type "US:Reimbursement"})}
|
||||||
{:account "Assets:FR:Check2721"
|
{:account "Liabilities:Payable:Accounts"
|
||||||
:amount (- total-reimbursement)
|
:amount (- total-reimbursement)
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta (assoc-project projects name {:entity name :tax-implication "Reimbursement"})}]))]
|
:meta (assoc-project projects name {:entity name :tax-implication "Reimbursement"})}])))]
|
||||||
[{:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
|
[(assoc template :postings postings)]))
|
||||||
: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)}]))
|
|
||||||
|
|
||||||
(defn individual-taxes
|
(defn individual-taxes
|
||||||
"Return a transaction of expenses/witholding for each employee."
|
"Return a transaction of expenses/witholding for each employee."
|
||||||
[date period receipt-no invoice-no projects data]
|
[date period receipt-no invoice-no projects data]
|
||||||
(for [[name records] (group-by :name data)]
|
(for [[name records] (group-by :name data)]
|
||||||
(let [retirement-lines (filter #(= (:type %) "Retirement") 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 receipt-no
|
||||||
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"})
|
||||||
|
:postings []}
|
||||||
|
retirement-lines (filter #(= (:type %) "Retirement") records)
|
||||||
witholding-lines (filter #(= (:type %) "Withholding") records)
|
witholding-lines (filter #(= (:type %) "Withholding") records)
|
||||||
;; We add these extra disability insurance/asset postings for NY only
|
;; We add these extra disability insurance/asset postings for NY only
|
||||||
;; as discussed in beancount/doc/Payroll.md.
|
;; as discussed in beancount/doc/Payroll.md.
|
||||||
insurance-lines (filter (fn [{:keys [category type]}]
|
insurance-lines (filter (fn [{:keys [category type]}]
|
||||||
(and (= type "Withholding")
|
(and (= type "Withholding")
|
||||||
(str/starts-with? category "NY Disability"))) records)
|
(str/starts-with? category "NY Disability"))) records)
|
||||||
total-retirement (->> retirement-lines
|
total-retirement (total retirement-lines)
|
||||||
(map :amount)
|
|
||||||
(reduce + 0M))
|
|
||||||
retirement-postings (for [{:keys [category amount]} retirement-lines]
|
retirement-postings (for [{:keys [category amount]} retirement-lines]
|
||||||
(if (= category "403b ER match")
|
(if (= category "403b ER match")
|
||||||
{:account "Expenses:Payroll:Salary"
|
{:account "Expenses:Payroll:Salary"
|
||||||
|
@ -192,8 +204,8 @@
|
||||||
:amount amount
|
:amount amount
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta {:payroll-type (cat->payroll-type category)}})
|
:meta {:payroll-type (cat->payroll-type category)}})
|
||||||
withholding-asset-postings [{:account "Assets:FR:Check2721"
|
withholding-asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||||
:amount (- (reduce + 0M (map :amount witholding-lines)))
|
:amount (- (total witholding-lines))
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta {:tax-implication "W2"}}]
|
:meta {:tax-implication "W2"}}]
|
||||||
insurance-postings (for [{:keys [category amount]} insurance-lines]
|
insurance-postings (for [{:keys [category amount]} insurance-lines]
|
||||||
|
@ -201,27 +213,30 @@
|
||||||
:amount (- amount)
|
:amount (- amount)
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta {:payroll-type (cat->payroll-type category)}})
|
:meta {:payroll-type (cat->payroll-type category)}})
|
||||||
insurance-asset-postings [{:account "Assets:FR:Check2721"
|
insurance-asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||||
:amount (reduce + 0M (map :amount insurance-lines))
|
:amount (total insurance-lines)
|
||||||
:currency "USD"}]]
|
:currency "USD"}]
|
||||||
{:date date :desc (format "Monthly Payroll - %s - TAXES - %s" period name)
|
all-postings (concat
|
||||||
: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
|
retirement-postings
|
||||||
liability-postings
|
liability-postings
|
||||||
withholding-postings
|
withholding-postings
|
||||||
withholding-asset-postings
|
withholding-asset-postings
|
||||||
insurance-postings
|
insurance-postings
|
||||||
insurance-asset-postings)})))
|
insurance-asset-postings)]
|
||||||
|
(assoc template :postings all-postings))))
|
||||||
|
|
||||||
|
;; TODO: Rename data to records?
|
||||||
|
|
||||||
(defn employer-taxes
|
(defn employer-taxes
|
||||||
"Return an employer taxes transaction."
|
"Return an employer taxes transaction."
|
||||||
[date period receipt-no projects data]
|
[date period receipt-no projects data]
|
||||||
(let [liability-lines (filter (fn [{:keys [category type]}]
|
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
|
||||||
|
:meta {:program "Conservancy:Payroll"
|
||||||
|
:project "Conservancy"
|
||||||
|
:invoice receipt-no
|
||||||
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
||||||
|
:postings []}
|
||||||
|
liability-lines (filter (fn [{:keys [category type]}]
|
||||||
(and (= type "Liability")
|
(and (= type "Liability")
|
||||||
(not (str/includes? category "Unemploy")))) data)
|
(not (str/includes? category "Unemploy")))) data)
|
||||||
liability-postings (for [{:keys [amount name category]} liability-lines]
|
liability-postings (for [{:keys [amount name category]} liability-lines]
|
||||||
|
@ -230,11 +245,13 @@
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
;; Use eg. "US:Medicare", not "US:Tax:Medicare" as
|
;; Use eg. "US:Medicare", not "US:Tax:Medicare" as
|
||||||
;; in individual-taxes as confirmed by Rosanne.
|
;; in individual-taxes as confirmed by Rosanne.
|
||||||
:meta (assoc-project projects
|
:meta (assoc-project
|
||||||
|
projects
|
||||||
name
|
name
|
||||||
{:entity name
|
{:entity name
|
||||||
:payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})})
|
:payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})})
|
||||||
total-liabilities (->> liability-postings (map :amount) (reduce + 0M))
|
;; Can this use total liability-lines?
|
||||||
|
total-liabilities (total liability-postings)
|
||||||
unemploy-lines (filter (fn [{:keys [category type]}]
|
unemploy-lines (filter (fn [{:keys [category type]}]
|
||||||
(and (= type "Liability")
|
(and (= type "Liability")
|
||||||
(str/includes? category "Unemploy"))) data)
|
(str/includes? category "Unemploy"))) data)
|
||||||
|
@ -247,23 +264,28 @@
|
||||||
{:entity (first (str/split category #" "))
|
{:entity (first (str/split category #" "))
|
||||||
:memo name ; distinguishes multiple employees in one state
|
:memo name ; distinguishes multiple employees in one state
|
||||||
:payroll-type (str "US:" (cat->payroll-type category))})})
|
:payroll-type (str "US:" (cat->payroll-type category))})})
|
||||||
total-unemploy (->> unemploy-postings (map :amount) (reduce + 0M))
|
;; Can this use total unemploy-lines?
|
||||||
asset-postings [{:account "Assets:FR:Check2721"
|
total-unemploy (total unemploy-postings)
|
||||||
|
asset-postings [{:account "Liabilities:Payable:Accounts"
|
||||||
:amount (- (+ total-liabilities total-unemploy))
|
:amount (- (+ total-liabilities total-unemploy))
|
||||||
:currency "USD"
|
:currency "USD"
|
||||||
:meta {:entity "Paychex"
|
:meta {:entity "Paychex"
|
||||||
:tax-implication "Tax-Payment"}}]]
|
:tax-implication "Tax-Payment"}}]
|
||||||
[{:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
|
all-postings (concat liability-postings unemploy-postings asset-postings)]
|
||||||
|
[(assoc template :postings all-postings)]))
|
||||||
|
|
||||||
|
(defn fees
|
||||||
|
"Return a payroll fees transaction."
|
||||||
|
[date period receipt-no invoice-no total-fees projects data]
|
||||||
|
(let [template {:date date :payee "Paychex" :desc (format "Monthly Payroll - %s - Fee" period)
|
||||||
:meta {:program "Conservancy:Payroll"
|
:meta {:program "Conservancy:Payroll"
|
||||||
:project "Conservancy"
|
:project "Conservancy"
|
||||||
:receipt receipt-no
|
:receipt receipt-no
|
||||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
:invoice invoice-no
|
||||||
:postings (concat liability-postings unemploy-postings asset-postings)}]))
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||||
|
:tax-implication "USA-Corporation"}
|
||||||
(defn payroll-fees
|
:postings []}
|
||||||
"Return a payroll fees transaction."
|
employees (distinct (map :name data))
|
||||||
[date period receipt-no invoice-no total-fees projects data]
|
|
||||||
(let [employees (distinct (map :name data))
|
|
||||||
exact-fee-allocation (split-fee total-fees (count employees))
|
exact-fee-allocation (split-fee total-fees (count employees))
|
||||||
employee-fees (map vector employees exact-fee-allocation)
|
employee-fees (map vector employees exact-fee-allocation)
|
||||||
expense-postings (for [[name fee] employee-fees]
|
expense-postings (for [[name fee] employee-fees]
|
||||||
|
@ -273,37 +295,113 @@
|
||||||
:meta (assoc-project projects name {:entity name})})
|
:meta (assoc-project projects name {:entity name})})
|
||||||
asset-postings [{:account "Assets:FR:Check2721"
|
asset-postings [{:account "Assets:FR:Check2721"
|
||||||
:amount (- total-fees)
|
:amount (- total-fees)
|
||||||
:currency "USD"}]]
|
:currency "USD"}]
|
||||||
[{:date date :payee "Paychex" :desc (format "Monthly Payroll - %s - Fee" period)
|
all-postings (concat expense-postings asset-postings)]
|
||||||
:meta {:program "Conservancy:Payroll"
|
[(assoc template :postings all-postings)]))
|
||||||
: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)}]))
|
|
||||||
|
|
||||||
(defn retirement
|
(defn retirement
|
||||||
"Return a retirement transaction."
|
"Return a retirement transaction."
|
||||||
[date period receipt-no invoice-no data]
|
[date period receipt-no invoice-no data]
|
||||||
(let [liability-postings (for [[name records] (group-by :name data)]
|
(let [template {:date date :desc (format "ASCENSUS TRUST RET PLAN - ACH DEBIT - Vanguard 403(b) - %s" period)
|
||||||
(let [total-retirement (->> records
|
|
||||||
(filter #(= (:type %) "Retirement"))
|
|
||||||
(map :amount)
|
|
||||||
(reduce + 0M))]
|
|
||||||
{:account "Liabilities:Payable:Accounts",
|
|
||||||
:amount total-retirement,
|
|
||||||
:currency "USD",
|
|
||||||
:meta {:entity name}}))
|
|
||||||
total-liabilities (->> liability-postings (map :amount) (reduce + 0M))
|
|
||||||
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"
|
:meta {:program "Conservancy:Payroll"
|
||||||
:project "Conservancy"
|
:project "Conservancy"
|
||||||
:receipt receipt-no
|
:receipt receipt-no
|
||||||
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
||||||
:tax-implication "Retirement-Pretax"
|
:tax-implication "Retirement-Pretax"
|
||||||
:invoice invoice-no}
|
:invoice invoice-no}
|
||||||
:postings (concat liability-postings asset-postings)}]))
|
:postings []}
|
||||||
|
liability-postings (for [[name records] (group-by :name data)]
|
||||||
|
(let [total-retirement (total-by-type "Retirement" records)]
|
||||||
|
{:account "Liabilities:Payable:Accounts"
|
||||||
|
:amount total-retirement
|
||||||
|
:currency "USD"
|
||||||
|
:meta {:entity name}}))
|
||||||
|
total-liabilities (total liability-postings)
|
||||||
|
asset-postings [{:account "Assets:FR:Check1345"
|
||||||
|
:amount (- total-liabilities)
|
||||||
|
:currency "USD"}]
|
||||||
|
all-postings (concat liability-postings asset-postings)]
|
||||||
|
[(assoc template :postings all-postings)]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn net-pay-ach-debit
|
||||||
|
;; TODO: Docstring
|
||||||
|
"Return a net pay transaction."
|
||||||
|
[date period receipt-no invoice-no projects data]
|
||||||
|
(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 records] (group-by :name data)]
|
||||||
|
(let [net-pay (total-by-type "Net Pay" records)
|
||||||
|
reimbursements (total-by-type "Reimbursement" 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" data)
|
||||||
|
total-reimbursements (total-by-type "Reimbursement" data)
|
||||||
|
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
|
||||||
|
;; TODO: Docstring
|
||||||
|
"Return an employer taxes transaction."
|
||||||
|
[date period receipt-no invoice-no projects data]
|
||||||
|
(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-lines (filter (fn [{:keys [category type]}]
|
||||||
|
(and (= type "Liability")
|
||||||
|
(not (str/includes? category "Unemploy")))) data)
|
||||||
|
total-liabilities (total liability-lines)
|
||||||
|
unemploy-lines (filter (fn [{:keys [category type]}]
|
||||||
|
(and (= type "Liability")
|
||||||
|
(str/includes? category "Unemploy"))) data)
|
||||||
|
total-unemploy (total unemploy-lines)
|
||||||
|
liability-postings [{:account "Liabilities:Payable:Accounts"
|
||||||
|
:amount (+ total-liabilities total-unemploy)
|
||||||
|
:currency "USD"
|
||||||
|
:meta {:entity "Paychex"}}]
|
||||||
|
withholding-liability-postings (flatten
|
||||||
|
(for [[name records] (group-by :name data)]
|
||||||
|
(let [witholding-lines (filter #(= (:type %) "Withholding") records)
|
||||||
|
insurance-lines (filter (fn [{:keys [category type]}]
|
||||||
|
(and (= type "Withholding")
|
||||||
|
(str/starts-with? category "NY Disability"))) records)]
|
||||||
|
[{:account "Liabilities:Payable:Accounts"
|
||||||
|
:amount (total witholding-lines)
|
||||||
|
:currency "USD"
|
||||||
|
:meta (assoc-project projects name {:entity name})}
|
||||||
|
{:account "Liabilities:Payable:Accounts"
|
||||||
|
:amount (- (total insurance-lines))
|
||||||
|
: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,9 +7,9 @@
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.test :as t :refer [deftest is]]))
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
(deftest payroll
|
(deftest net-pay
|
||||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
||||||
actual (import/payroll "DATE" "PERIOD" "TODO-PAY-RECEIPT" {} data)
|
actual (import/net-pay "DATE" "PERIOD" "TODO-PAY-RECEIPT" {} data)
|
||||||
expected '[{:date "DATE"
|
expected '[{:date "DATE"
|
||||||
:desc "Monthly Payroll - PERIOD - Net Pay"
|
:desc "Monthly Payroll - PERIOD - Net Pay"
|
||||||
:meta
|
:meta
|
||||||
|
@ -221,9 +221,9 @@
|
||||||
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}})}]]
|
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}})}]]
|
||||||
(is (= actual expected))))
|
(is (= actual expected))))
|
||||||
|
|
||||||
(deftest payroll-fees
|
(deftest fees
|
||||||
(let [data (import/read-csv (clojure.java.io/resource "example-paychex-pay-item-details.csv"))
|
(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)
|
actual (import/fees "DATE" "PERIOD" "TODO-FEES-RECEIPT" "TODO-FEES-INVOICE" 206.51 {} data)
|
||||||
expected '[{:date "DATE"
|
expected '[{:date "DATE"
|
||||||
:payee "Paychex"
|
:payee "Paychex"
|
||||||
:desc "Monthly Payroll - PERIOD - Fee"
|
:desc "Monthly Payroll - PERIOD - Fee"
|
||||||
|
|
Loading…
Reference in a new issue