"""Test adjustment_applied_processor.""" from unittest.mock import Mock, patch import pytest from adjustments_apply.ledger_adjustment_applied_item import LedgerAdjustmentAppliedItem from adjustments_apply.ledger_adjustment_applied_processor import ( LedgerAdjustmentAppliedProcessor, ) FLOWTHROUGH_TYPE_ID = 105 NON_FLOWTHROUGH_TYPE_ID = 1 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._get_exchange_rate' ) @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._group_by_contract_id' ) def test_ledger_adjustment_applied_processor_process( mock_group_by_contract_id, mock_get_exchange_rate, mock_exchange_rates, mock_pending_adjustments, mock_ledger_adjustment_applied, mock_ledger_accounts, mock_ledger_deposits, ): """Test process function.""" mock_get_exchange_rate.return_value = mock_exchange_rates[0]['rate'] abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_adjustment_applied_items = [ LedgerAdjustmentAppliedItem( adjustment['account_id'], adjustment['contract_id'], adjustment['abacus_event_id'], adjustment['worksheet_adjustment_id'], adjustment['adjustment_currency_code'], adjustment['adjustment_amount'], adjustment['account_currency_code'], next( ( rate['rate'] for rate in mock_exchange_rates if rate['from_currency_code'] == adjustment['adjustment_currency_code'] and rate['to_currency_code'] == adjustment['account_currency_code'] ), 1, ), adjustment['apply_to_flowthrough_payment'], adjustment['reference_adjustment_type_id'], adjustment['details'], ) for adjustment in mock_pending_adjustments ] grouped_entries = {222: [ledger_adjustment_applied_items[0]]} mock_group_by_contract_id.return_value = grouped_entries processor.process() assert mock_get_exchange_rate.call_count == len(mock_pending_adjustments) assert len(processor.ledger_adjustment_applied_items) == len( mock_ledger_adjustment_applied ) assert len(processor.ledger_adjustment_applied_items[0].details) == len( mock_ledger_adjustment_applied[0]['details'] ) mock_group_by_contract_id.assert_called_once() assert len(processor.ledger_account_entries) == len(grouped_entries.keys()) assert processor.ledger_account_entries[0] == mock_ledger_accounts[0] assert len(processor.ledger_deposit_entries) == 1 assert processor.ledger_deposit_entries[0] == mock_ledger_deposits[0] assert len(processor.ledger_contract_flowthrough_entries) == 0 assert len(processor.ledger_flowthrough_deposit_entries) == 0 def _create_dummy_ledger_adjustment_applied_item( contract_id, apply_to_flowthrough_payment, reference_adjustment_type_id=105, adjustment_amount=1000.00, ): account_id = 111 abacus_event_id = 11 worksheet_adjustment_id = 1 adjustment_currency_code = 'GBP' account_currency_code = 'GBP' rate = 1.00 return LedgerAdjustmentAppliedItem( account_id, contract_id, abacus_event_id, worksheet_adjustment_id, adjustment_currency_code, adjustment_amount, account_currency_code, rate, apply_to_flowthrough_payment, reference_adjustment_type_id, ) @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_process_only_flowthrough_adjustment( mock_generate_ledger_adjustment_applied_items, ): """Test process function. When only flowthrough adjustments are present. """ contract_id_1 = 222 processor = LedgerAdjustmentAppliedProcessor(11, 1, 2, [], []) processor.ledger_adjustment_applied_items = [ _create_dummy_ledger_adjustment_applied_item(contract_id_1, True), ] processor.process() # only flowthrough item is expected assert len(processor.ledger_account_entries) == 0 assert len(processor.ledger_contract_flowthrough_entries) == 1 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_process_flowthrough_and_account_adjustment( mock_generate_ledger_adjustment_applied_items, ): """Test process function. When flowthrough and account adjustments are present. """ contract_id_1 = 222 contract_id_2 = 333 processor = LedgerAdjustmentAppliedProcessor(11, 1, 2, [], []) processor.ledger_adjustment_applied_items = [ _create_dummy_ledger_adjustment_applied_item(contract_id_1, True), _create_dummy_ledger_adjustment_applied_item( contract_id_2, False, reference_adjustment_type_id=1 ), ] processor.process() # 1 flowthrough item and 1 account item is expected assert len(processor.ledger_account_entries) == 1 assert len(processor.ledger_contract_flowthrough_entries) == 1 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_process_flowthrough_and_account_single_contract_id( mock_generate_ledger_adjustment_applied_items, ): """Test process function. When single contract with flowthrough and account adjustments are present. """ contract_id_1 = 222 processor = LedgerAdjustmentAppliedProcessor(11, 1, 2, [], []) processor.ledger_adjustment_applied_items = [ _create_dummy_ledger_adjustment_applied_item(contract_id_1, True), _create_dummy_ledger_adjustment_applied_item( contract_id_1, False, reference_adjustment_type_id=1, ), ] processor.process() # adjustments should be grouped by apply_flowthrough in separate groups. assert len(processor.ledger_account_entries) == 1 assert (len(processor.ledger_contract_flowthrough_entries)) == 1 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_process_flowthrough_and_account_single_contract_id_with_split_amounts( mock_generate_ledger_adjustment_applied_items, ): """Test process function. When single contract with flowthrough and account adjustments are present. Account adjustments should be merged into single one. """ contract_id_1 = 222 processor = LedgerAdjustmentAppliedProcessor(11, 1, 2, [], []) processor.ledger_adjustment_applied_items = [ _create_dummy_ledger_adjustment_applied_item( contract_id_1, True, reference_adjustment_type_id=1, adjustment_amount=10 ), _create_dummy_ledger_adjustment_applied_item( contract_id_1, False, reference_adjustment_type_id=1, adjustment_amount=20 ), ] processor.process() # adjustments should be grouped by apply_flowthrough in separate groups. actual_flowthrough_entries = processor.ledger_contract_flowthrough_entries assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['currency_amount'] == '30.00' assert len(actual_flowthrough_entries) == 1 assert actual_flowthrough_entries[0]['currency_amount'] == '10.00' def test_generate_ledger_adjustment_applied_items( mock_exchange_rates, mock_pending_adjustments ): """Test _generate_ledger_adjustment_applied_entries.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) processor.ledger_adjustment_applied_items = Mock() processor._generate_ledger_adjustment_applied_items() assert processor.ledger_adjustment_applied_items.append.call_count == len( mock_pending_adjustments ) def test_generate_ledger_adjustment_applied_items_diff_statement_period( mock_exchange_rates, mock_pending_adjustments ): """Test _generate_ledger_adjustment_applied_entries. When apply_to_statement_period_id is different than current statement period. """ abacus_event_id = 11 statement_period_id = 1 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) processor.ledger_adjustment_applied_items = Mock() processor._generate_ledger_adjustment_applied_items() assert processor.ledger_adjustment_applied_items.append.call_count == 0 def test_generate_ledger_entries( mock_exchange_rates, mock_pending_adjustments, ): """Test _generate_ledger_entries function.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_adjustment_applied_items = [ LedgerAdjustmentAppliedItem( adjustment['account_id'], adjustment['contract_id'], adjustment['abacus_event_id'], adjustment['worksheet_adjustment_id'], adjustment['adjustment_currency_code'], adjustment['adjustment_amount'], adjustment['account_currency_code'], mock_exchange_rates[0]['rate'], adjustment['apply_to_flowthrough_payment'], adjustment['reference_adjustment_type_id'], ) for adjustment in mock_pending_adjustments ] grouped_entries = { 222: [ledger_adjustment_applied_items[0]], 223: [ledger_adjustment_applied_items[1]], 224: [ledger_adjustment_applied_items[2], ledger_adjustment_applied_items[3]], } processor._generate_ledger_entries(grouped_entries) assert processor.ledger_account_entries == [ { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_amount': '1781.09', 'currency_code': 'GBP', 'model_type': 'account', }, { 'abacus_event_id': 11, 'account_id': 112, 'contract_id': 223, 'currency_amount': '1781.94', 'currency_code': 'GBP', 'model_type': 'account', }, ] assert processor.ledger_deposit_entries == [ { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_code': 'GBP', 'model_type': 'deposit', 'rounded_amount': '1781.09', 'remaining_amount': '-0.0054740', }, { 'abacus_event_id': 11, 'account_id': 112, 'contract_id': 223, 'currency_code': 'GBP', 'model_type': 'deposit', 'rounded_amount': '1781.94', 'remaining_amount': '-0.0094800', }, ] assert processor.ledger_contract_flowthrough_entries == [ { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 224, 'currency_amount': '3567.44', 'currency_code': 'GBP', 'model_type': 'contract', } ] assert processor.ledger_flowthrough_deposit_entries == [ { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 224, 'currency_code': 'GBP', 'model_type': 'deposit', 'rounded_amount': '3567.44', 'remaining_amount': '-0.0168800', } ] def test_get_exchange_rate(mock_exchange_rates, mock_pending_adjustments): """Test getting exchange rate for pending adjustment.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) result = processor._get_exchange_rate(mock_pending_adjustments[0]) assert result == mock_exchange_rates[0]['rate'] def test_get_exchange_rate_same_currency(mock_exchange_rates, mock_pending_adjustments): """Test getting exchange rate for pending adjustment. When adjustment_currency_code == account_currency_code """ abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) result = processor._get_exchange_rate(mock_pending_adjustments[3]) assert result == 1.00 def test_get_exchange_rate_no_matching_currency(mock_pending_adjustments): """Test getting exchange rate for pending adjustment. When there is no matching currency. """ abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 mock_exchange_rates = [ { 'exchange_rate_id': 1, 'from_currency_code': 'USD', 'to_currency_code': 'AUD', 'rate': 0.89052, } ] processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) with pytest.raises(Exception) as e: processor._get_exchange_rate(mock_pending_adjustments[3]) assert 'Failed to find exchange rate for adjustment 4' in str(e.value) def test__group_by_contract_id( mock_pending_adjustments, mock_exchange_rates, mock_ledger_adjustment_applied, ): """Test _group_by_contract_id_apply_flowthrough when feature flag is enabled.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) processor.ledger_adjustment_applied_items = [ LedgerAdjustmentAppliedItem( adjustment['account_id'], adjustment['contract_id'], adjustment['abacus_event_id'], adjustment['worksheet_adjustment_id'], adjustment['adjustment_currency_code'], adjustment['adjustment_amount'], adjustment['account_currency_code'], mock_exchange_rates[0]['rate'], adjustment['apply_to_flowthrough_payment'], adjustment['reference_adjustment_type_id'], ) for adjustment in mock_pending_adjustments ] result = processor._group_by_contract_id() assert len(result) == 3 assert len(result[222]) == 1 assert len(result[223]) == 1 assert len(result[224]) == 2 def test_add_to_contract_ledger(mock_exchange_rates, mock_pending_adjustments): """Test _add_to_contract_ledger adds entries to account ledger.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_amount': '1781.09', 'currency_code': 'GBP', } deposit_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_code': 'GBP', 'rounded_amount': '1781.09', 'remaining_amount': '-0.0054740', } processor._add_to_contract_ledger(ledger_entry, deposit_entry) assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['model_type'] == 'account' assert processor.ledger_account_entries[0]['account_id'] == 111 assert len(processor.ledger_deposit_entries) == 1 assert processor.ledger_deposit_entries[0]['rounded_amount'] == '1781.09' def test_add_to_contract_ledger_without_deposit( mock_exchange_rates, mock_pending_adjustments ): """Test _add_to_contract_ledger without deposit entry.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_amount': '1781.09', 'currency_code': 'GBP', } processor._add_to_contract_ledger(ledger_entry) assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['model_type'] == 'account' assert len(processor.ledger_deposit_entries) == 0 def test_add_to_flowthrough_ledger(mock_exchange_rates, mock_pending_adjustments): """Test _add_to_flowthrough_ledger adds entries to flowthrough ledger.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_amount': '1781.09', 'currency_code': 'GBP', } deposit_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_code': 'GBP', 'rounded_amount': '1781.09', 'remaining_amount': '-0.0054740', } processor._add_to_flowthrough_ledger(ledger_entry, deposit_entry) assert len(processor.ledger_contract_flowthrough_entries) == 1 assert processor.ledger_contract_flowthrough_entries[0]['model_type'] == 'contract' assert processor.ledger_contract_flowthrough_entries[0]['account_id'] == 111 assert len(processor.ledger_flowthrough_deposit_entries) == 1 assert ( processor.ledger_flowthrough_deposit_entries[0]['rounded_amount'] == '1781.09' ) # noqa: E501 def test_add_to_flowthrough_ledger_without_deposit( mock_exchange_rates, mock_pending_adjustments ): """Test _add_to_flowthrough_ledger without deposit entry.""" abacus_event_id = 11 statement_period_id = 2 statement_period_adjustment_file_id = 1 processor = LedgerAdjustmentAppliedProcessor( abacus_event_id, statement_period_adjustment_file_id, statement_period_id, mock_exchange_rates, mock_pending_adjustments, ) ledger_entry = { 'abacus_event_id': 11, 'account_id': 111, 'contract_id': 222, 'currency_amount': '1781.09', 'currency_code': 'GBP', } processor._add_to_flowthrough_ledger(ledger_entry) assert len(processor.ledger_contract_flowthrough_entries) == 1 assert processor.ledger_contract_flowthrough_entries[0]['model_type'] == 'contract' assert len(processor.ledger_flowthrough_deposit_entries) == 0 # --------------------------------------------------------------------------- # Ledger routing predicate tests # # These directly test _need_add_to_contract_ledger and # _need_add_to_flowthrough_ledger for every combination of adjustment type # and apply_to_flowthrough_payment value. # --------------------------------------------------------------------------- class TestNeedAddToContractLedger: """Tests for _need_add_to_contract_ledger routing predicate.""" def _make_processor(self): return LedgerAdjustmentAppliedProcessor(1, 1, 1, [], []) def test_flowthrough_type_with_true_excluded_from_contract(self): """FT type + True → NOT on contract ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is False def test_flowthrough_type_with_false_included_in_contract(self): """FT type + False → on contract ledger (ACC-10310 scenario).""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is True def test_flowthrough_type_with_none_included_in_contract(self): """FT type + None → on contract ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is True def test_non_flowthrough_type_with_true_included_in_contract(self): """Non-FT type + True → on contract ledger (type != 105 short-circuits).""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is True def test_non_flowthrough_type_with_false_included_in_contract(self): """Non-FT type + False → on contract ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is True def test_non_flowthrough_type_with_none_included_in_contract(self): """Non-FT type + None → on contract ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_contract_ledger(item) is True class TestNeedAddToFlowthroughLedger: """Tests for _need_add_to_flowthrough_ledger routing predicate.""" def _make_processor(self): return LedgerAdjustmentAppliedProcessor(1, 1, 1, [], []) def test_flowthrough_type_with_true_included_in_flowthrough(self): """FT type + True → on flowthrough ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is True def test_flowthrough_type_with_false_excluded_from_flowthrough(self): """FT type + False → NOT on flowthrough ledger (ACC-10310 scenario).""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is False def test_flowthrough_type_with_none_excluded_from_flowthrough(self): """FT type + None → NOT on flowthrough ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is False def test_non_flowthrough_type_with_true_included_in_flowthrough(self): """Non-FT type + True → on flowthrough ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is True def test_non_flowthrough_type_with_false_excluded_from_flowthrough(self): """Non-FT type + False → NOT on flowthrough ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is False def test_non_flowthrough_type_with_none_excluded_from_flowthrough(self): """Non-FT type + None → NOT on flowthrough ledger.""" processor = self._make_processor() item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID ) assert processor._need_add_to_flowthrough_ledger(item) is False # --------------------------------------------------------------------------- # End-to-end ledger routing tests # # These test the full process() flow with the feature flag enabled, # asserting that adjustments land in exactly the right ledger(s) with # correct amounts. # --------------------------------------------------------------------------- class TestFlowthroughLedgerRouting: """End-to-end tests for flowthrough vs contract ledger routing. Each test sets up items directly on ledger_adjustment_applied_items and calls process(), then asserts ledger_account_entries and ledger_contract_flowthrough_entries. """ @staticmethod def _make_processor_with_items(items): processor = LedgerAdjustmentAppliedProcessor(11, 1, 2, [], []) processor.ledger_adjustment_applied_items = items return processor @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_ft_type_false_routes_to_contract_only(self, mock_gen): """ACC-10310: FT type + False → contract ledger only.""" item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID, adjustment_amount=500.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['model_type'] == 'account' assert processor.ledger_account_entries[0]['currency_amount'] == '500.00' assert len(processor.ledger_contract_flowthrough_entries) == 0 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_ft_type_none_routes_to_contract_only(self, mock_gen): """FT type + None → contract ledger only.""" item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID, adjustment_amount=750.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['currency_amount'] == '750.00' assert len(processor.ledger_contract_flowthrough_entries) == 0 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_ft_type_true_routes_to_flowthrough_only(self, mock_gen): """FT type + True → flowthrough ledger only.""" item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID, adjustment_amount=300.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 0 assert len(processor.ledger_contract_flowthrough_entries) == 1 ft_entry = processor.ledger_contract_flowthrough_entries[0] assert ft_entry['model_type'] == 'contract' assert ft_entry['currency_amount'] == '300.00' @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_non_ft_type_true_routes_to_both_ledgers(self, mock_gen): """Non-FT type + True → both contract AND flowthrough ledgers.""" item = _create_dummy_ledger_adjustment_applied_item( 222, True, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID, adjustment_amount=200.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 1 acct = processor.ledger_account_entries[0] assert acct['currency_amount'] == '200.00' assert len(processor.ledger_contract_flowthrough_entries) == 1 ft_entry = processor.ledger_contract_flowthrough_entries[0] assert ft_entry['currency_amount'] == '200.00' @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_non_ft_type_false_routes_to_contract_only(self, mock_gen): """Non-FT type + False → contract ledger only.""" item = _create_dummy_ledger_adjustment_applied_item( 222, False, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID, adjustment_amount=400.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['currency_amount'] == '400.00' assert len(processor.ledger_contract_flowthrough_entries) == 0 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_non_ft_type_none_routes_to_contract_only(self, mock_gen): """Non-FT type + None → contract ledger only.""" item = _create_dummy_ledger_adjustment_applied_item( 222, None, reference_adjustment_type_id=NON_FLOWTHROUGH_TYPE_ID, adjustment_amount=600.00, ) processor = self._make_processor_with_items([item]) processor.process() assert len(processor.ledger_account_entries) == 1 assert processor.ledger_account_entries[0]['currency_amount'] == '600.00' assert len(processor.ledger_contract_flowthrough_entries) == 0 @patch( 'adjustments_apply.ledger_adjustment_applied_processor.' 'LedgerAdjustmentAppliedProcessor._generate_ledger_adjustment_applied_items' ) def test_mixed_ft_items_same_contract_split_correctly(self, mock_gen): """Same contract with FT True and FT False items splits amounts correctly.""" contract_id = 222 processor = self._make_processor_with_items( [ _create_dummy_ledger_adjustment_applied_item( contract_id, True, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID, adjustment_amount=100.00, ), _create_dummy_ledger_adjustment_applied_item( contract_id, False, reference_adjustment_type_id=FLOWTHROUGH_TYPE_ID, adjustment_amount=250.00, ), ] ) processor.process() assert len(processor.ledger_account_entries) == 1 acct = processor.ledger_account_entries[0] assert acct['currency_amount'] == '250.00' assert len(processor.ledger_contract_flowthrough_entries) == 1 ft_entry = processor.ledger_contract_flowthrough_entries[0] assert ft_entry['currency_amount'] == '100.00'