# Features — edioverdrive (Transactflow)

Evidence date: 2026-10-16. Source: `main` at `a33b2b78` (2026-10-06).

Status words used here:

- **Current** — present on `main` and wired to a route, cron, or menu.
- **Completed** — called out in `CHANGELOG.md` or a scope spec as delivered, and still in the tree.
- **In development** — code comment or changelog says the behaviour is temporary or a placeholder.
- **Planned** — described as future work without an implementation.
- **Unclear** — code exists; product ownership is not established.
- **Removed** — changelog or composer says it was taken out.

Per-company transport behaviour lives in `scope/company_flow_maps/`. This list does not repeat every retailer format.

## Current — document exchange

| Feature | What it does | Where |
|---|---|---|
| Order ingest | Pull or read purchase orders per active transport and `docs_type` | `CronJobs::getAllOrders`, `GetXMLData` |
| Order export | Push seller-facing order files | `CronJobs::exportAllOrders`, `Orders` |
| Invoice ingest | Read seller invoice files or messages | `CronJobs::getAllInvoices`, `GetXMLData` |
| Invoice export | Send invoices on the transport whose `company_identifier` equals `invoice_name` | `CronJobs::exportAllInvoices`, `Invoices` |
| Order-response ingest | Separate cron from order ingest; DGB CSV to Makro SOAP is the documented live path | `CronJobs::getAllOrderResponses`, `MakroM2NorthSoapService` |
| Claims | Own tables and UI | `Claims`, migrations `2025-08-27-*` |
| Store API | JSON create-order and get-invoices for an organisation JWT | `StoreApiController`, `STORE_API_DOCUMENTATION.md` |
| Duplicate detection | PO + seller GLN; JAB also branch | `GetXMLData::saveOrderToDb` |
| Empty GTIN tolerance | Empty XML GTIN stored as empty string | Changelog, `saveOrderToDb` |
| UI export sandbox | Logged-in exports go to `writable/front_end_exports/` and are not cron input | Changelog |
| Source file view | Roles 1 and 2 can open the original file on orders, invoices, and order responses | List tab views |
| Invoice reprocess name fill | Fills only empty or `Unknown` `invoice_name` | `scope/specs/2026-09-22-invoice-reprocess-unknown-invoice-name.md` |
| Download Reports buyer filter | Short buyer name matches company and store GLNs | `InvoicesModel::applyInvoiceBuyerFilter` |

## Current — master data and admin

| Feature | Where |
|---|---|
| Companies, stores, vendor numbers, transport methods (notes, doc file format, credential expiry) | `Company`, migrations `2026-04-22` and `2026-07-01` |
| Buyer–seller links | `Clients`, `clients` table |
| Products and duplicate scan | `Products` |
| CSV import with saved column maps | `Csv`, `csv_import_templates` |
| Countries and VAT notification | `Countries`, `CronJobs::sendVatUpdateNotification` |
| Client billing data and billing report email | `clients_billing` migration `2025-08-14`, `sendCompaniesBillingReport` |
| Documents browser | `Documents`, `DocumentBrowserService`, menu migration `2026-06-12` |
| Download links list | `DownloadLinksList` |
| Orders and invoices reporting tables | `OrdersReporting`, `InvoicesReporting`, spark commands under `app/Commands/` |
| Chart reports (orders, invoices, claims, daily and monthly) | `app/Views/reports/` |
| Fault finder | `FaultFinder` |
| Server monitoring and site health | `ServerMonitoring`, `SiteHealth`, `MonitorSystemUsage` command |
| System settings: cron, email templates and logs, holidays, blocked users, fault types, general settings, error logs, transport method list | `SystemSettings` routes |
| Users, roles, menu permissions, company access | `Users`, `SystemUsers` |
| Login history and IP block list | `LoginHistory`, `BlockedUsers` |
| Maintenance banner | `MaintenanceMode`, `app/Views/modules/maintenance_*.php` |
| Email pixel | `GET tracking-pixel` (public) |
| ChatGPT page | `ChatGPT`, `chatgpt_helper.php` |
| Slack send | `Slack` (public route) |
| Developer menu management and CRUD generator | `Developers\MenuManagement`, `Developers\CRUDGenerator`. Sidebar hides titles Upload, CRUD Generator, and CSV Upload |

