Validations for new invoices
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
(ns auto-ap.ssr.invoice.new-invoice-wizard
|
||||
(:require [auto-ap.datomic
|
||||
:refer [conn pull-attr]]
|
||||
:refer [audit-transact conn pull-attr pull-ref]]
|
||||
[auto-ap.datomic.accounts :as d-accounts]
|
||||
[auto-ap.datomic.invoices :as d-invoices]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client
|
||||
assert-not-locked exception->4xx]]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.routes.invoice :as route]
|
||||
[auto-ap.routes.utils
|
||||
:refer [wrap-client-redirect-unauthenticated]]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
|
||||
[auto-ap.ssr.components :as com]
|
||||
@@ -16,13 +20,54 @@
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [->db-id apply-middleware-to-all-handlers clj-date-schema
|
||||
entity-id html-response money wrap-schema-enforce]]
|
||||
entity-id form-validation-error html-response money strip
|
||||
wrap-schema-enforce]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]
|
||||
[datomic.api :as dc]
|
||||
[iol-ion.query :refer [dollars=]]
|
||||
[malli.core :as mc]
|
||||
[malli.util :as mut]))
|
||||
[malli.util :as mut]
|
||||
[manifold.deferred :as d]))
|
||||
|
||||
(defn get-vendor [vendor-id]
|
||||
(dc/pull
|
||||
(dc/db conn)
|
||||
[:vendor/terms
|
||||
:vendor/automatically-paid-when-due
|
||||
{:vendor/default-account d-accounts/default-read
|
||||
:vendor/account-overrides
|
||||
[:vendor-account-override/client
|
||||
{:vendor-account-override/account d-accounts/default-read}]}
|
||||
{:vendor/terms-overrides
|
||||
[:vendor-terms-override/client :vendor-terms-override/terms]}]
|
||||
vendor-id))
|
||||
|
||||
(defn check-invoice-expense-account-location [iea]
|
||||
(let [account-location (pull-attr (dc/db conn) :account/location (:invoice-expense-account/account iea))]
|
||||
(when (and (seq account-location)
|
||||
(not= (:invoice-expense-account/location iea)
|
||||
account-location))
|
||||
(throw (ex-info "Exception." {:type (str "expected " account-location)})))
|
||||
(when (and (empty? account-location)
|
||||
(= "A" (:invoice-expense-account/location iea)))
|
||||
|
||||
(throw (ex-info "Exception." {:type "'A' not allowed"})))
|
||||
true))
|
||||
|
||||
(defn check-allowance [account-id]
|
||||
(let [allowance (:account/invoice-allowance (dc/pull (dc/db conn) '[{[:account/invoice-allowance :xform iol-ion.query/ident]
|
||||
[:db/ident]}]
|
||||
account-id))]
|
||||
(not= :allowance/denied
|
||||
allowance)))
|
||||
|
||||
(defn check-vendor-default-account [vendor-id]
|
||||
(some? (:vendor/default-account (get-vendor vendor-id))))
|
||||
|
||||
|
||||
|
||||
(def new-form-schema
|
||||
[:map
|
||||
@@ -30,13 +75,22 @@
|
||||
[:invoice/date clj-date-schema]
|
||||
[:invoice/due {:optional true} [:maybe clj-date-schema]]
|
||||
[:invoice/scheduled-payment {:optional true} [:maybe clj-date-schema]]
|
||||
[:invoice/vendor entity-id]
|
||||
[:invoice/vendor [:and entity-id
|
||||
[:fn {:error/message "Vendor is missing default expense account"}
|
||||
check-vendor-default-account]]]
|
||||
[:invoice/invoice-number [:string {:min 1 :decode/string strip}]]
|
||||
[:invoice/total money]
|
||||
[:invoice/expense-accounts
|
||||
[:vector {:coerce? true}
|
||||
[:map
|
||||
[:invoice-expense-account/account entity-id]
|
||||
[:invoice-expense-account/location :string]
|
||||
[:invoice-expense-account/amount money]]]]])
|
||||
[:and
|
||||
[:map
|
||||
[:invoice-expense-account/account [:and entity-id
|
||||
[:fn {:error/message "Not an allowed account."}
|
||||
check-allowance]]]
|
||||
[:invoice-expense-account/location :string]
|
||||
[:invoice-expense-account/amount money]]
|
||||
[:fn {:error/fn (fn [r x] (:type r))
|
||||
:error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]])
|
||||
|
||||
(defn clientize-vendor [{:vendor/keys [terms-overrides automatically-paid-when-due default-account account-overrides] :as vendor} client-id]
|
||||
(let [terms-override (->> terms-overrides
|
||||
@@ -68,18 +122,7 @@
|
||||
vendor))
|
||||
|
||||
|
||||
(defn get-vendor [vendor-id]
|
||||
(dc/pull
|
||||
(dc/db conn)
|
||||
[:vendor/terms
|
||||
:vendor/automatically-paid-when-due
|
||||
{:vendor/default-account d-accounts/default-read
|
||||
:vendor/account-overrides
|
||||
[:vendor-account-override/client
|
||||
{:vendor-account-override/account d-accounts/default-read}]}
|
||||
{:vendor/terms-overrides
|
||||
[:vendor-terms-override/client :vendor-terms-override/terms]}]
|
||||
vendor-id))
|
||||
|
||||
|
||||
(defrecord BasicDetailsStep [linear-wizard]
|
||||
mm/ModalWizardStep
|
||||
@@ -92,7 +135,7 @@
|
||||
[])
|
||||
|
||||
(step-schema [_]
|
||||
(mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment}))
|
||||
(mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment :invoice/total :invoice/invoice-number}))
|
||||
|
||||
(render-step [this request]
|
||||
(alog/peek ::check (:multi-form-state request))
|
||||
@@ -292,6 +335,7 @@
|
||||
(com/data-grid-cell {:class "align-top"}
|
||||
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))
|
||||
|
||||
|
||||
(defrecord AccountsStep [linear-wizard]
|
||||
mm/ModalWizardStep
|
||||
(step-name [_]
|
||||
@@ -342,6 +386,19 @@
|
||||
:invoice-expense-account/amount 100}]}))
|
||||
|
||||
|
||||
(defn assert-no-conflicting [{:invoice/keys [invoice-number client vendor]}]
|
||||
(when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice-number
|
||||
:invoice/vendor (->db-id vendor)
|
||||
:invoice/client (->db-id client)}))
|
||||
(form-validation-error (str "Invoice '" invoice-number "' already exists."))))
|
||||
|
||||
;; TODO warn on account usage based on allowance
|
||||
|
||||
(defn assert-invoice-amounts-add-up [{:keys [:invoice/expense-accounts :invoice/total]}]
|
||||
(let [expense-account-total (reduce + 0 (map (fn [x] (:invoice-expense-account/amount x)) expense-accounts))]
|
||||
(when-not (dollars= total expense-account-total)
|
||||
(form-validation-error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")))))
|
||||
|
||||
|
||||
(defrecord NewWizard2 [_ current-step]
|
||||
mm/LinearModalWizard
|
||||
@@ -373,17 +430,45 @@
|
||||
step-key)))
|
||||
(form-schema [_] new-form-schema)
|
||||
(submit [this {:keys [multi-form-state request-method identity] :as request}]
|
||||
|
||||
(let [invoice (:snapshot multi-form-state)
|
||||
client-id (->db-id (:invoice/client invoice))
|
||||
vendor-id (->db-id (:invoice/vendor invoice))
|
||||
transaction [:upsert-invoice (-> multi-form-state
|
||||
:snapshot
|
||||
(assoc :db/id "invoice")
|
||||
(assoc
|
||||
:invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state))
|
||||
:invoice/import-status :import-status/imported
|
||||
:invoice/status :invoice-status/unpaid)
|
||||
|
||||
(update :invoice/expense-accounts
|
||||
(fn [eas]
|
||||
(mapv (fn [ea]
|
||||
(-> ea
|
||||
(assoc :invoice-expense-account/amount
|
||||
(:invoice/total (:snapshot multi-form-state)))))
|
||||
eas)))
|
||||
(update :invoice/date coerce/to-date)
|
||||
(update :invoice/due coerce/to-date))]]
|
||||
|
||||
(assert-invoice-amounts-add-up invoice)
|
||||
(assert-no-conflicting invoice)
|
||||
(exception->4xx #(assert-can-see-client (:identity request) client-id))
|
||||
|
||||
(exception->4xx #(assert-not-locked client-id (:invoice/date invoice)))
|
||||
(let [transaction-result (audit-transact [transaction] (:identity request))]
|
||||
(solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"]))))
|
||||
(html-response [:div]
|
||||
:headers {"hx-trigger" "modalclose"})))
|
||||
:headers {"hx-trigger" "modalclose,invalidated"})))
|
||||
|
||||
(def new-wizard (->NewWizard2 nil nil))
|
||||
|
||||
|
||||
(defn initial-new-wizard-state [request]
|
||||
(mm/->MultiStepFormState {:TODO nil
|
||||
:invoice/date (time/plus (time/now) (time/days 12))}
|
||||
(mm/->MultiStepFormState {:invoice/date (time/now)}
|
||||
[]
|
||||
{:invoice/date (time/plus (time/now) (time/days 12))}))
|
||||
{:invoice/date (time/now)}))
|
||||
|
||||
(defn location-select [{{:keys [name account-id client-id value] :as qp} :query-params}]
|
||||
(html-response (location-select* {:name name
|
||||
|
||||
Reference in New Issue
Block a user