Files
tradon/modules/purchase_trade/docs/business/lots-and-quantities.en.md
2026-05-14 11:20:33 +02:00

29 KiB

Lots and Quantities

Language: en Mirror page: lots-and-quantities.md Status: partial migration Last code verification: 2026-05-13

This page consolidates BR-PT-LOT-001, BR-PT-LOT-002, and BR-PT-LOT-003. Goal: drive quantity rules from a readable business definition, then secure them with Python guards and SQL diagnostics.

Key Points

Operational SummaryA trade line has exactly one virtual lot. The virtual lot carries the global open balance, lot.qt carries the operational forecast, and physical lots consume that forecast.
Topic Short Rule
Entered quantity quantity_theorical is the business quantity.
Standard quantity quantity is a read-only technical counter.
Before physical lots quantity follows quantity_theorical.
After physical lots quantity reflects physical execution.
Open line Line amount uses quantity_theorical.
Finished line Line amount may switch back to physical execution.
Weight basis Purchase and sale may read two different states of the same lot.
Invoicing It chooses states from lot.qt.hist.
Controls Invariants are blocked in Python and auditable with SQL.
quantity_theorical
        |
        v
  virtual lot P1  --->  lot.qt forecast  --->  physical lot
        ^                    |
        |                    v
        +------ recalculation after consumption

Consultant Rules

BR-PT-LOT-001 - Virtual lot / forecast / physical life cycle

Moment Business Effect
Line creation One unique virtual lot is created.
Initialization The virtual lot takes quantity_theorical.
Forecast One open line is created in lot.qt.
Planning lot.qt may be split by sale, matching, transport, or shipment.
Physical add The physical lot consumes a precise lot.qt line.
After add lot.qt decreases and the virtual lot is recalculated.
Split of an open P1 balanceP1S1T1, P1S1T2, P1S2T3, P1S2T4
Key PointThese splits do not create several virtual lots. They only describe the forecast allocation of the open balance.

BR-PT-LOT-002 - Contractual quantity, counter quantity, finished line

Situation Reference Quantity
User entry quantity_theorical
No physical lot quantity = quantity_theorical
Physical lots exist quantity = sum of physical lots
finished = False Amount based on quantity_theorical
finished = True Amount based on physical execution
Weight basis available Amount based on the contract wb.qt_type state
What finished Does Not Dofinished does not erase the contractual quantity, delete physical lots, or hide their PnL. It only means that the open balance may be ignored for execution calculations.
Reading of the Same Physical Lot Possible State
Purchase BL through purchase.purchase.wb.qt_type
Sale LR or Weight Report through sale.sale.wb.qt_type
Invoicing Independent choice in lot.qt.hist

BR-PT-LOT-003 - Quantity Invariants

Invariant Formula
Conservation sum(physical lots) + virtual lot = quantity_theorical
Open forecast sum(non-zero lot.qt) = max(virtual lot, 0)
Case Rule
Purchase virtual lot Sum all lot.qt where lot_p = virtual lot, with or without lot_s.
Sale virtual lot Sum all lot.qt where lot_s = virtual lot, with or without lot_p.
lot.qt = 0 Ignored by checks; possible memory of a consumed forecast.
Negative virtual lot Allowed to compensate theoretical / executed gap; expected forecast = zero.
Non-zero orphan lot.qt Forbidden if neither lot_p nor lot_s is set.

BR-PT-LOT-004 - Quantity history and weighing

Element Rule
lot.qt.hist Carries lot quantity states.
lot.lot form No direct state entry.
Update Only through Do weighing.
Virtual lot No manual packing.
Virtual packing fields lot_qt and lot_unit are not editable.

Tolerances

Gap to ConfirmFull remaining-tolerance control in LotQt.add_physical_lots / LotQt.add_physical_lot still needs confirmation.
Point Target Rule
Level Global tolerance on line or contract.
Transport No independent tolerance per transport.
Over-execution Consumes remaining tolerance.
Under-execution Restores remaining tolerance.

Developer Section

Key Fields

  • Purchase line: purchase.line
  • Sale line: sale.line
  • Lot: lot.lot
  • Forecast: lot.qt
  • History: lot.qt.hist
  • Purchase business quantity: purchase.line.quantity_theorical
  • Sale business quantity: sale.line.quantity_theorical
  • Technical counter: quantity
  • Finished line: purchase.line.finished, sale.line.finished
  • Virtual / physical lot: lot.lot.lot_type = virtual / physic
  • Purchase link: lot.lot.line
  • Sale link: lot.lot.sale_line
  • Purchase forecast: lot.qt.lot_p
  • Sale forecast: lot.qt.lot_s
  • Forecast quantity: lot.qt.lot_quantity
  • Weight basis: purchase.purchase.wb, sale.sale.wb
  • Weight basis state: purchase.weight.basis.qt_type
  • Packing: lot.lot.lot_qt, lot.lot.lot_unit
  • Tolerances: tol_min, tol_max, tol_min_qt, tol_max_qt, tol_min_v, tol_max_v

