fix(ssr): only require account coding for manual transaction edits
Account coding lived in the always-applied base map of edit-form-schema, so every action (including the link/apply-rule/unlink actions) required a valid transaction-account/account. The edit modal always submits the Manual tab's (usually blank) account row, so link submits failed validation before reaching their save-handler and silently no-op'd. Move account validation into the :manual branch of the action :multi so link actions validate without it. Also surface whole-form validation errors in the wizard footer error bar: default-step-footer only handled top-level/sequential error shapes, so nested field-error maps (e.g. a hidden tab's account error) produced an empty bar and a silent failure. Add flatten-form-errors to flatten the humanized error tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -138,6 +138,33 @@
|
|||||||
[:div.space-y-1 {}
|
[:div.space-y-1 {}
|
||||||
children])
|
children])
|
||||||
|
|
||||||
|
(defn flatten-form-errors
|
||||||
|
"Walks a malli-humanized error structure and returns a flat sequence of
|
||||||
|
human-readable strings, prefixing each leaf message with the nearest
|
||||||
|
field name for context. Lets the footer's error bar surface every
|
||||||
|
validation error for the whole form, even ones whose field lives on a
|
||||||
|
hidden step/tab and so would otherwise be invisible."
|
||||||
|
([errors] (flatten-form-errors nil errors))
|
||||||
|
([field errors]
|
||||||
|
(let [label (cond (keyword? field) (name field)
|
||||||
|
(string? field) field
|
||||||
|
:else nil)
|
||||||
|
decorate (fn [msg] (if label (str label ": " msg) msg))]
|
||||||
|
(cond
|
||||||
|
(map? errors)
|
||||||
|
(mapcat (fn [[k v]] (flatten-form-errors k v)) errors)
|
||||||
|
|
||||||
|
(and (sequential? errors) (every? string? errors))
|
||||||
|
(map decorate errors)
|
||||||
|
|
||||||
|
(sequential? errors)
|
||||||
|
(mapcat #(flatten-form-errors field %) errors)
|
||||||
|
|
||||||
|
(string? errors)
|
||||||
|
[(decorate errors)]
|
||||||
|
|
||||||
|
:else nil))))
|
||||||
|
|
||||||
(defn default-step-footer [linear-wizard step & {:keys [validation-route
|
(defn default-step-footer [linear-wizard step & {:keys [validation-route
|
||||||
discard-button
|
discard-button
|
||||||
next-button
|
next-button
|
||||||
@@ -146,7 +173,8 @@
|
|||||||
[:div.flex.items-baseline.gap-x-4
|
[:div.flex.items-baseline.gap-x-4
|
||||||
(let [step-errors (:step-params fc/*form-errors*)]
|
(let [step-errors (:step-params fc/*form-errors*)]
|
||||||
(com/form-errors {:errors (or (:errors step-errors)
|
(com/form-errors {:errors (or (:errors step-errors)
|
||||||
(when (sequential? step-errors) step-errors))}))
|
(when (sequential? step-errors) step-errors)
|
||||||
|
(seq (distinct (flatten-form-errors step-errors))))}))
|
||||||
(when (not= (first (steps linear-wizard))
|
(when (not= (first (steps linear-wizard))
|
||||||
(step-key step))
|
(step-key step))
|
||||||
(when validation-route
|
(when validation-route
|
||||||
|
|||||||
@@ -72,6 +72,27 @@
|
|||||||
(or (not= approval-status :transaction-approval-status/approved)
|
(or (not= approval-status :transaction-approval-status/approved)
|
||||||
(seq accounts)))]])
|
(seq accounts)))]])
|
||||||
|
|
||||||
|
(def account-coding-schema
|
||||||
|
"Validation for manually-coded transaction account rows. Applied only for
|
||||||
|
the :manual action: link / apply-rule actions build their own accounts
|
||||||
|
server-side, so the (often blank) account row carried along by the manual
|
||||||
|
tab must not be required when one of those actions is submitted."
|
||||||
|
[:maybe
|
||||||
|
[:vector {:coerce? true}
|
||||||
|
[:and
|
||||||
|
[:map
|
||||||
|
[:db/id {:optional true} [:maybe [:or temp-id entity-id]]]
|
||||||
|
[:transaction-account/account [:and entity-id
|
||||||
|
[:fn {:error/message "Not an allowed account."}
|
||||||
|
#(check-allowance % :account/default-allowance)]]]
|
||||||
|
[:transaction-account/location :string]
|
||||||
|
[:transaction-account/amount :double]]
|
||||||
|
[:fn {:error/fn (fn [r x] (:type r))
|
||||||
|
:error/path [:transaction-account/location]}
|
||||||
|
(fn [iea]
|
||||||
|
(check-location-belongs (:transaction-account/location iea)
|
||||||
|
(:transaction-account/account iea)))]]]])
|
||||||
|
|
||||||
(def edit-form-schema
|
(def edit-form-schema
|
||||||
(mc/schema
|
(mc/schema
|
||||||
[:and
|
[:and
|
||||||
@@ -81,23 +102,7 @@
|
|||||||
[:transaction/memo {:optional true} [:maybe [:string {:decode/string strip}]]]
|
[:transaction/memo {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||||
[:transaction/vendor {:optional true} [:maybe entity-id]]
|
[:transaction/vendor {:optional true} [:maybe entity-id]]
|
||||||
[:transaction/approval-status {:optional true} [:maybe (ref->enum-schema "transaction-approval-status")]]
|
[:transaction/approval-status {:optional true} [:maybe (ref->enum-schema "transaction-approval-status")]]
|
||||||
[:amount-mode {:optional true} [:maybe [:enum "$" "%"]]]
|
[:amount-mode {:optional true} [:maybe [:enum "$" "%"]]]]
|
||||||
[:transaction/accounts {:optional true}
|
|
||||||
[:maybe
|
|
||||||
[:vector {:coerce? true}
|
|
||||||
[:and
|
|
||||||
[:map
|
|
||||||
[:db/id {:optional true} [:maybe [:or temp-id entity-id]]]
|
|
||||||
[:transaction-account/account [:and entity-id
|
|
||||||
[:fn {:error/message "Not an allowed account."}
|
|
||||||
#(check-allowance % :account/default-allowance)]]]
|
|
||||||
[:transaction-account/location :string]
|
|
||||||
[:transaction-account/amount :double]]
|
|
||||||
[:fn {:error/fn (fn [r x] (:type r))
|
|
||||||
:error/path [:transaction-account/location]}
|
|
||||||
(fn [iea]
|
|
||||||
(check-location-belongs (:transaction-account/location iea)
|
|
||||||
(:transaction-account/account iea)))]]]]]]
|
|
||||||
[:multi {:dispatch :action}
|
[:multi {:dispatch :action}
|
||||||
[:apply-rule [:map
|
[:apply-rule [:map
|
||||||
[:rule-id {:optional true} [:maybe entity-id]]]]
|
[:rule-id {:optional true} [:maybe entity-id]]]]
|
||||||
@@ -110,7 +115,8 @@
|
|||||||
[:autopay-invoice-ids {:decode/string (fn [x] (edn/read-string x))} [:vector {:coerce? true} entity-id]]]]
|
[:autopay-invoice-ids {:decode/string (fn [x] (edn/read-string x))} [:vector {:coerce? true} entity-id]]]]
|
||||||
[:link-payment [:map
|
[:link-payment [:map
|
||||||
[:payment-id entity-id]]]
|
[:payment-id entity-id]]]
|
||||||
[:manual (require-approval [:map])]]]))
|
[:manual (require-approval [:map
|
||||||
|
[:transaction/accounts {:optional true} account-coding-schema]])]]]))
|
||||||
|
|
||||||
(defn clientize-vendor [{:vendor/keys [terms-overrides automatically-paid-when-due default-account account-overrides] :as vendor} client-id]
|
(defn clientize-vendor [{:vendor/keys [terms-overrides automatically-paid-when-due default-account account-overrides] :as vendor} client-id]
|
||||||
(if (nil? vendor)
|
(if (nil? vendor)
|
||||||
|
|||||||
Reference in New Issue
Block a user