Pricing matrix
This commit is contained in:
@@ -33,7 +33,7 @@ des lignes achat / vente.
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matrix</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">La matrix existe dans le modele mais n'est pas encore le moteur principal du fixing automatique.</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Une matrix peut alimenter un composant de fixing, avec ligne generique ou conditions.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pricing period</td>
|
||||
@@ -228,6 +228,14 @@ Source: `BR-PT-016` et doublon historique `BR-PT-015`
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">L'utilisateur choisit une courbe, un calendrier et active <code>Auto</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Source curve</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le systeme lit les prix depuis <code>price_index</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Source matrix</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Le systeme lit les prix depuis <code>price_matrix.line</code>.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Trigger</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">L'utilisateur definit une periode de prix et une periode d'application.</td>
|
||||
</tr>
|
||||
@@ -255,6 +263,10 @@ Source: `BR-PT-016` et doublon historique `BR-PT-015`
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Cut-off</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>Pricing date max</code> arrete la generation apres cette date.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Ratio negatif</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Un ratio <code>-100%</code> soustrait le composant du prix basis.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -307,6 +319,46 @@ Summary: derniere ligne marquee last</code></pre>
|
||||
<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Exemple simple</strong><span>Pricing <code>01/04 -> 05/04</code>, Application vide. Le systeme lit les prix du 1 au 5 avril et cree les applications sur ces memes dates, sauf jours exclus par le calendrier si un calendrier est renseigne.</span></div>
|
||||
|
||||
|
||||
### BR-PT-PRI-006 - Matrix de premium ou spread
|
||||
|
||||
<table style="width:100%; border-collapse:collapse; margin:1rem 0 1.5rem 0; font-size:0.95rem;">
|
||||
<thead>
|
||||
<tr style="background:#eef3ff;">
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Cas</th>
|
||||
<th style="text-align:left; padding:0.55rem 0.7rem; border-bottom:2px solid #4051b5;">Regle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matrix generique</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Une ligne matrix sans origin, destination, product ni quality s'applique comme fallback global.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matrix conditionnelle</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Une ligne avec origin, destination, product ou quality est prioritaire si elle correspond a la ligne trade.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Prix</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>price_value</code> est converti vers l'unite / devise de la ligne si la matrix les renseigne.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Validite</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>valid_from</code> et <code>valid_to</code> limitent la periode d'utilisation de la matrix.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Calendrier</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Si le composant n'a pas de calendrier, le calendrier de la matrix est utilise.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Soustraction</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Pour soustraire un premium, mettre le ratio du composant a <code>-100%</code>.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="border-left:0.28rem solid #4051b5; background:#f5f7ff; padding:0.85rem 1rem; margin:1rem 0 1.4rem 0; border-radius:0.35rem;"><strong style="display:block; margin-bottom:0.35rem;">Usage premium simple</strong><span>Creer une matrix avec une ligne sans condition et <code>price_value = 45</code>, puis ajouter un composant de type <code>Matrix</code> sur la ligne trade. Avec un ratio <code>-100%</code>, ce composant diminue le prix basis de 45 dans le repere de la ligne.</span></div>
|
||||
|
||||
|
||||
## Section developpeur
|
||||
|
||||
### Modeles pivots
|
||||
@@ -389,7 +441,7 @@ Summary: derniere ligne marquee last</code></pre>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Matrix</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Disponible dans le modele et le MTM, pas encore completement integree au fixing auto.</td>
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;">Disponible dans le fixing auto, avec fallback generique et priorite aux lignes conditionnelles.</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #e0e0e0;">
|
||||
<td style="vertical-align:top; padding:0.5rem 0.7rem;"><code>prDate</code> / <code>apPrice</code></td>
|
||||
|
||||
@@ -20,7 +20,7 @@ des lignes achat / vente.
|
||||
| --- | --- |
|
||||
| Composant | Un `pricing.component` appartient a une ligne achat ou vente. |
|
||||
| Courbe | La courbe principale est `price_index`. |
|
||||
| Matrix | La matrix existe dans le modele mais n'est pas encore le moteur principal du fixing automatique. |
|
||||
| Matrix | Une matrix peut alimenter un composant de fixing, avec ligne generique ou conditions. |
|
||||
| Pricing period | Definit les dates ou lire les prix sur la courbe. |
|
||||
| Application period | Definit les dates ou appliquer les tranches de fixing. |
|
||||
| AVG | Utilise la moyenne progressive des prix lus. |
|
||||
@@ -105,6 +105,8 @@ Source: `BR-PT-016` et doublon historique `BR-PT-015`
|
||||
| Etape | Effet metier |
|
||||
| --- | --- |
|
||||
| Composant | L'utilisateur choisit une courbe, un calendrier et active `Auto`. |
|
||||
| Source curve | Le systeme lit les prix depuis `price_index`. |
|
||||
| Source matrix | Le systeme lit les prix depuis `price_matrix.line`. |
|
||||
| Trigger | L'utilisateur definit une periode de prix et une periode d'application. |
|
||||
| Lecture prix | Le systeme lit les prix de la courbe sur les dates de pricing. |
|
||||
| Application | Le systeme cree une ligne de pricing par date d'application. |
|
||||
@@ -112,6 +114,7 @@ Source: `BR-PT-016` et doublon historique `BR-PT-015`
|
||||
| Moyenne | Si `AVG` est coche, le prix fixe utilise la moyenne progressive. |
|
||||
| Fallback prix | Si `LAST` est coche, la courbe peut reprendre le dernier prix disponible. |
|
||||
| Cut-off | `Pricing date max` arrete la generation apres cette date. |
|
||||
| Ratio negatif | Un ratio `-100%` soustrait le composant du prix basis. |
|
||||
|
||||
```text
|
||||
Prix lus: P1 P2 P3 P4
|
||||
@@ -137,6 +140,22 @@ Summary: derniere ligne marquee last
|
||||
> 5 avril et cree les applications sur ces memes dates, sauf jours exclus par le
|
||||
> calendrier si un calendrier est renseigne.
|
||||
|
||||
### BR-PT-PRI-006 - Matrix de premium ou spread
|
||||
|
||||
| Cas | Regle |
|
||||
| --- | --- |
|
||||
| Matrix generique | Une ligne matrix sans origin, destination, product ni quality s'applique comme fallback global. |
|
||||
| Matrix conditionnelle | Une ligne avec origin, destination, product ou quality est prioritaire si elle correspond a la ligne trade. |
|
||||
| Prix | `price_value` est converti vers l'unite / devise de la ligne si la matrix les renseigne. |
|
||||
| Validite | `valid_from` et `valid_to` limitent la periode d'utilisation de la matrix. |
|
||||
| Calendrier | Si le composant n'a pas de calendrier, le calendrier de la matrix est utilise. |
|
||||
| Soustraction | Pour soustraire un premium, mettre le ratio du composant a `-100%`. |
|
||||
|
||||
> **Usage premium simple**
|
||||
> Creer une matrix avec une ligne sans condition et `price_value = 45`, puis
|
||||
> ajouter un composant de type `Matrix` sur la ligne trade. Avec un ratio
|
||||
> `-100%`, ce composant diminue le prix basis de 45 dans le repere de la ligne.
|
||||
|
||||
## Section developpeur
|
||||
|
||||
### Modeles pivots
|
||||
@@ -175,7 +194,7 @@ Summary: derniere ligne marquee last
|
||||
| `fallback` composant | Champ present, logique metier encore portee par `trigger.last`. |
|
||||
| `last` trigger | Signifie dernier prix disponible sur la curve. |
|
||||
| `last` pricing | Signifie derniere ligne du groupe pour le summary. |
|
||||
| Matrix | Disponible dans le modele et le MTM, pas encore completement integree au fixing auto. |
|
||||
| Matrix | Disponible dans le fixing auto, avec fallback generique et priorite aux lignes conditionnelles. |
|
||||
| `prDate` / `apPrice` | Variables intermediaires historiques peu exploitees. |
|
||||
| AVG | Moyenne progressive calculee sur la liste des prix lus. |
|
||||
|
||||
|
||||
@@ -709,25 +709,121 @@ class Component(ModelSQL, ModelView):
|
||||
triggers = fields.One2Many('pricing.trigger','component',"Period rules")
|
||||
pricing_date = fields.Date("Pricing date max")
|
||||
|
||||
def get_rec_name(self, name=None):
|
||||
if self.price_index:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
||||
else:
|
||||
return '[' + self.fix_type.name + '] '
|
||||
|
||||
def get_cur(self,name):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
|
||||
def get_nbdays(self, name):
|
||||
days = 0
|
||||
if self.triggers:
|
||||
for t in self.triggers:
|
||||
l,l2 = t.getApplicationListDates(self.calendar)
|
||||
days += len(l)
|
||||
return days
|
||||
def get_rec_name(self, name=None):
|
||||
if self.price_index:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
||||
if self.price_matrix:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_matrix.name
|
||||
else:
|
||||
return '[' + self.fix_type.name + '] '
|
||||
|
||||
def get_cur(self,name):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
|
||||
def get_calendar(self):
|
||||
if self.calendar:
|
||||
return self.calendar
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.calendar
|
||||
|
||||
@staticmethod
|
||||
def _record_id(record):
|
||||
return getattr(record, 'id', record)
|
||||
|
||||
@classmethod
|
||||
def _matches_optional_field(cls, matrix_line, field_name, target):
|
||||
value = getattr(matrix_line, field_name, None)
|
||||
if not value:
|
||||
return True, 0
|
||||
if target and cls._record_id(value) == cls._record_id(target):
|
||||
return True, 1
|
||||
return False, 0
|
||||
|
||||
def _matrix_valid_on(self, price_date):
|
||||
if not self.price_matrix:
|
||||
return False
|
||||
if hasattr(price_date, 'date'):
|
||||
price_date = price_date.date()
|
||||
valid_from = getattr(self.price_matrix, 'valid_from', None)
|
||||
valid_to = getattr(self.price_matrix, 'valid_to', None)
|
||||
if valid_from and price_date < valid_from:
|
||||
return False
|
||||
if valid_to and price_date > valid_to:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_matrix_owner_values(self):
|
||||
line = getattr(self, 'line', None) or getattr(self, 'sale_line', None)
|
||||
document = getattr(line, 'purchase', None) or getattr(line, 'sale', None)
|
||||
return {
|
||||
'origin': getattr(document, 'from_location', None),
|
||||
'destination': getattr(document, 'to_location', None),
|
||||
'product': getattr(line, 'product', None),
|
||||
'quality': getattr(line, 'quality', None),
|
||||
}
|
||||
|
||||
def _get_matrix_line(self):
|
||||
MatrixLine = Pool().get('price.matrix.line')
|
||||
lines = MatrixLine.search([('matrix', '=', self.price_matrix.id)])
|
||||
owner_values = self._get_matrix_owner_values()
|
||||
best_line = None
|
||||
best_score = -1
|
||||
for line in lines:
|
||||
score = 0
|
||||
for field_name, target in owner_values.items():
|
||||
matches, field_score = self._matches_optional_field(
|
||||
line, field_name, target)
|
||||
if not matches:
|
||||
break
|
||||
score += field_score
|
||||
else:
|
||||
if score > best_score:
|
||||
best_line = line
|
||||
best_score = score
|
||||
return best_line
|
||||
|
||||
def _convert_matrix_price(self, price, unit, currency):
|
||||
price_qt = Decimal(str(price or 0))
|
||||
matrix_unit = getattr(self.price_matrix, 'unit', None)
|
||||
matrix_currency = getattr(self.price_matrix, 'currency', None)
|
||||
if matrix_unit and unit:
|
||||
Uom = Pool().get('product.uom')
|
||||
price_qt *= Decimal(str(
|
||||
Uom.compute_qty(unit, float(1), matrix_unit)))
|
||||
if matrix_currency and currency and currency != matrix_currency:
|
||||
Currency = Pool().get('currency.currency')
|
||||
rates = Currency._get_rate([matrix_currency])
|
||||
rate = rates.get(matrix_currency.id)
|
||||
if rate:
|
||||
price_qt *= Decimal(str(rate))
|
||||
return round(price_qt, 4)
|
||||
|
||||
def get_price(self, price_date, unit, currency, last=False):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.get_price(price_date, unit, currency, last)
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
if not self._matrix_valid_on(price_date):
|
||||
return Decimal(0)
|
||||
line = self._get_matrix_line()
|
||||
if line:
|
||||
return self._convert_matrix_price(
|
||||
line.price_value, unit, currency)
|
||||
return Decimal(0)
|
||||
|
||||
def get_nbdays(self, name):
|
||||
days = 0
|
||||
if self.triggers:
|
||||
for t in self.triggers:
|
||||
l,l2 = t.getApplicationListDates(self.get_calendar())
|
||||
days += len(l)
|
||||
return days
|
||||
|
||||
@classmethod
|
||||
def delete(cls, components):
|
||||
@@ -1171,17 +1267,20 @@ class Trigger(ModelSQL,ModelView):
|
||||
current_date += datetime.timedelta(days=1)
|
||||
return l, lprice
|
||||
|
||||
def getprice(self,current_date):
|
||||
PI = Pool().get('price.price')
|
||||
PC = Pool().get('pricing.component')
|
||||
pc = PC(self.component)
|
||||
pi = PI(pc.price_index)
|
||||
val = {}
|
||||
val['date'] = current_date
|
||||
val['price'] = pi.get_price(current_date,pc.line.unit if pc.line else pc.sale_line.unit,pc.line.currency if pc.line else pc.sale_line.currency,self.last)
|
||||
val['avg'] = val['price']
|
||||
val['avg_minus_1'] = val['price']
|
||||
val['isAvg'] = self.average
|
||||
def getprice(self,current_date):
|
||||
PC = Pool().get('pricing.component')
|
||||
pc = PC(self.component)
|
||||
val = {}
|
||||
val['date'] = current_date
|
||||
line = pc.line if pc.line else pc.sale_line
|
||||
val['price'] = pc.get_price(
|
||||
current_date,
|
||||
line.unit,
|
||||
line.currency,
|
||||
self.last)
|
||||
val['avg'] = val['price']
|
||||
val['avg_minus_1'] = val['price']
|
||||
val['isAvg'] = self.average
|
||||
return val
|
||||
|
||||
class Period(ModelSQL,ModelView):
|
||||
|
||||
@@ -219,7 +219,7 @@ class Summary(ModelSQL,ModelView):
|
||||
Max(pp.fixed_qt+pp.unfixed_qt).as_('quantity'),
|
||||
Max(pp.fixed_qt).as_('fixed_qt'),
|
||||
(Min(pp.unfixed_qt)).as_('unfixed_qt'),
|
||||
(Max(Case((pp.last, pp.eod_price),else_=0)) * Max(pc.ratio / 100)).as_('price'),
|
||||
Max(Case((pp.last, pp.eod_price),else_=0)).as_('price'),
|
||||
(Max(pp.fixed_qt)/Max(pp.fixed_qt+pp.unfixed_qt)).as_('progress'),
|
||||
Max(pc.ratio).as_('ratio'),
|
||||
#where=wh,
|
||||
@@ -2120,18 +2120,19 @@ class Line(metaclass=PoolMeta):
|
||||
Pricing._sync_manual_last(pricings)
|
||||
Pricing._sync_eod_price(pricings)
|
||||
|
||||
if pc.triggers and pc.auto:
|
||||
prDate = []
|
||||
prPrice = []
|
||||
apDate = []
|
||||
apPrice = []
|
||||
for t in pc.triggers:
|
||||
prD, prP = t.getPricingListDates(pc.calendar)
|
||||
apD, apP = t.getApplicationListDates(pc.calendar)
|
||||
prDate.extend(prD)
|
||||
prPrice.extend(prP)
|
||||
apDate.extend(apD)
|
||||
apPrice.extend(apP)
|
||||
if pc.triggers and pc.auto:
|
||||
prDate = []
|
||||
prPrice = []
|
||||
apDate = []
|
||||
apPrice = []
|
||||
calendar = pc.get_calendar()
|
||||
for t in pc.triggers:
|
||||
prD, prP = t.getPricingListDates(calendar)
|
||||
apD, apP = t.getApplicationListDates(calendar)
|
||||
prDate.extend(prD)
|
||||
prPrice.extend(prP)
|
||||
apDate.extend(apD)
|
||||
apPrice.extend(apP)
|
||||
if pc.quota:
|
||||
prPrice = self.get_avg(prPrice)
|
||||
self.generate_pricing(pc,apDate,prPrice)
|
||||
@@ -2208,10 +2209,11 @@ class Line(metaclass=PoolMeta):
|
||||
p.fixed_qt = base_quantity
|
||||
if price > 0:
|
||||
p.unfixed_qt_price = price
|
||||
else:
|
||||
pr = Decimal(pc.price_index.get_price(p.pricing_date,self.unit,self.purchase.currency,True))
|
||||
pr = round(pr,4)
|
||||
p.unfixed_qt_price = pr
|
||||
else:
|
||||
pr = Decimal(pc.get_price(
|
||||
p.pricing_date, self.unit, self.purchase.currency, True))
|
||||
pr = round(pr,4)
|
||||
p.unfixed_qt_price = pr
|
||||
p.eod_price = p.get_eod_price_purchase()
|
||||
if (index == len(dl)-1) or (pc.pricing_date and (index < len(dl)-1 and dl_sorted[index+1].date() > pc.pricing_date)):
|
||||
p.last = True
|
||||
|
||||
@@ -1894,9 +1894,10 @@ class SaleLine(metaclass=PoolMeta):
|
||||
prPrice = []
|
||||
apDate = []
|
||||
apPrice = []
|
||||
calendar = pc.get_calendar()
|
||||
for t in pc.triggers:
|
||||
prD, prP = t.getPricingListDates(pc.calendar)
|
||||
apD, apP = t.getApplicationListDates(pc.calendar)
|
||||
prD, prP = t.getPricingListDates(calendar)
|
||||
apD, apP = t.getApplicationListDates(calendar)
|
||||
prDate.extend(prD)
|
||||
prPrice.extend(prP)
|
||||
apDate.extend(apD)
|
||||
@@ -1981,7 +1982,8 @@ class SaleLine(metaclass=PoolMeta):
|
||||
logger.info("GENERATE_1:%s",price)
|
||||
p.unfixed_qt_price = price
|
||||
else:
|
||||
pr = Decimal(pc.price_index.get_price(p.pricing_date,self.unit,self.sale.currency,True))
|
||||
pr = Decimal(pc.get_price(
|
||||
p.pricing_date, self.unit, self.sale.currency, True))
|
||||
pr = round(pr,4)
|
||||
logger.info("GENERATE_2:%s",pr)
|
||||
p.unfixed_qt_price = pr
|
||||
|
||||
@@ -1148,8 +1148,12 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
pricing_model = Mock()
|
||||
pricing_model.search.return_value = []
|
||||
pc_sale = Mock(id=1, quota_sale=Decimal('2'), pricing_date=None, price_index=Mock(get_price=Mock(return_value=Decimal('1'))))
|
||||
pc_purchase = Mock(id=1, quota=Decimal('3'), pricing_date=None, price_index=Mock(get_price=Mock(return_value=Decimal('1'))))
|
||||
pc_sale = Mock(
|
||||
id=1, quota_sale=Decimal('2'), pricing_date=None,
|
||||
get_price=Mock(return_value=Decimal('1')))
|
||||
pc_purchase = Mock(
|
||||
id=1, quota=Decimal('3'), pricing_date=None,
|
||||
get_price=Mock(return_value=Decimal('1')))
|
||||
|
||||
with patch('trytond.modules.purchase_trade.sale.Pool') as SalePool, patch(
|
||||
'trytond.modules.purchase_trade.purchase.Pool') as PurchasePool:
|
||||
@@ -1649,6 +1653,85 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(trigger.from_a, datetime.date(2026, 4, 1))
|
||||
self.assertEqual(trigger.to_a, datetime.date(2026, 4, 30))
|
||||
|
||||
def test_pricing_component_matrix_returns_generic_line_price(self):
|
||||
'matrix pricing can use an unconditional matrix line as a component price'
|
||||
Component = Pool().get('pricing.component')
|
||||
component = Component()
|
||||
component.price_source_type = 'matrix'
|
||||
component.price_matrix = Mock(
|
||||
id=10,
|
||||
valid_from=datetime.date(2026, 1, 1),
|
||||
valid_to=datetime.date(2026, 12, 31),
|
||||
unit=None,
|
||||
currency=None,
|
||||
)
|
||||
component.line = Mock(
|
||||
product=Mock(),
|
||||
purchase=Mock(from_location=Mock(), to_location=Mock()))
|
||||
component.sale_line = None
|
||||
matrix_line = Mock(
|
||||
origin=None,
|
||||
destination=None,
|
||||
product=None,
|
||||
quality=None,
|
||||
price_value=Decimal('45'))
|
||||
matrix_model = Mock(search=Mock(return_value=[matrix_line]))
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as PricingPool:
|
||||
PricingPool.return_value.get.return_value = matrix_model
|
||||
price = component.get_price(
|
||||
datetime.date(2026, 4, 1), Mock(), Mock(), False)
|
||||
|
||||
self.assertEqual(price, Decimal('45.0000'))
|
||||
|
||||
def test_pricing_component_matrix_prefers_specific_line_over_generic(self):
|
||||
'matrix pricing keeps generic lines as fallback below matching lines'
|
||||
Component = Pool().get('pricing.component')
|
||||
origin = Mock(id=1)
|
||||
component = Component()
|
||||
component.price_source_type = 'matrix'
|
||||
component.price_matrix = Mock(
|
||||
id=10,
|
||||
valid_from=None,
|
||||
valid_to=None,
|
||||
unit=None,
|
||||
currency=None,
|
||||
)
|
||||
component.line = Mock(
|
||||
product=Mock(id=20),
|
||||
purchase=Mock(from_location=origin, to_location=Mock(id=2)))
|
||||
component.sale_line = None
|
||||
generic = Mock(
|
||||
origin=None,
|
||||
destination=None,
|
||||
product=None,
|
||||
quality=None,
|
||||
price_value=Decimal('45'))
|
||||
specific = Mock(
|
||||
origin=origin,
|
||||
destination=None,
|
||||
product=None,
|
||||
quality=None,
|
||||
price_value=Decimal('43'))
|
||||
matrix_model = Mock(search=Mock(return_value=[generic, specific]))
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as PricingPool:
|
||||
PricingPool.return_value.get.return_value = matrix_model
|
||||
price = component.get_price(
|
||||
datetime.date(2026, 4, 1), Mock(), Mock(), False)
|
||||
|
||||
self.assertEqual(price, Decimal('43.0000'))
|
||||
|
||||
def test_pricing_component_matrix_uses_matrix_calendar_fallback(self):
|
||||
'matrix calendar is used when the component has no own calendar'
|
||||
Component = Pool().get('pricing.component')
|
||||
calendar = Mock()
|
||||
component = Component()
|
||||
component.calendar = None
|
||||
component.price_matrix = Mock(calendar=calendar)
|
||||
|
||||
self.assertEqual(component.get_calendar(), calendar)
|
||||
|
||||
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
|
||||
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
<field name="currency"/>
|
||||
<label name="calendar"/>
|
||||
<field name="calendar"/>
|
||||
<newline/>
|
||||
<label name="valid_from"/>
|
||||
<field name="valid_from"/>
|
||||
<label name="valid_to"/>
|
||||
<field name="valid_to"/>
|
||||
<newline/>
|
||||
<field name="lines" colspan="4"/>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user