# PRD — convergence_production (Convergence 2017)

Evidence date: 2026-10-18.

Source: `git@bitbucket.org:overdrive/convergence_production.git`, `master` at `a91a555` (2021-06-15).

There is no prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from the implementation. Provenance is labelled. Credentials found in source are not reproduced.

## Product purpose

**Implemented.** Convergence is the staff application Silicon Overdrive uses to run client support, quoting, and internal HR.

Evidence:

- Shell logo and footer in `layouts/layout.back.incl.php`: Silicon Overdrive.
- Email subjects in `cron.hourly.php`: "Convergence: New Ticket: …".
- Live host and database names in `system.php`: `convergence.overdrive.co.za`, `convergence_2017`.
- Helpdesk address on `under.construction.php`: `helpdesk@overdrive.co.za`.

**Unknown / Requires Confirmation:** the commercial name used with clients, and whether a successor system replaced this one after 2021.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| Staff user | `login.php` loads `sysUser` by email, joined to an active `sysSecurityGroup` | **Implemented** |
| Security group | Menu and flags come from `sysSecurity` for `$_SESSION[USER]->SECURITYGROUPID` | **Implemented**. Group names are data, not code |
| Department member | `sysUser.refDepartmentID` is stored on the session at login | **Implemented** |
| HR operator | Leave delete runs only when `SECURITYGROUPID == 3`. Leave buttons use `sysSecurity.blnSpecial` to treat the user as HR | **Implemented**. The number `3` is hardcoded. Whether group 3 is still "HR" is **Unknown / Requires Confirmation** |
| Line manager | Up to four line-manager ids on the staff record, matched to the logged-in user id | **Implemented** in `hr.leave.application.php` |
| Staff member on their own leave | Logged-in user id equals `refStaffID` | **Implemented** |
| Client contact | `ticket.ops.php` authorize and decline links carry `TicketID` and `ContactID`. The page uses `NemoBasic`, so it does not require a staff session | **Implemented** |
| Helpdesk mailbox | Email Scry reads mail addressed to `helpdesk@overdrive.co.za` | **Implemented** as a described process in `cron.email.scry.php` |
| Cron | `cron.*.php` construct `NemoBasic` and run without a user session. Empty server name is rewritten to the live host | **Implemented** |
| Guest | `Nemo` sends a missing session to `login.php`. A 2017-03-07 comment says guest access was turned off | **Implemented** for pages that construct `Nemo` |

The menu the user sees is whatever `sysMenuLevel1` / `sysMenuLevel2` rows their group may view. Those labels are not in git.

## Systems the product talks to

1. The staff browser, posting forms to the same PHP page (`frmNemo` in `layout.back.incl.php`).
2. MySQL database `convergence_2017` (application data and `sysSettings`).
3. MySQL database `convergence_2017_aux` (email rows and templates, via `switchBD` in `_nemo.email.cls.php`).
4. The server’s `mail()` function for outbound mail.
5. An Office 365 mailbox for inbound helpdesk mail. Host, username, and password are `sysSettings` keys. A sample INSERT in `emailscry/classes/class.MailFetch.php` contains a mailbox password. That secret is not copied here.
6. `http://www.mymobileapi.com/api5/http5.aspx` for SMS. Username, password, and live flag are `sysSettings` keys `smsUsername`, `smsPassword`, `smsLive`.
7. The filesystem: PHP sessions, uploads, profile pictures, training certificates, and FIM’s scan of the environment root.

There is no OAuth provider, payment gateway, or message queue in the tree.

## Major workflows

### WF-1 — Sign in

`login.php` (`Action=Login`):

1. Clears `$_SESSION[USER]`.
2. Selects `sysUser` where `strEmail` matches and the security group has `blnActive = 1`.
3. If the user row has `blnActive != 1`, the message is "User account is inactive."
4. Otherwise `NemoPassword::checkPassword("User", …)` compares a hash of salt plus password.
5. On success the session stores user id, username, email, security group id, and department id, writes "Login successful" to `sysLogin`, and redirects to `index.php`.
6. On failure it writes a `sysLogin` result and shows a warning.

`CheckSecurityLock` looks at the last five `sysLogin` rows that are either "Login failed: Incorrect password" or "Login successful". Five failures without a success in that window set `sysUser.blnActive = 0`, attribute the change to "Security Lockout", and email the user.

Password policy defaults, inserted only when `sysPassword` does not exist (`_nemo.password.cls.php`):

