• Mahmoud Aglan's avatar
    feat(registration): ask a product's own questions before selling it, and hold names to رباعي · d3113b08
    Mahmoud Aglan authored
    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: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    d3113b08
ParticipantForm.php 17.6 KB