299 lines
10 KiB
Markdown
299 lines
10 KiB
Markdown
# Lots et quantités
|
|
|
|
Langue : `fr`
|
|
Page miroir : [lots-and-quantities.en.md](lots-and-quantities.en.md)
|
|
Statut : `migration partielle`
|
|
Dernière vérification code : `2026-05-13`
|
|
|
|
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
|
|
|
|
> **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.
|
|
>
|
|
| Sujet | Règle courte |
|
|
| --- | --- |
|
|
| Quantité saisie | `quantity_theorical` est la quantité métier. |
|
|
| Quantité standard | `quantity` est un compteur technique non éditable. |
|
|
| Avant physique | `quantity` suit `quantity_theorical`. |
|
|
| Après physique | `quantity` reflète l'exécuté physique. |
|
|
| Ligne non finie | Le montant de ligne utilise `quantity_theorical`. |
|
|
| Ligne finie | Le montant peut revenir à l'exécuté physique. |
|
|
| Weight basis | Achat et vente peuvent lire deux états différents du même lot. |
|
|
| Facturation | Elle choisit ses états dans `lot.qt.hist`. |
|
|
| 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
|
|
```
|
|
|
|
## Règles consultant
|
|
|
|
### BR-PT-LOT-001 - Cycle de vie lot virtuel / forecast / physique
|
|
|
|
| Moment | Effet métier |
|
|
| --- | --- |
|
|
| Création de ligne | Création d'un lot virtuel unique. |
|
|
| Initialisation | Le lot virtuel reprend `quantity_theorical`. |
|
|
| Forecast | Une ligne ouverte est créée dans `lot.qt`. |
|
|
| Planification | `lot.qt` peut être subdivisé par vente, matching, transport ou shipment. |
|
|
| Ajout physique | Le lot physique consomme une ligne `lot.qt` précise. |
|
|
| Après ajout | `lot.qt` diminue et le lot virtuel est recalculé. |
|
|
|
|
> **Découpage d'un solde P1**
|
|
> `P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
|
|
>
|
|
> **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
|
|
|
|
| Situation | Quantité de référence |
|
|
| --- | --- |
|
|
| Saisie utilisateur | `quantity_theorical` |
|
|
| Aucun lot physique | `quantity = quantity_theorical` |
|
|
| Lots physiques présents | `quantity = somme des lots physiques` |
|
|
| `finished = False` | Montant basé sur `quantity_theorical` |
|
|
| `finished = True` | Montant basé sur l'exécuté physique |
|
|
| Weight basis disponible | Montant basé sur l'état `wb.qt_type` du contrat |
|
|
|
|
> **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.
|
|
>
|
|
| Lecture du même lot physique | État possible |
|
|
| --- | --- |
|
|
| Achat | BL via `purchase.purchase.wb.qt_type` |
|
|
| Vente | LR ou Weight Report via `sale.sale.wb.qt_type` |
|
|
| Facturation | Choix indépendant dans `lot.qt.hist` |
|
|
|
|
### BR-PT-LOT-003 - Invariants de quantité
|
|
|
|
| Invariant | Formule |
|
|
| --- | --- |
|
|
| Conservation | `somme(lots physiques) + lot virtuel = quantity_theorical` |
|
|
| Forecast ouvert | `somme(lot.qt non zéro) = max(lot virtuel, 0)` |
|
|
|
|
| Cas | Règle |
|
|
| --- | --- |
|
|
| Lot virtuel achat | Sommer tous les `lot.qt` où `lot_p = lot virtuel`, avec ou sans `lot_s`. |
|
|
| Lot virtuel vente | Sommer tous les `lot.qt` où `lot_s = lot virtuel`, avec ou sans `lot_p`. |
|
|
| `lot.qt = 0` | Ignoré par les checks ; mémoire possible d'une prévision vidée. |
|
|
| Lot virtuel négatif | Autorisé pour compenser l'écart théorique / exécuté ; forecast attendu = zéro. |
|
|
| `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
|
|
|
|
| Élément | Règle |
|
|
| --- | --- |
|
|
| `lot.qt.hist` | Porte les états de quantité d'un lot. |
|
|
| Fiche `lot.lot` | Pas de saisie directe des états. |
|
|
| Modification | Uniquement via `Do weighing`. |
|
|
| Lot virtuel | Pas de packing manuel. |
|
|
| Champs packing virtuel | `lot_qt` et `lot_unit` non éditables. |
|
|
|
|
### Tolérances
|
|
|
|
> **Gap à confirmer**
|
|
> Le contrôle complet de tolérance restante dans `LotQt.add_physical_lots` /
|
|
> `LotQt.add_physical_lot` reste à confirmer.
|
|
>
|
|
| Point | Règle cible |
|
|
| --- | --- |
|
|
| Niveau | Tolérance globale sur ligne ou contrat. |
|
|
| Transport | Pas de tolérance indépendante par transport. |
|
|
| Surconsommation | Consomme la tolérance restante. |
|
|
| Sous-consommation | Restitue de la tolérance restante. |
|
|
|
|
## Section développeur
|
|
|
|
### Champs clés
|
|
|
|
- Ligne achat : `purchase.line`
|
|
- Ligne vente : `sale.line`
|
|
- Lot : `lot.lot`
|
|
- Forecast : `lot.qt`
|
|
- Historique : `lot.qt.hist`
|
|
- Quantité métier achat : `purchase.line.quantity_theorical`
|
|
- Quantité métier vente : `sale.line.quantity_theorical`
|
|
- Compteur technique : `quantity`
|
|
- Ligne finie : `purchase.line.finished`, `sale.line.finished`
|
|
- Lot virtuel / physique : `lot.lot.lot_type = virtual / physic`
|
|
- Lien achat : `lot.lot.line`
|
|
- Lien vente : `lot.lot.sale_line`
|
|
- Forecast achat : `lot.qt.lot_p`
|
|
- Forecast vente : `lot.qt.lot_s`
|
|
- Quantité forecast : `lot.qt.lot_quantity`
|
|
- Weight basis : `purchase.purchase.wb`, `sale.sale.wb`
|
|
- État Weight basis : `purchase.weight.basis.qt_type`
|
|
- Packing : `lot.lot.lot_qt`, `lot.lot.lot_unit`
|
|
- Tolerances : `tol_min`, `tol_max`, `tol_min_qt`, `tol_max_qt`,
|
|
`tol_min_v`, `tol_max_v`
|
|
|
|
### Création ligne / lot virtuel
|
|
|
|
- Achat : `purchase.py`, `Line.validate`
|
|
- Vente : `sale.py`, `SaleLine.validate`
|
|
- Si `quantity_theorical` est saisi et que `quantity` est vide ou zéro :
|
|
- `quantity` est initialisée depuis `quantity_theorical` ;
|
|
- seulement si aucun lot physique n'existe.
|
|
- Si la ligne est éligible :
|
|
- pas `created_by_code` ;
|
|
- pas encore de lot ;
|
|
- produit non service ;
|
|
- `quantity_theorical != 0` ;
|
|
- création d'un lot `virtual`.
|
|
- Le lot virtuel reçoit une première entrée `lot.qt.hist`.
|
|
- `Lot.validate` crée le `lot.qt` ouvert via `createVirtualPart`.
|
|
|
|
### Modification de `quantity_theorical`
|
|
|
|
- Achat : `purchase.py`, `Line.write`
|
|
- Vente : `sale.py`, `SaleLine.write`
|
|
- Cible lot virtuel :
|
|
|
|
```text
|
|
target_quantity = quantity_theorical - somme(lots physiques convertis)
|
|
```
|
|
|
|
- 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)
|
|
```
|
|
|
|
- Si `free_quantity < 0` :
|
|
- blocage : `Please unlink or unmatch lot`.
|
|
- Si un `lot.qt` libre existe :
|
|
- sa quantité est remplacée.
|
|
- Si aucun `lot.qt` libre n'existe et `free_quantity > 0` :
|
|
- création d'un nouveau `lot.qt`.
|
|
- Les fees de ligne sont resynchronisés.
|
|
|
|
### Ajout de lots physiques
|
|
|
|
- Wizard : `lot.add`
|
|
- Méthodes :
|
|
- `LotQt.add_physical_lots`
|
|
- `LotQt.add_physical_lot`
|
|
- Source obligatoire : une ligne `lot.qt`.
|
|
- Ajout direct depuis un lot physique refusé.
|
|
- Ajout physique côté vente par ce wizard refusé : utiliser `Apply matching`.
|
|
- Le lot physique reprend :
|
|
- ligne achat ;
|
|
- vente matchée si présente ;
|
|
- shipment ;
|
|
- produit ;
|
|
- unité ;
|
|
- quantités ;
|
|
- premium ;
|
|
- chunk key.
|
|
- Après création :
|
|
- réduction de la ligne `lot.qt` source ;
|
|
- pas de quantité `lot.qt` négative ;
|
|
- recalcul du lot virtuel ;
|
|
- recalcul de `quantity` ;
|
|
- mise à jour moves et fees si nécessaire.
|
|
|
|
### Retrait de lots physiques
|
|
|
|
- Wizard : `lot.remove`
|
|
- Lot ouvert : retrait interdit.
|
|
- Lot avec `stock.move` :
|
|
- move obligatoire en `draft`.
|
|
- Lot matché ou shippé :
|
|
- warning confirmable.
|
|
- Effets :
|
|
- suppression du move draft ;
|
|
- restauration de la quantité dans `lot.qt` ;
|
|
- contexte restauré via shipment, `getVlot_p()`, `getVlot_s()` ;
|
|
- recalcul lot virtuel, `quantity`, fees.
|
|
|
|
### Weighing / états de quantité
|
|
|
|
- Wizard : `lot.weighing`
|
|
- Action UI : `Do weighing`
|
|
- Écrit ou met à jour `lot.qt.hist`.
|
|
- Peut mettre à jour `lot_state`.
|
|
- Synchronise :
|
|
- lot ;
|
|
- quantités ouvertes ;
|
|
- fees.
|
|
- Les vues `lot.qt.hist` sont consultatives.
|
|
|
|
### Quantité compteur `quantity`
|
|
|
|
- Méthode : `Lot._recalc_line_quantity`
|
|
- Sans physique :
|
|
- `quantity` suit le lot virtuel.
|
|
- Avec physiques :
|
|
- `quantity` somme uniquement les lots physiques.
|
|
- `quantity` est readonly côté ligne trade.
|
|
|
|
### Montant de ligne
|
|
|
|
- Achat : `purchase.line.on_change_with_amount()`
|
|
- Vente : `sale.line.on_change_with_amount()`
|
|
- Helper :
|
|
- `_get_amount_quantity()`
|
|
- `_get_weight_basis_quantity()`
|
|
- Priorités :
|
|
- `finished = False` : `quantity_theorical`
|
|
- `finished = True` + Weight basis disponible : somme physique dans cet état
|
|
- `finished = True` sans Weight basis exploitable : `quantity`
|
|
- fallback legacy : `quantity` si `quantity_theorical` vide
|
|
|
|
### Garde-fous Python
|
|
|
|
- Check central :
|
|
- `lot.lot.assert_lines_quantity_consistency()`
|
|
- Blocage `lot.qt` orphelin non zéro :
|
|
- `lot.qt.validate`
|
|
- Appels après :
|
|
- modification `quantity_theorical` ;
|
|
- création / suppression de lots physiques ;
|
|
- matching / unmatching ;
|
|
- shipping / unshipping ;
|
|
- weighing.
|
|
|
|
### Diagnostic SQL
|
|
|
|
- Script :
|
|
- [sql/quantity_consistency_checks.sql](sql/quantity_consistency_checks.sql)
|
|
- Usage :
|
|
- audit des bases de test ;
|
|
- audit des données historiques ;
|
|
- qualification avant correction.
|
|
- Le script ignore totalement les `lot.qt = 0`.
|
|
|
|
## Tests proches
|
|
|
|
- `modules/purchase_trade/tests/test_module.py`
|
|
- Couverture existante :
|
|
- `quantity` readonly ;
|
|
- initialisation depuis `quantity_theorical` ;
|
|
- protection si lots physiques ;
|
|
- amount sur théorique / physique / Weight basis ;
|
|
- resynchronisation des lots virtuels ;
|
|
- blocages quand l'open ne suffit plus.
|
|
- Tests à ajouter :
|
|
- `lot_hist` readonly ;
|
|
- `Do weighing` crée ou met à jour un état ;
|
|
- lot virtuel sans saisie directe `lot_qt` / `lot_unit` ;
|
|
- contrôles SQL rejoués sur jeux de données incohérents.
|