Move journal entry calculation and creation from the reconcile-ledger background job into the upsert-sales-summary tx function. Now any save of a sales summary (job recalculation, admin edit wizard, or manual touch) automatically creates the journal entry if balanced with all accounts mapped, or retracts it if conditions no longer hold. Eliminates the need for a separate upsert-sales-summary-ledger call and the reconcile ledger pass for sales summaries.
554 lines
29 KiB
Clojure
554 lines
29 KiB
Clojure
(ns auto-ap.ssr.admin.sales-summaries
|
|
(:require
|
|
[auto-ap.datomic
|
|
:refer [apply-pagination apply-sort-3 conn merge-query pull-many
|
|
query2]]
|
|
[auto-ap.datomic.accounts :as d-accounts]
|
|
[auto-ap.graphql.utils :refer [extract-client-ids]]
|
|
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
|
[auto-ap.routes.admin.sales-summaries :as route]
|
|
[auto-ap.routes.utils
|
|
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
|
[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.multi-modal :as mm]
|
|
[auto-ap.ssr.form-cursor :as fc]
|
|
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
|
|
[auto-ap.ssr.hx :as hx]
|
|
[auto-ap.ssr.svg :as svg]
|
|
[auto-ap.ssr.utils
|
|
:refer [apply-middleware-to-all-handlers clj-date-schema
|
|
default-grid-fields-schema entity-id html-response money
|
|
strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
|
|
[auto-ap.time :as atime]
|
|
[bidi.bidi :as bidi]
|
|
[clj-time.coerce :as c]
|
|
[clojure.string :as str]
|
|
[datomic.api :as dc]
|
|
[hiccup.util :as hu]
|
|
[iol-ion.query :refer [dollars=]]
|
|
[malli.core :as mc]
|
|
[malli.util :as mut]))
|
|
|
|
(def query-schema (mc/schema
|
|
[:maybe
|
|
(into [:map {:date-range [:date-range :start-date :end-date]}
|
|
|
|
[:start-date {:optional true}
|
|
[:maybe clj-date-schema]]
|
|
[:end-date {:optional true}
|
|
[:maybe clj-date-schema]] ]
|
|
default-grid-fields-schema)]))
|
|
|
|
(defn filters [request]
|
|
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
|
"hx-get" (bidi/path-for ssr-routes/only-routes
|
|
::route/table)
|
|
"hx-target" "#entity-table"
|
|
"hx-indicator" "#entity-table"}
|
|
|
|
#_[:fieldset.space-y-6
|
|
(date-range-field {:value {:start (:start-date (:query-params request))
|
|
:end (:end-date (:query-params request))}
|
|
:id "date-range"})
|
|
(com/field {:label "Source"}
|
|
(com/select {:name "source"
|
|
:class "hot-filter w-full"
|
|
:value (:source (:query-params request))
|
|
:placeholder ""
|
|
:options (ref->select-options "import-source" :allow-nil? true)}))
|
|
|
|
#_(com/field {:label "Code"}
|
|
(com/text-input {:name "code"
|
|
:id "code"
|
|
:class "hot-filter"
|
|
:value (:code (:query-params request))
|
|
:placeholder "11101"
|
|
:size :small}))]])
|
|
|
|
(def default-read '[:db/id
|
|
*
|
|
[:sales-summary/date :xform clj-time.coerce/from-date]
|
|
{:sales-summary/client [:client/code :client/name :db/id]}
|
|
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]
|
|
}
|
|
:ledger-mapped/account
|
|
:ledger-mapped/amount
|
|
:sales-summary-item/category
|
|
:sales-summary-item/sort-order
|
|
:db/id
|
|
:sales-summary-item/manual?]
|
|
} ])
|
|
|
|
(defn fetch-ids [db request]
|
|
(let [query-params (:query-params request)
|
|
valid-clients (extract-client-ids (:clients request)
|
|
(:client request)
|
|
(:client-id query-params)
|
|
(when (:client-code query-params)
|
|
[:client/code (:client-code query-params)]))
|
|
query (cond-> {:query {:find []
|
|
:in '[$ [?client ...]]
|
|
:where '[[?e :sales-summary/client ?client]]}
|
|
:args [db valid-clients]}
|
|
(or (:start-date query-params)
|
|
(:end-date query-params))
|
|
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
|
|
|
|
(:start-date query-params)
|
|
(merge-query {:query '{:in [?start-date]
|
|
:where [[(>= ?d ?start-date)]]}
|
|
:args [(-> query-params :start-date c/to-date)]})
|
|
|
|
(:end-date query-params)
|
|
(merge-query {:query '{:in [?end-date]
|
|
:where [[(< ?d ?end-date)]]}
|
|
:args [(-> query-params :end-date c/to-date)]})
|
|
|
|
|
|
true
|
|
(merge-query {:query {:find ['?sort-default '?e]
|
|
:where ['[?e :sales-summary/date ?sort-default]]}}))]
|
|
(cond->> (query2 query)
|
|
true (apply-sort-3 query-params)
|
|
true (apply-pagination query-params))))
|
|
|
|
(defn hydrate-results [ids db _]
|
|
(let [results (->> (pull-many db default-read ids)
|
|
(group-by :db/id))
|
|
refunds (->> ids
|
|
(map results)
|
|
(map first))]
|
|
refunds))
|
|
|
|
(defn fetch-page [request]
|
|
(let [db (dc/db conn)
|
|
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
|
|
|
[(->> (hydrate-results ids-to-retrieve db request))
|
|
matching-count]))
|
|
|
|
#_(defn get-debits [ss]
|
|
{:card-payments (+ (:sales-summary/total-card-payments ss 0.0)
|
|
(:sales-summary/total-card-fees ss 0.0)
|
|
(- (:sales-summary/total-card-refunds ss 0.0)))
|
|
:food-app-payments (+ (:sales-summary/total-food-app-payments ss 0.0)
|
|
(:sales-summary/total-food-app-fees ss 0.0)
|
|
(- (:sales-summary/total-food-app-refunds ss 0.0)))
|
|
:gift-card-payments (+ (:sales-summary/total-gift-card-payments ss 0.0)
|
|
(:sales-summary/total-gift-card-fees ss 0.0)
|
|
(- (:sales-summary/total-gift-card-refunds ss 0.0)))
|
|
#_#_:refunds (+ (:sales-summary/total-food-app-refunds ss 0.0)
|
|
(:sales-summary/total-card-refunds ss 0.0)
|
|
(:sales-summary/total-cash-refunds ss 0.0))
|
|
|
|
:fees (- (:sales-summary/total-card-fees ss 0.0))
|
|
:cash-payments (+ (:sales-summary/total-cash-payments ss 0.0)
|
|
(- (:sales-summary/total-cash-refunds ss 0.0)))
|
|
:total-unknown-processor-payments (:sales-summary/total-unknown-processor-payments ss 0.0)
|
|
:discounts (+ (:sales-summary/discount ss 0.0))
|
|
:returns (+ (:sales-summary/total-returns ss 0.0))})
|
|
(defn sort-items [ss]
|
|
(sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))
|
|
|
|
|
|
|
|
(defn total-debits [items]
|
|
(->> items
|
|
(filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)))
|
|
(map #(:ledger-mapped/amount % 0.0))
|
|
(reduce + 0.0)))
|
|
|
|
(defn total-credits [items]
|
|
(->> items
|
|
(filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)))
|
|
(map #(:ledger-mapped/amount % 0.0))
|
|
(reduce + 0.0)))
|
|
|
|
(def grid-page
|
|
(helper/build {:id "entity-table"
|
|
:id-fn :db/id
|
|
:nav com/admin-aside-nav
|
|
:fetch-page fetch-page
|
|
:page-specific-nav filters
|
|
:query-schema query-schema
|
|
:row-buttons (fn [_ entity]
|
|
[(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
|
::route/edit-wizard
|
|
:db/id (:db/id entity))}
|
|
svg/pencil)])
|
|
:oob-render
|
|
(fn [_request]
|
|
[])
|
|
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
|
:admin)}
|
|
"Admin"]
|
|
|
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
|
::route/page)}
|
|
"Sales Summaries"]]
|
|
:title "Sales Summaries"
|
|
:entity-name "Daily Summary"
|
|
:route ::route/table
|
|
:headers [{:key "client"
|
|
:name "Client"
|
|
:sort-key "client"
|
|
:hide? (fn [args]
|
|
(= (count (:clients args)) 1))
|
|
:render #(-> % :sales-summary/client :client/code)}
|
|
|
|
|
|
{:key "date"
|
|
:name "Date"
|
|
:sort-key "date"
|
|
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
|
|
|
|
|
|
{:key "debits"
|
|
:name "debits"
|
|
:sort-key "debits"
|
|
:render (fn [ss]
|
|
(let [total-debits (total-debits (:sales-summary/items ss))
|
|
total-credits (total-credits (:sales-summary/items ss))]
|
|
[:ul
|
|
(for [si (sort-items (:sales-summary/items ss))
|
|
:when (= :ledger-side/debit (:ledger-mapped/ledger-side si))]
|
|
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
|
|
(when-not (:ledger-mapped/account si)
|
|
[:span.pl-4 (com/pill {:color :red}
|
|
"missing account")])]
|
|
)
|
|
[:li (com/pill {:color (if (dollars= total-debits total-credits)
|
|
:primary
|
|
:red)} "Total: " (format "$%,.2f" total-debits))]]))}
|
|
{:key "credits"
|
|
:name "credits"
|
|
:sort-key "credits"
|
|
:render (fn [ss]
|
|
(let [total-debits (total-debits (:sales-summary/items ss))
|
|
total-credits (total-credits (:sales-summary/items ss))]
|
|
[:ul
|
|
(for [si (sort-items (:sales-summary/items ss))
|
|
:when (= :ledger-side/credit (:ledger-mapped/ledger-side si))]
|
|
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
|
|
(when-not (:ledger-mapped/account si)
|
|
[:span.pl-4 (com/pill {:color :red}
|
|
"missing account")])])
|
|
[:li (com/pill {:color (if (dollars= total-debits total-credits)
|
|
:primary
|
|
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
|
|
|
|
;; Architecture: Sales summary maintains granular detail (line items, fee types)
|
|
;; and is aggregated into ledger entries by account/location. Manual adjustments
|
|
;; are preserved during automatic recalculation.
|
|
|
|
(def row* (partial helper/row* grid-page))
|
|
(def table* (partial helper/table* grid-page))
|
|
|
|
(def edit-schema
|
|
[:map
|
|
[:db/id entity-id]
|
|
[:sales-summary/client [:map [:db/id entity-id]]]
|
|
[:sales-summary/items
|
|
[:vector {:coerce? true}
|
|
[:and
|
|
[:map
|
|
[:db/id [:or entity-id temp-id]]
|
|
[:sales-summary-item/category [:string {:decode/string strip}]]
|
|
[:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
|
|
(boolean? x)
|
|
x
|
|
(nil? x)
|
|
false
|
|
(str/blank? x)
|
|
false
|
|
:else
|
|
true))} :boolean]
|
|
[:ledger-mapped/account entity-id]
|
|
[:credit {:optional true} [:maybe money]]
|
|
[:debit {:optional true} [:maybe money]]]
|
|
[:fn {:error/message "Must choose one of credit/debit"
|
|
:error/path [:credit]}
|
|
(fn [x]
|
|
(not (and (:credit x)
|
|
(:debit x))))]]]] ])
|
|
|
|
|
|
(defn summary-total-row* [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))]
|
|
|
|
(com/data-grid-row {:id "total-row"
|
|
: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"}
|
|
(com/data-grid-cell {})
|
|
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "TOTAL"])
|
|
(com/data-grid-cell {:class "text-right"}
|
|
(format "$%,.2f" total-debits))
|
|
(com/data-grid-cell {:class "text-right"}
|
|
(format "$%,.2f" total-credits)))))
|
|
|
|
(defn unbalanced-row* [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))]
|
|
|
|
(com/data-grid-row {:id "total-row"
|
|
: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"}
|
|
(com/data-grid-cell {})
|
|
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "UNBALANCED"])
|
|
(com/data-grid-cell {:class "text-right"}
|
|
(when (and
|
|
(not (dollars= total-credits total-debits))
|
|
(> total-debits total-credits))
|
|
(format "$%,.2f" (- total-debits total-credits))))
|
|
(com/data-grid-cell {:class "text-right"}
|
|
(when
|
|
(and (not (dollars= total-credits total-debits))
|
|
(> total-credits total-debits))
|
|
(format "$%,.2f" (- total-credits total-debits)))))))
|
|
|
|
(defn- account-typeahead*
|
|
[{:keys [name value client-id]}]
|
|
[:div.flex.flex-col
|
|
(com/typeahead {:name name
|
|
:placeholder "Search..."
|
|
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
|
|
{:client-id client-id
|
|
:purpose "invoice"})
|
|
:value value
|
|
:content-fn (fn [value]
|
|
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
|
client-id)))})])
|
|
|
|
(defn sales-summary-item-row* [{:keys [value client-id]}]
|
|
(let [manual? (fc/field-value (:sales-summary-item/manual? value))]
|
|
(com/data-grid-row (cond-> {:x-ref "p"
|
|
:x-data (hx/json {})}
|
|
(fc/field-value (:new? value)) (hx/htmx-transition-appear ))
|
|
(fc/with-field :db/id
|
|
(com/hidden {:name (fc/field-name)
|
|
:value (fc/field-value)}))
|
|
(when manual?
|
|
(fc/with-field :sales-summary-item/manual?
|
|
(com/hidden {:name (fc/field-name)
|
|
:value true})))
|
|
(com/data-grid-cell {}
|
|
(fc/with-field :sales-summary-item/category
|
|
(if manual?
|
|
(com/validated-field {:errors (fc/field-errors)}
|
|
(com/text-input {:placeholder "Category/Explanation"
|
|
:name (fc/field-name)
|
|
:value (fc/field-value)}))
|
|
|
|
(list
|
|
(com/hidden {:name (fc/field-name)
|
|
:value (fc/field-value)})
|
|
(fc/field-value (:sales-summary-item/category value))))))
|
|
(com/data-grid-cell {}
|
|
(fc/with-field :ledger-mapped/account
|
|
(com/validated-field {:errors (fc/field-errors)}
|
|
(account-typeahead* {:value (fc/field-value)
|
|
:client-id client-id
|
|
:name (fc/field-name)}))))
|
|
(com/data-grid-cell {:class "text-right"}
|
|
|
|
(if manual?
|
|
(fc/with-field :debit
|
|
(com/validated-field {:errors (fc/field-errors)}
|
|
(com/money-input {:class "w-24"
|
|
:name (fc/field-name)
|
|
:value (fc/field-value)})))
|
|
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
|
:ledger-side/debit)
|
|
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
|
(com/data-grid-cell {:class "text-right"}
|
|
|
|
(if manual?
|
|
(fc/with-field :credit
|
|
(com/validated-field {:errors (fc/field-errors)}
|
|
(com/money-input {:class "w-24"
|
|
:name (fc/field-name)
|
|
:value (fc/field-value)})))
|
|
(when (= (fc/field-value (:ledger-mapped/ledger-side value))
|
|
:ledger-side/credit)
|
|
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
|
(com/data-grid-cell {:class "align-top"}
|
|
(when manual?
|
|
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
|
|
|
|
(defrecord MainStep [linear-wizard]
|
|
mm/ModalWizardStep
|
|
(step-name [_]
|
|
"Main")
|
|
(step-key [_]
|
|
:main)
|
|
|
|
(edit-path [_ _]
|
|
[])
|
|
|
|
(step-schema [_]
|
|
(mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))
|
|
|
|
(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)) ])
|
|
|
|
: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-[850px] lg:h-[900px]")))
|
|
|
|
(defn attach-ledger [i]
|
|
(cond-> i
|
|
(:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
|
|
:ledger-mapped/amount (:credit i))
|
|
(:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
|
|
:ledger-mapped/amount (:debit i))
|
|
true (dissoc :credit :debit)
|
|
true (assoc :sales-summary-item/manual? true)))
|
|
|
|
(defrecord EditWizard [_ current-step]
|
|
mm/LinearModalWizard
|
|
(hydrate-from-request
|
|
[this request]
|
|
this)
|
|
(navigate [this step-key]
|
|
(assoc this :current-step step-key))
|
|
(get-current-step
|
|
[this]
|
|
(mm/get-step this :main))
|
|
(render-wizard [this {:keys [multi-form-state] :as request}]
|
|
(mm/default-render-wizard
|
|
this request
|
|
:form-params
|
|
(-> mm/default-form-props
|
|
(assoc :hx-put
|
|
(str (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-submit))))
|
|
:render-timeline? false))
|
|
(steps [_]
|
|
[:main])
|
|
(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]
|
|
(->MainStep this)))
|
|
(form-schema [_]
|
|
edit-schema)
|
|
(submit [this {:keys [multi-form-state request-method identity] :as request}]
|
|
(let [result (:snapshot multi-form-state )
|
|
transaction [:upsert-sales-summary {:db/id (:db/id result)
|
|
:sales-summary/items (map
|
|
(fn [i]
|
|
(if (:sales-summary-item/manual? i)
|
|
(attach-ledger i)
|
|
{: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))
|
|
{:flash? true
|
|
:request request})
|
|
:headers (cond-> {"hx-trigger" "modalclose"
|
|
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
|
|
"hx-reswap" "outerHTML"})))))
|
|
|
|
(def edit-wizard (->EditWizard nil nil))
|
|
|
|
(defn initial-edit-wizard-state [request]
|
|
(let [entity (dc/pull (dc/db conn) default-read (:db/id (:route-params request)))
|
|
entity (select-keys entity (mut/keys edit-schema))
|
|
entity (update entity :sales-summary/items (comp #(map (fn [x]
|
|
(if (= :ledger-side/debit (:ledger-mapped/ledger-side x))
|
|
(assoc x :debit (:ledger-mapped/amount x))
|
|
(assoc x :credit (:ledger-mapped/amount x))))
|
|
%) sort-items))]
|
|
|
|
(mm/->MultiStepFormState entity [] entity)))
|
|
|
|
(def key->handler
|
|
(apply-middleware-to-all-handlers
|
|
(->>
|
|
{::route/page (helper/page-route grid-page)
|
|
::route/table (helper/table-route grid-page)
|
|
::route/edit-wizard (-> mm/open-wizard-handler
|
|
(mm/wrap-wizard edit-wizard)
|
|
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
|
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
|
::route/edit-wizard-navigate (-> mm/next-handler
|
|
(mm/wrap-wizard edit-wizard)
|
|
(mm/wrap-decode-multi-form-state))
|
|
::route/new-summary-item (-> (add-new-entity-handler [:step-params :sales-summary/items]
|
|
(fn render [cursor request]
|
|
(sales-summary-item-row*
|
|
{:value cursor
|
|
: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
|
|
(mm/wrap-wizard edit-wizard)
|
|
(mm/wrap-decode-multi-form-state))})
|
|
(fn [h]
|
|
(-> h
|
|
(wrap-copy-qp-pqp)
|
|
(wrap-apply-sort grid-page)
|
|
(wrap-merge-prior-hx)
|
|
(wrap-schema-enforce :query-schema query-schema)
|
|
(wrap-schema-enforce :hx-schema query-schema)
|
|
(wrap-admin)
|
|
(wrap-client-redirect-unauthenticated)))))
|
|
|
|
|