# app/modules/tenancy/schemas/merchant.py """ Pydantic schemas for Merchant model. These schemas are used for API request/response validation and serialization. """ from datetime import datetime from typing import Any from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator from app.modules.tenancy.schemas.store import StoreSummary class MerchantBase(BaseModel): """Base schema for merchant with common fields.""" name: str = Field(..., min_length=2, max_length=200, description="Merchant name") description: str | None = Field(None, description="Merchant description") contact_email: EmailStr = Field(..., description="Business contact email") contact_phone: str | None = Field(None, description="Business phone number") website: str | None = Field(None, description="Merchant website URL") business_address: str | None = Field(None, description="Physical business address") tax_number: str | None = Field(None, description="Tax/VAT registration number") @field_validator("contact_email") @classmethod def normalize_email(cls, v): """Normalize email to lowercase.""" return v.lower() if v else v class MerchantCreate(MerchantBase): """ Schema for creating a new merchant. Requires owner_email to create the associated owner user account. """ owner_email: EmailStr = Field( ..., description="Email for the merchant owner account" ) @field_validator("owner_email") @classmethod def normalize_owner_email(cls, v): """Normalize owner email to lowercase.""" return v.lower() if v else v model_config = ConfigDict(from_attributes=True) class MerchantUpdate(BaseModel): """ Schema for updating merchant information. All fields are optional to support partial updates. """ name: str | None = Field(None, min_length=2, max_length=200) description: str | None = None contact_email: EmailStr | None = None contact_phone: str | None = None website: str | None = None business_address: str | None = None tax_number: str | None = None # Status (Admin only) is_active: bool | None = None is_verified: bool | None = None @field_validator("contact_email") @classmethod def normalize_email(cls, v): """Normalize email to lowercase.""" return v.lower() if v else v model_config = ConfigDict(from_attributes=True) class MerchantResponse(BaseModel): """Standard schema for merchant response data.""" model_config = ConfigDict(from_attributes=True) id: int name: str description: str | None # Owner information owner_user_id: int owner_email: str | None = Field(None, description="Owner's email address") # Contact Information contact_email: str contact_phone: str | None website: str | None # Business Information business_address: str | None tax_number: str | None # Status Flags is_active: bool is_verified: bool # Timestamps created_at: str updated_at: str # Store statistics store_count: int = Field(0, description="Number of stores under this merchant") class MerchantDetailResponse(MerchantResponse): """ Detailed merchant response including store count and owner details. Used for merchant detail pages and admin views. """ # Owner details (from related User) owner_username: str | None = Field(None, description="Owner's username") # Store statistics active_store_count: int = Field( 0, description="Number of active stores under this merchant" ) # Stores list (optional, for detail view) stores: list | None = Field(None, description="List of stores under this merchant") class MerchantListResponse(BaseModel): """Schema for paginated merchant list.""" merchants: list[MerchantResponse] total: int skip: int limit: int class MerchantCreateResponse(BaseModel): """ Response after creating a merchant with owner account. Includes temporary password for the owner (shown only once). """ merchant: MerchantResponse owner_user_id: int owner_username: str owner_email: str temporary_password: str = Field( ..., description="Temporary password for owner (SHOWN ONLY ONCE)" ) login_url: str | None = Field(None, description="URL for merchant owner to login") class MerchantSummary(BaseModel): """Lightweight merchant summary for dropdowns and quick references.""" model_config = ConfigDict(from_attributes=True) id: int name: str is_active: bool is_verified: bool store_count: int = 0 class MerchantTransferOwnership(BaseModel): """ Schema for transferring merchant 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: str | None = 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 MerchantTransferOwnershipResponse(BaseModel): """Response after successful ownership transfer.""" message: str merchant_id: int merchant_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: str | None # ============================================================================ # Merchant Portal Schemas (for merchant-facing routes) # ============================================================================ class MerchantPortalProfileResponse(BaseModel): """Merchant profile as seen by the merchant owner.""" model_config = ConfigDict(from_attributes=True) id: int name: str description: str | None contact_email: str contact_phone: str | None website: str | None business_address: str | None tax_number: str | None is_verified: bool class MerchantPortalProfileUpdate(BaseModel): """Merchant profile update from the merchant portal. Excludes admin-only fields (is_active, is_verified).""" name: str | None = Field(None, min_length=2, max_length=200) description: str | None = None contact_email: EmailStr | None = None contact_phone: str | None = None website: str | None = None business_address: str | None = None tax_number: str | None = None class MerchantPortalStoreListResponse(BaseModel): """Paginated store list for the merchant portal.""" stores: list[StoreSummary] total: int skip: int limit: int