Files
tradon/notes/business_rules.md

9.0 KiB

Business rules

Memo racine pour les regles metier transverses ou multi-modules.

Pour les regles propres a un module, preferer la documentation locale quand elle existe, par exemple:

  • modules/purchase_trade/docs/business-rules.md
  • modules/purchase_trade/docs/padding-invoice-accounting.md

Session 2026-04-28 - Devises, TVA, padding et lots

account.move.line / saisie manuelle en seconde devise

  • rate est un vrai champ stocke et editable, plus seulement un champ de visualisation.
  • Le sens du taux suit l'usage Tryton: taux devise seconde / devise societe. Donc, avec une devise societe a 1, le montant societe se calcule par amount_second_currency / rate.
  • Une ligne est consideree comme manuelle si elle n'a ni origin, ni move_origin, ni move.origin.
  • En manuel, si l'utilisateur saisit d'abord amount_second_currency, le systeme reprend le dernier taux disponible pour la devise seconde a la date de la ligne, puis calcule debit ou credit.
  • Si l'utilisateur veut forcer le taux, il efface d'abord debit / credit, saisit rate, et le montant societe est recalcule depuis le montant en devise seconde.
  • Si le montant societe et le montant devise seconde sont deja presents, le taux implicite est abs(amount_second_currency) / abs(base_amount).
  • Les lignes generees automatiquement depuis facture doivent aussi renseigner rate quand amount_second_currency existe.
  • Un fallback central sur account.move.line.create calcule le taux manquant depuis les montants pour eviter les chemins oublies.
  • Comme rate devient stocke, une mise a jour du module est necessaire; les anciennes lignes doivent etre regenerees ou backfillees si on veut afficher le taux historique.

Causes racines des bugs devises

  • Deux on_change_amount_second_currency existaient; le second ecrasait la logique de conversion.
  • La branche negative testait deux fois > 0, donc un montant devise seconde negatif ne generait pas correctement le credit.
  • Le taux initial etait calcule a l'envers par rapport au standard Tryton attendu.
  • Les records transitoires Tryton peuvent ne pas exposer un champ non assigne; les chemins generiques doivent utiliser getattr(record, field, None).

account.invoice / taux et devise

  • on_change_with_rate depend explicitement de currency, company, invoice_date, selection_rate, rate_date et lines.
  • Ne pas declarer lines.amount dans @fields.depends: ce champ relationnel n'existe pas cote definition de vue et provoque un KeyError.
  • Les move lines de facture, lignes produit, lignes stock/drop et lignes TVA doivent renseigner rate des qu'elles portent amount_second_currency.

TVA

  • Les taxes sur invoice.lines[].taxes alimentent les taxes calculees de la facture via les lignes taxables.
  • Les taxes globales de facture (invoice.taxes) peuvent etre automatiques ou manuelles.
  • Les taxes manuelles sont preservees par update_taxes; les taxes automatiques sont synchronisees, supprimees ou recreees selon les lignes.
  • Une ligne facture genere des account.tax.line de type base.
  • Une ligne de taxe facture genere une move line comptable avec account.tax.line de type tax; si la taxe est manuelle, une ligne base complementaire peut etre ajoutee.
  • La seconde devise est portee par account.move.line (second_currency / amount_second_currency), pas par account.tax.line qui suit la devise comptable de la move line.

Padding

  • Les chemins padding doivent verifier que lot existe avant de lire sale_invoice_line_prov ou sale_invoice_line.
  • Le bug observe n'etait pas la logique padding elle-meme mais un acces direct a un lot absent sur certaines lignes de facture.

Payment term

  • Le calcul des echeances doit fonctionner meme si la premiere ligne facture n'a pas d'origin.
  • term_lines doit etre calcule hors de la branche qui depend du modele d'origine, avec une ligne metier optionnelle.

Periodes comptables

  • Period.find utilise un cache par societe/date/etat.
  • Une periode creee ou ouverte peut continuer a etre vue comme absente par un worker tant que le cache ou le serveur n'a pas ete rafraichi.

lot.qt / validation sale et purchase

  • Cote sale.line, la creation de la ligne cree deja le lot virtuel et sa quantite.
  • Le passage initial de quantity_theorical de None a la quantite de la ligne ne doit pas etre traite comme un delta physique; sinon lot.qt est double.
  • Le correctif sale ignore le delta quand l'ancienne valeur theorique est None; les modifications suivantes restent bien appliquees en delta.
  • Cote purchase.line, la logique etait deja protegee par une baseline sur la quantite courante du lot virtuel quand l'ancienne valeur est None.

