# Landed Cost Tool — Feature Handoff (V1)

**Audience**: any operator / engineer picking up the multi-invoice inbound shipment workflow.
**Replaces**: nothing — this is the new canonical handoff.
**Companion docs**:
- `LANDED_COST_TOOL_RUNBOOK_V1.md` (operator guide)
- `path_a_golden_standard.py` (Path A math reference, runnable)
- `plans/LANDED_COST_TOOL_WIRE_UP_PLAN_V1.md` (Phase A1–A5 delivery plan)
- `plans/LANDED_COST_TOOL_PHASE_A8_FIX_PLAN_V1.md` (Phase A8 audit-driven fix plan)

---

## Architecture

```
Browser /tools/landed-cost/shipments
       │
       ▼
Next.js BFF /api/core/[...path] (httpOnly cookie auth → Bearer token)
       │
       ▼
core-api (FastAPI, port 8000)
   ├─ POST/GET/PATCH/DELETE /v1/landed-cost/shipments/* (19 endpoints)
   │  ├─ FeatureGate.require("ui_menu_landed_cost")
   │  ├─ IdempotencyService for state-mutating endpoints
   │  ├─ emit_audit_event for every state transition
   │  └─ Engine: compute_shipment_allocation, validate_*, detect_display_rounding
   │
   └─ Connector calls (internal JWT, audience='connector-erpnext'):
      ├─ POST /v1/ledger/purchase-receipt
      ├─ POST /v1/ledger/purchase-invoice (with credit_to override)
      ├─ POST /v1/ledger/landed-cost-voucher/correction
      ├─ POST /v1/ledger/docs/cancel
      ├─ GET  /v1/ledger/docs/Purchase Receipt/{name}
      ├─ POST /v1/ledger/repost-item-valuation
      └─ PUT  /v1/ledger/items/{item_code}/valuation-method
       │
       ▼
connector-erpnext (FastAPI, port 7004)
       │
       ▼
ERPNext REST API (frappe)
```

**Key invariant**: core-api **NEVER** calls ERPNext directly. Every external hit goes through `connector-erpnext` via internal JWT. Verified by grep — zero `frappe` / `/api/resource` references in core-api.

## File map

| Layer | Path | Purpose |
|---|---|---|
| Models | `finanly/services/core-api/src/finanly_core_api/db/models.py:2002+` | Shipment, ShipmentInvoice, ShipmentInvoiceLine, ShipmentInvoiceCharge, SupplierDefaultAP |
| Migrations | `finanly/services/core-api/alembic/versions/0032_*.py`, `0033_*.py`, `0034_*.py` | 5 base tables, cost_center on charges, supplier_invoice_filename on invoices |
| Engine | `finanly/services/core-api/src/finanly_core_api/services/landed_cost_engine.py` | Path A redistribution, distribution bases, validators |
| Connector client | `finanly/services/core-api/src/finanly_core_api/services/landed_cost_connector_client.py` | All connector wrappers (PR/PI/LCV/cancel/repost/get_pr_doc) |
| Public router | `finanly/services/core-api/src/finanly_core_api/routers/landed_cost_shipment.py` | 19 endpoints |
| Connector | `finanly/services/connectors/erpnext/src/finanly_connector_erpnext/app.py` | All ERPNext-side endpoints (PR, PI, LCV correction, cancel, repost, FIFO toggle, valuation-method, get-doc) |
| UI | `ui/finanly-ui/app/tools/landed-cost/shipments/page.tsx` | Multi-invoice wizard |
| Sidebar | `ui/finanly-ui/components/layout/Sidebar.tsx:91-93` | Inbound Shipments nav entry |
| Engine tests | `finanly/services/core-api/tests/test_landed_cost_engine.py` | 16 unit tests |
| HTTP tests | `finanly/services/core-api/tests/test_landed_cost_shipment_post_e2e.py` | 3 integration tests with mocked connector |
| Math reference | `docs/features/landed_cost/path_a_golden_standard.py` | Canonical Path A redistribution math |
| Runbook | `docs/features/landed_cost/LANDED_COST_TOOL_RUNBOOK_V1.md` | Operator guide (13 sections) |