| Setting | Default written by code |
|---|---|
| `PASSWORD_Enable_Policy` | `1` |
| `PASSWORD_Strength_Check_MinLength` | `8` |
| `PASSWORD_MaxHistory` | `8` |
| `PASSWORD_Restricted_Words` | `password\|123` |
| `PASSWORD_Reset_Interval` | `60 MINUTE` |
| `PASSWORD_MaxAge` | `false` |

Live values can differ because `sysSettings` overrides these after the table exists. Provenance: **Implemented**, with live values **Unknown**.

"Lost your password?" posts to `ajaxFunctions.php` with `type=SendResetEmail`.

### WF-2 — Open the home dashboard

`index.php` requires a session. It renders:

- Tile stats (`Dashboard::TileStats`).
- A 30-day growth chart compared with the same 30 days a year earlier (`Dashboard::GrowthChart`).
- Ticket stats from 1 January of the current year through today.
- Outstanding tasks and in-progress tasks (`Dashboard::TodoTickets`).
- Project WBS only when `SERVER_NAME` is `lamp` (`Dashboard::RenderProjectWBS`). The comment dates this to 2018-08-27.

Provenance: **Implemented**.

### WF-3 — Work a ticket

`ticket.php` and `includes/ticket.cls.php` persist `crmTicket`.

Staff can list, filter (including status and priority), save, delete, complete, archive, merge, and email a ticket. Tasks live on `crmTask` and are edited from `task.php`.

Status constants in code:

| Constant | Value |
|---|---|
| `STATUS_NEW` | `New` |
| `STATUS_PENDING_AUTH` | `New: Pending Authorization` |
| `STATUS_PENDING` | `Pending` |

`Complete` sets status `Completed`. `Archive` sets `blnArchive = 1`. Client decline sets status `Closed`, `blnArchive = 1`, and a completed date. Client authorize sets status `New: Authorized`.

Other status labels come from `crmDepartmentStatus` where `strProcess = 'Ticket'` (the unused helper `XgetTicketStatusSQL` reads `vieDepartmentStatus`). The full status catalogue is data. Provenance: **Implemented** for the constants above. The rest is **Unknown** without the database.

Default lists exclude `blnArchive = 1`.

### WF-4 — Client authorises a ticket

When `crmClient.blnRequiresAuthorisation = 1` and the ticket has no approval date, the ticket screen shows "Ticket needs approval" and a request-authorisation action (`ticket.php`).

`Ticket::RequestAuthorization` builds authorize and decline links. `ticket.ops.php?Action=Approved` calls `Ticket::AuthorizeClient`. `Action=Declined` calls `Ticket::DeclineClient`.

`ticket.ops.php` does not construct `Nemo`, so it does not check a staff session. The link is a GET with `TicketID` and `ContactID`. Provenance: **Implemented**. Whether that unauthenticated link is still acceptable is **Requires Confirmation**.

If the client does not require authorisation, the approval fields are hidden. A commented line would have forced status `New`. That assignment is not active.

While status is `New: Pending Authorization`, the status control is read-only.

### WF-5 — Inbound helpdesk mail

`cron.email.scry.php` describes the process (comment dated 2018-02-08):

1. Connect to the mailbox from settings.
2. Fetch mail whose To or Cc is `helpdesk@overdrive.co.za` and whose date is after the latest `tblEmail.dtEmail`.
3. If the subject has no ticket id, match the From address to `crmContact.strEmail`, else match the domain to `crmClient`, else create a client and contact.
4. Create a ticket and a task, store the email on `tblEmail` with `refTicketID`, and store attachments.
5. If the subject has a ticket id and that ticket is archived, create a new ticket instead of appending.

Provenance: **Documented in the file header** and **Implemented** in `emailscry/classes/class.MailFetch.php` (the class comment says it was reworked for Email Scry and calls `Ticket::Save()` and `Task::Save()`). The mailbox credentials in the comment are not reproduced.

### WF-6 — SLA becomes tickets

Two cron pages create operational tickets from SLA definitions:

- `cron.daily.createticket.php` (comment 2017-06-22): create tickets and tasks for SLAs.
- `cron.daily.tickettask.php` and daily task 4 in `cron.daily.php`: for each active `slaTicket` that has not terminated, read `slaSchedule` and create `crmTicket` / `crmTask` when the weekly, monthly, or yearly schedule matches today.

Provenance: **Implemented**.

### WF-7 — Quote and convert

`proposal.php` / `includes/proposal.cls.php` store `crmProposal`, groups (`crmProposalGroup`), and items (`crmProposalItem`).

Staff can save, delete, number, duplicate, revise, price, order from a supplier, reorder lines, refresh item cost from the product, generate a sectional PDF (mPDF), and email the proposal.

