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:
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
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_basismust be"Per Bag"or"Lumpsum"(the only two values inDESTINATION_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_templateruns. - 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 = 0when a costing is created or a template is applied. - Service-type variable heads (
is_goods = 0) are added byappend_variable_charge_rows(). Goods-type variable heads (is_goods = 1) are added per item line bysync_item_procurement_rows()during validate. - A fresh costing therefore has
₹0in 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, thecif_totaland 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 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_itemis set toNone; 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:
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 unlessskip_expired=Trueis 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:
- 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. - For each candidate loading port (from the non-empty
port_of_loadingvalues inraw_rates, plus the costing's ownport_of_loadingif 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). - Discard any port where at least one head has zero compatible rates — a partial combination is not computed.
- Take the Cartesian product (
itertools.product) of the per-head compatible rates for each port. Each element of the product is one combination. - 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 runcalculate_charges,calculate_per_mt_costs,set_quoted_price,calculate_destination_block. The clone uses the same locked exchange rates as the parent. - Rank all combinations by
landed_cost_per_bagascending (thencif_totalas tiebreaker). - Persist all combinations ranked 1..N. Auto-apply the cheapest (rank 1) by calling
select_combinationimmediately, which writes the picked supplier and rate back onto the parent costing's charge rows and resetsquoted_price_per_mtso 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:
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_bufferand 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 pegpod_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.