adds general ledger links, allows starting new.

This commit is contained in:
2024-03-25 21:02:35 -07:00
parent ea7ad57da2
commit 506c075846
5 changed files with 153 additions and 122 deletions

View File

@@ -21,7 +21,7 @@
:hx-target-400 "#form-errors .error-content" :hx-target-400 "#form-errors .error-content"
:hx-trigger "submit" :hx-trigger "submit"
:hx-target "this" :hx-target "this"
"x-trap" "true" }) "x-trap" "true"})
(defprotocol ModalWizardStep (defprotocol ModalWizardStep
(step-key [this]) (step-key [this])
@@ -99,11 +99,11 @@
(step-name (get-step linear-wizard n))]))))) (step-name (get-step linear-wizard n))])))))
(defn back-button [linear-wizard step validation-route] (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) [: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)) {:from (encode-step-key (step-key step))
:to (encode-step-key (->> (partition-all 2 1 (steps linear-wizard)) :to (encode-step-key (->> (partition-all 2 1 (steps linear-wizard))
(filter (fn [[from to]] (filter (fn [[from to]]
(= to (step-key step)))) (= to (step-key step))))
ffirst))}) ffirst))})
:class "dark:text-blue-500"} :class "dark:text-blue-500"}
"Back"]) "Back"])
@@ -226,25 +226,28 @@
:else :else
"forward"))) "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 (def next-handler
(-> (fn [{:keys [wizard] :as request}] (-> (fn [{:keys [wizard] :as request}]
(let [current-step (get-current-step wizard) (render-navigate {:request request
to-step (:to (:query-params request)) :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")})))
(wrap-ensure-step) (wrap-ensure-step)
(wrap-schema-enforce :query-schema (wrap-schema-enforce :query-schema
[:map [:map
@@ -321,9 +324,9 @@
(defn open-wizard-handler [{:keys [wizard current-step] :as request}] (defn open-wizard-handler [{:keys [wizard current-step] :as request}]
(modal-response (modal-response
[:div#transitioner.flex-1 {:x-data (hx/json {"transitionType" "none"}) [:div#transitioner.flex-1 {:x-data (hx/json {"transitionType" "none"})
:x-ref "transitioner" :x-ref "transitioner"
:class "" :class ""
"@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) { "@htmx:after-request" "if(event.detail.xhr.getResponseHeader('x-transition-type')) {
$refs.transitioner.classList.remove('forward') $refs.transitioner.classList.remove('forward')
$refs.transitioner.classList.remove('backward'); $refs.transitioner.classList.remove('backward');
$refs.transitioner.classList.add('group/transition') $refs.transitioner.classList.add('group/transition')
@@ -363,17 +366,15 @@
main-transformer))))) main-transformer)))))
#_(comment #_(comment
(def f {"snapshot" (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}", "{: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" "[]", "edit-path" "[]",
"current-step" ":payment-details", "current-step" ":payment-details",
"mode" "advanced", "mode" "advanced",
"step-params" "step-params"
{"invoices" {"invoices"
{"0" {"invoice_id" "17592297837035", "amount" "1"}, {"0" {"invoice_id" "17592297837035", "amount" "1"},
"1" {"invoice_id" "17592297837049", "amount" "23.00"}}}}) "1" {"invoice_id" "17592297837049", "amount" "23.00"}}}})
(mc/decode [:map [:step-params {:optional true} [:maybe :any]]] (mc/decode [:map [:step-params {:optional true} [:maybe :any]]]
f f
main-transformer) main-transformer))
)

View File

@@ -6,6 +6,7 @@
:invoice/outstanding-balance :invoice/outstanding-balance
:invoice/source-url :invoice/source-url
[:invoice/date :xform clj-time.coerce/from-date] [:invoice/date :xform clj-time.coerce/from-date]
{:invoice/client [:client/code :db/id :client/name] {:invoice/client [:client/code :db/id :client/name]
:invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id :invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id
@@ -13,6 +14,7 @@
{:account/client-overrides [:account-client-override/name {:account/client-overrides [:account-client-override/name
{:account-client-override/client [:db/id]}]}]}] {:account-client-override/client [:db/id]}]}]}]
[:transaction/_invoices :as :invoice/transaction] [: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 [:payment/_invoices :as :invoice/payments] [:db/id :payment/date :payment/amount
{[:transaction/_payment :as :payment/transaction] [:db/id] {[:transaction/_payment :as :payment/transaction] [:db/id]

View File

@@ -3,7 +3,6 @@
:refer [audit-transact conn pull-attr]] :refer [audit-transact conn pull-attr]]
[auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.invoices :as d-invoices] [auto-ap.datomic.invoices :as d-invoices]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.graphql.utils :refer [assert-can-see-client [auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked exception->4xx]] assert-not-locked exception->4xx]]
[auto-ap.logging :as alog] [auto-ap.logging :as alog]
@@ -28,6 +27,7 @@
[clj-time.coerce :as coerce] [clj-time.coerce :as coerce]
[clj-time.core :as time] [clj-time.core :as time]
[datomic.api :as dc] [datomic.api :as dc]
[hiccup.util :as hu]
[iol-ion.query :refer [dollars=]] [iol-ion.query :refer [dollars=]]
[malli.core :as mc] [malli.core :as mc]
[malli.util :as mut])) [malli.util :as mut]))
@@ -193,7 +193,6 @@
:name (fc/field-name) :name (fc/field-name)
:error? (fc/field-errors) :error? (fc/field-errors)
:x-model "date" :x-model "date"
;; "@change" "date=$event.target.value" ;; TODO x-model does not work
:placeholder "1/1/2024"})])) :placeholder "1/1/2024"})]))
(fc/with-field :invoice/due (fc/with-field :invoice/due
(com/validated-field (com/validated-field
@@ -259,19 +258,19 @@
(defn- location-select* (defn- location-select*
[{:keys [name account-location client-locations value]}] [{:keys [name account-location client-locations value]}]
(com/select {:options (into [["" ""]] (let [options (into (cond account-location
(cond account-location [[account-location account-location]]
[[account-location account-location]]
(seq client-locations) (seq client-locations)
(into [["Shared" "Shared"]] (into [["Shared" "Shared"]]
(for [cl client-locations] (for [cl client-locations]
[cl cl])) [cl cl]))
:else :else
[["Shared" "Shared"]])) [["Shared" "Shared"]]))]
:name name (com/select {:options options
:value value :name name
:class "w-full"})) :value (ffirst options)
:class "w-full"})))
(defn- account-typeahead* (defn- account-typeahead*
[{:keys [name value client-id x-model]}] [{:keys [name value client-id x-model]}]
@@ -406,7 +405,8 @@
(format "$%,.2f" (:invoice/total snapshot))) (format "$%,.2f" (:invoice/total snapshot)))
(com/data-grid-cell {})))))]) (com/data-grid-cell {})))))])
:footer :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)) :validation-route ::route/new-wizard-navigate))
mm/Initializable mm/Initializable
(init-step-params (init-step-params
@@ -420,6 +420,44 @@
:invoice-expense-account/amount (:invoice/total (:step-params current))}]) :invoice-expense-account/amount (:invoice/total (:step-params current))}])
(: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]}] (defn assert-no-conflicting [{:invoice/keys [invoice-number client vendor]}]
(when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice-number (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)))))) (str (bidi/path-for ssr-routes/only-routes ::route/new-invoice-submit))))))
(steps [_] (steps [_]
[:basic-details [:basic-details
:accounts]) :accounts
:next-steps])
(get-step [this step-key] (get-step [this step-key]
(let [step-key-result (mc/parse mm/step-key-schema step-key) (let [step-key-result (mc/parse mm/step-key-schema step-key)
[step-key-type step-key] step-key-result] [step-key-type step-key] step-key-result]
(get {:basic-details (->BasicDetailsStep this) (get {:basic-details (->BasicDetailsStep this)
:accounts (->AccountsStep this)} :accounts (->AccountsStep this)
:next-steps (->NextSteps this)}
step-key))) step-key)))
(form-schema [_] new-form-schema) (form-schema [_] new-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}] (submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [invoice (:snapshot multi-form-state) (let [invoice (:snapshot multi-form-state)
client-id (->db-id (:invoice/client invoice)) client-id (->db-id (:invoice/client invoice))
vendor-id (->db-id (:invoice/vendor invoice)) vendor-id (->db-id (:invoice/vendor invoice))
transaction [:upsert-invoice (-> multi-form-state transaction [:upsert-invoice (-> multi-form-state
:snapshot :snapshot
(assoc :db/id "invoice") (assoc :db/id "invoice")
(assoc (assoc
:invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state)) :invoice/outstanding-balance (:invoice/total (:snapshot multi-form-state))
:invoice/import-status :import-status/imported :invoice/import-status :import-status/imported
:invoice/status :invoice-status/unpaid) :invoice/status :invoice-status/unpaid)
(update :invoice/expense-accounts (update :invoice/expense-accounts
(fn [eas] (fn [eas]
(mapv (fn [ea] (mapv (fn [ea]
(-> ea (-> ea
(assoc :invoice-expense-account/amount (assoc :invoice-expense-account/amount
(:invoice/total (:snapshot multi-form-state))))) (:invoice/total (:snapshot multi-form-state)))))
eas))) eas)))
(update :invoice/date coerce/to-date) (update :invoice/date coerce/to-date)
(update :invoice/due coerce/to-date))]] (update :invoice/due coerce/to-date))]]
(assert-invoice-amounts-add-up invoice) (assert-invoice-amounts-add-up invoice)
(assert-no-conflicting invoice) (assert-no-conflicting invoice)
(exception->4xx #(assert-can-see-client (:identity request) client-id)) (exception->4xx #(assert-can-see-client (:identity request) client-id))
(exception->4xx #(assert-not-locked client-id (:invoice/date invoice))) (exception->4xx #(assert-not-locked client-id (:invoice/date invoice)))
(let [transaction-result (audit-transact [transaction] (:identity request))] (let [transaction-result (audit-transact [transaction] (:identity request))]
(solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"])) (solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"]))
(html-response (mm/render-navigate {:request (assoc-in request [:multi-form-state :snapshot :db/id] (get-in transaction-result [:tempids "invoice"]))
(@(resolve 'auto-ap.ssr.invoices/row*) :to-step :next-steps})
#_(html-response
(@(resolve 'auto-ap.ssr.invoices/row*)
identity identity
(dc/pull (dc/db conn) default-read (get-in transaction-result [:tempids "invoice"])) (dc/pull (dc/db conn) default-read (get-in transaction-result [:tempids "invoice"]))
{:flash? true {:flash? true
:request request}) :request request})
:headers {"hx-trigger" "modalclose" :headers {"hx-trigger" "modalclose"
"hx-retarget" "#entity-table tbody" "hx-retarget" "#entity-table tbody"
"hx-reswap" "afterbegin" "hx-reswap" "afterbegin"
#_(format "#entity-table tr[data-id=\"%d\"]" (get-in transaction-result [:tempids "invoice"]))}))) #_(format "#entity-table tr[data-id=\"%d\"]" (get-in transaction-result [:tempids "invoice"]))})))
#_(html-response [:div] #_(html-response [:div]
:headers {"hx-trigger" "modalclose,invalidated"}))) :headers {"hx-trigger" "modalclose,invalidated"})))
(def new-wizard (->NewWizard2 nil nil)) (def new-wizard (->NewWizard2 nil nil))
@@ -529,12 +571,11 @@
(defn due-date [{:keys [multi-form-state]}] (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))) (let [vendor (clientize-vendor (get-vendor (:invoice/vendor (:step-params multi-form-state)))
(->db-id (:invoice/client (:step-params multi-form-state)))) (->db-id (:invoice/client (:step-params multi-form-state))))
good-date good-date
(or (when (and (:invoice/date (:step-params multi-form-state)) (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/plus (:invoice/date (:step-params multi-form-state))
(time/days (-> vendor :vendor/terms)))) (time/days (-> vendor :vendor/terms))))
(:invoice/due (:step-params multi-form-state)))] (:invoice/due (:step-params multi-form-state)))]
@@ -554,7 +595,7 @@
(->db-id (:invoice/client (:step-params multi-form-state)))) (->db-id (:invoice/client (:step-params multi-form-state))))
good-date good-date
(when (and (:invoice/due (:step-params multi-form-state)) (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)))] (:invoice/due (:step-params multi-form-state)))]
@@ -568,32 +609,13 @@
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
{::route/new-wizard (-> mm/open-wizard-handler {::route/new-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard new-wizard) (mm/wrap-wizard new-wizard)
(mm/wrap-init-multi-form-state initial-new-wizard-state)) (mm/wrap-init-multi-form-state initial-new-wizard-state))
::route/due-date (-> due-date ::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-wizard new-wizard)
(mm/wrap-decode-multi-form-state) (mm/wrap-decode-multi-form-state)
(wrap-nested-form-params)) (wrap-nested-form-params))
::route/scheduled-payment-date (-> scheduled-payment-date ::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-wizard new-wizard)
(mm/wrap-decode-multi-form-state) (mm/wrap-decode-multi-form-state)
(wrap-nested-form-params)) (wrap-nested-form-params))

