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 debt — receipts.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 store — tebs-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.