---
title: "Key Concepts"
space: "Wiki"
url: "https://jmtradelink.com/docs/getting-started/key-concepts"
updated: "2026-06-22"
---

# Key Concepts

This page defines the vocabulary you need before working with Export Management. Every term here maps to a real doctype, field, or constant in the codebase. Workflow steps reference these concepts; this page does not describe steps.

---

## Export Costing — the central document

An **Export Costing** (doctype `Export Costing`, naming series `EC-{YYYY}-{####}`) is the single document that drives the entire price-to-order cycle. It holds:

- the commodity lines being exported (items, packaging, container count)
- all cost charge rows (fixed and variable)
- vendor rate snapshots
- generated vendor combinations
- the per-bag selling price and the computed margin
- links to the resulting Quotation, Sales Order, and Export Shipment

Every other step — RFQs, Supplier Quotations, combinations, customer quotes — either feeds into or flows out of an Export Costing. There is one costing per deal; revisions create a new costing chained to the original.

**Status lifecycle** (auto-transitions never downgrade a `Won` or `Lost` record):

```
Draft → Rates Received → Costing Selected → Quoted → Won / Lost
```

---

## The Incoterms cascade

Costs accumulate through four cascade stages defined in `constants.py`:

```python
CASCADE_STAGES = ["Ex-Works", "FOB", "CFR", "CIF"]
```

Each stage is a *running total*: every stage adds its own rows to the sum of all previous stages. The `calculate_charges()` method in `export_costing.py` iterates `CASCADE_STAGES` in order, keeping a `running_total` that grows with each stage's rows.

| Stage | Typical charges included at this level |
|-------|----------------------------------------|
| **Ex-Works** | Procurement cost (goods), packaging bags |
| **FOB** | CFS handling, container stuffing, surveyor, pre-shipment |
| **CFR** | Sea freight |
| **CIF** | Marine insurance |

The cascade produces four cumulative totals stored on the costing: `ex_works_total`, `fob_total`, `cfr_total`, `cif_total`. The combination engine ranks and compares combinations by their `landed_cost_per_bag` (with `cif_total` as a tiebreaker).

> **Why cumulative?** The client's Excel sheet is structured the same way — each Incoterm column is the last column's number plus the new stage's additions. The code deliberately mirrors that layout so the numbers can be cross-checked row by row.

### The Destination stage — separate from the cascade

```python
DESTINATION_STAGE = "Destination"
```

The Destination stage is **not** part of `CASCADE_STAGES`. Destination charge rows do not accumulate into the INR cascade; instead they feed a separate per-bag block (`calculate_destination_block` / `PodClearingMixin`) that computes the full landed cost at the buyer's port in the destination currency (CFA/XOF).

Two restrictions are enforced at save time (`validate_destination_charges`):

- `qty_basis` must be `"Per Bag"` or `"Lumpsum"` (the only two values in `DESTINATION_ALLOWED_BASES`).
- The row currency must match the costing's `destination_currency`.

This means Destination rows are quoted in CFA and stay in CFA throughout; they are never converted to INR inside `calculate_charges`. Destination-stage currencies are also excluded from the automatic `ensure_exchange_rate_rows` logic — no FX row is created for them.

---

## Charge heads: Export Charge Item

An **Export Charge Item** (doctype `Export Charge Item`, autonamed by `charge_name`) is the master record for a cost category. It is the named slot that charge rows, raw rates, and RFQ items all reference.

Key fields:

| Field | Type | Meaning |
|-------|------|---------|
| `charge_name` | Data (PK) | Human-readable label, e.g. "Sea Freight", "Procurement Cost" |
| `default_stage` | Select | Which cascade stage this head normally lives at |
| `default_qty_basis` | Select | How quantity is resolved (Per Net MT, Per Bag, Per Container, Lumpsum, etc.) |
| `default_currency` | Link → Currency | Currency the vendor typically quotes in |
| `is_goods` | Check | Whether this head buys real product (see below) |
| `is_variable` | Check | Whether this head is vendor-sourced (see below) |
| `service_item` | Link → Item (read-only) | Auto-created non-stock ERPNext Item used on RFQ/PO rows |

### Fixed vs variable charge heads

The `is_variable` flag on `Export Charge Item` determines whether a head's rate comes from a template or from vendor quotations.

**Fixed heads** (`is_variable = 0`):
- Rates are set on the Costing Template and copied verbatim when `populate_from_template` runs.
- Examples: CFS surveyor fee, ECTN, Bill of Lading, insurance premium, port dues.
- The rate is known at setup time and does not require an RFQ.

**Variable heads** (`is_variable = 1`):
- The template does **not** supply a rate; variable heads are added with `rate = 0` when a costing is created or a template is applied.
- Service-type variable heads (`is_goods = 0`) are added by `append_variable_charge_rows()`. Goods-type variable heads (`is_goods = 1`) are added per item line by `sync_item_procurement_rows()` during validate.
- A fresh costing therefore has `₹0` in every variable row. This is intentional, not a gap.
- Rates arrive via Pull Vendor Rates after suppliers submit their Supplier Quotations.
- Examples: Procurement Cost (goods), Bags, Sea Freight, CFS Handling, Container.

