# Technical documentation — greenfields_web

Evidence date: 2026-10-13. Source: `main` at `c8fce1f4f9161d73b84b8d38b7ea8546de6ddb15`.

Committed secrets are named by file. Values are not copied.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. `composer.json` requires `^8.0`. `README.md` says 8.3. `overview.md` says `^8.0` |
| Framework | CodeIgniter 4. Lock: `codeigniter4/framework` v4.6.0. Constraint: `^4.0` |
| Package name | `codeigniter4/adminstarter`. Description "Transactflow". License MIT. `LICENSE` is the CodeIgniter / BCIT MIT text |
| Web root | `public/` (`public/index.php`). `spark` is the CLI entry |
| Database | MySQLi. `app/Config/Database.php` default host `localhost`, port 3306, charset `utf8`, collation `utf8_general_ci`, query cache on. `.env` sets database `adminoverdrivev3`, user `root`, password empty |
| Tests DB group | SQLite `:memory:` with prefix `db_` (`Database::$tests`) |
| Front end | Bootstrap, DataTables, Feather icons, jQuery, under `public/assets/`. Admin Overdrive and Transactflow images |
| Timezone | `Africa/Johannesburg` in `App.php` and `BaseController::initController` |
| Session | `.env` sets `CodeIgniter\Session\Handlers\DatabaseHandler`, cookie `ci_session`, save path `sessions`, match IP true, regenerate-destroy true, time-to-update 1440. `App.php` still has file-handler defaults and `sessionExpiration` 7200. Environment values override config when CodeIgniter loads `.env` |
| Cookies | `App.php`: SameSite Lax, Secure false, HTTPOnly false. `.env` cookie lines are commented |

`App.php` `$indexPage` is `index.php`. `$baseURL` in config is empty. `.env` sets `app.baseURL` to `http://localhost/admin-overdrive-v2/`.

## Repository layout

```text
app/Config          routes, filters, database, email, boot environments
app/Controllers     HTTP and cron
app/Models          18 model classes
app/Views           shell, EDI, labels, reports, settings
app/Database        migrations and seeds
app/Filters         Authentication, Authorization, Cors (Cors is not aliased)
app/Helpers         mail, menu, user access, slack, chatgpt, salesforce, africastalking
app/Libraries       JwtLibrary.php
app/Commands        monitor:system
public              front controller, assets, info.php, deploy.php, config.php
tests               CodeIgniter sample tests
vendor              committed Composer install
writable            logs, cache, session files, XML examples, debugbar JSON
```

`vendor/` and `writable/session/` session files are tracked. `.gitignore` does not ignore `.env`, `vendor/`, or `writable/`.

`builds` is a PHP CLI script from the CodeIgniter starter (`php builds release|development`). It is not a build-output directory.

`sonarqube_scan.bat` was listed when this task was created. It is not in `c8fce1f`.

## Application structure

`BaseController` loads helpers `cookie`, `date`, `security`, `menu`, `useraccess`, `africastalking`, `salesforce`, `mail`, `chatgpt`. It connects the default database, starts the session, and loads `UserModel` and `MenuModel` on every controller request.

`Common.php` defines `print_rr`, `logThis` (writes `{name}.txt` in the process working directory), `statusDisplay`, and `contentTypeDisplay`.

Auto-routing is enabled in `app/Config/Routes.php` after the explicit routes. Explicit routes cover login, users, system settings, products, labels, XML intake, fault finder, monitoring, reports (commented list route), developer menus, Slack, ChatGPT, `organisations`, and `store-api`.

Controllers present (31 PHP files): `ApiSimulation`, `ChatGPT`, `CronJobs`, `Csv`, `DbAdmin`, `Email`, `EmailPixelTracking`, `FaultFinder`, `File`, `GetXMLData`, `Home`, `LoginHistory`, `Reports`, `Rest`, `ServerMonitoring`, `SiteHealth`, `Slack`, `Soap`, `Store`, `SystemSettings`, `SystemUsers`, `Test`, `TestLog`, `Upload`, `UploadedFile`, `Users`, `Welcome`, and `Developers\CRUDGenerator`, `DbTableDataView`, `MenuManagement`.

Routes that name classes not in the tree:

- `Products`
- `LabelStatus`, `LabelUserGroups`, `LabelEmailTemplates`
- `Orders` (only `orders/getOrders` is explicit; menu URL `orders` would auto-route)
- `Company` (`company/getVendorNumbers` and related posts)
- `StoreApiController` (`store-api` resource)
- Organisations resource controller (model exists, controller file does not)

Classes instantiated but not in the tree: `OrdersModel`, `ProductsModel`, `EmailTemplateModel`. `LabelModel` is imported; the only `new LabelModel()` found is commented.

## Filters

`app/Config/Filters.php`:

- `isLoggedIn` → `App\Filters\Authentication`. Redirects to `/` when `session('isLoggedIn')` is not true.
- `isGranted` → `App\Filters\Authorization`. Looks up segment 1 in the menu table and `UserModel::checkUserAccess`. Missing menu redirects to `/`. Missing grant redirects to `blocked`.
- CSRF and honeypot aliases exist and are commented out of `$globals`.
- Debug toolbar runs after requests except `chatGPT/*`.

`app/Filters/Cors.php` exists and is not registered in `Filters.php`.

Except lists are in the PRD workflow W3. `Filters_old.php` is not loaded.

## Auth

Login, 2FA, reset, and company scope: `app/Controllers/Welcome.php` and `app/Models/UserModel.php`. Rules are in `business-rules.md` BR-003–BR-017.

`JwtLibrary` is used only by `ApiSimulation`, a `ResourceController` over `AppSessionModel`. No route in `Routes.php` points at `ApiSimulation`. With auto-routing, public methods are reachable only if filters allow the first segment. `apisimulation` is not on the except list and is not a seeded menu, so the grant filter redirects to `/` before the action.

Password reset tokens live in table `password_reset`. There is no migration for that table.

2FA codes live in `email_authentication_codes` (migration `2024-09-12-123957_AddTwoFactorAuthenticationTable.php`).

## Data

### Migrations present

| Table | Migration |
|---|---|
| `users` | `2022-11-20-124016_Users.php` and a later `2022-11-24-100652_User.php` |
| `organisations` | `2022-11-20-124016_Organisations.php` |
| `sessions` | `2022-11-20-124435_Session.php` |
| `user_role` | `2022-11-20-125608_UserRole.php` |
| `user_access` | `2022-11-20-125818_UserAccess.php` |
| `user_menu` | `2022-11-20-130307_UserMenu.php` |
| `user_submenu` | `2022-11-20-130307_UserSubmenu.php` |
| `user_menu_category` | `2022-11-25-102709_UserMenuCategory.php` |
| `media_file` | `2023-06-02-162422_MediaFile.php` |
| `cronjob_log` | `2023-06-02-163712_CronjobLog.php` |
| `cronjob_actions` | `2023-06-04-181855_CronjobActions.php` |
| `settings` | `2023-06-04-181945_Settings.php` |
| `language` | `2023-06-04-182128_Language.php` |
| `user_groups` | `2023-11-28-130041_UserGroups.php` |
| `label_email_template` | `2023-12-18-113954_LabelEmailTemplate.php` |
| `sent_queue` | `2024-01-18-092523_SentQeue.php` (filename spells Qeue) |
| `email_authentication_codes` | `2024-09-12-123957_AddTwoFactorAuthenticationTable.php` |
| `login_history` | `2024-09-18-132105_AddLoginHistoryTable.php` |
| `blocked_users_list` | `2024-09-20-084946_AddBlockedUsersListTable.php` |

### Tables used in code with no migration in this tree

Examples found while reading models and controllers: `companies`, `company_users`, `company_transport_methods`, `transport_method_options`, `vendor_numbers`, `orders` (via missing model), `invoices` (`InvoicesModel`), `products` (via missing model), `fault_type`, `fault_finder_log`, `clients`, `files`, `password_reset`, `blockedusers` (model name; migration name differs), `email_template_types`, `uploaded_file`, `app_session`, `test`, `test_log`, `site_health`.

