# Ad-spend Receipt Ingestion — Meta (live) + Google (partial) — Pascucci USA Inc

**Last updated**: 2026-06-04 · Company: Pascucci USA Inc · Mailbox: `finanly@pascuccicoffee.com`
(mailcow at `/home/docker/mailcow-dockerized`, IMAP `mail.capula.co` / host `127.0.0.1:993`).

Goal: every ad-spend / subscription card charge on the Mercury account ends up matched to a
**Purchase Invoice** in ERPNext (supplier = the vendor) with the **original receipt/invoice attached**,
so the books carry a document per transaction and the user matches the bank txn → bill in the tool.

---

## META (Facebook) — LIVE, automated
**How receipts arrive**: Luca auto-forwards each Meta receipt email to the mailbox. Meta sends a
**per-charge HTML receipt** (one email per card charge), with campaign breakdown + impressions +
reference number + transaction id.

**Ingestion (hourly)**: `finanly/services/jobs` Celery-beat task `meta_ad_receipts_ingest_all_tenants`
(`meta-ad-receipts-ingest-hourly`, `crontab(minute=0)`):
1. IMAP-poll the mailbox, parse each Meta receipt (`temp/meta_receipts/parse_receipts.py` logic, ported to
   `finanly/services/jobs/.../meta_receipts.py`). Card receipts only; prepaid-balance draws are skipped
   (they'd double-count the funds-add).
2. For each receipt with no PI yet (idempotent on `bill_no = META-<transaction_id>`), create an
   **Outstanding** Purchase Invoice via connector `POST /v1/ledger/ad-spend/purchase-invoices`
   (supplier `Meta inc`, expense `Facebook - PUI`, cc `Advertising - PUI`, one line per campaign with
   impressions), render the receipt to **PDF** (fpdf2 in-container) and attach it.
3. The PI sits Outstanding; when the Mercury debit-card txn lands, the user matches it in the transaction
   tool (existing "Match" action → Payment Entry Dr A/P / Cr Mercury-9908 → bill Paid).

**Backfill done** (this session): 25 historical Meta charges → PIs, GL-invariant re-link of the
JE-categorized ones, books verified unchanged to the cent. Tooling: `temp/meta_receipts/`.

**ACTIVATED (2026-06-04)**: the hourly job is LIVE. `META_IMAP_PASSWORD` is set in the gitignored
`infra/docker/.env`; `jobs`/`jobs-beat` rebuilt. Beat entry `meta-ad-receipts-ingest-hourly`
(`crontab(minute=0)`) runs it every hour. Verified live against the mailbox: 13 card receipts fetched,
all 13 deduped to existing PIs (`created=0, skipped_existing=13`), each PI still carries exactly one
receipt PDF — **no duplicate PI, no duplicate attachment**. Idempotency is double-keyed: PI on
`bill_no=META-<transaction_id>`, attachment on `file_name=meta-receipt-<ref>.pdf`
(`ref = reference_number or transaction_id[:12]`, identical to the backfill).

Two fixes were required to get the PDF path working in-container (both in this session):
- `finanly_jobs.meta_receipts.build_receipt_pdf`: fpdf2 2.8.x raised "Not enough horizontal space" on
  `multi_cell(w=0)` after a label cell → switched to an explicit value-column width (`epw - label`) + `set_x`.
- `finanly_jobs.tasks`: was missing `import base64` (the PDF branch never executed before, masking it).

**Known noise (not a books risk)**: the task targets every active tenant whose ERPNext integration is
bound to `Pascucci USA Inc`. In this env that resolves to 29 targets; only 1 has real credentials (→ 13
skipped), the other 28 are dev fixtures with no secret-broker creds and fail at credential lookup (502,
`core_api_secret_broker_error`) **before any write** — they create nothing. Follow-up (optional): scope
the task to the canonical Pascucci tenant, or skip a target once on missing creds, to quiet the per-hour
log (364 benign failures/run today).

---

## GOOGLE — PARTIAL (backfilled from statements; email ingestion TBD)
**What we DON'T know yet**: whether/how Google emails per-charge receipts. Unlike Meta, Google Ads sends a
**monthly statement** (PDF) listing the "PAYMENTS RECEIVED" card charges for the month — NOT a per-charge
email. Google **Workspace** sends a monthly **invoice** email with a PDF attachment.

**Backfill done** (this session, from the 2 statements + 1 Workspace invoice + 1 screenshot):
- **Google Ads** (supplier `Google`, expense `Google - PUI`, cc `Advertising - PUI`): 7 statement charges
  (Apr+May) → PIs `00075-00081`, GL-invariant re-link, each with its **monthly statement PDF** attached.
  Plus the `$131.14` Jun-1 monthly charge (Pending) → Outstanding PI `00086` + `june.png` screenshot.
- **Google Workspace** (supplier `Google`, expense `Subscriptions & Services - PUI`, cc `Admin and
  Overheads - PUI`): older charges ($42.57/$105.60/$52.80) → PIs `00082-00084` re-linked, **no attachment**
  (no email on file); the May invoice `5582237647` ($52.80, Pending Jun-2) → Outstanding PI `00085` + the
  emailed invoice PDF attached.
- Books verified: relinks invariant; the 2 Pending accruals (Ads $131.14 + Workspace $52.80) are the only
  ledger movement (intended new bills).

> **Continuation file (single source of truth for what's missing)**:
> `docs/GOOGLE_RECEIPT_INGESTION_CONTINUATION_V1.md` — open/parked until we confirm Google's email format.

**TODO when we learn how Google emails receipts** (moving forward):
1. Confirm the email format/sender for Google Ads charges (per-charge? or only the monthly statement?).
   - If **per-charge emails** arrive → build a Google parser + extend the hourly mailbox job to create a PI
     per charge with the email attached (mirror the Meta job; supplier `Google`, `Google - PUI`).
   - If only **monthly statements** → build a statement parser: read the "PAYMENTS RECEIVED" lines, create
     one PI per card charge, attach the **statement PDF** to each (what the backfill did by hand).
2. **Google Workspace**: per Luca — "per each Workspace email received, create the PI too, attach the
   email's PDF attachment." Build into the hourly mailbox job (supplier `Google`, expense
   `Subscriptions & Services - PUI`). Older Workspace charges with no email → PI without attachment.
3. Wire Google into the same hourly mailbox poll once the format is known; until then, backfill from
   forwarded statements/invoices as above.

---

## RECEIPT DOWNLOAD FROM THE PI SCREEN (requirement)
Receipts are attached to each Purchase Invoice as private ERPNext `File`s (Meta = generated PDF; Google =
statement/invoice PDF or screenshot). Requirement: the finanly **Purchase Invoice / invoicing screen** must
let the user **download the attached receipt PDF** (and Meta receipts must be saved as PDF in ERPNext and
downloadable there). Implementation: a finanly endpoint to list/stream a PI's attachments + a download
button on the invoicing page. (Tracked as a follow-up UI task.)
