Files
orion/models/database/vendor.py

199 lines
6.9 KiB
Python

# 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"<Vendor(id={self.id}, vendor_code='{self.vendor_code}', name='{self.name}', subdomain='{self.subdomain}')>"
# ========================================================================
# 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
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})>"