# Business rules — checkin.mypass.zone

Evidence date: 2026-10-19. Source: `master` at `09abf7a`, compared with `prod` at `a78d45a` and `ui-changes` at `be0f89bb`.

Rules are taken from code or from email text. Where the email states something the code does not enforce, the rule is marked **Documented**.

No separate business-rules document existed in the repository.

Secret values (PIN, database password, SMTP password, the identity number in `IndexController::testAction`) are not copied here.

## Access

### BR-001 — Shared PIN opens one event

**Implemented.** `LoginController::indexAction` compares the posted password to one hardcoded value. A match sets session `EVENT.EID` and sends the browser to `/`. Any other value is rejected with "Bad entered credentials".

The event id is `245` on `master`, `367` on `ui-changes`, and `479` on `prod`. Comments on `ui-changes` and `prod` call `245` DEV and call their own id PROD.

There is no per-person account on this path, no expiry of the PIN, and no record of who logged in.

### BR-002 — Collection pages require the event session

**Implemented.** `IndexController` and `PermitsController` redirect to `/login/` when `EVENT.EID` is missing.

`LoginController` actions, including image upload and company picture, do not apply this rule.

### BR-003 — Company and staff areas require an authenticated identity

**Implemented.** `UsersController` and `CompanyController` redirect to `/login/` when `Zend_Auth` has no identity. Permit create, save, duplicate, and cancel read `identity->MID` and `identity->CID`.

**Inferred.** The PIN login never writes that identity, so BR-001 does not satisfy BR-003. `LoginNow` would, and would set event id `22`, but the login form does not call it.

### BR-004 — Logout ends the session

**Implemented.** `/login/logout` clears auth storage and destroys the PHP session.

### BR-005 — The shell's event name is not the session event

**Implemented.** `default.phtml` prints `Kyalami 9 Hour 2020` on `master` and `HSBC Cape Town 7s` on `prod`. It does not query the event id from BR-001.

**Requires confirmation.** Which string, if either, is the business name of the event operators should see.

### BR-006 — The signed-in label is always Administrator

**Implemented.** The layout user menu says `Administrator` as literal text. It is not a role check.

## Collection

### BR-007 — The list is one accreditation id

**Implemented.** `getApplications` filters `Accreditation_Collections.AC` to the session event id. Search does not remove that filter.

### BR-008 — Ready to collect and collected

**Implemented** in the list view.

- Status `7` is shown as Ready To Collect and is the only status with a Collect button.
- Status `8` is shown as Collected and increments the company collected count.
- Every other status is shown as Pending and cannot be collected from the button.

**Unknown.** Which system sets status `7`. This repository's collect action writes the status it is given; the button sends `8`.

### BR-009 — Collect records status and two timestamps

**Implemented.** `collectAction` sets `Status` to the request `status`, and sets `LastUpdate` and `Collected` to the current timestamp (`Y-m-d H:i:s`, timezone `Africa/Johannesburg`).

The action does not require the previous status to be `7`. It does not require the service row to belong to the session event. **Inferred** from the SQL: any `AASID` accepted by the database can be updated by a browser that has the event session.

### BR-010 — Companies are groups of rows that share `ID`

**Implemented.** PHP groups `Accreditation_Collections` rows by `ID`. The label is `organisation`. The total is the number of people in the group. The collected total is the count of those people whose `ServiceStatus` is `8`.

The code also assigns group `Status` from `organisation`, and the company table does not display that field.

**Unknown / Requires Confirmation.** What `ID` means in `Accreditation_Collections`. The object is not defined in this repository.

### BR-011 — Search

**Implemented.** A non-empty search matches, with the text wrapped for `LIKE`: first name, surname, full name, organisation, ID number, role, barcode. Reference matches only when equal.

An empty search returns every row for the session event. There is no paging in the query.

### BR-012 — The first company starts open

**Implemented.** The fragment shows the first group's people and hides later groups until the operator expands them.

## Registration and people

These rules are in the code. They are not used by the PIN collection flow.

### BR-013 — One company registration number, one person id or email

**Implemented.** Registration stops if `Companies_Profile.RegistrationNumber` exists, or if `Company_Members` already has the same `IDNumber` or `Email`. The user-facing text for both cases is that the account already exists and to contact SARU.

### BR-014 — South African ID type

