fix(ssr): repair New Invoice + Transaction Edit 500s and broken modal sizes

Three regressions from the SSR rendering-modernization migration, all verified
live via agent-browser:

- BUG A — New Invoice: choosing a client 500'd from /invoice/new/due-date
  (ClassCastException: DateTime cannot be cast to java.util.Date). `due-date`
  and `scheduled-payment-date` called `coerce/from-date` on values already
  decoded to clj-time DateTimes. Drop the coerce; use the decoded dates.

- BUG C — Transaction Edit: any whole-form swap (mode toggle, vendor change,
  add/remove row) 500'd whenever the txn had >=1 autopay-invoice match
  (ClassCastException at links-body*: PersistentVector cannot be cast to Named).
  The autopay link-panel's hidden `action` input was missing `:form ""`, so it
  serialized alongside the main `action` hidden, producing a duplicate param
  that Ring collapsed to a vector. Add `:form ""` to match the unpaid/rule panels.

- Modal sizes: Vendor/Client/Invoice-Pay modals ballooned to full width because
  resources/public/output.css was missing their arbitrary Tailwind size classes.
  Root cause: tailwind.config.js `content` never scanned resources/templates/**/*.html
  (46 Selmer templates the migration introduced), so a rebuild also dropped
  template-only classes like md:w-[950px]. Add the templates glob and rebuild;
  all modal size classes now present, no working modal regressed.

Docs: add 2026-06-27 QA findings + resumable fix task list; cross-link from the
migration plan. Remaining (per the new plan): Vendor/Client inner step-body
overflow, wizard step animations, bulk-edit empty-selection 500, footer EDN leak.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 13:11:09 -07:00
parent 638a75925c
commit 74f1a49a10
6 changed files with 4928 additions and 25 deletions

View File

@@ -97,7 +97,7 @@
[:fn {:error/message "Not an allowed account."}
#(check-allowance % :account/invoice-allowance)]]]
[:invoice-expense-account/location :string]
[:invoice-expense-account/amount :double]]
[:invoice-expense-account/amount :double]]
[:fn {:error/fn (fn [r x] (:type r))
:error/path [:invoice-expense-account/location]}
(fn [iea]
@@ -113,13 +113,13 @@
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 (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
@@ -147,24 +147,24 @@
:else
[["Shared" "Shared"]]))]
(com/select {:options options
:name name
:name name
:value (or value (ffirst options))
:class "w-full"})))
(defn account-typeahead*
[{:keys [name value client-id x-model]}]
[:div.flex.flex-col
(com/typeahead {:name name
(com/typeahead {:name name
:placeholder "Search..."
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
{:client-id client-id
:purpose "invoice"})
:id name
:x-model x-model
:value value
:content-fn (fn [value]
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})])
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
{:client-id client-id
:purpose "invoice"})
:id name
:x-model x-model
:value value
:content-fn (fn [value]
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})])
(defn assert-no-conflicting [{:invoice/keys [invoice-number client vendor] :db/keys [id]}]
(when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice-number
@@ -184,7 +184,7 @@
(let [base-amount (int (/ shared-amount total-locations))
remainder (- shared-amount (* base-amount total-locations))]
{:base-amount base-amount
:remainder remainder}))
:remainder remainder}))
(defn- spread-expense-account
"Spreads the expense account amount across the given locations"
@@ -600,10 +600,9 @@
(defn due-date [request]
(let [{:invoice/keys [vendor client date due]} (form-vendor-client request)
vendor (clientize-vendor (get-vendor (->db-id vendor)) (->db-id client))
date (some-> date coerce/from-date)
good-date (or (when (and date (:vendor/terms vendor))
(time/plus date (time/days (:vendor/terms vendor))))
(some-> due coerce/from-date))]
due)]
(html-response
(com/date-input {:value (some-> good-date (atime/unparse-local atime/normal-date))
:name "invoice/due"
@@ -616,7 +615,7 @@
(let [{:invoice/keys [vendor client due]} (form-vendor-client request)
vendor (clientize-vendor (get-vendor (->db-id vendor)) (->db-id client))
good-date (when (and due (:vendor/automatically-paid-when-due vendor))
(some-> due coerce/from-date))]
due)]
(html-response
(com/date-input {:value (some-> good-date (atime/unparse-local atime/normal-date))
:name "invoice/scheduled-payment"

View File

@@ -658,7 +658,7 @@
(panel-wrap
(if (seq invoice-matches)
(panel-list* {:heading "Available Autopay Invoices"
:action-hidden (sc/hidden {:name "action" :value "link-autopay-invoices"})
:action-hidden (sc/hidden {:name "action" :value "link-autopay-invoices" :form ""})
:prompt "Select an autopay invoice to apply:"
:radio (sc/radio-card {:options (for [match-group invoice-matches]
{:value (pr-str (map :db/id match-group))