Add multi-tenant architecture and advanced document parsing capabilities

This commit is contained in:
Jonathan Pressnell
2025-08-07 16:22:28 -04:00
parent fbfe940a45
commit a4877aaa7d
7 changed files with 875 additions and 12 deletions

129
app/models/tenant.py Normal file
View File

@@ -0,0 +1,129 @@
"""
Tenant models for multi-company support in the Virtual Board Member AI System.
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import Column, String, DateTime, Boolean, Text, Integer, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
import uuid
import enum
from app.core.database import Base
class TenantStatus(str, enum.Enum):
"""Tenant status enumeration."""
ACTIVE = "active"
SUSPENDED = "suspended"
PENDING = "pending"
INACTIVE = "inactive"
class TenantTier(str, enum.Enum):
"""Tenant subscription tier."""
BASIC = "basic"
PROFESSIONAL = "professional"
ENTERPRISE = "enterprise"
CUSTOM = "custom"
class Tenant(Base):
"""Tenant model for multi-company support."""
__tablename__ = "tenants"
# Primary key
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
# Tenant identification
name = Column(String(255), nullable=False, unique=True)
slug = Column(String(100), nullable=False, unique=True) # URL-friendly identifier
domain = Column(String(255), nullable=True, unique=True) # Custom domain
# Company information
company_name = Column(String(255), nullable=False)
company_description = Column(Text, nullable=True)
industry = Column(String(100), nullable=True)
company_size = Column(String(50), nullable=True) # small, medium, large, enterprise
# Contact information
primary_contact_name = Column(String(255), nullable=False)
primary_contact_email = Column(String(255), nullable=False)
primary_contact_phone = Column(String(50), nullable=True)
# Subscription and billing
tier = Column(String(50), default=TenantTier.BASIC, nullable=False)
status = Column(String(50), default=TenantStatus.PENDING, nullable=False)
subscription_start_date = Column(DateTime, nullable=True)
subscription_end_date = Column(DateTime, nullable=True)
# Configuration
settings = Column(JSONB, nullable=True) # Tenant-specific settings
features_enabled = Column(JSONB, nullable=True) # Feature flags
storage_quota_gb = Column(Integer, default=10, nullable=False)
user_limit = Column(Integer, default=10, nullable=False)
# Security and compliance
data_retention_days = Column(Integer, default=2555, nullable=False) # 7 years default
encryption_level = Column(String(50), default="standard", nullable=False)
compliance_frameworks = Column(JSONB, nullable=True) # SOX, GDPR, etc.
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
activated_at = Column(DateTime, nullable=True)
# Relationships
users = relationship("User", back_populates="tenant", cascade="all, delete-orphan")
documents = relationship("Document", back_populates="tenant", cascade="all, delete-orphan")
commitments = relationship("Commitment", back_populates="tenant", cascade="all, delete-orphan")
audit_logs = relationship("AuditLog", back_populates="tenant", cascade="all, delete-orphan")
def __repr__(self):
return f"<Tenant(id={self.id}, name='{self.name}', company='{self.company_name}')>"
@property
def is_active(self) -> bool:
"""Check if tenant is active."""
return self.status == TenantStatus.ACTIVE
@property
def is_suspended(self) -> bool:
"""Check if tenant is suspended."""
return self.status == TenantStatus.SUSPENDED
@property
def has_expired_subscription(self) -> bool:
"""Check if subscription has expired."""
if not self.subscription_end_date:
return False
return datetime.utcnow() > self.subscription_end_date
def get_setting(self, key: str, default=None):
"""Get a tenant-specific setting."""
if not self.settings:
return default
return self.settings.get(key, default)
def set_setting(self, key: str, value):
"""Set a tenant-specific setting."""
if not self.settings:
self.settings = {}
self.settings[key] = value
def is_feature_enabled(self, feature: str) -> bool:
"""Check if a feature is enabled for this tenant."""
if not self.features_enabled:
return False
return self.features_enabled.get(feature, False)
def enable_feature(self, feature: str):
"""Enable a feature for this tenant."""
if not self.features_enabled:
self.features_enabled = {}
self.features_enabled[feature] = True
def disable_feature(self, feature: str):
"""Disable a feature for this tenant."""
if not self.features_enabled:
self.features_enabled = {}
self.features_enabled[feature] = False