**Implemented.** `IDType` is `1` when `Country` is exactly `SOUTH AFRICA`. Any other country string, including different capitalisation, sets `IDType` to `2`.

### BR-015 — New registrant is an administrator

**Implemented.** `Rol` is set to `Administrator`. `AllowLogin` is not set in `newcompanyAction`. Email confirmation sets `AllowLogin` to `1`.

### BR-016 — New company is active and tied to event 44

**Implemented.** `Companies_Profile.Active` is `1`. `Company_Events` is inserted with `EID = 44` and `Status = 2`.

That event id is not the collection event on `master` (`245`), `ui-changes` (`367`), or `prod` (`479`).

### BR-017 — Email confirmation is required before the mail says the account is active

**Documented** in the registration email: an administrator reviews the company, activation can take up to 48 hours, and the person must click the link.

**Implemented** on this host's `validateemailAction`: a matching `Validate_Key` clears the key and sets `Email_Confirmed`, `Email_Confirmed_Date`, and `AllowLogin`. There is no 48-hour timer and no administrator approval action in this repository.

The link in the email points at `supplier.sarugby.online`, not at this host.

### BR-018 — Staff password is generated when login is allowed

**Implemented** on `/users/add`. If `AllowLogin` is posted, a short random password is stored as `md5`. If it is omitted, `AllowLogin` is stored as `0`.

On profile update, a new password is generated only when `AllowLogin` is posted and the stored `AllowLogin` was `0`.

The new password is not emailed from `UsersController`.

### BR-019 — Password reset stores md5 and emails the new password

**Implemented.** Forgot password stores an `md5` key and emails a link. Reset stores `md5` of the posted password, enables login, confirms email, and emails the new password in the message body.

**Implemented defect.** The reset mail body uses `$userD`, which `resetAction` does not set. The greeting can be empty or raise a notice. The address emailed is still the row's email.

## Permits

### BR-020 — A permit profile is readable for any company once the event session exists

**Implemented.** `profileAction` uses `getPermit_admin` and does not filter by company. Missing permit redirects to `/`.

### BR-021 — A permit locks at 13:00 the day before the request date

**Implemented** in `profileAction`. `LOCKED` is `1` when `RequestDate` at 13:00 minus one day is already past. Timezone is `Africa/Johannesburg`.

The collection screen does not use this flag.

### BR-022 — Permit writes need a company identity

**Implemented** as property reads of `$this->identity` in create, save, duplicate, and cancel. **Inferred** failure on the PIN session because `init` never loads an identity.

## Deployment and environment

### BR-023 — Pipelines deploy `qa` and `prod`, not `master`

**Implemented** in `bitbucket-pipelines.yml`. Each branch zips the tree and runs `codedeploy_deploy.py` with that environment's AWS variables.

**Implemented** on the remote: branches are `master`, `prod`, and `ui-changes`. There is no `qa` branch. A push to `master` does not match a pipeline step.

### BR-024 — Files land on one path

**Implemented** in `appspec.yml`. Destination `/mnt/data/www/html/mypass.zone/checkin.mypass.zone/`, owner and group `apache`. Before install runs `scripts/install_dependencies` (`yum install -y httpd`). Application stop runs `scripts/remove_archives.sh` (deletes old CodeDeploy revision directories except the last successful install). Application start runs `scripts/start_server` (`service httpd start`).

The same appspec is on `master` and was not in the `prod` diff.

## Data handling rules visible in code

### BR-025 — Queries concatenate request values

**Implemented.** Applicant search, collect id, registration lookups, and password-reset lookups build SQL or `where` strings from request parameters.

This is recorded as behaviour. It is not an instruction for misuse.

### BR-026 — Uploaded images are decoded from a data URL and stored by id

**Implemented.** Company upload uses `{CID}_image.jpg` under `UPDATE_PATH/companies/`. Staff upload uses `{id}_image.jpg` under `UPDATE_PATH/accreditation/`. `UPDATE_PATH` is `public/archives`, which is gitignored.

`/login/uploadremotecompany` uses the posted `cid` and does not require the PIN session (BR-002).

### BR-027 — Errors from mail are swallowed

**Implemented.** `sendMail` catches exceptions and does not show them. Registration still returns the success JSON after `sendMail` returns.

## Not a rule

The registration email's 48-hour administrator review, the sidebar labels for employees and events, and the organisation "profile" demo page do not encode further enforceable rules. They are recorded in the PRD as screens or copy.
