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

@@ -25,7 +25,8 @@ class VendorCreate(BaseModel):
"""
Schema for creating a new vendor (storefront/brand) under an existing company.
Business contact info is inherited from the parent company.
Contact info is inherited from the parent company by default.
Optionally, provide contact fields to override from the start.
"""
# Parent company
@@ -51,6 +52,13 @@ class VendorCreate(BaseModel):
letzshop_csv_url_en: str | None = Field(None, description="English CSV URL")
letzshop_csv_url_de: str | None = Field(None, description="German CSV URL")
# Contact Info (optional - if not provided, inherited from company)
contact_email: str | None = Field(None, description="Override company contact email")
contact_phone: str | None = Field(None, description="Override company contact phone")
website: str | None = Field(None, description="Override company website")
business_address: str | None = Field(None, description="Override company business address")
tax_number: str | None = Field(None, description="Override company tax number")
@field_validator("subdomain")
@classmethod
def validate_subdomain(cls, v):
@@ -72,8 +80,8 @@ class VendorUpdate(BaseModel):
"""
Schema for updating vendor information (Admin only).
Note: Business contact info (contact_email, etc.) is at the Company level.
Use company update endpoints to modify those fields.
Contact fields can be overridden at the vendor level.
Set to null/empty to reset to company default (inherit).
"""
# Basic Information
@@ -90,6 +98,18 @@ class VendorUpdate(BaseModel):
is_active: bool | None = None
is_verified: bool | None = None
# Contact Info (set value to override, set to empty string to reset to inherit)
contact_email: str | None = Field(None, description="Override company contact email")
contact_phone: str | None = Field(None, description="Override company contact phone")
website: str | None = Field(None, description="Override company website")
business_address: str | None = Field(None, description="Override company business address")
tax_number: str | None = Field(None, description="Override company tax number")
# Special flag to reset contact fields to inherit from company
reset_contact_to_company: bool | None = Field(
None, description="If true, reset all contact fields to inherit from company"
)
@field_validator("subdomain")
@classmethod
def subdomain_lowercase(cls, v):
@@ -135,16 +155,14 @@ class VendorResponse(BaseModel):
class VendorDetailResponse(VendorResponse):
"""
Extended vendor response including company information.
Extended vendor response including company information and resolved contact info.
Includes company details like contact info and owner information.
Contact fields show the effective value (vendor override or company default)
with flags indicating if the value is inherited from the parent company.
"""
# Company info
company_name: str = Field(..., description="Name of the parent company")
company_contact_email: str = Field(..., description="Company business contact email")
company_contact_phone: str | None = Field(None, description="Company phone number")
company_website: str | None = Field(None, description="Company website URL")
# Owner info (at company level)
owner_email: str = Field(
@@ -152,6 +170,25 @@ class VendorDetailResponse(VendorResponse):
)
owner_username: str = Field(..., description="Username of the company owner")
# Resolved contact info (vendor override or company default)
contact_email: str | None = Field(None, description="Effective contact email")
contact_phone: str | None = Field(None, description="Effective contact phone")
website: str | None = Field(None, description="Effective website")
business_address: str | None = Field(None, description="Effective business address")
tax_number: str | None = Field(None, description="Effective tax number")
# Inheritance flags (True = value is inherited from company, not overridden)
contact_email_inherited: bool = Field(False, description="True if contact_email is from company")
contact_phone_inherited: bool = Field(False, description="True if contact_phone is from company")
website_inherited: bool = Field(False, description="True if website is from company")
business_address_inherited: bool = Field(False, description="True if business_address is from company")
tax_number_inherited: bool = Field(False, description="True if tax_number is from company")
# Original company values (for reference in UI)
company_contact_email: str | None = Field(None, description="Company's contact email")
company_contact_phone: str | None = Field(None, description="Company's phone number")
company_website: str | None = Field(None, description="Company's website URL")
class VendorCreateResponse(VendorDetailResponse):
"""