## Data model (5 tables, all RLS-protected)

```
shipments (id, shipment_ref, container_no, mawb_or_mbl, vessel, etd, eta,
           receiving_warehouse, ledger_company, status, idempotency_key,
           total_landed_target, created_at, posted_at, cancelled_at)

shipment_invoices (id, shipment_id, kind, bill_no, bill_date, supplier,
                   supplier_currency, exchange_rate, ap_account,
                   total_billed, file_attachment_id,
                   supplier_invoice_filename,
                   pr_doc_name, pi_doc_name, lcv_doc_name, posting_status)

shipment_invoice_lines (id, shipment_invoice_id, item_code, qty,
                        supplier_rate, line_amount, is_free,
                        fair_value_rate, weight_kg, volume_cbm,
                        cost_center, warehouse_override, idx)

shipment_invoice_charges (id, shipment_invoice_id, applies_to_invoice_id,
                          description, amount, expense_account,
                          charge_type, distribution_basis,
                          manual_allocations, cost_center, idx)

supplier_default_ap (tenant_id, supplier, ledger_company,
                     default_ap_account, notes)  PK = compound
```

## Key invariants

- Path A LCV's `total_taxes_and_charges = $0` (zero-net redistribution)
- Sum of Path A `applicable_charges` = $0 across all PR items
- Sample (Omaggio) lines: supplier_rate=$0, fair_value_rate>0, no Discount Received GL line
- After /post: `Stock - {category}` = supplier_net + sum(inventoriable charges) ± $0.01
- `Stock Adjustment - PUI` never holds > $0.01 from a single shipment
- **Date convention (CRITICAL — GAAP)**:
  - `PR.posting_date` = **physical warehouse arrival date** (Euro Cargo "Date Arrived" / Flexport receipt confirmation), NOT the supplier-invoice issue date
  - `PI.bill_date` = supplier invoice issue date (legal/tax record)
  - `PI.posting_date` = **same as PR.posting_date** (= arrival date) for goods PIs; broker-bill PIs keep their own bill_date as posting_date (when YOU book the bill — broker bills typically arrive close to or after physical receipt)
  - `LCV.posting_date` = arrival-aligned (must be ≥ PR.posting_date in ERPNext)
  - **Why**: ERPNext's LCV mechanic reposts the PR's GL inheriting `PR.posting_date`. If PR is dated to the supplier-invoice date and that crosses fiscal year, the LCV-reposted Cr lands in the wrong period while the cost-PI Dr (broker bill, broker-dated) lands in the next year → orphan expense + phantom contra in different fiscal years. See 2026-05-03 changelog entry for the exact incident on PR-9 / PR-10.

## What's done (as of 2026-05-02)

### A1–A5: scaffolding
- 5-table data model (migration 0032)
- Engine with Path A, distribution bases, display-rounding detection
- Connector FIFO toggle
- 19 public endpoints
- UI multi-invoice wizard

### A6–A7: real orchestration + compliance
- Full ERPNext orchestration in /post /cancel /amend (no terminal cop-out)
- All 19 endpoints carry SECURITY/TENANCY/IDEMPOTENCY/AUDIT annotations
- FeatureGate enforced on every endpoint
- Helper-derived account names (`_company_abbr`)

### A8.1: book-correctness
- cost_center on PR + PI + charges (migration 0033)
- Auto-reconcile post-Path-A (read PR, compute variance, post correction LCV if 0.005 < |v| < 1.00)
- Repost Item Valuation per (item, warehouse) after Path A
- bill_date strict (no fall-through)
- validate_lines_int_qty + validate_goods_total_billed pre-flights
- Per-invoice posting_status atomicity on partial failure

### A8.2: endpoint compliance
- All 19 endpoints carry the project-mandated 4-line annotation block
- Idempotency-Key required on /amend (peek/begin/finish wrap)

