This commit is contained in:
2026-05-14 10:40:35 +02:00
parent 0d034816f7
commit e6bbf2f1c1
2 changed files with 185 additions and 177 deletions

View File

@@ -1,4 +1,4 @@
# Lots and Quantities
# :material-scale-balance: Lots and Quantities
Language: `en`
Mirror page: [lots-and-quantities.md](lots-and-quantities.md)
@@ -10,111 +10,116 @@ 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.
## Key Points
## :material-bookmark-check: Key Points
- A trade line has exactly one virtual lot.
- The virtual lot represents the global open balance of the line.
- `lot.qt` represents the operational forecast: matching, forecast sale,
transport, shipment.
- Physical lots consume a `lot.qt` line.
- `quantity_theorical` is the business quantity entered by the user.
- `quantity` is a read-only technical counter.
- The line amount uses theoretical quantity while the line is not finished.
- A finished line may switch back to physical execution, possibly using the
Weight basis quantity state.
- Invoicing chooses its own quantity states from `lot.qt.hist`.
- Invariants are enforced by Python and auditable with SQL.
!!! 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.
## Consultant Rules
| 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. |
```text
quantity_theorical
|
v
virtual lot P1 ---> lot.qt forecast ---> physical lot
^ |
| v
+------ recalculation after consumption
```
## :material-account-tie: Consultant Rules
### BR-PT-LOT-001 - Virtual lot / forecast / physical life cycle
- When a `purchase line` or `sale line` is created:
- one virtual lot is created;
- its initial quantity is the contractual/theoretical quantity;
- one open `lot.qt` line is created.
- Virtual lot = global open balance.
- `lot.qt` = forecast allocation of that balance.
- Example split of an open balance `P1`:
- `P1S1T1`
- `P1S1T2`
- `P1S2T3`
- `P1S2T4`
- These splits do not create several virtual lots.
- A physical lot is created from a specific `lot.qt` line.
- The physical lot consumes that forecast.
- The `lot.qt` line decreases.
- The virtual lot is recalculated.
| 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. |
!!! example ":material-source-branch: Split of an open P1 balance"
`P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
!!! note ":material-lightbulb-outline: Key Point"
These splits do not create several virtual lots. They only describe the
forecast allocation of the open balance.
### BR-PT-LOT-002 - Contractual quantity, counter quantity, finished line
- `quantity_theorical` is the business quantity.
- `quantity` is not a business input.
- Before any physical lot:
- `quantity` is initialized from `quantity_theorical`.
- After physical lots are added:
- `quantity` reflects physical execution.
- While `finished = False`:
- the line amount uses `quantity_theorical`.
- When `finished = True`:
- the user accepts ignoring the open balance;
- the line amount may switch back to physical execution;
- the initial contract trace remains unchanged;
- physical lots and their PnL remain visible.
- If a Weight basis exists on the contract:
- purchase: state from `purchase.purchase.wb.qt_type`;
- sale: state from `sale.sale.wb.qt_type`;
- the same physical lot may be read as BL on purchase and LR on sale.
- Invoicing remains independent:
- it may select a specific state in `lot.qt.hist`;
- example: BL on purchase, Weight Report or LR on sale.
| 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 |
!!! 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.
| 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` |
### BR-PT-LOT-003 - Quantity Invariants
Conservation invariant:
| 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)` |
```text
sum(physical lots) + virtual lot quantity = quantity_theorical
```
Open forecast invariant:
```text
sum(non-zero lot.qt of the virtual lot) = max(virtual lot quantity, 0)
```
Details:
- Purchase: sum all `lot.qt` where `lot_p = virtual lot`, with or without
`lot_s`.
- Sale: sum all `lot.qt` where `lot_s = virtual lot`, with or without `lot_p`.
- `lot.qt = 0` rows are ignored:
- they may keep the memory of a consumed forecast;
- `lot.qt` never goes below zero.
- The virtual lot may become negative:
- it compensates the gap between `quantity_theorical` and physical execution;
- in that case expected open forecast remains zero.
- A non-zero `lot.qt` without `lot_p` and without `lot_s` is forbidden.
| 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. |
### BR-PT-LOT-004 - Quantity history and weighing
- `lot.qt.hist` carries lot quantity states.
- The `lot.lot` form is not a direct entry area for states.
- Updates are allowed only through `Do weighing`.
- A virtual lot does not receive manual packing:
- `lot_qt` is not editable;
- `lot_unit` is not editable.
| 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. |
### Tolerances
- Tolerance is global on the line or contract.
- It is not independent per transport.
- A physical lot may consume more or less than its forecast.
- This over/under execution consumes or restores remaining tolerance.
- Current gap: full remaining-tolerance control in `LotQt.add_physical_lots` /
`LotQt.add_physical_lot` still needs confirmation.
!!! warning ":material-alert-outline: Gap to Confirm"
Full remaining-tolerance control in `LotQt.add_physical_lots` /
`LotQt.add_physical_lot` still needs confirmation.
## Developer Section
| 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. |
## :material-code-braces: Developer Section
### Key Fields

View File

@@ -1,4 +1,4 @@
# Lots et quantités
# :material-scale-balance: Lots et quantités
Langue : `fr`
Page miroir : [lots-and-quantities.en.md](lots-and-quantities.en.md)
@@ -9,113 +9,116 @@ 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.
## À retenir
## :material-bookmark-check: À retenir
- Une ligne trade possède un seul lot virtuel.
- Le lot virtuel représente le solde ouvert global de la ligne.
- `lot.qt` représente le forecast opérationnel : matching, vente prévue,
transport, shipment.
- Les lots physiques consomment une ligne `lot.qt`.
- `quantity_theorical` est la quantité métier saisie.
- `quantity` est un compteur technique non éditable.
- Le montant de ligne utilise la quantité théorique tant que la ligne n'est pas
finie.
- Une ligne finie peut revenir à l'exécuté physique, éventuellement dans l'état
de quantité du Weight basis.
- La facturation choisit ses propres états de quantité dans `lot.qt.hist`.
- Les invariants sont contrôlés par Python et auditables en SQL.
!!! 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.
## Règles consultant
| 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. |
```text
quantity_theorical
|
v
lot virtuel P1 ---> lot.qt forecast ---> lot physique
^ |
| v
+------ recalcul après consommation
```
## :material-account-tie: Règles consultant
### BR-PT-LOT-001 - Cycle de vie lot virtuel / forecast / physique
- À la création d'une `purchase line` ou `sale line` :
- un lot virtuel est créé ;
- sa quantité initiale reprend la quantité contractuelle/théorique ;
- une ligne ouverte est créée dans `lot.qt`.
- Le lot virtuel = solde ouvert global.
- `lot.qt` = découpage prévisionnel de ce solde.
- Exemple de découpage d'un solde `P1` :
- `P1S1T1`
- `P1S1T2`
- `P1S2T3`
- `P1S2T4`
- Ces découpages ne créent pas plusieurs lots virtuels.
- Un lot physique est créé depuis une ligne `lot.qt` précise.
- Le lot physique consomme cette prévision.
- La ligne `lot.qt` diminue.
- Le lot virtuel est recalculé.
| É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é. |
### BR-PT-LOT-002 - Quantité contractuelle, quantité compteur, ligne finie
!!! example ":material-source-branch: Découpage d'un solde P1"
`P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
- `quantity_theorical` est la quantité métier.
- `quantity` n'est pas une saisie métier.
- Avant lot physique :
- `quantity` est initialisée depuis `quantity_theorical`.
- Après ajout de lots physiques :
- `quantity` reflète l'exécuté physique.
- Tant que `finished = False` :
- le montant de ligne utilise `quantity_theorical`.
- Quand `finished = True` :
- l'utilisateur accepte d'ignorer le reliquat ouvert ;
- le montant de ligne peut revenir à l'exécuté physique ;
- la trace contractuelle initiale reste intacte ;
- les lots physiques et leur PnL restent visibles.
- Si un Weight basis existe sur le contrat :
- achat : état via `purchase.purchase.wb.qt_type` ;
- vente : état via `sale.sale.wb.qt_type` ;
- le même lot physique peut être lu en BL côté achat et en LR côté vente.
- La facturation reste indépendante :
- elle peut choisir un état précis dans `lot.qt.hist` ;
- exemple : BL à l'achat, Weight Report ou LR à la vente.
!!! 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.
### 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 |
!!! 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.
| 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` |
### BR-PT-LOT-003 - Invariants de quantité
Invariant de conservation :
| 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)` |
```text
somme(lots physiques) + quantité du lot virtuel = quantity_theorical
```
Invariant du forecast ouvert :
```text
somme(lot.qt non zéro du lot virtuel) = max(quantité du lot virtuel, 0)
```
Précisions :
- Achat : sommer tous les `lot.qt``lot_p = lot virtuel`, avec ou sans
`lot_s`.
- Vente : sommer tous les `lot.qt``lot_s = lot virtuel`, avec ou sans
`lot_p`.
- Les `lot.qt = 0` sont ignorés :
- ils peuvent mémoriser une prévision vidée ;
- `lot.qt` ne descend jamais sous zéro.
- Le lot virtuel peut devenir négatif :
- il compense l'écart entre `quantity_theorical` et l'exécuté physique ;
- dans ce cas le forecast ouvert attendu reste zéro.
- Un `lot.qt` non zéro sans `lot_p` ni `lot_s` est interdit.
| Repère | Cas | Règle |
| --- | --- |
| :material-cart-arrow-down: | Lot virtuel achat | Sommer tous les `lot.qt``lot_p = lot virtuel`, avec ou sans `lot_s`. |
| :material-cart-arrow-up: | Lot virtuel vente | Sommer tous les `lot.qt``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é. |
### BR-PT-LOT-004 - Historique de quantité et weighing
- `lot.qt.hist` porte les états de quantité d'un lot.
- La fiche `lot.lot` n'est pas une zone de saisie directe des états.
- Modification autorisée uniquement via `Do weighing`.
- Un lot virtuel ne reçoit pas de packing manuel :
- `lot_qt` non éditable ;
- `lot_unit` non éditable.
| 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. |
### Tolérances
- La tolérance est globale sur la ligne ou le contrat.
- Elle n'est pas indépendante par transport.
- Un lot physique peut dépasser ou sous-consommer son forecast.
- Ce dépassement consomme ou restitue de la tolérance restante.
- Gap actuel : le contrôle complet de tolérance restante dans
`LotQt.add_physical_lots` / `LotQt.add_physical_lot` reste à confirmer.
!!! 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.
## Section développeur
| 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. |
## :material-code-braces: Section développeur
### Champs clés