# Landed Cost Tool — Maintenance Runbook (V1)

**Audience**: future operators (Luca + finance ops) booking inbound shipments into Finanly/ERPNext.
**Created**: 2026-05-02
**Version**: V1
**Replaces**: `plans/old/LANDED_COST_PARITY_IMPLEMENTATION_PLAN_V1.md`, `plans/old/PARITY_CONTRACT_LOCK_LANDED_COST_V1.md` (both deleted as superseded).
**Companions**: `path_a_golden_standard.py` (in this folder), `plans/LANDED_COST_TERMINAL_WORKFLOW_V1.md`, `plans/PHASE_F_LANDED_COST_AND_BASELINE_RUNBOOK_V1.md`, `plans/LANDED_COST_TOOL_WIRE_UP_PLAN_V1.md` (the Phase A delivery plan).
**Worked example**: Pascucci March-2026 Coffee + Machine shipment (containers `CICU6794096` + `CLKU5008331`). Every number in this doc is real, queryable from `erpnext_recovered_20250814` MariaDB.

---

## 1. Purpose

Use this tool when an **inbound shipment arrives** with **multiple invoices** and you need landed-cost-correct inventory + COGS in ERPNext.

A typical shipment generates 4 to 6 invoices that must roll up to one cost basis:
- Supplier goods invoice (Pascucci IT, in USD or EUR)
- Customs/duties invoice (broker — Euro Cargo, etc.)
- Freight invoice (sometimes the same broker, sometimes a separate forwarder)
- 3PL inbound invoice (Flexport-style: prep, palletizing, receipt-storage)
- Optionally: a separate "additional fees" invoice from the broker (Food & Drug, ISF, AMS, Brokerage)

All of these must land on the **same physical inventory** at the right per-unit cost. Charges that aren't inventoriable (post-receipt storage, penalties, DTC fees) must hit the P&L instead, never inventory.

**The tool's job**: take the raw invoices, distribute charges correctly, post a clean `Purchase Receipt → Purchase Invoice(s) → Landed Cost Voucher(s)` chain in ERPNext, and produce a reconciliation report that ties to the supplier invoice totals to-the-cent.

---

## 2. Conceptual model

```
┌──────────────────────────────────────────────────────────────────────┐
│ SHIPMENT (one container/MAWB)                                        │
│   shipment_ref = "march_2026_coffee_CICU6794096"                     │
│   container_no = "CICU6794096"                                       │
│   receiving_warehouse = "Flexport Coffee - PUI"                      │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   ┌──────────────────────────────────────────────────────────────┐   │
│   │ INVOICE #1   kind=goods                                      │   │
│   │   bill_no = "2025-VA-0002522"   supplier = "Pascucci Italia" │   │
│   │   ap_account = "Loan from Pascucci Italia - PUI"             │   │
│   │   total = $49,886.66                                         │   │
│   │   ┌─ LINES ─────────────────────────────────────────────┐    │   │
│   │   │  9 line items, each: qty/rate/amount/Omaggio?       │    │   │
│   │   │  e.g. PUI-INS-AMERICANO-SAMPLE 4995 × $0   FREE     │    │   │
│   │   │                                  fair_value $1.32   │    │   │
│   │   └─────────────────────────────────────────────────────┘    │   │
│   │   ┌─ CHARGES (within this invoice) ──────────────────┐       │   │
│   │   │  none on Pascucci's goods invoice                │       │   │
│   │   └──────────────────────────────────────────────────┘       │   │
│   └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
│   ┌──────────────────────────────────────────────────────────────┐   │
│   │ INVOICE #2   kind=customs_duties + freight                   │   │
│   │   bill_no = "1335680-01"        supplier = "Euro Cargo"      │   │
│   │   ap_account = "Accounts Payable - PUI"                      │   │
│   │   total = $9,873.79                                          │   │
│   │   ┌─ CHARGES ────────────────────────────────────────────┐   │   │
│   │   │  Customs Duties      $9,523.79  → Import Duties      │   │   │
│   │   │                                    inventoriable     │   │   │
│   │   │                                    basis: Amount     │   │   │
│   │   │  Documentation       $95.00     → Inbound Freight    │   │   │
│   │   │  AMS Transmission    $30.00       inventoriable      │   │   │
│   │   │  Brokerage           $125.00      basis: Amount      │   │   │
│   │   │  ISF (10+2)          $25.00                          │   │   │
│   │   │  Food & Drug Fee     $50.00                          │   │   │
│   │   │  AMS-Organic USDA    $25.00                          │   │   │
│   │   └──────────────────────────────────────────────────────┘   │   │
│   └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
│   ┌──────────────────────────────────────────────────────────────┐   │
│   │ INVOICE #3   kind=3pl_inbound                                │   │
│   │   bill_no = "642158" + "645137"  supplier = "Flexport"       │   │
│   │   ap_account = "Accounts Payable - PUI"                      │   │
│   │   total = $3,324.95 (per Flexport bill totals)               │   │
│   │   ┌─ CHARGES ────────────────────────────────────────────┐   │   │
│   │   │  Prep BCL Labels  $1,167.00 → Inbound Freight        │   │   │
│   │   │                                inventoriable         │   │   │
│   │   │  Inbounding Hand. $221.00   → Inbound Freight        │   │   │
│   │   │                                inventoriable         │   │   │
│   │   │  Receipt Storage  $238.00   → Inbound Freight        │   │   │
│   │   │                                inventoriable         │   │   │
│   │   │  Storage DTC      $506.50   → Storage Coffee/Mach    │   │   │
│   │   │                                period_expense        │   │   │
│   │   │  B2B Non-Compl.   $392.00   → Other Logistics Exp    │   │   │
│   │   │                                period_expense        │   │   │
│   │   │  …other penalties                                    │   │   │
│   │   └──────────────────────────────────────────────────────┘   │   │
│   └──────────────────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────────┘
                              ↓ Tool's job ↓
┌──────────────────────────────────────────────────────────────────────┐
│ ERPNext docs created (idempotently):                                 │
│  • 1 Purchase Receipt    (per goods invoice)                         │
│  • N Purchase Invoices   (one per invoice; period_expense lines      │
│                           land directly on P&L accounts)             │
│  • M Landed Cost Vouchers — one per inventoriable charge invoice +   │
│                              one Path A LCV if any Omaggio line      │
│  • Per-item Bin updated to new valuation_rate                        │
│  • Reconciliation report                                             │
└──────────────────────────────────────────────────────────────────────┘
```

