diff --git a/src/clj/auto_ap/parse/csv.clj b/src/clj/auto_ap/parse/csv.clj index 1ef80577..228ca539 100644 --- a/src/clj/auto_ap/parse/csv.clj +++ b/src/clj/auto_ap/parse/csv.clj @@ -10,8 +10,12 @@ (doto (cond (str/includes? (second header) "Customer's PO No.") :mama-lus + (str/includes? (str header) "Ship-To Number") + :sysco-style-2 + (str/includes? (str header) "Closed Date") - :sysco + :sysco-style-1 + :else nil) @@ -33,16 +37,16 @@ (first))] (u/parse-value :clj-time valid-fmt d))) -(defmethod parse-csv :sysco +(defmethod parse-csv :sysco-style-1 [rows] - (println "Importing Sysco") + (println "Importing Sysco-styled 1") (let [header (first rows)] (transduce (comp (drop 1) (map (fn [row] (into {} (map vector header row)))) (map (fn [row] - {:vendor-code "Sysco" + {:vendor-code nil :customer-identifier nil :invoice-number (get row "Inv #") :date (parse-date-fallover (get row "Invoice Date") ["M/d/yyyy"]) @@ -53,6 +57,28 @@ [] rows))) +(defmethod parse-csv :sysco-style-2 + [rows] + (println "Importing Sysco-styled 1") + (let [header (first rows)] + (doto + (transduce + (comp (drop 1) + (map (fn [row] + (into {} (map vector header row)))) + (map (fn [row] + {:vendor-code nil + :customer-identifier (get row "Ship-To Name") + :invoice-number (str/trim (get row "Invoice Number")) + :date (parse-date-fallover (get row "Invoice Date") ["yyyy-MM-dd"]) + :total (str/replace (get row "Original Amount") #"[,\$]" "") + :text (str/join " " (vals row)) + :full-text (str/join " " (vals row))}))) + conj + [] + rows) + println))) + (defmethod parse-csv :mama-lus [rows] (println "MAMA LU4") diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index b4e5c23f..bf93296e 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -173,7 +173,7 @@ ;; WINE WAREHOUSE {:vendor "Wine Warehouse" - :keywords [#"WINE WAREHOUSE"] + :keywords [#"WINE WAREHOUSE" #"Bottle prices include"] :extract {:date #"INVOICE DATE\s+([0-9]+/[0-9]+/[0-9]+)" :customer-identifier #"SHIP-TO-PARTY.*\n(.*?)(?=\s{2,})" :invoice-number #"INV #\s+(\d+)" @@ -181,6 +181,26 @@ :parser {:date [:clj-time "MM/dd/yyyy"] :total [:trim-commas nil]}} + ;; WINE WAREHOUSE 3 + {:vendor "Wine Warehouse" + :keywords [#"Wine Warehouse" #"PLEASE APPLY CREDIT"] + :extract {:date #"Credit Memo Number/Date\s+\d+\s+([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Ship-To-Party.*\n(.*?)\s{2,}" + :invoice-number #"Credit Memo Number/Date\s+(\d+)" + :total #"Total\s+([0-9]+\.[0-9]{2}-)"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]}} + + ;; WINE WAREHOUSE 2 + {:vendor "Wine Warehouse" + :keywords [#"WINE WAREHOUSE" #"Bill-to-Party"] + :extract {:date #"Invoice date\s+([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Bill-to-Party.*\n(.*?)\s{2,}" + :invoice-number #"Invoice number\s+(\d+)" + :total #"Gross\s+([0-9]+\.[0-9]{2})"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas nil]}} + ;; REGAL {:vendor "Regal Wine Co" :keywords [#"REGAL WINE"] @@ -295,6 +315,16 @@ :parser {:date [:clj-time "MM/dd/yyyy"] :total [:trim-commas-and-negate nil]}} + ;; Ocean Queen + {:vendor "Ocean Queen" + :keywords [#"Ocean Queen USA"] + :extract {:date #"([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Bill To.*\n\s*(.*?)\s{2,}" + :invoice-number #"Invoice #\n.*\n.*?(\d+)\n" + :total #"Total\s+\$([\d\.,\-]+)"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]}} + ;; CHEF's CHOICE {:vendor "Chef's Choice Produce Co" :keywords [#"(2170 MARTIN AVENUE|213-3886)"] diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index fe73231d..734e49fe 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -167,7 +167,33 @@ )) -(defn import-uploaded-invoice [client forced-location imports] +(defn match-vendor [vendor-code forced-vendor] + (when (and (not forced-vendor) (str/blank? vendor-code)) + (throw (ex-info (str "No vendor found. Please supply an forced vendor.") + {:vendor-code vendor-code}))) + (let [vendor-id (or forced-vendor + (->> (d/query + {:query {:find ['?vendor] + :in ['$ '?vendor-name] + :where ['[?vendor :vendor/name ?vendor-name]]} + :args [(d/db (d/connect uri)) vendor-code]}) + first + first))] + (when-not vendor-id + (throw (ex-info (str "Vendor matching name \"" vendor-code "\" not found.") + {:vendor-code vendor-code}))) + + (if-let [matching-vendor (->> (d/query + {:query {:find [(list 'pull '?vendor-id d-vendors/default-read)] + :in ['$ '?vendor-id]} + :args [(d/db (d/connect uri)) vendor-id]}) + first + first)] + matching-vendor + (throw (ex-info (str "No vendor with the name " vendor-code " was found.") + {:vendor-code vendor-code}))))) + +(defn import-uploaded-invoice [client forced-location forced-vendor imports] (let [clients (d-clients/get-all) transactions (reduce (fn [result {:keys [invoice-number customer-identifier total date vendor-code text full-text] :as info}] (println "searching for" vendor-code) @@ -184,18 +210,10 @@ :customer-identifier customer-identifier :vendor-code vendor-code}))) - [matching-vendor] (->> (d/query - {:query {:find [(list 'pull '?vendor d-vendors/default-read)] - :in ['$ '?vendor-name] - :where ['[?vendor :vendor/name ?vendor-name]]} - :args [(d/db (d/connect uri)) vendor-code]}) - first) - _ (when-not matching-vendor - (throw (ex-info (str "No vendor with the name " vendor-code " was found.") - {:invoice-number invoice-number - :customer-identifier customer-identifier - :vendor-code vendor-code}))) - _ (println "invoice \"" invoice-number "\"matches client " (:client/name matching-client) " (" (:db/id matching-client) ")") + matching-vendor (match-vendor vendor-code forced-vendor) + + + _ (println "invoice \"" invoice-number "\" matches client " (:client/name matching-client) " (" (:db/id matching-client) ")") matching-location (or (when-not (str/blank? forced-location) forced-location) (parse/best-location-match matching-client text full-text)) @@ -231,7 +249,7 @@ :else (conj result (cond-> (remove-nils #:invoice {:invoice/client (:db/id matching-client) :invoice/client-identifier customer-identifier - :invoice/vendor matching-vendor + :invoice/vendor (:db/id matching-vendor) :invoice/invoice-number invoice-number :invoice/total (Double/parseDouble total) :invoice/date (to-date date) @@ -248,7 +266,7 @@ )) [] imports)] -(when-not (seq transactions) + (when-not (seq transactions) (throw (ex-info "No invoices found." {:imports imports}))) @(d/transact (d/connect uri) (vec (set transactions))))) @@ -308,14 +326,18 @@ client :client client-2 "client" location :location - location-2 "location"} :params :as params} + location-2 "location" + vendor :vendor + vendor-2 "vendor"} :params :as params} (let [files (or files files-2) client (or client client-2) location (or location location-2) + vendor (some-> (or vendor vendor-2) + (Long/parseLong)) {:keys [filename tempfile]} files] #_(println params (.getPath tempfile) filename) (try - (import-uploaded-invoice client location (parse/parse-file (.getPath tempfile) filename)) + (import-uploaded-invoice client location vendor (parse/parse-file (.getPath tempfile) filename)) {:status 200 :body (pr-str {}) :headers {"Content-Type" "application/edn"}} diff --git a/src/cljs/auto_ap/views/components/typeahead.cljs b/src/cljs/auto_ap/views/components/typeahead.cljs index eb66eb86..900d0163 100644 --- a/src/cljs/auto_ap/views/components/typeahead.cljs +++ b/src/cljs/auto_ap/views/components/typeahead.cljs @@ -120,7 +120,7 @@ valid-matches)] valid-matches)) (defn typeahead-entity [{:keys [matches on-change field value class not-found-description - disabled not-found-value auto-focus]}] + disabled not-found-value auto-focus name]}] (let [text (r/atom (or (second (first (filter #(= (first %) value) matches))) "")) highlighted (r/atom nil) ] @@ -153,6 +153,8 @@ [:div.control [:div.tags.has-addons [:span.tag text] + (when name + [:input {:type "hidden" :name name :value (:id @highlighted)}]) [:a.tag.is-delete {:on-click (fn [] (select nil) (reset! text-atom nil) diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs index 42e65831..6520f667 100644 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/import_invoices.cljs @@ -7,16 +7,18 @@ [auto-ap.entities.clients :as client] [auto-ap.views.components.layouts :refer [side-bar-layout]] [auto-ap.views.components.invoices.side-bar :refer [invoices-side-bar]] - [auto-ap.views.utils :refer [dispatch-event]] + [auto-ap.views.utils :refer [dispatch-event bind-field]] [auto-ap.utils :refer [by]] [auto-ap.entities.vendors :as vendor] + [auto-ap.views.components.typeahead :refer [typeahead-entity]] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [cljsjs.dropzone :as dropzone] [cljs.reader :as edn] [clojure.string :as str])) (def dropzone (let [client (re-frame/subscribe [::subs/client]) - token (re-frame/subscribe [::subs/token])] + token (re-frame/subscribe [::subs/token]) + vendor (reagent/atom nil)] (with-meta (fn [] [:form.dz {:action "/api/invoices/upload"} @@ -25,6 +27,17 @@ [:a.button.is-static "Force Location"]] [:p.control [:input.input {:name "location" :placeholder "SG" :size "4" :maxlength "2" :style {:display "inline"}}]]] + [:div.field.has-addons + [:p.control + [:a.button.is-static "Force vendor"]] + [typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors]) + :match->text :name + :name "vendor" + :type "typeahead" + :on-change (fn [v] + (reset! vendor v)) + :value @vendor}] + ] [:div.tile.notification [:div.has-text-centered {:style {:padding "80px 0px" :width "100%"}}