Customer Profile: - Add profile API (GET/PUT /api/v1/shop/profile) - Add password change endpoint (PUT /api/v1/shop/profile/password) - Implement full profile page with preferences and password sections - Add CustomerPasswordChange schema Shop Authentication Fixes: - Add Authorization header to all shop account API calls - Fix orders, order-detail, messages pages authentication - Add proper redirect to login on 401 responses - Fix toast message showing noqa comment in shop-layout.js VAT Calculation: - Add shared VAT utility (app/utils/vat.py) - Add VAT fields to Order model (vat_regime, vat_rate, etc.) - Align order VAT calculation with invoice settings - Add migration for VAT fields on orders Validation Framework: - Fix base_validator.py with missing methods - Add validate_file, output_results, get_exit_code methods - Fix validate_all.py import issues Documentation: - Add launch-readiness.md tracking OMS status - Update to 95% feature complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
189 lines
6.2 KiB
Python
189 lines
6.2 KiB
Python
# tests/unit/utils/test_vat.py
|
|
"""Tests for VAT calculation utilities."""
|
|
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from app.utils.vat import (
|
|
EU_VAT_RATES,
|
|
VATRegime,
|
|
VATResult,
|
|
calculate_vat_amount,
|
|
determine_vat_regime,
|
|
get_vat_rate_for_country,
|
|
get_vat_rate_label,
|
|
is_eu_country,
|
|
)
|
|
|
|
|
|
class TestGetVatRateForCountry:
|
|
"""Tests for get_vat_rate_for_country function."""
|
|
|
|
def test_luxembourg_rate(self):
|
|
"""Luxembourg should have 17% VAT."""
|
|
assert get_vat_rate_for_country("LU") == Decimal("17.00")
|
|
|
|
def test_germany_rate(self):
|
|
"""Germany should have 19% VAT."""
|
|
assert get_vat_rate_for_country("DE") == Decimal("19.00")
|
|
|
|
def test_france_rate(self):
|
|
"""France should have 20% VAT."""
|
|
assert get_vat_rate_for_country("FR") == Decimal("20.00")
|
|
|
|
def test_hungary_rate(self):
|
|
"""Hungary should have 27% VAT (highest in EU)."""
|
|
assert get_vat_rate_for_country("HU") == Decimal("27.00")
|
|
|
|
def test_non_eu_country(self):
|
|
"""Non-EU countries should have 0% VAT."""
|
|
assert get_vat_rate_for_country("US") == Decimal("0.00")
|
|
assert get_vat_rate_for_country("CH") == Decimal("0.00")
|
|
|
|
def test_lowercase_country_code(self):
|
|
"""Should handle lowercase country codes."""
|
|
assert get_vat_rate_for_country("lu") == Decimal("17.00")
|
|
|
|
def test_all_eu_countries_covered(self):
|
|
"""All 27 EU member states should be in the VAT rates."""
|
|
assert len(EU_VAT_RATES) == 27
|
|
|
|
|
|
class TestIsEuCountry:
|
|
"""Tests for is_eu_country function."""
|
|
|
|
def test_eu_countries(self):
|
|
"""EU countries should return True."""
|
|
assert is_eu_country("LU") is True
|
|
assert is_eu_country("DE") is True
|
|
assert is_eu_country("FR") is True
|
|
|
|
def test_non_eu_countries(self):
|
|
"""Non-EU countries should return False."""
|
|
assert is_eu_country("US") is False
|
|
assert is_eu_country("CH") is False
|
|
assert is_eu_country("UK") is False
|
|
|
|
|
|
class TestGetVatRateLabel:
|
|
"""Tests for get_vat_rate_label function."""
|
|
|
|
def test_luxembourg_label(self):
|
|
"""Luxembourg label should be formatted correctly."""
|
|
label = get_vat_rate_label("LU", Decimal("17.00"))
|
|
assert label == "Luxembourg VAT 17.00%"
|
|
|
|
def test_germany_label(self):
|
|
"""Germany label should be formatted correctly."""
|
|
label = get_vat_rate_label("DE", Decimal("19.00"))
|
|
assert label == "Germany VAT 19.00%"
|
|
|
|
|
|
class TestDetermineVatRegime:
|
|
"""Tests for determine_vat_regime function."""
|
|
|
|
def test_domestic_sale(self):
|
|
"""Same country should be domestic VAT."""
|
|
result = determine_vat_regime(
|
|
seller_country="LU",
|
|
buyer_country="LU",
|
|
)
|
|
assert result.regime == VATRegime.DOMESTIC
|
|
assert result.rate == Decimal("17.00")
|
|
assert result.destination_country is None
|
|
|
|
def test_domestic_sale_germany(self):
|
|
"""German domestic sale should use 19% VAT."""
|
|
result = determine_vat_regime(
|
|
seller_country="DE",
|
|
buyer_country="DE",
|
|
)
|
|
assert result.regime == VATRegime.DOMESTIC
|
|
assert result.rate == Decimal("19.00")
|
|
|
|
def test_cross_border_b2b_reverse_charge(self):
|
|
"""B2B with VAT number should be reverse charge (0%)."""
|
|
result = determine_vat_regime(
|
|
seller_country="LU",
|
|
buyer_country="DE",
|
|
buyer_vat_number="DE123456789",
|
|
)
|
|
assert result.regime == VATRegime.REVERSE_CHARGE
|
|
assert result.rate == Decimal("0.00")
|
|
assert result.destination_country == "DE"
|
|
assert result.label == "Reverse charge"
|
|
|
|
def test_cross_border_b2c_with_oss(self):
|
|
"""B2C with OSS should use destination country VAT."""
|
|
result = determine_vat_regime(
|
|
seller_country="LU",
|
|
buyer_country="DE",
|
|
seller_oss_registered=True,
|
|
)
|
|
assert result.regime == VATRegime.OSS
|
|
assert result.rate == Decimal("19.00") # German VAT
|
|
assert result.destination_country == "DE"
|
|
|
|
def test_cross_border_b2c_without_oss(self):
|
|
"""B2C without OSS should use origin country VAT."""
|
|
result = determine_vat_regime(
|
|
seller_country="LU",
|
|
buyer_country="DE",
|
|
seller_oss_registered=False,
|
|
)
|
|
assert result.regime == VATRegime.ORIGIN
|
|
assert result.rate == Decimal("17.00") # Luxembourg VAT
|
|
assert result.destination_country == "DE"
|
|
|
|
def test_non_eu_sale(self):
|
|
"""Non-EU sales should be VAT exempt."""
|
|
result = determine_vat_regime(
|
|
seller_country="LU",
|
|
buyer_country="US",
|
|
)
|
|
assert result.regime == VATRegime.EXEMPT
|
|
assert result.rate == Decimal("0.00")
|
|
assert result.destination_country == "US"
|
|
|
|
def test_default_to_luxembourg(self):
|
|
"""Empty seller country should default to Luxembourg."""
|
|
result = determine_vat_regime(
|
|
seller_country="",
|
|
buyer_country="LU",
|
|
)
|
|
assert result.regime == VATRegime.DOMESTIC
|
|
assert result.rate == Decimal("17.00")
|
|
|
|
|
|
class TestCalculateVatAmount:
|
|
"""Tests for calculate_vat_amount function."""
|
|
|
|
def test_standard_calculation(self):
|
|
"""Test standard VAT calculation."""
|
|
# €100 at 17% = €17
|
|
result = calculate_vat_amount(10000, Decimal("17.00"))
|
|
assert result == 1700
|
|
|
|
def test_zero_rate(self):
|
|
"""Zero rate should return 0."""
|
|
result = calculate_vat_amount(10000, Decimal("0.00"))
|
|
assert result == 0
|
|
|
|
def test_negative_rate(self):
|
|
"""Negative rate should return 0."""
|
|
result = calculate_vat_amount(10000, Decimal("-5.00"))
|
|
assert result == 0
|
|
|
|
def test_rounding(self):
|
|
"""Test proper rounding to cents."""
|
|
# €33.33 at 19% = €6.3327, should round to €6.33 (633 cents)
|
|
result = calculate_vat_amount(3333, Decimal("19.00"))
|
|
assert result == 633
|
|
|
|
def test_large_amount(self):
|
|
"""Test calculation with large amounts."""
|
|
# €10,000 at 27% = €2,700
|
|
result = calculate_vat_amount(1000000, Decimal("27.00"))
|
|
assert result == 270000
|