# Reports Module

**Folder:** `CRUD/reports/` · **Reached from:** navbar → Reports · **Entry page:** `report_selector.php`

Five families of report, of which one — the **monthly invoice reports** — is far larger and more important than the rest, because it is what the month-end billing run is built on.

| Family | Purpose | Folder |
|---|---|---|
| **Monthly invoice** | The month-end billing report, per job type | `invoice_monthly_reports/` (31 files) |
| **Customer** | On-hire and movement reports per customer | `customer_reports/` (8) |
| **Machine** | Certificates, servicing, fitter jobs | `machine_reports/` (5) |
| **Utilisation** | How hard the fleet is working | `utilisation_reports/` (6) |
| **Cross-hire** | X-hire by customer | `Xhire/` (2) |
| **Hickey** | Insurance expiry chasing | `hickey_reports/` (1) |

Most reports follow a **selector → report** pattern: a `*_select.php` page collects the date range/customer, then hands off to the report page.

> Documented to the format set by [sites.md](sites.md).

---

## Monthly invoice reports

```mermaid
flowchart TD
    SEL[Month selector<br/>all_inv_month_report_select.php] --> ALL[Monthly report<br/>all_inv_month_report.php]
    ALL -->|per row, by job type| RT{job_type_name}
    RT -->|Self drive| SD[inv_report_update_sd.php]
    RT -->|Operated| OP[inv_report_update_op.php]
    RT -->|Site move| SM[inv_report_update_sm.php]
    RT -->|chargeable| C[inv_report_update_c.php]
    SD & OP & SM & C --> MIC[(monthly_invoice_check<br/>mic_invoice_no)]
    OPH[op_hours_lib.php<br/>op_hours_rules.php] -.operator hours.-> OP
    SQLLIB[sql/inv_monthly_report_sql.php] -.shared SQL.-> ALL
```

![Report Selector](../screenshots/reports__selector.png)

![Month Selector](../screenshots/reports__month_select.png)

`all_inv_month_report.php` lists everything billable for a month, then links each row to a **job-type-specific detail page** where the invoice number is recorded against `monthly_invoice_check`.

![Monthly Report](../screenshots/reports__all_month.png)

### The `_next` generation

Most of these pages exist twice — e.g. `all_inv_month_report.php` **and** `all_inv_month_report_next.php`, `inv_report_update_sd.php` and `..._sd_next.php`.

**These are not duplicates.** They have genuinely diverged (the `_next` versions are roughly twice the size — `sd_next` differs from `sd` by 1,250 lines) and represent a **newer generation of the report** running alongside the old. Both are reachable. `chargeable` is the exception: there is no `inv_report_update_c_next.php`, and `all_inv_month_report_next.php` explicitly comments on that.

| Job type | Current | `_next` |
|---|---|---|
| Self drive | `inv_report_update_sd.php` | `inv_report_update_sd_next.php` |
| Operated | `inv_report_update_op.php` | `inv_report_update_op_next.php` |
| Site move | `inv_report_update_sm.php` | `inv_report_update_sm_next.php` |
| Chargeable | `inv_report_update_c.php` | *(none — by design)* |

### Supporting libraries

| File | Purpose |
|---|---|
| `sql/inv_monthly_report_sql.php` | The shared report SQL (782 lines) |
| `op_hours_lib.php` / `op_hours_rules.php` | Operator-hours rules feeding the Operated report |
| `update_report.php` | Save handler |
| `inv_month_report_{c,op,sd,sm}[_next].php` | Thin dispatch stubs (6–11 lines each) |

> ⚠️ `sql/inv_monthly_report_sql.php` is included via `$_SERVER['DOCUMENT_ROOT']`, so **editing it inside a git worktree has no effect** — the include resolves to the main checkout. See the local-testing notes.

---

## Customer reports — `customer_reports/`

On-hire and movement reports, each with its own selector in `customer_reports_selectors/`:

