# Business rules — SaruEfiling.com

Evidence date: 2026-10-04. Source: `master` at `379437f8` (2023-03-07).

Rules are taken from code, views, and commit messages. 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 and new organisations insert credentials with `EFLogin` 1.

Provenance: **Implemented** (`MembersController`, `OrganisationsController`).

### 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 you haven't changed it 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. The string is not repeated here.

Provenance: **Implemented**. This is not described as an approved support procedure anywhere else in the repository. **Requires Confirmation** whether it is still an accepted operational backdoor.

### BR-004 — Session identity is the profile

After a valid login the auth storage holds the `profile` row, not only the credentials row. Later controllers read `JobTitle`, `UnionID`, `UnionType`, `Status`, and `Role` from that identity.

Provenance: **Implemented**.

### BR-005 — Status 0 must finish first login

`Status` greater than 0 goes to `/` after login. `Status` 0 goes to `/first`. `IndexController` repeats that redirect. `FirstController` sets `Status` to 1 on save.

`giroverviewAction` redirects to `/first` when `Status` is less than 0, which is a different comparison (`< 0` rather than `== 0`).

Provenance: **Implemented**. Whether negative status is a real data value is **Unknown**.

### BR-006 — Menu rights are a JSON document

`profile.Role` (also mirrored from credentials in places) is JSON. The default menu shows:

| Key | Menu |
|---|---|
| `members` | Personnel. Child `index` lists, child `add` adds |
| `organisations` | Organisations. Child `index` lists, child `add` creates |
| `forms` | Forms. Child `index` opens e-filing forms. Child `financial` opens templates. Child `add` is an empty "create" anchor |
| `grants` | Plan and budget. Children `profile`, `edit`, `Categories`, `Classifications` |

GIR is not in this table. It is always shown.

Provenance: **Implemented** (`menu/default.phtml`). **Inferred**: hiding a menu item is not the same as refusing the URL. See FR-010.

`MembersController::submitrolesAction` rebuilds that JSON from posted pairs and can set `EFLogin` and `Administrator` from the posted data. Scout coordinator flags inside the same JSON also set other columns.

Provenance: **Implemented**.

### BR-007 — Job title is separate from the role JSON

Approval uses `JobTitle`, not the role JSON.

| JobTitle | Behaviour in code | Label evidence |
|---|---|---|
| 35 | President approval fields | Field names `ApprovedPresident`, `PresidentID`. `addunion` looks up `JobTitle = 35` as president |
| 23 | CEO approval fields | Field names `ApprovedCEO`, `CEO_ID`. Commit text says CEO |
| 22 | Same CEO write as 23 in `approveformAction`. Approve button only on form 79 | Commit `24aea552` says "role 22 (Administrator)" |
| 30 | Used only as a fallback CEO when assigning a new union row, if 23 is missing, before falling through to 22 | `EpgController::addunion` |

Provenance: **Implemented** for the numbers. The word "Administrator" for 22 is **Documented** only in the commit message. **Requires Confirmation** against the live job-title table.

`FirstController` also carries string lists (Head Coach, CEO, Team Manager, and others) for profile dropdowns. Those strings are not the numeric job titles above.

### BR-006a — Active flag gates the main menu

`profile.Active` 1 shows the caption "Active" and renders `menu/default.phtml`. `Active` 0 shows "Inactive" and does not render that menu. The settings menu is a separate check (`UnionType` < 3) and does not look at `Active`.

Provenance: **Implemented** (`application/layouts/default.phtml`). What process sets `Active` was not traced. **Unknown / Requires Confirmation**.

## Organisation scope

### BR-008 — UnionType decides the home screen

| Condition | Result |
|---|---|
| `UnionType` < 3 | National-style dashboard: current-year projects and GIR ethnicity widgets |
| `UnionType` is 3 or 11 | Render `index/union.phtml` |
| `UnionType` > 3 and not 11 | Redirect to `/members/profile/id/{own id}` |

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

### BR-009 — UnionType decides whose forms appear

On the form list, `UnionType` > 2 forces the organisation id to the user's `UnionID`.

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

