diff --git a/src/clj/auto_ap/graphql/utils.clj b/src/clj/auto_ap/graphql/utils.clj index 07f411de..0bb0c734 100644 --- a/src/clj/auto_ap/graphql/utils.clj +++ b/src/clj/auto_ap/graphql/utils.clj @@ -73,6 +73,12 @@ (throw (ex-info message {:validation-error message})))) +(defn assert-failure-ssr + ([message] + (throw (ex-info message + {:validation-error message + :type :warning})))) + (defn assert-power-user [id] (when-not (#{"power-user" "admin"} (:user/role id)) (alog/warn ::unauthorized :user id :role "power-user") @@ -126,6 +132,12 @@ (>= (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)) "."))))) +(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]} (defn assert-none-locked [client-id dates] (doseq [d dates] diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index f714c293..07f65555 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -296,11 +296,18 @@ (try (handler request) (catch Throwable e - (if (= :notification (:type (ex-data e))) + (cond + (= :notification (:type (ex-data e))) {:status 200 :headers {"hx-trigger" (cheshire/generate-string {"notification" (str (hiccup/html [:div (.getMessage e)]))}) "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 :body (pr-str e)}))))) diff --git a/src/clj/auto_ap/ssr/components/page.clj b/src/clj/auto_ap/ssr/components/page.clj index b9f62210..ba2efea1 100644 --- a/src/clj/auto_ap/ssr/components/page.clj +++ b/src/clj/auto_ap/ssr/components/page.clj @@ -49,7 +49,7 @@ [:div {:class "p-4 text-lg w-full" :role "alert"} [:div.text-sm - [:pre#notification-details.text-xs {:x-text "notificationDetails"}]]]]]] + [:pre#notification-details.text-xs {:x-html "notificationDetails"}]]]]]] [:div {:x-show "showError" :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 diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index a54499b7..e1cb236a 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -13,8 +13,9 @@ print-checks-internal validate-belonging]] [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked - exception->4xx exception->notification - extract-client-ids notify-if-locked]] + assert-not-locked-ssr exception->4xx + exception->notification extract-client-ids + notify-if-locked]] [auto-ap.logging :as alog] [auto-ap.permissions :refer [can? wrap-must]] [auto-ap.query-params :refer [wrap-copy-qp-pqp]] @@ -58,7 +59,8 @@ [iol-ion.utils :refer [random-tempid]] [malli.core :as mc] [malli.transform :as mt] - [malli.util :as mut])) + [malli.util :as mut] + [slingshot.slingshot :refer [try+]])) (defn exact-match-id* [request] @@ -348,6 +350,25 @@ selected)] 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] (let [ids (:ids params) ids (if (seq ids) @@ -517,11 +538,12 @@ :db/id (:db/id entity))} svg/undo)) (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 ::route/undo-autopay :db/id (:db/id entity))} - "Undo payment"))]) + "Undo autopay"))]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} "Invoices"]] @@ -692,12 +714,19 @@ (let [invoice entity id (:db/id entity) _ (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) + (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 - (row* identity (assoc (dc/pull (dc/db conn) default-read id) - :invoice/status :invoice-status/unpaid) {:flash? true + (row* identity (dc/pull (dc/db conn) default-read id) {:flash? true :request request}) :headers (cond-> {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" id) "hx-reswap" "outerHTML"}))))