vendor features for admin and vendor admin area

This commit is contained in:
2025-10-19 16:03:25 +02:00
parent 06bb463468
commit 9aee314837
15 changed files with 1693 additions and 471 deletions

View File

@@ -31,6 +31,7 @@ class UserRegister(BaseModel):
class UserLogin(BaseModel):
username: str = Field(..., description="Username")
password: str = Field(..., description="Password")
vendor_code: Optional[str] = Field(None, description="Optional vendor code for context")
@field_validator("username")
@classmethod

View File

@@ -1,47 +1,86 @@
# models/schema/vendor.py
"""
Pydantic schemas for Vendor-related operations.
Schemas include:
- VendorCreate: For creating vendors with owner accounts
- VendorUpdate: For updating vendor information (Admin only)
- VendorResponse: Standard vendor response
- VendorDetailResponse: Vendor response with owner details
- VendorCreateResponse: Response after vendor creation (includes credentials)
- VendorListResponse: Paginated vendor list
- VendorSummary: Lightweight vendor info
- VendorTransferOwnership: For transferring vendor ownership
"""
import re
from datetime import datetime
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Any
from pydantic import BaseModel, ConfigDict, Field, field_validator
class VendorCreate(BaseModel):
"""Schema for creating a new vendor."""
vendor_code: str = Field(..., description="Unique vendor identifier (e.g., TECHSTORE)")
subdomain: str = Field(..., description="Unique subdomain for the vendor")
name: str = Field(..., description="Display name of the vendor")
description: Optional[str] = None
"""Schema for creating a new vendor with owner account."""
# Owner information - REQUIRED for admin creation
owner_email: str = Field(..., description="Email for the vendor owner account")
# Basic Information
vendor_code: str = Field(
...,
description="Unique vendor identifier (e.g., TECHSTORE)",
min_length=2,
max_length=50
)
subdomain: str = Field(
...,
description="Unique subdomain for the vendor",
min_length=2,
max_length=100
)
name: str = Field(
...,
description="Display name of the vendor",
min_length=2,
max_length=255
)
description: Optional[str] = Field(None, description="Vendor description")
# Contact information
contact_phone: Optional[str] = None
website: Optional[str] = None
# Owner Information (Creates User Account)
owner_email: str = Field(
...,
description="Email for the vendor owner (used for login and authentication)"
)
# Business information
business_address: Optional[str] = None
tax_number: Optional[str] = None
# Business Contact Information (Vendor Fields)
contact_email: Optional[str] = Field(
None,
description="Public business contact email (defaults to owner_email if not provided)"
)
contact_phone: Optional[str] = Field(None, description="Contact phone number")
website: Optional[str] = Field(None, description="Website URL")
# Letzshop CSV URLs (multi-language support)
letzshop_csv_url_fr: Optional[str] = None
letzshop_csv_url_en: Optional[str] = None
letzshop_csv_url_de: Optional[str] = None
# Business Details
business_address: Optional[str] = Field(None, description="Business address")
tax_number: Optional[str] = Field(None, description="Tax/VAT number")
# Theme configuration
theme_config: Optional[Dict] = Field(default_factory=dict)
# Marketplace URLs (multi-language support)
letzshop_csv_url_fr: Optional[str] = Field(None, description="French CSV URL")
letzshop_csv_url_en: Optional[str] = Field(None, description="English CSV URL")
letzshop_csv_url_de: Optional[str] = Field(None, description="German CSV URL")
@field_validator("owner_email")
# Theme Configuration
theme_config: Optional[Dict] = Field(default_factory=dict, description="Theme settings")
@field_validator("owner_email", "contact_email")
@classmethod
def validate_owner_email(cls, v):
if not v or "@" not in v or "." not in v:
raise ValueError("Valid email address required for vendor owner")
return v.lower()
def validate_emails(cls, v):
"""Validate email format and normalize to lowercase."""
if v and ("@" not in v or "." not in v):
raise ValueError("Invalid email format")
return v.lower() if v else v
@field_validator("subdomain")
@classmethod
def validate_subdomain(cls, v):
# Basic subdomain validation: lowercase alphanumeric with hyphens
"""Validate subdomain format: lowercase alphanumeric with hyphens."""
if v and not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', v):
raise ValueError("Subdomain must contain only lowercase letters, numbers, and hyphens")
return v.lower() if v else v
@@ -49,35 +88,66 @@ class VendorCreate(BaseModel):
@field_validator("vendor_code")
@classmethod
def validate_vendor_code(cls, v):
# Ensure vendor code is uppercase for consistency
"""Ensure vendor code is uppercase for consistency."""
return v.upper() if v else v
class VendorUpdate(BaseModel):
"""Schema for updating vendor information."""
name: Optional[str] = None
"""
Schema for updating vendor information (Admin only).
Note: owner_email is NOT included here. To change the owner,
use the transfer-ownership endpoint instead.
"""
# Basic Information
name: Optional[str] = Field(None, min_length=2, max_length=255)
description: Optional[str] = None
contact_email: Optional[str] = None
subdomain: Optional[str] = Field(None, min_length=2, max_length=100)
# Business Contact Information (Vendor Fields)
contact_email: Optional[str] = Field(
None,
description="Public business contact email"
)
contact_phone: Optional[str] = None
website: Optional[str] = None
# Business Details
business_address: Optional[str] = None
tax_number: Optional[str] = None
# Marketplace URLs
letzshop_csv_url_fr: Optional[str] = None
letzshop_csv_url_en: Optional[str] = None
letzshop_csv_url_de: Optional[str] = None
# Theme Configuration
theme_config: Optional[Dict] = None
# Status (Admin only)
is_active: Optional[bool] = None
is_verified: Optional[bool] = None
@field_validator("subdomain")
@classmethod
def subdomain_lowercase(cls, v):
"""Normalize subdomain to lowercase."""
return v.lower().strip() if v else v
@field_validator("contact_email")
@classmethod
def validate_contact_email(cls, v):
"""Validate contact email format."""
if v and ("@" not in v or "." not in v):
raise ValueError("Invalid email format")
return v.lower() if v else v
model_config = ConfigDict(from_attributes=True)
class VendorResponse(BaseModel):
"""Schema for vendor response data."""
"""Standard schema for vendor response data."""
model_config = ConfigDict(from_attributes=True)
id: int
@@ -87,24 +157,24 @@ class VendorResponse(BaseModel):
description: Optional[str]
owner_user_id: int
# Contact information
# Contact Information (Business)
contact_email: Optional[str]
contact_phone: Optional[str]
website: Optional[str]
# Business information
# Business Information
business_address: Optional[str]
tax_number: Optional[str]
# Letzshop URLs
# Marketplace URLs
letzshop_csv_url_fr: Optional[str]
letzshop_csv_url_en: Optional[str]
letzshop_csv_url_de: Optional[str]
# Theme configuration
# Theme Configuration
theme_config: Dict
# Status flags
# Status Flags
is_active: bool
is_verified: bool
@@ -113,6 +183,42 @@ class VendorResponse(BaseModel):
updated_at: datetime
class VendorDetailResponse(VendorResponse):
"""
Extended vendor response including owner information.
Includes both:
- contact_email (business contact)
- owner_email (owner's authentication email)
"""
owner_email: str = Field(
...,
description="Email of the vendor owner (for login/authentication)"
)
owner_username: str = Field(
...,
description="Username of the vendor owner"
)
class VendorCreateResponse(VendorDetailResponse):
"""
Response after creating vendor - includes generated credentials.
IMPORTANT: temporary_password is shown only once!
"""
temporary_password: str = Field(
...,
description="Temporary password for owner (SHOWN ONLY ONCE)"
)
login_url: Optional[str] = Field(
None,
description="URL for vendor owner to login"
)
class VendorListResponse(BaseModel):
"""Schema for paginated vendor list."""
vendors: List[VendorResponse]
@@ -132,9 +238,57 @@ class VendorSummary(BaseModel):
is_active: bool
class VendorCreateResponse(VendorResponse):
"""Extended response for vendor creation with owner credentials."""
owner_email: str
owner_username: str
temporary_password: str
login_url: Optional[str] = None
class VendorTransferOwnership(BaseModel):
"""
Schema for transferring vendor ownership to another user.
This is a critical operation that requires:
- Confirmation flag
- Reason for audit trail (optional)
"""
new_owner_user_id: int = Field(
...,
description="ID of the user who will become the new owner",
gt=0
)
confirm_transfer: bool = Field(
...,
description="Must be true to confirm ownership transfer"
)
transfer_reason: Optional[str] = Field(
None,
max_length=500,
description="Reason for ownership transfer (for audit logs)"
)
@field_validator("confirm_transfer")
@classmethod
def validate_confirmation(cls, v):
"""Ensure confirmation is explicitly true."""
if not v:
raise ValueError("Ownership transfer requires explicit confirmation")
return v
class VendorTransferOwnershipResponse(BaseModel):
"""Response after successful ownership transfer."""
message: str
vendor_id: int
vendor_code: str
vendor_name: str
old_owner: Dict[str, Any] = Field(
...,
description="Information about the previous owner"
)
new_owner: Dict[str, Any] = Field(
...,
description="Information about the new owner"
)
transferred_at: datetime
transfer_reason: Optional[str]