# AGENTS.md - Module `purchase_trade`

Ce guide complete le `AGENTS.md` racine.
Pour ce module, les regles locales ci-dessous priment.

## 1) Perimetre metier

Le module `purchase_trade` etend les flux achat/vente Tryton avec une logique
de negoce physique:

- contrats d'achat (`purchase.purchase`, `purchase.line`)
- contrats de vente (`sale.sale`, `sale.line`)
- lots physiques et virtuels
- matching achat/vente
- shipments et execution logistique
- frais (`fee.fee`)
- templates de documents metier et facture

## 2) Fichiers pivots

- Contrats achat:
  - `modules/purchase_trade/purchase.py`
- Contrats vente:
  - `modules/purchase_trade/sale.py`
- Lots / matching / invoicing:
  - `modules/purchase_trade/lot.py`
- Shipments / lien facture-lot:
  - `modules/purchase_trade/stock.py`
- Fees:
  - `modules/purchase_trade/fee.py`
- Bridge facture / templates:
  - `modules/purchase_trade/invoice.py`
- Vues:
  - `modules/purchase_trade/view/*.xml`
- Actions module:
  - `modules/purchase_trade/*.xml`
- Manifest:
  - `modules/purchase_trade/tryton.cfg`

## 3) Documentation locale a lire en priorite

- Regles metier:
  - `modules/purchase_trade/docs/business-rules.md`
- Regles templates:
  - `modules/purchase_trade/docs/template-rules.md`
- Catalogue des proprietes templates:
  - `modules/purchase_trade/docs/template-properties.md`
- Padding facture provisoire vente / validation comptable:
  - `modules/purchase_trade/docs/padding-invoice-accounting.md`

## 4) Invariants metier a preserver

- Un lot `virtual` est la reference d'ouverture de quantite pour une `purchase.line`.
- Une `sale.line` doit aussi avoir au minimum un lot `virtual`; une valuation
  cote sale ne doit donc pas disparaitre juste parce que le lot est `open`.
- Le lot physique est le pont principal entre:
  - `purchase.line`
  - `sale.line`
  - shipment
  - facture
- Pour remonter d'une facture vers shipment / BL / controller / fret:
  - privilegier le lot physique
  - ne pas multiplier des chemins d'acces concurrents
- Pour les champs de colis (`NB BALES`) dans les templates facture:
  - la source de verite est `line.lot.lot_qt`
  - sur une facture, sommer les `lot_qt` des lignes de facture
  - tenir compte du signe de la ligne de facture pour les notes finales
  - ne pas proratiser depuis le poids (`net` / `gross`)
- Le `FREIGHT VALUE` d'un template facture vient du `fee.fee` du shipment
  dont le produit est `Maritime freight`.
- Pour `stock/insurance.fodt`, le `Amount insured` doit venir en priorite de
  `110%` du total des `incoming_moves` (fallback fee `Insurance` si aucun
  montant incoming calculable).
- Pour le surveyor du certificat d'assurance shipment, la priorite est:
  `shipment.surveyor` -> `shipment.controller` -> fournisseur du fee
  `Insurance`.
- Pour `payment_order.fodt`, utiliser des proprietes
  `invoice.report_payment_order_*` plutot que des tokens legacy `<...>`.
- Ajouter un champ de template dans `Document Templates` ne rend pas le report
  visible dans la fiche: il faut aussi l'action `ir.action.report` +
  `ir.action.keyword` (`form_print`) cote `account.invoice`.
- Le wizard `Create contracts` en mode `matched` peut maintenant partir de
  plusieurs `lot.qt`, mais doit conserver un matching par lot source et laisser
  `created_by_code = True` sur les lignes creees pour ne pas declencher les
  creations automatiques de lots dans les validations.
- En valuation / PnL:
  - la valeur stockee dans `type` est la cle technique (`pur. priced`,
    `sale priced`, `pur. fee`, etc.), pas le label affiche dans l'UI
  - les references doivent rester coherentes avec le type de lot:
    `Purchase/Open`, `Purchase/Physic`, `Sale/Open`, `Sale/Physic`
  - pour une sale matchee, les lignes de valuation purchase generees sur un lot
    physique doivent aussi renseigner `sale` et `sale_line` afin de remonter
    dans l'onglet PnL de la sale
  - une sale non matchee doit etre valorisable "sale-first" et alimenter
    `valuation.valuation` / `valuation.valuation.line`
  - si une `sale.line` `basis` n'a ni `price_summary` ni `lot_price_sale`,
    creer quand meme une ligne `sale priced` avec `price = 0` et `amount = 0`
    plutot que de ne rien generer
  - le MTM ne doit etre renseigne que pour `pur. priced`, `sale priced` et
    `derivative`; jamais pour les fees
  - `mtm_price` doit afficher le prix brut de valorisation (sans ratio), alors
    que `mtm` reste le montant calcule selon la logique de strategie
