Allows undoing autopayments
This commit is contained in:
@@ -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]
|
||||||
|
|||||||
@@ -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)})))))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"}))))
|
||||||
|
|||||||
Reference in New Issue
Block a user