# PRD - gsa

Evidence date: 2026-10-13.

Source: `git@bitbucket.org:overdrive/gsa.git`, `master` at `ebb9cca93051bcd23a569d2b0a243a8d465bb740` (2020-08-26).

There is no prior PRD in the source repository or in Silicon Ark under `gsa/`. Requirements below are recovered from the implementation. Provenance is labelled. Database passwords in `include/system.inc.php` and `include/systemLive.inc.php` are not reproduced.

Silicon Ark also documents `thegsa` from a separate repository. Shared branding and schema do not by themselves prove which system is live. That relationship is **Unknown / Requires Confirmation**.

## Product purpose

**Documented.** The HTML title is `GSA`. The footer says `GSA System [v0.0A, 2008]`. The login image is titled `The GSA`.

**Implemented.** Staff manage magazine advertising: advertisers, contacts, sales people, contracts and monthly insertions, contact logs, renewal documents, and reports. A disabled monthly job was written to email advertisers a "GSA Star" summary of bookings in publications marked `blnSTAR`.

**Unknown / Requires Confirmation:** the business owner, whether this `gsa` revision is still in daily use, and whether "The GSA" is a publisher, a sales house, or both.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| Person at the login screen | `login.php`. Username and password | **Implemented** |
| Signed-in user | Cookie `ckyGSA` = `UserID\|strUser`. Renewed by `checkLogin` | **Implemented** |
| Inactive user | `tblUser.blnActive` must be 1. Message: "Login failed, your user account has been disabled." | **Implemented** |
| Page permission | `getAccess` reads `tblSecurity.intLevel` for the page file name and the user. `0` none, `1` view, `2` full. The user form labels these None, View, Full | **Implemented**. Which rows exist in a live DB is **Unknown** (a dump snapshot is in `db/gsa_20200811.sql`) |
| Item permission | `tblSecurityItem` against `tblmenulevel1Item`. Known item ids in code: menu item id `1` (edit-all when level > 1), menu item id `3` (commissions when level > 0), item label `Default` (may set the default contact), item label `Sales Person` (search screen) | **Implemented**. Labels for ids 1 and 3 are **Unknown** from code alone |
| Sales-person scope | `tblUserSalesPerson`. `UserOwnsSalesPerson` must be true, or the user has edit-all, or the sales person id is empty, before an advertiser or contact log can be saved | **Implemented** |
| Cron / import / some reports | `cron.daily.php`, `ximport.php`, `zimport.php`, `renewalpdf.php`, and several report wrappers have `checkLogin` commented out or never call it before work | **Implemented** |
| Deploy operator | No pipeline file in this repository | **Unknown** |

The menu is not hard-coded as labels in application PHP. `include/menu.inc.php` loads `tblmenulevel0` and `tblmenulevel1` where `tblSecurity.intLevel > 0` for the signed-in user. Logout is a fixed last item, `Log Out [username]`, pointing at `login.php`.

A page class with access below 2 renders inputs `disabled`. Methods that list data require access above 0. Methods that save or delete require access above 1. If `getAccess` finds no row, the code still reads `$row->intLevel`. **Unknown** what the runtime does in that case on the live host. `display_errors` behaviour and notice suppression follow the include files.

## Systems the product talks to

1. The browser, over PHP pages and form posts. `contract.php` also returns a small XML document of contacts and contracts when `arefCompanyID` is set, before the login check.
2. MySQL selected in `include/system.inc.php`: host `localhost`, database `gsa`. A commented alternate names host `cmysql5-1.wadns.net`. `systemLive.inc.php` also uses `localhost` / `gsa`. The dump `db/gsa_20200811.sql` header names `thegsegszw_db6` on `dedi483.jnb2.host-h.net`.
3. PHP `mail()` from `NemoEmail::Send` in `_nemo.email.gsa.cls.php`. The STAR script sets From to `GSA STAR Newsletter <info@thegsa.co.za>` and names `sham@the-gsa.co.za` and `jeff@the-gsa.co.za` in the template values. SMTP classes exist under `include/mailer/`. `NemoEmail::Send` uses `mail()`, not those classes as the primary path observed.
4. No first-party HTTP API client, queue, or object store appears in the application PHP.

