New enterprise feature
This commit is contained in:
218
services/tenant/app/repositories/tenant_location_repository.py
Normal file
218
services/tenant/app/repositories/tenant_location_repository.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Tenant Location Repository
|
||||
Handles database operations for tenant location data
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Any
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update, delete
|
||||
from sqlalchemy.orm import selectinload
|
||||
import structlog
|
||||
|
||||
from app.models.tenant_location import TenantLocation
|
||||
from app.models.tenants import Tenant
|
||||
from shared.database.exceptions import DatabaseError
|
||||
from .base import BaseRepository
|
||||
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class TenantLocationRepository(BaseRepository):
|
||||
"""Repository for tenant location operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(TenantLocation, session)
|
||||
|
||||
async def create_location(self, location_data: Dict[str, Any]) -> TenantLocation:
|
||||
"""
|
||||
Create a new tenant location
|
||||
|
||||
Args:
|
||||
location_data: Dictionary containing location information
|
||||
|
||||
Returns:
|
||||
Created TenantLocation object
|
||||
"""
|
||||
try:
|
||||
# Create new location instance
|
||||
location = TenantLocation(**location_data)
|
||||
self.session.add(location)
|
||||
await self.session.commit()
|
||||
await self.session.refresh(location)
|
||||
logger.info(f"Created new tenant location: {location.id} for tenant {location.tenant_id}")
|
||||
return location
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error(f"Failed to create tenant location: {str(e)}")
|
||||
raise DatabaseError(f"Failed to create tenant location: {str(e)}")
|
||||
|
||||
async def get_location_by_id(self, location_id: str) -> Optional[TenantLocation]:
|
||||
"""
|
||||
Get a location by its ID
|
||||
|
||||
Args:
|
||||
location_id: UUID of the location
|
||||
|
||||
Returns:
|
||||
TenantLocation object if found, None otherwise
|
||||
"""
|
||||
try:
|
||||
stmt = select(TenantLocation).where(TenantLocation.id == location_id)
|
||||
result = await self.session.execute(stmt)
|
||||
location = result.scalar_one_or_none()
|
||||
return location
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get location by ID: {str(e)}")
|
||||
raise DatabaseError(f"Failed to get location by ID: {str(e)}")
|
||||
|
||||
async def get_locations_by_tenant(self, tenant_id: str) -> List[TenantLocation]:
|
||||
"""
|
||||
Get all locations for a specific tenant
|
||||
|
||||
Args:
|
||||
tenant_id: UUID of the tenant
|
||||
|
||||
Returns:
|
||||
List of TenantLocation objects
|
||||
"""
|
||||
try:
|
||||
stmt = select(TenantLocation).where(TenantLocation.tenant_id == tenant_id)
|
||||
result = await self.session.execute(stmt)
|
||||
locations = result.scalars().all()
|
||||
return locations
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get locations by tenant: {str(e)}")
|
||||
raise DatabaseError(f"Failed to get locations by tenant: {str(e)}")
|
||||
|
||||
async def get_location_by_type(self, tenant_id: str, location_type: str) -> Optional[TenantLocation]:
|
||||
"""
|
||||
Get a location by tenant and type
|
||||
|
||||
Args:
|
||||
tenant_id: UUID of the tenant
|
||||
location_type: Type of location (e.g., 'central_production', 'retail_outlet')
|
||||
|
||||
Returns:
|
||||
TenantLocation object if found, None otherwise
|
||||
"""
|
||||
try:
|
||||
stmt = select(TenantLocation).where(
|
||||
TenantLocation.tenant_id == tenant_id,
|
||||
TenantLocation.location_type == location_type
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
location = result.scalar_one_or_none()
|
||||
return location
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get location by type: {str(e)}")
|
||||
raise DatabaseError(f"Failed to get location by type: {str(e)}")
|
||||
|
||||
async def update_location(self, location_id: str, location_data: Dict[str, Any]) -> Optional[TenantLocation]:
|
||||
"""
|
||||
Update a tenant location
|
||||
|
||||
Args:
|
||||
location_id: UUID of the location to update
|
||||
location_data: Dictionary containing updated location information
|
||||
|
||||
Returns:
|
||||
Updated TenantLocation object if successful, None if location not found
|
||||
"""
|
||||
try:
|
||||
stmt = (
|
||||
update(TenantLocation)
|
||||
.where(TenantLocation.id == location_id)
|
||||
.values(**location_data)
|
||||
)
|
||||
await self.session.execute(stmt)
|
||||
|
||||
# Now fetch the updated location
|
||||
location_stmt = select(TenantLocation).where(TenantLocation.id == location_id)
|
||||
result = await self.session.execute(location_stmt)
|
||||
location = result.scalar_one_or_none()
|
||||
|
||||
if location:
|
||||
await self.session.commit()
|
||||
logger.info(f"Updated tenant location: {location_id}")
|
||||
return location
|
||||
else:
|
||||
await self.session.rollback()
|
||||
logger.warning(f"Location not found for update: {location_id}")
|
||||
return None
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error(f"Failed to update location: {str(e)}")
|
||||
raise DatabaseError(f"Failed to update location: {str(e)}")
|
||||
|
||||
async def delete_location(self, location_id: str) -> bool:
|
||||
"""
|
||||
Delete a tenant location
|
||||
|
||||
Args:
|
||||
location_id: UUID of the location to delete
|
||||
|
||||
Returns:
|
||||
True if deleted successfully, False if location not found
|
||||
"""
|
||||
try:
|
||||
stmt = delete(TenantLocation).where(TenantLocation.id == location_id)
|
||||
result = await self.session.execute(stmt)
|
||||
|
||||
if result.rowcount > 0:
|
||||
await self.session.commit()
|
||||
logger.info(f"Deleted tenant location: {location_id}")
|
||||
return True
|
||||
else:
|
||||
await self.session.rollback()
|
||||
logger.warning(f"Location not found for deletion: {location_id}")
|
||||
return False
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error(f"Failed to delete location: {str(e)}")
|
||||
raise DatabaseError(f"Failed to delete location: {str(e)}")
|
||||
|
||||
async def get_active_locations_by_tenant(self, tenant_id: str) -> List[TenantLocation]:
|
||||
"""
|
||||
Get all active locations for a specific tenant
|
||||
|
||||
Args:
|
||||
tenant_id: UUID of the tenant
|
||||
|
||||
Returns:
|
||||
List of active TenantLocation objects
|
||||
"""
|
||||
try:
|
||||
stmt = select(TenantLocation).where(
|
||||
TenantLocation.tenant_id == tenant_id,
|
||||
TenantLocation.is_active == True
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
locations = result.scalars().all()
|
||||
return locations
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get active locations by tenant: {str(e)}")
|
||||
raise DatabaseError(f"Failed to get active locations by tenant: {str(e)}")
|
||||
|
||||
async def get_locations_by_tenant_with_type(self, tenant_id: str, location_types: List[str]) -> List[TenantLocation]:
|
||||
"""
|
||||
Get locations for a specific tenant filtered by location types
|
||||
|
||||
Args:
|
||||
tenant_id: UUID of the tenant
|
||||
location_types: List of location types to filter by
|
||||
|
||||
Returns:
|
||||
List of TenantLocation objects matching the criteria
|
||||
"""
|
||||
try:
|
||||
stmt = select(TenantLocation).where(
|
||||
TenantLocation.tenant_id == tenant_id,
|
||||
TenantLocation.location_type.in_(location_types)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
locations = result.scalars().all()
|
||||
return locations
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get locations by tenant and type: {str(e)}")
|
||||
raise DatabaseError(f"Failed to get locations by tenant and type: {str(e)}")
|
||||
@@ -381,3 +381,188 @@ class TenantRepository(TenantBaseRepository):
|
||||
async def activate_tenant(self, tenant_id: str) -> Optional[Tenant]:
|
||||
"""Activate a tenant"""
|
||||
return await self.activate_record(tenant_id)
|
||||
|
||||
async def get_child_tenants(self, parent_tenant_id: str) -> List[Tenant]:
|
||||
"""Get all child tenants for a parent tenant"""
|
||||
try:
|
||||
return await self.get_multi(
|
||||
filters={"parent_tenant_id": parent_tenant_id, "is_active": True},
|
||||
order_by="created_at",
|
||||
order_desc=False
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to get child tenants",
|
||||
parent_tenant_id=parent_tenant_id,
|
||||
error=str(e))
|
||||
raise DatabaseError(f"Failed to get child tenants: {str(e)}")
|
||||
|
||||
async def get_child_tenant_count(self, parent_tenant_id: str) -> int:
|
||||
"""Get count of child tenants for a parent tenant"""
|
||||
try:
|
||||
child_tenants = await self.get_child_tenants(parent_tenant_id)
|
||||
return len(child_tenants)
|
||||
except Exception as e:
|
||||
logger.error("Failed to get child tenant count",
|
||||
parent_tenant_id=parent_tenant_id,
|
||||
error=str(e))
|
||||
return 0
|
||||
|
||||
async def get_user_tenants_with_hierarchy(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all tenants a user has access to, organized in hierarchy.
|
||||
Returns parent tenants with their children nested.
|
||||
"""
|
||||
try:
|
||||
# Get all tenants where user is owner or member
|
||||
query_text = """
|
||||
SELECT DISTINCT t.*
|
||||
FROM tenants t
|
||||
LEFT JOIN tenant_members tm ON t.id = tm.tenant_id
|
||||
WHERE (t.owner_id = :user_id OR tm.user_id = :user_id)
|
||||
AND t.is_active = true
|
||||
ORDER BY t.tenant_type DESC, t.created_at ASC
|
||||
"""
|
||||
|
||||
result = await self.session.execute(text(query_text), {"user_id": user_id})
|
||||
|
||||
tenants = []
|
||||
for row in result.fetchall():
|
||||
record_dict = dict(row._mapping)
|
||||
tenant = self.model(**record_dict)
|
||||
tenants.append(tenant)
|
||||
|
||||
# Organize into hierarchy
|
||||
tenant_hierarchy = []
|
||||
parent_map = {}
|
||||
|
||||
# First pass: collect all parent/standalone tenants
|
||||
for tenant in tenants:
|
||||
if tenant.tenant_type in ['parent', 'standalone']:
|
||||
tenant_dict = {
|
||||
'id': str(tenant.id),
|
||||
'name': tenant.name,
|
||||
'subdomain': tenant.subdomain,
|
||||
'tenant_type': tenant.tenant_type,
|
||||
'business_type': tenant.business_type,
|
||||
'business_model': tenant.business_model,
|
||||
'city': tenant.city,
|
||||
'is_active': tenant.is_active,
|
||||
'children': [] if tenant.tenant_type == 'parent' else None
|
||||
}
|
||||
tenant_hierarchy.append(tenant_dict)
|
||||
parent_map[str(tenant.id)] = tenant_dict
|
||||
|
||||
# Second pass: attach children to their parents
|
||||
for tenant in tenants:
|
||||
if tenant.tenant_type == 'child' and tenant.parent_tenant_id:
|
||||
parent_id = str(tenant.parent_tenant_id)
|
||||
if parent_id in parent_map:
|
||||
child_dict = {
|
||||
'id': str(tenant.id),
|
||||
'name': tenant.name,
|
||||
'subdomain': tenant.subdomain,
|
||||
'tenant_type': 'child',
|
||||
'parent_tenant_id': parent_id,
|
||||
'city': tenant.city,
|
||||
'is_active': tenant.is_active
|
||||
}
|
||||
parent_map[parent_id]['children'].append(child_dict)
|
||||
|
||||
return tenant_hierarchy
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get user tenants with hierarchy",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def get_tenants_by_session_id(self, session_id: str) -> List[Tenant]:
|
||||
"""
|
||||
Get tenants associated with a specific demo session using the demo_session_id field.
|
||||
"""
|
||||
try:
|
||||
return await self.get_multi(
|
||||
filters={
|
||||
"demo_session_id": session_id,
|
||||
"is_active": True
|
||||
},
|
||||
order_by="created_at",
|
||||
order_desc=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to get tenants by session ID",
|
||||
session_id=session_id,
|
||||
error=str(e))
|
||||
raise DatabaseError(f"Failed to get tenants by session ID: {str(e)}")
|
||||
|
||||
async def get_professional_demo_tenants(self, session_id: str) -> List[Tenant]:
|
||||
"""
|
||||
Get professional demo tenants filtered by session.
|
||||
|
||||
Args:
|
||||
session_id: Required demo session ID to filter tenants
|
||||
|
||||
Returns:
|
||||
List of professional demo tenants for this specific session
|
||||
"""
|
||||
try:
|
||||
filters = {
|
||||
"business_model": "professional_bakery",
|
||||
"is_demo": True,
|
||||
"is_active": True,
|
||||
"demo_session_id": session_id # Always filter by session
|
||||
}
|
||||
|
||||
return await self.get_multi(
|
||||
filters=filters,
|
||||
order_by="created_at",
|
||||
order_desc=True
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to get professional demo tenants",
|
||||
session_id=session_id,
|
||||
error=str(e))
|
||||
raise DatabaseError(f"Failed to get professional demo tenants: {str(e)}")
|
||||
|
||||
async def get_enterprise_demo_tenants(self, session_id: str) -> List[Tenant]:
|
||||
"""
|
||||
Get enterprise demo tenants (parent and children) filtered by session.
|
||||
|
||||
Args:
|
||||
session_id: Required demo session ID to filter tenants
|
||||
|
||||
Returns:
|
||||
List of enterprise demo tenants (1 parent + 3 children) for this specific session
|
||||
"""
|
||||
try:
|
||||
# Get enterprise demo parent tenants for this session
|
||||
parent_tenants = await self.get_multi(
|
||||
filters={
|
||||
"tenant_type": "parent",
|
||||
"is_demo": True,
|
||||
"is_active": True,
|
||||
"demo_session_id": session_id # Always filter by session
|
||||
},
|
||||
order_by="created_at",
|
||||
order_desc=True
|
||||
)
|
||||
|
||||
# Get child tenants for the enterprise demo session
|
||||
child_tenants = await self.get_multi(
|
||||
filters={
|
||||
"tenant_type": "child",
|
||||
"is_demo": True,
|
||||
"is_active": True,
|
||||
"demo_session_id": session_id # Always filter by session
|
||||
},
|
||||
order_by="created_at",
|
||||
order_desc=True
|
||||
)
|
||||
|
||||
# Combine parent and child tenants
|
||||
return parent_tenants + child_tenants
|
||||
except Exception as e:
|
||||
logger.error("Failed to get enterprise demo tenants",
|
||||
session_id=session_id,
|
||||
error=str(e))
|
||||
raise DatabaseError(f"Failed to get enterprise demo tenants: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user