293 lines
10 KiB
Python
293 lines
10 KiB
Python
# 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"<Vendor(id={self.id}, vendor_code='{self.vendor_code}', name='{self.name}', subdomain='{self.subdomain}')>"
|
|
|
|
# ========================================================================
|
|
# 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"<VendorUser(vendor_id={self.vendor_id}, user_id={self.user_id})>"
|
|
|
|
|
|
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"<Role(id={self.id}, name='{self.name}', vendor_id={self.vendor_id})>"
|