### BR-010 — UnionType decides save stamps

On save or submit, `UnionType` < 3 updates `AdminDateModified` and, when submitting, `status` 1. `UnionType` 3 and above, when submitting, also set `DateSubmitted` and `SubmittedBy`. A non-submit save for those users sets `DateCompleted` and `AdminUserID` without changing `status`.

Provenance: **Implemented** (`savedataAction`). **Inferred**: `DateCompleted` here means "a union user saved a page", not "the form status is COMPLETED".

### BR-011 — Template menu is national and personal

Form Templates show when (`UnionType` is 1, or user id is 582, or user id is 1) and `forms.Actions.financial` is set. The create-form link also requires `UnionType` < 3 and `forms.Actions.add`.

Provenance: **Implemented**. Why users 582 and 1 are named in the view is **Unknown / Requires Confirmation**.

## Forms

### BR-012 — One row per union and form

`epg` is read with `UnionID` and `Form_id`. `createAction` inserts or updates on that pair. Answers live in JSON `formdata`, keyed by page id.

Provenance: **Implemented**.

### BR-013 — Status labels

| `epg.status` | Label on `form.phtml` | Writer found |
|---|---|---|
| 0 | DRAFT | `activateformAction`, `draftformAction` |
| 1 | SUBMITTED | `savedataAction` when the post status is 1 |
| 2 | COMPLETED | No writer in `EpgController` |

Provenance: **Implemented** for 0 and 1. Status 2 is **Unknown / Requires Confirmation**.

### BR-014 — Submit is presented as a lock

The confirm modal says the form will be locked and the user will not have access to change it. Submit sets `status` to 1. It does not by itself set a lock column. National users can set status back to 0. Named officers can open the form via `draftform`.

Provenance: **Documented** in the modal copy. **Implemented** behaviour is status 1 plus the reopen rules. The two do not match. **Requires Confirmation**.

### BR-015 — Confidential forms

If `epg_forms.Confidential` is 1 and `UnionType` > 2, the user must be in `MembersArray` or `ApproveArray` or they are redirected to `/epg/`. Users with `UnionType` <= 2 are not subject to that redirect. When the blocked flag is 1, the page script disables inputs, selects, and buttons inside the loaded form.

Provenance: **Implemented** (`formAction`, `form.phtml`).

### BR-016 — President approval

Shown when `JobTitle` is 35 and `ApprovedPresident` is not already this user id.

The post writes `PresidentID`, `ApprovedPresident`, and `PresidentApprovDate` to now (`Y-m-d H:i:s`). It does not change `status`. It does not send mail (the send block is commented out).

The browser alerts success before the post returns.

Provenance: **Implemented**.

### BR-017 — CEO approval

Shown when `JobTitle` is 23 and `ApprovedCEO` is not already this user id.

On form id 79 the same button is also shown for `JobTitle` 22.

The server accepts 23 or 22 for every form and writes `CEO_ID`, `ApprovedCEO`, `ApprovedPresident` (same user id), and `CEOApprovDate`.

Provenance: **Implemented**. Button versus server scope for job title 22 is a discrepancy. Writing `ApprovedPresident` during CEO approval is **Implemented** and may be unintentional. **Requires Confirmation**.

### BR-018 — Form 79 legend

Form 79 shows "Submitted" and "Approved" (the CEO icon). Other forms show "Submitted", "President Approved", and "CEO Approved".

Provenance: **Implemented** (`form.phtml`). The reason form 79 is special is **Unknown / Requires Confirmation**. Commit `0c3c5598` says the button "must be CEO, President or Heinrich". No later code mentions a person named Heinrich. Whether that name is job title 22 is **Requires Confirmation**.

### BR-019 — Who may open a submitted form

"Open Form" (`draftform`) is rendered when the user id equals the form row's Owner, Manager, Coordinator, or Administrator, or is 582, 1007, or 74664.

The action sets `status` 0, `PresidentID` 0, `ApprovedPresident` 0, `ApprovedCEO` 0, and sets `PresidentApprovDate` to now. It does not check the allow-list again.