`InvoicesModel::$table` is `invoices`. `CompanyModel::$table` is `companies`. `FaultFinderModel::$table` is `fault_finder_log`. `CronjobsModel::$table` is `cronjob_log`. `OrganisationModel::$table` is `organisations`. `ClientsModel::$table` is `clients`. `FileModel::$table` is `files`. `LoginHistoryModel::$table` is `login_history`. `BlockedUsersModel::$table` is `blockedusers`.

### Seeds

| Seeder | What it writes |
|---|---|
| `Users` | `admin@overdrive.co.za`, role 1, then calls `UserAccess`, `UserMenu`, `UserMenuCategory`, `UserRole`, `Organisations` |
| `UserRole` | Developer, Admin, Company Admin, Company User |
| `UserMenuCategory` | General, Settings, Developers |
| `UserMenu` | Nine menus. See `features.md` |
| `UserAccess` | Developer grants for those menus |
| `Organisations` | Organisation rows for the resource model |
| `FaultType` | Six fault names and a mail list. Not called by `Users` |
| `TransportMethodOptions` | One row, name `Folder`. Not called by `Users` |
| `ErrorCheckStatus`, `UploadedFile` | Present. Not called by `Users` |
| `Holidays.sql` | SQL file, not a PHP seeder |

`README.md` says to run `php spark db:seed users` (class `Users`).

## EDI processing

Intake entry: `GetXMLData::index`.

Shoprite: `Rest::processRestRequest` GET, Basic auth, headers `ContractID` and `UIUser`, accept XML, `verify` false. Saves a copy under `writable/company_orders_xml/shoprite/rest-orders/shoprite_order_{YmdHis}.xml`. Confirmation: `Rest::processRestOrderConfirmation` posts an `ArrayOfLong` body. Invoice push: `Rest::generateRESTInvoiceShoprite`.

Pick n Pay: `SoapClient` with disk WSDL cache and peer verification off. QA WSDL `https://test.pnpportal.co.za/ediservices_test/wsdl/PullService.wsdl`, UAT endpoint `https://uat.pnpportal.co.za/ediServices/pullservice.svc`. Production WSDL and endpoint are selected only when the company endpoint equals `https://www.pnpportal.co.za/ediServices/pullservice.svc`. Invoice methods: `generateSOAPInvoicePnP`, `generateSOAPInvoiceM2North`.

Folder files: `DirectoryIterator` processors, then `moveFileToDone` or `moveFileToErrors`. Parsers: Shoprite orders, generic orders, Godrej invoices, Namaqua invoices.

`writable/invoice_xml_templates/` holds M2North and Godrej examples. `writable/orders_xml_templates/default_taf_orders_export_xml.xml` is an orders example. `writable/debugbar/` holds debug-bar JSON from April 2025 timestamps in the filenames (`17443…`). Those files are runtime artefacts committed with the base commit.

## Mail, Slack, and other integrations

SMTP settings are properties on `app/Config/Email.php`: host `smtp.transactflow.io`, user `notifications@transactflow.io`, port 465, crypto `ssl`, protocol `smtp`. `SMTPPass` is a non-empty literal in that file.