| Report | Selector |
|---|---|
| `onhire_only_cus_report.php` | `onhire_only_cus_select.php` |
| `onhire_cus_monthly_report.php` | `onhire_cus_select_cus.php` |
| `onhire_cus_movementreport.php` | `onhire_cus_movementreport_select.php` |
| `onhire_cus_movement_all_report.php` | `onhire_cus_movement_all_report_select.php` |

Report pages return **HTTP 400 with a plain message** if opened without their parameters — correct behaviour; reach them via the selector.

## Machine reports — `machine_reports/`

![Machine Certificates](../screenshots/reports__machine_certs.png)

| Page | Purpose |
|---|---|
| `address_list_certs.php` | Certificates by site/address |
| `service_fitter.php` | Fitter servicing report |
| `service_fitter_read.php` | One servicing record (`?id=`) |
| `service_fitter_auto.php` | Automated servicing feed (API) |
| `service.php` | **Broken/dead** — see Known issues |

![Service Fitter](../screenshots/reports__service_fitter.png)

## Utilisation — `utilisation_reports/`

![Utilisation](../screenshots/reports__utilisation.png)

`utilisation_index.php` is the entry point, with `utilisation_report.php`, `_by_machine.php`, `_by_machine_pot.php` and `_total.php` as the views.

## Cross-hire & insurance

![Insurance Expiry](../screenshots/reports__expire_insurance.png)

- `Xhire/Xhire_only_cus_report.php` (+ selector) — cross-hire by customer.
- `hickey_reports/expire_insurance.php` — customers whose plant insurance is expiring. **Links to `customers_update_ins.php`**, the clone page missing its document-upload section (see [customers.md](customers.md#known-issues)).

---

## Known issues

Reviewed 2026-07-21. 32 of 41 tested pages were clean 200s; the customer-report 400s are correct parameter validation.

| # | Issue | Status |
|---|---|---|
| 1 | **`inv_report_update_c.php` fatals — and this is on the month-end billing path.** It queries `` `hickeyplant`.`plant_fuel_capacities` ``, a table that **does not exist** in the database. The local DB is a full mirror of the live dump (179 tables), so the table is very likely missing on live too. The schema name is also **hardcoded** (`hickeyplant`), while live's database is `hickeyhub_hickeyplant` — so even if the table existed, the reference would point at the wrong schema. This is the **chargeable** job-type report, reached from `all_inv_month_report.php`; there are 24 chargeable hires. | **Reported, NOT fixed** — needs your knowledge. Does `plant_fuel_capacities` exist on live? Making the JOIN optional would change reported figures, so this must not be guessed at |
| 2 | `inv_report_update_sm.php` and `inv_report_update_sm_next.php` warned `Undefined variable $mic_invoice_no` on every load — assigned inside two conditional branches but read unconditionally at the `$bgColor` line. The sibling `inv_report_update_sd.php` already initialises it. | **Fixed** — added `$mic_invoice_no = '';`, matching the sibling. Warnings now 0 |
| 3 | **A folder literally named `back up/` sits inside the live tree** (`invoice_monthly_reports/month_reports/back up/`, 3 files, ~2,090 lines). It is referenced by **zero** files — the page inventory only marked it "live" because the basenames match the real files. The copies have diverged badly from the live versions (771 and 2,402 differing lines). | **Open** — dead weight; recommend removing from the repo |
| 4 | `machine_reports/service.php` (489 lines) fatals on `Table 'hickeyplant.service_list' doesn't exist` — that table exists nowhere in the database and the file is unreferenced. | **Open** — confirmed dead, safe to delete |
| 5 | Other unreferenced files: `utilisation_reports/utilisation_report1.php`, `month_reports_selectors/{sd,op}_inv_month_report_select[_next].php`, `inv_month_report_sm.php`. | **Open** — the selector variants need care; some `_next` selectors are live |
| 6 | `op_hours_rules.php` runs schema changes on load (the self-migrating pattern seen portal-wide). | **Open** — architectural, see [fleet.md](fleet.md#known-issues) |

> **Note on the inventory's "live" flag:** reference detection matches on **basename**, so a stale copy sharing a filename with a live file inherits its "live" status (as happened with `back up/`). When judging whether something is dead, check the *path* is referenced, not just the name.
