docs
This commit is contained in:
@@ -3,8 +3,14 @@
|
||||
Statut: `migration partielle`
|
||||
Dernière mise à jour: `2026-05-13`
|
||||
|
||||
Ce dossier devient la source de lecture thématique pour les règles business du
|
||||
module `purchase_trade`.
|
||||
Ce dossier devient la source de lecture thématique publiée dans le wiki pour les
|
||||
règles business du module `purchase_trade`.
|
||||
|
||||
Certaines pages peuvent être générées depuis une source de vérité plus sobre,
|
||||
rangée hors du dossier wiki dans `modules/purchase_trade/docs_source/`. Dans ce
|
||||
cas, la page publiée dans `modules/purchase_trade/docs/` porte un commentaire
|
||||
`Generated from ...` en tête de fichier et ne doit pas être modifiée
|
||||
directement.
|
||||
|
||||
Chaque page doit rester lisible par deux publics:
|
||||
|
||||
@@ -31,6 +37,21 @@ Toute modification d'une règle business, d'un statut, d'un champ technique ou
|
||||
d'un point de vigilance doit être reportée dans les deux pages au même moment.
|
||||
Les deux pages doivent indiquer leur page miroir en en-tête.
|
||||
|
||||
## Convention source / wiki
|
||||
|
||||
Pour les pages qui ont besoin d'une présentation riche dans MkDocs:
|
||||
|
||||
- éditer la source de vérité dans `modules/purchase_trade/docs_source/`;
|
||||
- régénérer la version wiki avec:
|
||||
|
||||
```bash
|
||||
python modules/purchase_trade/docs/tools/render_business_docs.py
|
||||
```
|
||||
|
||||
Le rendu wiki privilégie du HTML simple et portable plutôt que des extensions
|
||||
MkDocs optionnelles. Cela évite d'exposer dans le wiki des marqueurs non rendus
|
||||
comme `!!!` ou `:material-...:`.
|
||||
|
||||
## Convention de rédaction
|
||||
|
||||
Pour chaque règle durable, utiliser autant que possible ce format:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# :material-scale-balance: Lots and Quantities
|
||||
<!-- 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)
|
||||
@@ -10,116 +12,287 @@ This page consolidates `BR-PT-LOT-001`, `BR-PT-LOT-002`, and
|
||||
Goal: drive quantity rules from a readable business definition, then secure
|
||||
them with Python guards and SQL diagnostics.
|
||||
|
||||
## :material-bookmark-check: Key Points
|
||||
## Key Points
|
||||
|
||||
!!! abstract ":material-compass-outline: Operational Summary"
|
||||
A 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.
|
||||
<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>
|
||||
|
||||
| Marker | Topic | Short Rule |
|
||||
| --- | --- |
|
||||
| :material-pencil: | Entered quantity | `quantity_theorical` is the business quantity. |
|
||||
| :material-counter: | Standard quantity | `quantity` is a read-only technical counter. |
|
||||
| :material-timeline-clock-outline: | Before physical lots | `quantity` follows `quantity_theorical`. |
|
||||
| :material-package-variant-closed: | After physical lots | `quantity` reflects physical execution. |
|
||||
| :material-lock-open-variant-outline: | Open line | Line amount uses `quantity_theorical`. |
|
||||
| :material-check-circle-outline: | Finished line | Line amount may switch back to physical execution. |
|
||||
| :material-weight: | Weight basis | Purchase and sale may read two different states of the same lot. |
|
||||
| :material-file-document-check-outline: | Invoicing | It chooses states from `lot.qt.hist`. |
|
||||
| :material-shield-check-outline: | Controls | Invariants are blocked in Python and auditable with SQL. |
|
||||
<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>
|
||||
|
||||
```text
|
||||
quantity_theorical
|
||||
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>quantity_theorical
|
||||
|
|
||||
v
|
||||
virtual lot P1 ---> lot.qt forecast ---> physical lot
|
||||
virtual lot P1 ---> lot.qt forecast ---> physical lot
|
||||
^ |
|
||||
| v
|
||||
+------ recalculation after consumption
|
||||
```
|
||||
+------ recalculation after consumption</code></pre>
|
||||
|
||||
## :material-account-tie: Consultant Rules
|
||||
## Consultant Rules
|
||||
|
||||
### BR-PT-LOT-001 - Virtual lot / forecast / physical life cycle
|
||||
|
||||
| Step | Moment | Business Effect |
|
||||
| --- | --- |
|
||||
| :material-plus-box-outline: | Line creation | One unique virtual lot is created. |
|
||||
| :material-play-circle-outline: | Initialization | The virtual lot takes `quantity_theorical`. |
|
||||
| :material-map-marker-path: | Forecast | One open line is created in `lot.qt`. |
|
||||
| :material-source-branch: | Planning | `lot.qt` may be split by sale, matching, transport, or shipment. |
|
||||
| :material-package-variant: | Physical add | The physical lot consumes a precise `lot.qt` line. |
|
||||
| :material-refresh: | After add | `lot.qt` decreases and the virtual lot is recalculated. |
|
||||
<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>
|
||||
|
||||
!!! example ":material-source-branch: Split of an open P1 balance"
|
||||
`P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
|
||||
<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>
|
||||
|
||||
!!! note ":material-lightbulb-outline: Key Point"
|
||||
These splits do not create several virtual lots. They only describe the
|
||||
forecast allocation of the open balance.
|
||||
<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
|
||||
|
||||
| Marker | Situation | Reference Quantity |
|
||||
| --- | --- |
|
||||
| :material-pencil: | User entry | `quantity_theorical` |
|
||||
| :material-package-variant-remove: | No physical lot | `quantity = quantity_theorical` |
|
||||
| :material-package-variant-closed: | Physical lots exist | `quantity = sum of physical lots` |
|
||||
| :material-lock-open-variant-outline: | `finished = False` | Amount based on `quantity_theorical` |
|
||||
| :material-check-circle-outline: | `finished = True` | Amount based on physical execution |
|
||||
| :material-weight: | Weight basis available | Amount based on the contract `wb.qt_type` state |
|
||||
<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>
|
||||
|
||||
!!! warning ":material-alert-outline: What `finished` Does Not Do"
|
||||
`finished` 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.
|
||||
<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>
|
||||
|
||||
| Marker | Reading of the Same Physical Lot | Possible State |
|
||||
| --- | --- |
|
||||
| :material-cart-arrow-down: | Purchase | BL through `purchase.purchase.wb.qt_type` |
|
||||
| :material-cart-arrow-up: | Sale | LR or Weight Report through `sale.sale.wb.qt_type` |
|
||||
| :material-file-document-check-outline: | Invoicing | Independent choice in `lot.qt.hist` |
|
||||
<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
|
||||
|
||||
| Marker | Invariant | Formula |
|
||||
| --- | --- |
|
||||
| :material-shield-check-outline: | Conservation | `sum(physical lots) + virtual lot = quantity_theorical` |
|
||||
| :material-chart-timeline-variant: | Open forecast | `sum(non-zero lot.qt) = max(virtual lot, 0)` |
|
||||
<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;">Open forecast</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>sum(non-zero lot.qt) = max(virtual lot, 0)</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
| Marker | Case | Rule |
|
||||
| --- | --- |
|
||||
| :material-cart-arrow-down: | Purchase virtual lot | Sum all `lot.qt` where `lot_p = virtual lot`, with or without `lot_s`. |
|
||||
| :material-cart-arrow-up: | Sale virtual lot | Sum all `lot.qt` where `lot_s = virtual lot`, with or without `lot_p`. |
|
||||
| :material-numeric-0-box-outline: | `lot.qt = 0` | Ignored by checks; possible memory of a consumed forecast. |
|
||||
| :material-minus-circle-outline: | Negative virtual lot | Allowed to compensate theoretical / executed gap; expected forecast = zero. |
|
||||
| :material-alert-octagon-outline: | Non-zero orphan `lot.qt` | Forbidden if neither `lot_p` nor `lot_s` is set. |
|
||||
<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;">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
|
||||
|
||||
| Marker | Element | Rule |
|
||||
| --- | --- |
|
||||
| :material-history: | `lot.qt.hist` | Carries lot quantity states. |
|
||||
| :material-form-textbox: | `lot.lot` form | No direct state entry. |
|
||||
| :material-scale: | Update | Only through `Do weighing`. |
|
||||
| :material-cloud-outline: | Virtual lot | No manual packing. |
|
||||
| :material-lock-outline: | Virtual packing fields | `lot_qt` and `lot_unit` are not editable. |
|
||||
<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
|
||||
|
||||
!!! warning ":material-alert-outline: Gap to Confirm"
|
||||
Full remaining-tolerance control in `LotQt.add_physical_lots` /
|
||||
`LotQt.add_physical_lot` still needs confirmation.
|
||||
<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;">Gap to Confirm</strong><span>Full remaining-tolerance control in <code>LotQt.add_physical_lots</code> / <code>LotQt.add_physical_lot</code> still needs confirmation.</span></div>
|
||||
|
||||
| Marker | Point | Target Rule |
|
||||
| --- | --- |
|
||||
| :material-target: | Level | Global tolerance on line or contract. |
|
||||
| :material-truck-outline: | Transport | No independent tolerance per transport. |
|
||||
| :material-arrow-up-bold-outline: | Over-execution | Consumes remaining tolerance. |
|
||||
| :material-arrow-down-bold-outline: | Under-execution | Restores remaining tolerance. |
|
||||
<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;">Target 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;">Global tolerance on line or contract.</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;">Over-execution</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Consumes remaining tolerance.</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;">Restores remaining tolerance.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## :material-code-braces: Developer Section
|
||||
## Developer Section
|
||||
|
||||
### Key Fields
|
||||
|
||||
@@ -166,17 +339,13 @@ quantity_theorical
|
||||
- Sale: `sale.py`, `SaleLine.write`
|
||||
- Virtual lot target:
|
||||
|
||||
```text
|
||||
target_quantity = quantity_theorical - sum(converted physical lots)
|
||||
```
|
||||
<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>
|
||||
|
||||
- If `target_quantity < 0`:
|
||||
- block with `Please unlink or unmatch lot`.
|
||||
- Free `lot.qt` target:
|
||||
|
||||
```text
|
||||
free_quantity = target_quantity - sum(already matched or shipped lot.qt)
|
||||
```
|
||||
<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>
|
||||
|
||||
- If `free_quantity < 0`:
|
||||
- block with `Please unlink or unmatch lot`.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# :material-scale-balance: Lots et quantités
|
||||
<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
|
||||
|
||||
# Lots et quantités
|
||||
|
||||
Langue : `fr`
|
||||
Page miroir : [lots-and-quantities.en.md](lots-and-quantities.en.md)
|
||||
@@ -9,116 +11,287 @@ Cette page consolide `BR-PT-LOT-001`, `BR-PT-LOT-002` et `BR-PT-LOT-003`.
|
||||
Objectif : piloter les règles de quantité depuis une définition métier lisible,
|
||||
puis les sécuriser par des checks Python et des diagnostics SQL.
|
||||
|
||||
## :material-bookmark-check: À retenir
|
||||
## À retenir
|
||||
|
||||
!!! abstract ":material-compass-outline: Résumé opérationnel"
|
||||
Une ligne trade possède un seul lot virtuel. Le lot virtuel porte le solde
|
||||
ouvert global, `lot.qt` porte le forecast opérationnel, et les lots
|
||||
physiques consomment ce forecast.
|
||||
<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;">Résumé opérationnel</strong><span>Une ligne trade possède un seul lot virtuel. Le lot virtuel porte le solde ouvert global, <code>lot.qt</code> porte le forecast opérationnel, et les lots physiques consomment ce forecast.</span></div>
|
||||
|
||||
| Repère | Sujet | Règle courte |
|
||||
| --- | --- |
|
||||
| :material-pencil: | Quantité saisie | `quantity_theorical` est la quantité métier. |
|
||||
| :material-counter: | Quantité standard | `quantity` est un compteur technique non éditable. |
|
||||
| :material-timeline-clock-outline: | Avant physique | `quantity` suit `quantity_theorical`. |
|
||||
| :material-package-variant-closed: | Après physique | `quantity` reflète l'exécuté physique. |
|
||||
| :material-lock-open-variant-outline: | Ligne non finie | Le montant de ligne utilise `quantity_theorical`. |
|
||||
| :material-check-circle-outline: | Ligne finie | Le montant peut revenir à l'exécuté physique. |
|
||||
| :material-weight: | Weight basis | Achat et vente peuvent lire deux états différents du même lot. |
|
||||
| :material-file-document-check-outline: | Facturation | Elle choisit ses états dans `lot.qt.hist`. |
|
||||
| :material-shield-check-outline: | Contrôles | Invariants bloqués en Python et auditables en SQL. |
|
||||
<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;">Sujet</th>
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle courte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Quantité saisie</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity_theorical</code> est la quantité métier.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Quantité standard</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> est un compteur technique non éditable.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Avant physique</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> suit <code>quantity_theorical</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Après physique</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity</code> reflète l'exécuté physique.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ligne non finie</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le montant de ligne utilise <code>quantity_theorical</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ligne finie</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le montant peut revenir à l'exécuté physique.</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;">Achat et vente peuvent lire deux états différents du même lot.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Facturation</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Elle choisit ses états dans <code>lot.qt.hist</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Contrôles</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Invariants bloqués en Python et auditables en SQL.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```text
|
||||
quantity_theorical
|
||||
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>quantity_theorical
|
||||
|
|
||||
v
|
||||
lot virtuel P1 ---> lot.qt forecast ---> lot physique
|
||||
lot virtuel P1 ---> lot.qt forecast ---> lot physique
|
||||
^ |
|
||||
| v
|
||||
+------ recalcul après consommation
|
||||
```
|
||||
+------ recalcul après consommation</code></pre>
|
||||
|
||||
## :material-account-tie: Règles consultant
|
||||
## Règles consultant
|
||||
|
||||
### BR-PT-LOT-001 - Cycle de vie lot virtuel / forecast / physique
|
||||
|
||||
| Étape | Moment | Effet métier |
|
||||
| --- | --- |
|
||||
| :material-plus-box-outline: | Création de ligne | Création d'un lot virtuel unique. |
|
||||
| :material-play-circle-outline: | Initialisation | Le lot virtuel reprend `quantity_theorical`. |
|
||||
| :material-map-marker-path: | Forecast | Une ligne ouverte est créée dans `lot.qt`. |
|
||||
| :material-source-branch: | Planification | `lot.qt` peut être subdivisé par vente, matching, transport ou shipment. |
|
||||
| :material-package-variant: | Ajout physique | Le lot physique consomme une ligne `lot.qt` précise. |
|
||||
| :material-refresh: | Après ajout | `lot.qt` diminue et le lot virtuel est recalculé. |
|
||||
<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;">Effet métier</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Création de ligne</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Création d'un lot virtuel unique.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Initialisation</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le lot virtuel reprend <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;">Une ligne ouverte est créée dans <code>lot.qt</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Planification</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> peut être subdivisé par vente, matching, transport ou shipment.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ajout physique</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le lot physique consomme une ligne <code>lot.qt</code> précise.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Après ajout</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> diminue et le lot virtuel est recalculé.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
!!! example ":material-source-branch: Découpage d'un solde P1"
|
||||
`P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
|
||||
<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;">Découpage d'un solde P1</strong><span><code>P1S1T1</code>, <code>P1S1T2</code>, <code>P1S2T3</code>, <code>P1S2T4</code></span></div>
|
||||
|
||||
!!! note ":material-lightbulb-outline: Point clé"
|
||||
Ces découpages ne créent pas plusieurs lots virtuels. Ils décrivent
|
||||
seulement la répartition prévisionnelle du solde ouvert.
|
||||
<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;">Point clé</strong><span>Ces découpages ne créent pas plusieurs lots virtuels. Ils décrivent seulement la répartition prévisionnelle du solde ouvert.</span></div>
|
||||
|
||||
### BR-PT-LOT-002 - Quantité contractuelle, compteur, ligne finie
|
||||
|
||||
| Repère | Situation | Quantité de référence |
|
||||
| --- | --- |
|
||||
| :material-pencil: | Saisie utilisateur | `quantity_theorical` |
|
||||
| :material-package-variant-remove: | Aucun lot physique | `quantity = quantity_theorical` |
|
||||
| :material-package-variant-closed: | Lots physiques présents | `quantity = somme des lots physiques` |
|
||||
| :material-lock-open-variant-outline: | `finished = False` | Montant basé sur `quantity_theorical` |
|
||||
| :material-check-circle-outline: | `finished = True` | Montant basé sur l'exécuté physique |
|
||||
| :material-weight: | Weight basis disponible | Montant basé sur l'état `wb.qt_type` du contrat |
|
||||
<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;">Quantité de référence</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Saisie utilisateur</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;">Aucun lot physique</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;">Lots physiques présents</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>quantity = somme des lots physiques</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;">Montant basé sur <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;">Montant basé sur l'exécuté physique</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Weight basis disponible</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Montant basé sur l'état <code>wb.qt_type</code> du contrat</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
!!! warning ":material-alert-outline: Ce que `finished` ne fait pas"
|
||||
`finished` n'efface pas la quantité contractuelle, ne supprime pas les lots
|
||||
physiques et ne masque pas leur PnL. Il signifie seulement que le reliquat
|
||||
ouvert peut être ignoré pour les calculs d'exécution.
|
||||
<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;">Ce que <code>finished</code> ne fait pas</strong><span><code>finished</code> n'efface pas la quantité contractuelle, ne supprime pas les lots physiques et ne masque pas leur PnL. Il signifie seulement que le reliquat ouvert peut être ignoré pour les calculs d'exécution.</span></div>
|
||||
|
||||
| Repère | Lecture du même lot physique | État possible |
|
||||
| --- | --- |
|
||||
| :material-cart-arrow-down: | Achat | BL via `purchase.purchase.wb.qt_type` |
|
||||
| :material-cart-arrow-up: | Vente | LR ou Weight Report via `sale.sale.wb.qt_type` |
|
||||
| :material-file-document-check-outline: | Facturation | Choix indépendant dans `lot.qt.hist` |
|
||||
<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;">Lecture du même lot physique</th>
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">État possible</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Achat</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">BL via <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;">Vente</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">LR ou Weight Report via <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;">Facturation</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Choix indépendant dans <code>lot.qt.hist</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### BR-PT-LOT-003 - Invariants de quantité
|
||||
|
||||
| Repère | Invariant | Formule |
|
||||
| --- | --- |
|
||||
| :material-shield-check-outline: | Conservation | `somme(lots physiques) + lot virtuel = quantity_theorical` |
|
||||
| :material-chart-timeline-variant: | Forecast ouvert | `somme(lot.qt non zéro) = max(lot virtuel, 0)` |
|
||||
<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;">Formule</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>somme(lots physiques) + lot virtuel = quantity_theorical</code></td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Forecast ouvert</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>somme(lot.qt non zéro) = max(lot virtuel, 0)</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
| Repère | Cas | Règle |
|
||||
| --- | --- |
|
||||
| :material-cart-arrow-down: | Lot virtuel achat | Sommer tous les `lot.qt` où `lot_p = lot virtuel`, avec ou sans `lot_s`. |
|
||||
| :material-cart-arrow-up: | Lot virtuel vente | Sommer tous les `lot.qt` où `lot_s = lot virtuel`, avec ou sans `lot_p`. |
|
||||
| :material-numeric-0-box-outline: | `lot.qt = 0` | Ignoré par les checks ; mémoire possible d'une prévision vidée. |
|
||||
| :material-minus-circle-outline: | Lot virtuel négatif | Autorisé pour compenser l'écart théorique / exécuté ; forecast attendu = zéro. |
|
||||
| :material-alert-octagon-outline: | `lot.qt` non zéro orphelin | Interdit si ni `lot_p` ni `lot_s` n'est renseigné. |
|
||||
<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;">Cas</th>
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel achat</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sommer tous les <code>lot.qt</code> où <code>lot_p = lot virtuel</code>, avec ou sans <code>lot_s</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel vente</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sommer tous les <code>lot.qt</code> où <code>lot_s = lot virtuel</code>, avec ou sans <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;">Ignoré par les checks ; mémoire possible d'une prévision vidée.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel négatif</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Autorisé pour compenser l'écart théorique / exécuté ; forecast attendu = zéro.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot.qt</code> non zéro orphelin</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Interdit si ni <code>lot_p</code> ni <code>lot_s</code> n'est renseigné.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### BR-PT-LOT-004 - Historique de quantité et weighing
|
||||
|
||||
| Repère | Élément | Règle |
|
||||
| --- | --- |
|
||||
| :material-history: | `lot.qt.hist` | Porte les états de quantité d'un lot. |
|
||||
| :material-form-textbox: | Fiche `lot.lot` | Pas de saisie directe des états. |
|
||||
| :material-scale: | Modification | Uniquement via `Do weighing`. |
|
||||
| :material-cloud-outline: | Lot virtuel | Pas de packing manuel. |
|
||||
| :material-lock-outline: | Champs packing virtuel | `lot_qt` et `lot_unit` non éditables. |
|
||||
<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;">Élément</th>
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Règle</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;">Porte les états de quantité d'un lot.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Fiche <code>lot.lot</code></td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pas de saisie directe des états.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Modification</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Uniquement via <code>Do weighing</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Lot virtuel</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pas de packing manuel.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Champs packing virtuel</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>lot_qt</code> et <code>lot_unit</code> non éditables.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Tolérances
|
||||
|
||||
!!! warning ":material-alert-outline: Gap à confirmer"
|
||||
Le contrôle complet de tolérance restante dans `LotQt.add_physical_lots` /
|
||||
`LotQt.add_physical_lot` reste à confirmer.
|
||||
<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;">Gap à confirmer</strong><span>Le contrôle complet de tolérance restante dans <code>LotQt.add_physical_lots</code> / <code>LotQt.add_physical_lot</code> reste à confirmer.</span></div>
|
||||
|
||||
| Repère | Point | Règle cible |
|
||||
| --- | --- |
|
||||
| :material-target: | Niveau | Tolérance globale sur ligne ou contrat. |
|
||||
| :material-truck-outline: | Transport | Pas de tolérance indépendante par transport. |
|
||||
| :material-arrow-up-bold-outline: | Surconsommation | Consomme la tolérance restante. |
|
||||
| :material-arrow-down-bold-outline: | Sous-consommation | Restitue de la tolérance restante. |
|
||||
<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;">Règle cible</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Niveau</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Tolérance globale sur ligne ou contrat.</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;">Pas de tolérance indépendante par transport.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Surconsommation</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Consomme la tolérance restante.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Sous-consommation</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Restitue de la tolérance restante.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## :material-code-braces: Section développeur
|
||||
## Section développeur
|
||||
|
||||
### Champs clés
|
||||
|
||||
@@ -165,17 +338,13 @@ quantity_theorical
|
||||
- Vente : `sale.py`, `SaleLine.write`
|
||||
- Cible lot virtuel :
|
||||
|
||||
```text
|
||||
target_quantity = quantity_theorical - somme(lots physiques convertis)
|
||||
```
|
||||
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>target_quantity = quantity_theorical - somme(lots physiques convertis)</code></pre>
|
||||
|
||||
- Si `target_quantity < 0` :
|
||||
- blocage : `Please unlink or unmatch lot`.
|
||||
- Cible `lot.qt` libre :
|
||||
|
||||
```text
|
||||
free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)
|
||||
```
|
||||
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)</code></pre>
|
||||
|
||||
- Si `free_quantity < 0` :
|
||||
- blocage : `Please unlink or unmatch lot`.
|
||||
|
||||
275
modules/purchase_trade/docs/tools/render_business_docs.py
Normal file
275
modules/purchase_trade/docs/tools/render_business_docs.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""Render purchase_trade business docs for the MkDocs wiki.
|
||||
|
||||
The files in ``docs_source/business`` are the source of truth. The generated
|
||||
files are written back to ``docs/business`` because the wiki already points to
|
||||
``docs``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BUSINESS = ROOT / "business"
|
||||
SOURCE = ROOT.parent / "docs_source" / "business"
|
||||
TARGETS = (
|
||||
"lots-and-quantities.md",
|
||||
"lots-and-quantities.en.md",
|
||||
)
|
||||
|
||||
|
||||
MATERIAL_ICON = re.compile(r":material-[a-z0-9-]+:\s*")
|
||||
|
||||
|
||||
def clean_inline(text: str) -> str:
|
||||
text = MATERIAL_ICON.sub("", text)
|
||||
text = re.sub(r"\s{2,}", " ", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def split_table_row(line: str) -> list[str]:
|
||||
return [cell.strip() for cell in line.strip().strip("|").split("|")]
|
||||
|
||||
|
||||
def is_separator_row(line: str) -> bool:
|
||||
cells = split_table_row(line)
|
||||
return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell) for cell in cells)
|
||||
|
||||
|
||||
def normalize_table(lines: list[str], index: int) -> tuple[list[str], int]:
|
||||
rows = []
|
||||
while index < len(lines) and lines[index].lstrip().startswith("|"):
|
||||
rows.append(lines[index])
|
||||
index += 1
|
||||
if len(rows) < 2 or not is_separator_row(rows[1]):
|
||||
return rows, index
|
||||
|
||||
table = [split_table_row(row) for row in rows]
|
||||
header = [clean_inline(cell) for cell in table[0]]
|
||||
drop_first = header and header[0].lower() in {
|
||||
"repere",
|
||||
"repère",
|
||||
"marker",
|
||||
"etape",
|
||||
"étape",
|
||||
"step",
|
||||
}
|
||||
if drop_first:
|
||||
table = [row[1:] for row in table]
|
||||
|
||||
cleaned = []
|
||||
for row_index, row in enumerate(table):
|
||||
if row_index == 1 and is_separator_row("| " + " | ".join(row) + " |"):
|
||||
row = ["---"] * len(table[0])
|
||||
cleaned.append("| " + " | ".join(clean_inline(cell) for cell in row) + " |")
|
||||
return cleaned, index
|
||||
|
||||
|
||||
def normalize_source(text: str) -> str:
|
||||
lines = text.splitlines()
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
in_code = False
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if line.startswith("```"):
|
||||
in_code = not in_code
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if in_code:
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if line.startswith("!!! "):
|
||||
match = re.match(r"!!!\s+\w+\s+\"(.+)\"", line)
|
||||
title = clean_inline(match.group(1) if match else "Note")
|
||||
out.append(f"> **{title}**")
|
||||
i += 1
|
||||
while i < len(lines) and (
|
||||
lines[i].startswith(" ") or not lines[i].strip()
|
||||
):
|
||||
if lines[i].strip():
|
||||
out.append("> " + clean_inline(lines[i].strip()))
|
||||
else:
|
||||
out.append(">")
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if line.lstrip().startswith("|"):
|
||||
table, i = normalize_table(lines, i)
|
||||
if len(table) > 1 and is_separator_row(table[1]):
|
||||
header_len = len(split_table_row(table[0]))
|
||||
table[1] = "| " + " | ".join(["---"] * header_len) + " |"
|
||||
out.extend(table)
|
||||
continue
|
||||
|
||||
if not line.strip():
|
||||
out.append("")
|
||||
elif line.startswith("#"):
|
||||
out.append(clean_inline(line))
|
||||
else:
|
||||
leading = re.match(r"^\s*", line).group(0)
|
||||
out.append(leading + clean_inline(line.strip()))
|
||||
i += 1
|
||||
|
||||
return "\n".join(out).strip() + "\n"
|
||||
|
||||
|
||||
def render_inline(text: str) -> str:
|
||||
placeholders: list[str] = []
|
||||
|
||||
def keep_link(match: re.Match[str]) -> str:
|
||||
placeholders.append(
|
||||
f'<a href="{html.escape(match.group(2), quote=True)}">{html.escape(match.group(1))}</a>'
|
||||
)
|
||||
return f"@@LINK{len(placeholders) - 1}@@"
|
||||
|
||||
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", keep_link, text)
|
||||
text = html.escape(text)
|
||||
text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
|
||||
for idx, value in enumerate(placeholders):
|
||||
text = text.replace(f"@@LINK{idx}@@", value)
|
||||
return text
|
||||
|
||||
|
||||
def render_table(rows: list[str]) -> str:
|
||||
parsed = [split_table_row(row) for row in rows]
|
||||
header = parsed[0]
|
||||
body = parsed[2:]
|
||||
out = [
|
||||
'<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">',
|
||||
"<thead>",
|
||||
'<tr style="background:#eef3ff;">',
|
||||
]
|
||||
for cell in header:
|
||||
out.append(
|
||||
'<th style="text-align:left; padding:0.55rem 0.7rem; '
|
||||
'border-bottom:2px solid #4051b5;">'
|
||||
f"{render_inline(cell)}</th>"
|
||||
)
|
||||
out.extend(["</tr>", "</thead>", "<tbody>"])
|
||||
for row in body:
|
||||
out.append('<tr style="border-bottom:1px solid #e0e0e0;">')
|
||||
for cell in row:
|
||||
out.append(
|
||||
'<td style="vertical-align:top; padding:0.5rem 0.7rem;">'
|
||||
f"{render_inline(cell)}</td>"
|
||||
)
|
||||
out.append("</tr>")
|
||||
out.extend(["</tbody>", "</table>"])
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def render_callout(lines: list[str]) -> str:
|
||||
title = ""
|
||||
body = []
|
||||
for line in lines:
|
||||
content = line[1:].strip()
|
||||
if content.startswith("**") and content.endswith("**") and not title:
|
||||
title = content.strip("*")
|
||||
elif content:
|
||||
body.append(content)
|
||||
title_html = (
|
||||
f'<strong style="display:block; margin-bottom:0.35rem;">{render_inline(title)}</strong>'
|
||||
if title
|
||||
else ""
|
||||
)
|
||||
body_html = " ".join(render_inline(line) for line in body)
|
||||
return (
|
||||
'<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; '
|
||||
'padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;">'
|
||||
f"{title_html}<span>{body_html}</span></div>"
|
||||
)
|
||||
|
||||
|
||||
def render_source(text: str) -> str:
|
||||
lines = text.splitlines()
|
||||
out = [
|
||||
"<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->",
|
||||
"",
|
||||
]
|
||||
in_code = False
|
||||
code_lines: list[str] = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if line.startswith("```"):
|
||||
if in_code:
|
||||
out.append(
|
||||
'<pre style="background:#263238; color:#eef7ff; padding:1rem; '
|
||||
'border-radius:0.35rem; overflow:auto;"><code>'
|
||||
+ html.escape("\n".join(code_lines))
|
||||
+ "</code></pre>"
|
||||
)
|
||||
code_lines = []
|
||||
in_code = False
|
||||
else:
|
||||
in_code = True
|
||||
i += 1
|
||||
continue
|
||||
if in_code:
|
||||
code_lines.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if line.startswith(">"):
|
||||
block = []
|
||||
while i < len(lines) and lines[i].startswith(">"):
|
||||
if not lines[i][1:].strip():
|
||||
i += 1
|
||||
break
|
||||
block.append(lines[i])
|
||||
i += 1
|
||||
out.append(render_callout(block))
|
||||
out.append("")
|
||||
continue
|
||||
|
||||
if line.lstrip().startswith("|"):
|
||||
table = []
|
||||
while i < len(lines) and lines[i].lstrip().startswith("|"):
|
||||
table.append(lines[i])
|
||||
i += 1
|
||||
if len(table) > 1 and is_separator_row(table[1]):
|
||||
out.append(render_table(table))
|
||||
else:
|
||||
out.extend(table)
|
||||
continue
|
||||
|
||||
out.append(line)
|
||||
i += 1
|
||||
|
||||
return "\n".join(out).strip() + "\n"
|
||||
|
||||
|
||||
def render_all(normalize: bool = False) -> None:
|
||||
SOURCE.mkdir(parents=True, exist_ok=True)
|
||||
for name in TARGETS:
|
||||
source = SOURCE / name
|
||||
target = BUSINESS / name
|
||||
if normalize:
|
||||
source.write_text(normalize_source(source.read_text(encoding="utf-8")), encoding="utf-8")
|
||||
target.write_text(render_source(source.read_text(encoding="utf-8")), encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--normalize-source",
|
||||
action="store_true",
|
||||
help="Clean existing source files from wiki-only syntax before rendering.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
render_all(normalize=args.normalize_source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user