from sqlalchemy import func, select from payment.api import db """Unit tests for worksheet_payable_balance_after_tax handler.""" import datetime from payment.models.worksheet_account_contract_payable_details import ( WorksheetAccountContractPayableDetails, ) from payment.models.worksheet_payable_balance_after_tax import ( WorksheetPayableBalanceAfterTax, ) from payment.utils.format_response import prepare_generic_success_response from tests.utils.factories import ( PaymentGroupPaymentAccountDetailFactory, WorksheetAccountContractClosingBalanceFactory, WorksheetAccountContractPayableDetailsFactory, WorksheetPayableBalanceAfterTaxFactory, ) def test_bulk_create_success( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test successful creation of worksheet_payable_balance_after_tax in bulk.""" worksheet_payable_balance_after_tax_1 = ( WorksheetPayableBalanceAfterTaxFactory.build( contract_id=1, ) ) worksheet_payable_balance_after_tax_2 = ( WorksheetPayableBalanceAfterTaxFactory.build( contract_id=2, ) ) post_body = [ { 'worksheet_account_contract_closing_balance_id': worksheet_payable_balance_after_tax_1.worksheet_account_contract_closing_balance_id, # noqa 'contract_id': worksheet_payable_balance_after_tax_1.contract_id, 'account_id': worksheet_payable_balance_after_tax_1.account_id, 'payable_amount_pre_tax': str( worksheet_payable_balance_after_tax_1.payable_amount_pre_tax ), 'tax_withholding_amount': str( worksheet_payable_balance_after_tax_1.tax_withholding_amount ), 'vat_amount': str(worksheet_payable_balance_after_tax_1.vat_amount), 'payable_amount_post_tax': str( worksheet_payable_balance_after_tax_1.vat_amount ), 'currency_code': worksheet_payable_balance_after_tax_1.currency_code, 'country_of_tax_residence': worksheet_payable_balance_after_tax_1.country_of_tax_residence, # noqa 'country_of_tax_policy': worksheet_payable_balance_after_tax_1.country_of_tax_policy, # noqa }, { 'worksheet_account_contract_closing_balance_id': worksheet_payable_balance_after_tax_2.worksheet_account_contract_closing_balance_id, # noqa 'contract_id': worksheet_payable_balance_after_tax_2.contract_id, 'account_id': worksheet_payable_balance_after_tax_2.account_id, 'payable_amount_pre_tax': str( worksheet_payable_balance_after_tax_2.payable_amount_pre_tax ), 'tax_withholding_amount': str( worksheet_payable_balance_after_tax_2.tax_withholding_amount ), 'vat_amount': str(worksheet_payable_balance_after_tax_2.vat_amount), 'payable_amount_post_tax': str( worksheet_payable_balance_after_tax_2.payable_amount_post_tax ), 'currency_code': worksheet_payable_balance_after_tax_2.currency_code, 'country_of_tax_residence': worksheet_payable_balance_after_tax_2.country_of_tax_residence, # noqa 'country_of_tax_policy': worksheet_payable_balance_after_tax_2.country_of_tax_policy, # noqa }, ] event_id = 1 statement_period_id = 1 assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 0 ) response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=post_body, ) assert response.status_code == 201 assert response.json == prepare_generic_success_response() assert db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == len(post_body) def test_bulk_create_with_deleted_worksheet_records_success( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test successful creation of worksheet_account_contract_closing_bal in bulk.""" pass # waiting for deletion logic to be implemented def test_bulk_create_already_exist( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test creation of worksheet_payable_balance_after_tax with existing record.""" event_id = 1 statement_period_id = 1 worksheet_payable_balance_after_tax_1 = ( WorksheetPayableBalanceAfterTaxFactory.build() ) post_body = [ { 'worksheet_account_contract_closing_balance_id': worksheet_payable_balance_after_tax_1.worksheet_account_contract_closing_balance_id, # noqa 'contract_id': worksheet_payable_balance_after_tax_1.contract_id, 'account_id': worksheet_payable_balance_after_tax_1.account_id, 'payable_amount_pre_tax': str( worksheet_payable_balance_after_tax_1.payable_amount_pre_tax ), 'tax_withholding_amount': str( worksheet_payable_balance_after_tax_1.tax_withholding_amount ), 'vat_amount': str(worksheet_payable_balance_after_tax_1.vat_amount), 'payable_amount_post_tax': str( worksheet_payable_balance_after_tax_1.payable_amount_post_tax ), 'currency_code': worksheet_payable_balance_after_tax_1.currency_code, 'country_of_tax_residence': worksheet_payable_balance_after_tax_1.country_of_tax_residence, # noqa 'country_of_tax_policy': worksheet_payable_balance_after_tax_1.country_of_tax_policy, # noqa }, ] response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=post_body, ) assert response.status_code == 201 assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 1 ) response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=post_body, ) assert response.status_code == 400 assert response.json['message'] == 'The worksheet already exists' assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 1 ) def test_bulk_create_validation_error( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test creation of worksheet_payable_balance_after_tax with validation error.""" event_id = 1 statement_period_id = 1 post_body = [ { 'worksheet_account_contract_closing_balance_id': -1, 'contract_id': -1, 'account_id': -1, 'payable_amount_pre_tax': 'invalid', 'tax_withholding_amount': 'invalid', 'vat_amount': 'invalid', 'payable_amount_post_tax': 'invalid', 'currency_code': '', 'country_of_tax_residence': '', 'country_of_tax_policy': '', }, { 'worksheet_account_contract_closing_balance_id': 0, 'contract_id': 0, 'account_id': 0, 'payable_amount_pre_tax': '', 'tax_withholding_amount': '', 'vat_amount': '', 'payable_amount_post_tax': '', 'currency_code': '', 'country_of_tax_residence': '', 'country_of_tax_policy': '', }, ] response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=post_body, ) assert response.status_code == 400 assert response.json['message']['json'] == { '0': { 'worksheet_account_contract_closing_balance_id': [ 'Must be greater than or equal to 0.' ], 'contract_id': ['Must be greater than or equal to 0.'], 'account_id': ['Must be greater than or equal to 0.'], 'payable_amount_pre_tax': ['Not a valid number.'], 'tax_withholding_amount': ['Not a valid number.'], 'vat_amount': ['Not a valid number.'], 'payable_amount_post_tax': ['Not a valid number.'], 'currency_code': ['Must be specified.'], 'country_of_tax_policy': ['Must be specified.'], 'country_of_tax_residence': ['Must be specified.'], }, '1': { 'payable_amount_pre_tax': ['Not a valid number.'], 'tax_withholding_amount': ['Not a valid number.'], 'vat_amount': ['Not a valid number.'], 'payable_amount_post_tax': ['Not a valid number.'], 'currency_code': ['Must be specified.'], 'country_of_tax_residence': ['Must be specified.'], 'country_of_tax_policy': ['Must be specified.'], }, } assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 0 ) def test_bulk_create_validation_error_no_data( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test creation of worksheet_payable_balance_after_tax with no data.""" event_id = 1 statement_period_id = 1 response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=[], ) assert response.status_code == 400 assert response.json['message'] == 'No instances to create' assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 0 ) def test_bulk_create_validation_integrity_error( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test creation of worksheet with integrity error.""" event_id = 999 statement_period_id = 999 worksheet_payable_balance_after_tax_1 = ( WorksheetPayableBalanceAfterTaxFactory.build() ) post_body = [ { 'worksheet_account_contract_closing_balance_id': worksheet_payable_balance_after_tax_1.worksheet_account_contract_closing_balance_id, # noqa 'contract_id': worksheet_payable_balance_after_tax_1.contract_id, 'account_id': worksheet_payable_balance_after_tax_1.account_id, 'payable_amount_pre_tax': str( worksheet_payable_balance_after_tax_1.payable_amount_pre_tax ), 'tax_withholding_amount': str( worksheet_payable_balance_after_tax_1.tax_withholding_amount ), 'vat_amount': str(worksheet_payable_balance_after_tax_1.vat_amount), 'payable_amount_post_tax': str( worksheet_payable_balance_after_tax_1.payable_amount_post_tax ), 'currency_code': worksheet_payable_balance_after_tax_1.currency_code, 'country_of_tax_residence': worksheet_payable_balance_after_tax_1.country_of_tax_residence, # noqa 'country_of_tax_policy': worksheet_payable_balance_after_tax_1.country_of_tax_policy, # noqa }, ] response = fixture_client.post( f'/worksheet-payable-balance-after-tax/event/{event_id}/statement-period/{statement_period_id}/bulk/', # noqa json=post_body, ) assert response.status_code == 400 assert ( response.json['message'] == 'IntegrityError: Cannot add or update a child row' ) assert ( db.session.execute( select(func.count()).select_from( WorksheetPayableBalanceAfterTax.filter_active().subquery() ) ).scalar_one() == 0 ) def test_bulk_delete_worksheet_payable_balance_after_tax( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk deletion of the worksheet_payable_balance_after_tax.""" worksheet1 = WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=1) worksheet2 = WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=1) WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=2) PaymentGroupPaymentAccountDetailFactory.create( # noqa worksheet_account_contract_payable_after_tax_id=worksheet1.worksheet_account_contract_payable_after_tax_id ) WorksheetPayableBalanceAfterTax.soft_delete_by_event_id(event_id=1) res = fixture_client.delete('/worksheet-payable-balance-after-tax/event/1/bulk') assert res.status_code == 204 assert db.session.execute( select(WorksheetPayableBalanceAfterTax).where( WorksheetPayableBalanceAfterTax.deleted_at != None # noqa ) ).scalars().all() == [worksheet2] def test_get_worksheet_payable_balance_after_tax( fixture_client, mock_statement_periods, mock_accounts, mock_contracts, mock_abacus_event, mock_ledger_account_contracts, ): """Gest get worksheet_payable_balance_after_tax records.""" event_id = 1 statement_period_id = 1 WorksheetAccountContractClosingBalanceFactory.create( abacus_event_id=event_id, statement_period_id=statement_period_id ) worksheet1 = WorksheetPayableBalanceAfterTaxFactory.create( abacus_event_id=event_id, statement_period_id=statement_period_id ) worksheet2 = WorksheetPayableBalanceAfterTaxFactory.create( abacus_event_id=event_id, statement_period_id=statement_period_id ) WorksheetPayableBalanceAfterTaxFactory.create( abacus_event_id=event_id + 1, statement_period_id=statement_period_id + 1 ) WorksheetPayableBalanceAfterTaxFactory.create(deleted_at=datetime.datetime.now()) res = fixture_client.get('/worksheet-payable-balance-after-tax/statement-period/1/') assert res.status_code == 200 assert res.json['total_count'] == 2 assert ( res.json['items'][0]['worksheet_account_contract_payable_after_tax_id'] == worksheet1.worksheet_account_contract_payable_after_tax_id ) assert ( res.json['items'][1]['worksheet_account_contract_payable_after_tax_id'] == worksheet2.worksheet_account_contract_payable_after_tax_id ) res = fixture_client.get('/worksheet-payable-balance-after-tax/event/1/') assert res.status_code == 200 assert res.json['total_count'] == 2 assert ( res.json['items'][0]['worksheet_account_contract_payable_after_tax_id'] == worksheet1.worksheet_account_contract_payable_after_tax_id ) assert ( res.json['items'][1]['worksheet_account_contract_payable_after_tax_id'] == worksheet2.worksheet_account_contract_payable_after_tax_id ) def test_get_worksheet_payable_balance_after_tax_overview( fixture_client, mock_statement_periods, mock_accounts, mock_contracts, mock_abacus_event, mock_ledger_account_contracts, mock_worksheet_account_contract_closing_balance, ): """Test overview endpoint for worksheet_payable_balance_after_tax.""" event_id = 1 worksheet1 = WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=event_id) worksheet2 = WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=event_id) WorksheetPayableBalanceAfterTaxFactory.create(abacus_event_id=event_id + 1) res = fixture_client.get( f'/worksheet-payable-balance-after-tax/event/{event_id}/overview/' ) assert res.status_code == 200 assert res.json['payable_amount_pre_tax'] == str( worksheet1.payable_amount_pre_tax + worksheet2.payable_amount_pre_tax ) assert res.json['tax_withholding_amount'] == str( worksheet1.tax_withholding_amount + worksheet2.tax_withholding_amount ) assert res.json['vat_amount'] == str(worksheet1.vat_amount + worksheet2.vat_amount) assert res.json['payable_amount_post_tax'] == str( worksheet1.payable_amount_post_tax + worksheet2.payable_amount_post_tax ) def test_delete_worksheet_payable_balance_after_tax_success( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test successful deletion of worksheet_payable_balance_after_tax instance.""" item = WorksheetPayableBalanceAfterTaxFactory.create() obj_id = item.worksheet_account_contract_payable_after_tax_id details_item = WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=item ) details_item_id = details_item.worksheet_account_contract_payable_details_id assert db.session.get(WorksheetPayableBalanceAfterTax, obj_id).deleted_at is None assert ( db.session.get( WorksheetAccountContractPayableDetails, details_item_id ).deleted_at is None ) res = fixture_client.delete(f'/worksheet-payable-balance-after-tax/{obj_id}/') assert res.status_code == 204 deleted_item = db.session.get(WorksheetPayableBalanceAfterTax, obj_id) assert deleted_item.deleted_at is not None deleted_details_item = db.session.get( WorksheetAccountContractPayableDetails, details_item_id ) assert deleted_details_item.deleted_at is not None def test_get_worksheet_payable_balance_after_tax_detail_success( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test successful retrieval of worksheet_payable_balance_after_tax detail.""" worksheet = WorksheetPayableBalanceAfterTaxFactory.create() res = fixture_client.get( f'/worksheet-payable-balance-after-tax/{worksheet.worksheet_account_contract_payable_after_tax_id}/' ) assert res.status_code == 200 assert ( res.json['worksheet_account_contract_payable_after_tax_id'] == worksheet.worksheet_account_contract_payable_after_tax_id ) def test_get_worksheet_payable_balance_after_tax_detail_not_found( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test retrieval of non-existent worksheet_payable_balance_after_tax detail.""" invalid_id = 99999 res = fixture_client.get(f'/worksheet-payable-balance-after-tax/{invalid_id}/') assert res.status_code == 400 def test_get_worksheet_payable_balance_after_tax_detail_deleted( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test retrieval of deleted worksheet_payable_balance_after_tax detail.""" worksheet = WorksheetPayableBalanceAfterTaxFactory.create() worksheet.delete_by_id_or_error( worksheet.worksheet_account_contract_payable_after_tax_id, soft_delete=True ) res = fixture_client.get( f'/worksheet-payable-balance-after-tax/{worksheet.worksheet_account_contract_payable_after_tax_id}/' ) assert res.status_code == 400 def test_bulk_update_worksheet_payable_balance_after_tax_success( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test successful bulk update of worksheet_payable_balance_after_tax.""" worksheet_1 = WorksheetPayableBalanceAfterTaxFactory.create( tax_withholding_amount=10.00, vat_amount=5.00, payable_amount_post_tax=85.00, ) worksheet_2 = WorksheetPayableBalanceAfterTaxFactory.create( tax_withholding_amount=20.00, vat_amount=10.00, payable_amount_post_tax=170.00, ) put_body = [ { 'worksheet_account_contract_payable_after_tax_id': worksheet_1.worksheet_account_contract_payable_after_tax_id, 'tax_withholding_amount': '15.00', 'vat_amount': '7.50', 'payable_amount_post_tax': '77.50', }, { 'worksheet_account_contract_payable_after_tax_id': worksheet_2.worksheet_account_contract_payable_after_tax_id, 'tax_withholding_amount': '25.00', 'vat_amount': '12.50', 'payable_amount_post_tax': '162.50', }, ] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 200 # Verify response contains updated items assert isinstance(response.json, list) assert len(response.json) == 2 # Verify updates updated_worksheet_1 = WorksheetPayableBalanceAfterTax.get_by_id( worksheet_1.worksheet_account_contract_payable_after_tax_id ) assert str(updated_worksheet_1.tax_withholding_amount) == '15.00' assert str(updated_worksheet_1.vat_amount) == '7.50' assert str(updated_worksheet_1.payable_amount_post_tax) == '77.50' updated_worksheet_2 = WorksheetPayableBalanceAfterTax.get_by_id( worksheet_2.worksheet_account_contract_payable_after_tax_id ) assert str(updated_worksheet_2.tax_withholding_amount) == '25.00' assert str(updated_worksheet_2.vat_amount) == '12.50' assert str(updated_worksheet_2.payable_amount_post_tax) == '162.50' def test_bulk_update_worksheet_payable_balance_after_tax_not_found( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk update with non-existent worksheet ID.""" invalid_id = 99999 put_body = [ { 'worksheet_account_contract_payable_after_tax_id': invalid_id, 'tax_withholding_amount': '15.00', 'vat_amount': '7.50', 'payable_amount_post_tax': '77.50', }, ] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 400 def test_bulk_update_worksheet_payable_balance_after_tax_validation_error( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk update with invalid data.""" worksheet = WorksheetPayableBalanceAfterTaxFactory.create() # Missing required field put_body = [ { 'worksheet_account_contract_payable_after_tax_id': worksheet.worksheet_account_contract_payable_after_tax_id, 'tax_withholding_amount': '15.00', 'vat_amount': '7.50', # Missing payable_amount_post_tax }, ] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 400 def test_bulk_update_worksheet_payable_balance_after_tax_invalid_id( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk update with invalid ID type.""" put_body = [ { 'worksheet_account_contract_payable_after_tax_id': 'invalid', 'tax_withholding_amount': '15.00', 'vat_amount': '7.50', 'payable_amount_post_tax': '77.50', }, ] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 400 def test_bulk_update_worksheet_payable_balance_after_tax_empty_list( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk update with empty list.""" put_body = [] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 200 assert response.json == [] def test_bulk_update_worksheet_payable_balance_after_tax_duplicate_ids( fixture_client, mock_contracts, mock_accounts, mock_abacus_event, mock_statement_periods, mock_worksheet_account_contract_closing_balance, ): """Test bulk update with duplicate worksheet IDs.""" worksheet = WorksheetPayableBalanceAfterTaxFactory.create( tax_withholding_amount=10.00, vat_amount=5.00, payable_amount_post_tax=85.00, ) put_body = [ { 'worksheet_account_contract_payable_after_tax_id': worksheet.worksheet_account_contract_payable_after_tax_id, 'tax_withholding_amount': '15.00', 'vat_amount': '7.50', 'payable_amount_post_tax': '77.50', }, { 'worksheet_account_contract_payable_after_tax_id': worksheet.worksheet_account_contract_payable_after_tax_id, 'tax_withholding_amount': '25.00', 'vat_amount': '12.50', 'payable_amount_post_tax': '162.50', }, ] response = fixture_client.put( '/worksheet-payable-balance-after-tax/bulk', json=put_body, ) assert response.status_code == 400