**Key concepts**:
- A **Shipment** holds N invoices (1 goods + customs + freight + 3PL = 4 typical).
- Each **Invoice** belongs to exactly one shipment, has one supplier, one AP account, one set of line items (only `kind=goods`) and/or charges (any kind).
- **Lines** are items received into stock, with optional `is_free` (Omaggio) toggle.
- **Charges** are amounts that distribute to the inventory cost OR hit the P&L directly, depending on `charge_type`.
- A `charges.applies_to_invoice_id` link lets a freight invoice's charges attach to a *different* invoice's line items (typical: customs on Euro Cargo invoice → Pascucci goods receipt).

---

## 3. Workflow at `/tools/landed-cost`

The wizard (Phase A, post-merge — see `plans/LANDED_COST_TOOL_WIRE_UP_PLAN_V1.md`) has 3 panes:

### Pane 1 — Shipment header
Enter once per physical shipment:
- Container number, MAWB/MBL, vessel, ETD, ETA
- **Date Arrived** (physical warehouse receipt date — Euro Cargo customs invoice "Date Arrived" or Flexport receipt confirmation). **CRITICAL — drives PR/PI/LCV posting_date per GAAP.** Do NOT confuse with ETA (planned) or supplier `bill_date` (Pascucci's invoice date in Italy). See §3a.
- Receiving warehouse (default `Flexport Coffee - PUI` for Pascucci coffee; `Flexport Machines - PUI` for machines)
- Ledger company (`Pascucci USA Inc`)

> **NOTE (2026-05-03)**: The tool currently does NOT yet collect `arrival_date` separately and wrongly defaults `PR.posting_date` to the supplier `bill_date`. This is gap #7 in `LANDED_COST_TOOL_HANDOFF_V1.md`. Until fixed, the operator MUST manually align shipment timing or accept that PR.posting_date will be the supplier-invoice date. If the supplier `bill_date` and physical arrival cross a fiscal year, **stop and escalate before posting** — the LCV-reposting mechanic will leak duty/freight credit into the prior fiscal year (see runbook §3a).

### Pane 2 — Invoices (repeating)

For each invoice:
1. Click **+ Add invoice**, pick `kind` (goods / customs_duties / freight / 3pl_inbound)
2. Enter bill number, bill date, supplier, total billed, currency, exchange rate (lock at posting)
3. **AP account picker** auto-populates from `supplier_default_ap` (e.g., Pascucci → `Loan from Pascucci Italia - PUI`); override per invoice if needed
4. Drag-and-drop the **PDF** to attach (stored, queryable later for audit)
5. **Lines** (only for `kind=goods`):
   - Item picker (autocomplete)
   - qty, supplier_rate, line_amount
   - **Display-rounding warning**: if `abs(line_amount/qty − supplier_rate) > 0.005`, the row turns yellow with hint "computed unit is $X but you entered rate $Y — confirm which to use"
   - **`is_free` toggle**: when on, supplier_rate forces to 0, line_amount forces to 0, and a **fair_value_rate** field appears (auto-suggested from item's last list price)
   - weight_kg, volume_cbm (for Volume/Weight charge distribution downstream)
   - cost_center, warehouse_override (rare)
6. **Charges** (any kind):
   - Description, amount
   - **Expense account**: where the charge posts in P&L if `charge_type=period_expense`, OR which expense gets credited when the LCV capitalizes the charge into stock
   - **`charge_type`**: `inventoriable` (becomes part of landed cost) vs `period_expense` (goes straight to P&L; never enters inventory)
   - **`distribution_basis`**: `Amount` / `Qty` / `Weight` / `Volume` / `Manual` (only matters when `charge_type=inventoriable`)
   - **`applies_to`**: which invoice's line items receive the charge — usually "this invoice" but can be "all goods invoices in shipment" (e.g., a freight invoice attaches to the goods invoice receipt)

### Pane 3 — Preview & Post

Click **Preview**:
- **Per-item allocation table**: qty × supplier_rate + each charge contribution = landed total + $/u
- **Path A column** (highlighted): for Omaggio lines, the fair-value redistribution share. Sample lines show positive (they inherit charges); other lines show small negative (they shed proportional charges); column sums to $0
- **GL preview**: full Dr/Cr table of what the post will write (account, amount). User sees this before committing
- **Reconciliation check**: "PI totals: $X. PR + LCV totals: $Y. Difference: $Z (must be ≤ $0.01)."

If reconciliation passes, **Post to ERPNext** is enabled. Click it:
- Creates 1 PR per goods invoice
- Creates 1 PI per invoice (period_expense lines included as PI line items hitting their expense_account)
- Creates 1 LCV per inventoriable-charge invoice
- Creates 1 Path A LCV if any line is `is_free=true`
- Returns to `/tools/landed-cost/shipments/{id}/report` (the completion certificate)

---

## 3a. Date convention (CRITICAL — GAAP)

**Rule** (apply on every shipment):

| ERPNext field | Source | Notes |
|---|---|---|
| `Purchase Receipt.posting_date` | **Physical warehouse arrival date** | Euro Cargo customs invoice → "Date Arrived" field. Or Flexport receipt confirmation. NOT supplier `bill_date`. |
| `Purchase Invoice.bill_date` (goods) | Supplier invoice issue date | Pascucci's "Data" / "date" on `2025-VA-0002522` etc. Legal/tax record — preserved as-is. |
| `Purchase Invoice.posting_date` (goods) | Same as PR.posting_date (= arrival date) | When YOU book the bill. Goods PI is the AP-side of the same physical event. |
| `Purchase Invoice.bill_date` / `posting_date` (broker — Euro Cargo, Flexport) | Broker invoice date | Broker-bill PIs use the broker's invoice date for both fields (broker dates and warehouse arrival are typically within days). |
| `Landed Cost Voucher.posting_date` | Arrival-aligned | ERPNext requires `LCV.posting_date ≥ PR.posting_date`. If broker bill predates arrival, set LCV to arrival date (cannot be before PR). |

### Why arrival_date — not bill_date — drives PR.posting_date

ERPNext's LCV mechanic does NOT use `LCV.posting_date` for the GL repost. **It reposts the PR's GL entries inheriting `PR.posting_date`**. So:

- LCV reposts: `Cr Inbound Freight & Handling - Coffee` $1,976 → dated `PR.posting_date`
- LCV reposts: `Cr Import Duties - Coffee` $9,523.79 → dated `PR.posting_date`
- LCV reposts: `Dr Stock - Coffee` (capitalized total) → dated `PR.posting_date`

Meanwhile the cost PI (broker bill, e.g. Euro Cargo) posts the matching debits at the broker's invoice date:
- `Dr Inbound Freight & Handling - Coffee` $1,976 → dated broker invoice date
- `Dr Import Duties - Coffee` $9,523.79 → dated broker invoice date
- `Cr Accounts Payable` (broker AP) → dated broker invoice date

These two halves must wash to **$0 in the same fiscal year**. They will only do so if `PR.posting_date` is approximately the same period as the broker invoice date — which is what physical arrival gives you. Setting `PR.posting_date` to the supplier-invoice issue date (which can be months earlier — e.g., Pascucci ships from Italy in December but containers arrive in February) is what causes orphan expense + phantom contra entries in different fiscal years.

### Pre-flight check before posting

Before clicking **Post**, look at the dates panel of the shipment:

```
Goods bill_date      = 2025-12-24   (supplier in Italy)
Goods arrival_date   = 2026-02-20   (Euro Cargo "Date Arrived")
Customs bill_date    = 2026-02-18   (Euro Cargo broker)
Freight bill_date    = 2026-04-06   (Flexport)
```

**STOP AND ESCALATE** if:
- `bill_date` and `arrival_date` are in different fiscal years
- AND the tool has not yet been fixed to use `arrival_date` for `PR.posting_date` (gap #7 in handoff doc)

The 2026-05-03 incident: Pascucci March-2026 Coffee + Machine shipments crossed FY2025→FY2026 because supplier `bill_date` (2025-12-24 / 2026-01-16) was used for `PR.posting_date` instead of arrival (2026-02-20 / 2026-03-01). This produced:
- FY2025 P&L: $11,499.79 phantom credit on `Import Duties - Coffee` + `Inbound Freight & Handling - Coffee` (LCV-reposted Cr, no offset)
- FY2026 P&L: $11,499.79 orphan debit on the same accounts (cost-PI Dr, no offset)
- FY2025 BS: `Stock - Coffee` inflated by $11,499.79 a year too early

The fix: surgically updated 40 rows across MariaDB (parent docs + GL Entry posting_date+fiscal_year + SLE posting_date + Payment Ledger Entry posting_date + Serial and Batch Bundle posting_date) and 12 rows in Finanly Postgres (`ledger_gl_entries`). All wrapped in atomic transactions with row-count assertions.

See `temp/lcv_date_surgery_2026-05-03.sql` (MariaDB stored procedure with assertions) and `temp/lcv_pg_surgery_2026-05-03.sql` (Postgres canonical mirror) for the exact transaction.

---

## 4. Path A (Omaggio / ASC 705-20 fair-value redistribution)

### Why Path A exists

When a supplier invoices free goods ("Omaggio" / "Sample" / promotional units) on the same invoice as paid goods, US GAAP **ASC 705-20** says: the free goods are **vendor consideration tied to inventory purchase**, NOT income. The right treatment is to:

1. NOT book a "Discount Received" line in P&L (that would treat it as income, wrong)
2. NOT book a "Purchase Discount" contra-expense to COGS at receipt (that would deflate COGS, wrong)
3. **Redistribute the inventoriable charges** (duties + freight + 3PL inbound) so the free units carry their **fair-value share** of charges, and the paid units carry slightly LESS

This way:
- Total `Stock - Coffee` = supplier net pay + total inventoriable charges (unchanged from the simple case)
- Free units sit in inventory at $X/u of charges only (no list price)
- Paid units sit at slightly lower per-unit cost than they would if the free units had absorbed $0
- COGS is correct at the moment of sale (no P&L hit on receipt; flows through naturally)

### Worked example: Pascucci March-2026 Coffee

The real shipment in the books right now (LCV-19, posted 2026-05-02):

**Inputs**:
- Goods invoice net pay: **$49,886.66** (sample line at $0 due to Omaggio)
- Sample fair value: **4,995 × $1.32 list = $6,593.40** (the price the supplier WOULD have charged)
- Total fair-value base: **$56,480.06** (= invoice Tot.merce, paid lines + sample at fair)
- Total inventoriable charges: **$11,499.79** (= $9,873.79 Euro Cargo + $1,626.00 Flexport inbound)

**Math** (full implementation: `path_a_golden_standard.py` in this folder):

For each item, the **fair-value charge target** is:
```
target_charge_i = (qty_i × fair_unit_i / total_fair_value) × total_inventoriable_charges
```

For each item, the **Path A applicable_charge** (what the new Path A LCV stores) is:
```
path_a_applicable_charge_i = target_charge_i − (LCV_A_share_i + LCV_B_share_i)
```

where `LCV_A_share_i` and `LCV_B_share_i` are the per-item allocations of the Euro Cargo and Flexport LCVs respectively (computed by Distribute-by-Amount on book amounts).

**Invariant**:
```
sum( path_a_applicable_charge_i ) = $0.00 across all items
```

The Path A LCV is a **zero-net redistribution**. It moves charge dollars FROM paid items TO the sample item without changing the total.

**Result** (Pascucci sample, PUI-INS-AMERICANO-SAMPLE):
- Path A applicable_charges: **+$1,342.47**
- Per-unit landed cost: $1,342.47 ÷ 4,995 = **$0.2688/u**
- Supplier rate stays $0; no income line, no contra-expense booked

**Result** (one paid item, PUI-CAP-RISERVA-20):
- LCV-7 share + LCV-9 share: $2,072.95
- Path A applicable_charges: −$241.99
- Net charges absorbed: $1,830.96
- Per-unit landed cost: ($8,992.56 + $1,830.96) ÷ 2,136 = **$5.0672/u**
  (vs. $5.07 if no Omaggio existed)

### Sum check (the cents must match)

```
sum(book) + sum(charges) = sum(landed)
$49,886.66 +  $11,499.79 = $61,386.45  ✓
```

### Read the canonical script

For the exact rounding strategy, residual handling, and numeric examples: open `path_a_golden_standard.py` next to this doc. It runs standalone:
```bash
python3 docs/features/landed_cost/path_a_golden_standard.py
```
and prints the full per-item table for the Pascucci shipment.

### Common Path A traps

| Trap | Symptom | Fix |
|---|---|---|
| Sample fair-value-rate left at 0 | Sample lands at $0/u (uninventoried) | UI auto-suggests from item's last list price; user must confirm before posting |
| Forgot to mark `is_free=true` | Sample treated as paid; supplier net pay overstated | Display-rounding warning catches this if line_amount=0 + supplier_rate=0 + qty>0 (degenerate); explicit toggle prevents it |
| Path A LCV's per-line shares don't sum to $0 | ERPNext rejects "Distribute Manually" LCV | Engine adjusts the **largest line's** share by the residual cent so the sum is exact |
| Sample line gets absorbed into LCV-7+9 distribution-by-amount math | Sample gets $0 charge share (since book = $0); paid items overshoot | This is correct — by-amount distribution gives sample $0; Path A LCV is what gives sample its fair-value share |

---

## 5. Distribution modes

When an `inventoriable` charge is added to an invoice, pick a `distribution_basis`:

| Basis | When to use | What it weights by |
|---|---|---|
| **Amount** | Default for most charges. Customs duties typically by Amount (matches HTS-by-value duty calc) | `line.amount` (book amount per item) |
| **Qty** | Per-piece processing fees (e.g., Prep Labels charged per unit) | `line.qty` |
| **Weight** | Sea/air freight charged by weight (LCL freight, dim weight) | `line.weight_kg` |
| **Volume** | Sea freight by CBM, palletized handling | `line.volume_cbm` |
| **Manual** | Path A redistribution, or any case where you have hand-computed per-line allocations | per-line `applicable_charges` you provide |

### Italian display-rounding trap (mandatory validator)

Italian invoices (Pascucci, others) print prices with **2-decimal display rounding**. Example from `2025-VA-0002522`:

| Code | Printed price | Printed amount | Computed (amount ÷ qty) |
|---|---|---|---|
| 31385 | `2,63` | `2.104,00` | $1.315 (= 2,104 ÷ 1,600) |
| 42203 | `1,32` | `1.325,52` | $1.315 (= 1,325.52 ÷ 1,008) |

The printed price column is **display-rounded** to 2 decimals; the actual computational unit is held at higher precision and only the line Amount preserves it. **Always trust the line Amount, not the printed price.**

The tool's validator:
```python
def detect_display_rounding(line):
    # > $0.005 means more than half a cent diverges → display rounding
    return abs(line.line_amount / line.qty - line.supplier_rate) > Decimal('0.005')
```

If triggered, the UI shows:
> ⚠️ Computed unit is **$1.315** but you entered rate **$1.32**. Italian invoices display-round prices. Use computed value (recommended) or override.

The Pascucci March-2026 reconciliation took 2 painful sessions because PR-9 was originally booked at the **printed** $1.32 instead of the **computed** $1.315 — overstating SRBNB by $13.04 and burning $13.04 into Stock Adjustment (→ COGS) before being surgically fixed. **Don't repeat this.**

---

## 6. Capitalizable vs period charges

Per **ASC 330**, only costs to **bring inventory to its current location and condition for sale** are capitalizable. Post-receipt holding costs and penalties go straight to P&L.

| Charge | charge_type | Reason |
|---|---|---|
| Customs duties | `inventoriable` | Cost of import; required to bring goods into US |
| Freight (ocean/air) | `inventoriable` | Cost to move goods to receiving warehouse |
| Brokerage / ISF / AMS / Documentation | `inventoriable` | Pre-receipt clearance costs |
| Food & Drug Fee | `inventoriable` | Required for FDA-regulated import clearance |
| AMS-Organic USDA | `inventoriable` | Required for organic-import clearance |
| 3PL Inbounding Handling (palletization, receipt) | `inventoriable` | Pre-shelf prep |
| 3PL Prep / Labels | `inventoriable` | Required to make goods saleable |
| **Storage fees (DTC, monthly)** | **`period_expense`** | Holding cost AFTER receipt; not inventoriable per ASC 330 |
| **B2B Non-Compliance Fees** | **`period_expense`** | Penalty; not a cost of acquisition |
| **Over-Receive / Unexpected SKU fees** | **`period_expense`** | Operational penalty |
| **Missing Container Label** | **`period_expense`** | Penalty |

### Flexport bill 642158 worked example

| Line | Amount | charge_type | Account |
|---|---|---|---|
| Prep - BCL Labels | $1,167.00 | inventoriable | Inbound Freight & Handling - Coffee - PUI |
| B2B Non-Compliance Fees | $392.00 | period_expense | Other Logistics Expense - PUI |
| Over Receive | $16.00 | period_expense | Other Logistics Expense - PUI |
| Unexpected SKU | $108.00 | period_expense | Other Logistics Expense - PUI |
| Storage Fees - DTC (Coffee) | $197.08 | period_expense | Storage Coffee - PUI |
| Storage Fees - DTC (Machines) | $261.62 | period_expense | Storage Machines - PUI |
| **Total billed** | **$2,141.70** | | |
| **Capitalized to inventory** | **$1,167.00** | (Prep BCL Labels only) | |
| **Expensed directly** | **$974.70** | | |

The PI for this invoice has 6 line items; only **Prep BCL Labels** flows into the LCV; the other 5 hit P&L when the PI posts.

---

## 7. AP account selection

Two patterns exist depending on supplier relationship:

| Pattern | Account | When to use |
|---|---|---|
| **Inter-company loan** | `Loan from Pascucci Italia - PUI` | Pascucci IT supplies Pascucci USA on a running balance / loan structure (not arms-length AP). All Pascucci invoices credit this account. |
| **Standard AP** | `Accounts Payable - PUI` | Arms-length suppliers: Euro Cargo, Flexport, USDA fees, etc. |

### Default + override model

The `supplier_default_ap` table holds the default per supplier:
```sql
SELECT supplier, ledger_company, default_ap_account FROM supplier_default_ap;
```

| supplier | ledger_company | default_ap_account |
|---|---|---|
| Pascucci Italia | Pascucci USA Inc | Loan from Pascucci Italia - PUI |
| Flexport - PUI | Pascucci USA Inc | Accounts Payable - PUI |
| Euro Cargo Express | Pascucci USA Inc | Accounts Payable - PUI |

UI auto-fills from this on supplier select; user can **override per invoice** in the wizard if a one-off treatment is needed (e.g., a Pascucci shipment paid against AP because the loan is being settled).

### Known suppliers in books today (Pascucci USA Inc)

Run this query to see active AP-like accounts:
```sql
SELECT name, account_name, root_type FROM tabAccount
WHERE company='Pascucci USA Inc' AND account_type IN ('Payable')
ORDER BY name;
```

---

## 8. FIFO toggle

ERPNext defaults to **Moving Average** valuation. To switch to FIFO for an item:

```bash
# Per-item, opt-in (default OFF)
curl -X PUT $CONNECTOR/v1/ledger/items/PUI-CAP-RISERVA-20/valuation-method \
  -H "Authorization: Bearer $JWT" \
  -H "X-Tenant-Id: $TENANT" \
  -d '{"valuation_method":"FIFO"}'
```

Or via UI: `/tools/products` page, per-item toggle.

### Important behavior

- **Forward-only**: existing SLE rows are NOT retroactively recalculated. FIFO applies to receipts AFTER the toggle date.
- **Cutover documentation**: when toggling, log the cutover date in `audit_events` (the connector does this automatically). Future questions like "why did COGS jump on April 4?" are answered by the audit log.
- **Cannot mix per warehouse**: Item.valuation_method is item-global. If you need different methods per warehouse for the same item, that's a different ERPNext feature (Stock Reservation) — out of scope here.

### Why the user usually wants FIFO

Moving Average drifts when receipts arrive at different rates over time — recent expensive receipts blend with older cheap ones, hiding gross-margin signals on individual lots. FIFO consumes oldest layers first, so margin reports reflect the actual cost layers being sold.

---

## 9. Idempotency, cancel, amend

### Idempotency

Every shipment has an `idempotency_key`. All ERPNext docs created carry this key in their `remarks`:
```
remarks = "Finanly shipment={shipment_ref} kind={invoice_kind} bill={bill_no}"
```

Re-posting the same shipment (e.g., network glitch retry) returns existing PR/PI/LCV doc names — no duplicates.

### Cancel

`POST /v1/landed-cost/shipments/{id}/cancel` walks the created docs in **reverse** posting order:

1. Cancel Path A LCV (if any)
2. Cancel charge LCVs (customs LCV, freight LCV, 3PL LCV)
3. Cancel Purchase Invoices
4. Cancel Purchase Receipt(s)

ERPNext's cascade ensures GL/SLE entries become `is_cancelled=1`. The shipment row marks `status='cancelled'`, `cancelled_at` populated, audit log entry written.

If any single doc fails to cancel (e.g., dependent stock movement exists), the cascade halts and `status='cancelled_partial'` with the orphan list logged for manual cleanup.

### Amend

`POST /v1/landed-cost/shipments/{id}/amend` clones the shipment to a new draft. User edits as needed, then posts. On successful post, the original is auto-cancelled. Use this when:
- Supplier sent a corrected invoice
- You found an entry error after posting
- Charges were misrouted (inventoriable vs period)

### Hard rule

**Never edit an `is_cancelled=1` LCV's child rows directly in DB**. Cancellation is the audit trail; surgical edits create silent drift. Use cancel + amend.

---

## 10. Reconciliation report

Generated automatically on `/v1/landed-cost/shipments/{id}/post`. Available at `/tools/landed-cost/shipments/{id}/report`. Side-by-side:

```
Pascucci March-2026 Coffee Shipment
Container CICU6794096          posted 2026-05-02

INVOICE TOTALS                      ERP BOOK TOTALS
─────────────────────                ─────────────────────
Pascucci Italia 2025-VA-0002522      PR  MAT-PRE-2026-00009
  Net to pay:    $49,886.66            Stock - Coffee Dr:  $61,386.45
                                       SRBNB Cr:           $49,886.66
                                       Inbound Freight Cr:  $1,976.00
                                       Import Duties Cr:    $9,523.79
Euro Cargo 1335680-01                PI  ACC-PINV-2026-00023 ($49,886.66)
  Total:         $9,873.79             Loan-Pascucci Cr:   $49,886.66
                                       SRBNB Dr:           $49,886.66
Flexport bills 642158 + 645137       LCV MAT-LCV-2026-00007 ($9,873.79)
  Capitalized:   $1,626.00           LCV MAT-LCV-2026-00009 ($1,626.00)
  Expensed:      $1,694.95           LCV MAT-LCV-2026-00019 (Path A, net $0)

TOTAL EXPECTED LANDED                TOTAL POSTED LANDED
$49,886.66 + $9,873.79 + $1,626.00   = $61,386.45 ✓
= $61,386.45                          variance: $0.00
```

The report highlights any variance > $0.01 in red. Acceptance threshold: **≤ $0.01 across the whole shipment**, otherwise re-amend.

---

## 11. Troubleshooting

### Stock - Coffee balance ≠ sum of Bin

```sql
-- Should equal:
SELECT 'GL'   AS src, ROUND(SUM(debit)-SUM(credit),2) FROM `tabGL Entry`
  WHERE account='Stock - Coffee - PUI' AND is_cancelled=0;
SELECT 'BIN'  AS src, ROUND(SUM(stock_value),2)        FROM tabBin
  WHERE warehouse='Flexport Coffee - PUI';
```

Causes:
- SLE for an issue still uses old val_rate after a retro repricing (rare; FIFO/MA-toggle artifact)
- Bin row stale because Repost Item Valuation didn't run (run it via ERPNext UI or `frappe.get_doc('Repost Item Valuation', ...)`)

### Stock Adjustment - PUI accumulating > $0.01

This account should NEVER hold more than 1¢ per shipment. If it does:
```sql
SELECT voucher_type, voucher_no, posting_date, ROUND(debit,2), ROUND(credit,2), remarks
FROM `tabGL Entry`
WHERE account='Stock Adjustment - PUI' AND is_cancelled=0
ORDER BY posting_date DESC;
```

Look for nudge LCVs (negative-amount LCVs with `expense_account='Stock Adjustment - PUI'`). These were the Pascucci-era hack and should not recur. **Stop and investigate** — likely a display-rounding entry slipped past the validator.

### SRBNB not netting to $0

After PR + matching PI, SRBNB should be $0 net for that bill:
```sql
SELECT voucher_no, ROUND(SUM(debit),2) AS dr, ROUND(SUM(credit),2) AS cr
FROM `tabGL Entry`
WHERE account='Stock Received But Not Billed - PUI'
  AND voucher_no IN ('MAT-PRE-2026-00009','ACC-PINV-2026-00023')
  AND is_cancelled=0
GROUP BY voucher_no;
```

PR contributes Cr, PI contributes Dr — they should match. Causes for non-zero:
- PR booked at one total, PI booked at a different total (display-rounding bug). Fix: cancel PI, edit PR rates, recreate PI at matching total.
- PI partially paid before total fix-up — outstanding_amount drift. Fix: check `tabPurchase Invoice.outstanding_amount` before any rate-amendment surgery.

### Sample line shows $0/u landed (uninventoried)

The Omaggio fair-value didn't apply. Check:
1. Line has `is_free=true`?
2. Line has `fair_value_rate > 0`?
3. Path A LCV exists for this PR (`SELECT name FROM tabLanded Cost Voucher WHERE distribute_charges_based_on='Distribute Manually' AND ...`)?
4. Path A LCV has applicable_charges > 0 for this sample line?

If any No: re-amend the shipment with correct flags.

### Multi-tenant Flexport bill spans Coffee + Machines

If Flexport bill 645137 has Storage DTC for both Coffee ($309.42) and Machines ($410.83), the inventoriable Inbounding-Handling lines also need correct cost-center routing. Pattern:
- One PI per bill (auto-created from invoice intake)
- Per-line `expense_account` distinguishes Coffee vs Machines
- LCV per goods PR (separate Coffee-LCV and Machines-LCV) takes only the relevant Inbounding lines

The cross-shipment splitting UI is **Phase C** (deferred). For now, manually split per line `expense_account` on intake.

### FX cutoff mismatch

Pascucci invoices in EUR but billed in USD. Because the supplier already prices in USD on the invoice, no FX conversion needed at receipt. **However**: if a future supplier sends in EUR and we pay in USD on a different date, lock `exchange_rate` at PI posting; payment-date FX gain/loss hits `Foreign Exchange Gain/Loss - PUI` (separate ledger flow, not the LCV tool's job).

---

## 12. Reference SQL — diagnose a posted shipment

Replace `MAT-PRE-2026-00009` with the target PR, `ACC-PINV-2026-00023` with the target PI.

```sql
-- 1. PR line items: rates, amounts, lcv_charges, val_rate
SELECT idx, item_code, ROUND(qty,0) AS qty, ROUND(rate,4) AS rate,
       ROUND(amount,2) AS amount, ROUND(landed_cost_voucher_amount,2) AS lcv,
       ROUND(amount + landed_cost_voucher_amount,2) AS landed,
       ROUND(valuation_rate,9) AS val_rate
FROM `tabPurchase Receipt Item`
WHERE parent='MAT-PRE-2026-00009'
ORDER BY idx;

-- 2. PR GL footprint
SELECT account, ROUND(SUM(debit),2) AS dr, ROUND(SUM(credit),2) AS cr
FROM `tabGL Entry`
WHERE voucher_no='MAT-PRE-2026-00009' AND is_cancelled=0
GROUP BY account ORDER BY account;

-- 3. All active LCVs touching this PR
SELECT lcv.name, lcv.docstatus, lcv.posting_date,
       ROUND(lcv.total_taxes_and_charges,2) AS total,
       lcv.distribute_charges_based_on
FROM `tabLanded Cost Purchase Receipt` lcvpr
JOIN `tabLanded Cost Voucher` lcv ON lcv.name=lcvpr.parent
WHERE lcvpr.receipt_document='MAT-PRE-2026-00009'
  AND lcv.docstatus=1
ORDER BY lcv.posting_date, lcv.name;

-- 4. LCV per-item allocation (sum-check by LCV)
SELECT parent, ROUND(SUM(applicable_charges),2) AS total_share, COUNT(*) AS rows
FROM `tabLanded Cost Item`
WHERE parent IN ( ... LCV names from query 3 ... )
GROUP BY parent;

-- 5. PI matching the PR's supplier invoice
SELECT name, bill_no, ROUND(grand_total,2) AS gt,
       ROUND(outstanding_amount,2) AS outstanding, status
FROM `tabPurchase Invoice`
WHERE bill_no='2025-VA-0002522' AND docstatus=1;

-- 6. SRBNB net (must be $0 after PR + PI both posted)
SELECT voucher_type, voucher_no, ROUND(SUM(debit),2) AS dr, ROUND(SUM(credit),2) AS cr
FROM `tabGL Entry`
WHERE account='Stock Received But Not Billed - PUI'
  AND voucher_no IN ('MAT-PRE-2026-00009','ACC-PINV-2026-00023')
  AND is_cancelled=0
GROUP BY voucher_type, voucher_no;

-- 7. Bin = Stock GL check
SELECT 'GL'  AS src, ROUND(SUM(debit)-SUM(credit),2) AS bal FROM `tabGL Entry`
  WHERE account='Stock - Coffee - PUI' AND is_cancelled=0
UNION ALL
SELECT 'BIN' AS src, ROUND(SUM(stock_value),2)         FROM tabBin
  WHERE warehouse='Flexport Coffee - PUI';

-- 8. Stock Adjustment must be near $0
SELECT 'Stock Adjustment - PUI' AS account,
       ROUND(IFNULL(SUM(debit)-SUM(credit),0),2) AS bal
FROM `tabGL Entry` WHERE account='Stock Adjustment - PUI' AND is_cancelled=0;
```

---

## 13. Permanent invariants (the bar for "books are correct")

These must hold after every shipment posts. If any is violated, the books are wrong.

| # | Invariant | Why |
|---|---|---|
| **I1** | `Stock - {category}` GL balance = sum of `Bin.stock_value` for that warehouse, to the cent | If they diverge, an SLE/Bin update was missed. ERPNext's invariant. |
| **I2** | After PR + matching PI both posted, `SRBNB` net = $0 for that bill | PR Cr SRBNB; PI Dr SRBNB. If they don't match, supplier-rate misread or partial-pay drift. |
| **I3** | `Stock Adjustment - PUI` balance NEVER accumulates > $0.01 from a single shipment | This account is COGS. If it grows, you burned fake COGS into P&L. Pascucci-era $20.40 burn is the cautionary tale. |
| **I4** | Path A LCV `total_taxes_and_charges` = $0.00, sum of per-item `applicable_charges` = $0.00 | Path A is zero-net redistribution. Non-zero means math is wrong. |
| **I5** | Sample (Omaggio) units carry fair-value charge share, supplier_rate = $0, no `Discount Received` line | ASC 705-20: vendor consideration → reduce inventoriable cost, not P&L income. |
| **I6** | Each PR's LCV-set per-item shares sum to that LCV's `total_taxes_and_charges` (within ±$0.01) | ERPNext's distribution invariant. Residuals applied to largest line. |
| **I7** | Each LCV's `expense_account` sum (per LCV) = ERPNext-side credit to that account | Every charge dollar is accounted for in P&L expense (capitalizable charges credit expense as they capitalize into Stock). |
| **I8** | `outstanding_amount` on a PI never updated post-payment without going through Payment Entry reversal | Direct `outstanding_amount` edits break payment reconciliation. |
| **I9** | Display-rounding deviations ≤ $0.005 per line, OR explicit user override logged in audit_events | Catches the $1.32-vs-$1.315 trap on entry, not 6 months later. |
| **I10** | Per-supplier `default_ap_account` from `supplier_default_ap` is honored unless explicitly overridden per invoice (with override logged) | Pascucci → Loan; everyone else → AP. Surprise overrides leave audit footprint. |

---

## Appendix A — Pascucci March-2026 final state (as of 2026-05-02)

After the surgical clean-up, books are PERFECT:

| Bucket | Value |
|---|---|
| Stock - Coffee receipt total | $61,386.45 |
| Stock - Coffee balance after 3 VARIETY issues | **$61,286.76** |
| Stock - Machines receipt total | $80,669.32 |
| Stock - Machines balance after 3 MACHINE issues | **$80,504.68** |
| Stock Adjustment - PUI | **$0.00** |
| SRBNB - PUI net | **$0.00** |
| AP from this shipment | $49,886.66 (Loan from Pascucci Italia) + $0 + $0 + $4 + $2,141.70 + $1,179.25 (Flexport via AP) + $9,873.79 + $8,412.87 + $175 (Euro Cargo via AP) |
| Sample (PUI-INS-AMERICANO-SAMPLE) per-unit landed | **$0.2688** |
| Path A LCV-19 net | **$0.00** |

Active LCVs:
- LCV-7 ($9,873.79 Euro Cargo Coffee)
- LCV-8 ($8,587.87 Euro Cargo Machine)
- LCV-9 ($1,626.00 Flexport inbound capitalized)
- LCV-19 (Path A net $0)

Deleted (rate-fixup nudges, no longer needed): LCV-10, LCV-11, LCV-15, LCV-18.

---

*End of runbook V1. Updates go in V2 with a new file (don't edit V1 in-place once published).*
