# Business rules — saruefiling

Evidence date: 2026-10-04. Source: `master` at `9b734e0` (2019-07-03).

Rules are taken from code, views, and the README. Nothing here is a policy document from outside the repository. Where the code implies a rule that nobody wrote down, the rule is marked **Inferred**.

Secrets are not copied.

## Identity and access

### BR-001 — E-filing flag

A login username is accepted only when `co_credetials.EFLogin` is 1. If it is not, the posted email is replaced with a random value before authentication.

Provenance: **Implemented** (`LoginController::indexAction`).

New personnel insert credentials with `EFLogin` 1 (`MembersController` add and reset-account paths). Role save starts from `EFLogin` 0 and sets 1 only when the posted role data says so.

Provenance: **Implemented**.

### BR-002 — Password storage

Passwords are stored and compared as MD5. There is no length, complexity, expiry, or lockout rule in the login action. The reset email says the Footprint password expired because it has not been changed in a while. No code in `LoginController` compares a password age.

Provenance: **Implemented** for MD5. The expiry sentence is **Documented** in the email body and **not implemented** as a date check.

### BR-003 — Hardcoded login bypass

If the posted password equals a fixed string in `LoginController`, the adapter is given the stored password hash instead of an MD5 of the typed value. If that string matches and `count` of the credential row is greater than 1, the action redirects to `/` without authenticating. The string is not repeated here.

Provenance: **Implemented**. This is not described in the README. Treat it as a defect unless someone confirms it was intentional.

### BR-004 — First login

`profile.Status` of 0 sends the user to `/first`. Saving the player or personnel form sets `Status` to 1. `JobType` 2 selects the player form. Every other `JobType` selects the personnel form.

Provenance: **Implemented** (`IndexController::init`, `FirstController`).

### BR-005 — Active flag

`Active` 1 shows a green `Active` caption and the main menu. `Active` 0 shows a red `Inactive` caption and does not render `menu/default.phtml`. Settings can still render for `UnionType` less than 3.

Provenance: **Implemented** (`layouts/default.phtml`).

### BR-006 — Role JSON

Navigation is permissioned by keys on `profile.Role`, decoded as JSON:

- `members` plus `Actions.index` or `Actions.add`
- `organisations` plus `Actions.index` or `Actions.add`
- `forms` plus `Actions.index`, and `Actions.financial` for templates
- `grants` plus `Actions.profile`, `Actions.edit`, `Actions.Categories`, `Actions.Classifications`

Provenance: **Implemented** (`menu/default.phtml`).

There is no central ACL check that every controller action reads this JSON before it runs. `EpgController::init` decodes the role into the view and does not reject a missing `forms` key. A person who knows the URL can open a controller that only hides its menu link.

Provenance: **Inferred** from the absence of a shared authorisation check. **Requires Confirmation** whether a plugin outside the inspected actions enforces it. `Bootstrap.php` is empty. The only front-controller plugin registered in `application.ini` is `PG_Controller_Plugin_LayoutPicker`.

### BR-007 — Settings audience

Access control, sent mail, and mail templates are linked when `UnionType` is less than 3.

Provenance: **Implemented**.

### BR-008 — Template editors

The Form Templates link is rendered only when `UnionType` is 1 or `UserID` is 582 or `UserID` is 1, and `forms.Actions.financial` is set.

Provenance: **Implemented**. The business reason for user ids 582 and 1 is **Unknown**.

## Union type

### BR-009 — Dashboard by union type

- Less than 3: project list for the current year.
- 3 or 11: union home (`index/union.phtml`).
- Greater than 3 and not 11: redirect to the user's own member profile.

Provenance: **Implemented** (`IndexController::indexAction`). The names of these numeric types are **Unknown / Requires Confirmation**.

### BR-010 — Form list scope

`EpgController::indexAction` replaces the requested union id with the signed-in `UnionID` when `UnionType` is greater than 2.

Provenance: **Implemented**.

### BR-011 — Confidential form

When `epg_forms.Confidential` is 1 and `UnionType` is greater than 2, the user must be in `MembersArray` or `ApproveArray` or the action redirects to `/epg/`. Users with `UnionType` less than 3 are not blocked.

Provenance: **Implemented** (`EpgController::formAction`).

## E-filing forms

### BR-012 — Status numbers

Views use these labels:

| `epg.status` | Label | Where |
|---|---|---|
| 0 | DRAFT | `epg/index.phtml`, `epg/list.phtml` |
| greater than 0 | SUBMITTED | `epg/index.phtml`, until an approver id is set |
| 1 | SUBMITTED | `epg/list.phtml` |
| 2 | COMPLETED | `epg/list.phtml` |

`ApprovedPresident` greater than 0, or `ApprovedCEO` greater than 0, replaces the label with `APPROVED` on the index list. Either id is enough. The screen does not wait for both.

Provenance: **Implemented** for the labels. No write of `status` = 2 was found in `EpgController`. How COMPLETED is stored is **Unknown / Requires Confirmation**.

### BR-013 — Save versus submit

`savedataAction` treats the request parameter `status` as follows.

National path (`UnionType` less than 3):

- Parameter 1: store JSON, set `status` 1, set `AdminDateModified`, notify. Do not set `DateSubmitted`.
- Any other parameter: store JSON and `AdminDateModified` only.

Other union types:

- Parameter 1: store JSON, set `status` 1, `DateSubmitted`, `SubmittedBy`, `AdminDateModified`, notify.
- Any other parameter: store JSON, set `DateCompleted` and `AdminUserID`. Do not change `status`.

Provenance: **Implemented**.

The confirm copy says a submit locks the form. The submit button is omitted when status is already 1 and `UnionType` is greater than 2.

Provenance: **Documented** in `epg/form.phtml` and `epg/loadform.phtml`, and **Implemented** as that button rule.

### BR-014 — Approval job titles

- `JobTitle` 35 may approve until `ApprovedPresident` is that user's id. The update writes `PresidentID`, `ApprovedPresident`, and `PresidentApprovDate`.
- `JobTitle` 23 may approve until `ApprovedCEO` is that user's id. The update writes `CEO_ID`, `ApprovedCEO`, `CEOApprovDate`, and also sets `ApprovedPresident` to the same user id.

Provenance: **Implemented**. Calling 35 "president" and 23 "CEO" is **Inferred** from the column names. The button text is only `Approve`.

`addunion()` looks up `JobTitle` 35 as the president user and `JobTitle` 23 as the CEO user, then 30, then 22, when creating rows. Those fallbacks do not control the Approve button.

Provenance: **Implemented**.

### BR-015 — Reopen

`UnionType` less than 3 can call `activateform` when status is 1 or 2. The action sets `status` to 0. If `AdminUserID` is greater than 0 it sends template mail.

Provenance: **Implemented**.

### BR-016 — Form JSON

Answers for a page are stored by merging into `epg.formdata` JSON under the page key. Provenance: **Implemented** (`savedataAction`).

## Grants

### BR-017 — Grant request status writes

On `grantsrequest`:

- Reject sets `Status` 4 and a reason.
- Reactivate sets `Status` 1.
- `approvedAction` sets `Status` 3, `AllocatedAmount`, `AllocatedUserID`, and `DateAllocated`.
- `approvegrantAction` sets `Status` 3 and `Rate` when an allocated amount is posted.

Provenance: **Implemented** (`GrantsController`). Display names for 1, 3, and 4 were not found in the grants views. **Unknown / Requires Confirmation**.

### BR-018 — Who sees plan administration

Manage projects, grant categories, and grant classifications are menu links only when `UnionType` is less than 3, and only when the matching `grants` action flag is set. The projects link itself needs `grants.Actions.profile` and does not check `UnionType` in the menu.

Provenance: **Implemented** (`menu/default.phtml`).

## Mail

### BR-019 — Two senders

Password reset uses `LoginController::sendMailNew`: SMTP `mail.sarugby.co.za`, from `accreditation@sarugby.co.za`, display name `Accreditation at sarugby.co.za`, subject `SARugby Notification`. Failures are swallowed.

Other template mail uses `EmailLayout::sendMail`: SMTP `mail.footprintapp.net`, from `support@saruefiling.com`, display name `SA Rugby Efiling`, subject `SARugby Notification`, and a row in `Email_report` (`Sent` 1 or 0).

A commented transport in `EmailLayout` pointed at `mail.saruefiling.com`. It is not the transport that is constructed.

Provenance: **Implemented**.

### BR-020 — Reset copy

The reset body addresses the user by first and last name, claims the Footprint password expired, and signs off as `Footprint Team`. The link is `http://{SERVER_NAME}/login/reset/user/{key}`.

Provenance: **Documented** in the PHP string in `LoginController::sendResetLink`.

## Data rules visible in code

### BR-021 — Credentials table name

The credentials model uses the table name `co_credetials` (spelled that way).

Provenance: **Implemented** (`application/models/Credentials.php`).

### BR-022 — Profile and unions

People are rows in `profile`. Organisations used by the e-filing list are `ef_unions` (`Model_Unions`). `Model_Organisation` points at `ef_provinces`, not at a table named organisation.

Provenance: **Implemented**.

### BR-023 — Dashboard windows

Squad and school counts use `FormDate` after 1 January of last year and `EndDate` before 1 January of this year. Ethnicity slices use the current calendar year for sex splits, and month or week boundaries built from `date()` in `Africa/Johannesburg`.

Provenance: **Implemented** (`IndexController`). The business meaning of "schools" here is **Inferred**: the code calls `getSquadsQuery` with a second argument of 1 and assigns the result to `TotalSchools`.

## Scout API

### BR-024 — App login

`/app/login` succeeds only when the credential password matches MD5 and the role JSON contains `scout.Actions.app`. It does not check `EFLogin`. It stores a new `Token` and returns `UserID` and `Name`.

Provenance: **Implemented** (`AppController::loginAction`).

### BR-025 — Hardcoded token on send

`sendAction` overwrites the request token with a constant before it loads scout details. The constant is not repeated here.

Provenance: **Implemented**. Treat as a defect unless confirmed.

## Rules that look like defects

These are implemented behaviours. They are not promoted to approved policy.

- Login bypass string (BR-003).
- `JobTitle` 23 also writes `ApprovedPresident` (BR-014).
- `display_errors` is forced on in `public/index.php` while production INI turns them off.
- SQL fragments are built by concatenating request parameters.
- `AppController::sendAction` ignores the caller's token (BR-025).
- `git ftp push` is commented out, so the pipeline as written runs `git ftp init` on every `master` and `development` build.

Provenance: **Implemented**.
