diff --git a/modules/purchase_trade/AGENTS.md b/modules/purchase_trade/AGENTS.md index 150d34a..35933b6 100644 --- a/modules/purchase_trade/AGENTS.md +++ b/modules/purchase_trade/AGENTS.md @@ -142,10 +142,15 @@ de negoce physique: de facture inclut deja le montant padding - les comptes de padding viennent de `account.configuration`: `Default Sale Padding` et `Default Accrual Padding` - - la provisoire doit passer le couple sale/accrual, et la finale doit passer - l'inverse pour le montant exact comptabilise en provisoire - - preferer stocker ce montant au moment de la provisoire plutot que le - recalculer depuis la finale + - 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 diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md index 4deabe4..1cc1381 100644 --- a/modules/purchase_trade/docs/business-rules.md +++ b/modules/purchase_trade/docs/business-rules.md @@ -431,10 +431,17 @@ Owner technique: `a completer` - Validation comptable: - au `Validate`, le move principal de facture inclut deja le padding car il est integre a `account.invoice.line.quantity` - - la provisoire doit utiliser le couple de comptes configure + - la provisoire cree un `additional_move` avec le couple de comptes configure `Default Sale Padding` / `Default Accrual Padding` - - la finale doit passer l'ecriture inverse pour exactement le montant padding + - montant provisoire: + `lot.sale_invoice_padding * account.invoice.line.unit_price` + - la finale cree l'ecriture inverse pour exactement le montant padding comptabilise lors de la provisoire + - le montant d'extourne finale doit etre relu depuis + `lot.sale_invoice_line_prov`, afin de reprendre le prix, la devise, la date + et le taux de la provisoire + - le calcul de quantite finale doit retirer le padding de la quantite + provisoire avant de calculer le delta - voir `modules/purchase_trade/docs/padding-invoice-accounting.md` - Priorite: - `importante` diff --git a/modules/purchase_trade/docs/padding-invoice-accounting.md b/modules/purchase_trade/docs/padding-invoice-accounting.md index b7704d0..31d602e 100644 --- a/modules/purchase_trade/docs/padding-invoice-accounting.md +++ b/modules/purchase_trade/docs/padding-invoice-accounting.md @@ -1,294 +1,249 @@ -# Padding facture provisoire vente - notes comptables +# Padding facture provisoire vente - notes de session -Statut: `draft` +Statut: `implemente - a valider comptablement` Derniere mise a jour: `2026-04-26` -Scope: `lot.invoice` -> `account.invoice` -> validation comptable. +Scope: `lot.invoice` -> `account.invoice.line` -> `account.move` ## 1) Objectif metier Le padding sert a augmenter temporairement la quantite facturee sur une facture provisoire cote vente, afin de constituer une provision avant la facture finale. +Le padding ne change jamais la quantite physique du lot. Il represente seulement +la part incluse en plus dans la facture provisoire. + Exemple: - 2 lots factures ensemble - padding global saisi dans le wizard: `1000` - repartition actuelle: `500` par lot - chaque ligne facture affiche: - - `quantity = quantite reelle du lot + padding du lot` + - `Quantity = quantite reelle du lot + padding du lot` - `Inc. padding = padding du lot` -Le padding ne modifie pas la quantite physique du lot. Il doit rester visible et -traçable comme ecart entre la facture provisoire et le lot. +## 2) Saisie et stockage -## 2) Donnees creees ou utilisees +### Wizard `lot.invoice` + +- Champ: `sale_padding` +- Label UI: `Global padding` +- Visible uniquement pour: + - `type = sale` + - `action = prov` + +Au clic `Invoice`, le wizard: + +1. prend le padding global saisi +2. le repartit egalement entre les lots selectionnes +3. ecrit la part de chaque lot dans `lot.sale_invoice_padding` +4. lance la creation de la facture provisoire vente ### `lot.lot` - Champ: `sale_invoice_padding` -- Role: memoire de la part de padding appliquee au lot pour la facture - provisoire vente. -- UI: readonly sur le lot. +- Label UI: `Sale invoice padding` +- Mode UI: readonly +- Role: memoire durable de la part de padding appliquee au lot. + +Ce champ est la source de verite fonctionnelle pour: + +- afficher le padding sur la facture +- exclure le padding du calcul de delta de la facture finale +- calculer les ecritures comptables specifiques au padding ### `account.invoice.line` - Champ fonctionnel: `included_padding` - Label UI: `Inc. padding` -- Role: affichage de `line.lot.sale_invoice_padding` sur la ligne facture. -- La valeur n'est pas stockee sur la ligne; elle lit le padding du lot. +- Unite affichee: meme unite que `Quantity` +- Role: afficher `line.lot.sale_invoice_padding` sur la ligne facture. -### Wizard `lot.invoice` +La valeur n'est pas stockee sur la ligne facture. Elle lit le padding depuis le +lot pour eviter une deuxieme source de verite. -- Champ: `sale_padding` (`Global padding`) -- Visible seulement pour: - - `type = sale` - - `action = prov` -- Repartition actuelle: - - padding global / nombre de lots selectionnes +## 3) Impact sur la facture provisoire -## 3) Flux de creation de facture avec padding +Lors de la creation des lignes de facture vente provisoire: -1. L'utilisateur ouvre le wizard `lot.invoice` depuis les lots physiques. -2. Le wizard prepare deux listes: - - `lot_p` pour achat - - `lot_s` pour vente -3. En mode vente provisoire, l'utilisateur saisit `Global padding`. -4. Au clic `Invoice`, le wizard: - - calcule la part de padding par lot - - ecrit cette part dans `lot.sale_invoice_padding` - - appelle `sale.sale._process_invoice(..., action='prov')` -5. `sale.line.get_invoice_line(lots, action)` cree une ligne facture par lot - physique. -6. Pour chaque ligne positive `out` / `Pro forma`, le module ajoute le padding - du lot a `invoice_line.quantity`. -7. L'amount de la ligne augmente naturellement via: - - `quantity * unit_price` +- seules les lignes positives `out` / `Pro forma` sont impactees +- `invoice_line.quantity` est augmente de `lot.sale_invoice_padding` +- le montant de ligne augmente naturellement car Tryton calcule: + - `quantity * unit_price` -## 4) Ce qui se passe actuellement lors de `Validate` d'une facture +Consequence: -Bouton UI: `Validate` sur `account.invoice`. +- le move principal de facture contient deja le chiffre d'affaires augmente par + le padding +- `Inc. padding` rend visible la part artificielle incluse dans la quantite +- le lot garde sa quantite physique reelle -Code principal: +## 4) Impact sur la facture finale -- `modules/account_invoice/invoice.py` - - `Invoice.validate_invoice` - - `Invoice.get_move` - - `InvoiceLine.get_move_lines` - - `Invoice.do_lot_invoicing` - - `Invoice.cleanMoves` +La facture finale doit comparer la quantite physique finale avec la quantite +reelle deja facturee en provisoire, pas avec la quantite provisoire augmentee du +padding. -### Etapes de `validate_invoice` +Regle: -1. Verifie les taxes avec `_check_taxes`. -2. Attribue le numero via `set_number`. -3. Stocke les caches de montants via `_store_cache`. -4. Pour chaque facture: - - appelle `invoice.get_move()` - - affecte le move a `invoice.move` s'il est nouveau - - appelle `invoice.do_lot_invoicing()` -5. Sauvegarde les moves crees. -6. Nettoie certaines lignes de move via `cleanMoves`. -7. Sauvegarde les factures. +- quantite provisoire reelle = + `lot.sale_invoice_line_prov.quantity - lot.sale_invoice_padding` +- delta final = + `lot.quantite_physique_courante - quantite_provisoire_reelle` -Note importante deja actee dans le projet: +Exemple: -- `Validate` cree deja le `account.move` et attribue le `number` aussi pour les - factures client (`type = out`). -- `Post` ne doit plus etre le premier moment ou ces donnees apparaissent cote - client. +- lot initial: `993` +- padding provisoire: `100` +- facture provisoire: `1093` +- apres weighing: `1080` +- delta final attendu: `1080 - 993 = 87` -### `get_move()` +Sans cette correction, le wizard comparerait `1080` a `1093` et proposerait +`-13`, ce qui est faux car le padding ne doit pas etre considere comme une +quantite physique deja facturee. -`get_move()` construit le move comptable principal de la facture: +## 5) Configuration comptable -- appelle `update_taxes(exception=True)` -- parcourt `invoice.lines` -- pour chaque ligne, appelle `line.get_move_lines()` -- ajoute les lignes de taxes via `tax.get_move_lines()` -- ajoute la ligne tiers / receivable-payable via `_get_move_line` -- ajoute eventuellement une ligne de change via `_get_exchange_move_line` -- cree un `account.move` avec: - - `journal = invoice.journal` - - `period` trouve depuis la date comptable - - `origin = invoice` - - `lines = move_lines` +Deux comptes par defaut sont ajoutes dans `Financial / Configuration`, dans une +section dediee `Padding` placee avant la section `Invoice`. -### `account.invoice.line.get_move_lines()` +Champs: -Pour une ligne facture `type = line`: +- `Default Sale Padding` + - champ technique: `default_sale_padding_account` + - compte metier attendu: `80021` + - nature attendue: revenu +- `Default Accrual Padding` + - champ technique: `default_accrual_padding_account` + - compte metier attendu: `42021` + - nature attendue: bilan / accrual -- cree une ligne `account.move.line` -- rattache `line.lot = self.invoice.lines[0].lot` - - attention: comportement existant, pas forcement la ligne courante -- convertit le montant si devise differente -- pour une facture client (`type = out`): - - montant positif => credit du compte de revenu de la ligne - - montant negatif => debit -- met `line.account = self.account` -- met `line.origin = self` -- calcule les lignes de taxe +Les champs suivent le pattern Tryton de `account.configuration`: -Pour le padding actuel, comme il augmente `invoice_line.quantity`, il est deja -inclus dans le montant de revenu du move principal. +- champ `MultiValue` sur `account.configuration` +- stockage dans `account.configuration.default_account` +- domaine par compagnie via le contexte `company` -### `do_lot_invoicing()` +## 6) Ecriture padding a la validation d'une provisoire -`do_lot_invoicing()` traite des ajustements lies aux lots: +Point d'entree: -- groupe les lignes facture par `i.lot` -- calcule: - - `var_price` - - `var_qt` -- retrouve le mouvement stock: - - fournisseur pour `type = in` - - client pour `type = out` -- declenche des actions stock / shipment si necessaire -- calcule le COG du lot -- prepare des lignes d'ajustement via `_get_move_lines` -- actuellement, le move d'ajustement n'est cree/poste que si: - - `adjust_move_lines` existe - - `self.type == 'in'` +- `account.invoice.validate_invoice` +- puis `invoice.do_lot_invoicing()` +- surcharge `purchase_trade.invoice.Invoice.do_lot_invoicing` -Consequence importante pour le padding: +Condition: -- les ajustements de quantite/prix existants sont principalement effectifs cote - achat (`type = in`) -- une facture client provisoire avec padding augmente deja le move principal de - vente, mais ne genere pas encore d'ecritures specifiques padding separees +- facture client: `invoice.type = out` +- reference facture: `Provisional` +- ligne facture: `description = Pro forma` +- ligne rattachee a un lot +- `lot.sale_invoice_padding > 0` -### `_get_move_lines(gl, amount, drop, IsUsd=False, stock_move=None)` +Montant: -Helper existant pour creer des paires de lignes de move liees au stock/COG: +- `padding_amount = lot.sale_invoice_padding * invoice_line.unit_price` +- devise: devise de la facture provisoire +- montant societe: converti avec la devise, le taux et la date de la facture + provisoire -- rattache `lot`, `fee`, `origin` -- gere la conversion devise / second currency -- choisit les comptes stock / stock out / stock in / COG selon le signe, fee et - contexte -- peut creer une variante `drop` +Ecriture creee en `additional_move`: -Ce helper est un candidat possible pour reutilisation partielle, mais il est -aujourd'hui pense pour des ajustements stock/COG, pas pour isoler un padding de -facture provisoire vente. +- description: `Sale padding accrual` +- debit: `Default Sale Padding` (`80021`) +- credit: `Default Accrual Padding` (`42021`) +- `origin` des lignes: ligne de facture provisoire +- `lot` des lignes: lot concerne +- `party` renseigne si le compte le requiert -## 5) Question comptable a trancher pour le padding +Raison du `additional_move`: -Le besoin restant est: au `Validate` d'une facture provisoire vente avec padding, -generer des ecritures propres a ce padding. +- ne pas modifier la construction standard du move principal de facture +- isoler l'ecriture padding pour audit et lettrage metier +- permettre le posting coordonne avec la facture -Points a definir avant implementation: +## 7) Ecriture inverse a la finale -- Le move padding doit-il etre: - - integre dans le move principal de facture ? - - cree comme `additional_move` ? - - cree comme move separe avec `origin = invoice` ? -- Quels comptes utiliser ? - - compte de revenu temporaire / provision ? - - compte de contrepartie padding ? - - compte lie au produit ? - - compte de configuration dedie ? -- Faut-il neutraliser une partie du revenu principal deja augmente par la - quantity, ou seulement ajouter une ecriture analytique/stock parallele ? -- Le padding doit-il impacter: - - revenu client ? - - stock out ? - - COG ? - - PnL / valuation ? -- Que doit faire la facture finale ? - - reprendre le padding provisoire ? - - le contrepasser ? - - ne rien faire si la finale facture la vraie quantite ? -- Le montant padding a utiliser est: - - `lot.sale_invoice_padding * invoice_line.unit_price` - - dans la devise de facture - - a convertir si devise societe differente selon la logique standard +La finale doit extourner exactement le padding comptabilise sur la provisoire. +Elle ne doit pas recalculer le montant avec le prix final. -## 5.1) Decision comptable cible +Source du montant: -Comptes de configuration ajoutes sur `account.configuration`: - -- `default_sale_padding_account` - - label UI: `Default Sale Padding` - - compte attendu metier: `80021` - - nature: revenu -- `default_accrual_padding_account` - - label UI: `Default Accrual Padding` - - compte attendu metier: `42021` - - nature: bilan / accrual - -Facture provisoire vente: - -- condition: - - `invoice.type = out` - - `invoice.reference = Provisional` - - `invoice_line.description = Pro forma` - - `invoice_line.lot.sale_invoice_padding > 0` -- montant: - - `padding_amount = lot.sale_invoice_padding * invoice_line.unit_price` - - le prix est celui de la facture provisoire, pas un prix recalcule plus tard -- ecriture attendue: - - couple `80021 / 42021` - - rattachement au lot obligatoire sur les lignes si possible - -Facture finale vente: - -- condition: - - lot deja facture en provisoire avec padding - - la finale doit inverser le padding deja provisionne -- montant: - - exactement le montant comptabilise lors de la provisoire - - ne pas recalculer avec le prix de la finale -- ecriture attendue: - - couple inverse `42021 / 80021` - -Decision technique retenue: - -- ne pas ajouter de nouveau champ montant pour l'instant -- utiliser le lien deja ecrit sur le lot: +- le lot porte deja le lien vers la ligne provisoire: - `lot.sale_invoice_line_prov` -- au moment de la finale, retrouver la ligne provisoire via ce champ -- calculer le montant a extourner avec: - - `lot.sale_invoice_padding * lot.sale_invoice_line_prov.unit_price` -- utiliser la devise, le taux et la date de la facture provisoire pour obtenir - le meme montant societe que l'ecriture initiale -- garder le rattachement comptable par `account.move.line.lot` - -Raison: - -- evite les ecarts si le prix final change -- reutilise le lien direct deja maintenu entre le lot et la ligne provisoire -- rend la reprise finale audit-friendly -- garde le pivot comptable lot coherent - -## 6) Invariants a preserver - -- Le padding ne change pas la quantite physique du lot. -- Le padding est uniquement pour facture provisoire vente: - - `invoice.type = out` - - `invoice.reference = Provisional` - - lignes `description = Pro forma` -- L'utilisateur doit voir clairement l'ecart: - - `Quantity` - - `Inc. padding` -- Le lien principal reste: - - `account.invoice.line.lot` +- le montant d'extourne utilise: - `lot.sale_invoice_padding` -- Les futures ecritures doivent etre rattachees au lot autant que possible pour - rester visibles dans le pivot comptable du lot. + - `lot.sale_invoice_line_prov.unit_price` + - la devise, la date et le taux de la facture provisoire -## 7) Tests a prevoir pour la suite +Condition: -- Validation d'une facture provisoire vente avec padding: - - move principal existe - - montant facture inclut le padding - - ecritures specifiques padding creees selon la regle retenue - - lignes rattachees au bon lot -- Facture provisoire vente sans padding: - - aucun move padding specifique -- Facture finale vente: - - aucun nouveau padding applique automatiquement - - comportement de reprise/contrepassation selon decision metier -- Facture achat: - - aucun impact padding vente -- Multi-lots: - - padding reparti correctement - - ecritures detaillees par lot si la regle comptable l'exige +- facture client: `invoice.type = out` +- reference facture: `Final` +- ligne facture: `description = Final` +- lot avec `sale_invoice_padding > 0` +- lot avec `sale_invoice_line_prov` + +Ecriture creee en `additional_move`: + +- description: `Sale padding reversal` +- debit: `Default Accrual Padding` (`42021`) +- credit: `Default Sale Padding` (`80021`) +- montant: identique au montant padding de la provisoire +- `origin` des lignes: ligne de facture provisoire +- `lot` des lignes: lot concerne + +## 8) Posting + +Au `Validate`: + +- le move principal de facture est cree +- le ou les `additional_moves` padding sont crees en draft + +Au `Post`: + +- les `additional_moves` padding deja crees en draft sont postes +- si une facture est postee directement depuis draft, le module cree d'abord les + moves padding manquants puis les poste + +Une detection par description evite de recreer deux fois le meme move padding +sur la meme facture: + +- `Sale padding accrual` +- `Sale padding reversal` + +## 9) Invariants a preserver + +- Le padding est uniquement pour les factures provisoires cote vente. +- Le padding ne modifie jamais la quantite physique du lot. +- `lot.sale_invoice_padding` reste la memoire fonctionnelle du padding. +- La facture affiche toujours l'ecart via `Inc. padding`. +- La facture finale ignore le padding pour calculer le delta de quantite. +- L'extourne finale reprend le montant de la provisoire, pas le prix final. +- Les ecritures padding doivent rester rattachees au lot. + +## 10) Points de vigilance + +- Le sens debit/credit actuellement implemente est: + - provisoire: debit `80021`, credit `42021` + - finale: debit `42021`, credit `80021` + Ce sens doit etre valide par la comptabilite si le plan attendu impose une + convention differente. +- La detection du type de facture s'appuie sur `invoice.reference`: + - `Provisional` + - `Final` +- La detection des lignes s'appuie sur `invoice_line.description`: + - `Pro forma` + - `Final` +- Si ces libelles changent, la logique padding doit etre ajustee. +- Les tests unitaires complets n'ont pas pu etre lances dans l'environnement + local actuel a cause de dependances manquantes (`pytest`, puis `jwt`). + +## 11) Validations techniques de session + +Validations effectuees localement: + +- compilation Python ciblee des fichiers touches +- validation XML ciblee des vues et definitions modifiees +- `git diff --check` sans erreur bloquante, avec seulement des alertes CRLF + existantes dans le repository diff --git a/notes/template_business_rules.md b/notes/template_business_rules.md index 5c978a3..490efe8 100644 --- a/notes/template_business_rules.md +++ b/notes/template_business_rules.md @@ -107,6 +107,17 @@ Scope: templates Relatorio + ponts `report_*` Python. lot ne doit pas changer. - Au `Validate` d'une facture, le move principal inclut deja le padding car il est integre dans `account.invoice.line.quantity`. -- Les ecritures comptables specifiques au padding restent a definir; point - d'entree documente: +- Deux comptes de configuration pilotent les ecritures padding: + `Default Sale Padding` et `Default Accrual Padding`, dans la section + `Padding` de `Financial / Configuration`. +- A la validation d'une provisoire vente, un `additional_move` est cree: + debit `Default Sale Padding`, credit `Default Accrual Padding`, pour + `sale_invoice_padding * unit_price` de la ligne provisoire. +- A la finale, l'ecriture inverse est creee pour le meme montant, calcule depuis + `lot.sale_invoice_line_prov` afin de reprendre le prix, la devise, la date et + le taux de la provisoire, pas ceux de la finale. +- Le wizard final doit ignorer le padding quand il calcule le delta de quantite: + comparer la quantite physique finale a + `lot.sale_invoice_line_prov.quantity - lot.sale_invoice_padding`. +- Detail complet: `modules/purchase_trade/docs/padding-invoice-accounting.md`.