Improve AI logic
This commit is contained in:
@@ -4,8 +4,8 @@ Handles basic CRUD operations for tenants
|
||||
"""
|
||||
|
||||
import structlog
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Path
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
||||
from typing import Dict, Any, List
|
||||
from uuid import UUID
|
||||
|
||||
from app.schemas.tenants import TenantResponse, TenantUpdate
|
||||
@@ -30,6 +30,47 @@ def get_enhanced_tenant_service():
|
||||
logger.error("Failed to create enhanced tenant service", error=str(e))
|
||||
raise HTTPException(status_code=500, detail="Service initialization failed")
|
||||
|
||||
@router.get(route_builder.build_base_route("", include_tenant_prefix=False), response_model=List[TenantResponse])
|
||||
@track_endpoint_metrics("tenants_list")
|
||||
async def get_active_tenants(
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
||||
):
|
||||
"""Get all active tenants - Available to service accounts and admins"""
|
||||
|
||||
logger.info(
|
||||
"Get active tenants request received",
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
user_id=current_user.get("user_id"),
|
||||
user_type=current_user.get("type", "user"),
|
||||
is_service=current_user.get("type") == "service",
|
||||
role=current_user.get("role"),
|
||||
service_name=current_user.get("service", "none")
|
||||
)
|
||||
|
||||
# Allow service accounts to call this endpoint
|
||||
if current_user.get("type") != "service":
|
||||
# For non-service users, could add additional role checks here if needed
|
||||
logger.debug(
|
||||
"Non-service user requesting active tenants",
|
||||
user_id=current_user.get("user_id"),
|
||||
role=current_user.get("role")
|
||||
)
|
||||
|
||||
tenants = await tenant_service.get_active_tenants(skip=skip, limit=limit)
|
||||
|
||||
logger.debug(
|
||||
"Get active tenants successful",
|
||||
count=len(tenants),
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return tenants
|
||||
|
||||
@router.get(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
|
||||
@track_endpoint_metrics("tenant_get")
|
||||
async def get_tenant(
|
||||
|
||||
@@ -14,6 +14,7 @@ AuditLog = create_audit_log_model(Base)
|
||||
# Import all models to register them with the Base metadata
|
||||
from .tenants import Tenant, TenantMember, Subscription
|
||||
from .coupon import CouponModel, CouponRedemptionModel
|
||||
from .events import Event, EventTemplate
|
||||
|
||||
# List all models for easier access
|
||||
__all__ = [
|
||||
@@ -23,4 +24,6 @@ __all__ = [
|
||||
"AuditLog",
|
||||
"CouponModel",
|
||||
"CouponRedemptionModel",
|
||||
"Event",
|
||||
"EventTemplate",
|
||||
]
|
||||
|
||||
136
services/tenant/app/models/events.py
Normal file
136
services/tenant/app/models/events.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Event Calendar Models
|
||||
Database models for tracking local events, promotions, and special occasions
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, Float, Date
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from shared.database.base import Base
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class Event(Base):
|
||||
"""
|
||||
Table to track events that affect bakery demand.
|
||||
|
||||
Events include:
|
||||
- Local events (festivals, markets, concerts)
|
||||
- Promotions and sales
|
||||
- Weather events (heat waves, storms)
|
||||
- School holidays and breaks
|
||||
- Special occasions
|
||||
"""
|
||||
__tablename__ = "events"
|
||||
|
||||
# Primary identification
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
# Event information
|
||||
event_name = Column(String(500), nullable=False)
|
||||
event_type = Column(String(100), nullable=False, index=True) # promotion, festival, holiday, weather, school_break, sport_event, etc.
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# Date and time
|
||||
event_date = Column(Date, nullable=False, index=True)
|
||||
start_time = Column(DateTime(timezone=True), nullable=True)
|
||||
end_time = Column(DateTime(timezone=True), nullable=True)
|
||||
is_all_day = Column(Boolean, default=True)
|
||||
|
||||
# Impact estimation
|
||||
expected_impact = Column(String(50), nullable=True) # low, medium, high, very_high
|
||||
impact_multiplier = Column(Float, nullable=True) # Expected demand multiplier (e.g., 1.5 = 50% increase)
|
||||
affected_product_categories = Column(String(500), nullable=True) # Comma-separated categories
|
||||
|
||||
# Location
|
||||
location = Column(String(500), nullable=True)
|
||||
is_local = Column(Boolean, default=True) # True if event is near bakery
|
||||
|
||||
# Status
|
||||
is_confirmed = Column(Boolean, default=False)
|
||||
is_recurring = Column(Boolean, default=False)
|
||||
recurrence_pattern = Column(String(200), nullable=True) # e.g., "weekly:monday", "monthly:first_saturday"
|
||||
|
||||
# Actual impact (filled after event)
|
||||
actual_impact_multiplier = Column(Float, nullable=True)
|
||||
actual_sales_increase_percent = Column(Float, nullable=True)
|
||||
|
||||
# Metadata
|
||||
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))
|
||||
created_by = Column(String(255), nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"tenant_id": str(self.tenant_id),
|
||||
"event_name": self.event_name,
|
||||
"event_type": self.event_type,
|
||||
"description": self.description,
|
||||
"event_date": self.event_date.isoformat() if self.event_date else None,
|
||||
"start_time": self.start_time.isoformat() if self.start_time else None,
|
||||
"end_time": self.end_time.isoformat() if self.end_time else None,
|
||||
"is_all_day": self.is_all_day,
|
||||
"expected_impact": self.expected_impact,
|
||||
"impact_multiplier": self.impact_multiplier,
|
||||
"affected_product_categories": self.affected_product_categories,
|
||||
"location": self.location,
|
||||
"is_local": self.is_local,
|
||||
"is_confirmed": self.is_confirmed,
|
||||
"is_recurring": self.is_recurring,
|
||||
"recurrence_pattern": self.recurrence_pattern,
|
||||
"actual_impact_multiplier": self.actual_impact_multiplier,
|
||||
"actual_sales_increase_percent": self.actual_sales_increase_percent,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"created_by": self.created_by,
|
||||
"notes": self.notes
|
||||
}
|
||||
|
||||
|
||||
class EventTemplate(Base):
|
||||
"""
|
||||
Template for recurring events.
|
||||
Allows easy creation of events based on patterns.
|
||||
"""
|
||||
__tablename__ = "event_templates"
|
||||
|
||||
# Primary identification
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
# Template information
|
||||
template_name = Column(String(500), nullable=False)
|
||||
event_type = Column(String(100), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# Default values
|
||||
default_impact = Column(String(50), nullable=True)
|
||||
default_impact_multiplier = Column(Float, nullable=True)
|
||||
default_affected_categories = Column(String(500), nullable=True)
|
||||
|
||||
# Recurrence
|
||||
recurrence_pattern = Column(String(200), nullable=False) # e.g., "weekly:saturday", "monthly:last_sunday"
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Metadata
|
||||
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))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"tenant_id": str(self.tenant_id),
|
||||
"template_name": self.template_name,
|
||||
"event_type": self.event_type,
|
||||
"description": self.description,
|
||||
"default_impact": self.default_impact,
|
||||
"default_impact_multiplier": self.default_impact_multiplier,
|
||||
"default_affected_categories": self.default_affected_categories,
|
||||
"recurrence_pattern": self.recurrence_pattern,
|
||||
"is_active": self.is_active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None
|
||||
}
|
||||
@@ -154,6 +154,32 @@ class TenantSettings(Base):
|
||||
"enable_supplier_score_optimization": True
|
||||
})
|
||||
|
||||
# ML Insights Settings (AI Insights Service)
|
||||
ml_insights_settings = Column(JSON, nullable=False, default=lambda: {
|
||||
# Inventory ML (Safety Stock Optimization)
|
||||
"inventory_lookback_days": 90,
|
||||
"inventory_min_history_days": 30,
|
||||
|
||||
# Production ML (Yield Prediction)
|
||||
"production_lookback_days": 90,
|
||||
"production_min_history_runs": 30,
|
||||
|
||||
# Procurement ML (Supplier Analysis & Price Forecasting)
|
||||
"supplier_analysis_lookback_days": 180,
|
||||
"supplier_analysis_min_orders": 10,
|
||||
"price_forecast_lookback_days": 180,
|
||||
"price_forecast_horizon_days": 30,
|
||||
|
||||
# Forecasting ML (Dynamic Rules)
|
||||
"rules_generation_lookback_days": 90,
|
||||
"rules_generation_min_samples": 10,
|
||||
|
||||
# Global ML Settings
|
||||
"enable_ml_insights": True,
|
||||
"ml_insights_auto_trigger": False,
|
||||
"ml_confidence_threshold": 0.80
|
||||
})
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
@@ -280,5 +306,20 @@ class TenantSettings(Base):
|
||||
"diversification_threshold": 1000,
|
||||
"max_single_percentage": 0.70,
|
||||
"enable_supplier_score_optimization": True
|
||||
},
|
||||
"ml_insights_settings": {
|
||||
"inventory_lookback_days": 90,
|
||||
"inventory_min_history_days": 30,
|
||||
"production_lookback_days": 90,
|
||||
"production_min_history_runs": 30,
|
||||
"supplier_analysis_lookback_days": 180,
|
||||
"supplier_analysis_min_orders": 10,
|
||||
"price_forecast_lookback_days": 180,
|
||||
"price_forecast_horizon_days": 30,
|
||||
"rules_generation_lookback_days": 90,
|
||||
"rules_generation_min_samples": 10,
|
||||
"enable_ml_insights": True,
|
||||
"ml_insights_auto_trigger": False,
|
||||
"ml_confidence_threshold": 0.80
|
||||
}
|
||||
}
|
||||
|
||||
283
services/tenant/app/repositories/event_repository.py
Normal file
283
services/tenant/app/repositories/event_repository.py
Normal file
@@ -0,0 +1,283 @@
|
||||
"""
|
||||
Event Repository
|
||||
Data access layer for events
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import date, datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, and_, or_, func
|
||||
from uuid import UUID
|
||||
import structlog
|
||||
|
||||
from app.models.events import Event, EventTemplate
|
||||
from shared.database.repository import BaseRepository
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class EventRepository(BaseRepository[Event]):
|
||||
"""Repository for event management"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(Event, session)
|
||||
|
||||
async def get_events_by_date_range(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
start_date: date,
|
||||
end_date: date,
|
||||
event_types: List[str] = None,
|
||||
confirmed_only: bool = False
|
||||
) -> List[Event]:
|
||||
"""
|
||||
Get events within a date range.
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
start_date: Start date (inclusive)
|
||||
end_date: End date (inclusive)
|
||||
event_types: Optional filter by event types
|
||||
confirmed_only: Only return confirmed events
|
||||
|
||||
Returns:
|
||||
List of Event objects
|
||||
"""
|
||||
try:
|
||||
query = select(Event).where(
|
||||
and_(
|
||||
Event.tenant_id == tenant_id,
|
||||
Event.event_date >= start_date,
|
||||
Event.event_date <= end_date
|
||||
)
|
||||
)
|
||||
|
||||
if event_types:
|
||||
query = query.where(Event.event_type.in_(event_types))
|
||||
|
||||
if confirmed_only:
|
||||
query = query.where(Event.is_confirmed == True)
|
||||
|
||||
query = query.order_by(Event.event_date)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
logger.debug("Retrieved events by date range",
|
||||
tenant_id=str(tenant_id),
|
||||
start_date=start_date.isoformat(),
|
||||
end_date=end_date.isoformat(),
|
||||
count=len(events))
|
||||
|
||||
return list(events)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get events by date range",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def get_events_for_date(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
event_date: date
|
||||
) -> List[Event]:
|
||||
"""
|
||||
Get all events for a specific date.
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
event_date: Date to get events for
|
||||
|
||||
Returns:
|
||||
List of Event objects
|
||||
"""
|
||||
try:
|
||||
query = select(Event).where(
|
||||
and_(
|
||||
Event.tenant_id == tenant_id,
|
||||
Event.event_date == event_date
|
||||
)
|
||||
).order_by(Event.start_time)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
return list(events)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get events for date",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def get_upcoming_events(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
days_ahead: int = 30,
|
||||
limit: int = 100
|
||||
) -> List[Event]:
|
||||
"""
|
||||
Get upcoming events.
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
days_ahead: Number of days to look ahead
|
||||
limit: Maximum number of events to return
|
||||
|
||||
Returns:
|
||||
List of upcoming Event objects
|
||||
"""
|
||||
try:
|
||||
from datetime import date, timedelta
|
||||
|
||||
today = date.today()
|
||||
future_date = today + timedelta(days=days_ahead)
|
||||
|
||||
query = select(Event).where(
|
||||
and_(
|
||||
Event.tenant_id == tenant_id,
|
||||
Event.event_date >= today,
|
||||
Event.event_date <= future_date
|
||||
)
|
||||
).order_by(Event.event_date).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
return list(events)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get upcoming events",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def create_event(self, event_data: Dict[str, Any]) -> Event:
|
||||
"""Create a new event"""
|
||||
try:
|
||||
event = Event(**event_data)
|
||||
self.session.add(event)
|
||||
await self.session.flush()
|
||||
|
||||
logger.info("Created event",
|
||||
event_id=str(event.id),
|
||||
event_name=event.event_name,
|
||||
event_date=event.event_date.isoformat())
|
||||
|
||||
return event
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to create event", error=str(e))
|
||||
raise
|
||||
|
||||
async def update_event_actual_impact(
|
||||
self,
|
||||
event_id: UUID,
|
||||
actual_impact_multiplier: float,
|
||||
actual_sales_increase_percent: float
|
||||
) -> Optional[Event]:
|
||||
"""
|
||||
Update event with actual impact after it occurs.
|
||||
|
||||
Args:
|
||||
event_id: Event UUID
|
||||
actual_impact_multiplier: Actual demand multiplier observed
|
||||
actual_sales_increase_percent: Actual sales increase percentage
|
||||
|
||||
Returns:
|
||||
Updated Event or None
|
||||
"""
|
||||
try:
|
||||
event = await self.get(event_id)
|
||||
if not event:
|
||||
return None
|
||||
|
||||
event.actual_impact_multiplier = actual_impact_multiplier
|
||||
event.actual_sales_increase_percent = actual_sales_increase_percent
|
||||
|
||||
await self.session.flush()
|
||||
|
||||
logger.info("Updated event actual impact",
|
||||
event_id=str(event_id),
|
||||
actual_multiplier=actual_impact_multiplier)
|
||||
|
||||
return event
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to update event actual impact",
|
||||
event_id=str(event_id),
|
||||
error=str(e))
|
||||
return None
|
||||
|
||||
async def get_events_by_type(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
event_type: str,
|
||||
limit: int = 100
|
||||
) -> List[Event]:
|
||||
"""Get events by type"""
|
||||
try:
|
||||
query = select(Event).where(
|
||||
and_(
|
||||
Event.tenant_id == tenant_id,
|
||||
Event.event_type == event_type
|
||||
)
|
||||
).order_by(Event.event_date.desc()).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
return list(events)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get events by type",
|
||||
tenant_id=str(tenant_id),
|
||||
event_type=event_type,
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
|
||||
class EventTemplateRepository(BaseRepository[EventTemplate]):
|
||||
"""Repository for event template management"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(EventTemplate, session)
|
||||
|
||||
async def get_active_templates(self, tenant_id: UUID) -> List[EventTemplate]:
|
||||
"""Get all active event templates for a tenant"""
|
||||
try:
|
||||
query = select(EventTemplate).where(
|
||||
and_(
|
||||
EventTemplate.tenant_id == tenant_id,
|
||||
EventTemplate.is_active == True
|
||||
)
|
||||
).order_by(EventTemplate.template_name)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
templates = result.scalars().all()
|
||||
|
||||
return list(templates)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get active templates",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def create_template(self, template_data: Dict[str, Any]) -> EventTemplate:
|
||||
"""Create a new event template"""
|
||||
try:
|
||||
template = EventTemplate(**template_data)
|
||||
self.session.add(template)
|
||||
await self.session.flush()
|
||||
|
||||
logger.info("Created event template",
|
||||
template_id=str(template.id),
|
||||
template_name=template.template_name)
|
||||
|
||||
return template
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to create event template", error=str(e))
|
||||
raise
|
||||
@@ -184,7 +184,7 @@ class SupplierSelectionSettings(BaseModel):
|
||||
|
||||
@validator('price_weight', 'lead_time_weight', 'quality_weight', 'reliability_weight')
|
||||
def validate_weights_sum(cls, v, values):
|
||||
weights = [values.get('price_weight', 0.40), values.get('lead_time_weight', 0.20),
|
||||
weights = [values.get('price_weight', 0.40), values.get('lead_time_weight', 0.20),
|
||||
values.get('quality_weight', 0.20), values.get('reliability_weight', 0.20)]
|
||||
total = sum(weights)
|
||||
if total > 1.0:
|
||||
@@ -192,6 +192,32 @@ class SupplierSelectionSettings(BaseModel):
|
||||
return v
|
||||
|
||||
|
||||
class MLInsightsSettings(BaseModel):
|
||||
"""ML Insights configuration settings"""
|
||||
# Inventory ML (Safety Stock Optimization)
|
||||
inventory_lookback_days: int = Field(90, ge=30, le=365, description="Days of demand history for safety stock analysis")
|
||||
inventory_min_history_days: int = Field(30, ge=7, le=180, description="Minimum days of history required")
|
||||
|
||||
# Production ML (Yield Prediction)
|
||||
production_lookback_days: int = Field(90, ge=30, le=365, description="Days of production history for yield analysis")
|
||||
production_min_history_runs: int = Field(30, ge=10, le=100, description="Minimum production runs required")
|
||||
|
||||
# Procurement ML (Supplier Analysis & Price Forecasting)
|
||||
supplier_analysis_lookback_days: int = Field(180, ge=30, le=730, description="Days of order history for supplier analysis")
|
||||
supplier_analysis_min_orders: int = Field(10, ge=5, le=100, description="Minimum orders required for analysis")
|
||||
price_forecast_lookback_days: int = Field(180, ge=90, le=730, description="Days of price history for forecasting")
|
||||
price_forecast_horizon_days: int = Field(30, ge=7, le=90, description="Days to forecast ahead")
|
||||
|
||||
# Forecasting ML (Dynamic Rules)
|
||||
rules_generation_lookback_days: int = Field(90, ge=30, le=365, description="Days of sales history for rule learning")
|
||||
rules_generation_min_samples: int = Field(10, ge=5, le=100, description="Minimum samples required for rule generation")
|
||||
|
||||
# Global ML Settings
|
||||
enable_ml_insights: bool = Field(True, description="Enable/disable ML insights generation")
|
||||
ml_insights_auto_trigger: bool = Field(False, description="Automatically trigger ML insights in daily workflow")
|
||||
ml_confidence_threshold: float = Field(0.80, ge=0.0, le=1.0, description="Minimum confidence threshold for ML recommendations")
|
||||
|
||||
|
||||
# ================================================================
|
||||
# REQUEST/RESPONSE SCHEMAS
|
||||
# ================================================================
|
||||
@@ -210,6 +236,7 @@ class TenantSettingsResponse(BaseModel):
|
||||
safety_stock_settings: SafetyStockSettings
|
||||
moq_settings: MOQSettings
|
||||
supplier_selection_settings: SupplierSelectionSettings
|
||||
ml_insights_settings: MLInsightsSettings
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -229,6 +256,7 @@ class TenantSettingsUpdate(BaseModel):
|
||||
safety_stock_settings: Optional[SafetyStockSettings] = None
|
||||
moq_settings: Optional[MOQSettings] = None
|
||||
supplier_selection_settings: Optional[SupplierSelectionSettings] = None
|
||||
ml_insights_settings: Optional[MLInsightsSettings] = None
|
||||
|
||||
|
||||
class CategoryUpdateRequest(BaseModel):
|
||||
|
||||
@@ -265,18 +265,34 @@ class EnhancedTenantService:
|
||||
|
||||
async def get_user_tenants(self, owner_id: str) -> List[TenantResponse]:
|
||||
"""Get all tenants owned by a user"""
|
||||
|
||||
|
||||
try:
|
||||
async with self.database_manager.get_session() as db_session:
|
||||
await self._init_repositories(db_session)
|
||||
tenants = await self.tenant_repo.get_tenants_by_owner(owner_id)
|
||||
return [TenantResponse.from_orm(tenant) for tenant in tenants]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting user tenants",
|
||||
owner_id=owner_id,
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def get_active_tenants(self, skip: int = 0, limit: int = 100) -> List[TenantResponse]:
|
||||
"""Get all active tenants"""
|
||||
|
||||
try:
|
||||
async with self.database_manager.get_session() as db_session:
|
||||
await self._init_repositories(db_session)
|
||||
tenants = await self.tenant_repo.get_active_tenants(skip=skip, limit=limit)
|
||||
return [TenantResponse.from_orm(tenant) for tenant in tenants]
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting active tenants",
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
error=str(e))
|
||||
return []
|
||||
|
||||
async def search_tenants(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user