# Business rules — bluecard.footprintapp.net

Evidence date: 2026-10-19. Source: `origin/prod` at `aa2cc42` unless noted.

Provenance: **Documented** (copy in the UI or email), **Implemented** (enforced or performed by code), **Inferred**.

No business-rules document existed in the source repository. Rules below are what the code and its on-screen text do. They are not a new policy.

Where the code does not enforce a rule that the label suggests, the gap is stated.

## Product rules visible in copy

### BR-001 — What a submission is

**Documented** in the consent step. The text says the reporter gives informed consent for SA Rugby and nominated research partners to use, process, and store the personal information and the Blue Card head/concussion data for injury records and research, including Currie Cup and other amateur, club, school, and provincial activity associated with SA Rugby. It says analysed or published information will be depersonalised and anonymous.

**Implemented:** the only action that stands in for that consent is pressing "Agree & Submit". No consent field is stored by the controller.

**Unknown:** whether SA Rugby treats that button as sufficient consent, and whether research partners receive a feed from this application. No export or partner API is in the prod module.

### BR-002 — Who the programme speaks as

**Documented.** Emails and the thank-you panel are signed "The BokSmart Team". The email says not to reply. The privacy sentence says SARU processes the personal information under the SARU privacy policy linked from the message.

**Implemented** as fixed HTML in `IndexController`.

### BR-003 — Clinical guidance offered

**Documented** in the email and the thank-you panel. The reader is told to use an age-appropriate stand-down, medical clearance, and the Graduated Return to Sport process, and is given Sports Concussion SA phone numbers, website, and mailbox, plus BokSmart concussion links.

**Implemented** as copy and as file attachments. The application does not calculate a stand-down period, record clearance, or track return to play. The Excel register is a file the recipient can use. **Inferred** from the attachment and the absence of any clearance workflow.

## Who may submit

### BR-004 — No account is required

**Implemented.** The form, `/index/submit`, and `/index/test` do not check a logged-in user.

### BR-005 — Designations

**Implemented.** The designation list is fixed: Referee, Coach, Team Manager, Physiotherapist, Medical Doctor, Parent, Guardian, Player. The server does not reject any other posted value. A client can post a role that is not in the list.

### BR-006 — Player versus someone else

**Implemented.**

- Role `Player`: the separate player step is removed, the reporter's own player fields are shown, and `player_contant` is used. The applicant is not also pre-added to CC.
- Any other role: the player step stays, the reporter's player-area fields are hidden and cleared when switching away from Player, `contant` is used, and the applicant email is added to CC as well as used as To.

**Inferred.** "Player" is compared with a case-sensitive string. The select options use that exact spelling, so the browser path matches. A posted value with different case takes the non-player branch.

### BR-007 — BokSmart number

**Implemented** in the browser only. The field is shown for Referee and Coach and cleared otherwise. It is not required. The server does not check the format or look the number up.

## What must be entered

### BR-008 — Client required fields

**Implemented** by class `required` and jQuery Validate while moving forward:

- Injury date, hour, minute, venue, cause, injured team, opposing team.
- Reporter name, surname, email, mobile.
- When role is Player: ID field, age, and the player-area selects (position, gender, ethnicity are always given a first option, so a select is rarely empty).
- On the other-player step: player name, surname, email, mobile, ID, age, school/club, town/city, province. Position, gender, and ethnicity again have a default option.

Not required on the client, even where the label has an asterisk or the copy implies they matter:

- Provincial union (`UnionName`).
- BokSmart number.
- Contact role, name, and email.

### BR-009 — Finish does not re-validate

**Implemented.** The steps `onFinishing` handler does not return the result of `form.valid()`. The first finish click posts the serialised form. Hidden fields are ignored only while changing steps (`ignore = ":disabled,:hidden"`). That setting is not what gates the post.

### BR-010 — Server validation is the empty injury date

**Implemented.** The only normalisation in `submitAction` is: missing or empty `InjuryDate` becomes the current date `d/m/Y`.

Not enforced on the server:

- Required presence of name, email, teams, venue, or cause.
- Email format.
- Hour 0–23 or minute 0–59. The browser limits those inputs to two digits.
- Age as a number or a range. The browser limits age to two digits.
- Union chosen from the list loaded for the page.
- Contact count of four.
- Uniqueness of the reference number.

### BR-011 — Time and date shape

**Documented** under the time inputs: enter 24-hour time, example 17:15. Minute defaults to `00`.

