"""Test WorksheetCalculator.""" from decimal import Decimal from src.constants import PayableDetailTypes from src.processors.base.worksheet_calculator import WorksheetCalculator from src.processors.models import ContractLevelFlowthroughAllocationData from tests.unit.factories import ( ContractCloseBalanceFactory, EligibleAccountLevelDataFactory, FlowthroughAllocationFactory, PayableBalanceAfterTaxEntryFactory, WorksheetPayableCalculatorDetailFactory, ) mock_eligible_account = EligibleAccountLevelDataFactory.build( account_id=1, contract_id=1, currency_code='USD', country_of_tax_policy='USA', country_of_tax_residence='USA', ) mock_payable_closed_balance = ContractCloseBalanceFactory.build( account_id=mock_eligible_account.account_id, currency_code=mock_eligible_account.currency_code, contract_id=1, amount=1000.00, ) class TestWorksheetCalculator: """Test Worksheet Calculator suite.""" def test__init__payable(self) -> None: """Test that the worksheet calculator sets up properly.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) assert test_worksheet.contract_id == mock_payable_closed_balance.contract_id assert test_worksheet.currency_code == mock_eligible_account.currency_code assert test_worksheet.currency_code == mock_eligible_account.currency_code assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert ( test_worksheet.payable_amount_post_tax == mock_payable_closed_balance.amount ) assert test_worksheet.vat_amount is None assert test_worksheet.tax_withholding_amount is None def test__init__payable_below_zero(self) -> None: """Test that when the closed balance is below zero the payable amount is treated as zero.""" mock_closed_balance = ContractCloseBalanceFactory.build( account_id=mock_eligible_account.account_id, contract_id=1, currency_code=mock_eligible_account.currency_code, amount=-1000.00, ) test_worksheet = WorksheetCalculator(mock_closed_balance, mock_eligible_account) assert test_worksheet.contract_id == mock_closed_balance.contract_id assert test_worksheet.currency_code == mock_eligible_account.currency_code assert test_worksheet.currency_code == mock_eligible_account.currency_code assert test_worksheet.payable_amount_pre_tax == Decimal(0) assert test_worksheet.payable_amount_post_tax == Decimal(0) assert test_worksheet.vat_amount is None assert test_worksheet.tax_withholding_amount is None def test_append_wht_item(self) -> None: """Test that you are able to append a wht item and it changes the payable.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) mock_wht_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal(-100.00) ) assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert ( test_worksheet.payable_amount_post_tax == mock_payable_closed_balance.amount ) test_worksheet.append_wht_item(mock_wht_item) assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert test_worksheet.tax_withholding_amount == mock_wht_item.amount_payable assert test_worksheet.payable_amount_post_tax == Decimal(900.00) assert mock_wht_item in test_worksheet._details def test_append_vat_item(self) -> None: """Test that you are able to append a vat item and it changes the payable.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) mock_vat_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal(50.00) ) assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert ( test_worksheet.payable_amount_post_tax == mock_payable_closed_balance.amount ) test_worksheet.append_vat_item(mock_vat_item) assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert test_worksheet.vat_amount == mock_vat_item.amount_payable assert test_worksheet.payable_amount_post_tax == Decimal(1050.00) assert mock_vat_item in test_worksheet._details def test_append_wht_and_vat_items(self) -> None: """Test that you can append both wht and vat items and they are combined.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) mock_wht_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal(-100.00) ) mock_vat_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal(50.00) ) test_worksheet.append_wht_item(mock_wht_item) test_worksheet.append_vat_item(mock_vat_item) assert ( test_worksheet.payable_amount_pre_tax == mock_payable_closed_balance.amount ) assert test_worksheet.tax_withholding_amount == mock_wht_item.amount_payable assert test_worksheet.vat_amount == mock_vat_item.amount_payable # 1000 - 100 + 50 = 950 assert test_worksheet.payable_amount_post_tax == Decimal(950.00) def test_get_payable_balance_after_tax_item(self) -> None: """Test returns payable balance item.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) response = test_worksheet.get_payable_balance_after_tax_item() assert type(response).__name__ == 'PayableBalanceAfterTax' assert response.payable_amount_pre_tax == test_worksheet.payable_amount_pre_tax assert ( response.payable_amount_post_tax == test_worksheet.payable_amount_post_tax ) assert response.tax_withholding_amount == test_worksheet.tax_withholding_amount assert response.vat_amount == test_worksheet.vat_amount def test_get_payable_detail_items(self) -> None: """Test get_payable_detail_items success.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) response = test_worksheet.get_payable_detail_items(123) assert len(response) == 1 assert type(response[0]).__name__ == 'ContractPayableDetails' assert response[0].worksheet_account_contract_payable_after_tax_id == 123 def test_apply_flowthrough_items_when_flowthrough_less_than_closing_balance( self, ) -> None: """Test apply flowthrough when flowthrough amount is less than closing balance.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) # Closing balance is 1000, flowthrough sum is 500 allocation_1 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('300.00'), ) allocation_2 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('200.00'), ) contract_allocations = ContractLevelFlowthroughAllocationData( sum=Decimal('500.00'), items=[allocation_1, allocation_2] ) assert test_worksheet.payable_amount_pre_tax == Decimal('1000.00') assert test_worksheet.payable_amount_post_tax == Decimal('1000.00') assert test_worksheet._closing_balance_detail.amount_payable == Decimal( '1000.00' ) test_worksheet.apply_flowthrough_items(contract_allocations) # Pre-tax and post-tax should remain unchanged assert test_worksheet.payable_amount_pre_tax == Decimal('1000.00') assert test_worksheet.payable_amount_post_tax == Decimal('1000.00') # Should have 3 details: closing balance + 2 flowthrough items assert len(test_worksheet._details) == 3 # Check closing balance detail remains unchanged in _details closing_balance_detail = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.closing_balance ] assert len(closing_balance_detail) == 1 assert closing_balance_detail[0].amount_payable == Decimal('1000.00') # Check flowthrough items have zero amount_payable (set_payable_zero=True) ft_details = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.flowthrough_allocation ] assert len(ft_details) == 2 assert all(d.amount_payable == Decimal('0') for d in ft_details) assert ft_details[0].target_id == allocation_1.payment_allocation_id assert ft_details[1].target_id == allocation_2.payment_allocation_id def test_apply_flowthrough_items_when_flowthrough_equals_to_closing_balance( self, ) -> None: """Test apply flowthrough when flowthrough amount is equal to closing balance.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) # Closing balance is 1000, flowthrough sum is 1000 allocation_1 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('500.00'), ) allocation_2 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('500.00'), ) contract_allocations = ContractLevelFlowthroughAllocationData( sum=Decimal('1000.00'), items=[allocation_1, allocation_2] ) assert test_worksheet.payable_amount_pre_tax == Decimal('1000.00') assert test_worksheet.payable_amount_post_tax == Decimal('1000.00') assert test_worksheet._closing_balance_detail.amount_payable == Decimal( '1000.00' ) test_worksheet.apply_flowthrough_items(contract_allocations) # Pre-tax and post-tax should remain unchanged assert test_worksheet.payable_amount_pre_tax == Decimal('1000.00') assert test_worksheet.payable_amount_post_tax == Decimal('1000.00') # Should have 3 details: closing balance + 2 flowthrough items assert len(test_worksheet._details) == 3 # Check closing balance detail remains unchanged in _details closing_balance_detail = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.closing_balance ] assert len(closing_balance_detail) == 1 assert closing_balance_detail[0].amount_payable == Decimal('1000.00') # Check flowthrough items have zero amount_payable (set_payable_zero=True) ft_details = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.flowthrough_allocation ] assert len(ft_details) == 2 assert all(d.amount_payable == Decimal('0') for d in ft_details) assert ft_details[0].target_id == allocation_1.payment_allocation_id assert ft_details[1].target_id == allocation_2.payment_allocation_id def test_apply_flowthrough_items_when_flowthrough_greater_than_closing_balance( self, ) -> None: """Test apply flowthrough when flowthrough amount is greater than closing balance.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) # Closing balance is 1000, flowthrough sum is 1500 allocation_1 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('800.00'), ) allocation_2 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('700.00'), ) contract_allocations = ContractLevelFlowthroughAllocationData( sum=Decimal('1500.00'), items=[allocation_1, allocation_2] ) assert test_worksheet.payable_amount_pre_tax == Decimal('1000.00') assert test_worksheet.payable_amount_post_tax == Decimal('1000.00') assert test_worksheet._closing_balance_detail.amount_payable == Decimal( '1000.00' ) test_worksheet.apply_flowthrough_items(contract_allocations) # Pre-tax should be overridden to flowthrough sum assert test_worksheet.payable_amount_pre_tax == Decimal('1500.00') assert test_worksheet.payable_amount_post_tax == Decimal('1500.00') # Closing balance detail should be set to zero assert test_worksheet._closing_balance_detail.amount_payable == Decimal('0') # Should have 3 details: closing balance + 2 flowthrough items assert len(test_worksheet._details) == 3 # Check closing balance detail is set to zero in _details closing_balance_detail = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.closing_balance ] assert len(closing_balance_detail) == 1 assert closing_balance_detail[0].amount_payable == Decimal('0') # Check flowthrough items have their original amounts ft_details = [ d for d in test_worksheet._details if d.payable_detail_type_id == PayableDetailTypes.flowthrough_allocation ] assert len(ft_details) == 2 assert ft_details[0].amount_payable == Decimal('800.00') assert ft_details[1].amount_payable == Decimal('700.00') assert ft_details[0].target_id == allocation_1.payment_allocation_id assert ft_details[1].target_id == allocation_2.payment_allocation_id def test_apply_flowthrough_items_with_zero_sum(self) -> None: """Test apply flowthrough returns early when sum is zero.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) allocation_1 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('1.00'), ) allocation_2 = FlowthroughAllocationFactory.build( contract_id=mock_payable_closed_balance.contract_id, amount_to_payment=Decimal('-1.00'), ) contract_allocations = ContractLevelFlowthroughAllocationData( sum=Decimal('0.00'), items=[allocation_1, allocation_2] ) initial_details_count = len(test_worksheet._details) initial_pre_tax = test_worksheet.payable_amount_pre_tax initial_post_tax = test_worksheet.payable_amount_post_tax test_worksheet.apply_flowthrough_items(contract_allocations) # Items are processed when sum is zero but there are still valid flowthrough items. assert test_worksheet.payable_amount_pre_tax == initial_pre_tax assert test_worksheet.payable_amount_post_tax == initial_post_tax assert len(test_worksheet._details) == initial_details_count + 2 def test_apply_flowthrough_items_with_empty_items(self) -> None: """Test apply flowthrough returns early when items list is empty.""" test_worksheet = WorksheetCalculator( mock_payable_closed_balance, mock_eligible_account ) contract_allocations = ContractLevelFlowthroughAllocationData( sum=Decimal('0.00'), items=[] ) initial_details_count = len(test_worksheet._details) initial_pre_tax = test_worksheet.payable_amount_pre_tax initial_post_tax = test_worksheet.payable_amount_post_tax test_worksheet.apply_flowthrough_items(contract_allocations) # Nothing should change assert test_worksheet.payable_amount_pre_tax == initial_pre_tax assert test_worksheet.payable_amount_post_tax == initial_post_tax assert len(test_worksheet._details) == initial_details_count def test_from_payable_balance_after_tax_entry(self) -> None: """Test building a calculator from a persisted after-tax entry.""" entry = PayableBalanceAfterTaxEntryFactory.build( worksheet_account_contract_payable_after_tax_id=42, worksheet_account_contract_closing_balance_id=100, contract_id=10, account_id=1, currency_code='USD', payable_amount_pre_tax=Decimal('1000.00'), tax_withholding_amount=Decimal('-50.00'), vat_amount=Decimal('20.00'), payable_amount_post_tax=Decimal('970.00'), country_of_tax_residence='USA', country_of_tax_policy='USA', ) calculator = WorksheetCalculator.from_payable_balance_after_tax_entry(entry) assert calculator.closing_balance_id == 100 assert calculator.contract_id == 10 assert calculator.account_id == 1 assert calculator.currency_code == 'USD' assert calculator.payable_amount_pre_tax == Decimal('1000.00') # tax amounts are reset; only rebuilt corrections will repopulate them assert calculator.tax_withholding_amount is None assert calculator.vat_amount is None assert calculator.payable_amount_post_tax == Decimal('1000.00') assert calculator.country_of_tax_residence == 'USA' assert calculator.country_of_tax_policy == 'USA' # starts with an empty details list (no closing balance detail) assert calculator._details == [] def test_from_payable_balance_after_tax_entry_emits_only_corrections(self) -> None: """Calculator from an entry emits only the appended correction details.""" entry = PayableBalanceAfterTaxEntryFactory.build( payable_amount_pre_tax=Decimal('1000.00'), ) calculator = WorksheetCalculator.from_payable_balance_after_tax_entry(entry) wht_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal('-100.00'), payable_detail_type_id=PayableDetailTypes.withholding_tax_correction, ) vat_item = WorksheetPayableCalculatorDetailFactory.build( amount_payable=Decimal('50.00'), payable_detail_type_id=PayableDetailTypes.vat_correction, ) calculator.append_wht_item(wht_item) calculator.append_vat_item(vat_item) assert calculator.tax_withholding_amount == Decimal('-100.00') assert calculator.vat_amount == Decimal('50.00') # 1000 - 100 + 50 = 950 assert calculator.payable_amount_post_tax == Decimal('950.00') details = calculator.get_payable_detail_items(123) # only the two correction details, no closing-balance row assert len(details) == 2 assert all( d.worksheet_account_contract_payable_after_tax_id == 123 for d in details ) assert {d.payable_detail_type_id for d in details} == { PayableDetailTypes.withholding_tax_correction, PayableDetailTypes.vat_correction, }