diff --git a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj index cd8aa987..1de76014 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -397,7 +397,7 @@ {:errors (fc/field-errors) :x-hx-val:account-id "accountId" :hx-vals (hx/json {:name (fc/field-name) - :clientId client-id}) + :client-id client-id}) :x-dispatch:changed "accountId" :hx-trigger "changed" :hx-get (bidi/path-for ssr-routes/only-routes ::route/location-select) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 7cc532b5..dd0087cd 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -374,6 +374,8 @@ (pay-button* {:ids (selected->ids request (:query-params request))}))) + + ;; TODO test as a real user (def grid-page (helper/build {:id "entity-table" @@ -409,11 +411,19 @@ :db/id (:db/id entity)) :hx-confirm "Are you sure you want to void this invoice?"} svg/trash)) - (when (can? (:identity request) {:subject :invoice :activity :edit}) + (when (and (can? (:identity request) {:subject :invoice :activity :edit}) + (#{:invoice-status/unpaid :invoice-status/paid} (:invoice/status entity))) (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes ::route/edit-wizard :db/id (:db/id entity))} - svg/pencil))]) + svg/pencil)) + (when (and (can? (:identity request) {:subject :invoice :activity :edit}) + (#{:invoice-status/voided} (:invoice/status entity))) + (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes + ::route/unvoid + :db/id (:db/id entity))} + svg/undo))]) + :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} "Invoices"]] :title (fn [r] @@ -514,6 +524,39 @@ (def row* (partial helper/row* grid-page)) +(defn unvoid-invoice [{:as request :keys [identity entity]}] + (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)) + history (dc/history (dc/db conn)) + txs (dc/q {:find ['?tx '?e '?original-status '?original-outstanding '?total '?ea '?ea-amount] + :where ['[?e :invoice/status :invoice-status/voided ?tx true] + '[?e :invoice/status ?original-status ?tx false] + '[?e :invoice/outstanding-balance ?original-outstanding ?tx false] + '[?e :invoice/total ?total ?tx false] + '[?ea :invoice-expense-account/amount ?ea-amount ?tx false]] + :in ['$ '?e]} + history id) + [last-transaction] (->> txs (sort-by first) (last)) + tx [[:upsert-invoice + (->> txs + (filter (fn [[tx]] (= tx last-transaction))) + (reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]] + (-> new-transaction + (assoc :db/id entity + :invoice/total total + :invoice/status original-status + :invoice/outstanding-balance original-outstanding) + (update :invoice/expense-accounts (fnil conj []) {:db/id expense-account :invoice-expense-account/amount expense-account-amount}))) + {}))]] + _ (audit-transact tx identity)] + (alog/info ::unvoiding-invoice :transaction :tx) + (html-response + (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"})))) (defn delete [{invoice :entity :as request identity :identity}] (exception->notification @@ -885,7 +928,7 @@ (format "Pay in full ($%,.2f)" total)))} {:value "advanced" :content "Customize payments"}]}) - [:div.space-y-4 + [:div.space-y-4 (fc/with-field :invoices (com/validated-field {:errors (fc/field-errors)} @@ -1141,6 +1184,9 @@ (wrap-implied-route-param :status :invoice-status/unpaid)) ::route/voided-page (-> (helper/page-route grid-page) (wrap-implied-route-param :status :invoice-status/voided)) + ::route/unvoid (-> unvoid-invoice + (wrap-entity [:route-params :db/id] default-read) + (wrap-schema-enforce :route-params [:map [:db/id entity-id]])) ::route/pay-button (-> pay-button (wrap-schema-enforce :query-schema query-schema)) ::route/delete (-> delete diff --git a/src/clj/auto_ap/ssr/svg.clj b/src/clj/auto_ap/ssr/svg.clj index 3ff28bbf..a35139bf 100644 --- a/src/clj/auto_ap/ssr/svg.clj +++ b/src/clj/auto_ap/ssr/svg.clj @@ -496,7 +496,7 @@ [:path {:d "M6.5 9.616H1a0.5 0.5 0 0 0 -0.5 0.5v12a0.5 0.5 0 0 0 0.5 0.5h22a0.5 0.5 0 0 0 0.5 -0.5v-12a0.5 0.5 0 0 0 -0.5 -0.5h-5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}] [:path {:d "M17.004 12.616h3s0.5 0 0.5 0.5v2s0 0.5 -0.5 0.5h-3s-0.5 0 -0.5 -0.5v-2s0 -0.5 0.5 -0.5", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}] [:path {:d "m16.757 2.823 -0.8 -0.8a1 1 0 0 0 -1.414 0L12.5 4.07", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]]) - + (def paperclip [:svg {:xmlns "http://www.w3.org/2000/svg", @@ -512,4 +512,11 @@ :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", - :stroke-width "1"}]]) \ No newline at end of file + :stroke-width "1"}]]) + +(def undo + [:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24", :id "Undo--Streamline-Streamline--3.0"} + [:defs] + [:title "undo"] + [:path {:d "m1.5 0.498 0 7 7 0", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}] + [:path {:d "M1.5 7.5a11.656 11.656 0 0 1 16.179 -2.647 11.508 11.508 0 0 1 0.11 18.645", :fill "none", :stroke "currentcolor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1"}]]) diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index 12d9d3d7..b6b19552 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -21,6 +21,7 @@ "/bulk-delete" {:get ::bulk-delete :delete ::bulk-delete-confirm} ["/" [#"\d+" :db/id]] {:delete ::delete + "/unvoid" ::unvoid "/edit" ::edit-wizard} "/table" ::table