---
title: "Revisions"
space: "Wiki"
url: "https://jmtradelink.com/docs/quoting-and-winning/revisions"
updated: "2026-06-22"
---

# Revisions

**Section:** Quoting and Winning

---

## What a Revision Is

A revision is a new Export Costing that is a child of an earlier one. When you call **Create > Revision** on any costing, the system copies it in full, advances the version counter, pins both documents to the same root ancestor, and returns the new costing in Draft status — ready to receive fresh vendor rates, a new combination, and a new customer quote.

The original is never touched. It remains exactly as it was — status, math, linked documents and all — so the full pricing history of a deal is auditable without any "track changes" infrastructure.

---

## The Code: `make_revision`

File: `export_management/export_management/doctype/export_costing/export_costing_actions.py`

```python
@frappe.whitelist()
def make_revision(export_costing: str) -> str:
    # spec §18 — new version for re-quoting; never overwrite old math
    source = frappe.get_doc("Export Costing", export_costing)
    source.check_permission("write")

    revision = frappe.copy_doc(source)
    revision.version = cint(source.version or 1) + 1
    # chain every revision to the root costing so the whole family lists together
    revision.revision_of = source.revision_of or source.name
    revision.status = "Draft"
    revision.costing_date = today()
    revision.shipment = None
    revision.quotation = None
    revision.sales_order = None
    revision.set("raw_rates", [])
    for charge_row in revision.charges:
        charge_row.supplier = None
        charge_row.supplier_quotation = None
    revision.insert()
    return revision.name
```

`frappe.copy_doc` does a deep copy of the document and all its child tables. Everything that is not explicitly reset afterwards carries over unchanged.

---

## What Is Copied vs. What Is Reset

### Fields Reset on Every Revision

| Field | Type | Reset to | Why |
|---|---|---|---|
| `version` | Int | `cint(source.version or 1) + 1` | Distinguishes siblings; v1 → v2 → v3 as the deal evolves |
| `revision_of` | Link → Export Costing | root ancestor's name | Chains every revision to the same root so the whole family lists together |
| `status` | Select | `"Draft"` | The new costing hasn't been worked yet — no inherited status |
| `costing_date` | Date | `today()` | Date reflects when this version was prepared, not when the original was |
| `shipment` | Link → Export Shipment | `None` | The old shipment belongs to the old version |
| `quotation` | Link → Quotation | `None` | The old quote was priced on the old math |
| `sales_order` | Link → Sales Order | `None` | The order was placed against the original; the revision re-quotes |
| `raw_rates` | Child table | Emptied (`[]`) | These are vendor-rate snapshots for a specific point in time — stale on a new version |
| `charges[*].supplier` | Link → Supplier | `None` | Per-row — the old vendor pick is cleared so Pull can re-assign |
| `charges[*].supplier_quotation` | Link → Supplier Quotation | `None` | Per-row — the old SQ reference is cleared for the same reason |

### Fields Carried Over Unchanged

Everything not in the table above is an exact copy:

- **Item lines** — commodity, packaging spec, container count, weights. The deal is for the same goods.
- **Charge structure** — every charge head row (FOB, sea freight, CFS, insurance, ECTN, BL, etc.) with its `qty_basis`, `qty`, and typed `rate`. The charge structure is part of the deal template, not the vendor negotiation.
- **Rates as typed** — the `rate` and `currency` columns on each charge row carry over. If a fixed charge such as the BL fee or surveyor cost is already known, the revision inherits it and you only need to re-source the variable heads.
- **Customer, opportunity, port of loading/discharge, container type, destination currency, selling price/bag, costing template, FX rates.**

> **Why carry the rates over?** Fixed charges (ECTN, BL, insurance) rarely change between rounds. Keeping them avoids re-entering a dozen known costs by hand. The vendor-sourced heads (`charges[*].supplier` and `charges[*].supplier_quotation`) are cleared precisely because those are the unknowns that triggered the revision.

---

## The Version Chain

`revision_of` always points at the root costing, never at an intermediate version. The logic is:

```python
revision.revision_of = source.revision_of or source.name
```

If you are revising v1 (original, no `revision_of`), the new v2 gets `revision_of = v1.name`.  
If you then revise v2 (which already has `revision_of = v1.name`), the new v3 also gets `revision_of = v1.name`.

This means every member of the family shares the same `revision_of` value. A list view filtered on `revision_of = "EC-0001"` returns all revisions of that deal in one place, regardless of depth.

The `version` field (hidden, read-only on the form) provides the sequence: 1, 2, 3. It defaults to `1` on a fresh costing and increments by exactly 1 on each `make_revision` call — using `cint(source.version or 1) + 1` so an unset version is treated as 1 rather than 0.

