# Open Issues — handover for whoever picks this up next

**Written:** 2026-07-13
**Books state at time of writing:** clean. Bin = GL = $79,366.39. Trial balance
765,382.18 = 765,382.18. Finanly ≡ ERPNext on all 78 P&L/BS lines (Pascucci 50, Nourishing 28),
0 mismatches, both balance sheets balance.

Everything below is **verified against live data**, not recalled. Each item states what is known,
what is NOT known, and what must be checked before acting.

---

## 1. The $5.95 valuation residue — HIGHEST PRIORITY

Full write-up: **`VALUATION_RESIDUE_ISSUE_V1.md`** (same directory). Read it before touching this.

**Summary:** Pascucci's inventory is carried $5.95 above true landed cost. COGS understated,
inventory and net profit overstated. It **grows on every sale** and never self-corrects.

**Cause:** Material Issue rows pin an explicit `basic_rate`
(`finanly/services/connectors/erpnext/src/finanly_connector_erpnext/app.py:2247` and `:2288`).
The pin is **deliberate** — it is what keeps `Bin.stock_value` equal to the GL `Stock - *`
accounts to the cent. **Do not remove it.** Its side effect is a per-issue rounding residue that
strands in `Stock - *`.

**Constraints any fix MUST satisfy simultaneously:**
1. `SUM(Bin.stock_value)` must still equal the GL `Stock` accounts, to the cent.
2. The landed cost must not change (it is set by the invoice + Landed Cost Voucher).

**What was already tried and FAILED — do not repeat:** a corrective Landed Cost Voucher. An LCV
changes the receipt's landed cost and triggers a **retroactive repost**, which recomputes outgoing
SLE values from the moving average and would **overwrite the pinned rates on every Material Issue
since April**. It was posted live (`MAT-LCV-2026-00020`/`00021`), broke `Bin == GL`, and was
cancelled.

**Untested direction:** a **Stock Reconciliation** revaluation writes `Bin` *and* the
`Stock Adjustment` offset in the same voucher, so both sides move together. **Before building it,
answer:** does a Stock Reconciliation itself enqueue a `Repost Item Valuation`? If it does, the
same pinned-rate hazard applies and this approach is **also unsafe**.

---

## 2. Flexport disposal handling — NOT automated, and must not be guessed

**Current facts (verified):**
- Flexport marks a real disposal with `Category = "Disposed"` in the Inventory Transaction
  History. There are **2 such rows, totalling 4 units of `PUI-CAP-SAMPLE-120`**, both in
  **Quarantine**. At the landed rate ($33.2316) that is **$132.93**. It is **not booked**.
- **Operator rule:** items in **Quarantine are HELD** — Flexport waits for the operator's import
  decision before disposing. They stay there until the operator acts. **Quarantine stock is
  therefore NOT automatically a loss.** Only Quarantine needs to be looked at.
- **No disposal fee has ever been billed.** Every line item on all 9 booked Flexport invoices
  (bills 639186 → 659863) was read. Charge types present: Fulfillment Fees, Storage Fees (DTC +
  Reserve), Returns-Shipping, Prep/BCL Labels, Non-Compliance (B2B + DTC), Over Receive,
  Unexpected SKU, Missing Container Label, and credits. **There is no disposal / removal /
  destruction charge on any invoice.**

**DANGER — the previous disposal job was catastrophically wrong and is now DISABLED:**
`disposal_sync_daily` read `Category = Adjustment` and booked the net as an inventory write-off.
`Adjustment` is Flexport's own **cycle-count churn** (it nets −1,679 units over 365 days and means
nothing). Units also move in and out of a `Missing` state (net +1 across 327 rows), so transient
mislays were booked as permanent losses. Three Stock Entries were posted from this and had to be
cancelled. The beat entry is commented out in
`finanly/services/jobs/src/finanly_jobs/celery_app.py`. **Do not re-enable it.**

