diff --git a/AGENTS.md b/AGENTS.md
index ce99e0e..68d598a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -69,6 +69,10 @@ Guide rapide pour les agents qui codent dans ce repository.
- `notes/business_rules.md`
- Regles metier locales `purchase_trade`:
- `modules/purchase_trade/docs/business-rules.md`
+ - `modules/purchase_trade/docs_source/business/` pour les sources de verite
+ des pages business publiees dans le wiki
+ - `modules/purchase_trade/docs/business/` pour les pages generees lues par le
+ wiki
- Decisions templates / reports:
- `notes/template_business_rules.md`
- Documentation comptable et reporting:
@@ -80,6 +84,7 @@ Guide rapide pour les agents qui codent dans ce repository.
- Regles sensibles `purchase_trade` a relire avant de toucher lots, quantites
ou fees:
- `modules/purchase_trade/AGENTS.md`
+ - `modules/purchase_trade/docs_source/business/lots-and-quantities.md`
- `modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021
(`quantity_theorical`, `lot.qt`, lots physiques, fees et PnL fee).
diff --git a/modules/purchase_trade/AGENTS.md b/modules/purchase_trade/AGENTS.md
index 91e50fa..acb8c88 100644
--- a/modules/purchase_trade/AGENTS.md
+++ b/modules/purchase_trade/AGENTS.md
@@ -41,6 +41,10 @@ de negoce physique:
- Regles metier:
- `modules/purchase_trade/docs/business-rules.md`
+- Documentation business publiee dans le wiki:
+ - `modules/purchase_trade/docs/business/*.md`
+- Sources de verite de la documentation business generee:
+ - `modules/purchase_trade/docs_source/business/*.md`
- Regles templates:
- `modules/purchase_trade/docs/template-rules.md`
- Catalogue des proprietes templates:
@@ -179,6 +183,23 @@ de negoce physique:
## 5) Conventions de modification
+### Documentation business
+
+- Ne pas modifier directement une page generee sous
+ `modules/purchase_trade/docs/business/` si elle contient le commentaire
+ `Generated from docs_source/business`.
+- Toute regle business nouvelle ou modifiee doit etre editee dans
+ `modules/purchase_trade/docs_source/business/`.
+- Apres modification des sources business, regenerer le wiki avec:
+ `python modules/purchase_trade/docs/tools/render_business_docs.py`
+- Avant de rendre une modification documentaire, verifier que le rendu publie
+ est synchronise avec:
+ `python modules/purchase_trade/docs/tools/render_business_docs.py --check`
+- Les pages francaises et anglaises miroir doivent rester synchronisees.
+- Le rendu publie doit rester lisible dans MkDocs meme sans extensions
+ optionnelles: eviter les marqueurs bruts `!!!` et `:material-...:` dans les
+ fichiers publies.
+
1. Modifier la logique metier dans le fichier pivot le plus proche.
2. Si un template `.fodt` devient complexe, deplacer la logique dans une
propriete Python `report_*`.
diff --git a/modules/purchase_trade/docs/business/INDEX.md b/modules/purchase_trade/docs/business/INDEX.md
index af2d99d..8764737 100644
--- a/modules/purchase_trade/docs/business/INDEX.md
+++ b/modules/purchase_trade/docs/business/INDEX.md
@@ -1,48 +1,91 @@
+
+
# Index thematique des regles business
Statut: `migration partielle`
## Comment chercher une regle
-- Contrats, dates, lieux, banques: [contracts.md](contracts.md)
-- Lots virtuels, lots physiques, `lot.qt`, weighing: [FR](lots-and-quantities.md) / [EN](lots-and-quantities.en.md)
-- Matching, Create Contracts, back-to-back: [matching.md](matching.md)
-- Shipments, controllers, SLA, weight reports: [shipments-execution.md](shipments-execution.md)
-- Pricing manuel, basis, premium, linked currency: [pricing.md](pricing.md)
-- Fees, freight, lots effectifs, `% rate`: [fees.md](fees.md)
-- Valuation, PnL, MTM, derivatives: [valuation-pnl-mtm.md](valuation-pnl-mtm.md)
-- Factures provisoires/finales, padding: [invoicing.md](invoicing.md)
-- Impacts `account.move`, validate/post: [accounting-bridge.md](accounting-bridge.md)
-- Comptes bancaires, payment terms, payment orders: [payments-banking.md](payments-banking.md)
-- Relatorio, `.fodt`, proprietes `report_*`: [reports-templates.md](reports-templates.md)
-- Risque, credit, forex: [risk-credit-forex.md](risk-credit-forex.md)
-- Rapport Lots Management: [lots-management.md](lots-management.md)
-- Diagnostics SQL des invariants: [sql/README.md](sql/README.md)
+
+- Contrats, dates, lieux, banques: contracts.md
+
+- Lots virtuels, lots physiques,
lot.qt, weighing: FR / EN
+
+- Matching, Create Contracts, back-to-back: matching.md
+
+- Shipments, controllers, SLA, weight reports: shipments-execution.md
+
+- Pricing manuel, basis, premium, linked currency: pricing.md
+
+- Fees, freight, lots effectifs,
% rate: fees.md
+
+- Valuation, PnL, MTM, derivatives: valuation-pnl-mtm.md
+
+- Factures provisoires/finales, padding: invoicing.md
+
+- Impacts
account.move, validate/post: accounting-bridge.md
+
+- Comptes bancaires, payment terms, payment orders: payments-banking.md
+
+- Relatorio,
.fodt, proprietes report_*: reports-templates.md
+
+- Risque, credit, forex: risk-credit-forex.md
+
+- Rapport Lots Management: lots-management.md
+
+- Diagnostics SQL des invariants: sql/README.md
+
+
## Regles migrees dans cette premiere passe
-- `BR-PT-CON-001`: texte par defaut de pricing rule.
-- `BR-PT-CON-002`: delivery period coherent.
-- `BR-PT-CON-003`: lieux stock propages dans Create Contracts.
-- `BR-PT-LOT-001`: cycle de vie des lots et des quantites.
-- `BR-PT-LOT-002`: quantity contractuelle, execute physique et ligne finie.
-- `BR-PT-LOT-003`: garde-fous Python et diagnostics SQL des invariants de
- quantite.
-- `BR-PT-MAT-001`: Create Contracts multi-lots.
-- `BR-PT-SHP-001`: affectation controller.
-- `BR-PT-SHP-002`: couts SLA controller.
-- `BR-PT-SHP-003`: weight reports distants.
-- `BR-PT-PRI-001`: premium dans priced et basis.
-- `BR-PT-PRI-002`: linked currency.
-- `BR-PT-PRI-003`: pricing manuel.
-- `BR-PT-FEE-001`: maritime freight depuis fee shipment.
-- `BR-PT-FEE-002`: lots effectifs des fees.
-- `BR-PT-FEE-003`: `% rate` via delta de financement.
-- `BR-PT-VAL-001`: valuation achat/vente et sale-first.
-- `BR-PT-VAL-002`: references de valuation.
-- `BR-PT-VAL-003`: MTM hors fees.
-- `BR-PT-INV-001`: padding facture provisoire vente.
-- `BR-PT-ACC-001`: Validate facture client attribue le numero.
-- `BR-PT-PAY-001`: comptes bancaires tiers vs compagnie.
-- `BR-PT-RPT-001`: templates trade via proprietes Python.
-- `BR-PT-LOTMGT-001`: filtres Lots Management.
+
+BR-PT-CON-001: texte par defaut de pricing rule.
+
+BR-PT-CON-002: delivery period coherent.
+
+BR-PT-CON-003: lieux stock propages dans Create Contracts.
+
+BR-PT-LOT-001: cycle de vie des lots et des quantites.
+
+BR-PT-LOT-002: quantity contractuelle, execute physique et ligne finie.
+
+BR-PT-LOT-003: garde-fous Python et diagnostics SQL des invariants de quantite.
+
+BR-PT-MAT-001: Create Contracts multi-lots.
+
+BR-PT-SHP-001: affectation controller.
+
+BR-PT-SHP-002: couts SLA controller.
+
+BR-PT-SHP-003: weight reports distants.
+
+BR-PT-PRI-001: premium dans priced et basis.
+
+BR-PT-PRI-002: linked currency.
+
+BR-PT-PRI-003: pricing manuel.
+
+BR-PT-FEE-001: maritime freight depuis fee shipment.
+
+BR-PT-FEE-002: lots effectifs des fees.
+
+BR-PT-FEE-003: % rate via delta de financement.
+
+BR-PT-VAL-001: valuation achat/vente et sale-first.
+
+BR-PT-VAL-002: references de valuation.
+
+BR-PT-VAL-003: MTM hors fees.
+
+BR-PT-INV-001: padding facture provisoire vente.
+
+BR-PT-ACC-001: Validate facture client attribue le numero.
+
+BR-PT-PAY-001: comptes bancaires tiers vs compagnie.
+
+BR-PT-RPT-001: templates trade via proprietes Python.
+
+BR-PT-LOTMGT-001: filtres Lots Management.
+
+
diff --git a/modules/purchase_trade/docs/business/README.md b/modules/purchase_trade/docs/business/README.md
index 8046a59..499f027 100644
--- a/modules/purchase_trade/docs/business/README.md
+++ b/modules/purchase_trade/docs/business/README.md
@@ -1,3 +1,5 @@
+
+
# Guide de lecture des règles business
Statut: `migration partielle`
@@ -6,32 +8,44 @@ Dernière mise à jour: `2026-05-13`
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.
+Toutes les pages business publiées dans `modules/purchase_trade/docs/business/`
+sont générées depuis une source de vérité plus sobre, rangée hors du dossier
+wiki dans `modules/purchase_trade/docs_source/business/`.
+
+Les pages publiées portent un commentaire `Generated from ...` en tête de
+fichier et ne doivent pas être modifiées directement. Aucun contenu business ne
+doit être ajouté ou modifié sans passer par cette source puis par le script de
+génération.
Chaque page doit rester lisible par deux publics:
-- les consultants, qui ont besoin d'une règle fonctionnelle stable sans détail
- de code inutile;
-- les développeurs, qui ont besoin des champs, modèles, fichiers et tests
- concernés pour appliquer la règle sans l'interpréter.
+
+- les consultants, qui ont besoin d'une règle fonctionnelle stable sans détail de code inutile;
+
+- les développeurs, qui ont besoin des champs, modèles, fichiers et tests concernés pour appliquer la règle sans l'interpréter.
+
+
## Convention de langues
Chaque page thématique durable doit exister en deux versions maintenues
ensemble:
-- une page française, rédigée en français correct avec accents, typographie et
- formulations naturelles pour le wiki consultant;
-- une page anglaise miroir, portant le même contenu fonctionnel et technique.
+
+- une page française, rédigée en français correct avec accents, typographie et formulations naturelles pour le wiki consultant;
+
+- une page anglaise miroir, portant le même contenu fonctionnel et technique.
+
+
Convention de nommage:
-- page française principale: `theme.md`;
-- page anglaise miroir: `theme.en.md`.
+
+- page française principale:
theme.md;
+
+- page anglaise miroir:
theme.en.md.
+
+
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.
@@ -39,50 +53,61 @@ 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:
+Pour toute page business:
-- éditer la source de vérité dans `modules/purchase_trade/docs_source/`;
-- régénérer la version wiki avec:
+
+- éditer la source de vérité dans
modules/purchase_trade/docs_source/business/;
+
+- régénérer la version wiki avec:
+
+
-```bash
-python modules/purchase_trade/docs/tools/render_business_docs.py
-```
+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-...:`.
+Les fichiers générés dans `modules/purchase_trade/docs/business/` sont des
+artefacts de publication: ils peuvent être relus, mais toute correction doit
+être reportée dans `docs_source/business/` avant régénération.
+
+Avant de livrer une modification documentaire, vérifier que les pages publiées
+sont à jour avec:
+
+python modules/purchase_trade/docs/tools/render_business_docs.py --check
+
## Convention de rédaction
Pour chaque règle durable, utiliser autant que possible ce format:
-```md
-### BR-PT-THEME-001 - Titre court
+### BR-PT-THEME-001 - Titre court
Statut: active
Source: business-rules.md / note de session / décision projet
#### Règle consultant
-Texte fonctionnel, sans nom de champ si ce n'est pas nécessaire.
+Texte fonctionnel, sans nom de champ si ce n'est pas nécessaire.
#### Notes développeur
- Modèles/champs:
- Fichiers:
- Tests:
-- Points de vigilance:
-```
+- Points de vigilance:
## Convention de validation
Quand une règle business devient structurante pour l'intégrité des données, elle
doit être accompagnée autant que possible de deux garde-fous:
-- un check applicatif bloquant dans le code Python, appelé à la fin des flux qui
- modifient les données concernées;
-- un diagnostic SQL en lecture seule pour auditer les bases existantes ou les
- bases de test.
+
+- un check applicatif bloquant dans le code Python, appelé à la fin des flux qui modifient les données concernées;
+
+- un diagnostic SQL en lecture seule pour auditer les bases existantes ou les bases de test.
+
+
Les diagnostics SQL du module sont rangés dans `business/sql/`. Ils ne
remplacent pas les règles applicatives: ils servent à retrouver et qualifier les
@@ -94,10 +119,19 @@ Les anciennes pages ne sont pas supprimées à cette étape. Elles restent des
sources de vérification jusqu'à ce que chaque décision soit promue dans une
page thématique:
-- `modules/purchase_trade/docs/business-rules.md`
-- `modules/purchase_trade/docs/fees.md`
-- `modules/purchase_trade/docs/padding-invoice-accounting.md`
-- `modules/purchase_trade/docs/template-rules.md`
-- `modules/purchase_trade/docs/template-properties.md`
-- `notes/business_rules.md`
-- `notes/template_business_rules.md`
+
+modules/purchase_trade/docs/business-rules.md
+
+modules/purchase_trade/docs/fees.md
+
+modules/purchase_trade/docs/padding-invoice-accounting.md
+
+modules/purchase_trade/docs/template-rules.md
+
+modules/purchase_trade/docs/template-properties.md
+
+notes/business_rules.md
+
+notes/template_business_rules.md
+
+
diff --git a/modules/purchase_trade/docs/business/accounting-bridge.md b/modules/purchase_trade/docs/business/accounting-bridge.md
index 9ed8d82..ca93606 100644
--- a/modules/purchase_trade/docs/business/accounting-bridge.md
+++ b/modules/purchase_trade/docs/business/accounting-bridge.md
@@ -1,3 +1,5 @@
+
+
# Pont comptable
Statut: `migration partielle`
@@ -13,13 +15,16 @@ validation, comme une facture fournisseur.
### Notes developpeur
-- Cible: `account.invoice` avec `type = out`.
-- Workflow `Validate`: creer `account.move` et attribuer `number`.
-- Workflow `Post`: ne doit pas reintroduire une session fraiche specifique au
- flux client.
+
+- Cible:
account.invoice avec type = out.
+
+- Workflow
Validate: creer account.move et attribuer number.
+
+- Workflow
Post: ne doit pas reintroduire une session fraiche specifique au flux client.
+
+
## Notes de migration
Les notes comptables detaillees restent dans `notes/accounting/` tant qu'elles
n'ont pas ete promues ici.
-
diff --git a/modules/purchase_trade/docs/business/contracts.md b/modules/purchase_trade/docs/business/contracts.md
index ad12d06..9c8bfe1 100644
--- a/modules/purchase_trade/docs/business/contracts.md
+++ b/modules/purchase_trade/docs/business/contracts.md
@@ -1,3 +1,5 @@
+
+
# Contrats achat / vente
Statut: `migration partielle`
@@ -13,9 +15,14 @@ repris automatiquement sur les nouvelles lignes achat et vente.
### Notes developpeur
-- Configuration: `purchase_trade.configuration.pricing_rule`.
-- Cibles: `purchase.line.pricing_rule`, `sale.line.pricing_rule`.
-- Les lignes existantes ne sont pas modifiees retroactivement.
+
+- Configuration:
purchase_trade.configuration.pricing_rule.
+
+- Cibles:
purchase.line.pricing_rule, sale.line.pricing_rule.
+
+- Les lignes existantes ne sont pas modifiees retroactivement.
+
+
## BR-PT-CON-002 - Delivery period coherent
@@ -28,9 +35,12 @@ borne renseignee reste acceptee.
### Notes developpeur
-- Champs: `purchase.line.from_del`, `purchase.line.to_del`,
- `sale.line.from_del`, `sale.line.to_del`.
-- Validation attendue: bloquer si `from_del > to_del`.
+
+- Champs:
purchase.line.from_del, purchase.line.to_del, sale.line.from_del, sale.line.to_del.
+
+- Validation attendue: bloquer si
from_del > to_del.
+
+
## BR-PT-CON-003 - Propagation des lieux stock dans Create Contracts
@@ -43,8 +53,13 @@ logistiques doivent etre proposes selon le flux source pour eviter la ressaisie.
### Notes developpeur
-- Champs: `from_location`, `to_location`.
-- Flux fournisseur vers client: recopier le couple source.
-- Achat vers stock puis vente: `sale.from_location = purchase.to_location`.
-- Vente depuis stock puis achat: `purchase.to_location = sale.from_location`.
-
+
+- Champs:
from_location, to_location.
+
+- Flux fournisseur vers client: recopier le couple source.
+
+- Achat vers stock puis vente:
sale.from_location = purchase.to_location.
+
+- Vente depuis stock puis achat:
purchase.to_location = sale.from_location.
+
+
diff --git a/modules/purchase_trade/docs/business/fees.md b/modules/purchase_trade/docs/business/fees.md
index 47e9cbf..902b5b8 100644
--- a/modules/purchase_trade/docs/business/fees.md
+++ b/modules/purchase_trade/docs/business/fees.md
@@ -1,3 +1,5 @@
+
+
# Fees
Statut: `migration partielle`
@@ -15,10 +17,16 @@ shipment, pas d'un champ direct de la facture.
### Notes developpeur
-- Retrouver le lot physique depuis la facture.
-- Retrouver son `shipment_in`.
-- Chercher le `fee.fee` avec `product.name = 'Maritime freight'`.
-- Utiliser `fee.get_amount()`.
+
+- Retrouver le lot physique depuis la facture.
+
+- Retrouver son
shipment_in.
+
+- Chercher le
fee.fee avec product.name = 'Maritime freight'.
+
+- Utiliser
fee.get_amount().
+
+
## BR-PT-FEE-002 - Les fees lies aux lots privilegient les physiques
@@ -31,12 +39,18 @@ physique est lie, les lots physiques deviennent la base de calcul du fee.
### Notes developpeur
-- Ne pas supprimer le lien virtuel: il reste le fallback.
-- Quantite `ppack`: somme de `lot.lot_qt` des physiques.
-- Modes quantitatifs: quantites courantes converties des physiques.
-- La meme selection s'applique au PnL fee.
-- Points de synchronisation: creation fee, lien `fee.lots`, changement de
- `quantity_theorical`, weighing, suppression de physique.
+
+- Ne pas supprimer le lien virtuel: il reste le fallback.
+
+- Quantite
ppack: somme de lot.lot_qt des physiques.
+
+- Modes quantitatifs: quantites courantes converties des physiques.
+
+- La meme selection s'applique au PnL fee.
+
+- Points de synchronisation: creation fee, lien
fee.lots, changement de quantity_theorical, weighing, suppression de physique.
+
+
## BR-PT-FEE-003 - Fees `% rate` via delta de financement
@@ -49,7 +63,11 @@ de la ligne d'estimation `BL date`, pas avec la date du jour.
### Notes developpeur
-- Formule: `amount = unit_price * quantity * (price / 100) * fin_int_delta / 360`.
-- Source du delta: ligne `Estimated date` avec `trigger = bldate`.
-- Si aucune ligne `bldate` n'existe, ne pas calculer de montant `% rate`.
-
+
+- Formule:
amount = unit_price * quantity * (price / 100) * fin_int_delta / 360.
+
+- Source du delta: ligne
Estimated date avec trigger = bldate.
+
+- Si aucune ligne
bldate n'existe, ne pas calculer de montant % rate.
+
+
diff --git a/modules/purchase_trade/docs/business/glossary.md b/modules/purchase_trade/docs/business/glossary.md
index 5987986..e4e1dc6 100644
--- a/modules/purchase_trade/docs/business/glossary.md
+++ b/modules/purchase_trade/docs/business/glossary.md
@@ -1,22 +1,42 @@
+
+
# Glossaire purchase_trade
Statut: `migration partielle`
-- `Purchase Line`: ligne d'achat.
-- `Sale Line`: ligne de vente.
-- `quantity_theorical`: quantite contractuelle theorique d'une ligne.
-- `Virtual Lot`: lot de type `virtual`, representant un reliquat ouvert.
-- `Physical Lot`: lot de type `physic`, representant une quantite executee.
-- `lot.qt`: ligne de quantite ouverte, matchee ou rattachee a un shipment.
-- `lot.qt ouvert`: `lot.qt` libre, sans lot oppose et sans shipment.
-- `Shipment In`: shipment entrant utilise aussi pour les flux dropship dans ce module.
-- `Dropship`: flux fournisseur vers client, sans passage par stock interne.
-- `Inbound`: flux entrant classique qui ne correspond pas au dropship.
-- `Basis`: mode de prix construit a partir d'un prix de marche et d'un premium.
-- `Premium`: prime ou discount commercial ajoute au prix economique.
-- `Linked currency`: saisie d'un prix dans une devise/unite liee, par exemple `USC/LB`.
-- `Valuation`: lignes de PnL generees pour prix, fees, derivatives et MTM.
-- `MTM`: mark-to-market applique aux lignes valorisables au marche.
-- `Fee`: frais commercial ou logistique rattache a une ligne, un lot ou un shipment.
-- `Report property`: propriete Python exposee pour simplifier un template Relatorio.
-
+
+Purchase Line: ligne d'achat.
+
+Sale Line: ligne de vente.
+
+quantity_theorical: quantite contractuelle theorique d'une ligne.
+
+Virtual Lot: lot de type virtual, representant un reliquat ouvert.
+
+Physical Lot: lot de type physic, representant une quantite executee.
+
+lot.qt: ligne de quantite ouverte, matchee ou rattachee a un shipment.
+
+lot.qt ouvert: lot.qt libre, sans lot oppose et sans shipment.
+
+Shipment In: shipment entrant utilise aussi pour les flux dropship dans ce module.
+
+Dropship: flux fournisseur vers client, sans passage par stock interne.
+
+Inbound: flux entrant classique qui ne correspond pas au dropship.
+
+Basis: mode de prix construit a partir d'un prix de marche et d'un premium.
+
+Premium: prime ou discount commercial ajoute au prix economique.
+
+Linked currency: saisie d'un prix dans une devise/unite liee, par exemple USC/LB.
+
+Valuation: lignes de PnL generees pour prix, fees, derivatives et MTM.
+
+MTM: mark-to-market applique aux lignes valorisables au marche.
+
+Fee: frais commercial ou logistique rattache a une ligne, un lot ou un shipment.
+
+Report property: propriete Python exposee pour simplifier un template Relatorio.
+
+
diff --git a/modules/purchase_trade/docs/business/invariants.md b/modules/purchase_trade/docs/business/invariants.md
index a38144a..c7c0480 100644
--- a/modules/purchase_trade/docs/business/invariants.md
+++ b/modules/purchase_trade/docs/business/invariants.md
@@ -1,3 +1,5 @@
+
+
# Invariants structurants
Statut: `migration partielle`
@@ -15,11 +17,14 @@ contrats et l'execution logistique.
### Notes developpeur
-- Source historique: `BR-PT-002`.
-- Voir aussi: [lots-and-quantities.md](lots-and-quantities.md),
- [matching.md](matching.md), [reports-templates.md](reports-templates.md).
-- Champs frequents: `lot.line`, `lot.sale_line`, `lot_shipment_in`,
- `lot_shipment_internal`, `lot_shipment_out`.
+
## INV-PT-002 - Le reliquat ouvert ne doit pas doubler les lots physiques
@@ -31,19 +36,22 @@ executer.
### Notes developpeur
-- Source historique: `BR-PT-020`.
-- Le calcul doit tenir compte de la quantite contractuelle, des lots physiques
- existants et des `lot.qt` deja matches ou shippes.
-- Regle de conservation:
- `sum(lots physiques) + lot virtuel = quantity_theorical`.
-- Regle du forecast ouvert:
- `sum(lot.qt non zero) = max(lot virtuel, 0)`.
-- Les lignes `lot.qt` a zero sont ignorees par les checks: elles peuvent servir
- de memoire d'une prevision consommee.
-- Le check applicatif est centralise dans
- `lot.lot.assert_lines_quantity_consistency()`.
-- Le diagnostic SQL correspondant est
- [sql/quantity_consistency_checks.sql](sql/quantity_consistency_checks.sql).
+
+- Source historique:
BR-PT-020.
+
+- Le calcul doit tenir compte de la quantite contractuelle, des lots physiques existants et des
lot.qt deja matches ou shippes.
+
+- Regle de conservation:
sum(lots physiques) + lot virtuel = quantity_theorical.
+
+- Regle du forecast ouvert:
sum(lot.qt non zero) = max(lot virtuel, 0).
+
+- Les lignes
lot.qt a zero sont ignorees par les checks: elles peuvent servir de memoire d'une prevision consommee.
+
+- Le check applicatif est centralise dans
lot.lot.assert_lines_quantity_consistency().
+
+- Le diagnostic SQL correspondant est sql/quantity_consistency_checks.sql.
+
+
## INV-PT-003 - Les fees utilisent leurs lots effectifs
@@ -55,10 +63,14 @@ effective de calcul.
### Notes developpeur
-- Source historique: `BR-PT-021`.
-- Ne pas supprimer le lien virtuel: il reste le fallback si les physiques sont
- retires.
-- Voir [fees.md](fees.md).
+
+- Source historique:
BR-PT-021.
+
+- Ne pas supprimer le lien virtuel: il reste le fallback si les physiques sont retires.
+
+- Voir fees.md.
+
+
## INV-PT-004 - Les templates doivent rester simples
@@ -69,7 +81,11 @@ chemin technique pour les retrouver est complexe.
### Notes developpeur
-- Preferer des proprietes Python `report_*` aux expressions Genshi complexes.
-- Ne pas supposer qu'une variable locale comme `shipment` existe partout dans
- un `.fodt`.
-- Voir [reports-templates.md](reports-templates.md).
+
+- Preferer des proprietes Python
report_* aux expressions Genshi complexes.
+
+- Ne pas supposer qu'une variable locale comme
shipment existe partout dans un .fodt.
+
+- Voir reports-templates.md.
+
+
diff --git a/modules/purchase_trade/docs/business/invoicing.md b/modules/purchase_trade/docs/business/invoicing.md
index ac3c51a..13b1a5b 100644
--- a/modules/purchase_trade/docs/business/invoicing.md
+++ b/modules/purchase_trade/docs/business/invoicing.md
@@ -1,3 +1,5 @@
+
+
# Facturation trade
Statut: `migration partielle`
@@ -13,12 +15,15 @@ constituer une provision, sans modifier la quantite physique du lot.
### Notes developpeur
-- Le padding global du wizard `lot.invoice` est reparti entre les lots
- selectionnes.
-- La ligne facture expose `Inc. padding`.
-- Le lot conserve sa part dans `sale_invoice_padding`.
-- La facture finale retire le padding de la quantite provisoire avant de
- calculer le delta.
-- Les ecritures d'extourne doivent relire la provisoire depuis
- `lot.sale_invoice_line_prov`.
-
+
+- Le padding global du wizard
lot.invoice est reparti entre les lots selectionnes.
+
+- La ligne facture expose
Inc. padding.
+
+- Le lot conserve sa part dans
sale_invoice_padding.
+
+- La facture finale retire le padding de la quantite provisoire avant de calculer le delta.
+
+- Les ecritures d'extourne doivent relire la provisoire depuis
lot.sale_invoice_line_prov.
+
+
diff --git a/modules/purchase_trade/docs/business/lots-and-quantities.en.md b/modules/purchase_trade/docs/business/lots-and-quantities.en.md
index 0d8157d..dcf4608 100644
--- a/modules/purchase_trade/docs/business/lots-and-quantities.en.md
+++ b/modules/purchase_trade/docs/business/lots-and-quantities.en.md
@@ -296,174 +296,379 @@ them with Python guards and SQL diagnostics.
### 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`
+
+- 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`.
+
+- 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:
+
+- Purchase:
purchase.py, Line.write
+
+- Sale:
sale.py, SaleLine.write
+
+- Virtual lot target:
+
+
target_quantity = quantity_theorical - sum(converted physical lots)
-- If `target_quantity < 0`:
- - block with `Please unlink or unmatch lot`.
-- Free `lot.qt` target:
+
+- If
target_quantity < 0:
+
+- block with
Please unlink or unmatch lot.
+
+
+
+- Free
lot.qt target:
+
+
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.
+
+- 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.
+
+- 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.
+
+- Wizard:
lot.remove
+
+- Open lot: removal forbidden.
+
+- Lot with
stock.move:
+
+
+- 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.
+
+- 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.
+
+- 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
+
+- 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.
+
+- Central check:
+
+lot.lot.assert_lines_quantity_consistency()
+
+
+
+- Non-zero orphan
lot.qt block:
+
+
+- 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`.
+
+- Script:
+
+
+- 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.
+
+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.
+
+
+
+
diff --git a/modules/purchase_trade/docs/business/lots-and-quantities.md b/modules/purchase_trade/docs/business/lots-and-quantities.md
index f19b83f..50cedf2 100644
--- a/modules/purchase_trade/docs/business/lots-and-quantities.md
+++ b/modules/purchase_trade/docs/business/lots-and-quantities.md
@@ -295,173 +295,379 @@ puis les sécuriser par des checks Python et des diagnostics SQL.
### 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`
+
+- 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`.
+
+- 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 :
+
+- Achat :
purchase.py, Line.write
+
+- Vente :
sale.py, SaleLine.write
+
+- Cible lot virtuel :
+
+
target_quantity = quantity_theorical - somme(lots physiques convertis)
-- Si `target_quantity < 0` :
- - blocage : `Please unlink or unmatch lot`.
-- Cible `lot.qt` libre :
+
+- Si
target_quantity < 0 :
+
+- blocage :
Please unlink or unmatch lot.
+
+
+
+- Cible
lot.qt libre :
+
+
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.
+
+- 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.
+
+- 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.
+
+- 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.
+
+- 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.
+
+- 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
+
+- 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.
+
+- Check central :
+
+lot.lot.assert_lines_quantity_consistency()
+
+
+
+- Blocage
lot.qt orphelin non zéro :
+
+
+- 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`.
+
+- Script :
+
+
+- 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.
+
+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.
+
+
+
+
diff --git a/modules/purchase_trade/docs/business/lots-management.md b/modules/purchase_trade/docs/business/lots-management.md
index fd89c29..96279d5 100644
--- a/modules/purchase_trade/docs/business/lots-management.md
+++ b/modules/purchase_trade/docs/business/lots-management.md
@@ -1,3 +1,5 @@
+
+
# Lots Management
Statut: `migration partielle`
@@ -13,15 +15,21 @@ commercial, le sens achat/vente et l'avancement logistique.
### Notes developpeur
-- Filtres: `Matching status`, `Side`, `Shipping status`, `Dimension`,
- `Strategy`.
-- Dates `As of` / `To`: `purchase.purchase_date` et `sale.sale_date`.
-- `Unshipped`: aucun `shipment_in`.
-- `Scheduled`: shipment `draft`.
-- `Shipped`: shipment `started`.
-- `Received`: shipment `received` ou `done`.
-- `Shipment Type = Dropship` si `from_location.type = supplier` et
- `to_location.type = customer`, sinon `Inbound`.
-- `Mark as finished` masque seulement les reliquats ouverts / virtuels, pas
- les lots physiques.
-
+
+- Filtres:
Matching status, Side, Shipping status, Dimension, Strategy.
+
+- Dates
As of / To: purchase.purchase_date et sale.sale_date.
+
+Unshipped: aucun shipment_in.
+
+Scheduled: shipment draft.
+
+Shipped: shipment started.
+
+Received: shipment received ou done.
+
+Shipment Type = Dropship si from_location.type = supplier et to_location.type = customer, sinon Inbound.
+
+Mark as finished masque seulement les reliquats ouverts / virtuels, pas les lots physiques.
+
+
diff --git a/modules/purchase_trade/docs/business/matching.md b/modules/purchase_trade/docs/business/matching.md
index 89c617c..1fc1f77 100644
--- a/modules/purchase_trade/docs/business/matching.md
+++ b/modules/purchase_trade/docs/business/matching.md
@@ -1,3 +1,5 @@
+
+
# Matching achat / vente
Statut: `migration partielle`
@@ -14,14 +16,16 @@ lot source.
### Notes developpeur
-- La quantite du wizard doit correspondre a la somme des quantites ouvertes
- selectionnees.
-- Creer une ligne par `lot.qt` source.
-- Conserver `created_by_code = True` pour eviter les creations automatiques
- parasites lors des validations.
+
+- La quantite du wizard doit correspondre a la somme des quantites ouvertes selectionnees.
+
+- Creer une ligne par
lot.qt source.
+
+- Conserver
created_by_code = True pour eviter les creations automatiques parasites lors des validations.
+
+
## Notes de migration
Les regles sur `Apply matching` presentes dans les notes de session du
`2026-05-09` doivent encore etre promues ici.
-
diff --git a/modules/purchase_trade/docs/business/payments-banking.md b/modules/purchase_trade/docs/business/payments-banking.md
index 977ff4a..cf318be 100644
--- a/modules/purchase_trade/docs/business/payments-banking.md
+++ b/modules/purchase_trade/docs/business/payments-banking.md
@@ -1,3 +1,5 @@
+
+
# Paiements et banques
Statut: `migration partielle`
@@ -13,9 +15,13 @@ bancaire utilise par la compagnie courante pour encaisser ou payer.
### Notes developpeur
-- Contrats: `sale.sale`, `purchase.purchase`.
-- `bank_account`: compte de la party du contrat.
-- `our_bank_account`: compte de la compagnie courante, selectionnable parmi
- les comptes disponibles.
-- La devise du contrat est prioritaire pour proposer un compte par defaut.
-
+
+- Contrats:
sale.sale, purchase.purchase.
+
+bank_account: compte de la party du contrat.
+
+our_bank_account: compte de la compagnie courante, selectionnable parmi les comptes disponibles.
+
+- La devise du contrat est prioritaire pour proposer un compte par defaut.
+
+
diff --git a/modules/purchase_trade/docs/business/pricing.md b/modules/purchase_trade/docs/business/pricing.md
index 3ebee56..2b432d3 100644
--- a/modules/purchase_trade/docs/business/pricing.md
+++ b/modules/purchase_trade/docs/business/pricing.md
@@ -1,3 +1,5 @@
+
+
# Pricing, basis, premium
Statut: `migration partielle`
@@ -13,9 +15,14 @@ la ligne soit en prix fixe ou en basis.
### Notes developpeur
-- `unit_price` reste le prix de base hors premium.
-- Montant economique: `unit_price + premium converti si necessaire`.
-- En basis, le premium s'applique aussi aux blocs valorises.
+
+unit_price reste le prix de base hors premium.
+
+- Montant economique:
unit_price + premium converti si necessaire.
+
+- En basis, le premium s'applique aussi aux blocs valorises.
+
+
## BR-PT-PRI-002 - Linked currency
@@ -28,11 +35,14 @@ dans ce meme repere puis converti pour les calculs internes.
### Notes developpeur
-- Champs obligatoires si active: `linked_price`, `linked_currency`,
- `linked_unit`.
-- En `basis + linked currency`, `linked_price` represente le basis brut hors
- premium.
-- `amount` ajoute le premium converti.
+
+- Champs obligatoires si active:
linked_price, linked_currency, linked_unit.
+
+- En
basis + linked currency, linked_price represente le basis brut hors premium.
+
+amount ajoute le premium converti.
+
+
## BR-PT-PRI-003 - Pricing manuel
@@ -45,9 +55,13 @@ le prix de marche. Les cumuls et prix moyens sont calcules automatiquement.
### Notes developpeur
-- Champs saisis: `quantity`, `settl_price`.
-- Champs derives: `fixed_qt`, `fixed_qt_price`, `unfixed_qt`,
- `unfixed_qt_price`, `eod_price`, `last`.
-- Groupe metier: `line + component` ou `sale_line + component`.
-- Le composant choisi doit appartenir a la ligne courante.
-
+
+- Champs saisis:
quantity, settl_price.
+
+- Champs derives:
fixed_qt, fixed_qt_price, unfixed_qt, unfixed_qt_price, eod_price, last.
+
+- Groupe metier:
line + component ou sale_line + component.
+
+- Le composant choisi doit appartenir a la ligne courante.
+
+
diff --git a/modules/purchase_trade/docs/business/reports-templates.md b/modules/purchase_trade/docs/business/reports-templates.md
index c59463a..f150c0a 100644
--- a/modules/purchase_trade/docs/business/reports-templates.md
+++ b/modules/purchase_trade/docs/business/reports-templates.md
@@ -1,12 +1,19 @@
+
+
# Reports et templates
Statut: `migration partielle`
Voir aussi:
-- `../template-rules.md`
-- `../template-properties.md`
-- `../../../../notes/template_business_rules.md`
+
+../template-rules.md
+
+../template-properties.md
+
+../../../../notes/template_business_rules.md
+
+
## BR-PT-RPT-001 - Templates trade via proprietes Python
@@ -19,21 +26,30 @@ d'expressions fragiles dans le fichier bureautique.
### Notes developpeur
-- Preferer des proprietes Python simples, souvent prefixees `report_*`.
-- Dans les placeholders XML, utiliser `"` et `'` plutot que des
- antislashs.
-- Pour les factures liees a vente/achat/shipment, privilegier le lot physique
- comme pont.
-- Verifier le cache `invoice_report_cache` avant de conclure qu'une action
- report pointe vers le mauvais `.fodt`.
-- Pour les templates shipment, preferer `records[0]...` ou des proprietes sur
- `stock.shipment.in` plutot qu'une variable locale supposee.
+
+- Preferer des proprietes Python simples, souvent prefixees
report_*.
+
+- Dans les placeholders XML, utiliser
" et ' plutot que des antislashs.
+
+- Pour les factures liees a vente/achat/shipment, privilegier le lot physique comme pont.
+
+- Verifier le cache
invoice_report_cache avant de conclure qu'une action report pointe vers le mauvais .fodt.
+
+- Pour les templates shipment, preferer
records[0]... ou des proprietes sur stock.shipment.in plutot qu'une variable locale supposee.
+
+
## Decisions deja documentees a migrer ensuite
-- `insurance.fodt`: compagnie courante, amount insured a 110%, surveyor.
-- `packing_list.fodt`: date du jour, unites depuis `purchase.line`.
-- `bill.fodt`: maturity date reelle et montant en lettres depuis le total.
-- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids, shipments et lots.
-- `sale_ict.fodt`: priorite lots et unite reelle.
-
+
+insurance.fodt: compagnie courante, amount insured a 110%, surveyor.
+
+packing_list.fodt: date du jour, unites depuis purchase.line.
+
+bill.fodt: maturity date reelle et montant en lettres depuis le total.
+
+invoice_ict.fodt / invoice_ict_final.fodt: poids, shipments et lots.
+
+sale_ict.fodt: priorite lots et unite reelle.
+
+
diff --git a/modules/purchase_trade/docs/business/risk-credit-forex.md b/modules/purchase_trade/docs/business/risk-credit-forex.md
index 8157c24..4842046 100644
--- a/modules/purchase_trade/docs/business/risk-credit-forex.md
+++ b/modules/purchase_trade/docs/business/risk-credit-forex.md
@@ -1,3 +1,5 @@
+
+
# Risque, credit, forex
Statut: `placeholder`
@@ -9,7 +11,11 @@ Aucune regle canonique `purchase_trade` n'a ete promue ici dans cette premiere
passe. Les notes comptables et forex existantes doivent etre relues avant toute
migration:
-- `notes/accounting/README.md`
-- `notes/accounting/business_rules.md`
-- `notes/accounting/reporting.md`
-
+
+notes/accounting/README.md
+
+notes/accounting/business_rules.md
+
+notes/accounting/reporting.md
+
+
diff --git a/modules/purchase_trade/docs/business/sessions.md b/modules/purchase_trade/docs/business/sessions.md
index 5cb8e8a..cde9119 100644
--- a/modules/purchase_trade/docs/business/sessions.md
+++ b/modules/purchase_trade/docs/business/sessions.md
@@ -1,3 +1,5 @@
+
+
# Journal de migration et sessions
Statut: `non canonique`
@@ -7,25 +9,28 @@ canonique seulement quand elle est reprise dans une page thematique.
## Sources historiques
-- `modules/purchase_trade/docs/business-rules.md`
-- `modules/purchase_trade/docs/business-rules-architecture-proposal.md`
-- `notes/business_rules.md`
-- `notes/template_business_rules.md`
+
+modules/purchase_trade/docs/business-rules.md
+
+modules/purchase_trade/docs/business-rules-architecture-proposal.md
+
+notes/business_rules.md
+
+notes/template_business_rules.md
+
+
## Notes deja partiellement promues
-- Session `2026-04-30`: PnL fees ouverts et `% rate`, promue dans
- [fees.md](fees.md) et [valuation-pnl-mtm.md](valuation-pnl-mtm.md).
-- Session `2026-05-01`: solde ouvert apres lots physiques et lots effectifs
- des fees, promue dans [lots-and-quantities.md](lots-and-quantities.md) et
- [fees.md](fees.md).
-- Session `2026-05-06`: Remove physical lot, promue dans
- [lots-and-quantities.md](lots-and-quantities.md).
-- Session `2026-05-09`: Lots Management, promue partiellement dans
- [lots-management.md](lots-management.md).
-- Session `2026-05-13`: cadrage `quantity_theorical` / `quantity`, amount de
- ligne, Weight basis, invariants de quantite, checks Python bloquants et
- diagnostic SQL. Promue dans
- [lots-and-quantities.md](lots-and-quantities.md),
- [lots-and-quantities.en.md](lots-and-quantities.en.md),
- [invariants.md](invariants.md) et [sql/README.md](sql/README.md).
+
+- Session
2026-04-30: PnL fees ouverts et % rate, promue dans fees.md et valuation-pnl-mtm.md.
+
+- Session
2026-05-01: solde ouvert apres lots physiques et lots effectifs des fees, promue dans lots-and-quantities.md et fees.md.
+
+- Session
2026-05-06: Remove physical lot, promue dans lots-and-quantities.md.
+
+- Session
2026-05-09: Lots Management, promue partiellement dans lots-management.md.
+
+- Session
2026-05-13: cadrage quantity_theorical / quantity, amount de ligne, Weight basis, invariants de quantite, checks Python bloquants et diagnostic SQL. Promue dans lots-and-quantities.md, lots-and-quantities.en.md, invariants.md et sql/README.md.
+
+
diff --git a/modules/purchase_trade/docs/business/shipments-execution.md b/modules/purchase_trade/docs/business/shipments-execution.md
index b9e9784..f8bfe4d 100644
--- a/modules/purchase_trade/docs/business/shipments-execution.md
+++ b/modules/purchase_trade/docs/business/shipments-execution.md
@@ -1,3 +1,5 @@
+
+
# Shipments et execution
Statut: `migration partielle`
@@ -13,10 +15,16 @@ retard par rapport a son objectif.
### Notes developpeur
-- Configuration: onglet `Execution` de `party.party`.
-- La zone du shipment vient de `shipment.to_location.country`.
-- Une region parente couvre ses sous-regions.
-- `% achieved` compte seulement les shipments deja affectes a un controller.
+
+- Configuration: onglet
Execution de party.party.
+
+- La zone du shipment vient de
shipment.to_location.country.
+
+- Une region parente couvre ses sous-regions.
+
+% achieved compte seulement les shipments deja affectes a un controller.
+
+
## BR-PT-SHP-002 - Couts SLA controller par pays et/ou lieu
@@ -29,8 +37,12 @@ lieu. Le couple est le cas le plus specifique.
### Notes developpeur
-- Matching: `country + location`, puis `location`, puis `country`.
-- Le pays vient de `shipment.to_location.country`.
+
+- Matching:
country + location, puis location, puis country.
+
+- Le pays vient de
shipment.to_location.country.
+
+
## BR-PT-SHP-003 - Weight reports distants par lot
@@ -43,7 +55,11 @@ sur le shipment.
### Notes developpeur
-- Exporter seulement les lots physiques des `incoming_moves`.
-- Exiger au minimum `controller` et `returned_id` sur le shipment.
-- Conserver les cles distantes et la date d'envoi sur le `weight.report`.
-
+
+- Exporter seulement les lots physiques des
incoming_moves.
+
+- Exiger au minimum
controller et returned_id sur le shipment.
+
+- Conserver les cles distantes et la date d'envoi sur le
weight.report.
+
+
diff --git a/modules/purchase_trade/docs/business/sql/README.md b/modules/purchase_trade/docs/business/sql/README.md
index 426119d..66e02b9 100644
--- a/modules/purchase_trade/docs/business/sql/README.md
+++ b/modules/purchase_trade/docs/business/sql/README.md
@@ -1,3 +1,5 @@
+
+
# SQL diagnostics for purchase_trade business rules
These scripts are read-only diagnostics for a PostgreSQL test database.
@@ -23,24 +25,34 @@ reported as anomalous.
Run it on a restored test database:
-```sql
-\i modules/purchase_trade/docs/business/sql/quantity_consistency_checks.sql
-```
+\i modules/purchase_trade/docs/business/sql/quantity_consistency_checks.sql
The script returns rows only when it finds a potential issue.
Main columns:
-- `check_name`: invariant or diagnostic that failed.
-- `contract_model`: `purchase.purchase` or `sale.sale`.
-- `contract_id`: database id of the contract.
-- `contract_number`: purchase or sale contract number.
-- `line_id`: `purchase.line` or `sale.line` id depending on the check.
-- `virtual_lot_id`: virtual lot involved in the inconsistency.
-- `observed_value`: value found in the database.
-- `expected_value`: value required by the business rule.
-- `diff`: observed minus expected.
-- `detail`: human-readable explanation.
+
+check_name: invariant or diagnostic that failed.
+
+contract_model: purchase.purchase or sale.sale.
+
+contract_id: database id of the contract.
+
+contract_number: purchase or sale contract number.
+
+line_id: purchase.line or sale.line id depending on the check.
+
+virtual_lot_id: virtual lot involved in the inconsistency.
+
+observed_value: value found in the database.
+
+expected_value: value required by the business rule.
+
+diff: observed minus expected.
+
+detail: human-readable explanation.
+
+
Cross-category UoM rows are reported as manual-review diagnostics because the
Python code may pass explicit conversion factors that cannot be inferred safely
diff --git a/modules/purchase_trade/docs/business/valuation-pnl-mtm.md b/modules/purchase_trade/docs/business/valuation-pnl-mtm.md
index 073441a..4e9e446 100644
--- a/modules/purchase_trade/docs/business/valuation-pnl-mtm.md
+++ b/modules/purchase_trade/docs/business/valuation-pnl-mtm.md
@@ -1,3 +1,5 @@
+
+
# Valuation, PnL, MTM
Statut: `migration partielle`
@@ -13,12 +15,14 @@ n'est pas encore matchee a un achat.
### Notes developpeur
-- Une `sale.line` non matchee doit generer au minimum `sale priced`, `sale fee`
- et `derivative` si applicable.
-- Une sale basis sans detail de prix doit quand meme produire une ligne a zero
- ou au prix economique fallback selon la regle applicable.
-- Ne pas attacher arbitrairement une sale unique si plusieurs sales sont
- matchees au meme ouvert.
+
+- Une
sale.line non matchee doit generer au minimum sale priced, sale fee et derivative si applicable.
+
+- Une sale basis sans detail de prix doit quand meme produire une ligne a zero ou au prix economique fallback selon la regle applicable.
+
+- Ne pas attacher arbitrairement une sale unique si plusieurs sales sont matchees au meme ouvert.
+
+
## BR-PT-VAL-002 - References de valuation
@@ -31,9 +35,12 @@ vente, ouverte ou physique.
### Notes developpeur
-- References autorisees: `Purchase/Open`, `Purchase/Physic`, `Sale/Open`,
- `Sale/Physic`.
-- Un lot virtuel ne doit pas sortir avec une reference physique.
+
+- References autorisees:
Purchase/Open, Purchase/Physic, Sale/Open, Sale/Physic.
+
+- Un lot virtuel ne doit pas sortir avec une reference physique.
+
+
## BR-PT-VAL-003 - MTM hors fees
@@ -45,7 +52,11 @@ Le mark-to-market s'applique aux prix et aux derives, pas aux frais.
### Notes developpeur
-- MTM autorise pour `pur. priced`, `sale priced`, `derivative`.
-- Fees hors MTM: `pur. fee`, `sale fee`, `shipment fee`, `line fee`.
-- Pour les fees: `mtm_price`, `mtm`, `strategy` doivent rester vides.
-
+
+- MTM autorise pour
pur. priced, sale priced, derivative.
+
+- Fees hors MTM:
pur. fee, sale fee, shipment fee, line fee.
+
+- Pour les fees:
mtm_price, mtm, strategy doivent rester vides.
+
+
diff --git a/modules/purchase_trade/docs/tools/render_business_docs.py b/modules/purchase_trade/docs/tools/render_business_docs.py
index 0a2574a..bb2dac4 100644
--- a/modules/purchase_trade/docs/tools/render_business_docs.py
+++ b/modules/purchase_trade/docs/tools/render_business_docs.py
@@ -10,18 +10,13 @@ from __future__ import annotations
import argparse
import html
import re
+import sys
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*")
@@ -190,6 +185,56 @@ def render_callout(lines: list[str]) -> str:
)
+def is_bullet_line(line: str) -> bool:
+ return bool(re.match(r"^\s*-\s+", line))
+
+
+def bullet_level(line: str) -> int:
+ return len(re.match(r"^\s*", line).group(0)) // 2
+
+
+def render_list(lines: list[str]) -> str:
+ root: list[dict[str, object]] = []
+ stack: list[tuple[int, list[dict[str, object]]]] = [(-1, root)]
+ last_item: dict[str, object] | None = None
+
+ for line in lines:
+ if is_bullet_line(line):
+ level = bullet_level(line)
+ content = re.sub(r"^\s*-\s+", "", line).strip()
+ while stack[-1][0] >= level:
+ stack.pop()
+ item: dict[str, object] = {
+ "content": content,
+ "children": [],
+ }
+ stack[-1][1].append(item)
+ stack.append((level, item["children"]))
+ last_item = item
+ elif line.strip() and last_item is not None:
+ last_item["content"] = str(last_item["content"]) + " " + line.strip()
+
+ def render_items(items: list[dict[str, object]], level: int = 0) -> str:
+ margin = "1.1rem" if level == 0 else "1.35rem"
+ bullet = "disc" if level == 0 else "circle"
+ parts = [
+ f''
+ ]
+ for item in items:
+ children = item["children"]
+ parts.append(
+ '- '
+ f"{render_inline(str(item['content']))}"
+ )
+ if children:
+ parts.append(render_items(children, level + 1))
+ parts.append("
")
+ parts.append("
")
+ return "\n".join(parts)
+
+ return render_items(root)
+
+
def render_source(text: str) -> str:
lines = text.splitlines()
out = [
@@ -244,20 +289,47 @@ def render_source(text: str) -> str:
out.extend(table)
continue
+ if is_bullet_line(line):
+ list_lines = []
+ while i < len(lines) and (
+ is_bullet_line(lines[i])
+ or (
+ lines[i].startswith(" ")
+ and lines[i].strip()
+ and not lines[i].lstrip().startswith("|")
+ )
+ ):
+ list_lines.append(lines[i])
+ i += 1
+ out.append(render_list(list_lines))
+ continue
+
out.append(line)
i += 1
return "\n".join(out).strip() + "\n"
-def render_all(normalize: bool = False) -> None:
+def source_files() -> list[Path]:
+ return sorted(path for path in SOURCE.rglob("*.md") if path.is_file())
+
+
+def render_all(normalize: bool = False, check: bool = False) -> bool:
SOURCE.mkdir(parents=True, exist_ok=True)
- for name in TARGETS:
- source = SOURCE / name
- target = BUSINESS / name
+ ok = True
+ for source in source_files():
+ target = BUSINESS / source.relative_to(SOURCE)
+ target.parent.mkdir(parents=True, exist_ok=True)
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")
+ rendered = render_source(source.read_text(encoding="utf-8"))
+ if check:
+ if not target.exists() or target.read_text(encoding="utf-8") != rendered:
+ print(f"Outdated generated doc: {target.relative_to(ROOT.parent)}")
+ ok = False
+ else:
+ target.write_text(rendered, encoding="utf-8")
+ return ok
def main() -> None:
@@ -267,8 +339,16 @@ def main() -> None:
action="store_true",
help="Clean existing source files from wiki-only syntax before rendering.",
)
+ parser.add_argument(
+ "--check",
+ action="store_true",
+ help="Fail if generated wiki files are not up to date.",
+ )
args = parser.parse_args()
- render_all(normalize=args.normalize_source)
+ if args.normalize_source and args.check:
+ parser.error("--normalize-source cannot be used with --check")
+ if not render_all(normalize=args.normalize_source, check=args.check):
+ sys.exit(1)
if __name__ == "__main__":
diff --git a/modules/purchase_trade/docs_source/business/INDEX.md b/modules/purchase_trade/docs_source/business/INDEX.md
new file mode 100644
index 0000000..af2d99d
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/INDEX.md
@@ -0,0 +1,48 @@
+# Index thematique des regles business
+
+Statut: `migration partielle`
+
+## Comment chercher une regle
+
+- Contrats, dates, lieux, banques: [contracts.md](contracts.md)
+- Lots virtuels, lots physiques, `lot.qt`, weighing: [FR](lots-and-quantities.md) / [EN](lots-and-quantities.en.md)
+- Matching, Create Contracts, back-to-back: [matching.md](matching.md)
+- Shipments, controllers, SLA, weight reports: [shipments-execution.md](shipments-execution.md)
+- Pricing manuel, basis, premium, linked currency: [pricing.md](pricing.md)
+- Fees, freight, lots effectifs, `% rate`: [fees.md](fees.md)
+- Valuation, PnL, MTM, derivatives: [valuation-pnl-mtm.md](valuation-pnl-mtm.md)
+- Factures provisoires/finales, padding: [invoicing.md](invoicing.md)
+- Impacts `account.move`, validate/post: [accounting-bridge.md](accounting-bridge.md)
+- Comptes bancaires, payment terms, payment orders: [payments-banking.md](payments-banking.md)
+- Relatorio, `.fodt`, proprietes `report_*`: [reports-templates.md](reports-templates.md)
+- Risque, credit, forex: [risk-credit-forex.md](risk-credit-forex.md)
+- Rapport Lots Management: [lots-management.md](lots-management.md)
+- Diagnostics SQL des invariants: [sql/README.md](sql/README.md)
+
+## Regles migrees dans cette premiere passe
+
+- `BR-PT-CON-001`: texte par defaut de pricing rule.
+- `BR-PT-CON-002`: delivery period coherent.
+- `BR-PT-CON-003`: lieux stock propages dans Create Contracts.
+- `BR-PT-LOT-001`: cycle de vie des lots et des quantites.
+- `BR-PT-LOT-002`: quantity contractuelle, execute physique et ligne finie.
+- `BR-PT-LOT-003`: garde-fous Python et diagnostics SQL des invariants de
+ quantite.
+- `BR-PT-MAT-001`: Create Contracts multi-lots.
+- `BR-PT-SHP-001`: affectation controller.
+- `BR-PT-SHP-002`: couts SLA controller.
+- `BR-PT-SHP-003`: weight reports distants.
+- `BR-PT-PRI-001`: premium dans priced et basis.
+- `BR-PT-PRI-002`: linked currency.
+- `BR-PT-PRI-003`: pricing manuel.
+- `BR-PT-FEE-001`: maritime freight depuis fee shipment.
+- `BR-PT-FEE-002`: lots effectifs des fees.
+- `BR-PT-FEE-003`: `% rate` via delta de financement.
+- `BR-PT-VAL-001`: valuation achat/vente et sale-first.
+- `BR-PT-VAL-002`: references de valuation.
+- `BR-PT-VAL-003`: MTM hors fees.
+- `BR-PT-INV-001`: padding facture provisoire vente.
+- `BR-PT-ACC-001`: Validate facture client attribue le numero.
+- `BR-PT-PAY-001`: comptes bancaires tiers vs compagnie.
+- `BR-PT-RPT-001`: templates trade via proprietes Python.
+- `BR-PT-LOTMGT-001`: filtres Lots Management.
diff --git a/modules/purchase_trade/docs_source/business/README.md b/modules/purchase_trade/docs_source/business/README.md
new file mode 100644
index 0000000..4b736d2
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/README.md
@@ -0,0 +1,117 @@
+# Guide de lecture des règles business
+
+Statut: `migration partielle`
+Dernière mise à jour: `2026-05-13`
+
+Ce dossier devient la source de lecture thématique publiée dans le wiki pour les
+règles business du module `purchase_trade`.
+
+Toutes les pages business publiées dans `modules/purchase_trade/docs/business/`
+sont générées depuis une source de vérité plus sobre, rangée hors du dossier
+wiki dans `modules/purchase_trade/docs_source/business/`.
+
+Les pages publiées portent un commentaire `Generated from ...` en tête de
+fichier et ne doivent pas être modifiées directement. Aucun contenu business ne
+doit être ajouté ou modifié sans passer par cette source puis par le script de
+génération.
+
+Chaque page doit rester lisible par deux publics:
+
+- les consultants, qui ont besoin d'une règle fonctionnelle stable sans détail
+ de code inutile;
+- les développeurs, qui ont besoin des champs, modèles, fichiers et tests
+ concernés pour appliquer la règle sans l'interpréter.
+
+## Convention de langues
+
+Chaque page thématique durable doit exister en deux versions maintenues
+ensemble:
+
+- une page française, rédigée en français correct avec accents, typographie et
+ formulations naturelles pour le wiki consultant;
+- une page anglaise miroir, portant le même contenu fonctionnel et technique.
+
+Convention de nommage:
+
+- page française principale: `theme.md`;
+- page anglaise miroir: `theme.en.md`.
+
+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 toute page business:
+
+- éditer la source de vérité dans `modules/purchase_trade/docs_source/business/`;
+- 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-...:`.
+
+Les fichiers générés dans `modules/purchase_trade/docs/business/` sont des
+artefacts de publication: ils peuvent être relus, mais toute correction doit
+être reportée dans `docs_source/business/` avant régénération.
+
+Avant de livrer une modification documentaire, vérifier que les pages publiées
+sont à jour avec:
+
+```bash
+python modules/purchase_trade/docs/tools/render_business_docs.py --check
+```
+
+## Convention de rédaction
+
+Pour chaque règle durable, utiliser autant que possible ce format:
+
+```md
+### BR-PT-THEME-001 - Titre court
+
+Statut: active
+Source: business-rules.md / note de session / décision projet
+
+#### Règle consultant
+
+Texte fonctionnel, sans nom de champ si ce n'est pas nécessaire.
+
+#### Notes développeur
+
+- Modèles/champs:
+- Fichiers:
+- Tests:
+- Points de vigilance:
+```
+
+## Convention de validation
+
+Quand une règle business devient structurante pour l'intégrité des données, elle
+doit être accompagnée autant que possible de deux garde-fous:
+
+- un check applicatif bloquant dans le code Python, appelé à la fin des flux qui
+ modifient les données concernées;
+- un diagnostic SQL en lecture seule pour auditer les bases existantes ou les
+ bases de test.
+
+Les diagnostics SQL du module sont rangés dans `business/sql/`. Ils ne
+remplacent pas les règles applicatives: ils servent à retrouver et qualifier les
+écarts déjà présents dans une base.
+
+## Sources pendant la migration
+
+Les anciennes pages ne sont pas supprimées à cette étape. Elles restent des
+sources de vérification jusqu'à ce que chaque décision soit promue dans une
+page thématique:
+
+- `modules/purchase_trade/docs/business-rules.md`
+- `modules/purchase_trade/docs/fees.md`
+- `modules/purchase_trade/docs/padding-invoice-accounting.md`
+- `modules/purchase_trade/docs/template-rules.md`
+- `modules/purchase_trade/docs/template-properties.md`
+- `notes/business_rules.md`
+- `notes/template_business_rules.md`
diff --git a/modules/purchase_trade/docs_source/business/accounting-bridge.md b/modules/purchase_trade/docs_source/business/accounting-bridge.md
new file mode 100644
index 0000000..9ed8d82
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/accounting-bridge.md
@@ -0,0 +1,25 @@
+# Pont comptable
+
+Statut: `migration partielle`
+
+## BR-PT-ACC-001 - Validate facture client attribue aussi le numero
+
+Source: `BR-PT-017`
+
+### Regle consultant
+
+Une facture client doit recevoir son mouvement comptable et son numero des la
+validation, comme une facture fournisseur.
+
+### Notes developpeur
+
+- Cible: `account.invoice` avec `type = out`.
+- Workflow `Validate`: creer `account.move` et attribuer `number`.
+- Workflow `Post`: ne doit pas reintroduire une session fraiche specifique au
+ flux client.
+
+## Notes de migration
+
+Les notes comptables detaillees restent dans `notes/accounting/` tant qu'elles
+n'ont pas ete promues ici.
+
diff --git a/modules/purchase_trade/docs_source/business/contracts.md b/modules/purchase_trade/docs_source/business/contracts.md
new file mode 100644
index 0000000..ad12d06
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/contracts.md
@@ -0,0 +1,50 @@
+# Contrats achat / vente
+
+Statut: `migration partielle`
+
+## BR-PT-CON-001 - Texte par defaut de pricing rule
+
+Source: `BR-PT-013`
+
+### Regle consultant
+
+Le texte de regle de pricing recurrent doit etre configure une seule fois et
+repris automatiquement sur les nouvelles lignes achat et vente.
+
+### Notes developpeur
+
+- Configuration: `purchase_trade.configuration.pricing_rule`.
+- Cibles: `purchase.line.pricing_rule`, `sale.line.pricing_rule`.
+- Les lignes existantes ne sont pas modifiees retroactivement.
+
+## BR-PT-CON-002 - Delivery period coherent
+
+Source: `BR-PT-014` historique, doublon de numerotation a corriger
+
+### Regle consultant
+
+Une periode de livraison ne peut pas commencer apres sa date de fin. Une seule
+borne renseignee reste acceptee.
+
+### Notes developpeur
+
+- Champs: `purchase.line.from_del`, `purchase.line.to_del`,
+ `sale.line.from_del`, `sale.line.to_del`.
+- Validation attendue: bloquer si `from_del > to_del`.
+
+## BR-PT-CON-003 - Propagation des lieux stock dans Create Contracts
+
+Source: `BR-PT-024`
+
+### Regle consultant
+
+Quand un contrat miroir est cree depuis une quantite ouverte, les lieux
+logistiques doivent etre proposes selon le flux source pour eviter la ressaisie.
+
+### Notes developpeur
+
+- Champs: `from_location`, `to_location`.
+- Flux fournisseur vers client: recopier le couple source.
+- Achat vers stock puis vente: `sale.from_location = purchase.to_location`.
+- Vente depuis stock puis achat: `purchase.to_location = sale.from_location`.
+
diff --git a/modules/purchase_trade/docs_source/business/fees.md b/modules/purchase_trade/docs_source/business/fees.md
new file mode 100644
index 0000000..47e9cbf
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/fees.md
@@ -0,0 +1,55 @@
+# Fees
+
+Statut: `migration partielle`
+
+Voir aussi la page technique historique: `../fees.md`.
+
+## BR-PT-FEE-001 - Freight value depuis fee shipment
+
+Source: `BR-PT-003`
+
+### Regle consultant
+
+La valeur de fret affichee sur les documents facture vient du fee maritime du
+shipment, pas d'un champ direct de la facture.
+
+### Notes developpeur
+
+- Retrouver le lot physique depuis la facture.
+- Retrouver son `shipment_in`.
+- Chercher le `fee.fee` avec `product.name = 'Maritime freight'`.
+- Utiliser `fee.get_amount()`.
+
+## BR-PT-FEE-002 - Les fees lies aux lots privilegient les physiques
+
+Source: `BR-PT-021`
+
+### Regle consultant
+
+Un fee suit le lot virtuel tant qu'aucun lot physique n'est lie. Des qu'un lot
+physique est lie, les lots physiques deviennent la base de calcul du fee.
+
+### Notes developpeur
+
+- Ne pas supprimer le lien virtuel: il reste le fallback.
+- Quantite `ppack`: somme de `lot.lot_qt` des physiques.
+- Modes quantitatifs: quantites courantes converties des physiques.
+- La meme selection s'applique au PnL fee.
+- Points de synchronisation: creation fee, lien `fee.lots`, changement de
+ `quantity_theorical`, weighing, suppression de physique.
+
+## BR-PT-FEE-003 - Fees `% rate` via delta de financement
+
+Source: `BR-PT-016` historique et notes `2026-04-30`
+
+### Regle consultant
+
+Les frais financiers en pourcentage se calculent avec le delta de financement
+de la ligne d'estimation `BL date`, pas avec la date du jour.
+
+### Notes developpeur
+
+- Formule: `amount = unit_price * quantity * (price / 100) * fin_int_delta / 360`.
+- Source du delta: ligne `Estimated date` avec `trigger = bldate`.
+- Si aucune ligne `bldate` n'existe, ne pas calculer de montant `% rate`.
+
diff --git a/modules/purchase_trade/docs_source/business/glossary.md b/modules/purchase_trade/docs_source/business/glossary.md
new file mode 100644
index 0000000..5987986
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/glossary.md
@@ -0,0 +1,22 @@
+# Glossaire purchase_trade
+
+Statut: `migration partielle`
+
+- `Purchase Line`: ligne d'achat.
+- `Sale Line`: ligne de vente.
+- `quantity_theorical`: quantite contractuelle theorique d'une ligne.
+- `Virtual Lot`: lot de type `virtual`, representant un reliquat ouvert.
+- `Physical Lot`: lot de type `physic`, representant une quantite executee.
+- `lot.qt`: ligne de quantite ouverte, matchee ou rattachee a un shipment.
+- `lot.qt ouvert`: `lot.qt` libre, sans lot oppose et sans shipment.
+- `Shipment In`: shipment entrant utilise aussi pour les flux dropship dans ce module.
+- `Dropship`: flux fournisseur vers client, sans passage par stock interne.
+- `Inbound`: flux entrant classique qui ne correspond pas au dropship.
+- `Basis`: mode de prix construit a partir d'un prix de marche et d'un premium.
+- `Premium`: prime ou discount commercial ajoute au prix economique.
+- `Linked currency`: saisie d'un prix dans une devise/unite liee, par exemple `USC/LB`.
+- `Valuation`: lignes de PnL generees pour prix, fees, derivatives et MTM.
+- `MTM`: mark-to-market applique aux lignes valorisables au marche.
+- `Fee`: frais commercial ou logistique rattache a une ligne, un lot ou un shipment.
+- `Report property`: propriete Python exposee pour simplifier un template Relatorio.
+
diff --git a/modules/purchase_trade/docs_source/business/invariants.md b/modules/purchase_trade/docs_source/business/invariants.md
new file mode 100644
index 0000000..a38144a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/invariants.md
@@ -0,0 +1,75 @@
+# Invariants structurants
+
+Statut: `migration partielle`
+
+Ces invariants doivent etre relus avant de modifier des flux `purchase_trade`
+touchant lots, quantites, fees, PnL, factures ou templates.
+
+## INV-PT-001 - Le lot physique est le pont metier stable
+
+### Regle consultant
+
+Quand une information doit relier achat, vente, shipment et facture, le chemin
+fonctionnel de reference passe par le lot physique. Il porte le lien entre les
+contrats et l'execution logistique.
+
+### Notes developpeur
+
+- Source historique: `BR-PT-002`.
+- Voir aussi: [lots-and-quantities.md](lots-and-quantities.md),
+ [matching.md](matching.md), [reports-templates.md](reports-templates.md).
+- Champs frequents: `lot.line`, `lot.sale_line`, `lot_shipment_in`,
+ `lot_shipment_internal`, `lot_shipment_out`.
+
+## INV-PT-002 - Le reliquat ouvert ne doit pas doubler les lots physiques
+
+### Regle consultant
+
+Une quantite deja executee physiquement ne doit pas rester disponible comme
+quantite ouverte. Le reliquat ouvert represente seulement ce qui reste a
+executer.
+
+### Notes developpeur
+
+- Source historique: `BR-PT-020`.
+- Le calcul doit tenir compte de la quantite contractuelle, des lots physiques
+ existants et des `lot.qt` deja matches ou shippes.
+- Regle de conservation:
+ `sum(lots physiques) + lot virtuel = quantity_theorical`.
+- Regle du forecast ouvert:
+ `sum(lot.qt non zero) = max(lot virtuel, 0)`.
+- Les lignes `lot.qt` a zero sont ignorees par les checks: elles peuvent servir
+ de memoire d'une prevision consommee.
+- Le check applicatif est centralise dans
+ `lot.lot.assert_lines_quantity_consistency()`.
+- Le diagnostic SQL correspondant est
+ [sql/quantity_consistency_checks.sql](sql/quantity_consistency_checks.sql).
+
+## INV-PT-003 - Les fees utilisent leurs lots effectifs
+
+### Regle consultant
+
+Un fee ouvert suit le lot virtuel tant qu'il n'y a pas de lot physique. Des
+qu'un ou plusieurs lots physiques sont lies au fee, ils deviennent la base
+effective de calcul.
+
+### Notes developpeur
+
+- Source historique: `BR-PT-021`.
+- Ne pas supprimer le lien virtuel: il reste le fallback si les physiques sont
+ retires.
+- Voir [fees.md](fees.md).
+
+## INV-PT-004 - Les templates doivent rester simples
+
+### Regle consultant
+
+Les documents doivent afficher des informations metier stables, meme si le
+chemin technique pour les retrouver est complexe.
+
+### Notes developpeur
+
+- Preferer des proprietes Python `report_*` aux expressions Genshi complexes.
+- Ne pas supposer qu'une variable locale comme `shipment` existe partout dans
+ un `.fodt`.
+- Voir [reports-templates.md](reports-templates.md).
diff --git a/modules/purchase_trade/docs_source/business/invoicing.md b/modules/purchase_trade/docs_source/business/invoicing.md
new file mode 100644
index 0000000..ac3c51a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/invoicing.md
@@ -0,0 +1,24 @@
+# Facturation trade
+
+Statut: `migration partielle`
+
+## BR-PT-INV-001 - Padding facture provisoire vente
+
+Source: `BR-PT-019` et `../padding-invoice-accounting.md`
+
+### Regle consultant
+
+Le padding d'une facture provisoire vente augmente la quantite facturee pour
+constituer une provision, sans modifier la quantite physique du lot.
+
+### Notes developpeur
+
+- Le padding global du wizard `lot.invoice` est reparti entre les lots
+ selectionnes.
+- La ligne facture expose `Inc. padding`.
+- Le lot conserve sa part dans `sale_invoice_padding`.
+- La facture finale retire le padding de la quantite provisoire avant de
+ calculer le delta.
+- Les ecritures d'extourne doivent relire la provisoire depuis
+ `lot.sale_invoice_line_prov`.
+
diff --git a/modules/purchase_trade/docs_source/business/lots-management.md b/modules/purchase_trade/docs_source/business/lots-management.md
new file mode 100644
index 0000000..fd89c29
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/lots-management.md
@@ -0,0 +1,27 @@
+# Lots Management
+
+Statut: `migration partielle`
+
+## BR-PT-LOTMGT-001 - Separations matching, side et shipping status
+
+Source: `BR-PT-023` et notes `2026-05-09`
+
+### Regle consultant
+
+Le rapport Lots Management doit permettre de lire separement le matching
+commercial, le sens achat/vente et l'avancement logistique.
+
+### Notes developpeur
+
+- Filtres: `Matching status`, `Side`, `Shipping status`, `Dimension`,
+ `Strategy`.
+- Dates `As of` / `To`: `purchase.purchase_date` et `sale.sale_date`.
+- `Unshipped`: aucun `shipment_in`.
+- `Scheduled`: shipment `draft`.
+- `Shipped`: shipment `started`.
+- `Received`: shipment `received` ou `done`.
+- `Shipment Type = Dropship` si `from_location.type = supplier` et
+ `to_location.type = customer`, sinon `Inbound`.
+- `Mark as finished` masque seulement les reliquats ouverts / virtuels, pas
+ les lots physiques.
+
diff --git a/modules/purchase_trade/docs_source/business/matching.md b/modules/purchase_trade/docs_source/business/matching.md
new file mode 100644
index 0000000..89c617c
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/matching.md
@@ -0,0 +1,27 @@
+# Matching achat / vente
+
+Statut: `migration partielle`
+
+## BR-PT-MAT-001 - Create Contracts multi-lots conserve le matching source
+
+Source: `BR-PT-012` et doublon historique `BR-PT-013`
+
+### Regle consultant
+
+Le wizard `Create contracts` peut creer un seul contrat miroir depuis plusieurs
+quantites ouvertes selectionnees. Chaque ligne creee doit rester reliee a son
+lot source.
+
+### Notes developpeur
+
+- La quantite du wizard doit correspondre a la somme des quantites ouvertes
+ selectionnees.
+- Creer une ligne par `lot.qt` source.
+- Conserver `created_by_code = True` pour eviter les creations automatiques
+ parasites lors des validations.
+
+## Notes de migration
+
+Les regles sur `Apply matching` presentes dans les notes de session du
+`2026-05-09` doivent encore etre promues ici.
+
diff --git a/modules/purchase_trade/docs_source/business/payments-banking.md b/modules/purchase_trade/docs_source/business/payments-banking.md
new file mode 100644
index 0000000..977ff4a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/payments-banking.md
@@ -0,0 +1,21 @@
+# Paiements et banques
+
+Statut: `migration partielle`
+
+## BR-PT-PAY-001 - Distinguer banque tiers et banque compagnie
+
+Source: `BR-PT-018`
+
+### Regle consultant
+
+Un contrat distingue le compte bancaire du client ou fournisseur du compte
+bancaire utilise par la compagnie courante pour encaisser ou payer.
+
+### Notes developpeur
+
+- Contrats: `sale.sale`, `purchase.purchase`.
+- `bank_account`: compte de la party du contrat.
+- `our_bank_account`: compte de la compagnie courante, selectionnable parmi
+ les comptes disponibles.
+- La devise du contrat est prioritaire pour proposer un compte par defaut.
+
diff --git a/modules/purchase_trade/docs_source/business/pricing.md b/modules/purchase_trade/docs_source/business/pricing.md
new file mode 100644
index 0000000..3ebee56
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/pricing.md
@@ -0,0 +1,53 @@
+# Pricing, basis, premium
+
+Statut: `migration partielle`
+
+## BR-PT-PRI-001 - Le premium fait partie du prix economique
+
+Source: `BR-PT-008`
+
+### Regle consultant
+
+Le premium ou discount saisi sur une ligne fait partie du prix economique, que
+la ligne soit en prix fixe ou en basis.
+
+### Notes developpeur
+
+- `unit_price` reste le prix de base hors premium.
+- Montant economique: `unit_price + premium converti si necessaire`.
+- En basis, le premium s'applique aussi aux blocs valorises.
+
+## BR-PT-PRI-002 - Linked currency
+
+Source: `BR-PT-009` et `BR-PT-010`
+
+### Regle consultant
+
+Quand le prix est saisi dans une devise ou unite liee, le premium est exprime
+dans ce meme repere puis converti pour les calculs internes.
+
+### Notes developpeur
+
+- Champs obligatoires si active: `linked_price`, `linked_currency`,
+ `linked_unit`.
+- En `basis + linked currency`, `linked_price` represente le basis brut hors
+ premium.
+- `amount` ajoute le premium converti.
+
+## BR-PT-PRI-003 - Pricing manuel
+
+Source: `BR-PT-016` et doublon historique `BR-PT-015`
+
+### Regle consultant
+
+En pricing manuel, l'utilisateur saisit uniquement la quantite fixee du jour et
+le prix de marche. Les cumuls et prix moyens sont calcules automatiquement.
+
+### Notes developpeur
+
+- Champs saisis: `quantity`, `settl_price`.
+- Champs derives: `fixed_qt`, `fixed_qt_price`, `unfixed_qt`,
+ `unfixed_qt_price`, `eod_price`, `last`.
+- Groupe metier: `line + component` ou `sale_line + component`.
+- Le composant choisi doit appartenir a la ligne courante.
+
diff --git a/modules/purchase_trade/docs_source/business/reports-templates.md b/modules/purchase_trade/docs_source/business/reports-templates.md
new file mode 100644
index 0000000..c59463a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/reports-templates.md
@@ -0,0 +1,39 @@
+# Reports et templates
+
+Statut: `migration partielle`
+
+Voir aussi:
+
+- `../template-rules.md`
+- `../template-properties.md`
+- `../../../../notes/template_business_rules.md`
+
+## BR-PT-RPT-001 - Templates trade via proprietes Python
+
+Source: `template-rules.md`, `template-properties.md`, notes AGENTS
+
+### Regle consultant
+
+Un document trade doit afficher des informations metier fiables, sans dependre
+d'expressions fragiles dans le fichier bureautique.
+
+### Notes developpeur
+
+- Preferer des proprietes Python simples, souvent prefixees `report_*`.
+- Dans les placeholders XML, utiliser `"` et `'` plutot que des
+ antislashs.
+- Pour les factures liees a vente/achat/shipment, privilegier le lot physique
+ comme pont.
+- Verifier le cache `invoice_report_cache` avant de conclure qu'une action
+ report pointe vers le mauvais `.fodt`.
+- Pour les templates shipment, preferer `records[0]...` ou des proprietes sur
+ `stock.shipment.in` plutot qu'une variable locale supposee.
+
+## Decisions deja documentees a migrer ensuite
+
+- `insurance.fodt`: compagnie courante, amount insured a 110%, surveyor.
+- `packing_list.fodt`: date du jour, unites depuis `purchase.line`.
+- `bill.fodt`: maturity date reelle et montant en lettres depuis le total.
+- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids, shipments et lots.
+- `sale_ict.fodt`: priorite lots et unite reelle.
+
diff --git a/modules/purchase_trade/docs_source/business/risk-credit-forex.md b/modules/purchase_trade/docs_source/business/risk-credit-forex.md
new file mode 100644
index 0000000..8157c24
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/risk-credit-forex.md
@@ -0,0 +1,15 @@
+# Risque, credit, forex
+
+Statut: `placeholder`
+
+Cette page est reservee aux regles business sur le risque, le credit et les
+ecarts de change.
+
+Aucune regle canonique `purchase_trade` n'a ete promue ici dans cette premiere
+passe. Les notes comptables et forex existantes doivent etre relues avant toute
+migration:
+
+- `notes/accounting/README.md`
+- `notes/accounting/business_rules.md`
+- `notes/accounting/reporting.md`
+
diff --git a/modules/purchase_trade/docs_source/business/sessions.md b/modules/purchase_trade/docs_source/business/sessions.md
new file mode 100644
index 0000000..5cb8e8a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/sessions.md
@@ -0,0 +1,31 @@
+# Journal de migration et sessions
+
+Statut: `non canonique`
+
+Cette page sert de routeur vers les notes historiques. Une note devient
+canonique seulement quand elle est reprise dans une page thematique.
+
+## Sources historiques
+
+- `modules/purchase_trade/docs/business-rules.md`
+- `modules/purchase_trade/docs/business-rules-architecture-proposal.md`
+- `notes/business_rules.md`
+- `notes/template_business_rules.md`
+
+## Notes deja partiellement promues
+
+- Session `2026-04-30`: PnL fees ouverts et `% rate`, promue dans
+ [fees.md](fees.md) et [valuation-pnl-mtm.md](valuation-pnl-mtm.md).
+- Session `2026-05-01`: solde ouvert apres lots physiques et lots effectifs
+ des fees, promue dans [lots-and-quantities.md](lots-and-quantities.md) et
+ [fees.md](fees.md).
+- Session `2026-05-06`: Remove physical lot, promue dans
+ [lots-and-quantities.md](lots-and-quantities.md).
+- Session `2026-05-09`: Lots Management, promue partiellement dans
+ [lots-management.md](lots-management.md).
+- Session `2026-05-13`: cadrage `quantity_theorical` / `quantity`, amount de
+ ligne, Weight basis, invariants de quantite, checks Python bloquants et
+ diagnostic SQL. Promue dans
+ [lots-and-quantities.md](lots-and-quantities.md),
+ [lots-and-quantities.en.md](lots-and-quantities.en.md),
+ [invariants.md](invariants.md) et [sql/README.md](sql/README.md).
diff --git a/modules/purchase_trade/docs_source/business/shipments-execution.md b/modules/purchase_trade/docs_source/business/shipments-execution.md
new file mode 100644
index 0000000..b9e9784
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/shipments-execution.md
@@ -0,0 +1,49 @@
+# Shipments et execution
+
+Statut: `migration partielle`
+
+## BR-PT-SHP-001 - Affectation controller par ecart a l'objectif
+
+Source: `BR-PT-014`
+
+### Regle consultant
+
+Le controller propose automatiquement est celui dont la zone a le plus grand
+retard par rapport a son objectif.
+
+### Notes developpeur
+
+- Configuration: onglet `Execution` de `party.party`.
+- La zone du shipment vient de `shipment.to_location.country`.
+- Une region parente couvre ses sous-regions.
+- `% achieved` compte seulement les shipments deja affectes a un controller.
+
+## BR-PT-SHP-002 - Couts SLA controller par pays et/ou lieu
+
+Source: `BR-PT-014-bis`
+
+### Regle consultant
+
+Un cout controller peut etre defini pour un pays, un lieu, ou le couple pays +
+lieu. Le couple est le cas le plus specifique.
+
+### Notes developpeur
+
+- Matching: `country + location`, puis `location`, puis `country`.
+- Le pays vient de `shipment.to_location.country`.
+
+## BR-PT-SHP-003 - Weight reports distants par lot
+
+Source: `BR-PT-015`
+
+### Regle consultant
+
+L'export distant par lot part du weight report global choisi par l'utilisateur
+sur le shipment.
+
+### Notes developpeur
+
+- Exporter seulement les lots physiques des `incoming_moves`.
+- Exiger au minimum `controller` et `returned_id` sur le shipment.
+- Conserver les cles distantes et la date d'envoi sur le `weight.report`.
+
diff --git a/modules/purchase_trade/docs_source/business/sql/README.md b/modules/purchase_trade/docs_source/business/sql/README.md
new file mode 100644
index 0000000..426119d
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/sql/README.md
@@ -0,0 +1,47 @@
+# SQL diagnostics for purchase_trade business rules
+
+These scripts are read-only diagnostics for a PostgreSQL test database.
+
+They exist to support the same business rules enforced by Python guards. The
+expected workflow is:
+
+1. write the consultant/developer rule in the thematic documentation;
+2. enforce the invariant in the application code when feasible;
+3. provide a read-only SQL diagnostic to audit existing data.
+
+## quantity_consistency_checks.sql
+
+Checks the two core lot quantity invariants documented in
+`lots-and-quantities.md` and `lots-and-quantities.en.md`.
+
+Zero `lot_qt` rows are ignored completely. They are treated as legitimate
+memory of an open quantity consumed by a physical lot. This is required because
+`lot_qt` represents usable open forecast and stops at zero, while the virtual
+lot may become negative to compensate the difference between theoretical and
+executed quantity. A non-zero `lot_qt` row without both `lot_p` and `lot_s` is
+reported as anomalous.
+
+Run it on a restored test database:
+
+```sql
+\i modules/purchase_trade/docs/business/sql/quantity_consistency_checks.sql
+```
+
+The script returns rows only when it finds a potential issue.
+
+Main columns:
+
+- `check_name`: invariant or diagnostic that failed.
+- `contract_model`: `purchase.purchase` or `sale.sale`.
+- `contract_id`: database id of the contract.
+- `contract_number`: purchase or sale contract number.
+- `line_id`: `purchase.line` or `sale.line` id depending on the check.
+- `virtual_lot_id`: virtual lot involved in the inconsistency.
+- `observed_value`: value found in the database.
+- `expected_value`: value required by the business rule.
+- `diff`: observed minus expected.
+- `detail`: human-readable explanation.
+
+Cross-category UoM rows are reported as manual-review diagnostics because the
+Python code may pass explicit conversion factors that cannot be inferred safely
+from SQL alone.
diff --git a/modules/purchase_trade/docs_source/business/valuation-pnl-mtm.md b/modules/purchase_trade/docs_source/business/valuation-pnl-mtm.md
new file mode 100644
index 0000000..073441a
--- /dev/null
+++ b/modules/purchase_trade/docs_source/business/valuation-pnl-mtm.md
@@ -0,0 +1,51 @@
+# Valuation, PnL, MTM
+
+Statut: `migration partielle`
+
+## BR-PT-VAL-001 - La valuation couvre achat, vente et sale-first
+
+Source: `BR-PT-004`, `BR-PT-006`, `BR-PT-011`
+
+### Regle consultant
+
+Le PnL doit exister pour les achats et pour les ventes, meme quand une vente
+n'est pas encore matchee a un achat.
+
+### Notes developpeur
+
+- Une `sale.line` non matchee doit generer au minimum `sale priced`, `sale fee`
+ et `derivative` si applicable.
+- Une sale basis sans detail de prix doit quand meme produire une ligne a zero
+ ou au prix economique fallback selon la regle applicable.
+- Ne pas attacher arbitrairement une sale unique si plusieurs sales sont
+ matchees au meme ouvert.
+
+## BR-PT-VAL-002 - References de valuation
+
+Source: `BR-PT-005`
+
+### Regle consultant
+
+La reference de PnL doit decrire la nature de la ligne valorisee: achat ou
+vente, ouverte ou physique.
+
+### Notes developpeur
+
+- References autorisees: `Purchase/Open`, `Purchase/Physic`, `Sale/Open`,
+ `Sale/Physic`.
+- Un lot virtuel ne doit pas sortir avec une reference physique.
+
+## BR-PT-VAL-003 - MTM hors fees
+
+Source: `BR-PT-007`
+
+### Regle consultant
+
+Le mark-to-market s'applique aux prix et aux derives, pas aux frais.
+
+### Notes developpeur
+
+- MTM autorise pour `pur. priced`, `sale priced`, `derivative`.
+- Fees hors MTM: `pur. fee`, `sale fee`, `shipment fee`, `line fee`.
+- Pour les fees: `mtm_price`, `mtm`, `strategy` doivent rester vides.
+