`NewRevision` sets the previous proposal’s status to `3c. Revised`. `Duplicate` sets the new row’s status to `Draft`. Other statuses are `crmDepartmentStatus` values (a commented foreign key points `crmProposal.strStatus` at that table).

`createTmpTicket` / `createTicketFromTmp` build tickets and tasks from proposal groups and items. New ticket and task status in that insert is `New`. Task priority in that insert is `Medium`.

Provenance: **Implemented**.

### WF-8 — Leave

`hr.leave.application.php` and `includes/hr.leave.application.cls.php`.

Statuses: `New`, `Submitted`, `Pending Approval`, `Approved`, `Declined`, `Cancelled`.

The on-screen steps (`getLeaveApplicationSteps`) say:

1. Create and save.
2. Submit. The applicant cannot change the application after submit.
3. After HR accepts it, line managers give final approval. HR or a line manager can decline. The system notifies the applicant.
4. Time off.

Code transitions:

| Action | New status |
|---|---|
| `Submit` | `Submitted` |
| `ApproveHR` | `Pending Approval` (stores selected line managers in `strNotifyLineManagers`) |
| `ApproveMAN` | `Approved` (sets approver and approval date) |
| `Decline` | `Declined` (stores `txtDeclineNotes`) |
| `Cancel` | `Cancelled` |
| `Delete` | Sets `blnActive = 0` only when security group id is `3` |

Who sees which button is a matrix of Staff, Manager, HR, and HR_Manager against status. HR is `blnSpecial = 1` on the page security row. Manager is a line-manager id match. HR_Manager is both, and is then set back to HR after the button matrix is applied. Provenance: **Implemented**.

Working days (`calculateDays`) skip Saturdays, Sundays, and active `hrPublicHoliday` dates between start and end, then multiply by `hrLeaveType.dblLeaveDays`. The loop stops and returns `-1` after 90 iterations. Provenance: **Implemented**.

Hourly cron cases `Leave.Submit`, `Leave.Decline`, `Leave.ApproveMAN`, and `Leave.ApproveHR` exist and are inside block comments. Daily cron still sends leave reminders (tasks 5 and 6). Provenance: the hourly leave mail cases are **commented out**. Whether a database trigger still queues them is **Unknown**.

### WF-9 — Performance review

`hr.performance.review.php`, `hr.performance.review.initialize.php`, `hr.survey.wizard.php`, `hr.my.performance.review.php`.

A review (`hrPerformanceReview`) has a staff member, a line manager, a role, and up to four 360 reviewers (two added in a 2019-11-29 comment, two more in a 2019-12-09 comment).

The survey wizard steps are: Hard KPAs and KPIs, Soft KPAs and KPIs, Job role objectives and goals, Personal development plan, Employee satisfaction. The manager’s final step has a confirmation checkbox (2019-06-21 comment).

Hourly cron sends `PR.INI.EE`, `PR.INI.360`, `PR.INI.MAN`, and `PR.SubmitMAN`. Daily cron reminds HR (task 7) and reminds "R3" staff to submit (task 8). Provenance: **Implemented**. What "R3" means beyond that comment is **Unknown / Requires Confirmation**.

### WF-10 — Scheduled reminders

`cron.daily.php` tasks, each gated by a `sysSettings` enabled flag:

| Task | What it does |
|---|---|
| 1 | Email the service delivery manager about tickets whose follow-up date is today |
| 2 | Email technicians about outstanding tasks. Includes "View Task" and "Mark as Completed" links (2019-01-17) |
| 3 | Email the salesperson about proposals due for follow-up |
| 4 | Create tickets and tasks from SLA schedules |
| 5 | Remind HR and managers about pending leave |
| 6 | Remind staff to submit a leave application |
| 7 | Remind HR about performance reviews due for follow-up |
| 8 | Remind R3 staff to submit a review |

`cron.hourly.php` sends `Ticket.New` (to the service delivery manager), `Ticket.Notification` (to the contact), and `Task.Assigned` (to the technician, skipping tasks on archived tickets). Performance-review initials are also hourly. Reschedule offsets for High, Medium, Low, and Leave come from `sysSettings`.

Provenance: **Implemented**. The clock that hits these URLs is **Unknown**.

## Functional requirements

### FR-001 — Authenticated shell

The system shows the Silicon Overdrive shell only after a successful login. Pages that construct `Nemo` redirect to `login.php` when `$_SESSION[USER]` is missing.

