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` Language: `en`
Mirror page: [lots-and-quantities.md](lots-and-quantities.md) 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 Goal: drive quantity rules from a readable business definition, then secure
them with Python guards and SQL diagnostics. them with Python guards and SQL diagnostics.
## Key Points ## :material-bookmark-check: Key Points
- A trade line has exactly one virtual lot. !!! abstract ":material-compass-outline: Operational Summary"
- The virtual lot represents the global open balance of the line. A trade line has exactly one virtual lot. The virtual lot carries the
- `lot.qt` represents the operational forecast: matching, forecast sale, global open balance, `lot.qt` carries the operational forecast, and
transport, shipment. physical lots consume that forecast.
- 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.
## 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 ### BR-PT-LOT-001 - Virtual lot / forecast / physical life cycle
- When a `purchase line` or `sale line` is created: | Step | Moment | Business Effect |
- one virtual lot is created; | --- | --- |
- its initial quantity is the contractual/theoretical quantity; | :material-plus-box-outline: | Line creation | One unique virtual lot is created. |
- one open `lot.qt` line is created. | :material-play-circle-outline: | Initialization | The virtual lot takes `quantity_theorical`. |
- Virtual lot = global open balance. | :material-map-marker-path: | Forecast | One open line is created in `lot.qt`. |
- `lot.qt` = forecast allocation of that balance. | :material-source-branch: | Planning | `lot.qt` may be split by sale, matching, transport, or shipment. |
- Example split of an open balance `P1`: | :material-package-variant: | Physical add | The physical lot consumes a precise `lot.qt` line. |
- `P1S1T1` | :material-refresh: | After add | `lot.qt` decreases and the virtual lot is recalculated. |
- `P1S1T2`
- `P1S2T3` !!! example ":material-source-branch: Split of an open P1 balance"
- `P1S2T4` `P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
- These splits do not create several virtual lots.
- A physical lot is created from a specific `lot.qt` line. !!! note ":material-lightbulb-outline: Key Point"
- The physical lot consumes that forecast. These splits do not create several virtual lots. They only describe the
- The `lot.qt` line decreases. forecast allocation of the open balance.
- The virtual lot is recalculated.
### BR-PT-LOT-002 - Contractual quantity, counter quantity, finished line ### BR-PT-LOT-002 - Contractual quantity, counter quantity, finished line
- `quantity_theorical` is the business quantity. | Marker | Situation | Reference Quantity |
- `quantity` is not a business input. | --- | --- |
- Before any physical lot: | :material-pencil: | User entry | `quantity_theorical` |
- `quantity` is initialized from `quantity_theorical`. | :material-package-variant-remove: | No physical lot | `quantity = quantity_theorical` |
- After physical lots are added: | :material-package-variant-closed: | Physical lots exist | `quantity = sum of physical lots` |
- `quantity` reflects physical execution. | :material-lock-open-variant-outline: | `finished = False` | Amount based on `quantity_theorical` |
- While `finished = False`: | :material-check-circle-outline: | `finished = True` | Amount based on physical execution |
- the line amount uses `quantity_theorical`. | :material-weight: | Weight basis available | Amount based on the contract `wb.qt_type` state |
- When `finished = True`:
- the user accepts ignoring the open balance; !!! warning ":material-alert-outline: What `finished` Does Not Do"
- the line amount may switch back to physical execution; `finished` does not erase the contractual quantity, delete physical lots,
- the initial contract trace remains unchanged; or hide their PnL. It only means that the open balance may be ignored for
- physical lots and their PnL remain visible. execution calculations.
- If a Weight basis exists on the contract:
- purchase: state from `purchase.purchase.wb.qt_type`; | Marker | Reading of the Same Physical Lot | Possible State |
- sale: state from `sale.sale.wb.qt_type`; | --- | --- |
- the same physical lot may be read as BL on purchase and LR on sale. | :material-cart-arrow-down: | Purchase | BL through `purchase.purchase.wb.qt_type` |
- Invoicing remains independent: | :material-cart-arrow-up: | Sale | LR or Weight Report through `sale.sale.wb.qt_type` |
- it may select a specific state in `lot.qt.hist`; | :material-file-document-check-outline: | Invoicing | Independent choice in `lot.qt.hist` |
- example: BL on purchase, Weight Report or LR on sale.
### BR-PT-LOT-003 - Quantity Invariants ### 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 | Marker | Case | Rule |
sum(physical lots) + virtual lot quantity = quantity_theorical | --- | --- |
``` | :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`. |
Open forecast invariant: | :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. |
```text | :material-alert-octagon-outline: | Non-zero orphan `lot.qt` | Forbidden if neither `lot_p` nor `lot_s` is set. |
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.
### BR-PT-LOT-004 - Quantity history and weighing ### BR-PT-LOT-004 - Quantity history and weighing
- `lot.qt.hist` carries lot quantity states. | Marker | Element | Rule |
- The `lot.lot` form is not a direct entry area for states. | --- | --- |
- Updates are allowed only through `Do weighing`. | :material-history: | `lot.qt.hist` | Carries lot quantity states. |
- A virtual lot does not receive manual packing: | :material-form-textbox: | `lot.lot` form | No direct state entry. |
- `lot_qt` is not editable; | :material-scale: | Update | Only through `Do weighing`. |
- `lot_unit` is not editable. | :material-cloud-outline: | Virtual lot | No manual packing. |
| :material-lock-outline: | Virtual packing fields | `lot_qt` and `lot_unit` are not editable. |
### Tolerances ### Tolerances
- Tolerance is global on the line or contract. !!! warning ":material-alert-outline: Gap to Confirm"
- It is not independent per transport. Full remaining-tolerance control in `LotQt.add_physical_lots` /
- A physical lot may consume more or less than its forecast. `LotQt.add_physical_lot` still needs confirmation.
- 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.
## 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 ### Key Fields

View File

@@ -1,4 +1,4 @@
# Lots et quantités # :material-scale-balance: Lots et quantités
Langue : `fr` Langue : `fr`
Page miroir : [lots-and-quantities.en.md](lots-and-quantities.en.md) 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, 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. 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. !!! abstract ":material-compass-outline: Résumé opérationnel"
- Le lot virtuel représente le solde ouvert global de la ligne. Une ligne trade possède un seul lot virtuel. Le lot virtuel porte le solde
- `lot.qt` représente le forecast opérationnel : matching, vente prévue, ouvert global, `lot.qt` porte le forecast opérationnel, et les lots
transport, shipment. physiques consomment ce forecast.
- 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.
## 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 ### BR-PT-LOT-001 - Cycle de vie lot virtuel / forecast / physique
- À la création d'une `purchase line` ou `sale line` : | Étape | Moment | Effet métier |
- un lot virtuel est créé ; | --- | --- |
- sa quantité initiale reprend la quantité contractuelle/théorique ; | :material-plus-box-outline: | Création de ligne | Création d'un lot virtuel unique. |
- une ligne ouverte est créée dans `lot.qt`. | :material-play-circle-outline: | Initialisation | Le lot virtuel reprend `quantity_theorical`. |
- Le lot virtuel = solde ouvert global. | :material-map-marker-path: | Forecast | Une ligne ouverte est créée dans `lot.qt`. |
- `lot.qt` = découpage prévisionnel de ce solde. | :material-source-branch: | Planification | `lot.qt` peut être subdivisé par vente, matching, transport ou shipment. |
- Exemple de découpage d'un solde `P1` : | :material-package-variant: | Ajout physique | Le lot physique consomme une ligne `lot.qt` précise. |
- `P1S1T1` | :material-refresh: | Après ajout | `lot.qt` diminue et le lot virtuel est recalculé. |
- `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é.
### 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. !!! note ":material-lightbulb-outline: Point clé"
- `quantity` n'est pas une saisie métier. Ces découpages ne créent pas plusieurs lots virtuels. Ils décrivent
- Avant lot physique : seulement la répartition prévisionnelle du solde ouvert.
- `quantity` est initialisée depuis `quantity_theorical`.
- Après ajout de lots physiques : ### BR-PT-LOT-002 - Quantité contractuelle, compteur, ligne finie
- `quantity` reflète l'exécuté physique.
- Tant que `finished = False` : | Repère | Situation | Quantité de référence |
- le montant de ligne utilise `quantity_theorical`. | --- | --- |
- Quand `finished = True` : | :material-pencil: | Saisie utilisateur | `quantity_theorical` |
- l'utilisateur accepte d'ignorer le reliquat ouvert ; | :material-package-variant-remove: | Aucun lot physique | `quantity = quantity_theorical` |
- le montant de ligne peut revenir à l'exécuté physique ; | :material-package-variant-closed: | Lots physiques présents | `quantity = somme des lots physiques` |
- la trace contractuelle initiale reste intacte ; | :material-lock-open-variant-outline: | `finished = False` | Montant basé sur `quantity_theorical` |
- les lots physiques et leur PnL restent visibles. | :material-check-circle-outline: | `finished = True` | Montant basé sur l'exécuté physique |
- Si un Weight basis existe sur le contrat : | :material-weight: | Weight basis disponible | Montant basé sur l'état `wb.qt_type` du contrat |
- achat : état via `purchase.purchase.wb.qt_type` ;
- vente : état via `sale.sale.wb.qt_type` ; !!! warning ":material-alert-outline: Ce que `finished` ne fait pas"
- le même lot physique peut être lu en BL côté achat et en LR côté vente. `finished` n'efface pas la quantité contractuelle, ne supprime pas les lots
- La facturation reste indépendante : physiques et ne masque pas leur PnL. Il signifie seulement que le reliquat
- elle peut choisir un état précis dans `lot.qt.hist` ; ouvert peut être ignoré pour les calculs d'exécution.
- exemple : BL à l'achat, Weight Report ou LR à la vente.
| 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é ### 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 | Repère | Cas | Règle |
somme(lots physiques) + quantité du lot virtuel = quantity_theorical | --- | --- |
``` | :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`. |
Invariant du forecast ouvert : | :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. |
```text | :material-alert-octagon-outline: | `lot.qt` non zéro orphelin | Interdit si ni `lot_p` ni `lot_s` n'est renseigné. |
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.
### BR-PT-LOT-004 - Historique de quantité et weighing ### BR-PT-LOT-004 - Historique de quantité et weighing
- `lot.qt.hist` porte les états de quantité d'un lot. | Repère | Élément | Règle |
- La fiche `lot.lot` n'est pas une zone de saisie directe des états. | --- | --- |
- Modification autorisée uniquement via `Do weighing`. | :material-history: | `lot.qt.hist` | Porte les états de quantité d'un lot. |
- Un lot virtuel ne reçoit pas de packing manuel : | :material-form-textbox: | Fiche `lot.lot` | Pas de saisie directe des états. |
- `lot_qt` non éditable ; | :material-scale: | Modification | Uniquement via `Do weighing`. |
- `lot_unit` non éditable. | :material-cloud-outline: | Lot virtuel | Pas de packing manuel. |
| :material-lock-outline: | Champs packing virtuel | `lot_qt` et `lot_unit` non éditables. |
### Tolérances ### Tolérances
- La tolérance est globale sur la ligne ou le contrat. !!! warning ":material-alert-outline: Gap à confirmer"
- Elle n'est pas indépendante par transport. Le contrôle complet de tolérance restante dans `LotQt.add_physical_lots` /
- Un lot physique peut dépasser ou sous-consommer son forecast. `LotQt.add_physical_lot` reste à confirmer.
- 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.
## 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 ### Champs clés