major refactoring adding vendor and customer features
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
"""Database models package."""
|
||||
|
||||
from .base import Base
|
||||
from .customer import Customer
|
||||
from .order import Order
|
||||
from .user import User
|
||||
from .marketplace_product import MarketplaceProduct
|
||||
from .stock import Stock
|
||||
from .inventory import Inventory
|
||||
from .vendor import Vendor
|
||||
from .product import Product
|
||||
from .marketplace_import_job import MarketplaceImportJob
|
||||
@@ -13,7 +15,9 @@ __all__ = [
|
||||
"Base",
|
||||
"User",
|
||||
"MarketplaceProduct",
|
||||
"Stock",
|
||||
"Inventory",
|
||||
"Customer",
|
||||
"Order",
|
||||
"Vendor",
|
||||
"Product",
|
||||
"MarketplaceImportJob",
|
||||
|
||||
64
models/database/customer.py
Normal file
64
models/database/customer.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, JSON, Numeric
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.database import Base
|
||||
from .base import TimestampMixin
|
||||
|
||||
|
||||
class Customer(Base, TimestampMixin):
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
email = Column(String(255), nullable=False, index=True) # Unique within vendor scope
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
phone = Column(String(50))
|
||||
customer_number = Column(String(100), nullable=False, index=True) # Vendor-specific ID
|
||||
preferences = Column(JSON, default=dict)
|
||||
marketing_consent = Column(Boolean, default=False)
|
||||
last_order_date = Column(DateTime)
|
||||
total_orders = Column(Integer, default=0)
|
||||
total_spent = Column(Numeric(10, 2), default=0)
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="customers")
|
||||
addresses = relationship("CustomerAddress", back_populates="customer")
|
||||
orders = relationship("Order", back_populates="customer")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Customer(id={self.id}, vendor_id={self.vendor_id}, email='{self.email}')>"
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
if self.first_name and self.last_name:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
return self.email
|
||||
|
||||
|
||||
class CustomerAddress(Base, TimestampMixin):
|
||||
__tablename__ = "customer_addresses"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
|
||||
address_type = Column(String(50), nullable=False) # 'billing', 'shipping'
|
||||
first_name = Column(String(100), nullable=False)
|
||||
last_name = Column(String(100), nullable=False)
|
||||
company = Column(String(200))
|
||||
address_line_1 = Column(String(255), nullable=False)
|
||||
address_line_2 = Column(String(255))
|
||||
city = Column(String(100), nullable=False)
|
||||
postal_code = Column(String(20), nullable=False)
|
||||
country = Column(String(100), nullable=False)
|
||||
is_default = Column(Boolean, default=False)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
customer = relationship("Customer", back_populates="addresses")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CustomerAddress(id={self.id}, customer_id={self.customer_id}, type='{self.address_type}')>"
|
||||
41
models/database/inventory.py
Normal file
41
models/database/inventory.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# models/database/inventory.py
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
|
||||
class Inventory(Base, TimestampMixin):
|
||||
__tablename__ = "inventory"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False, index=True)
|
||||
|
||||
location = Column(String, nullable=False, index=True)
|
||||
quantity = Column(Integer, nullable=False, default=0)
|
||||
reserved_quantity = Column(Integer, default=0)
|
||||
|
||||
# Optional: Keep GTIN for reference/reporting
|
||||
gtin = Column(String, index=True)
|
||||
|
||||
# Relationships
|
||||
product = relationship("Product", back_populates="inventory_entries")
|
||||
vendor = relationship("Vendor")
|
||||
|
||||
# Constraints
|
||||
__table_args__ = (
|
||||
UniqueConstraint("product_id", "location", name="uq_inventory_product_location"),
|
||||
Index("idx_inventory_vendor_product", "vendor_id", "product_id"),
|
||||
Index("idx_inventory_product_location", "product_id", "location"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Inventory(product_id={self.product_id}, location='{self.location}', quantity={self.quantity})>"
|
||||
|
||||
@property
|
||||
def available_quantity(self):
|
||||
"""Calculate available quantity (total - reserved)."""
|
||||
return max(0, self.quantity - self.reserved_quantity)
|
||||
@@ -1,10 +1,8 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index,
|
||||
Integer, String, Text, UniqueConstraint)
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from the central database module instead of creating a new one
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
@@ -13,20 +11,17 @@ class MarketplaceImportJob(Base, TimestampMixin):
|
||||
__tablename__ = "marketplace_import_jobs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# Import configuration
|
||||
marketplace = Column(String, nullable=False, index=True, default="Letzshop")
|
||||
source_url = Column(String, nullable=False)
|
||||
|
||||
# Status tracking
|
||||
status = Column(
|
||||
String, nullable=False, default="pending"
|
||||
) # pending, processing, completed, failed, completed_with_errors
|
||||
source_url = Column(String, nullable=False)
|
||||
marketplace = Column(
|
||||
String, nullable=False, index=True, default="Letzshop"
|
||||
) # Index for marketplace filtering
|
||||
vendor_name = Column(String, nullable=False, index=True) # Index for vendor filtering
|
||||
vendor_id = Column(
|
||||
Integer, ForeignKey("vendors.id"), nullable=False
|
||||
) # Add proper foreign key
|
||||
user_id = Column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
) # Foreign key to users table
|
||||
|
||||
# Results
|
||||
imported_count = Column(Integer, default=0)
|
||||
@@ -35,28 +30,26 @@ class MarketplaceImportJob(Base, TimestampMixin):
|
||||
total_processed = Column(Integer, default=0)
|
||||
|
||||
# Error handling
|
||||
error_message = Column(String)
|
||||
error_message = Column(Text)
|
||||
|
||||
# Timestamps
|
||||
started_at = Column(DateTime(timezone=True))
|
||||
completed_at = Column(DateTime(timezone=True))
|
||||
|
||||
started_at = Column(DateTime)
|
||||
completed_at = Column(DateTime)
|
||||
|
||||
# Relationship to user
|
||||
user = relationship("User", foreign_keys=[user_id])
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="marketplace_import_jobs")
|
||||
user = relationship("User", foreign_keys=[user_id])
|
||||
|
||||
# Additional indexes for marketplace import job queries
|
||||
# Indexes for performance
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"idx_marketplace_import_user_marketplace", "user_id", "marketplace"
|
||||
), # User's marketplace imports
|
||||
Index("idx_marketplace_import_vendor_status", "status"), # Vendor import status
|
||||
Index("idx_marketplace_import_vendor_id", "vendor_id"),
|
||||
Index("idx_import_vendor_status", "vendor_id", "status"),
|
||||
Index("idx_import_vendor_created", "vendor_id", "created_at"),
|
||||
Index("idx_import_user_marketplace", "user_id", "marketplace"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"<MarketplaceImportJob(id={self.id}, marketplace='{self.marketplace}', vendor='{self.vendor_name}', "
|
||||
f"status='{self.status}', imported={self.imported_count})>"
|
||||
f"<MarketplaceImportJob(id={self.id}, vendor_id={self.vendor_id}, "
|
||||
f"marketplace='{self.marketplace}', status='{self.status}', "
|
||||
f"imported={self.imported_count})>"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ class MarketplaceProduct(Base, TimestampMixin):
|
||||
availability = Column(String, index=True) # Index for filtering
|
||||
price = Column(String)
|
||||
brand = Column(String, index=True) # Index for filtering
|
||||
gtin = Column(String, index=True) # Index for stock lookups
|
||||
gtin = Column(String, index=True) # Index for inventory lookups
|
||||
mpn = Column(String)
|
||||
condition = Column(String)
|
||||
adult = Column(String)
|
||||
@@ -57,13 +57,6 @@ class MarketplaceProduct(Base, TimestampMixin):
|
||||
) # Index for marketplace filtering
|
||||
vendor_name = Column(String, index=True, nullable=True) # Index for vendor filtering
|
||||
|
||||
# Relationship to stock (one-to-many via GTIN)
|
||||
stock_entries = relationship(
|
||||
"Stock",
|
||||
foreign_keys="Stock.gtin",
|
||||
primaryjoin="MarketplaceProduct.gtin == Stock.gtin",
|
||||
viewonly=True,
|
||||
)
|
||||
product = relationship("Product", back_populates="marketplace_product")
|
||||
|
||||
# Additional indexes for marketplace queries
|
||||
|
||||
86
models/database/order.py
Normal file
86
models/database/order.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# models/database/order.py
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String, Text, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
|
||||
class Order(Base, TimestampMixin):
|
||||
"""Customer orders."""
|
||||
__tablename__ = "orders"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False, index=True)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False, index=True)
|
||||
|
||||
order_number = Column(String, nullable=False, unique=True, index=True)
|
||||
|
||||
# Order status
|
||||
status = Column(String, nullable=False, default="pending", index=True)
|
||||
# pending, processing, shipped, delivered, cancelled, refunded
|
||||
|
||||
# Financial
|
||||
subtotal = Column(Float, nullable=False)
|
||||
tax_amount = Column(Float, default=0.0)
|
||||
shipping_amount = Column(Float, default=0.0)
|
||||
discount_amount = Column(Float, default=0.0)
|
||||
total_amount = Column(Float, nullable=False)
|
||||
currency = Column(String, default="EUR")
|
||||
|
||||
# Addresses (stored as IDs)
|
||||
shipping_address_id = Column(Integer, ForeignKey("customer_addresses.id"), nullable=False)
|
||||
billing_address_id = Column(Integer, ForeignKey("customer_addresses.id"), nullable=False)
|
||||
|
||||
# Shipping
|
||||
shipping_method = Column(String, nullable=True)
|
||||
tracking_number = Column(String, nullable=True)
|
||||
|
||||
# Notes
|
||||
customer_notes = Column(Text, nullable=True)
|
||||
internal_notes = Column(Text, nullable=True)
|
||||
|
||||
# Timestamps
|
||||
paid_at = Column(DateTime, nullable=True)
|
||||
shipped_at = Column(DateTime, nullable=True)
|
||||
delivered_at = Column(DateTime, nullable=True)
|
||||
cancelled_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
customer = relationship("Customer", back_populates="orders")
|
||||
items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan")
|
||||
shipping_address = relationship("CustomerAddress", foreign_keys=[shipping_address_id])
|
||||
billing_address = relationship("CustomerAddress", foreign_keys=[billing_address_id])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Order(id={self.id}, order_number='{self.order_number}', status='{self.status}')>"
|
||||
|
||||
|
||||
class OrderItem(Base, TimestampMixin):
|
||||
"""Individual items in an order."""
|
||||
__tablename__ = "order_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
order_id = Column(Integer, ForeignKey("orders.id"), nullable=False, index=True)
|
||||
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
|
||||
|
||||
# Product details at time of order (snapshot)
|
||||
product_name = Column(String, nullable=False)
|
||||
product_sku = Column(String, nullable=True)
|
||||
|
||||
quantity = Column(Integer, nullable=False)
|
||||
unit_price = Column(Float, nullable=False)
|
||||
total_price = Column(Float, nullable=False)
|
||||
|
||||
# Inventory tracking
|
||||
inventory_reserved = Column(Boolean, default=False)
|
||||
inventory_fulfilled = Column(Boolean, default=False)
|
||||
|
||||
# Relationships
|
||||
order = relationship("Order", back_populates="items")
|
||||
product = relationship("Product")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<OrderItem(id={self.id}, order_id={self.order_id}, product_id={self.product_id})>"
|
||||
@@ -1,13 +1,12 @@
|
||||
# models/database/product.py
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index,
|
||||
Integer, String, Text, UniqueConstraint)
|
||||
from sqlalchemy import Boolean, Column, Float, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from the central database module instead of creating a new one
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
|
||||
class Product(Base, TimestampMixin):
|
||||
__tablename__ = "products"
|
||||
|
||||
@@ -15,12 +14,12 @@ class Product(Base, TimestampMixin):
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
marketplace_product_id = Column(Integer, ForeignKey("marketplace_products.id"), nullable=False)
|
||||
|
||||
# Vendor-specific overrides (can override the main product data)
|
||||
product_id = Column(String) # Vendor's internal product ID
|
||||
price = Column(Float) # Override main product price
|
||||
# Vendor-specific overrides
|
||||
product_id = Column(String) # Vendor's internal SKU
|
||||
price = Column(Float)
|
||||
sale_price = Column(Float)
|
||||
currency = Column(String)
|
||||
availability = Column(String) # Override availability
|
||||
availability = Column(String)
|
||||
condition = Column(String)
|
||||
|
||||
# Vendor-specific metadata
|
||||
@@ -28,13 +27,14 @@ class Product(Base, TimestampMixin):
|
||||
is_active = Column(Boolean, default=True)
|
||||
display_order = Column(Integer, default=0)
|
||||
|
||||
# Inventory management
|
||||
# Inventory settings
|
||||
min_quantity = Column(Integer, default=1)
|
||||
max_quantity = Column(Integer)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="product")
|
||||
vendor = relationship("Vendor", back_populates="products")
|
||||
marketplace_product = relationship("MarketplaceProduct", back_populates="product")
|
||||
inventory_entries = relationship("Inventory", back_populates="product", cascade="all, delete-orphan")
|
||||
|
||||
# Constraints
|
||||
__table_args__ = (
|
||||
@@ -42,3 +42,16 @@ class Product(Base, TimestampMixin):
|
||||
Index("idx_product_active", "vendor_id", "is_active"),
|
||||
Index("idx_product_featured", "vendor_id", "is_featured"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Product(id={self.id}, vendor_id={self.vendor_id}, product_id='{self.product_id}')>"
|
||||
|
||||
@property
|
||||
def total_inventory(self):
|
||||
"""Calculate total inventory across all locations."""
|
||||
return sum(inv.quantity for inv in self.inventory_entries)
|
||||
|
||||
@property
|
||||
def available_inventory(self):
|
||||
"""Calculate available inventory (total - reserved)."""
|
||||
return sum(inv.available_quantity for inv in self.inventory_entries)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Index,
|
||||
Integer, String, Text, UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from the central database module instead of creating a new one
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
class Stock(Base, TimestampMixin):
|
||||
__tablename__ = "stock"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
gtin = Column(
|
||||
String, index=True, nullable=False
|
||||
) # Foreign key relationship would be ideal
|
||||
location = Column(String, nullable=False, index=True)
|
||||
quantity = Column(Integer, nullable=False, default=0)
|
||||
reserved_quantity = Column(Integer, default=0) # For orders being processed
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id")) # Optional: vendor -specific stock
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
|
||||
# Composite unique constraint to prevent duplicate GTIN-location combinations
|
||||
__table_args__ = (
|
||||
UniqueConstraint("gtin", "location", name="uq_stock_gtin_location"),
|
||||
Index(
|
||||
"idx_stock_gtin_location", "gtin", "location"
|
||||
), # Composite index for efficient queries
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Stock(gtin='{self.gtin}', location='{self.location}', quantity={self.quantity})>"
|
||||
@@ -14,6 +14,8 @@ class User(Base, TimestampMixin):
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
username = Column(String, unique=True, index=True, nullable=False)
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
role = Column(String, nullable=False, default="user") # user, admin, vendor_owner
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
@@ -23,7 +25,16 @@ class User(Base, TimestampMixin):
|
||||
marketplace_import_jobs = relationship(
|
||||
"MarketplaceImportJob", back_populates="user"
|
||||
)
|
||||
|
||||
# Vendor relationships
|
||||
owned_vendors = relationship("Vendor", back_populates="owner")
|
||||
vendor_memberships = relationship("VendorUser", foreign_keys="[VendorUser.user_id]", back_populates="user")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(username='{self.username}', email='{self.email}', role='{self.role}')>"
|
||||
return f"<User(id={self.id}, username='{self.username}', email='{self.email}', role='{self.role}')>"
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
if self.first_name and self.last_name:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
return self.username
|
||||
@@ -1,7 +1,6 @@
|
||||
from sqlalchemy import (Boolean, Column, ForeignKey, Integer, String, Text)
|
||||
from sqlalchemy import (Boolean, Column, ForeignKey, Integer, String, Text, JSON)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from the central database module instead of creating a new one
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
@@ -13,15 +12,22 @@ class Vendor(Base, TimestampMixin):
|
||||
vendor_code = Column(
|
||||
String, unique=True, index=True, nullable=False
|
||||
) # e.g., "TECHSTORE", "FASHIONHUB"
|
||||
vendor_name = Column(String, nullable=False) # Display name
|
||||
subdomain = Column(String(100), unique=True, nullable=False, index=True)
|
||||
name = Column(String, nullable=False) # Display name
|
||||
description = Column(Text)
|
||||
owner_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
owner_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
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)
|
||||
@@ -32,7 +38,47 @@ class Vendor(Base, TimestampMixin):
|
||||
|
||||
# Relationships
|
||||
owner = relationship("User", back_populates="owned_vendors")
|
||||
product = relationship("Product", back_populates="vendor")
|
||||
marketplace_import_jobs = relationship(
|
||||
"MarketplaceImportJob", back_populates="vendor"
|
||||
)
|
||||
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")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Vendor(id={self.id}, vendor_code='{self.vendor_code}', name='{self.name}', subdomain='{self.subdomain}')>"
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# Relationships
|
||||
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) # "Owner", "Manager", "Editor", "Viewer"
|
||||
permissions = Column(JSON, default=list) # ["products.create", "orders.view", etc.]
|
||||
|
||||
# Relationships
|
||||
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})>"
|
||||
Reference in New Issue
Block a user