Multiple clients=no show for non-admins
This commit is contained in:
@@ -483,7 +483,11 @@
|
|||||||
:auto-ap/add-invoice-similarity {:txes [[{:db/ident :invoice/similarity
|
:auto-ap/add-invoice-similarity {:txes [[{:db/ident :invoice/similarity
|
||||||
:db/doc "How close an invoice matches its import"
|
:db/doc "How close an invoice matches its import"
|
||||||
:db/valueType :db.type/double
|
:db/valueType :db.type/double
|
||||||
:db/cardinality :db.cardinality/one}]]}}
|
:db/cardinality :db.cardinality/one}]]}
|
||||||
|
:auto-ap/add-source-url-admin-only {:txes [[{:db/ident :invoice/source-url-admin-only
|
||||||
|
:db/doc "Can only admins see this invoice?"
|
||||||
|
:db/valueType :db.type/boolean
|
||||||
|
:db/cardinality :db.cardinality/one}]]}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
[auto-ap.datomic.vendors :as d-vendors]
|
[auto-ap.datomic.vendors :as d-vendors]
|
||||||
[auto-ap.graphql.checks :as gq-checks]
|
[auto-ap.graphql.checks :as gq-checks]
|
||||||
[auto-ap.graphql.utils
|
[auto-ap.graphql.utils
|
||||||
:refer [->graphql
|
:refer [<-graphql
|
||||||
<-graphql
|
|
||||||
assert-admin
|
assert-admin
|
||||||
assert-can-see-client
|
assert-can-see-client
|
||||||
assert-power-user
|
assert-power-user
|
||||||
enum->keyword]]
|
enum->keyword]
|
||||||
|
:as u]
|
||||||
[auto-ap.utils :refer [dollars=]]
|
[auto-ap.utils :refer [dollars=]]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clj-time.core :as time]
|
[clj-time.core :as time]
|
||||||
@@ -20,6 +20,13 @@
|
|||||||
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
|
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
|
||||||
[datomic.api :as d]))
|
[datomic.api :as d]))
|
||||||
|
|
||||||
|
(defn ->graphql [invoice user ]
|
||||||
|
(if (= "admin" (:user/role user))
|
||||||
|
(u/->graphql invoice)
|
||||||
|
(u/->graphql (if (:invoice/source-url-admin-only invoice)
|
||||||
|
(dissoc invoice :invoice/source-url)
|
||||||
|
invoice))))
|
||||||
|
|
||||||
(defn get-invoice-page [context args _]
|
(defn get-invoice-page [context args _]
|
||||||
|
|
||||||
(let [args (assoc args :id (:id context))
|
(let [args (assoc args :id (:id context))
|
||||||
@@ -28,7 +35,7 @@
|
|||||||
(<-graphql)
|
(<-graphql)
|
||||||
(update :status enum->keyword "invoice-status")
|
(update :status enum->keyword "invoice-status")
|
||||||
(d-invoices/get-graphql))]
|
(d-invoices/get-graphql))]
|
||||||
[{:invoices (map ->graphql invoices)
|
[{:invoices (mapv #(->graphql % (:id context)) invoices)
|
||||||
:outstanding outstanding
|
:outstanding outstanding
|
||||||
:total invoice-count
|
:total invoice-count
|
||||||
:count (count invoices)
|
:count (count invoices)
|
||||||
@@ -38,7 +45,7 @@
|
|||||||
(defn get-all-invoices [context args _]
|
(defn get-all-invoices [context args _]
|
||||||
(assert-admin (:id context))
|
(assert-admin (:id context))
|
||||||
(map
|
(map
|
||||||
->graphql
|
u/->graphql
|
||||||
(first (d-invoices/get-graphql (assoc (<-graphql args)
|
(first (d-invoices/get-graphql (assoc (<-graphql args)
|
||||||
:count Integer/MAX_VALUE)))))
|
:count Integer/MAX_VALUE)))))
|
||||||
|
|
||||||
@@ -147,7 +154,7 @@
|
|||||||
|
|
||||||
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
||||||
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
|
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
|
||||||
(->graphql))))
|
(->graphql (:id context)))))
|
||||||
|
|
||||||
(defn assert-bank-account-belongs [client-id bank-account-id]
|
(defn assert-bank-account-belongs [client-id bank-account-id]
|
||||||
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
|
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
|
||||||
@@ -166,7 +173,7 @@
|
|||||||
bank-account-id
|
bank-account-id
|
||||||
type
|
type
|
||||||
(:id context))
|
(:id context))
|
||||||
->graphql)))
|
u/->graphql)))
|
||||||
|
|
||||||
(defn edit-invoice [context {{:keys [id due invoice_number total date expense_accounts scheduled_payment] :as in} :invoice} _]
|
(defn edit-invoice [context {{:keys [id due invoice_number total date expense_accounts scheduled_payment] :as in} :invoice} _]
|
||||||
(let [invoice (d-invoices/get-by-id id)
|
(let [invoice (d-invoices/get-by-id id)
|
||||||
@@ -196,7 +203,7 @@
|
|||||||
(map (fn [d] [:db/retract id :invoice/expense-accounts d]) deleted))
|
(map (fn [d] [:db/retract id :invoice/expense-accounts d]) deleted))
|
||||||
(:id context))
|
(:id context))
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
(->graphql))))
|
(->graphql (:id context)))))
|
||||||
|
|
||||||
(defn void-invoice [context {id :invoice_id} _]
|
(defn void-invoice [context {id :invoice_id} _]
|
||||||
(let [invoice (d-invoices/get-by-id id)
|
(let [invoice (d-invoices/get-by-id id)
|
||||||
@@ -210,7 +217,7 @@
|
|||||||
(:invoice/expense-accounts invoice))}]
|
(:invoice/expense-accounts invoice))}]
|
||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id) (->graphql))))
|
(-> (d-invoices/get-by-id id) (->graphql (:id context)))))
|
||||||
|
|
||||||
(defn unvoid-invoice [context {id :invoice_id} _]
|
(defn unvoid-invoice [context {id :invoice_id} _]
|
||||||
(let [invoice (d-invoices/get-by-id id)
|
(let [invoice (d-invoices/get-by-id id)
|
||||||
@@ -239,7 +246,7 @@
|
|||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
(->graphql))))
|
(->graphql (:id context)))))
|
||||||
|
|
||||||
(defn unautopay-invoice [context {id :invoice_id} _]
|
(defn unautopay-invoice [context {id :invoice_id} _]
|
||||||
(let [invoice (d/entity (d/db conn) id)
|
(let [invoice (d/entity (d/db conn) id)
|
||||||
@@ -251,7 +258,7 @@
|
|||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
(->graphql))))
|
(->graphql (:id context)))))
|
||||||
|
|
||||||
(defn edit-expense-accounts [context args _]
|
(defn edit-expense-accounts [context args _]
|
||||||
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
|
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
|
||||||
@@ -268,27 +275,28 @@
|
|||||||
(map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d]) deleted))
|
(map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d]) deleted))
|
||||||
(:id context))
|
(:id context))
|
||||||
(->graphql
|
(->graphql
|
||||||
(d-invoices/get-by-id (:invoice_id args)))))
|
(d-invoices/get-by-id (:invoice_id args))
|
||||||
|
(:id context))))
|
||||||
|
|
||||||
(def objects
|
(def objects
|
||||||
{:invoice
|
{:invoice
|
||||||
{:fields {:id {:type :id}
|
{:fields {:id {:type :id}
|
||||||
:original_id {:type 'Int}
|
:original_id {:type 'Int}
|
||||||
:client_identifier {:type 'String}
|
:client_identifier {:type 'String}
|
||||||
:total {:type 'String}
|
:total {:type 'String}
|
||||||
:source_url {:type 'String}
|
:source_url {:type 'String}
|
||||||
:outstanding_balance {:type 'String}
|
:outstanding_balance {:type 'String}
|
||||||
:invoice_number {:type 'String}
|
:invoice_number {:type 'String}
|
||||||
:status {:type 'String}
|
:status {:type 'String}
|
||||||
:expense_accounts {:type '(list :invoices_expense_accounts)}
|
:expense_accounts {:type '(list :invoices_expense_accounts)}
|
||||||
:similarity {:type 'Float}
|
:similarity {:type 'Float}
|
||||||
:date {:type :iso_date}
|
:date {:type :iso_date}
|
||||||
:due {:type :iso_date}
|
:due {:type :iso_date}
|
||||||
:client_id {:type 'Int}
|
:client_id {:type 'Int}
|
||||||
:payments {:type '(list :invoice_payment)}
|
:payments {:type '(list :invoice_payment)}
|
||||||
:vendor {:type :vendor}
|
:vendor {:type :vendor}
|
||||||
:client {:type :client}
|
:client {:type :client}
|
||||||
:scheduled_payment {:type :iso_date}}}
|
:scheduled_payment {:type :iso_date}}}
|
||||||
|
|
||||||
:invoices_expense_accounts
|
:invoices_expense_accounts
|
||||||
{:fields {:id {:type :id}
|
{:fields {:id {:type :id}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
(defn reset-id [i]
|
(defn reset-id [i]
|
||||||
(update i :invoice-number
|
(update i :invoice-number
|
||||||
(fn [n] (if (re-matches #"#+" n)
|
(fn [n] (if (re-matches #"#+" n)
|
||||||
nil
|
nil
|
||||||
n))))
|
n))))
|
||||||
|
|
||||||
(defn assoc-client-code [i]
|
(defn assoc-client-code [i]
|
||||||
(let [[client-code default-location] (str/split (:location i) #"-" )]
|
(let [[client-code default-location] (str/split (:location i) #"-")]
|
||||||
(cond-> i
|
(cond-> i
|
||||||
client-code (assoc :client-code client-code)
|
client-code (assoc :client-code client-code)
|
||||||
default-location (assoc :default-location default-location)
|
default-location (assoc :default-location default-location)
|
||||||
@@ -38,13 +38,12 @@
|
|||||||
|
|
||||||
(defn parse-client [{:keys [client-code client default-location]} clients]
|
(defn parse-client [{:keys [client-code client default-location]} clients]
|
||||||
(if-let [id (:db/id (or (clients client-code)
|
(if-let [id (:db/id (or (clients client-code)
|
||||||
(clients client)))]
|
(clients client)))]
|
||||||
(do
|
(do
|
||||||
(when (not ((set (:client/locations (or (clients client-code)
|
(when (not ((set (:client/locations (or (clients client-code)
|
||||||
(clients client))))
|
(clients client))))
|
||||||
default-location))
|
default-location))
|
||||||
(throw (Exception. (str "Location '" default-location "' not found for client '" client-code "'.")))
|
(throw (Exception. (str "Location '" default-location "' not found for client '" client-code "'."))))
|
||||||
)
|
|
||||||
id)
|
id)
|
||||||
(throw (Exception. (str "Client code '" client-code "' and client named '" client "' not found.")))))
|
(throw (Exception. (str "Client code '" client-code "' and client named '" client "' not found.")))))
|
||||||
|
|
||||||
@@ -77,9 +76,9 @@
|
|||||||
all-vendors (by :vendor/name (d-vendors/get-graphql {}))
|
all-vendors (by :vendor/name (d-vendors/get-graphql {}))
|
||||||
all-clients (d-clients/get-all)
|
all-clients (d-clients/get-all)
|
||||||
all-clients (merge (by :client/code all-clients) (by :client/name all-clients))
|
all-clients (merge (by :client/code all-clients) (by :client/name all-clients))
|
||||||
rows (->> (str/split excel-rows #"\n" )
|
rows (->> (str/split excel-rows #"\n")
|
||||||
(map #(str/split % #"\t"))
|
(map #(str/split % #"\t"))
|
||||||
(map #(into {} (map (fn [c k] [k c] ) % columns)))
|
(map #(into {} (map (fn [c k] [k c]) % columns)))
|
||||||
(map reset-id)
|
(map reset-id)
|
||||||
(map assoc-client-code)
|
(map assoc-client-code)
|
||||||
(map (c/parse-or-error :client-id #(parse-client % all-clients)))
|
(map (c/parse-or-error :client-id #(parse-client % all-clients)))
|
||||||
@@ -92,12 +91,8 @@
|
|||||||
(map (c/parse-or-error :total c/parse-amount))
|
(map (c/parse-or-error :total c/parse-amount))
|
||||||
(map (c/parse-or-error :date c/parse-date)))]
|
(map (c/parse-or-error :date c/parse-date)))]
|
||||||
|
|
||||||
|
|
||||||
rows))
|
rows))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn match-vendor [vendor-code forced-vendor]
|
(defn match-vendor [vendor-code forced-vendor]
|
||||||
(when (and (not forced-vendor) (str/blank? vendor-code))
|
(when (and (not forced-vendor) (str/blank? vendor-code))
|
||||||
(throw (ex-info (str "No vendor found. Please supply an forced vendor.")
|
(throw (ex-info (str "No vendor found. Please supply an forced vendor.")
|
||||||
@@ -124,7 +119,6 @@
|
|||||||
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
|
||||||
{:vendor-code vendor-code})))))
|
{:vendor-code vendor-code})))))
|
||||||
|
|
||||||
|
|
||||||
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-override location-override]} clients]
|
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-override location-override]} clients]
|
||||||
(let [[matching-client similarity] (cond
|
(let [[matching-client similarity] (cond
|
||||||
account-number (parse/best-match clients account-number 0.0)
|
account-number (parse/best-match clients account-number 0.0)
|
||||||
@@ -150,11 +144,6 @@
|
|||||||
:invoice/outstanding-balance (Double/parseDouble total)
|
:invoice/outstanding-balance (Double/parseDouble total)
|
||||||
:invoice/status :invoice-status/unpaid})))
|
:invoice/status :invoice-status/unpaid})))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn validate-invoice [invoice user]
|
(defn validate-invoice [invoice user]
|
||||||
(when-not (:invoice/client invoice)
|
(when-not (:invoice/client invoice)
|
||||||
(throw (ex-info (str "Searched clients for '" (:invoice/client-identifier invoice) "'. No client found in file. Select a client first.")
|
(throw (ex-info (str "Searched clients for '" (:invoice/client-identifier invoice) "'. No client found in file. Select a client first.")
|
||||||
@@ -171,16 +160,16 @@
|
|||||||
(defn extant-invoice? [{:invoice/keys [invoice-number vendor client]}]
|
(defn extant-invoice? [{:invoice/keys [invoice-number vendor client]}]
|
||||||
(try
|
(try
|
||||||
(->> (d/query
|
(->> (d/query
|
||||||
(cond-> {:query {:find ['?e '?outstanding-balance '?status '?import-status2]
|
(cond-> {:query {:find ['?e '?outstanding-balance '?status '?import-status2]
|
||||||
:in ['$ '?invoice-number '?vendor '?client]
|
:in ['$ '?invoice-number '?vendor '?client]
|
||||||
:where '[[?e :invoice/invoice-number ?invoice-number]
|
:where '[[?e :invoice/invoice-number ?invoice-number]
|
||||||
[?e :invoice/vendor ?vendor]
|
[?e :invoice/vendor ?vendor]
|
||||||
[?e :invoice/client ?client]
|
[?e :invoice/client ?client]
|
||||||
[?e :invoice/outstanding-balance ?outstanding-balance]
|
[?e :invoice/outstanding-balance ?outstanding-balance]
|
||||||
[?e :invoice/status ?status]
|
[?e :invoice/status ?status]
|
||||||
[?e :invoice/import-status ?import-status]
|
[?e :invoice/import-status ?import-status]
|
||||||
[?import-status :db/ident ?import-status2]]}
|
[?import-status :db/ident ?import-status2]]}
|
||||||
:args [(d/db (d/connect uri)) invoice-number vendor client]}))
|
:args [(d/db (d/connect uri)) invoice-number vendor client]}))
|
||||||
first
|
first
|
||||||
boolean)
|
boolean)
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
@@ -194,7 +183,7 @@
|
|||||||
|
|
||||||
(defn invoice-rows->transaction [rows user]
|
(defn invoice-rows->transaction [rows user]
|
||||||
(->> rows
|
(->> rows
|
||||||
(mapcat (fn [{:keys [vendor-id total client-id amount date invoice-number default-location account-id check vendor automatically-paid-when-due schedule-payment-dom]}]
|
(mapcat (fn [{:keys [vendor-id total client-id date invoice-number default-location check automatically-paid-when-due]}]
|
||||||
(let [invoice #:invoice {:db/id (.toString (java.util.UUID/randomUUID))
|
(let [invoice #:invoice {:db/id (.toString (java.util.UUID/randomUUID))
|
||||||
:vendor vendor-id
|
:vendor vendor-id
|
||||||
:client client-id
|
:client client-id
|
||||||
@@ -204,14 +193,14 @@
|
|||||||
:automatically-paid-when-due automatically-paid-when-due
|
:automatically-paid-when-due automatically-paid-when-due
|
||||||
:total total
|
:total total
|
||||||
:outstanding-balance (if (= "Cash" check)
|
:outstanding-balance (if (= "Cash" check)
|
||||||
0.0
|
0.0
|
||||||
total)
|
total)
|
||||||
:status (if (= "Cash" check)
|
:status (if (= "Cash" check)
|
||||||
:invoice-status/paid
|
:invoice-status/paid
|
||||||
:invoice-status/unpaid)
|
:invoice-status/unpaid)
|
||||||
:invoice-number invoice-number
|
:invoice-number invoice-number
|
||||||
:date (to-date date)}
|
:date (to-date date)}
|
||||||
payment (if (= :invoice-status/paid (:invoice/status invoice))
|
payment (when (= :invoice-status/paid (:invoice/status invoice))
|
||||||
#:invoice-payment {:invoice (:db/id invoice)
|
#:invoice-payment {:invoice (:db/id invoice)
|
||||||
:amount (:invoice/total invoice)
|
:amount (:invoice/total invoice)
|
||||||
:payment (remove-nils #:payment {:db/id (.toString (java.util.UUID/randomUUID))
|
:payment (remove-nils #:payment {:db/id (.toString (java.util.UUID/randomUUID))
|
||||||
@@ -220,20 +209,27 @@
|
|||||||
:type :payment-type/cash
|
:type :payment-type/cash
|
||||||
:amount (:invoice/total invoice)
|
:amount (:invoice/total invoice)
|
||||||
:status :payment-status/cleared
|
:status :payment-status/cleared
|
||||||
:date (:invoice/date invoice)})}
|
:date (:invoice/date invoice)})})]
|
||||||
)]
|
|
||||||
[[:propose-invoice (d-invoices/code-invoice (validate-invoice (remove-nils invoice)
|
[[:propose-invoice (d-invoices/code-invoice (validate-invoice (remove-nils invoice)
|
||||||
user))]
|
user))]
|
||||||
(some-> payment remove-nils)])))
|
(some-> payment remove-nils)])))
|
||||||
(filter identity)))
|
(filter identity)))
|
||||||
|
|
||||||
|
(defn admin-only-if-multiple-clients [is]
|
||||||
|
(let [client-count (->> is
|
||||||
|
(map :invoice/client)
|
||||||
|
set
|
||||||
|
count)]
|
||||||
|
(map #(assoc % :invoice/source-url-admin-only (boolean (> client-count 1))) is)))
|
||||||
|
|
||||||
(defn import-uploaded-invoice [user imports]
|
(defn import-uploaded-invoice [user imports]
|
||||||
(lc/with-context {:area "upload-invoice"}
|
(lc/with-context {:area "upload-invoice"}
|
||||||
(log/info "Number of invoices to import is" (count imports) )
|
(log/info "Number of invoices to import is" (count imports))
|
||||||
(let [clients (d-clients/get-all)
|
(let [clients (d-clients/get-all)
|
||||||
potential-invoices (->> imports
|
potential-invoices (->> imports
|
||||||
(mapv #(import->invoice % clients))
|
(mapv #(import->invoice % clients))
|
||||||
(mapv #(validate-invoice % user))
|
(mapv #(validate-invoice % user))
|
||||||
|
admin-only-if-multiple-clients
|
||||||
(filter #(not (extant-invoice? %)))
|
(filter #(not (extant-invoice? %)))
|
||||||
(mapv d-invoices/code-invoice)
|
(mapv d-invoices/code-invoice)
|
||||||
(mapv (fn [i] [:propose-invoice i])))]
|
(mapv (fn [i] [:propose-invoice i])))]
|
||||||
@@ -243,16 +239,14 @@
|
|||||||
(log/info "creating invoice" potential-invoices)
|
(log/info "creating invoice" potential-invoices)
|
||||||
@(d/transact (d/connect uri) potential-invoices))))
|
@(d/transact (d/connect uri) potential-invoices))))
|
||||||
|
|
||||||
|
|
||||||
(defn validate-account-rows [rows code->existing-account]
|
(defn validate-account-rows [rows code->existing-account]
|
||||||
(when-let [bad-types (seq (->> rows
|
(when-let [bad-types (seq (->> rows
|
||||||
(filter (fn [[account _ _ type :as row]]
|
(filter (fn [[account _ _ type]]
|
||||||
(and (not (code->existing-account (Integer/parseInt account)))
|
(and (not (code->existing-account (Integer/parseInt account)))
|
||||||
(not (#{"Asset" "Liability" "Revenue" "Expense" "Equity" "Dividend"} type)))
|
(not (#{"Asset" "Liability" "Revenue" "Expense" "Equity" "Dividend"} type)))))))]
|
||||||
))))]
|
(throw (ex-info (str "You are adding accounts without a valid type")
|
||||||
(throw (ex-info (str "You are adding accounts without a valid type" )
|
|
||||||
{:rows bad-types})))
|
{:rows bad-types})))
|
||||||
(when-let [duplicate-rows (seq (->> rows
|
(when-let [duplicate-rows (seq (->> rows
|
||||||
(filter (fn [[account]]
|
(filter (fn [[account]]
|
||||||
(not-empty account)))
|
(not-empty account)))
|
||||||
(group-by (fn [[account]]
|
(group-by (fn [[account]]
|
||||||
@@ -262,19 +256,17 @@
|
|||||||
(filter (fn [duplicates]
|
(filter (fn [duplicates]
|
||||||
(apply not= duplicates)))
|
(apply not= duplicates)))
|
||||||
#_(map (fn [[[_ account]]]
|
#_(map (fn [[[_ account]]]
|
||||||
account))
|
account))))]
|
||||||
))]
|
(throw (ex-info (str "You have duplicated rows with different values.")
|
||||||
(throw (ex-info (str "You have duplicated rows with different values." )
|
|
||||||
{:rows duplicate-rows}))))
|
{:rows duplicate-rows}))))
|
||||||
|
|
||||||
(defn import-account-overrides [customer filename]
|
(defn import-account-overrides [customer filename]
|
||||||
(let [conn (d/connect uri)
|
(let [conn (d/connect uri)
|
||||||
[header & rows] (-> filename (io/reader) csv/read-csv)
|
[_ & rows] (-> filename (io/reader) csv/read-csv)
|
||||||
[client-id] (first (d/query (-> {:query {:find ['?e]
|
[client-id] (first (d/query (-> {:query {:find ['?e]
|
||||||
:in ['$ '?z]
|
:in ['$ '?z]
|
||||||
:where [['?e :client/code '?z]]}
|
:where [['?e :client/code '?z]]}
|
||||||
:args [(d/db (d/connect uri)) customer]})))
|
:args [(d/db (d/connect uri)) customer]})))
|
||||||
headers (map read-string header)
|
|
||||||
code->existing-account (by :account/numeric-code (map first (d/query {:query {:find ['(pull ?e [:account/numeric-code
|
code->existing-account (by :account/numeric-code (map first (d/query {:query {:find ['(pull ?e [:account/numeric-code
|
||||||
{:account/applicability [:db/ident]}
|
{:account/applicability [:db/ident]}
|
||||||
:db/id])]
|
:db/id])]
|
||||||
@@ -286,8 +278,6 @@
|
|||||||
:in ['$ '?client-id]
|
:in ['$ '?client-id]
|
||||||
:where [['?e :account-client-override/client '?client-id]]}
|
:where [['?e :account-client-override/client '?client-id]]}
|
||||||
:args [(d/db (d/connect uri)) client-id]}))
|
:args [(d/db (d/connect uri)) client-id]}))
|
||||||
|
|
||||||
|
|
||||||
rows (transduce (comp
|
rows (transduce (comp
|
||||||
(map (fn [[_ account account-name override-name _ type]]
|
(map (fn [[_ account account-name override-name _ type]]
|
||||||
[account account-name override-name type]))
|
[account account-name override-name type]))
|
||||||
@@ -314,8 +304,7 @@
|
|||||||
(:db/ident (:account/applicability existing)))
|
(:db/ident (:account/applicability existing)))
|
||||||
(and (not-empty override-name)
|
(and (not-empty override-name)
|
||||||
(not-empty account-name)
|
(not-empty account-name)
|
||||||
(not= override-name account-name)
|
(not= override-name account-name))))
|
||||||
)))
|
|
||||||
[{:db/id (:db/id existing)
|
[{:db/id (:db/id existing)
|
||||||
:account/client-overrides [{:account-client-override/client client-id
|
:account/client-overrides [{:account-client-override/client client-id
|
||||||
:account-client-override/name (or (not-empty override-name)
|
:account-client-override/name (or (not-empty override-name)
|
||||||
@@ -346,13 +335,11 @@
|
|||||||
@(d/transact conn txes)
|
@(d/transact conn txes)
|
||||||
txes))
|
txes))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn import-transactions-cleared-against [file]
|
(defn import-transactions-cleared-against [file]
|
||||||
(let [[header & rows] (-> file (io/reader) csv/read-csv)
|
(let [[_ & rows] (-> file (io/reader) csv/read-csv)
|
||||||
txes (transduce
|
txes (transduce
|
||||||
(comp
|
(comp
|
||||||
(filter (fn [[transaction-id cleared-against]]
|
(filter (fn [[transaction-id _]]
|
||||||
(d/pull (d/db (d/connect uri)) '[:transaction/amount] (Long/parseLong transaction-id))))
|
(d/pull (d/db (d/connect uri)) '[:transaction/amount] (Long/parseLong transaction-id))))
|
||||||
(map (fn [[transaction-id cleared-against]]
|
(map (fn [[transaction-id cleared-against]]
|
||||||
{:db/id (Long/parseLong transaction-id)
|
{:db/id (Long/parseLong transaction-id)
|
||||||
@@ -362,140 +349,137 @@
|
|||||||
rows)]
|
rows)]
|
||||||
@(d/transact (d/connect uri) txes)))
|
@(d/transact (d/connect uri) txes)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defroutes routes
|
(defroutes routes
|
||||||
(wrap-routes
|
(wrap-routes
|
||||||
(context "/" []
|
(context "/" []
|
||||||
(context "/transactions" []
|
(context "/transactions" []
|
||||||
(POST "/batch-upload"
|
(POST "/batch-upload"
|
||||||
{{:keys [data]} :edn-params user :identity}
|
{{:keys [data]} :edn-params user :identity}
|
||||||
(assert-admin user)
|
(assert-admin user)
|
||||||
(try
|
(try
|
||||||
(let [stats (manual/import-batch (manual/tabulate-data data) (:user/name user))]
|
(let [stats (manual/import-batch (manual/tabulate-data data) (:user/name user))]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (pr-str stats)
|
:body (pr-str stats)
|
||||||
:headers {"Content-Type" "application/edn"}})
|
:headers {"Content-Type" "application/edn"}})
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(log/error e)
|
(log/error e)
|
||||||
{:status 500
|
{:status 500
|
||||||
:body (pr-str {:message (.getMessage e)
|
:body (pr-str {:message (.getMessage e)
|
||||||
:error (.toString e)
|
:error (.toString e)
|
||||||
:data (ex-data e)})
|
:data (ex-data e)})
|
||||||
:headers {"Content-Type" "application/edn"}}))))
|
:headers {"Content-Type" "application/edn"}}))))
|
||||||
|
|
||||||
(context "/invoices" []
|
(context "/invoices" []
|
||||||
(POST "/upload"
|
(POST "/upload"
|
||||||
|
{{files :file
|
||||||
|
files-2 "file"
|
||||||
|
client :client
|
||||||
|
client-2 "client"
|
||||||
|
location :location
|
||||||
|
location-2 "location"
|
||||||
|
vendor :vendor
|
||||||
|
vendor-2 "vendor"} :params
|
||||||
|
user :identity}
|
||||||
|
(let [files (or files files-2)
|
||||||
|
client (or client client-2)
|
||||||
|
location (or location location-2)
|
||||||
|
vendor (some-> (or vendor vendor-2)
|
||||||
|
(Long/parseLong))
|
||||||
|
{:keys [filename tempfile]} files]
|
||||||
|
(lc/with-context {:parsing-file filename}
|
||||||
|
(try
|
||||||
|
(let [extension (last (str/split (.getName (io/file filename)) #"\."))
|
||||||
|
s3-location (str "invoice-files/" (str (UUID/randomUUID)) "." extension)
|
||||||
|
_ (s3/put-object :bucket-name (:data-bucket env)
|
||||||
|
:key s3-location
|
||||||
|
:input-stream (io/input-stream tempfile)
|
||||||
|
:metadata {:content-type "application/pdf"})
|
||||||
|
imports (->> (parse/parse-file (.getPath tempfile) filename)
|
||||||
|
(map #(assoc %
|
||||||
|
:client-override client
|
||||||
|
:location-override location
|
||||||
|
:vendor-override vendor
|
||||||
|
:source-url (str "http://" (:data-bucket env)
|
||||||
|
".s3-website-us-east-1.amazonaws.com/"
|
||||||
|
s3-location))))]
|
||||||
|
(import-uploaded-invoice user imports))
|
||||||
|
{:status 200
|
||||||
|
:body (pr-str {})
|
||||||
|
:headers {"Content-Type" "application/edn"}}
|
||||||
|
(catch Exception e
|
||||||
|
(log/warn e)
|
||||||
|
{:status 400
|
||||||
|
:body (pr-str {:message (.getMessage e)
|
||||||
|
:error (.toString e)
|
||||||
|
:data (ex-data e)})
|
||||||
|
:headers {"Content-Type" "application/edn"}})))))
|
||||||
|
|
||||||
|
(POST "/upload-integreat"
|
||||||
|
{{: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)
|
||||||
|
_ @(d/transact (d/connect uri) (invoice-rows->transaction (:new grouped-rows)
|
||||||
|
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"}})))
|
||||||
|
(POST "/transactions/cleared-against"
|
||||||
|
{{files :file
|
||||||
|
files-2 "file"} :params
|
||||||
|
user :identity}
|
||||||
|
(let [files (or files files-2)
|
||||||
|
{:keys [tempfile]} files]
|
||||||
|
(assert-admin user)
|
||||||
|
(try
|
||||||
|
(import-transactions-cleared-against (.getPath tempfile))
|
||||||
|
{:status 200
|
||||||
|
:body (pr-str {})
|
||||||
|
:headers {"Content-Type" "application/edn"}}
|
||||||
|
(catch Exception e
|
||||||
|
(log/error e)
|
||||||
|
{:status 500
|
||||||
|
:body (pr-str {:message (.getMessage e)
|
||||||
|
:error (.toString e)
|
||||||
|
:data (ex-data e)})
|
||||||
|
:headers {"Content-Type" "application/edn"}}))))
|
||||||
|
(wrap-json-response (POST "/account-overrides"
|
||||||
{{files :file
|
{{files :file
|
||||||
files-2 "file"
|
files-2 "file"
|
||||||
client :client
|
client :client
|
||||||
client-2 "client"
|
client-2 "client"} :params
|
||||||
location :location
|
|
||||||
location-2 "location"
|
|
||||||
vendor :vendor
|
|
||||||
vendor-2 "vendor"} :params :as params
|
|
||||||
user :identity}
|
user :identity}
|
||||||
(let [files (or files files-2)
|
(let [files (or files files-2)
|
||||||
client (or client client-2)
|
client (or client client-2)
|
||||||
location (or location location-2)
|
{:keys [tempfile]} files]
|
||||||
vendor (some-> (or vendor vendor-2)
|
|
||||||
(Long/parseLong))
|
|
||||||
{:keys [filename tempfile]} files]
|
|
||||||
(lc/with-context {:parsing-file filename}
|
|
||||||
(try
|
|
||||||
(let [extension (last (str/split (.getName (io/file filename)) #"\." ))
|
|
||||||
s3-location (str "invoice-files/" (str (UUID/randomUUID)) "." extension)
|
|
||||||
_ (s3/put-object :bucket-name (:data-bucket env)
|
|
||||||
:key s3-location
|
|
||||||
:input-stream (io/input-stream tempfile)
|
|
||||||
:metadata {:content-type "application/pdf"})
|
|
||||||
imports (->> (parse/parse-file (.getPath tempfile) filename)
|
|
||||||
(map #(assoc %
|
|
||||||
:client-override client
|
|
||||||
:location-override location
|
|
||||||
:vendor-override vendor
|
|
||||||
:source-url (str "http://" (:data-bucket env)
|
|
||||||
".s3-website-us-east-1.amazonaws.com/"
|
|
||||||
s3-location))))]
|
|
||||||
(import-uploaded-invoice user imports))
|
|
||||||
{:status 200
|
|
||||||
:body (pr-str {})
|
|
||||||
:headers {"Content-Type" "application/edn"}}
|
|
||||||
(catch Exception e
|
|
||||||
(log/warn e)
|
|
||||||
{:status 400
|
|
||||||
:body (pr-str {:message (.getMessage e)
|
|
||||||
:error (.toString e)
|
|
||||||
:data (ex-data e)})
|
|
||||||
:headers {"Content-Type" "application/edn"}})))))
|
|
||||||
|
|
||||||
(POST "/upload-integreat"
|
|
||||||
{{: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)
|
|
||||||
inserted-rows @(d/transact (d/connect uri) (invoice-rows->transaction (:new grouped-rows)
|
|
||||||
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"}})))
|
|
||||||
(POST "/transactions/cleared-against"
|
|
||||||
{{files :file
|
|
||||||
files-2 "file"} :params :as params
|
|
||||||
user :identity}
|
|
||||||
(let [files (or files files-2)
|
|
||||||
{:keys [filename tempfile]} files]
|
|
||||||
(assert-admin user)
|
(assert-admin user)
|
||||||
(try
|
(try
|
||||||
(import-transactions-cleared-against (.getPath tempfile))
|
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (pr-str {})
|
:body (import-account-overrides client (.getPath tempfile))
|
||||||
:headers {"Content-Type" "application/edn"}}
|
:headers {"Content-Type" "application/json"}}
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(log/error e)
|
(log/error e)
|
||||||
{:status 500
|
{:status 500
|
||||||
:body (pr-str {:message (.getMessage e)
|
:body {:message (.getMessage e)
|
||||||
:error (.toString e)
|
:data (ex-data e)}
|
||||||
:data (ex-data e)})
|
:headers {"Content-Type" "application/json"}}))))))
|
||||||
:headers {"Content-Type" "application/edn"}}))))
|
|
||||||
(wrap-json-response (POST "/account-overrides"
|
|
||||||
{{files :file
|
|
||||||
files-2 "file"
|
|
||||||
client :client
|
|
||||||
client-2 "client"} :params :as params
|
|
||||||
user :identity}
|
|
||||||
(let [files (or files files-2)
|
|
||||||
client (or client client-2)
|
|
||||||
{:keys [filename tempfile]} files]
|
|
||||||
(assert-admin user)
|
|
||||||
(try
|
|
||||||
{:status 200
|
|
||||||
:body (import-account-overrides client (.getPath tempfile))
|
|
||||||
:headers {"Content-Type" "application/json"}}
|
|
||||||
(catch Exception e
|
|
||||||
(log/error e)
|
|
||||||
{:status 500
|
|
||||||
:body {:message (.getMessage e)
|
|
||||||
:data (ex-data e)}
|
|
||||||
:headers {"Content-Type" "application/json"}}))))))
|
|
||||||
wrap-secure))
|
wrap-secure))
|
||||||
|
|||||||
Reference in New Issue
Block a user