Document Receipt — Remediation (Reclassify, Reprocess, Status Gating)
| Owner | Classification | Version | Effective | Next review | Status |
|---|---|---|---|---|---|
| Platform Engineering | Internal | 0.1 | 2026-09-05 | 2026-12-05 | Draft |
Purpose. The operator/admin companion to Email & Document Ingestion. That guide covers how an inbound document is classified, extracted and promoted. This one covers what a human does when a receipt is wrong or stuck — reclassify it, reprocess it — and the rules that govern which fields are editable and which actions apply at each status. The document-receipt ("document queue") is a coresystem concept shared by every module (invoice, journal, remittance, …), so this behaviour is a single shared pattern, not a per-module rebuild.
Where the behaviour lives
| Piece | Location | Responsibility |
|---|---|---|
| Shared lifecycle config | packages/shared/src/configs/documentreceipt.config.ts (blitz-ui) | Single source of truth: status tiers, can* gating, status colours, valid LOV vocabulary, the populate helper, the reprocess endpoint. Every module's detail page imports this — do not re-implement per module. |
| Detail page | apps/invoicehub/.../admin/document-receipt-details/[id] (invoice; journal/remittance have equivalents) | Diagnostic-first UI — status + "why it's stuck" + status-based actions + reclassify controls + read-only facts. |
| Reprocess trigger | POST /api/o/finance/coresystem/v1/workflows/rundocprocess { documentreceiptid } | Re-runs the ingestion pipeline for one receipt. The same trigger email ingestion and re-extract use. |
| Update endpoint | PUT /api/o/finance/coresystem/v1/documentreceipts (full body) | Persists reclassification + status. Use PUT, not POST (see gotchas). |
Status tiers → what's editable, which actions
Status is a lifecycle state — it is never hand-edited; the pipeline and the actions move it. Each status maps to a tier that decides editability and the available actions:
| Tier | Statuses | Editable | Actions |
|---|---|---|---|
| needsWork | New · Queued · Unassigned · Extracting · Extracted · Error · ExtractionFailed · ValidationFailed | Document Type · Subcategory (reclassify) | Reprocess · Reject · Delete |
| inFlow | Processing · Validated · Assigned · PendingApproval · Approved | — locked | Reject · Delete |
| done | Processed · Completed | — locked | Delete |
| terminalOpen | Cancelled · Rejected | — locked | Reprocess (revive) · Delete |
Rationale: reclassify is allowed only before the receipt is promoted into a downstream queue (the needsWork tier). Once it's inFlow/done, a domain record exists downstream; changing the type in place would orphan it — delete + re-ingest instead.
Reclassify → Reprocess loop
- Operator corrects Document Type / Subcategory on the detail page (only enabled in the
needsWorktier). - Reprocess: persist the change and reset status to
Queued(onePUT), then callrundocprocess. rundocprocessre-extracts and re-promotes bydocumenttype, so a receipt reclassified invoice→remittance lands in the remittance queue.
Reprocess only actually runs if the pipeline is live.
rundocprocessrequires the receipt inQueuedstate (hard gate) and depends on thedocument-processqueueschedule + the workflow worker running (see the reproducibility caveat in the ingestion guide). If the status flips toQueuedbut never advances and no newreceiptactionhistoryrows appear, the drainer/worker is not running — the UI did its part.
Gotchas (each of these cost real debugging time)
- Subcategory
INVOICEmust be in the LOV. The classifier assigns the genericINVOICEsubcategory at intake — PO vs Non-PO is not knowable pre-extraction. IfINVOICE(or any classified value) is missing from theDOCUMENTRECEIPT_SUBCATEGORYLOV (sys.lov/sys.lovvalue), any receipt PUT is rejected 500 ("Invalid subcategory"). Seed source of truth:systemd/prisma/seed.ts. - No post-extraction subcategory refinement (open gap). PO/Non-PO should be set after extraction + PO matching (PO# matched →
PO_INVOICE, elseNON_PO_INVOICE), but no step writes it back today — subcategory is set once at classification. SoINVOICEis the honest resting value; do not force a premature PO/Non-PO guess. - Save uses
PUT, notPOST.POST /documentreceiptsis an upsert path that 500s on an existing receipt;PUT(updateInstance) is correct. Fetch the row, mutate, PUT it back. - Dropdowns must include the current stored value or an ejs dropdown renders blank. The shared
withReceiptCurrentOptionhelper guarantees this — intake data is inconsistent (lowercase types, values outside the canonical list). - Status is not a free field. Do not expose a status dropdown for editing; show it as a chip. It changes only via actions / the pipeline.
Related
- Email & Document Ingestion — the ingestion pipeline this remediates.
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-09-05 | Platform Engineering | Initial draft — status-tier gating, reclassify→reprocess loop, shared config, subcategory/LOV/PUT gotchas, refinement gap. |