### A8.3: UI completeness
- Sidebar nav entry
- Post button gated on `preview.totals.path_a_total ≤ $0.01`
- Cancel + Amend buttons in detail view
- CompletionCertificate component (post-success display)
- CancelLogSection component
- Display-rounding ⚠ indicator on line rows
- cost_center input on Add Charge form

### A8.4: HTTP integration test
- 3 e2e tests with mocked connector (Pascucci scenario + 2 negative pre-flights)
- 19/19 tests pass

### A8.5: critical runtime fixes (caught by integration tests)
- get_db_tenant returns Session, not (Session, tenant_id) tuple — fixed across 19 endpoints
- success_envelope signature: `(*, data, request_id)` not positional dict — fixed across 22 call sites
- AP-account fallback `Accounts Payable - {abbr}` doesn't exist; real default is
  `Accounts Payable (A/P) - {abbr}` — fixed in `_default_ap_account`
- supplier_default_ap seeded for 20 Pascucci tenants × 5 suppliers = 100 rows
- ShipmentCharge TS type missing cost_center — fixed
- supplier_invoice_filename column added (migration 0034) for PDF audit reference

### A8.6: pickers + post-resume + amend auto-cancel + supplier_default_ap admin
- **Reference-list autocomplete**: 5 pickers (suppliers, items, accounts, cost
  centers, warehouses) wired into all 4 forms via the existing `/v1/ledger/*`
  endpoints. Operator types or selects from real ERPNext data — typo → 502
  mid-post is now nearly impossible.
- **POST /shipments/{id}/post-resume**: flips `cancelled_partial` → `allocated`
  so the operator can re-issue `/post`; connector remarks-tag idempotency
  ensures already-created docs are reused.
- **Auto-cancel source on amend post**: `post_shipment` now walks
  `audit_events` for the most-recent `shipment.amended` referencing the
  posted clone and cascade-cancels the source's PR/PI/LCV docs in ERPNext.
  Response includes `auto_cancel_source` with the per-doc cancel_log.
- **3 supplier_default_ap admin endpoints**: GET / PUT / DELETE at
  `/v1/landed-cost/shipments/_admin/supplier-default-ap` so tenants can
  manage AP mappings without raw SQL.

## Deployed state (2026-05-02)

| Component | Status |
|---|---|
| Container images | Rebuilt and running with the new code |
| Alembic head | `0034_lc_inv_filename` |
| Routes | 408 total; 19 shipment endpoints + 3 admin + 1 post-resume registered |
| Tests | 19/19 passing inside the rebuilt container |
| Seed data | 100 supplier_default_ap rows across 20 Pascucci tenants |

## Still pending — what would block a real shipment test

| # | Gap | Severity | Notes |
|---|---|---|---|
| 1 | **No browser smoke test** — UI compiles clean but hasn't been opened end-to-end | HIGH | Operator must run; can't be done from this side |
| 2 | **No real ERPNext smoke test** — all integration tests use mocked connector responses | HIGH | Operator must run with real auth; ERPNext-side schema mismatches surface here |
| 3 | **Real PDF binary upload to tabFile** — only filename text capture today | MEDIUM | Needs connector multipart endpoint → ERPNext `tabFile` API |
| 4 | **`charges.applies_to_invoice_id` cross-attach ignored in /post** | LOW | Multi-PR shipments only; Pascucci has 1 PR per shipment |
| 5 | **No FX revaluation** — `supplier_currency` + `exchange_rate` unused | LOW | Pascucci is USD-billed; not blocking |
| 6 | **No tenant-onboarding flow** — feature_key + ERPNext integration setup is manual | LOW | One-time per tenant; current ops process works |
| 7 | **PR.posting_date wrongly set to `bill_date` (supplier-invoice issue date)** — code: `routers/landed_cost_shipment.py:1441` `pr_posting_date = goods_inv.bill_date.isoformat()`; pre-flight error message at L1273-1287 explicitly enforces the wrong rule. **Should be physical arrival date.** Schema gap: `Shipment` model (`db/models.py:1963`) has `etd` + `eta` but NO `arrival_date` field. **First fixed in DB by surgery on 2026-05-03** for PR-9/PR-10/PI-23/PI-25/LCV-7/LCV-8 — the tool itself still emits wrong dates on next post. | **HIGH** | Required fix: (a) add `arrival_date date NOT NULL` to `shipments` table via new alembic migration, (b) UI Pane 1 collects "Date Arrived" (Euro Cargo customs invoice "Date Arrived" field, or Flexport "received_at"), (c) replace L1441 with `pr_posting_date = s.arrival_date.isoformat()`, (d) replace L1473 `primary_posting_date` similarly, (e) goods-PI posting_date should also use arrival_date (currently L1527 uses `inv.bill_date` — for goods PIs this needs to flip to arrival_date; for broker bills `inv.bill_date` stays correct), (f) keep LCV constraint `posting_date ≥ PR.posting_date` (already enforced by ERPNext). |