---

## Why Not Just Edit the Original?

The original costing may already be in status `Quoted` or `Won`, with a linked Supplier Quotation, Quotation, and possibly a Sales Order. Overwriting its numbers would:

1. Destroy the audit trail of what was actually quoted and agreed.
2. Break the link integrity: the Quotation and Sales Order priced off the old math would silently mismatch the new numbers.
3. Make it impossible to compare rounds — "why did we drop $8/MT between v1 and v3?" is a question that comes up after a deal closes.

A revision keeps the old version frozen. You re-pull rates, regenerate combinations, set a new selling price, and create a new customer Quotation — all on the new document, against the original's audit baseline.

---

## Typical Revision Workflow

1. On an existing costing in any status → **Create > Revision**.
2. The new costing opens in Draft with a fresh `costing_date` and an incremented `version`.
3. **Pull Vendor Rates** → `raw_rates` fills with the latest submitted Supplier Quotation rows.
4. **Generate Combinations** → picks the cheapest vendor/port combination, applies rates, resets `quoted_price_per_mt` so it recomputes from the new cost.
5. Adjust **Selling Price / Bag** if the new cost requires a price change.
6. **Create > Quotation** → sends the revised price to the customer.
7. If the customer accepts, submit the Quotation → Sales Order → costing flips to Won.

---

## The `quoted_price_per_mt` Staleness Limitation

`quoted_price_per_mt` is a cached internal reference figure (cost per gross MT in the quote currency). It drives `quoted_value` on the form. The field is hidden on the form (not exposed for direct editing) but is not marked read-only in the DocType definition — it can be set programmatically or via the API.

`set_quoted_price` in `export_costing.py` recomputes it only in two situations:

1. The field is empty (zero or null).
2. The quote currency changed since the last save.

**Generate Combinations** triggers recomputation indirectly by calling `reset_quoted_price()` first (sets it to 0), which causes condition 1 to fire on the next save.

> **Gotcha:** If you apply a combination and then set `quoted_price_per_mt` to a non-zero value outside the normal flow, that value will persist even if the underlying charge rates change on a subsequent save — because the field is non-zero and the currency has not changed, so the recompute branch is skipped. The safe path is: never set `quoted_price_per_mt` directly. Let Generate Combinations (which resets it) drive the value, or use Regenerate Quotation which also triggers a full recalculation. A future "manual override flag" would close this fully — currently noted as a deferred limitation in `USAGE.md`.

On a freshly created revision, `raw_rates` is empty and no combination has been applied, so `quoted_price_per_mt` carries the value from the source costing. It will recompute correctly as soon as you run Generate Combinations (which resets it to 0 first), or when you change the quote currency.

---

## Status Flow After a Revision

The status lifecycle on a revision is the same as on any costing:

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

The old costing retains whatever status it had (e.g. `Quoted` or `Lost`). The two siblings have independent statuses. Auto-transitions that fire on Sales Order submission only update the specific costing that is linked to that Sales Order, so a Won v1 and a Draft v3 can coexist without conflict.

---

## Example: Demo Scenario

The demo seed (`export_management.export_management.demo.seed.run`) creates a Won costing for JM Tradelink with a linked Export Shipment. If the shipment is delayed and the customer requests a re-quote at new freight rates, the correct move is:

1. Open the Won costing.
2. **Create > Revision** → produces, say, `EC-0005` (v2, revision_of = `EC-0003`).
3. Pull the new freight SQ (the forwarder re-quoted at a different rate).
4. Generate Combinations — the engine picks the cheapest port/vendor for the new rates.
5. If the new landed cost is higher, adjust Selling Price / Bag accordingly.
6. Create > Quotation → send to customer.

`EC-0003` (Won, with its original math and shipment) is untouched throughout.

---

## Summary Table

| Aspect | Behavior |
|---|---|
| Entry point | **Create > Revision** button on any Export Costing |
| Backend function | `make_revision` in `export_costing_actions.py` |
| Copy mechanism | `frappe.copy_doc` (deep copy of doc + all child tables) |
| Version numbering | `cint(source.version or 1) + 1`; defaults to 1 on originals |
| Family anchor | `revision_of` always points at the root (v1), never an intermediate |
| Status on creation | Always `Draft` |
| Costing date | Set to `today()` at revision time |
| Linked documents cleared | `shipment`, `quotation`, `sales_order` |
| Vendor data cleared | `raw_rates` table emptied; `supplier` + `supplier_quotation` on every charge row nulled |
| What is kept | Item lines, full charge structure, typed rates, customer, ports, currencies, selling price/bag |
| Old costing | Frozen — never modified by `make_revision` |
