Key changes: - Removed office-centric model (deleted offices.py, office-rules) - Renamed to team-rules, managers are part of their own team - Team calendar visible to all (read-only for employees) - Admins can have a manager assigned
366 lines
14 KiB
Python
366 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
|
|
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"}
|