20 KiB
20 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 balance
P1S1T1, P1S1T2, P1S2T3, P1S2T4Key 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_theoricalis entered andquantityis empty or zero:quantityis initialized fromquantity_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
virtuallot.
- not
- The virtual lot receives a first
lot.qt.histentry. Lot.validatecreates the openlot.qtthroughcreateVirtualPart.
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.
- block with
- Free
lot.qttarget:
free_quantity = target_quantity - sum(already matched or shipped lot.qt)
- If
free_quantity < 0:- block with
Please unlink or unmatch lot.
- block with
- If a free
lot.qtexists:- replace its quantity.
- If no free
lot.qtexists andfree_quantity > 0:- create a new
lot.qt.
- create a new
- Line fees are resynchronized.
Adding Physical Lots
- Wizard:
lot.add - Methods:
LotQt.add_physical_lotsLotQt.add_physical_lot
- Mandatory source: one
lot.qtline. - 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.qtis reduced; lot.qtcannot become negative;- virtual lot is recalculated;
quantityis recalculated;- moves and fees are updated when needed.
- source
Removing Physical Lots
- Wizard:
lot.remove - Open lot: removal forbidden.
- Lot with
stock.move:- move must be
draft.
- move must be
- 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.histviews are consultative.
Quantity Counter quantity
- Method:
Lot._recalc_line_quantity - Without physical lots:
quantityfollows the virtual lot.
- With physical lots:
quantitysums physical lots only.
quantityis 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_theoricalfinished = True+ usable Weight basis: physical sum in that statefinished = Truewithout usable Weight basis:quantity- legacy fallback:
quantityifquantity_theoricalis empty
Python Guards
- Central check:
lot.lot.assert_lines_quantity_consistency()
- Non-zero orphan
lot.qtblock:lot.qt.validate
- Called after:
quantity_theoricalupdate;- physical lot creation / deletion;
- matching / unmatching;
- shipping / unshipping;
- weighing.
SQL Diagnostic
- Script:
- Use:
- test database audit;
- historical data audit;
- qualification before repair.
- The script completely ignores
lot.qt = 0.
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.
- readonly
- Tests to add:
- readonly
lot_hist; Do weighingcreates or updates a state;- virtual lot without direct
lot_qt/lot_unitentry; - SQL checks replayed on inconsistent datasets.
- readonly