# Invoice Match — PHP UI (`app`)

CodeIgniter 4 + Shield front end for the Invoice Match tool. It is the browser
UI, Shield authentication, workbook uploads, run orchestration from the user's
side, report Excel generation (PhpSpreadsheet) and **all MySQL memory writes**
(`name_map`, `memory_rules`) plus reviewer updates to `results`. It also owns the
database **schema** via CI4 migrations. See `../docs/CONTRACT.md` for the shared
contract (DB schema §2, HTTP API §3, SSE §4, config §6).

## How it fits

```
browser ──HTTP──▶ web (nginx) ──FPM──▶ app (this, CI4+Shield) ──HTTP──▶ service (FastAPI+Strands)
                                           │                                   │
                                           └───────────── MySQL 8 ◀────────────┘
```

- `web` (nginx) is the only container with a published port; it serves
  `public/` and proxies PHP-FPM on `app:9000`.
- The app reads runs/results straight from MySQL for the list and detail views,
  and relays the service's SSE stream to the browser while a run is live
  (`RunController::stream`, proxied to `GET /runs/{id}/stream`).
- The Python `service` is the only component that reaches Odoo or a model
  provider, and the only writer of the run snapshot tables.

## Requirements

- Runs in Docker (`../docker-compose.yml`). The image is `php:8.3-fpm` with
  `pdo_mysql`, `mysqli`, `zip`, `gd` and `intl`.
- Dependencies (declared in `composer.json`, installed by the image / entrypoint):
  `codeigniter4/framework:^4.5`, `codeigniter4/shield:^1.1`,
  `phpoffice/phpspreadsheet:^2.0`.

## Configuration

Non-secret settings and all credentials come from the environment (see
`../.env.example`). Key variables this app reads:

| var | purpose |
| --- | --- |
| `database.default.hostname` / `.database` / `.username` / `.password` | DB connection (compose sets these) |
| `MYSQL_HOST` / `MYSQL_DATABASE` / `MYSQL_USER` / `MYSQL_PASSWORD` | fallback DB connection |
| `SERVICE_URL` | Python service base URL (default `http://service:8000`) |
| `app.baseURL` | public base URL for generated links |
| `encryption.key` | app key — generate with `php spark key:generate` |
| `PROVIDER_DEFAULT` | default provider in the composer dropdown |
| `UPLOAD_DIR` | shared uploads path (default `/data/uploads`) |
| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | seeded admin account (optional) |
| `SEED_ON_BOOT` | if `true`, the entrypoint seeds on first start |

## Migrate & seed

The entrypoint waits for MySQL then runs migrations automatically. To do it by
hand inside the `app` container:

```bash
# Framework + Shield + Settings + app migrations (creates CONTRACT §2 tables
# plus Shield's auth tables):
php spark migrate --all

# Seed name maps, connection secret-references, and the admin user:
php spark db:seed InitialSeeder
```

The app owns exactly the tables in CONTRACT §2 (`runs`, `planned_lines`,
`invoices`, `results`, `name_map`, `memory_rules`, `traces`, `connections`),
created by the migrations in `app/Database/Migrations/`. Shield creates its own
auth tables through its package migrations (hence `--all`).

## Default admin

Seeded by `InitialSeeder` (override with env):

- email: `admin@overdrive.co.za` (or `ADMIN_EMAIL`)
- password: `ChangeMe!12345` (or `ADMIN_PASSWORD`) — **change on first login**
- group: `admin`

Self-registration is disabled; additional reviewers are provisioned by an admin
(`php spark shield:user create`, then `shield:user addgroup`).

## Routes (all behind Shield's `session` filter except auth)

| path | controller | purpose |
| --- | --- | --- |
| `GET /` | `DashboardController::index` | run list + composer |
| `GET /runs/new`, `POST /runs` | `RunController` | upload + start a run |
| `GET /runs/{id}` | `RunController::show` | conversation + result |
| `GET /runs/{id}/stream` | `RunController::stream` | SSE proxy to the service |
| `GET /runs/{id}/report` | `ReportController::download` | Excel report |
| `GET /memory` + POSTs | `MemoryController` | name maps, rules, inline review |

## Notes

- `app/Config/Mimes.php` is a trimmed subset of the framework's mime map (Office,
  web assets, documents) — enough for xlsx uploads/downloads. Extend if needed.
- `public/` holds `index.php`, `.htaccess` and static assets (`assets/css`,
  `assets/js`). The front-end uses plain JS `EventSource` (no build step).