**Implemented.** Datepicker format is `dd/mm/yyyy`. A separate `$("#datePicker")` initialiser uses `yy-mm-dd` and no element has that id. The timepicker initialiser targets `#TimeOfIncident`, which is not an input id (it is the label's `for`). The visible time controls are two text inputs. **Inferred:** the datepicker on `.input-group.date` is the one that affects the date field; the other two initialisers do not attach to the visible time fields.

## Who is notified

### BR-012 — Recipients

**Implemented.**

1. To: `ApplicantEmail`, when it is non-empty, with the CC list on that one message.
2. CC candidates: for a non-player, the applicant email again; then every non-empty BokSmart rep email for the union; then every non-empty contact email.
3. If `ApplicantEmail` is empty, each CC address is sent its own message and those messages have no CC list.
4. If both the applicant email and the CC list are empty, no mail is sent. The insert still runs.

`PlayerEmail` is not a recipient by itself.

### BR-013 — Which union contacts

**Implemented.** `profile` rows with `BoksmartRep = 1` whose `UnionID` equals the `un_id` of `ef_unions` where `un_name` equals the posted union name, `un_type = 3`, and `main = 1`.

A commented query would also have included sibling unions under the same parent and `un_type` 3 or 12. That query is not active.

The union name is interpolated into SQL. A name that changes the statement changes the lookup. This is **Implemented** behaviour and a defect to review. No exploit string is recorded here.

Empty union name still runs the lookup with an empty name. The dropdown is allowed to post empty (BR-008).

### BR-014 — Message content rules

**Implemented.**

- Subject is always "Bluecard Notification".
- From is always `bluecard@sarugby.co.za`.
- Date line is `d F Y` at send time, not the injury date.
- Reference printed in the mail is the posted `RefNumber`, which started as the page's generated value and can be changed by the client before post.
- Player-role template is `player_contant`. The call passes the player's full name as the greeting and passes an empty string as the name substituted for "[the logged Blue Card player]".
- Non-player template uses the applicant full name as the greeting and `PlayerName` + `PlayerSurname` as the player name.
- Gender "she" when `PlayerGender` is `Female`, otherwise "he". The current template has no `[he/she]` token, so the value is unused.

### BR-015 — Documents that go with the mail

**Implemented.** Submit always attempts the seven attachments listed in the PRD (research consent through prevention). `testAction` omits the research-consent file. The thank-you links use the parallel filenames under `bluecarddownload/` and the zipped register, not the attachment filenames.

### BR-016 — Failure does not block the other side

**Implemented.** A mail exception is printed and swallowed. The insert runs anyway. An insert exception is printed and the action still exits. The browser shows the thank-you panel whenever the HTTP call succeeds, including when the body is a printed exception.

There is no retry queue.

## What is stored

### BR-017 — One row per submit request

**Implemented.** `insertData` inserts the posted `data` array into `BlueCard_Consents`. `Contact` is replaced with a JSON string first.

There is no duplicate check on reference number, player, or injury date. Two submits create two rows if both inserts succeed.

### BR-018 — Column contract is the table, not the form

**Inferred.** `Zend_Db_Table::insert` writes keys that match columns. Extra keys raise an exception, which is printed. The form posts the October 2022 school, town, and province fields. The repository has no `CREATE TABLE`. Whether those keys are columns is **Unknown / Requires Confirmation**.

### BR-019 — Reference number

**Implemented.** Four random bytes, hex-encoded, uppercased. Not stored ahead of submit. Not reserved. The thank-you panel quotes the number from the HTML that was rendered, even if a different number was posted.

### BR-020 — Ethnicity list

**Implemented.** Options are `EthnicityName` from rows with `EthCountry = 'South Africa'`, alphabetical. The server does not check that the posted ethnicity is one of those names.

### BR-021 — Unions listed

**Implemented.** `un_name` where `un_type = 3` and `main = 1`. No further filter (for example active or rugby-only) is in this query. What `un_type` 3 and `main` 1 mean in the union table is **Unknown**. **Inferred** from the query that these flags are the provincial unions the form is meant to show, because the label says "Provincial Rugby Union".

## Access and abuse rules

### BR-022 — Test route sends mail

**Implemented.** `/index/test` sends a real SMTP message with attachments to an address written in the source, using sample names, and does not insert a consent row. There is no environment check.

### BR-023 — Secrets in source

**Implemented.** Database username and password are in `application/configs/application.ini`. SMTP username and password are in `sendMail`. `.gitignore` names `application/configs/application.ini`, and the file is still in the prod tree. This document does not copy the secrets.

Rotation is not a rule the application enforces. It is a review item.

### BR-024 — Errors are shown to the browser

**Implemented.** `public/index.php` enables `display_errors`. Mail and database exceptions are `print_r`'d into the response. `application.ini` says the opposite (`display_errors = 0`, `displayExceptions = 0`) and loses to the front controller.

## Rules that are not in this application

- No stand-down calculator, medical clearance, or return-to-play tracker.
- No reviewer approval before the row is stored.
- No role permission beyond the fields the browser shows.
- No rate limit, captcha, or CSRF token was found.
- No deletion, correction, or export screen for a person named in a submission.
- No rule that prod and qa use different databases beyond the host string. Both ini files name the database `teamwizz_SARU`. Whether the two hosts hold different data is **Unknown**.