Provenance: **Implemented**. The allow-list is view-only. **Inferred**: anyone who can POST `/epg/draftform` while signed in can clear approvals. **Requires Confirmation** if that endpoint was meant to be restricted.

### BR-020 — National reopen

When `UnionType` < 3 and status is 1 or 2, a control posts `activateform`, which sets `status` 0 and may email the admin user with template arguments `(2, 6)`. Approval columns are not cleared.

Provenance: **Implemented**. Different from BR-019.

### BR-021 — Empty values become zero

`Model_EPG::SortEmptyData` replaces empty values with `0` before insert or update. That runs for `insertData` and `updateData`. `approveformAction` calls `update` on the table directly, so this coercion does not apply to approval writes.

Provenance: **Implemented**. **Inferred**: a blank answer saved through `updateData` can become the number 0.

### BR-022 — N/A is a real answer

Several HTML elements offer `N/A` together with Yes/No or with a count (Once through Five Times). A script treats the value `N/A` as its own case when scoring or validating.

Provenance: **Implemented** (`loadform.phtml`, commits `1a71d4c9`, `622862ff`).

### BR-023 — File upload on the header is a single user

The folder icon is shown only when `FileUpload` is 1 and the user id is 1. Category options come from `Model_DocumentType`.

Provenance: **Implemented**.

## People and GIR

### BR-024 — Player versus personnel

`JobType` 2 uses the player first-login and player field set. Other job types use the personnel set. Coach, admin, and medical title lists are hardcoded arrays on `FirstController`.

Provenance: **Implemented**.

### BR-025 — ID document types

ID copy upload on first login allows `jpg`, `png`, `jpeg`, `gif`, and `pdf` only. Other extensions are ignored.

Provenance: **Implemented**.

### BR-026 — GIR is visible to every signed-in menu

The GIR group is outside the role `if` blocks. The overview action still requires a Zend_Auth identity.

Provenance: **Implemented**. **Inferred**: every account that can load the default menu can navigate to GIR, subject to the action's own login check.

### BR-027 — Dashboard ethnicity windows

For the national dashboard the code counts `gir_profile` rows for the current calendar year split by sex, plus created-this-month, created-this-week (since the previous Monday), and the previous month and previous week.

Provenance: **Implemented** (`IndexController`). The week boundary uses `strtotime('-1 Monday')` and an undefined `$start_week` on the following line. **Inferred**: the week widget can warn or mis-count. **Requires Confirmation** against a running server.

## Grants and plans

### BR-028 — Plan menu versus grant workflow

The menu exposes projects and, for `UnionType` < 3, manage projects plus grant categories and classifications. `GrantsController` implements request, save amount, comment, approve, reject, and reactivate actions that are not all linked from `menu/default.phtml`.

Provenance: **Implemented**. Which grant states are still used in production is **Unknown / Requires Confirmation**.

## Mail

### BR-029 — Product sender

Application mail from `EmailLayout` uses `support@saruefiling.com` and the name `SA Rugby Efiling`.

Provenance: **Implemented**.

### BR-030 — Approval mail is off

The send inside `approveformAction` is commented out. Submit notifications inside `savedataAction` are commented out. `activateformAction` still calls `EmailTemplate2` when `AdminUserID` > 0.

Provenance: **Implemented**. Commit `822a86fe` message: "Removed email notification".

## Deployment rules visible in the repo

### BR-031 — Branch to environment

`development` pipeline deploys with `QA_*` variables. `master` pipeline deploys with `PROD_*` variables. Both zip the working tree and run `codedeploy_deploy.py`.

Provenance: **Implemented** (`bitbucket-pipelines.yml`). Whether those pipelines are still enabled in Bitbucket is **Unknown / Requires Confirmation**.

## Rules that must not be invented

The repository does not state:

- A retention period for forms or uploads.
- A financial year that differs from the calendar year. Lists and the dashboard use `date("Y")` and a `Year` column.
- An approval quorum, a required order of president then CEO, or a rule that both approvals move status to COMPLETED.
- A privacy notice for ID copies or GIR participant data.
- Service levels, support hours, or which unions must file which forms.
