Add vendor pre-population for bulk code and individual edit forms
- Add vendor-changed HTMX handlers for both bulk code and individual edit - Pre-populate default account at 100% when vendor is selected and no accounts exist - Fix render-accounts-section to render from step-params correctly - Change bulk code vendor-changed from hx-get to hx-post to include form data - Add routes for vendor-changed endpoints - Update e2e tests to cover vendor pre-population - Run lein cljfmt fix across codebase
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
(ns auto-ap.ssr.invoices
|
||||
(:require
|
||||
[auto-ap.datomic
|
||||
:refer [add-sorter-fields apply-pagination apply-sort-3
|
||||
audit-transact audit-transact-batch conn merge-query
|
||||
observable-query pull-many]]
|
||||
:refer [add-sorter-fields apply-pagination apply-sort-3
|
||||
audit-transact audit-transact-batch conn merge-query
|
||||
observable-query pull-many]]
|
||||
[auto-ap.datomic.accounts :as d-accounts]
|
||||
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
@@ -23,7 +23,7 @@
|
||||
[auto-ap.routes.payments :as payment-route]
|
||||
[auto-ap.routes.transactions :as transaction-routes]
|
||||
[auto-ap.routes.utils
|
||||
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
||||
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
||||
[auto-ap.rule-matching :as rm]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
@@ -41,13 +41,13 @@
|
||||
[auto-ap.ssr.components.date-range :as dr]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [apply-middleware-to-all-handlers assert-schema
|
||||
clj-date-schema dissoc-nil-transformer entity-id
|
||||
form-validation-error html-response main-transformer
|
||||
many-entity modal-response money percentage
|
||||
ref->enum-schema round-money strip wrap-entity
|
||||
wrap-implied-route-param wrap-merge-prior-hx
|
||||
wrap-schema-enforce]]
|
||||
:refer [apply-middleware-to-all-handlers assert-schema
|
||||
clj-date-schema dissoc-nil-transformer entity-id
|
||||
form-validation-error html-response main-transformer
|
||||
many-entity modal-response money percentage
|
||||
ref->enum-schema round-money strip wrap-entity
|
||||
wrap-implied-route-param wrap-merge-prior-hx
|
||||
wrap-schema-enforce]]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.utils :refer [by dollars-0? dollars=]]
|
||||
[bidi.bidi :as bidi]
|
||||
@@ -63,7 +63,6 @@
|
||||
[malli.util :as mut]
|
||||
[slingshot.slingshot :refer [try+]]))
|
||||
|
||||
|
||||
(defn exact-match-id* [request]
|
||||
(if (nat-int? (:exact-match-id (:query-params request)))
|
||||
[:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"}
|
||||
@@ -105,9 +104,9 @@
|
||||
(:db/id (:client request))))
|
||||
:class "filter-trigger"}))
|
||||
(dr/date-range-field {:value {:start (:start-date (:query-params request))
|
||||
:end (:end-date (:query-params request))}
|
||||
:id "date-range"
|
||||
:apply-button? true})
|
||||
:end (:end-date (:query-params request))}
|
||||
:id "date-range"
|
||||
:apply-button? true})
|
||||
(com/field {:label "Check #"}
|
||||
(com/text-input {:name "check-number"
|
||||
:id "check-number"
|
||||
@@ -122,7 +121,7 @@
|
||||
:value (:invoice-number (:query-params request))
|
||||
:placeholder "e.g., ABC-456"
|
||||
:size :small}))
|
||||
|
||||
|
||||
(com/field {:label "Amount"}
|
||||
[:div.flex.space-x-4.items-baseline
|
||||
(com/money-input {:name "amount-gte"
|
||||
@@ -143,7 +142,6 @@
|
||||
:size :small})])
|
||||
(exact-match-id* request)]])
|
||||
|
||||
|
||||
(defn fetch-ids [db {:keys [query-params route-params] :as request}]
|
||||
(let [valid-clients (extract-client-ids (:clients request)
|
||||
(:client-id request)
|
||||
@@ -165,7 +163,6 @@
|
||||
(some-> (:start-date query-params) coerce/to-date)
|
||||
(some-> (:end-date query-params) coerce/to-date)]]}
|
||||
|
||||
|
||||
(:client-id query-params)
|
||||
(merge-query {:query {:in ['?client-id]
|
||||
:where ['[?e :invoice/client ?client-id]]}
|
||||
@@ -177,7 +174,6 @@
|
||||
'[?client-id :client/code ?client-code]]}
|
||||
:args [(:client-code query-params)]})
|
||||
|
||||
|
||||
(:start (:due-range query-params)) (merge-query {:query {:in '[?start-due]
|
||||
:where ['[?e :invoice/due ?due]
|
||||
'[(>= ?due ?start-due)]]}
|
||||
@@ -188,14 +184,13 @@
|
||||
'[(<= ?due ?end-due)]]}
|
||||
:args [(coerce/to-date (:end (:due-range query-params)))]})
|
||||
|
||||
|
||||
(:import-status query-params)
|
||||
(merge-query {:query {:in ['?import-status]
|
||||
:where ['[?e :invoice/import-status ?import-status]]}
|
||||
:args [(:import-status query-params)]})
|
||||
|
||||
(not (:import-status query-params))
|
||||
(merge-query {:query { :where ['[?e :invoice/import-status :import-status/imported]]} })
|
||||
(merge-query {:query {:where ['[?e :invoice/import-status :import-status/imported]]}})
|
||||
|
||||
(:status route-params)
|
||||
(merge-query {:query {:in ['?status]
|
||||
@@ -269,7 +264,6 @@
|
||||
(apply-sort-3 (assoc query-params :default-asc? false))
|
||||
(apply-pagination query-params))))
|
||||
|
||||
|
||||
(defn hydrate-results [ids db _]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
@@ -279,31 +273,30 @@
|
||||
refunds))
|
||||
|
||||
(defn sum-outstanding [ids]
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/outstanding-balance ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/outstanding-balance ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(defn sum-total-amount [ids]
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/total ?o]]
|
||||
}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/total ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(defn fetch-page [request]
|
||||
(let [db (dc/db conn)
|
||||
@@ -351,7 +344,6 @@
|
||||
(assoc-in [:query-params :start] 0)
|
||||
(assoc-in [:query-params :per-page] 250))))
|
||||
|
||||
|
||||
:else
|
||||
selected)]
|
||||
ids))
|
||||
@@ -369,21 +361,20 @@
|
||||
|
||||
(defn can-undo-autopayment [invoice]
|
||||
(try+
|
||||
(assert-can-undo-autopayment invoice)
|
||||
(assert-can-undo-autopayment invoice)
|
||||
true
|
||||
(catch [:type :warning] {}
|
||||
false)))
|
||||
|
||||
false)))
|
||||
|
||||
(defn pay-button* [params]
|
||||
(let [ids (:ids params)
|
||||
ids (if (seq ids)
|
||||
ids (if (seq ids)
|
||||
(map first
|
||||
(dc/q '[:find ?i
|
||||
:in $ [?i ...]
|
||||
:where (not [?i :invoice/scheduled-payment])]
|
||||
(dc/db conn)
|
||||
ids))
|
||||
(dc/q '[:find ?i
|
||||
:in $ [?i ...]
|
||||
:where (not [?i :invoice/scheduled-payment])]
|
||||
(dc/db conn)
|
||||
ids))
|
||||
ids)
|
||||
selected-client-count (if (seq ids)
|
||||
(ffirst
|
||||
@@ -417,18 +408,17 @@
|
||||
outstanding-balances)
|
||||
total (reduce + 0.0 vendor-totals)
|
||||
paying-credit? (and (> (count ids) 1)
|
||||
(= 1 (count vendor-totals))
|
||||
at-least-one-positive-payment
|
||||
(dollars-0? total))]
|
||||
|
||||
(= 1 (count vendor-totals))
|
||||
at-least-one-positive-payment
|
||||
(dollars-0? total))]
|
||||
|
||||
[:div (cond-> {:hx-target "this"
|
||||
|
||||
|
||||
:hx-trigger "click from:#pay-button"
|
||||
:x-tooltip "{allowHTML: true, content: () => $refs.template.innerHTML, appendTo: $root}"}
|
||||
paying-credit? (assoc :hx-post (bidi/path-for ssr-routes/only-routes ::route/pay-using-credit))
|
||||
(not paying-credit? ) (assoc :hx-get (bidi/path-for ssr-routes/only-routes
|
||||
::route/pay-wizard)))
|
||||
(not paying-credit?) (assoc :hx-get (bidi/path-for ssr-routes/only-routes
|
||||
::route/pay-wizard)))
|
||||
(com/button {:color :primary
|
||||
:id "pay-button"
|
||||
:disabled (or (= (count (:ids params)) 0)
|
||||
@@ -445,14 +435,13 @@
|
||||
(cond
|
||||
paying-credit?
|
||||
"Pay invoices using credit"
|
||||
|
||||
|
||||
(> (count ids) 0)
|
||||
|
||||
|
||||
(format "Pay %d invoices ($%,.2f)"
|
||||
(count ids)
|
||||
(or total 0.0))
|
||||
|
||||
|
||||
|
||||
(or (= 0 (count ids))
|
||||
(> selected-client-count 1))
|
||||
(list "Pay " (com/badge {} "!"))
|
||||
@@ -474,13 +463,11 @@
|
||||
:else
|
||||
[:div "Click to choose a bank account"])]]))
|
||||
|
||||
|
||||
(defn pay-button [request]
|
||||
(html-response
|
||||
(pay-button* {:ids (selected->ids request
|
||||
(:query-params request))})))
|
||||
|
||||
|
||||
;; TODO test as a real user
|
||||
(def grid-page
|
||||
(helper/build {:id "entity-table"
|
||||
@@ -493,9 +480,9 @@
|
||||
:oob-render
|
||||
(fn [request]
|
||||
[(assoc-in (dr/date-range-field {:value {:start (:start-date (:query-params request))
|
||||
:end (:end-date (:query-params request))}
|
||||
:id "date-range"
|
||||
:apply-button? true}) [1 :hx-swap-oob] true)
|
||||
:end (:end-date (:query-params request))}
|
||||
:id "date-range"
|
||||
:apply-button? true}) [1 :hx-swap-oob] true)
|
||||
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
|
||||
:query-schema query-schema
|
||||
:parse-query-params (fn [p]
|
||||
@@ -547,12 +534,11 @@
|
||||
:db/id (:db/id entity))}
|
||||
svg/undo))
|
||||
(when (and (can? (:identity request) {:subject :invoice :activity :edit})
|
||||
(can-undo-autopayment entity)
|
||||
)
|
||||
(can-undo-autopayment entity))
|
||||
(com/button {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
::route/undo-autopay
|
||||
:db/id (:db/id entity))}
|
||||
"Undo autopay"))])
|
||||
::route/undo-autopay
|
||||
:db/id (:db/id entity))}
|
||||
"Undo autopay"))])
|
||||
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
|
||||
"Invoices"]]
|
||||
@@ -573,7 +559,7 @@
|
||||
(= 1 (count (:client/locations (:client args))))))
|
||||
:render (fn [x] [:div.flex.items-center.gap-2 (-> x :invoice/client :client/name)
|
||||
(map #(com/pill {:color :primary} (-> % :invoice-expense-account/location))
|
||||
(:invoice/expense-accounts x)) ])}
|
||||
(:invoice/expense-accounts x))])}
|
||||
{:key "vendor"
|
||||
:name "Vendor"
|
||||
:sort-key "vendor"
|
||||
@@ -593,13 +579,12 @@
|
||||
:name "Due"
|
||||
:show-starting "xl" ;; xl:table-cell
|
||||
:render (fn [{:invoice/keys [due]}]
|
||||
(if-let [due-date (some-> due (atime/unparse-local atime/normal-date)) ]
|
||||
(let [
|
||||
today (time/now)
|
||||
(if-let [due-date (some-> due (atime/unparse-local atime/normal-date))]
|
||||
(let [today (time/now)
|
||||
[start end] (if (time/before? due today)
|
||||
[due today]
|
||||
[today due])
|
||||
i (time/interval start end )
|
||||
i (time/interval start end)
|
||||
days (if (time/before? due today)
|
||||
(- (time/in-days i))
|
||||
(time/in-days i))]
|
||||
@@ -607,23 +592,23 @@
|
||||
[:div.text-primary-700 "today"]
|
||||
(> days 0)
|
||||
[:div.text-primary-700 (format "in %d days", days)]
|
||||
:else
|
||||
[:div.text-red-700 (format "%d days ago", (- days))]))))}
|
||||
:else
|
||||
[:div.text-red-700 (format "%d days ago", (- days))]))))}
|
||||
{:key "status"
|
||||
:name "Status"
|
||||
:render (fn [{:invoice/keys [status scheduled-payment]}]
|
||||
(cond (= status :invoice-status/paid)
|
||||
(com/pill {:color :primary} "Paid")
|
||||
(= status :invoice-status/voided)
|
||||
(com/pill {:color :red} "Voided")
|
||||
|
||||
scheduled-payment
|
||||
(com/pill {:color :yellow} "Scheduled")
|
||||
(com/pill {:color :primary} "Paid")
|
||||
(= status :invoice-status/voided)
|
||||
(com/pill {:color :red} "Voided")
|
||||
|
||||
(= status :invoice-status/unpaid)
|
||||
(com/pill {:color :secondary} "Unpaid")
|
||||
:else
|
||||
""))}
|
||||
scheduled-payment
|
||||
(com/pill {:color :yellow} "Scheduled")
|
||||
|
||||
(= status :invoice-status/unpaid)
|
||||
(com/pill {:color :secondary} "Unpaid")
|
||||
:else
|
||||
""))}
|
||||
{:key "accounts"
|
||||
:name "Account"
|
||||
:show-starting "lg"
|
||||
@@ -656,32 +641,32 @@
|
||||
:class "w-8"
|
||||
:render (fn [i]
|
||||
(link-dropdown
|
||||
(into []
|
||||
(concat (->> i
|
||||
:invoice/payments
|
||||
(map :invoice-payment/payment)
|
||||
(filter (fn [p]
|
||||
(not= :payment-status/voided
|
||||
(:payment/status p))))
|
||||
(mapcat (fn [p]
|
||||
(cond-> [{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
||||
::payment-route/all-page)
|
||||
{:exact-match-id (:db/id p)})
|
||||
:content (str (format "$%,.2f" (:payment/amount p))
|
||||
(some-> (:payment/date p) coerce/to-date-time (atime/unparse-local atime/normal-date) (#(str " payment on " %))))}]
|
||||
(:payment/transaction p) (conj {:link (hu/url (bidi/path-for ssr-routes/only-routes ::transaction-routes/all-page)
|
||||
{:exact-match-id (:db/id (first (:payment/transaction p)))})
|
||||
:color :secondary
|
||||
:content "Transaction"})))))
|
||||
(when (:invoice/journal-entry i)
|
||||
[{:link (hu/url (bidi/path-for ssr-routes/only-routes ::ledger-routes/all-page)
|
||||
{:exact-match-id (:db/id (first (:invoice/journal-entry i)))})
|
||||
:color :yellow
|
||||
:content "Ledger entry"}])
|
||||
(when (:invoice/source-url i)
|
||||
[{:link (:invoice/source-url i)
|
||||
:color :secondary
|
||||
:content "File"}])))))}]}))
|
||||
(into []
|
||||
(concat (->> i
|
||||
:invoice/payments
|
||||
(map :invoice-payment/payment)
|
||||
(filter (fn [p]
|
||||
(not= :payment-status/voided
|
||||
(:payment/status p))))
|
||||
(mapcat (fn [p]
|
||||
(cond-> [{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
||||
::payment-route/all-page)
|
||||
{:exact-match-id (:db/id p)})
|
||||
:content (str (format "$%,.2f" (:payment/amount p))
|
||||
(some-> (:payment/date p) coerce/to-date-time (atime/unparse-local atime/normal-date) (#(str " payment on " %))))}]
|
||||
(:payment/transaction p) (conj {:link (hu/url (bidi/path-for ssr-routes/only-routes ::transaction-routes/all-page)
|
||||
{:exact-match-id (:db/id (first (:payment/transaction p)))})
|
||||
:color :secondary
|
||||
:content "Transaction"})))))
|
||||
(when (:invoice/journal-entry i)
|
||||
[{:link (hu/url (bidi/path-for ssr-routes/only-routes ::ledger-routes/all-page)
|
||||
{:exact-match-id (:db/id (first (:invoice/journal-entry i)))})
|
||||
:color :yellow
|
||||
:content "Ledger entry"}])
|
||||
(when (:invoice/source-url i)
|
||||
[{:link (:invoice/source-url i)
|
||||
:color :secondary
|
||||
:content "File"}])))))}]}))
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
|
||||
@@ -722,18 +707,16 @@
|
||||
(defn undo-autopay [{:as request :keys [identity entity]}]
|
||||
(let [invoice entity
|
||||
id (:db/id entity)
|
||||
_ (assert-can-see-client identity (:db/id (:invoice/client invoice)))
|
||||
]
|
||||
_ (assert-can-see-client identity (:db/id (:invoice/client invoice)))]
|
||||
(alog/info ::undoing-autopay :transaction :tx)
|
||||
(assert-can-undo-autopayment invoice)
|
||||
(audit-transact
|
||||
[[:upsert-invoice {:db/id id
|
||||
:invoice/status :invoice-status/unpaid
|
||||
:invoice/outstanding-balance (:invoice/total entity)
|
||||
:invoice/scheduled-payment nil}]]
|
||||
(audit-transact
|
||||
[[:upsert-invoice {:db/id id
|
||||
:invoice/status :invoice-status/unpaid
|
||||
:invoice/outstanding-balance (:invoice/total entity)
|
||||
:invoice/scheduled-payment nil}]]
|
||||
identity)
|
||||
|
||||
|
||||
(html-response
|
||||
(row* identity (dc/pull (dc/db conn) default-read id) {:flash? true
|
||||
:request request})
|
||||
@@ -847,8 +830,6 @@
|
||||
id)
|
||||
(count all-ids)))
|
||||
|
||||
|
||||
|
||||
(defn bulk-delete-dialog-confirm [request]
|
||||
(alog/peek (:form-params request))
|
||||
(let [ids (selected->ids request (:form-params request))
|
||||
@@ -861,8 +842,7 @@
|
||||
(count ids))})})))
|
||||
|
||||
#_(defn pay-invoices-from-balance [context {invoices :invoices
|
||||
client-id :client_id} _]
|
||||
)
|
||||
client-id :client_id} _])
|
||||
|
||||
(defn pay-using-credit [request]
|
||||
(alog/peek (:form-params request))
|
||||
@@ -896,7 +876,6 @@
|
||||
0.001))
|
||||
invoices)
|
||||
|
||||
|
||||
total-to-pay (reduce + 0 (map :invoice/outstanding-balance invoices-to-be-paid))
|
||||
_ (when (<= total-to-pay 0.001)
|
||||
(throw (ex-info "Select some invoices that need to be paid" {:type :form-validation})))
|
||||
@@ -926,8 +905,6 @@
|
||||
[total-to-pay []])))
|
||||
(into {}))
|
||||
|
||||
|
||||
|
||||
vendor-id (:db/id (:invoice/vendor (first invoices)))
|
||||
payment {:db/id (str vendor-id)
|
||||
:payment/amount total-to-pay
|
||||
@@ -949,7 +926,6 @@
|
||||
:notification (format "Successfully paid %d invoices."
|
||||
(count invoices))})})))
|
||||
|
||||
|
||||
(defn does-amount-exceed-outstanding? [amount outstanding-balance]
|
||||
(let [outstanding-balance (round-money outstanding-balance)
|
||||
amount (round-money amount)]
|
||||
@@ -1018,12 +994,11 @@
|
||||
:to (mm/encode-step-key :payment-details)})}
|
||||
"Credit")
|
||||
(com/button {:x-ref "button"
|
||||
"@click.prevent.capture" "$tooltip($refs.tooltip.innerHTML, {allowHTML: true, onMount(i) {htmx.process(i.popper)}, interactive:true, theme:\"light\", timeout:5000})" }
|
||||
"@click.prevent.capture" "$tooltip($refs.tooltip.innerHTML, {allowHTML: true, onMount(i) {htmx.process(i.popper)}, interactive:true, theme:\"light\", timeout:5000})"}
|
||||
"Pay"))
|
||||
[:template { :x-ref "tooltip"}
|
||||
[:div.flex.flex-col.gap-2 {
|
||||
:data-key "vis"
|
||||
:class "p-4 w-max" }
|
||||
[:template {:x-ref "tooltip"}
|
||||
[:div.flex.flex-col.gap-2 {:data-key "vis"
|
||||
:class "p-4 w-max"}
|
||||
(when (= :bank-account-type/check
|
||||
(:bank-account/type bank-account))
|
||||
(com/button {:color :primary
|
||||
@@ -1094,7 +1069,6 @@
|
||||
:can-handwrite? can-handwrite?
|
||||
:credit-only? credit-only?}))
|
||||
|
||||
|
||||
(defn can-handwrite? [invoices]
|
||||
(let [selected-vendors (set (map (comp :db/id :invoice/vendor) invoices))]
|
||||
(and
|
||||
@@ -1110,7 +1084,6 @@
|
||||
(reduce + 0.0 (map :invoice/outstanding-balance is))))
|
||||
(every? #(<= % 0.0))))
|
||||
|
||||
|
||||
(defrecord ChoosePaymentMethodModal [linear-wizard]
|
||||
mm/ModalWizardStep
|
||||
(step-name [_]
|
||||
@@ -1195,7 +1168,7 @@
|
||||
(format "Pay in full ($%,.2f)" total)))}
|
||||
{:value "advanced"
|
||||
:content "Customize payments"}]})
|
||||
|
||||
|
||||
[:div.space-y-4
|
||||
(fc/with-field :invoices
|
||||
(com/validated-field
|
||||
@@ -1345,7 +1318,6 @@
|
||||
(:snapshot multi-form-state)
|
||||
mt/strip-extra-keys-transformer)
|
||||
|
||||
|
||||
_ (assert-schema payment-form-schema snapshot)
|
||||
|
||||
_ (exception->4xx
|
||||
@@ -1354,7 +1326,7 @@
|
||||
(= "" (:check-number snapshot)))
|
||||
(throw (Exception. "Check number is required")))
|
||||
true))
|
||||
|
||||
|
||||
result (exception->4xx
|
||||
#(do
|
||||
(when (:handwritten-date snapshot)
|
||||
@@ -1378,7 +1350,7 @@
|
||||
:payment-type/credit
|
||||
:else :payment-type/debit)
|
||||
identity
|
||||
(:handwritten-date snapshot))
|
||||
(:handwritten-date snapshot))
|
||||
(catch Exception e
|
||||
(println e))))))]
|
||||
(modal-response
|
||||
@@ -1455,11 +1427,10 @@
|
||||
(defn redirect-handler [target-route]
|
||||
(fn handle [request]
|
||||
{:status 302
|
||||
:headers {"Location" (str (hu/url (bidi.bidi/path-for ssr-routes/only-routes
|
||||
:headers {"Location" (str (hu/url (bidi.bidi/path-for ssr-routes/only-routes
|
||||
target-route)
|
||||
(:query-params request)))}}))
|
||||
|
||||
|
||||
(defn initial-bulk-edit-state [request]
|
||||
(mm/->MultiStepFormState {:search-params (:query-params request)
|
||||
:expense-accounts [{:db/id "123"
|
||||
@@ -1479,7 +1450,7 @@
|
||||
(com/typeahead {:name name
|
||||
:placeholder "Search..."
|
||||
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
|
||||
{ :purpose "invoice"})
|
||||
{:purpose "invoice"})
|
||||
:id name
|
||||
:x-model x-model
|
||||
:value value
|
||||
@@ -1487,8 +1458,6 @@
|
||||
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
||||
client-id)))})])
|
||||
|
||||
|
||||
|
||||
;; TODO clientize
|
||||
(defn all-ids-not-locked [all-ids]
|
||||
(->> all-ids
|
||||
@@ -1527,7 +1496,7 @@
|
||||
(com/validated-field
|
||||
{:errors (fc/field-errors)
|
||||
:x-hx-val:account-id "accountId"
|
||||
:hx-vals (hx/json {:name (fc/field-name) })
|
||||
:hx-vals (hx/json {:name (fc/field-name)})
|
||||
:x-dispatch:changed "accountId"
|
||||
:hx-trigger "changed"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/location-select)
|
||||
@@ -1536,7 +1505,7 @@
|
||||
(location-select* {:name (fc/field-name)
|
||||
:account-location (:account/location (cond->> (:account @value)
|
||||
(nat-int? (:account @value)) (dc/pull (dc/db conn)
|
||||
'[:account/location])))
|
||||
'[:account/location])))
|
||||
:value (fc/field-value)}))))
|
||||
(fc/with-field :percentage
|
||||
(com/data-grid-cell
|
||||
@@ -1544,10 +1513,10 @@
|
||||
(com/validated-field
|
||||
{:errors (fc/field-errors)}
|
||||
(com/money-input {:name (fc/field-name)
|
||||
:class "w-16 amount-field"
|
||||
:value (some-> (fc/field-value)
|
||||
(* 100)
|
||||
(long))}))))
|
||||
:class "w-16 amount-field"
|
||||
:value (some-> (fc/field-value)
|
||||
(* 100)
|
||||
(long))}))))
|
||||
(com/data-grid-cell {:class "align-top"}
|
||||
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))
|
||||
|
||||
@@ -1587,7 +1556,7 @@
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
::route/bulk-edit-new-account)
|
||||
:row-offset 0
|
||||
:index (count (fc/field-value)) }
|
||||
:index (count (fc/field-value))}
|
||||
"New account")
|
||||
(com/data-grid-row {}
|
||||
(com/data-grid-cell {})
|
||||
@@ -1617,7 +1586,6 @@
|
||||
:next-button (com/button {:color :primary :x-ref "next" :class "w-32"} "Save"))
|
||||
:validation-route ::route/new-wizard-navigate))))
|
||||
|
||||
|
||||
(defn maybe-code-accounts [invoice account-rules valid-locations]
|
||||
(with-precision 2
|
||||
(let [accounts (vec (mapcat
|
||||
@@ -1667,9 +1635,9 @@
|
||||
(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 :accounts)))
|
||||
(if current-step
|
||||
(mm/get-step this current-step)
|
||||
(mm/get-step this :accounts)))
|
||||
(render-wizard [this {:keys [multi-form-state] :as request}]
|
||||
(mm/default-render-wizard
|
||||
this request
|
||||
@@ -1683,44 +1651,43 @@
|
||||
(get-step [this step-key]
|
||||
(let [step-key-result (mc/parse mm/step-key-schema step-key)
|
||||
[step-key-type step-key] step-key-result]
|
||||
(get {:accounts (->AccountsStep this) }
|
||||
(get {:accounts (->AccountsStep this)}
|
||||
step-key)))
|
||||
(form-schema [_]
|
||||
(mc/schema [:map
|
||||
[:expense-accounts
|
||||
(many-entity {:min 1}
|
||||
[:account entity-id]
|
||||
[:location [:string {:min 1 :error/message "required"}]]
|
||||
[:percentage percentage])]]))
|
||||
(many-entity {:min 1}
|
||||
[:account entity-id]
|
||||
[:location [:string {:min 1 :error/message "required"}]]
|
||||
[:percentage percentage])]]))
|
||||
(submit [this {:keys [multi-form-state request-method identity] :as request}]
|
||||
(let [selected-ids (selected->ids (assoc request :query-params (:search-params (:snapshot multi-form-state))) (:search-params (:snapshot multi-form-state)))
|
||||
all-ids (all-ids-not-locked selected-ids)
|
||||
invoices (pull-many (dc/db conn) '[:db/id :invoice/total {:invoice/client [:client/locations]}] (vec all-ids)) ]
|
||||
(assert-percentages-add-up (:snapshot multi-form-state))
|
||||
|
||||
(doseq [a (-> multi-form-state :snapshot :expense-accounts)
|
||||
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account a))]]
|
||||
(when (and location (not= location (:location a)))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
||||
(throw (ex-info err {:validation-error err})))))
|
||||
(alog/info ::bulk-code :count (count all-ids))
|
||||
(audit-transact-batch
|
||||
(map (fn [i]
|
||||
[:upsert-invoice {:db/id (:db/id i)
|
||||
:invoice/expense-accounts (maybe-code-accounts i (-> multi-form-state :snapshot :expense-accounts) (-> i :invoice/client :client/locations))}])
|
||||
invoices)
|
||||
(:identity request))
|
||||
|
||||
(html-response
|
||||
[:div]
|
||||
:headers (cond-> {"hx-trigger" (hx/json { "modalclose" ""
|
||||
"invalidated" ""
|
||||
"notification" (str "Successfully coded " (count all-ids) " invoices.")})
|
||||
"hx-reswap" "outerHTML"})))))
|
||||
(let [selected-ids (selected->ids (assoc request :query-params (:search-params (:snapshot multi-form-state))) (:search-params (:snapshot multi-form-state)))
|
||||
all-ids (all-ids-not-locked selected-ids)
|
||||
invoices (pull-many (dc/db conn) '[:db/id :invoice/total {:invoice/client [:client/locations]}] (vec all-ids))]
|
||||
(assert-percentages-add-up (:snapshot multi-form-state))
|
||||
|
||||
(doseq [a (-> multi-form-state :snapshot :expense-accounts)
|
||||
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account a))]]
|
||||
(when (and location (not= location (:location a)))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
||||
(throw (ex-info err {:validation-error err})))))
|
||||
(alog/info ::bulk-code :count (count all-ids))
|
||||
(audit-transact-batch
|
||||
(map (fn [i]
|
||||
[:upsert-invoice {:db/id (:db/id i)
|
||||
:invoice/expense-accounts (maybe-code-accounts i (-> multi-form-state :snapshot :expense-accounts) (-> i :invoice/client :client/locations))}])
|
||||
invoices)
|
||||
(:identity request))
|
||||
|
||||
(html-response
|
||||
[:div]
|
||||
:headers (cond-> {"hx-trigger" (hx/json {"modalclose" ""
|
||||
"invalidated" ""
|
||||
"notification" (str "Successfully coded " (count all-ids) " invoices.")})
|
||||
"hx-reswap" "outerHTML"})))))
|
||||
|
||||
(def bulk-edit-wizard (->BulkEditWizard nil nil))
|
||||
|
||||
|
||||
(defn bulk-edit-total* [request]
|
||||
(let [total (->> (-> request
|
||||
:multi-form-state
|
||||
@@ -1740,7 +1707,7 @@
|
||||
(filter number?)
|
||||
(reduce + 0.0))
|
||||
balance (- 100.0
|
||||
(* 100.0 total))]
|
||||
(* 100.0 total))]
|
||||
[:span {:class (when-not (dollars= 0.0 balance)
|
||||
"text-red-300")}
|
||||
(format "%.1f%%" balance)]))
|
||||
@@ -1769,31 +1736,31 @@
|
||||
::route/legacy-voided-invoices (redirect-handler ::route/voided-page)
|
||||
::route/legacy-new-invoice (redirect-handler ::route/new-wizard)
|
||||
::route/bulk-edit (-> mm/open-wizard-handler
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-init-multi-form-state initial-bulk-edit-state))
|
||||
::route/bulk-edit-submit (-> mm/submit-handler
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-total (-> bulk-edit-total
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-balance (-> bulk-edit-balance
|
||||
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-new-account (->
|
||||
(add-new-entity-handler [:step-params :expense-accounts]
|
||||
(fn render [cursor request]
|
||||
(bulk-edit-account-row*
|
||||
{:value cursor }))
|
||||
(fn build-new-row [base _]
|
||||
(assoc base :invoice-expense-account/location "Shared")))
|
||||
(wrap-schema-enforce :query-schema [:map
|
||||
[:client-id {:optional true}
|
||||
[:maybe entity-id]]]))
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-init-multi-form-state initial-bulk-edit-state))
|
||||
::route/bulk-edit-submit (-> mm/submit-handler
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-total (-> bulk-edit-total
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-balance (-> bulk-edit-balance
|
||||
|
||||
(mm/wrap-wizard bulk-edit-wizard)
|
||||
(mm/wrap-decode-multi-form-state)
|
||||
(wrap-must {:subject :invoice :activity :bulk-edit}))
|
||||
::route/bulk-edit-new-account (->
|
||||
(add-new-entity-handler [:step-params :expense-accounts]
|
||||
(fn render [cursor request]
|
||||
(bulk-edit-account-row*
|
||||
{:value cursor}))
|
||||
(fn build-new-row [base _]
|
||||
(assoc base :invoice-expense-account/location "Shared")))
|
||||
(wrap-schema-enforce :query-schema [:map
|
||||
[:client-id {:optional true}
|
||||
[:maybe entity-id]]]))
|
||||
|
||||
::route/undo-autopay (-> undo-autopay
|
||||
(wrap-entity [:route-params :db/id] default-read)
|
||||
|
||||
Reference in New Issue
Block a user