Allows upload of CSV of sysco with line item parsing
This commit is contained in:
@@ -1760,3 +1760,37 @@ Id,Sysco Category,Sysco Description,Integreat Account,Integreat Account Code,Nic
|
|||||||
1759,MEATS,PORK SHANK BONE KUROBUTA PR12,Beef/Pork Costs,51110,
|
1759,MEATS,PORK SHANK BONE KUROBUTA PR12,Beef/Pork Costs,51110,
|
||||||
1760,CANNED AND DRY,SEASONING ITALIAN WHL,Food Costs,50000,
|
1760,CANNED AND DRY,SEASONING ITALIAN WHL,Food Costs,50000,
|
||||||
1761,PRODUCE,MUSHROOM PORTABELLA CAP 4-5,Produce Costs,51200,
|
1761,PRODUCE,MUSHROOM PORTABELLA CAP 4-5,Produce Costs,51200,
|
||||||
|
1762,PAPER & DISP,BAG PAPER 250 CT,Paper Costs,55000,
|
||||||
|
1763,MEATS,BEEF SHLDR TERES MAJOR SEL,Beef/Pork Costs,51110,
|
||||||
|
1764,PAPER & DISP,BOWL PLASTIC COATING 42 OZ,Paper Costs,55000,
|
||||||
|
1765,PAPER & DISP,BOX CATERING 21X13X4.25 LOGO,Paper Costs,55000,
|
||||||
|
1766,CANNED AND DRY,CANDY MILK CHOC SHELLS,Food Costs,50000,
|
||||||
|
1767,CANNED AND DRY,CHOCOLATE DUBAI PISTCHO KUNFEH,Food Costs,50000,
|
||||||
|
1768,PAPER & DISP,CONTAINER PAPER 1/30 OZ NTG,Paper Costs,55000,
|
||||||
|
1769,PAPER & DISP,CONTAINER PAPER 4/110OZ NTG,Paper Costs,55000,
|
||||||
|
1770,PAPER & DISP,CUP PAPER COLD 22 OZ LOGO NTG,Paper Costs,55000,
|
||||||
|
1771,PAPER & DISP,CUP PORTION PLAS CLR 1.50 OZ,Paper Costs,55000,
|
||||||
|
1772,CANNED AND DRY,DESSERT CUP,Food Costs,50000,
|
||||||
|
1773,FROZEN,DESSERT MINI PLAIN BEIGNET,Food Costs,50000,
|
||||||
|
1774,CANNED AND DRY,DIP GARLIC TOUM,Food Costs,50000,
|
||||||
|
1775,CANNED AND DRY,DRINK ENERGY ORANGE SPRKLNG,Soft Beverage Costs,52000,
|
||||||
|
1776,CANNED AND DRY,DRINK ENERGY PEACH VIBE SPRKLG,Soft Beverage Costs,52000,
|
||||||
|
1777,CANNED AND DRY,DRINK ENERGY TROPICAL VIBE,Soft Beverage Costs,52000,
|
||||||
|
1778,PAPER & DISP,FILM PVC 18X2000 ROLL,Paper Costs,55000,
|
||||||
|
1779,CANNED AND DRY,JUICE CONC MANDARIN CARDAMOM,Food Costs,50000,
|
||||||
|
1780,CANNED AND DRY,JUICE CONC STRAWB DRAGON,Food Costs,50000,
|
||||||
|
1781,PAPER & DISP,LID CLEAR PET 42 OZ,Paper Costs,55000,
|
||||||
|
1782,PAPER & DISP,LID DOME DESSERT CUP,Paper Costs,55000,
|
||||||
|
1783,PAPER & DISP,NAPKIN 2PLY INTR FOLD 6.3X8.26,Paper Costs,55000,
|
||||||
|
1784,CANNED AND DRY,PASTE HERB HARISSA MOROCCAN,Food Costs,50000,
|
||||||
|
1785,CANNED AND DRY,PASTE TAHINI DRESSING,Food Costs,50000,
|
||||||
|
1786,FROZEN,PASTRY BEIGNET MN FLD CHOCCRML,Food Costs,50000,
|
||||||
|
1787,CANNED AND DRY,PEPPER BANANA MILD RING,Food Costs,50000,
|
||||||
|
1788,CANNED AND DRY,RICE MIX NICKS,Food Costs,50000,
|
||||||
|
1789,CANNED AND DRY,SODA CHERRY VISSINADA GREEK,Soft Beverage Costs,52000,
|
||||||
|
1790,CANNED AND DRY,SODA COLA PEPSI ZERO SUGAR,Soft Beverage Costs,52000,
|
||||||
|
1791,CANNED AND DRY,SODA PEPSI COLA,Soft Beverage Costs,52000,
|
||||||
|
1792,FROZEN,SPANAKOPITA SPINACH COOKED,Food Costs,50000,
|
||||||
|
1793,PAPER & DISP,SPOON PLAS TEA PP X-HVY BLK,Paper Costs,55000,
|
||||||
|
1794,PAPER & DISP,WRAP PAPER 14X14 LOGO VER2,Paper Costs,55000,
|
||||||
|
1795,DAIRY PRODUCTS,YOGURT FRZN NF NICK THE GREEK,Dairy Costs,51300,
|
||||||
|
|||||||
|
@@ -100,6 +100,50 @@
|
|||||||
:invoice updated-invoice)
|
:invoice updated-invoice)
|
||||||
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]
|
(defn extract-invoice-details [csv-rows sysco-vendor]
|
||||||
(let [[header-row & csv-rows] csv-rows
|
(let [[header-row & csv-rows] csv-rows
|
||||||
header-row (into {} (map vector header-keys header-row))
|
header-row (into {} (map vector header-keys header-row))
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
(str/includes? (str header) "DISCOUNT_MESSAGE")
|
(str/includes? (str header) "DISCOUNT_MESSAGE")
|
||||||
:wismettac
|
:wismettac
|
||||||
|
|
||||||
|
(str/includes? (str header) "Item Description")
|
||||||
|
:sysco-invoices-list
|
||||||
|
|
||||||
(str/includes? (str header) "Status")
|
(str/includes? (str header) "Status")
|
||||||
:ledyard
|
:ledyard
|
||||||
|
|
||||||
@@ -34,7 +37,6 @@
|
|||||||
|
|
||||||
(str/includes? (str header) "PARENT CUSTOMER NAME")
|
(str/includes? (str header) "PARENT CUSTOMER NAME")
|
||||||
:worldwide
|
:worldwide
|
||||||
|
|
||||||
:else
|
:else
|
||||||
nil)]
|
nil)]
|
||||||
(alog/info ::csv-type-determined :type csv-type)
|
(alog/info ::csv-type-determined :type csv-type)
|
||||||
@@ -198,6 +200,30 @@
|
|||||||
[]
|
[]
|
||||||
(drop 1 rows)))
|
(drop 1 rows)))
|
||||||
|
|
||||||
|
(defmethod parse-csv :sysco-invoices-list
|
||||||
|
[rows]
|
||||||
|
(let [header (first rows)]
|
||||||
|
(->> (drop 1 rows)
|
||||||
|
(map (fn [row] (into {} (map vector header row))))
|
||||||
|
(filter (fn [row]
|
||||||
|
(let [qty (get row "Current Quantity")]
|
||||||
|
(not (or (str/blank? qty) (= qty "0"))))))
|
||||||
|
(group-by (fn [row] (get row "Invoice")))
|
||||||
|
(map (fn [[_ invoice-rows]]
|
||||||
|
(let [first-row (first invoice-rows)]
|
||||||
|
{:invoice-number (get first-row "Invoice")
|
||||||
|
:date (parse-date-fallover (get first-row "Invoice Date") ["yyyy-MM-dd"])
|
||||||
|
:total (str/replace (get first-row "Amount Due") #"[,\$]" "")
|
||||||
|
:customer-identifier (get first-row "Ship To Name")
|
||||||
|
:vendor-code "Sysco"
|
||||||
|
:text (str/join " " (mapcat vals invoice-rows))
|
||||||
|
:full-text (str/join " " (mapcat vals invoice-rows))
|
||||||
|
:line-items (mapv (fn [row]
|
||||||
|
{:description (get row "Item Description")
|
||||||
|
:amount (Double/parseDouble (str/replace (get row "Total Amount") #"[,\$]" ""))})
|
||||||
|
invoice-rows)})))
|
||||||
|
vec)))
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:unused-binding]}
|
#_{:clj-kondo/ignore [:unused-binding]}
|
||||||
(defmethod parse-csv nil
|
(defmethod parse-csv nil
|
||||||
[rows]
|
[rows]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[auto-ap.datomic.vendors :as d-vendors]
|
[auto-ap.datomic.vendors :as d-vendors]
|
||||||
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
|
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
|
||||||
[auto-ap.import.manual :as manual]
|
[auto-ap.import.manual :as manual]
|
||||||
|
[auto-ap.jobs.sysco :as sysco]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
[auto-ap.parse :as parse]
|
[auto-ap.parse :as parse]
|
||||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
||||||
{:vendor-code vendor-code})))))
|
{:vendor-code vendor-code})))))
|
||||||
|
|
||||||
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-override location-override import-status]}]
|
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-override location-override import-status line-items]}]
|
||||||
(let [matching-client (cond
|
(let [matching-client (cond
|
||||||
account-number (:db/id (d-clients/exact-match account-number))
|
account-number (:db/id (d-clients/exact-match account-number))
|
||||||
customer-identifier (:db/id (d-clients/best-match customer-identifier))
|
customer-identifier (:db/id (d-clients/best-match customer-identifier))
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
matching-client)
|
matching-client)
|
||||||
text
|
text
|
||||||
full-text))]
|
full-text))]
|
||||||
#:invoice {:db/id (random-tempid)
|
(cond-> #:invoice {:db/id (random-tempid)
|
||||||
:invoice/client matching-client
|
:invoice/client matching-client
|
||||||
:invoice/client-identifier (or account-number customer-identifier)
|
:invoice/client-identifier (or account-number customer-identifier)
|
||||||
:invoice/vendor (:db/id matching-vendor)
|
:invoice/vendor (:db/id matching-vendor)
|
||||||
@@ -82,7 +83,8 @@
|
|||||||
:invoice/location matching-location
|
:invoice/location matching-location
|
||||||
:invoice/import-status (or import-status :import-status/pending)
|
:invoice/import-status (or import-status :import-status/pending)
|
||||||
:invoice/outstanding-balance (Double/parseDouble total)
|
:invoice/outstanding-balance (Double/parseDouble total)
|
||||||
:invoice/status :invoice-status/unpaid}))
|
:invoice/status :invoice-status/unpaid}
|
||||||
|
(seq line-items) (assoc :line-items line-items))))
|
||||||
|
|
||||||
(defn validate-invoice [invoice user]
|
(defn validate-invoice [invoice user]
|
||||||
(when-not (:invoice/client invoice)
|
(when-not (:invoice/client invoice)
|
||||||
@@ -111,6 +113,7 @@
|
|||||||
(map #(validate-invoice % user))
|
(map #(validate-invoice % user))
|
||||||
admin-only-if-multiple-clients
|
admin-only-if-multiple-clients
|
||||||
(mapv d-invoices/code-invoice)
|
(mapv d-invoices/code-invoice)
|
||||||
|
(mapv sysco/maybe-code-line-items)
|
||||||
(mapv (fn [i] [:propose-invoice i])))]
|
(mapv (fn [i] [:propose-invoice i])))]
|
||||||
|
|
||||||
(alog/info ::creating-invoice :invoices potential-invoices)
|
(alog/info ::creating-invoice :invoices potential-invoices)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
[auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
|
[auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
|
||||||
can-see-client? exception->notification
|
can-see-client? exception->notification
|
||||||
extract-client-ids]]
|
extract-client-ids]]
|
||||||
|
[auto-ap.jobs.sysco :as sysco]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
[auto-ap.parse :as parse]
|
[auto-ap.parse :as parse]
|
||||||
[auto-ap.permissions :refer [can? wrap-must]]
|
[auto-ap.permissions :refer [can? wrap-must]]
|
||||||
@@ -597,7 +598,7 @@
|
|||||||
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
||||||
{:vendor-code vendor-code})))))
|
{:vendor-code vendor-code})))))
|
||||||
|
|
||||||
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-search vendor-override location-override import-status]} user]
|
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-search vendor-override location-override import-status line-items]} user]
|
||||||
(when-not total
|
(when-not total
|
||||||
(throw (Exception. "Couldn't parse total from file.")))
|
(throw (Exception. "Couldn't parse total from file.")))
|
||||||
(when-not date
|
(when-not date
|
||||||
@@ -624,7 +625,7 @@
|
|||||||
matching-client)
|
matching-client)
|
||||||
text
|
text
|
||||||
full-text))]
|
full-text))]
|
||||||
#:invoice {:db/id (random-tempid)
|
(cond-> #:invoice {:db/id (random-tempid)
|
||||||
:invoice/uploader (-> user :db/id)
|
:invoice/uploader (-> user :db/id)
|
||||||
:invoice/client matching-client
|
:invoice/client matching-client
|
||||||
:invoice/client-identifier (or account-number customer-identifier)
|
:invoice/client-identifier (or account-number customer-identifier)
|
||||||
@@ -636,7 +637,8 @@
|
|||||||
:invoice/location matching-location
|
:invoice/location matching-location
|
||||||
:invoice/import-status (or import-status :import-status/pending)
|
:invoice/import-status (or import-status :import-status/pending)
|
||||||
:invoice/outstanding-balance (Double/parseDouble total)
|
:invoice/outstanding-balance (Double/parseDouble total)
|
||||||
:invoice/status :invoice-status/unpaid}))
|
:invoice/status :invoice-status/unpaid}
|
||||||
|
(seq line-items) (assoc :line-items line-items))))
|
||||||
|
|
||||||
(defn validate-invoice [invoice user]
|
(defn validate-invoice [invoice user]
|
||||||
(let [missing-keys (for [k [:invoice/invoice-number :invoice/client :invoice/vendor :invoice/total :invoice/outstanding-balance :invoice/date]
|
(let [missing-keys (for [k [:invoice/invoice-number :invoice/client :invoice/vendor :invoice/total :invoice/outstanding-balance :invoice/date]
|
||||||
@@ -681,6 +683,7 @@
|
|||||||
proposed-invoices (->> potential-invoices
|
proposed-invoices (->> potential-invoices
|
||||||
(filter #(not (:error-message %)))
|
(filter #(not (:error-message %)))
|
||||||
(mapv d-invoices/code-invoice)
|
(mapv d-invoices/code-invoice)
|
||||||
|
(mapv sysco/maybe-code-line-items)
|
||||||
(mapv (fn [i] [:propose-invoice i])))]
|
(mapv (fn [i] [:propose-invoice i])))]
|
||||||
|
|
||||||
(alog/info ::creating-invoice :invoices proposed-invoices)
|
(alog/info ::creating-invoice :invoices proposed-invoices)
|
||||||
|
|||||||
Reference in New Issue
Block a user