ITSA points 28.06
This commit is contained in:
@@ -9,7 +9,7 @@ from unittest.mock import ANY, Mock, call, patch
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval
|
||||
from trytond.pyson import Eval, PYSONDecoder
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.exceptions import UserError, UserWarning
|
||||
from trytond.transaction import Transaction
|
||||
@@ -902,6 +902,48 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
scenario.get_valuation_date(),
|
||||
datetime.date(2026, 6, 18))
|
||||
|
||||
def test_itsa_mtm_scenario_defaults_calendar(self):
|
||||
'ITSA mtm scenarios default to the sulphuric acid calendar'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_calendar',
|
||||
return_value=17):
|
||||
self.assertEqual(pricing_module.MtmScenario.default_calendar(), 17)
|
||||
|
||||
def test_itsa_mtm_component_defaults(self):
|
||||
'ITSA mtm components default market price, ratio and USD currency'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_fix_type',
|
||||
return_value=21), patch(
|
||||
'trytond.modules.purchase_trade.pricing.default_itsa_currency',
|
||||
return_value=840):
|
||||
self.assertEqual(pricing_module.Mtm.default_fix_type(), 21)
|
||||
self.assertEqual(
|
||||
pricing_module.Mtm.default_ratio(), Decimal('100'))
|
||||
self.assertEqual(pricing_module.Mtm.default_currency(), 840)
|
||||
|
||||
def test_itsa_pricing_component_defaults(self):
|
||||
'ITSA pricing definitions default market price, ratio, USD and calendar'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_fix_type',
|
||||
return_value=21), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_calendar',
|
||||
return_value=17), patch(
|
||||
'trytond.modules.purchase_trade.pricing.default_itsa_currency',
|
||||
return_value=840):
|
||||
self.assertEqual(pricing_module.Component.default_fix_type(), 21)
|
||||
self.assertEqual(
|
||||
pricing_module.Component.default_ratio(), Decimal('100'))
|
||||
self.assertEqual(
|
||||
pricing_module.Component.default_fixed_currency(), 840)
|
||||
self.assertEqual(pricing_module.Component.default_calendar(), 17)
|
||||
|
||||
def test_mtm_scenario_resolves_relative_valuation_dates(self):
|
||||
'mtm scenario can resolve relative valuation dates'
|
||||
scenario = pricing_module.MtmScenario()
|
||||
@@ -1059,7 +1101,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
self.assertEqual(len(target), 3)
|
||||
self.assertEqual(target[0], values)
|
||||
self.assertEqual(target[1]['type'], 'mtm')
|
||||
self.assertEqual(target[0]['pnl'], Decimal('-100.00'))
|
||||
self.assertEqual(target[1]['type'], 'pur. mtm')
|
||||
self.assertEqual(target[1]['mtm_curve'], curve_a.id)
|
||||
self.assertEqual(target[1]['price'], None)
|
||||
self.assertEqual(target[1]['amount'], Decimal('0'))
|
||||
@@ -1068,7 +1111,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(target[1]['mtm_price_prev'], Decimal('90.0000'))
|
||||
self.assertEqual(target[1]['amount_prev'], Decimal('-540.00'))
|
||||
self.assertEqual(target[1]['mtm'], Decimal('-600.00'))
|
||||
self.assertEqual(target[2]['type'], 'mtm')
|
||||
self.assertEqual(target[1]['pnl'], Decimal('600.00'))
|
||||
self.assertEqual(target[2]['type'], 'pur. mtm')
|
||||
self.assertEqual(target[2]['mtm_curve'], curve_b.id)
|
||||
self.assertEqual(target[2]['price'], None)
|
||||
self.assertEqual(target[2]['amount'], Decimal('0'))
|
||||
@@ -1077,6 +1121,55 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(target[2]['mtm_price_prev'], Decimal('45.0000'))
|
||||
self.assertEqual(target[2]['amount_prev'], Decimal('-180.00'))
|
||||
self.assertEqual(target[2]['mtm'], Decimal('-200.00'))
|
||||
self.assertEqual(target[2]['pnl'], Decimal('200.00'))
|
||||
|
||||
@with_transaction()
|
||||
def test_sale_strategy_mtm_lines_use_sale_type(self):
|
||||
'sale strategy MTM lines are labelled as sale MTM'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
line = Mock(unit=Mock())
|
||||
strategy = Mock(
|
||||
scenario=Mock(valuation_date=datetime.date(2026, 6, 5)),
|
||||
currency=None,
|
||||
components=[],
|
||||
get_mtm=Mock(return_value=Decimal('250')))
|
||||
line.mtm = [strategy]
|
||||
values = {
|
||||
'type': 'sale priced',
|
||||
'amount': Decimal('100'),
|
||||
'base_amount': Decimal('100'),
|
||||
'quantity': Decimal('10'),
|
||||
}
|
||||
target = []
|
||||
|
||||
Valuation._append_pnl_values(target, values, line)
|
||||
|
||||
self.assertEqual(target[1]['type'], 'sale mtm')
|
||||
self.assertEqual(target[1]['mtm'], Decimal('250'))
|
||||
self.assertEqual(target[1]['pnl'], Decimal('-250.00'))
|
||||
|
||||
def test_pnl_converts_strategy_mtm_to_base_currency(self):
|
||||
'valuation PnL compares base amount with MTM in base currency'
|
||||
Valuation = valuation_module.Valuation
|
||||
source_currency = Mock(id=1)
|
||||
base_currency = Mock(id=2)
|
||||
strategy = Mock(currency=source_currency, scenario=None)
|
||||
values = {
|
||||
'base_amount': Decimal('100'),
|
||||
'base_currency': base_currency,
|
||||
}
|
||||
Currency = Mock()
|
||||
Currency.compute.return_value = Decimal('90')
|
||||
|
||||
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||
PoolMock.return_value.get.return_value = Currency
|
||||
|
||||
pnl = Valuation._pnl_amount(
|
||||
values, strategy=strategy, mtm_amount=Decimal('120'))
|
||||
|
||||
Currency.compute.assert_called_once_with(
|
||||
source_currency, Decimal('120'), base_currency)
|
||||
self.assertEqual(pnl, Decimal('10.00'))
|
||||
|
||||
def test_purchase_pnl_uses_partial_lotqt_match_quantity(self):
|
||||
'open matched purchase and sale pnl use the matched lot.qt quantity'
|
||||
@@ -1547,6 +1640,28 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertFalse(
|
||||
valuation_module.ValuationProcess._sale_line_is_unmatched(sale_line))
|
||||
|
||||
def test_regenerate_for_purchase_lines_deduplicates_targets(self):
|
||||
'targeted valuation regeneration processes each purchase line once'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
line = Mock(id=10)
|
||||
|
||||
with patch.object(Valuation, 'generate') as generate:
|
||||
Valuation.regenerate_for_purchase_lines([line, line])
|
||||
|
||||
generate.assert_called_once_with(line, valuation_type='all')
|
||||
|
||||
def test_regenerate_for_sale_lines_deduplicates_targets(self):
|
||||
'targeted valuation regeneration processes each sale line once'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
sale_line = Mock(id=20)
|
||||
|
||||
with patch.object(
|
||||
Valuation, 'generate_from_sale_line') as generate_from_sale_line:
|
||||
Valuation.regenerate_for_sale_lines([sale_line, sale_line])
|
||||
|
||||
generate_from_sale_line.assert_called_once_with(
|
||||
sale_line, valuation_type='all')
|
||||
|
||||
def test_parse_numbers_supports_inline_and_legacy_separators(self):
|
||||
'parse_numbers keeps supporting inline entry and legacy separators'
|
||||
self.assertEqual(
|
||||
@@ -1567,6 +1682,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
{'derivative'})
|
||||
self.assertIn('pur. priced', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('mtm', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('pur. mtm', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('sale mtm', Valuation._get_generate_types('goods'))
|
||||
|
||||
def test_filter_values_by_types_keeps_matching_entries_only(self):
|
||||
'type filtering keeps only the requested valuation entries'
|
||||
@@ -3596,6 +3713,26 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertTrue(SaleLine.quantity.readonly)
|
||||
self.assertTrue(PurchaseLine.quantity.readonly)
|
||||
|
||||
def test_allow_modification_after_validation_line_readonly_rule(self):
|
||||
'configuration flag unlocks lines only in quotation state'
|
||||
Sale = Pool().get('sale.sale')
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
|
||||
for model in (Sale, Purchase):
|
||||
expression = model.lines.states['readonly']
|
||||
self.assertTrue(PYSONDecoder({
|
||||
'state': 'quotation',
|
||||
'allow_modification_after_validation': False,
|
||||
}).decode(expression))
|
||||
self.assertFalse(PYSONDecoder({
|
||||
'state': 'quotation',
|
||||
'allow_modification_after_validation': True,
|
||||
}).decode(expression))
|
||||
self.assertTrue(PYSONDecoder({
|
||||
'state': 'confirmed',
|
||||
'allow_modification_after_validation': True,
|
||||
}).decode(expression))
|
||||
|
||||
def test_purchase_line_initial_quantity_uses_theoretical_quantity(self):
|
||||
'purchase line initializes technical quantity from contractual quantity'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
@@ -4004,6 +4141,84 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
'quantity': Decimal('1'),
|
||||
})
|
||||
|
||||
def test_purchase_and_sale_line_create_default_estimated_bl_date(self):
|
||||
'new lines default missing estimated BL date from delivery period start'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
SaleLine = Pool().get('sale.line')
|
||||
period = Mock(beg_date=datetime.date(2026, 5, 1))
|
||||
purchase_values = {'del_period': period}
|
||||
sale_values = {'del_period': period}
|
||||
|
||||
PurchaseLine._set_default_estimated_bl_date_values(purchase_values)
|
||||
SaleLine._set_default_estimated_bl_date_values(sale_values)
|
||||
|
||||
self.assertEqual(purchase_values['estimated_date'], [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])])
|
||||
self.assertEqual(sale_values['estimated_date'], [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])])
|
||||
|
||||
def test_purchase_and_sale_line_keep_explicit_estimated_bl_date(self):
|
||||
'new lines keep explicit estimated BL date'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
SaleLine = Pool().get('sale.line')
|
||||
period = Mock(beg_date=datetime.date(2026, 5, 1))
|
||||
explicit = [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 9),
|
||||
}])]
|
||||
purchase_values = {'del_period': period, 'estimated_date': list(explicit)}
|
||||
sale_values = {'del_period': period, 'estimated_date': list(explicit)}
|
||||
|
||||
PurchaseLine._set_default_estimated_bl_date_values(purchase_values)
|
||||
SaleLine._set_default_estimated_bl_date_values(sale_values)
|
||||
|
||||
self.assertEqual(purchase_values['estimated_date'], explicit)
|
||||
self.assertEqual(sale_values['estimated_date'], explicit)
|
||||
|
||||
def test_purchase_line_write_creates_missing_estimated_bl_date(self):
|
||||
'purchase line write creates missing estimated BL date from period'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
line = Mock(
|
||||
id=42,
|
||||
del_period=Mock(beg_date=datetime.date(2026, 5, 1)),
|
||||
estimated_date=[],
|
||||
)
|
||||
|
||||
with patch('trytond.modules.purchase_trade.purchase.Pool') as PoolMock:
|
||||
Estimated = Mock()
|
||||
PoolMock.return_value.get.return_value = Estimated
|
||||
PurchaseLine._create_missing_estimated_bl_dates([line])
|
||||
|
||||
Estimated.create.assert_called_once_with([{
|
||||
'line': 42,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])
|
||||
|
||||
def test_sale_line_write_creates_missing_estimated_bl_date(self):
|
||||
'sale line write creates missing estimated BL date from period'
|
||||
SaleLine = Pool().get('sale.line')
|
||||
line = Mock(
|
||||
id=43,
|
||||
del_period=Mock(beg_date=datetime.date(2026, 5, 1)),
|
||||
estimated_date=[],
|
||||
)
|
||||
|
||||
with patch('trytond.modules.purchase_trade.sale.Pool') as PoolMock:
|
||||
Estimated = Mock()
|
||||
PoolMock.return_value.get.return_value = Estimated
|
||||
SaleLine._create_missing_estimated_bl_dates([line])
|
||||
|
||||
Estimated.create.assert_called_once_with([{
|
||||
'sale_line': 43,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])
|
||||
|
||||
def test_sale_and_purchase_parent_write_check_embedded_line_commands(self):
|
||||
'sale and purchase writes validate embedded one2many line commands'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
Reference in New Issue
Block a user