diff --git a/src/clj/auto_ap/routes/invoices.clj b/src/clj/auto_ap/routes/invoices.clj index 7a1ddfad..e5811c4b 100644 --- a/src/clj/auto_ap/routes/invoices.clj +++ b/src/clj/auto_ap/routes/invoices.clj @@ -93,6 +93,60 @@ (update x :errors conj {:info (.getMessage e) :details (str e)}))))) +(defn parse-invoice-rows [excel-rows] + (let [columns [:raw-date :vendor-name :check :location :invoice-number :amount :company :bill-entered :bill-rejected :added-on :exported-on :default-expense-account] + all-vendors (by :vendor/name (d-vendors/get-graphql {})) + all-clients (d-clients/get-all) + all-clients (merge (by :client/code all-clients) (by :client/name all-clients)) + rows (->> (str/split excel-rows #"\n" ) + (map #(str/split % #"\t")) + (map #(into {} (map (fn [c k] [k c] ) % columns))) + (map reset-id) + (map assoc-client-code) + (map (parse-or-error :client-id #(parse-client % all-clients))) + (map (parse-or-error :vendor #(parse-vendor % all-vendors))) + (map (parse-or-error :vendor-id #(parse-vendor-id %))) + (map (parse-or-error :default-expense-account parse-default-expense-account)) + (map (parse-or-error :invoice-number parse-invoice-number)) + (map (parse-or-error :total parse-amount)) + (map (parse-or-error :date parse-date)))] + rows)) + +(defn invoice-rows->transaction [rows] + (->> rows + (mapcat (fn [{:keys [vendor-id total client-id amount date invoice-number default-location default-expense-account check vendor]}] + (let [invoice #:invoice {:db/id (.toString (java.util.UUID/randomUUID)) + :vendor vendor-id + :client client-id + :default-location default-location + :default-expense-account default-expense-account + :total total + :outstanding-balance (if (= "Cash" check) + 0.0 + total) + :status (if (= "Cash" check) + :invoice-status/paid + :invoice-status/unpaid) + :invoice-number invoice-number + :date (to-date date) + :expense-accounts [#:invoice-expense-account {:expense-account-id (or default-expense-account (:vendor/default-expense-account vendor)) + :location default-location + :amount total}]} + payment (if (= :invoice-status/paid (:invoice/status invoice)) + #:invoice-payment {:invoice (:db/id invoice) + :amount (:invoice/total invoice) + :payment (remove-nils #:payment {:db/id (.toString (java.util.UUID/randomUUID)) + :vendor (:invoice/vendor invoice) + :client (:invoice/client invoice) + :type :payment-type/cash + :amount (:invoice/total invoice) + :status :payment-status/cleared + :date (:invoice/date invoice)})} + )] + [invoice payment]))) + (filter identity) + (map remove-nils))) + (defroutes routes (wrap-routes (context "/" [] @@ -134,78 +188,36 @@ (POST "/upload-integreat" {{:keys [excel-rows]} :edn-params user :identity} (assert-admin user) - (let [columns [:raw-date :vendor-name :check :location :invoice-number :amount :company :bill-entered :bill-rejected :added-on :exported-on :default-expense-account] - all-vendors (by :vendor/name (d-vendors/get-graphql {})) - all-clients (d-clients/get-all) - all-clients (merge (by :client/code all-clients) (by :client/name all-clients)) - rows (->> (str/split excel-rows #"\n" ) - (map #(str/split % #"\t")) - (map #(into {} (map (fn [c k] [k c] ) % columns))) - (map reset-id) - (map assoc-client-code) - (map (parse-or-error :client-id #(parse-client % all-clients))) - (map (parse-or-error :vendor #(parse-vendor % all-vendors))) - (map (parse-or-error :vendor-id #(parse-vendor-id %))) - (map (parse-or-error :default-expense-account parse-default-expense-account)) - (map (parse-or-error :invoice-number parse-invoice-number)) - (map (parse-or-error :total parse-amount)) - (map (parse-or-error :date parse-date))) - error-rows (filter :errors rows) - vendors-not-found (->> rows + (let [parsed-invoice-rows (parse-invoice-rows excel-rows) + existing-rows (->> (d-invoices/raw-graphql {}) + (filter (fn [i] (not= :invoice-status/voided (:invoice/status i)))) + (map (fn [{:keys [:invoice/vendor :invoice/client :invoice/invoice-number]}] + [(:db/id vendor) (:db/id client) invoice-number])) + set) + grouped-rows (group-by + (fn [i] + (cond (seq (:errors i)) + :error + + (existing-rows [(:vendor-id i) (:client-id i) (:invoice-number i)]) + :exists + + :else + :new)) + parsed-invoice-rows) + + + vendors-not-found (->> parsed-invoice-rows (filter #(and (nil? (:vendor-id %)) (not= "Cash" (:check %)))) (map :vendor-name) set) - get-dupe-keys (fn [i] - (select-keys i [:invoice/vendor :invoice/client :invoice/invoice-number])) - existing-rows (->> (d-invoices/raw-graphql {}) - (filter (fn [i] (not= :invoice-status/voided (:invoice/status i)))) - (map (fn [i] (-> i - (update :invoice/client :db/id) - (update :invoice/vendor :db/id)))) - (map get-dupe-keys) - set) - total-rows (->> (filter #(not (seq (:errors %))) rows) - (map (fn [{:keys [vendor-id total client-id amount date invoice-number default-location default-expense-account check vendor]}] - #:invoice {:db/id (.toString (java.util.UUID/randomUUID)) - :vendor vendor-id - :client client-id - :default-location default-location - :default-expense-account default-expense-account - :total total - :outstanding-balance (if (= "Cash" check) - 0.0 - total) - :status (if (= "Cash" check) - :invoice-status/paid - :invoice-status/unpaid) - :invoice-number invoice-number - :date (to-date date) - :expense-accounts [#:invoice-expense-account {:expense-account-id (or default-expense-account (:vendor/default-expense-account vendor)) - :location default-location - :amount total}]}))) - insert-rows (vec (->> total-rows - (filter #(not (existing-rows (get-dupe-keys %) ))) - (mapcat (fn [i] - (if (= :invoice-status/paid (:invoice/status i)) - [i #:invoice-payment {:invoice (:db/id i) - :amount (:invoice/total i) - :payment (remove-nils #:payment {:db/id (.toString (java.util.UUID/randomUUID)) - :vendor (:invoice/vendor i) - :client (:invoice/client i) - :type :payment-type/cash - :amount (:invoice/total i) - :status :payment-status/cleared - :date (:invoice/date i)})}] - [i]))) - (map remove-nils))) - - inserted-rows @(d/transact (d/connect uri) insert-rows) - already-imported-count (- (count total-rows) (count insert-rows))] + + inserted-rows @(d/transact (d/connect uri) (invoice-rows->transaction (:new grouped-rows)))] {:status 200 - :body (pr-str {:imported (count total-rows) - :already-imported already-imported-count + :body (pr-str {:imported (count (:new grouped-rows)) + :already-imported (count (:exists grouped-rows)) :vendors-not-found vendors-not-found - :errors (map #(dissoc % :date) error-rows)}) + :errors (:error grouped-rows)}) :headers {"Content-Type" "application/edn"}})))) wrap-secure))