WFDASH-WEBAPP/docs/deliveries-n8n-workflow.md

263 lines
8.9 KiB
Markdown
Raw Permalink Normal View History

# Deliveries — n8n Signed-Copy Workflow
> Spec for the future n8n flow that closes out `/deliveries` items
> by uploading the scanned signed invoice/DN to PocketBase and
> flipping the order item to `completed`. Read this when you're
> ready to wire the workflow — every endpoint, payload shape, and
> field is defined below.
## Status: NOT YET BUILT
This is a **spec document**, not a working integration. The page
`/deliveries` ships with three tabs (To Load / Back from Driver /
Checked In); the `checked_in` items sit there until n8n lands.
The n8n workflow itself is owned by you, not by the app — this
doc just defines the contract so the workflow can be built
independently.
## What it does
Once the office has typed the reference number on the signed
paper (the `checked_in` tab in the app), the signed copy still
needs to be:
1. **Scanned** — by whatever scanner / phone-camera / IMAP inbox
the office uses today.
2. **Uploaded** to PocketBase on the right `order_items` row.
3. **Status flipped** to `completed` so the office can stop
looking at it.
The app exposes the PB schema fields for steps 2 + 3; n8n owns
step 1.
## API contract (everything the workflow needs)
### Inputs the workflow must know
The workflow runs per signed copy, not per order. A single scan
might cover 1 line or 20. To find the right `order_items` rows
the workflow uses the **reference number** the operator typed at
check-in time. The contract is:
```
workflow_in:
reference: string // e.g. "INV-12345" or "DN26071401"
file: Buffer // the scanned signed copy
filename: string // e.g. "INV-12345-signed.pdf"
```
### 1. Find the order_items
Hit the PocketBase list endpoint with the reference filter:
```
GET /api/collections/order_items/records?filter=delivery_reference%20%3D%20%22INV-12345%22&perPage=200
```
URL-decoded: `filter=delivery_reference = "INV-12345"`
> PB's `~` is substring (case-insensitive); `=` is exact. Use
> exact — the reference is meant to be exact, and a fuzzy match
> risks pulling in the wrong customer's rows. If the workflow
> has only a partial reference (e.g. the scanner OCR'd "INV-1234"
> with the last digit missing), it should fail loudly rather than
> guess.
Response shape (each item):
```json
{
"id": "abc123def456ghi",
"delivery_reference": "INV-12345",
"checked_in_at": "2026-07-20T11:30:00Z",
"checked_in_by": "user_id_here",
"item_status": "checked_in"
}
```
> ⚠️ Only items with `item_status = "checked_in"` should be
> auto-completed. An item that's been typo'd and lives at
> `with_driver` or earlier in the lifecycle should be left
> alone — the office might re-route it.
Optional pre-flight guard:
```
GET /api/collections/order_items/records?filter=delivery_reference%20%3D%20%22INV-12345%22%20%26%26%20item_status%20%3D%20%22checked_in%22
```
This way the workflow only operates on rows that are
genuinely ready to complete.
### 2. Upload the signed copy
The signed copy goes onto `order_items.order_item_attachements`
(multi-file field, `maxSelect: 10`, accepts any MIME). This is
the existing field for "all attachments on this line" — same
field that delivery-note PDFs, supplier POs, etc. live on.
No new collection, no new field.
Per item, do a single PATCH with the file. PocketBase's REST
endpoint for file uploads on an existing record is:
```
PATCH /api/collections/order_items/records/{id}
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="order_item_attachements"; filename="INV-12345-signed.pdf"
Content-Type: application/pdf
<bytes>
--boundary--
```
> Naming convention: use the reference number as the filename
> stem (`{reference}-signed.pdf` or `{.ext}` as the scanner
> produces). The retrieval side — see "How the office sees it
> back" below — relies on the filename pattern to find the
> signed copy on a given item. Don't append timestamps or random
> suffixes.
### 3. Flip the status to `completed`
Same record, same endpoint, JSON body this time:
```
PATCH /api/collections/order_items/records/{id}
Content-Type: application/json
{ "item_status": "completed" }
```
No body auth header is needed if the workflow uses the admin
PB token (recommended — see "Auth" below).
### 4. Repeat for each item in the batch
Loop over the items from step 1 and do steps 2 + 3 per item.
A single scan = one batch = N items = N×(PATCH file + PATCH status)
calls. PB doesn't have a batch endpoint; the per-item round trip
is fine for typical batch sizes (1–20).
If the workflow needs to be idempotent (resume after a crash):
check `item_status` on the response from step 3. If it's already
`completed`, skip. If it's still `checked_in`, the PATCH probably
failed — log + retry once.
## Auth
The workflow should use the **PocketBase superuser token**, not a
per-user token. The PB schema's rule is
`@request.auth.id != ''` on the order_items update, so any
authenticated principal works, but the superuser path is the
simplest because:
- It doesn't expire while the workflow is running.
- It doesn't need refresh handling.
- The signed copy is an automated upload, not a user action —
per-user audit doesn't add anything.
To grab the token, use the same flow as the rest of the
back-office n8n workflows: a single "Bootstrap" HTTP node at the
top of the workflow that hits
`POST /api/collections/_superusers/auth-with-password` with the
admin email + password from the n8n credential store, then stores
`token` in a workflow variable. Every subsequent HTTP node reads
`Authorization: Bearer {{$token}}`.
> If your PB instance is on the v0.23+ rename (admin collection
> is `_superusers` not `_admins`), see
> `docs/migration-audit.md` for the endpoint confirmation.
## How the office sees it back
Once the file is uploaded, the office has two ways to find it:
1. **Inside the existing order detail view** (the one at
`/orders/[id]`) — the page already reads
`order_item_attachements`. The new file just shows up
alongside the others.
2. **Inside the `/deliveries` "Checked In" tab** — when v2 of
the page ships (out of scope for v1), it'll show a "View
signed copy" link per row that points at PB's file URL. The
app's `api/v1/files/[collection]/[recordId]/[filename]`
route is the canonical way to stream a PB file back through
Next.js with auth.
## Filename contract
The workflow must use a deterministic filename per item so the
retrieval side can find it without scanning the whole attachment
list:
```
{reference}-signed{ext}
```
Examples:
- `INV-12345-signed.pdf`
- `INV-12345-signed.png`
- `DN26071401-signed.pdf`
If the scanner produces something else, the workflow should
**rename it** to this pattern before the upload — not rely on
whatever the device names it. The reference is the only stable
key; everything else is mutable.
## Failures to handle
The workflow should treat these as "log + retry" rather than
"throw and stop the whole run":
- **0 items found for the reference** — typo'd reference or a
paper that was never typed into the office. Don't create a new
item; surface the unmatched reference to a "needs review" sink
(a separate `logs` collection or a Slack message — your call).
- **HTTP 5xx on the PATCH** — PB blip. Retry once with a 2s backoff.
- **`item_status` came back as `cancelled`** — the office
cancelled the item in the meantime. Skip the upload + status
flip; surface to "needs review".
The workflow should treat these as **hard failures** that abort
the run:
- **More than 50 items for one reference** — almost certainly a
data bug. Don't auto-upload 200 files based on a single scan.
- **Auth token expired mid-run** — re-bootstrap from the start.
## Suggested n8n shape
If you want a starting point:
1. **Trigger**: depends on the scanner. Common patterns:
- Watch a dedicated IMAP folder (signed copies get emailed
from the scanner).
- Watch a folder (e.g. `/shared/scanner/` on a Synology).
- A webhook the office hits from a phone app.
2. **OCR / extract reference**: if the scan is an image, OCR
it (Tesseract via a `Execute Command` node, or one of the
hosted OCR services) to read the reference number from the
paper. Cross-check against the manually-typed reference from
the office — if they disagree, route to "needs review".
3. **HTTP Bootstrap**: get the PB superuser token.
4. **HTTP Find items**: list by reference (step 1 above).
5. **Loop per item**: upload + flip (steps 2 + 3 above).
6. **Log + cleanup**: move the source file out of the watch
folder, log success/failure.
## Out of scope for the app
These are deliberately **not** part of the `/deliveries` page
v1 and don't need any app change to support:
- The actual scan ingestion (n8n).
- The OCR (n8n).
- The signed-copy viewer in the page (a future "Completed" tab
once there's enough data to be worth showing).
- A "Completed" tab on the page. When the workflow lands and
`item_status = "completed"` rows accumulate, we can add a 4th
tab. The schema already supports it — the page just doesn't
render it yet.