feat(billing): migrate New Invoice form to FormGuard (PMS-518) #354

Merged
nrupard merged 2 commits from feat/PMS-518-migrate-billing-form into main 2026-06-25 21:16:18 +02:00
Owner

What

PMS-518 increment: migrates the billing New Invoice form (handle_create) onto the shared FormGuard.

Why

The handler validated field-by-field with first-failure-returns, so a missing Company hid a missing date, which hid a missing description, etc. (the same masking class as PMS-514). Now every required field is validated through guard.field(...) into its own inline slot, all failures surface at once, and the first invalid field is focused.

Changes

  • Company keeps its specific banner message (the CompanyPicker has no inline slot) and blocks via note_invalid.
  • Invoice Date / Due Date / Line Description get per-field inline slots (invoice_date_error / due_date_error / line_description_error) instead of sharing the form-level banner.
  • Quantity / Unit Price use [Rule::Required, Rule::Number{min:0}] - the canonical "must not be negative." / "must be a number." messages replace the bespoke ones; the value is still sent as a string for the server to parse.
  • The cross-field "due date on or after invoice date" check is preserved, runs only when both dates are present, and routes to due_date_error + note_invalid.

Behaviour

  • Submit an empty New Invoice -> Company (banner) + Invoice Date + Due Date + Description + Quantity + Unit Price all flag at once (each inline), first invalid focused, no POST. Previously only the first error showed.

Verification

just check parity via the rust-builder image: clippy --all-targets -- -D warnings, fmt --check, cargo check --target wasm32-unknown-unknown - all green.

Scope: the New Invoice create form. The billing modals (Record Payment, Edit Invoice, Tax Rate, Gateway) are further increments. Part of PMS-518.

## What PMS-518 increment: migrates the billing **New Invoice** form (`handle_create`) onto the shared `FormGuard`. ## Why The handler validated field-by-field with first-failure-`return`s, so a missing Company hid a missing date, which hid a missing description, etc. (the same masking class as PMS-514). Now every required field is validated through `guard.field(...)` into its own inline slot, all failures surface at once, and the first invalid field is focused. ## Changes - **Company** keeps its specific banner message (the `CompanyPicker` has no inline slot) and blocks via `note_invalid`. - **Invoice Date / Due Date / Line Description** get per-field inline slots (`invoice_date_error` / `due_date_error` / `line_description_error`) instead of sharing the form-level banner. - **Quantity / Unit Price** use `[Rule::Required, Rule::Number{min:0}]` - the canonical "must not be negative." / "must be a number." messages replace the bespoke ones; the value is still sent as a string for the server to parse. - The cross-field "due date on or after invoice date" check is preserved, runs only when both dates are present, and routes to `due_date_error` + `note_invalid`. ## Behaviour - Submit an empty New Invoice -> Company (banner) + Invoice Date + Due Date + Description + Quantity + Unit Price all flag at once (each inline), first invalid focused, no POST. Previously only the first error showed. ## Verification `just check` parity via the rust-builder image: `clippy --all-targets -- -D warnings`, `fmt --check`, `cargo check --target wasm32-unknown-unknown` - all green. Scope: the New Invoice create form. The billing modals (Record Payment, Edit Invoice, Tax Rate, Gateway) are further increments. Part of PMS-518.
feat(billing): migrate New Invoice form to FormGuard (PMS-518)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 20m36s
0083a28122
Moves the New Invoice handler (handle_create) onto the shared FormGuard. It previously validated field-by-field with first-failure-returns, so a missing Company hid a missing date, which hid a missing description, etc. Now every required field is validated through guard.field(...) into its own inline error slot, all failures surface at once, and the first invalid field is focused.

- Company keeps its specific banner message (the CompanyPicker has no inline slot) and blocks via note_invalid.
- Invoice Date / Due Date / Line Description get per-field inline slots (invoice_date_error / due_date_error / line_description_error) instead of sharing the form-level banner.
- Quantity / Unit Price use [Rule::Required, Rule::Number{min:0}] - the canonical "must not be negative." / "must be a number." messages replace the bespoke ones; the value is still sent as a string for the server to parse.
- The cross-field "due date on or after invoice date" check is preserved, runs only when both dates are present, and routes to due_date_error + note_invalid.

just check parity (rust-builder image): clippy --all-targets -D warnings, fmt --check, cargo check --target wasm32-unknown-unknown all green.

#PMS-518
feat(billing): migrate the billing modals to FormGuard (PMS-518)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 20m59s
Create release / Create release from merged PR (pull_request) Has been skipped
f770dc01b2
Follows the New Invoice migration on this branch; brings the rest of billing onto the shared guard so every modal reports its failures together with focus-first.

- Record Payment modal: Company (banner + note_invalid), Payment Date (new per-field slot, Rule::Required); Amount and Invoice keep their bespoke Decimal/UUID parses (reused in the body) and add focus-first via note_invalid. Replaces the ok-flag with guard.blocked().
- Edit Invoice modal: Invoice/Due dates get per-field slots; per-line Description (Rule::Required) and Quantity/Unit Price ([Rule::Required, Rule::Number{min:0}]) get per-line err fields on EditableLine; empty-line-set rule routes to the banner + note_invalid.
- Tax Rate modal: Name (Rule::Required) + Rate ([Rule::Required, Rule::Number{min:0}]) get per-field slots, replacing the presence-only banner returns.
- Gateway modal: Config JSON keeps its serde_json parse (reused in the body) and routes its message to a new inline slot + focus-first.

Values sent to the server are unchanged. just check parity (rust-builder image): clippy --all-targets -D warnings, fmt --check, cargo check --target wasm32-unknown-unknown all green.

#PMS-518
nrupard scheduled this pull request to auto merge when all checks succeed 2026-06-25 21:11:50 +02:00
nrupard deleted branch feat/PMS-518-migrate-billing-form 2026-06-25 21:16:19 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!354
No description provided.