<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->

# Lots and Quantities

Language: `en`
Mirror page: [lots-and-quantities.md](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

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Operational Summary</strong><span>A trade line has exactly one virtual lot. The virtual lot carries the global open balance, <code>lot.qt</code> carries the operational forecast, and physical lots consume that forecast.</span></div>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Topic</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Short Rule</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Entered quantity</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity_theorical</code> is the business quantity.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Standard quantity</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> is a read-only technical counter.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Before physical lots</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> follows <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">After physical lots</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> reflects physical execution.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Open line</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line amount uses <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Finished line</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line amount may switch back to physical execution.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Weight basis</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Purchase and sale may read two different states of the same lot.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Invoicing</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">It chooses states from <code>lot.qt.hist</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Controls</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Invariants are blocked in Python and auditable with SQL.</td>
</tr>
</tbody>
</table>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>quantity_theorical
        |
        v
  virtual lot P1  ---&gt;  lot.qt forecast  ---&gt;  physical lot
        ^                    |
        |                    v
        +------ recalculation after consumption</code></pre>

## Consultant Rules

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

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Moment</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Business Effect</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line creation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">One unique virtual lot is created.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Initialization</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">The virtual lot takes <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">One open line is created in <code>lot.qt</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Planning</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> may be split by sale, matching, transport, or shipment.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Physical add</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">The physical lot consumes a precise <code>lot.qt</code> line.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">After add</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> decreases and the virtual lot is recalculated.</td>
</tr>
</tbody>
</table>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Split of an open P1 balance</strong><span><code>P1S1T1</code>, <code>P1S1T2</code>, <code>P1S2T3</code>, <code>P1S2T4</code></span></div>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Key Point</strong><span>These splits do not create several virtual lots. They only describe the forecast allocation of the open balance.</span></div>

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

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Situation</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Reference Quantity</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">User entry</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">No physical lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity = quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Physical lots exist</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity = sum of physical lots</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>finished = False</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Amount based on <code>quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>finished = True</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Amount based on physical execution</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Weight basis available</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Amount based on the contract <code>wb.qt_type</code> state</td>
</tr>
</tbody>
</table>

<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">What <code>finished</code> Does Not Do</strong><span><code>finished</code> 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.</span></div>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Reading of the Same Physical Lot</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Possible State</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Purchase</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">BL through <code>purchase.purchase.wb.qt_type</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sale</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">LR or Weight Report through <code>sale.sale.wb.qt_type</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Invoicing</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Independent choice in <code>lot.qt.hist</code></td>
</tr>
</tbody>
</table>

### BR-PT-LOT-003 - Quantity Invariants

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Invariant</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Formula</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Conservation</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>sum(physical lots) + virtual lot = quantity_theorical</code></td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Minimum open forecast</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>sum(non-zero lot.qt) &gt;= max(virtual lot, 0)</code></td>
</tr>
</tbody>
</table>

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Case</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Rule</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Purchase virtual lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sum all <code>lot.qt</code> where <code>lot_p = virtual lot</code>, with or without <code>lot_s</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sale virtual lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sum all <code>lot.qt</code> where <code>lot_s = virtual lot</code>, with or without <code>lot_p</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt = 0</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ignored by checks; possible memory of a consumed forecast.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast overconsumption</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Allowed after tolerance confirmation: <code>lot.qt</code> stops at zero, but the virtual lot absorbs the whole physical lot.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> greater than virtual lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Accepted when remaining open forecast exceeds the positive virtual lot because of physical overconsumption.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Negative virtual lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Allowed to compensate theoretical / executed gap; expected forecast = zero.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Non-zero orphan <code>lot.qt</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forbidden if neither <code>lot_p</code> nor <code>lot_s</code> is set.</td>
</tr>
</tbody>
</table>

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

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Element</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Rule</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt.hist</code></td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Carries lot quantity states.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.lot</code> form</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">No direct state entry.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Update</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Only through <code>Do weighing</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Virtual lot</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">No manual packing.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Virtual packing fields</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot_qt</code> and <code>lot_unit</code> are not editable.</td>
</tr>
</tbody>
</table>

### Tolerances

<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
<thead>
<tr style="background:#eef3ff;">
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Point</th>
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Rule</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Level</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Tolerance is defined on the <code>purchase</code> / <code>sale</code> header.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Inheritance</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lines inherit the header tolerance by default.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Transport</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">No independent tolerance per transport.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Physical lot addition</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">The check is performed during <code>Add physical lots</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matched forecast</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">If <code>lot_qt</code> carries both <code>lot_p</code> and <code>lot_s</code>, the check applies to both purchase and sale sides.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line excess</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Confirmable warning in English when the projected physical quantity exceeds the current line tolerance. The message names the blocking side: <code>purchase</code>, <code>sale</code>, or both if both checks are triggered successively.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Global envelope</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">A line excess is accepted after confirmation and consumes the contract-level envelope.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Remaining tolerance</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Contract lines are recalculated with <code>inherit_tol = False</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Over-execution</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">A line exceeding the header tolerance keeps a <code>+</code> tolerance at least equal to its actual excess.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Other lines</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Their <code>+</code> tolerance is reduced according to the remaining envelope.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Under-execution</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Mechanically restores tolerance available to other lines on the next recalculation.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Open matching</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>Go to matching</code> may match above the strict open balance if the projected quantity remains within <code>Qt max</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line gauge</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">On a line with physical lots, the gauge uses the sum of physical lots; without physical lots, it uses the sum of linked <code>lot.qt</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Header gauge</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">The purchase/sale gauge is the weighted average of line gauges, weighted by <code>quantity_theorical</code>.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matching gauge</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">In <code>Go to matching</code>, the gauge projects <code>already matched + Qt to match</code> against <code>quantity_theorical</code>, with <code>-tol_min</code> / <code>tol_max</code> bounds.</td>
</tr>
<tr style="border-bottom:1px solid #e0e0e0;">
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Line gauge layout</td>
<td style="vertical-align:top; padding:0.5rem 0.7rem;">On <code>purchase.line</code> / <code>sale.line</code> forms, place the gauge and <code>targeted_qt</code> directly in the main grid, without a <code>colspan=&quot;4&quot;</code> subgroup, and use <code>xalign=&quot;0&quot;</code> to avoid a double right shift.</td>
</tr>
</tbody>
</table>

## Developer Section

### Key Fields

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Purchase line: <code>purchase.line</code>
</li>
<li style="margin:0.38rem 0;">Sale line: <code>sale.line</code>
</li>
<li style="margin:0.38rem 0;">Lot: <code>lot.lot</code>
</li>
<li style="margin:0.38rem 0;">Forecast: <code>lot.qt</code>
</li>
<li style="margin:0.38rem 0;">History: <code>lot.qt.hist</code>
</li>
<li style="margin:0.38rem 0;">Purchase business quantity: <code>purchase.line.quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;">Sale business quantity: <code>sale.line.quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;">Technical counter: <code>quantity</code>
</li>
<li style="margin:0.38rem 0;">Finished line: <code>purchase.line.finished</code>, <code>sale.line.finished</code>
</li>
<li style="margin:0.38rem 0;">Virtual / physical lot: <code>lot.lot.lot_type = virtual / physic</code>
</li>
<li style="margin:0.38rem 0;">Purchase link: <code>lot.lot.line</code>
</li>
<li style="margin:0.38rem 0;">Sale link: <code>lot.lot.sale_line</code>
</li>
<li style="margin:0.38rem 0;">Purchase forecast: <code>lot.qt.lot_p</code>
</li>
<li style="margin:0.38rem 0;">Sale forecast: <code>lot.qt.lot_s</code>
</li>
<li style="margin:0.38rem 0;">Forecast quantity: <code>lot.qt.lot_quantity</code>
</li>
<li style="margin:0.38rem 0;">Weight basis: <code>purchase.purchase.wb</code>, <code>sale.sale.wb</code>
</li>
<li style="margin:0.38rem 0;">Weight basis state: <code>purchase.weight.basis.qt_type</code>
</li>
<li style="margin:0.38rem 0;">Packing: <code>lot.lot.lot_qt</code>, <code>lot.lot.lot_unit</code>
</li>
<li style="margin:0.38rem 0;">Tolerances: <code>tol_min</code>, <code>tol_max</code>, <code>tol_min_qt</code>, <code>tol_max_qt</code>, <code>tol_min_v</code>, <code>tol_max_v</code>, <code>tolerance_used</code>, <code>tolerance_min</code>, <code>tolerance_max</code>
</li>
</ul>

### Line / Virtual Lot Creation

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Purchase: <code>purchase.py</code>, <code>Line.validate</code>
</li>
<li style="margin:0.38rem 0;">Sale: <code>sale.py</code>, <code>SaleLine.validate</code>
</li>
<li style="margin:0.38rem 0;">If <code>quantity_theorical</code> is entered and <code>quantity</code> is empty or zero:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> is initialized from <code>quantity_theorical</code>;
</li>
<li style="margin:0.38rem 0;">only if no physical lot exists.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">If the line is eligible:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">not <code>created_by_code</code>;
</li>
<li style="margin:0.38rem 0;">no lot yet;
</li>
<li style="margin:0.38rem 0;">non-service product;
</li>
<li style="margin:0.38rem 0;"><code>quantity_theorical != 0</code>;
</li>
<li style="margin:0.38rem 0;">create one <code>virtual</code> lot.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">The virtual lot receives a first <code>lot.qt.hist</code> entry.
</li>
<li style="margin:0.38rem 0;"><code>Lot.validate</code> creates the open <code>lot.qt</code> through <code>createVirtualPart</code>.
</li>
<li style="margin:0.38rem 0;">If the line is <code>created_by_code</code>:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">it comes from a business workflow (<code>Create contracts</code>, matching, etc.);
</li>
<li style="margin:0.38rem 0;"><code>Lot.validate</code> does not create an automatic open <code>lot.qt</code>;
</li>
<li style="margin:0.38rem 0;">the workflow attaches or splits its source <code>lot.qt</code>;
</li>
<li style="margin:0.38rem 0;">the blocking check applies only after the workflow is stable.
</li>
</ul>
</li>
</ul>

### Updating `quantity_theorical`

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Purchase: <code>purchase.py</code>, <code>Line.write</code>
</li>
<li style="margin:0.38rem 0;">Sale: <code>sale.py</code>, <code>SaleLine.write</code>
</li>
<li style="margin:0.38rem 0;">Virtual lot target:
</li>
</ul>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>target_quantity = quantity_theorical - sum(converted physical lots)</code></pre>

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">If <code>target_quantity &lt; 0</code>:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">block with <code>Please unlink or unmatch lot</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Free <code>lot.qt</code> target:
</li>
</ul>

<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>free_quantity = target_quantity - sum(already matched or shipped lot.qt)</code></pre>

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">If <code>free_quantity &lt; 0</code>:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">block with <code>Please unlink or unmatch lot</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">If a free <code>lot.qt</code> exists:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">replace its quantity.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">If no free <code>lot.qt</code> exists and <code>free_quantity &gt; 0</code>:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">create a new <code>lot.qt</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Line fees are resynchronized.
</li>
</ul>

### Adding Physical Lots

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard: <code>lot.add</code>
</li>
<li style="margin:0.38rem 0;">Methods:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>LotQt.add_physical_lots</code>
</li>
<li style="margin:0.38rem 0;"><code>LotQt.add_physical_lot</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Mandatory source: one <code>lot.qt</code> line.
</li>
<li style="margin:0.38rem 0;">Direct add from a physical lot is refused.
</li>
<li style="margin:0.38rem 0;">Physical add on sale side through this wizard is refused: use <code>Apply matching</code>.
</li>
<li style="margin:0.38rem 0;">The physical lot inherits:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">purchase line;
</li>
<li style="margin:0.38rem 0;">matched sale, if any;
</li>
<li style="margin:0.38rem 0;">shipment;
</li>
<li style="margin:0.38rem 0;">product;
</li>
<li style="margin:0.38rem 0;">unit;
</li>
<li style="margin:0.38rem 0;">quantities;
</li>
<li style="margin:0.38rem 0;">premium;
</li>
<li style="margin:0.38rem 0;">chunk key.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">After creation:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">source <code>lot.qt</code> is reduced;
</li>
<li style="margin:0.38rem 0;"><code>lot.qt</code> cannot become negative;
</li>
<li style="margin:0.38rem 0;">virtual lot is recalculated;
</li>
<li style="margin:0.38rem 0;"><code>quantity</code> is recalculated;
</li>
<li style="margin:0.38rem 0;">moves and fees are updated when needed.
</li>
</ul>
</li>
</ul>

### Removing Physical Lots

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard: <code>lot.remove</code>
</li>
<li style="margin:0.38rem 0;">Open lot: removal forbidden.
</li>
<li style="margin:0.38rem 0;">Lot with <code>stock.move</code>:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">move must be <code>draft</code>.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Matched or shipped lot:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">confirmable warning.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Effects:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">draft move deletion;
</li>
<li style="margin:0.38rem 0;">restore quantity into <code>lot.qt</code>;
</li>
<li style="margin:0.38rem 0;">restore context through shipment, <code>getVlot_p()</code>, <code>getVlot_s()</code>;
</li>
<li style="margin:0.38rem 0;">recalculate virtual lot, <code>quantity</code>, fees.
</li>
</ul>
</li>
</ul>

### Weighing / Quantity States

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Wizard: <code>lot.weighing</code>
</li>
<li style="margin:0.38rem 0;">UI action: <code>Do weighing</code>
</li>
<li style="margin:0.38rem 0;">Writes or updates <code>lot.qt.hist</code>.
</li>
<li style="margin:0.38rem 0;">May update <code>lot_state</code>.
</li>
<li style="margin:0.38rem 0;">Synchronizes:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">lot;
</li>
<li style="margin:0.38rem 0;">open quantities;
</li>
<li style="margin:0.38rem 0;">fees.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;"><code>lot.qt.hist</code> views are consultative.
</li>
</ul>

### Quantity Counter `quantity`

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Method: <code>Lot._recalc_line_quantity</code>
</li>
<li style="margin:0.38rem 0;">Without physical lots:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> follows the virtual lot.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">With physical lots:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity</code> sums physical lots only.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;"><code>quantity</code> is readonly on trade lines.
</li>
</ul>

### Line Amount

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Purchase: <code>purchase.line.on_change_with_amount()</code>
</li>
<li style="margin:0.38rem 0;">Sale: <code>sale.line.on_change_with_amount()</code>
</li>
<li style="margin:0.38rem 0;">Helpers:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>_get_amount_quantity()</code>
</li>
<li style="margin:0.38rem 0;"><code>_get_weight_basis_quantity()</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Priorities:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>finished = False</code>: <code>quantity_theorical</code>
</li>
<li style="margin:0.38rem 0;"><code>finished = True</code> + usable Weight basis: physical sum in that state
</li>
<li style="margin:0.38rem 0;"><code>finished = True</code> without usable Weight basis: <code>quantity</code>
</li>
<li style="margin:0.38rem 0;">legacy fallback: <code>quantity</code> if <code>quantity_theorical</code> is empty
</li>
</ul>
</li>
</ul>

### Python Guards

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Central check:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>lot.lot.assert_lines_quantity_consistency()</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Trigger rule:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">block inconsistent final states;
</li>
<li style="margin:0.38rem 0;">do not block internal transient workflow states;
</li>
<li style="margin:0.38rem 0;">use <code>Lot.skip_quantity_consistency()</code> only around a sequence that restores the invariants afterwards;
</li>
<li style="margin:0.38rem 0;">call an explicit final check after the sequence.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Non-zero orphan <code>lot.qt</code> block:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>lot.qt.validate</code>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Called after:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><code>quantity_theorical</code> update;
</li>
<li style="margin:0.38rem 0;">physical lot creation / deletion;
</li>
<li style="margin:0.38rem 0;">matching / unmatching;
</li>
<li style="margin:0.38rem 0;">shipping / unshipping;
</li>
<li style="margin:0.38rem 0;">matched <code>Create contracts</code>;
</li>
<li style="margin:0.38rem 0;">weighing.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">The <code>created_by_code</code> case is intentionally excluded from the immediate <code>Lot.validate</code> check:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">the virtual lot is saved before the matched <code>lot.qt</code> is attached;
</li>
<li style="margin:0.38rem 0;">the invariant is checked by the final workflow guard.
</li>
</ul>
</li>
</ul>

### SQL Diagnostic

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Script:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;"><a href="sql/quantity_consistency_checks.sql">sql/quantity_consistency_checks.sql</a>
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Use:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">test database audit;
</li>
<li style="margin:0.38rem 0;">historical data audit;
</li>
<li style="margin:0.38rem 0;">qualification before repair.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">The script completely ignores <code>lot.qt = 0</code>.
</li>
</ul>

## Nearby Tests

<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/tests/test_module.py</code>
</li>
<li style="margin:0.38rem 0;">Existing coverage:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">readonly <code>quantity</code>;
</li>
<li style="margin:0.38rem 0;">initialization from <code>quantity_theorical</code>;
</li>
<li style="margin:0.38rem 0;">protection when physical lots exist;
</li>
<li style="margin:0.38rem 0;">amount on theoretical / physical / Weight basis;
</li>
<li style="margin:0.38rem 0;">virtual lot resynchronization;
</li>
<li style="margin:0.38rem 0;">blocking when open quantity is not enough.
</li>
</ul>
</li>
<li style="margin:0.38rem 0;">Tests to add:
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
<li style="margin:0.38rem 0;">readonly <code>lot_hist</code>;
</li>
<li style="margin:0.38rem 0;"><code>Do weighing</code> creates or updates a state;
</li>
<li style="margin:0.38rem 0;">virtual lot without direct <code>lot_qt</code> / <code>lot_unit</code> entry;
</li>
<li style="margin:0.38rem 0;">SQL checks replayed on inconsistent datasets.
</li>
</ul>
</li>
</ul>
