# Technical documentation — pernod_ricard

Evidence date: 2026-10-07.

Source: `master` at `24c1e1273886b76f61492eeba2ac6b4be38086e5` (2020-11-18).

## Application type

Two classic PHP applications, `supplier/` and `webadmin/`, plus a one-file redirect at the repository root. There is no `composer.json`, no `package.json`, no Docker file, and no pinned PHP version. Database access is `mysqli`. Pages include a local copy of a framework whose classes are named `Nemo*`.

`webadmin/PHPExcel/`, `webadmin/Excel/`, and `webadmin/fpdi/` are vendored libraries used for Excel export and PDF parsing. They are not the product.

Both `system.php` files set `display_errors` to `1` and `error_reporting` to `E_ALL` without `E_STRICT`, `E_NOTICE`, and `E_WARNING`.

## Repository layout

| Path | Role |
|---|---|
| `index.php` | Redirect to the supplier URL |
| `.htaccess` | HTTP to HTTPS |
| `supplier/` | Supplier portal |
| `supplier/_framework/` | Nemo framework copy used by the supplier app |
| `supplier/includes/` | `my.invoice.cls.php`, `my.payment.cls.php`, `user.cls.php`, `faq.cls.php` |
| `supplier/documents/TC/` | Terms PDF linked from the first-login form |
| `webadmin/` | Administration portal and `cron.daily.php` |
| `webadmin/_framework/` | Nemo framework copy, including `_nemo.password.cls.php` |
| `webadmin/includes/` | Invoice, supplier, payment, remittance, AP team, email, FAQ, settings, security group, import, reports |
| `webadmin/documents/payments/` | Directory the payment import scans |
| `webadmin/js/`, `webadmin/css/` | Browser assets, including `passwordstrength.js` |
| `bitbucket-pipelines.yml` | FTP deploy |
| `.git-ftp-ignore` | Excludes the pipeline file from `git ftp` |
| `.gitignore` | Generic template (IDE, logs, media). It does not ignore `system.php` |

`sonar-project.properties` and `sonarqube_scan.bat` are not in this commit. The onboarding scan listed them. Provenance: **Unknown**.

The supplier app loads the password class from `../webadmin/_framework/_nemo.password.cls.php`. Provenance: **Implemented** (`supplier/login.php`, `supplier/index.php`).

## Runtime configuration

Host selection is `$_SERVER[SERVER_NAME]` against keys in `supplier/system.php` and `webadmin/system.php`. A missing name, or the name `prsa-supplierportal.co.za`, is rewritten to `www.prsa-supplierportal.co.za`.

| Key | Database host | Database name | Session directory suffix |
|---|---|---|---|
| `debian` | `192.168.100.6` | `pernodricard_2016` | `/pernod_ricard/{supplier or webadmin}/SESSION/` |
| `lamp` | `localhost` | `pernodricard_2016` | same pattern |
| `dev.overdrive.co.za` | `sql47.jnb1.host-h.net` | `pernodricard_2016` | same pattern. `SERVER_PROTOCOL` forced to `HTTPS` |
| `www.prsa-supplierportal.co.za` | `sql37.jnb2.host-h.net` | `pernoricard_2016` | `/supplier/SESSION/` or `/webadmin/SESSION/` under the document root. `SERVER_PROTOCOL` forced to `HTTPS` |

Usernames are also in those files. Passwords are in those files in plain text. They are omitted from this document. Comment dated 2017-11-02: "Hetzner Breach Reset All FTP/DB details".

`supplier/system.php` still contains a commented earlier QA block (`cmysql5-5.wadns.net`, database `sil27_pernodricard_2016`). The webadmin file does not keep that commented block.

After connect, every row of `sysSettings` is copied into `$SystemSettings` by `strSetting`. Keys the PHP reads by name include:

| Key | Used for |
|---|---|
| `SUPPLIER_Document_Path` | Invoice file directory |
| `SMTP Send As`, `SMTP BCC` | Mail from and BCC |
| `PASSWORD_Enable_Policy`, `PASSWORD_Strength_Check`, `PASSWORD_Strength_Check_MinLength`, `PASSWORD_MaxAge`, `PASSWORD_MaxHistory`, `PASSWORD_Restricted_Words`, `PASSWORD_Reset_Interval` | Password policy |
| `DUE_DATE_MODIFIER`, `OVERDUE_DATE_MODIFIER` | Task 2 cutoffs |
| `OverdueCC` | Combined overdue mail |
| `OverduePaymentsExceptionCC` | Payments exception mail |

`DIR_Download_JDE_Payments` is assigned the literal `???` inside `cron.daily.php` and is not read from `sysSettings`. `DIR_Import_JDE_Payments` is assigned `documents/payments/` in that same script.

Pipeline variables, not in Git: `FTP_USERNAME_QA`, `FTP_PASSWORD_QA`, `FTP_HOST_QA`.

Session garbage-collection lifetime is 7200 seconds. `$HTTP` is hardcoded to `https` after the protocol is parsed.

## Data

There is no schema dump. Tables and views named by SQL in the application:

