# models/database/vendor.py """ Vendor model with theme support. A vendor has ONE active theme stored in the vendor_themes table. Theme presets available: default, modern, classic, minimal, vibrant """ from sqlalchemy import (Boolean, Column, ForeignKey, Integer, String, Text, JSON) from sqlalchemy.orm import relationship from app.core.config import settings from app.core.database import Base from models.database.base import TimestampMixin class Vendor(Base, TimestampMixin): __tablename__ = "vendors" id = Column(Integer, primary_key=True, index=True) vendor_code = Column( String, unique=True, index=True, nullable=False ) subdomain = Column(String(100), unique=True, nullable=False, index=True) name = Column(String, nullable=False) description = Column(Text) owner_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Contact information contact_email = Column(String) contact_phone = Column(String) website = Column(String) # Letzshop URLs - multi-language support letzshop_csv_url_fr = Column(String) letzshop_csv_url_en = Column(String) letzshop_csv_url_de = Column(String) # Business information business_address = Column(Text) tax_number = Column(String) # Status is_active = Column(Boolean, default=True) is_verified = Column(Boolean, default=False) # ======================================================================== # Relationships # ======================================================================== owner = relationship("User", back_populates="owned_vendors") vendor_users = relationship("VendorUser", back_populates="vendor") products = relationship("Product", back_populates="vendor") customers = relationship("Customer", back_populates="vendor") orders = relationship("Order", back_populates="vendor") marketplace_import_jobs = relationship("MarketplaceImportJob", back_populates="vendor") domains = relationship( "VendorDomain", back_populates="vendor", cascade="all, delete-orphan", order_by="VendorDomain.is_primary.desc()" ) # Single theme relationship (ONE vendor = ONE theme) vendor_theme = relationship( "VendorTheme", back_populates="vendor", uselist=False, cascade="all, delete-orphan" ) def __repr__(self): return f"" # ======================================================================== # Theme Helper Methods # ======================================================================== def get_effective_theme(self) -> dict: """ Get active theme for this vendor. Returns theme from vendor_themes table, or default theme if not set. Returns: dict: Theme configuration with colors, fonts, layout, etc. """ # Check vendor_themes table if self.vendor_theme and self.vendor_theme.is_active: return self.vendor_theme.to_dict() # Return default theme return self._get_default_theme() def _get_default_theme(self) -> dict: """Default theme configuration""" return { "theme_name": "default", "colors": { "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899", "background": "#ffffff", "text": "#1f2937", "border": "#e5e7eb" }, "fonts": { "heading": "Inter, sans-serif", "body": "Inter, sans-serif" }, "branding": { "logo": None, "logo_dark": None, "favicon": None, "banner": None }, "layout": { "style": "grid", "header": "fixed", "product_card": "modern" }, "social_links": {}, "custom_css": None, "css_variables": { "--color-primary": "#6366f1", "--color-secondary": "#8b5cf6", "--color-accent": "#ec4899", "--color-background": "#ffffff", "--color-text": "#1f2937", "--color-border": "#e5e7eb", "--font-heading": "Inter, sans-serif", "--font-body": "Inter, sans-serif", } } def get_primary_color(self) -> str: """Get primary color from active theme""" theme = self.get_effective_theme() return theme.get("colors", {}).get("primary", "#6366f1") def get_logo_url(self) -> str: """Get logo URL from active theme""" theme = self.get_effective_theme() return theme.get("branding", {}).get("logo") # ======================================================================== # Domain Helper Methods # ======================================================================== @property def primary_domain(self): """Get the primary custom domain for this vendor""" for domain in self.domains: if domain.is_primary and domain.is_active: return domain.domain return None @property def all_domains(self): """Get all active domains (subdomain + custom domains)""" domains = [f"{self.subdomain}.{settings.platform_domain}"] for domain in self.domains: if domain.is_active: domains.append(domain.domain) return domains # Keep your existing VendorUser and Role models unchanged class VendorUser(Base, TimestampMixin): __tablename__ = "vendor_users" id = Column(Integer, primary_key=True, index=True) vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) role_id = Column(Integer, ForeignKey("roles.id"), nullable=False) invited_by = Column(Integer, ForeignKey("users.id")) is_active = Column(Boolean, default=True, nullable=False) vendor = relationship("Vendor", back_populates="vendor_users") user = relationship("User", foreign_keys=[user_id], back_populates="vendor_memberships") inviter = relationship("User", foreign_keys=[invited_by]) role = relationship("Role", back_populates="vendor_users") def __repr__(self): return f"" class Role(Base, TimestampMixin): __tablename__ = "roles" id = Column(Integer, primary_key=True, index=True) vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) name = Column(String(100), nullable=False) permissions = Column(JSON, default=list) vendor = relationship("Vendor") vendor_users = relationship("VendorUser", back_populates="role") def __repr__(self): return f""