# Capture Flows — PODs, PDIs & Timesheets

**Folder:** `CRUD/forms/` (pod, pdi, timesheets) · **Reached from:** navbar → Forms, and the [Uploads](../../CRUD/uploads.php) page

The three paperwork flows that feed the [Hire List](hire-list.md). Each follows the **same shape**, which is the key thing to understand — learn one and you know all three:

1. **Create** a blank form for a hire (on screen or printable)
2. The driver/fitter completes it in the field, on paper or on a tablet
3. **Upload** the completed sheets — usually a batch scan of many pages
4. **Review** the batch: the system splits it and matches each page to a hire
5. The document is filed against the hire and appears on its documents panel

| Flow | What it records | Folder |
|---|---|---|
| **POD** — Proof of Delivery | Signed confirmation that a machine was delivered/collected | `forms/pod/` |
| **PDI** — Pre-Delivery Inspection | Machine condition check before it goes out | `forms/pdi/` |
| **Timesheets** | Operator hours worked (drives operated-hire invoicing) | `forms/timesheets/` |

> `forms/` also contains staff forms, alerts, fitter's reports, medical self-certs, PDF signing and credit applications — **not covered here**; those are a separate pass.

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

---

## The shared shape

```mermaid
flowchart LR
    B[Blank form<br/>blank_*.php] --> P[Printed / tablet]
    P --> F[Completed in the field]
    F --> U[Batch upload<br/>*_upload.php]
    U --> S[Split + OCR<br/>extractor service]
    S --> R[Batch review<br/>*_batch_review.php]
    R --> M[Matched to a hire]
    M --> D[Filed against the hire<br/>documents_panel.php]
    T[Template builder<br/>*_template_builder.php] -.defines where fields sit.-> S
```

Each flow has the same file set, with the prefix changed:

| File pattern | Purpose |
|---|---|
| `blank_*.php` / `*_create.php` | Produce a blank form for a hire |
| `*_create_printable.php` | Printer-friendly version |
| `*_manage.php` / `*_index.php` | List and manage submitted forms |
| `*_upload.php` | Upload completed sheets (batch) |
| `*_batch_review.php` | Review a split batch and match pages to hires (`?id_batch=`) |
| `*_upload_review.php` | Review a single upload |
| `*_template_builder.php` | Define where fields sit on the scanned page |
| `*_template_preview.php` | Preview that template against a sample (uses OCR) |
| `upload*.php` (legacy) | Original plain uploaders, kept on [Legacy Uploads](../../CRUD/legacy_uploads.php) |

---

## PODs — `forms/pod/`

![POD Manage](../screenshots/forms__pod_manage.png)

Proof of delivery. 27 files, ~14,300 lines.

| Page | Purpose |
|---|---|
| `pod_manage.php` | Main POD management view |
| `pod_index.php` | Delivery dockets list |
| `blank_pod.php` / `blank_pod_printable.php` | Generate a blank POD |
| `update_blank_pod.php` | Edit a blank before printing |
| `pod_selector_driver.php` | Driver-facing POD picker (tablet) |
| `combine_pods.php` | Merge several PODs into one document |
| `edit_delivery_docket.php` | Edit a delivery docket (`?id=`) |
| `pod_upload/pod_upload.php` | Batch upload |
| `pod_upload/pod_batch_review.php` | Review a split batch (`?id_batch=`) |
| `pod_upload/pod_template_builder.php` | Field-position template |

![POD Upload](../screenshots/forms__pod_upload.png)

![Blank POD](../screenshots/forms__blank_pod.png)

## PDIs — `forms/pdi/`

![PDI Create](../screenshots/forms__pdi_create.png)

Pre-delivery inspection — the machine condition check. 27 files, ~10,700 lines.

| Page | Purpose |
|---|---|
| `pdi_create.php` / `pdi_create_printable.php` | Raise a PDI |
| `pdi_checklist_template.php` | The inspection checklist itself |
| `pdi_index.php` | PDI list, linking to the scanned snapshot image |
| `pdi_upload/pdi_upload.php` | Batch upload |
| `pdi_upload/pdi_batch_review.php` | Review a split batch |
| `pdi_upload/blank_pdi_upload.php` | Upload blanks |
| `pdi_upload/pdi_template_builder.php` | Field-position template |

