From 46a5d4cbfd4b26aa6cb3372b4dd4ec7625be0d36 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Mon, 11 Feb 2019 22:00:36 -0800 Subject: [PATCH] made invoice import back to working, mostly --- src/clj/auto_ap/graphql.clj | 22 +++-- src/clj/auto_ap/graphql/invoices.clj | 13 +++ src/clj/auto_ap/parse.clj | 32 ++++--- src/clj/auto_ap/parse/templates.clj | 5 +- src/clj/auto_ap/routes/invoices.clj | 11 ++- .../views/components/invoices/side_bar.cljs | 6 +- .../auto_ap/views/pages/import_invoices.cljs | 96 ++++++++++++++----- 7 files changed, 132 insertions(+), 53 deletions(-) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 31991d0a..6239f25a 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -337,12 +337,20 @@ :bank_account_type {:values [{:enum-value :check} {:enum-value :cash}]}} :mutations - {:print_checks {:type :check_result - :args {:invoice_payments {:type '(list :invoice_payment_amount)} - :bank_account_id {:type :id} - :type {:type :payment_type} - :client_id {:type :id}} - :resolve :mutation/print-checks} + {:reject_invoices {:type '(list :id) + :args {:invoices {:type '(list :id)}} + :resolve :mutation/reject-invoices} + + :approve_invoices {:type '(list :id) + :args {:invoices {:type '(list :id)}} + :resolve :mutation/approve-invoices} + + :print_checks {:type :check_result + :args {:invoice_payments {:type '(list :invoice_payment_amount)} + :bank_account_id {:type :id} + :type {:type :payment_type} + :client_id {:type :id}} + :resolve :mutation/print-checks} :add_handwritten_check {:type :check_result :args {:invoice_id {:type :id} @@ -467,6 +475,8 @@ :get-user get-user :mutation/add-handwritten-check gq-checks/add-handwritten-check :mutation/print-checks print-checks + :mutation/reject-invoices gq-invoices/reject-invoices + :mutation/approve-invoices gq-invoices/approve-invoices :mutation/edit-user gq-users/edit-user :mutation/add-invoice gq-invoices/add-invoice :mutation/edit-invoice gq-invoices/edit-invoice diff --git a/src/clj/auto_ap/graphql/invoices.clj b/src/clj/auto_ap/graphql/invoices.clj index 4c6d4601..9e00f4ae 100644 --- a/src/clj/auto_ap/graphql/invoices.clj +++ b/src/clj/auto_ap/graphql/invoices.clj @@ -29,6 +29,19 @@ (d-invoices/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))) +(defn reject-invoices [context {:keys [invoices] :as in} value] + (assert-admin (:id context)) + + (let [transactions (map (fn [i] [:db/retractEntity i ]) invoices) + transaction-result @(d/transact (d/connect uri) transactions)] + invoices)) + +(defn approve-invoices [context {:keys [invoices] :as in} value] + (assert-admin (:id context)) + (let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices) + transaction-result @(d/transact (d/connect uri) transactions)] + invoices)) + (defn add-invoice [context {{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value] (when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice_number :invoice/vendor vendor_id diff --git a/src/clj/auto_ap/parse.clj b/src/clj/auto_ap/parse.clj index b8322722..92278a6a 100644 --- a/src/clj/auto_ap/parse.clj +++ b/src/clj/auto_ap/parse.clj @@ -11,6 +11,12 @@ (defmulti parse-value (fn [method _ _] method)) + +(defmethod parse-value :trim-commas + [_ _ value] + (str/replace value #"," "") + ) + (defmethod parse-value :clj-time [_ format value] (time/from-time-zone (f/parse (f/formatter format) value) @@ -30,16 +36,17 @@ #(extract-template % (dissoc template :multi)) (str/split text (:multi template))) - [(->> template - :extract - (reduce-kv - (fn [result k v] - (let [value (some-> (first (map second (re-seq v text))) - str/trim ) - [value-parser parser-params] (-> template :parser k)] - (assoc result k (parse-value value-parser parser-params value)))) - {:vendor-code (:vendor template) - :text text}))])) + (when template + [(->> template + :extract + (reduce-kv + (fn [result k v] + (let [value (some-> (first (map second (re-seq v text))) + str/trim ) + [value-parser parser-params] (-> template :parser k)] + (assoc result k (parse-value value-parser parser-params value)))) + {:vendor-code (:vendor template) + :text text}))]))) (defn parse [text] (println text) @@ -72,11 +79,10 @@ (defn best-match [clients invoice-client-name] (->> clients - (mapcat (fn [{:keys [:db/id :client/matches] :as client :or {matches []}}] - (println matches) + (mapcat (fn [{:keys [:db/id :client/matches :client/name] :as client :or {matches []}}] (map (fn [m] [client (m/jaccard (.toLowerCase invoice-client-name) (.toLowerCase m))]) - matches))) + (conj matches name)))) (filter #(< (second %) 0.25)) (sort-by second) ffirst)) diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index 8ab123a0..b2607b64 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -10,13 +10,14 @@ :invoice-number #"\s+[0-9]+/[0-9]+/[0-9]+\s+([0-9]+)"} :parser {:date [:clj-time "MM/dd/yyyy"]}} - {:vendor "GGM" + {:vendor "Golden Gate Meat Company, Inc" :keywords [#"Golden Gate Meat"] :extract {:total #"Invoice Total\:\s+\$([\d.,]+)" :customer-identifier #"Bill To\s*:\s*([\w ]+)\s{2,}" :date #"Printed:\s+([0-9]+/[0-9]+/[0-9]+)" :invoice-number #"Invoice\s+[^\n]+\n[^\n]+\n\s+([0-9]+)"} - :parser {:date [:clj-time "MM/dd/yyyy"]}} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas nil]}} {:vendor "CINTAS" :keywords [#"CINTAS CORPORATION"] diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index 2cc7d1e6..61a4efc0 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -158,20 +158,21 @@ :args [(d/db (d/connect uri)) vendor-code]})) first) matching-client (parse/best-match clients customer-identifier) + _ (println "New invoice matches client" matching-client) matching-location (parse/best-location-match matching-client text ) - _ (println "LOCATION" matching-location) - [existing-id existing-outstanding-balance existing-status import-status] (->> (d/query - (cond-> {:query {:find ['?e '?outstanding-balance '?status '?import-status] + (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]]} + [?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)] + (if (= :import-status/imported import-status) result (conj result (remove-nils #:invoice {:invoice/client (:db/id matching-client) @@ -190,7 +191,7 @@ )) [] imports)] - (println transactions) + @(d/transact (d/connect uri) transactions) )) diff --git a/src/cljs/auto_ap/views/components/invoices/side_bar.cljs b/src/cljs/auto_ap/views/components/invoices/side_bar.cljs index f4923341..7cbc8bf2 100644 --- a/src/cljs/auto_ap/views/components/invoices/side_bar.cljs +++ b/src/cljs/auto_ap/views/components/invoices/side_bar.cljs @@ -19,20 +19,20 @@ [:li.menu-item [:a.item {:href (bidi/path-for routes/routes :unpaid-invoices) :class [(active-when ap = :unpaid-invoices)]} - [:span {:class "icon icon-accounting-document" :style {:font-size "25px"}}] + [:span {:class "icon icon-accounting-invoice-mail" :style {:font-size "25px"}}] [:span {:class "name"} "Unpaid Invoices"]]] [:li.menu-item [:a.item {:href (bidi/path-for routes/routes :paid-invoices) :class [(active-when ap = :paid-invoices)]} - [:span {:class "icon icon-accounting-invoice-mail" :style {:font-size "25px"}}] + [:span {:class "icon icon-check-payment-give" :style {:font-size "25px"}}] [:span {:class "name"} "Paid Invoices"]]] [:li.menu-item [:a.item {:href (bidi/path-for routes/routes :import-invoices) :class [(active-when ap = :import-invoices)]} - [:span {:class "icon icon-accounting-invoice-mail" :style {:font-size "25px"}}] + [:span {:class "icon icon-accounting-document" :style {:font-size "25px"}}] [:span {:class "name"} "Import Invoices"]]]]] [:div diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs index fcb89fb2..662bcb12 100644 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/import_invoices.cljs @@ -6,6 +6,7 @@ [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.entities.vendors :as vendor] [auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table] [cljsjs.dropzone :as dropzone] @@ -69,15 +70,32 @@ (assoc-in [:status :loading] false)))) (re-frame/reg-event-fx - ::reject-invoices - (fn [cofx [_ on-success]] - {:http {:method :post - :token (-> cofx :db :user) - :uri (str "/api/invoices/reject" - (when-let [client-id (:id @(re-frame/subscribe [::subs/client]))] - (str "?client=" client-id))) - :on-success on-success - }})) + ::reject-invoices-clicked + (fn [{:keys [db]} [_ invoices on-success]] + {:graphql + {:token (-> db :user) + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "RejectInvoices"} + + :venia/queries [[:reject-invoices + {:invoices (keys invoices)} + []]]} + :on-success [::invalidated]} + })) + +(re-frame/reg-event-fx + ::approve-invoices-clicked + (fn [{:keys [db]} [_ invoices on-success]] + {:graphql + {:token (-> db :user) + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "ApproveInvoices"} + + :venia/queries [[:approve-invoices + {:invoices (keys invoices)} + []]]} + :on-success [::invalidated]} + })) (re-frame/reg-event-fx ::approve-invoices @@ -90,6 +108,45 @@ :on-success on-success }})) +(re-frame/reg-event-db + ::toggle-check + (fn [db [_ id invoice]] + (-> db + (update-in [::invoice-page :checked] (fn [x] + (let [x (or x {})] + (if (x id) + (dissoc x id) + (assoc x id invoice)))))))) + +(defn approve-reject-button [checked] + [:div.is-pulled-right + + [:button.button.is-success {:on-click (dispatch-event [::approve-invoices-clicked checked]) + :disabled (if (seq checked) + "" + "disabled")} + "Approve " + (when (> (count checked )) + (str + (count checked) + " invoices")) + + + [:span " "]] + [:button.button.is-danger {:on-click (dispatch-event [::reject-invoices-clicked checked]) + :disabled (if (seq checked) + "" + "disabled")} + "Reject " + (when (> (count checked )) + (str + (count checked) + " invoices")) + + + [:span " "] + ]]) + (def import-invoices-content (with-meta (fn [] @@ -104,31 +161,22 @@ [:div {:class "card-header"} [:span {:class "card-header-title"} "Found Invoices"]] [:div {:class "card-content"} + [approve-reject-button (:checked @invoice-page)] (if (:loading @status) [:h1.title [:i.fa.fa-spin.fa-spinner]] (if (seq (:invoices @invoice-page)) [invoice-table {:id :approved :invoice-page invoice-page + :check-boxes true + :checked (:checked @invoice-page) + :on-check-changed (fn [which invoice] + (re-frame/dispatch [::toggle-check which invoice])) :status (re-frame/subscribe [::subs/status]) :params (re-frame/subscribe [::params]) :on-params-change (fn [params] (re-frame/dispatch [::params-change params])) }] - [:span "No pending invoices"]))] - (if (and (seq (:invoices @invoice-page)) (not (:loading @status))) - [:div.card-footer - [:a.card-footer-item - {:on-click (fn [e] - (.preventDefault e) - (re-frame/dispatch [::approve-invoices - [::invalidated]]))} - "Accept all"] - [:a.card-footer-item - {:on-click (fn [e] - (.preventDefault e) - (re-frame/dispatch [::reject-invoices - [::invalidated]]))} - "Reject all"]])]])) + [:span "No pending invoices"]))]]])) {:component-will-mount (fn [] (re-frame/dispatch-sync [::invalidated]))}))