| Name | Role in the code |
|---|---|
| `sysUser` | Login, terms flag `blnTCAccepted`, `refSecurityGroupID`, `refCompanyID`, `strDepartment`, `blnActive` |
| `sysSecurityGroup` | Group must be active to log in |
| `sysSecurity` | Per-script `blnView`, `blnSave`, `blnDelete`, `blnNew`, `blnSpecial` |
| `sysPassword` | `salt`, `hash`, `dtPassword` |
| `sysLogin` | Login result strings used by the lockout |
| `sysSettings` | Runtime settings |
| `sysMenuLevel1`, `sysMenuLevel2` | Menu labels and URLs |
| `tblInvoice` | Invoice header, status, file-name columns, amounts, dates |
| `tblSupplier` | Supplier, email, `intTerms`, `strAccountCode`, `strDepartment`, `refAPTeamID` |
| `tblAPTeam` | AP team name and email |
| `tblPayment` | Imported or recorded payments, `strProcessStatus` |
| `tmpPayment` | Staging table for the CSV import |
| `tblEmail`, email template table | Outbound mail log and templates (`NemoEmail`) |
| `vieDepartment` | Department filter options |

Comments in `invoice.cls.php` and `supplier.cls.php` also show `ALTER TABLE` statements that were notes in the file, not a migration runner.

Invoice status values written by code: `New`, `Due`, `Overdue`, `Paid`, `Partial Payment`. The webadmin invoice filter lists the first four only. The report filter also lists `Partial Payment`.

Payment process status values written by code: `New`, `Processed`, `Warning`, `Error`.

## Authentication

Email and password on both portals. The password class compares the submitted password to `sysPassword`. The older MD5 path is commented out.

Lockout: the last five login rows for that user, among "Login failed: Incorrect password" and "Login successful", are counted. Five failures with no success in that window set `blnActive` to 0, `strLastUser` to "Security Lockout", and send a mail.

Webadmin group id `10` is redirected to the supplier app after the password succeeds. That branch does not create `$_SESSION[SUPPLIER]`. `webadmin/login.php` unsets `$_SESSION[USER]` only at the start of the page, then writes it again before the group test. Provenance: **Implemented**.

Logout is the framework logout label. The exact session keys cleared were not traced past the translator. **Unknown** beyond the login pages unsetting `SUPPLIER` or `USER` when the login page itself loads.

## Daily script

`webadmin/cron.daily.php` is a web-reachable PHP script. It replaces `$_SESSION[USER]` with the cron user. It is not wrapped in a secret token in the portion that was read. Provenance: **Implemented**. Whether the web server blocks it is **Unknown / Requires Confirmation**.

Task order on the default path: 5 (import), 6 (paid-invoice mail, send commented out), 7 (exception report), 1 (due dates), 2 (status), 3 (due and overdue mail). Task 4 does not run unless `Action` is `TESTING`, and its SQL is commented out.

Import matching (`import.payments.CSV.cls.php`):

- Loads CSV rows into `tmpPayment`.
- Warns when the invoice number does not match and when the PO number does not match.
- Warns when the amount casts to a negative decimal.
- Inserts into `tblPayment` when supplier id and invoice id are non-zero and process status is `New` or empty.
- Sets the invoice to `Paid` or `Partial Payment` from the summed payment amounts, using `ROUND(..., 0)`.

A sample spreadsheet path is commented: `documents/payments/Supplier portal JDE integration example.20180517.xls`. One filename is excluded from archiving in a condition: `JDE Payments.20180706.DO_NOT_ARCHIVE.csv`. The import class comment dates the work to 2018-07-02.

## Email

`NemoEmail::LoadEmailTemplate` loads a template by name and `Substitute` replaces placeholders. Names used by this product:

`Reset Password`, `Send Welcome Email`, `Invoice Query`, `Invoice Paid`, `Paid Invoices to Supplier`, `Payments Exception Report`, `Due Invoices`, `Overdue Invoices`.

The framework file also contains commented course-template examples (`Course Start`, and others) that are not called by the portal pages. Provenance: **Implemented** as leftover examples in `_nemo.email.cls.php`.

Sent and failed messages update `tblEmail.strStatus` to `Sent` or `Error`.

## Deployment

`bitbucket-pipelines.yml` uses image `python:3.5.7`. The step installs `zip`, `git`, and `git-ftp` with `apt-get`. It does not run PHP, tests, or SonarQube.

| Branch in the YAML | Command that is not commented | Remote path |
|---|---|---|
| `master` | `git ftp push` | `ftp://$FTP_HOST_QA/public_html/prsa.overdriveqa.co.za` |
| `development` | `git ftp init` | `ftp://$FTP_HOST_QA/prsa.overdriveqa.co.za` |

Only `master` exists on the remote. Provenance of a successful deploy: **Unknown**.

## Security notes from the tree

These are observations, not a penetration test.

- Database passwords are committed in `supplier/system.php` and `webadmin/system.php`. This document does not repeat them.
- `display_errors` is on, and a failed connection prints `mysqli_connect_error()`.
- The other-file upload writes `$_POST[strOtherFileType]` into the SQL column list.
- `cron.daily.php` has no shared-secret check in the code that was read.
- Invoice files are linked directly when the path exists. No download gateway script was found.
- There is no automated test suite that would catch a regression in login, pay, or import.

## Development and testing process

History is 20 commits, 16–18 November 2020, authors including Bitbucket's online editor and `maanie@overdrive.co.za`. No pull-request merge commits appear in `git log`. No test directory and no PHPUnit configuration were found for the portal.

Local host keys `debian` and `lamp` point at database `pernodricard_2016` with the session path under `/pernod_ricard/`. How a developer was expected to create that database is **Unknown**.

## Monitoring, queues, and workers

No queue, cache, or monitoring configuration is in the repository. The only background-style entry point is `cron.daily.php`, and nothing in Git shows what invokes it.
