""" Manager Rules Routes Manager settings, closing days, guarantees, and exclusions Key concept: Managers own parking spots and set rules for their managed users. Rules are set at manager level (users have manager_id pointing to their manager). """ from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy.orm import Session import uuid from database.connection import get_db from database.models import ( User, ManagerClosingDay, ManagerWeeklyClosingDay, ParkingGuarantee, ParkingExclusion ) from utils.auth_middleware import require_admin, require_manager_or_admin, get_current_user router = APIRouter(prefix="/api/managers", tags=["managers"]) # Request/Response Models class ClosingDayCreate(BaseModel): date: str # YYYY-MM-DD reason: str | None = None class WeeklyClosingDayCreate(BaseModel): weekday: int # 0=Sunday, 1=Monday, ..., 6=Saturday class GuaranteeCreate(BaseModel): user_id: str start_date: str | None = None end_date: str | None = None class ExclusionCreate(BaseModel): user_id: str start_date: str | None = None end_date: str | None = None class ManagerSettingsUpdate(BaseModel): parking_quota: int | None = None spot_prefix: str | None = None # Manager listing and details @router.get("") def list_managers(db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Get all managers with their managed user count and parking quota""" managers = db.query(User).filter(User.role == "manager").all() result = [] for manager in managers: managed_user_count = db.query(User).filter(User.manager_id == manager.id).count() result.append({ "id": manager.id, "name": manager.name, "email": manager.email, "parking_quota": manager.manager_parking_quota or 0, "spot_prefix": manager.manager_spot_prefix, "managed_user_count": managed_user_count }) return result @router.get("/{manager_id}") def get_manager_details(manager_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Get manager details including managed users and parking settings""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") managed_user_count = db.query(User).filter(User.manager_id == manager_id).count() return { "id": manager.id, "name": manager.name, "email": manager.email, "parking_quota": manager.manager_parking_quota or 0, "spot_prefix": manager.manager_spot_prefix, "managed_user_count": managed_user_count } @router.put("/{manager_id}/settings") def update_manager_settings(manager_id: str, data: ManagerSettingsUpdate, db: Session = Depends(get_db), user=Depends(require_admin)): """Update manager parking settings (admin only)""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") if data.parking_quota is not None: if data.parking_quota < 0: raise HTTPException(status_code=400, detail="Parking quota must be non-negative") manager.manager_parking_quota = data.parking_quota if data.spot_prefix is not None: if data.spot_prefix and not data.spot_prefix.isalpha(): raise HTTPException(status_code=400, detail="Spot prefix must be a letter") if data.spot_prefix: data.spot_prefix = data.spot_prefix.upper() existing = db.query(User).filter( User.manager_spot_prefix == data.spot_prefix, User.id != manager_id ).first() if existing: raise HTTPException(status_code=400, detail=f"Spot prefix '{data.spot_prefix}' is already used") manager.manager_spot_prefix = data.spot_prefix manager.updated_at = datetime.utcnow().isoformat() db.commit() return { "id": manager.id, "parking_quota": manager.manager_parking_quota, "spot_prefix": manager.manager_spot_prefix } @router.get("/{manager_id}/users") def get_manager_users(manager_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Get all users in a manager's team (including the manager themselves)""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") # Include users managed by this manager + the manager themselves users = db.query(User).filter( (User.manager_id == manager_id) | (User.id == manager_id) ).all() return [{"id": u.id, "name": u.name, "email": u.email, "role": u.role} for u in users] # Closing days @router.get("/{manager_id}/closing-days") def get_manager_closing_days(manager_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)): """Get closing days for a manager""" days = db.query(ManagerClosingDay).filter( ManagerClosingDay.manager_id == manager_id ).order_by(ManagerClosingDay.date).all() return [{"id": d.id, "date": d.date, "reason": d.reason} for d in days] @router.post("/{manager_id}/closing-days") def add_manager_closing_day(manager_id: str, data: ClosingDayCreate, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Add a closing day for a manager""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") existing = db.query(ManagerClosingDay).filter( ManagerClosingDay.manager_id == manager_id, ManagerClosingDay.date == data.date ).first() if existing: raise HTTPException(status_code=400, detail="Closing day already exists for this date") closing_day = ManagerClosingDay( id=str(uuid.uuid4()), manager_id=manager_id, date=data.date, reason=data.reason ) db.add(closing_day) db.commit() return {"id": closing_day.id, "message": "Closing day added"} @router.delete("/{manager_id}/closing-days/{closing_day_id}") def remove_manager_closing_day(manager_id: str, closing_day_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Remove a closing day for a manager""" closing_day = db.query(ManagerClosingDay).filter( ManagerClosingDay.id == closing_day_id, ManagerClosingDay.manager_id == manager_id ).first() if not closing_day: raise HTTPException(status_code=404, detail="Closing day not found") db.delete(closing_day) db.commit() return {"message": "Closing day removed"} # Weekly closing days @router.get("/{manager_id}/weekly-closing-days") def get_manager_weekly_closing_days(manager_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)): """Get weekly closing days for a manager""" days = db.query(ManagerWeeklyClosingDay).filter( ManagerWeeklyClosingDay.manager_id == manager_id ).all() return [{"id": d.id, "weekday": d.weekday} for d in days] @router.post("/{manager_id}/weekly-closing-days") def add_manager_weekly_closing_day(manager_id: str, data: WeeklyClosingDayCreate, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Add a weekly closing day for a manager""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") if data.weekday < 0 or data.weekday > 6: raise HTTPException(status_code=400, detail="Weekday must be 0-6 (0=Sunday, 6=Saturday)") existing = db.query(ManagerWeeklyClosingDay).filter( ManagerWeeklyClosingDay.manager_id == manager_id, ManagerWeeklyClosingDay.weekday == data.weekday ).first() if existing: raise HTTPException(status_code=400, detail="Weekly closing day already exists for this weekday") weekly_closing = ManagerWeeklyClosingDay( id=str(uuid.uuid4()), manager_id=manager_id, weekday=data.weekday ) db.add(weekly_closing) db.commit() return {"id": weekly_closing.id, "message": "Weekly closing day added"} @router.delete("/{manager_id}/weekly-closing-days/{weekly_id}") def remove_manager_weekly_closing_day(manager_id: str, weekly_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Remove a weekly closing day for a manager""" weekly_closing = db.query(ManagerWeeklyClosingDay).filter( ManagerWeeklyClosingDay.id == weekly_id, ManagerWeeklyClosingDay.manager_id == manager_id ).first() if not weekly_closing: raise HTTPException(status_code=404, detail="Weekly closing day not found") db.delete(weekly_closing) db.commit() return {"message": "Weekly closing day removed"} # Guarantees @router.get("/{manager_id}/guarantees") def get_manager_guarantees(manager_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Get parking guarantees for a manager""" guarantees = db.query(ParkingGuarantee).filter(ParkingGuarantee.manager_id == manager_id).all() result = [] for g in guarantees: target_user = db.query(User).filter(User.id == g.user_id).first() result.append({ "id": g.id, "user_id": g.user_id, "user_name": target_user.name if target_user else None, "start_date": g.start_date, "end_date": g.end_date }) return result @router.post("/{manager_id}/guarantees") def add_manager_guarantee(manager_id: str, data: GuaranteeCreate, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Add parking guarantee for a manager""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") if not db.query(User).filter(User.id == data.user_id).first(): raise HTTPException(status_code=404, detail="User not found") existing = db.query(ParkingGuarantee).filter( ParkingGuarantee.manager_id == manager_id, ParkingGuarantee.user_id == data.user_id ).first() if existing: raise HTTPException(status_code=400, detail="User already has a parking guarantee") guarantee = ParkingGuarantee( id=str(uuid.uuid4()), manager_id=manager_id, user_id=data.user_id, start_date=data.start_date, end_date=data.end_date, created_at=datetime.utcnow().isoformat() ) db.add(guarantee) db.commit() return {"id": guarantee.id, "message": "Guarantee added"} @router.delete("/{manager_id}/guarantees/{guarantee_id}") def remove_manager_guarantee(manager_id: str, guarantee_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Remove parking guarantee for a manager""" guarantee = db.query(ParkingGuarantee).filter( ParkingGuarantee.id == guarantee_id, ParkingGuarantee.manager_id == manager_id ).first() if not guarantee: raise HTTPException(status_code=404, detail="Guarantee not found") db.delete(guarantee) db.commit() return {"message": "Guarantee removed"} # Exclusions @router.get("/{manager_id}/exclusions") def get_manager_exclusions(manager_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Get parking exclusions for a manager""" exclusions = db.query(ParkingExclusion).filter(ParkingExclusion.manager_id == manager_id).all() result = [] for e in exclusions: target_user = db.query(User).filter(User.id == e.user_id).first() result.append({ "id": e.id, "user_id": e.user_id, "user_name": target_user.name if target_user else None, "start_date": e.start_date, "end_date": e.end_date }) return result @router.post("/{manager_id}/exclusions") def add_manager_exclusion(manager_id: str, data: ExclusionCreate, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Add parking exclusion for a manager""" manager = db.query(User).filter(User.id == manager_id, User.role == "manager").first() if not manager: raise HTTPException(status_code=404, detail="Manager not found") if not db.query(User).filter(User.id == data.user_id).first(): raise HTTPException(status_code=404, detail="User not found") existing = db.query(ParkingExclusion).filter( ParkingExclusion.manager_id == manager_id, ParkingExclusion.user_id == data.user_id ).first() if existing: raise HTTPException(status_code=400, detail="User already has a parking exclusion") exclusion = ParkingExclusion( id=str(uuid.uuid4()), manager_id=manager_id, user_id=data.user_id, start_date=data.start_date, end_date=data.end_date, created_at=datetime.utcnow().isoformat() ) db.add(exclusion) db.commit() return {"id": exclusion.id, "message": "Exclusion added"} @router.delete("/{manager_id}/exclusions/{exclusion_id}") def remove_manager_exclusion(manager_id: str, exclusion_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)): """Remove parking exclusion for a manager""" exclusion = db.query(ParkingExclusion).filter( ParkingExclusion.id == exclusion_id, ParkingExclusion.manager_id == manager_id ).first() if not exclusion: raise HTTPException(status_code=404, detail="Exclusion not found") db.delete(exclusion) db.commit() return {"message": "Exclusion removed"}