This commit is contained in:
2025-03-15 21:20:19 -07:00
parent b1ce23bfcf
commit dd2f6508fe
4 changed files with 104 additions and 122 deletions

View File

@@ -25,8 +25,9 @@
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [->db-id apply-middleware-to-all-handlers check-allowance
check-location-belongs entity-id html-response modal-response
ref->enum-schema strip wrap-entity wrap-schema-enforce]]
check-location-belongs entity-id form-validation-error
html-response modal-response ref->enum-schema strip wrap-entity
wrap-schema-enforce]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
@@ -34,8 +35,7 @@
[hiccup.util :as hu]
[iol-ion.query :refer [dollars=]]
[iol-ion.tx :refer [random-tempid]]
[malli.core :as mc]
[malli.util :as mut]))
[malli.core :as mc]))
(def transaction-approval-status
{:transaction-approval-status/unapproved "Unapproved"
@@ -627,45 +627,30 @@
[:div.font-medium "Date"]
[:div (some-> payment :payment/date (atime/unparse-local atime/normal-date))]]
[:div.mt-4 {:hx-post (bidi/path-for ssr-routes/only-routes ::route/unlink-payment)
:hx-params "transaction-id, action"
:hx-trigger "unlinkPayment"
:hx-target "#payment-matches"
:hx-include "this"
:hx-swap "outerHTML"
:hx-confirm "Are you sure you want to unlink this payment?"}
(com/hidden {:name "action"
:value "unlink-payment"
:form ""})
(com/hidden {:name "transaction-id" :value tx-id :form ""})
(com/a-button {:color :red :size :small
"@click" "$dispatch('unlinkPayment')"} "Unlink Payment")]]])
(if (seq payments)
[:div
[:h3.text-lg.font-bold.mb-4 "Available Payments"]
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/link-payment)
:hx-trigger "matchPayment"
:hx-target "#modal-holder"
:hx-include "this"
:hx-swap "outerHTML"}
(com/hidden {:name "action"
:value "link-payment"
:form ""})
(com/hidden {:name "transaction-id"
:value (-> request :entity :db/id)
:form ""})
[:div.space-y-2
[:label.block.text-sm.font-medium.mb-1 "Select a payment to match:"]
(when payments
(com/radio-card {:options (for [payment payments]
{:value (:db/id payment)
:content (str (:payment/invoice-number payment) " - "
(-> payment :payment/vendor :vendor/name)
" - Amount: $" (format "%.2f" (:payment/amount payment))
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
:name "payment-id"
:width "w-full"}))
(com/a-button {"@click" "$dispatch('matchPayment')"} "Match" #_[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Match"])]]]
[:div.space-y-2
[:label.block.text-sm.font-medium.mb-1 "Select a payment to match:"]
(when payments
(let [payment-id-field (fc/with-field :payment-id (fc/field-name ))]
(com/radio-card {:options (for [payment payments]
{:value (:db/id payment)
:content (str (:payment/invoice-number payment) " - "
(-> payment :payment/vendor :vendor/name)
" - Amount: $" (format "%.2f" (:payment/amount payment))
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
:name payment-id-field
:width "w-full"})))
(com/a-button {"@click" "$dispatch('matchPayment')"} "Match" #_[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Match"])]]
[:div.text-center.py-4.text-gray-500 "No matching payments available for this transaction."])]))
(defn count-payment-matches [request]
@@ -809,8 +794,9 @@
(-> request :multi-form-state :snapshot :action)))
(defmethod save-handler
:link-payment [{{:keys [transaction-id payment-id]} :form-params :as request transaction :entity}]
(let [payment (d-checks/get-by-id payment-id)]
:link-payment [{{ {:keys [transaction-id payment-id]} :snapshot} :multi-form-state :as request transaction :entity}]
(let [_ (println "PAYMENT ID IS")
payment (d-checks/get-by-id payment-id)]
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
(exception->4xx #(assert-can-see-client (:identity request) (-> payment :payment/client :db/id)))
@@ -818,7 +804,10 @@
(when (not= (-> transaction :transaction/client :db/id)
(-> payment :payment/client :db/id))
(throw (ex-info "Clients don't match" {:validation-error "Payment and client do not match."})))
(form-validation-error "Clients don't match."
:payment-client-id (:payment/client payment)
:transaction-client-id (:transaction/client transaction))
#_(throw (ex-info "Clients don't match" {:validation-error "Payment and client do not match."})))
(when-not (dollars= (- (:transaction/amount transaction))
(:payment/amount payment))
@@ -828,7 +817,7 @@
[{:db/id (:db/id payment)
:payment/status :payment-status/cleared
:payment/date (coerce/to-date (first (sort [(:payment/date payment)
(:transaction/date transaction)])))}
(coerce/to-date-time (:transaction/date transaction))])))}
[:upsert-transaction
{:db/id (:db/id transaction)
@@ -1025,86 +1014,74 @@
[:div]
:headers {"hx-trigger" "modalclose"}))))
(defn unlink-payment [{{:keys [transaction-id] :as fp} :form-params :as request}]
(let [transaction (dc/pull (dc/db conn)
'[:transaction/approval-status
(defn unlink-payment [{{{transaction-id :db/id} :snapshot} :multi-form-state :as request}]
:transaction/date
:transaction/location
:transaction/vendor
:transaction/accounts
:transaction/status
:transaction/client [:db/id]
{:transaction/payment [:payment/date
{[:payment/status :xform iol-ion.query/ident] [:db/ident]} :db/id]}]
transaction-id)
payment (-> transaction :transaction/payment)]
(fc/start-form (:multi-form-state request) (when (:form-errors request) {:step-params (:form-errors request)})
(let [transaction (dc/pull (dc/db conn)
'[:transaction/approval-status
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
(exception->4xx #(assert-not-locked (-> transaction :transaction/client :db/id) (:transaction/date transaction)))
:transaction/date
:transaction/location
:transaction/vendor
:transaction/accounts
:transaction/status
:transaction/client [:db/id]
{:transaction/payment [:payment/date
{[:payment/status :xform iol-ion.query/ident] [:db/ident]} :db/id]}]
transaction-id)
payment (-> transaction :transaction/payment)]
(when (not= :payment-status/cleared (-> payment :payment/status))
(throw (ex-info "Payment can't be undone because it isn't cleared."
{:validation-error "Payment can't be undone because it isn't cleared."})))
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
(exception->4xx #(assert-not-locked (-> transaction :transaction/client :db/id) (:transaction/date transaction)))
(let [is-autopay-payment? (some->> (dc/q {:find ['?sp]
:in ['$ '?payment]
:where ['[?ip :invoice-payment/payment ?payment]
'[?ip :invoice-payment/invoice ?i]
'[(get-else $ ?i :invoice/scheduled-payment "N/A") ?sp]]}
(dc/db conn) (:db/id payment))
seq
(map first)
(every? #(instance? java.util.Date %)))]
(if is-autopay-payment?
(audit-transact
(-> [{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id (:db/id transaction)
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]
[:db/retractEntity (:db/id payment)]]
(into (map (fn [[invoice-payment]]
[:db/retractEntity invoice-payment])
(dc/q {:find ['?ip]
:in ['$ '?p]
:where ['[?ip :invoice-payment/payment ?p]]}
(dc/db conn)
(:db/id payment)))))
(:identity request))
(audit-transact
[{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id (:db/id transaction)
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]]
(:identity request))))
(when (not= :payment-status/cleared (-> payment :payment/status))
(throw (ex-info "Payment can't be undone because it isn't cleared."
{:validation-error "Payment can't be undone because it isn't cleared."})))
(solr/touch-with-ledger (:db/id transaction))
(html-response (payment-matches-view request)
:headers {"hx-trigger" "unlinked"})
(let [is-autopay-payment? (some->> (dc/q {:find ['?sp]
:in ['$ '?payment]
:where ['[?ip :invoice-payment/payment ?payment]
'[?ip :invoice-payment/invoice ?i]
'[(get-else $ ?i :invoice/scheduled-payment "N/A") ?sp]]}
(dc/db conn) (:db/id payment))
seq
(map first)
(every? #(instance? java.util.Date %)))]
(if is-autopay-payment?
(audit-transact
(-> [{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id (:db/id transaction)
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]
[:db/retractEntity (:db/id payment)]]
(into (map (fn [[invoice-payment]]
[:db/retractEntity invoice-payment])
(dc/q {:find ['?ip]
:in ['$ '?p]
:where ['[?ip :invoice-payment/payment ?p]]}
(dc/db conn)
(:db/id payment)))))
(:identity request))
(audit-transact
[{:db/id (:db/id payment)
:payment/status :payment-status/pending}
[:upsert-transaction
{:db/id (:db/id transaction)
:transaction/approval-status :transaction-approval-status/unapproved
:transaction/payment nil
:transaction/vendor nil
:transaction/location nil
:transaction/accounts nil}]]
(:identity request))))
#_(modal-response
(com/success-modal {:title "Transaction unlinked successfully"}
[:p.text-gray-600.mt-2 "The transaction has been unlinked from its payment."]
[:p "If you'd like to also void the payment, click "
(com/link
{:hx-boost true
:href (hu/url (bidi/path-for ssr-routes/only-routes ::payment-route/all-page)
{:exact-match-id (:db/id payment)})}
"here")
" to view the payment"])
:headers {"hx-trigger" "invalidated"})))
(solr/touch-with-ledger (:db/id transaction))
(html-response (fc/with-field :step-params (payment-matches-view request))
:headers {"hx-trigger" "unlinked"}))))
#_(def save-schema
(mc/schema
@@ -1143,6 +1120,7 @@
(form-schema [_]
edit-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}]
(println "SUBMITTING")
(save-handler request)
))
@@ -1209,9 +1187,11 @@
[:client-id {:optional true}
[:maybe entity-id]]]))
#_#_::route/unlink-payment (-> unlink-payment
(wrap-entity [:form-params :transaction-id] d-transactions/default-read)
(wrap-schema-enforce :form-schema
::route/unlink-payment (-> unlink-payment
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
(mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state)
#_(wrap-schema-enforce :form-schema
save-schema))}
(fn [h]
(-> h

View File

@@ -29,13 +29,10 @@
[:link {:rel "stylesheet", :href "/output.css"}]
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
[:script { :src "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" :defer true}]
[:link {:rel "stylesheet" :href "https://unpkg.com/tippy.js@6/dist/tippy.css"}]
[:link {:rel "stylesheet" :href "https://unpkg.com/tippy.js@6/themes/light.css"}]
(if (= "dev" (:dd-env env))
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.js"
:crossorigin= "anonymous"}]
[:script {:src "https://unpkg.com/htmx.org@1.9.6/dist/htmx.min.js"
:crossorigin= "anonymous"}])
[:link {:rel "stylesheet" :href "/css/tippy/tippy.css"}]
[:link {:rel "stylesheet" :href "/css/tippy/light.css"}]
[:script {:src "/js/htmx.min.js"
:crossorigin= "anonymous"}]
[:script {:src "https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"}]
[:script {:src "/js/htmx-disable.js"}]

View File

@@ -196,6 +196,7 @@
:form-errors (assoc-in {} path [m])}))))
(defn form-validation-error [m & {:as data}]
(alog/warn ::form-validaiton-error :data data)
(throw+ (ex-info m (merge data {:type :form-validation
:form-validation-errors [m]}))))

4
tasks
View File

@@ -9,3 +9,7 @@
* Make sure that "Shared" really shares locations
* make sure transactions support import-batch-id query parameter
* make locked transactions clearer
* unlinking then linking requires first closing the dialog.
* make approved/unapproved be on the actions page
* make memo on the acations page