progress on invoices.

This commit is contained in:
2024-03-14 07:16:59 -07:00
parent 3b49a0804a
commit 5b9c4b7aef
17 changed files with 519 additions and 110 deletions

View File

@@ -4,7 +4,7 @@
: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]
[auto-ap.graphql.checks :as gq-checks :refer [print-checks-internal]]
[auto-ap.graphql.utils :refer [assert-can-see-client
exception->notification
extract-client-ids notify-if-locked]]
@@ -17,15 +17,18 @@
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[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.hiccup-helper :as hh]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema
dissoc-nil-transformer entity-id html-response
main-transformer modal-response ref->enum-schema strip
wrap-entity wrap-merge-prior-hx wrap-schema-enforce]]
main-transformer modal-response money ref->enum-schema
strip wrap-entity wrap-merge-prior-hx wrap-schema-enforce]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
@@ -33,7 +36,8 @@
[datomic.api :as dc]
[hiccup.util :as hu]
[malli.core :as mc]
[malli.transform :as mt]))
[malli.transform :as mt]
[malli.util :as mut]))
(defn exact-match-id* [request]
@@ -310,10 +314,14 @@
0)]
[:div {:hx-target "this"
:hx-get (bidi/path-for ssr-routes/only-routes
::route/pay-wizard)
:hx-trigger "click from:#pay-button"
:x-data (hx/json {:popper nil
:hovering false})
"x-init" "popper = Popper.createPopper($refs.button, $refs.tooltip, {placement: 'bottom', strategy: 'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
(com/button {:color :primary
:id "pay-button"
:disabled (or (= (count (:ids params)) 0)
(not= 1 selected-client-count))
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
@@ -330,11 +338,11 @@
(str "Pay " (count (:ids params)) " invoices")
"Pay")
(when (or (= 0 (count ids))
(when (or (= 0 (count ids))
(> selected-client-count 1))
(com/badge {} "!")))
[:div (hx/alpine-appear {:x-ref "tooltip"
:x-show "hovering"
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"})
(cond
@@ -468,17 +476,6 @@
(def row* (partial helper/row* grid-page))
(comment
(mc/decode query-schema {"exact-match-id" "123"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"exact-match-id" nil} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"exact-match-id" ""} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"start-date" "12/21/2023"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"payment-type" "food"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"vendor" "87"} (mt/transformer main-transformer mt/strip-extra-keys-transformer))
(mc/decode query-schema {"start-date" #inst "2023-12-21T08:00:00.000-00:00"} (mt/transformer main-transformer mt/strip-extra-keys-transformer)))
;; TODO clientize accounts
@@ -596,6 +593,272 @@
updated-count
(count ids))})})))
(def payment-form-schema
(mc/schema [:map
[:client entity-id]
[:invoices [:vector {:coerce? true}
[:map
[:invoice-id entity-id]
[:amount money]]]]
[:bank-account entity-id]
[:method :string]]))
(defn bank-account-card-base [{:keys [bg-color text-color icon bank-account]}]
[:div {:class "w-[30em] cursor-move"}
(com/card {:class "w-full"}
[:div.flex.items-stretch {:x-data (hx/json {:chosen false
:popper nil})
"x-init" "popper = Popper.createPopper($refs.button, $refs.tooltip, {placement: 'bottom', strategy: 'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
(com/hidden {:name "item"
:value (:db/id bank-account)})
[:div.grow-0.flex.flex-col.justify-center
[:div.p-1.m-2.rounded-full
{:class
bg-color}
[:div {:class
(hh/add-class "p-1.5 w-8 h-8" text-color)}
icon]]]
[:div.flex.flex-col.grow.m-2
[:div.font-medium.text-gray-700 (:bank-account/name bank-account)]
[:div.font-light.text-gray-600 (:bank-account/bank-name bank-account)]]
[:div.grow-0.m-2.self-center
(com/button {:x-ref "button"
"@click.prevent.capture" "chosen=true; $nextTick(() => popper.update())"}
"Pay")
[:div.flex.flex-col.gap-2 (hx/alpine-appear {:x-show "chosen" :x-ref "tooltip"
:data-key "vis"
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"
"@click.outside" "chosen=false"})
(com/button {:color :primary
:x-show "chosen"
"@click.prevent.capture" "chosen=true"
:hx-vals (hx/json {"step-params[bank-account]" (:db/id bank-account)})
:hx-put (hu/url (bidi/path-for ssr-routes/only-routes ::route/pay-wizard-navigate)
{:from (mm/encode-step-key :choose-method)
:to (mm/encode-step-key :payment-details)})}
"Print check")
(com/button {:x-show "chosen"
"@click.prevent.capture" "chosen=true"}
"Debit")
(com/button {:x-show "chosen"
"@click.prevent.capture" "chosen=true"}
"Handwrite check")]]])])
(defmulti bank-account-card (comp :bank-account/type))
(defmethod bank-account-card :bank-account-type/cash [bank-account]
(bank-account-card-base {:bg-color "bg-green-50"
:text-color "text-green-600"
:icon svg/dollar
:bank-account bank-account}))
(defmethod bank-account-card
:bank-account-type/credit
[bank-account]
(bank-account-card-base {:bg-color "bg-purple-50"
:text-color "text-purple-600"
:icon svg/credit-card
:bank-account bank-account}))
(defmethod bank-account-card
:bank-account-type/check [bank-account]
(bank-account-card-base {:bg-color "bg-blue-50"
:text-color "text-blue-600"
:icon svg/check
:bank-account bank-account}))
(defrecord ChoosePaymentMethodModal [linear-wizard]
mm/ModalWizardStep
(step-name [_]
"Payment method")
(step-key [_]
:choose-method)
(edit-path [_ _]
[])
(step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:bank-account}))
(render-step [this request]
(mm/default-render-step
linear-wizard this
:head [:div "Pay " (count (:invoices (:snapshot (:multi-form-state request))))]
: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))]
[:div.flex.flex-col.space-y-2
(for [ba bank-accounts]
(bank-account-card ba))]))
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/navigate)
:validation-route ::route/navigate)))
(defrecord PaymentDetailsStep [linear-wizard]
mm/ModalWizardStep
(step-name [_]
"Details")
(step-key [_]
:payment-details)
(edit-path [_ _]
[])
(step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:invoices}))
(render-step [this request]
(mm/default-render-step
linear-wizard this
:head [:div "HI"]
:body (mm/default-step-body
{}
[:div {}
(com/radio-list {:x-model "mode"
:name "mode"
:options [{:value "simple"
:content "Pay in full"}
{:value "advanced"
:content "Customize payments"}]})
[:div.space-y-4 (hx/alpine-appear {:x-show "mode==\"advanced\""})
(fc/with-field :invoices
(com/data-grid
{:headers [(com/data-grid-header {} "Vendor")
(com/data-grid-header {} "Invoice Number")
(com/data-grid-header {:class "text-right"} "Total")
(com/data-grid-header {:class "text-right"} "Pay")]}
(fc/cursor-map
(fn [i]
(println fc/*current*)
(com/data-grid-row
{}
(com/data-grid-cell
{}
(-> (fc/field-value) :invoice :invoice/vendor :vendor/name))
(com/data-grid-cell
{}
(fc/with-field :invoice-id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
(-> (fc/field-value) :invoice :invoice/invoice-number))
(com/data-grid-cell
{:class "text-right"}
(format "$%,.2f" (-> (fc/field-value) :invoice :invoice/outstanding-balance)))
(com/data-grid-cell
{:class "w-20"}
(fc/with-field :amount
(com/validated-field {:errors (fc/field-errors)}
(com/money-input {:value (format "%.2f" (fc/field-value)) :class "w-20"
:name (fc/field-name)
:error? (fc/error?)})))))))))]])
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/pay-wizard-navigate)
:validation-route ::route/navigate)))
(defrecord PayWizard [form-params current-step entity]
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
:invoice/invoice-number
:db/id])
:in $ [?i ...]]
(dc/db conn)
(map :invoice-id (get-in request [:multi-form-state :step-params :invoices])))
(map first)
(sort-by (juxt (comp :invoice/vendor :vendor/name)
:invoice/invoice-number)))
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)))]
(clojure.pprint/pprint (:step-params (:multi-form-state request)))
(mm/default-render-wizard
this request
:form-params
(-> mm/default-form-props
(assoc :hx-post
(str (bidi/path-for ssr-routes/only-routes ::route/pay-submit)))
(assoc :x-data (hx/json {:mode "simple"}))))))
(steps [_]
[:choose-method
:payment-details])
(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]
(if (= :step step-key-type)
(get {:choose-method (->ChoosePaymentMethodModal this)
:payment-details (->PaymentDetailsStep this)}
step-key)
(get {:bank-account (->ChoosePaymentMethodModal this)}
(first step-key)))))
(form-schema [_] payment-form-schema)
(submit [_ {: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)
result (print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
:amount (:amount i)})
(:invoices snapshot))
(:client snapshot)
(:bank-account snapshot)
:payment-type/check
identity)]
(alog/info ::printed :result result)
(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
svg/thumbs-up]
[:div "Your checks are ready. Click "
(com/link {:href (:pdf-url result)} "here")
" to download."]]))))
#_(html-response
[:div]
:headers {"hx-trigger" (hx/json {:notification (str "Printed!")})}))))
(def pay-wizard
(->PayWizard nil nil nil))
(def key->handler
@@ -611,7 +874,38 @@
(wrap-admin))
::route/bulk-delete (-> bulk-delete-dialog
(wrap-admin))
::route/pay-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard pay-wizard)
(mm/wrap-init-multi-form-state (fn [request]
;; 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)
:client (-> invoices first :invoice/client :db/id)}
[]
{}))))
#_(wrap-entity [:route-params :db/id] default-read)
#_(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
::route/pay-submit (-> mm/submit-handler
(mm/wrap-wizard pay-wizard)
(mm/wrap-decode-multi-form-state))
::route/pay-wizard-navigate
(-> mm/next-handler
(mm/wrap-wizard pay-wizard)
(mm/wrap-decode-multi-form-state))
::route/table (helper/table-route grid-page)}
(fn [h]
@@ -620,4 +914,7 @@
(wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema)
(wrap-client-redirect-unauthenticated)))))
(wrap-client-redirect-unauthenticated)))))