294 lines
9.7 KiB
Python
294 lines
9.7 KiB
Python
# services/tenant/app/services/tenant_settings_service.py
|
|
"""
|
|
Tenant Settings Service
|
|
Business logic for managing tenant-specific operational settings
|
|
"""
|
|
|
|
import structlog
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from uuid import UUID
|
|
from typing import Optional, Dict, Any
|
|
from fastapi import HTTPException, status
|
|
|
|
from ..models.tenant_settings import TenantSettings
|
|
from ..repositories.tenant_settings_repository import TenantSettingsRepository
|
|
from ..schemas.tenant_settings import (
|
|
TenantSettingsUpdate,
|
|
ProcurementSettings,
|
|
InventorySettings,
|
|
ProductionSettings,
|
|
SupplierSettings,
|
|
POSSettings,
|
|
OrderSettings,
|
|
ReplenishmentSettings,
|
|
SafetyStockSettings,
|
|
MOQSettings,
|
|
SupplierSelectionSettings,
|
|
NotificationSettings
|
|
)
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
class TenantSettingsService:
|
|
"""
|
|
Service for managing tenant settings
|
|
Handles validation, CRUD operations, and default value management
|
|
"""
|
|
|
|
# Map category names to schema validators
|
|
CATEGORY_SCHEMAS = {
|
|
"procurement": ProcurementSettings,
|
|
"inventory": InventorySettings,
|
|
"production": ProductionSettings,
|
|
"supplier": SupplierSettings,
|
|
"pos": POSSettings,
|
|
"order": OrderSettings,
|
|
"replenishment": ReplenishmentSettings,
|
|
"safety_stock": SafetyStockSettings,
|
|
"moq": MOQSettings,
|
|
"supplier_selection": SupplierSelectionSettings,
|
|
"notification": NotificationSettings
|
|
}
|
|
|
|
# Map category names to database column names
|
|
CATEGORY_COLUMNS = {
|
|
"procurement": "procurement_settings",
|
|
"inventory": "inventory_settings",
|
|
"production": "production_settings",
|
|
"supplier": "supplier_settings",
|
|
"pos": "pos_settings",
|
|
"order": "order_settings",
|
|
"replenishment": "replenishment_settings",
|
|
"safety_stock": "safety_stock_settings",
|
|
"moq": "moq_settings",
|
|
"supplier_selection": "supplier_selection_settings",
|
|
"notification": "notification_settings"
|
|
}
|
|
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
self.repository = TenantSettingsRepository(db)
|
|
|
|
async def get_settings(self, tenant_id: UUID) -> TenantSettings:
|
|
"""
|
|
Get tenant settings, creating defaults if they don't exist
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
|
|
Returns:
|
|
TenantSettings object
|
|
|
|
Raises:
|
|
HTTPException: If tenant not found
|
|
"""
|
|
try:
|
|
# Try to get existing settings using repository
|
|
settings = await self.repository.get_by_tenant_id(tenant_id)
|
|
|
|
logger.info(f"Existing settings lookup for tenant {tenant_id}: {'found' if settings else 'not found'}")
|
|
|
|
# Create default settings if they don't exist
|
|
if not settings:
|
|
logger.info(f"Creating default settings for tenant {tenant_id}")
|
|
settings = await self._create_default_settings(tenant_id)
|
|
logger.info(f"Successfully created default settings for tenant {tenant_id}")
|
|
|
|
return settings
|
|
except Exception as e:
|
|
logger.error("Failed to get or create tenant settings", tenant_id=tenant_id, error=str(e), exc_info=True)
|
|
# Re-raise as HTTPException to match the expected behavior
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to get tenant settings: {str(e)}"
|
|
)
|
|
|
|
async def update_settings(
|
|
self,
|
|
tenant_id: UUID,
|
|
updates: TenantSettingsUpdate
|
|
) -> TenantSettings:
|
|
"""
|
|
Update tenant settings
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
updates: TenantSettingsUpdate object with new values
|
|
|
|
Returns:
|
|
Updated TenantSettings object
|
|
"""
|
|
settings = await self.get_settings(tenant_id)
|
|
|
|
# Update each category if provided
|
|
if updates.procurement_settings is not None:
|
|
settings.procurement_settings = updates.procurement_settings.dict()
|
|
|
|
if updates.inventory_settings is not None:
|
|
settings.inventory_settings = updates.inventory_settings.dict()
|
|
|
|
if updates.production_settings is not None:
|
|
settings.production_settings = updates.production_settings.dict()
|
|
|
|
if updates.supplier_settings is not None:
|
|
settings.supplier_settings = updates.supplier_settings.dict()
|
|
|
|
if updates.pos_settings is not None:
|
|
settings.pos_settings = updates.pos_settings.dict()
|
|
|
|
if updates.order_settings is not None:
|
|
settings.order_settings = updates.order_settings.dict()
|
|
|
|
if updates.replenishment_settings is not None:
|
|
settings.replenishment_settings = updates.replenishment_settings.dict()
|
|
|
|
if updates.safety_stock_settings is not None:
|
|
settings.safety_stock_settings = updates.safety_stock_settings.dict()
|
|
|
|
if updates.moq_settings is not None:
|
|
settings.moq_settings = updates.moq_settings.dict()
|
|
|
|
if updates.supplier_selection_settings is not None:
|
|
settings.supplier_selection_settings = updates.supplier_selection_settings.dict()
|
|
|
|
return await self.repository.update(settings)
|
|
|
|
async def get_category(self, tenant_id: UUID, category: str) -> Dict[str, Any]:
|
|
"""
|
|
Get settings for a specific category
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
category: Category name (procurement, inventory, production, etc.)
|
|
|
|
Returns:
|
|
Dictionary with category settings
|
|
|
|
Raises:
|
|
HTTPException: If category is invalid
|
|
"""
|
|
if category not in self.CATEGORY_COLUMNS:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid category: {category}. Valid categories: {', '.join(self.CATEGORY_COLUMNS.keys())}"
|
|
)
|
|
|
|
settings = await self.get_settings(tenant_id)
|
|
column_name = self.CATEGORY_COLUMNS[category]
|
|
|
|
return getattr(settings, column_name)
|
|
|
|
async def update_category(
|
|
self,
|
|
tenant_id: UUID,
|
|
category: str,
|
|
updates: Dict[str, Any]
|
|
) -> TenantSettings:
|
|
"""
|
|
Update settings for a specific category
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
category: Category name
|
|
updates: Dictionary with new values
|
|
|
|
Returns:
|
|
Updated TenantSettings object
|
|
|
|
Raises:
|
|
HTTPException: If category is invalid or validation fails
|
|
"""
|
|
if category not in self.CATEGORY_COLUMNS:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid category: {category}"
|
|
)
|
|
|
|
# Validate updates using the appropriate schema
|
|
schema = self.CATEGORY_SCHEMAS[category]
|
|
try:
|
|
validated_data = schema(**updates)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
detail=f"Validation error: {str(e)}"
|
|
)
|
|
|
|
# Get existing settings and update the category
|
|
settings = await self.get_settings(tenant_id)
|
|
column_name = self.CATEGORY_COLUMNS[category]
|
|
setattr(settings, column_name, validated_data.dict())
|
|
|
|
return await self.repository.update(settings)
|
|
|
|
async def reset_category(self, tenant_id: UUID, category: str) -> Dict[str, Any]:
|
|
"""
|
|
Reset a category to default values
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
category: Category name
|
|
|
|
Returns:
|
|
Dictionary with reset category settings
|
|
|
|
Raises:
|
|
HTTPException: If category is invalid
|
|
"""
|
|
if category not in self.CATEGORY_COLUMNS:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid category: {category}"
|
|
)
|
|
|
|
# Get default settings for the category
|
|
defaults = TenantSettings.get_default_settings()
|
|
column_name = self.CATEGORY_COLUMNS[category]
|
|
default_category_settings = defaults[column_name]
|
|
|
|
# Update the category with defaults
|
|
settings = await self.get_settings(tenant_id)
|
|
setattr(settings, column_name, default_category_settings)
|
|
|
|
await self.repository.update(settings)
|
|
|
|
return default_category_settings
|
|
|
|
async def _create_default_settings(self, tenant_id: UUID) -> TenantSettings:
|
|
"""
|
|
Create default settings for a new tenant
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
|
|
Returns:
|
|
Newly created TenantSettings object
|
|
"""
|
|
defaults = TenantSettings.get_default_settings()
|
|
|
|
settings = TenantSettings(
|
|
tenant_id=tenant_id,
|
|
procurement_settings=defaults["procurement_settings"],
|
|
inventory_settings=defaults["inventory_settings"],
|
|
production_settings=defaults["production_settings"],
|
|
supplier_settings=defaults["supplier_settings"],
|
|
pos_settings=defaults["pos_settings"],
|
|
order_settings=defaults["order_settings"],
|
|
replenishment_settings=defaults["replenishment_settings"],
|
|
safety_stock_settings=defaults["safety_stock_settings"],
|
|
moq_settings=defaults["moq_settings"],
|
|
supplier_selection_settings=defaults["supplier_selection_settings"]
|
|
)
|
|
|
|
return await self.repository.create(settings)
|
|
|
|
async def delete_settings(self, tenant_id: UUID) -> None:
|
|
"""
|
|
Delete tenant settings (used when tenant is deleted)
|
|
|
|
Args:
|
|
tenant_id: UUID of the tenant
|
|
"""
|
|
await self.repository.delete(tenant_id)
|