Allows undoing autopayments

This commit is contained in:
2025-02-03 13:34:19 -08:00
parent 329a1dc0d4
commit f1036e257c
4 changed files with 58 additions and 10 deletions

View File

@@ -73,6 +73,12 @@
(throw (ex-info message (throw (ex-info message
{:validation-error message})))) {:validation-error message}))))
(defn assert-failure-ssr
([message]
(throw (ex-info message
{:validation-error message
:type :warning}))))
(defn assert-power-user [id] (defn assert-power-user [id]
(when-not (#{"power-user" "admin"} (:user/role id)) (when-not (#{"power-user" "admin"} (:user/role id))
(alog/warn ::unauthorized :user id :role "power-user") (alog/warn ::unauthorized :user id :role "power-user")
@@ -126,6 +132,12 @@
(>= (compare locked-until (coerce/to-date date)) 0)) (>= (compare locked-until (coerce/to-date date)) 0))
(assert-failure (str "Integreat has locked finances prior to " (-> locked-until coerce/to-date-time (atime/unparse-local atime/normal-date)) "."))))) (assert-failure (str "Integreat has locked finances prior to " (-> locked-until coerce/to-date-time (atime/unparse-local atime/normal-date)) ".")))))
(defn assert-not-locked-ssr [client-id date]
(let [locked-until (get-locked-until client-id)]
(when (and locked-until
(>= (compare locked-until (coerce/to-date date)) 0))
(assert-failure-ssr (str "Integreat has locked finances prior to " (-> locked-until coerce/to-date-time (atime/unparse-local atime/normal-date)) ".")))))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn assert-none-locked [client-id dates] (defn assert-none-locked [client-id dates]
(doseq [d dates] (doseq [d dates]

View File

@@ -296,11 +296,18 @@
(try (try
(handler request) (handler request)
(catch Throwable e (catch Throwable e
(if (= :notification (:type (ex-data e))) (cond
(= :notification (:type (ex-data e)))
{:status 200 {:status 200
:headers {"hx-trigger" (cheshire/generate-string :headers {"hx-trigger" (cheshire/generate-string
{"notification" (str (hiccup/html [:div (.getMessage e)]))}) {"notification" (str (hiccup/html [:div (.getMessage e)]))})
"hx-reswap" "none"}} "hx-reswap" "none"}}
(= :warning (:type (ex-data e)))
{:status 200
:headers {"hx-trigger" (cheshire/generate-string
{"notification" (str (hiccup/html [:div (.getMessage e)]))})
"hx-reswap" "none"}} ;; TODO make a warning box so you don't have to reuse the notifaction box, or make it reuse the same box but theme differently
:else
{:status 500 {:status 500
:body (pr-str e)}))))) :body (pr-str e)})))))

View File

@@ -49,7 +49,7 @@
[:div {:class "p-4 text-lg w-full" :role "alert"} [:div {:class "p-4 text-lg w-full" :role "alert"}
[:div.text-sm [:div.text-sm
[:pre#notification-details.text-xs {:x-text "notificationDetails"}]]]]]] [:pre#notification-details.text-xs {:x-html "notificationDetails"}]]]]]]
[:div {:x-show "showError" [:div {:x-show "showError"
:x-init ""} :x-init ""}
[:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg [:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg

View File

@@ -13,8 +13,9 @@
print-checks-internal print-checks-internal
validate-belonging]] validate-belonging]]
[auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
exception->4xx exception->notification assert-not-locked-ssr exception->4xx
extract-client-ids notify-if-locked]] exception->notification extract-client-ids
notify-if-locked]]
[auto-ap.logging :as alog] [auto-ap.logging :as alog]
[auto-ap.permissions :refer [can? wrap-must]] [auto-ap.permissions :refer [can? wrap-must]]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.query-params :refer [wrap-copy-qp-pqp]]
@@ -58,7 +59,8 @@
[iol-ion.utils :refer [random-tempid]] [iol-ion.utils :refer [random-tempid]]
[malli.core :as mc] [malli.core :as mc]
[malli.transform :as mt] [malli.transform :as mt]
[malli.util :as mut])) [malli.util :as mut]
[slingshot.slingshot :refer [try+]]))
(defn exact-match-id* [request] (defn exact-match-id* [request]
@@ -348,6 +350,25 @@
selected)] selected)]
ids)) ids))
(defn assert-can-undo-autopayment [invoice]
(assert-not-locked-ssr (:db/id (:invoice/client invoice)) (:invoice/date invoice))
(when (not= :invoice-status/paid (:invoice/status invoice))
(throw (ex-info "This invoice isn't paid." {:type :warning})))
(when-not (:invoice/scheduled-payment invoice)
(throw (ex-info "This invoice didn't have a scheduled payment" {:type :warning})))
(when (:invoice/payments invoice)
(throw (ex-info "This invoice has a linked payment. Void the payment to undo the payment." {:type :warning}))))
(defn can-undo-autopayment [invoice]
(try+
(assert-can-undo-autopayment invoice)
true
(catch [:type :warning] {}
false)))
(defn pay-button* [params] (defn pay-button* [params]
(let [ids (:ids params) (let [ids (:ids params)
ids (if (seq ids) ids (if (seq ids)
@@ -517,11 +538,12 @@
:db/id (:db/id entity))} :db/id (:db/id entity))}
svg/undo)) svg/undo))
(when (and (can? (:identity request) {:subject :invoice :activity :edit}) (when (and (can? (:identity request) {:subject :invoice :activity :edit})
(#{:invoice-status/paid} (:invoice/status entity))) (can-undo-autopayment entity)
)
(com/button {:hx-put (bidi/path-for ssr-routes/only-routes (com/button {:hx-put (bidi/path-for ssr-routes/only-routes
::route/undo-autopay ::route/undo-autopay
:db/id (:db/id entity))} :db/id (:db/id entity))}
"Undo payment"))]) "Undo autopay"))])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Invoices"]] "Invoices"]]
@@ -692,12 +714,19 @@
(let [invoice entity (let [invoice entity
id (:db/id entity) id (:db/id entity)
_ (assert-can-see-client identity (:db/id (:invoice/client invoice))) _ (assert-can-see-client identity (:db/id (:invoice/client invoice)))
_ (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice)) ] ]
(alog/info ::undoing-autopay :transaction :tx) (alog/info ::undoing-autopay :transaction :tx)
(assert-can-undo-autopayment invoice)
(audit-transact
[[:upsert-invoice {:db/id id
:invoice/status :invoice-status/unpaid
:invoice/outstanding-balance (:invoice/total entity)
:invoice/scheduled-payment nil}]]
identity)
(html-response (html-response
(row* identity (assoc (dc/pull (dc/db conn) default-read id) (row* identity (dc/pull (dc/db conn) default-read id) {:flash? true
:invoice/status :invoice-status/unpaid) {:flash? true
:request request}) :request request})
:headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id) :headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id)
"hx-reswap" "outerHTML"})))) "hx-reswap" "outerHTML"}))))