# Business rules — menu.mypass.zone

Evidence date: 2026-10-09. Source: `master` at `ca37151` (2022-07-21).

Rules below are taken from controller, model, and view behaviour. Nothing in the repository is a separate business-rules document. Where the code implies a rule that nobody wrote down, the rule is marked **Inferred**.

Unused models (fixtures, accreditation, companies, and the rest listed in `features.md`) are not treated as rules of this product. They are not called by the menu controllers.

## Access

### BR-001 — The menu is public

No controller checks a session, a PIN, or `Zend_Auth` before listing venues, dates, sessions, dishes, or nutrition.

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

### BR-002 — One tournament on the home page

The home page requests venues with `TID` equal to `284` only.

Provenance: **Implemented** (`IndexController::indexAction`). The meaning of `284` (which event, which season) is **Unknown / Requires Confirmation**.

### BR-003 — Later pages do not repeat the tournament filter

Date, session, menu, and nutrition queries filter by the id in the URL (venue id, session id, or nutrition id). They do not check that the row belongs to tournament `284`.

Provenance: **Implemented** in `getAllByVenue`, `getRow`, `getAllByVenueAndDates`, `FoodSessionItems::getAll`, `Nutritions::getRow`. **Inferred** consequence: a visitor who can guess another venue or session id can read it even when it is outside tournament `284`.

### BR-004 — This application does not edit menus

The pages only read. Insert, update, and delete methods exist on the food models and have no caller in the controllers.

Provenance: **Implemented**. Where editing happens is **Unknown / Requires Confirmation**.

## Venue, date, and session selection

### BR-005 — Venue order

Venues are ordered by `Venue` ascending.

Provenance: **Implemented** (`MenuVenues::getAllByID`).

### BR-006 — One tile per date

Dates for a venue come from `FoodSession_view` filtered by `VID`, ordered by `Date` then `StartTime`, grouped by `Date`. The tile label is `date("j F", strtotime(Date))` (day and month, no year).

Provenance: **Implemented**. Which session id represents the group when several sessions share a date is left to the database. **Inferred**.

### BR-007 — A date tile is a pointer to a session row

Choosing a date requests `/session/menus/id/{SID}`. The action loads that session, then lists every session with the same `VID` and the same `Date`. The list is ordered by date then start time and is not grouped.

Provenance: **Implemented** (`menusAction`).

### BR-008 — Session card content

Each session card shows the venue image (`SmallImage`), `Title`, the date as `j F Y`, `StartTime`, and `EndTime`. Times are printed as stored. This code does not reformat them.

Provenance: **Implemented** (`session/menus.phtml`).

### BR-009 — Empty lists stay quiet

If a query returns no rows, the page heading still renders and the list is skipped. The views do not show a "none found" message.

Provenance: **Implemented** (`count(...) > 0` in the venue, date, session, and menu views).

## Dishes and nutrition

### BR-010 — Dish order

Items for a session are ordered by `Position` ascending.

Provenance: **Implemented** (`FoodSessionItems::getAll`).

### BR-011 — Descriptions are rendered as HTML

`Title` and `Description` are echoed without escaping. The nutrition script then finds anchors inside that HTML.

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

### BR-012 — Nutrition id is taken from the link href

The browser splits each dish-link `href` on `_` and uses the second segment as the `NID` for `/session/nutrition/id/{NID}`.

Provenance: **Implemented** client rule. The editorial rule for how those hrefs must be written is not in the repository. **Unknown / Requires Confirmation**.

### BR-013 — Allergens are a JSON list of ids

`Nutritions.Allergens` is passed through `json_decode(..., true)` and then `Allergens.AID IN (...)`.

Provenance: **Implemented** decode. The JSON shape is **Inferred** from the `IN` predicate. No sample document is stored in Git.

### BR-014 — Nutrition fields shown to the visitor

The popup shows title, allergen image and title, description, portion, energy in the two stored fields `Energy_Cal` and `Energy_Kj`, protein, carbohydrate (`Carbs`), and fat. Units are not added by the template. Whatever is stored is what is shown.

Provenance: **Implemented** (`session/nutrition.phtml`).

### BR-015 — Nutrition is a fragment

`nutritionAction` disables the layout. It is intended to be injected into the modal.

Provenance: **Implemented**.

### BR-016 — Missing nutrition is not handled

The fragment reads `$this->ROW->Title` and the other fields with no existence check.

Provenance: **Inferred** failure when `NID` does not match a row.

## Presentation rules

### BR-017 — Timezone

PHP's default timezone is `Africa/Johannesburg`.

Provenance: **Implemented** (`public/index.php`). Date labels use that timezone when `strtotime` runs.

### BR-018 — Branding

The HTML title is "SA Rugby - Official Home of the Springboks". The footer copyright is the current year plus "SARU. All Rights Reserved." The header image alt text is "logo".

Provenance: **Documented** in the layout (the strings are in the template, not in a CMS).

### BR-019 — Public share URL

The QR code encodes `https://www.sarugby.online/menu/session/menu/id/{SID}/`, not a `menu.mypass.zone` URL.

Provenance: **Implemented** (`session/menu.phtml`). Whether that URL is the supported public address is **Unknown / Requires Confirmation**.

### BR-020 — Banner target

The banner links to `/menu`. This repository has no controller for that path. The navigation partial that would mark "Menu" current is not included.

Provenance: **Implemented** link. The destination behaviour is the error controller unless another layer rewrites `/menu`. **Unknown / Requires Confirmation**.

## Data rules visible on the models

These are the identifiers the read path uses. They are **Implemented** as column names in queries, not as a documented schema.

| Concept | Identifier | Where |
|---|---|---|
| Tournament on the home page | `TID` | `FoodSessionVenues_view` |
| Venue | `VID` | venues view, session view |
| Session | `SID` | `FoodSession_view`, `SessionFood_Items` |
| Menu item | `IID` | `SessionFood_Items` (used by `getRow` / `updateData` / `deleteData`, not by the menu page) |
| Nutrition | `NID` | `Nutritions` |
| Allergen | `AID` | `Allergens` |

`Menu_Venues` is the Zend table name on `Model_MenuVenues`. The home page does not select from that table. It selects from `FoodSessionVenues_view`. Provenance: **Implemented**.

## Conflicts that need a human decision

1. The home page is limited to tournament `284`. Every later query trusts the id in the URL. Confirm whether that is acceptable.
2. The QR code and the banner disagree about the public path (`www.sarugby.online/menu/...` versus `/menu` on the current host).
3. Deploy history says the tree used to target `bluecard.footprintapp.net` and was repointed at `menu.mypass.zone` on 2022-07-21. Confirm that the menu host, and not the bluecard host, is the product this repository is supposed to be.
4. `application.ini` is required to boot and is intentionally untracked. The database, and therefore the live tournament data, cannot be confirmed from Git.