> **Critical behaviour:** because variable heads start at `₹0`, the `cif_total` and every downstream figure (per-MT cost, per-bag cost, margin) are meaningless on a fresh costing. The margin shown before Generate Combinations is applied is not a real margin — it is a margin against ₹0 variable costs. See [Combinations and rate application](#combinations) below.

### Goods heads vs service heads

The `is_goods` flag distinguishes two kinds of variable heads:

**Goods heads** (`is_goods = 1`):
- Buy the **real product Item** (e.g. the rice Item) on RFQ and PO rows.
- `service_item` is set to `None`; the product item on the costing item line is used directly.
- There is exactly one charge row per goods head **per item line** on the costing — rice and cashew each get their own Procurement Cost row. `sync_item_procurement_rows()` maintains this automatically: it is additive, so existing rows with pulled rates are never overwritten.

**Service heads** (`is_goods = 0`):
- Cover logistics costs (freight, handling, insurance, etc.) that do not map to a product.
- `ensure_service_item()` auto-creates a non-stock ERPNext Item named after the charge (in the "Export Charges" item group) the first time the charge head is saved. This synthetic Item is what appears on RFQ and PO line items so that ERPNext's purchase flow can process it.

The distinction matters when building RFQs: goods go to mills/producers; service items go to freight forwarders, surveyors, etc. Vendors on one RFQ never see the other's heads.

---

## Raw rates — snapshots from Supplier Quotations

**Raw rates** are point-in-time snapshots of submitted Supplier Quotation line rates, stored in the `raw_rates` child table of an Export Costing.

The source doctype is fixed in `constants.py`:

```python
RAW_RATE_SOURCE_DOCTYPE = "Supplier Quotation"
```

The snapshot is intentional. `pull_vendor_rates()` reads submitted SQs once and writes their rates into `raw_rates`. After that, the combination engine reads **only** `raw_rates` — it never queries `Supplier Quotation` again. This means:

- If a vendor amends their quotation, you re-pull to refresh the snapshot.
- Historical combinations remain reproducible even if the source SQ changes.
- Expired rates are flagged (`is_expired`) but kept unless `skip_expired=True` is passed.

Each raw rate row records: `charge_item`, `supplier`, `item_code` (for goods), `rate`, `currency`, `qty_basis`, `port_of_loading`, `port_of_discharge`, `container_type`, and validity dates. Port fields are populated from the RFQ or the SQ itself (SQ-level port wins over RFQ-level port).

A blank `port_of_loading` on a raw rate row is a **wildcard**: that rate is compatible with any loading port in the combination engine.

---

## Combinations — full landed-cost computation per vendor-port permutation {#combinations}

A **combination** (doctype `Export Cost Combination`) is a complete landed-cost computation for one specific assignment of suppliers to variable charge heads, for one loading port.

The engine (`generate_combinations` in `CombinationEngineMixin`) works as follows:

1. Group raw rates by `(charge_item, item_code)` head. Each group has one or more competing vendor rates. Expired rates are excluded from this grouping.
2. For each candidate loading port (from the non-empty `port_of_loading` values in `raw_rates`, plus the costing's own `port_of_loading` if not already present — falling back to `[self.port_of_loading]` if all rates are wildcards), filter each head's rates to those compatible with that port (exact match or wildcard).
3. Discard any port where at least one head has zero compatible rates — a partial combination is not computed.
4. Take the Cartesian product (`itertools.product`) of the per-head compatible rates for each port. Each element of the product is one combination.
5. For each combination, run the **full calculation sequence** on a transient in-memory clone of the costing (`compute_combination_clone`): apply the picks' rates to the clone's charge rows, then run `calculate_charges`, `calculate_per_mt_costs`, `set_quoted_price`, `calculate_destination_block`. The clone uses the same locked exchange rates as the parent.
6. Rank all combinations by `landed_cost_per_bag` ascending (then `cif_total` as tiebreaker).
7. Persist all combinations ranked 1..N. **Auto-apply the cheapest** (rank 1) by calling `select_combination` immediately, which writes the picked supplier and rate back onto the parent costing's charge rows and resets `quoted_price_per_mt` so the per-MT figure recomputes from the new rates.

> **Why full-combination costing instead of cheapest-per-head?** A per-head greedy pick can produce an impossible or suboptimal route — for instance, the cheapest freight supplier might not serve the port where the cheapest mill ships from. The engine considers every valid combination as a complete landed cost, matching the client's original Excel approach.

After Generate runs, the costing carries real rates and its margin is meaningful. The Combination Comparison report marks rank 1 green; to override the auto-applied pick, open any other combination and choose **Select This Combination**.

Regenerating discards all unselected combinations, rebuilds, and re-applies the new cheapest. The old auto-pick is deselected first.

---

## Currency model: INR, EUR, and the CFA peg

The company books in **Indian Rupee**:

```python
COMPANY_CURRENCY = "INR"
```

All cascade-stage charge amounts compute into INR (`amount_inr`). Foreign-currency charges (USD, EUR, etc.) are converted at the **buffered exchange rate** (`base_rate + fx_buffer`) — the conservative side, matching the client's Excel convention. Per-MT display figures use the **base rate** (no buffer) for the quote-facing side.

The destination currency is CFA Franc (XOF), which is legally pegged to the Euro at a fixed rate. The costing holds two peg fields snapshotted from Export Settings at creation time:

- `cfa_rate_per_eur` — CFA units per 1 EUR (e.g. 655.957)
- `cfa_rate_per_usd` — CFA units per 1 USD (approximate, not fixed)

EUR is always required in the exchange rate table. Even if no charge row is denominated in EUR, `ensure_exchange_rate_rows` adds EUR because the destination block (`calculate_destination_block`) uses the EUR→INR base rate as the bridge to convert the adjusted CIF cost into CFA per bag:

```
adjusted_cost_per_mt_inr = cif_total × (1 + finance_cost_percent) × (1 + profit_percent) / total_gross_mt
cost_per_bag_destination = (adjusted_cost_per_mt_inr / EUR base rate) × mt_per_bag × cfa_rate_per_eur
```

The `finance_cost_percent` and `profit_percent` uplifts are applied by `get_cost_per_mt_inr()` before any per-MT or per-bag figure is derived; `cif_total` alone does not feed the destination block directly.

The USD path is computed in parallel (`compute_usd_landed`) as an informational comparison. `cheaper_currency` on the costing flags whichever path produces the lower `landed_cost_per_bag`; the EUR path is the ranking basis and wins ties.

> **Why snapshot the peg?** The CFA/EUR rate is fixed by treaty and rarely changes, but the INR/EUR market rate moves daily. Snapshotting at costing creation means historical costings don't drift when Export Settings is later updated. The same isolation applies to `fx_buffer` and the import-duty lane params (`bfu_percent`, `transitor_per_container`, `tax_percent`, `commission_per_bag`).

---

## Quantity bases

Charge rows resolve their quantity automatically from the costing's computed item totals. The available bases (from `Export Charge Item.default_qty_basis` and `resolve_charge_qty` in `export_costing.py`):

| Qty Basis | Resolves to |
|-----------|-------------|
| Per Net MT | `total_net_mt` (or item's `net_weight_mt` for item-scoped rows) |
| Per Gross MT | `total_gross_mt` |
| Per Kg | `total_net_mt × 1000` |
| Per Bag | `total_bags` |
| Per Container | `total_containers` |
| Per BL | `no_of_bl` |
| Lumpsum | 1 |
| Percent of Subtotal | Applied as `rate% × running_total_at_stage_start` — no separate qty |
| Manual | Uses the qty typed directly on the charge row |

An item-scoped charge row (one with `item_code` set, i.e. a goods/procurement row) draws from that specific item's quantities, not the shipment totals.

---

## Selling price, margin, and the per-bag frame

The customer buys in the destination currency (CFA/XOF), priced **per bag**. The `present_selling_price_per_bag` field (set in `set_destination_margins`) holds this. Margin is:

```
margin_per_bag = present_selling_price_per_bag − landed_cost_per_bag
margin_percent = margin_per_bag / landed_cost_per_bag × 100
```

`landed_cost_per_bag` is the sum of three components:
- **`cost_per_bag_destination`** — the adjusted CIF cost (after finance and profit uplift) converted into CFA via the EUR peg
- **`pod_clearing_per_bag`** — structured import duty build-up (BFU, transitor, tax, commission)
- **`pod_expense_per_bag`** — Destination-stage charge rows (per-bag or lumpsum)

The `quoted_price_per_mt` field exists as an internal reference for the INR/USD cost view and for generating the Quotation value. It is automatically recomputed whenever it is blank, whenever the quote currency changes, and whenever a combination is applied. A manually entered value can go stale if the cost changes without a subsequent Generate — see the current limitations in USAGE.md.

---

## What to read next

- **Workflow: Sourcing Vendor Rates** — how RFQs are created, how the guest quote link works, and how Pull Vendor Rates populates raw rates.
- **Workflow: Generate Combinations** — step-by-step walkthrough of the combination screen and how to override the auto-selected pick.
- **Workflow: Quote and Order** — generating the customer Quotation in CFA, creating the Sales Order, and transitioning the costing to Won.
- **Reference: Export Settings** — FX buffer, CFA peg, rate staleness days, max combinations cap.
- **Reference: Costing Templates** — how fixed-cost rows and destination lane defaults are maintained centrally.
