# Business rules — Sunmet2020

Evidence date: 2026-10-02.

Source: `git@bitbucket.org:overdrive/sunmet2020.git`, `master` at `10caee0`.

No business-rules document exists in the source repository. Nothing below is a signed policy. Each rule is labelled:

- **Documented** — written in view copy or a commit message.
- **Implemented** — what the code does.
- **Inferred** — a reasonable reading of that behaviour, not stated as policy.

Passwords stored in the source are not copied here.

## Event identity

**BR-001.** The portal title is "The Sun Met (2020) - Accreditation Portal".

- Provenance: **Documented** in `application/layouts/default.phtml`.
- The event row is selected by hard-coded `aCode` `5DA997A51439E` on `/` and on sign-in. **Implemented** in `IndexController::indexAction` and `ApplicationController::loginAction`.
- **Inferred:** that code is the Sun Met 2020 accreditation. **Unknown / Requires Confirmation**, because the name on screen is the database column `AccreditationName`, not the HTML title.

**BR-002.** An unknown accreditation code does not show a form. The response is the text "Sorry this page does not exits".

- Provenance: **Implemented**.

**BR-003.** `/application/apply/code/{code}` may open a different accreditation than the home page.

- Provenance: **Implemented** (`applyAction` reads the URL). Sign-in does not follow that code. **Implemented** limitation. Whether other codes are in scope for this host is **Unknown / Requires Confirmation**.

## Who may apply

**BR-004.** A visitor who is not signed in may submit the wizard.

- Provenance: **Implemented.** No account is created. The reference plus email is the later login.

**BR-005.** A visitor who already has `user_session.AAID > 0` and opens the apply action is sent to their profile instead of the wizard.

- Provenance: **Implemented** (`isloggedIN` at the start of `applyAction`). `IndexController` does not do this.

**BR-006.** The same email should not be used twice for the same accreditation (`AC`).

- Provenance: **Documented** by the browser string "Email already in use,please use different email address."
- Provenance: **Implemented** only in that browser check (`checkEmails`).
- The insert does not reject a duplicate. **Inferred** that uniqueness was intended. **Unknown / Requires Confirmation** whether the server must enforce it.

## What the application must contain

**BR-007.** The browser requires first name, surname, email (email format), mobile, ID or passport number, organisation, category, role, reason for application, terms acceptance, and a headshot unless the selected role has `photo = 1`.

- Provenance: **Implemented** as jQuery Validate classes in `apply.phtml`.
- The server stores the posted array and does not repeat these checks. **Implemented.**

**BR-008.** Reason text. The form says: "Please provide a brief but specific description of your exact roles and responsibilities for this event. Accreditation approval and zone access is dependent on the accuracy of your submission."

- Provenance: **Documented** in `apply.phtml`. No code scores or checks the text.

**BR-009.** Headshot. The form says: "Please supply a JPG headshot with this application, regardless of whether you have applied previously for accreditation... (no larger than 1.5mb)."

- Provenance: **Documented** in `apply.phtml`.
- **Implemented** exception: if the role's `photo` flag is `1`, the image field is not `required`. Meaning of the flag is **Unknown / Requires Confirmation** (the flag's name and the rule move in opposite directions).
- Server does not check MIME, dimensions, or the 1.5mb limit. **Implemented.**

**BR-010.** Terms. The applicant must accept: "I hereby fully accept all the terms and conditions inclusive of SA Rugby’s Tournament and Competitions Accreditations Policy as well as the Safety at Sports and Recreational Events Act of 2009." The modal shows the database field `TermsAndConditions`.

- Provenance: **Documented** in `apply.phtml`. Checkbox is required in the browser only.
- The Act is named, not quoted. The SA Rugby policy text is not in git. **Unknown / Requires Confirmation** for the full legal wording.

**BR-011.** When `AccreditationNote` is non-empty, an extra checkbox shows that note. It is not `required`.

- Provenance: **Implemented.**

## Categories, roles, media, days

