"""Unit tests for Worksheet Payment Custom schemas.""" from decimal import Decimal from abacus_common_logic.constants.constants import DATETIME_FORMAT from marshmallow import ValidationError import pytest from payment.schemas.worksheet_payment_custom import ( WorksheetPaymentCustomDetailSchema, WorksheetPaymentCustomFilterParamsPostSchema, WorksheetPaymentCustomPostSchema, ) from tests.utils.factories import WorksheetPaymentCustomFactory def test_worksheet_payment_custom_post_schema_load(): """Test WorksheetPaymentCustomPostSchema with all fields.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'withholding_tax_rate': '5.00', 'vat_amount': '5.00', 'vat_rate': '10.00', 'amount_after_withholding_and_vat': '115.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['account_id'] == 1 assert result['contract_id'] == 2 assert result['currency_code'] == 'USD' assert result['amount'] == Decimal('100.00') assert result['withholding_tax_amount'] == Decimal('10.00') assert result['withholding_tax_rate'] == Decimal('5.00') assert result['vat_amount'] == Decimal('5.00') assert result['vat_rate'] == Decimal('10.00') assert result['amount_after_withholding_and_vat'] == Decimal('115.00') assert result['activity_statement_period_id'] == 3 assert result['statement_period_id'] == 4 assert result['payment_name'] == 'Test Payment' def test_worksheet_payment_custom_post_schema_load_with_null_values(): """Test WorksheetPaymentCustomPostSchema with null optional fields. Optional tax/vat fields now default to 0 when omitted, but explicit nulls are not allowed. """ test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': None, 'withholding_tax_rate': None, 'vat_amount': None, 'vat_rate': None, 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'withholding_tax_amount' in errors assert 'withholding_tax_rate' in errors assert 'vat_amount' in errors assert 'vat_rate' in errors def test_worksheet_payment_custom_post_schema_load_without_optional_fields(): """Test WorksheetPaymentCustomPostSchema without optional fields. Missing tax/vat fields should default to 0. """ test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['account_id'] == 1 assert result['amount'] == Decimal('100.00') assert result['withholding_tax_amount'] == Decimal('0') assert result['withholding_tax_rate'] == Decimal('0') assert result['vat_amount'] == Decimal('0') assert result['vat_rate'] == Decimal('0') assert result['amount_after_withholding_and_vat'] == Decimal('100.00') def test_worksheet_payment_custom_post_schema_load_with_positive_wht(): """Test WorksheetPaymentCustomPostSchema with positive withholding tax.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'amount_after_withholding_and_vat': '120.00', 'withholding_tax_rate': '5.00', 'vat_rate': '10.00', 'vat_amount': '10.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['withholding_tax_amount'] == Decimal('10.00') assert result['amount_after_withholding_and_vat'] == Decimal('120.00') def test_worksheet_payment_custom_post_schema_load_with_negative_vat(): """Test WorksheetPaymentCustomPostSchema with negative VAT.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'vat_amount': '-5.00', 'amount_after_withholding_and_vat': '90.00', 'withholding_tax_rate': '5.00', 'withholding_tax_amount': '-5.00', 'vat_rate': '10.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['vat_amount'] == Decimal('-5.00') assert result['amount_after_withholding_and_vat'] == Decimal('90.00') def test_worksheet_payment_custom_post_schema_load_failure_negative_amount(): """Test WorksheetPaymentCustomPostSchema fails with negative amount.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '-100.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'amount' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_load_failure_negative_rates(): """Test WorksheetPaymentCustomPostSchema fails with negative amount.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'withholding_tax_rate': '-5.00', 'vat_amount': '5.00', 'vat_rate': '-10.00', 'amount_after_withholding_and_vat': '115.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'withholding_tax_rate' in exc_info.value.messages assert 'vat_rate' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_load_failure_zero_post_tax_amount(): """Test WorksheetPaymentCustomPostSchema fails with zero post-tax amount.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '100.00', 'amount_after_withholding_and_vat': '0.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'amount_after_withholding_and_vat' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_load_failure_negative_post_tax_amount(): """Test WorksheetPaymentCustomPostSchema fails with negative post-tax amount.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '150.00', 'amount_after_withholding_and_vat': '-50.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'amount_after_withholding_and_vat' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_load_failure_missing_required_fields(): """Test WorksheetPaymentCustomPostSchema fails with missing required fields.""" test_data = { 'account_id': 1, } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'contract_id' in errors assert 'currency_code' in errors assert 'amount' in errors assert 'amount_after_withholding_and_vat' in errors def test_worksheet_payment_custom_post_schema_load_failure_missing_rates_fields(): """Test WorksheetPaymentCustomPostSchema fails with missing rates fields.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'amount_after_withholding_and_vat': '115.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'withholding_tax_rate' in errors assert 'vat_rate' in errors def test_worksheet_payment_custom_post_schema_load_failure_missing_withholding_tax_amount(): """Fail when withholding_tax_rate is set but withholding_tax_amount is missing.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', # withholding_tax_amount is intentionally missing 'withholding_tax_rate': '5.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'withholding_tax_amount' in errors def test_worksheet_payment_custom_post_schema_load_failure_missing_vat_amount(): """Fail when vat_rate is set but vat_amount is missing.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', # vat_amount is intentionally missing 'vat_rate': '10.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'vat_amount' in errors def test_worksheet_payment_custom_post_schema_load_failure_empty_string_fields(): """Test WorksheetPaymentCustomPostSchema fails with empty strings.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': '', 'amount': '100.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': '', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert 'currency_code' in errors assert 'payment_name' in errors def test_worksheet_payment_custom_detail_schema_dump(): """Test WorksheetPaymentCustomDetailSchema dump.""" record = WorksheetPaymentCustomFactory.build( worksheet_payment_custom_id=1, account_id=1, contract_id=2, currency_code='USD', amount=Decimal('100.00'), withholding_tax_amount=Decimal('10.00'), withholding_tax_rate=Decimal('5.00'), vat_amount=Decimal('5.00'), vat_rate=Decimal('10.00'), amount_after_withholding_and_vat=Decimal('95.00'), activity_statement_period_id=3, statement_period_id=4, payment_name='Test Payment', ) schema = WorksheetPaymentCustomDetailSchema() result = schema.dump(record) assert result['worksheet_payment_custom_id'] == 1 assert result['account_id'] == 1 assert result['contract_id'] == 2 assert result['currency_code'] == 'USD' assert result['amount'] == '100.00' assert result['withholding_tax_amount'] == '10.00' assert result['withholding_tax_rate'] == '5.00' assert result['vat_amount'] == '5.00' assert result['vat_rate'] == '10.00' assert result['amount_after_withholding_and_vat'] == '95.00' assert result['activity_statement_period_id'] == 3 assert result['statement_period_id'] == 4 assert result['payment_name'] == 'Test Payment' assert result['created_at'] == record.created_at.strftime(DATETIME_FORMAT) assert result['last_modified'] == record.last_modified.strftime(DATETIME_FORMAT) assert result['created_by'] == record.created_by assert result['last_modified_by'] == record.last_modified_by # Calculation validation tests def test_worksheet_payment_custom_post_schema_valid_calculation_all_values(): """Test valid calculation with all values present.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'withholding_tax_rate': '5.00', 'vat_rate': '10.00', 'amount_after_withholding_and_vat': '115.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('115.00') def test_worksheet_payment_custom_post_schema_valid_calculation_with_positive_wht(): """Test valid calculation with positive withholding tax.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '10.00', 'withholding_tax_rate': '5.00', 'vat_rate': '10.00', 'amount_after_withholding_and_vat': '120.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('120.00') def test_worksheet_payment_custom_post_schema_valid_calculation_with_negative_vat(): """Test valid calculation with negative VAT.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '-5.00', 'vat_amount': '-5.00', 'withholding_tax_rate': '5.00', 'vat_rate': '10.00', 'amount_after_withholding_and_vat': '90.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('90.00') def test_worksheet_payment_custom_post_schema_valid_calculation_with_missing_wht_vat(): """Test valid calculation with missing withholding tax and VAT fields.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('100.00') def test_worksheet_payment_custom_post_schema_valid_calculation_with_zero_values(): """Test valid calculation with zero values for withholding tax and VAT.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '0.00', 'vat_amount': '0.00', 'amount_after_withholding_and_vat': '100.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('100.00') def test_worksheet_payment_custom_post_schema_valid_with_null_amount_after(): """Test that null amount_after is allowed (no validation on null).""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'amount_after_withholding_and_vat': None, 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'amount_after_withholding_and_vat' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_valid_without_amount_after(): """Test that missing amount_after raises validation error.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) assert 'amount_after_withholding_and_vat' in exc_info.value.messages def test_worksheet_payment_custom_post_schema_invalid_calculation_incorrect_sum(): """Test invalid calculation where the sum is incorrect.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'amount_after_withholding_and_vat': '100.00', # Should be 115.00 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert '_schema' in errors or 'amount_after_withholding_and_vat' in errors def test_worksheet_payment_custom_post_schema_invalid_calculation_too_low(): """Test invalid calculation where amount_after is too low.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'withholding_tax_amount': '10.00', 'vat_amount': '5.00', 'amount_after_withholding_and_vat': '80.00', # Should be 115.00 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert '_schema' in errors or 'amount_after_withholding_and_vat' in errors def test_worksheet_payment_custom_post_schema_invalid_calculation_missing_wht_vat(): """Test invalid calculation with missing withholding tax and VAT fields.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.00', 'amount_after_withholding_and_vat': '90.00', # Should be 100.00 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert '_schema' in errors or 'amount_after_withholding_and_vat' in errors def test_worksheet_payment_custom_post_schema_valid_calculation_with_decimals(): """Test valid calculation with decimal values.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.50', 'withholding_tax_amount': '10.25', 'withholding_tax_rate': '10.00', 'vat_amount': '5.15', 'vat_rate': '5.00', 'amount_after_withholding_and_vat': '115.90', 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() result = schema.load(test_data) assert result['amount_after_withholding_and_vat'] == Decimal('115.90') def test_worksheet_payment_custom_post_schema_invalid_calculation_decimal_precision(): """Test invalid calculation due to decimal precision issues.""" test_data = { 'account_id': 1, 'contract_id': 2, 'currency_code': 'USD', 'amount': '100.50', 'withholding_tax_amount': '10.25', 'vat_amount': '5.15', 'amount_after_withholding_and_vat': '115.91', # Should be 115.90 'activity_statement_period_id': 3, 'statement_period_id': 4, 'payment_name': 'Test Payment', } schema = WorksheetPaymentCustomPostSchema() with pytest.raises(ValidationError) as exc_info: schema.load(test_data) errors = exc_info.value.messages assert '_schema' in errors or 'amount_after_withholding_and_vat' in errors def test_worksheet_payment_custom_filter_params_schema_empty(): """Test WorksheetPaymentCustomFilterParamsPostSchema with empty filters.""" schema = WorksheetPaymentCustomFilterParamsPostSchema() result = schema.load({}) # Should have default empty filters assert result['filters'] == {} def test_worksheet_payment_custom_filter_params_schema_with_ids(): """Test WorksheetPaymentCustomFilterParamsPostSchema with IDs in filters.""" schema = WorksheetPaymentCustomFilterParamsPostSchema() result = schema.load({'filters': {'worksheet_payment_custom_ids': [1, 2, 3]}}) assert result['filters']['worksheet_payment_custom_ids'] == [1, 2, 3] def test_worksheet_payment_custom_filter_params_schema_with_pagination(): """Test WorksheetPaymentCustomFilterParamsPostSchema without pagination (pagination is separate).""" schema = WorksheetPaymentCustomFilterParamsPostSchema() result = schema.load({'filters': {}}) # Pagination (offset/limit) is not part of this schema assert result['filters'] == {} assert 'offset' not in result assert 'limit' not in result def test_worksheet_payment_custom_filter_params_schema_combined(): """Test WorksheetPaymentCustomFilterParamsPostSchema with filters only.""" schema = WorksheetPaymentCustomFilterParamsPostSchema() result = schema.load({'filters': {'worksheet_payment_custom_ids': [1, 2]}}) assert result['filters']['worksheet_payment_custom_ids'] == [1, 2] # Pagination is handled separately via query params assert 'offset' not in result assert 'limit' not in result def test_worksheet_payment_custom_filter_params_schema_single_id(): """Test WorksheetPaymentCustomFilterParamsPostSchema with single ID in filters.""" schema = WorksheetPaymentCustomFilterParamsPostSchema() result = schema.load({'filters': {'worksheet_payment_custom_ids': [42]}}) assert result['filters']['worksheet_payment_custom_ids'] == [42]