Valuation / PNL achat

  • Une purchase line peut ne pas produire de valorisation si elle est finie, sans lot, filtree par valuation_type, sans lot_price pour les modes priced / efp, sans fees relies a des lots, ou sans derivatives.
  • En mode basis, sans composant, le fallback metier continue de passer par la ligne summary quand elle existe.

Session 2026-04-29 - Purchase trade: rate fee et PnL lots ouverts

fee.fee / mode % rate

  • Le calcul du montant d'un fee en mode rate est aligne entre purchase et sale.
  • Il ne depend pas du payment_term.
  • Il ne depend pas de la date du jour ni de fee_date.
  • La periode de financement est directement fin_int_delta sur la ligne Estimated date avec trigger = bldate.
  • La formule reste en interet simple ACT/360: amount = rate_factor * unit_price * quantity, avec rate_factor = (price / 100) * fin_int_delta / 360.
  • Si aucune Estimated Date bldate n'est renseignee, le fee % rate ne calcule pas de montant.

Valuation / PnL avec lots ouverts matches

  • Tant que les lots physiques ne sont pas crees, une purchase et une sale peuvent etre matchees uniquement via lot.qt: lot_p purchase ouvert -> lot_s sale ouvert.
  • Dans ce cas, les lignes PnL purchase-side (Pur. price, Pur. fee) doivent aussi renseigner sale et sale_line pour apparaitre dans l'onglet PnL de la sale matchee.
  • Quand le lot physique existe, lot.sale_line reste le chemin prioritaire.
  • Si un lot purchase ouvert est matche a plusieurs sales differentes, ne pas attacher arbitrairement une seule sale aux lignes purchase-side.
  • Mark as finished ne veut pas dire "ne plus calculer le PnL": il ignore seulement le reliquat ouvert/virtuel; les lots physiques, fees physiques et derivatives continuent d'etre valorises.

Session 2026-04-30 - PnL fees ouverts et % rate

PnL fees sur lots ouverts

  • Un lot ouvert / virtuel avec quantite courante a zero ne doit plus generer de lignes de fees PnL.
  • Cette regle suit la meme intention que Mark as finished: le reliquat ouvert/virtuel ignore ne doit pas porter de PnL, y compris pour les fees.
  • Les lots physiques restent valorisables; le filtre vise seulement les lots non physiques vides.
  • Le cas important est celui des fees rate, ppack ou lumpsum, dont le montant peut rester non nul meme quand la quantite affichee du lot est zero.

Fee % rate

  • fin_int_delta est la periode absolue de calcul du financement.
  • Le montant ne depend pas de Date.today(), de fee_date, ni d'un intervalle entre la date du jour et BL date + delta.
  • Purchase et sale appliquent la meme formule ACT/360: amount = unit_price * quantity * (price / 100) * fin_int_delta / 360.
  • La ligne Estimated date avec trigger = bldate reste le support metier pour porter fin_int_delta; la date estimee elle-meme ne sert pas au calcul du montant % rate.

Session 2026-05-01 - Solde ouvert apres lots physiques

purchase.line / sale.line et lot.qt

  • Une modification de quantity_theorical ne doit plus ajuster le lot.qt libre uniquement par delta.
  • Le solde ouvert est recalcule en tenant compte des lots physiques deja crees: quantity_theorical - somme(lots physiques).
  • Le lot.qt libre non matche / non shippe est ensuite aligne sur ce solde moins les lot.qt deja matches ou shippes.
  • Si ce calcul donne un solde negatif, la modification est bloquee avec Please unlink or unmatch lot.
  • Cas de reference: une ligne achat passee de 10000 a 20000 avec deja 10000 physiques doit rester a 10000 ouverts et 10000 physiques.

fee.fee / fee.lots

  • Le lien entre un fee et son lot virtuel est conserve comme fallback.
  • Des qu'un fee possede au moins un lot physique dans fee.lots, les lots physiques deviennent la base effective de fee.quantity.
  • Une hausse purement ouverte de quantity_theorical n'impacte donc pas un fee deja porte par des lots physiques.
  • En ppack, fee.quantity suit la somme des lot_qt physiques; dans les autres modes quantitatifs, elle suit la somme des quantites courantes des lots physiques.
  • A la suppression du dernier physique, le fee retombe sur son lot virtuel.
  • Le PnL fee suit la meme logique: full open tant que seul le virtuel existe, puis uniquement les lots physiques effectifs.