docs
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
# Lots and Quantities
|
||||
|
||||
Language: `en`
|
||||
Mirror page: [lots-and-quantities.md](lots-and-quantities.md)
|
||||
Status: `partial migration`
|
||||
Last code verification: `2026-05-13`
|
||||
|
||||
This page consolidates `BR-PT-LOT-001`, `BR-PT-LOT-002`, and
|
||||
`BR-PT-LOT-003`.
|
||||
Goal: drive quantity rules from a readable business definition, then secure
|
||||
them with Python guards and SQL diagnostics.
|
||||
|
||||
## Key Points
|
||||
|
||||
> **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.
|
||||
>
|
||||
| Topic | Short Rule |
|
||||
| --- | --- |
|
||||
| Entered quantity | `quantity_theorical` is the business quantity. |
|
||||
| Standard quantity | `quantity` is a read-only technical counter. |
|
||||
| Before physical lots | `quantity` follows `quantity_theorical`. |
|
||||
| After physical lots | `quantity` reflects physical execution. |
|
||||
| Open line | Line amount uses `quantity_theorical`. |
|
||||
| Finished line | Line amount may switch back to physical execution. |
|
||||
| Weight basis | Purchase and sale may read two different states of the same lot. |
|
||||
| Invoicing | It chooses states from `lot.qt.hist`. |
|
||||
| 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
|
||||
```
|
||||
|
||||
## Consultant Rules
|
||||
|
||||
### BR-PT-LOT-001 - Virtual lot / forecast / physical life cycle
|
||||
|
||||
| Moment | Business Effect |
|
||||
| --- | --- |
|
||||
| Line creation | One unique virtual lot is created. |
|
||||
| Initialization | The virtual lot takes `quantity_theorical`. |
|
||||
| Forecast | One open line is created in `lot.qt`. |
|
||||
| Planning | `lot.qt` may be split by sale, matching, transport, or shipment. |
|
||||
| Physical add | The physical lot consumes a precise `lot.qt` line. |
|
||||
| After add | `lot.qt` decreases and the virtual lot is recalculated. |
|
||||
|
||||
> **Split of an open P1 balance**
|
||||
> `P1S1T1`, `P1S1T2`, `P1S2T3`, `P1S2T4`
|
||||
>
|
||||
> **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
|
||||
|
||||
| Situation | Reference Quantity |
|
||||
| --- | --- |
|
||||
| User entry | `quantity_theorical` |
|
||||
| No physical lot | `quantity = quantity_theorical` |
|
||||
| Physical lots exist | `quantity = sum of physical lots` |
|
||||
| `finished = False` | Amount based on `quantity_theorical` |
|
||||
| `finished = True` | Amount based on physical execution |
|
||||
| Weight basis available | Amount based on the contract `wb.qt_type` state |
|
||||
|
||||
> **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.
|
||||
>
|
||||
| Reading of the Same Physical Lot | Possible State |
|
||||
| --- | --- |
|
||||
| Purchase | BL through `purchase.purchase.wb.qt_type` |
|
||||
| Sale | LR or Weight Report through `sale.sale.wb.qt_type` |
|
||||
| Invoicing | Independent choice in `lot.qt.hist` |
|
||||
|
||||
### BR-PT-LOT-003 - Quantity Invariants
|
||||
|
||||
| Invariant | Formula |
|
||||
| --- | --- |
|
||||
| Conservation | `sum(physical lots) + virtual lot = quantity_theorical` |
|
||||
| Open forecast | `sum(non-zero lot.qt) = max(virtual lot, 0)` |
|
||||
|
||||
| Case | Rule |
|
||||
| --- | --- |
|
||||
| Purchase virtual lot | Sum all `lot.qt` where `lot_p = virtual lot`, with or without `lot_s`. |
|
||||
| Sale virtual lot | Sum all `lot.qt` where `lot_s = virtual lot`, with or without `lot_p`. |
|
||||
| `lot.qt = 0` | Ignored by checks; possible memory of a consumed forecast. |
|
||||
| Negative virtual lot | Allowed to compensate theoretical / executed gap; expected forecast = zero. |
|
||||
| Non-zero orphan `lot.qt` | Forbidden if neither `lot_p` nor `lot_s` is set. |
|
||||
|
||||
### BR-PT-LOT-004 - Quantity history and weighing
|
||||
|
||||
| Element | Rule |
|
||||
| --- | --- |
|
||||
| `lot.qt.hist` | Carries lot quantity states. |
|
||||
| `lot.lot` form | No direct state entry. |
|
||||
| Update | Only through `Do weighing`. |
|
||||
| Virtual lot | No manual packing. |
|
||||
| Virtual packing fields | `lot_qt` and `lot_unit` are not editable. |
|
||||
|
||||
### Tolerances
|
||||
|
||||
> **Gap to Confirm**
|
||||
> Full remaining-tolerance control in `LotQt.add_physical_lots` /
|
||||
> `LotQt.add_physical_lot` still needs confirmation.
|
||||
>
|
||||
| Point | Target Rule |
|
||||
| --- | --- |
|
||||
| Level | Global tolerance on line or contract. |
|
||||
| Transport | No independent tolerance per transport. |
|
||||
| Over-execution | Consumes remaining tolerance. |
|
||||
| Under-execution | Restores remaining tolerance. |
|
||||
|
||||
## Developer Section
|
||||
|
||||
### Key Fields
|
||||
|
||||
- Purchase line: `purchase.line`
|
||||
- Sale line: `sale.line`
|
||||
- Lot: `lot.lot`
|
||||
- Forecast: `lot.qt`
|
||||
- History: `lot.qt.hist`
|
||||
- Purchase business quantity: `purchase.line.quantity_theorical`
|
||||
- Sale business quantity: `sale.line.quantity_theorical`
|
||||
- Technical counter: `quantity`
|
||||
- Finished line: `purchase.line.finished`, `sale.line.finished`
|
||||
- Virtual / physical lot: `lot.lot.lot_type = virtual / physic`
|
||||
- Purchase link: `lot.lot.line`
|
||||
- Sale link: `lot.lot.sale_line`
|
||||
- Purchase forecast: `lot.qt.lot_p`
|
||||
- Sale forecast: `lot.qt.lot_s`
|
||||
- Forecast quantity: `lot.qt.lot_quantity`
|
||||
- Weight basis: `purchase.purchase.wb`, `sale.sale.wb`
|
||||
- Weight basis state: `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`
|
||||
|
||||
### Line / Virtual Lot Creation
|
||||
|
||||
- Purchase: `purchase.py`, `Line.validate`
|
||||
- Sale: `sale.py`, `SaleLine.validate`
|
||||
- If `quantity_theorical` is entered and `quantity` is empty or zero:
|
||||
- `quantity` is initialized from `quantity_theorical`;
|
||||
- only if no physical lot exists.
|
||||
- If the line is eligible:
|
||||
- not `created_by_code`;
|
||||
- no lot yet;
|
||||
- non-service product;
|
||||
- `quantity_theorical != 0`;
|
||||
- create one `virtual` lot.
|
||||
- The virtual lot receives a first `lot.qt.hist` entry.
|
||||
- `Lot.validate` creates the open `lot.qt` through `createVirtualPart`.
|
||||
|
||||
### Updating `quantity_theorical`
|
||||
|
||||
- Purchase: `purchase.py`, `Line.write`
|
||||
- Sale: `sale.py`, `SaleLine.write`
|
||||
- Virtual lot target:
|
||||
|
||||
```text
|
||||
target_quantity = quantity_theorical - sum(converted physical lots)
|
||||
```
|
||||
|
||||
- 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)
|
||||
```
|
||||
|
||||
- If `free_quantity < 0`:
|
||||
- block with `Please unlink or unmatch lot`.
|
||||
- If a free `lot.qt` exists:
|
||||
- replace its quantity.
|
||||
- If no free `lot.qt` exists and `free_quantity > 0`:
|
||||
- create a new `lot.qt`.
|
||||
- Line fees are resynchronized.
|
||||
|
||||
### Adding Physical Lots
|
||||
|
||||
- Wizard: `lot.add`
|
||||
- Methods:
|
||||
- `LotQt.add_physical_lots`
|
||||
- `LotQt.add_physical_lot`
|
||||
- Mandatory source: one `lot.qt` line.
|
||||
- Direct add from a physical lot is refused.
|
||||
- Physical add on sale side through this wizard is refused: use
|
||||
`Apply matching`.
|
||||
- The physical lot inherits:
|
||||
- purchase line;
|
||||
- matched sale, if any;
|
||||
- shipment;
|
||||
- product;
|
||||
- unit;
|
||||
- quantities;
|
||||
- premium;
|
||||
- chunk key.
|
||||
- After creation:
|
||||
- source `lot.qt` is reduced;
|
||||
- `lot.qt` cannot become negative;
|
||||
- virtual lot is recalculated;
|
||||
- `quantity` is recalculated;
|
||||
- moves and fees are updated when needed.
|
||||
|
||||
### Removing Physical Lots
|
||||
|
||||
- Wizard: `lot.remove`
|
||||
- Open lot: removal forbidden.
|
||||
- Lot with `stock.move`:
|
||||
- move must be `draft`.
|
||||
- Matched or shipped lot:
|
||||
- confirmable warning.
|
||||
- Effects:
|
||||
- draft move deletion;
|
||||
- restore quantity into `lot.qt`;
|
||||
- restore context through shipment, `getVlot_p()`, `getVlot_s()`;
|
||||
- recalculate virtual lot, `quantity`, fees.
|
||||
|
||||
### Weighing / Quantity States
|
||||
|
||||
- Wizard: `lot.weighing`
|
||||
- UI action: `Do weighing`
|
||||
- Writes or updates `lot.qt.hist`.
|
||||
- May update `lot_state`.
|
||||
- Synchronizes:
|
||||
- lot;
|
||||
- open quantities;
|
||||
- fees.
|
||||
- `lot.qt.hist` views are consultative.
|
||||
|
||||
### Quantity Counter `quantity`
|
||||
|
||||
- Method: `Lot._recalc_line_quantity`
|
||||
- Without physical lots:
|
||||
- `quantity` follows the virtual lot.
|
||||
- With physical lots:
|
||||
- `quantity` sums physical lots only.
|
||||
- `quantity` is readonly on trade lines.
|
||||
|
||||
### Line Amount
|
||||
|
||||
- Purchase: `purchase.line.on_change_with_amount()`
|
||||
- Sale: `sale.line.on_change_with_amount()`
|
||||
- Helpers:
|
||||
- `_get_amount_quantity()`
|
||||
- `_get_weight_basis_quantity()`
|
||||
- Priorities:
|
||||
- `finished = False`: `quantity_theorical`
|
||||
- `finished = True` + usable Weight basis: physical sum in that state
|
||||
- `finished = True` without usable Weight basis: `quantity`
|
||||
- legacy fallback: `quantity` if `quantity_theorical` is empty
|
||||
|
||||
### Python Guards
|
||||
|
||||
- Central check:
|
||||
- `lot.lot.assert_lines_quantity_consistency()`
|
||||
- Non-zero orphan `lot.qt` block:
|
||||
- `lot.qt.validate`
|
||||
- Called after:
|
||||
- `quantity_theorical` update;
|
||||
- physical lot creation / deletion;
|
||||
- matching / unmatching;
|
||||
- shipping / unshipping;
|
||||
- weighing.
|
||||
|
||||
### SQL Diagnostic
|
||||
|
||||
- Script:
|
||||
- [sql/quantity_consistency_checks.sql](sql/quantity_consistency_checks.sql)
|
||||
- Use:
|
||||
- test database audit;
|
||||
- historical data audit;
|
||||
- qualification before repair.
|
||||
- The script completely ignores `lot.qt = 0`.
|
||||
|
||||
## Nearby Tests
|
||||
|
||||
- `modules/purchase_trade/tests/test_module.py`
|
||||
- Existing coverage:
|
||||
- readonly `quantity`;
|
||||
- initialization from `quantity_theorical`;
|
||||
- protection when physical lots exist;
|
||||
- amount on theoretical / physical / Weight basis;
|
||||
- virtual lot resynchronization;
|
||||
- blocking when open quantity is not enough.
|
||||
- Tests to add:
|
||||
- readonly `lot_hist`;
|
||||
- `Do weighing` creates or updates a state;
|
||||
- virtual lot without direct `lot_qt` / `lot_unit` entry;
|
||||
- SQL checks replayed on inconsistent datasets.
|
||||
@@ -0,0 +1,298 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user