- En pricing:
  - le `unit_price` doit rester un prix de base, hors `premium`
  - le `premium` doit impacter le prix total economique et donc le `amount`,
    aussi bien en `priced` qu'en `basis`
  - dans `pricing.pricing` en saisie manuelle, l'utilisateur renseigne
    seulement `quantity` et `settl_price`
  - `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et
    `eod_price` sont des valeurs derivees et ne doivent pas etre saisies a la
    main
  - en manuel, `fixed_qt` = cumul des `quantity` du groupe trie par
    `pricing_date`
  - en manuel, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`
  - en manuel, `unfixed_qt_price` = `settl_price` de la ligne
  - pour les documents commerciaux / facture, une ligne `basis` affiche le
    `premium` comme prix visible, pas le prix economique total
  - si `linked currency` est active, le `premium` est saisi dans la devise /
    unite liee (ex: `USC/LB`) puis converti vers le repere de la ligne pour le
    calcul du `amount`
  - en `basis + linked currency`, le `linked_price` doit representer le prix
    basis brut (hors premium) dans la devise liee; le `unit_price` reste ce
    prix brut converti, et le `premium` converti est ajoute seulement dans
    l'`amount`
  - si `linked currency` est cochee, `linked_price`, `linked_currency` et
    `linked_unit` sont requis
  - dans les forms, presenter le bloc prix dans l'ordre:
    `price_type` -> linked fields -> `premium` -> `unit_price` -> `amount`
  - en valuation `basis`, le premium s'applique a chaque composant, pas
    uniquement a une ligne de resume
  - pour une ligne `basis` sans `price_summary`, la valuation fallback doit
    utiliser `unit_price + premium` (et pas `unit_price` seul)
  - a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun
    matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour
    alimenter le PnL sale-first
- En padding de facture provisoire vente:
  - le padding est saisi globalement dans `lot.invoice`
  - il est uniquement applique aux factures provisoires cote vente
  - il est reparti par lot et stocke dans `lot.sale_invoice_padding`
  - la ligne facture affiche `Inc. padding` depuis le lot
  - le padding augmente `account.invoice.line.quantity`, donc le move principal
    de facture inclut deja le montant padding
  - les comptes de padding viennent de `account.configuration`:
    `Default Sale Padding` et `Default Accrual Padding`
  - la section `Padding` doit rester avant la section `Invoice` dans
    `Financial / Configuration`
  - la provisoire cree un `additional_move` debit `Default Sale Padding` /
    credit `Default Accrual Padding`
  - la finale cree l'inverse en reprenant le montant depuis
    `lot.sale_invoice_line_prov`, donc avec le prix, la devise, la date et le
    taux de la provisoire
  - le wizard final doit calculer son delta depuis la quantite provisoire hors
    padding: `sale_invoice_line_prov.quantity - sale_invoice_padding`

## 5) Conventions de modification

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_*`.
3. Pour une facture trade, preferer enrichir `modules/purchase_trade/invoice.py`
   plutot que surcharger lourdement le `.fodt`.
4. Si une regle metier durable change, mettre a jour
   `docs/business-rules.md`.
5. Si une convention de template change, mettre a jour
   `docs/template-rules.md`.
6. Pour les vues XML Tryton de ce module, utiliser `editable="1"` sur les
   `<tree>` editables; ne pas utiliser `editable="bottom"`.
7. Ne pas ajouter `keyword_open` / `open_keyword` sur les vues `<tree>`:
   ces attributs ne sont pas supportes par Tryton.
8. Si une regle de texte par defaut durable est demandee sur achat/vente,
   preferer un singleton de configuration expose dans un menu fonctionnel
   existant plutot qu'un menu technique `purchase_trade`.

## 6) Pieges connus

- Plusieurs actions de report `account.invoice` peuvent sembler rendre le meme
  document a cause du cache `invoice_report_cache`.
- Les reports alternatifs (`Final Invoice`, `Prepayment`, etc.) ne doivent pas
  reutiliser le cache du report standard sans verification.
- Pour les donnees achat/vente partagees, ne pas supposer qu'une facture de
  vente doit lire directement sur la `sale.line`: souvent, la verite metier
  passe par le lot physique et/ou la `account.invoice.line`.
- Les templates `invoice_ict*` peuvent partager les memes proprietes Python;
  si une regle doit valoir pour provisional et final, la mettre dans
  `modules/purchase_trade/invoice.py` plutot que dupliquer dans les `.fodt`.
- Dans les ecrans PnL, le label `Sale price` correspond au type stocke
  `sale priced`; idem pour `Pur. price` / `pur. priced`.
- Une ligne `basis` sans resume de pricing peut sinon disparaitre de la
  valuation si aucun fallback explicite a `0` n'est prevu.
- Le calcul du prix peut diverger entre `unit_price`, `linked_price`,
  `lot_price` et valuation si le premium n'est pas traite explicitement dans
  chaque maillon.
- Sur `account.invoice`, le workflow `Validate` doit maintenant aligner
  fournisseur et client pour:
  - creation du `account.move`
  - attribution du `number`
  - `Post` ne doit pas reintroduire une difference de session/fresh login cote
    client
- Pour le padding provisoire vente, ne pas changer la quantite physique du lot:
  l'ecart doit rester porte par `lot.sale_invoice_padding` et visible sur la
  facture via `Inc. padding`.

## 7) Definition of done (module `purchase_trade`)

- Le flux achat/vente/lot cible reste coherent.
- Les impacts templates/facture ont ete verifies conceptuellement.
- Les docs locales ont ete mises a jour si une nouvelle regle durable a emerge.
- Le patch reste minimal et local au domaine demande.
