Improvements on validations
This commit is contained in:
@@ -9,10 +9,11 @@
|
|||||||
[auto-ap.time :refer [parse iso-date]]
|
[auto-ap.time :refer [parse iso-date]]
|
||||||
[auto-ap.utils :refer [dollars=]]
|
[auto-ap.utils :refer [dollars=]]
|
||||||
[datomic.api :as d]
|
[datomic.api :as d]
|
||||||
[auto-ap.datomic :refer [uri remove-nils audit-transact]]
|
[auto-ap.datomic :refer [uri remove-nils audit-transact conn]]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clj-time.core :as time]
|
[clj-time.core :as time]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]
|
||||||
|
[clojure.tools.logging :as log]))
|
||||||
|
|
||||||
(defn get-invoice-page [context args value]
|
(defn get-invoice-page [context args value]
|
||||||
|
|
||||||
@@ -51,6 +52,8 @@
|
|||||||
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number :validation-error (str "Invoice '" invoice_number "' already exists.")}))))
|
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number :validation-error (str "Invoice '" invoice_number "' already exists.")}))))
|
||||||
|
|
||||||
(defn expense-account->entity [{:keys [id account_id amount location]}]
|
(defn expense-account->entity [{:keys [id account_id amount location]}]
|
||||||
|
|
||||||
|
|
||||||
(remove-nils #:invoice-expense-account {:amount (Double/parseDouble amount)
|
(remove-nils #:invoice-expense-account {:amount (Double/parseDouble amount)
|
||||||
:db/id id
|
:db/id id
|
||||||
:account account_id
|
:account account_id
|
||||||
@@ -90,16 +93,37 @@
|
|||||||
set)]
|
set)]
|
||||||
(set/difference existing-ids specified-ids)))
|
(set/difference existing-ids specified-ids)))
|
||||||
|
|
||||||
|
(defn assert-valid-expense-accounts [expense_accounts]
|
||||||
|
(doseq [expense-account expense_accounts
|
||||||
|
:let [account (d/entity (d/db conn) (:account_id expense-account))]]
|
||||||
|
(log/info "ACCOUNT" (:account/location account) )
|
||||||
|
(when (empty? (:location expense-account))
|
||||||
|
(throw (ex-info "Expense account is missing location" {:validation-error "Expense account is missing location"})))
|
||||||
|
|
||||||
|
(when (and (not (empty? (:account/location account)))
|
||||||
|
(not= (:location expense-account)
|
||||||
|
(:account/location account)))
|
||||||
|
(let [err (str "Account uses location '" (:location expense-account) "' but expects '" (:account/location account) "'")]
|
||||||
|
(throw (ex-info err
|
||||||
|
{:validation-error err}))))
|
||||||
|
|
||||||
|
(when (and (empty? (:account/location account))
|
||||||
|
(= "A" (:location expense-account)))
|
||||||
|
(let [err (str "Account uses location '" (:location expense-account) "', which is reserved for liabilities, equities, and assets.")]
|
||||||
|
(throw (ex-info err
|
||||||
|
{:validation-error err}))))
|
||||||
|
|
||||||
|
|
||||||
|
(when (nil? (:account_id expense-account))
|
||||||
|
(throw (ex-info "Expense account is missing account" {:validation-error "Expense account is missing account"})))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn add-invoice [context {{:keys [total expense_accounts invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value]
|
(defn add-invoice [context {{:keys [total expense_accounts invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value]
|
||||||
(assert-no-conflicting in)
|
(assert-no-conflicting in)
|
||||||
(assert-can-see-client (:id context) client_id)
|
(assert-can-see-client (:id context) client_id)
|
||||||
(doseq [expense-account expense_accounts]
|
(assert-valid-expense-accounts expense_accounts)
|
||||||
(when (empty? (:location expense-account))
|
|
||||||
(throw (ex-info "Expense account is missing location" {:validation-error "Expense account is missing location"})))
|
|
||||||
(when (nil? (:account_id expense-account))
|
|
||||||
(throw (ex-info "Expense account is missing account" {:validation-error "Expense account is missing account"}))))
|
|
||||||
(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))))
|
||||||
@@ -112,6 +136,7 @@
|
|||||||
(assert-no-conflicting in)
|
(assert-no-conflicting in)
|
||||||
(assert-can-see-client (:id context) client_id)
|
(assert-can-see-client (:id context) client_id)
|
||||||
(assert-bank-account-belongs client_id bank-account-id)
|
(assert-bank-account-belongs client_id bank-account-id)
|
||||||
|
(assert-valid-expense-accounts (:expense_accounts in))
|
||||||
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
||||||
(-> (gq-checks/print-checks [{:invoice-id (get-in transaction-result [:tempids "invoice"])
|
(-> (gq-checks/print-checks [{:invoice-id (get-in transaction-result [:tempids "invoice"])
|
||||||
:amount total}]
|
:amount total}]
|
||||||
@@ -137,6 +162,8 @@
|
|||||||
paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
|
paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
|
||||||
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
|
||||||
deleted (deleted-expense-accounts invoice expense_accounts)
|
deleted (deleted-expense-accounts invoice expense_accounts)
|
||||||
|
_ (assert-valid-expense-accounts expense_accounts)
|
||||||
|
|
||||||
|
|
||||||
updated-invoice (cond-> {:db/id id
|
updated-invoice (cond-> {:db/id id
|
||||||
:invoice/invoice-number invoice_number
|
:invoice/invoice-number invoice_number
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
(ns auto-ap.graphql.transactions
|
(ns auto-ap.graphql.transactions
|
||||||
(:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-can-see-client assert-admin ident->enum-f snake->kebab enum->keyword]]
|
(:require [auto-ap.datomic
|
||||||
[auto-ap.datomic.transactions :as d-transactions]
|
:refer
|
||||||
[auto-ap.datomic.vendors :as d-vendors]
|
[audit-transact audit-transact-batch conn remove-nils]]
|
||||||
|
[auto-ap.datomic.accounts :as a]
|
||||||
[auto-ap.datomic.checks :as d-checks]
|
[auto-ap.datomic.checks :as d-checks]
|
||||||
|
[auto-ap.datomic.transaction-rules :as tr]
|
||||||
|
[auto-ap.datomic.transactions :as d-transactions]
|
||||||
[auto-ap.graphql.transaction-rules :as g-tr]
|
[auto-ap.graphql.transaction-rules :as g-tr]
|
||||||
[datomic.api :as d]
|
[auto-ap.graphql.utils
|
||||||
[auto-ap.datomic :refer [uri remove-nils audit-transact audit-transact-batch]]
|
:refer
|
||||||
[com.walmartlabs.lacinia :refer [execute]]
|
[->graphql
|
||||||
[com.walmartlabs.lacinia.executor :as executor]
|
<-graphql
|
||||||
[com.walmartlabs.lacinia.resolve :as resolve]
|
assert-admin
|
||||||
[auto-ap.utils :refer [by dollars=]]
|
assert-can-see-client
|
||||||
[auto-ap.time :refer [parse normal-date]]
|
enum->keyword
|
||||||
[auto-ap.datomic.clients :as d-clients]
|
ident->enum-f
|
||||||
|
snake->kebab]]
|
||||||
|
[auto-ap.rule-matching :as rm]
|
||||||
|
[auto-ap.utils :refer [dollars=]]
|
||||||
|
[clj-time.coerce :as coerce]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[auto-ap.datomic.accounts :as a]
|
[clojure.tools.logging :as log]
|
||||||
[auto-ap.datomic.transaction-rules :as tr]
|
[datomic.api :as d]))
|
||||||
[auto-ap.rule-matching :as rm]
|
|
||||||
[clj-time.coerce :as coerce]
|
|
||||||
[clojure.tools.logging :as log]))
|
|
||||||
|
|
||||||
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
|
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
|
||||||
|
|
||||||
@@ -93,9 +97,33 @@
|
|||||||
set)]
|
set)]
|
||||||
(set/difference existing-ids specified-ids)))
|
(set/difference existing-ids specified-ids)))
|
||||||
|
|
||||||
|
(defn assert-valid-expense-accounts [accounts]
|
||||||
|
(doseq [trans-account accounts
|
||||||
|
:let [account (d/entity (d/db conn) (:account_id trans-account))]]
|
||||||
|
(when (empty? (:location trans-account))
|
||||||
|
(throw (ex-info "Account is missing location" {:validation-error "Account is missing location"})))
|
||||||
|
|
||||||
|
(when (and (not (empty? (:account/location account)))
|
||||||
|
(not= (:location trans-account)
|
||||||
|
(:account/location account)))
|
||||||
|
(let [err (str "Account uses location '" (:location trans-account) "' but expects '" (:account/location account) "'")]
|
||||||
|
(throw (ex-info err
|
||||||
|
{:validation-error err}))))
|
||||||
|
|
||||||
|
(when (and (empty? (:account/location account))
|
||||||
|
(= "A" (:location trans-account)))
|
||||||
|
(let [err (str "Account uses location '" (:location trans-account) "', which is reserved for liabilities, equities, and assets.")]
|
||||||
|
(throw (ex-info err
|
||||||
|
{:validation-error err}))))
|
||||||
|
|
||||||
|
|
||||||
|
(when (nil? (:account_id trans-account))
|
||||||
|
(throw (ex-info "Account is missing account" {:validation-error "Account is missing account"})))))
|
||||||
|
|
||||||
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status forecast_match] :as transaction} :transaction} value]
|
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status forecast_match] :as transaction} :transaction} value]
|
||||||
(let [existing-transaction (d-transactions/get-by-id id)
|
(let [existing-transaction (d-transactions/get-by-id id)
|
||||||
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
|
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
|
||||||
|
_ (assert-valid-expense-accounts accounts)
|
||||||
deleted (deleted-accounts existing-transaction accounts)
|
deleted (deleted-accounts existing-transaction accounts)
|
||||||
account-total (reduce + 0 (map (fn [x] (Double/parseDouble (:amount x))) accounts))
|
account-total (reduce + 0 (map (fn [x] (Double/parseDouble (:amount x))) accounts))
|
||||||
missing-locations (seq (set/difference
|
missing-locations (seq (set/difference
|
||||||
|
|||||||
@@ -190,113 +190,114 @@
|
|||||||
(let [change-event [::forms/change ::form]
|
(let [change-event [::forms/change ::form]
|
||||||
{:keys [data] } @(re-frame/subscribe [::forms/form ::form])
|
{:keys [data] } @(re-frame/subscribe [::forms/form ::form])
|
||||||
locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])
|
locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])
|
||||||
{:keys [form field raw-field error-notification submit-button ]} transaction-form
|
{:keys [form-inline form field raw-field error-notification submit-button ]} transaction-form
|
||||||
is-admin? @(re-frame/subscribe [::subs/is-admin?])
|
is-admin? @(re-frame/subscribe [::subs/is-admin?])
|
||||||
should-disable-for-client? (and (not is-admin?)
|
should-disable-for-client? (and (not is-admin?)
|
||||||
(not= :requires-feedback (:original-status data)))]
|
(not= :requires-feedback (:original-status data)))]
|
||||||
[form {:title "Transaction"}
|
(form-inline {:title "Transaction"}
|
||||||
|
[:<>
|
||||||
|
|
||||||
(when (and @(re-frame/subscribe [::subs/is-admin?])
|
(when (and @(re-frame/subscribe [::subs/is-admin?])
|
||||||
(get-in data [:yodlee-merchant]))
|
(get-in data [:yodlee-merchant]))
|
||||||
[:div.control
|
[:div.control
|
||||||
[:p.help "Merchant"]
|
[:p.help "Merchant"]
|
||||||
[:input.input {:type "text"
|
[:input.input {:type "text"
|
||||||
:disabled true
|
:disabled true
|
||||||
:value (str (get-in data [:yodlee-merchant :name])
|
:value (str (get-in data [:yodlee-merchant :name])
|
||||||
" - "
|
" - "
|
||||||
(get-in data [:yodlee-merchant :yodlee-id]))}]])
|
(get-in data [:yodlee-merchant :yodlee-id]))}]])
|
||||||
|
|
||||||
(when is-admin?
|
(when is-admin?
|
||||||
[field "Matched Rule"
|
(field "Matched Rule"
|
||||||
[:input.input {:type "text"
|
[:input.input {:type "text"
|
||||||
:field [:matched-rule :note]
|
:field [:matched-rule :note]
|
||||||
:disabled "disabled"}]])
|
:disabled "disabled"}]))
|
||||||
[field "Amount"
|
(field "Amount"
|
||||||
[:input.input {:type "text"
|
[:input.input {:type "text"
|
||||||
:field [:amount]
|
:field [:amount]
|
||||||
:disabled "disabled"}]]
|
:disabled "disabled"}])
|
||||||
[field "Description"
|
(field "Description"
|
||||||
[:input.input {:type "text"
|
[:input.input {:type "text"
|
||||||
:field [:description-original]
|
:field [:description-original]
|
||||||
:disabled "disabled"}]]
|
:disabled "disabled"}])
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
(and (seq (:potential-transaction-rule-matches data))
|
(and (seq (:potential-transaction-rule-matches data))
|
||||||
(not (:matched-rule data))
|
(not (:matched-rule data))
|
||||||
(not (:payment data))
|
(not (:payment data))
|
||||||
is-admin?)
|
is-admin?)
|
||||||
[potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]
|
[potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]
|
||||||
|
|
||||||
|
|
||||||
(and (seq (:potential-payment-matches data))
|
(and (seq (:potential-payment-matches data))
|
||||||
(not (:payment data))
|
(not (:payment data))
|
||||||
is-admin?)
|
is-admin?)
|
||||||
[potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]
|
[potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]
|
||||||
|
|
||||||
(and (not (seq (:potential-payment-matches data)))
|
(and (not (seq (:potential-payment-matches data)))
|
||||||
(not (seq (:potential-transaction-rule-matches data))))
|
(not (seq (:potential-transaction-rule-matches data))))
|
||||||
[:div
|
[:div
|
||||||
[field "Vendor"
|
(field "Vendor"
|
||||||
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
|
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
|
||||||
:match->text :name
|
:match->text :name
|
||||||
:type "typeahead-entity"
|
:type "typeahead-entity"
|
||||||
:auto-focus true
|
:auto-focus true
|
||||||
:field [:vendor]
|
:field [:vendor]
|
||||||
:disabled (or (boolean (:payment data))
|
:disabled (or (boolean (:payment data))
|
||||||
should-disable-for-client?)}]]
|
should-disable-for-client?)}])
|
||||||
[field nil
|
(field nil
|
||||||
[expense-accounts-field
|
[expense-accounts-field
|
||||||
{:type "expense-accounts"
|
{:type "expense-accounts"
|
||||||
:field [:accounts]
|
:field [:accounts]
|
||||||
:max (Math/abs (js/parseFloat (:amount data)))
|
:max (Math/abs (js/parseFloat (:amount data)))
|
||||||
:descriptor "credit account"
|
:descriptor "credit account"
|
||||||
:disabled (or (boolean (:payment data))
|
:disabled (or (boolean (:payment data))
|
||||||
should-disable-for-client?)
|
should-disable-for-client?)
|
||||||
:locations locations}]]
|
:locations locations}])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[field "Approval Status"
|
(field "Approval Status"
|
||||||
[button-radio
|
[button-radio
|
||||||
{:type "button-radio"
|
{:type "button-radio"
|
||||||
:field [:approval-status]
|
:field [:approval-status]
|
||||||
:options [[:unapproved "Unapproved"]
|
:options [[:unapproved "Unapproved"]
|
||||||
[:requires-feedback "Client Review"]
|
[:requires-feedback "Client Review"]
|
||||||
[:approved "Approved"]
|
[:approved "Approved"]
|
||||||
[:excluded "Excluded from Ledger"]]
|
[:excluded "Excluded from Ledger"]]
|
||||||
:disabled should-disable-for-client?}]]
|
:disabled should-disable-for-client?}])
|
||||||
|
|
||||||
[field "Forecasted-transaction"
|
(field "Forecasted-transaction"
|
||||||
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
|
[typeahead-entity {:matches @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))])
|
||||||
:match->text :identifier
|
:match->text :identifier
|
||||||
:type "typeahead-entity"
|
:type "typeahead-entity"
|
||||||
:field [:forecast-match]}]]
|
:field [:forecast-match]}])
|
||||||
|
|
||||||
|
|
||||||
[error-notification]
|
(error-notification)
|
||||||
(when-not should-disable-for-client?
|
(when-not should-disable-for-client?
|
||||||
[submit-button "Save"])]
|
(submit-button "Save"))]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
|
|
||||||
[:div
|
[:div
|
||||||
[field "Approval Status"
|
(field "Approval Status"
|
||||||
[button-radio
|
[button-radio
|
||||||
{:type "button-radio"
|
{:type "button-radio"
|
||||||
:field [:approval-status]
|
:field [:approval-status]
|
||||||
:options [[:unapproved "Unapproved"]
|
:options [[:unapproved "Unapproved"]
|
||||||
[:requires-feedback "Client Review"]
|
[:requires-feedback "Client Review"]
|
||||||
[:approved "Approved"]
|
[:approved "Approved"]
|
||||||
[:excluded "Excluded from Ledger"]]
|
[:excluded "Excluded from Ledger"]]
|
||||||
:disabled should-disable-for-client?}]]
|
:disabled should-disable-for-client?}])
|
||||||
|
|
||||||
[field "Forecasted-transaction"
|
(field "Forecasted-transaction"
|
||||||
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
|
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
|
||||||
:match->text :identifier
|
:match->text :identifier
|
||||||
:type "typeahead-entity"
|
:type "typeahead-entity"
|
||||||
:field [:forecast-match]}]]
|
:field [:forecast-match]}])
|
||||||
|
|
||||||
[error-notification]
|
(error-notification)
|
||||||
(when-not should-disable-for-client?
|
(when-not should-disable-for-client?
|
||||||
[submit-button "Save"])])])])
|
(submit-button "Save"))])]))])
|
||||||
|
|||||||
Reference in New Issue
Block a user