From 506c075846da5d7cae9f87ee8c5cff5c8a5cbeb3 Mon Sep 17 00:00:00 2001 From: Bryce Date: Mon, 25 Mar 2024 21:02:35 -0700 Subject: [PATCH] adds general ledger links, allows starting new. --- .../auto_ap/ssr/components/multi_modal.clj | 83 ++++---- src/clj/auto_ap/ssr/invoice/common.clj | 2 + .../ssr/invoice/new_invoice_wizard.clj | 180 ++++++++++-------- src/clj/auto_ap/ssr/invoices.clj | 5 + .../auto_ap/views/components/layouts.cljs | 5 +- 5 files changed, 153 insertions(+), 122 deletions(-) diff --git a/src/clj/auto_ap/ssr/components/multi_modal.clj b/src/clj/auto_ap/ssr/components/multi_modal.clj index 989db15d..38bd0939 100644 --- a/src/clj/auto_ap/ssr/components/multi_modal.clj +++ b/src/clj/auto_ap/ssr/components/multi_modal.clj @@ -21,7 +21,7 @@ :hx-target-400 "#form-errors .error-content" :hx-trigger "submit" :hx-target "this" - "x-trap" "true" }) + "x-trap" "true"}) (defprotocol ModalWizardStep (step-key [this]) @@ -99,11 +99,11 @@ (step-name (get-step linear-wizard n))]))))) (defn back-button [linear-wizard step validation-route] [:a.cursor-pointer.whitespace-nowrap.font-medium.text-blue-600 {:hx-put (hu/url (bidi/path-for ssr-routes/only-routes validation-route) - {:from (encode-step-key (step-key step)) - :to (encode-step-key (->> (partition-all 2 1 (steps linear-wizard)) - (filter (fn [[from to]] - (= to (step-key step)))) - ffirst))}) + {:from (encode-step-key (step-key step)) + :to (encode-step-key (->> (partition-all 2 1 (steps linear-wizard)) + (filter (fn [[from to]] + (= to (step-key step)))) + ffirst))}) :class "dark:text-blue-500"} "Back"]) @@ -226,25 +226,28 @@ :else "forward"))) +(defn render-navigate [{ {:keys [wizard] :as request} :request to-step :to-step}] + (let [current-step (get-current-step wizard) + wizard (navigate wizard to-step) + new-step (get-current-step wizard) + transition-type (get-transition-type wizard (step-key current-step) to-step)] + (html-response + (render-wizard wizard + (-> request + (assoc :multi-form-state (-> (:multi-form-state request) + (merge-multi-form-state) + (select-state (edit-path new-step request) {}) + (#(cond-> % + (satisfies? Initializable new-step) + (assoc :step-params + (init-step-params new-step % request)))))))) + :headers {"HX-reswap" (when transition-type "outerHTML swap:0.16s") + "x-transition-type" (or transition-type "none")}))) + (def next-handler (-> (fn [{:keys [wizard] :as request}] - (let [current-step (get-current-step wizard) - to-step (:to (:query-params request)) - wizard (navigate wizard to-step) - new-step (get-current-step wizard) - transition-type (get-transition-type wizard (step-key current-step) to-step)] - (html-response - (render-wizard wizard - (-> request - (assoc :multi-form-state (-> (:multi-form-state request) - (merge-multi-form-state) - (select-state (edit-path new-step request) {}) - (#(cond-> % - (satisfies? Initializable new-step) - (assoc :step-params - (init-step-params new-step % request)))))))) - :headers {"HX-reswap" (when transition-type "outerHTML swap:0.16s") - "x-transition-type" (or transition-type "none")}))) + (render-navigate {:request request + :to-step (:to (:query-params request))})) (wrap-ensure-step) (wrap-schema-enforce :query-schema [:map @@ -321,9 +324,9 @@ (defn open-wizard-handler [{:keys [wizard current-step] :as request}] (modal-response [:div#transitioner.flex-1 {:x-data (hx/json {"transitionType" "none"}) - :x-ref "transitioner" - :class "" - "@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { + :x-ref "transitioner" + :class "" + "@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { $refs.transitioner.classList.remove('forward') $refs.transitioner.classList.remove('backward'); $refs.transitioner.classList.add('group/transition') @@ -361,19 +364,17 @@ :any]]] (:form-params request) main-transformer))))) - -#_(comment - (def f {"snapshot" - "{:invoices [{:invoice_id 17592297837035, :amount 23.0, :invoice {:db/id 17592297837035, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"702,34\"}} {:invoice_id 17592297837049, :amount 23.0, :invoice {:db/id 17592297837049, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"80[234234\"}}], :client 17592232555238}", - "edit-path" "[]", - "current-step" ":payment-details", - "mode" "advanced", - "step-params" - {"invoices" - {"0" {"invoice_id" "17592297837035", "amount" "1"}, - "1" {"invoice_id" "17592297837049", "amount" "23.00"}}}}) - (mc/decode [:map [:step-params {:optional true} [:maybe :any]]] - f - main-transformer) - ) \ No newline at end of file +#_(comment + (def f {"snapshot" + "{:invoices [{:invoice_id 17592297837035, :amount 23.0, :invoice {:db/id 17592297837035, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"702,34\"}} {:invoice_id 17592297837049, :amount 23.0, :invoice {:db/id 17592297837049, :invoice/vendor {:db/id 17592186045722, :vendor/name \"Sysco\"}, :invoice/client {:db/id 17592232555238}, :invoice/outstanding-balance 23.0, :invoice/invoice-number \"80[234234\"}}], :client 17592232555238}", + "edit-path" "[]", + "current-step" ":payment-details", + "mode" "advanced", + "step-params" + {"invoices" + {"0" {"invoice_id" "17592297837035", "amount" "1"}, + "1" {"invoice_id" "17592297837049", "amount" "23.00"}}}}) + (mc/decode [:map [:step-params {:optional true} [:maybe :any]]] + f + main-transformer)) \ No newline at end of file diff --git a/src/clj/auto_ap/ssr/invoice/common.clj b/src/clj/auto_ap/ssr/invoice/common.clj index f73fa5d8..cbef98ea 100644 --- a/src/clj/auto_ap/ssr/invoice/common.clj +++ b/src/clj/auto_ap/ssr/invoice/common.clj @@ -5,6 +5,7 @@ :invoice/total :invoice/outstanding-balance :invoice/source-url + [:invoice/date :xform clj-time.coerce/from-date] {:invoice/client [:client/code :db/id :client/name] @@ -13,6 +14,7 @@ {:account/client-overrides [:account-client-override/name {:account-client-override/client [:db/id]}]}]}] [:transaction/_invoices :as :invoice/transaction] [:db/id] + [:journal-entry/_original-entity :as :invoice/journal-entry] [:db/id] [:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount {[:transaction/_payment :as :payment/transaction] [:db/id] 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 b5c5a83c..37931616 100644 --- a/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj +++ b/src/clj/auto_ap/ssr/invoice/new_invoice_wizard.clj @@ -3,7 +3,6 @@ :refer [audit-transact conn pull-attr]] [auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.invoices :as d-invoices] - [auto-ap.ssr.invoice.common :refer [default-read]] [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked exception->4xx]] [auto-ap.logging :as alog] @@ -28,6 +27,7 @@ [clj-time.coerce :as coerce] [clj-time.core :as time] [datomic.api :as dc] + [hiccup.util :as hu] [iol-ion.query :refer [dollars=]] [malli.core :as mc] [malli.util :as mut])) @@ -193,7 +193,6 @@ :name (fc/field-name) :error? (fc/field-errors) :x-model "date" - ;; "@change" "date=$event.target.value" ;; TODO x-model does not work :placeholder "1/1/2024"})])) (fc/with-field :invoice/due (com/validated-field @@ -259,19 +258,19 @@ (defn- location-select* [{:keys [name account-location client-locations value]}] - (com/select {:options (into [["" ""]] - (cond account-location - [[account-location account-location]] + (let [options (into (cond account-location + [[account-location account-location]] - (seq client-locations) - (into [["Shared" "Shared"]] - (for [cl client-locations] - [cl cl])) - :else - [["Shared" "Shared"]])) - :name name - :value value - :class "w-full"})) + (seq client-locations) + (into [["Shared" "Shared"]] + (for [cl client-locations] + [cl cl])) + :else + [["Shared" "Shared"]]))] + (com/select {:options options + :name name + :value (ffirst options) + :class "w-full"}))) (defn- account-typeahead* [{:keys [name value client-id x-model]}] @@ -379,11 +378,11 @@ (com/data-grid-header {:class "w-16"})]} (fc/cursor-map #(invoice-expense-account-row* {:value % :client-id (:invoice/client snapshot)})) - + (com/data-grid-new-row {:colspan 4 :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard-new-account) - :row-offset 0 + :row-offset 0 :index (count (fc/field-value)) :tr-params {:hx-vals (hx/json {:client-id (:invoice/client snapshot)})}} "New account") @@ -406,7 +405,8 @@ (format "$%,.2f" (:invoice/total snapshot))) (com/data-grid-cell {})))))]) :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)) mm/Initializable (init-step-params @@ -420,6 +420,44 @@ :invoice-expense-account/amount (:invoice/total (:step-params current))}]) (:step-params current)))) +;; TODO closing should render the final row + +(defrecord NextSteps [linear-wizard] + mm/ModalWizardStep + (step-name [_] + "Next Steps") + (step-key [_] + :next-steps) + + (edit-path [_ _] + []) + + (step-schema [_] + (mut/select-keys (mm/form-schema linear-wizard) #{})) + + (render-step [this {{:keys [snapshot] :as multi-form-state} :multi-form-state :as request}] + + (mm/default-render-step + linear-wizard this + :head [:div.p-2 "Invoice accounts "] + :body (mm/default-step-body + {} + [:p.text-lg "Would you like to pay this invoice now?"] + [:div.flex.flex-col.gap-2 + (com/button {:color :primary + :class "w-24" + :hx-get (hu/url (bidi.bidi/path-for ssr-routes/only-routes ::route/pay-wizard) + {:selected (:db/id snapshot)})} "Pay now") + (com/button {:color :secondary + :class "w-24" + :hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/new-wizard)} "Add another") + (com/button {:class "w-24" + "@click" "$dispatch('modalclose')"} + "Close")]) + :footer + nil + :validation-route ::route/new-wizard-navigate))) + (defn assert-no-conflicting [{:invoice/keys [invoice-number client vendor]}] (when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice-number @@ -455,61 +493,65 @@ (str (bidi/path-for ssr-routes/only-routes ::route/new-invoice-submit)))))) (steps [_] [:basic-details - :accounts]) + :accounts + :next-steps]) (get-step [this step-key] (let [step-key-result (mc/parse mm/step-key-schema step-key) [step-key-type step-key] step-key-result] (get {:basic-details (->BasicDetailsStep this) - :accounts (->AccountsStep this)} + :accounts (->AccountsStep this) + :next-steps (->NextSteps this)} step-key))) (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") - (assoc - :invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state)) - :invoice/import-status :import-status/imported - :invoice/status :invoice-status/unpaid) + (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") + (assoc + :invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state)) + :invoice/import-status :import-status/imported + :invoice/status :invoice-status/unpaid) - (update :invoice/expense-accounts - (fn [eas] - (mapv (fn [ea] - (-> ea - (assoc :invoice-expense-account/amount - (:invoice/total (:snapshot multi-form-state))))) - eas))) - (update :invoice/date coerce/to-date) - (update :invoice/due coerce/to-date))]] + (update :invoice/expense-accounts + (fn [eas] + (mapv (fn [ea] + (-> ea + (assoc :invoice-expense-account/amount + (:invoice/total (:snapshot multi-form-state))))) + eas))) + (update :invoice/date coerce/to-date) + (update :invoice/due coerce/to-date))]] - (assert-invoice-amounts-add-up invoice) - (assert-no-conflicting invoice) - (exception->4xx #(assert-can-see-client (:identity request) client-id)) + (assert-invoice-amounts-add-up 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"])) - (html-response - (@(resolve 'auto-ap.ssr.invoices/row*) + (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"])) + (mm/render-navigate {:request (assoc-in request [:multi-form-state :snapshot :db/id] (get-in transaction-result [:tempids "invoice"])) + :to-step :next-steps}) + #_(html-response + (@(resolve 'auto-ap.ssr.invoices/row*) - identity - (dc/pull (dc/db conn) default-read (get-in transaction-result [:tempids "invoice"])) - {:flash? true - :request request}) - :headers {"hx-trigger" "modalclose" - "hx-retarget" "#entity-table tbody" - "hx-reswap" "afterbegin" - - #_(format "#entity-table tr[data-id=\"%d\"]" (get-in transaction-result [:tempids "invoice"]))}))) + identity + (dc/pull (dc/db conn) default-read (get-in transaction-result [:tempids "invoice"])) + {:flash? true + :request request}) + :headers {"hx-trigger" "modalclose" + "hx-retarget" "#entity-table tbody" + "hx-reswap" "afterbegin" + + #_(format "#entity-table tr[data-id=\"%d\"]" (get-in transaction-result [:tempids "invoice"]))}))) - #_(html-response [:div] - :headers {"hx-trigger" "modalclose,invalidated"}))) + #_(html-response [:div] + :headers {"hx-trigger" "modalclose,invalidated"}))) (def new-wizard (->NewWizard2 nil nil)) @@ -529,12 +571,11 @@ (defn due-date [{:keys [multi-form-state]}] - (alog/peek ::date multi-form-state) (let [vendor (clientize-vendor (get-vendor (:invoice/vendor (:step-params multi-form-state))) (->db-id (:invoice/client (:step-params multi-form-state)))) good-date (or (when (and (:invoice/date (:step-params multi-form-state)) - (-> vendor :vendor/terms)) ;; TODO get specific terms + (-> vendor :vendor/terms)) (time/plus (:invoice/date (:step-params multi-form-state)) (time/days (-> vendor :vendor/terms)))) (:invoice/due (:step-params multi-form-state)))] @@ -554,7 +595,7 @@ (->db-id (:invoice/client (:step-params multi-form-state)))) good-date (when (and (:invoice/due (:step-params multi-form-state)) - (:vendor/automatically-paid-when-due vendor)) ;; TODO get specific terms + (:vendor/automatically-paid-when-due vendor)) (:invoice/due (:step-params multi-form-state)))] @@ -568,32 +609,13 @@ (def key->handler (apply-middleware-to-all-handlers {::route/new-wizard (-> mm/open-wizard-handler - (mm/wrap-wizard new-wizard) (mm/wrap-init-multi-form-state initial-new-wizard-state)) ::route/due-date (-> due-date - #_(wrap-schema-decode :form-schema [:map - [:step-params {:optional true} - [:maybe - [:map - [:invoice/due {:optional true} [:maybe clj-date-schema]]]]] - [:date {:optional true} [:maybe clj-date-schema]] - [:due-date {:optional true} [:maybe clj-date-schema]] - [:client-id {:optional true} [:maybe entity-id]] - [:vendor-id {:optional true} [:maybe entity-id]]]) (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) (wrap-nested-form-params)) ::route/scheduled-payment-date (-> scheduled-payment-date - #_(wrap-schema-decode :form-schema [:map - [:step-params {:optional true} - [:maybe - [:map - [:invoice/due {:optional true} [:maybe clj-date-schema]]]]] - [:date {:optional true} [:maybe clj-date-schema]] - [:due-date {:optional true} [:maybe clj-date-schema]] - [:client-id {:optional true} [:maybe entity-id]] - [:vendor-id {:optional true} [:maybe entity-id]]]) (mm/wrap-wizard new-wizard) (mm/wrap-decode-multi-form-state) (wrap-nested-form-params)) diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index a7dc751a..85563017 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -496,6 +496,11 @@ {:exact-match-id (:db/id (first (:payment/transaction p)))}) :color :secondary :content "Transaction"}))))) + (when (:invoice/journal-entry i) + [{:link (hu/url (bidi/path-for client-routes/routes :ledger) + {:exact-match-id (:db/id (first (:invoice/journal-entry i)))}) + :color :yellow + :content "Ledger entry"}]) (when (:invoice/source-url i) [{:link (:invoice/source-url i) :color :secondary diff --git a/src/cljs/auto_ap/views/components/layouts.cljs b/src/cljs/auto_ap/views/components/layouts.cljs index 31d725f9..a555e0a4 100644 --- a/src/cljs/auto_ap/views/components/layouts.cljs +++ b/src/cljs/auto_ap/views/components/layouts.cljs @@ -7,6 +7,7 @@ [auto-ap.routes :as routes] [auto-ap.ssr-routes :as ssr-routes] [auto-ap.routes.payments :as payment-routes] + [auto-ap.routes.invoice :as invoice-routes] [auto-ap.subs :as subs] [auto-ap.views.components.modal :as modal] [auto-ap.views.components.search :as search] @@ -176,11 +177,11 @@ "Home" ] (when (p/can? @user {:subject :invoice-page}) [:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})] - :href (bidi/path-for routes/routes :unpaid-invoices)} + :href (str (bidi/path-for ssr-routes/only-routes ::invoice-routes/all-page) "?date-range=month")} "Invoices" ]) (when (p/can? @user {:subject :payment-page}) [:a.navbar-item {:class [(active-when ap = :payments)] - :href (bidi/path-for ssr-routes/only-routes ::payment-routes/all-page)} + :href (str (bidi/path-for ssr-routes/only-routes ::payment-routes/all-page) "?date-range=month")} "Payments" ]) (when (p/can? @user {:subject :pos-page}) [:a.navbar-item {:class [(active-when ap = :pos-sales)]