"""Functional tests for payment_group_payment_account.""" import pytest from payment.constants.constants import PAYMENT_TYPES from payment.constants.error import ( ERROR_PAYMENT_UPDATE_INVALID_STATUS, ERROR_UNKNOWN_CURRENCY, ) from tests.utils.factories import ( PaymentGroupPaymentAccountDetailFactory, PaymentGroupPaymentAccountFactory, PaymentGroupPaymentBatchAccountFactory, PaymentGroupPaymentBatchFactory, PaymentGroupPaymentFactory, WorksheetAccountContractClosingBalanceFactory, WorksheetAccountContractPayableDetailsFactory, WorksheetPayableBalanceAfterTaxFactory, ) def test_bulk_create_success(fixture_client, mock_accounts): """POST /payment-group-payment//accounts/ success.""" payment_group_payment = PaymentGroupPaymentFactory.create() payoneer_program_id = 100158970 payment_group_payment_id = payment_group_payment.payment_group_payment_id statement_period_id = payment_group_payment.statement_period_id post_body = [ { 'account_id': 1, 'contracts_payable': [ { 'contract_id': 123, 'current_balance': '100.00', 'currency_code': 'USD', } ], 'currency_code': 'USD', 'current_balance': '100.00', 'last_payment': '1000.00', 'tax_withholding': None, 'vat_amount': None, 'balance_after_tax': '100.00', 'payoneer_program_id': payoneer_program_id, 'last_statement_period_id': None, }, { 'account_id': 2, 'contracts_payable': [ { 'contract_id': 456, 'current_balance': '150.00', 'currency_code': 'CAD', }, { 'contract_id': 789, 'current_balance': '50.00', 'currency_code': 'CAD', }, ], 'currency_code': 'CAD', 'current_balance': '200.00', 'last_payment': '2000.00', 'tax_withholding': '10', 'vat_amount': '11.01', 'balance_after_tax': '190.00', 'payoneer_program_id': payoneer_program_id, 'last_statement_period_id': None, }, ] res = fixture_client.post( f'/payment-group-payment/{payment_group_payment_id}/accounts/', json=post_body ) assert res.status_code == 201 assert len(res.json) == len(post_body) assert all(pa.get('account_id') for pa in res.json) assert all(pa.get('contracts_payable') for pa in res.json) assert all(pa.get('currency_code') for pa in res.json) assert all(pa.get('current_balance') for pa in res.json) assert all(pa.get('last_payment') for pa in res.json) assert all( pa.get('payment_group_payment_id') == payment_group_payment_id for pa in res.json ) assert all(pa.get('payoneer_program_id') == payoneer_program_id for pa in res.json) assert all(pa.get('payment_group_payment_account_id') for pa in res.json) assert all(pa.get('prior_payment_group_payment_id') is None for pa in res.json) assert all( pa.get('current_statement_period_id') == statement_period_id for pa in res.json ) assert post_body[0]['vat_amount'] == res.json[0]['vat_amount'] assert post_body[1]['vat_amount'] == res.json[1]['vat_amount'] def test_bulk_create_account_with_pending_payment( fixture_client, mock_accounts, create_mock_payment_state, mock_contracts, mock_abacus_event, mock_worksheet_account_contract_closing_balance, ): """POST /payment-group-payment//accounts/ success.""" account_id = 1 payoneer_program_id = 100158970 existing_payment_group_payment_id = 2 existing_payment_group_payment = PaymentGroupPaymentFactory.create( payment_group_payment_id=existing_payment_group_payment_id ) existing_payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment=existing_payment_group_payment, account_id=account_id, current_balance=1000.00, balance_after_tax=1000.00, ) worksheet_payable = WorksheetPayableBalanceAfterTaxFactory.create( account_id=account_id, ) PaymentGroupPaymentAccountDetailFactory.create( worksheet_account_contract_payable_after_tax_id=worksheet_payable.worksheet_account_contract_payable_after_tax_id, payment_group_payment_account=existing_payment_account, ) payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id statement_period_id = payment_group_payment.statement_period_id WorksheetPayableBalanceAfterTaxFactory.create( account_id=account_id, abacus_event_id=3 ) post_body = [ { 'account_id': account_id, 'contracts_payable': [ { 'contract_id': 123, 'current_balance': '100.00', 'currency_code': 'USD', } ], 'currency_code': 'USD', 'current_balance': '100.00', 'last_payment': '1000.00', 'tax_withholding': '10.00', 'vat_amount': None, 'balance_after_tax': '190.00', 'payoneer_program_id': payoneer_program_id, }, { 'account_id': 2, 'contracts_payable': [ { 'contract_id': 456, 'current_balance': '150.00', 'currency_code': 'CAD', }, { 'contract_id': 789, 'current_balance': '50.00', 'currency_code': 'CAD', }, ], 'currency_code': 'CAD', 'current_balance': '200.00', 'last_payment': '2000.00', 'tax_withholding': '0.00', 'vat_amount': '11.01', 'balance_after_tax': '200.00', 'payoneer_program_id': payoneer_program_id, }, ] res = fixture_client.post( f'/payment-group-payment/{payment_group_payment_id}/accounts/', json=post_body ) assert res.status_code == 201 assert ( not existing_payment_account.payment_group_payment_id == payment_group_payment_id ) assert any( pa.get('prior_payment_group_payment_id') == existing_payment_group_payment_id for pa in res.json ) assert all( pa.get('payment_group_payment_id') == payment_group_payment_id for pa in res.json ) assert all(pa.get('payoneer_program_id') == payoneer_program_id for pa in res.json) assert all( pa.get('current_statement_period_id') == statement_period_id for pa in res.json ) assert post_body[0]['vat_amount'] == res.json[0]['vat_amount'] assert post_body[1]['vat_amount'] == res.json[1]['vat_amount'] def test_bulk_create_error(fixture_client, mock_accounts): """POST /payment-group-payment//accounts/ error.""" payment_group_payment = PaymentGroupPaymentFactory.create() payoneer_program_id = 100158970 payment_group_payment_id = payment_group_payment.payment_group_payment_id post_body = [ { 'account_id': 1, 'contracts_payable': [ { 'contract_id': 123, 'current_balance': '100.00', 'currency_code': 'LOL', } ], 'currency_code': 'LOL', 'current_balance': '100.00', 'last_payment': '1000.00', 'tax_withholding': '0.00', 'balance_after_tax': '100.00', 'payoneer_program_id': payoneer_program_id, } ] res = fixture_client.post( f'/payment-group-payment/{payment_group_payment_id}/accounts/', json=post_body ) assert res.status_code == 400 assert res.json['code'] == 'error' assert res.json['message'] == ERROR_UNKNOWN_CURRENCY.format(code='LOL') def test_delete_payment_group_payment_account(fixture_client, mock_accounts): """DELETE /payment-group-payment-account//.""" payment_account = PaymentGroupPaymentAccountFactory.create() payment_account_id = payment_account.payment_group_payment_account_id assert not payment_account.deleted_at assert not payment_account.deleted_by res = fixture_client.delete(f'/payment-group-payment-account/{payment_account_id}') assert res.status_code == 204 assert payment_account.deleted_at assert payment_account.deleted_by def test_delete_payment_group_payment_account_error( fixture_client, mock_accounts, create_mock_payment_state ): """DELETE /payment-group-payment-account//.""" payment_account = PaymentGroupPaymentAccountFactory.create() payment_account_id = payment_account.payment_group_payment_account_id assert not payment_account.deleted_at assert not payment_account.deleted_by res = fixture_client.delete(f'/payment-group-payment-account/{payment_account_id}') assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') == ERROR_PAYMENT_UPDATE_INVALID_STATUS assert not payment_account.deleted_at assert not payment_account.deleted_by def test_get_list_by_payment_group_payment( fixture_client, mock_accounts, mock_account_payees ): """GET /payment-group-payment//accounts.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id payment_accounts = [ PaymentGroupPaymentAccountFactory.create( account_id=i, payment_group_payment=payment_group_payment ) for i in range(1, 4) ] query_string = 'limit=5&offset=0&sort_by=account_name&sort_order=asc' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 200 assert res.json.get('total_count') == len(payment_accounts) items = res.json.get('items') assert all(item.get('account_id') for item in items) assert all(item.get('account_payee_id') for item in items) assert all(item.get('account_name') for item in items) assert all(item.get('contracts_payable') for item in items) assert all(item.get('currency_code') for item in items) assert all(item.get('current_balance') for item in items) assert all(item.get('last_payment') for item in items) assert all(item.get('payment_difference') for item in items) assert all(item.get('percent_difference') for item in items) assert all(item.get('payment_group_payment_account_id') for item in items) assert all(item.get('payment_group_payment_id') for item in items) assert all(item.get('payoneer_payee_id') for item in items) assert all(item.get('prior_payment_group_payment_id') is None for item in items) assert all(item.get('current_statement_period_id') for item in items) assert all(item.get('current_statement_period_name') for item in items) assert all(item.get('last_statement_period_id') is None for item in items) assert all(item.get('last_statement_period_name') is None for item in items) def test_get_list_by_payment_group_payment_default( fixture_client, mock_accounts, mock_account_payees ): """Test GET is successful with no query params and returns non-pending results.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id [ PaymentGroupPaymentAccountFactory.create( account_id=i, payment_group_payment=payment_group_payment ) for i in range(1, 4) ] res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts/' ) assert res.status_code == 200 assert res.json.get('total_count') assert res.json.get('items') def test_get_list_by_payment_group_payment_deleted( fixture_client, mock_accounts, mock_account_payees ): """Test GET returns payment_group_payment_accounts that are NOT deleted.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id active = [ PaymentGroupPaymentAccountFactory.create( account_id=i, payment_group_payment=payment_group_payment ) for i in range(1, 4) ] deleted = PaymentGroupPaymentAccountFactory.create( deleted_at='2021-10-01', deleted_by='user', account_id=5, payment_group_payment=payment_group_payment, ) res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts/' ) assert res.status_code == 200 assert res.json.get('total_count') == len(active) assert len(payment_group_payment.payment_accounts) > res.json.get('total_count') assert deleted.payment_group_payment_account_id not in [ item.get('payment_group_payment_account_id') for item in res.json.get('items') ] def test_get_list_by_payment_group_payment_not_pending( fixture_client, mock_accounts, mock_account_payees ): """Test GET returns payment_group_payment_accounts NOT part of another group.""" pending = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id payment_accounts = [ PaymentGroupPaymentAccountFactory.create( account_id=i, payment_group_payment=payment_group_payment ) for i in range(1, 4) ] pending_payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment=payment_group_payment, prior_payment_group_payment_id=pending.payment_group_payment_id, account_id=5, ) res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts/' ) assert res.status_code == 200 assert res.json.get('total_count') == len(payment_accounts) assert len(payment_group_payment.payment_accounts) > res.json.get('total_count') assert pending_payment_account.payment_group_payment_account_id not in [ item.get('payment_group_payment_account_id') for item in res.json.get('items') ] def test_get_list_by_payment_group_payment_invalid_param(fixture_client, mock_accounts): """Test GET returns an error when request params are invalid.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id [ PaymentGroupPaymentAccountFactory.create( account_id=i, payment_group_payment=payment_group_payment ) for i in range(1, 4) ] query_string = 'limit=not_an_int' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'offset=not_an_int' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'sort_by=invalid_column' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'sort_order=invalid_order' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') def test_get_prior_payment_pending_accounts( fixture_client, mock_accounts, mock_account_payees ): """GET /payment-group-payment//accounts/pending/.""" pending = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id pending_payment_accounts = [ PaymentGroupPaymentAccountFactory.create( account_id=i, prior_payment_group_payment_id=pending.payment_group_payment_id, payment_group_payment=payment_group_payment, ) for i in range(1, 4) ] payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment=payment_group_payment, account_id=5 ) query_string = 'limit=5&offset=0&sort_by=account_name&sort_order=asc' url = f'/payment-group-payment/{payment_group_payment_id}/accounts/pending/' res = fixture_client.get(f'{url}?{query_string}') assert res.status_code == 200 assert res.json.get('total_count') == len(pending_payment_accounts) assert len(payment_group_payment.payment_accounts) > res.json.get('total_count') items = res.json.get('items') assert all(item.get('account_id') for item in items) assert all(item.get('account_name') for item in items) assert all(item.get('contracts_payable') for item in items) assert all(item.get('currency_code') for item in items) assert all(item.get('current_balance') for item in items) assert all(item.get('last_payment') for item in items) assert all(item.get('payment_group_payment_account_id') for item in items) assert all(item.get('payment_group_payment_id') for item in items) assert all(item.get('prior_payment_group_payment_id') for item in items) assert payment_account.payment_group_payment_account_id not in [ item.get('payment_group_payment_account_id') for item in res.json.get('items') ] def test_get_prior_payment_pending_accounts_default( fixture_client, mock_accounts, mock_account_payees ): """Test GET is successful with no query params and returns only pending results.""" pending = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id [ PaymentGroupPaymentAccountFactory.create( account_id=i, prior_payment_group_payment_id=pending.payment_group_payment_id, payment_group_payment=payment_group_payment, ) for i in range(1, 4) ] res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts/pending/' ) assert res.status_code == 200 assert res.json.get('total_count') assert res.json.get('items') def test_get_prior_payment_pending_accounts_deleted( fixture_client, mock_accounts, mock_account_payees ): """Test GET returns pending payment_group_payment_accounts that are not deleted.""" pending = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id active = [ PaymentGroupPaymentAccountFactory.create( account_id=i, prior_payment_group_payment_id=pending.payment_group_payment_id, payment_group_payment=payment_group_payment, ) for i in range(1, 4) ] deleted = PaymentGroupPaymentAccountFactory.create( deleted_at='2021-10-01', deleted_by='user', payment_group_payment=payment_group_payment, prior_payment_group_payment_id=pending.payment_group_payment_id, account_id=5, ) res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts/pending/' ) assert res.status_code == 200 assert res.json.get('total_count') == len(active) assert len(payment_group_payment.payment_accounts) > res.json.get('total_count') assert deleted.payment_group_payment_account_id not in [ item.get('payment_group_payment_account_id') for item in res.json.get('items') ] def test_get_prior_payment_pending_accounts_error(fixture_client, mock_accounts): """Test GET returns an error when request params are invalid.""" prior = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id [ PaymentGroupPaymentAccountFactory.create( account_id=i, prior_payment_group_payment_id=prior.payment_group_payment_id, payment_group_payment=payment_group_payment, ) for i in range(1, 4) ] url = f'/payment-group-payment/{payment_group_payment_id}/accounts/pending' query_string = 'limit=not_an_int' res = fixture_client.get(f'{url}?{query_string}') assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'offset=not_an_int' res = fixture_client.get(f'{url}?{query_string}') assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'sort_by=invalid_column' res = fixture_client.get(f'{url}?{query_string}') assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') query_string = 'sort_order=invalid_order' res = fixture_client.get(f'{url}?{query_string}') assert res.status_code == 400 assert res.json.get('code') == 'error' assert res.json.get('message') def test_get_last_posted_payment_by_account(fixture_client, mock_payments): """GET /payment-group-payment-account/last-payment//.""" account_id = 7 res = fixture_client.get( f'/payment-group-payment-account/last-payment/{account_id}/' ) assert res.status_code == 200 assert res.json.get('account_id') == account_id assert res.json.get('balance_after_tax') == '200.00' assert isinstance(res.json.get('contracts_payable'), list) assert res.json.get('currency_code') == 'USD' assert res.json.get('current_balance') == '200.00' assert res.json.get('current_statement_period_id') == 1 assert res.json.get('last_payment') == '400.00' assert res.json.get('last_statement_period_id') is None assert res.json.get('note') is None assert res.json.get('payment_group_payment_account_id') == 7 assert res.json.get('payment_group_payment_id') == 501 assert res.json.get('payoneer_program_id') == 1001 assert res.json.get('prior_payment_group_payment_id') is None assert res.json.get('tax_withholding') is None assert res.json.get('vat_amount') is None def test_get_last_posted_payment_by_account_none(fixture_client, mock_accounts): """GET /payment-group-payment-account/last-payment//.""" payment_account = PaymentGroupPaymentAccountFactory.create() account_id = payment_account.account_id res = fixture_client.get( f'/payment-group-payment-account/last-payment/{account_id}/' ) assert res.status_code == 200 assert not res.json def test_get_pending_payment_by_account( fixture_client, mock_accounts, create_mock_payment_state ): """GET /payment-group-payment-account/pending-payment//.""" pending_payment_group = PaymentGroupPaymentFactory.create( payment_group_payment_id=2 ) payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment=pending_payment_group ) account_id = payment_account.account_id res = fixture_client.get( f'/payment-group-payment-account/pending-payment/{account_id}/' ) assert res.status_code == 200 assert res.json.get('account_id') == account_id assert res.json.get('contracts_payable') == payment_account.contracts_payable assert res.json.get('currency_code') == payment_account.currency_code assert res.json.get('current_balance') == str(payment_account.current_balance) assert res.json.get('last_payment') == str(payment_account.last_payment) assert ( res.json.get('payment_group_payment_id') == payment_account.payment_group_payment_id ) assert ( res.json.get('payment_group_payment_account_id') == payment_account.payment_group_payment_account_id ) assert ( res.json.get('prior_payment_group_payment_id') == payment_account.prior_payment_group_payment_id ) def test_get_pending_payment_by_account_none(fixture_client, mock_accounts): """GET /payment-group-payment-account/pending-payment//.""" payment_account = PaymentGroupPaymentAccountFactory.create() account_id = payment_account.account_id res = fixture_client.get( f'/payment-group-payment-account/pending-payment/{account_id}/' ) assert res.status_code == 200 assert not res.json def test_get_totals_by_payment_group_payment(fixture_client, mock_accounts): """GET /payment-group-payment//overview.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id payment_accounts_eur = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='EUR', payment_group_payment=payment_group_payment, ) for i in range(1, 3) ] payment_accounts_gbr = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='GBR', payment_group_payment=payment_group_payment, ) for i in range(3, 6) ] res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/overview' ) assert res.status_code == 200 assert len(res.json) == 2 assert res.json[0].get('currency_code') == 'GBR' assert res.json[0].get('account_count') == len(payment_accounts_gbr) assert res.json[1].get('currency_code') == 'EUR' assert res.json[1].get('account_count') == len(payment_accounts_eur) assert all(item.get('currency_code') for item in res.json) assert all(item.get('currency_total') for item in res.json) assert all( item.get('payment_group_payment_id') == payment_group_payment_id for item in res.json ) def test_get_totals_by_payment_group_payment_ids(fixture_client, mock_accounts): """GET /payment-group-payment//overview.""" payment_group_payment1 = PaymentGroupPaymentFactory.create() payment_group_payment1_id = payment_group_payment1.payment_group_payment_id payment_group_payment2 = PaymentGroupPaymentFactory.create() payment_group_payment2_id = payment_group_payment2.payment_group_payment_id payment_accounts_eur = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='EUR', payment_group_payment=payment_group_payment1, ) for i in range(1, 3) ] payment_accounts_gbr = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='GBR', payment_group_payment=payment_group_payment2, ) for i in range(3, 6) ] post_body = [payment_group_payment1_id, payment_group_payment2_id] res = fixture_client.post( '/payment-group-payment/overview/dataloader', json=post_body ) body = res.json.get('items') assert res.status_code == 200 assert len(body) == 2 payment_group1 = body[0].get('data')[0] assert payment_group1.get('currency_code') == 'EUR' assert payment_group1.get('account_count') == len(payment_accounts_eur) payment_group2 = body[1].get('data')[0] assert payment_group2.get('currency_code') == 'GBR' assert payment_group2.get('account_count') == len(payment_accounts_gbr) assert all(item.get('data')[0].get('currency_code') for item in body) assert all(item.get('data')[0].get('currency_total') for item in body) assert all( item.get('data')[0].get('payment_group_payment_id') in [payment_group_payment1_id, payment_group_payment2_id] for item in body ) def test_get_totals_by_payment_group_payment_deleted(fixture_client, mock_accounts): """Test totals exclude any payment_group_payment_accounts that are deleted.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id payment_accounts = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='USD', payment_group_payment=payment_group_payment, ) for i in range(1, 3) ] deleted_payment_account = PaymentGroupPaymentAccountFactory.create( currency_code='USD', payment_group_payment=payment_group_payment, deleted_at='2021-10-01', deleted_by='dana', account_id=4, ) res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/overview' ) assert res.status_code == 200 assert len(res.json) == 1 assert res.json[0].get('account_count') == len(payment_accounts) assert res.json[0].get('currency_total') == str( sum(pa.current_balance for pa in payment_group_payment.payment_accounts) - deleted_payment_account.current_balance ) def test_get_totals_by_payment_group_payment_pending(fixture_client, mock_accounts): """Test totals exclude payment_accounts part of another payment_group_payment.""" pending = PaymentGroupPaymentFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment_id = payment_group_payment.payment_group_payment_id payment_accounts = [ PaymentGroupPaymentAccountFactory.create( account_id=i, currency_code='CAD', payment_group_payment=payment_group_payment, ) for i in range(1, 3) ] pending_payment_account = PaymentGroupPaymentAccountFactory.create( currency_code='CAD', payment_group_payment=payment_group_payment, prior_payment_group_payment_id=pending.payment_group_payment_id, account_id=4, ) res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/overview' ) assert res.status_code == 200 assert len(res.json) == 1 assert res.json[0].get('account_count') == len(payment_accounts) assert res.json[0].get('currency_total') == str( sum(pa.current_balance for pa in payment_group_payment.payment_accounts) - pending_payment_account.current_balance ) def test_update_payment_group_payment_account(fixture_client, mock_accounts): """PUT /payment-group-payment-account//.""" payment_account = PaymentGroupPaymentAccountFactory.create() payment_account_id = payment_account.payment_group_payment_account_id assert not payment_account.note put_body = {'note': 'hello world'} res = fixture_client.put( f'/payment-group-payment-account/{payment_account_id}', json=put_body ) assert res.status_code == 201 assert payment_account.note == put_body.get('note') def test_get_list_by_payment_group_payment_with_payment_status( fixture_client, mock_payments ): """GET /payment-group-payment//accounts.""" payment_group_payment_id = 501 # from mock_payments test fixtures payment_status = ['Batch Failure', 'Canceled', 'Failed', 'Pending', 'Successful'] query_string = 'limit=10&offset=0&sort_by=payment_status&sort_order=asc' res = fixture_client.get( f'/payment-group-payment/{payment_group_payment_id}/accounts?{query_string}' ) assert res.status_code == 200 items = res.json.get('items') assert res.json.get('total_count') == 8 error_codes = [ item.get('payment_error_code') for item in items if item.get('payment_error_code') ] assert items[0].get('payment_status') is None assert all(item.get('payment_status') in payment_status for item in items[1:8]) assert len(error_codes) == 4 assert items[1].get('payment_error_code') == 'Batch 001 Has Failed' assert items[2].get('payment_error_code') == 'Batch 001 Has Failed' assert items[3].get('payment_error_code') == 'Payment Rejected' assert items[4].get('payment_error_code') == 'Insufficient Funds' @pytest.mark.parametrize('payment_type', PAYMENT_TYPES) def test_get_credit_data_success( payment_type, fixture_client, mock_accounts, mock_contracts, mock_abacus_event, mock_ledger_account_contracts, ): """Test GET /payment-group-payment-account///credit.""" # noqa worksheet_closing_balance = WorksheetAccountContractClosingBalanceFactory.create() worksheet_after_tax1 = WorksheetPayableBalanceAfterTaxFactory.create( # noqa worksheet_account_contract_closing_balance_id=worksheet_closing_balance.worksheet_account_contract_closing_balance_id ) detail1 = PaymentGroupPaymentAccountDetailFactory.create( # noqa worksheet_account_contract_payable_after_tax_id=worksheet_after_tax1.worksheet_account_contract_payable_after_tax_id ) worksheet_after_tax2 = WorksheetPayableBalanceAfterTaxFactory.create( # noqa worksheet_account_contract_closing_balance_id=worksheet_closing_balance.worksheet_account_contract_closing_balance_id ) detail2 = PaymentGroupPaymentAccountDetailFactory.create( # noqa worksheet_account_contract_payable_after_tax_id=worksheet_after_tax2.worksheet_account_contract_payable_after_tax_id ) PaymentGroupPaymentBatchAccountFactory.create( payment_group_payment_account=detail2.payment_group_payment_account ) res = fixture_client.get( f'/payment-group-payment-account/' f'{detail1.payment_group_payment_account_id}/{payment_type}/credit' ) match payment_type: case PAYMENT_TYPES.WHT: # negative according to formula, make positive amount = ( detail1.tax_withholding_amount * -1 if detail1.tax_withholding_amount else detail1.tax_withholding_amount ) case PAYMENT_TYPES.VAT: # positive according to formula, make negative amount = ( detail1.vat_amount * -1 if detail1.vat_amount else detail1.vat_amount ) case _: # positive according to formula, return as is amount = detail1.payable_amount_post_tax assert res.status_code == 200 assert res.json == [ { 'account_id': detail1.account_id, 'amount': str(amount), 'contract_id': detail1.contract_id, 'currency_code': detail1.currency_code, 'worksheet_account_contract_payable_after_tax_id': detail1.worksheet_account_contract_payable_after_tax_id, } ] @pytest.mark.parametrize('payment_type', PAYMENT_TYPES) def test_get_credit_data_no_zero_amounts( payment_type, fixture_client, mock_accounts, mock_contracts, mock_abacus_event, mock_ledger_account_contracts, ): """Test GET /payment-group-payment-account///credit.""" # noqa worksheet_closing_balance = WorksheetAccountContractClosingBalanceFactory.create() worksheet_after_tax1 = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_closing_balance_id=worksheet_closing_balance.worksheet_account_contract_closing_balance_id ) detail1 = PaymentGroupPaymentAccountDetailFactory.create( worksheet_account_contract_payable_after_tax_id=worksheet_after_tax1.worksheet_account_contract_payable_after_tax_id, payable_amount_pre_tax=0, tax_withholding_amount=0, payable_amount_post_tax=0, vat_amount=0, ) worksheet_after_tax2 = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_closing_balance_id=worksheet_closing_balance.worksheet_account_contract_closing_balance_id ) detail2 = PaymentGroupPaymentAccountDetailFactory.create( worksheet_account_contract_payable_after_tax_id=worksheet_after_tax2.worksheet_account_contract_payable_after_tax_id ) PaymentGroupPaymentBatchAccountFactory.create( payment_group_payment_account=detail2.payment_group_payment_account ) res = fixture_client.get( f'/payment-group-payment-account/' f'{detail1.payment_group_payment_account_id}/{payment_type}/credit' ) assert res.status_code == 200 assert res.json == [] def test_get_credit_data_failure(fixture_client, faker): """Test GET /payment-group-payment-account///credit.""" # noqa payment_group_payment_account_id = faker.pyint() payment_type = faker.pystr(prefix='test_') res = fixture_client.get( f'/payment-group-payment-account' f'/{payment_group_payment_account_id}/{payment_type}/credit' ) assert res.status_code == 404 def test_get_paginated_list_of_last_payment_by_filters_success( fixture_client, mock_abacus_event, mock_statement_periods, mock_accounts, mock_contracts, create_mock_payment_state, ): """Test POST /payment-group-payment-account/last-payment/bulk.""" payment_group_payment_account = PaymentGroupPaymentAccountFactory.create( account_id=1, current_statement_period_id=2, payment_group_payment_account_id=7, ) payment_group_payment_batch = PaymentGroupPaymentBatchFactory.create( payment_group_payment_batch_id=3 ) PaymentGroupPaymentBatchAccountFactory.create( payment_group_payment_batch=payment_group_payment_batch, payment_group_payment_account=payment_group_payment_account, ) res = fixture_client.post( '/payment-group-payment-account/last-payment/bulk/?limit=100&offset=0', json={ 'filters': { 'account_ids': [1], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 1 assert ( res.json['items'][0]['payment_group_payment_account_id'] == payment_group_payment_account.payment_group_payment_account_id ) def test_get_paginated_list_of_last_payment_by_filters_failure( fixture_client, mock_abacus_event, mock_statement_periods, mock_accounts, mock_contracts, mock_worksheet_account_contract_closing_balance, create_mock_payment_state, ): """Test POST /payment-group-payment-account/last-payment/bulk.""" res = fixture_client.post( '/payment-group-payment-account/last-payment/bulk/?limit=1a&offset=0b', json={ 'filters': { 'account_ids': [1, 2], } }, ) assert res.status_code == 400, res.text assert res.json == { 'code': 'error', 'message': { 'query': { 'limit': ['Not a valid integer.'], 'offset': ['Not a valid integer.'], } }, } res = fixture_client.post( '/payment-group-payment-account/last-payment/bulk/?limit=100&offset=0', json={ 'filters': { 'account_ids': ['1a', '2b'], } }, ) assert res.status_code == 400, res.text assert res.json == { 'code': 'error', 'message': { 'json': { 'filters': { 'account_ids': { '0': ['Must be an integer ' 'greater or equal to ' '0.'], '1': ['Must be an integer ' 'greater or equal to ' '0.'], }, } } }, } res = fixture_client.post( '/payment-group-payment-account/last-payment/bulk/?limit=100&offset=0', json={ 'filters': { 'payment_statuses': ['complete'], } }, ) assert res.status_code == 400, res.text assert res.json == { 'code': 'error', 'message': { 'json': { 'filters': { 'account_ids': ['Must be specified.'], 'payment_statuses': ['Unknown field.'], } } }, } def test_payments_search_post_success(fixture_client, mock_payments): """Test POST /payment-group-payment-account/search with various filters.""" # no filters res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': {'account_ids': [], 'contract_ids': [], 'payment_statuses': []} }, ) assert res.status_code == 200 assert res.json['total_count'] == 8 assert len(res.json['items']) == 8 # filter by payment_statuses res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': { 'account_ids': [], 'contract_ids': [], 'payment_statuses': ['complete', 'error'], } }, ) assert res.status_code == 200 assert res.json['total_count'] == 2 assert len(res.json['items']) == 2 assert res.json['items'][0]['account_id'] == 6 assert res.json['items'][1]['account_id'] == 7 # filter by account_ids res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': { 'account_ids': [1], 'contract_ids': [], 'payment_statuses': [], } }, ) assert res.status_code == 200 assert res.json['total_count'] == 1 assert len(res.json['items']) == 1 assert res.json['items'][0]['account_id'] == 1 # filter by contract_ids res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': { 'account_ids': [], 'contract_ids': [2], 'payment_statuses': [], } }, ) assert res.status_code == 200 assert res.json['total_count'] == 1 assert len(res.json['items']) == 1 # filter by all filters res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': { 'account_ids': [1], 'contract_ids': [1], 'payment_statuses': ['init'], } }, ) assert res.status_code == 200 assert res.json['total_count'] == 1 assert len(res.json['items']) == 1 def test_payments_search_post_failure(fixture_client, mock_payments): """Test POST /payment-group-payment-account/search failure validation.""" res = fixture_client.post( '/payment-group-payment-account/search?limit=300&offset=0', json={ 'filters': { 'account_ids': ['noint'], 'contract_ids': ['noint'], 'payment_statuses': [False], } }, ) assert res.status_code == 400 assert res.json == { 'code': 'error', 'message': { 'json': { 'filters': { 'account_ids': { '0': ['Must be an integer ' 'greater or equal to ' '0.'] }, 'contract_ids': { '0': ['Must be an integer ' 'greater or equal to ' '0.'] }, 'payment_statuses': {'0': ['Not a valid ' 'string.']}, } } }, } def test_get_payable_details_bulk_success( fixture_client, mock_abacus_event, mock_accounts, mock_contracts, mock_worksheet_account_contract_closing_balance_active, ): """Test POST /payment-group-payment-account/payable-details/bulk success.""" # Create payment_group_payment_account payment_group_payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment_account_id=1, account_id=1, ) # Create worksheet payable balance after tax worksheet_balance = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_payable_after_tax_id=1, ) # Create payment_group_payment_account_detail payment_detail = PaymentGroupPaymentAccountDetailFactory.create( payment_group_payment_account=payment_group_payment_account, worksheet_account_contract_payable_after_tax_id=worksheet_balance.worksheet_account_contract_payable_after_tax_id, ) # Create worksheet_account_contract_payable_details payable_details = WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance, payable_detail_type_id=6, account_id=1, contract_id=1, statement_period_id=1, ) res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [1], 'payable_detail_type_ids': [6], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 1 assert len(res.json['items']) == 1 assert ( res.json['items'][0]['worksheet_account_contract_payable_details_id'] == payable_details.worksheet_account_contract_payable_details_id ) assert res.json['items'][0]['payable_detail_type_id'] == 6 assert res.json['items'][0]['account_id'] == 1 def test_get_payable_details_bulk_multiple_accounts( fixture_client, mock_abacus_event, mock_accounts, mock_contracts, mock_worksheet_account_contract_closing_balance_active, ): """Test POST /payment-group-payment-account/payable-details/bulk with multiple accounts.""" # Create first payment_group_payment_account payment_group_payment_account_1 = PaymentGroupPaymentAccountFactory.create( payment_group_payment_account_id=1, account_id=1, ) # Create second payment_group_payment_account payment_group_payment_account_2 = PaymentGroupPaymentAccountFactory.create( payment_group_payment_account_id=2, account_id=2, ) # Create worksheet balances worksheet_balance_1 = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_payable_after_tax_id=1, ) worksheet_balance_2 = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_payable_after_tax_id=2, ) # Create payment details PaymentGroupPaymentAccountDetailFactory.create( payment_group_payment_account=payment_group_payment_account_1, worksheet_account_contract_payable_after_tax_id=worksheet_balance_1.worksheet_account_contract_payable_after_tax_id, ) PaymentGroupPaymentAccountDetailFactory.create( payment_group_payment_account=payment_group_payment_account_2, worksheet_account_contract_payable_after_tax_id=worksheet_balance_2.worksheet_account_contract_payable_after_tax_id, ) # Create payable details WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance_1, payable_detail_type_id=6, account_id=1, contract_id=1, statement_period_id=1, ) WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance_2, payable_detail_type_id=6, account_id=2, contract_id=2, statement_period_id=1, ) res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [1, 2], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 2 assert len(res.json['items']) == 2 def test_get_payable_details_bulk_with_pagination( fixture_client, mock_abacus_event, mock_accounts, mock_contracts, mock_worksheet_account_contract_closing_balance_active, ): """Test POST /payment-group-payment-account/payable-details/bulk with pagination.""" payment_group_payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment_account_id=1, account_id=1, ) worksheet_balance = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_payable_after_tax_id=1, ) PaymentGroupPaymentAccountDetailFactory.create( payment_group_payment_account=payment_group_payment_account, worksheet_account_contract_payable_after_tax_id=worksheet_balance.worksheet_account_contract_payable_after_tax_id, ) # Create multiple payable details for i in range(5): WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance, payable_detail_type_id=6, account_id=1, contract_id=i + 1, statement_period_id=1, target_table=f'table_{i}', target_id=i + 1, ) # First page res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=2&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [1], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 5 assert len(res.json['items']) == 2 # Second page res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=2&offset=2', json={ 'filters': { 'payment_group_payment_account_ids': [1], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 5 assert len(res.json['items']) == 2 def test_get_payable_details_bulk_failure_validation( fixture_client, ): """Test POST /payment-group-payment-account/payable-details/bulk validation failures.""" # Missing required filters res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={}, ) assert res.status_code == 400, res.text assert 'filters' in res.json['message']['json'] # Empty payment_group_payment_account_ids list res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [], } }, ) assert res.status_code == 400, res.text # Invalid payment_group_payment_account_ids type res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': ['invalid'], } }, ) assert res.status_code == 400, res.text # Invalid pagination parameters res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=invalid&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [1], } }, ) assert res.status_code == 400, res.text def test_get_payable_details_bulk_filters_soft_deleted( fixture_client, mock_abacus_event, mock_accounts, mock_contracts, mock_worksheet_account_contract_closing_balance_active, ): """Test POST /payment-group-payment-account/payable-details/bulk filters out soft-deleted records.""" payment_group_payment_account = PaymentGroupPaymentAccountFactory.create( payment_group_payment_account_id=1, account_id=1, ) worksheet_balance = WorksheetPayableBalanceAfterTaxFactory.create( worksheet_account_contract_payable_after_tax_id=1, ) PaymentGroupPaymentAccountDetailFactory.create( payment_group_payment_account=payment_group_payment_account, worksheet_account_contract_payable_after_tax_id=worksheet_balance.worksheet_account_contract_payable_after_tax_id, ) # Create active payable detail WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance, payable_detail_type_id=6, account_id=1, contract_id=1, statement_period_id=1, ) # Create soft-deleted payable detail from datetime import datetime WorksheetAccountContractPayableDetailsFactory.create( worksheet_account_contract_payable_after_tax=worksheet_balance, payable_detail_type_id=6, account_id=1, contract_id=2, statement_period_id=1, deleted_at=datetime.now(), ) res = fixture_client.post( '/payment-group-payment-account/payable-details/bulk?limit=100&offset=0', json={ 'filters': { 'payment_group_payment_account_ids': [1], } }, ) assert res.status_code == 200, res.text assert res.json['total_count'] == 1 assert len(res.json['items']) == 1