diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index 9358f0a5..5cdf993a 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -47,6 +47,12 @@ :invoice-status/paid :invoice-status/unpaid)]])})}]] ) +(def add-client-identifier + [[{:db/ident :invoice/client-identifier + :db/doc "An identifier found in an uploaded invoice" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]]) + (defn -main [& args] (println "Creating database ..." uri) (doto (d/create-database uri) println) @@ -125,18 +131,13 @@ :requires [:auto-ap/add-hidden-to-vendor]} :auto-ap/convert-invoices {:txes-fn `add-general-ledger/convert-invoices :requires [:auto-ap/convert-vendors]} - :auto-ap/add-yodlee-merchant2 {:txes add-general-ledger/add-yodlee-merchant :requires [:auto-ap/convert-vendors]} :auto-ap/add-external-id-to-ledger {:txes add-general-ledger/add-external-id-to-ledger :requires [:auto-ap/add-yodlee-merchant2]} :auto-ap/add-exclude-to-transaction {:txes add-general-ledger/add-exclude-to-transaction :requires [:auto-ap/add-external-id-to-ledger]} - - + :auto-ap/add-client-identifier2 {:txes add-client-identifier :requires [:auto-ap/make-every-account-visible]} :auto-ap/add-transaction-rules {:txes add-general-ledger/add-transaction-rules :requires [:auto-ap/add-exclude-to-transaction]} :auto-ap/add-bank-account-locations {:txes add-general-ledger/add-bank-account-locations :requires [:auto-ap/add-transaction-rules]} - :auto-ap/convert-transactions {:txes-fn `add-general-ledger/convert-transactions :requires [:auto-ap/add-bank-account-locations]} - - - }] + :auto-ap/convert-transactions {:txes-fn `add-general-ledger/convert-transactions :requires [:auto-ap/add-bank-account-locations]}}] (println "Conforming database...") (c/ensure-conforms conn norms-map) (when (not (seq args)) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index e6179156..7b4499cb 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -70,6 +70,7 @@ :address {:type :address} :location_matches {:type '(list :location_match)} :locations {:type '(list String)} + :matches {:type '(list String)} :bank_accounts {:type '(list :bank_account)}}} :contact {:fields {:id {:type :id} @@ -253,6 +254,7 @@ :invoice {:fields {:id {:type :id} :original_id {:type 'Int} + :client_identifier {:type 'String} :total {:type 'String} :outstanding_balance {:type 'String} :invoice_number {:type 'String} @@ -473,6 +475,7 @@ :email {:type 'String} :address {:type :add_address} :locations {:type '(list String)} + :matches {:type '(list String)} :location_matches {:type '(list :edit_location_match)} :bank_accounts {:type '(list :edit_bank_account)}}} :edit_bank_account diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index b1b899b3..2816ee6f 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -25,11 +25,17 @@ id (or (:db/id client) "new-client") _ (println id) _ (println edit_client) + _ (when client + @(d/transact (d/connect uri) + (into + (mapv (fn [lm] [:db/retractEntity (:db/id lm)]) (:client/location-matches client)) + (mapv (fn [m] [:db/retract (:db/id client) :client/matches m]) (:client/matches client))))) transactions [(remove-nils {:db/id id :client/code (if (str/blank? (:client/code client)) (:code edit_client) (:client/code client)) :client/name (:name edit_client) + :client/matches (:matches edit_client) :client/email (:email edit_client) :client/locations (filter identity (:locations edit_client)) :client/location-matches (->> (:location_matches edit_client) @@ -65,7 +71,16 @@ })] result @(d/transact (d/connect uri) transactions)] (println result "ID" id) - (-> result :tempids (get id) (or id) d-clients/get-by-id ->graphql))) + (-> result :tempids (get id) (or id) d-clients/get-by-id + (update :client/location-matches + (fn [lms] + (mapcat (fn [lm] + (map (fn [m] + {:location-match/match m + :location-match/location (:location-match/location lm)}) + (:location-match/matches lm))) + lms))) + ->graphql))) (defn get-client [context args value] diff --git a/src/clj/auto_ap/parse.clj b/src/clj/auto_ap/parse.clj index e666340d..52fb658f 100644 --- a/src/clj/auto_ap/parse.clj +++ b/src/clj/auto_ap/parse.clj @@ -19,6 +19,7 @@ (defn extract-template ([text template] + (println "template" template) (if (:multi template) (mapcat #(extract-template % text (dissoc template :multi)) @@ -37,12 +38,15 @@ (first (map second (re-seq v full-text)))) str/trim ) [value-parser parser-params] (-> template :parser k)] - (assoc result k (u/parse-value value-parser parser-params value)))) + (assoc result k (try (u/parse-value value-parser parser-params value) + (catch Exception e + (println e)))))) {:vendor-code (:vendor template) :text text :full-text full-text}))]))) (defn parse [text] + (println "Parsing PDF " text) (reset! last-text text) (->> t/pdf-templates (filter (partial template-applies? text)) @@ -79,7 +83,6 @@ (let [fuzzy-match (->> clients (mapcat (fn [{:keys [:db/id :client/matches :client/name] :as client :or {matches []}}] (map (fn [m] - (println m invoice-client-name) [client (m/jaccard (.toLowerCase invoice-client-name) (.toLowerCase m))]) (conj matches name)))) (filter #(< (second %) 0.25)) @@ -111,7 +114,6 @@ (map (fn [match] [location match]) matches))) (filter (fn [[location match]] - (println "loc " location match text) (re-find (re-pattern (str "(?i)" match)) text)) ) first first) @@ -124,3 +126,10 @@ first) (:client/default-location client) (first (:client/locations client)))) + +(defn dbg-parse [v] + (doto + (map + (fn [x] (dissoc x :full-text :text)) + (parse v)) + clojure.pprint/pprint )) diff --git a/src/clj/auto_ap/parse/csv.clj b/src/clj/auto_ap/parse/csv.clj index f70b7727..318ee98d 100644 --- a/src/clj/auto_ap/parse/csv.clj +++ b/src/clj/auto_ap/parse/csv.clj @@ -20,16 +20,19 @@ (defmethod parse-csv :mama-lus [rows] - (println "MAMA LU") + (println "MAMA LU4") (transduce (comp (drop 1) (map (fn [[_ po-number despatch-number invoice-number invoice-date customer value :as row]] {:vendor-code "Mama Lu's Foods" :customer-identifier customer :invoice-number (str po-number "-" invoice-number ) - :date (u/parse-value :clj-time "MM/dd/yy HH:ss" invoice-date) - :total value - :text (str/join " " row)}))) + :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))) + :total (str/replace value #"," "") + :text (str/join " " row) + :full-text (str/join " " row)}))) conj [] rows)) diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index f9e8fd7c..07087551 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -5,7 +5,9 @@ (def pdf-templates - [{:vendor "CHFW" + [ + ;; CHEF's WAREHOUSE + {:vendor "CHFW" :keywords [#"CHEF'S WAREHOUSE"] :extract {:total #"2 WKS C\.C\.\s+([\d.,]+)" :customer-identifier #"\n([A-Z][A-Z ]+)\s{2,}" @@ -13,6 +15,7 @@ :invoice-number #"\s+[0-9]+/[0-9]+/[0-9]+\s+([0-9]+)"} :parser {:date [:clj-time "MM/dd/yyyy"]}} + ;; GGM {:vendor "Golden Gate Meat Company, Inc" :keywords [#"Golden Gate Meat"] :extract {:total #"Invoice Total\:\s+\$([\d.,]+)" @@ -22,6 +25,7 @@ :parser {:date [:clj-time "MM/dd/yyyy"] :total [:trim-commas nil]}} + ;; CINTAS {:vendor "CINTAS" :keywords [#"CINTAS CORPORATION"] :extract {:invoice-number #"INVOICE\s#\s+([\d.,]+)" @@ -31,13 +35,17 @@ :parser {:date [:clj-time "MM/dd/yy"]} :multi #"\f\f"} + ;; CARBONIC {:vendor "Carbonic Service Inc" :keywords [#"CARBONIC SERVICE INC"] :extract {:invoice-number #"Invoice #\s*\n\s*[\w\.]+\s+[\w\./]+(.*)\s*\n" :customer-identifier #"Bill To[^\n]+\n[^\n]*\n([\w ]+)\s{2,}" :date #"Invoice #\s*\n\s*[\w\.]+\s+([\w\./]+)" - :total #"Total\s+\$([0-9.]+)"} - :parser {:date [:clj-time "MM/dd/yy"]}} + :total #"Total\s+\$([0-9.,]+)"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas nil]}} + + ;; DVW {:vendor "DVW Commercial" :keywords [#"DVW Commercial"] :extract {:date #"\s*([0-9]+/[0-9]+/[0-9]+)" @@ -45,6 +53,8 @@ :invoice-number #"Invoice\s*\n\s*([\w\./]+)*" :total #"Total:\s+\$ ([0-9.]+)"} :parser {:date [:clj-time "MM/dd/yy"]}} + + ;; DAYLIGHT FOOD {:vendor "Daylight Foods" :keywords [#"DAYLIGHT FOODS"] :extract {:date #"\n\s*Date[^\n]+\n\s*([0-9]+/[0-9]+/[0-9]+)" @@ -52,36 +62,193 @@ :invoice-number #"Invoice\s([\w\./]+)*" :total #"Total Invoice\s+([0-9.]+)"} :parser {:date [:clj-time "MM/dd/yy"]}} + + ;; SOUTHBAY FRESH {:vendor "Southbay Fresh Produce" :keywords [#"SOUTH BAY FRESH PRODUCE"] :extract {:date #"^([0-9]+/[0-9]+/[0-9]+)" - :customer-identifier #"FAX:[^\n]+\n\s+([A-Za-z ]+)\s{2}" - :invoice-number #"^[0-9]+/[0-9]+/[0-9]+\s+(\d+)" - :total #"\$([0-9.]+)"} + :customer-identifier #"To:[^\n]*\n\s+([A-Za-z' ]+)\s{2}" + :invoice-number #"INV #\/(\d+)" + :total #"\$([0-9.]+)\."} :parser {:date [:clj-time "MM/dd/yyyy"]} :multi #"\n" - :multi-match? #"^[0-9]+/[0-9]+/[0-9]+\s+(\d+)"} - {:vendor "Performance Food Group" + :multi-match? #"^[0-9]+/[0-9]+/[0-9]+\s+INV "} + + ;; PFG - LEDYARD + {:vendor "Performance Food Group - LEDYARD" :keywords [#"performancefoodservice"] :extract {:date #"DELIVER TO[^\n]+\n.+?(?=[0-9]+/[0-9]+/[0-9]+)([0-9]+/[0-9]+/[0-9]+)" :customer-identifier #"DELIVER TO[^\n]+\n\s*[\S ]+?(?=\s{2,}([\S ]+?)\s{2,})" ;; ([\S ]+)\s{2,} :invoice-number #"DELIVER TO[^\n]+\n.+?(?=\d+)(\d+)\s*\n" - :total #"([0-9.]+)\s+Status Code"} - :parser {:date [:clj-time "MM/dd/yy"]}} + :total #"([0-9.\-]+)\s+Status Code"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas-and-negate nil]}} + + ;; SOUTHERN GLAZER'S + {:vendor "Southern Glazers" + :keywords [#"Southern Glazer's"] + :extract {:date #"INVOICE DATE(?s:.*)(?= (?:[0-9]+/[0-9]+/[0-9]+)\s+([0-9]+/[0-9]+/[0-9]+)) " + :customer-identifier #"SOLD TO:(?:.*)(?=\n)\n(.*)(?=\s{2,})" ;; ([\S ]+)\s{2,} + :invoice-number #"INVOICE\n(?:.*?)(?=\d{4,})(\d+)" + :total #"PAY THIS AMOUNT(?s:.*)(?= ([0-9,]+\.[0-9]{2}))"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas nil]}} + + ;; GOLDEN BRANDS + {:vendor "Golden Brands San Jose" + :keywords [#"GOLDEN BRANDS"] + :extract {:date #"0430\n(.*)" + :customer-identifier #"Account:(?:.*\n)(.*(?=\s{2,}))" + :invoice-number #"Invoice#: (\d+)" + :total #"Invoice Total\s+([0-9]+\.[0-9]{2})"} + :parser {:date [:clj-time "EEE MMM dd, yyyy HH:mm aa"] + :total [:trim-commas nil]}} + + ;; WINE WAREHOUSE + {:vendor "Wine Warehouse" + :keywords [#"WINE WAREHOUSE"] + :extract {:date #"INVOICE DATE\s+([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"SHIP-TO-PARTY.*\n(.*?)(?=\s{2,})" + :invoice-number #"INV #\s+(\d+)" + :total #"PLEASE PAY THIS AMOUNT\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"] + :extract {:date #"INVOICE DATE.*\n\n(?:.*?)([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"INVOICE\n(.*?)\s{2,}" + :invoice-number #"INVOICE NUMBER.*\n\n(?:.*?)(\d+)" + :total #"Total Amount Due(?:.*?)([0-9,]+\.[0-9]{2})"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas nil]}} + + ;; ALSCO + {:vendor "Alsco" + :keywords [#"Alsco"] + :extract {:date #"Invoice Date:\s+(.*)" + :customer-identifier #"Invoice F o r(?:.*?)\n\s+(.*?)\s{2,}" + :invoice-number #" (\S+)\n\s+Invoice Date" + :total #"Invoice Total\s+\$([0-9,]+\.[0-9]{2})"} + :parser {:date [:clj-time "MMM dd yyyy"] + :total [:trim-commas nil]}} + + ;; SUNCREST + {:vendor "Suncrest USA Inc" + :keywords [#"Suncrest.*Invoice"] + :extract {:date #"Date.*\n\s*\n(?:.*?)([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Bill To(?:.*?)\n\n(.*?)\s{2,}" + :invoice-number #"Invoice #.*\n\s*\n(?:.*?)\s{2,}(\d{5,})" + :total #"Balance Due\s+\$([0-9,]+\.[0-9]{2})"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas nil]}} + + ;; SUNCREST STATEMENT + {:vendor "Suncrest USA Inc" + :keywords [#"Suncrest.*\n.*Statement"] + :extract {:date #"^([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"To:(?:.*?)\n\s*(.*?)\s{2,}" + :invoice-number #"INV #(\d+)" + :total #"Orig\. Amount \$([0-9,]+\.[0-9]{2})"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas nil]} + :multi #"\n" + :multi-match? #"^[0-9]+/[0-9]+/[0-9]+\s+INV "} + + ;; US FOODS {:vendor "US Foods" :keywords [#"US Foods"] :extract {:date #"INVOICE NUMBER[^\n]+\n\n\d+\s+\d+\s+([0-9]+/[0-9]+/[0-9]+)" :customer-identifier #"BILL TO[^\n]+\n([\S ]+?)(?=\s{2,})" ;; ([\S ]+)\s{2,} :invoice-number #"INVOICE NUMBER[^\n]+\n\n\d+\s+(\d+)" - :total #"DELIVERED AMOUNT\s+\$([0-9.]+)"} - :parser {:date [:clj-time "MM/dd/yyyy"]}} + :total #"(?:DELIVERED AMOUNT|PLEASE REMIT).*(?=\$)\$([0-9.,]+)\s*\n"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas nil]}} + + ;; SYSCO {:vendor "Sysco" :keywords [#"SYSCO"] :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"]}}]) + :parser {:date [:clj-time "MM/dd/yyyy"]}} + + ;; LE BOULANGER + {:vendor "Le Boulanger" + :keywords [#"Le Boulanger"] + :extract {:date #"Invoice Date: ([^\n]+)\n" + :customer-identifier #"Ship to\n+\s+([\S ]+?)(?=\s{2,})" + :invoice-number #"Invoice No: ([^\n]+)\n" + :total #" Total:\s+([\d\.]+)"} + :parser {:date [:clj-time "MMM dd, yyyy"]}} + + + ;; A&B + {:vendor "A&B Produce" + :keywords [#"ABProduce"] + :extract {:date #"^\s+([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"BILL TO:[^\n]+\n[^\n]+\n[^\n]+\n(.*)\s{2,}" + :invoice-number #"(\d+)\s+(?:INV|C/M)" + :total #" (?:INV|C/M)\s+([\d\.\-]+)"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]} + :multi #"\n" + :multi-match? #"^\s+[0-9]+/[0-9]+/[0-9]+\s+\d+\s+(INV|C/M)\s+"} + + ;; CHEF's CHOICE + {:vendor "Chef's Choice Produce Co" + :keywords [#"(2170 MARTIN AVENUE|213-3886)"] + :extract {:date #"([0-9/]{10,10})" + :customer-identifier #"\n B\s+([\S ]+?)(?=\s{2,}I) " + :invoice-number #"^0*([0-9]+)" + :total #"INVOICE\s+([\d\.]+)"} + :parser {:date [:clj-time "MM/dd/yyyy"]} ;; may want to try two approaches [:clj-time ["MM/dd/yyyy" "MM1dd1yyyy"]] + :multi #"\n" + :multi-match? #"\s+INVOICE\s+"} + + ;; FRESH AND BELT + {:vendor "Fresh and Best Produce" + :keywords [#"freshbestproduce"] + :extract {:date #"\n\s+([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Bill To[^\n]+\n([A-Za-z ']+)" + :invoice-number #"\n\s+[0-9/]+\s+(\d+)" + :total #"Balance Due\s+\$([0-9\.]+)"} + :parser {:date [:clj-time "MM/dd/yyyy"]}} + + + ;; PFG - ROMA + {:vendor "Performance Food Group - ROMA" + :keywords [#"Performance Food Group, Inc\n\f"] + :extract {:date #"Date: ([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"BILL TO:\s+([\S ]+?)(?=\s{2,})" + :invoice-number #"INVOICE NO.\s+ ([\d]+)" + :total #"([\d\.,]+)\s+INVOICE TOTAL"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas nil]}} + + ;; PFG - ROMA LOOK 1 + {:vendor "Performance Food Group - ROMA" + :keywords [#"inquiries call 1-800-233-6211"] + :extract {:date #"([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"BILL TO:\s+([\S ]+?)(?=\s{2,})" + :invoice-number #"^\s+([\dA-Z]+)" + :total #"([\d\.,\-]+\.[\d\-]+)"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]} + :multi #"\n" + :multi-match? #"^\s+[\d]{6,8}\s+\d+"} + + ;; JFC + {:vendor "JFC International" + :keywords [#"48490 MILMONT DRIVE"] + :extract {:date #"([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"SOLD\s+([\S ]+?)(?=(\s{2,}|\n))" + :invoice-number #"(\S+)\s+(?=[0-9]+/[0-9]+/[0-9]+)" + :total #"(?:INVOICE|TOTAL)\s+([\d\.,\-]+\.[\d\-]+( CR)?)"} + :parser {:date [:clj-time "MM/dd/yyyy"] + :total [:trim-commas-and-negate nil]}}]) (defn offset [c x y] (.toString (CellAddress. (+ y (.getRow (.getAddress c))) (+ x (.getColumn (.getAddress c))) ))) diff --git a/src/clj/auto_ap/parse/util.clj b/src/clj/auto_ap/parse/util.clj index 380d9b7c..b07f3e2a 100644 --- a/src/clj/auto_ap/parse/util.clj +++ b/src/clj/auto_ap/parse/util.clj @@ -14,6 +14,17 @@ (str/replace value #"," "") ) +(defmethod parse-value :trim-commas-and-negate + [_ _ value] + (let [[_ raw-value] (re-find #"([\d\.]+)" + (-> value + (str/replace #"," "") + (str/replace #"-" "")))] + (if (or (str/includes? value "-") + (str/includes? value "CR")) + (str (- (Double/parseDouble raw-value))) + (str raw-value)))) + (defmethod parse-value :clj-time [_ format value] (time/from-time-zone (f/parse (f/formatter format) value) diff --git a/src/clj/auto_ap/routes/auth.clj b/src/clj/auto_ap/routes/auth.clj index 1a8e3141..627cc7d6 100644 --- a/src/clj/auto_ap/routes/auth.clj +++ b/src/clj/auto_ap/routes/auth.clj @@ -19,6 +19,7 @@ (defroutes routes (GET "/oauth" {{:strs [code]} :query-params :keys [scheme] :as r {:strs [host]} :headers} + (println "Authenticating with" r "..." code) (try (let [auth (-> "https://accounts.google.com/o/oauth2/token" (http/post @@ -44,17 +45,19 @@ ;; TODO - these namespaces are not being transmitted/deserialized properly (if (and token user) - {:status 301 - :headers {"Location" (str "/?jwt=" (jwt/sign (doto {:user (:name profile) - :exp (time/plus (time/now) (time/days 30)) - :user/clients (map (fn [c] - (dissoc c :client/bank-accounts )) - (:user/clients user)) - :user/role (name (:user/role user)) - :user/name (:name profile)} - println) - (:jwt-secret env) - {:alg :hs512}))}} + (let [jwt (jwt/sign (doto {:user (:name profile) + :exp (time/plus (time/now) (time/days 30)) + :user/clients (map (fn [c] + (dissoc c :client/bank-accounts :client/location-matches)) + (:user/clients user)) + :user/role (name (:user/role user)) + :user/name (:name profile)} + println) + (:jwt-secret env) + {:alg :hs512})] + (println "authenticated. using jwt" jwt) + {:status 301 + :headers {"Location" (str "/?jwt=" jwt)}}) {:status 401 :body "Couldn't authenticate"})) (catch Exception e diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index 7c45116b..61b3cefe 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -166,7 +166,6 @@ (defn import-uploaded-invoice [imports] (let [clients (d-clients/get-all) - _ (clojure.pprint/pprint imports) transactions (reduce (fn [result {:keys [invoice-number customer-identifier total date vendor-code text full-text] :as info}] (println "searching for" vendor-code) @@ -204,6 +203,7 @@ :else (conj result (remove-nils #:invoice {:invoice/client (:db/id matching-client) + :invoice/client-identifier customer-identifier :invoice/vendor matching-vendor :invoice/invoice-number invoice-number :invoice/total (Double/parseDouble total) @@ -219,8 +219,7 @@ )) [] imports)] - - @(d/transact (d/connect uri) transactions))) + @(d/transact (d/connect uri) (vec (set transactions))))) (defroutes routes (wrap-routes diff --git a/src/clj/auto_ap/yodlee/core.clj b/src/clj/auto_ap/yodlee/core.clj index 50ae8816..af7daa53 100644 --- a/src/clj/auto_ap/yodlee/core.clj +++ b/src/clj/auto_ap/yodlee/core.clj @@ -163,6 +163,21 @@ (recur (concat transactions transaction-batch) (+ batch-size skip)) transactions))))) +(defn count-specific-transactions [account] + (let [cob-session (login-cobrand) + user-session (login-user cob-session)] + + (-> (str (:yodlee-base-url env) "/transactions/count?accountId=" account) + (doto println) + + (client/get {:headers (doto + (merge base-headers {"Authorization" (auth-header cob-session user-session)}) + println) + :as :json}) + :body + :transaction + ))) + (defn get-access-token [] (let [cob-session (login-cobrand) user-session (login-user cob-session) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index 8c282a67..0ddc7b91 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -45,7 +45,7 @@ :graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code :email :locations [:location-matches [:location :match]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations] ] + [:id :name :code :email :matches :locations [:location-matches [:location :match]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations] ] [:address [:street1 :street2 :city :state :zip]]]] [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code]] @@ -68,7 +68,7 @@ (fn [{:keys [db]} [_ token user]] {:graphql {:token token :query-obj {:venia/queries [[:client - [:id :name :code [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations] ]]] + [:id :name :code :matches :locations [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id :locations] ]]] [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code]] [:accounts [:numeric-code :name :location :type :account_set :id]]]} diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs index 1fc89175..40af5457 100644 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ b/src/cljs/auto_ap/views/components/invoice_table.cljs @@ -47,7 +47,7 @@ {:venia/queries [[:invoice_page (assoc params :client-id (:id @(re-frame/subscribe [::subs/client]))) - [[:invoices [:id :total :outstanding-balance :invoice-number :date :status + [[:invoices [:id :total :outstanding-balance :invoice-number :date :status :client-identifier [:vendor [:name :id]] [:expense_accounts [:amount :id :location [:account [:id :name :numeric-code :location ]]]] @@ -58,7 +58,7 @@ :start :end]]]}) -(defn invoice-table [{:keys [id invoice-page status on-params-change vendors params check-boxes checked on-check-changed on-edit-invoice on-void-invoice on-unvoid-invoice expense-event]}] +(defn invoice-table [{:keys [id invoice-page status on-params-change vendors params check-boxes checked on-check-changed on-edit-invoice on-void-invoice on-unvoid-invoice expense-event overrides]}] (let [visible-checks @(re-frame/subscribe [::visible-checks]) visible-expense-accounts @(re-frame/subscribe [::visible-expense-accounts]) selected-client @(re-frame/subscribe [::subs/client]) @@ -156,7 +156,9 @@ (on-check-changed id i)))} ]]) (when-not selected-client - [:td (:name client)]) + [:td (if-let [client-override (:client overrides)] + (client-override i) + (:name client))]) [:td (:name vendor)] [:td invoice-number] [:td (date->str date) ] diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index 4288d607..78bafbf2 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -31,6 +31,7 @@ ::edit-client-clicked (fn [{:keys [db]} [_ client-id]] {:db (-> db + (forms/stop-form ::new-client) (forms/start-form ::new-client (get (:clients db) client-id)))})) (re-frame/reg-sub @@ -42,6 +43,7 @@ :code (:code new-client-data) ;; TODO add validation can't change :email (:email new-client-data) :locations (:locations new-client-data) + :matches (vec (:matches new-client-data)) :location-matches (:location-matches new-client-data) :address {:street1 (:street1 (:address new-client-data)) :street2 (:street2 (:address new-client-data)), @@ -87,7 +89,7 @@ :operation/name "EditClient"} :venia/queries [{:query/data [:edit-client {:edit-client new-client-req} - [:id :name :code :email :locations [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} + [:id :name :code :email :locations :matches [:location-matches [:location :match]] [:address [:street1 :street2 :city :state :zip]] [:bank-accounts [:id :number :check-number :name :code :bank-code :bank-name :routing :type :visible :yodlee-account-id :sort-order :locations]]]]}]} :on-success [::save-complete] :on-error [::forms/save-error ::new-client]}} {:db new-client-form})))) @@ -118,6 +120,23 @@ (update-in [:bank-accounts which-account :locations] #(conj (or % #{}) (get-in client [:bank-accounts which-account :location-select]))) (update-in [:bank-accounts which-account] dissoc :location-select)))) +(re-frame/reg-event-db + ::add-new-match + [(forms/in-form ::new-client) (re-frame/path [:data])] + (fn [client _] + (-> client + (update :matches conj (:match client)) + (update :matches set) + (dissoc :match)))) + +(re-frame/reg-event-db + ::remove-match + [(forms/in-form ::new-client) (re-frame/path [:data])] + (fn [client [_ which]] + (-> client + (update :matches set) + (update :matches disj which)))) + (re-frame/reg-event-db ::add-new-location-match [(forms/in-form ::new-client) (re-frame/path [:data])] @@ -126,6 +145,18 @@ (update :location-matches conj (:location-match client)) (dissoc :location-match)))) +(re-frame/reg-event-db + ::remove-location-match + [(forms/in-form ::new-client) (re-frame/path [:data])] + (fn [client [_ i]] + (-> client + (update :location-matches (fn [lm] + (->> lm + (map vector (range)) + (filter (fn [[index item]] + (not= index i))) + (map second))))))) + (re-frame/reg-event-db ::add-new-bank-account [(forms/in-form ::new-client) (re-frame/path [:data])] @@ -433,6 +464,22 @@ :spec ::entity/email :event change-event :subscription new-client}]]]] + [:div.field + [:p.help "Matches"] + [:div.control + [:div.field.has-addons + [:p.control + [bind-field + [:input.input {:type "text" + :field :match + :event change-event + :subscription new-client}]]] + [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-match])} "Add"]]]] + [:ul + (for [match (:matches new-client)] + ^{:key match} [:li match [:a {:on-click (dispatch-event [::remove-match match])} [:span.icon [:span.fa.fa-times]]]])]] + + [:div.field [:p.help "Locations"] @@ -470,9 +517,11 @@ :event change-event :subscription new-client}]]] [:p.control [:button.button.is-primary {:on-click (dispatch-event [::add-new-location-match])} "Add"]]] + [:ul - (for [{:keys [location match]} (:location-matches new-client)] - ^{:key location} [:li match "->" location ])]]] + (for [[index {:keys [location match]}] (map vector (range) (:location-matches new-client))] + ^{:key index} [:li match "->" location [:a {:on-click (dispatch-event [::remove-location-match index])} [:span.icon + [:span.fa.fa-times]]]])]]] [:div {:style {:padding-bottom "0.75em" :padding-top "0.75em"}} [:h2.subtitle "Address"] diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs index 0c889f3c..31db5e41 100644 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/import_invoices.cljs @@ -171,6 +171,11 @@ (if (seq (:invoices @invoice-page)) [invoice-table {:id :approved :invoice-page invoice-page + :overrides {:client (fn [row] + [:p (:name (:client row)) + [:p [:i.is-size-7 (:client-identifier row)]]] + + )} :check-boxes true :checked (:checked @invoice-page) :on-check-changed (fn [which invoice]