feat(vendor): add contact info inheritance from company

Vendors can now override company contact information for specific branding.
Fields are nullable - if null, value is inherited from parent company.

Database changes:
- Add vendor.contact_email, contact_phone, website, business_address, tax_number
- All nullable (null = inherit from company)
- Alembic migration: 28d44d503cac

Model changes:
- Add effective_* properties for resolved values
- Add get_contact_info_with_inheritance() helper

Schema changes:
- VendorCreate: Optional contact fields for override at creation
- VendorUpdate: Contact fields + reset_contact_to_company flag
- VendorDetailResponse: Resolved values + *_inherited flags

API changes:
- GET/PUT vendor endpoints return resolved contact info
- PUT accepts contact overrides (empty string = reset to inherit)
- _build_vendor_detail_response helper for consistent responses

Service changes:
- admin_service.update_vendor handles reset_contact_to_company flag
- Empty strings converted to None for inheritance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 22:30:31 +01:00
parent dd51df7b31
commit 846f92e7e4
6 changed files with 506 additions and 96 deletions

View File

@@ -0,0 +1,37 @@
"""add contact fields to vendor
Revision ID: 28d44d503cac
Revises: 9f3a25ea4991
Create Date: 2025-12-03 22:26:02.161087
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '28d44d503cac'
down_revision: Union[str, None] = '9f3a25ea4991'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Add nullable contact fields to vendor table
# These allow vendor-specific branding/identity, overriding company defaults
op.add_column('vendors', sa.Column('contact_email', sa.String(255), nullable=True))
op.add_column('vendors', sa.Column('contact_phone', sa.String(50), nullable=True))
op.add_column('vendors', sa.Column('website', sa.String(255), nullable=True))
op.add_column('vendors', sa.Column('business_address', sa.Text(), nullable=True))
op.add_column('vendors', sa.Column('tax_number', sa.String(100), nullable=True))
def downgrade() -> None:
# Remove contact fields from vendor table
op.drop_column('vendors', 'tax_number')
op.drop_column('vendors', 'business_address')
op.drop_column('vendors', 'website')
op.drop_column('vendors', 'contact_phone')
op.drop_column('vendors', 'contact_email')