# Business rules — track.mypass.zone

Evidence date: 2026-10-01. Source: `master` at `ec4cf5d`.

Rules are copied from code, email text, or FAQ answers. Where the code does not enforce a sentence that the FAQ states, the rule is marked **Documented** and the gap is marked **Inferred**.

No separate business-rules document existed in the repository.

## 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` to `367` and sends the browser to `/`. Any other value is rejected with "Bad entered credentials".

The literal PIN is omitted from this document. It has been in Git since the initial commit (the compared event id changed in `b8652a3`; the secret string did not).

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

### BR-002 — Dashboard requires the event session

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

### BR-003 — Company 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 `44`, 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.

## Registration and people

### BR-005 — 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-006 — South African ID type

**Implemented.** If the submitted company country equals the string `SOUTH AFRICA`, `IDType` is 1. Any other country sets `IDType` 2. No checksum of the ID number is applied.

### BR-007 — Activation story versus code

**Documented** in the registration email: an administrator reviews the company, activation can take up to 48 hours, and the person must confirm email before they can connect to an event.

**Implemented** differently:

- The company row is inserted with `Active = 1`.
- Email confirmation sets `AllowLogin = 1` and `Email_Confirmed = 1` immediately.
- No administrator approval step exists in this repository.
- The new company is attached to event `44` with `Company_Events.Status = 2`, not to event `367`.

**Requires confirmation** which of these is the intended rule.

### BR-008 — Single administrator

**Documented** in the FAQ: "Can I add another staff member to my company administration profile? No, only one administrator is allowed."

**Implemented** differently. `UsersController` offers roles Administrator, Director, Management, Operations, Driver, Technician, and General Staff, and `addAction` inserts another `Company_Members` row for the same `CID`. Nothing in that action counts existing administrators.

### BR-009 — Staff identification at the venue

**Documented** in the FAQ: every person entering to work must carry a valid identity document. The permit email and FAQ also require a printed permit and a Vehicle Access Pass. This application does not check documents at a gate.

### BR-010 — Passwords

**Implemented.**

- Registration stores `md5` of the submitted password.
- Reset stores `md5` of the new password and emails the new password in clear text.
- Enabling login on a new staff member stores `md5` of a random 3-byte hex id. That plain value is not emailed.
- `LoginNow` passes the posted password to `Zend_Auth_Adapter_DbTable` as the credential for column `password`. The adapter treatment (plain compare versus hash) is whatever the stored column contains. This action is not used by the PIN form.

Password reset also sets `AllowLogin` and `Email_Confirmed` to 1 even when the person had not confirmed before.

## Permits

### BR-011 — Load-in and load-out

**Documented** in the FAQ: a load-in permit is for the period before and during the event. A load-out permit is for post-event collection, removal, or work.

**Implemented.** Load type `1` is Load-In and `2` is Load-Out on the create form and on the dashboard filter. The dashboard opens on type `1`.

The submission email chooses the words "Load-Out" when `$permit["Surname"] == 2`, and otherwise "Load-In". **Inferred** defect: the condition does not read `LoadType`.

### BR-012 — One permit per vehicle per day

**Documented** in the FAQ: a permit cannot cover more than one day or a week; each vehicle each day needs its own permit; a previous permit can be duplicated onto another day.

**Implemented.** Duplicate exists (`duplicatepermitAction`) and copies the row onto the session event with status 2. The create action does not reject a date range or a second permit for the same vehicle and date.

### BR-013 — Delivery versus work

**Documented** in the FAQ:

- A delivery permit is for deliveries only, at most 2 hours, and is limited in access areas.
- A work permit is for a contracted job and requires a health and safety file for inspection.

**Implemented.** Service type is a foreign key to `Accreditation_Load_Services_Types` (`SID`, display `Value`). `addpermitAction` does not compare `TimeOut` minus `TimeIn` to 2 hours and does not store a safety file.

The email for every submission tells the recipient to hold a health and safety file and lists 14 document types (public liability, letter of good standing, safety plan, legal appointments, tools and equipment, PPE, risk assessment, toolbox talks, MSDS, medical records, training records, professional registration, method statements, emergency procedures). The FAQ checklist also includes a signed 37(2) agreement and a site induction form, which the email list does not include.

### BR-014 — Minimum fields to request a permit