## Current — security and accounts

| Feature | Where |
|---|---|
| Email 2FA and TOTP, with switch back to email | `Users` 2FA routes, `TwoFactorService`, `scope/Task_2FA_TOTP.md` |
| Password policy (12+ and character classes, 72 byte cap) | `app/Libraries/PasswordPolicy.php` |
| `user_passwords` dual-write beside `users.password` | Migrations `2026-05-08-*`, `UserModel` |
| Password rotation emails | `CronJobs::sendPasswordRotationEmails` |
| Stale user deactivation (6 months, not role 1, monthly recurrence 43200) | `deactivateStaleUsers` |
| Lockout: 5 failures in 5 minutes, statuses 1 and 4 only | `LoginHistoryModel::checkFailures` |
| Staff login during maintenance | `/silicon-staff-login` |

## Current — operations

| Feature | Where |
|---|---|
| CronEngine on `GET /cronJobs` | `CronJobs::index` |
| Working window 06:00–22:59 | `CRON_DAILY_WINDOW_START_HOUR = 6` |
| Primary heartbeat (log type 9998) and backup (9997), shutdown trace 9996, engine log 9999 | `CronJobs` constants, `scope/Task_CronEngine_Heartbeat.md` |
| Xneelo crontab poke every 5 minutes; cron-job.org backup heartbeat only | `scope/Task_CronJobs_Implementation_Overview.md` |
| Manual single-job run from Cron Settings | `POST system_settings/runCronJob` |
| Recurrence “2 Times Daily” (`540`) | Migration `2026-08-13-090000` |
| Evening extra run for health digest from 21:00 | `OFF_HOURS_CUTOFF_CRON_FUNCTIONS` |
| Customer vs admin error wording | `formatErrorMessageForCustomer`, `formatErrorMessageForAdmin` |
| Transport credential expiry mail | `notifyTransportCredentialExpiry` |
| Unknown buyer/seller name mail | `notifyReportingUnknownNames` |
| Undownloaded-order alert after 1 hour, 30 minute cooldown | `ORDERS_NOT_DOWNLOADED_ALERT_*` |
| QA and production FTP deploy; shop QA pipeline on branch `storefront` | `bitbucket-pipelines.yml` |
| PHPUnit unit tests and Playwright smoke | `tests/`, `e2e/` |

Named cron methods on `CronJobs` (a row in `cronjob_actions` must point at the method before the engine calls it):

`sendScheduledEmails`, `sendApprovalEmails`, `sendPasswordRotationEmails`, `getAllOrders`, `getAllInvoices`, `getAllOrderResponses`, `exportAllOrders`, `exportAllInvoices`, `exportAllOrderResponses`, `sendDailyReports`, `sendOutstandingInvoicesReport`, `sendDailyReportsToClients`, `sendCompaniesBillingReport`, `deactivateStaleUsers`, `sendCustomerErrorNotification`, `notifyReportingUnknownNames`, `notifyTransportCredentialExpiry`, `notifyCronHealthDigest`.

Which of these rows are `active = 1` in production is **Unknown / Requires Confirmation**. Migrations insert several of them as active. A later admin change would not show in Git.

## Completed recently (still on `main`)

From `CHANGELOG.md` Unreleased, relative to the 2026-10-06 tip:

