New enterprise feature

This commit is contained in:
Urtzi Alfaro
2025-11-30 09:12:40 +01:00
parent f9d0eec6ec
commit 972db02f6d
176 changed files with 19741 additions and 1361 deletions

View File

@@ -13,6 +13,7 @@ AuditLog = create_audit_log_model(Base)
# Import all models to register them with the Base metadata
from .tenants import Tenant, TenantMember, Subscription
from .tenant_location import TenantLocation
from .coupon import CouponModel, CouponRedemptionModel
from .events import Event, EventTemplate
@@ -21,6 +22,7 @@ __all__ = [
"Tenant",
"TenantMember",
"Subscription",
"TenantLocation",
"AuditLog",
"CouponModel",
"CouponRedemptionModel",

View File

@@ -0,0 +1,59 @@
"""
Tenant Location Model
Represents physical locations for enterprise tenants (central production, retail outlets)
"""
from sqlalchemy import Column, String, Boolean, DateTime, Float, ForeignKey, Text, Integer, JSON
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from datetime import datetime, timezone
import uuid
from shared.database.base import Base
class TenantLocation(Base):
"""TenantLocation model - represents physical locations for enterprise tenants"""
__tablename__ = "tenant_locations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
# Location information
name = Column(String(200), nullable=False)
location_type = Column(String(50), nullable=False) # central_production, retail_outlet
address = Column(Text, nullable=False)
city = Column(String(100), default="Madrid")
postal_code = Column(String(10), nullable=False)
latitude = Column(Float, nullable=True)
longitude = Column(Float, nullable=True)
# Location-specific configuration
delivery_windows = Column(JSON, nullable=True) # { "monday": "08:00-12:00,14:00-18:00", ... }
capacity = Column(Integer, nullable=True) # For production capacity in kg/day or storage capacity
max_delivery_radius_km = Column(Float, nullable=True, default=50.0)
# Operational hours
operational_hours = Column(JSON, nullable=True) # { "monday": "06:00-20:00", ... }
is_active = Column(Boolean, default=True)
# Contact information
contact_person = Column(String(200), nullable=True)
contact_phone = Column(String(20), nullable=True)
contact_email = Column(String(255), nullable=True)
# Custom delivery scheduling configuration per location
delivery_schedule_config = Column(JSON, nullable=True) # { "delivery_days": "Mon,Wed,Fri", "time_window": "07:00-10:00" }
# Metadata
metadata_ = Column(JSON, nullable=True)
# Timestamps
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
# Relationships
tenant = relationship("Tenant", back_populates="locations")
def __repr__(self):
return f"<TenantLocation(id={self.id}, tenant_id={self.tenant_id}, name={self.name}, type={self.location_type})>"

View File

@@ -56,6 +56,11 @@ class Tenant(Base):
# Ownership (user_id without FK - cross-service reference)
owner_id = Column(UUID(as_uuid=True), nullable=False, index=True)
# Enterprise tier hierarchy fields
parent_tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="RESTRICT"), nullable=True, index=True)
tenant_type = Column(String(50), default="standalone", nullable=False) # standalone, parent, child
hierarchy_path = Column(String(500), nullable=True) # Materialized path for queries
# Timestamps
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
@@ -63,6 +68,9 @@ class Tenant(Base):
# Relationships - only within tenant service
members = relationship("TenantMember", back_populates="tenant", cascade="all, delete-orphan")
subscriptions = relationship("Subscription", back_populates="tenant", cascade="all, delete-orphan")
locations = relationship("TenantLocation", back_populates="tenant", cascade="all, delete-orphan")
child_tenants = relationship("Tenant", back_populates="parent_tenant", remote_side=[id])
parent_tenant = relationship("Tenant", back_populates="child_tenants", remote_side=[parent_tenant_id])
# REMOVED: users relationship - no cross-service SQLAlchemy relationships
@@ -115,7 +123,7 @@ class TenantMember(Base):
user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # No FK - cross-service reference
# Role and permissions specific to this tenant
# Valid values: 'owner', 'admin', 'member', 'viewer'
# Valid values: 'owner', 'admin', 'member', 'viewer', 'network_admin'
role = Column(String(50), default="member")
permissions = Column(Text) # JSON string of permissions