- Actor: staff user.
- Trigger: opening a back-office page.
- Output: redirect to `login.php`, or the layout in `layout.back.incl.php`.
- Evidence: `_framework/_nemo.cls.php` constructor; `layouts/layout.back.incl.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-002 — Account lockout

After five consecutive incorrect-password failures, counted from `sysLogin` without an intervening success, the user is set inactive and emailed.

- Actor: system, on a failed login.
- Evidence: `CheckSecurityLock` in `_framework/system.functions.inc.php`.
- Implementation: **Implemented**.
- Confidence: High.
- Note: inactive users and inactive security groups are rejected before the password check. A locked user sees "User account is inactive."

### FR-003 — Menu by security group

The side menu lists level-1 and level-2 items the user’s security group may view (`blnView = 1`), that are active, and whose file exists or that have children.

- Evidence: `_framework/_nemo.menu.cls.php`.
- Implementation: **Implemented**.
- Confidence: High.
- Discrepancy: `iniSecurity()` then forces the current page’s `blnView` to `1`, so a typed URL is not blocked by this flag.

### FR-004 — Permission flags

Each menu item can grant view, save, delete, new, and special. Leave treats special as HR.

- Evidence: `sysSecurity` columns selected in `_nemo.cls.php` and `_nemo.menu.cls.php`; `hr.leave.application.php`.
- Implementation: **Implemented** as data plus the leave interpretation.
- Confidence: High for the columns. Medium for every other page’s use of the flags, because most pages do not re-check them after the forced `blnView`.

### FR-005 — Client and contact records

Staff can list and save clients (`crmClient`), sites (`crmClientSite`), and contacts (`crmContact`). A client has payment terms and an optional default support user. An empty domain is stored as NULL (2018-03-08 comment in `client.cls.php`). An unselected default support user is stored as NULL (2018-04-12).

- Evidence: `client.php`, `client.site.php`, `contact.php`, and their classes.
- Implementation: **Implemented**.
- Confidence: High.

### FR-006 — Ticket lifecycle

Staff can create and update tickets and tasks, filter out archived tickets by default, complete a ticket (status `Completed`), archive it, merge selected tickets into a new ticket with status `New` and archive the sources, and email the ticket.

- Evidence: `includes/ticket.cls.php` (`Save`, `Complete`, `Archive`, `mergeTickets`, `EmailTicket`); `task.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-007 — Client authorisation

When the client requires authorisation, the ticket shows a pending-approval state and the client can open `ticket.ops.php` to authorise (`New: Authorized`) or decline (`Closed` and archived).

- Evidence: `ticket.php`, `Ticket::AuthorizeClient`, `Ticket::DeclineClient`, `ticket.ops.php`.
- Implementation: **Implemented**.
- Confidence: High.
- Constraint: the client link does not require a staff login.

### FR-008 — Email to ticket

Mail to the helpdesk address creates or updates a ticket as described in WF-5, including the archived-ticket branch.

- Evidence: `cron.email.scry.php`, `emailscry/classes/class.MailFetch.php`.
- Implementation: **Implemented**.
- Confidence: Medium. The header is explicit. The full matching branches were not executed against a mailbox.

### FR-009 — SLA schedule materialisation

Active, non-terminated SLA tickets produce operational tickets and tasks when a schedule occurrence falls on the run date.

- Evidence: `cron.daily.php` task 4, `cron.daily.tickettask.php`, `sla.schedule.cls.php`, `sla.ticket.cls.php`.
- Implementation: **Implemented**.
- Confidence: Medium. Schedule matching is in the cron loops. It was not executed.

### FR-010 — Time on a ticket

Staff record time (`crmTime`) against a user, ticket, and task. The list orders by time descending. A 2018-05-17 comment says the default time snaps to the last 30-minute interval.

- Evidence: `time.php`, `includes/time.cls.php`.
- Implementation: **Implemented**.
- Confidence: High for the entity. Medium for the 30-minute snap (comment plus class; the snap function was not line-traced).

### FR-011 — Proposal document

Staff can build a proposal from groups and items, duplicate it as `Draft`, revise it (previous status `3c. Revised`), export a sectional PDF, email it, and create tickets from its lines.

- Evidence: `includes/proposal.cls.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-012 — Products and bundles

Staff maintain products (`crmProduct`), bundles (`crmProductBundle`), and bundle items (`crmProductBundleItem`). Proposal item cost can be refreshed from the product.

- Evidence: `product.php`, `product.bundle.php`, `Proposal::UpdateProposalItemsCostFromProduct`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-013 — Leave application

Staff submit leave. HR acceptance moves it to `Pending Approval`. A line manager’s approval moves it to `Approved`. Decline and cancel set those statuses. Working days exclude weekends and public holidays. Delete (soft, `blnActive = 0`) is limited to security group 3.

- Evidence: `includes/hr.leave.application.cls.php`, `hr.leave.application.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-014 — Leave and HR reports