## Major workflows

### WF-1 - Log in

`login.php` clears `ckyGSA`, then includes the system file.

On `btnLogin`:

1. Load `tblUser` where `strUser` matches the quoted username.
2. If no row: "Login failed, please try again".
3. If `md5($strPassword)` equals `strPassword` and `blnActive == 1`: set cookie `ckyGSA` to `UserID|strUser` for `$intCookieTime` and redirect to `index.php`.
4. If password matches but inactive: message that the account is disabled and to contact a systems administrator.
5. Else: "Login failed, please try again".

Client-side MD5 of the password exists in markup but is not wired to `onclick` (disabled). The server hashes with `md5()`.

Provenance: **Implemented**.

### WF-2 - Use the home page and menu

`index.php` calls `checkLogin()`, prints heading `GSA`, and includes the menu. Menu items come from the security tables for the cookie user. Choosing Log Out returns to `login.php`.

Provenance: **Implemented**.

### WF-3 - Maintain advertisers and contacts

`advertiser.php` loads `Advertiser`. Users with view access can search and list companies. Full access plus sales-person ownership (or edit-all) is required to save. Delete is blocked when contracts or contacts exist. Contacts are edited via `contact.php` / advertiser acts; one default contact per company is supported when the user has the Default item permission.

Provenance: **Implemented**.

### WF-4 - Maintain contracts

`contract.php` calls `checkLogin()` after an optional early XML branch for `arefCompanyID`. Users create and edit contract headers and monthly items (position, size, colour, shape, amounts, skip, cancel, update label). Validation requires advertiser, sales person, status, publication, position, size, items count, payment type, amount, discount, and start date. Duplicate and renew behaviours exist in the contract class. Email preview/send helpers live in backup class `include/zzcontract.cls.php`.

Provenance: **Implemented**.

### WF-5 - Log contact activity

`contactlog.php` records type, status, discussion, action, follow-up, optional contract, and linked magazines. Save is gated by page access and sales-person ownership rules similar to advertisers.

Provenance: **Implemented**.

### WF-6 - Run reports

`reports.php` shows `ReportConsole` with radio choices: Invoice List, Planning List, Cancelled, Move, Prospect (+ by contact, export), Commissions (security item 3), Sales Person Expiry, Contact Report, Client Spend, Income List, Followup, Summarised Totals, Advert Updates. Each opens a matching root script and class under `include/reports/`. Many of those root scripts have `checkLogin()` commented out; the console itself calls `checkLogin()`.

Provenance: **Implemented**.

### WF-7 - Renewal document and PDF

`renewaldoc.php` (login required) builds and stores a renewal document. `renewalpdf.php` outputs FPDF A4 and has `checkLogin()` commented out.

Provenance: **Implemented**.

### WF-8 - STAR newsletter (disabled)

`cron.daily.php` includes the system file, then `die("site inactive 20180108")`. Dead code after that would load magazines with `blnSTAR = 1`, build booking rows for the current year-month, load email template `STAR`, and send via `NemoEmail`.

Provenance: **Implemented** (present, hard-stopped).

### WF-9 - Imports

`ximport.php` and `zimport.php` bulk-load data. `checkLogin()` is commented out.

Provenance: **Implemented**. Operational use is **Unknown**.

## Functional requirements

### FR-001 — Authenticate with username and MD5 password

Requirement: A user can sign in with a username and password. The system stores and compares MD5 hashes and rejects inactive users.

Evidence: `login.php`, `tblUser`.

Implementation: Implemented. Confidence: High.

### FR-002 — Authorise pages and actions by security level

Requirement: Each menu page has None / View / Full access per user. View allows listing; Full allows save/delete. Form controls disable below Full.

Evidence: `getAccess`, `user.cls.php`, page classes.

