# models/database/vendor.py - ENHANCED VERSION """ Enhanced Vendor model with theme support. Changes from your current version: 1. Keep existing theme_config JSON field 2. Add optional VendorTheme relationship for advanced themes 3. Add helper methods for theme access """ 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) # Simple theme config (JSON) # This stores basic theme settings like colors, fonts theme_config = Column(JSON, default=dict) # 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()" ) theme = relationship( "VendorTheme", back_populates="vendor", uselist=False, cascade="all, delete-orphan" ) # Optional advanced theme (for premium vendors) # This is optional - vendors can use theme_config OR VendorTheme advanced_theme = relationship( "VendorTheme", back_populates="vendor", uselist=False, cascade="all, delete-orphan" ) def __repr__(self): return f"" # ======================================================================== # Theme Helper Methods # ======================================================================== @property def active_theme(self): """Get vendor's active theme or return default""" if self.theme and self.theme.is_active: return self.theme return None @property def theme(self): """ Get theme configuration for this vendor. Priority: 1. Advanced theme (VendorTheme) if configured 2. theme_config JSON field 3. Default theme Returns dict with theme configuration. """ # Priority 1: Advanced theme if self.advanced_theme and self.advanced_theme.is_active: return self.advanced_theme.to_dict() # Priority 2: Basic theme_config if self.theme_config: return self._normalize_theme_config(self.theme_config) # Priority 3: Default theme return self._get_default_theme() def _normalize_theme_config(self, config: dict) -> dict: """ Normalize theme_config JSON to standard format. Ensures backward compatibility with existing theme_config. """ return { "theme_name": config.get("theme_name", "basic"), "colors": config.get("colors", { "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899" }), "fonts": config.get("fonts", { "heading": "Inter, sans-serif", "body": "Inter, sans-serif" }), "branding": config.get("branding", { "logo": None, "logo_dark": None, "favicon": None }), "layout": config.get("layout", { "style": "grid", "header": "fixed" }), "custom_css": config.get("custom_css", None), "css_variables": self._generate_css_variables(config) } def _generate_css_variables(self, config: dict) -> dict: """Generate CSS custom properties from theme config""" colors = config.get("colors", {}) fonts = config.get("fonts", {}) return { "--color-primary": colors.get("primary", "#6366f1"), "--color-secondary": colors.get("secondary", "#8b5cf6"), "--color-accent": colors.get("accent", "#ec4899"), "--color-background": colors.get("background", "#ffffff"), "--color-text": colors.get("text", "#1f2937"), "--color-border": colors.get("border", "#e5e7eb"), "--font-heading": fonts.get("heading", "Inter, sans-serif"), "--font-body": fonts.get("body", "Inter, sans-serif"), } 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 }, "layout": { "style": "grid", "header": "fixed" }, "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", } } @property def primary_color(self): """Get primary color from theme""" return self.theme.get("colors", {}).get("primary", "#6366f1") @property def logo_url(self): """Get logo URL from theme""" return self.theme.get("branding", {}).get("logo") def update_theme(self, theme_data: dict): """ Update vendor theme configuration. Args: theme_data: Dict with theme settings {colors: {...}, fonts: {...}, etc} """ if not self.theme_config: self.theme_config = {} # Update theme_config JSON if "colors" in theme_data: self.theme_config["colors"] = theme_data["colors"] if "fonts" in theme_data: self.theme_config["fonts"] = theme_data["fonts"] if "branding" in theme_data: self.theme_config["branding"] = theme_data["branding"] if "layout" in theme_data: self.theme_config["layout"] = theme_data["layout"] if "custom_css" in theme_data: self.theme_config["custom_css"] = theme_data["custom_css"] # ======================================================================== # 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""