The report console runs Project Summary, Leave Summary, Annual Leave Cycle, Sick Leave Cycle, and Leave Details. Actions include Run Report, Export, Email Report, Excel, and PDF.

- Evidence: `report.console.php` `addReport` calls.
- Implementation: **Implemented**.
- Confidence: High.
- A second project-summary report is commented out.

### FR-015 — Performance review wizard

HR can initialise a review. The employee, 360 reviewers, and manager are notified. The wizard walks the five steps named in WF-9. The manager confirms before submit.

- Evidence: `hr.survey.wizard.cls.php`, `cron.hourly.php` PR cases, `cron.daily.php` tasks 7 and 8.
- Implementation: **Implemented**.
- Confidence: Medium. Steps and mail cases are in code. The exact status strings are department-status data.

### FR-016 — Training records

Staff maintain training courses, materials, and certificates (`eduTrainingCourse`, `eduTrainingMaterial`, `eduTrainingCertificate`). Certificate files are stored under `edulab/training_certificates/`.

- Evidence: `edu.training.*.php`, `edulab/training_certificates/`.
- Implementation: **Implemented**.
- Confidence: High for the screens and tables. The files in `edulab` include sample images and a CPD PDF. Whether they are production records or test uploads is **Unknown**.

### FR-017 — Client servers and updates

Staff record a client server (`soServer`: name, IP, OS, client) and a software update (`soUpdate`: update, repository, current version, available version, rating). An update can be linked to servers (`soServerUpdate`). A CSV importer writes those tables (`includes/import.updates.CSV.cls.php`).

- Evidence: `so.server.cls.php`, `so.update.cls.php`, `so.server.update.cls.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-018 — Reference data

Staff maintain departments, department statuses (process, department, order, action), service boards, fault codes, fix codes, payment terms, FAQ, email templates, users, security groups, public holidays, leave types, roles, core values, KPAs, and KPIs.

- Evidence: the matching root pages and `includes/*.cls.php` `NemoDatabase` table names.
- Implementation: **Implemented**.
- Confidence: High.

### FR-019 — Maintenance mode

When `sysSettings.SYSTEM_UNDER_CONSTRUCTION` is `1`, `system.php` clears the session and redirects every script except `under.construction.php` to that page. The page shows a Silicon Overdrive message and the helpdesk email.

- Evidence: `system.php`, `under.construction.php`.
- Implementation: **Implemented**.
- Confidence: High.

### FR-020 — File integrity job

`cron.fim.php` runs `NemoFIM` about every two hours (comment). It can reset a master snapshot or ignore a path. Excluded path fragments come from `sysSettings` key `FIM_EXCLUDE_PATH_LIKE`. Audit rows go to `sysAudit_$env`.

- Evidence: `cron.fim.php`, `_framework/_nemo.fim.cls.php`.
- Implementation: **Implemented**.
- Confidence: Medium. The scan loop was not executed.

### FR-021 — Audit

Saves on the main entities construct `NemoAudit` and write `sysAuditLog`. Communication events can write `sysCommsLog`.

- Evidence: `_framework/_nemo.audit.cls.php` and the `new NemoAudit` calls in entity classes.
- Implementation: **Implemented**.
- Confidence: High.

## Important constraints

- One PHP page per screen, form POST back to itself, no front-end framework.
- Business status labels for tickets, tasks, and proposals are rows in `crmDepartmentStatus`, not a fixed enum, except for the constants named in this PRD.
- Sessions last three days (`session.gc_maxlifetime` 259200) and are files under the environment’s `sessionpath`.
- `lamp` is configured at the live database (2018-08-06 comment).
- Queries concatenate request values. `NemoDatabase::qs` exists. Many queries do not use it.
- `display_errors` is on in `system.php`.

## Assumptions (not requirements)

- That security group 3 is the HR group. The code compares the id. It does not load the group name.
- That the menu in production points at `ticket.php` rather than `opportunity.php`. The latter is an older ticket copy.
- That Email Scry is scheduled. The page exists. The scheduler does not.

## Requires confirmation

1. Is `convergence.overdrive.co.za` still served from this tree?
2. Should the `blnView = 1` override in `iniSecurity()` stay?
3. Should client authorize/decline links remain unauthenticated?
4. Are the hardcoded database and mailbox secrets still valid, and have they been rotated since they were committed?
5. Which of the parallel copies (`.pj`, `.russel`, dated `20210315`, `proposal.bak`) is the page the menu opens?
6. Is the opportunity mock intended to replace the ticket list, or is it abandoned?
7. What does the performance-review label "R3" mean?
8. Are the files under `edulab/training_certificates/` production data?
