Making progress on a good invoice experience.
This commit is contained in:
@@ -132,7 +132,9 @@
|
||||
{:account-client-override/client [:db/id]}]}]}]
|
||||
[:transaction/_invoices :as :invoice/transaction] [:db/id]
|
||||
[: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/vendor [:vendor/name :db/id]}])
|
||||
|
||||
@@ -473,12 +475,15 @@
|
||||
(link-dropdown
|
||||
(concat (->> i
|
||||
:invoice/payments
|
||||
(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/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/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)
|
||||
{:exact-match-id (:db/id (first (:payment/transaction p)))})
|
||||
:color :secondary
|
||||
@@ -609,16 +614,10 @@
|
||||
|
||||
;; TODO
|
||||
;; 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 credits, just highlight it
|
||||
;; Entering 0.00 payment should just remove it from the set
|
||||
;; Better text to link to pdfs
|
||||
;; include hint about margins from the dialog
|
||||
;; refresh the window after printing
|
||||
;; Handwritten date
|
||||
;; filter only for unlocked
|
||||
;; filter only for unpaid
|
||||
;; inline all the check printing stuff
|
||||
|
||||
(defn does-amount-exceed-outstanding? [amount outstanding-balance]
|
||||
(let [outstanding-balance (round-money outstanding-balance)
|
||||
@@ -655,8 +654,8 @@
|
||||
invoices)))]]]
|
||||
[:has-warning? :boolean]
|
||||
[:bank-account entity-id]
|
||||
[:check-number {:optional true}
|
||||
:int]
|
||||
[:check-number {:optional true} :int]
|
||||
[:handwritten-date {:optional true} [:maybe clj-date-schema]]
|
||||
[:mode [:enum :simple :advanced]]
|
||||
[: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-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"
|
||||
@@ -729,7 +726,6 @@
|
||||
:to (mm/encode-step-key :payment-details)})}
|
||||
"Handwrite check"))]]])])
|
||||
|
||||
|
||||
(defmulti bank-account-card (fn [ba _]
|
||||
(:bank-account/type ba)))
|
||||
(defmethod bank-account-card :bank-account-type/cash [bank-account can-handwrite?]
|
||||
@@ -807,7 +803,7 @@
|
||||
[])
|
||||
|
||||
(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]
|
||||
(mm/default-render-step
|
||||
@@ -816,7 +812,7 @@
|
||||
:body (mm/default-step-body
|
||||
{}
|
||||
[:div {}
|
||||
(if (= :handwrite-check (:method (:snapshot (:multi-form-state request))))
|
||||
(when (= :handwrite-check (:method (:snapshot (:multi-form-state request))))
|
||||
(fc/with-field :check-number
|
||||
(com/validated-field
|
||||
{:errors (fc/field-errors)
|
||||
@@ -825,6 +821,16 @@
|
||||
:name (fc/field-name)
|
||||
:error? (fc/field-errors)
|
||||
: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"
|
||||
:name "step-params[mode]"
|
||||
:options [{:value "simple"
|
||||
@@ -858,7 +864,11 @@
|
||||
(-> (fc/field-value) :invoice :invoice/invoice-number))
|
||||
(com/data-grid-cell
|
||||
{: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
|
||||
{:class "w-20"}
|
||||
(fc/with-field :amount
|
||||
@@ -872,15 +882,16 @@
|
||||
|
||||
|
||||
(defn add-handwritten-check [request wizard snapshot]
|
||||
(let [snapshot (assoc snapshot :date (time/now))
|
||||
invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic
|
||||
(let [invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic
|
||||
bank-account-id (:bank-account snapshot)
|
||||
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]
|
||||
(assert-can-see-client (:identity request) (:invoice/client invoice)))
|
||||
client-id (:db/id (:invoice/client (first invoices)))
|
||||
_ (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))
|
||||
base-payment (base-payment invoices
|
||||
(:invoice/vendor (first invoices))
|
||||
@@ -894,7 +905,7 @@
|
||||
:payment/type :payment-type/check
|
||||
:payment/status :payment-status/pending
|
||||
: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))
|
||||
(:identity request))]
|
||||
(doseq [[_ i] (:tempids result)]
|
||||
@@ -980,7 +991,7 @@
|
||||
(throw (Exception. "Check number is required")))
|
||||
true))
|
||||
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)
|
||||
(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
|
||||
:amount (:amount i)})
|
||||
@@ -1003,9 +1014,15 @@
|
||||
[: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."]])))))))
|
||||
(when-not (:pdf-url result)
|
||||
[:div "That's a wrap. Your payment is complete."])
|
||||
(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
|
||||
(->PayWizard nil nil nil))
|
||||
@@ -1028,8 +1045,7 @@
|
||||
|
||||
(mm/wrap-wizard pay-wizard)
|
||||
(mm/wrap-init-multi-form-state (fn [request]
|
||||
(exception->notification
|
||||
;; TODO handle locked
|
||||
(exception->notification
|
||||
#(let [selected-ids (selected->ids request (:query-params request))
|
||||
selected-ids (->> (dc/q '[:find ?i
|
||||
:in $ [?i ...]
|
||||
@@ -1042,8 +1058,8 @@
|
||||
selected-ids)
|
||||
(map first))
|
||||
_ (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))
|
||||
(not= (count selected-ids)
|
||||
(count (:selected (:query-params request)))))
|
||||
@@ -1058,15 +1074,16 @@
|
||||
(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)
|
||||
:mode :simple
|
||||
:client (-> invoices first :invoice/client :db/id)
|
||||
:has-warning? (boolean has-warning?)}
|
||||
[]
|
||||
{:mode :simple
|
||||
:has-warning? (boolean has-warning?)}))))))
|
||||
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i)
|
||||
:amount (:invoice/outstanding-balance i)})
|
||||
invoices)
|
||||
:mode :simple
|
||||
:client (-> invoices first :invoice/client :db/id)
|
||||
:has-warning? (boolean has-warning?)
|
||||
:handwritten-date (time/now)}
|
||||
[]
|
||||
{:mode :simple
|
||||
:has-warning? (boolean has-warning?)}))))))
|
||||
::route/pay-submit (-> mm/submit-handler
|
||||
|
||||
(mm/wrap-wizard pay-wizard)
|
||||
|
||||
Reference in New Issue
Block a user