Skip to content
Ritesh FirodiyaGet in touch

Work / Chitragupt / Wiki / Concepts

receipt-to-transaction-linkage

Conceptcanonicalverified 2026-08-18

CONCEPT.RECEIPT-TO-TRANSACTION-LINKAGE

Summary

Evidence-only documents (receipts, bills, invoices) never create their own ledger entries — instead, the backend matches each one to the bank transaction that already represents the same cash movement, so the user sees one thing, not two.

Why it matters

A user who uploads both a flight ticket and the bank statement carrying the same debit would otherwise see that spend counted twice — once from the receipt, once from the bank row. Evidence-only forms (travel-receipt, utility-bill, motor-insurance-policy, generic-receipt, professional-receipt) deliberately emit no ledger mapper of their own; the bank statement is the single source of truth for cash movement. linkEvidenceToLedger (apps/functions/src/inbox/link-evidence-to-ledger.ts) closes the gap between "the document exists" and "the user can see it's the same ₹5,000" by matching on amount + a ±7-day date window and stamping the link both ways.

Implications

  • Matching runs once, automatically, at document-confirm time — not user-initiated.
  • Only forms in EVIDENCE_FORMS_FOR_LINKING (packages/shared/src/config/evidence-linking.ts) are attempted. hr-letter is deliberately excluded — offer/relieving letters carry no matching debit. Any new evidence-only form type must be added there explicitly; it is not automatic.
  • A match requires an exact paise amount and a candidate within 7 days — no tolerance. A partial payment or a paise-rounding difference produces no_candidate_found, not a fuzzy match.
  • More than 5 exact-amount, same-day candidates (e.g. bulk EMI batch) is treated as ambiguous_multiple_matches and deliberately left unlinked rather than guessed.
  • The outcome of every attempt (linked, no_extractable_amount_or_date, no_candidate_found, ambiguous_multiple_matches) is stamped on the document as link_reason and surfaced in the Inbox (LinkedTransactionNote) — a receipt that didn't link tells the user why instead of silently showing nothing.
  • The confirm-time attempt is one-shot: if the matching bank statement is uploaded after the receipt, only hourlyRetryEvidenceLinking (apps/functions/src/triggers/scheduled/hourly-retry-evidence-linking.ts) — which re-queries rows still at link_reason: no_candidate_found, capped at EVIDENCE_LINK_RETRY.max_retries hourly passes — can still resolve it. no_extractable_amount_or_date and ambiguous_multiple_matches are not retried; re-running doesn't change either outcome.
  • N-to-1 is supported: an invoice and its payment receipt can both stack onto the same ledger row via evidence_doc_ids rather than fighting over it.

Related

Sources

  • apps/functions/src/inbox/link-evidence-to-ledger.ts
  • apps/functions/src/triggers/scheduled/hourly-retry-evidence-linking.ts
  • apps/functions/src/triggers/firestore/handlers/persist-confirmed-document.ts
  • packages/shared/src/config/evidence-linking.ts
  • packages/shared/src/config/evidence-link-retry.ts
  • apps/website/src/components/inbox/LinkedTransactionNote.tsx

Every project of mine is written down like this.

Read the résumé