automatically set scheduled payment

This commit is contained in:
2024-03-24 14:26:30 -07:00
parent 6bd475e86d
commit ede4d756af
4 changed files with 170 additions and 55 deletions

View File

@@ -156,12 +156,14 @@
(dissoc :size))]) (dissoc :size))])
(defn date-input- [{:keys [size] :as params}] (defn date-input- [{:keys [size] :as params}]
[:div.shrink [:div.shrink {:x-data (hx/json {:value (:value params)})}
[:input [:input
(-> params (-> params
(update :class (fnil hh/add-class "") default-input-classes) (update :class (fnil hh/add-class "") default-input-classes)
(assoc :x-modelable "value")
(assoc :type "text") (assoc :type "text")
(assoc "_" (hiccup/raw "init initDatepicker(me)")) (assoc "_" (hiccup/raw "init initDatepicker(me)"))
(assoc "@change" "value = $event.target.value; console.log(value)")
(assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\") (assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\")
htmx:beforeCleanupElement: this.dp.destroy()")) htmx:beforeCleanupElement: this.dp.destroy()"))
(update :class #(str % (use-size size) " w-full")) (update :class #(str % (use-size size) " w-full"))

View File

@@ -15,9 +15,8 @@
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]] [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils [auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema :refer [->db-id apply-middleware-to-all-handlers clj-date-schema
entity-id html-response money wrap-schema-decode entity-id html-response money wrap-schema-enforce]]
wrap-schema-enforce]]
[auto-ap.time :as atime] [auto-ap.time :as atime]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clj-time.core :as time] [clj-time.core :as time]
@@ -30,6 +29,7 @@
[:invoice/client entity-id] [:invoice/client entity-id]
[:invoice/date clj-date-schema] [:invoice/date clj-date-schema]
[:invoice/due {:optional true} [:maybe clj-date-schema]] [:invoice/due {:optional true} [:maybe clj-date-schema]]
[:invoice/scheduled-payment {:optional true} [:maybe clj-date-schema]]
[:invoice/vendor entity-id] [:invoice/vendor entity-id]
[:invoice/expense-accounts [:invoice/expense-accounts
[:vector {:coerce? true} [:vector {:coerce? true}
@@ -49,7 +49,7 @@
[]) [])
(step-schema [_] (step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due})) (mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment}))
(render-step [this request] (render-step [this request]
(alog/peek ::check (:multi-form-state request)) (alog/peek ::check (:multi-form-state request))
@@ -62,7 +62,9 @@
(fc/field-value (:invoice/client fc/*current*))) (fc/field-value (:invoice/client fc/*current*)))
:vendorId (fc/field-value (:invoice/vendor fc/*current*)) :vendorId (fc/field-value (:invoice/vendor fc/*current*))
:date (-> (fc/field-value (:invoice/date fc/*current*)) :date (-> (fc/field-value (:invoice/date fc/*current*))
(atime/unparse-local atime/normal-date))})} (atime/unparse-local atime/normal-date))
:due (some-> (fc/field-value (:invoice/due fc/*current*))
(atime/unparse-local atime/normal-date))})}
(fc/with-field :invoice/client (fc/with-field :invoice/client
(if (:client request) (if (:client request)
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
@@ -103,7 +105,8 @@
(atime/unparse-local atime/normal-date)) (atime/unparse-local atime/normal-date))
:name (fc/field-name) :name (fc/field-name)
:error? (fc/field-errors) :error? (fc/field-errors)
"@change" "date=$event.target.value" ;; TODO x-model does not work :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
@@ -115,13 +118,15 @@
:x-hx-val:vendor-id "vendorId" :x-hx-val:vendor-id "vendorId"
:x-hx-val:date "date" :x-hx-val:date "date"
:x-dispatch:changed "[clientId, vendorId, date]" :x-dispatch:changed "[clientId, vendorId, date]"
:x-effect "console.log([clientId, vendorId, date])"
:hx-trigger "changed" :hx-trigger "changed"
:hx-target "this" :hx-target "this"
:hx-swap "innerHTML"} :hx-swap "innerHTML"}
(com/date-input {:value (some-> (fc/field-value) (com/date-input {:value (some-> (fc/field-value)
(atime/unparse-local atime/normal-date)) (atime/unparse-local atime/normal-date))
:name (fc/field-name) :name (fc/field-name)
:x-model "due"
:x-effect "console.log('hello due', [due])"
:error? (fc/field-errors) :error? (fc/field-errors)
:placeholder "1/1/2024"})])) :placeholder "1/1/2024"})]))
(fc/with-field :invoice/scheduled-payment (fc/with-field :invoice/scheduled-payment
@@ -129,11 +134,19 @@
{:label "Scheduled payment (optional)" {:label "Scheduled payment (optional)"
:errors (fc/field-errors)} :errors (fc/field-errors)}
[:div {:class "w-24"} [:div {:class "w-24"}
(com/date-input {:value (some-> (fc/field-value) [:div {:class "w-24"
(atime/unparse-local atime/normal-date))
:name (fc/field-name) :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/scheduled-payment-date)
:error? (fc/field-errors) :x-dispatch:changed "[clientId, vendorId, due]"
:placeholder "1/1/2024"})]))] :x-effect "console.log('hello', [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"})]]))]
(fc/with-field :invoice/invoice-number (fc/with-field :invoice/invoice-number
(com/validated-field (com/validated-field
@@ -280,14 +293,14 @@
:validation-route ::route/new-wizard-navigate)) :validation-route ::route/new-wizard-navigate))
mm/Initializable mm/Initializable
(init-step-params (init-step-params
[_ request] [_ request]
(alog/peek ::INIT {:invoice/expense-accounts [{:db/id "123" (alog/peek ::INIT {:invoice/expense-accounts [{:db/id "123"
:invoice-expense-account/amount 100}]}) :invoice-expense-account/amount 100}]})
{:invoice/expense-accounts [{:db/id "123" {:invoice/expense-accounts [{:db/id "123"
:invoice-expense-account/location "Shared" :invoice-expense-account/location "Shared"
:invoice-expense-account/account (ffirst (dc/q '[:find ?a :where [?a :account/name]] :invoice-expense-account/account (ffirst (dc/q '[:find ?a :where [?a :account/name]]
(dc/db conn))) (dc/db conn)))
:invoice-expense-account/amount 100}]})) :invoice-expense-account/amount 100}]}))
@@ -340,24 +353,98 @@
(pull-attr (dc/db conn) :account/location)) (pull-attr (dc/db conn) :account/location))
:client-locations (some->> client-id :client-locations (some->> client-id
(pull-attr (dc/db conn) :client/locations))}))) (pull-attr (dc/db conn) :client/locations))})))
(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]
(= (->db-id (:vendor-terms-override/client to))
client-id)))
(map :vendor-terms-override/terms)
first)
account (or (->> account-overrides
(filter (fn [to]
(= (->db-id (:vendor-account-override/client to))
client-id)))
(map :vendor-account-override/account)
first)
default-account)
account (d-accounts/clientize account client-id)
automatically-paid-when-due (->> automatically-paid-when-due
(filter (fn [to]
(= (->db-id to)
client-id)))
seq
boolean)
vendor (cond-> vendor
terms-override (assoc :vendor/terms terms-override)
true (assoc :vendor/automatically-paid-when-due automatically-paid-when-due
:vendor/default-account account)
true (dissoc :vendor/account-overrides :vendor/terms-overrides))]
vendor))
(comment
(clojure.pprint/pprint
(clientize-vendor (dc/pull
(dc/db conn)
[:vendor/terms
:vendor/automatically-paid-when-due
{:vendor/default-account d-accounts/default-read
:vendor/account-overrides
[:vendor-account-override/client
{:vendor-account-override/account d-accounts/default-read}]}
{:vendor/terms-overrides
[:vendor-terms-override/client :vendor-terms-override/terms]}]
(ffirst (dc/q '[:find ?v :in $ :where [?v :vendor/name "Sysco"]] (dc/db conn))))
(pull-attr (dc/db conn) :db/id [:client/code "DEMO"]))))
(defn get-vendor [vendor-id]
(dc/pull
(dc/db conn)
[:vendor/terms
:vendor/automatically-paid-when-due
{:vendor/default-account d-accounts/default-read
:vendor/account-overrides
[:vendor-account-override/client
{:vendor-account-override/account d-accounts/default-read}]}
{:vendor/terms-overrides
[:vendor-terms-override/client :vendor-terms-override/terms]}]
vendor-id))
(defn due-date [{:keys [multi-form-state]}] (defn due-date [{:keys [multi-form-state]}]
(alog/peek ::date multi-form-state) (alog/peek ::date multi-form-state)
(let [vendor (dc/pull (dc/db conn) (let [vendor (clientize-vendor (get-vendor (:invoice/vendor (:step-params multi-form-state)))
[:vendor/terms {:vendor/terms-overrides (->db-id (:invoice/client (:step-params multi-form-state))))
[:vendor-terms-override/client :vendor-terms-override/terms]}]
(:invoice/vendor (: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)) ;; TODO get specific 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)))]
(alog/peek ::good-date multi-form-state)
(html-response (html-response
(com/date-input {:value (some-> good-date (com/date-input {:value (some-> good-date
(atime/unparse-local atime/normal-date)) (atime/unparse-local atime/normal-date))
:name "step-params[invoice/due]" :name "step-params[invoice/due]"
:x-init (format "due='%s'" (some-> good-date
(atime/unparse-local atime/normal-date)))
:x-model "due"
:error? false
:placeholder "1/1/2024"}))))
(defn scheduled-payment-date [{:keys [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
(when (and (:invoice/due (:step-params multi-form-state))
(:vendor/automatically-paid-when-due vendor)) ;; TODO get specific terms
(:invoice/due (:step-params multi-form-state)))]
(html-response
(com/date-input {:value (some-> good-date
(atime/unparse-local atime/normal-date))
:name "step-params[invoice/scheduled-payment]"
:error? false :error? false
:placeholder "1/1/2024"})))) :placeholder "1/1/2024"}))))
@@ -369,17 +456,30 @@
(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 #_(wrap-schema-decode :form-schema [:map
[:step-params {:optional true} [:step-params {:optional true}
[:maybe [:maybe
[:map [:map
[:invoice/due {:optional true} [:maybe clj-date-schema]]]]] [:invoice/due {:optional true} [:maybe clj-date-schema]]]]]
[:date {:optional true} [:maybe clj-date-schema]] [:date {:optional true} [:maybe clj-date-schema]]
[:due-date {:optional true} [:maybe clj-date-schema]] [:due-date {:optional true} [:maybe clj-date-schema]]
[:client-id {:optional true} [:maybe entity-id]] [:client-id {:optional true} [:maybe entity-id]]
[:vendor-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
#_(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/location-select (-> location-select ::route/location-select (-> location-select
(wrap-schema-enforce :query-schema [:map (wrap-schema-enforce :query-schema [:map
[:name :string] [:name :string]

View File

@@ -185,28 +185,28 @@
(mt2/transformer (mt2/transformer
{:decoders {:decoders
{'inst? {:compile (fn [schema _] {'inst? {:compile (fn [schema _]
(let [properties (mc/properties schema) (let [properties (mc/properties schema)
format (:format properties atime/normal-date)] format (:format properties atime/normal-date)]
(fn [m] (fn [m]
(if (string? m) (if (string? m)
(coerce/to-date-time (atime/parse m format)) (coerce/to-date-time (atime/parse m format))
m))))}} m))))}}
:encoders :encoders
{'inst? {'inst?
{:compile (fn [schema _] {:compile (fn [schema _]
(let [properties (mc/properties schema) (let [properties (mc/properties schema)
format (:format properties atime/normal-date)] format (:format properties atime/normal-date)]
(fn [m] (fn [m]
(cond (cond
(inst? m) (inst? m)
(atime/unparse-local (coerce/to-date-time m) format) (atime/unparse-local (coerce/to-date-time m) format)
(instance? org.joda.time.DateTime m) (instance? org.joda.time.DateTime m)
(atime/unparse-local m format) (atime/unparse-local m format)
:else :else
m))))}}})) m))))}}}))
@@ -244,6 +244,18 @@
m)) m))
m))))}}})) m))))}}}))
(defn ->db-id [m]
(cond
(map? m)
(:db/id m)
(nat-int? m)
m
(and (string? m) (not-empty m))
(Long/parseLong m)
:else
m))
(def pull-transformer (def pull-transformer
(mt2/transformer {:decoders (mt2/transformer {:decoders
{:entity-map {:entity-map
@@ -302,7 +314,7 @@
(get headers "hx-request"))] (get headers "hx-request"))]
(alog/peek ::check {:enabled? is-htmx-that-should-inherit-url-parameters? (alog/peek ::check {:enabled? is-htmx-that-should-inherit-url-parameters?
:params (:query-params request)}) :params (:query-params request)})
(if is-htmx-that-should-inherit-url-parameters? (if is-htmx-that-should-inherit-url-parameters?
(handler (update request :query-params (fn [qp] (handler (update request :query-params (fn [qp]
(->> (concat (:hx-query-params request) qp) (->> (concat (:hx-query-params request) qp)
@@ -311,7 +323,7 @@
(def dissoc-nil-transformer (def dissoc-nil-transformer
(let [e {:map {:compile (fn [schema _] (let [e {:map {:compile (fn [schema _]
(fn [data] (fn [data]
(if (map? data) (if (map? data)
(filter-vals (filter-vals
@@ -580,7 +592,7 @@
(with-precision 2 (with-precision 2
(double (.setScale (bigdec d) 2 java.math.RoundingMode/HALF_UP)))) (double (.setScale (bigdec d) 2 java.math.RoundingMode/HALF_UP))))
(defn wrap-implied-route-param [handler & {:as route-params}] (defn wrap-implied-route-param [handler & {:as route-params}]
(fn [request] (fn [request]
(handler (update-in request [:route-params] merge route-params)))) (handler (update-in request [:route-params] merge route-params))))

View File

@@ -6,6 +6,7 @@
"/new" {:get ::new-wizard "/new" {:get ::new-wizard
:post ::new-invoice-submit :post ::new-invoice-submit
"/due-date" ::due-date "/due-date" ::due-date
"/scheduled-payment-date" ::scheduled-payment-date
"/navigate" ::new-wizard-navigate "/navigate" ::new-wizard-navigate
"/account/new" ::new-wizard-new-account "/account/new" ::new-wizard-new-account
"/account/location-select" ::location-select} "/account/location-select" ::location-select}