Allows upload of CSV of sysco with line item parsing

This commit is contained in:
2026-05-26 21:53:04 -07:00
parent 200056098f
commit a4d7ac5982
5 changed files with 187 additions and 77 deletions

View File

@@ -18,7 +18,7 @@
[datomic.api :as dc])
(:import (java.util UUID)))
(def sysco-name->line (atom nil))
(def sysco-name->line (atom nil))
(defn get-sysco->line []
(when (nil? @sysco-name->line)
(reset! sysco-name->line
@@ -90,16 +90,60 @@
tax)
updated-invoice (assoc invoice :invoice/expense-accounts
(for [[account amount] items-with-tax]
#:invoice-expense-account {:db/id (random-tempid)
:account account
#:invoice-expense-account {:db/id (random-tempid)
:account account
:location (:invoice/location invoice)
:amount amount}))]
:amount amount}))]
(if (check-okay-amount? updated-invoice)
updated-invoice
(do (alog/warn ::itemized-expenses-not-adding-up
:invoice updated-invoice)
invoice))))
(defn code-invoices-list-items [invoice]
(with-precision 2
(let [line-items (:line-items invoice)
invoice-total (if (string? (:invoice/total invoice))
(Double/parseDouble (:invoice/total invoice))
(:invoice/total invoice))
abs-total (Math/abs invoice-total)
expense-accounts (reduce
(fn [acc {:keys [description amount]}]
(let [account (get-line-account description)]
(update acc account (fnil + 0.0) amount)))
{}
line-items)
total-line-amount (reduce + 0.0 (vals expense-accounts))
accounts (if (zero? total-line-amount)
[]
(vec (for [[account amount] expense-accounts]
(let [ratio (/ amount total-line-amount)
cents (int (Math/round (* ratio abs-total 100)))]
#:invoice-expense-account {:db/id (random-tempid)
:account account
:location (:invoice/location invoice)
:amount (* 0.01 cents)}))))
accounts (mapv
(fn [a]
(update a :invoice-expense-account/amount
#(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
accounts)
leftover (with-precision 2 (.round (bigdec (- abs-total
(reduce + 0.0 (map :invoice-expense-account/amount accounts))))
*math-context*))
accounts (if (seq accounts)
(update-in accounts [(dec (count accounts)) :invoice-expense-account/amount] #(+ % (double leftover)))
[])]
(dissoc (assoc invoice :invoice/expense-accounts accounts) :line-items))))
(defn maybe-code-line-items [invoice]
(if (and (seq (:line-items invoice))
(= "Sysco" (-> (dc/pull (dc/db conn) [:vendor/name] (:invoice/vendor invoice))
:vendor/name)))
(code-invoices-list-items invoice)
(dissoc invoice :line-items)))
(defn extract-invoice-details [csv-rows sysco-vendor]
(let [[header-row & csv-rows] csv-rows
header-row (into {} (map vector header-keys header-row))
@@ -139,31 +183,31 @@
:invoice-number (header-row "InvoiceNumber")
:customer-name (header-row "CustomerName"))
(cond-> #:invoice {:invoice-number (header-row "InvoiceNumber")
(cond-> #:invoice {:invoice-number (header-row "InvoiceNumber")
:db/id (random-tempid)
:total (+ total tax)
:total (+ total tax)
:outstanding-balance (+ total tax)
:location (parse/best-location-match (dc/pull (dc/db conn)
[{:client/location-matches [:location-match/location :location-match/matches]}
:client/default-location
:client/locations]
(:db/id matching-client))
location-hint
location-hint)
:date (coerce/to-date date)
:vendor (:db/id sysco-vendor)
:client (:db/id matching-client)
:import-status :import-status/imported
:status :invoice-status/unpaid
:client-identifier customer-identifier}
:location (parse/best-location-match (dc/pull (dc/db conn)
[{:client/location-matches [:location-match/location :location-match/matches]}
:client/default-location
:client/locations]
(:db/id matching-client))
location-hint
location-hint)
:date (coerce/to-date date)
:vendor (:db/id sysco-vendor)
:client (:db/id matching-client)
:import-status :import-status/imported
:status :invoice-status/unpaid
:client-identifier customer-identifier}
true (code-invoice)
code-items (code-individual-items csv-rows tax))))
(defn mark-key [k]
(s3/copy-object {:source-bucket-name bucket-name
(s3/copy-object {:source-bucket-name bucket-name
:destination-bucket-name bucket-name
:destination-key (str/replace-first k "pending" "imported")
:source-key k})
:destination-key (str/replace-first k "pending" "imported")
:source-key k})
(s3/delete-object {:bucket-name bucket-name
:key k}))
@@ -180,7 +224,7 @@
([] (get-test-invoice-file 999))
([i]
(nth (->> (s3/list-objects-v2 {:bucket-name "data.prod.app.integreatconsult.com"
:prefix "sysco/imported"})
:prefix "sysco/imported"})
:object-summaries
(map :key))
i)))
@@ -205,10 +249,10 @@
(defn import-sysco []
(let [sysco-vendor (get-sysco-vendor)
keys (->> (s3/list-objects-v2 {:bucket-name bucket-name
:prefix "sysco/pending"})
:object-summaries
(map :key))]
keys (->> (s3/list-objects-v2 {:bucket-name bucket-name
:prefix "sysco/pending"})
:object-summaries
(map :key))]
(alog/info ::importing-sysco
:count (count keys)
@@ -219,10 +263,10 @@
(try
(let [invoice-key (str "invoice-files/" (UUID/randomUUID) ".csv") ;
invoice-url (str "https://" (:data-bucket env) "/" invoice-key)]
(s3/copy-object {:source-bucket-name (:data-bucket env)
(s3/copy-object {:source-bucket-name (:data-bucket env)
:destination-bucket-name (:data-bucket env)
:source-key k
:destination-key invoice-key})
:source-key k
:destination-key invoice-key})
[[:propose-invoice
(-> k
read-sysco-csv
@@ -232,13 +276,13 @@
(alog/error ::cant-load-file
:file k
:error e e)
(s3/copy-object {:source-bucket-name (:data-bucket env)
(s3/copy-object {:source-bucket-name (:data-bucket env)
:destination-bucket-name (:data-bucket env)
:source-key k
:destination-key (str "sysco/error/"
(.getName (io/file k)))})
:source-key k
:destination-key (str "sysco/error/"
(.getName (io/file k)))})
[])))))
result (audit-transact transaction {:user/name "sysco importer" :user/role "admin"})])
result (audit-transact transaction {:user/name "sysco importer" :user/role "admin"})])
(doseq [k keys]
(mark-key k))))