excel import moved over
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
(ns auto-ap.routes.invoices
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [audit-transact conn remove-nils]]
|
||||
[auto-ap.datomic.accounts :as a]
|
||||
[auto-ap.datomic :refer [audit-transact conn]]
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
[auto-ap.datomic.invoices :as d-invoices]
|
||||
[auto-ap.datomic.vendors :as d-vendors]
|
||||
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
|
||||
[auto-ap.import.manual :as manual]
|
||||
[auto-ap.import.manual.common :as c]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.parse :as parse]
|
||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||
[auto-ap.utils :refer [by]]
|
||||
@@ -18,98 +17,11 @@
|
||||
[clojure.string :as str]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[digest]
|
||||
[iol-ion.tx :refer [random-tempid]]
|
||||
[ring.middleware.json :refer [wrap-json-response]]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[auto-ap.logging :as alog])
|
||||
[ring.middleware.json :refer [wrap-json-response]])
|
||||
(:import
|
||||
(java.util UUID)))
|
||||
|
||||
(defn reset-id [i]
|
||||
(update i :invoice-number
|
||||
(fn [n] (if (re-matches #"#+" n)
|
||||
nil
|
||||
n))))
|
||||
|
||||
(defn assoc-client-code [i]
|
||||
(let [[client-code default-location] (str/split (:location i) #"-")]
|
||||
(cond-> i
|
||||
client-code (assoc :client-code client-code)
|
||||
default-location (assoc :default-location default-location)
|
||||
(not client-code) (update :errors conj {:info "No client code found"})
|
||||
(not default-location) (update :errors conj {:info "No default location found"}))))
|
||||
|
||||
(defn parse-client [{:keys [client-code client default-location]} clients]
|
||||
(if-let [id (:db/id (or (clients client-code)
|
||||
(clients client)))]
|
||||
(do
|
||||
(when (not ((set (:client/locations (or (clients client-code)
|
||||
(clients client))))
|
||||
default-location))
|
||||
(throw (Exception. (str "Location '" default-location "' not found for client '" client-code "'."))))
|
||||
id)
|
||||
(throw (Exception. (str "Client code '" client-code "' and client named '" client "' not found.")))))
|
||||
|
||||
(defn parse-invoice-number [{:keys [invoice-number]}]
|
||||
(or invoice-number ""))
|
||||
|
||||
(defn parse-vendor [{:keys [vendor-name check]} vendor-name->vendor]
|
||||
(let [v (vendor-name->vendor vendor-name)]
|
||||
(cond v
|
||||
v
|
||||
|
||||
(= "Cash" check)
|
||||
nil
|
||||
|
||||
:else
|
||||
(throw (Exception. (str "Vendor '" vendor-name "' not found."))))))
|
||||
|
||||
(defn parse-automatically-paid-when-due [{:keys [vendor client-id]}]
|
||||
(boolean ((set (map :db/id (:vendor/automatically-paid-when-due vendor))) client-id)))
|
||||
|
||||
(defn parse-schedule-payment-dom [{:keys [vendor client-id]}]
|
||||
(get (by (comp :db/id :vendor-schedule-payment-dom/client) :vendor-schedule-payment-dom/dom (:vendor/schedule-payment-dom vendor))
|
||||
client-id))
|
||||
|
||||
(defn parse-invoice-rows [excel-rows]
|
||||
(let [columns [:raw-date :vendor-name :check :location :invoice-number :amount :client-name :bill-entered :bill-rejected :added-on :exported-on :account-numeric-code]
|
||||
tabulated (->> (str/split excel-rows #"\n")
|
||||
(map #(str/split % #"\t"))
|
||||
(map #(into {} (map (fn [c k] [k c]) % columns))))
|
||||
vendor-name->vendor (->>
|
||||
(set (map :vendor-name tabulated))
|
||||
(dc/q '[:find ?n ?v
|
||||
:in $ [?n ...]
|
||||
:where [?v :vendor/name ?n]]
|
||||
(dc/db conn)
|
||||
)
|
||||
(into {}))
|
||||
all-clients (merge (into {}(dc/q '[:find ?n (pull ?v [:db/id :client/locations])
|
||||
:in $
|
||||
:where [?v :client/name ?n]]
|
||||
(dc/db conn)))
|
||||
(into {}
|
||||
(dc/q '[:find ?n (pull ?v [:db/id :client/locations])
|
||||
:in $
|
||||
:where [?v :client/code ?n]]
|
||||
(dc/db conn))))
|
||||
rows (->> tabulated
|
||||
(map reset-id)
|
||||
(map assoc-client-code)
|
||||
(map (c/parse-or-error :client-id #(parse-client % all-clients)))
|
||||
(map (c/parse-or-error :vendor-id #(parse-vendor % vendor-name->vendor)))
|
||||
(map (c/parse-or-error :automatically-paid-when-due #(parse-automatically-paid-when-due %)))
|
||||
(map (c/parse-or-error :schedule-payment-dom #(parse-schedule-payment-dom %)))
|
||||
(map (c/parse-or-error :account-id c/parse-account-numeric-code))
|
||||
(map (c/parse-or-error :invoice-number parse-invoice-number))
|
||||
(map (c/parse-or-error :total c/parse-amount))
|
||||
(map (c/parse-or-error :date c/parse-date)))]
|
||||
(alog/info ::parsed
|
||||
:rows (take 10 rows))
|
||||
|
||||
rows))
|
||||
|
||||
(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.")
|
||||
@@ -179,69 +91,6 @@
|
||||
invoice)
|
||||
|
||||
|
||||
(defn invoice-rows->transaction [rows user]
|
||||
(->> rows
|
||||
(mapcat (fn [{:keys [vendor-id total client-id date invoice-number default-location check automatically-paid-when-due account-id]}]
|
||||
(let [payment-id (.toString (java.util.UUID/randomUUID))
|
||||
transaction-id (.toString (java.util.UUID/randomUUID))
|
||||
invoice #:invoice {:db/id (.toString (java.util.UUID/randomUUID))
|
||||
:vendor vendor-id
|
||||
:client client-id
|
||||
:default-location default-location
|
||||
:location default-location
|
||||
:import-status :import-status/imported
|
||||
:automatically-paid-when-due automatically-paid-when-due
|
||||
: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)}
|
||||
payment (when (= :invoice-status/paid (:invoice/status invoice))
|
||||
#:invoice-payment {:invoice (:db/id invoice)
|
||||
:amount (:invoice/total invoice)
|
||||
:payment (remove-nils #:payment {:db/id payment-id
|
||||
:vendor (:invoice/vendor invoice)
|
||||
:client (:invoice/client invoice)
|
||||
:type :payment-type/cash
|
||||
:amount (:invoice/total invoice)
|
||||
:status :payment-status/cleared
|
||||
:date (:invoice/date invoice)})})
|
||||
transaction (when (= :invoice-status/paid (:invoice/status invoice))
|
||||
(let [[[bank-account]] (seq (dc/q '[:find ?ba
|
||||
:in $ ?c
|
||||
:where [?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/type :bank-account-type/cash]
|
||||
]
|
||||
(dc/db conn)
|
||||
client-id))]
|
||||
[:upsert-transaction #:transaction {:amount (- (:invoice/total invoice))
|
||||
:payment payment-id
|
||||
:client (:invoice/client invoice)
|
||||
:status "POSTED"
|
||||
:bank-account bank-account
|
||||
:db/id #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
|
||||
:id #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
|
||||
:raw-id transaction-id
|
||||
:vendor (:invoice/vendor invoice)
|
||||
:description-original "Cash payment"
|
||||
:date (to-date date)
|
||||
:approval-status :transaction-approval-status/approved
|
||||
:accounts [{:db/id (str #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id) "-account")
|
||||
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:transaction-account/location "A"
|
||||
:transaction-account/amount (Math/abs (:invoice/total invoice))}]}]))
|
||||
]
|
||||
[[:propose-invoice (d-invoices/code-invoice (validate-invoice (remove-nils invoice)
|
||||
user)
|
||||
account-id)]
|
||||
(some-> payment remove-nils)
|
||||
transaction])))
|
||||
(filter identity)))
|
||||
|
||||
(defn admin-only-if-multiple-clients [is]
|
||||
(let [client-count (->> is
|
||||
(map :invoice/client)
|
||||
@@ -443,36 +292,6 @@
|
||||
:data (ex-data e)})
|
||||
:headers {"Content-Type" "application/edn"}})))))
|
||||
|
||||
(defn bulk-upload-invoices [{{:keys [excel-rows]} :edn-params user :identity}]
|
||||
(assert-admin user)
|
||||
(let [parsed-invoice-rows (parse-invoice-rows excel-rows)
|
||||
existing-rows (set (d-invoices/get-existing-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)
|
||||
_ (audit-transact (invoice-rows->transaction (:new grouped-rows)
|
||||
user)
|
||||
user)]
|
||||
{:status 200
|
||||
: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 grouped-rows))})
|
||||
:headers {"Content-Type" "application/edn"}}))
|
||||
|
||||
(defn cleared-against [{{files :file
|
||||
files-2 "file"} :params
|
||||
user :identity}]
|
||||
|
||||
Reference in New Issue
Block a user