**CLOSED in A8.6**: pickers (#4 from prior list), `/post-resume` (#5 from prior list), `/amend` auto-cancel-on-post (#6 from prior list), supplier_default_ap admin UI (#10 from prior list).

## Key prerequisites in ERPNext (verified 2026-05-02)

For Pascucci USA Inc (`erpnext_recovered_20250814` DB):

- **Item**: `FINANLY-INBOUND-LOGISTICS` (is_stock_item=0, group=Services) — used for non-goods PI lines
- **Suppliers**: `Pascucci Italia`, `Caffé Pascucci Torrefazione SpA`, `Flexport - PUI`, `Euro Cargo Express`, `Eurocargo`
- **Accounts**:
  - `Loan from Pascucci Italia - PUI` (Payable, used for Pascucci goods)
  - `Flexport - PUI` (Payable, used for Flexport bills)
  - `Accounts Payable (A/P) - PUI` (default Payable, used for Euro Cargo)
  - `Stock Adjustment - PUI` (Stock Adjustment, used for variance LCV)
  - `Inbound Freight & Handling - Coffee/Machines - PUI` (Expense Account)
  - `Import Duties - Coffee/Machines - PUI` (Expense Account)
  - `Storage Coffee/Machines - PUI` (Expense Account, period_expense charges)
  - `Other Logistics Expense - PUI` (Expense Account, penalty/non-compliance)
- **Warehouses**: `Flexport Coffee - PUI`, `Flexport Machines - PUI`
- **Cost centers**: `eCommerce - Coffee - PUI` (and others — operator must specify per line)

## How to run end-to-end

1. Open `/tools/landed-cost/shipments` (sidebar → Inbound Shipments)
2. Click **+ New Shipment** → fill header (shipment_ref, container, ETA, warehouse, ledger company)
3. **+ Add Invoice** for each: 1 goods invoice + 1 customs invoice + 1 freight invoice + (optional) 1 3PL invoice
4. For the goods invoice: **+ Add Line** for each SKU (set is_free=true + fair_value_rate for Omaggio)
5. For non-goods invoices: **+ Add Charge** (set charge_type=inventoriable for capitalize, period_expense for P&L direct; pick distribution_basis and expense_account)
6. Click **Preview Allocation** — verify Path A net ≈ $0.00 and per-item table looks right
7. Click **Post to ERPNext** (gated until preview Path A ≤ $0.01)
8. Read the **Completion Certificate** card — confirms PR/PIs/LCVs created, reconcile variance ≤ $0.01, repost completed
9. If wrong: **Cancel + Cascade** (reverse-orders all ERPNext docs) or **Amend (Deep Clone)** to start over from a copy

---

## Verification plan — TEST WHEN POSSIBLE

The tool has been built end-to-end (data model, engine, orchestration,
UI, compliance, reconcile, repost, idempotency, audit) and verified with
mocked-connector integration tests (19/19 pass). The following items
**have NOT been tested against a real running shipment** because no test
shipment exists today (Pascucci March-2026 is already posted — LCV-19
in `erpnext_recovered_20250814`). Run these the next time a real
inbound shipment arrives.

### TEST-1 — Browser smoke (first thing to do)
**Trigger**: any time after deploy.

| Step | Expected | If broken |
|---|---|---|
| Open `/tools/landed-cost/shipments` (logged-in operator) | Page renders, sidebar shows "Inbound Shipments" | UI bug or auth/feature_key issue |
| Click **+ New Shipment** | Form appears with 8 fields including warehouse picker | Component crash |
| Type "Flex" in warehouse | Autocomplete shows `Flexport Coffee - PUI`, `Flexport Machines - PUI` | `/v1/ledger/warehouses` proxy or RLS issue |
| Submit → see new shipment in list | Status badge shows `draft` | DB write failed |

**Likely failure modes**: BFF proxy 401 (cookie not propagated), missing
feature flag (`ui_menu_landed_cost`) on tenant, datalist options empty
(connector list endpoint failing).

### TEST-2 — Real ERPNext post (the big one)
**Trigger**: when a new physical shipment arrives with PDFs in `~/Dropbox/Claude Files/cogs/<shipment>/`.

| Step | Expected | If broken |
|---|---|---|
| Add 1 goods invoice + 1 customs invoice (skip Flexport for first test) | Both visible with auto-resolved AP accounts | supplier_default_ap miss or supplier name typo |
| Add lines on goods invoice using item_code picker | Computed unit column shows correct rate; ⚠ icon if rate ≠ amount/qty | Display-rounding bug |
| For Omaggio line: toggle is_free, enter fair_value_rate | Form accepts; Path A target shows in preview | Engine bug |
| Add customs charge (Customs Duties → Import Duties - Coffee - PUI, Distribute by Amount) | Charge saved | Account picker filter or charge_type validation |
| Click **Preview** | Per-item table; sample at fair-value share; Path A net ≤ $0.01 | Engine math drift |
| Click **Post** | Completion Cert green; reconcile variance ≤ $0.01; repost log all `ok` | One of: PR creation 502, PI submit rejected (cost_center missing on P&L line), LCV manual-distribution sum mismatch, FINANLY-INBOUND-LOGISTICS not stocked |

**Verify books in ERPNext** (paste from runbook §12):
- `Stock - Coffee - PUI` GL = expected supplier_net + sum(charges) ± $0.01
- SRBNB net = $0 across PR + PI
- `Stock Adjustment - PUI` should not have grown by more than $0.01 from this shipment
- New LCV docs visible at `tabLanded Cost Voucher` with the right tags

### TEST-3 — Cancel + Amend
**Trigger**: same shipment as TEST-2, after successful post.

| Step | Expected |
|---|---|
| Click **Cancel + Cascade** | All PR/PI/LCV docs go to `docstatus=2`; `cancel_log` shows `cancelled` per row |
| GL re-check: shipment-induced balances all zero out | |
| OR click **Amend (Deep Clone)** instead, edit, post the clone | Source auto-cancels on clone post; `auto_cancel_source` in response |

### TEST-4 — Cancelled-partial recovery
**Trigger**: simulate by killing the connector mid-post (advanced).

| Step | Expected |
|---|---|
| Mid-post: connector dies → shipment lands in `cancelled_partial` | `posting_log` in audit captures step that failed |
| `POST /shipments/{id}/post-resume` (from API tester) | Status flips back to `allocated` |
| Re-issue `POST /shipments/{id}/post` | Idempotent: existing PR/PI/LCV docs reused via remarks-tag dedup; only the missing steps run |

### TEST-5 — Display-rounding catch
**Trigger**: same as TEST-2 but enter a deliberately rounded rate.

| Step | Expected |
|---|---|
| Enter line: qty=1008, rate=1.32, amount=1325.52 (the Pascucci 42203 trap) | UI shows ⚠ on the row; computed unit cell shows $1.3150 |
| Hover ⚠ → tooltip explains divergence | |
| Decide: type 1.315 in rate (matches computed) or accept rounded ($0.04 drift will be flagged on Preview Path A net) | |

### TEST-6 — Pre-flight rejections
**Trigger**: deliberately submit invalid data.

| Trigger | Expected response |
|---|---|
| Goods invoice with `bill_date=null` | `/post` → 409 `missing_bill_date` |
| Goods total_billed differs from sum(line_amount) by > $0.01 | `/post` → 409 `goods_total_mismatch` |
| Fractional `qty` on a line | `/post` → 422 `fractional_qty_not_supported` |
| Path A net > $0.01 (forgot to set fair_value_rate on Omaggio) | `/post` → 409 `path_a_residual_too_large` |

### TEST-7 — Supplier-default-AP admin
**Trigger**: add a new supplier mapping.

| Step | Expected |
|---|---|
| `PUT /v1/landed-cost/shipments/_admin/supplier-default-ap` with `{supplier:"Test Supplier", ledger_company:"Pascucci USA Inc", default_ap_account:"Accounts Payable (A/P) - PUI"}` | 200; row visible on `GET` |
| Create invoice with `supplier="Test Supplier"`, leave `ap_account` blank | Auto-resolves to the seeded account |
| `DELETE /_admin/supplier-default-ap?supplier=Test%20Supplier&ledger_company=Pascucci%20USA%20Inc` | 200; future invoices fall back to `Accounts Payable (A/P) - PUI` |

### TEST-8 — FIFO toggle (per-item, optional)
**Trigger**: change valuation method on one item.

| Step | Expected |
|---|---|
| `PUT /v1/ledger/items/PUI-CAP-RISERVA-20/valuation-method` body `{valuation_method:"FIFO"}` | 200; before='Moving Average' after='FIFO' |
| Receive new PR for that item | SLE row uses FIFO layer accounting (forward-only) |

### TEST-9 — Date convention (arrival_date drives PR.posting_date)
**Trigger**: any new shipment after gap #7 is fixed.

**Pre-condition**: shipment with goods invoice `bill_date = 2026-X-Y` and `arrival_date = 2026-X-Z` where Z > Y (and ideally crosses a fiscal-year boundary to make the bug visible).

| Step | Expected | If broken |
|---|---|---|
| Header pane shows separate `eta` and `arrival_date` fields | Both required; `arrival_date` accepts a value distinct from `eta` and from `bill_date` | Schema migration not applied or UI form missing |
| Post the shipment | PR.posting_date = `arrival_date`; goods PI.posting_date = `arrival_date`; goods PI.bill_date = supplier-invoice issue date (unchanged) | If PR.posting_date = bill_date, the gap #7 fix did not land |
| Inspect ERPNext: `SELECT name, posting_date FROM tabPurchase Receipt WHERE name LIKE 'MAT-PRE-...'` | posting_date matches the operator-entered arrival_date | Code still uses bill_date |
| Inspect ERPNext: `SELECT name, posting_date, bill_date FROM tabPurchase Invoice WHERE name=...` (goods PI) | posting_date = arrival_date; bill_date = supplier-invoice issue date | Goods PI tied to wrong date side |
| Inspect ERPNext: `SELECT name, posting_date, bill_date FROM tabPurchase Invoice WHERE supplier IN ('Euro Cargo Express','Flexport - PUI')` (broker PIs) | posting_date = bill_date (broker invoice date — unchanged behavior) | Broker PI date logic regressed |
| Inspect ERPNext: `SELECT name, posting_date FROM tabLanded Cost Voucher WHERE name=...` | posting_date ≥ PR.posting_date (ERPNext-enforced); ideally = arrival_date | LCV-before-PR validation will fail at submit anyway |
| Inspect GL: `SELECT account, fiscal_year, SUM(debit-credit) FROM tabGL Entry WHERE account IN ('Import Duties - Coffee - PUI','Inbound Freight & Handling - Coffee - PUI') AND voucher_no IN (PR-name, broker-PI-name) GROUP BY account, fiscal_year` | Each (account, fiscal_year) row → SUM = $0 (full wash within the same FY) | Cross-year split → orphan Dr or Cr in one year |

**Why this test exists**: 2026-05-03 surgery on PR-9 (Coffee, supplier_bill_date 2025-12-24, arrival 2026-02-20) and PR-10 (Machine, supplier_bill_date 2026-01-16, arrival 2026-03-01) had to fix `$9,523.79 + $1,976.00` of Coffee duty/freight that crossed the FY2025→FY2026 boundary because PR.posting_date was wrongly = bill_date. The tool currently re-introduces this bug on every post (gap #7).

### Reference SQL diagnostics

For any post-failure investigation, run the queries in
`LANDED_COST_TOOL_RUNBOOK_V1.md` §12 (PR / GL / LCV / SLE / Bin) against
the active ERPNext database (`erpnext_recovered_20250814` for Pascucci).

### What "PASS" means

Across all 8 tests:
- No 5xx from connector or core-api
- All Path A nets ≤ $0.01
- All reconcile variances ≤ $0.01
- Books in ERPNext match: `Stock - {category}` = supplier_net + capitalized charges (within $0.01)
- `Stock Adjustment - PUI` never accumulates > $0.01 from a single shipment
- Audit log has the expected events: `shipment.created`, `shipment.posted`, `shipment.amended`, `shipment.cancelled`, etc.

If any test fails: file the symptom + the GL diagnostic output + the
shipment_id; the engineer (or future agent) picks up from there. The
reference data point is **Pascucci March-2026 Coffee shipment (LCV-19)**
which proved the Path A math reconciles to-the-cent under real ERPNext
conditions.

### Known-but-deferred tests

These are NOT in the verification plan because they require additional
implementation first:

- **PDF binary upload to `tabFile`** — backend column captures filename
  text only; binary upload deferred.
- **`charges.applies_to_invoice_id` cross-attach** — engine ignores it
  in `/post`; charges always attach to the first goods invoice's PR.
  Multi-PR shipments (rare for Pascucci) would mis-route.
- **FX revaluation** — `supplier_currency` + `exchange_rate` columns
  exist but are not wired into PI posting. Pascucci is USD-billed, not
  blocking.
- **Tenant onboarding** — adding a new tenant still requires manual SQL
  to insert the feature flag + ERPNext IntegrationInstance row.

## Related commits (in chronological order)

```
b3d5927 checkpoint: BI/UI/Cube WIP + landed-cost Path A golden standard + wire-up plan
cc0460a Phase A1: shipment data model (5 tables)
b0f1d82 Phase A2: engine extension
0058e8b Phase A3: connector — FIFO toggle endpoint
13a2458 Phase A4: 19 public shipment endpoints
cc4da98 Phase A5: UI shipment wizard
b7d51a1 Phase A6: full ERPNext orchestration in /post /cancel /amend
9356bc2 Phase A7: finanly.ai compliance sweep
9a9fca3 Phase A7b: HTTP-level idempotency on /post
791c53a Phase A8.1: book-correctness fixes
5744804 Phase A8.2: endpoint compliance
1d1e44c Phase A8.3: UI completeness
c95f04a Phase A8.4: HTTP integration test + 2 critical runtime bugs fixed
9c2c71a Phase A8.5a: real ERPNext defaults + supplier seeds
63367df Phase A8.5b: TS types + supplier_invoice_filename
```

## Test commands

```bash
# Inside the core-api container:
docker exec -e FINANLY_ALLOW_LIVE_DB_TESTS=1 -e FINANLY_DB_TESTS=1 finanly-core-api-1 \
  bash -c "cd /app && python3 -m pytest tests/test_landed_cost_engine.py tests/test_landed_cost_shipment_post_e2e.py -v"
# Expect: 19 passed

# Apply migrations (already done; this is a no-op):
docker exec finanly-core-api-1 bash -c "cd /app && alembic upgrade head"
# Expect: 0034_lc_inv_filename (head)

# Re-run path_a_golden_standard math:
python3 docs/features/landed_cost/path_a_golden_standard.py
# Expect: Stock - Coffee TOTAL: $61,386.45
```

## Reference SQL (read-only diagnostics)

See `LANDED_COST_TOOL_RUNBOOK_V1.md` §12 for the diagnostic SQL block (PR / GL / LCV / SLE / Bin queries).
