Skip to content

name: reference_receipt_tracker description: Receipt tracker, mobile capture at receipts.justinsforge.com plus a Receipts tab on the finances hub, with a daily reconcile that links receipts to bank txns and attaches images to ERPNext JEs type: reference


Receipt Tracker

Two surfaces over one Flask app (forge-finances.service, Console :8096), plus a daily reconcile. Solves the SimpleFIN lag: a receipt photo arrives 1-2 days before its bank transaction, so receipts land pending and get linked once the txn appears.

Surfaces

  • receipts.justinsforge.com (mobile capture). Camera button (<input capture="environment">), business chips (WIEB / JWVR / NOVA / SIP / GOC), optional notes. Submit stores the image to data/receipts/<YYYY-MM-DD>_<uuid>.<ext> and inserts a receipts row as status=pending. Rides the VR Alliance CF tunnel to 127.0.0.1:8096; CF Access app 0a3afa54-... allows [email protected] only.
  • Receipts tab on finances.justinsforge.com (/receipts/review): pending receipts with a manual-match dropdown, recently matched/auto receipts with their ERPNext attach state, and an "Open capture" button to the mobile site.

Both hosts tunnel to the same origin; forge_finances_app.py has a before_request that sends receipts.justinsforge.com/ to /receipts/ while finances/ stays the dashboard.

Code

  • scripts/forge_finances_ui_receipts.py, blueprint: /receipts/ (capture page), /receipts/upload (POST), /receipts/image/<id>, /receipts/review (hub tab), /receipts/<id>/match (manual link). Registered in forge_finances_app.py; nav item added in forge_finances_ui_base.NAV_ITEMS ("Receipts", camera glyph).
  • scripts/forge_finance_receipt_reconcile.py, the daily engine (see below).
  • DB: receipts table in forge_finance_db.py (init() creates it). Columns: id, created_at, image_path (UNIQUE), company, notes, status, matched_txn_id, erpnext_file_id, updated_at. Helpers: insert_receipt (idempotent on image_path), list_receipts, get_receipt, set_receipt_match, set_receipt_erpnext_file. Constants RECEIPT_STATUSES, RECEIPT_COMPANIES.

Reconcile (forge_finance_receipt_reconcile.py)

Timer forge-finance-receipt-reconcile.timer at 08:00 CT (after the 07:00 sync). Dry-run by default; --apply writes. --no-erpnext links only.

  1. For each pending receipt, find business-slice outflow txns whose effective scope (COALESCE(NULLIF(t.scope_override,''), a.scope, 'personal'), source IN simplefin/bankcsv, superseded=0) equals the receipt company, dated within +/-2 days of capture. Exactly one candidate, auto-link (status=matched). Zero or >1, stay pending (never guess between two).
  2. For each matched receipt with no erpnext_file_id, look up the JE the daily post engine made (cheque_no=FINDB-<txn_id>, doctype Journal Entry), upload the image via /api/method/upload_file as a private attachment, store the File docname. No JE yet (txn unposted or held behind the seam), skip and retry next run.

Auth reuses forge_erpnext_post_books.load_env() / pb.SITE (token header, Host: erpnext.justinsforge.com); upload is multipart via requests. Idempotent: links once, and _existing_file checks for an already-attached File before re-uploading, so a crash between upload and DB write never double-attaches.

Why 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 today. Expense Claim is employee reimbursement, not applicable.

Notes

  • READ-ONLY on accounts/transactions (shared lake contract, see data/finance/COORDINATION.md); WRITES only the receipts table and ADDS File attachments to existing JEs. Never writes GL, never creates/cancels JEs.
  • Receipt images are user data: data/receipts/.gitignore keeps the dir, ignores the photos.
  • Related: [[reference_forge_biz_apps]], [[reference_erpnext_daily_post]], [[reference_cloudflare]].

[Claude Code]