From 179e3b219bb97bf16b49c314d68285c2427a2646 Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 2 Apr 2024 20:52:40 -0700 Subject: [PATCH] Tweaks for editing invoices --- src/clj/auto_ap/ssr/components/inputs.clj | 107 +++++++++--------- .../ssr/invoice/new_invoice_wizard.clj | 70 +++++++++--- src/clj/auto_ap/ssr/invoices.clj | 6 +- src/cljc/auto_ap/routes/invoice.cljc | 4 +- 4 files changed, 115 insertions(+), 72 deletions(-) diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index ea9baed3..01f5531e 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -39,58 +39,58 @@ (defn typeahead- [params] [:div.relative {:x-data (hx/json {:open false - :baseUrl (if (str/includes? (:url params) "?") - (str (:url params) "&q=") - (str (:url params) "?q=")) - :value {:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))} - :search "" - :active -1 - :elements (if ((:value-fn params identity) (:value params)) - [{:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}] - []) - :popper nil + :baseUrl (if (str/includes? (:url params) "?") + (str (:url params) "&q=") + (str (:url params) "?q=")) + :value {:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))} + :search "" + :active -1 + :elements (if ((:value-fn params identity) (:value params)) + [{:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}] + []) + :popper nil :warning_badge nil}) - :x-modelable "value.value" - :x-model (:x-model params) - :x-init "popper = Popper.createPopper($refs.input, $refs.dropdown, {placement: 'bottom-start', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [0, 10]}}}) - warning_badge = Popper.createPopper($refs.warning_badge, $refs.warning_pop, {placement: 'top', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [10,0 ]}}})" - } - [:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes) - (hh/add-class "cursor-pointer")) - "@click.prevent" "open = !open; popper.update()" - "@keydown.down.prevent.stop" "open = true; popper.update()" - "@keydown.backspace" "value = {value: '', label: '' }" - :tabindex 0 - :x-init (:x-init params) - :x-ref "input" - } - [:input (-> params - (dissoc :class) - (dissoc :value-fn) - (dissoc :content-fn) + :x-modelable "value.value" + :x-model (:x-model params) + :x-init "popper = Popper.createPopper($refs.input, $refs.dropdown, {placement: 'bottom-start', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [0, 10]}}}) + warning_badge = Popper.createPopper($refs.warning_badge, $refs.warning_pop, {placement: 'top', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [10,0 ]}}})"} + (if (:disabled params) + [:span {:x-text "value.label"}] + [:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes) + (hh/add-class "cursor-pointer")) + "@click.prevent" "open = !open; popper.update()" + "@keydown.down.prevent.stop" "open = true; popper.update()" + "@keydown.backspace" "value = {value: '', label: '' }" + :tabindex 0 + :x-init (:x-init params) + :x-ref "input"} + [:input (-> params + (dissoc :class) + (dissoc :value-fn) + (dissoc :content-fn) - (dissoc :placeholder) - (dissoc :x-model) - (assoc - "x-ref" "hidden" - :type "hidden" - ":value" "value.value" - :x-init (hiccup/raw (str "$watch('value', v => $dispatch('change')); "))))] - [:div.flex.w-full.justify-items-stretch - [:span.flex-grow.text-left {"x-text" "value.label"}] - [:div {:class "w-3 h-3 m-1 inline ml-1 justify-self-end text-gray-500 self-center"} - svg/drop-down] - [:div {:x-show "value.warning" - :x-ref "warning_badge" - :x-effect "if (value.warning) { $nextTick(()=> warning_badge.update()) }"} - (tags/badge- {:class "peer"} "!") + (dissoc :placeholder) + (dissoc :x-model) + + (assoc + "x-ref" "hidden" + :type "hidden" + ":value" "value.value" + :x-init (hiccup/raw (str "$watch('value', v => $dispatch('change')); "))))] + [:div.flex.w-full.justify-items-stretch + [:span.flex-grow.text-left {"x-text" "value.label"}] + [:div {:class "w-3 h-3 m-1 inline ml-1 justify-self-end text-gray-500 self-center"} + svg/drop-down] + [:div {:x-show "value.warning" + :x-ref "warning_badge" + :x-effect "if (value.warning) { $nextTick(()=> warning_badge.update()) }"} + (tags/badge- {:class "peer"} "!") - [:div {:x-show "value.warning" - :x-ref "warning_pop" - :class "hidden peer-hover:block bg-red-50 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4" - :x-text "value.warning"} - ]]]] + [:div {:x-show "value.warning" + :x-ref "warning_pop" + :class "hidden peer-hover:block bg-red-50 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4" + :x-text "value.warning"}]]]]) [:ul.dropdown-contents {:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 ring-1" "x-ref" "dropdown" @@ -104,12 +104,12 @@ "x-show " "open" "x-trap" "open" "@click.outside" "open=false;"} - + [:input {:type "text" :class (-> (:class params) - (or "") - (hh/add-class default-input-classes) - (hh/replace-wildcard ["rounded" "border"] "border-bottom bg-gray-100 rounded-t-lg w-full")) + (or "") + (hh/add-class default-input-classes) + (hh/replace-wildcard ["rounded" "border"] "border-bottom bg-gray-100 rounded-t-lg w-full")) "x-model" "search" "placeholder" (:placeholder params) "@keydown.down.prevent" "active ++; active = active >= elements.length - 1 ? elements.length - 1 : active" @@ -127,8 +127,7 @@ "x-html" "element.label"}]]] [:template {:x-if "elements.length == 0"} [:li {:class "px-4 py-2 flex gap-2 items-center outline-0 focus:bg-neutral-100 hover:bg-neutral-100 whitespace-nowrap [&.active]:bg-primary-500 text-gray-800 dark:text-gray-100 text-xs "} - "No results found"]]] - ]]) + "No results found"]]]]]) (defn use-size [size] 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 e4c8a02c..3fd87a04 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -2,10 +2,10 @@ (:require [auto-ap.datomic :refer [audit-transact conn pull-attr]] [auto-ap.datomic.accounts :as d-accounts] + [auto-ap.ssr.invoice.common :refer [default-read]] [auto-ap.datomic.invoices :as d-invoices] [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked exception->4xx]] - [auto-ap.logging :as alog] [auto-ap.routes.invoice :as route] [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated]] @@ -22,7 +22,6 @@ [auto-ap.ssr.utils :refer [->db-id apply-middleware-to-all-handlers clj-date-schema entity-id form-validation-error html-response money strip - ->OOBElements wrap-schema-enforce]] [auto-ap.time :as atime] [bidi.bidi :as bidi] @@ -70,10 +69,15 @@ (defn check-vendor-default-account [vendor-id] (some? (:vendor/default-account (get-vendor vendor-id)))) +;; TODO negative expense accounts for negative invoices? +;; TODO make changing expense accounts work for editing invoices +;; TODO make a change of total automatically change the expense accounts when editing +;; TODO just close when editing, don't go to print screen (def new-form-schema [:map + [:db/id {:optional true} [:maybe entity-id]] [:customize-due-and-scheduled? {:optional true :default false :decode/arbitrary (fn [x] (if (= "" x) false x))} [:maybe :boolean]] @@ -81,10 +85,11 @@ [:invoice/date clj-date-schema] [:invoice/due {:optional true} [:maybe clj-date-schema]] [:invoice/scheduled-payment {:optional true} [:maybe clj-date-schema]] - [:invoice/vendor [:and entity-id - [:fn {:error/message "Vendor is missing default expense account"} - check-vendor-default-account]]] - [:invoice/invoice-number [:string {:min 1 :decode/string strip}]] + [:invoice/vendor {:optional true} + [:and entity-id + [:fn {:error/message "Vendor is missing default expense account"} + check-vendor-default-account]]] + [:invoice/invoice-number {:optional true} [:string {:min 1 :decode/string strip}]] [:invoice/total money] [:invoice/expense-accounts [:vector {:coerce? true} @@ -98,6 +103,15 @@ [:fn {:error/fn (fn [r x] (:type r)) :error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]]) +(defn wrap-schema [s] + [:and s + [:fn (fn [{:keys [:db/id :invoice/invoice-number :invoice/vendor] :as z}] + (println "HERE" id invoice-number vendor z) + (if id + true + (and invoice-number vendor)))]]) + + (defn clientize-vendor [{:vendor/keys [terms-overrides automatically-paid-when-due default-account account-overrides] :as vendor} client-id] (let [terms-override (->> terms-overrides (filter (fn [to] @@ -139,7 +153,7 @@ []) (step-schema [_] - (mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment :invoice/total :invoice/invoice-number :customize-due-and-scheduled?})) + (wrap-schema (mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment :invoice/total :invoice/invoice-number :db/id :customize-due-and-scheduled?}))) (render-step [this request] (mm/default-render-step @@ -157,6 +171,12 @@ :scheduledPayment (some-> (fc/field-value (:invoice/scheduled-payment fc/*current*)) (atime/unparse-local atime/normal-date)) :customizeDueAndScheduled (fc/field-value (:customize-due-and-scheduled? fc/*current*))})} + (fc/with-field :db/id + (when (fc/field-value) + (com/hidden {:name (fc/field-name) + :value (fc/field-value)}))) + + (fc/with-field :customize-due-and-scheduled? (com/hidden {:name (fc/field-name) :value (fc/field-value) @@ -184,6 +204,7 @@ [:div.w-96 (com/typeahead {:name (fc/field-name) :error? (fc/error?) + :disabled (boolean (-> request :multi-form-state :snapshot :db/id)) :class "w-96" :placeholder "Search..." :url (bidi/path-for ssr-routes/only-routes :vendor-search) @@ -254,6 +275,7 @@ :errors (fc/field-errors)} [:div {:class "w-24"} (com/text-input {:value (-> (fc/field-value)) + :disabled (boolean (-> request :multi-form-state :snapshot :db/id)) :name (fc/field-name) :error? (fc/field-errors) :placeholder "HA-123"})])) @@ -302,7 +324,7 @@ [:div.flex.flex-col (com/typeahead {:name name :placeholder "Search..." - :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search) + :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search) {:client-id client-id :purpose "invoice"}) :id name @@ -468,7 +490,7 @@ :body (mm/default-step-body {} [:p.text-lg "Would you like to pay this invoice now?"] - + (com/navigation-button-list {} (com/navigation-button (-> {:class "w-48" :hx-get (hu/url (bidi.bidi/path-for ssr-routes/only-routes ::route/pay-wizard) @@ -481,7 +503,7 @@ hx/trigger-click-or-enter) "Add another") (com/navigation-button {:class "w-48" :next-arrow? false "@click" "$dispatch('modalclose') " - "@keyup.enter.stop" "$dispatch('modalclose')"} + "@keyup.enter.stop" "$dispatch('modalclose')"} "Close"))) :footer nil @@ -561,15 +583,16 @@ :accounts (->AccountsStep this) :next-steps (->NextSteps this)} step-key))) - (form-schema [_] new-form-schema) + (form-schema [_] + new-form-schema) (submit [this {:keys [multi-form-state request-method identity] :as request}] (let [invoice (:snapshot multi-form-state) client-id (->db-id (:invoice/client invoice)) vendor-id (->db-id (:invoice/vendor invoice)) transaction [:upsert-invoice (-> multi-form-state :snapshot - (assoc :db/id "invoice") - (dissoc :customize-due-and-scheduled?) + (assoc :db/id (or (:db/id invoice) "invoice")) + (dissoc :customize-due-and-scheduled? :invoice/journal-entry :invoice/payments) (assoc :invoice/expense-accounts (if-let [ieas (seq (-> multi-form-state :snapshot :invoice/expense-accounts))] ieas [{:db/id "123" @@ -585,15 +608,15 @@ (update :invoice/date coerce/to-date) (update :invoice/due coerce/to-date))]] (assert-invoice-amounts-add-up (second transaction)) - (assert-no-conflicting invoice) + (when-not (:db/id invoice) + (assert-no-conflicting invoice)) (exception->4xx #(assert-can-see-client (:identity request) client-id)) (exception->4xx #(assert-not-locked client-id (:invoice/date invoice))) (let [transaction-result (audit-transact [transaction] (:identity request))] (solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"])) (assoc-in (mm/render-navigate {:request (assoc-in request [:multi-form-state :snapshot :db/id] (get-in transaction-result [:tempids "invoice"])) - :to-step :next-steps - }) + :to-step :next-steps}) [:headers "hx-trigger"] "invalidated"))))) (def new-wizard (->NewWizard2 nil nil)) @@ -604,6 +627,17 @@ [] {:invoice/date (time/now)})) + + + +(defn initial-edit-wizard-state [request] + (let [entity (dc/pull (dc/db conn) default-read (:db/id (:route-params request))) + entity (select-keys entity (mut/keys new-form-schema))] + + (mm/->MultiStepFormState entity + [] + entity))) + (defn location-select [{{:keys [name account-id client-id value] :as qp} :query-params}] (html-response (location-select* {:name name :value value @@ -676,6 +710,10 @@ {::route/new-wizard (-> mm/open-wizard-handler (mm/wrap-wizard new-wizard) (mm/wrap-init-multi-form-state initial-new-wizard-state)) + ::route/edit-wizard (-> mm/open-wizard-handler + (mm/wrap-wizard new-wizard) + (mm/wrap-init-multi-form-state initial-edit-wizard-state) + (wrap-schema-enforce :route-schema [:map [:db/id entity-id]])) ::route/due-date (-> due-date (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index cfcb5fa0..9dfc9617 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -407,7 +407,11 @@ ::route/delete :db/id (:db/id entity)) :hx-confirm "Are you sure you want to void this invoice?"} - svg/trash))]) + svg/trash)) + (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes + ::route/edit-wizard + :db/id (:db/id entity)) } + svg/pencil)]) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} "Invoices"]] :title (fn [r] diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index 62c39255..0aef9eed 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -12,13 +12,15 @@ "/account/location-select" ::location-select "/account/prediction" ::account-prediction "/total" ::expense-account-total} + "/pay-button" ::pay-button "/pay" {:get ::pay-wizard "/navigate" ::pay-wizard-navigate :post ::pay-submit} "/bulk-delete" {:get ::bulk-delete :delete ::bulk-delete-confirm} - ["/" [#"\d+" :db/id]] {:delete ::delete} + ["/" [#"\d+" :db/id]] {:delete ::delete + "/edit" ::edit-wizard} "/table" ::table "/glimpse" {"" {:get :invoice-glimpse