# Business rules — myPass-registration

Evidence date: 2026-10-09. Source: `master` at `e1a887a` (2023-01-18).

Rules below are what the registration page enforces. The Footprint API may enforce more. Those server rules are **Unknown** unless the client checks the response.

Each rule is labelled **Implemented**, **Inferred**, or **Documented**. Inferred means the code behaves this way and the repository never states it as policy.

## BR-001 — One event per hostname

**Implemented.**

The event is the `eventID` returned for the first label of `location.hostname`. The page does not offer an event picker.

If styling fails or omits `eventID`, the event is `87` (`domains.demo`).

The other event ids in `domains.js` are not applied: `sarugby` 209, `9hour` 346, `local` 87, `rwcsevens` 367, `formulae` 392, `rwcsevensmedia` 388, `default_old` 87.

## BR-002 — The form is public

**Implemented.**

There is no login, role, or session check on the registration page. Submitting does not send an auth header or cookie (`credentials` are commented out).

## BR-003 — Steps stay locked until the current step is filled

**Implemented.**

Tab labels ignore clicks. NEXT on Personal and Identity is hidden until every visible `input`, `textarea`, and `select` in that step has a non-empty value.

Exceptions on that gate:

- Address line 2 may be empty.
- Organisation reference may be empty (the organisation name may not).
- An element with `style.display == 'none'` is skipped.

The gate does not use the theme's email, name, or phone patterns. A non-empty email of any shape passes.

**Inferred:** the placeholder nationality `default` passes this gate, and the preselected date 1 January 1990 passes it, so neither has to be changed.

## BR-004 — Address and city follow the event

**Implemented.**

`RegistrationFields.Address` is assigned to the CSS display of the address block, both address lines, and the postal code. `RegistrationFields.City` is assigned to the city block, town/city, and state/province.

**Requires Confirmation:** which strings the API sends. The client does not translate `1`/`0` into `block`/`none`.

## BR-005 — Nationality drives country, calling code, and ethnicity

**Implemented.**

On nationality change:

- Country becomes the first option whose label contains the nationality text.
- Calling code becomes the first option whose label contains the nationality text.
- Ethnicity is shown and enabled only when nationality is exactly `South Africa`. Otherwise the row is hidden and left disabled.

Ethnicity options: `African_Black` (selected by default), `White`, `Indian`, `Coloured`.

**Implemented:** submit always sends the select's value, including when the row is hidden. A non-South African application can therefore carry `African_Black` without the applicant choosing it.

**Inferred:** matching uses `option.text.includes(nationality)`, so a short nationality can match the wrong country if one label contains another. No test covers this.

## BR-006 — Gender is posted as M or F

**Implemented.**

Radios: Male `1` (checked by default), Female `0`, Prefer not to say `2`.

The payload does not send those numbers. It sends `F` when Female is checked and `M` otherwise. Prefer not to say is `M`.

**Requires Confirmation:** that the API wants `M`/`F` and that Prefer not to say should be stored as male.

## BR-007 — Date of birth is three selects

**Implemented.**

Posted as `{day}-{month}-{year}` using the option values (`01`–`31`, `01`–`12`, year). There is no check that the day exists in the month. Years run from 1930 to the current year. Default selection is 1 January 1990.

## BR-008 — Mobile number

**Implemented.**

Calling code and local number are separate fields. A tick is shown when the local number length is greater than 7. Submit removes leading zeros from the local number only. The number is not checked to be numeric before post.

## BR-009 — Pass name is derived, then editable

**Implemented.**

First name copies into Pass Name. Each surname change appends `' ' + surname` to whatever is already in Pass Name. Later edits to first name do not remove a surname already appended.

## BR-010 — Organisation reference

**Implemented,** when the event does not set `RequireOrganisationReference` to `"0"`.

- The reference is uppercased for lookup and for submit.
- Lookup runs automatically when the field length is exactly 6, and on demand from the search control or the change event.
- The name shown is `TradingName` of the first returned row. The name field stays disabled.
- If that row's `LimitReached` is `1`, the applicant cannot continue. The page tells them to contact their organisation team lead.

**Implemented,** when the flag is `"0"`: no reference field; the applicant types the organisation name.

**Requires Confirmation:** the real reference length. Auto-search uses 6. A comment on the fetch URL shows a 12-character example.

**Inferred:** a failed or empty lookup does not show an error. An empty disabled name then fails BR-003, so the applicant cannot reach the next step.

## BR-011 — Registration limit is terminal for this visit

**Implemented.**