Line / Virtual Lot Creation

  • Purchase: purchase.py, Line.validate
  • Sale: sale.py, SaleLine.validate
  • If quantity_theorical is entered and quantity is empty or zero:
    • quantity is initialized from quantity_theorical;
    • only if no physical lot exists.
  • If the line is eligible:
    • not created_by_code;
    • no lot yet;
    • non-service product;
    • quantity_theorical != 0;
    • create one virtual lot.
  • The virtual lot receives a first lot.qt.hist entry.
  • Lot.validate creates the open lot.qt through createVirtualPart.

Updating quantity_theorical

  • Purchase: purchase.py, Line.write
  • Sale: sale.py, SaleLine.write
  • Virtual lot target:
target_quantity = quantity_theorical - sum(converted physical lots)
  • If target_quantity < 0:
    • block with Please unlink or unmatch lot.
  • Free lot.qt target:
free_quantity = target_quantity - sum(already matched or shipped lot.qt)
  • If free_quantity < 0:
    • block with Please unlink or unmatch lot.
  • If a free lot.qt exists:
    • replace its quantity.
  • If no free lot.qt exists and free_quantity > 0:
    • create a new lot.qt.
  • Line fees are resynchronized.

Adding Physical Lots

  • Wizard: lot.add
  • Methods:
    • LotQt.add_physical_lots
    • LotQt.add_physical_lot
  • Mandatory source: one lot.qt line.
  • Direct add from a physical lot is refused.
  • Physical add on sale side through this wizard is refused: use Apply matching.
  • The physical lot inherits:
    • purchase line;
    • matched sale, if any;
    • shipment;
    • product;
    • unit;
    • quantities;
    • premium;
    • chunk key.
  • After creation:
    • source lot.qt is reduced;
    • lot.qt cannot become negative;
    • virtual lot is recalculated;
    • quantity is recalculated;
    • moves and fees are updated when needed.

Removing Physical Lots

  • Wizard: lot.remove
  • Open lot: removal forbidden.
  • Lot with stock.move:
    • move must be draft.
  • Matched or shipped lot:
    • confirmable warning.
  • Effects:
    • draft move deletion;
    • restore quantity into lot.qt;
    • restore context through shipment, getVlot_p(), getVlot_s();
    • recalculate virtual lot, quantity, fees.

Weighing / Quantity States

  • Wizard: lot.weighing
  • UI action: Do weighing
  • Writes or updates lot.qt.hist.
  • May update lot_state.
  • Synchronizes:
    • lot;
    • open quantities;
    • fees.
  • lot.qt.hist views are consultative.

Quantity Counter quantity

  • Method: Lot._recalc_line_quantity
  • Without physical lots:
    • quantity follows the virtual lot.
  • With physical lots:
    • quantity sums physical lots only.
  • quantity is readonly on trade lines.

Line Amount

  • Purchase: purchase.line.on_change_with_amount()
  • Sale: sale.line.on_change_with_amount()
  • Helpers:
    • _get_amount_quantity()
    • _get_weight_basis_quantity()
  • Priorities:
    • finished = False: quantity_theorical
    • finished = True + usable Weight basis: physical sum in that state
    • finished = True without usable Weight basis: quantity
    • legacy fallback: quantity if quantity_theorical is empty

Python Guards

  • Central check:
    • lot.lot.assert_lines_quantity_consistency()
  • Non-zero orphan lot.qt block:
    • lot.qt.validate
  • Called after:
    • quantity_theorical update;
    • physical lot creation / deletion;
    • matching / unmatching;
    • shipping / unshipping;
    • weighing.

SQL Diagnostic

Nearby Tests

  • modules/purchase_trade/tests/test_module.py
  • Existing coverage:
    • readonly quantity;
    • initialization from quantity_theorical;
    • protection when physical lots exist;
    • amount on theoretical / physical / Weight basis;
    • virtual lot resynchronization;
    • blocking when open quantity is not enough.
  • Tests to add:
    • readonly lot_hist;
    • Do weighing creates or updates a state;
    • virtual lot without direct lot_qt / lot_unit entry;
    • SQL checks replayed on inconsistent datasets.