**BR-012.** Categories offered on the public form are those in `AccreditationCategory_view` for this accreditation with `Teams = 0` and `hidden = 0`, ordered by category name.

- Provenance: **Implemented** in `getAccreditationCategoryPublic`. Team categories and hidden categories are excluded. **Inferred** that team categories are for a different audience.

**BR-013.** Roles offered are rows in `AccreditationRoles_view` for this accreditation id (`AID`), ordered by role name.

- Provenance: **Implemented** in `getAccreditationRoleLive`. Changing category rebuilds the role list to roles whose `ACID` equals the category id. **Implemented** in `ChangeSelectCat`.

**BR-014.** Media block (producer name, producer email, and day checkboxes):

- PHP shows it when the accreditation `MediaOnly` is `1`, and hides it otherwise.
- After a category change, if `MediaOnly` is `0` and the category value is `1`, the block is shown and `.MediaOnly` inputs become required. Any other category hides it and clears that required class.
- If `MediaOnly` is already `1`, category change does not alter the block.

- Provenance: **Implemented** in `apply.phtml`.
- **Inferred:** category id `1` is a media category used when the whole event is not media-only. **Unknown / Requires Confirmation.**

**BR-015.** Day selection is offered only inside the media block when `DaysDuration > 1`. Days are labelled "Day 1" onward. If every box is checked, `DaysAttending` is the string `All`. Otherwise it is the selected day numbers.

- Provenance: **Implemented** in `countDaysSelected`. If the field is missing, the server stores an empty string.

**BR-016.** A role with `SupplierReg = 1` shows this note: owners, directors, or managers who need the load-in/out schedule before event day must register the company with SA Rugby at `http://supplier.sarugby.online`.

- Provenance: **Documented** and **Implemented** as a notice. Registration is not performed here. The application can still be submitted.

## Close of applications

**BR-017.** Applications close at the end of `CloseDate` (`CloseDate` concatenated with ` 24:00:00`, compared with `time()` in `Africa/Johannesburg`). After that, the closed view says "Accreditation applications is now closed." and points to `ContactName` and `ContactEmail`.

- Provenance: **Implemented** and **Documented** (closed copy).
- The view change does not `return`. A POST still inserts. **Implemented.** **Unknown / Requires Confirmation** whether late posts must be refused.
- An empty `CloseDate` is not handled. **Implemented** gap.

## Reference number and one submission, several events

**BR-018.** The reference is 3 random bytes rendered as uppercase hex (6 characters). It is stored on `AccreditationApplycation.Ref` and becomes the login secret together with email.

- Provenance: **Implemented.** Uniqueness is not checked before insert. Collision handling is **Unknown**.

**BR-019.** Linked accreditations with `DateTo` on or after today may be ticked on the Apply step. The label is the secondary name, the from-date, and the location. The hint says the person may select one or multiple events.

- Provenance: **Documented** and **Implemented.**
- Each ticked id is another insert with the same personal data and the same reference, and `AC` replaced by that id. Photo upload and the confirmation email run once, for the primary `AC` only. **Implemented.**
- **Inferred:** the extra rows are applications to those linked events. Login on this site matches `AC` of the hard-coded event only, so a linked-only row would not sign in here. **Unknown / Requires Confirmation.**

## Status

**BR-020.** Profile status, in order:

| Condition | Label | Fields |
|---|---|---|
| `Pending == 1` | Pending | Editable (`status_locked` stays 0) |
| else `Status == 0` and `DateApply < LastUpdate` | In Review | Editable (the lock line is commented out) |
| else `Status == 1` | Confirmed | Locked |
| `Status == 2` | Declined | Locked |
| `Status == 3` | Approved | Locked |
| else | New Application | Editable |

- Provenance: **Implemented** in `profile.phtml`.
- **Inferred:** Pending overrides the numeric status. In Review means someone has touched the row after the application date (`LastUpdate` later than `DateApply`). Confirmed, Declined, and Approved are terminal on this screen.
- No action in this repository sets status 1 or 3. Attendance cancellation sets status 2. **Implemented.** Who sets Confirmed and Approved is **Unknown / Requires Confirmation** (**Inferred:** the Footprint admin).

