Skip to content

Receipt ↔ document store unification (receipts gap 1f)

Goal

Merge the receipts vs documents storage shapes. Large refactor — dedicated scoping pass before touching. Owner: proceed (don't rush).

Log

  • 2026-06-13 doing — opening with a scoping pass (no code changes yet): map the receipts storage shape vs the documents storage shape, their normalizers/ accessors, upload + read paths, and where they're consumed together. Output = a unification plan for owner sign-off before any refactor.
  • 2026-06-13 SCOPING DONE — see below. Headline: the STORE IS ALREADY UNIFIED. Awaiting owner decision on whether any of the residual seams are worth touching.

Scoping result (2026-06-13)

The storage schema is already unified. Receipts and documents are ONE Firestore record shape and ONE collection: - Document interface (lib/accounting/types.ts:2708); Receipt = Document alias (:2812). Discriminated by type: DocumentType ('receipt' | 'vendor_invoice' | 'workspace_invoice' | 'gcp_invoice' | 'invoice_pdf' | 'contract' | 'quote' | 'wopc' | 'other'). - Collection: aote-system/file-archive/documents/entries/{id} (receipts.ts:54 getReceiptsCollection() resolves there). RECEIPTS_SUBCOLLECTION='receipts' is a back-compat alias → same collection (types.ts:2831). - Folders shared at file-archive/folders/entries/{id}. Matching vocabulary unified via MatchRecordType. viewerLinks reference docs by documentId.

So the "large refactor" premise is mostly already satisfied. Residual, genuinely-not-unified seams (the actual remaining 1f surface): A. Naming debtreceipts.ts / getReceiptsCollection() / storage/receipts.ts / /api/accounting/receipts/* are "receipt"-named but operate the unified document store. Cosmetic; pure rename churn. B. Two parallel API + upload surfaces over the SAME store/api/accounting/receipts(+/upload,/[id]) vs /api/records/vendor-invoices(+/upload). Divergent GCS blob prefixes: receipts/… (storage/receipts.ts:53) vs documents/…. Same bucket, same Firestore collection — only the blob-path prefix and endpoint differ. C. Two UI tabs (ReceiptsTab, VendorInvoicesTab) read the same collection, filtered by type. D. WOPC is a genuinely separate storetebs-epl/payees/{abbr}/wopc/{id} (lib/wopc.server.ts), NOT file-archive, despite type:'wopc' existing. Folding it in = a real data migration (own counter, payee-scoped path, line-item invoice trace from T-004). Higher risk, debatable value.

Recommendation: don't do a big refactor. The schema is unified. Options for the owner: (1) cosmetic naming/API consolidation (low risk, low urgency); (2) unify the GCS blob prefix (needs blob migration); (3) fold WOPC in (real migration — likely NOT worth it). Default = (1) light touch or close as "already unified". - 2026-06-13 DONE — store-shape unification goal is already met (single Document schema + collection). Owner pivoted the real concern to data residency + RBAC: the cosmetic-cleanup residual is superseded by T-021 (relocate the store to tebs-erl) and T-022 (RBAC enforcement). Closing T-012.