View File

@@ -496,6 +496,11 @@
{:exact-match-id (:db/id (first (:payment/transaction p)))}) {:exact-match-id (:db/id (first (:payment/transaction p)))})
:color :secondary :color :secondary
:content "Transaction"}))))) :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) (when (:invoice/source-url i)
[{:link (:invoice/source-url i) [{:link (:invoice/source-url i)
:color :secondary :color :secondary

View File

@@ -7,6 +7,7 @@
[auto-ap.routes :as routes] [auto-ap.routes :as routes]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.routes.payments :as payment-routes] [auto-ap.routes.payments :as payment-routes]
[auto-ap.routes.invoice :as invoice-routes]
[auto-ap.subs :as subs] [auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal] [auto-ap.views.components.modal :as modal]
[auto-ap.views.components.search :as search] [auto-ap.views.components.search :as search]
@@ -176,11 +177,11 @@
"Home" ] "Home" ]
(when (p/can? @user {:subject :invoice-page}) (when (p/can? @user {:subject :invoice-page})
[:a.navbar-item {:class [(active-when ap #{:unpaid-invoices :paid-invoices})] [: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" ]) "Invoices" ])
(when (p/can? @user {:subject :payment-page}) (when (p/can? @user {:subject :payment-page})
[:a.navbar-item {:class [(active-when ap = :payments)] [: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" ]) "Payments" ])
(when (p/can? @user {:subject :pos-page}) (when (p/can? @user {:subject :pos-page})
[:a.navbar-item {:class [(active-when ap = :pos-sales)] [:a.navbar-item {:class [(active-when ap = :pos-sales)]