# Portal Review — Open Items & Questions

Consolidated from the module-by-module review (2026-07-21). Every item below was **found but not actioned**, because it needs your decision, your knowledge of the business, or your go-ahead. Bugs that were safe to fix have already been fixed and are on the `develop` branch.

Grouped by what's needed from you. Each links to the guide with the full detail.

---

## A. Needs your knowledge — do NOT guess

### A1. `inv_report_update_c.php` (chargeable report) — ✅ FIXED 2026-07-22
The chargeable monthly report was the only one never migrated to the current schema: it selected fuel from a phantom `plant_fuel_capacities` table (never created) and hardcoded the `hickeyplant` schema in 63 places (the working sd/op/sm reports do neither — they get fuel from `plant_specifications`).
**Fix (David confirmed chargeable doesn't need fuel):** the phantom fuel table is neutralised in-place (replaced with a `SELECT NULL` derived table, so fuel shows blank and the generated-SQL structure/parens are untouched — no figures change, there was nothing to regress since it fatalled); the 63 `hickeyplant.` prefixes removed so it uses the connected DB like every other report; and `display_errors` made env-aware so the live report renders cleanly. Verified locally: fatal gone, chargeable row renders, SQL executes cleanly.
**Still yours to confirm:** the schema fix can only be proven on live (locally the DB is *literally named* `hickeyplant`, so the change is a no-op here). Please sanity-check the chargeable report on the dev site. **Follow-up:** the whole report family hardcodes `display_errors` on and carries latent undefined-variable warnings — a broader cleanup, not blocking. → [reports.md](guide/reports.md#known-issues)

### A2. Insurance edit route couldn't upload the certificate — ✅ FIXED 2026-07-22
The Insurance Expiry report used to open a near-duplicate page (`customers_update_ins.php`) that was **missing the Evidence Documents upload section** — exactly where you'd attach a renewed certificate.
**Fix:** consolidated to the single `customers_update.php` (which has the upload section). It now takes `?from=insurance` (a whitelisted enum, not a raw URL — no open-redirect) so **Submit and Back return to the Insurance Expiry report when you came from there, and to the customer list otherwise**. The Insurance report links straight to it; the old `customers_update_ins.php` is now a 301 redirect stub (kept so bookmarks still work — safe to delete later). Verified: both nav paths, the POST-submit redirect, the stub redirect, and that the insurance route now shows the Evidence Documents section. → [customers.md](guide/customers.md#known-issues)

### A3. `pdi_repair_982199.php` — ✅ DONE 2026-07-22
David confirmed the one-off PDI repair is complete. The file self-documented *"Delete this file once every row shows filed,"* so it has been **deleted** (it was unreferenced). → [capture-flows.md](guide/capture-flows.md#known-issues)

### A4. "Is this feature actually used?" — several near-empty link tables — ✅ RESOLVED 2026-07-22
Verified each empty table against the code that writes to it. **Five are fully wired up** — real, reachable INSERT paths in live code; they're empty only because staff haven't used the feature yet, not because anything is broken (David's read: "wired up to use, just no data sent to them yet" — confirmed):

| Table | Live write path |
|---|---|
| `supplier_invoice_hire_link` (4 rows) | `supplier_invoice_hire_link_save.php` ← Charge-On & Invoice Review |
| `driver_infringements` | `drivers/record_infringement.php` ← infringements dashboard/history |
| `driver_alerts` | `forms/alerts/alerts_create.php` ← alerts CRUD family + forms dashboard |
| `fitters_report_hire_link` | `forms/fitters_report/fr_charge_lib.php` (FR charge-on lib) |
| `customer_documents` | `customers/customer_documents_upload.php` ← modal on customer page |

**One is genuinely dead:** `internal_invoice_ocr_templates` — no PHP file reads or writes it anywhere (only appears in `docs/database-schema.md`). The internal-invoice OCR that "works really well" doesn't use it: `batch_review.php`'s **Run OCR** button calls `invoice_ocr_run.php`, which shells out to `pdftotext -layout` and applies **hardcoded regexes** (Customer Acc / Invoice No / PO No), writing to `internal_invoice_pages`. The table was created for a *planned* per-layout template approach (mirroring the supplier-invoice overlay templates) that was never built — the simpler text-extraction method proved good enough. **✅ Dropped by David 2026-07-22 (see Section B).** → [invoice-review.md](guide/invoice-review.md#known-issues)

### A5. Offline/tablet capture — ✅ FIXED 2026-07-22 (PDI, TS, POD, FR); MDSC flagged
Investigated the `*_offline_sync.php` endpoints. Only **FR** (fitter's report) was correctly wired; the rest were broken by path/name drift in the service worker `CRUD/global_sw.js`. Fixes:
- **PDI** — SW fetched `/CRUD/pdi/pdi_offline_sync.php` + `/CRUD/pdi/pdi_upload_image.php` (missing `/forms/`); corrected to `/CRUD/forms/pdi/…`.
- **POD** — SW fetched `/CRUD/pod/pod_update.php` which **doesn't exist** (renamed to `pod_offline_sync.php`); repointed to `/CRUD/forms/pod/pod_offline_sync.php` (verified it accepts the SW's multipart `$_POST`; added `fromSync=1`). Precache entries fixed too.
- **TS** — SW fetched `timesheet_offline_sync.php` (real file is `ts_offline_sync.php`); corrected in the fetch + precache. Also `syncTimesheets()` was never drained on `activate`/`syncNow` (only on the Chrome-only `sync` event) — added it to both so iOS/manual sync drains timesheets.
- **SW registration** — `fr_index.php` and `pdi_index.php` registered `/CRUD/pdi/pdi_cache_sw.js` which **doesn't exist** (silent 404); repointed to the real `/CRUD/global_sw.js`. Cache bumped `v20 → v21` so the corrected precache list re-installs.
- **Verified:** all corrected endpoints resolve (no 404s — `fr_offline_sync.php`, the known-good one, returns the same `400`-on-GET as the fixed siblings); SW JS structurally valid; PHP lint clean.
- **⚠️ MDSC excluded — not nearly-done, a stub:** `mdsc_offline_sync.php` is a verbatim copy of `fr_offline_sync.php` (it `INSERT INTO fitter_reports` with `fr_*` fields — nothing medical) and `mdsc_create.php` has **no** client-side IndexedDB capture. Wiring it would corrupt data. Building MDSC offline capture is a real task needing the medical-self-cert table schema + form design — **David's call whether it's wanted** before it's built.
- **Still needs a real device test:** end-to-end (capture offline on a tablet → reconnect → row lands in DB) can only be proven on the dev site with a device — service worker cache lifecycle + background sync are invisible to server-side checks. → [forms-other.md](guide/forms-other.md#known-issues)

---

## B. Confirmed dead — ✅ ALL DELETED 2026-07-22

David approved deleting the whole section. Before deleting, each was **re-verified** unreferenced (path-aware, so live siblings sharing a basename weren't confused for the dead copy) and also checked against JS/HTML for `fetch()`/AJAX callers. That re-check caught **one false positive** — `get_prices.php` is actually live — so it was **kept** (see note below the table). Everything else was removed.

| File(s) | Status | Why it was dead |
|---|---|---|
| `CRUD/functions/holidays.php` + `holidays2.php` | ✅ deleted | Dead working-days prototypes; hardcoded 2019–2020 list + stray `echo` demos. Live reports query the `holiday_dates` table instead. |
| `CRUD/hs/risk_assessment/legacy/` (3 files) | ✅ deleted | Zero references; dead copy of the RA form/management/view trio |
| `CRUD/hs/risk_assessments/` (plural) — whole folder, 4 files | ✅ deleted | **Doc-error catch:** the earlier note wrongly called the *plural* folder "live." The **live** RA system is the *singular* `hs/risk_assessment/` (linked from `document_registry.php` + `h_and_s_dashboard.php` — untouched). The plural folder was a dead island: `ra_master_dashboard.php` (parse error, never ran) + `risk_assessment_{form,management,view}.php` that only linked to each other, no external entry point. Folder removed. |
| `CRUD/fleet/fors/riddor/` (4 files) | ✅ deleted | Dead diverged duplicate of the live `hs/riddor/` |
| `CRUD/reports/.../month_reports/back up/` (3 files) | ✅ deleted | Folder literally named "back up"; zero references |
| `CRUD/hs/risk_assessments/ra_master_dashboard.php` | ✅ deleted | Parse error in its only commit — never ran; unreferenced |
| `CRUD/maps/maps_add_lat_long_pc.php` | ✅ deleted | Fatals (`Class "Database" not found`); unreferenced |
| `CRUD/reports/machine_reports/service.php` | ✅ deleted | Fatals on a missing table; only `service_fitter.php` (kept) is linked |
| `CRUD/functions/get_prices_byid.php` | ✅ deleted | Unreferenced; not included by `get_prices.php` or anything else |
| `CRUD/functions/get_prices.php` | ⛔ **KEPT — LIVE** | Fetched by `scripts.html` price-refresh — see note below |
| `CRUD/customers/debtors/api/get_debtors.php` | ✅ deleted | Unreferenced (debtor pages query the DB directly); was unauthenticated |
| `CRUD/customers/statements/api/download_by_account.php` | ✅ deleted | Unreferenced |
| `CRUD/fleet/fleet_reports_api.php` | ✅ deleted | Unreferenced |
| `CRUD/fleet/map_ultra_low_emission_zone.php` | ✅ deleted | Superseded older version of `map_ulez_zones.php` |
| `CRUD/plant_list/loler_cert_widget.php` | ✅ deleted | Unreferenced |
| `CRUD/hire_list/delivery_actions/{confirmed,pl_confirmed,pl_delivered,pl_collected}.php` (4) | ✅ deleted | Unreferenced variants; only `delivered.php`/`collected.php` are linked (kept) |
| `CRUD/z-dev-tools/phpinfo.php` | ✅ deleted | PHP config dump; one `.htaccess` change from exposure |

**⛔ `get_prices.php` was NOT dead — the old "unreferenced" note was wrong.** It's called via `fetch('/CRUD/functions/get_prices.php', …)` in `scripts.html:163`, powering the live **auto price-suggestion** on hire create/edit (customer/plant/hire-type change → suggested rate). It stays. Only its unused sibling `get_prices_byid.php` was removed.

**✅ DONE 2026-07-22 — `internal_invoice_ocr_templates` dropped by David.** It was empty and unreferenced by any PHP (planned internal-invoice OCR template store, never built; live OCR uses `pdftotext` + regex). `DROP TABLE IF EXISTS internal_invoice_ocr_templates;` has been run on live. No code change was needed.

---

## C. Live but unlinked — do these need a navbar/menu link?

These pages **work** when you type the URL, but nothing in the portal links to them — so staff can't reach them. They probably need a link rather than deletion. (I won't touch the navbar without asking.)

- ~~`CRUD/hs/h_and_s_dashboard.php`~~ — ✅ **RESOLVED 2026-07-22:** its look was merged into the live `hs/hs_dashboard.php` (the real H&S hub, linked from 6 pages) and this file **deleted**. The dead links it carried (`compliance_checklist`, `audit_trail`, `h_and_s_reports`, `safety_metrics`, `trends_analysis` — none exist) were dropped; stats rewired to real tables (`coshh_products` = 23, RA/assessment tables).
- ~~`CRUD/suppliers/supplier_invoices/supplier_invoice_annotate.php`~~ — ✅ **RESOLVED 2026-07-22:** superseded and **deleted**. `supplier_invoice_review.php` has the same PDF.js stamping toolbar (all 5 stamps: to-charge-on / approved / approved+dates / not-damaged / query) writing to the **same** `save_supplier_invoice_annotations.php` store, **plus** text notes and server-side burn/print. Nothing linked to the standalone page. No stamp type lost.
- ~~`CRUD/forms/fitters_report/fr_index.php`~~ — ✅ **RESOLVED 2026-07-22:** now reachable via a **"Fitters Reports"** button on `CRUD/fitter/fitter_index.php` (added alongside a new **"PDI Management"** button → `forms/pdi/pdi_index.php`). No navbar change needed.
- ~~`CRUD/forms/medical_sc/mdsc_index.php`~~ — ✅ **RESOLVED 2026-07-22:** now reachable via a **"Medical Self-Certs"** button on `CRUD/drivers/drivers_index.php` (medical self-certs are driver records, so it lives on the driver dashboard). No navbar change needed.
- ~~`CRUD/plant_specifications/plant_lift_{charts,dimensions,workrange}/upload_*.php` (3 uploaders)~~ — ✅ **RESOLVED 2026-07-22 (superseded, deleted):** replaced by the unified `plant_specifications/upload_spec_document.php` (Document Type dropdown → lift_chart / lift_dimensions / lift_work_range, saving `<Model>.<ext>` into the *same* three folders). The new page is already linked from `uploads.php`. The three old scripts were unreferenced and their error_logs showed they were broken (bad relative includes). Folders + stored lift images kept; each folder's `.htaccess` had its `Require all granted` exception for the old script removed, so the upload folders are now execute-denied with no carve-out (security tightened).

**Section C is now fully resolved** — no navbar changes were required; every item was either given a page-level button or found to be already superseded.

→ [health-safety.md](guide/health-safety.md), [suppliers.md](guide/suppliers.md), [forms-other.md](guide/forms-other.md), [hire-support.md](guide/hire-support.md)

---

## D. Structural — not bugs, but worth a plan

### D1. Copy-paste page families (big maintenance risk)
Pages were cloned to change a back-link, so a fix to one doesn't reach the others. Progress:
- ~~`hire_delete/` — three 168-line files differing by 3 return-URL lines~~ — ✅ **DONE 2026-07-22:** collapsed to a single `delete.php` driven by a whitelisted `?from=` enum (`tocollect` → `to_collect.php`, else `hire_list.php`), carried across the POST via a hidden field so the confirmation returns to the right list. `to_collect.php`'s links re-pointed to `delete.php?from=tocollect`; `delete_tc.php` (redundant) and `delete_pl.php` (was **dead** — no caller; `plantlist_onhire.php` has no delete link) both deleted. Verified both return paths via the guard redirect + confirmation render (no destructive POST run).
- ~~`hire_offhire_cancel/` — three 155-line files~~ — ✅ **DONE 2026-07-22:** collapsed to one `offhire_cancel.php` with a whitelisted `?from=` enum (3 destinations: `plantonhire` → `plantlist_onhire.php`, `tocollect` → `to_collect.php`, else `hire_list.php`), carried across the POST via a hidden field. Callers re-pointed: `plantlist_onhire.php` (×6, via the shared `hphRenderPlantActions()` — its href builder now uses a smart `?`/`&` separator so a cancel-path with a query string stays well-formed), `to_collect.php` (×2), `xh_list.php` (×1); `hire_list.php` already used the base file (default). `offhire_cancel_pl.php` + `offhire_cancel_tocollect.php` deleted. All 3 return paths verified via guard redirect + confirmation render (unknown `from` falls back to `hire_list`; no destructive POST run).
- `customers_update` / `_ins` — ✅ consolidated earlier (A2).
- `plantlist_update*` — ✅ **PARTIALLY DONE 2026-07-22 (twins merged); modernisation deferred (David's product call).**
  - `plantlist_update_hrs.php` is a *separate* "Plant Hours Update" feature (1571-line diff) — left alone.
  - **`_pl` + `_hl` merged** — verified true twins (differ only by 3 return destinations; the whole `$sqlV` query + ~1,180 lines identical bar reformatting). Collapsed into `plantlist_update_pl.php` with a whitelisted `?from=` enum (`hirelist` → `hire_list.php`, default = plant-on-hire: redirect `plantlist_onhire.php`, Back `plantlist_index.php`), carried across the POST via a hidden field. The 2 `_hl` callers (`hire_list.php`, `plant_loler_alert.php`) re-pointed to `plantlist_update_pl.php?...&from=hirelist`; the 8 `_pl` callers unchanged (default). `plantlist_update_hl.php` deleted. Both paths verified via guard redirect + confirmation render (no destructive POST).
  - **⏸️ Deferred — `update.php` modernisation (David's decision).** `update.php` (plant-list/PDI/audit routes) is the *modern* editor: dynamic `hire_plant_additions` system (11 codes), works-docs modal, hardened saves. `_pl` (now also serving hire-list) is the *older* editor with hardcoded equipment checkboxes. Moving the hire-list/on-hire routes onto `update.php` would change save behaviour on core plant records (hardcoded flags → dynamic additions) — a deliberate, separately-tested change, only if David wants the modern UI on those routes. Not a cleanup-pass item.
- `hire_updates/` — ✅ **ASSESSED 2026-07-22: recommend LEAVE AS-IS (not a clone family).** The 8 files are three unrelated groups:
  - **Move trio** `update_move_sd/op/standing` (77–134-line diffs) — *not* return-URL clones. The 77 lines are ~15 of parameterisable text **plus ~60 of genuinely different form structure**: Self-Drive collects Collection Number + Fuel/AdBlue-on-return + a "Change XH to Hickey" button that Standing lacks; Operated swaps in driver fields. Three distinct move operations with different field sets/rules. Merging would thread `if(moveType)` branches through the form + save handler (harder to read than 3 focused files) and risks core hire-move/billing logic (job-type writes, audit triggers, the driver-clearing rule). **Keep separate.**
  - `update_move_XH` ("Swap Fleet Number", 542 lines off), `tocollect_update`, `history_modal_update` — separate pages (500–1,600-line diffs), not clones. **Leave.**
  - `update.php` vs `update_pl.php` (297 lines) — same modern-vs-older split as `plantlist_update`; a deliberate product-decision merge with save-testing **if David wants it**, not a cleanup item.

**D1 conclusion:** the true clone families (`hire_delete`, `hire_offhire_cancel`, `customers_update/_ins`, `plantlist_update` twins) are consolidated. `hire_updates/` is *not* that problem and is best left as-is. Remaining optional item: unify the main hire editor `update.php`/`update_pl.php` (+ the plant `update.php` modernisation) — both David's product calls, not mechanical cleanups.

**The two clean trios (`hire_delete/`, `hire_offhire_cancel/`) are done. The remaining two families (`plantlist_update*`, `hire_updates/`) are real refactors — recommend scoping each on its own rather than a quick pass.**

A `?from=` parameter collapses the delete and off-hire-cancel trios to one file each (delete trio done). Concentrated in customers, plant_list and hire_list — fleet and suppliers were clean. → [hire-list.md](guide/hire-list.md#known-issues)

### D2. ~15 files run schema changes (`ALTER`/`CREATE TABLE`) on every page load — assessed 2026-07-22
Wrapped in try/catch, so it works, but schema state depends on someone visiting a page, and every request fires a DDL statement. **Classified (20 DDL hits total):**
- *Legit, leave alone:* `hs/setup_document_versions.php`, `hs/risk_assessment/run_migration.php` (explicit setup/migration scripts); `op_inv_month_report_select.php` (on-demand report working table).
- *`invoice_review/monthly_cleanup.php`* — ✅ **REWORKED 2026-07-22 (David's request: stop the table-per-month proliferation).** It used to `CREATE TABLE <src>_archive_YYYY_MM` + `INSERT SELECT *` + `TRUNCATE src` for 5 tables — a new table per source **per month**, unmanageable and read by nothing (the only viewer, `view_archives.php`, is folder-based; its `$archiveTableName` was dead code). Now a single `archiveAndClear()` helper folds each source into **one** consolidated `<src>_archive` (append-only: `CREATE … AS SELECT … WHERE 1=0` drops the source PK/auto-increment so ids reused after each TRUNCATE can't collide; carries `archive_row_id` surrogate PK + `archived_period` (YYYY-MM) + `archived_at`). Read a month with `WHERE archived_period='2026-07'`. Verified: cross-month inserts don't collide (throwaway-table test) and the existing per-month tables consolidate cleanly. **Migration for existing tables:** `CRUD/z-dev-tools/consolidate_archive_tables.php` (dry-run default; `--apply` to migrate + drop the per-month tables; `--local` for the mirror). Verified on the mirror — 4 per-month tables folded into 3 consolidated ones (`pdi_uploads_archive` holding 2026-06 + 2026-07 side by side), no data loss.
- *The real drift (~15):* `fleet/fleet_documents.php` + `_update.php`, `fleet/mileage/mileage_recorder.php` + `mileage_detail.php`, the `supplier_invoices` sage/paid/stamp columns (`unpaid_invoices`, `sage_entry`, `supplier_invoice_flag_save`, `supplier_invoice_burn`, `save_supplier_invoice_annotations`, `charge_on_lib`), `forms/fitters_report/fr_charge_lib.php` + `fr_service_lib.php`, `forms/pdi/pdi_upload/upload_form_types.php`, `reports/.../op_hours_rules.php`, `ghg/scope2_index.php`.

**✅ DONE 2026-07-22.** Two phases, both complete:
1. **Migration written & validated** — [docs/migrations/2026-07-22_consolidate_page_load_ddl.sql](migrations/2026-07-22_consolidate_page_load_ddl.sql), idempotent (`IF NOT EXISTS` throughout), ran twice clean on the local mirror. David confirmed all tables/columns exist on live + dev.
2. **Per-load DDL stripped from all 15 files** — the `CREATE`/`ALTER` removed; function shells kept as no-ops so callers are unchanged; the `op_overtime_rules` default-rule seeds (billing config) deliberately KEPT (INSERT IGNORE); the one-time `charge_type` backfill dropped (already run; preserved commented in the migration). All 15 lint clean; spot-checked pages load HTTP 200 with no column errors.

**Consequence:** the migration is now REQUIRED on any fresh deploy — the code no longer self-heals its schema. The 3 remaining DDL files are legitimate and left as-is: `hs/setup_document_versions.php` (setup script), `invoice_review/monthly_cleanup.php` (monthly archive tables), `op_inv_month_report_select.php` (on-demand report table). → [fleet.md](guide/fleet.md#known-issues)

### D3. Driver "PCN" vs "Infringement" terminology — ✅ INVESTIGATED 2026-07-22 (no code change; David's call)
The premise (UI inverted vs table names) **no longer holds** — verified against current code. Each label already matches its table:
- **"Infringement"** (titles "Driver Infringements" / "Record Infringement" / "Infringement History"; dashboard "Infringements") → `driver_infringements`. "PCN" appears nowhere in `CRUD/drivers/`.
- **"Hours / WTD Report"** ("Driver Hours & WTD Report") → `driver_hours_reports`.
- **"PCN"** → the *separate* vehicle module `CRUD/fleet/pcn/` → `pcn_records`. Unrelated to drivers.

So there's nothing to "align" — the code is already table-aligned. An earlier note recorded DaveO wanting the transport-industry convention (label `driver_infringements` as "PCN", `driver_hours_reports` as "Infringements"); the current code does not implement that, and restoring it would make "PCN" ambiguous (vehicle `pcn_records` *and* driver `driver_infringements`). **✅ DECIDED 2026-07-22 (David): keep the current table-aligned naming. No rename. Closed.** → [people.md](guide/people.md#known-issues)

### D4. `header('Location: …')` calls without a following `exit` — ✅ DONE 2026-07-22
Redirect-but-keep-executing bug (the `category_read.php` class). Swept with a detector (`header('Location')` not followed by a stopper within 3 non-blank lines), then hand-verified each. **9 real bugs fixed across 7 files** — all either a guard redirect (`if (null==$id) { header(...); }`) or a post-action redirect (after INSERT/DELETE) that fell through into the page render (undefined-var warnings + a broken form shown after the action):
- `hire_list/hire_updates/history_modal_update.php` (guard)
- `reports/.../inv_report_update_c.php` (guard + post-insert — 2)
- `hire_list/chargeable/chargeable_create.php` (post-insert)
- `missing_info/missing_info_updates/missing_info_update_PO.php` (post-update)
- `plant_list/plantlist_delete.php` (guard + post-delete — 2)
- `plant_list/plant_list_works/plantlist_create.php` (guard + post-insert — 2)

The remaining 12 detector hits are **confirmed false positives** — the `if (cond) { header(A); } else { header(B); } exit;` idiom (fleet deletes, the `*_upload_finalise` next-in-batch redirects, `create_from_hirelist`, `gra/ms/ra_edit`) and the two multi-line `page_save.php` `header('Location:' . fn(…))` calls that have `exit;` after the closing paren. → [small-modules.md](guide/small-modules.md#known-issues)

### D5. `holiday_dates` module DOES feed the report working-day calc — ✅ CORRECTED 2026-07-22
*(Earlier note was wrong.)* Bank holidays maintained in the UI (`holiday_dates/holidaydate_index.php`) **do** drive the monthly reports. Every live report's own `number_of_working_days()` queries the table at runtime — `SELECT hol_date FROM holiday_dates WHERE hol_type='Holiday'` (e.g. `inv_report_update_sd.php:119`) — and subtracts those dates; the `update_move_*` hire handlers read it too. The only hardcoded 2019–2020 holiday lists lived in the dead `functions/holidays.php` + `holidays2.php` prototypes, now **deleted** (see Section B). No action outstanding. → [small-modules.md](guide/small-modules.md#known-issues)

---

## Already fixed (on `develop`, for reference)

16 bugs, incl. live-page fatals (`pod_index` unauthenticated, `PlantData` hardcoded DB name, `site_move_read` fatal, `coshh_product` fatal, `plantlistworks_index` fatal); the unauthenticated-endpoint sweep (`update_single_location`, `fetch_loler_emails`, `timesheet_upload` + 7 more); and 25 orphan-file deletions. Full detail in each guide's *Known issues* table and [security-audit-2026-07.md](security-audit-2026-07.md).
