Skip to content

Receipt Tracker, Build Complete (2026-06-13)

Mobile receipt capture + pending-reconciliation queue that links receipts to bank transactions once SimpleFIN catches up (1-2 day lag), then attaches the image to the matching ERPNext Journal Entry. Built and tested end-to-end, live.

What shipped

Piece Where
receipts table forge_finance_db.py SCHEMA + helpers (insert_receipt idempotent on image_path, list_receipts, set_receipt_match, set_receipt_erpnext_file); init() creates it
Capture + review UI forge_finances_ui_receipts.py blueprint; registered in forge_finances_app.py; nav item in forge_finances_ui_base.NAV_ITEMS
Reconcile engine forge_finance_receipt_reconcile.py (--apply / dry-run / --no-erpnext)
Timer forge-finance-receipt-reconcile.timer (08:00 CT) + .service, enabled
CF hostname receipts.justinsforge.com on VR Alliance tunnel to 127.0.0.1:8096
CF Access app 0a3afa54-a1de-45d3-9c19-5cb94ac56641, allow [email protected] only
Memory memory/general/reference_receipt_tracker.md + MEMORY.md index entry

Surfaces

  • receipts.justinsforge.com: camera capture, business chips (WIEB/JWVR/NOVA/SIP/GOC), optional notes. Stores data/receipts/<date>_<uuid>.<ext>, inserts status=pending. Bare-host root redirect (before_request in the app) sends it to /receipts/.
  • Receipts tab on the hub (/receipts/review): pending receipts with a manual-match dropdown of nearby business charges, recently matched/auto with ERPNext attach state, "Open capture" button to the mobile site.

Reconcile logic (08:00 CT, after the 07:00 sync)

  1. Pending receipt, find business-slice outflow txns (effective scope = COALESCE(NULLIF(t.scope_override,''), a.scope, 'personal'), source IN simplefin/bankcsv, superseded=0) within +/-2 days of capture. Exactly one, auto-link status=matched. Zero or >1, stay pending for manual match.
  2. Matched + no erpnext_file_id, look up the JE (cheque_no=FINDB-<txn_id>, doctype Journal Entry), upload image via /api/method/upload_file, store the File docname. No JE yet, retry next run.

READ-ONLY on accounts/transactions; WRITES only receipts + ADDS File attachments. Logged intent + design in data/finance/COORDINATION.md before the schema touch.

Tested (real, end-to-end)

  • Capture page + host redirect + finances dashboard unchanged (200/302/200).
  • Upload POST stored image to disk + inserted pending row; re-insert same path returned same id (idempotent).
  • Review tab renders pending + thumbnails; image serve 200.
  • Matcher against live lake: unique-charge day, 1 candidate (would auto-link); ambiguous day, 5 candidates (stays pending).
  • ERPNext attach against a live JE (ACC-JV-2026-00211 / FINDB-27854): upload 200, File 93732a2867 created, erpnext_file_id stored, re-run skipped (idempotent), _existing_file caught it. Then deleted the test File (202) and all test receipt rows + images, so ERPNext and the receipts table are clean for production.
  • CF: public https://receipts.justinsforge.com/ returns 302 to the Access login (edge + tunnel + Access wired).

Design decision: JE, not Purchase Invoice

forge_erpnext_daily_post.py posts business expenses as Journal Entries (not Purchase Invoices), so the JE found by cheque_no=FINDB-<txn_id> is the only correct attach target. Expense Claim is employee reimbursement, not applicable. Flagged to erp-migration in COORDINATION.md in case they ever want PIs instead.

Open / future

  • First real auto-link happens the morning after Justin captures a receipt whose charge has posted (SimpleFIN lag). Watch the 08:00 run; failures notify via forge_notify.sh.
  • Receipts dated on/before the ERPNext seam (2026-06-08) may be HELD (no JE), so they link to a txn but stay attach-pending until erp-migration releases the JE. Expected.
  • No receipt-delete UI yet (only capture + manual match). Add if Justin wants to discard a bad photo from the review tab.

[Claude Code]