# People — Drivers, Fitters, Contacts & Users

**Folders:** `CRUD/drivers/`, `CRUD/fitter/`, `CRUD/contacts/`, `CRUD/users/` · **Reached from:** navbar → Drivers / Fitter / Users

Four small modules covering the people the portal knows about — the drivers who operate and deliver, the fitters who service machines, customer contacts, and the portal's own logins.

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

---

## Drivers — `CRUD/drivers/` (18 files)

![Drivers](../screenshots/people__drivers_index.png)

The driver database (`drivers`, 607 rows) plus three compliance areas: **licence checks**, **PCNs/infringements**, and **hours / Working Time Directive**.

| Page | Purpose |
|---|---|
| `drivers_index.php` | All drivers |
| `driver_list.php` | Hickey drivers list |
| `drivers_create.php` / `drivers_update.php` / `drivers_read.php` | Maintain a driver (`?id=`) |
| `drivers_add_update.php` | AJAX add/update |
| `driver_actions/driver_archive.php` / `_reactivate.php` | Archive rather than delete |
| `driver_timesheet.php` | A driver's timesheet view |

### ⚠️ Terminology trap — read this before using the driver compliance pages

The UI wording and the table names are **inverted**. This will mislead you when writing queries:

| The UI says… | The data actually lives in… | Rows |
|---|---|---|
| **"PCN"** (Total PCNs, Open PCNs, Latest PCN, "Record new PCN / violation") | `driver_infringements` | 0 |
| **"Infringement" / "Hours & WTD"** | `driver_hours_reports` | 36 |

So `infringements_dashboard.php` is titled *"Driver Infringements"* but its headline counters are **PCNs** drawn from `driver_infringements`, while the body of the report comes from `driver_hours_reports`. Reading the table name as a guide to the feature will send you to the wrong place.

| Page | Reads / writes |
|---|---|
| `infringements_dashboard.php` | Both `driver_infringements` (PCN counters) and `driver_hours_reports` |
| `record_infringement.php` | Writes `driver_infringements` |
| `infringement_history.php` | Reads `driver_infringements` |
| `record_hours_report.php` | Reads/writes `driver_hours_reports` — "Driver Hours & WTD Report" |

![Infringements Dashboard](../screenshots/people__infringements.png)

![Hours Report](../screenshots/people__hours_report.png)

### Licence checks

![Licence Checks](../screenshots/people__licence_checks.png)

`licence_checks_dashboard.php` and `record_licence_check.php` / `licence_check_history.php`, backed by `driver_licence_checks` (27 rows).

---

## Fitters — `CRUD/fitter/` (12 files)

![Fitter Dashboard](../screenshots/people__fitter_index.png)

The fitter job board (`fitter`, 1,159 rows) — jobs raised against machines and worked through by the fitters. Distinct from a **fitter's report**, which is the completed paperwork and lives in [forms](forms-other.md#fitters-reports--fitters_report).

| Page | Purpose |
|---|---|
| `fitter_index.php` | The job dashboard |
| `fitter_create.php` / `fitter_update.php` / `fitter_read.php` | Maintain a job |
| `fitter_actions/fitter_complete.php` | Mark complete |
| `fitter_actions/fitter_complete_hl.php` | Same, but returns to the hire list |
| `fitter_actions/fitter_delete.php` | Delete a job |
| `fitter_alert_bar.php` | Alert strip included by other pages |
| `fitter_reliability_report.php` | Machine reliability from fitter job history |
| `fitter_iso_report.php` | ISO reporting view |
| `pdi_upload_handler.php` / `pdi_download.php` | PDI attachments on a fitter job |

![Fitter Reliability](../screenshots/people__fitter_reliability.png)

---

## Contacts — `CRUD/contacts/` (7 files)

![Contacts](../screenshots/people__contacts.png)

Customer contacts (`contact_list`, 245 rows) — names, phone numbers and emails linked to a customer account.

| Page | Purpose |
|---|---|
| `contacts_index.php` | All contacts |
| `contacts_form.php` | The shared add/edit form |
| `contacts_create.php` / `contacts_create_json.php` | Create (page + AJAX) |
| `contacts_update.php` / `contacts_delete.php` | Maintain a contact (`?id=`) |

Contacts feed the LOLER-certificate send flow in [hire-list.md](hire-list.md#loler-sending--send_loler) and the site records in [sites.md](sites.md).

---

## Users — `CRUD/users/` (5 files)

![Users](../screenshots/people__users.png)

Portal logins (`users`, 4 rows). **Manager-only** — every page carries `require_role('manager')` on top of the central guard, because this is the privilege-escalation surface.

| Page | Purpose |
|---|---|
| `users_index.php` | All logins |
| `users_create.php` | Create a login — username, password, role |
| `users_update.php` | Edit username and role (**not** the password) |
| `users_delete.php` | Remove a login |
| `manage_page_permissions.php` | Per-page role rules (`users_page_permissions`, 4 rows) |

![Page Permissions](../screenshots/people__permissions.png)

**Roles:** `manager`, `driver`, `fitter`. Driver and fitter are *field staff* and are confined by `CRUD/auth.php` to the capture flows (PODs, PDIs, timesheets, fitter's reports) — everything else is manager-only by default. See the [security review](../security-audit-2026-07.md) for the role model.

**Passwords:** minimum 10 characters, no forced complexity. There is **no self-service password change or reset** — a manager creates the login, and `users_update.php` never touches the password. Changing someone's password means deleting and recreating the login.

---

## Known issues

Reviewed 2026-07-21.

**No bugs found across all four modules.** Every tested page returned HTTP 200 with no PHP errors, warnings or deprecations. The 302s were id-required pages redirecting correctly, and `users_update.php` returns 400 ("User ID not provided") without one.

| # | Observation | Status |
|---|---|---|
| 1 | The driver **PCN / infringement** terminology is inverted against the table names (see above). | **Open** — worth aligning the wording, or at least keeping this note handy |
| 2 | `driver_infringements` and `driver_alerts` are **empty** (0 rows) while `driver_hours_reports` (36) and `driver_licence_checks` (27) are in use. | **Open** — confirm whether PCN recording is actually used |
| 3 | `fitters_report_hire_link` is empty (0 rows) despite the fitter's-report charge-on feature existing. | **Open** — same question as `supplier_invoice_hire_link` in [suppliers.md](suppliers.md#known-issues) |
| 4 | Unreferenced: `drivers/drivers_list_update.php` (429 lines), `fitter/fitter_read.php`, `contacts/contacts_read.php`. | **Open** — `drivers_list_update.php` is substantial; confirm before removing |
