Rename lines/data to records, put currency on same line as amount, add docs

This commit is contained in:
Ben Sturmfels 2024-03-15 13:14:04 +11:00
parent dc5692c718
commit 59a15c3d6f
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0

View file

@ -65,10 +65,14 @@
(assoc m :project (get projects name)) (assoc m :project (get projects name))
m)) m))
(defn total [records] (defn total
"Sum the provided records."
[records]
(->> records (map :amount) (reduce + 0M))) (->> records (map :amount) (reduce + 0M)))
(defn total-by-type [type records] (defn total-by-type
"Sum up the provided records that match this type."
[type records]
(->> records (->> records
(filter #(= (:type %) type)) (filter #(= (:type %) type))
(map :amount) (map :amount)
@ -130,7 +134,7 @@
(defn net-pay (defn net-pay
"Return a net pay transaction." "Return a net pay transaction."
[date period receipt-no projects data] [date period receipt-no projects records]
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay" period) (let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
:meta {:program "Conservancy:Payroll" :meta {:program "Conservancy:Payroll"
:project "Conservancy" :project "Conservancy"
@ -140,32 +144,28 @@
:payroll-type "US:General"} :payroll-type "US:General"}
:postings []} :postings []}
postings (flatten postings (flatten
(for [[name records] (group-by :name data)] (for [[name employee-records] (group-by :name records)]
(let [total-net-pay (total-by-type "Net Pay" records) (let [total-net-pay (total-by-type "Net Pay" employee-records)
total-reimbursement (total-by-type "Reimbursement" records) total-reimbursement (total-by-type "Reimbursement" employee-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 "Liabilities:Payable:Accounts" {:account "Liabilities:Payable:Accounts"
:amount (- actual-total-net-pay) :amount (- actual-total-net-pay) :currency "USD"
:currency "USD"
:meta {:entity name}} :meta {:entity name}}
{:account "Expenses:Hosting" {:account "Expenses:Hosting"
: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 "Liabilities:Payable:Accounts" {: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"})}])))]
[(assoc template :postings postings)])) [(assoc template :postings 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 records]
(for [[name records] (group-by :name data)] (for [[name employee-records] (group-by :name records)]
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - %s" period name) (let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - %s" period name)
:meta (assoc-project :meta (assoc-project
projects name projects name
@ -175,47 +175,40 @@
:invoice receipt-no :invoice receipt-no
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}) :approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"})
:postings []} :postings []}
retirement-lines (filter #(= (:type %) "Retirement") records) retirement-records (filter #(= (:type %) "Retirement") employee-records)
witholding-lines (filter #(= (:type %) "Withholding") records) witholding-records (filter #(= (:type %) "Withholding") employee-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-records (filter (fn [{:keys [category type]}]
(and (= type "Withholding") (and (= type "Withholding")
(str/starts-with? category "NY Disability"))) records) (str/starts-with? category "NY Disability"))) employee-records)
total-retirement (total retirement-lines) total-retirement (total retirement-records)
retirement-postings (for [{:keys [category amount]} retirement-lines] retirement-postings (for [{:keys [category amount]} retirement-records]
(if (= category "403b ER match") (if (= category "403b ER match")
{:account "Expenses:Payroll:Salary" {:account "Expenses:Payroll:Salary"
:amount amount :amount amount :currency "USD"
:currency "USD"
:meta {:payroll-type "US:403b:Match" :meta {:payroll-type "US:403b:Match"
:invoice invoice-no}} :invoice invoice-no}}
{:account "Expenses:Payroll:Salary" {:account "Expenses:Payroll:Salary"
:amount amount :amount amount :currency "USD"
:currency "USD"
:meta {:payroll-type "US:403b:Employee" :meta {:payroll-type "US:403b:Employee"
:invoice invoice-no}})) :invoice invoice-no}}))
liability-postings [{:account "Liabilities:Payable:Accounts" liability-postings [{:account "Liabilities:Payable:Accounts"
:amount (- total-retirement) :amount (- total-retirement) :currency "USD"
:currency "USD"
:meta {:invoice invoice-no}}] :meta {:invoice invoice-no}}]
withholding-postings (for [{:keys [category amount]} witholding-lines] withholding-postings (for [{:keys [category amount]} witholding-records]
{:account "Expenses:Payroll:Salary" {:account "Expenses:Payroll:Salary"
: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 "Liabilities:Payable:Accounts" withholding-asset-postings [{:account "Liabilities:Payable:Accounts"
:amount (- (total witholding-lines)) :amount (- (total witholding-records)) :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-records]
{:account "Expenses:Insurance" {:account "Expenses:Insurance"
: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 "Liabilities:Payable:Accounts" insurance-asset-postings [{:account "Liabilities:Payable:Accounts"
:amount (total insurance-lines) :amount (total insurance-records) :currency "USD"}]
:currency "USD"}]
all-postings (concat all-postings (concat
retirement-postings retirement-postings
liability-postings liability-postings
@ -225,24 +218,21 @@
insurance-asset-postings)] insurance-asset-postings)]
(assoc template :postings all-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 records]
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period) (let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
:meta {:program "Conservancy:Payroll" :meta {:program "Conservancy:Payroll"
:project "Conservancy" :project "Conservancy"
:invoice receipt-no :invoice receipt-no
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"} :approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
:postings []} :postings []}
liability-lines (filter (fn [{:keys [category type]}] liability-records (filter (fn [{:keys [category type]}]
(and (= type "Liability") (and (= type "Liability")
(not (str/includes? category "Unemploy")))) data) (not (str/includes? category "Unemploy")))) records)
liability-postings (for [{:keys [amount name category]} liability-lines] liability-postings (for [{:keys [amount name category]} liability-records]
{:account "Expenses:Payroll:Tax" {:account "Expenses:Payroll:Tax"
:amount amount :amount amount :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 :meta (assoc-project
@ -250,33 +240,29 @@
name name
{:entity name {:entity name
:payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})}) :payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})})
;; Can this use total liability-lines? total-liabilities (total liability-records)
total-liabilities (total liability-postings) unemploy-records (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"))) records)
unemploy-postings (for [{:keys [amount name category]} unemploy-lines] unemploy-postings (for [{:keys [amount name category]} unemploy-records]
{:account "Expenses:Payroll:Tax" {:account "Expenses:Payroll:Tax"
:amount amount :amount amount :currency "USD"
:currency "USD"
:meta (assoc-project projects :meta (assoc-project projects
name name
{: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))})})
;; Can this use total unemploy-lines? total-unemploy (total unemploy-records)
total-unemploy (total unemploy-postings)
asset-postings [{:account "Liabilities:Payable:Accounts" 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"}}]
all-postings (concat liability-postings unemploy-postings asset-postings)] all-postings (concat liability-postings unemploy-postings asset-postings)]
[(assoc template :postings all-postings)])) [(assoc template :postings all-postings)]))
(defn fees (defn fees
"Return a payroll fees transaction." "Return a transaction paying payroll fees to Paychex payroll fees transaction."
[date period receipt-no invoice-no total-fees projects data] [date period receipt-no invoice-no total-fees projects records]
(let [template {:date date :payee "Paychex" :desc (format "Monthly Payroll - %s - Fee" period) (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"
@ -285,23 +271,21 @@
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt" :approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
:tax-implication "USA-Corporation"} :tax-implication "USA-Corporation"}
:postings []} :postings []}
employees (distinct (map :name data)) employees (distinct (map :name records))
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]
{:account "Expenses:Payroll:Fees" {:account "Expenses:Payroll:Fees"
:amount fee :amount fee :currency "USD"
:currency "USD"
: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"}]
all-postings (concat expense-postings asset-postings)] all-postings (concat expense-postings asset-postings)]
[(assoc template :postings all-postings)])) [(assoc template :postings all-postings)]))
(defn retirement (defn retirement
"Return a retirement transaction." "Return a transaction paying off retirement liabilities."
[date period receipt-no invoice-no data] [date period receipt-no invoice-no records]
(let [template {:date date :desc (format "ASCENSUS TRUST RET PLAN - ACH DEBIT - Vanguard 403(b) - %s" period) (let [template {: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"
@ -310,24 +294,21 @@
:tax-implication "Retirement-Pretax" :tax-implication "Retirement-Pretax"
:invoice invoice-no} :invoice invoice-no}
:postings []} :postings []}
liability-postings (for [[name records] (group-by :name data)] liability-postings (for [[name employee-records] (group-by :name records)]
(let [total-retirement (total-by-type "Retirement" records)] (let [total-retirement (total-by-type "Retirement" employee-records)]
{:account "Liabilities:Payable:Accounts" {:account "Liabilities:Payable:Accounts"
:amount total-retirement :amount total-retirement :currency "USD"
:currency "USD"
:meta {:entity name}})) :meta {:entity name}}))
total-liabilities (total liability-postings) total-liabilities (total liability-postings)
asset-postings [{:account "Assets:FR:Check1345" asset-postings [{:account "Assets:FR:Check1345"
:amount (- total-liabilities) :amount (- total-liabilities) :currency "USD"}]
:currency "USD"}]
all-postings (concat liability-postings asset-postings)] all-postings (concat liability-postings asset-postings)]
[(assoc template :postings all-postings)])) [(assoc template :postings all-postings)]))
(defn net-pay-ach-debit (defn net-pay-ach-debit
;; TODO: Docstring "Return a transaction paying off net pay liabilities"
"Return a net pay transaction." [date period receipt-no invoice-no projects records]
[date period receipt-no invoice-no projects data]
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay - ACH debit" period) (let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay - ACH debit" period)
:meta {:program "Conservancy:Payroll" :meta {:program "Conservancy:Payroll"
:project "Conservancy" :project "Conservancy"
@ -338,34 +319,29 @@
:payroll-type "US:General"} :payroll-type "US:General"}
:postings []} :postings []}
employee-postings (flatten employee-postings (flatten
(for [[name records] (group-by :name data)] (for [[name employee-records] (group-by :name records)]
(let [net-pay (total-by-type "Net Pay" records) (let [net-pay (total-by-type "Net Pay" employee-records)
reimbursements (total-by-type "Reimbursement" records)] reimbursements (total-by-type "Reimbursement" employee-records)]
[{:account "Liabilities:Payable:Accounts" [{:account "Liabilities:Payable:Accounts"
:amount (- net-pay reimbursements) :amount (- net-pay reimbursements) :currency "USD"
:currency "USD"
:meta (assoc-project projects name {:entity name})} :meta (assoc-project projects name {:entity name})}
{:account "Liabilities:Payable:Accounts" {:account "Liabilities:Payable:Accounts"
:amount reimbursements :amount reimbursements :currency "USD"
:currency "USD"
:meta (assoc-project projects name {:entity name})}]))) :meta (assoc-project projects name {:entity name})}])))
total-net-pay (total-by-type "Net Pay" data) total-net-pay (total-by-type "Net Pay" records)
total-reimbursements (total-by-type "Reimbursement" data) total-reimbursements (total-by-type "Reimbursement" records)
total-net-pay-posting {:account "Assets:FR:Check2721" total-net-pay-posting {:account "Assets:FR:Check2721"
:amount (- (- total-net-pay total-reimbursements)) :amount (- (- total-net-pay total-reimbursements)) :currency "USD"
:currency "USD"
:meta {:entity "Paychex" :tax-implication "W2"}} :meta {:entity "Paychex" :tax-implication "W2"}}
total-reimbursements-posting {:account "Assets:FR:Check2721" total-reimbursements-posting {:account "Assets:FR:Check2721"
:amount (- total-reimbursements) :amount (- total-reimbursements) :currency "USD"
:currency "USD"
:meta {:entity "Paychex" :tax-implication "Reimbursement"}} :meta {:entity "Paychex" :tax-implication "Reimbursement"}}
all-postings (concat employee-postings [total-net-pay-posting total-reimbursements-posting])] all-postings (concat employee-postings [total-net-pay-posting total-reimbursements-posting])]
[(assoc template :postings all-postings)])) [(assoc template :postings all-postings)]))
(defn taxes-ach-debit (defn taxes-ach-debit
;; TODO: Docstring "Return a transaction paying off tax liabilities."
"Return an employer taxes transaction." [date period receipt-no invoice-no projects records]
[date period receipt-no invoice-no projects data]
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - ACH debit" period) (let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - ACH debit" period)
:meta {:program "Conservancy:Payroll" :meta {:program "Conservancy:Payroll"
:project "Conservancy" :project "Conservancy"
@ -373,35 +349,32 @@
:invoice invoice-no :invoice invoice-no
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"} :approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
:postings []} :postings []}
liability-lines (filter (fn [{:keys [category type]}] liability-records (filter (fn [{:keys [category type]}]
(and (= type "Liability") (and (= type "Liability")
(not (str/includes? category "Unemploy")))) data) (not (str/includes? category "Unemploy")))) records)
total-liabilities (total liability-lines) total-liabilities (total liability-records)
unemploy-lines (filter (fn [{:keys [category type]}] unemploy-records (filter (fn [{:keys [category type]}]
(and (= type "Liability") (and (= type "Liability")
(str/includes? category "Unemploy"))) data) (str/includes? category "Unemploy"))) records)
total-unemploy (total unemploy-lines) total-unemploy (total unemploy-records)
liability-postings [{:account "Liabilities:Payable:Accounts" liability-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"}}]
withholding-liability-postings (flatten withholding-liability-postings (flatten
(for [[name records] (group-by :name data)] (for [[name employee-records] (group-by :name records)]
(let [witholding-lines (filter #(= (:type %) "Withholding") records) (let [witholding-records (filter #(= (:type %) "Withholding") employee-records)
insurance-lines (filter (fn [{:keys [category type]}] insurance-records (filter (fn [{:keys [category type]}]
(and (= type "Withholding") (and (= type "Withholding")
(str/starts-with? category "NY Disability"))) records)] (str/starts-with? category "NY Disability")))
employee-records)]
[{:account "Liabilities:Payable:Accounts" [{:account "Liabilities:Payable:Accounts"
:amount (total witholding-lines) :amount (total witholding-records) :currency "USD"
:currency "USD"
:meta (assoc-project projects name {:entity name})} :meta (assoc-project projects name {:entity name})}
{:account "Liabilities:Payable:Accounts" {:account "Liabilities:Payable:Accounts"
:amount (- (total insurance-lines)) :amount (- (total insurance-records)) :currency "USD"
:currency "USD"
: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-liabilities total-unemploy (total withholding-liability-postings))) :amount (- (+ total-liabilities total-unemploy (total withholding-liability-postings))) :currency "USD"
:currency "USD"
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}}] :meta {:entity "Paychex" :tax-implication "Tax-Payment"}}]
all-postings (concat withholding-liability-postings liability-postings asset-postings)] all-postings (concat withholding-liability-postings liability-postings asset-postings)]
[(assoc template :postings all-postings)])) [(assoc template :postings all-postings)]))