**What needs deciding:** whether Quarantine/Disposed units should be booked at all, and if so, at
what trigger (operator's import decision, presumably — not a Flexport report row).

---

## 3. Personal tenant: "Gas" category should be renamed to "Car"

**Verified:** a `Gas` category still exists in `personal_categories`. The operator asked for it to
be **renamed to `Car`** — on all historical transactions AND as the selectable category going
forward. **This was never done.**

---

## 4. Known gaps in ingestion (features that do not exist yet)

- **Google Ads has no email-ingest parser.** Only Google *Workspace* invoices are parsed
  (`finanly/services/jobs/src/finanly_jobs/google_receipts.py`). Google Ads receipts were
  reconciled by hand this session against three forwarded statements ($2,582.95 booked =
  $2,582.95 accrued). If automation is wanted, it must be built.
- **Shopify monthly platform-fee invoices are not in the system.** No Shopify API exposes them;
  they are available only via CSV export or by forwarding the email.

---

## 5. Ops issues that were fixed this session (uncommitted, for context)

These are in the working tree, **not committed**:

- **Celery Beat was dead for 23 days** (2026-06-19 → 2026-07-12), so nothing ingested. Root cause
  read from the kernel: beat's Redis socket sat ESTABLISHED with 437 bytes unread, `cmd=subscribe`,
  idle 22.9 days; Celery's Redis transport defaults to `socket_timeout=None`, so the single beat
  thread blocked forever while Docker still reported the container "Up". Fixed with bounded socket
  timeouts + a heartbeat key + a container HEALTHCHECK
  (`celery_app.py`, `beat_healthcheck.py`, `infra/docker/compose.core.yml`).
- **Shopify order line-item fetch** silently returned `[]` on timeout, which meant COGS was not
  booked. Now retries 3× with a 120s timeout and raises 502 instead of failing silently
  (`core-api/routers/recon.py`).
- `product_classifier.py` — routes `adjustment:chargeback_fee`.
- `google_receipts.py` — invoice-date fallback parse for Workspace PDFs.

---

## 6. Things that are CORRECT and must not be "fixed"

Listed because they look like bugs and are not. **Two of these were written up as "issues" in the
first draft of this document and were WRONG.** Verify before you touch anything:

0. **There are NO pending transactions.** A `status='pending'` count in
   `canonical_transactions` is NOT a backlog. Every such row is **pre-2026-02-16** and comes from
   `legacy_transaction_cache`, `legacy_sqlite`, or an old `mercury` import — they are **migration
   artifacts of the ERPNext→Finanly port** and the UI correctly excludes them. The operator's UI
   shows zero pending, and that is the truth.

0b. **The Nourishing Mercury sync is NOT stale.** `last_sync_at = 2026-02-23` and the newest
   Nourishing Mercury transaction is also **2026-02-23**. They agree. There is simply no newer
   activity. Nothing is broken.

1. **The starter kit is a single product.** Its COGS lands entirely on `Machines - PUI`, including
   the `PUI-CAP-SAMPLE-120` component whose inventory is relieved from `Stock - Coffee - PUI`.
   **Intended. Confirmed by the operator.**
2. **The pinned `basic_rate` on Material Issue rows is deliberate** (see §1). It fixes a 4-decimal
   → 2-decimal rounding discrepancy and keeps Bin = GL. **Do not remove it.**
3. **58 Bank Transactions with status `Reconciled`** are the invoice-payment chain (Purchase
   Invoice → Payment Entry → bank line matched: Meta, Google, Flexport, vendor payments, internal
   transfers). ERPNext sets `Reconciled` when a bank line is matched to a Payment Entry. That is
   correct. The "must stay Unreconciled" rule applies to the *categorization* (Journal Entry) path,
   which is a different mechanism. No bank transaction has more than one linked posting.
4. **`fulfillment_order_id=7050970726570` has two Stock Entries** (`MAT-STE-2026-00016` /
   `00017`). The second is a supplementary entry booking a RISERVA line an older bug had skipped.
   Different items, no overlap. Not a duplicate.
5. **38,299 failed `ledger_facts_refresh` sync_runs** are all on the test tenants `Parity Tenant`
   and `T`. **Zero** on Pascucci, Nourishing, Fine Line, or Ad Astrum. The hourly refresh is
   healthy for the real tenants, and the on-view refresh path in
   `core-api/routers/financial_statements.py` keeps the UI reports current regardless.

---

## 7. Process rules the next agent must follow

These exist because they were violated in this session, with consequences:

1. **`/home/docker/erpnext/` is READ-ONLY.** Changes go through Finanly, never by writing directly
   to ERPNext. (Violated 5× this session, including working around a blocked permission guard.)
2. **The operator does not use the ERPNext UI.** "I do not use erpnext UI this is why finanly
   exist." Fixes must be reachable through Finanly.
3. **Never post to production before the side effects are understood.** The LCV moved the GL but
   not `Bin`, because ERPNext posts LCV GL entries immediately and queues the valuation repost.
4. **Verify before asserting.** Three different figures were reported for the same residue
   ($7.08 → $5.19 → $5.95) because the first two were computed against the pre-LCV receipt rate
   and missed `MAT-LCV-2026-00009` ($1,626, 10 Apr). Measure from ERPNext's own
   `stock_value_difference`.
5. **Bank Transactions must end `Unreconciled`** on the categorization path, never `Reconciled`.
