220 lines
10 KiB
Markdown
220 lines
10 KiB
Markdown
# 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`.
|
|
- Le montant affiche sur `fee.fee` reste non signe, meme si `fin_int_delta`
|
|
est negatif. Le PnL applique seul le signe metier:
|
|
`PAY` negatif, `REC` positif.
|
|
|
|
## 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.
|
|
|
|
## Session 2026-05-06 - Remove physical lot
|
|
|
|
### `lot.remove` / lot physique shippe ou matche
|
|
|
|
- L'action `Remove physical lot` ne bloque plus automatiquement un lot physique
|
|
shippe ou matche.
|
|
- Si le lot est shippe, matche, ou les deux, l'utilisateur doit confirmer un
|
|
warning avant suppression.
|
|
- La suppression reste interdite si le `stock.move` lie au lot physique n'est
|
|
pas en etat `draft`, car le mouvement peut deja etre engage ou comptabilise.
|
|
- Quand la suppression est confirmee, le `lot.move`, le `stock.move` draft et
|
|
le lot physique sont supprimes.
|
|
- La quantite du lot physique est restauree dans `lot.qt` avec le meme contexte
|
|
metier:
|
|
- meme lot purchase virtuel (`lot_p`);
|
|
- meme shipment si le lot etait shippe;
|
|
- meme lot sale virtuel (`lot_s`) si le lot etait matche.
|
|
- Si une ligne `lot.qt` existe deja avec ce meme contexte, la quantite est
|
|
agregee sur la ligne existante au lieu de creer un doublon.
|