**BR-021.** The profile lock is browser `disabled` attributes. `updateAction` does not read status and does not require a session. A POST with an `AAID` updates that row.

- Provenance: **Implemented.** The screen rule and the write rule disagree. **Requires Confirmation** if the lock is meant to be enforced.

**BR-022.** Cancelling attendance (`att=2`) sets `Status` 2, `History` "Declined by Applicant", `DeclinedDate` now, and clears `AttendanceKey`.

- Provenance: **Implemented.** The decline email adds the reason "Applicant confirmed NON-ATTENDANCE / CANCELLATION for this event." **Documented** as that reason string in code, not as a policy file.

**BR-023.** Confirming attendance (`att=1`) does not change `Status`. It stores `AttendanceRequest` 1, the response time, and clears `AttendanceKey`.

- Provenance: **Implemented.**

## Email content rules

**BR-024.** A confirmation email is sent only when the accreditation row's `EmailNotes` is non-empty. Decline and attendance mails use `EmailRejected` and `EmailAttandanceConfirmed` the same way (decline mail is skipped when `EmailRejected` is empty).

- Provenance: **Implemented.**

**BR-025.** Templates may contain `[ACCREDITATIONNAME]`, `[CONTACTNAME]`, `[CONTACTTELEPHONE]`, `[CONTACTEMAIL]`, `[REFNUMBER]`, `[APPLICANTNAME]`, and for a decline `[REASONNOTE]`.

- Provenance: **Implemented.**

**BR-026.** The visible subject is always "The Sun Met Notification". A different subject is built and discarded.

- Provenance: **Implemented.**

## Access and security rules that the code actually applies

**BR-027.** Sign-in succeeds only when `AC`, `Ref`, and `email` all match one application row. The message on failure is "Bad credentials."

- Provenance: **Implemented.** Comparison is exact. There is no rate limit, captcha, or second factor. **Implemented** absence.

**BR-028.** Session namespace `user_session` is the only gate on the profile page. Cookie lifetime in config is 864000 seconds.

- Provenance: **Implemented.**

**BR-029.** There is no role-based staff permission in this application. Administrator, permit, and gate models are unused here.

- Provenance: **Implemented** (absent from the request path).

## Data rules

**BR-030.** New rows set `History` to `None`, `DateApply` to the current Johannesburg timestamp, and copy `TID` and `FID` from the accreditation profile.

- Provenance: **Implemented.**

**BR-031.** `LastUpdate` on profile save uses PHP format `Y-m-d H:s:i`. The `s` token is seconds, placed where minutes usually are.

- Provenance: **Implemented.** Effect on the In Review comparison (`DateApply < LastUpdate`) is **Unknown** when the stored string is not a real clock time.

**BR-032.** Public categories exclude `Teams != 0` and `hidden != 0`. Other flags on roles (`photo`, `SupplierReg`) change the form only.

- Provenance: **Implemented.**

## Removed rule

**BR-033.** Media applicants were asked which agency invited them: Openfield (`Invitefrom` 1) or Roth Media (`Invitefrom` 2), and that question was required when `MediaOnly == 1`.

- Provenance: **Documented** as removed by commit `cf9251d` ("Remove Openfield / Roth media from Sunmet media", 2020-01-13).
- The live wizard does not post `Invitefrom`. A comment in `index/index.phtml` still contains the old radios. **Implemented.**

## Rules not established

- Quota, `AssignedNumbers`, or `AvailableNumbers`. `getLine` reads those columns. No controller calls it.
- Fees, payments, or invoices. None in the controllers or views.
- Age, ID checksum, or passport format. The field is a required text input.
- Approval criteria beyond the reason-for-application sentence.
- How `AttendanceKey`, zones, and services are assigned.
- Retention of photos and applications.
- Whether the Safety at Sports and Recreational Events Act citation is still the required legal wording for this event.
