2024-02-21 23:11:07 +00:00
|
|
|
;; Copyright 2024 Ben Sturmfels
|
|
|
|
;; License: GPLv3-or-later
|
|
|
|
|
2024-02-20 03:19:23 +00:00
|
|
|
(ns import
|
|
|
|
(:require [clojure.data.csv :as csv]
|
|
|
|
[clojure.java.io :as io]
|
|
|
|
[clojure.string :as str]))
|
|
|
|
|
2024-02-22 01:43:32 +00:00
|
|
|
(defn- employee-name->entity-tag
|
2024-02-21 23:11:07 +00:00
|
|
|
"Convert the name from the Pay Item Details report into an entity slug for Beancount."
|
|
|
|
[name]
|
|
|
|
;; Should potentially be a lookup table in config.
|
2024-02-20 03:19:23 +00:00
|
|
|
(case name
|
|
|
|
"Sharp, Sage A" "Sharp-Sage-A"
|
|
|
|
(-> name
|
|
|
|
(str/replace " Jr." "")
|
|
|
|
(str/replace ", " "-")
|
|
|
|
(str/replace #" \w$" ""))))
|
|
|
|
|
2024-02-22 23:09:49 +00:00
|
|
|
(defn read-csv
|
2024-02-21 23:11:07 +00:00
|
|
|
"Read in CSV and return a vector of maps with only the fields we want.
|
|
|
|
Merges the various number fields into a single \"amount\" field."
|
|
|
|
[filename]
|
2024-02-21 14:02:38 +00:00
|
|
|
(with-open [reader (io/reader filename)]
|
|
|
|
(doall
|
2024-02-22 23:09:49 +00:00
|
|
|
(for [[_ name _ category type & totals] (csv/read-csv reader)]
|
|
|
|
{:name (employee-name->entity-tag name)
|
|
|
|
:category category
|
|
|
|
:type type
|
|
|
|
:amount (apply max (map bigdec (remove str/blank? totals)))}))))
|
2024-02-21 14:02:38 +00:00
|
|
|
|
2024-02-21 23:11:07 +00:00
|
|
|
(defn- cat->payroll-type
|
|
|
|
"Map the CSV withholding categories to Beancount payroll-type tags."
|
|
|
|
[cat]
|
|
|
|
;; May need to become config, similar to names. Should prompt you if no
|
|
|
|
;; mapping exists to avoid mistakes.
|
2024-02-20 03:19:23 +00:00
|
|
|
(case cat
|
|
|
|
"Fed Income Tax" "US:Tax:Income"
|
|
|
|
"Medicare" "US:Tax:Medicare"
|
|
|
|
"IL Income Tax" "US:IL:Tax:Income"
|
|
|
|
"NY Income Tax" "US:NY:Tax:Income"
|
|
|
|
"NYC Income Tax" "US:NY:Tax:NYC"
|
|
|
|
"OR Income Tax" "US:OR:Tax:Income"
|
|
|
|
"OH Income Tax" "US:OH:Tax:Income"
|
|
|
|
"PNTSD Income Tax" "US:OH:Tax:PNTSD"
|
|
|
|
"COLMB Income Tax" "US:OH:Tax:COLUMB"
|
|
|
|
"Social Security" "US:Tax:SocialSecurity"
|
|
|
|
"NY Disability" "US:NY:Disability"
|
|
|
|
"OR Disability PFL" "US:OR:Disability:PFL"
|
|
|
|
"NY Disability PFL" "US:NY:Disability:PFL"
|
|
|
|
"OR TRANS STT" "US:OR:Tax:STT"
|
2024-02-20 07:26:45 +00:00
|
|
|
"Fed Unemploy" "US:Unemployment"
|
|
|
|
"IL Unemploy" "IL:Unemployment"
|
|
|
|
"NY Unemploy" "NY:Unemployment"
|
|
|
|
"OR Unemploy" "OR:Unemployment"
|
2024-02-21 14:02:38 +00:00
|
|
|
"NY Re-empl Svc" "US:NY:Reempt"
|
2024-02-20 03:19:23 +00:00
|
|
|
cat))
|
|
|
|
|
2024-02-21 23:11:07 +00:00
|
|
|
(defn- assoc-project
|
|
|
|
"Conditionally adds a specific project tag to metadata.
|
|
|
|
Some employees need to be tagged with a particular project to override the
|
|
|
|
default \"Conservancy\" project."
|
2024-03-06 04:23:27 +00:00
|
|
|
[projects name m]
|
|
|
|
(if (contains? projects name)
|
|
|
|
(assoc m :project (get projects name))
|
2024-02-21 14:45:15 +00:00
|
|
|
m))
|
2024-02-21 14:02:38 +00:00
|
|
|
|
2024-03-15 02:14:04 +00:00
|
|
|
(defn total
|
|
|
|
"Sum the provided records."
|
|
|
|
[records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(->> records (map :amount) (reduce + 0M)))
|
|
|
|
|
2024-03-15 02:14:04 +00:00
|
|
|
(defn total-by-type
|
|
|
|
"Sum up the provided records that match this type."
|
|
|
|
[type records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(->> records
|
|
|
|
(filter #(= (:type %) type))
|
|
|
|
(map :amount)
|
|
|
|
(reduce + 0M)))
|
|
|
|
|
2024-02-21 23:11:07 +00:00
|
|
|
(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)"
|
2024-02-21 14:02:38 +00:00
|
|
|
[total n]
|
|
|
|
(let [total (bigdec total)
|
|
|
|
fee-share (.setScale (bigdec (/ (double total) n)) 2 java.math.RoundingMode/FLOOR)
|
|
|
|
extra-cents (* 100M (- total (* fee-share n)))
|
|
|
|
base-fee-allocation (repeat fee-share)
|
|
|
|
cents-allocation (take n (concat (repeat extra-cents 0.01M) (repeat 0)))]
|
|
|
|
(map + base-fee-allocation cents-allocation)))
|
2024-02-20 03:19:23 +00:00
|
|
|
|
2024-02-21 23:11:07 +00:00
|
|
|
(defn render-transaction
|
|
|
|
"Turn a Beancount transaction data structure into text."
|
|
|
|
[{:keys [date payee desc meta postings]}]
|
|
|
|
(str/join (concat
|
|
|
|
;; Transaction date/description
|
|
|
|
[(if payee
|
|
|
|
(format "%s txn \"%s\" \"%s\"\n" date payee desc)
|
|
|
|
(format "%s txn \"%s\"\n" date desc))]
|
|
|
|
;; Transaction metadata
|
|
|
|
(for [[k v] meta]
|
|
|
|
(format " %s: \"%s\"\n" (name k) v))
|
|
|
|
;; Postings and posting metadata
|
|
|
|
(for [{:keys [amount account currency meta]} postings]
|
2024-02-26 21:27:48 +00:00
|
|
|
(when-not (zero? amount)
|
2024-02-21 23:11:07 +00:00
|
|
|
(format " %-40s %10.2f %s\n%s" account amount currency
|
|
|
|
(str/join (for [[k v] meta]
|
|
|
|
(format " %s: \"%s\"\n" (name k) v)))))))))
|
|
|
|
|
2024-02-26 21:27:48 +00:00
|
|
|
;; Each of the below functions returns one of the five payroll
|
|
|
|
;; transaction "sections" (one or more Beancount transaction entry):
|
2024-02-22 03:06:45 +00:00
|
|
|
;;
|
|
|
|
;; * net pay (single transaction)
|
|
|
|
;; * individual taxes (one transaction for each employee)
|
|
|
|
;; * employer taxes (single transaction)
|
|
|
|
;; * fees (single transaction)
|
|
|
|
;; * retirement (single trasaction)
|
|
|
|
;;
|
2024-02-26 21:27:48 +00:00
|
|
|
;; These transaction sections are described in detail in
|
|
|
|
;; beancount/doc/Payroll.md (though in truth the initial importer was written
|
|
|
|
;; without realising that file existed and instead by precisely matching the
|
|
|
|
;; recent manually created payroll transactions).
|
|
|
|
;;
|
2024-03-15 02:46:08 +00:00
|
|
|
;; These functions take the input CSV records, pre-formatted and grouped by
|
2024-02-22 03:06:45 +00:00
|
|
|
;; 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.)
|
|
|
|
|
2024-03-15 01:17:14 +00:00
|
|
|
(defn net-pay
|
2024-02-21 14:02:38 +00:00
|
|
|
"Return a net pay transaction."
|
2024-03-15 02:27:16 +00:00
|
|
|
[date period invoice-no projects records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(let [template {:date date :desc (format "Monthly Payroll - %s - Net Pay" period)
|
|
|
|
:meta {:program "Conservancy:Payroll"
|
|
|
|
:project "Conservancy"
|
2024-03-15 02:27:16 +00:00
|
|
|
:invoice invoice-no
|
2024-03-15 01:17:14 +00:00
|
|
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"
|
|
|
|
:tax-implication "W2"
|
|
|
|
:payroll-type "US:General"}
|
|
|
|
:postings []}
|
|
|
|
postings (flatten
|
2024-03-15 02:14:04 +00:00
|
|
|
(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)
|
2024-03-15 01:17:14 +00:00
|
|
|
actual-total-net-pay (- total-net-pay total-reimbursement)]
|
|
|
|
[{:account "Expenses:Payroll:Salary"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount actual-total-net-pay :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project projects name {:entity name})}
|
|
|
|
{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- actual-total-net-pay) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta {:entity name}}
|
|
|
|
{:account "Expenses:Hosting"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount total-reimbursement :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project projects name {:entity name :payroll-type "US:Reimbursement"})}
|
|
|
|
{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- total-reimbursement) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project projects name {:entity name :tax-implication "Reimbursement"})}])))]
|
|
|
|
[(assoc template :postings postings)]))
|
2024-02-20 03:19:23 +00:00
|
|
|
|
2024-02-21 14:02:38 +00:00
|
|
|
(defn individual-taxes
|
|
|
|
"Return a transaction of expenses/witholding for each employee."
|
2024-03-15 02:27:16 +00:00
|
|
|
[date period pay-invoice-no retirement-invoice-no projects records]
|
2024-03-15 02:14:04 +00:00
|
|
|
(for [[name employee-records] (group-by :name records)]
|
2024-03-15 01:17:14 +00:00
|
|
|
(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
|
2024-03-15 02:27:16 +00:00
|
|
|
:invoice pay-invoice-no
|
2024-03-15 01:17:14 +00:00
|
|
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"})
|
|
|
|
:postings []}
|
2024-03-15 02:14:04 +00:00
|
|
|
retirement-records (filter #(= (:type %) "Retirement") employee-records)
|
|
|
|
witholding-records (filter #(= (:type %) "Withholding") employee-records)
|
2024-02-22 23:16:50 +00:00
|
|
|
;; We add these extra disability insurance/asset postings for NY only
|
|
|
|
;; as discussed in beancount/doc/Payroll.md.
|
2024-03-15 02:14:04 +00:00
|
|
|
insurance-records (filter (fn [{:keys [category type]}]
|
2024-02-26 21:27:48 +00:00
|
|
|
(and (= type "Withholding")
|
2024-03-15 02:14:04 +00:00
|
|
|
(str/starts-with? category "NY Disability"))) employee-records)
|
|
|
|
total-retirement (total retirement-records)
|
|
|
|
retirement-postings (for [{:keys [category amount]} retirement-records]
|
2024-02-22 01:31:10 +00:00
|
|
|
(if (= category "403b ER match")
|
|
|
|
{:account "Expenses:Payroll:Salary"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount amount :currency "USD"
|
2024-02-22 01:31:10 +00:00
|
|
|
:meta {:payroll-type "US:403b:Match"
|
2024-03-15 02:27:16 +00:00
|
|
|
:invoice retirement-invoice-no}}
|
2024-02-22 01:31:10 +00:00
|
|
|
{:account "Expenses:Payroll:Salary"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount amount :currency "USD"
|
2024-02-22 01:31:10 +00:00
|
|
|
:meta {:payroll-type "US:403b:Employee"
|
2024-03-15 02:27:16 +00:00
|
|
|
:invoice retirement-invoice-no}}))
|
2024-02-21 14:02:38 +00:00
|
|
|
liability-postings [{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- total-retirement) :currency "USD"
|
2024-03-15 02:27:16 +00:00
|
|
|
:meta {:invoice retirement-invoice-no}}]
|
2024-03-15 02:14:04 +00:00
|
|
|
withholding-postings (for [{:keys [category amount]} witholding-records]
|
2024-02-21 14:02:38 +00:00
|
|
|
{:account "Expenses:Payroll:Salary"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount amount :currency "USD"
|
2024-02-21 23:11:07 +00:00
|
|
|
:meta {:payroll-type (cat->payroll-type category)}})
|
2024-03-15 01:17:14 +00:00
|
|
|
withholding-asset-postings [{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- (total witholding-records)) :currency "USD"
|
2024-02-21 14:02:38 +00:00
|
|
|
:meta {:tax-implication "W2"}}]
|
2024-03-15 02:14:04 +00:00
|
|
|
insurance-postings (for [{:keys [category amount]} insurance-records]
|
2024-02-21 14:02:38 +00:00
|
|
|
{:account "Expenses:Insurance"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- amount) :currency "USD"
|
2024-02-21 23:11:07 +00:00
|
|
|
:meta {:payroll-type (cat->payroll-type category)}})
|
2024-03-15 01:17:14 +00:00
|
|
|
insurance-asset-postings [{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (total insurance-records) :currency "USD"}]
|
2024-03-15 01:17:14 +00:00
|
|
|
all-postings (concat
|
|
|
|
retirement-postings
|
|
|
|
liability-postings
|
|
|
|
withholding-postings
|
|
|
|
withholding-asset-postings
|
|
|
|
insurance-postings
|
|
|
|
insurance-asset-postings)]
|
|
|
|
(assoc template :postings all-postings))))
|
|
|
|
|
2024-02-21 14:02:38 +00:00
|
|
|
(defn employer-taxes
|
|
|
|
"Return an employer taxes transaction."
|
2024-03-15 02:27:16 +00:00
|
|
|
[date period invoice-no projects records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(let [template {:date date :desc (format "Monthly Payroll - %s - TAXES - Employer" period)
|
|
|
|
:meta {:program "Conservancy:Payroll"
|
|
|
|
:project "Conservancy"
|
2024-03-15 02:27:16 +00:00
|
|
|
:invoice invoice-no
|
2024-03-15 01:17:14 +00:00
|
|
|
:approval "Financial/Employment-Records/memo-re-board-approval-of-payroll.txt"}
|
|
|
|
:postings []}
|
2024-03-15 02:14:04 +00:00
|
|
|
liability-records (filter (fn [{:keys [category type]}]
|
2024-02-26 21:27:48 +00:00
|
|
|
(and (= type "Liability")
|
2024-03-15 02:14:04 +00:00
|
|
|
(not (str/includes? category "Unemploy")))) records)
|
|
|
|
liability-postings (for [{:keys [amount name category]} liability-records]
|
2024-02-22 01:31:10 +00:00
|
|
|
{:account "Expenses:Payroll:Tax"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount amount :currency "USD"
|
2024-02-22 23:16:50 +00:00
|
|
|
;; Use eg. "US:Medicare", not "US:Tax:Medicare" as
|
|
|
|
;; in individual-taxes as confirmed by Rosanne.
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project
|
|
|
|
projects
|
|
|
|
name
|
|
|
|
{:entity name
|
|
|
|
:payroll-type (str/replace (cat->payroll-type category) "Tax:" "")})})
|
2024-03-15 02:14:04 +00:00
|
|
|
total-liabilities (total liability-records)
|
|
|
|
unemploy-records (filter (fn [{:keys [category type]}]
|
2024-02-26 21:27:48 +00:00
|
|
|
(and (= type "Liability")
|
2024-03-15 02:14:04 +00:00
|
|
|
(str/includes? category "Unemploy"))) records)
|
|
|
|
unemploy-postings (for [{:keys [amount name category]} unemploy-records]
|
2024-02-22 01:31:10 +00:00
|
|
|
{:account "Expenses:Payroll:Tax"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount amount :currency "USD"
|
2024-03-06 04:23:27 +00:00
|
|
|
:meta (assoc-project projects
|
2024-03-14 21:53:45 +00:00
|
|
|
name
|
|
|
|
{:entity (first (str/split category #" "))
|
2024-03-15 02:14:04 +00:00
|
|
|
:memo name ; distinguishes multiple employees in one state
|
2024-03-14 21:53:45 +00:00
|
|
|
:payroll-type (str "US:" (cat->payroll-type category))})})
|
2024-03-15 02:14:04 +00:00
|
|
|
total-unemploy (total unemploy-records)
|
2024-03-15 01:17:14 +00:00
|
|
|
asset-postings [{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- (+ total-liabilities total-unemploy)) :currency "USD"
|
2024-02-21 14:02:38 +00:00
|
|
|
:meta {:entity "Paychex"
|
2024-03-15 01:17:14 +00:00
|
|
|
:tax-implication "Tax-Payment"}}]
|
|
|
|
all-postings (concat liability-postings unemploy-postings asset-postings)]
|
|
|
|
[(assoc template :postings all-postings)]))
|
2024-02-20 03:19:23 +00:00
|
|
|
|
2024-03-15 01:17:14 +00:00
|
|
|
(defn fees
|
2024-03-15 02:14:04 +00:00
|
|
|
"Return a transaction paying payroll fees to Paychex payroll fees transaction."
|
|
|
|
[date period receipt-no invoice-no total-fees projects records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(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 []}
|
2024-03-15 02:14:04 +00:00
|
|
|
employees (distinct (map :name records))
|
2024-02-21 14:02:38 +00:00
|
|
|
exact-fee-allocation (split-fee total-fees (count employees))
|
|
|
|
employee-fees (map vector employees exact-fee-allocation)
|
|
|
|
expense-postings (for [[name fee] employee-fees]
|
2024-02-21 07:08:49 +00:00
|
|
|
{:account "Expenses:Payroll:Fees"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount fee :currency "USD"
|
2024-03-06 04:23:27 +00:00
|
|
|
:meta (assoc-project projects name {:entity name})})
|
2024-02-21 14:02:38 +00:00
|
|
|
asset-postings [{:account "Assets:FR:Check2721"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- total-fees) :currency "USD"}]
|
2024-03-15 01:17:14 +00:00
|
|
|
all-postings (concat expense-postings asset-postings)]
|
|
|
|
[(assoc template :postings all-postings)]))
|
2024-02-20 03:19:23 +00:00
|
|
|
|
2024-02-21 14:02:38 +00:00
|
|
|
(defn retirement
|
2024-03-15 02:14:04 +00:00
|
|
|
"Return a transaction paying off retirement liabilities."
|
|
|
|
[date period receipt-no invoice-no records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(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 []}
|
2024-03-15 02:14:04 +00:00
|
|
|
liability-postings (for [[name employee-records] (group-by :name records)]
|
|
|
|
(let [total-retirement (total-by-type "Retirement" employee-records)]
|
2024-03-15 01:17:14 +00:00
|
|
|
{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount total-retirement :currency "USD"
|
2024-02-21 14:02:38 +00:00
|
|
|
:meta {:entity name}}))
|
2024-03-15 01:17:14 +00:00
|
|
|
total-liabilities (total liability-postings)
|
2024-02-21 14:02:38 +00:00
|
|
|
asset-postings [{:account "Assets:FR:Check1345"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- total-liabilities) :currency "USD"}]
|
2024-03-15 01:17:14 +00:00
|
|
|
all-postings (concat liability-postings asset-postings)]
|
|
|
|
[(assoc template :postings all-postings)]))
|
|
|
|
|
|
|
|
|
|
|
|
(defn net-pay-ach-debit
|
2024-03-15 02:14:04 +00:00
|
|
|
"Return a transaction paying off net pay liabilities"
|
|
|
|
[date period receipt-no invoice-no projects records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(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
|
2024-03-15 02:14:04 +00:00
|
|
|
(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)]
|
2024-03-15 01:17:14 +00:00
|
|
|
[{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- net-pay reimbursements) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project projects name {:entity name})}
|
|
|
|
{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount reimbursements :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta (assoc-project projects name {:entity name})}])))
|
2024-03-15 02:14:04 +00:00
|
|
|
total-net-pay (total-by-type "Net Pay" records)
|
|
|
|
total-reimbursements (total-by-type "Reimbursement" records)
|
2024-03-15 01:17:14 +00:00
|
|
|
total-net-pay-posting {:account "Assets:FR:Check2721"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- (- total-net-pay total-reimbursements)) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta {:entity "Paychex" :tax-implication "W2"}}
|
|
|
|
total-reimbursements-posting {:account "Assets:FR:Check2721"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- total-reimbursements) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
: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
|
2024-03-15 02:14:04 +00:00
|
|
|
"Return a transaction paying off tax liabilities."
|
|
|
|
[date period receipt-no invoice-no projects records]
|
2024-03-15 01:17:14 +00:00
|
|
|
(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 []}
|
2024-03-15 02:14:04 +00:00
|
|
|
liability-records (filter (fn [{:keys [category type]}]
|
2024-03-15 01:17:14 +00:00
|
|
|
(and (= type "Liability")
|
2024-03-15 02:14:04 +00:00
|
|
|
(not (str/includes? category "Unemploy")))) records)
|
|
|
|
total-liabilities (total liability-records)
|
|
|
|
unemploy-records (filter (fn [{:keys [category type]}]
|
2024-03-15 01:17:14 +00:00
|
|
|
(and (= type "Liability")
|
2024-03-15 02:14:04 +00:00
|
|
|
(str/includes? category "Unemploy"))) records)
|
|
|
|
total-unemploy (total unemploy-records)
|
2024-03-15 01:17:14 +00:00
|
|
|
liability-postings [{:account "Liabilities:Payable:Accounts"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (+ total-liabilities total-unemploy) :currency "USD"
|
|
|
|
:meta {:entity "Paychex"}}]
|
2024-03-15 01:17:14 +00:00
|
|
|
withholding-liability-postings (flatten
|
2024-03-15 02:14:04 +00:00
|
|
|
(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})}])))
|
2024-03-15 01:17:14 +00:00
|
|
|
asset-postings [{:account "Assets:FR:Check2721"
|
2024-03-15 02:14:04 +00:00
|
|
|
:amount (- (+ total-liabilities total-unemploy (total withholding-liability-postings))) :currency "USD"
|
2024-03-15 01:17:14 +00:00
|
|
|
:meta {:entity "Paychex" :tax-implication "Tax-Payment"}}]
|
|
|
|
all-postings (concat withholding-liability-postings liability-postings asset-postings)]
|
|
|
|
[(assoc template :postings all-postings)]))
|