**Documented** in the FAQ: date and time in and out, vehicle licence number, driver name, surname, and ID or passport, vehicle type and total weight including contents, and additional occupants.

**Implemented.** The form has those fields plus reference, job description, make and model, heights, and trailer fields. The POST handler saves whatever is posted. Empty occupant names are skipped. An empty driver name is skipped.

### BR-015 — Status meanings on the board

**Implemented** as badges:

| Status | Shown as |
|---|---|
| 0, 1, 2, 3 | Pending |
| 4 | Approved |
| 5 | Cancelled |
| 6 | Declined |
| 7 | Checked-In |
| 8 | Delayed |
| 9 | Checked-Out |

New and duplicated requests are stored as 2. Cancel stores 5 and the message the caller posts.

**Inferred.** Approval, decline, check-in, delay, and check-out are written by another system. `Permit_Status` can be read by `AccreditationPermits::getStatus` and the dashboard does not use that table; the numbers above are hardcoded in the view.

The top-of-page counter treats status 4 as both approved and declined. **Inferred** defect against the badge rule.

### BR-016 — Cut-off for changes

Three different clocks exist.

| Source | Rule | Provenance |
|---|---|---|
| Permit confirmation email | Cancellations and changes may be submitted up to 1pm daily. Changes for the next day after that time will not be accepted | Documented |
| `PermitsController::profileAction` and `profiletestAction` | `LOCKED = 1` when now is after 13:00 on the calendar day before `RequestDate` | Implemented |
| Dashboard row in `index/index.phtml` | Lock when status is below 7 and now is after 10:30 on the day before `RequestDate` | Implemented |

`savepermitAction` and `cancelAction` do not read either lock. **Inferred:** the lock is a display flag. The server still accepts the update.

The email also says administrators may change the requested date and time to balance load, and that load-out is first come, first served. No balancing or queue rule is coded here.

### BR-017 — Permit number shown to the applicant

**Implemented.** After insert, `PermitNr` becomes `PM` plus the new id padded to 6 digits. The email prints `{PermitNr}/{year of RequestDate}`.

`getRef` can build a number from `Accreditation_List.PreFix` and a count of permits for the event. `addpermitAction` overwrites it. `duplicatepermitAction` keeps the `getRef` value.

### BR-018 — Who the permit is issued to

**Documented.** A courier or a subcontracted company is entered under the applicant's own company. The applicant must send the approved document to the courier and must include the subcontractor's staff in the applicant's safety file.

**Implemented.** Create uses the authenticated company id. There is no second company on the permit in the fields read.

### BR-019 — Printed permit and gate

**Documented.** Entry requires the printed permit. The gate and special instructions are printed on the issued permit. A map is described as sitting under the "Connected to event" widget on the supplier portal or on the permit. Staff at an operations centre can print a permit.

**Implemented** in this app: the PDF action fetches the permit HTML from footprintapp.net. It does not check that status is approved. The FAQ says download is available only after approval, from a menu icon beside the approved field.

### BR-020 — Zone capacity warning

**Implemented** in the dashboard. For each zone on a permit, capacity weight is `(ParkingBaysLimit + ParkingDisabledLimit + ParkingBusLimit) * MaxWeight`. Current weight adds `VehicleWeight + TrailerWeight`. If max weight is greater than 0 and current weight exceeds capacity, the zone status flag is set to 1. This does not block a new request.

## Contacts named in copy

These addresses are written in emails or FAQ answers. They are not configuration switches.

| Address or phone | Where |
|---|---|
| `accreditation@sarugby.co.za` | From-address, registration, permit, and password emails |
| `+27219287000` | Footer of registration and permit emails, labelled Registration Contact Centre |
| `HealthandSafety7s@sarugby.co.za` | FAQ, health and safety |
| `security@sarugby.co.za` | FAQ, security |
| "Danie xxxxxx" | FAQ contact for health and safety. The surname is redacted in the source string itself |

## Conflicts that need a human decision

1. Event `367` (dashboard PIN) versus event `44` (registration and unused `LoginNow`).
2. "Reviewed within 48 hours" versus `Active = 1` at insert.
3. "Only one administrator" versus the staff add form.
4. Change cut-off at 13:00, at 10:30, or not enforced on save.
5. Delivery permits limited to 2 hours versus no duration check.
6. Download only after approval versus PDF action with no status check.
7. FAQ declined/approved wording versus the status-4 counter that increments both totals.
