""" Manager Rules Routes Manager settings, closing days, guarantees, and exclusions Key concept: Managers own parking spots and set rules for all their managed offices. Rules are set at manager level, not office level. """ 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 ( Office, User, OfficeMembership, 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 offices and parking quota""" managers = db.query(User).filter(User.role == "manager").all() result = [] for manager in managers: memberships = db.query(OfficeMembership).filter(OfficeMembership.user_id == manager.id).all() office_ids = [m.office_id for m in memberships] offices = db.query(Office).filter(Office.id.in_(office_ids)).all() if office_ids else [] 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, "offices": [{"id": o.id, "name": o.name} for o in offices] }) 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 offices 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") memberships = db.query(OfficeMembership).filter(OfficeMembership.user_id == manager_id).all() office_ids = [m.office_id for m in memberships] offices = db.query(Office).filter(Office.id.in_(office_ids)).all() if office_ids else [] return { "id": manager.id, "name": manager.name, "email": manager.email, "parking_quota": manager.manager_parking_quota or 0, "spot_prefix": manager.manager_spot_prefix, "offices": [{"id": o.id, "name": o.name} for o in offices] } @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 from offices managed by this 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") memberships = db.query(OfficeMembership).filter(OfficeMembership.user_id == manager_id).all() managed_office_ids = [m.office_id for m in memberships] if not managed_office_ids: return [] users = db.query(User).filter(User.office_id.in_(managed_office_ids)).all() return [{"id": u.id, "name": u.name, "email": u.email, "role": u.role, "office_id": u.office_id} 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"}