feat: add customer profile, VAT alignment, and fix shop auth

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>
This commit is contained in:
2026-01-02 20:31:48 +01:00
parent b5b32fb351
commit 82c07c165f
21 changed files with 2224 additions and 85 deletions

View File

@@ -0,0 +1,72 @@
# alembic/versions/q5e6f7a8b9c0_add_vat_fields_to_orders.py
"""Add VAT fields to orders table.
Adds vat_regime, vat_rate, vat_rate_label, and vat_destination_country
to enable proper VAT tracking at order creation time, aligned with
invoice VAT logic.
Revision ID: q5e6f7a8b9c0
Revises: p4d5e6f7a8b9
Create Date: 2026-01-02 10:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'q5e6f7a8b9c0'
down_revision: Union[str, None] = 'p4d5e6f7a8b9'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Add VAT regime (domestic, oss, reverse_charge, origin, exempt)
op.add_column(
'orders',
sa.Column('vat_regime', sa.String(20), nullable=True)
)
# Add VAT rate as percentage (e.g., 17.00 for 17%)
op.add_column(
'orders',
sa.Column('vat_rate', sa.Numeric(5, 2), nullable=True)
)
# Add human-readable VAT label (e.g., "Luxembourg VAT 17%")
op.add_column(
'orders',
sa.Column('vat_rate_label', sa.String(100), nullable=True)
)
# Add destination country for cross-border sales (ISO code)
op.add_column(
'orders',
sa.Column('vat_destination_country', sa.String(2), nullable=True)
)
# Populate VAT fields for existing orders based on shipping country
# Default to 'domestic' for LU orders and 'origin' for other EU orders
op.execute("""
UPDATE orders
SET vat_regime = CASE
WHEN ship_country_iso = 'LU' THEN 'domestic'
WHEN ship_country_iso IN ('AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE') THEN 'origin'
ELSE 'exempt'
END,
vat_destination_country = CASE
WHEN ship_country_iso != 'LU' AND ship_country_iso IN ('AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE') THEN ship_country_iso
ELSE NULL
END
WHERE vat_regime IS NULL
""")
def downgrade() -> None:
op.drop_column('orders', 'vat_destination_country')
op.drop_column('orders', 'vat_rate_label')
op.drop_column('orders', 'vat_rate')
op.drop_column('orders', 'vat_regime')