25.03.26
This commit is contained in:
113
modules/purchase_trade/docs/business-rules.md
Normal file
113
modules/purchase_trade/docs/business-rules.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Business Rules - Purchase Trade
|
||||
|
||||
Statut: `draft`
|
||||
Version: `v0.1`
|
||||
Derniere mise a jour: `2026-03-25`
|
||||
Owner metier: `a completer`
|
||||
Owner technique: `a completer`
|
||||
|
||||
## 1) Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Hors scope:
|
||||
- Modules impactes:
|
||||
- `purchase_trade`
|
||||
- `lot`
|
||||
|
||||
## 2) Glossaire
|
||||
|
||||
- `Purchase Line`: ligne d'achat.
|
||||
- `quantity_theorical`: quantite theorique contractuelle de la ligne.
|
||||
- `Virtual Lot`: lot unique de type `virtual` rattache a une `purchase.line`.
|
||||
- `lot.qt`: table des quantites ouvertes, matchées ou shippées par lot.
|
||||
- `lot.qt ouvert`: enregistrement `lot.qt` avec `lot_p = virtual lot`, `lot_s = None` et 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_theorical` est 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 `virtual` rattache a la `purchase.line`.
|
||||
- Conditions d'entree:
|
||||
- Une `purchase.line` existe deja.
|
||||
- Son champ `quantity_theorical` est modifie via `write`.
|
||||
- Un lot `virtual` est rattache a la ligne.
|
||||
- Resultat attendu:
|
||||
- Si `delta > 0`:
|
||||
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
|
||||
- augmenter le `lot.qt` ouvert existant
|
||||
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
|
||||
- Si `delta < 0`:
|
||||
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
|
||||
- diminuer la quantite courante du lot `virtual` du meme delta
|
||||
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
|
||||
- Definition du `lot.qt` ouvert:
|
||||
- `lot_p = virtual lot`
|
||||
- `lot_s = None`
|
||||
- `lot_shipment_in = None`
|
||||
- `lot_shipment_internal = None`
|
||||
- `lot_shipment_out = None`
|
||||
- Exceptions:
|
||||
- si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien
|
||||
- Priorite:
|
||||
- `bloquante`
|
||||
- Source:
|
||||
- `Decision metier documentee dans les commentaires de purchase_trade.purchase.Line.write`
|
||||
|
||||
## 4) Exemples concrets
|
||||
|
||||
### Exemple E1 - Augmentation simple
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 120`
|
||||
- `lot.qt ouvert = 40`
|
||||
- Attendu:
|
||||
- lot `virtual` augmente de `20`
|
||||
- `lot.qt ouvert` passe de `40` a `60`
|
||||
|
||||
### Exemple E2 - Augmentation sans lot.qt ouvert
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 110`
|
||||
- aucun `lot.qt` ouvert
|
||||
- Attendu:
|
||||
- lot `virtual` augmente de `10`
|
||||
- creation d'un `lot.qt` ouvert a `10`
|
||||
|
||||
### Exemple E3 - Diminution possible
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 90`
|
||||
- `lot.qt ouvert = 25`
|
||||
- Attendu:
|
||||
- lot `virtual` diminue de `10`
|
||||
- `lot.qt ouvert` passe de `25` a `15`
|
||||
|
||||
### Exemple E4 - Diminution impossible
|
||||
|
||||
- Donnees:
|
||||
- `ancienne quantity_theorical = 100`
|
||||
- `nouvelle quantity_theorical = 80`
|
||||
- `lot.qt ouvert = 5`
|
||||
- Attendu:
|
||||
- blocage avec `Please unlink or unmatch lot`
|
||||
|
||||
## 5) Impact code attendu
|
||||
|
||||
- Fichiers Python concernes:
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- `modules/purchase_trade/lot.py`
|
||||
|
||||
## 6) Strategie de tests
|
||||
|
||||
Pour cette regle, couvrir au minimum:
|
||||
|
||||
- augmentation avec `lot.qt` ouvert existant
|
||||
- augmentation sans `lot.qt` ouvert
|
||||
- diminution possible
|
||||
- diminution impossible avec erreur
|
||||
@@ -333,20 +333,25 @@ class Fee(ModelSQL,ModelView):
|
||||
elif self.mode == 'rate':
|
||||
#take period with estimated trigger date
|
||||
if self.line:
|
||||
if self.line.purchase.payment_term:
|
||||
if self.line.estimated_date:
|
||||
beg_date = self.fee_date if self.fee_date else Date.today()
|
||||
est_date = self.line.purchase.payment_term.lines[0].get_date(beg_date,self.line)
|
||||
if est_date and beg_date:
|
||||
factor = InterestCalculator.calculate(
|
||||
start_date=beg_date,
|
||||
end_date=est_date,
|
||||
rate=self.price/100,
|
||||
rate_type='annual',
|
||||
convention='ACT/360',
|
||||
compounding='simple'
|
||||
est_lines = [dd for dd in self.line.estimated_date if dd.trigger == 'bldate']
|
||||
est_line = est_lines[0] if est_lines else None
|
||||
if est_line and est_line.estimated_date:
|
||||
est_date = est_line.estimated_date + datetime.timedelta(
|
||||
days=est_line.fin_int_delta or 0
|
||||
)
|
||||
if est_date and beg_date:
|
||||
factor = InterestCalculator.calculate(
|
||||
start_date=beg_date,
|
||||
end_date=est_date,
|
||||
rate=self.price/100,
|
||||
rate_type='annual',
|
||||
convention='ACT/360',
|
||||
compounding='simple'
|
||||
)
|
||||
|
||||
return round(factor * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2)
|
||||
return round(factor * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2)
|
||||
if self.sale_line:
|
||||
if self.sale_line.sale.payment_term:
|
||||
beg_date = self.fee_date if self.fee_date else Date.today()
|
||||
|
||||
@@ -56,6 +56,7 @@ class Estimated(ModelSQL, ModelView):
|
||||
|
||||
trigger = fields.Selection(TRIGGERS,"Trigger")
|
||||
estimated_date = fields.Date("Estimated date")
|
||||
fin_int_delta = fields.Integer("Financing interests delta")
|
||||
|
||||
class MtmScenario(ModelSQL, ModelView):
|
||||
"MtM Scenario"
|
||||
|
||||
@@ -1225,7 +1225,6 @@ class Line(metaclass=PoolMeta):
|
||||
old_values = {}
|
||||
|
||||
for records, values in zip(args[::2], args[1::2]):
|
||||
logger.info("***WRITE***:%s",values)
|
||||
if 'quantity_theorical' in values:
|
||||
for record in records:
|
||||
old_values[record.id] = record.quantity_theorical
|
||||
@@ -1238,7 +1237,6 @@ class Line(metaclass=PoolMeta):
|
||||
old = Decimal(old_values[line.id] or 0)
|
||||
new = Decimal(line.quantity_theorical or 0)
|
||||
delta = new - old
|
||||
logger.info("***WRITE_DELTA***:%s",delta)
|
||||
if delta > 0:
|
||||
virtual_lots = [lot for lot in (line.lots or []) if lot.lot_type == 'virtual']
|
||||
if not virtual_lots:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<tree editable="1">
|
||||
<field name="trigger"/>
|
||||
<field name="estimated_date"/>
|
||||
<field name="fin_int_delta"/>
|
||||
</tree>
|
||||
|
||||
Reference in New Issue
Block a user