Files
org-parking/app/routes/managers.py
Stefano Manfredi ce9e2fdf2a fix landing page
2025-12-02 23:18:43 +00:00

391 lines
14 KiB
Python

"""
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
from sqlalchemy import func
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
from utils.helpers import generate_uuid
from app import config
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()
# Batch query to get managed user counts for all managers at once
manager_ids = [m.id for m in managers]
if manager_ids:
counts = db.query(User.manager_id, func.count(User.id)).filter(
User.manager_id.in_(manager_ids)
).group_by(User.manager_id).all()
managed_counts = {manager_id: count for manager_id, count in counts}
else:
managed_counts = {}
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_counts.get(manager.id, 0)
}
for manager in managers
]
@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=generate_uuid(),
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=generate_uuid(),
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()
# Batch query to get all user names at once
user_ids = [g.user_id for g in guarantees]
if user_ids:
users = db.query(User).filter(User.id.in_(user_ids)).all()
user_lookup = {u.id: u.name for u in users}
else:
user_lookup = {}
return [
{
"id": g.id,
"user_id": g.user_id,
"user_name": user_lookup.get(g.user_id),
"start_date": g.start_date,
"end_date": g.end_date
}
for g in guarantees
]
@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=generate_uuid(),
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()
# Batch query to get all user names at once
user_ids = [e.user_id for e in exclusions]
if user_ids:
users = db.query(User).filter(User.id.in_(user_ids)).all()
user_lookup = {u.id: u.name for u in users}
else:
user_lookup = {}
return [
{
"id": e.id,
"user_id": e.user_id,
"user_name": user_lookup.get(e.user_id),
"start_date": e.start_date,
"end_date": e.end_date
}
for e in exclusions
]
@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=generate_uuid(),
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"}