40 KiB
40 KiB
Business Rules - Purchase Trade
Statut: draft
Version: v0.8
Derniere mise a jour: 2026-05-10
Owner metier: a completer
Owner technique: a completer
1) Scope
- Domaine:
purchase_trade - Hors scope:
- Modules impactes:
purchase_tradelot
2) Glossaire
Purchase Line: ligne d'achat.quantity_theorical: quantite theorique contractuelle de la ligne.Virtual Lot: lot unique de typevirtualrattache a unepurchase.line.lot.qt: table des quantites ouvertes, matchées ou shippées par lot.lot.qt ouvert: enregistrementlot.qtaveclot_p = virtual lot,lot_s = Noneet sans shipment.
3) Regles metier
BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
- Intent: conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans
lot.qt. - Description:
- Quand
purchase.line.quantity_theoricalest modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur. - La regle s'applique au lot unique de type
virtualrattache a lapurchase.line.
- Quand
- Conditions d'entree:
- Une
purchase.lineexiste deja. - Son champ
quantity_theoricalest modifie viawrite. - Un lot
virtualest rattache a la ligne.
- Une
- Resultat attendu:
- Si
delta > 0:- augmenter la quantite courante du lot
virtualviaset_current_quantitypour conserver l'historiquelot.qt.hist - augmenter le
lot.qtouvert existant - si aucun
lot.qtouvert n'existe, en creer un nouveau avec le delta
- augmenter la quantite courante du lot
- Si
delta < 0:- diminuer le
lot.qtouvert uniquement si la quantite ouverte disponible est suffisante - diminuer la quantite courante du lot
virtualdu meme delta - si aucun
lot.qtouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreurPlease unlink or unmatch lot
- diminuer le
- Si
- Definition du
lot.qtouvert:lot_p = virtual lotlot_s = Nonelot_shipment_in = Nonelot_shipment_internal = Nonelot_shipment_out = None
- Exceptions:
- si aucun lot
virtualn'est trouve sur la ligne, la regle ne fait rien
- si aucun lot
- Priorite:
bloquante
- Source:
Decision metier documentee dans les commentaires de purchase_trade.purchase.Line.write
BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
- Intent: disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
- Description:
- Le lot physique (
lot_type = physic) porte simultanement le lien vers:- la
purchase.linevialot.line - la
sale.linevialot.sale_line - le shipment via
lot.lot_shipment_in/lot.lot_shipment_internal/lot.lot_shipment_out
- la
- Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
- Le lot physique (
- Resultat attendu:
- depuis une facture d'achat:
- remonter a la
purchase.line - puis au lot physique de la ligne
- puis au shipment et aux donnees logistiques associees
- remonter a la
- depuis une facture de vente:
- remonter a la
sale.line - puis au lot physique matchant qui porte aussi la
purchase.line - puis au shipment et aux donnees logistiques associees
- remonter a la
- depuis une facture d'achat:
- Cas d'usage typiques:
- recuperer
bl_date,bl_number,controller,from_location,to_location - retrouver une facture provisoire liee au lot
- retrouver des fees rattaches au shipment
- recuperer
- Priorite:
structurante
BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
- Intent: afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
- Description:
- Le
FREIGHT VALUEd'une facture ne doit pas etre pris sur la facture elle-meme. - Il doit etre calcule a partir du
fee.feerattache au shipment (shipment_in) du lot physique relie a la facture.
- Le
- Regle de navigation:
- retrouver le lot physique pertinent depuis la facture
- retrouver son shipment
- chercher le
fee.feeavec:shipment_in = shipment.idproduct.name = 'Maritime freight'
- utiliser
fee.get_amount()comme montant de fret
- Portee:
- s'applique aussi bien aux factures d'achat qu'aux factures de vente
- cote vente, la remontee doit passer par le lot physique qui fait le lien entre
purchase.lineetsale.line
- Priorite:
importante
BR-PT-004 - La valuation doit couvrir les flux purchase et sale, y compris les sales non matchees
- Intent: obtenir un PnL coherent cote achat et cote vente, meme lorsqu'une sale n'est pas encore matchee a une purchase.
- Description:
- Le flux historique de valuation part de
purchase.linepuis remonte vers les ventes via les lots/lots matchants. - Le systeme doit egalement savoir valoriser directement une
sale.linenon matchee ("sale-first"). - Une sale non matchee doit creer des lignes dans
valuation.valuationetvaluation.valuation.lineafin d'apparaitre dans l'onglet PnL de la sale.
- Le flux historique de valuation part de
- Resultat attendu:
- pour une
sale.linenon matchee, generer au minimum les types:sale pricedsale feederivativesi la ligne porte des derives
- si la sale est matchee via un lot physique, les lignes purchase portees par
ce lot physique doivent aussi renseigner
saleetsale_line - une sale matchee doit donc voir:
- ses lignes
sale * - les lignes purchase portees par le lot physique partage
- ses lignes
- avant creation du lot physique, si le matching existe seulement via
lot.qt(lot_ppurchase ouvert ->lot_ssale ouvert), les lignes PnL purchase-side doivent aussi renseignersaleetsale_lineafin d'apparaitre dans l'onglet PnL de la sale matchee - un lot ouvert / virtuel avec quantite courante a zero ne doit pas generer de lignes de fees PnL residuelles
- si plusieurs sales differentes sont matchees au meme lot ouvert, ne pas attacher arbitrairement une sale unique aux lignes purchase-side
- pour une
- Priorite:
structurante
BR-PT-005 - Les references de valuation doivent decrire la nature du lot de la ligne
- Intent: eviter les ambiguïtes dans les ecrans PnL entre lots
openet lotsphysic. - Description:
- La reference affichee dans la valuation doit decrire la ligne elle-meme, pas son vis-a-vis.
- Les references autorisees pour les lignes de prix sont:
Purchase/OpenPurchase/PhysicSale/OpenSale/Physic
- Resultat attendu:
- un lot
virtualcote purchase ne doit jamais sortir avec la referencePurchase/Physic - un lot
virtualcote sale ne doit jamais sortir avec la referenceSale/Physic - un lot physique matche peut produire:
- une ligne purchase en
Purchase/Physic - une ligne sale en
Sale/Physic
- une ligne purchase en
- un open sale matche a un open purchase peut produire des quantites egales
tout en gardant des references differentes (
Purchase/OpenvsSale/Open)
- un lot
- Priorite:
importante
BR-PT-006 - Une sale basis sans prix detaille doit quand meme apparaitre en valuation
- Intent: ne pas perdre les lignes de PnL lorsque le detail de pricing n'est pas encore renseigne.
- Description:
- Une
sale.linede typebasispeut exister avec un lotvirtual, sansprice_summaryet sanslot_price_sale. - Dans ce cas, la valuation doit quand meme creer une ligne
sale priced.
- Une
- Resultat attendu:
- si
price_summaryest vide:- creer une ligne
sale priced - avec
price = 0 - avec
amount = 0 - avec un
statede typeunfixed
- creer une ligne
- si
lot_price_saleest vide sur un lot sale, utilisersale_line.unit_pricecomme fallback quand il existe
- si
- Priorite:
importante
BR-PT-007 - Le MTM de valuation ne s'applique pas aux fees
- Intent: distinguer les lignes de prix marquables au marche des lignes de frais qui ne doivent pas etre mark-to-market.
- Description:
- Le systeme peut renseigner
mtm_price,mtmetstrategyuniquement pour:pur. pricedsale pricedderivative
- Les fees (
pur. fee,sale fee,shipment fee,line fee) ne doivent jamais porter de valorisation MTM.
- Le systeme peut renseigner
- Resultat attendu:
- les lignes de fee doivent conserver:
mtm_price = NULLmtm = NULLstrategy = NULL
mtm_pricedoit representer le prix brut de valorisation sans appliquer le ratio de composantmtmreste le montant calcule selon la logique de strategie
- les lignes de fee doivent conserver:
- Priorite:
structurante
BR-PT-007-bis - Mark as finished ignore seulement le reliquat ouvert
- Intent: conserver le PnL reel des lots executes tout en masquant le reliquat ouvert d'une ligne terminee.
- Description:
- Sur une
purchase.lineou unesale.line, le champfinished(Mark as finished) ne signifie pas que la ligne ne doit plus etre valorisee. - Il signifie seulement que les lots ouverts / virtuels restants ne doivent plus alimenter la valuation.
- Dans
Lots Management, la meme regle s'applique aux ligneslot.qt: une lignelot.qtdoit etre masquee si son lot virtuel purchase (lot_p) ou son lot virtuel sale (lot_s) est rattache a une ligne marquee finie. - Cette exclusion vaut meme si la ligne
lot.qtest deja matchee ou liee a un shipment.
- Sur une
- Resultat attendu:
- les lots physiques continuent de produire:
- PnL prix
- PnL fees
- les derivatives continuent de produire du PnL meme si la ligne est marquee finie
- les lots virtuels / ouverts d'une ligne finie sont ignores
- les lots physiques restent visibles dans
Lots Management, meme si leur ligne purchase ou sale est marquee finie
- les lots physiques continuent de produire:
- Priorite:
structurante
BR-PT-008 - Le premium fait partie du prix contractuel en priced et en basis
- Intent: garantir que le montant total valorise et facture reflete toujours le premium/discount saisi sur la ligne.
- Description:
- Le
premiumd'unepurchase.lineousale.linedoit impacter le prix total quelle que soit laprice_type. - Cette regle vaut pour:
- les calculs de
amount - la valuation / PnL
- les calculs de
- Le
- Resultat attendu:
- le
unit_pricereste le prix de base, hors premium - en
priced, le montant economique =unit_price + premium - en
basis, le premium s'ajoute aussi au prix total economique - en valuation
basis, le premium s'applique a chaque composant valorise (ex: meme premium repete sur chaque bloc ICE)
- le
- Exemple metier:
8.30 USC/LB 500 TONS ON ICE MCH'268.30 USC/LB 500 TONS ON ICE MAY 26- le premium
8.30 USC/LBs'applique a chaque composant
- Priorite:
structurante
BR-PT-009 - En linked currency, le premium est exprime dans la devise/unite liee
- Intent: respecter la facon dont les traders saisissent les prix sur certains
produits (ex: coton en
USC/LB). - Description:
- Quand
enable_linked_currencyest coche, lepremiumest saisi dans la devise / unite liee, pas dans la devise / unite native de la ligne. - Le systeme doit convertir ce premium vers le repere de la ligne pour les calculs internes de montant et de valuation.
- Quand
- Resultat attendu:
premiumest interprete dans le reperelinked_currency/linked_unit- le
unit_pricene doit pas absorber ce premium - les
amountet valuations doivent refleter ce premium converti - si
linked currencyest cochee,linked_price,linked_currencyetlinked_unitsont obligatoires
- Priorite:
structurante
BR-PT-010 - En basis + linked currency, le linked price suit le basis brut
- Intent: rendre lisible la decomposition entre prix basis de marche et premium.
- Description:
- Quand une ligne est en
basisetlinked currency, le bloclinked_pricedoit etre recalcule automatiquement. - Ce
linked_pricedoit representer le prix basis brut, hors premium. - Le
unit_pricede la ligne doit rester ce prix brut converti. - Le premium converti n'est ajoute qu'au niveau du
amount.
- Quand une ligne est en
- Resultat attendu:
- modification du basis -> mise a jour automatique du
linked_price linked_price= base market / basisunit_price=linked_priceconvertiamount= quantite * (unit_price+ premium converti)
- modification du basis -> mise a jour automatique du
- Priorite:
importante
BR-PT-011 - Une sale line non matchee avec lot virtuel doit generer une valuation sale-first des la validation
- Intent: ne pas attendre un matching purchase pour afficher le PnL d'une sale ouverte.
- Description:
- Lors de la validation d'une
sale.line, le systeme peut creer un lotvirtual. - Si aucun
lot.qtne relie ce lot a unepurchase.line, il faut tout de meme generer la valuation cote sale.
- Lors de la validation d'une
BR-PT-012 - Le wizard Create contracts peut creer un seul achat matche a plusieurs open sales
- Intent: permettre la creation d'un contrat achat unique a partir de plusieurs
lot.qtde vente selectionnes. - Description:
- En mode
matched, le wizardCreate contractspeut recevoir plusieurslot.qtselectionnes. - Il doit creer un seul contrat, avec une ligne par lot source selectionne.
- Chaque ligne doit conserver son lot d'origine pour le matching.
- En mode
- Resultat attendu:
- le wizard agrege les quantites de la selection
- il refuse une quantite saisie differente du total selectionne
- il conserve
created_by_code = Truesur les lignes creees pour ne pas declencher les creations automatiques parasites lors des validations
- Priorite:
importante
BR-PT-013 - Le texte par defaut de pricing_rule est configure globalement
- Intent: centraliser un texte metier recurrent reutilise a la creation des lignes achat et vente.
- Description:
- Le module expose un singleton
purchase_trade.configurationavec un champ textepricing_rule. - Toute nouvelle
purchase.lineetsale.linedoit prendre ce texte comme valeur par defaut depricing_rule.
- Le module expose un singleton
- Resultat attendu:
- la configuration est accessible depuis le menu
Prices - la valeur sert de defaut a la creation des lignes
- les lignes existantes ne sont pas modifiees retroactivement
- la configuration est accessible depuis le menu
- Priorite:
importante
BR-PT-014 - L'affectation d'un controller doit suivre l'ecart a l'objectif regional
- Intent: repartir les controllers selon les cibles definies dans l'onglet
Executiondesparty.party. - Description:
- chaque ligne
party.executionfixe une cible% targetedpour un controller sur unecountry.region - le
% achievedest calcule a partir desstock.shipment.indeja affectes a un controller dans cette zone - la zone d'un shipment est determinee par
shipment.to_location.country - une region parente couvre aussi ses sous-regions
- chaque ligne
- Resultat attendu:
- pour une ligne
party.execution,achieved_percent=shipments de la zone avec ce controller / shipments controles de la zone - le denominateur ne compte que les
stock.shipment.inqui ont deja uncontroller; les shipments encore non affectes ne biaisent donc pas la statistique affichee - lors d'un choix automatique de controller, la priorite va a la regle dont
l'ecart
targeted - achievedest le plus eleve - un controller a
80%cible et40%reel doit donc passer avant un controller a50%cible et45%reel sur la meme zone - l'appartenance a la zone se lit depuis
shipment.to_location.country, et une region parente couvre aussi ses sous-regions
- pour une ligne
- Priorite:
importante
BR-PT-014-bis - Les couts SLA controller peuvent cibler pays et/ou lieu
- Intent: permettre de definir le cout d'un controller soit pour un pays, soit pour une location, soit pour un couple pays + location.
- Description:
- Dans l'onglet
Executiondeparty.party, les lignes SLA (party.execution.place) peuvent porter:countrylocation- ou les deux.
- Lors de la creation automatique du fee controller sur un
stock.shipment.in, le systeme continue de partir deshipment.to_location. - Le pays utilise pour le matching est
shipment.to_location.country.
- Dans l'onglet
- Priorite de matching:
- couple
country + location - puis
locationseule - puis
countryseul
- couple
- Resultat attendu:
- un cout defini uniquement sur un pays s'applique a toutes les destinations de ce pays.
- un cout defini uniquement sur une location s'applique a cette destination, quel que soit le pays porte par la location.
- un cout defini sur le couple pays + location est le plus specifique et prime les deux autres.
- Priorite:
importante
BR-PT-015 - Les weight reports distants par lot partent du weight report global attache au shipment
- Intent: separer la creation du
weight.reportglobal et l'export detaille par lot vers le systeme distant. - Description:
- l'automation cree le
weight.reportglobal et l'attache austock.shipment.in - l'export FastAPI par lot ne part plus directement de l'automation
- l'utilisateur ouvre le
weight.reportvoulu depuis le shipment et lance l'action d'export depuis ce rapport
- l'automation cree le
- Resultat attendu:
- le rapport choisi sert de base unique pour calculer les payloads par lot
- seuls les lots physiques des
incoming_movesdu shipment sont exportes - l'action exige au minimum un
controlleret unreturned_idsur le shipment
- les cles renvoyees par le systeme distant et la date d'envoi sont
conservees sur le
weight.reportlocal - Priorite:
importante
BR-PT-016 - En pricing manuel, seules la quantite fixee du jour et le prix de marche sont saisis
- Intent: simplifier la saisie utilisateur et garantir une coherence unique
entre les colonnes de
pricing.pricing. - Description:
- Pour une ligne de
pricing.pricingen mode manuel, l'utilisateur ne doit saisir que:quantitysettl_price
- Les autres colonnes de suivi sont derivees automatiquement sur tout le
groupe metier (
line + componentousale_line + component) trie parpricing_date.
- Pour une ligne de
- Resultat attendu:
fixed_qt= cumul desquantityfixed_qt_price= moyenne ponderee cumulee dessettl_priceunfixed_qt= quantite de base de la ligne -fixed_qtunfixed_qt_price= dernier prix disponible de la courbe du composant quand le composant est lie a une courbe; sinon fallback sursettl_pricede la ligneeod_price= moyenne ponderee entre jambe fixee et non fixeelast=Truereste unique par groupe et suit la plus grandepricing_date
- Hors scope:
- la generation automatique des lignes quand
pricing.component.auto = Truene doit pas changer de comportement
- la generation automatique des lignes quand
- Priorite:
structurante
BR-PT-017 - Le workflow Validate des factures client doit aussi attribuer le numero
- Intent: aligner le comportement des factures client et fournisseur au moment
de
Validate. - Description:
- Lors du workflow
Validatesuraccount.invoice, une facture client (type = out) doit maintenant:- creer son
account.move - recevoir son
number
- creer son
- La numerotation ne doit plus etre repoussee au
Postcote client.
- Lors du workflow
- Resultat attendu:
- a l'issue de
Validate, une facture fournisseur ou client possede deja:- son
account.move - son
number
- son
Postconserve son role de posting comptable sans reintroduire de difference de session/fresh login cote client
- a l'issue de
- Priorite:
importante
- Resultat attendu:
- apres creation du lot virtuel, si aucun matching purchase n'existe:
- appeler
Valuation.generate_from_sale_line(line) - creer au moins la ligne
sale pricedfallback si la ligne porte un prix economique via le premium
- appeler
- apres creation du lot virtuel, si aucun matching purchase n'existe:
- Priorite:
importante
BR-PT-018 - Les contrats distinguent le compte bancaire tiers du compte bancaire compagnie
- Intent: eviter de confondre le compte bancaire du client/fournisseur avec le compte bancaire de la compagnie courante utilise pour encaisser ou payer.
- Description:
- Sur
sale.saleetpurchase.purchase,bank_accountrepresente le compte bancaire propre a lapartydu contrat. - Sur
sale.saleetpurchase.purchase,our_bank_accountrepresente le compte bancaire utilise par la compagnie courante pour encaisser ou payer. bank_accountest limite aux comptes bancaires de la party du contrat.our_bank_accountreste librement selectionnable parmi les comptes bancaires disponibles.
- Sur
- Resultat attendu:
- si plusieurs comptes existent, le compte dont la devise correspond a la devise du contrat est propose en priorite
- si aucun compte ne matche la devise, le premier compte disponible est propose
- le champ
Our Bank Accountest pre-rempli depuis les comptes de la compagnie quand possible, mais sa recherche n'est pas limitee a ces comptes
- Priorite:
importante
BR-PT-019 - Le padding de facture provisoire vente augmente la quantite facturee sans modifier le lot physique
- Intent: permettre de constituer une provision sur une facture provisoire vente tout en gardant la trace de l'ecart avec la quantite reelle du lot.
- Description:
- Le wizard
lot.invoiceexpose un padding global uniquement pour les factures provisoires cote vente. - Ce padding global est reparti egalement entre les lots selectionnes.
- La quantite de chaque ligne de facture provisoire vente est augmentee de la part de padding du lot.
- Le padding ne modifie pas la quantite physique du lot.
- Le wizard
- Resultat attendu:
- deux lots factures ensemble avec un padding global de
1000recoivent chacun500de padding - la ligne facture affiche la quantite augmentee
- la ligne facture expose
Inc. padding - le lot conserve sa part de padding dans
sale_invoice_padding
- deux lots factures ensemble avec un padding global de
- Validation comptable:
- au
Validate, le move principal de facture inclut deja le padding car il est integre aaccount.invoice.line.quantity - la provisoire cree un
additional_moveavec le couple de comptes configureDefault Sale Padding/Default Accrual 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
- au
- Priorite:
importante
BR-PT-012 - Fallback valuation basis sans summary: utiliser le prix economique de la ligne
- Intent: eviter qu'une valuation
basisouverte sorte a zero alors que la ligne a bien une valeur economique via le premium. - Description:
- Une ligne
basispeut ne pas avoir encore deprice_summary. - Dans ce cas, la valuation fallback ne doit pas prendre
unit_priceseul si celui-ci est brut et hors premium.
- Une ligne
- Resultat attendu:
- le fallback valuation
basisdoit utiliser:unit_price + premium converti
- cette regle vaut au minimum pour:
sale.linenon matcheepurchase.linesans summary
- le fallback valuation
- Priorite:
importante
BR-PT-013 - Create Contracts multi-lots doit conserver un matching par lot source
- Intent: permettre la creation d'un seul contrat mirror a partir de plusieurs open quantities sans perdre le lien lot-a-lot.
- Description:
- Le wizard
Create contractspeut etre lance avec plusieurslot.qtselectionnes. - En creation
matched, le systeme doit creer un seul contrat avec une ligne par lot source selectionne, et chaque ligne doit etre matchee avec son lot d'origine.
- Le wizard
- Resultat attendu:
- la quantite totale du wizard = somme des open quantities selectionnees
- le contrat cree porte plusieurs lignes si plusieurs lots source sont selectionnes
- chaque ligne creee reutilise le
shipment_originet le lot source qui lui correspondent created_by_codedoit rester positionne sur les lignes creees par wizard pour eviter la recreation automatique de lots virtuels dans lesvalidatedepurchase.line,sale.lineetlot.lot
- Priorite:
importante
BR-PT-014 - Delivery period: From doit rester inferieur ou egal a To
- Intent: eviter les periodes de livraison incoherentes sur les lignes achat et vente.
- Description:
- Les champs
from_deletto_delsont presents surpurchase.lineetsale.line. - Si les deux dates sont renseignees,
from_delne doit jamais etre posterieur ato_del.
- Les champs
- Resultat attendu:
- la sauvegarde d'une
purchase.lineousale.lineest bloquee sifrom_del > to_del - une date ouverte reste autorisee si seulement une des deux bornes est renseignee
- la sauvegarde d'une
- Priorite:
importante
BR-PT-015 - Pricing manuel: composant limite a la ligne courante
- Intent: eviter qu'une ligne de pricing saisie manuellement utilise un composant rattache a une autre ligne de contrat.
- Description:
- Dans l'onglet
Pricing datesd'unepurchase.line, le champpricing.pricing.price_componentdoit proposer uniquement les composants dontpricing.component.lineest la ligne achat courante. - Dans l'onglet
Pricing datesd'unesale.line, il doit proposer uniquement les composants dontpricing.component.sale_lineest la ligne vente courante. - Une ligne de pricing sans composant reste possible pour le mode manuel sans component.
- Dans l'onglet
- Resultat attendu:
- le domaine UI filtre les composants sur la ligne courante
- une validation serveur bloque aussi un composant appartenant a une autre ligne
- Priorite:
importante
BR-PT-016 - Les fees % rate utilisent le delta de financement
- Intent: aligner le calcul des frais financiers
% rateentre achat et vente. - Description:
- Pour un
fee.feeen moderate, le calcul ne depend pas dupayment_term, ni de la date du jour, ni defee_date. - La periode de calcul est directement le champ
fin_int_deltade la ligneEstimated dateavectrigger = bldate.
- Pour un
- Resultat attendu:
- purchase et sale appliquent la meme formule:
amount = unit_price * quantity * (price / 100) * fin_int_delta / 360 - le montant affiche sur
fee.feedoit rester non signe: unfin_int_deltanegatif ne doit pas afficher un fee negatif - le PnL applique seul le signe metier:
PAY=> montant negatifREC=> montant positif
- si aucune Estimated Date
bldaten'est renseignee, aucun montant% raten'est calcule
- purchase et sale appliquent la meme formule:
- Priorite:
importante
BR-PT-020 - Le solde lot.qt ouvert suit les lots physiques existants
- Intent: eviter qu'une hausse ou baisse de quantite contractuelle double le reliquat ouvert quand des lots physiques existent deja.
- Description:
- Lorsqu'une
purchase.line.quantity_theoricalousale.line.quantity_theoricalest modifiee, lelot.qtlibre ne doit pas etre ajuste uniquement par delta. - Le systeme doit recalculer le solde ouvert cible depuis la quantite
contractuelle, les lots physiques deja crees et les quantites deja
allouees dans des
lot.qtmatches ou shippes.
- Lorsqu'une
- Resultat attendu:
- quantite virtuelle cible =
quantity_theorical - somme(lots physiques convertis dans l'unite ligne) lot.qtlibre non matche / non shippe =quantite virtuelle cible - somme(lot.qt deja matches ou shippes)- si le resultat devient negatif, bloquer avec
Please unlink or unmatch lot - exemple: une ligne achat passee de
10000a20000avec deja10000physiques doit afficher10000ouverts et10000physiques, pas20000ouverts plus10000physiques.
- quantite virtuelle cible =
- Impacts fees:
- apres toute modification de
quantity_theorical, les fees de la ligne sont resynchronises avec la regle BR-PT-021. - si le fee est encore uniquement porte par le lot virtuel, sa quantite suit donc la nouvelle quantite virtuelle.
- si le fee possede deja des lots physiques, une hausse ou baisse uniquement ouverte n'impacte pas sa quantite.
- apres toute modification de
- Priorite:
structurante
BR-PT-021 - Les fees lies aux lots privilegient les physiques
- Intent: eviter qu'un fee cree sur un lot virtuel reste calcule sur la quantite contractuelle totale apres creation de lots physiques.
- Detail implementation / PnL:
- voir
modules/purchase_trade/docs/fees.md
- voir
- Description:
- Le lien
fee.lotsavec le 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 du fee et le virtuel est ignore pour la quantite.
- Le lien
- Resultat attendu:
- si aucun physique n'existe,
fee.quantitysuit le lot virtuel rattache au fee. - si des physiques existent,
fee.quantitysuit uniquement la somme des lots physiques rattaches au fee. - pour
ppack, la somme se fait surlot.lot_qtdes physiques. - pour les modes quantitatifs (
perqt,rate,pprice,pcost), la somme se fait sur les quantites courantes converties des lots physiques. - supprimer le dernier physique fait retomber le fee sur son lot virtuel, puisque le lien virtuel est conserve.
- le PnL fee applique la meme selection: full open tant qu'il n'y a que le virtuel, puis uniquement les lots physiques effectifs.
- si aucun physique n'existe,
- Points de synchronisation obligatoires:
- creation d'un fee lie a une ligne ou a un shipment
- ajout d'un lot physique dans
fee.lots - modification de
purchase.line.quantity_theoricalousale.line.quantity_theorical - weighing / modification de quantite d'un lot physique
- suppression d'un lot physique ou suppression d'un lien
fee.lots
- Regle de conception:
- ne pas supprimer le lien
fee.lotsvers le lot virtuel lors de la creation de physiques. - le virtuel reste le fallback permettant de revenir au cas ouvert si tous les physiques sont supprimes.
- toute logique metier, comptable ou PnL doit utiliser les lots effectifs du fee: physiques s'il y en a, sinon virtuels.
- ne pas supprimer le lien
- Priorite:
structurante
BR-PT-022 - Remove physical lot restaure le lot.qt ouvert avec son contexte
- Intent: permettre d'annuler un lot physique cree par erreur sans perdre le contexte metier de shipment ou de matching deja porte par ce lot.
- Description:
- L'action
Remove physical lotpeut supprimer un lot physique meme s'il est shippe, matche, ou les deux. - Dans ces cas, l'utilisateur doit recevoir un warning confirmable avant la suppression.
- La suppression n'est autorisee que si le
stock.movelie au lot physique est encore en etatdraft. - Si le
stock.moven'est pas endraft, l'action est bloquee car le flux peut deja avoir des impacts stock/comptables.
- L'action
- Resultat attendu:
- le
lot.moveet lestock.movecorrespondant sont supprimes avec le lot physique. - la quantite courante convertie du lot physique est restauree dans
lot.qt. - si le lot etait shippe, la ligne
lot.qtrestauree conserve le shipment (lot_shipment_in,lot_shipment_internaloulot_shipment_out). - si le lot etait matche, la ligne
lot.qtrestauree conserve le lot sale virtuel (lot_s). - si une ligne
lot.qtexiste deja avec le meme lot purchase virtuel, le meme shipment et le memelot_s, la quantite est agregee sur cette ligne plutot que de creer un doublon. - si aucune ligne compatible n'existe, une nouvelle ligne
lot.qtest creee.
- le
- Priorite:
importante
BR-PT-023 - Lots Management separe matching, side et shipping status
- Intent: rendre le rapport
lot.reportexploitable sans melanger le statut de matching, le sens achat/vente et l'avancement logistique. - Description:
- Le filtre
Matching statusne porte que le matching commercial:All,Matched,Not Matched. - Le filtre
Sidepermet de lire:All,Purchase,Sale. - Le filtre
Shipping statusporte uniquement le lienshipment_indulot.lotou dulot.qt. - Les dates de contexte
As ofetTofiltrent les contrats viapurchase.purchase_dateetsale.sale_date. - Si le filtre
SidevautPurchase, les bornes de dates s'appliquent aupurchase_date. - Si le filtre
SidevautSale, les bornes de dates s'appliquent ausale_date. - Si le filtre
SidevautAll, une ligne est conservee si sonpurchase_dateou sonsale_dateentre dans les bornes. - Le filtre
Dimensiondu rapport cible une valeur de dimension analytique rattachee au contrat achat ou vente viaanalytic.dimension.assignment. - Le filtre
Strategycible les strategies MTM rattachees aux lignes achat ou vente (purchase.strategy/sale.strategy).
- Le filtre
- Resultat attendu:
Unshipped: aucunshipment_inlie aulot.lotou aulot.qt.Scheduled: unshipment_inest lie et son etat estdraft.Shipped: unshipment_inest lie et son etat eststarted.Received: unshipment_inest lie et son etat estreceivedoudone.- Le rapport affiche une colonne
Shipping status. - Le rapport affiche
Purchase Delivery Perioddepuis la ligne achat etSale Delivery Perioddepuis la ligne vente; l'ancien champ mixteDel Periodne doit plus prendre la periode sale comme fallback achat.
- Shipment type:
- Sur
stock.shipment.inet danslot.report,Shipment TypevautDropshipsifrom_location.type = supplieretto_location.type = customer. - Dans tous les autres cas,
Shipment TypevautInbound.
- Sur
- Priorite:
importante
BR-PT-024 - Create Contracts propage les lieux stock selon le flux miroir
- Intent: eviter une ressaisie des lieux logistiques quand un contrat miroir est cree depuis une quantite ouverte.
- Description:
- Les champs concernes sont les
stock.locationfrom_locationetto_locationdes contrats achat et vente. - Si le contrat source est en flux direct fournisseur -> client
(
from_location.type = supplieretto_location.type = customer), le contrat cree reprend le meme couplefrom_location/to_location. - Ce flux correspond au mode
Dropshipaffiche sur les shipments. - Si un contrat vente est cree depuis un achat dont
to_location.type = storage, lefrom_locationde la vente est pre-rempli avec ceto_locationachat. - Si un contrat achat est cree depuis une vente dont
from_location.type = storage, leto_locationde l'achat est pre-rempli avec cefrom_locationvente.
- Les champs concernes sont les
- Resultat attendu:
- achat supplier -> customer vers vente:
sale.from_location = purchase.from_locationetsale.to_location = purchase.to_location. - vente supplier -> customer vers achat:
purchase.from_location = sale.from_locationetpurchase.to_location = sale.to_location. - achat vers stock puis vente:
sale.from_location = purchase.to_location. - vente depuis stock puis achat:
purchase.to_location = sale.from_location.
- achat supplier -> customer vers vente:
- Priorite:
importante
4) Exemples concrets
Exemple E1 - Augmentation simple
- Donnees:
ancienne quantity_theorical = 100nouvelle quantity_theorical = 120lot.qt ouvert = 40
- Attendu:
- lot
virtualaugmente de20 lot.qt ouvertpasse de40a60
- lot
Exemple E2 - Augmentation sans lot.qt ouvert
- Donnees:
ancienne quantity_theorical = 100nouvelle quantity_theorical = 110- aucun
lot.qtouvert
- Attendu:
- lot
virtualaugmente de10 - creation d'un
lot.qtouvert a10
- lot
Exemple E3 - Diminution possible
- Donnees:
ancienne quantity_theorical = 100nouvelle quantity_theorical = 90lot.qt ouvert = 25
- Attendu:
- lot
virtualdiminue de10 lot.qt ouvertpasse de25a15
- lot
Exemple E4 - Diminution impossible
- Donnees:
ancienne quantity_theorical = 100nouvelle quantity_theorical = 80lot.qt ouvert = 5
- Attendu:
- blocage avec
Please unlink or unmatch lot
- blocage avec
5) Impact code attendu
- Fichiers Python concernes:
modules/purchase_trade/purchase.pymodules/purchase_trade/lot.pymodules/purchase_trade/valuation.pymodules/purchase_trade/sale.py
6) Strategie de tests
Pour cette regle, couvrir au minimum:
- augmentation avec
lot.qtouvert existant - augmentation sans
lot.qtouvert - diminution possible
- diminution impossible avec erreur
- valuation purchase/sale sur lot physique matche
- valuation sale-first sur sale non matchee avec lot virtual
- valuation sale
basissansprice_summary - absence de MTM sur les fees
- premium en
priced - premium en
basis - premium en
linked currency - synchro
basis->linked_price->unit_price
7) Notes de fin de session
Session 2026-04-30 - PnL fees ouverts et % rate
- Les fees PnL ne doivent pas etre generes pour un lot ouvert / virtuel dont la quantite courante est a zero.
- Cette regle evite les lignes PnL residuelles avec
quantity = 0maisamount != 0, notamment pour les feesrate,ppacketlumpsum. - La logique est volontairement proche de
Mark as finished: quand le reliquat ouvert/virtuel n'est plus valorisable, ses fees ne le sont pas non plus. - Les lots physiques restent hors de ce filtre.
- Pour les fees
% rate,fin_int_deltaest la periode absolue de calcul. - Le calcul
% ratene depend plus de la date du jour, defee_date, ni deBL date + deltacomme date de fin. - Formule commune purchase/sale:
amount = unit_price * quantity * (price / 100) * fin_int_delta / 360. - La ligne
Estimated dateavectrigger = bldatesert a porterfin_int_delta; la date estimee n'entre pas dans le calcul du montant. - Le montant fee affiche reste toujours non signe, meme si
fin_int_deltaest negatif; le signe est applique uniquement en PnL viaPAY/REC.
Session 2026-05-09 - Lots Management et Apply matching
- Le rapport
Lots Managementsepare maintenant les filtres:Matching status,Shipping status,Side,DimensionetStrategy. - Les bornes
As of/Tofiltrent surpurchase.purchase_dateetsale.sale_dateuniquement quand elles sont renseignees; elles ne doivent pas filtrer par defaut a l'ouverture du report. - Le filtre
Dimensioncible une valeur dynamique de dimension analytique viaanalytic.dimension.assignment.value, pour les achats comme pour les ventes. - Le filtre
Strategycible les strategies MTM rattachees aux lignes achat ou vente (purchase.strategy/sale.strategy). Apply matchingdoit proposer les reliquats ouverts meme si le meme lot purchase ou sale possede deja une autre lignelot.qtmatchee.- Une ligne est exclue ou bloquee dans
Apply matchingseulement si la lignelot.qtselectionnee est elle-meme deja matchee:- cote purchase:
lot_prenseigne etlot_svide - cote sale:
lot_srenseigne etlot_pvide
- cote purchase:
- Exemple: un achat de
1000avec800deja matches et200encore ouverts doit laisser le reliquat200selectionnable dansApply matching. - Dans
Link to transport, le type de shipment reste impose aShipment In; l'utilisateur ne doit pas pouvoir basculer vers Out/Internal depuis cette action. Mark as finisheddansLots Managementmasque uniquement les reliquats ouverts / virtuels portes parlot.qt.- Le filtre doit regarder les deux cotes presents sur la ligne
lot.qt:lot_p.line.finishedcote purchase etlot_s.sale_line.finishedcote sale, meme quand le filtreSidevautAll. - Les lots physiques ne doivent pas etre masques par ce flag; ils restent visibles pour conserver l'historique execute.
Session 2026-05-17 - Tolerances, jauges et Go to matching
Apply matchingest remplace cote utilisateur parGo to matching; l'action legacy est conservee mais masquee.Go to matchingprecharge les ligneslot.qtouvertes selectionnees depuisLots Management.- Le matching ouvert peut depasser le solde ouvert strict si la quantite
projetee reste dans la tolerance de la ligne (
Qt max). - Les lignes de matching affichent
Qt min,Qt maxet une jaugeTolerance used. - Dans
Go to matching, la jauge est dynamique: elle projettedeja matche + Qt to matchcontrequantity_theorical, avec bornes-tol_min/tol_max. - Les jauges de
purchase.lineetsale.lineutilisent:- la somme des lots physiques s'il en existe sur la ligne;
- sinon la somme des
lot.qtrattaches a la ligne.
- Les jauges header
purchase.purchase/sale.salerepresentent la moyenne ponderee des jauges de lignes, au prorata dequantity_theorical. - Cote
sale, les quantiteslot.qtsont lues en valeur absolue pour les jauges de tolerance. - Le widget SAO
tolerance_gaugeest autorise dans les schemas de vuesformettreeavecmin,max,min_field,max_field,centeretdigits. - En formulaire de ligne (
purchase.line/sale.line), ne pas encapsulertolerance_gaugeettargeted_qtdans un sous-groupecolspan="4": cela decale visuellement le bloc vers la droite dans SAO. Les placer directement dans la grille principale et forcerxalign="0"sur la jauge et surtargeted_qtpour garder un alignement a gauche stable.