330 lines
10 KiB
Python
330 lines
10 KiB
Python
# services/external/app/repositories/calendar_repository.py
|
|
"""
|
|
Calendar Repository - Manages school calendars and tenant location contexts
|
|
"""
|
|
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
from sqlalchemy import select, and_, or_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
import structlog
|
|
import uuid
|
|
|
|
from app.models.calendar import SchoolCalendar, TenantLocationContext
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
class CalendarRepository:
|
|
"""Repository for school calendar and tenant location data"""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
# ===== School Calendar Operations =====
|
|
|
|
async def create_school_calendar(
|
|
self,
|
|
city_id: str,
|
|
calendar_name: str,
|
|
school_type: str,
|
|
academic_year: str,
|
|
holiday_periods: List[Dict[str, Any]],
|
|
school_hours: Dict[str, Any],
|
|
source: Optional[str] = None,
|
|
enabled: bool = True
|
|
) -> SchoolCalendar:
|
|
"""Create a new school calendar"""
|
|
try:
|
|
calendar = SchoolCalendar(
|
|
id=uuid.uuid4(),
|
|
city_id=city_id,
|
|
calendar_name=calendar_name,
|
|
school_type=school_type,
|
|
academic_year=academic_year,
|
|
holiday_periods=holiday_periods,
|
|
school_hours=school_hours,
|
|
source=source,
|
|
enabled=enabled
|
|
)
|
|
|
|
self.session.add(calendar)
|
|
await self.session.commit()
|
|
await self.session.refresh(calendar)
|
|
|
|
logger.info(
|
|
"School calendar created",
|
|
calendar_id=str(calendar.id),
|
|
city_id=city_id,
|
|
school_type=school_type
|
|
)
|
|
|
|
return calendar
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Error creating school calendar",
|
|
city_id=city_id,
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
async def get_calendar_by_id(
|
|
self,
|
|
calendar_id: uuid.UUID
|
|
) -> Optional[SchoolCalendar]:
|
|
"""Get school calendar by ID"""
|
|
stmt = select(SchoolCalendar).where(SchoolCalendar.id == calendar_id)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_calendars_by_city(
|
|
self,
|
|
city_id: str,
|
|
enabled_only: bool = True
|
|
) -> List[SchoolCalendar]:
|
|
"""Get all school calendars for a city"""
|
|
stmt = select(SchoolCalendar).where(SchoolCalendar.city_id == city_id)
|
|
|
|
if enabled_only:
|
|
stmt = stmt.where(SchoolCalendar.enabled == True)
|
|
|
|
stmt = stmt.order_by(SchoolCalendar.academic_year.desc(), SchoolCalendar.school_type)
|
|
|
|
result = await self.session.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
async def get_calendar_by_city_type_year(
|
|
self,
|
|
city_id: str,
|
|
school_type: str,
|
|
academic_year: str
|
|
) -> Optional[SchoolCalendar]:
|
|
"""Get specific calendar by city, type, and year"""
|
|
stmt = select(SchoolCalendar).where(
|
|
and_(
|
|
SchoolCalendar.city_id == city_id,
|
|
SchoolCalendar.school_type == school_type,
|
|
SchoolCalendar.academic_year == academic_year,
|
|
SchoolCalendar.enabled == True
|
|
)
|
|
)
|
|
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def update_calendar(
|
|
self,
|
|
calendar_id: uuid.UUID,
|
|
**kwargs
|
|
) -> Optional[SchoolCalendar]:
|
|
"""Update school calendar"""
|
|
try:
|
|
calendar = await self.get_calendar_by_id(calendar_id)
|
|
if not calendar:
|
|
return None
|
|
|
|
for key, value in kwargs.items():
|
|
if hasattr(calendar, key):
|
|
setattr(calendar, key, value)
|
|
|
|
calendar.updated_at = datetime.utcnow()
|
|
|
|
await self.session.commit()
|
|
await self.session.refresh(calendar)
|
|
|
|
logger.info(
|
|
"School calendar updated",
|
|
calendar_id=str(calendar_id),
|
|
fields=list(kwargs.keys())
|
|
)
|
|
|
|
return calendar
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Error updating school calendar",
|
|
calendar_id=str(calendar_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
async def delete_calendar(self, calendar_id: uuid.UUID) -> bool:
|
|
"""Delete school calendar"""
|
|
try:
|
|
calendar = await self.get_calendar_by_id(calendar_id)
|
|
if not calendar:
|
|
return False
|
|
|
|
await self.session.delete(calendar)
|
|
await self.session.commit()
|
|
|
|
logger.info("School calendar deleted", calendar_id=str(calendar_id))
|
|
return True
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Error deleting school calendar",
|
|
calendar_id=str(calendar_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
# ===== Tenant Location Context Operations =====
|
|
|
|
async def create_or_update_tenant_location_context(
|
|
self,
|
|
tenant_id: uuid.UUID,
|
|
city_id: str,
|
|
school_calendar_id: Optional[uuid.UUID] = None,
|
|
neighborhood: Optional[str] = None,
|
|
local_events: Optional[List[Dict[str, Any]]] = None,
|
|
notes: Optional[str] = None
|
|
) -> TenantLocationContext:
|
|
"""Create or update tenant location context"""
|
|
try:
|
|
# Check if context exists
|
|
existing = await self.get_tenant_location_context(tenant_id)
|
|
|
|
if existing:
|
|
# Update existing
|
|
existing.city_id = city_id
|
|
if school_calendar_id is not None:
|
|
existing.school_calendar_id = school_calendar_id
|
|
if neighborhood is not None:
|
|
existing.neighborhood = neighborhood
|
|
if local_events is not None:
|
|
existing.local_events = local_events
|
|
if notes is not None:
|
|
existing.notes = notes
|
|
existing.updated_at = datetime.utcnow()
|
|
|
|
await self.session.commit()
|
|
await self.session.refresh(existing)
|
|
|
|
logger.info(
|
|
"Tenant location context updated",
|
|
tenant_id=str(tenant_id)
|
|
)
|
|
|
|
return existing
|
|
else:
|
|
# Create new
|
|
context = TenantLocationContext(
|
|
tenant_id=tenant_id,
|
|
city_id=city_id,
|
|
school_calendar_id=school_calendar_id,
|
|
neighborhood=neighborhood,
|
|
local_events=local_events or [],
|
|
notes=notes
|
|
)
|
|
|
|
self.session.add(context)
|
|
await self.session.commit()
|
|
await self.session.refresh(context)
|
|
|
|
logger.info(
|
|
"Tenant location context created",
|
|
tenant_id=str(tenant_id),
|
|
city_id=city_id
|
|
)
|
|
|
|
return context
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Error creating/updating tenant location context",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
async def get_tenant_location_context(
|
|
self,
|
|
tenant_id: uuid.UUID
|
|
) -> Optional[TenantLocationContext]:
|
|
"""Get tenant location context"""
|
|
stmt = select(TenantLocationContext).where(
|
|
TenantLocationContext.tenant_id == tenant_id
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_tenant_with_calendar(
|
|
self,
|
|
tenant_id: uuid.UUID
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Get tenant location context with full calendar details"""
|
|
context = await self.get_tenant_location_context(tenant_id)
|
|
if not context:
|
|
return None
|
|
|
|
result = {
|
|
"tenant_id": str(context.tenant_id),
|
|
"city_id": context.city_id,
|
|
"neighborhood": context.neighborhood,
|
|
"local_events": context.local_events,
|
|
"notes": context.notes,
|
|
"calendar": None
|
|
}
|
|
|
|
if context.school_calendar_id:
|
|
calendar = await self.get_calendar_by_id(context.school_calendar_id)
|
|
if calendar:
|
|
result["calendar"] = {
|
|
"calendar_id": str(calendar.id),
|
|
"calendar_name": calendar.calendar_name,
|
|
"school_type": calendar.school_type,
|
|
"academic_year": calendar.academic_year,
|
|
"holiday_periods": calendar.holiday_periods,
|
|
"school_hours": calendar.school_hours,
|
|
"source": calendar.source
|
|
}
|
|
|
|
return result
|
|
|
|
async def delete_tenant_location_context(
|
|
self,
|
|
tenant_id: uuid.UUID
|
|
) -> bool:
|
|
"""Delete tenant location context"""
|
|
try:
|
|
context = await self.get_tenant_location_context(tenant_id)
|
|
if not context:
|
|
return False
|
|
|
|
await self.session.delete(context)
|
|
await self.session.commit()
|
|
|
|
logger.info(
|
|
"Tenant location context deleted",
|
|
tenant_id=str(tenant_id)
|
|
)
|
|
return True
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Error deleting tenant location context",
|
|
tenant_id=str(tenant_id),
|
|
error=str(e)
|
|
)
|
|
raise
|
|
|
|
# ===== Helper Methods =====
|
|
|
|
async def get_all_tenants_for_calendar(
|
|
self,
|
|
calendar_id: uuid.UUID
|
|
) -> List[TenantLocationContext]:
|
|
"""Get all tenants using a specific calendar"""
|
|
stmt = select(TenantLocationContext).where(
|
|
TenantLocationContext.school_calendar_id == calendar_id
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return list(result.scalars().all())
|