Improvements on validations

This commit is contained in:
Bryce Covert
2020-10-07 07:51:27 -07:00
parent 71fc8f69eb
commit 84eedbf56f
3 changed files with 172 additions and 116 deletions

View File

@@ -9,10 +9,11 @@
[auto-ap.time :refer [parse iso-date]]
[auto-ap.utils :refer [dollars=]]
[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.core :as time]
[clojure.set :as set]))
[clojure.set :as set]
[clojure.tools.logging :as log]))
(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.")}))))
(defn expense-account->entity [{:keys [id account_id amount location]}]
(remove-nils #:invoice-expense-account {:amount (Double/parseDouble amount)
:db/id id
:account account_id
@@ -90,16 +93,37 @@
set)]
(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]
(assert-no-conflicting in)
(assert-can-see-client (:id context) client_id)
(doseq [expense-account 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"}))))
(assert-valid-expense-accounts expense_accounts)
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
(->graphql))))
@@ -112,6 +136,7 @@
(assert-no-conflicting in)
(assert-can-see-client (:id context) client_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))]
(-> (gq-checks/print-checks [{:invoice-id (get-in transaction-result [:tempids "invoice"])
:amount total}]
@@ -137,6 +162,8 @@
paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
deleted (deleted-expense-accounts invoice expense_accounts)
_ (assert-valid-expense-accounts expense_accounts)
updated-invoice (cond-> {:db/id id
:invoice/invoice-number invoice_number

View File

@@ -1,24 +1,28 @@
(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]]
[auto-ap.datomic.transactions :as d-transactions]
[auto-ap.datomic.vendors :as d-vendors]
(:require [auto-ap.datomic
:refer
[audit-transact audit-transact-batch conn remove-nils]]
[auto-ap.datomic.accounts :as a]
[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]
[datomic.api :as d]
[auto-ap.datomic :refer [uri remove-nils audit-transact audit-transact-batch]]
[com.walmartlabs.lacinia :refer [execute]]
[com.walmartlabs.lacinia.executor :as executor]
[com.walmartlabs.lacinia.resolve :as resolve]
[auto-ap.utils :refer [by dollars=]]
[auto-ap.time :refer [parse normal-date]]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.graphql.utils
:refer
[->graphql
<-graphql
assert-admin
assert-can-see-client
enum->keyword
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.string :as str]
[auto-ap.datomic.accounts :as a]
[auto-ap.datomic.transaction-rules :as tr]
[auto-ap.rule-matching :as rm]
[clj-time.coerce :as coerce]
[clojure.tools.logging :as log]))
[clojure.tools.logging :as log]
[datomic.api :as d]))
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
@@ -93,9 +97,33 @@
set)]
(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]
(let [existing-transaction (d-transactions/get-by-id id)
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
_ (assert-valid-expense-accounts accounts)
deleted (deleted-accounts existing-transaction accounts)
account-total (reduce + 0 (map (fn [x] (Double/parseDouble (:amount x))) accounts))
missing-locations (seq (set/difference

View File

@@ -190,113 +190,114 @@
(let [change-event [::forms/change ::form]
{:keys [data] } @(re-frame/subscribe [::forms/form ::form])
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?])
should-disable-for-client? (and (not is-admin?)
(not= :requires-feedback (:original-status data)))]
[form {:title "Transaction"}
(form-inline {:title "Transaction"}
[:<>
(when (and @(re-frame/subscribe [::subs/is-admin?])
(get-in data [:yodlee-merchant]))
[:div.control
[:p.help "Merchant"]
[:input.input {:type "text"
:disabled true
:value (str (get-in data [:yodlee-merchant :name])
" - "
(get-in data [:yodlee-merchant :yodlee-id]))}]])
(when (and @(re-frame/subscribe [::subs/is-admin?])
(get-in data [:yodlee-merchant]))
[:div.control
[:p.help "Merchant"]
[:input.input {:type "text"
:disabled true
:value (str (get-in data [:yodlee-merchant :name])
" - "
(get-in data [:yodlee-merchant :yodlee-id]))}]])
(when is-admin?
[field "Matched Rule"
[:input.input {:type "text"
:field [:matched-rule :note]
:disabled "disabled"}]])
[field "Amount"
[:input.input {:type "text"
:field [:amount]
:disabled "disabled"}]]
[field "Description"
[:input.input {:type "text"
:field [:description-original]
:disabled "disabled"}]]
(when is-admin?
(field "Matched Rule"
[:input.input {:type "text"
:field [:matched-rule :note]
:disabled "disabled"}]))
(field "Amount"
[:input.input {:type "text"
:field [:amount]
:disabled "disabled"}])
(field "Description"
[:input.input {:type "text"
:field [:description-original]
:disabled "disabled"}])
(cond
(and (seq (:potential-transaction-rule-matches data))
(not (:matched-rule data))
(not (:payment data))
is-admin?)
[potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]
(cond
(and (seq (:potential-transaction-rule-matches data))
(not (:matched-rule data))
(not (:payment data))
is-admin?)
[potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]
(and (seq (:potential-payment-matches data))
(not (:payment data))
is-admin?)
[potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]
(and (seq (:potential-payment-matches data))
(not (:payment data))
is-admin?)
[potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]
(and (not (seq (:potential-payment-matches data)))
(not (seq (:potential-transaction-rule-matches data))))
[:div
[field "Vendor"
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
:match->text :name
:type "typeahead-entity"
:auto-focus true
:field [:vendor]
:disabled (or (boolean (:payment data))
should-disable-for-client?)}]]
[field nil
[expense-accounts-field
{:type "expense-accounts"
:field [:accounts]
:max (Math/abs (js/parseFloat (:amount data)))
:descriptor "credit account"
:disabled (or (boolean (:payment data))
should-disable-for-client?)
:locations locations}]]
(and (not (seq (:potential-payment-matches data)))
(not (seq (:potential-transaction-rule-matches data))))
[:div
(field "Vendor"
[typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors])
:match->text :name
:type "typeahead-entity"
:auto-focus true
:field [:vendor]
:disabled (or (boolean (:payment data))
should-disable-for-client?)}])
(field nil
[expense-accounts-field
{:type "expense-accounts"
:field [:accounts]
:max (Math/abs (js/parseFloat (:amount data)))
:descriptor "credit account"
:disabled (or (boolean (:payment data))
should-disable-for-client?)
:locations locations}])
[field "Approval Status"
[button-radio
{:type "button-radio"
:field [:approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]
:disabled should-disable-for-client?}]]
(field "Approval Status"
[button-radio
{:type "button-radio"
:field [:approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]
:disabled should-disable-for-client?}])
[field "Forecasted-transaction"
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
:match->text :identifier
:type "typeahead-entity"
:field [:forecast-match]}]]
(field "Forecasted-transaction"
[typeahead-entity {:matches @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))])
:match->text :identifier
:type "typeahead-entity"
:field [:forecast-match]}])
[error-notification]
(when-not should-disable-for-client?
[submit-button "Save"])]
(error-notification)
(when-not should-disable-for-client?
(submit-button "Save"))]
:else
[:div
[field "Approval Status"
[button-radio
{:type "button-radio"
:field [:approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]
:disabled should-disable-for-client?}]]
:else
[:div
(field "Approval Status"
[button-radio
{:type "button-radio"
:field [:approval-status]
:options [[:unapproved "Unapproved"]
[:requires-feedback "Client Review"]
[:approved "Approved"]
[:excluded "Excluded from Ledger"]]
:disabled should-disable-for-client?}])
[field "Forecasted-transaction"
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
:match->text :identifier
:type "typeahead-entity"
:field [:forecast-match]}]]
(field "Forecasted-transaction"
[typeahead-entity {:matches (doto @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))]) println)
:match->text :identifier
:type "typeahead-entity"
:field [:forecast-match]}])
[error-notification]
(when-not should-disable-for-client?
[submit-button "Save"])])])])
(error-notification)
(when-not should-disable-for-client?
(submit-button "Save"))])]))])