- Boxer empty-GTIN insert fix (the changelog names PO 175060 on 25 Sep 2026 as the incident).
- Order duplicate key changed to PO + seller so one Boxer PO can exist for two sellers (`feature/1063-boxer-niew-po-store-duplicate`).
- Invoice reprocess leaves real EDI buyer labels alone.
- Download Reports buyer filter uses GLNs.
- Fault-finder mail includes the client.
- Login lockout ignores successful logins.
- SMTP, Slack, OpenAI, and deploy tokens moved to `.env` keys.
- Makro M2North field mapping into TAF export (`buyer_ref_no`, UOM, VAT fallback).
- DGB to Makro order-response CSV, including column drift detection and empty seller-product-code fallback.
- Makro ingest ack is RECEIVED, not REJECTED, on duplicate or empty save.
- Cron heartbeat split between Xneelo and cron-job.org.
- Health-digest HTML attachment (newest 40 document errors per cron).
- Front-end export path separated from `temp_export`.
- Dual 2FA.
- `user_passwords` phase 1.

## In development or placeholder

| Item | Evidence |
|---|---|
| Generic order-response export | `Orders::exportOrderResponsesViaCron` returns “No order-response export pipeline is configured for this company yet.” Ingest for DGB/Makro is separate and is current. |
| Company Admin order-response menu | Comment: hide until buyer/supplier scoping is ready. Filter hard-blocks role 3. |
| Password table | Changelog calls `user_passwords` “phase 1 compatibility”. Legacy `users.password` is still written. |
| Changelog version | Only `[Unreleased]`. No tagged release notes in that file. |
| SonarQube CI step | Commented out in `bitbucket-pipelines.yml`. `sonar-project.properties` and a badge view remain. |

## Planned

No separate roadmap file was found. These are the only explicit “not yet” notes seen in code:

- Order-response export pipeline per company (the notice above).
- Company Admin scoping for order responses (the temporary comment). Removal of the block is described as future, not scheduled.

Jira project `SI` board 12 is linked from `README.md` as “Development TODOS”. The board was not read. Planned work that exists only in Jira is **Unknown / Requires Confirmation**.

## Unclear — present in code, product role not confirmed

| Item | Why it is unclear |
|---|---|
| `Car`, `CarApi`, cost-category models | Controllers and models exist. They are not in the seeded EDI menu titles that were read. No scope doc describes a vehicle or cost-accounting product. |
| Labels, label status, meeting groups, label email templates | Seeded menus from the admin starter. Still routed. How they relate to EDI operations is not stated. |
| `Users_old.php` | Second users controller. `setAutoRoute(true)` may still expose it. |
| `storefront` pipeline branch | Deploys to `FTP_HOST_SHOPQA`. No product doc in this pass defines the shop app. |
| Role id 7 | Treated as admin in one label view. Not in `UserRole` seeder. |
| Organisations REST resource | Documented as a JWT API in `Routes.php` comments. How it relates to `companies` needs confirmation. |

## Removed

| Item | Evidence |
|---|---|
| `aws/aws-sdk-php` | Changelog: removed, no app usage |
| `orhanerday/open-ai` | Changelog: ChatGPT uses cURL helper instead |
| `ezyang/htmlpurifier` as a PhpSpreadsheet 5 transitive | Changelog |

Salesforce and Africa’s Talking files remain, but the functions do not run (commented or empty). Treat them as unused, not as features.

## Deprecated behaviour to avoid

- Matching orders on PO number alone. Current rule is PO + seller GLN.
- Assuming `processFTPFilesIn` runs for every invoice folder. It runs for FTP + `docs_type = transferIn` only (go-live policy).
- Treating `exportAllOrderResponses` as the DGB to Makro sender. That sender is the ingest / `respondOrder` path.
- Using the July 2026 company-id matrix as production without a database check.

## Feature index by source file

- Auth and 2FA: `app/Controllers/Welcome.php`, `app/Controllers/Users.php`, `app/Services/TwoFactorService.php`
- EDI core: `app/Controllers/GetXMLData.php`, `Orders.php`, `Invoices.php`, `Claims.php`, `OrderResponses.php`
- Schedule: `app/Controllers/CronJobs.php`, `scope/Task_CronJobs_Implementation_Overview.md`
- Partner variants: `scope/company_flow_maps/{id}_{slug}.md`
- Store API: `STORE_API_DOCUMENTATION.md`, `app/Controllers/StoreApiController.php`
