From ea7ad57da2e18df52063908be65b67401d806e51 Mon Sep 17 00:00:00 2001 From: Bryce Date: Mon, 25 Mar 2024 20:26:04 -0700 Subject: [PATCH] Proper validations and total row. --- src/clj/auto_ap/ssr/components/data_grid.clj | 9 +- src/clj/auto_ap/ssr/invoice/common.clj | 21 +++ .../ssr/invoice/new_invoice_wizard.clj | 132 ++++++++++++------ src/clj/auto_ap/ssr/invoices.clj | 22 +-- src/cljc/auto_ap/routes/invoice.cljc | 3 +- 5 files changed, 122 insertions(+), 65 deletions(-) create mode 100644 src/clj/auto_ap/ssr/invoice/common.clj diff --git a/src/clj/auto_ap/ssr/components/data_grid.clj b/src/clj/auto_ap/ssr/components/data_grid.clj index 13bf62b2..0a18808a 100644 --- a/src/clj/auto_ap/ssr/components/data_grid.clj +++ b/src/clj/auto_ap/ssr/components/data_grid.clj @@ -123,10 +123,11 @@ [:div {:class "flex items-center justify-center w-full h-full border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700 bg-opacity-50" } [:div {:class "px-3 py-1 text-xs font-medium leading-none text-center text-blue-800 bg-blue-200 rounded-full animate-pulse dark:bg-blue-900 dark:text-blue-200"} "loading..."]]])]) -(defn new-row- [{:keys [index colspan tr-params] :as params} & content] +(defn new-row- [{:keys [index colspan tr-params row-offset] :as params} & content] (row- (merge {:class "new-row" - :x-data (hx/json {:newRowIndex index}) + :x-data (hx/json {:newRowIndex index + :offset (or row-offset 0)}) } tr-params) (cell- {:colspan colspan @@ -135,10 +136,10 @@ (a-button- (merge (dissoc params :index :colspan) { - "@click" "$dispatch('newRow', {index: newRowIndex++})" + "@click" "$dispatch('newRow', {index: (newRowIndex++)})" :color :secondary :hx-trigger "newRow" - :hx-vals (hiccup/raw "js:{index: event.detail.index}") + :hx-vals (hiccup/raw "js:{index: event.detail.index }") :hx-target "closest .new-row" :hx-swap "beforebegin"}) content)]))) diff --git a/src/clj/auto_ap/ssr/invoice/common.clj b/src/clj/auto_ap/ssr/invoice/common.clj new file mode 100644 index 00000000..f73fa5d8 --- /dev/null +++ b/src/clj/auto_ap/ssr/invoice/common.clj @@ -0,0 +1,21 @@ +(ns auto-ap.ssr.invoice.common) + +(def default-read '[:db/id + :invoice/invoice-number + :invoice/total + :invoice/outstanding-balance + :invoice/source-url + + [:invoice/date :xform clj-time.coerce/from-date] + {:invoice/client [:client/code :db/id :client/name] + :invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id + :account/location + {:account/client-overrides [:account-client-override/name + {:account-client-override/client [:db/id]}]}]}] + [:transaction/_invoices :as :invoice/transaction] [:db/id] + [:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount + + {[:transaction/_payment :as :payment/transaction] [:db/id] + [:payment/status :xform iol-ion.query/ident] [:db/ident]}] + [:invoice/status :xform iol-ion.query/ident] [:db/ident] + :invoice/vendor [:vendor/name :db/id]}]) \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj index ba24c1c1..b5c5a83c 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -1,8 +1,9 @@ (ns auto-ap.ssr.invoice.new-invoice-wizard (:require [auto-ap.datomic - :refer [audit-transact conn pull-attr pull-ref]] + :refer [audit-transact conn pull-attr]] [auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.invoices :as d-invoices] + [auto-ap.ssr.invoice.common :refer [default-read]] [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked exception->4xx]] [auto-ap.logging :as alog] @@ -29,8 +30,7 @@ [datomic.api :as dc] [iol-ion.query :refer [dollars=]] [malli.core :as mc] - [malli.util :as mut] - [manifold.deferred :as d])) + [malli.util :as mut])) (defn get-vendor [vendor-id] (dc/pull @@ -88,7 +88,7 @@ [:fn {:error/message "Not an allowed account."} check-allowance]]] [:invoice-expense-account/location :string] - [:invoice-expense-account/amount money]] + [:invoice-expense-account/amount [:double {:min 0}]]] [:fn {:error/fn (fn [r x] (:type r)) :error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]]) @@ -122,6 +122,7 @@ vendor)) +;; TODO account / vendor search should use allowances (defrecord BasicDetailsStep [linear-wizard] @@ -330,11 +331,22 @@ (com/validated-field {:errors (fc/field-errors)} (com/money-input {:name (fc/field-name) - :class "w-16" + :class "w-16 amount-field" :value (fc/field-value)})))) (com/data-grid-cell {:class "align-top"} (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))) +(defn invoice-expense-account-total* [request] + (format "$%,.2f" (->> (-> request + :multi-form-state + :step-params + :invoice/expense-accounts) + (map (fnil :invoice-expense-account/amount 0.0)) + (filter number?) + (reduce + 0.0)))) + +(defn invoice-expense-account-total [request] + (html-response (invoice-expense-account-total* request))) (defrecord AccountsStep [linear-wizard] mm/ModalWizardStep @@ -349,7 +361,7 @@ (step-schema [_] (mut/select-keys (mm/form-schema linear-wizard) #{:invoice/expense-accounts})) - (render-step [this {{:keys [snapshot]} :multi-form-state :as request}] + (render-step [this {{:keys [snapshot] :as multi-form-state} :multi-form-state :as request}] (alog/peek ::mfs (:step-params (:multi-form-state request))) (mm/default-render-step linear-wizard this @@ -363,27 +375,50 @@ {:errors (fc/field-errors)} (com/data-grid {:headers [(com/data-grid-header {} "Account") (com/data-grid-header {:class "w-32"} "Location") - (com/data-grid-header {:class "w-16"} "%") + (com/data-grid-header {:class "w-16"} "$") (com/data-grid-header {:class "w-16"})]} (fc/cursor-map #(invoice-expense-account-row* {:value % :client-id (:invoice/client snapshot)})) + (com/data-grid-new-row {:colspan 4 :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard-new-account) + :row-offset 0 :index (count (fc/field-value)) :tr-params {:hx-vals (hx/json {:client-id (:invoice/client snapshot)})}} - "New account"))))]) + "New account") + (com/data-grid-row {} + (com/data-grid-cell {}) + (com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "TOTAL"]) + (com/data-grid-cell {:id "total" + :class "text-right" + :hx-trigger "change from:closest form target:.amount-field" + :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total) + :hx-target "this" + :hx-swap "innerHTML"} + (invoice-expense-account-total* request)) + (com/data-grid-cell {})) + (com/data-grid-row {} + + (com/data-grid-cell {}) + (com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "INVOICE TOTAL"]) + (com/data-grid-cell {:class "text-right"} + (format "$%,.2f" (:invoice/total snapshot))) + (com/data-grid-cell {})))))]) :footer (mm/default-step-footer linear-wizard this :validation-route ::route/new-wizard-navigate) :validation-route ::route/new-wizard-navigate)) mm/Initializable (init-step-params [_ current request] - {:invoice/expense-accounts [{:db/id "123" - :invoice-expense-account/location "Shared" - :invoice-expense-account/account (:db/id (:vendor/default-account (clientize-vendor (get-vendor (->db-id (:invoice/vendor (:snapshot current)))) - (->db-id (:invoice/client (:snapshot current)))))) - :invoice-expense-account/amount 100}]})) + (alog/peek ::step (:step-params current)) + (if (not (seq (:invoice/expense-accounts (:step-params current)))) + (assoc (:step-params current) :invoice/expense-accounts [{:db/id "123" + :invoice-expense-account/location "Shared" + :invoice-expense-account/account (:db/id (:vendor/default-account (clientize-vendor (get-vendor (->db-id (:invoice/vendor (:snapshot current)))) + (->db-id (:invoice/client (:snapshot current)))))) + :invoice-expense-account/amount (:invoice/total (:step-params current))}]) + (:step-params current)))) (defn assert-no-conflicting [{:invoice/keys [invoice-number client vendor]}] @@ -431,36 +466,50 @@ (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) + (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))]] + (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)) + (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,invalidated"}))) + (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 + (@(resolve 'auto-ap.ssr.invoices/row*) + + identity + (dc/pull (dc/db conn) default-read (get-in transaction-result [:tempids "invoice"])) + {:flash? true + :request request}) + :headers {"hx-trigger" "modalclose" + "hx-retarget" "#entity-table tbody" + "hx-reswap" "afterbegin" + + #_(format "#entity-table tr[data-id=\"%d\"]" (get-in transaction-result [:tempids "invoice"]))}))) + + + #_(html-response [:div] + :headers {"hx-trigger" "modalclose,invalidated"}))) (def new-wizard (->NewWizard2 nil nil)) @@ -548,6 +597,9 @@ (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) (wrap-nested-form-params)) + ::route/expense-account-total (-> invoice-expense-account-total + (mm/wrap-wizard new-wizard) + (mm/wrap-decode-multi-form-state)) ::route/location-select (-> location-select (wrap-schema-enforce :query-schema [:map [:name :string] diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 2092a942..a7dc751a 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -3,7 +3,7 @@ [auto-ap.datomic :refer [add-sorter-fields apply-pagination apply-sort-3 audit-transact conn merge-query observable-query - pull-attr pull-many]] + pull-many]] [auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.bank-accounts :as d-bank-accounts] [auto-ap.datomic.invoices :as d-invoices] @@ -23,7 +23,6 @@ :refer [wrap-admin 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] [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]] [auto-ap.ssr.components.multi-modal :as mm] @@ -31,6 +30,7 @@ [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]] [auto-ap.ssr.hiccup-helper :as hh] [auto-ap.ssr.hx :as hx] + [auto-ap.ssr.invoice.common :refer [default-read]] [auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard] [auto-ap.ssr.pos.common :refer [date-range-field*]] [auto-ap.ssr.svg :as svg] @@ -120,25 +120,7 @@ (exact-match-id* request)]]) -(def default-read '[:db/id - :invoice/invoice-number - :invoice/total - :invoice/outstanding-balance - :invoice/source-url - [:invoice/date :xform clj-time.coerce/from-date] - {:invoice/client [:client/code :db/id :client/name] - :invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id - :account/location - {:account/client-overrides [:account-client-override/name - {:account-client-override/client [:db/id]}]}]}] - [:transaction/_invoices :as :invoice/transaction] [:db/id] - [:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount - - {[:transaction/_payment :as :payment/transaction] [:db/id] - [:payment/status :xform iol-ion.query/ident] [:db/ident]}] - [:invoice/status :xform iol-ion.query/ident] [:db/ident] - :invoice/vendor [:vendor/name :db/id]}]) (defn fetch-ids [db {:keys [query-params route-params] :as request}] (let [valid-clients (extract-client-ids (:clients request) diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index ee1b13e8..ce5524e4 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -9,7 +9,8 @@ "/scheduled-payment-date" ::scheduled-payment-date "/navigate" ::new-wizard-navigate "/account/new" ::new-wizard-new-account - "/account/location-select" ::location-select} + "/account/location-select" ::location-select + "/total" ::expense-account-total} "/pay-button" ::pay-button "/pay" {:get ::pay-wizard "/navigate" ::pay-wizard-navigate