![PDI Checklist](../screenshots/forms__pdi_checklist.png)

![PDI Upload](../screenshots/forms__pdi_upload.png)

Completed PDI snapshots are stored as images at `/CRUD/forms/pdi/pdi/<pdi_number>.png`.

## Timesheets — `forms/timesheets/`

![Timesheet Manage](../screenshots/forms__ts_manage.png)

Operator hours. 24 files, ~8,400 lines. **These feed operated-hire invoicing**, so accuracy matters more here than in the other two flows.

| Page | Purpose |
|---|---|
| `timesheet_manage.php` | Main timesheet view |
| `timesheet_create.php` / `_printable.php` | Raise a timesheet |
| `hours_drivers_index.php` | Driver hours (`hours_drivers`) — the operated-invoice source |
| `timesheet_upload/ts_upload.php` | Batch upload |
| `timesheet_upload/ts_batch_review.php` | Review a split batch |
| `timesheet_upload/ts_edit_review.php` | Edit during review |
| `timesheet_upload/blank_ts_upload.php` | Upload blanks |

![Timesheet Upload](../screenshots/forms__ts_upload.png)

![Driver Hours](../screenshots/forms__hours_drivers.png)

---

## OCR / extractor service

Batch review depends on an **external OCR service on `127.0.0.1:8012`** (a separate Python service under `CRUD/suppliers/supplier_invoices/extractor/`). If it is not running:

- `*_template_preview.php` returns **HTTP 500 — "OCR failed: Failed to connect to 127.0.0.1 port 8012"**
- Batch splitting/matching will not work

Service health is visible at `CRUD/suppliers/supplier_invoices/ocr_status.php`. **This is environmental, not a code fault** — expect it on a local machine where the service isn't started.

## Parameter conventions

Review pages validate their inputs properly and return **HTTP 400 with a plain message** when a required parameter is missing (e.g. `Missing id_batch`) rather than erroring — good behaviour, and worth preserving.

| Page type | Required parameter |
|---|---|
| `*_batch_review.php` | `?id_batch=` |
| `*_upload_review.php` | `?id_upload=` |
| `edit_delivery_docket.php` | `?id=` |

---

## Known issues

Found during the review pass on 2026-07-21.

| # | Issue | Status |
|---|---|---|
| 1 | **`pod_index.php` had no authentication and leaked server paths.** Its opening block used the short tag `<?` instead of `<?php`. With `short_open_tag=Off` (the default here) the entire block was emitted as **text rather than executed** — so `session.php` never ran (page was unauthenticated), `database.php` never loaded (fatal `Class "Database" not found` further down), and the `require_once` lines were printed into the page source, disclosing filesystem paths. Same class as the C1/H2 findings in the [security review](../security-audit-2026-07.md). | **Fixed** — changed to `<?php`; page now authenticates, renders, and leaks nothing |
| 2 | A portal-wide sweep found **14 files containing `<?`**. Triaged: 12 are incidental (inside JS/strings/regex) and harmless. Only one other file genuinely began with an inert block — `CRUD/functions/holidays2.php` — which is unreferenced but was fixed anyway. | **Fixed** — 0 files now start with a short tag |
| 3 | `pdi_index.php` emitted **25 deprecation notices** per load: `htmlspecialchars(): Passing null to parameter #1`. Line 109 passed `$row['pdi_number']` unguarded while the line above it correctly cast to string. Deprecated in PHP 8.1 and an error in a future version. | **Fixed** — null-guarded and `rawurlencode`d the URL path segment, matching the pattern applied to `plantlist_index.php` in the security review |
| 4 | 13 orphan candidates across these three flows, including `pdi_index.php`, `pod_template_pdf_file.php`, `pod_template_preview.php`, `timesheet_upload.php`, `ts_offline_sync.php`, `mdsc_offline_sync.php`. The two `*_offline_sync.php` files suggest an offline/tablet sync feature that may be unfinished or abandoned. | **Open** — resolve per-flow; check whether offline sync was ever completed |
| 5 | `pdi_upload/pdi_repair_982199.php` is a one-off data-repair tool that self-documents *"Delete this file once every row shows filed"*. | **Open** — needs confirmation the repair finished |