`.env` comments out `SMTP_HOST`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SENDGRID_API_KEY`, `OPENAI_API_KEY`, and `GOOGLE_MAP_API_KEY`. The live SMTP settings are the Email config class, not those commented keys.

`sendMail` BCC rules and the Slack exclusion are BR-039–BR-043.

`public/deploy.php` is a standalone script, not a CodeIgniter route. It checks a query token and an IP CIDR list, posts to Slack, and runs `/usr/bin/git pull`. The project string is `SAWIS: Label`. The token, Slack bot token, and webhook in that file are secrets and are not copied here. The git pull line that targeted `origin development` is commented; the active command is `git pull` with no branch.

`app/Helpers/salesforce_helper.php` does not connect. `app/Helpers/africastalking_helper.php` is commented, including a sandbox key that is not copied here.

AWS SDK, mPDF, and PhpSpreadsheet are in `composer.json` only.

## Cron and monitoring

`GET /cronJobs` → `CronJobs::index`. Details are PRD workflow W4.

`php spark monitor:system` (`App\Commands\MonitorSystemUsage`) writes `writable/monitoring.pid`, refuses to start when `posix_kill($pid, 0)` says the pid is alive, then loops `monitorSystemUsage()` and `sleep(5)`. The comment on `sleep` says 10 seconds. The call is 5.

`ServerMonitoring` reads `top` and disk output and stores threshold breaches. Routes: `monitoring`, `monitoring/getSystemUsage`, `getSystemUsageChart`, `getMonitorLogs`, `updateAlertThresholds`.

## Environment and deployment

| Source | What it sets |
|---|---|
| `.env` (tracked) | `CI_ENVIRONMENT=development`, `CI_DEVELOPER=jaco`, local base URL, database session, database `adminoverdrivev3`, empty DB password, `encryption.key` (set; not copied) |
| `app/Config/Boot/development.php`, `production.php`, `testing.php` | CodeIgniter environment boot files |
| `app/Config/Email.php` | SMTP, including a password literal |
| `bitbucket-pipelines.yml` | On branch `main` only: full clone, SonarQube scan pipe `sonarsource/sonarqube-scan:3.0.2`, cache `~/.sonar`. Variables `SONAR_HOST_URL` and `SONAR_TOKEN` |
| `sonar-project.properties` | `sonar.projectKey=overdrive_greenfields_web_27a4f6e1-4797-4a41-9f35-778b64b4c283` |
| `README.md` | Local WAMP vhost `adminOverdrive.local` → `c:/wamp64/www/adminOverdrive/public` |
| `public/deploy.php` | IP-restricted `git pull`, not wired to the pipeline |

No Docker, Terraform, or AWS deploy file was found. Supabase database keys in `.env` are commented.

`public/config.php` is an empty PHP file in the web root (`<?php` and a closing tag only). It is not the CodeIgniter `app/Config` tree.

`public/info.php` is `<?php phpinfo(); ?>` and is web-accessible if the vhost document root is `public/`.

## Security notes (evidence, not a review)

- CSRF global filter is off. Auto-routing is on.
- Cron, Slack, the tracking pixel, and the organisation and store API routes skip login.
- Production cron protection is a shared query-string constant in source.
- SMTP password, encryption key, deploy token, and Slack tokens are in tracked files.
- REST and SOAP clients and the Slack helper disable SSL verification.
- Shoprite `UIUser` header is `api_key:api_secret`.
- Password reset does not expire, and the unknown-user response still names the email address.
- 2FA codes are mailed in clear text and are not single-use beyond `status` 1.
- `Welcome::resetPassword` takes the token from the previous URL stored in session.
- Session files are committed under `writable/session/`.
- `MenuManagement` can write new PHP controller and view files from the developer screen.
- Seeded Developer password is a short numeric literal.

## Tests and local commands

`phpunit.xml.dist` bootstraps CodeIgniter's PHPUnit bootstrap, covers `./app` except views and `Routes.php`, and has one suite `App` over `./tests`.

`tests/unit/HealthTest.php` asserts `APPPATH` is defined and that base URL is set in `.env` or config. `tests/database/ExampleDatabaseTest.php` and `tests/session/ExampleSessionTest.php` are framework samples. `tests/README.md` is the CodeIgniter tests readme.

Composer script: `"test": "phpunit"`. README also documents `php spark migrate`, `php spark db:seed users`, `php spark key:generate`, `php spark serve`.

This onboarding did not run the test suite or the application.

## Git history

Four commits on 2025-04-11, all on `main`. No other remote branches. The functional tree arrived in `c8fce1f` ("base commit"). Sonar properties and the pipeline file were added in the two commits before it, after an empty initial commit.

## Docs inside the source repository

| File | What it is |
|---|---|
| `README.md` | Admin Overdrive Starter install notes, PHP 8.3, Jira and Confluence links, a CRUD heading with no body |
| `overview.md` | Directory map and inferences ("likely", "appears"). PHP version and feature list do not match a line-by-line reading of `app/` |
| `app/Views/edioverdrive/readme.txt` | One line: "Put all EDI Overdrive views in here" |
| `LICENSE` | MIT, CodeIgniter |

`README.md` tells the reader to copy `env` to `.env`. There is no `env` file. `.env` is already committed.

## Related Silicon Ark set

`silicon_ark/wca_dms/` documents another Admin Overdrive tree in which several EDI controllers are described as missing. Those controllers are in this repository. The two documents should not be merged until someone confirms they are the same product.
