tries sales changes

This commit is contained in:
2026-05-18 15:38:07 -07:00
parent de1c154706
commit a156ac99fe
5 changed files with 1016 additions and 50 deletions

View File

@@ -133,6 +133,47 @@
(str (subs s 0 (- max-len 3)) "...")
s))
(defn account-display-cell [{:keys [item field-name-prefix client-id]}]
(let [account-id (:ledger-mapped/account item)
account-name (when account-id
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read account-id)
client-id)))]
[:div.account-cell.flex.items-center.gap-2
(com/hidden {:name (str field-name-prefix "[ledger-mapped/account]")
:value (or account-id "")})
(if account-id
[:span.text-sm account-name]
(com/pill {:color :red} "Missing acct"))
(com/a-icon-button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/edit-item-account)
:hx-target "closest .account-cell"
:hx-swap "outerHTML"
:hx-vals (hx/json {:item-index (or (:item-index item) 0)
:client-id client-id
:current-account-id (or account-id "")})}
svg/pencil)]))
(defn account-edit-cell [{:keys [field-name-prefix client-id current-account-id]}]
(let [account-input-name (str field-name-prefix "[ledger-mapped/account]")]
[:div.account-cell.flex.flex-col.gap-2
(account-typeahead* {:name account-input-name
:value current-account-id
:client-id client-id})
[:div.flex.gap-1
(com/a-icon-button {:hx-put (bidi/path-for ssr-routes/only-routes ::route/save-item-account)
:hx-target "closest .account-cell"
:hx-swap "outerHTML"
:hx-include "closest .account-cell"
:hx-vals (hx/json {:field-name-prefix field-name-prefix
:client-id client-id})}
svg/check)
(com/a-icon-button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/cancel-item-account)
:hx-target "closest .account-cell"
:hx-swap "outerHTML"
:hx-vals (hx/json {:field-name-prefix field-name-prefix
:client-id client-id
:current-account-id (or current-account-id "")})}
svg/x)]]))
(def grid-page
(helper/build {:id "entity-table"
:id-fn :db/id
@@ -304,11 +345,7 @@
(total-debits))]
(com/data-grid-row {:id "total-row"
:class "bg-slate-50 border-t-2 border-slate-300"
: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"}
:class "bg-slate-50 border-t-2 border-slate-300"}
(com/data-grid-cell {})
(com/data-grid-cell {:class "text-right"}
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-slate-600
@@ -337,11 +374,7 @@
credit-over? (and unbalanced? (> total-credits total-debits))]
(com/data-grid-row {:id "unbalanced-row"
:class (when unbalanced? "bg-red-50 border-t border-red-200")
: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"}
:class (when unbalanced? "bg-red-50 border-t border-red-200")}
(com/data-grid-cell {})
(com/data-grid-cell {:class "text-right"}
(when unbalanced?
@@ -357,7 +390,45 @@
(format "$%,.2f" (- total-credits total-debits))]))
(com/data-grid-cell {}))))
(defn- account-typeahead*
(defn summary-total-display [request]
(let [total-credits (-> request
:multi-form-state
:step-params
:sales-summary/items
(total-credits))
total-debits (-> request
:multi-form-state
:step-params
:sales-summary/items
(total-debits))]
[:div.flex.justify-between.text-sm.py-1.border-t.mt-1
{:id "total-display"}
[:span.font-semibold "Total"]
[:div.flex.gap-8
[:span.font-mono (format "$%,.2f" total-debits)]
[:span.font-mono (format "$%,.2f" total-credits)]]]))
(defn unbalanced-display [request]
(let [total-credits (-> request
:multi-form-state
:step-params
:sales-summary/items
(total-credits))
total-debits (-> request
:multi-form-state
:step-params
:sales-summary/items
(total-debits))
delta (- total-debits total-credits)]
(when-not (dollars-0? delta)
[:div.flex.justify-between.text-sm.py-1
{:id "unbalanced-display"}
[:span.font-semibold.text-red-600 "Unbalanced"]
[:div.flex.gap-8
[:span.font-mono (when (pos? delta) (format "$%,.2f" delta))]
[:span.font-mono (when (neg? delta) (format "$%,.2f" (Math/abs delta)))]]])))
(defn account-typeahead*
[{:keys [name value client-id]}]
[:div.flex.flex-col
(com/typeahead {:name name
@@ -446,38 +517,124 @@
(render-step
[this {:keys [multi-form-state] :as request}]
(mm/default-render-step
linear-wizard this
:head [:div.p-2 "New invoice"]
:body (mm/default-step-body
{}
[:div
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
(com/data-grid {:headers
[(com/data-grid-header {} "Category")
(com/data-grid-header {} "Account")
(com/data-grid-header {} "Debits")
(com/data-grid-header {} "Credits")
(com/data-grid-header {} "")]}
(fc/with-field :sales-summary/items
(list
(fc/cursor-map #(sales-summary-item-row* {:value %
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))}))
(com/data-grid-new-row {:colspan 5
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
:row-offset 0
:index (count (fc/field-value))
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
"New Summary Item")))
(summary-total-row* request)
(unbalanced-row* request))])
(let [client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))
items (:sales-summary/items (:step-params multi-form-state))
sorted-items (sort-items items)
indexed-items (map-indexed vector sorted-items)
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side (second %))) indexed-items)
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side (second %))) indexed-items)
max-rows (max (count debit-items) (count credit-items))
padded-debits (concat debit-items (repeat (- max-rows (count debit-items)) nil))
padded-credits (concat credit-items (repeat (- max-rows (count credit-items)) nil))]
(mm/default-render-step
linear-wizard this
:head [:div.p-2 "Edit Summary"]
:body (mm/default-step-body
{}
[:div
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
[:div.grid.grid-cols-2.gap-6
[:div
[:div.font-semibold.text-sm.mb-2 "Debits"]
[:div.space-y-1
(for [[actual-idx item] padded-debits]
(if item
(let [manual? (:sales-summary-item/manual? item)]
(if manual?
[:div.flex.items-center.gap-2.text-sm {:x-ref "p" :x-data (hx/json {})}
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
:value (:db/id item)})
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/manual?]")
:value "true"})
(fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
item []
(fc/with-field :sales-summary-item/category
(com/text-input {:placeholder "Category"
:name (fc/field-name)
:value (fc/field-value)
:class "w-32 text-sm"})))
(account-typeahead* {:name (str "step-params[sales-summary/items][" actual-idx "][ledger-mapped/account]")
:value (:ledger-mapped/account item)
:client-id client-id})
(fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
item []
(fc/with-field :debit
(com/money-input {:class "w-24 text-right font-mono tabular-nums"
:name (fc/field-name)
:value (fc/field-value)})))
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)]
[:div.flex.items-center.gap-2.text-sm
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
:value (:db/id item)})
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/category]")
:value (:sales-summary-item/category item)})
[:span.text-gray-500 (truncate (:sales-summary-item/category item) 30)]
(account-display-cell {:item (assoc item :item-index actual-idx)
:field-name-prefix (str "step-params[sales-summary/items][" actual-idx "]")
:client-id client-id})
[:span.font-mono.tabular-nums.text-gray-900 (format "$%,.2f" (:ledger-mapped/amount item))]]))
[:div.h-6]))]
[:div.mt-2.border-t.pt-1
(summary-total-display request)
(unbalanced-display request)]]
[:div
[:div.font-semibold.text-sm.mb-2 "Credits"]
[:div.space-y-1
(for [[actual-idx item] padded-credits]
(if item
(let [manual? (:sales-summary-item/manual? item)]
(if manual?
[:div.flex.items-center.gap-2.text-sm {:x-ref "p" :x-data (hx/json {})}
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
:value (:db/id item)})
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/manual?]")
:value "true"})
(fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
item []
(fc/with-field :sales-summary-item/category
(com/text-input {:placeholder "Category"
:name (fc/field-name)
:value (fc/field-value)
:class "w-32 text-sm"})))
(account-typeahead* {:name (str "step-params[sales-summary/items][" actual-idx "][ledger-mapped/account]")
:value (:ledger-mapped/account item)
:client-id client-id})
(fc/start-form-with-prefix [(str "step-params[sales-summary/items][" actual-idx "]")]
item []
(fc/with-field :credit
(com/money-input {:class "w-24 text-right font-mono tabular-nums"
:name (fc/field-name)
:value (fc/field-value)})))
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)]
[:div.flex.items-center.gap-2.text-sm
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][db/id]")
:value (:db/id item)})
(com/hidden {:name (str "step-params[sales-summary/items][" actual-idx "][sales-summary-item/category]")
:value (:sales-summary-item/category item)})
[:span.text-gray-500 (truncate (:sales-summary-item/category item) 30)]
(account-display-cell {:item (assoc item :item-index actual-idx)
:field-name-prefix (str "step-params[sales-summary/items][" actual-idx "]")
:client-id client-id})
[:span.font-mono.tabular-nums.text-gray-900 (format "$%,.2f" (:ledger-mapped/amount item))]]))
[:div.h-6]))]
[:div.mt-2.border-t.pt-1
(summary-total-display request)
(unbalanced-display request)]]]
[:div.mt-4.border-t.pt-2
(fc/with-field :sales-summary/items
(com/data-grid-new-row {:colspan 2
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
:row-offset 0
:index (count (fc/field-value))
:tr-params {:hx-vals (hx/json {:client-id client-id})}}
"New Summary Item"))]])
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
:validation-route ::route/edit-wizard-navigate
:width-height-class "lg:w-[920px] lg:h-[640px]")))
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
:validation-route ::route/edit-wizard-navigate
:width-height-class "lg:w-[900px] lg:h-[600px]")))
(defn attach-ledger [i]
(cond-> i
@@ -524,7 +681,6 @@
{:db/id (:db/id i)
:ledger-mapped/account (:ledger-mapped/account i)}))
(:sales-summary/items result))}]]
(clojure.pprint/pprint (:sales-summary/items result))
@(dc/transact conn [transaction])
(html-response
(row* identity (dc/pull (dc/db conn) default-read (:db/id result))
@@ -547,6 +703,48 @@
(mm/->MultiStepFormState entity [] entity)))
(defn edit-item-account [request]
(let [{:keys [item-index client-id current-account-id]} (:query-params request)
item-index (if (string? item-index) (Integer/parseInt item-index) item-index)
field-name-prefix (str "step-params[sales-summary/items][" item-index "]")
current-account-id (when (and current-account-id (not= current-account-id ""))
(if (string? current-account-id)
(Long/parseLong current-account-id)
current-account-id))
client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
(html-response
(account-edit-cell {:field-name-prefix field-name-prefix
:client-id client-id
:current-account-id current-account-id}))))
(defn save-item-account [request]
(let [{:keys [field-name-prefix client-id]} (:query-params request)
account-input-name (str field-name-prefix "[ledger-mapped/account]")
account-id-str (get-in request [:form-params account-input-name])
account-id (when (and account-id-str (not= account-id-str ""))
(Long/parseLong account-id-str))
item {:ledger-mapped/account account-id
:item-index (second (re-find #"\[(\d+)\]" (or field-name-prefix "")))}
client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
(html-response
(account-display-cell {:item item
:field-name-prefix field-name-prefix
:client-id client-id}))))
(defn cancel-item-account [request]
(let [{:keys [field-name-prefix client-id current-account-id]} (:query-params request)
account-id (when (and current-account-id (not= current-account-id ""))
(if (string? current-account-id)
(Long/parseLong current-account-id)
current-account-id))
item {:ledger-mapped/account account-id
:item-index (second (re-find #"\[(\d+)\]" (or field-name-prefix "")))}
client-id (if (string? client-id) (Long/parseLong client-id) client-id)]
(html-response
(account-display-cell {:item item
:field-name-prefix field-name-prefix
:client-id client-id}))))
(def key->handler
(apply-middleware-to-all-handlers
(->>
@@ -566,10 +764,17 @@
:client-id (:client-id (:query-params request))}))
(fn build-new-row [base _]
(assoc base :sales-summary-item/manual? true)))
(wrap-schema-enforce :query-schema [:map
[:client-id {:optional true}
[:maybe entity-id]]]))
::route/edit-wizard-submit (-> mm/submit-handler
(wrap-schema-enforce :query-schema [:map
[:client-id {:optional true}
[:maybe entity-id]]]))
::route/edit-item-account (-> edit-item-account
(wrap-schema-enforce :query-schema [:map
[:item-index nat-int?]
[:client-id {:optional true} [:maybe entity-id]]
[:current-account-id {:optional true} [:maybe :string]]]))
::route/save-item-account save-item-account
::route/cancel-item-account cancel-item-account
::route/edit-wizard-submit (-> mm/submit-handler
(mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state))})
(fn [h]

View File

@@ -3,5 +3,8 @@
:put ::edit-wizard-submit}
"/table" ::table
["/" [#"\d+" :db/id]] {:get ::edit-wizard }
"/edit/navigate" ::edit-wizard-navigate
"/edit/sales-summary-item" ::new-summary-item})
"/edit/navigate" ::edit-wizard-navigate
"/edit/sales-summary-item" ::new-summary-item
"/edit/item-account" ::edit-item-account
"/edit/save-item-account" ::save-item-account
"/edit/cancel-item-account" ::cancel-item-account})