Limit reached hides `#tab-group-2` and shows the limit panel. The only continuation on that panel is CLOSE, which reloads the page. The client does not submit an application after the flag.

## BR-012 — Category filters role

**Implemented.**

Roles whose `ACID` equals the selected category are listed. If the category value is `0`, every role is listed. The client does not check that a role was deliberately chosen; it selects from whatever options the payload contained.

## BR-013 — Role description is required

**Implemented.**

The textarea is inside the Identity gate, so an empty description blocks NEXT. The on-page instruction is: "Describe your function or responsibility at the event. Please include your area of work or if you require any additional access and the reason for it."

The different sentence in `domains.js` (race names such as SGT Championship, BMW Club, V8 Supercars, VW Polo Cup, GTC Supa Cup) is not shown.

## BR-014 — A photo file is required to leave Identity

**Implemented.**

The file input is part of the non-empty gate, so NEXT stays disabled until a file is chosen. Accepted types are `image/*`. The cropped or original data URL is sent as `profileImage` when that assignment has run. The browser file path string is sent as `fileUpload`.

**Inferred:** leaving Identity or moving to Submit also calls crop if the applicant has not cropped yet. That call needs Cropme to have been started with EDIT PHOTO. Without that, the step change throws and does not finish.

## BR-015 — Media events need at least one linked event

**Implemented.**

If `MediaEvent` is not the string `"0"`, the Events step is shown and NEXT requires one or more checked boxes. Posted ids are the checkbox values (`LID`).

If `MediaEvent` is `"0"`, the Events step is hidden. The code attempts to skip it and does so incompletely (`conclick` instead of `onclick`). See FR-007 in `prd.md`.

**Inferred:** "media" is the name of the flag, not a documented product rule. What `0` means to the business is **Requires Confirmation**.

## BR-016 — A closed event cannot be submitted

**Implemented.**

Compare the close date and today as local calendar dates. If the close date is before today or is today, hide the details and show "Registration for this event is now closed." Submit is not available because the details block is hidden.

**Inferred:** equality counts as closed, so the close date itself is not an open day. The clock is the browser's, not `Africa/Johannesburg` or any zone named in this repo.

**Inferred:** an unparseable `CloseDate` does not close the form.

## BR-017 — Terms must be accepted

**Implemented.**

SUBMIT is enabled only when `#tandcCheck` is checked. The terms text is the event's `TermsAndConditions` HTML, not a document stored in this repository.

The payload sends `tandcCheck` as `1` or `0`. The client does not send a terms version or timestamp.

## BR-018 — Covid declaration is not collected, and the flag is still yes

**Implemented.**

The checkbox is not on the page (removed 2023-01-18). The comment that required both Covid and terms is inactive. Submit sets `covidCheck` to `1` unconditionally.

**Requires Confirmation:** whether new applications should still be recorded as a positive Covid declaration.

## BR-019 — Confirmation contact address is fixed

**Implemented** in the client. **Documented** in the success sentence.

The panel always prints `info@mypass.zone`. It does not print `json.EMAIL`. Commented code mentions `accreditations@sarugbyevents.co.za`.

The sentence "We've sent you a confirmation email" is copy. This repository does not send mail.

## BR-020 — Success ends the form

**Implemented.**

A JSON response hides the form and shows the reference. CLOSE wipes text inputs and reloads, which starts W1 again. There is no edit-after-submit flow.

A failed submit leaves the loader visible and does not restore SUBMIT. **Implemented.**

## BR-021 — No client-side duplicate check

**Implemented** as absence.

The page does not check whether the ID number, email, or passport was already submitted. Any such rule would be on the API. **Unknown.**

## BR-022 — Cached page can ignore a new deploy until the cache name changes

**Implemented.**

The service worker serves cached `index.html` when it has one. The cache name is `Cache-Version-v1.309`. HTML meta tags request no cache; the worker does not read them.

## Rules that are not enforced here

| Expected by a reader of the labels | What the code does |
|---|---|
| Email must look like an email | Icon only (`custom.js`). NEXT allows any non-empty string |
| ID / passport has a format | Any non-empty string |
| Prefer not to say is its own gender | Posted as `M` |
| Covid box must be ticked | Box removed; flag forced to `1` |
| Organisation reference is 12 hex characters | Auto-search at 6 characters; no pattern check |
| Terms "I have read" means the modal was opened | Checkbox alone enables SUBMIT |
| Phone on the identity step | Field removed |
| Domain list in `domains.js` selects the event | Only the demo fallback is used |
