Skip to content
Last updated: Sep 25, 2026

Document Receipt — Remediation (Reclassify, Reprocess, Status Gating) ​

OwnerClassificationVersionEffectiveNext reviewStatus
Platform EngineeringInternal0.12026-09-052026-12-05Draft

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 ​

PieceLocationResponsibility
Shared lifecycle configpackages/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 pageapps/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 triggerPOST /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 endpointPUT /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:

TierStatusesEditableActions
needsWorkNew · Queued · Unassigned · Extracting · Extracted · Error · ExtractionFailed · ValidationFailedDocument Type · Subcategory (reclassify)Reprocess · Reject · Delete
inFlowProcessing · Validated · Assigned · PendingApproval · Approved— lockedReject · Delete
doneProcessed · Completed— lockedDelete
terminalOpenCancelled · Rejected— lockedReprocess (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 ​

  1. Operator corrects Document Type / Subcategory on the detail page (only enabled in the needsWork tier).
  2. Reprocess: persist the change and reset status to Queued (one PUT), then call rundocprocess.
  3. rundocprocess re-extracts and re-promotes by documenttype, so a receipt reclassified invoice→remittance lands in the remittance queue.

Reprocess only actually runs if the pipeline is live. rundocprocess requires the receipt in Queued state (hard gate) and depends on the document-processqueue schedule + the workflow worker running (see the reproducibility caveat in the ingestion guide). If the status flips to Queued but never advances and no new receiptactionhistory rows appear, the drainer/worker is not running — the UI did its part.

Gotchas (each of these cost real debugging time) ​

  • Subcategory INVOICE must be in the LOV. The classifier assigns the generic INVOICE subcategory at intake — PO vs Non-PO is not knowable pre-extraction. If INVOICE (or any classified value) is missing from the DOCUMENTRECEIPT_SUBCATEGORY LOV (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, else NON_PO_INVOICE), but no step writes it back today — subcategory is set once at classification. So INVOICE is the honest resting value; do not force a premature PO/Non-PO guess.
  • Save uses PUT, not POST. POST /documentreceipts is 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 withReceiptCurrentOption helper 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.

Revision history ​

VersionDateAuthorChange
0.12026-09-05Platform EngineeringInitial draft — status-tier gating, reclassify→reprocess loop, shared config, subcategory/LOV/PUT gotchas, refinement gap.

Finaisse Internal — Confidential. Access-restricted; not for external distribution.