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 52890211..0c35be58 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -73,6 +73,9 @@ (def new-form-schema [:map + [:customize-due-and-scheduled? {:optional true :default false :decode/arbitrary (fn [x] (if (= "" x) + false + x))} [:maybe :boolean]] [:invoice/client entity-id] [:invoice/date clj-date-schema] [:invoice/due {:optional true} [:maybe clj-date-schema]] @@ -138,7 +141,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})) + (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?})) (render-step [this request] (alog/peek ::check (:multi-form-state request)) @@ -153,7 +156,14 @@ :date (-> (fc/field-value (:invoice/date fc/*current*)) (atime/unparse-local atime/normal-date)) :due (some-> (fc/field-value (:invoice/due fc/*current*)) - (atime/unparse-local atime/normal-date))})} + (atime/unparse-local atime/normal-date)) + :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 :customize-due-and-scheduled? + (com/hidden {:name (fc/field-name) + :value (fc/field-value) + :x-model "customizeDueAndScheduled"})) (fc/with-field :invoice/client (if (:client request) (com/hidden {:name (fc/field-name) @@ -184,55 +194,65 @@ :content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c)) :x-model "vendorId"})])) - [:div.flex.gap-x-8.md:flex-row.flex-col - (fc/with-field :invoice/date - (com/validated-field - {:label "Date" - :errors (fc/field-errors)} - [:div {:class "w-24"} - (com/date-input {:value (some-> (fc/field-value) - (atime/unparse-local atime/normal-date)) - :name (fc/field-name) - :error? (fc/field-errors) - :x-model "date" - :placeholder "1/1/2024"})])) - (fc/with-field :invoice/due - (com/validated-field - {:label "Due (optional)" - :errors (fc/field-errors)} + + (fc/with-field :invoice/date + (com/validated-field + {:label "Date" + :errors (fc/field-errors)} + [:div {:class "w-24"} + (com/date-input {:value (some-> (fc/field-value) + (atime/unparse-local atime/normal-date)) + :name (fc/field-name) + :error? (fc/field-errors) + :x-model "date" + :placeholder "1/1/2024"})])) + (com/validated-field {} + [:div {:x-show "!customizeDueAndScheduled"} + (com/link {"@click" "customizeDueAndScheduled=true" + :x-show "!due && !scheduledPayment"} + "Add due / scheduled payment date") + (com/link {"@click" "customizeDueAndScheduled=true" + :x-show "due || scheduledPayment"} + "Change due / scheduled payment date")]) + (fc/with-field :invoice/due + (com/validated-field + (hx/alpine-appear {:label "Due (optional)" + :errors (fc/field-errors) + :x-show "customizeDueAndScheduled"}) + [:div {:class "w-24" + :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/due-date) + :x-hx-val:client-id "clientId" ;; TODO maybe just use the whole form + :x-hx-val:vendor-id "vendorId" + :x-hx-val:date "date" + :x-dispatch:changed "[clientId, vendorId, date]" + :hx-trigger "changed" + :hx-target "this" + :hx-swap "innerHTML"} + (com/date-input {:value (some-> (fc/field-value) + (atime/unparse-local atime/normal-date)) + :name (fc/field-name) + :x-model "due" + + :error? (fc/field-errors) + :placeholder "1/1/2024"})])) + (fc/with-field :invoice/scheduled-payment + (com/validated-field + (hx/alpine-appear {:label "Scheduled payment (optional)" + :errors (fc/field-errors) + :x-show "customizeDueAndScheduled"}) + [:div {:class "w-24"} [:div {:class "w-24" - :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/due-date) - :x-hx-val:client-id "clientId" ;; TODO maybe just use the whole form - :x-hx-val:vendor-id "vendorId" - :x-hx-val:date "date" - :x-dispatch:changed "[clientId, vendorId, date]" + + :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/scheduled-payment-date) + :x-dispatch:changed "[clientId, vendorId, due]" :hx-trigger "changed" :hx-target "this" :hx-swap "innerHTML"} (com/date-input {:value (some-> (fc/field-value) (atime/unparse-local atime/normal-date)) :name (fc/field-name) - :x-model "due" - :error? (fc/field-errors) - :placeholder "1/1/2024"})])) - (fc/with-field :invoice/scheduled-payment - (com/validated-field - {:label "Scheduled payment (optional)" - :errors (fc/field-errors)} - [:div {:class "w-24"} - [:div {:class "w-24" - - :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/scheduled-payment-date) - :x-dispatch:changed "[clientId, vendorId, due]" - :hx-trigger "changed" - :hx-target "this" - :hx-swap "innerHTML"} - (com/date-input {:value (some-> (fc/field-value) - (atime/unparse-local atime/normal-date)) - :name (fc/field-name) - :error? (fc/field-errors) - :placeholder "1/1/2024"})]]))] + :placeholder "1/1/2024"})]])) (fc/with-field :invoice/invoice-number (com/validated-field @@ -252,10 +272,19 @@ :name (fc/field-name) :class "w-24" :error? (fc/field-errors) - :placeholder "212.44"})]))]) + :placeholder "212.44"})])) + + [:div#expense-account-prediction + (hx/alpine-appear + {:x-dispatch:bryce "[vendorId]" + :hx-trigger "load, bryce" + :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/account-prediction) + :hx-target "this" + :hx-swap "innerHTML"})]]) :footer - (mm/default-step-footer linear-wizard this :validation-route ::route/new-wizard-navigate) + (mm/default-step-footer linear-wizard this :validation-route ::route/new-wizard-navigate + :next-button (com/button {:color :primary :x-ref "next" :class "w-32"} "Save")) :validation-route ::route/new-wizard-navigate))) (defn- location-select* @@ -544,6 +573,14 @@ transaction [:upsert-invoice (-> multi-form-state :snapshot (assoc :db/id "invoice") + (dissoc :customize-due-and-scheduled?) + (assoc :invoice/expense-accounts (if-let [ieas (seq (-> multi-form-state :snapshot :invoice/expense-accounts))] + ieas + [{:db/id "123" + :invoice-expense-account/location "Shared" + :invoice-expense-account/account (:db/id (:vendor/default-account (clientize-vendor (get-vendor vendor-id) + client-id))) + :invoice-expense-account/amount (:invoice/total (:snapshot multi-form-state))}])) (assoc :invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state)) :invoice/import-status :import-status/imported @@ -551,8 +588,9 @@ (maybe-spread-locations) (update :invoice/date coerce/to-date) (update :invoice/due coerce/to-date))]] + (alog/peek ::test transaction) - (assert-invoice-amounts-add-up invoice) + (assert-invoice-amounts-add-up (second transaction)) (assert-no-conflicting invoice) (exception->4xx #(assert-can-see-client (:identity request) client-id)) @@ -631,6 +669,27 @@ :error? false :placeholder "1/1/2024"})))) +(defn account-prediction [{:keys [multi-form-state form-errors]}] + (let [vendor (clientize-vendor (get-vendor (:invoice/vendor (:step-params multi-form-state))) + (->db-id (:invoice/client (:step-params multi-form-state)))) ] + + (html-response + (if-let [account-name (:account/name (:vendor/default-account vendor))] + [:div {:class "transition duration-300 ease-in-out htmx-added:opacity-0 opacity-100"} + [:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} "Default expense account"] + [:div.flex.gap-2.items-center (com/pill {:color :primary} account-name) + (com/validated-save-button (cond-> {:errors form-errors + ;;:x-data (hx/json {}) + :class "w-24" + :hx-put (hu/url (bidi/path-for ssr-routes/only-routes ::route/new-wizard-navigate) + {:from (mm/encode-step-key :basic-details) + :to (mm/encode-step-key :accounts)}) + :hx-target "closest form"}) + + "Change" + [:div.w-5.h-5 svg/arrow-right])]] + [:div])))) + (def key->handler (apply-middleware-to-all-handlers {::route/new-wizard (-> mm/open-wizard-handler @@ -640,6 +699,10 @@ (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) (wrap-nested-form-params)) + ::route/account-prediction (-> account-prediction + (mm/wrap-wizard new-wizard) + (mm/wrap-decode-multi-form-state) + (wrap-nested-form-params)) ::route/scheduled-payment-date (-> scheduled-payment-date (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index ce5524e4..62c39255 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -10,6 +10,7 @@ "/navigate" ::new-wizard-navigate "/account/new" ::new-wizard-new-account "/account/location-select" ::location-select + "/account/prediction" ::account-prediction "/total" ::expense-account-total} "/pay-button" ::pay-button "/pay" {:get ::pay-wizard