# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from decimal import Decimal from unittest.mock import Mock, patch from trytond.pool import Pool from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.modules.purchase_trade import valuation as valuation_module class PurchaseTradeTestCase(ModuleTestCase): 'Test purchase_trade module' module = 'purchase_trade' @with_transaction() def test_get_totals_without_rows(self): 'get_totals returns zeros when the query has no row' Valuation = Pool().get('valuation.valuation') cursor = Mock() cursor.fetchone.return_value = None connection = Mock() connection.cursor.return_value = cursor with patch( 'trytond.modules.purchase_trade.valuation.Transaction' ) as Transaction, patch.object( Valuation, '__table__', return_value='valuation_valuation'): Transaction.return_value.connection = connection self.assertEqual( Valuation.get_totals(), (Decimal(0), Decimal(0))) @with_transaction() def test_get_totals_without_previous_total(self): 'get_totals converts null variation to zero' Valuation = Pool().get('valuation.valuation') cursor = Mock() cursor.fetchone.return_value = (Decimal('42.50'), None) connection = Mock() connection.cursor.return_value = cursor with patch( 'trytond.modules.purchase_trade.valuation.Transaction' ) as Transaction, patch.object( Valuation, '__table__', return_value='valuation_valuation'): Transaction.return_value.connection = connection self.assertEqual( Valuation.get_totals(), (Decimal('42.50'), Decimal(0))) @with_transaction() def test_get_mtm_applies_component_ratio_as_percentage(self): 'get_mtm treats component ratio as a percentage' Strategy = Pool().get('mtm.strategy') strategy = Strategy() strategy.scenario = Mock( valuation_date='2026-03-29', use_last_price=True, ) strategy.currency = Mock() strategy.components = [Mock( price_source_type='curve', price_index=Mock(get_price=Mock(return_value=Decimal('100'))), price_matrix=None, ratio=Decimal('25'), )] line = Mock(unit=Mock()) self.assertEqual( strategy.get_mtm(line, Decimal('10')), Decimal('250.00')) def test_get_strategy_mtm_price_returns_unit_price(self): 'strategy mtm price exposes the raw unit valuation price' strategy = Mock( scenario=Mock( valuation_date='2026-03-29', use_last_price=True, ), currency=Mock(), ) strategy.components = [Mock( price_source_type='curve', price_index=Mock(get_price=Mock(return_value=Decimal('100'))), price_matrix=None, ratio=Decimal('25'), )] line = Mock(unit=Mock()) self.assertEqual( valuation_module.Valuation._get_strategy_mtm_price(strategy, line), Decimal('100.0000')) def test_sale_line_is_unmatched_checks_lot_links(self): 'sale line unmatched helper ignores empty matches and detects linked purchases' sale_line = Mock() sale_line.get_matched_lines.return_value = [] self.assertTrue( valuation_module.ValuationProcess._sale_line_is_unmatched(sale_line)) linked = Mock(lot_p=Mock(line=Mock())) sale_line.get_matched_lines.return_value = [linked] self.assertFalse( valuation_module.ValuationProcess._sale_line_is_unmatched(sale_line)) def test_parse_numbers_supports_inline_and_legacy_separators(self): 'parse_numbers keeps supporting inline entry and legacy separators' self.assertEqual( valuation_module.ValuationProcess._parse_numbers( 'PUR-001 PUR-002, PUR-003\nPUR-004;PUR-005' ), ['PUR-001', 'PUR-002', 'PUR-003', 'PUR-004', 'PUR-005']) def test_get_generate_types_maps_business_groups(self): 'valuation type groups map to the expected stored valuation types' Valuation = Pool().get('valuation.valuation') self.assertEqual( Valuation._get_generate_types('fees'), {'line fee', 'pur. fee', 'sale fee', 'shipment fee'}) self.assertEqual( Valuation._get_generate_types('derivatives'), {'derivative'}) self.assertIn('pur. priced', Valuation._get_generate_types('goods')) def test_filter_values_by_types_keeps_matching_entries_only(self): 'type filtering keeps only the requested valuation entries' Valuation = Pool().get('valuation.valuation') values = [ {'type': 'pur. fee', 'amount': Decimal('10')}, {'type': 'pur. priced', 'amount': Decimal('20')}, {'type': 'derivative', 'amount': Decimal('30')}, ] self.assertEqual( Valuation._filter_values_by_types( values, {'pur. fee', 'derivative'}), [ {'type': 'pur. fee', 'amount': Decimal('10')}, {'type': 'derivative', 'amount': Decimal('30')}, ]) def test_sale_report_crop_name_handles_missing_crop(self): 'sale report crop name returns an empty string when crop is missing' Sale = Pool().get('sale.sale') sale = Sale() sale.crop = None self.assertEqual(sale.report_crop_name, '') sale.crop = Mock(name='crop') sale.crop.name = 'Main Crop' self.assertEqual(sale.report_crop_name, 'Main Crop') del ModuleTestCase