From 8fb27d6c665e7e44ee4f8da694094faf386e8a0d Mon Sep 17 00:00:00 2001 From: Bryce Date: Sun, 17 Mar 2024 22:37:12 -0700 Subject: [PATCH] handwriting checks. --- src/clj/auto_ap/datomic/clients.clj | 48 ++-- src/clj/auto_ap/graphql/checks.clj | 1 - src/clj/auto_ap/ssr/invoices.clj | 366 ++++++++++++++++++---------- src/clj/auto_ap/ssr/utils.clj | 5 + 4 files changed, 269 insertions(+), 151 deletions(-) diff --git a/src/clj/auto_ap/datomic/clients.clj b/src/clj/auto_ap/datomic/clients.clj index a408e7ff..5e55b2f1 100644 --- a/src/clj/auto_ap/datomic/clients.clj +++ b/src/clj/auto_ap/datomic/clients.clj @@ -37,11 +37,10 @@ :bank-account/plaid-account [:plaid-account/name :db/id :plaid-account/number :plaid-account/balance] :bank-account/intuit-bank-account [:intuit-bank-account/name :intuit-bank-account/external-id :db/id] :bank-account/integration-status [:integration-status/message - :db/id - :integration-status/last-attempt - :integration-status/last-updated - {:integration-status/state [:db/ident]}]} - ]} + :db/id + :integration-status/last-attempt + :integration-status/last-updated + {:integration-status/state [:db/ident]}]}]} {:yodlee-provider-account/_client [*]} {:plaid-item/_client [*]} {:client/emails [:db/id :email-contact/email :email-contact/description]}]) @@ -61,7 +60,7 @@ (fn [bas] (map (fn [i ba] (-> ba - (update :bank-account/type :db/ident ) + (update :bank-account/type :db/ident) (update-in [:bank-account/integration-status :integration-status/state] :db/ident) (update-in [:bank-account/integration-status :integration-status/last-attempt] #(some-> % coerce/to-date-time)) (update-in [:bank-account/integration-status :integration-status/last-updated] #(some-> % coerce/to-date-time)) @@ -71,9 +70,9 @@ (defn get-all [] (->> (dc/q '[:find (pull ?e r) :in $ r - :where [?e :client/name]] - (dc/db conn) - full-read) + :where [?e :client/name]] + (dc/db conn) + full-read) (map first) (map cleanse))) @@ -98,23 +97,23 @@ (map cleanse))) (defn get-by-id [id] - (->> - (dc/pull (dc/db conn ) - full-read - id) + (->> + (dc/pull (dc/db conn) + full-read + id) (cleanse))) (defn code->id [code] - (->> + (->> (dc/q '[:find ?e - :in $ ?code - :where [?e :client/code ?code]] + :in $ ?code + :where [?e :client/code ?code]] (dc/db conn) code) (first) (first))) (defn best-match [identifier] - (when (and identifier (not-empty identifier)) + (when (and identifier (not-empty identifier)) (some-> (solr/query solr/impl "clients" {"query" (format "_text_:\"%s\"" (str/upper-case (solr/escape identifier))) "fields" "id"}) @@ -126,7 +125,7 @@ (defn exact-match [identifier] - (when (and identifier (not-empty identifier)) + (when (and identifier (not-empty identifier)) (some-> (solr/query solr/impl "clients" {"query" (format "exact:\"%s\"" (str/upper-case (solr/escape identifier))) "fields" "id"}) @@ -149,7 +148,6 @@ "code" (:client/code result) "exact" (map str/upper-case matches)}))) - (defn raw-graphql-ids [db args] (let [name-like-ids (cond (not (str/blank? (:name-like args))) (set (map (comp #(Long/parseLong %) :id) @@ -172,24 +170,24 @@ matching-ids) (set (map :db/id (:clients args)))) - + query (cond-> {:query {:find [] - :in ['$ ] + :in ['$] :where []} :args [db]} valid-ids (merge-query {:query {:in ['[?e ...]]} :args [(set valid-ids)]}) - + (:sort args) (add-sorter-fields {"name" ['[?e :client/name ?sort-name]]} args) true (merge-query {:query {:find ['?sort-default '?e] :where ['[?e :client/name ?sort-default]]}}))] (->> (query2 query) - (apply-sort-3 (update args :sort conj {:sort-key "default-2" :asc true})) - (apply-pagination args)))) + (apply-sort-3 (update args :sort conj {:sort-key "default-2" :asc true})) + (apply-pagination args)))) (defn graphql-results [ids db args] (let [results (->> (pull-many db full-read @@ -201,6 +199,6 @@ (defn get-graphql-page [args] (let [db (dc/db conn) {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] - + [(->> (graphql-results ids-to-retrieve db args)) matching-count])) diff --git a/src/clj/auto_ap/graphql/checks.clj b/src/clj/auto_ap/graphql/checks.clj index f101def9..a77df35a 100644 --- a/src/clj/auto_ap/graphql/checks.clj +++ b/src/clj/auto_ap/graphql/checks.clj @@ -423,7 +423,6 @@ nil)})) (defn get-payment-page [context args _] - (alog/info ::TEST) (let [[payments checks-count] (d-checks/get-graphql (-> args :filters (<-graphql) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index d72b15ce..62fa7de8 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -4,9 +4,14 @@ :refer [add-sorter-fields apply-pagination apply-sort-3 audit-transact conn merge-query observable-query pull-many]] - [auto-ap.graphql.checks :as gq-checks :refer [print-checks-internal]] + [auto-ap.datomic.bank-accounts :as d-bank-accounts] + [auto-ap.datomic.invoices :as d-invoices] + [auto-ap.graphql.checks :as gq-checks :refer [base-payment + invoice-payments + print-checks-internal + validate-belonging]] [auto-ap.graphql.utils :refer [assert-can-see-client - exception->4xx + assert-not-locked exception->4xx exception->notification extract-client-ids notify-if-locked]] [auto-ap.logging :as alog] @@ -15,6 +20,7 @@ [auto-ap.routes.payments :as payment-route] [auto-ap.routes.utils :refer [wrap-admin wrap-client-redirect-unauthenticated]] + [auto-ap.solr :as solr] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.components :as com] [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]] @@ -29,10 +35,13 @@ :refer [apply-middleware-to-all-handlers clj-date-schema dissoc-nil-transformer entity-id html-response main-transformer modal-response money ref->enum-schema - strip wrap-entity wrap-merge-prior-hx wrap-schema-enforce]] + round-money strip wrap-entity wrap-merge-prior-hx + wrap-schema-enforce]] [auto-ap.time :as atime] + [auto-ap.utils :refer [by dollars=]] [bidi.bidi :as bidi] [clj-time.coerce :as coerce] + [clj-time.core :as time] [clojure.string :as str] [datomic.api :as dc] [hiccup.util :as hu] @@ -112,6 +121,7 @@ (def default-read '[:db/id :invoice/invoice-number :invoice/total + :invoice/outstanding-balance :invoice/source-url [:invoice/date :xform clj-time.coerce/from-date] @@ -446,12 +456,15 @@ (com/pill {:color :secondary} "+ " (dec (count expense-accounts)) " more")])])} - {:key "total" - :name "Total" - :sort-key "total" + {:key "outstanding" + :name "Outstanding" + :sort-key "outstanding-balance" :class "text-right" - :render (fn [{:invoice/keys [total]}] - (some->> total (format "$%,.2f")))} + :render (fn [{:invoice/keys [outstanding-balance total]}] + [:div + (some->> outstanding-balance (format "$%,.2f")) + (when-not (dollars= outstanding-balance total) + [:div.text-xs.text-gray-400 (format "of $%,.2f" total)])])} {:key "links" :name "Links" :show-starting "lg" @@ -594,40 +607,61 @@ updated-count (count ids))})}))) +;; TODO +;; voiding invoice - should you allow if there are ANY payments? i think no +;; Do filtering of invoices on the dialog. Show a little banner if the total count doesn't match +;; Allow for paying balances from set of invoices for one vendor +;; Allow for credits, just highlight it +;; Entering 0.00 payment should just remove it from the set +;; Better text to link to pdfs +;; include hint about margins from the dialog +;; refresh the window after printing +;; Handwritten date +;; filter only for unlocked +;; filter only for unpaid + (defn does-amount-exceed-outstanding? [amount outstanding-balance] - (or (and (> outstanding-balance 0) - (> amount outstanding-balance)) - (and (> outstanding-balance 0) - (<= amount 0)) - (and (< outstanding-balance 0) - (< amount outstanding-balance)) - (and (< outstanding-balance 0) - (>= amount 0)))) + (let [outstanding-balance (round-money outstanding-balance) + amount (round-money amount)] + (or (and (> outstanding-balance 0) + (> amount outstanding-balance)) + (and (> outstanding-balance 0) + (<= amount 0)) + (and (< outstanding-balance 0) + (< amount outstanding-balance)) + (and (< outstanding-balance 0) + (>= amount 0))))) + (def payment-form-schema - (mc/schema [:map - [:client entity-id] - [:invoices [:and - [:vector {:coerce? true} - [:map - [:invoice-id entity-id] - [:amount money]]] - [:fn {:error/message "All payments must not exceed their outstanding balance."} - (fn [invoices] - (let [outstanding-balances (->> (dc/q '[:find ?i ?ob - :in $ [?i ...] - :where [?i :invoice/outstanding-balance ?ob]] - (dc/db conn) - (map :invoice-id invoices)) - (into {}))] - (every? #(not (does-amount-exceed-outstanding? (:amount %) (outstanding-balances (:invoice-id %)))) - invoices)))]]] - [:bank-account entity-id] - [:mode [:enum :simple :advanced]] - [:method [:enum :debit :print-check :cash :handwrite-check]]])) + (mc/schema + [:map + [:client entity-id] + [:invoices [:and + [:vector {:coerce? true} + [:map + [:invoice-id entity-id] + [:amount money]]] + [:fn {:error/message "All payments must not exceed their outstanding balance."} + (fn [invoices] + (let [outstanding-balances (->> (dc/q '[:find ?i ?ob + :in $ [?i ...] + :where [?i :invoice/outstanding-balance ?ob]] + (dc/db conn) + (map :invoice-id invoices)) + (into {}))] + (every? (fn [%] + (not (does-amount-exceed-outstanding? (:amount %) (outstanding-balances (:invoice-id %))))) + invoices)))]]] + [:has-warning? :boolean] + [:bank-account entity-id] + [:check-number {:optional true} + :int] + [:mode [:enum :simple :advanced]] + [:method [:enum :debit :print-check :cash :handwrite-check]]])) -(defn bank-account-card-base [{:keys [bg-color text-color icon bank-account]}] - [:div {:class "w-[30em] cursor-move"} +(defn bank-account-card-base [{:keys [bg-color text-color icon bank-account can-handwrite?]}] + [:div {:class "w-[30em]"} (com/card {:class "w-full"} [:div.flex.items-stretch {:x-data (hx/json {:chosen false :popper nil}) @@ -685,7 +719,8 @@ {:from (mm/encode-step-key :choose-method) :to (mm/encode-step-key :payment-details)})} "Debit")) - (when (= :bank-account-type/check (:bank-account/type bank-account)) + (when (and (= :bank-account-type/check (:bank-account/type bank-account)) + can-handwrite?) (com/button {:minimal-loading? true :hx-vals (hx/json {"step-params[bank-account]" (:db/id bank-account) "step-params[method]" "handwrite-check"}) @@ -695,29 +730,39 @@ "Handwrite check"))]]])]) -(defmulti bank-account-card (comp :bank-account/type)) -(defmethod bank-account-card :bank-account-type/cash [bank-account] +(defmulti bank-account-card (fn [ba _] + (:bank-account/type ba))) +(defmethod bank-account-card :bank-account-type/cash [bank-account can-handwrite?] (bank-account-card-base {:bg-color "bg-green-50" :text-color "text-green-600" :icon svg/dollar - :bank-account bank-account})) + :bank-account bank-account + :can-handwrite? can-handwrite?})) (defmethod bank-account-card :bank-account-type/credit - [bank-account] + [bank-account can-handwrite?] (bank-account-card-base {:bg-color "bg-purple-50" :text-color "text-purple-600" :icon svg/credit-card - :bank-account bank-account})) + :bank-account bank-account + :can-handwrite? can-handwrite?})) (defmethod bank-account-card - :bank-account-type/check [bank-account] + :bank-account-type/check [bank-account can-handwrite?] (bank-account-card-base {:bg-color "bg-blue-50" :text-color "text-blue-600" :icon svg/check - :bank-account bank-account})) + :bank-account bank-account + :can-handwrite? can-handwrite?})) +(defn can-handwrite? [invoices] + (let [selected-vendors (set (map (comp :db/id :invoice/vendor) invoices))] + (and + (= 1 (count selected-vendors)) + (> (reduce + 0 (map :invoice/outstanding-balance invoices)) 0.0)))) + (defrecord ChoosePaymentMethodModal [linear-wizard] mm/ModalWizardStep @@ -732,29 +777,24 @@ (step-schema [_] (mut/select-keys (mm/form-schema linear-wizard) #{:bank-account :method})) - (render-step [this request] - (mm/default-render-step - linear-wizard this - :head [:div.p-2 "Pay " (count (:invoices (:snapshot (:multi-form-state request)))) " invoices"] - :body (mm/default-step-body - {} - (let [bank-accounts (->> (dc/q '[:find (pull ?ba [:bank-account/name :bank-account/sort-order :bank-account/visible - :bank-account/bank-name - :db/id - {[:bank-account/type :xform iol-ion.query/ident] [:db/ident]}]) - :in $ ?c - :where [?c :client/bank-accounts ?ba]] - (dc/db conn) - (:client (:snapshot (:multi-form-state request)))) ;; TODO - (map first) - (sort-by :bank-account/sort-order))] + (render-step + [this request] + (let [invoices (:invoices (:snapshot (:multi-form-state request))) + can-handwrite? (can-handwrite? (map (comp (:invoice-by-id linear-wizard) :invoice-id) invoices))] + (mm/default-render-step + linear-wizard this + :head [:div.p-2.inline-flex.gap-2.items-center "Pay " (count invoices) " invoices" + (when (:has-warning? (:snapshot (:multi-form-state request))) + (com/pill {:color :yellow} + "Some of the selected invoices may be locked or paid."))] + :body (mm/default-step-body + {} [:div.flex.flex-col.space-y-2 - (for [ba bank-accounts] - (bank-account-card ba))])) - :footer - nil - #_(mm/default-step-footer linear-wizard this :validation-route ::route/navigate) - :validation-route ::route/pay-wizard-navigate))) + (for [ba (:bank-accounts linear-wizard)] + (bank-account-card ba can-handwrite?))]) + :footer + nil + :validation-route ::route/pay-wizard-navigate)))) (defrecord PaymentDetailsStep [linear-wizard] mm/ModalWizardStep @@ -767,7 +807,7 @@ []) (step-schema [_] - (mut/select-keys (mm/form-schema linear-wizard) #{:invoices})) + (mut/select-keys (mm/form-schema linear-wizard) #{:invoices :check-number})) (render-step [this request] (mm/default-render-step @@ -776,10 +816,21 @@ :body (mm/default-step-body {} [:div {} + (if (= :handwrite-check (:method (:snapshot (:multi-form-state request)))) + (fc/with-field :check-number + (com/validated-field + {:errors (fc/field-errors) + :label "Check number"} + (com/int-input {:value (fc/field-value) + :name (fc/field-name) + :error? (fc/field-errors) + :placeholder "10001"})))) (com/radio-list {:x-model "mode" :name "step-params[mode]" :options [{:value "simple" - :content "Pay in full"} + :content (format "Pay in full ($%,.2f)" (reduce + 0.0 + (map (comp :invoice/outstanding-balance (:invoice-by-id linear-wizard) :invoice-id) + (:invoices (:snapshot (:multi-form-state request))))))} {:value "advanced" :content "Customize payments"}]}) [:div.space-y-4 (hx/alpine-appear {:x-show "mode==\"advanced\""}) @@ -820,22 +871,39 @@ :validation-route ::route/pay-wizard-navigate))) +(defn add-handwritten-check [request wizard snapshot] + (let [snapshot (assoc snapshot :date (time/now)) + invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic + bank-account-id (:bank-account snapshot) + bank-account (d-bank-accounts/get-by-id bank-account-id) + _ (doseq [invoice invoices] + (assert-can-see-client (:identity request) (:invoice/client invoice))) + client-id (:db/id (:invoice/client (first invoices))) + _ (validate-belonging (:db/id (:client/_bank-accounts bank-account)) invoices bank-account) + _ (assert-not-locked client-id (:date snapshot)) + invoice-payment-lookup (by :invoice-id :amount (:invoices snapshot)) + base-payment (base-payment invoices + (:invoice/vendor (first invoices)) + (:invoice/client (first invoices)) + bank-account + :payment-type/check + 0 + invoice-payment-lookup)] + (let [result (audit-transact + (into [(assoc base-payment + :payment/type :payment-type/check + :payment/status :payment-status/pending + :payment/check-number (:check-number snapshot) + :payment/date (coerce/to-date (:date snapshot)))] + (invoice-payments invoices invoice-payment-lookup)) + (:identity request))] + (doseq [[_ i] (:tempids result)] + (solr/touch-with-ledger i))))) -(defrecord PayWizard [form-params current-step entity] +(defrecord PayWizard [form-params current-step invoice-by-id] mm/LinearModalWizard (hydrate-from-request [this request] - this - #_(assoc this :entity (:entity request))) - (navigate [this step-key] - (assoc this :current-step step-key)) - (get-current-step [this] - (if current-step - (mm/get-step this current-step) - (mm/get-step this :choose-method))) - (render-wizard [this {:keys [multi-form-state] :as request}] - - ;; TODO should this be customized based off the selections they make? (let [invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id] :invoice/client [:db/id]} :invoice/outstanding-balance @@ -843,14 +911,37 @@ :db/id]) :in $ [?i ...]] (dc/db conn) - (map :invoice-id (get-in request [:multi-form-state :step-params :invoices]))) + (map :invoice-id (get-in request [:multi-form-state :snapshot :invoices]))) (map first) (sort-by (juxt (comp :invoice/vendor :vendor/name) - :invoice/invoice-number))) - request (update-in request [:multi-form-state :step-params :invoices] + :invoice/invoice-number)))] + (assoc this :invoice-by-id (by :db/id invoices) + :bank-accounts (->> (dc/q '[:find (pull ?ba [:bank-account/name :bank-account/sort-order :bank-account/visible + :bank-account/bank-name + :db/id + {[:bank-account/type :xform iol-ion.query/ident] [:db/ident]}]) + :in $ ?c + :where [?c :client/bank-accounts ?ba]] + (dc/db conn) + (:client (:snapshot (:multi-form-state request)))) + (map first) + (sort-by :bank-account/sort-order))))) + (navigate [this step-key] + (assoc this :current-step step-key)) + (get-current-step [this] + (if current-step + (mm/get-step this current-step) + (mm/get-step this :choose-method))) + (render-wizard [this {:keys [multi-form-state] :as request}] + (let [request (update-in request [:multi-form-state :step-params :invoices] (fn [form-invoices] - (mapv (fn [form-invoice i] - (assoc form-invoice :invoice i)) form-invoices invoices)))] + (->> form-invoices + (map (fn [form-invoice] + (assoc form-invoice :invoice ((:invoice-by-id this) (:invoice-id form-invoice))))) + (sort-by + (juxt (comp :vendor/name :invoice/vendor :invoice) + (comp :invoice/invoice-number :invoice))) + (into []))))] (mm/default-render-wizard this request :form-params @@ -858,9 +949,9 @@ (assoc :hx-post (str (bidi/path-for ssr-routes/only-routes ::route/pay-submit))) (assoc :x-data (hx/json {:mode (some-> multi-form-state - :step-params - :mode - name)})))))) + :step-params + :mode + name)})))))) (steps [_] [:choose-method @@ -877,31 +968,37 @@ (get {:bank-account (->ChoosePaymentMethodModal this)} (first step-key))))) (form-schema [_] payment-form-schema) - (submit [_ {:keys [multi-form-state request-method identity] :as request}] + (submit [this {:keys [multi-form-state request-method identity] :as request}] (let [snapshot (mc/decode payment-form-schema (:snapshot multi-form-state) mt/strip-extra-keys-transformer) + _ (exception->4xx + #(if (= :handwrite-check (:method snapshot)) + (when (or (not (some? (:check-number snapshot))) + (= "" (:check-number snapshot))) + (throw (Exception. "Check number is required"))) + true)) result (exception->4xx - #(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i) - :amount (:amount i)}) - (:invoices snapshot)) - (:client snapshot) - (:bank-account snapshot) - (cond (= :print-check (:method snapshot)) - :payment-type/check - (= :debit (:method snapshot)) - :payment-type/debit - (= :cash (:method snapshot)) - :payment-type/cash - :else :payment-type/debit) - identity))] - (alog/info ::printed :result result) + #(if (= :handwrite-check (:method snapshot)) ;; TODO validation for single vendor again? + (add-handwritten-check request this snapshot) + (print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i) + :amount (:amount i)}) + (:invoices snapshot)) + (:client snapshot) + (:bank-account snapshot) + (cond (= :print-check (:method snapshot)) + :payment-type/check + (= :debit (:method snapshot)) + :payment-type/debit + (= :cash (:method snapshot)) + :payment-type/cash + :else :payment-type/debit) ;; TODO might not be right + identity)))] (modal-response (com/modal {} (com/modal-card-advanced {:class "transition duration-300 ease-in-out htmx-swapping:-translate-x-2/3 htmx-swapping:opacity-0 htmx-swapping:scale-0 htmx-added:translate-x-2/3 htmx-added:opacity-0 htmx-added:scale-0 scale-100 translate-x-0 opacity-100"} - (com/modal-body {} [:div.flex.flex-col.mt-4.space-y-4.items-center [:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300.animate-gg @@ -931,26 +1028,45 @@ (mm/wrap-wizard pay-wizard) (mm/wrap-init-multi-form-state (fn [request] + (exception->notification ;; TODO handle locked - (let [selected-ids (selected->ids request (:query-params request)) - invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id] - :invoice/client [:db/id]} - :invoice/outstanding-balance - :invoice/invoice-number - :db/id]) - :in $ [?i ...]] - (dc/db conn) - selected-ids) - (map first) - (sort-by (juxt (comp :invoice/vendor :vendor/name) - :invoice/invoice-number)))] - (mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i) - :amount (:invoice/outstanding-balance i)}) - invoices) - :mode :simple - :client (-> invoices first :invoice/client :db/id)} - [] - {:mode :simple}))))) + #(let [selected-ids (selected->ids request (:query-params request)) + selected-ids (->> (dc/q '[:find ?i + :in $ [?i ...] + :where [?i :invoice/status :invoice-status/unpaid] + [?i :invoice/client ?c] + [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu] + [?i :invoice/date ?d] + [(>= ?d ?lu)]] + (dc/db conn) + selected-ids) + (map first)) + _ (when (= 0 (count selected-ids)) + (throw (ex-info "No selected invoices are applicable for payment" {:type :notification}) )) + + has-warning? (and (:selected (:query-params request)) + (not= (count selected-ids) + (count (:selected (:query-params request))))) + invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id] + :invoice/client [:db/id]} + :invoice/outstanding-balance + :invoice/invoice-number + :db/id]) + :in $ [?i ...]] + (dc/db conn) + selected-ids) + (map first) + (sort-by (juxt (comp :invoice/vendor :vendor/name) + :invoice/invoice-number)))] + (mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i) + :amount (:invoice/outstanding-balance i)}) + invoices) + :mode :simple + :client (-> invoices first :invoice/client :db/id) + :has-warning? (boolean has-warning?)} + [] + {:mode :simple + :has-warning? (boolean has-warning?)})))))) ::route/pay-submit (-> mm/submit-handler (mm/wrap-wizard pay-wizard) diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index 2b63f0e4..ec881314 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -557,4 +557,9 @@ {:x 87} main-transformer)) +(defn round-money [d] + (with-precision 2 + (double (.setScale (bigdec d) 2 java.math.RoundingMode/HALF_UP)))) + +