# PRD — sawis (SAWIS Online)

Evidence date: 2026-10-04.

Source: `https://bitbucket.org/silicon_bit_bucket/sawis.git`, branch `dev_2020` at `069966f` (2026-09-14, merge of PHP 8.2 upgrade pull request #1747).

There is no prior PRD in this repository or in Silicon Ark for this folder. `NewKnowledgeBase.md` is an implementation note log, not a requirements document. Requirements below were recovered from code and from copy on `home.php`. Provenance is marked. Passwords and vendor secrets are not copied.

The login API this application calls is specified in `silicon_ark/sawis_api/prd.md`.

## Product purpose

SAWIS Online is the signed-in system SAWIS members and staff use to capture and read wine-industry operational data: membership, farms, vineyard blocks, cellars, tanks, harvests, blends, certification numbers, bottling, duties, and transfers.

Provenance: **Documented** purpose, vision, mission, and promise on `home.php`. **Implemented** as the page and class set on `dev_2020`. The letters in “SAWIS” are not expanded in the repository.

## Users and roles

Menu rows and the exact name of every security group are stored in MySQL (`sysSecurityGroup`, `sysMenuLevel1`, `sysMenuLevel2`, `sysSecurity`). Git does not contain that data. Roles below are the ones the PHP names or special-cases.

| Role | What the code does with them | Provenance |
|---|---|---|
| Anonymous visitor | Sees the login form on `index.php`. Can start member registration and password reset | **Implemented** |
| Signed-in user | Session `$_SESSION["USER"]` with user id, email, security group, member id (`MEMBERID` / `COMPANYID`), language, profile image. Pages that need a member redirect to `index.php` when `COMPANYID` or `MEMBERID` is missing (`home.php`) | **Implemented** |
| Admin Dev | Security group name `Admin Dev`. Numeric id **1** is stated in a comment in `includes/user.navigation.analytics.report.cls.php` (“Admin Dev”, `SECURITYGROUPID=1`). Referrer column on navigation analytics is limited to this id | **Documented** in that comment; **Implemented** as the id check |
| Sawis Admin | Security group name `Sawis Admin`. Together with Admin Dev, receives a hash login URL stored on `$_SESSION["USERADMIN"]` | **Implemented** name check in `index.php` and `login.token.php` |
| Admin pair (ids 1 and 2) | May filter other cellars, see the admin report catalogue, open process-logic rules, WSB traceability cellar filter, and are exempt from the pilot-group page gate | **Implemented**. Mapping of id 2 to the name “Sawis Admin” is **Inferred** from that pair. **Requires Confirmation** |
| Sawis View Only | Named group. Edit, approve, and new-document buttons are hidden on member, pending member, relationship, single vineyard, and WS screens | **Implemented** (comments dated 2017-12-04, Rachel). Numeric id is **Unknown** |
| Unapproved member | `member.profile.php` skips edit when `SECURITYGROUPID == 13`, comment “no edit if member is still unapproved” | **Inferred** from that comment that id 13 is the unapproved member group. **Requires Confirmation** |
| Groups 4 and 11 | Extra access on tanks (`tanks.php` allows 1, 2, 4, 11). Group 4 is also allowed on wine transfers and a stocktake branch | **Implemented** as id checks. Names **Unknown** |
| Group 111 | Extra branch on `my.farms.php` | **Implemented**. Name **Unknown** |
| Member Superuser and Member User | `my.user.php` comment: the security-group dropdown for a member managing users is limited to these two names | **Documented** in that comment. Ids **Unknown** |
| Inspector | User flag `blnInspector` and `InspectorCode` on `user.php`. Inspector audit on wine transfers and tanks | **Implemented** |
| Cron | `_cron.daily.php` sets session user id `-1`, username `CRON`, security group 1 | **Implemented** |
| Pilot member | `tblMember.blnAllowStockMovement = 1`. Non-admin users outside this set are sent home from cellar-operations pages | **Implemented** (`system.php`) |

`sysSecurity` flags used by the menu are `blnView`, `blnDelete`, `blnSave`, `blnSpecial` (`_framework/_nemo.menu.cls.php`). A level-2 item with `blnView` false is rendered as an inactive link. Which entities carry which flags is database data. **Unknown** from git.

## Environments the user can land on

`system.php` picks one settings file from `$_SERVER["SERVER_NAME"]`:

| Host names | Settings file | Application database | API base (from the settings file) |
|---|---|---|---|
| `www.sawisonline.co.za`, `sawisonline.co.za`, `10.0.0.60` | `system.server.PROD.php` | `sawis` on `10.0.0.61` | `api.sawisonline.co.za` (host `10.0.0.173`, database `sawis_api`) |
| `qa.sawisonline.co.za`, `10.0.0.149`, `196.22.249.180`, `196.22.249.180:11443` | `system.server.QA.php` | `sawis` on `srv22-050` | `10.0.0.108/sawis_api` |
| `training.sawisonline.co.za` | `system.server.TRAINING.php` | `sawis_training` on `srv22-050` | `api.sawisonline.co.za` (same host as production API) |
| `localhost`, `localhost:8888`, `10.0.0.145`, `lamp`, `192.168.100.6` | `system.server.DEV.php` | Last assignment in the file wins. See technical documentation | Last assignment in the file wins |

`system.server.LOCAL.php` is not included by the switch. Provenance: **Implemented** host map. Whether each host is still in DNS is **Unknown**.

## Main workflows

### W1 — Sign in

Trigger: POST `Action=Login` on `index.php` with `strUsername` (matched to `sysUser.strEmail`) and `strPassword`.

Behaviour, in order:

1. Load `sysUser` joined to `sysSecurityGroup` where the group is active (`blnActive = 1`).
2. If the user row is missing, log “Login failed: User not found or SG not active”, show “Login Details are invalid.” If the same IP has more than 10 failed rows in `sysLogin` in the last minute, insert `sysIPBlock` with note “Bot activity detected”.
3. If `refMemberID` is 0, or the linked `tmpMember.strStatus` is `1.0 - New`, show “User does not have a member record” and offer Load draft (`registration.member.php?Action=LoadDraft`). The `refMemberID == null` case is allowed through the member-id check (`!= 0` OR `== null`).
4. If `sysUser.blnActive` is not 1, log “Login failed: Inactive User” and show “User account is inactive.”
5. Switch the database connection to the API database, read `apiVendor` for this environment’s vendor key, read the last stored password hash, switch back.
6. HTTP GET `{BASE_URL_API}/api.php` with `call=authenticate` and the vendor password. Failure text `AUTHENTICATION FAILED` or `INVALID VENDOR KEY` is shown.
7. HTTP GET `call=loginUser` with username, plaintext password, and the token from step 6. A non-empty XML body, or a `strHash` query value equal to the last stored hash, counts as success.
8. On success, fill `$_SESSION["USER"]`, force language `EN` (comment 2017-10-11: default language English on the admin side — the assignment is unconditional), `INSERT IGNORE` into `sysUserCompanyGroup`, write `tblLogin` “Login successful”, redirect to `home.php` (or `registration.farm.php` when `Nav=SAWIS2`).
9. Admin Dev and Sawis Admin also store a hash URL on `$_SESSION["USERADMIN"]` that can repeat login via `strHash` without the password.

Provenance: **Implemented** in `index.php`. Password rules and token lifetime are owned by `sawis_api` (see that PRD). The hash bypass is **Implemented** here as `UserHash == $np->hash`.

`login.token.php` builds the same session fields from a token path. It was not line-audited beyond the session assignment and the Admin Dev / Sawis Admin branch.

### W2 — Reset password

Trigger: `resetPassword.php`.

The script calls the API `authenticate`, then `call=setPassword` with email, new password, and token. Success and failure are reflected on the login page as `LoginMsg=resetSuccess` or `resetError` (“Password Has Been Reset…” / “Password Reset Failed…”).

Provenance: **Implemented**. Email template and who is allowed to request a reset are in `sawis_api`.

### W3 — Register a member

Trigger: `registration.member.php` using `includes/member.registration.cls.php` and the Nemo wizard. Session object `S3REGISTRATION`. Draft rows live in `tmpMember` (`RegistrationArgs` serialized).

The wizard offers three user types (English labels):

1. “I am a new member” (`NewMember`).
2. “I want to link to an existing member” (`SelectMember`).
3. “Change of ownership of an existing cellar” (`ChangeOfOwnership`), which asks for an existing member id.

Membership type radios are enabled only when `tblMemberType.strMemberTypeCode` contains the code. Labels below are the English strings in the class.

| Code | Label on the form | Provenance |
|---|---|---|
| `CELPVT` | Private cellar (grapes are crushed) | **Documented** and **Implemented** |
| `CELPRD` | Cellar owned by grape farmers (producer cellar) | **Documented** and **Implemented** |
| `CELWSL` | Wholesaler cellar, no crushing. Subcodes `WSLWIN`, `WSLSPT`, `WSLSAW` | **Documented** and **Implemented** |
| `WSLPRD` | Producing wholesaler cellar (also crushes). Same three subcodes | **Documented** and **Implemented** |
| `WSLDIS` | Wholesaler distillery | **Documented** and **Implemented** |
| `BOTCEL` | Bottler with a cellar; does not take ownership | **Documented** and **Implemented** |
| `JUICEL` | Juice cellar (must / non-alcoholic grape juice) | **Documented** and **Implemented** |
| `MFRCON` | Concentrate manufacturer | **Documented** and **Implemented** |
| `WSLEXP` | Wholesaler exporter, no physical building | **Documented** and **Implemented** |
| `WSL000` | Wholesaler that trades bulk quantities, no physical building | **Documented** and **Implemented** |
| `JUI000` | Juice seller, no physical building | **Documented** and **Implemented** |
| `EXP000` | Exporter of packaged wine or grape spirits | **Documented** and **Implemented** |
| `CERPVT` | Entity registered under an existing SAWIS entity for certification only | **Documented** and **Implemented**. BG1 comment: radio options 1, 3, 4 disabled for `CERPVT` |
| `FRM000` | Entity that grows wine grapes | **Documented** and **Implemented** |

Sub-option labels `A.1`, `A.2`, `C.1`–`C.3` are translation keys. Their full sentences were not copied here. A comment (2017-07-03) says membership type `CERPVT` shows “Facilitation SAWIS Member”.

Pending members are approved from `member.pending.php`. Sawis View Only does not get the approve button.

Provenance: **Implemented**. The approval business criteria beyond the button are **Unknown** (they are not a single documented checklist in git).

### W4 — Register a farm and vineyard blocks

Trigger: after member registration, `Nav=SAWIS2` sends the user to `registration.farm.php`. Ongoing maintenance is `farm.php`, `my.farms.php`, `block.php`, `my.block.php`, `registration.blocks.php`, `single.vineyard.php`.

Block comments record these rules (2017, Gael / pj):

- Row width and vine spacing: not below 0.5 and not above 5. Vine spacing may be 0, but not between 0 and 0.5.
- If vintage year minus planted year is less than 3, block status is New; otherwise Production.
- Vines closing = vines opening − vines uprooted + vines grafted + vines amended.

Pending farm subdivision and transfer: `farm.pending.subdivision.php`, `farm.pending.transfer.php`. Pending single vineyard: `single.vineyard.pending.php`.

`my.farms.php` can POST a farm id to `https://sungis10.stb.sun.ac.za/sawisvm/` and open the response in a new window. `forsilicon.html` is a standalone version of that call. Provenance: **Implemented**. What the GIS service returns is **Unknown** (the service is outside this repo).

### W5 — Harvest (BG1) and loads

`BG1.php` / `includes/BG1.cls.php` is the harvest declaration (comments from 2016-11-24: IPW check, wine descriptor, smallest common location, admin wizard, validation). Session key `BG1` is cleared when an admin switches member (`home.php`).

`loads.php` and `my.loads.php` record harvest loads. `NewKnowledgeBase.md` (2026-01-04 and 2026-01-26):

- A WS lookup sets whether the WS number is single-vineyard and may prefill “Services For”.
- Farm selection refreshes the member list, block dropdown, IPW status, and production area.
- Single-vineyard WS: cultivar stays blank until a block is chosen.
- Otherwise, cultivar auto-selects only when the WS number has exactly one cultivar.

Provenance: **Implemented** behaviour described in the note log and class comments. The full field list of a BG1 form is the database table `tblHarvest` via Nemo details. Column-level rules beyond the comments above were not re-derived.

### W6 — Tanks, stock movement, press, loss

`tanks.php` / `my.tanks.php` maintain the tank register. Movable tanks must send a positive integer `TankID` (`NewKnowledgeBase.md`, 2025-12-01). Inspector surplus and tank inspector-audit uploads are actions on `tanks.php`.

`stock.movement.php` moves volume between tanks. Output-tank WS/WSB dropdowns list certification numbers before “None” and “Uncertified” (note 2026-01-08). Crush/press/separator output status excludes “Finalize”; fermenting/settling (or settling only for all-white loads) are the choices, and finalized tanks stay locked (same note).

`press.register.php` is the press register. `loss.php` / `my.loss.php` is the loss list. Pagination note (2025-12-03): loss output difference is still calculated on the full filtered set, not the current page.

A commented block in `_cron.daily.php` would unlock tanks locked for more than 30 minutes. It is not running. Provenance: **Implemented** as comments only. **Requires Confirmation** whether tank locks are cleared by something else.

### W7 — Blends, WS, and WSB numbers

- `blend.sandbox.php` (created 2021-06-21) and `my.blend.sandbox.manage.php` prepare a blend. `blend.master.php` (created 2022-03-01) stores blend master rows searchable by member, cellar, WS, and WSB id.
- `ws.php` / `wsb.numbers.php` maintain certification numbers. WSB search placeholder format is `123456BLN01` (6-character number, 3-character transaction type, 2-digit iteration), also parsed that way in `get.and.store.wsb.number.traceability.data.php`.
- Blend sandbox single-vineyard warning (note 2026-01-30): fires when a selected WSB has a single-vineyard value, or selected WS tanks disagree on single vineyard. The string `"null"` is ignored.
- When a blend source tank is also an output tank, `SetTankToDefault` is called with `$blnDontCloseMoveableTank = 1` so the movable output tank stays open (note 2026-01-21).
- Input tank-register rows set `blnInitialFinalLitres` when output content is finalised for Concentrate, Bottling, Distilled, De Alcoholised, and Must. Output tank-register rows stay unflagged (note 2026-01-27).

Provenance: **Implemented** as described in class comments and `NewKnowledgeBase.md`.

### W8 — WSB traceability

Trigger: `wsb.number.traceability.php`. Admins (group 1 or 2) may type a cellar id. Other users see their own records (class comment).

The user picks Dendrogram or Bulleted List. Retrieval is a CLI-style PHP process: `get.and.store.wsb.number.traceability.data.php` takes `FullWSBNumber` and server name as arguments. It reads `tblBlendMaster` and also calls the mapper:

`{MapperAPIServerPath}/ice/default.asp?Category=SAWISONL&Service=WSBNUMQUERY3&CTWSB=…&TRNTYP=…&BLNNUM=…`

Status is checked by `check.wsb.number.traceability.data.retrieval.status.php`. Start is `start.wsb.number.traceability.data.retrieval.process.php`.

Provenance: **Implemented**. Where `MapperAPIServerPath` is configured (settings row versus code) was not fully traced. The mapper’s availability is **Unknown**.

### W9 — Bottling, duties, transfers, bulk exports

- `bottling.applications.php` / `my.bottling.applications.php`. Special WSB lookup is scoped by `tblBottlingNotices.refTankID` plus size, date, and company, so a WSB is not reused across tanks (note 2026-01-29).
- `duties.php` / `my.duties.php`. Excel export right-aligns Consignor ID, Consignee ID, Price Per Litre, and Actual L (note 2026-02-02). Admin email setting for the packhouse cron defaults to `sawis3@sawis.co.za` when the cron inserts missing settings.
- `wine.transfers.php` / `my.wine.transfers.php`. Groups 1, 2, and 4 may enter. Inspector audit accepts or records a report and uploads files under `documents/Inspector_Audit_Documents/` (note 2025-11-27). Context is `wine` or `tank`.
- `wine.type.content.allocation.php` is labelled “My SARS Documents” in the pilot-group URL map (`my.wine.type.content.allocation.php`).
- `bulk.exports.php` lists bulk exports. Vintage filter uses `sysSettings` key `BG1_Vintage` and the 50 years below it.

Provenance: **Implemented**.

### W10 — Switch member or user (staff only)

`home.php` comment: only SAWIS admins and admin devs may switch. `blnSwitch=member` replaces company, member, and security group and prefixes the username with `pp.`. `blnSwitch=user` also replaces username and email. In-progress registration sessions (`S1REGISTRATION`, `S2REGISTRATION`, `S3REGISTRATION`, `BG1`) are unset first.

`switch.company.php` is a separate script for company switch. It was not line-audited.

Provenance: **Implemented** for the home-page switch. The comment says admins; the PHP condition that enforces “only admins” should be confirmed against the rest of `home.php` before treating the comment as the only gate. **Requires Confirmation** if any non-admin path can pass `blnSwitch`.

### W11 — Reports

`report.php` builds two catalogues.

Admin (security group 1 or 2): Harvest, Fortification, Tank Walkthrough List, Sawis 10, Sawis 5, Tonnage per Farm, Block Totals, WS WSB Transactions, Cellar Stock Per Farm, Cellar Stock Per Cultivar.

Member: Fortification, Tank Walkthrough List, WS WSB Transactions. Commented-out member reports that are not added: Tonnage per Hectare, Litres per Tonne, Tonnage per Farm, Block Totals.

Admin reports take a requested cellar or member id. Member fortification uses the session company when the user is not group 1 or 2 (`GenerateFortificationPDF`). Default selected report key in session is `timesheet.rpt` when unset. No `timesheet.rpt.php` was found under `includes/`. **Requires Confirmation** whether that default key is leftover and errors on first open.

Exports set a download filename from the report. Older `.xls` headers remain in comments; the live export branch was not fully read.

Separate report scripts also exist: `rpt.cellar.stock.per.cultivar.php`, `rpt.cellar.stock.per.farm.php`, `rpt.content.per.wine.type.php`, `rpt.content.per.ws.number.php`, `checklist.report.php`, `stocktake.report.php`, `block.report.php`, `user.navigation.analytics.report.php`.

### W12 — Daily cron

`_cron.daily.php`, default action:

1. `maintainUCG()` — keep `sysUserCompanyGroup` aligned with `sysUser` and member relationships (comment: task 1, 2016-05-13).
2. If `sysSettings.CRON_Packhouse_Task_Active` is true, email members whose `strSARSPackhouseNumber` is empty, whose registration is older than `CRON_Packhouse_Task_Check_Months_Interval` months (seed value 3), and whose `dtFirstEdit` is after `CRON_Packhouse_Task_Min_Reg_Date` (seed `2016-01-01`). Member mail is sent only when `CRON_Packhouse_Task_Notify_Member` is true (seed `false`). An admin summary always goes to `CRON_Packhouse_Task_Admin_Email` (seed `sawis3@sawis.co.za`) when at least one row matches. Templates: `CRON Packhouse Task MEMBER_AF`, `MEMBER_EN`, `ADMIN`. From-address setting: `SMTP Send As Sawis3`.

The script inserts those settings rows if `CRON_Packhouse_Task_Active` is not already set.

Provenance: **Implemented**. Who schedules the URL or CLI is **Unknown**. Tank-unlock SQL in the same file is commented out.

### W13 — Maintenance and sync lock

If `sysSettings.SYSTEM_UNDER_CONSTRUCTION` is `1`, the session is cleared and the browser is sent to `server.error.php?MID=uc` (comment 2018-10-02).

If `blnSyncInProgress` is `1`, every request sleeps in a loop up to 60 seconds, then dies with a refresh message.

An older under-construction redirect at the top of `index.php` is disabled (`&& false`).

Provenance: **Implemented**.

## Functional requirements

### FR-001 — Sign-in gate

The system allows a user in only when the email matches an active user in an active security group, the API vendor authenticates, and `loginUser` returns XML or the stored hash matches. Inactive users and unknown emails are rejected and logged.

Evidence: `index.php`. Implementation: **Implemented**. Confidence: **High**.

### FR-002 — Registration draft

A user whose member status is `1.0 - New`, or whose `refMemberID` is 0, is not given a normal session. They can continue a draft on `registration.member.php`.

Evidence: `index.php` `Action=Login` and `Action=Load Draft`. Implementation: **Implemented**. Confidence: **High**.

### FR-003 — Failed-login IP block

More than 10 failed logins from the same IP inside one minute inserts `sysIPBlock`. Whether later requests actually refuse that IP is enforced in `ip.block.php` / a shared include. The insert itself is in `index.php`. The block check on subsequent requests was not re-read. **Requires Confirmation** that a blocked IP cannot still POST the login form.

### FR-004 — Member data isolation

`checkMemberAccess($tbl, $MemberID, $arrEntity)` returns true for a user only when `sysUser.refMemberID` equals the member id. For a member-relationship row it also allows a match on `refEntityID` or `syncProductionCellarID`. Admins (group 1 or 2) are treated separately in `project.functions.inc.php` around the navigation helper at the cited admin check.

Evidence: `project.functions.inc.php` `checkMemberAccess`. Implementation: **Implemented**. Confidence: **Medium** — the function is long and entity-specific branches exist below the portion read (`checkMemberAccessTest.php` comments name farm, block, BG1, and single-vineyard cases).

### FR-005 — Pilot gate for cellar operations

Users who are not group 1 or 2, and whose member id is not in `tblMember` where `blnAllowStockMovement = 1`, are redirected to `home.php?RestrictedPilot=1` when they open:

`press.register.php`, `my.loads.php`, `my.tanks.php`, `stock.movement.php`, `blend.sandbox.php`, `my.blend.sandbox.manage.php`, `my.wine.transfers.php`, `my.wine.type.content.allocation.php`, `my.duties.php`, `my.loss.php`.

Evidence: `system.php`. Implementation: **Implemented**. Confidence: **High**.

### FR-006 — Two languages

User-visible strings use `$_TRANSLATION["EN"]` and `$_TRANSLATION["AF"]`. Login currently forces `$_SESSION["USER"]->LANGUAGE = "EN"` after a successful login. A 2018-02-27 comment on `registration.member.php` says a session check broke Afrikaans/English switching.

Provenance: translations **Implemented**. Whether a member can remain in Afrikaans after login is **Requires Confirmation** because of the forced `EN` assignment.

### FR-007 — Confidentiality of individual information

The home page states individual information is confidential. The implementation of that statement is the member-access checks, security-group menu, and admin-only cross-cellar filters. There is no separate policy engine.

Provenance: **Documented** on `home.php`. Enforcement is **Implemented** piecewise, not as one rule.

### FR-008 — Certification number traceability

Given a WSB number, the system can build a dendrogram or bulleted list from `tblBlendMaster` and from mapper service `WSBNUMQUERY3`.

Evidence: `includes/wsb.number.traceability.cls.php`, `get.and.store.wsb.number.traceability.data.php`. Implementation: **Implemented**. Confidence: **Medium** (mapper path and failure text exist; operational success depends on the mapper).

### FR-009 — Process logic rules

Group 1 or 2 can create and edit `tblProcessLogicRules`. Status is read-only on the form. A rule in status `In Review for Implementation` shows an Approve Rule button. The comment says status changes when the description changes, or the rule becomes inactive when deleted from the list.

Evidence: `process.logic.rules.php`. Implementation: **Implemented**. Confidence: **Medium** — `ApproveRule()` body in the class was not re-read.

### FR-010 — Navigation analytics

Every page load with a user session calls `Global_LogUserNavigation()` unless the query string is pagination-only (`page=N` only). Reports batch logs to avoid memory exhaustion. Detail pages (`blnDetailsPage=1`) group by the script name. Security-group filter uses `sysUser.refSecurityGroupID`. Default report type is Dashboard.

Evidence: `system.php`, `NewKnowledgeBase.md` entries 2026-01-04 to 2026-01-25, `includes/user.navigation.analytics.report.cls.php`. Implementation: **Implemented**. Confidence: **High** for the behaviours named in the note log.

## Integrations

| System | Direction | Trigger | Data | Auth | Failure |
|---|---|---|---|---|---|
| `sawis_api` `api.php` | Outbound HTTP | Login, password reset, vendor lookup | `VendorKey`, `call`, username, password, token | Vendor password then user password. See `sawis_api` PRD | Login shows the API text (`AUTHENTICATION FAILED`, `INVALID VENDOR KEY`, or the `loginUser` body) |
| Mapper `ice/default.asp` | Outbound HTTP | WSB traceability retrieval | `Category=SAWISONL`, `Service=WSBNUMQUERY3`, WSB parts | **Unknown** | JSON `Message` is read; exact user-facing text was not fully extracted |
| Stellenbosch `sungis10.stb.sun.ac.za/sawisvm/` | Browser POST | Farm map button | JSON `farm_id` | None in the page | Popup stays blank if the POST fails. No error UI in `forsilicon.html` |
| SMTP | Outbound | Cron packhouse mail, NemoEmail elsewhere | Templates in `tblEmailTemplate` | Settings keys `SMTP Send As Sawis3`, `SMTP BCC` | Not shown in the cron script beyond `Send()` |
| SonarQube | CI | Push to `master`, `dev_2020`, or any PR | PHP and non-PHP scans | `SONAR_TOKEN` | Pipeline step retries twice (`max-retries: 2`) |
| MySQL | Local to the PHP process | Every page | Domain tables | Username and password in `system.server.*.php` | Connection failure sets a critical message “Database Connection has Failed!” and does not print the settings (comment 2017-11-01) |

## Important constraints

- Session garbage-collection lifetime is 7200 seconds. Session files are stored under the host’s `DOCUMENT_ROOT/SESSION/` (path varies by environment file).
- Upload and POST body limits are set in `system.php` to 10M.
- `display_errors` is `1`. `error_reporting` hides strict, notice, warning, and deprecated.
- List pages that adopted `Sauer_Pagination_Library` use page size 100 (note 2025-12-03). Checklist and export contexts skip pagination.
- Sync lock can stall every request for up to 60 seconds.
- Under-construction flag logs everyone out, including an in-flight session.
- `deploy.php` has no login check and hard-resets the working tree to `origin/qa_2020`.

## Requirements discovered from implementation, not from a signed specification

Everything in FR-001 through FR-010 except the home-page purpose statement is recovered from code. Do not treat `NewKnowledgeBase.md` notes as approved business rules. They record what a developer changed.

## Conflicts and gaps

1. Task snapshot commit `592b278f` (2026-02-02) is not the tip that was read (`069966f`, 2026-09-14).
2. Default branch `master` (`da74b067`, 2025-10-08) was not the inspection target. Pipelines scan both `master` and `dev_2020`.
3. Training uses the production API host while its own database is `sawis_training`.
4. `system.server.DEV.php` assigns `localhost` database settings more than once. Only the last assignment runs. Earlier blocks are commented or overwritten.
5. Forced language `EN` on login versus bilingual translation tables.
6. Default report id `timesheet.rpt` may not have an include file.
7. Security group ids 2, 4, 11, 13, and 111 are used as numbers. Only id 1 is explicitly named in a comment.