Implementation: Implemented. Confidence: High.

### FR-003 — Scope advertiser edits by sales person

Requirement: Saving an advertiser or contact log requires Full access and either ownership of the sales person, edit-all, or an empty sales person id.

Evidence: `UserOwnsSalesPerson`, `Advertiser::Save`, `ContactLog::Save`.

Implementation: Implemented. Confidence: High.

### FR-004 — Manage advertisers

Requirement: Users can search, list, create, update, and (when allowed) delete advertisers with company fields, VAT, account number, agency/bad-payer flags, salesperson, and positions.

Evidence: `advertiser.php`, `include/advertiser.cls.php`, `tblCompany`.

Implementation: Implemented. Confidence: High.

### FR-005 — Manage contacts

Requirement: Users can maintain contacts under a company, including default and subscribe status.

Evidence: `contact.php`, `include/contact.cls.php`, `tblContact`.

Implementation: Implemented. Confidence: High.

### FR-006 — Manage contracts and monthly items

Requirement: Users can create and edit contracts against publications with payment type, amount, discount, free flag, and a series of monthly items that can be skipped, cancelled, or labelled.

Evidence: `contract.php`, `contract.cls.php`, `contractitem.cls.php`, `tblContract`, `tblContractItem`.

Implementation: Implemented. Confidence: High.

### FR-007 — Contact logging

Requirement: Users can record contact-log entries with type, status, follow-up, and magazine links.

Evidence: `contactlog.php`, related lookup pages.

Implementation: Implemented. Confidence: High.

### FR-008 — Operational reports

Requirement: Users can open a report console and run the listed operational reports (view / export / PDF patterns as coded per report).

Evidence: `reports.php`, `include/reports/`.

Implementation: Implemented. Confidence: High.

### FR-009 — Commission report gated by item security

Requirement: The Commissions report control is enabled only when `tblSecurityItem` for menu item id 3 has `intLevel > 0` for the user.

Evidence: `ReportConsole::Show`.

Implementation: Implemented. Confidence: High.

### FR-010 — Renewal document and PDF

Requirement: Users can create renewal documents and generate a renewal PDF.

Evidence: `renewaldoc.php`, `renewalpdf.php`.

Implementation: Implemented. Confidence: High. Note: PDF entry skips login check in this revision.

### FR-011 — Maintain lookup reference data

Requirement: Users with access can maintain branch, colour, magazine, payment type, position, sales person, shape, size, statuses, title, update label, contact-log type/status, subscribe status, email template, and users.

Evidence: matching root PHP pages and `include/*.cls.php`.

Implementation: Implemented. Confidence: High.

### FR-012 — STAR monthly newsletter

Requirement: On a schedule, email advertisers a STAR summary for magazines with `blnSTAR = 1`.

Evidence: `cron.daily.php`.

Implementation: Present but disabled (`site inactive 20180108`). Confidence: High for code; **Unknown** whether a replacement exists outside this repo.

### FR-013 — Company XML for contracts UI

Requirement: When `arefCompanyID` is supplied to `contract.php`, return XML of contacts and open contracts for that company before login.

Evidence: top of `contract.php`.

Implementation: Implemented. Confidence: High.

## Important constraints

- Passwords are MD5 and the auth cookie is not signed (**Implemented** security behaviour; not endorsed as modern practice).
- `gpc_extract` promotes request and cookie values into globals (**Implemented**).
- STAR mail and several unauthenticated entry points exist (**Implemented**).
- No automated tests or CI config in this repository (**Implemented** absence).

## Assumptions

None are treated as requirements. The following remain **Unknown / Requires Confirmation**:

1. Whether production still runs this `gsa` tree or the newer `thegsa` tree.
2. Which bootstrap file and database host production uses.
3. Whether STAR newsletter was replaced after 2018.
4. Business meaning of status IDs and listing tiers beyond data text.
5. Whether `settings.php` and `ajax.php` are used (they reference missing or broken paths).
