feat(registration): ask a product's own questions before selling it, and hold names to رباعي
Two things the desk could not previously be made to capture.
A product now carries the details it cannot be sold without — a size, a
colour, the name printed on the back. The admin declares them per product
as either free text or a list of predefined answers; the registration
wizard renders them inline on the cart row and refuses to move to payment
while a required one is blank.
None of this is a separate product, and none of it is reconstructable
after the fact from an invoice line that says "قميص تدريب" — which is why
the answers freeze onto invoice_items.metadata and into the line
description at sale time. Renaming "لارج" to "L" next season must not
rewrite what a player ordered last season, exactly as prices freeze.
hotbuyCustomizations is browser-writable by necessity — it is what the
receptionist is typing — so nothing downstream trusts it. Both the step
guard and confirm() re-read the questions through Eloquent (Product
carries BranchScope, so another branch's product resolves to nothing) and
check every answer against ProductCustomization::accepts(). A select must
match its own list: the dropdown is a convenience, not the check.
Separately, a person entered into the system must now carry a four-part
name. Egyptian records are keyed on it — national ID, birth certificate,
federation card, school file — and a player registered as "محمد أحمد"
matches none of them; two players sharing a first and father's name are
common enough that parts three and four are what tell them apart. Applied
where people are created at the desk and in the portal: the registration
wizard (player and guardian), retroactive enrolment, the participant form,
and portal sign-up. Four is a floor, not a target.
FullName splits on Unicode whitespace explicitly. \s under /u still only
means ASCII whitespace, and an Arabic keyboard produces U+00A0 — glued
together, a perfectly valid four-part name would have been rejected.
Deliberately left alone:
- ParticipantImport is exempt. Enforcing the rule on a bulk import would
block loading an existing roster, which is the one case where the short
names are already a fact.
- Records already in the database are untouched. This validates at entry.
- The POS terminal, settlement wizard, group screen and essential-
deliveries screen sell these same products and do not yet ask for the
customizations.
- Kits carry no customizations, only products.
Migration is additive and guarded; CHECK constraint matches the enum
character for character. 382 passed / 99 skipped on the restored Postgres
tenant, 303 passed / 178 skipped on SQLite.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
This diff is collapsed.
This diff is collapsed.
Please register or sign in to comment