Making progress on a good invoice experience.
This commit is contained in:
@@ -122,7 +122,7 @@ htmx.defineExtension('trigger-filter', {
|
|||||||
initDatepicker = function(elem) {
|
initDatepicker = function(elem) {
|
||||||
const modalParent = elem.closest('#modal-content');
|
const modalParent = elem.closest('#modal-content');
|
||||||
if (modalParent) {
|
if (modalParent) {
|
||||||
elem.dp = new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true, container: ".modal-stack"});
|
elem.dp = new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true, container: "#modal-content .modal-card"});
|
||||||
} else {
|
} else {
|
||||||
elem.dp = new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true});
|
elem.dp = new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -70,7 +70,7 @@
|
|||||||
[:div {:hx-get (bidi/path-for ssr-routes/only-routes
|
[:div {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
route
|
route
|
||||||
:request-method :get)
|
:request-method :get)
|
||||||
:hx-trigger "clientSelected from:body"
|
:hx-trigger "clientSelected from:body, invalidated from:body"
|
||||||
:hx-swap "outerHTML swap:300ms"
|
:hx-swap "outerHTML swap:300ms"
|
||||||
:id id}
|
:id id}
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,5 @@
|
|||||||
|
|
||||||
(defn modal-card-advanced- [params & children]
|
(defn modal-card-advanced- [params & children]
|
||||||
[:div (merge params
|
[:div (merge params
|
||||||
{:class (hh/add-class "bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content flex flex-col max-h-screen max-w-screen" (:class params "")) })
|
{:class (hh/add-class "modal-card bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white modal-content flex flex-col max-h-screen max-w-screen" (:class params "")) })
|
||||||
children])
|
children])
|
||||||
|
|||||||
@@ -4,13 +4,8 @@
|
|||||||
[auto-ap.ssr.svg :as svg]))
|
[auto-ap.ssr.svg :as svg]))
|
||||||
|
|
||||||
(defn link-dropdown [links]
|
(defn link-dropdown [links]
|
||||||
(if (<= (count links) 2)
|
(if (> (count links) 0)
|
||||||
[:div.flex.flex-col.gap-y-2
|
|
||||||
(for [l links]
|
|
||||||
[:div.flex-initial
|
|
||||||
[:a {:href (:link l)}
|
|
||||||
(com/pill {:color (or (:color l) :primary) :class "truncate"}
|
|
||||||
(:content l))]])]
|
|
||||||
[:div {:x-data (hx/json {:popper nil
|
[:div {:x-data (hx/json {:popper nil
|
||||||
:show false})
|
:show false})
|
||||||
"@click.outside" "show=false"
|
"@click.outside" "show=false"
|
||||||
@@ -18,13 +13,13 @@
|
|||||||
:x-init "popper = Popper.createPopper($refs.link, $refs.tooltip, {placement: 'bottom', strategy: 'fixed'})"}
|
:x-init "popper = Popper.createPopper($refs.link, $refs.tooltip, {placement: 'bottom', strategy: 'fixed'})"}
|
||||||
|
|
||||||
(com/a-icon-button {:x-ref "link" "@click.prevent" "show=!show; $nextTick(() => popper.update());" :class "relative"}
|
(com/a-icon-button {:x-ref "link" "@click.prevent" "show=!show; $nextTick(() => popper.update());" :class "relative"}
|
||||||
svg/three-dots
|
svg/paperclip
|
||||||
(com/badge {} (count links)))
|
(com/badge {} (count links)))
|
||||||
[:div.divide-y.divide-gray-200.bg-white.rounded-lg.shadow (hx/alpine-appear {:x-ref "tooltip" :x-show "show" :data-key "show"})
|
[:div.divide-y.divide-gray-200.bg-white.rounded-lg.shadow.z-50 (hx/alpine-appear {:x-ref "tooltip" :x-show "show" :data-key "show"})
|
||||||
[:div {:class "p-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}
|
[:div {:class "p-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}
|
||||||
[:div.flex.flex-col.gap-y-2
|
[:div.flex.flex-col.gap-y-2
|
||||||
(for [l links]
|
(for [l links]
|
||||||
[:div.flex-initial
|
[:div.flex-initial
|
||||||
[:a {:href (:link l)}
|
[:a {:href (:link l)}
|
||||||
(com/pill {:color (or (:color l) :primary) :class "truncate w-24 block shrink grow-0"}
|
(com/pill {:color (or (:color l) :primary) :class "truncate block shrink grow-0"}
|
||||||
(:content l))]])]]]]))
|
(:content l))]])]]]]))
|
||||||
@@ -208,6 +208,7 @@
|
|||||||
|
|
||||||
(defn table-route [grid-spec]
|
(defn table-route [grid-spec]
|
||||||
(-> (fn table [{:keys [identity] :as request}]
|
(-> (fn table [{:keys [identity] :as request}]
|
||||||
|
(alog/peek ::qp (pr-str (:query-params request)))
|
||||||
(let [unparse-query-params (or (:unparse-query grid-spec)
|
(let [unparse-query-params (or (:unparse-query grid-spec)
|
||||||
default-unparse-query-params)]
|
default-unparse-query-params)]
|
||||||
(html-response (table*
|
(html-response (table*
|
||||||
@@ -216,11 +217,18 @@
|
|||||||
request)
|
request)
|
||||||
:headers {"hx-push-url" (str "?" (url/map->query
|
:headers {"hx-push-url" (str "?" (url/map->query
|
||||||
(dissoc (if (:query-schema grid-spec)
|
(dissoc (if (:query-schema grid-spec)
|
||||||
(update (filter-vals #(not (nil? %))
|
(do
|
||||||
(m/encode (:query-schema grid-spec)
|
(alog/peek ::setup4
|
||||||
(:query-params request)
|
(pr-str (update (filter-vals #(not (nil? %))
|
||||||
main-transformer))
|
(m/encode (:query-schema grid-spec)
|
||||||
"sort" sort->query)
|
(:query-params request)
|
||||||
|
main-transformer))
|
||||||
|
"sort" sort->query)))
|
||||||
|
(update (filter-vals #(not (nil? %))
|
||||||
|
(m/encode (:query-schema grid-spec)
|
||||||
|
(:query-params request)
|
||||||
|
main-transformer))
|
||||||
|
"sort" sort->query))
|
||||||
(unparse-query-params (:parsed-query-params request)))
|
(unparse-query-params (:parsed-query-params request)))
|
||||||
"selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here
|
"selected" "all-selected")))} ;; TODO seems hacky to special case selected and all-selected here
|
||||||
:oob (when-let [oob-render (:oob-render grid-spec)]
|
:oob (when-let [oob-render (:oob-render grid-spec)]
|
||||||
|
|||||||
@@ -132,7 +132,9 @@
|
|||||||
{:account-client-override/client [:db/id]}]}]}]
|
{:account-client-override/client [:db/id]}]}]}]
|
||||||
[:transaction/_invoices :as :invoice/transaction] [:db/id]
|
[:transaction/_invoices :as :invoice/transaction] [:db/id]
|
||||||
[:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount
|
[:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount
|
||||||
{[:transaction/_payment :as :payment/transaction] [:db/id]}]
|
|
||||||
|
{[: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/status :xform iol-ion.query/ident] [:db/ident]
|
||||||
:invoice/vendor [:vendor/name :db/id]}])
|
:invoice/vendor [:vendor/name :db/id]}])
|
||||||
|
|
||||||
@@ -473,12 +475,15 @@
|
|||||||
(link-dropdown
|
(link-dropdown
|
||||||
(concat (->> i
|
(concat (->> i
|
||||||
:invoice/payments
|
:invoice/payments
|
||||||
|
(filter (fn [p]
|
||||||
|
(not= :payment-status/voided
|
||||||
|
(:payment/status p))))
|
||||||
(mapcat (fn [p]
|
(mapcat (fn [p]
|
||||||
(cond-> [{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
(cond-> [{:link (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
::payment-route/page)
|
::payment-route/page)
|
||||||
{:exact-match-id (:db/id p)})
|
{:exact-match-id (:db/id p)})
|
||||||
:content (str (format "$%,.2f" (:payment/amount p))
|
:content (str (format "$%,.2f" (:payment/amount p))
|
||||||
(some-> (:payment/date p) coerce/to-date-time (atime/unparse-local atime/standard-time) (#(str " on " %))))}]
|
(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 client-routes/routes :transactions)
|
(:payment/transaction p) (conj {:link (hu/url (bidi/path-for client-routes/routes :transactions)
|
||||||
{:exact-match-id (:db/id (first (:payment/transaction p)))})
|
{:exact-match-id (:db/id (first (:payment/transaction p)))})
|
||||||
:color :secondary
|
:color :secondary
|
||||||
@@ -609,16 +614,10 @@
|
|||||||
|
|
||||||
;; TODO
|
;; TODO
|
||||||
;; voiding invoice - should you allow if there are ANY payments? i think no
|
;; 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 paying balances from set of invoices for one vendor
|
||||||
;; Allow for credits, just highlight it
|
;; Allow for credits, just highlight it
|
||||||
;; Entering 0.00 payment should just remove it from the set
|
;; Entering 0.00 payment should just remove it from the set
|
||||||
;; Better text to link to pdfs
|
;; inline all the check printing stuff
|
||||||
;; 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]
|
(defn does-amount-exceed-outstanding? [amount outstanding-balance]
|
||||||
(let [outstanding-balance (round-money outstanding-balance)
|
(let [outstanding-balance (round-money outstanding-balance)
|
||||||
@@ -655,8 +654,8 @@
|
|||||||
invoices)))]]]
|
invoices)))]]]
|
||||||
[:has-warning? :boolean]
|
[:has-warning? :boolean]
|
||||||
[:bank-account entity-id]
|
[:bank-account entity-id]
|
||||||
[:check-number {:optional true}
|
[:check-number {:optional true} :int]
|
||||||
:int]
|
[:handwritten-date {:optional true} [:maybe clj-date-schema]]
|
||||||
[:mode [:enum :simple :advanced]]
|
[:mode [:enum :simple :advanced]]
|
||||||
[:method [:enum :debit :print-check :cash :handwrite-check]]]))
|
[:method [:enum :debit :print-check :cash :handwrite-check]]]))
|
||||||
|
|
||||||
@@ -679,9 +678,7 @@
|
|||||||
[:div.font-medium.text-gray-700 (:bank-account/name bank-account)]
|
[:div.font-medium.text-gray-700 (:bank-account/name bank-account)]
|
||||||
[:div.font-light.text-gray-600 (:bank-account/bank-name bank-account)]]
|
[:div.font-light.text-gray-600 (:bank-account/bank-name bank-account)]]
|
||||||
[:div.grow-0.m-2.self-center
|
[:div.grow-0.m-2.self-center
|
||||||
|
|
||||||
(com/button {:x-ref "button"
|
(com/button {:x-ref "button"
|
||||||
|
|
||||||
"@click.prevent.capture" "chosen=true; $nextTick(() => popper.update())"}
|
"@click.prevent.capture" "chosen=true; $nextTick(() => popper.update())"}
|
||||||
"Pay")
|
"Pay")
|
||||||
[:div.flex.flex-col.gap-2 (hx/alpine-appear {:x-show "chosen" :x-ref "tooltip"
|
[:div.flex.flex-col.gap-2 (hx/alpine-appear {:x-show "chosen" :x-ref "tooltip"
|
||||||
@@ -729,7 +726,6 @@
|
|||||||
:to (mm/encode-step-key :payment-details)})}
|
:to (mm/encode-step-key :payment-details)})}
|
||||||
"Handwrite check"))]]])])
|
"Handwrite check"))]]])])
|
||||||
|
|
||||||
|
|
||||||
(defmulti bank-account-card (fn [ba _]
|
(defmulti bank-account-card (fn [ba _]
|
||||||
(:bank-account/type ba)))
|
(:bank-account/type ba)))
|
||||||
(defmethod bank-account-card :bank-account-type/cash [bank-account can-handwrite?]
|
(defmethod bank-account-card :bank-account-type/cash [bank-account can-handwrite?]
|
||||||
@@ -807,7 +803,7 @@
|
|||||||
[])
|
[])
|
||||||
|
|
||||||
(step-schema [_]
|
(step-schema [_]
|
||||||
(mut/select-keys (mm/form-schema linear-wizard) #{:invoices :check-number}))
|
(mut/select-keys (mm/form-schema linear-wizard) #{:invoices :check-number :handwritten-date}))
|
||||||
|
|
||||||
(render-step [this request]
|
(render-step [this request]
|
||||||
(mm/default-render-step
|
(mm/default-render-step
|
||||||
@@ -816,7 +812,7 @@
|
|||||||
:body (mm/default-step-body
|
:body (mm/default-step-body
|
||||||
{}
|
{}
|
||||||
[:div {}
|
[:div {}
|
||||||
(if (= :handwrite-check (:method (:snapshot (:multi-form-state request))))
|
(when (= :handwrite-check (:method (:snapshot (:multi-form-state request))))
|
||||||
(fc/with-field :check-number
|
(fc/with-field :check-number
|
||||||
(com/validated-field
|
(com/validated-field
|
||||||
{:errors (fc/field-errors)
|
{:errors (fc/field-errors)
|
||||||
@@ -825,6 +821,16 @@
|
|||||||
:name (fc/field-name)
|
:name (fc/field-name)
|
||||||
:error? (fc/field-errors)
|
:error? (fc/field-errors)
|
||||||
:placeholder "10001"}))))
|
:placeholder "10001"}))))
|
||||||
|
(when (= :handwrite-check (:method (:snapshot (:multi-form-state request))))
|
||||||
|
(fc/with-field :handwritten-date
|
||||||
|
(com/validated-field
|
||||||
|
{:errors (fc/field-errors)
|
||||||
|
:label "Date"}
|
||||||
|
(com/date-input {:value (-> (fc/field-value)
|
||||||
|
(atime/unparse-local atime/normal-date))
|
||||||
|
:name (fc/field-name)
|
||||||
|
:error? (fc/field-errors)
|
||||||
|
:placeholder "1/1/2020"}))))
|
||||||
(com/radio-list {:x-model "mode"
|
(com/radio-list {:x-model "mode"
|
||||||
:name "step-params[mode]"
|
:name "step-params[mode]"
|
||||||
:options [{:value "simple"
|
:options [{:value "simple"
|
||||||
@@ -858,7 +864,11 @@
|
|||||||
(-> (fc/field-value) :invoice :invoice/invoice-number))
|
(-> (fc/field-value) :invoice :invoice/invoice-number))
|
||||||
(com/data-grid-cell
|
(com/data-grid-cell
|
||||||
{:class "text-right"}
|
{:class "text-right"}
|
||||||
(format "$%,.2f" (-> (fc/field-value) :invoice :invoice/outstanding-balance)))
|
[:span.inline-flex.gap-2
|
||||||
|
(when (< (-> (fc/field-value) :invoice :invoice/outstanding-balance) 0.0)
|
||||||
|
(com/pill {:color :yellow}
|
||||||
|
"credit")) ;; TODO this credit should be based on the total for the vendor
|
||||||
|
(format "$%,.2f" (-> (fc/field-value) :invoice :invoice/outstanding-balance))])
|
||||||
(com/data-grid-cell
|
(com/data-grid-cell
|
||||||
{:class "w-20"}
|
{:class "w-20"}
|
||||||
(fc/with-field :amount
|
(fc/with-field :amount
|
||||||
@@ -872,15 +882,16 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn add-handwritten-check [request wizard snapshot]
|
(defn add-handwritten-check [request wizard snapshot]
|
||||||
(let [snapshot (assoc snapshot :date (time/now))
|
(let [invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic
|
||||||
invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic
|
|
||||||
bank-account-id (:bank-account snapshot)
|
bank-account-id (:bank-account snapshot)
|
||||||
bank-account (d-bank-accounts/get-by-id bank-account-id)
|
bank-account (d-bank-accounts/get-by-id bank-account-id)
|
||||||
|
_ (when-not (= 1 (count (set (map (comp :db/id :invoice/vendor) invoices))))
|
||||||
|
(throw (ex-info "Can only write a handwritten check for a single vendor." {:type :form-validation})))
|
||||||
_ (doseq [invoice invoices]
|
_ (doseq [invoice invoices]
|
||||||
(assert-can-see-client (:identity request) (:invoice/client invoice)))
|
(assert-can-see-client (:identity request) (:invoice/client invoice)))
|
||||||
client-id (:db/id (:invoice/client (first invoices)))
|
client-id (:db/id (:invoice/client (first invoices)))
|
||||||
_ (validate-belonging (:db/id (:client/_bank-accounts bank-account)) invoices bank-account)
|
_ (validate-belonging (:db/id (:client/_bank-accounts bank-account)) invoices bank-account)
|
||||||
_ (assert-not-locked client-id (:date snapshot))
|
_ (assert-not-locked client-id (:handwritten-date snapshot))
|
||||||
invoice-payment-lookup (by :invoice-id :amount (:invoices snapshot))
|
invoice-payment-lookup (by :invoice-id :amount (:invoices snapshot))
|
||||||
base-payment (base-payment invoices
|
base-payment (base-payment invoices
|
||||||
(:invoice/vendor (first invoices))
|
(:invoice/vendor (first invoices))
|
||||||
@@ -894,7 +905,7 @@
|
|||||||
:payment/type :payment-type/check
|
:payment/type :payment-type/check
|
||||||
:payment/status :payment-status/pending
|
:payment/status :payment-status/pending
|
||||||
:payment/check-number (:check-number snapshot)
|
:payment/check-number (:check-number snapshot)
|
||||||
:payment/date (coerce/to-date (:date snapshot)))]
|
:payment/date (coerce/to-date (:handwritten-date snapshot)))]
|
||||||
(invoice-payments invoices invoice-payment-lookup))
|
(invoice-payments invoices invoice-payment-lookup))
|
||||||
(:identity request))]
|
(:identity request))]
|
||||||
(doseq [[_ i] (:tempids result)]
|
(doseq [[_ i] (:tempids result)]
|
||||||
@@ -980,7 +991,7 @@
|
|||||||
(throw (Exception. "Check number is required")))
|
(throw (Exception. "Check number is required")))
|
||||||
true))
|
true))
|
||||||
result (exception->4xx
|
result (exception->4xx
|
||||||
#(if (= :handwrite-check (:method snapshot)) ;; TODO validation for single vendor again?
|
#(if (= :handwrite-check (:method snapshot))
|
||||||
(add-handwritten-check request this snapshot)
|
(add-handwritten-check request this snapshot)
|
||||||
(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
|
(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
|
||||||
:amount (:amount i)})
|
:amount (:amount i)})
|
||||||
@@ -1003,9 +1014,15 @@
|
|||||||
[:div.flex.flex-col.mt-4.space-y-4.items-center
|
[: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
|
[:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300.animate-gg
|
||||||
svg/thumbs-up]
|
svg/thumbs-up]
|
||||||
[:div "Your checks are ready. Click "
|
(when-not (:pdf-url result)
|
||||||
(com/link {:href (:pdf-url result)} "here")
|
[:div "That's a wrap. Your payment is complete."])
|
||||||
" to download."]])))))))
|
(when (:pdf-url result)
|
||||||
|
[:div "Your checks are ready. Click "
|
||||||
|
(com/link {:href (:pdf-url result) :target "_new"} "here")
|
||||||
|
" to download and print."])
|
||||||
|
(when (:pdf-url result)
|
||||||
|
[:div.text-xs.italic [:em "Remember to turn off all scaling and margins."]])])))
|
||||||
|
:headers {"hx-trigger" "invalidated"}))))
|
||||||
|
|
||||||
(def pay-wizard
|
(def pay-wizard
|
||||||
(->PayWizard nil nil nil))
|
(->PayWizard nil nil nil))
|
||||||
@@ -1029,7 +1046,6 @@
|
|||||||
(mm/wrap-wizard pay-wizard)
|
(mm/wrap-wizard pay-wizard)
|
||||||
(mm/wrap-init-multi-form-state (fn [request]
|
(mm/wrap-init-multi-form-state (fn [request]
|
||||||
(exception->notification
|
(exception->notification
|
||||||
;; TODO handle locked
|
|
||||||
#(let [selected-ids (selected->ids request (:query-params request))
|
#(let [selected-ids (selected->ids request (:query-params request))
|
||||||
selected-ids (->> (dc/q '[:find ?i
|
selected-ids (->> (dc/q '[:find ?i
|
||||||
:in $ [?i ...]
|
:in $ [?i ...]
|
||||||
@@ -1042,7 +1058,7 @@
|
|||||||
selected-ids)
|
selected-ids)
|
||||||
(map first))
|
(map first))
|
||||||
_ (when (= 0 (count selected-ids))
|
_ (when (= 0 (count selected-ids))
|
||||||
(throw (ex-info "No selected invoices are applicable for payment" {:type :notification}) ))
|
(throw (ex-info "No selected invoices are applicable for payment" {:type :notification})))
|
||||||
|
|
||||||
has-warning? (and (:selected (:query-params request))
|
has-warning? (and (:selected (:query-params request))
|
||||||
(not= (count selected-ids)
|
(not= (count selected-ids)
|
||||||
@@ -1058,15 +1074,16 @@
|
|||||||
(map first)
|
(map first)
|
||||||
(sort-by (juxt (comp :invoice/vendor :vendor/name)
|
(sort-by (juxt (comp :invoice/vendor :vendor/name)
|
||||||
:invoice/invoice-number)))]
|
:invoice/invoice-number)))]
|
||||||
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i)
|
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i)
|
||||||
:amount (:invoice/outstanding-balance i)})
|
:amount (:invoice/outstanding-balance i)})
|
||||||
invoices)
|
invoices)
|
||||||
:mode :simple
|
:mode :simple
|
||||||
:client (-> invoices first :invoice/client :db/id)
|
:client (-> invoices first :invoice/client :db/id)
|
||||||
:has-warning? (boolean has-warning?)}
|
:has-warning? (boolean has-warning?)
|
||||||
[]
|
:handwritten-date (time/now)}
|
||||||
{:mode :simple
|
[]
|
||||||
:has-warning? (boolean has-warning?)}))))))
|
{:mode :simple
|
||||||
|
:has-warning? (boolean has-warning?)}))))))
|
||||||
::route/pay-submit (-> mm/submit-handler
|
::route/pay-submit (-> mm/submit-handler
|
||||||
|
|
||||||
(mm/wrap-wizard pay-wizard)
|
(mm/wrap-wizard pay-wizard)
|
||||||
|
|||||||
@@ -496,3 +496,20 @@
|
|||||||
[:path {:d "M6.5 9.616H1a0.5 0.5 0 0 0 -0.5 0.5v12a0.5 0.5 0 0 0 0.5 0.5h22a0.5 0.5 0 0 0 0.5 -0.5v-12a0.5 0.5 0 0 0 -0.5 -0.5h-5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
[:path {:d "M6.5 9.616H1a0.5 0.5 0 0 0 -0.5 0.5v12a0.5 0.5 0 0 0 0.5 0.5h22a0.5 0.5 0 0 0 0.5 -0.5v-12a0.5 0.5 0 0 0 -0.5 -0.5h-5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||||
[:path {:d "M17.004 12.616h3s0.5 0 0.5 0.5v2s0 0.5 -0.5 0.5h-3s-0.5 0 -0.5 -0.5v-2s0 -0.5 0.5 -0.5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
[:path {:d "M17.004 12.616h3s0.5 0 0.5 0.5v2s0 0.5 -0.5 0.5h-3s-0.5 0 -0.5 -0.5v-2s0 -0.5 0.5 -0.5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]
|
||||||
[:path {:d "m16.757 2.823 -0.8 -0.8a1 1 0 0 0 -1.414 0L12.5 4.07", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]])
|
[:path {:d "m16.757 2.823 -0.8 -0.8a1 1 0 0 0 -1.414 0L12.5 4.07", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]])
|
||||||
|
|
||||||
|
(def paperclip
|
||||||
|
[:svg
|
||||||
|
{:xmlns "http://www.w3.org/2000/svg",
|
||||||
|
:viewBox "0 0 24 24",
|
||||||
|
:id "Attachment--Streamline-Streamline--3.0"}
|
||||||
|
[:desc "Attachment Streamline Icon: https://streamlinehq.com"]
|
||||||
|
[:defs]
|
||||||
|
[:title "attachment"]
|
||||||
|
[:path
|
||||||
|
{:d
|
||||||
|
"m7.618 15.345 8.666 -8.666a2.039 2.039 0 1 1 2.883 2.883L7.461 21.305a4.078 4.078 0 0 1 -5.767 -5.768L13.928 3.305a5.606 5.606 0 0 1 7.929 7.928L13.192 19.9",
|
||||||
|
:fill "none",
|
||||||
|
:stroke "currentcolor",
|
||||||
|
:stroke-linecap "round",
|
||||||
|
:stroke-linejoin "round",
|
||||||
|
:stroke-width "1"}]])
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
[malli.registry :as mr]
|
[malli.registry :as mr]
|
||||||
[malli.transform :as mt2]
|
[malli.transform :as mt2]
|
||||||
[slingshot.slingshot :refer [throw+ try+]]
|
[slingshot.slingshot :refer [throw+ try+]]
|
||||||
[taoensso.encore :refer [filter-vals]]))
|
[taoensso.encore :refer [filter-vals]]
|
||||||
|
[reagent.debug :as d]))
|
||||||
|
|
||||||
(defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}]
|
(defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}]
|
||||||
{:status status
|
{:status status
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
[hiccup]
|
[hiccup]
|
||||||
(mapcat identity
|
(mapcat identity
|
||||||
(-> opts
|
(-> opts
|
||||||
(assoc-in [:headers "hx-trigger"] "modalopen")
|
(update-in [:headers "hx-trigger"] (fn [ht] (str/join ", " (filter identity [ht "modalopen"]))))
|
||||||
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
(assoc-in [:headers "hx-retarget"] "#modal-content")
|
||||||
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
(assoc-in [:headers "hx-reswap"] "innerHTML"))))))
|
||||||
|
|
||||||
@@ -179,23 +180,35 @@
|
|||||||
:form-validation-errors [m]}))))
|
:form-validation-errors [m]}))))
|
||||||
|
|
||||||
(def clj-date-schema
|
(def clj-date-schema
|
||||||
(mc/schema [inst? {:decode/arbitrary (fn [m]
|
(mc/schema [inst? {:date-format atime/normal-date}]))
|
||||||
(alog/peek ::decode
|
|
||||||
(if (string? m)
|
|
||||||
(coerce/to-date-time (atime/parse m atime/normal-date))
|
|
||||||
|
|
||||||
m)))
|
(def date-transformer
|
||||||
:encode/arbitrary (fn [m]
|
(mt2/transformer
|
||||||
(alog/peek ::encode
|
{:decoders
|
||||||
(cond
|
{'inst? {:compile (fn [schema _]
|
||||||
(inst? m)
|
(let [properties (mc/properties schema)
|
||||||
(atime/unparse-local (coerce/to-date-time m) atime/normal-date)
|
format (:format properties atime/normal-date)]
|
||||||
|
(fn [m]
|
||||||
|
(if (string? m)
|
||||||
|
(coerce/to-date-time (atime/parse m format))
|
||||||
|
|
||||||
(instance? org.joda.time.DateTime m)
|
m))))}}
|
||||||
(atime/unparse-local m atime/normal-date)
|
:encoders
|
||||||
|
{'inst?
|
||||||
|
{:compile (fn [schema _]
|
||||||
|
(let [properties (mc/properties schema)
|
||||||
|
format (:format properties atime/normal-date)]
|
||||||
|
(fn [m]
|
||||||
|
(cond
|
||||||
|
(inst? m)
|
||||||
|
(atime/unparse-local (coerce/to-date-time m) format)
|
||||||
|
|
||||||
|
(instance? org.joda.time.DateTime m)
|
||||||
|
(atime/unparse-local m format)
|
||||||
|
|
||||||
|
:else
|
||||||
|
m))))}}}))
|
||||||
|
|
||||||
:else
|
|
||||||
m)))}]))
|
|
||||||
|
|
||||||
|
|
||||||
(def date-range-transformer
|
(def date-range-transformer
|
||||||
@@ -307,6 +320,7 @@
|
|||||||
(def main-transformer
|
(def main-transformer
|
||||||
(mt2/transformer
|
(mt2/transformer
|
||||||
parse-empty-as-nil
|
parse-empty-as-nil
|
||||||
|
date-transformer
|
||||||
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
(mt2/key-transformer {:encode keyword->str :decode str->keyword})
|
||||||
mt2/string-transformer
|
mt2/string-transformer
|
||||||
mt2/json-transformer
|
mt2/json-transformer
|
||||||
@@ -559,7 +573,7 @@
|
|||||||
|
|
||||||
(defn round-money [d]
|
(defn round-money [d]
|
||||||
(with-precision 2
|
(with-precision 2
|
||||||
(double (.setScale (bigdec d) 2 java.math.RoundingMode/HALF_UP))))
|
(double (.setScale (bigdec d) 2 java.math.RoundingMode/HALF_UP))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user