diff --git a/src/clj/auto_ap/parse/csv.clj b/src/clj/auto_ap/parse/csv.clj index 318ee98d..82247043 100644 --- a/src/clj/auto_ap/parse/csv.clj +++ b/src/clj/auto_ap/parse/csv.clj @@ -18,6 +18,17 @@ :default (fn default [rows] nil)) +(defn parse-date-fallover [d fmts] + (if-let [valid-fmt (->> fmts + (filter (fn [f] + (try + (u/parse-value :clj-time f d) + (catch Exception e + nil)) + )) + (first))] + (u/parse-value :clj-time valid-fmt d))) + (defmethod parse-csv :mama-lus [rows] (println "MAMA LU4") @@ -27,9 +38,7 @@ {:vendor-code "Mama Lu's Foods" :customer-identifier customer :invoice-number (str po-number "-" invoice-number ) - :date (try (u/parse-value :clj-time "M/d/yyyy HH:ss" invoice-date) - (catch Exception _ - (u/parse-value :clj-time "M/d/yyyy" invoice-date))) + :date (parse-date-fallover invoice-date ["M/d/yyyy HH:ss" "M/d/yyyy HH:mm:ss aa" "M/d/yyyy"]) :total (str/replace value #"," "") :text (str/join " " row) :full-text (str/join " " row)}))) diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index e5ee60af..4956f275 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -121,12 +121,12 @@ ;; Young's Market Co - INVOICE {:vendor "Youngs Market" :keywords [#"P.O.Box 743564"] - :extract {:date #"INVOICE DATE\n(?:.*?)(\S+)\n" - :customer-identifier #"INVOICE DATE\n [0-9]+\s+(.*?)\s{2,}" - :invoice-number #"INVOICE DATE\n(?:.*?)\s{2,}(\d+?)\s+\S+\n" - :total #"Net Amount(?:.*\n){4}(?:.*?)([0-9\.]+)\n"} + :extract {:date #"(?:INVOICE|CREDIT) DATE\n(?:.*?)(\S+)\n" + :customer-identifier #"(?:INVOICE|CREDIT) DATE\n [0-9]+\s+(.*?)\s{2,}" + :invoice-number #"(?:INVOICE|CREDIT) DATE\n(?:.*?)\s{2,}(\d+?)\s+\S+\n" + :total #"Net Amount(?:.*\n){4}(?:.*?)([\-]?[0-9\.]+)\n"} :parser {:date [:clj-time "dd-MMM-yy"] - :total [:trim-commas nil]}} + :total [:trim-commas-and-negate nil]}} ;; WINE WAREHOUSE {:vendor "Wine Warehouse" @@ -168,6 +168,16 @@ :parser {:date [:clj-time "MM/dd/yyyy"] :total [:trim-commas nil]}} + ;; P&R + {:vendor "P & R PAPER SUPPLY CO" + :keywords [#"PAPER SUPPLY COMPANY"] + :extract {:date #"Invoiced.*\n\s+\S+\s+(\S+)" + :customer-identifier #"Bill To.*\n\s*(.*?)\s{2,}" + :invoice-number #"Invoice#.*\n.*\n.*?(\S+)\s+\d+\n" + :total #"INVOICE TOTAL\s+([\-]?[\d,]+\.\d+)"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas-and-negate nil]}} + ;; SUNCREST STATEMENT {:vendor "Suncrest USA Inc" :keywords [#"Suncrest.*\n.*Statement"] @@ -196,8 +206,9 @@ :extract {:date #"INVOICE NUMBER[^\n]+\n([^\n]+)\n" :customer-identifier #"INVOICE NUMBER[^\n]+\n[^\n]+\n([\S ]+?)(?=\s{2,})" ;; ([\S ]+)\s{2,} :invoice-number #"INVOICE NUMBER[^\n]+\n[^\n]+\n.*?(?=[\d]{9})(\d{9})" - :total #"\s{2,}INVOICE\s{2,}.*?(?=TOTAL)TOTAL\s+([0-9.]+)"} - :parser {:date [:clj-time "MM/dd/yyyy"]}} + :total #"\s{2,}INVOICE\s{2,}.*?(?=TOTAL)TOTAL\s+([0-9.]+[\-]?)"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas-and-negate nil]}} ;; LE BOULANGER {:vendor "Le Boulanger" @@ -263,6 +274,18 @@ :multi #"\n" :multi-match? #"^\s+[\d]{6,8}\s+\d+"} + ;; ACME BREAD + {:vendor "Acme Bread" + :keywords [#"acmebread\.com"] + :extract {:date #"([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Print Date.*\n.*\n(.*)" + :invoice-number #"^\s*(\d+)" + :total #"\s{2,}(\d+\.\d{2})\s{2,}"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]} + :multi #"\n" + :multi-match? #"^\s*\d+\s+([0-9]+/[0-9]+/[0-9]+)"} + ;; PFG - ROMA {:vendor "Performance Food Group - ROMA" :keywords [#"Performance Food Group, Inc\n\f"] @@ -328,6 +351,30 @@ :keywords [#"Alt_invoice_number"] :extract (fn [wb vendor] (let [[sheet] (d/sheet-seq wb)] + (println "COUNT" (count (transduce (comp + (drop-while (fn [c] + (not (re-find #"Customer_id" (str (d/read-cell c)))))) + (drop 9) + (filter (fn [c] + (= 0 (.getColumnIndex c)))) + (filter (fn [c] + (not (str/blank? (str/trim (or (d/read-cell (d/select-cell (offset c 1 0) sheet)) "")))))) + (map (fn [c] + {:customer-identifier (str/trim (d/read-cell (d/select-cell (offset c 1 0) sheet))) + :text (d/read-cell (d/select-cell (offset c 1 0) sheet)) + :full-text (d/read-cell (d/select-cell (offset c 1 0) sheet)) + :date (u/parse-value :clj-time "MM/dd/yyyy" (str/trim (d/read-cell (d/select-cell (offset c 5 0) sheet)))) + :invoice-number (->> + (re-find #"^(?:0+([A-Z0-9]+))|([A-Z]+[A-Z0-9]+)" (str/trim (d/read-cell (d/select-cell (offset c 2 0) sheet)))) + (drop 1 ) + (filter identity) + first) + :total (str (d/read-cell (d/select-cell (offset c 7 0) sheet))) + :vendor-code vendor})) + (filter :customer-identifier)) + conj + [] + (d/cell-seq sheet)))) (transduce (comp (drop-while (fn [c] (not (re-find #"Customer_id" (str (d/read-cell c)))))) @@ -342,7 +389,7 @@ :full-text (d/read-cell (d/select-cell (offset c 1 0) sheet)) :date (u/parse-value :clj-time "MM/dd/yyyy" (str/trim (d/read-cell (d/select-cell (offset c 5 0) sheet)))) :invoice-number (->> - (re-find #"^(?:0+([A-Z0-9]+))|([A-Z]+[A-Z0-9]+)" (str/trim (d/read-cell (d/select-cell (offset c 3 0) sheet)))) + (re-find #"^(?:0+([A-Z0-9]+))|([A-Z]+[A-Z0-9]+)" (str/trim (d/read-cell (d/select-cell (offset c 2 0) sheet)))) (drop 1 ) (filter identity) first) diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index 61b3cefe..386570a8 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -176,23 +176,36 @@ '[?vendor :vendor/default-account ?default-account]]} :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 "matching" customer-identifier "-" matching-vendor) matching-client (parse/best-match clients customer-identifier) - _ (println "New invoice matches client" matching-client) + _ (println "invoice \"" invoice-number "\"matches client" (:client/name matching-client)) matching-location (parse/best-location-match matching-client text full-text) [existing-id existing-outstanding-balance existing-status import-status] (when (and matching-client matching-location) + (try (->> (d/query - (cond-> {:query {:find ['?e '?outstanding-balance '?status '?import-status2] - :in ['$ '?invoice-number '?vendor '?client] - :where '[[?e :invoice/invoice-number ?invoice-number] - [?e :invoice/vendor ?vendor] - [?e :invoice/client ?client] - [?e :invoice/outstanding-balance ?outstanding-balance] - [?e :invoice/status ?status] - [?e :invoice/import-status ?import-status] - [?import-status :db/ident ?import-status2]]} - :args [(d/db (d/connect uri)) invoice-number matching-vendor (:db/id matching-client)]})) - first))] + (cond-> {:query {:find ['?e '?outstanding-balance '?status '?import-status2] + :in ['$ '?invoice-number '?vendor '?client] + :where '[[?e :invoice/invoice-number ?invoice-number] + [?e :invoice/vendor ?vendor] + [?e :invoice/client ?client] + [?e :invoice/outstanding-balance ?outstanding-balance] + [?e :invoice/status ?status] + [?e :invoice/import-status ?import-status] + [?import-status :db/ident ?import-status2]]} + :args [(d/db (d/connect uri)) invoice-number matching-vendor (:db/id matching-client)]})) + first) + (catch Exception e + (throw (ex-info (str "Failed to find potential matching invoice with" + " invoice " invoice-number + " vendor " matching-vendor + " client " (:client/name matching-client)) + {:args [ invoice-number matching-vendor (:db/id matching-client)]}))) + ))] (cond (not (and matching-location matching-client)) @@ -219,7 +232,12 @@ )) [] imports)] +(when-not (seq transactions) + (throw (ex-info "No invoices found." + {:imports imports}))) @(d/transact (d/connect uri) (vec (set transactions))))) + + (defroutes routes (wrap-routes @@ -260,6 +278,7 @@ (context "/invoices" [] (POST "/upload" +<<<<<<< HEAD {{ file :file } :params :as params} #_(clojure.pprint/pprint params) (let [{:keys [filename tempfile]} file] @@ -267,6 +286,21 @@ {:status 200 :body (pr-str {}) :headers {"Content-Type" "application/edn"}})) +======= + {{ files "file"} :params :as params} + (let [{:keys [filename tempfile]} files] + (try + (import-uploaded-invoice (parse/parse-file (.getPath tempfile) filename)) + {:status 200 + :body (pr-str {}) + :headers {"Content-Type" "application/edn"}} + (catch Exception e + {:status 500 + :body (pr-str {:message (.getMessage e) + :data (ex-data e)}) + :headers {"Content-Type" "application/edn"}})) + )) +>>>>>>> master (POST "/upload-integreat" {{:keys [excel-rows]} :edn-params user :identity} (assert-admin user) diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs index 31db5e41..974677e8 100644 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/import_invoices.cljs @@ -28,7 +28,9 @@ (js/Dropzone. (reagent/dom-node this) (clj->js {:init (fn [] (.on (js-this) "success" (fn [_ files] - (re-frame/dispatch [::invalidated])))) + (re-frame/dispatch [::invalidated]))) + (.on (js-this) "error" (fn [_ error] + (re-frame/dispatch [::errored error])))) :paramName "file" :headers {"Authorization" (str "Token " @token)} :url (str "/api/invoices/upload" @@ -42,6 +44,11 @@ (fn [db] (-> db ::invoice-page))) +(re-frame/reg-sub + ::error + (fn [db] + (-> db ::error))) + (re-frame/reg-sub ::params (fn [db] @@ -57,6 +64,7 @@ (fn [cofx [_ params]] {:db (-> (:db cofx) + (dissoc ::error) (assoc-in [:status :loading] true) (assoc-in [::params] params)) :graphql {:token (-> cofx :db :user) @@ -72,6 +80,11 @@ (assoc ip :checked (by :id (:invoices ip))))) (assoc-in [:status :loading] false)))) +(re-frame/reg-event-db + ::errored + (fn [db [_ error]] + (assoc db ::error (:message (edn/read-string error))))) + (re-frame/reg-event-fx ::reject-invoices-clicked (fn [{:keys [db]} [_ invoices on-success]] @@ -154,12 +167,17 @@ (with-meta (fn [] (let [invoice-page (re-frame/subscribe [::invoice-page]) - status (re-frame/subscribe [::subs/status])] + status (re-frame/subscribe [::subs/status]) + error (re-frame/subscribe [::error])] [:div [:h1.title "Upload invoices"] [dropzone] + - [:div {:class "section"}] + [:div {:class "section"} + (when @error + + [:div.notification.is-warning @error])] [:div {:class "card found-invoices",} [:div {:class "card-header"} [:span {:class "card-header-title"} "Found Invoices"]]