Initial commit - production deployment
This commit is contained in:
223
services/production/app/api/production_schedules.py
Normal file
223
services/production/app/api/production_schedules.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# services/production/app/api/production_schedules.py
|
||||
"""
|
||||
Production Schedules API - ATOMIC CRUD operations on ProductionSchedule model
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||
from typing import Optional
|
||||
from datetime import date, datetime, timedelta
|
||||
from uuid import UUID
|
||||
import structlog
|
||||
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
from shared.auth.access_control import require_user_role
|
||||
from shared.routing import RouteBuilder
|
||||
from shared.security import create_audit_logger, AuditSeverity, AuditAction
|
||||
from app.core.database import get_db
|
||||
from app.services.production_service import ProductionService
|
||||
from app.models import AuditLog
|
||||
from app.schemas.production import (
|
||||
ProductionScheduleCreate,
|
||||
ProductionScheduleUpdate,
|
||||
ProductionScheduleResponse
|
||||
)
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
route_builder = RouteBuilder('production')
|
||||
router = APIRouter(tags=["production-schedules"])
|
||||
|
||||
# Initialize audit logger with the production service's AuditLog model
|
||||
audit_logger = create_audit_logger("production-service", AuditLog)
|
||||
|
||||
|
||||
def get_production_service(request: Request) -> ProductionService:
|
||||
"""Dependency injection for production service"""
|
||||
from app.core.database import database_manager
|
||||
notification_service = getattr(request.app.state, 'notification_service', None)
|
||||
return ProductionService(database_manager, settings, notification_service)
|
||||
|
||||
|
||||
@router.get(
|
||||
route_builder.build_base_route("schedules"),
|
||||
response_model=dict
|
||||
)
|
||||
async def get_production_schedule(
|
||||
tenant_id: UUID = Path(...),
|
||||
start_date: Optional[date] = Query(None, description="Start date for schedule"),
|
||||
end_date: Optional[date] = Query(None, description="End date for schedule"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db=Depends(get_db)
|
||||
):
|
||||
"""Get production schedule for a date range"""
|
||||
try:
|
||||
# Default to next 7 days if no dates provided
|
||||
if not start_date:
|
||||
start_date = datetime.now().date()
|
||||
if not end_date:
|
||||
end_date = start_date + timedelta(days=7)
|
||||
|
||||
from app.repositories.production_schedule_repository import ProductionScheduleRepository
|
||||
schedule_repo = ProductionScheduleRepository(db)
|
||||
|
||||
schedules = await schedule_repo.get_schedules_by_date_range(
|
||||
str(tenant_id), start_date, end_date
|
||||
)
|
||||
|
||||
schedule_data = {
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
"schedules": [
|
||||
{
|
||||
"id": str(schedule.id),
|
||||
"date": schedule.schedule_date.isoformat(),
|
||||
"shift_start": schedule.shift_start.isoformat(),
|
||||
"shift_end": schedule.shift_end.isoformat(),
|
||||
"capacity_utilization": schedule.utilization_percentage,
|
||||
"batches_planned": schedule.total_batches_planned,
|
||||
"is_finalized": schedule.is_finalized
|
||||
}
|
||||
for schedule in schedules
|
||||
],
|
||||
"total_schedules": len(schedules)
|
||||
}
|
||||
|
||||
logger.info("Retrieved production schedule",
|
||||
tenant_id=str(tenant_id),
|
||||
start_date=start_date.isoformat(),
|
||||
end_date=end_date.isoformat(),
|
||||
schedules_count=len(schedules))
|
||||
|
||||
return schedule_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting production schedule",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to get production schedule")
|
||||
|
||||
|
||||
@router.get(
|
||||
route_builder.build_resource_detail_route("schedules", "schedule_id"),
|
||||
response_model=ProductionScheduleResponse
|
||||
)
|
||||
async def get_production_schedule_details(
|
||||
tenant_id: UUID = Path(...),
|
||||
schedule_id: UUID = Path(...),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db=Depends(get_db)
|
||||
):
|
||||
"""Retrieve full schedule details including assignments"""
|
||||
try:
|
||||
from app.repositories.production_schedule_repository import ProductionScheduleRepository
|
||||
schedule_repo = ProductionScheduleRepository(db)
|
||||
|
||||
schedule = await schedule_repo.get(schedule_id)
|
||||
if not schedule or str(schedule.tenant_id) != str(tenant_id):
|
||||
raise HTTPException(status_code=404, detail="Production schedule not found")
|
||||
|
||||
logger.info("Retrieved production schedule details",
|
||||
schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
|
||||
return ProductionScheduleResponse.model_validate(schedule)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error getting production schedule details",
|
||||
error=str(e), schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to get production schedule details")
|
||||
|
||||
|
||||
@router.post(
|
||||
route_builder.build_base_route("schedules"),
|
||||
response_model=ProductionScheduleResponse
|
||||
)
|
||||
@require_user_role(['admin', 'owner'])
|
||||
async def create_production_schedule(
|
||||
schedule_data: ProductionScheduleCreate,
|
||||
tenant_id: UUID = Path(...),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
production_service: ProductionService = Depends(get_production_service)
|
||||
):
|
||||
"""Generate or manually create a daily/shift schedule (Admin+ only)"""
|
||||
try:
|
||||
schedule = await production_service.create_production_schedule(tenant_id, schedule_data)
|
||||
|
||||
logger.info("Created production schedule",
|
||||
schedule_id=str(schedule.id), tenant_id=str(tenant_id))
|
||||
|
||||
return ProductionScheduleResponse.model_validate(schedule)
|
||||
|
||||
except ValueError as e:
|
||||
logger.warning("Invalid schedule data", error=str(e), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error("Error creating production schedule",
|
||||
error=str(e), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to create production schedule")
|
||||
|
||||
|
||||
@router.put(
|
||||
route_builder.build_resource_detail_route("schedules", "schedule_id"),
|
||||
response_model=ProductionScheduleResponse
|
||||
)
|
||||
@require_user_role(['admin', 'owner'])
|
||||
async def update_production_schedule(
|
||||
schedule_update: ProductionScheduleUpdate,
|
||||
tenant_id: UUID = Path(...),
|
||||
schedule_id: UUID = Path(...),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
production_service: ProductionService = Depends(get_production_service)
|
||||
):
|
||||
"""Edit schedule before finalizing (Admin+ only)"""
|
||||
try:
|
||||
schedule = await production_service.update_production_schedule(tenant_id, schedule_id, schedule_update)
|
||||
|
||||
logger.info("Updated production schedule",
|
||||
schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
|
||||
return ProductionScheduleResponse.model_validate(schedule)
|
||||
|
||||
except ValueError as e:
|
||||
logger.warning("Invalid schedule update", error=str(e), schedule_id=str(schedule_id))
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error("Error updating production schedule",
|
||||
error=str(e), schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to update production schedule")
|
||||
|
||||
|
||||
@router.delete(
|
||||
route_builder.build_resource_detail_route("schedules", "schedule_id")
|
||||
)
|
||||
async def delete_production_schedule(
|
||||
tenant_id: UUID = Path(...),
|
||||
schedule_id: UUID = Path(...),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db=Depends(get_db)
|
||||
):
|
||||
"""Delete a production schedule (if not finalized)"""
|
||||
try:
|
||||
from app.repositories.production_schedule_repository import ProductionScheduleRepository
|
||||
schedule_repo = ProductionScheduleRepository(db)
|
||||
|
||||
schedule = await schedule_repo.get(schedule_id)
|
||||
if not schedule or str(schedule.tenant_id) != str(tenant_id):
|
||||
raise HTTPException(status_code=404, detail="Production schedule not found")
|
||||
|
||||
if schedule.is_finalized:
|
||||
raise HTTPException(status_code=400, detail="Cannot delete finalized schedule")
|
||||
|
||||
await schedule_repo.delete(schedule_id)
|
||||
|
||||
logger.info("Deleted production schedule",
|
||||
schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
|
||||
return {"message": "Production schedule deleted successfully"}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Error deleting production schedule",
|
||||
error=str(e), schedule_id=str(schedule_id), tenant_id=str(tenant_id))
|
||||
raise HTTPException(status_code=500, detail="Failed to delete production schedule")
|
||||
Reference in New Issue
Block a user