Files
org-parking/app/routes/offices.py
Stefano Manfredi c74a0ed350 Initial commit: Parking Manager
Features:
- Manager-centric parking spot management
- Fair assignment algorithm (parking/presence ratio)
- Presence tracking calendar
- Closing days (specific & weekly recurring)
- Guarantees and exclusions
- Authelia/LLDAP integration for SSO

Stack:
- FastAPI backend
- SQLite database
- Vanilla JS frontend
- Docker deployment
2025-11-26 23:37:50 +00:00

198 lines
6.8 KiB
Python

"""
Office Management Routes
Admin CRUD for offices and manager-office memberships
"""
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
from utils.auth_middleware import require_admin, require_manager_or_admin, get_current_user
router = APIRouter(prefix="/api/offices", tags=["offices"])
# Request/Response Models
class OfficeCreate(BaseModel):
name: str
location: str | None = None
class OfficeUpdate(BaseModel):
name: str | None = None
location: str | None = None
class OfficeResponse(BaseModel):
id: str
name: str
location: str | None = None
created_at: str | None
class Config:
from_attributes = True
class AddManagerRequest(BaseModel):
user_id: str
# Office CRUD Routes
@router.get("")
def list_offices(db: Session = Depends(get_db), user=Depends(get_current_user)):
"""List all offices with counts"""
offices = db.query(Office).all()
result = []
for office in offices:
manager_count = db.query(OfficeMembership).filter(OfficeMembership.office_id == office.id).count()
employee_count = db.query(User).filter(User.office_id == office.id).count()
result.append({
"id": office.id,
"name": office.name,
"location": office.location,
"created_at": office.created_at,
"manager_count": manager_count,
"employee_count": employee_count
})
return result
@router.get("/{office_id}", response_model=OfficeResponse)
def get_office(office_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)):
"""Get office by ID"""
office = db.query(Office).filter(Office.id == office_id).first()
if not office:
raise HTTPException(status_code=404, detail="Office not found")
return office
@router.post("", response_model=OfficeResponse)
def create_office(data: OfficeCreate, db: Session = Depends(get_db), user=Depends(require_admin)):
"""Create new office (admin only)"""
office = Office(
id=str(uuid.uuid4()),
name=data.name,
location=data.location,
created_at=datetime.utcnow().isoformat()
)
db.add(office)
db.commit()
db.refresh(office)
return office
@router.put("/{office_id}", response_model=OfficeResponse)
def update_office(office_id: str, data: OfficeUpdate, db: Session = Depends(get_db), user=Depends(require_admin)):
"""Update office (admin only)"""
office = db.query(Office).filter(Office.id == office_id).first()
if not office:
raise HTTPException(status_code=404, detail="Office not found")
if data.name is not None:
office.name = data.name
if data.location is not None:
office.location = data.location
office.updated_at = datetime.utcnow().isoformat()
db.commit()
db.refresh(office)
return office
@router.delete("/{office_id}")
def delete_office(office_id: str, db: Session = Depends(get_db), user=Depends(require_admin)):
"""Delete office (admin only)"""
office = db.query(Office).filter(Office.id == office_id).first()
if not office:
raise HTTPException(status_code=404, detail="Office not found")
if db.query(User).filter(User.office_id == office_id).count() > 0:
raise HTTPException(status_code=400, detail="Cannot delete office with assigned users")
db.delete(office)
db.commit()
return {"message": "Office deleted"}
# Office membership routes (linking managers to offices)
@router.get("/{office_id}/managers")
def get_office_managers(office_id: str, db: Session = Depends(get_db), user=Depends(require_manager_or_admin)):
"""Get managers for an office"""
memberships = db.query(OfficeMembership).filter(OfficeMembership.office_id == office_id).all()
manager_ids = [m.user_id for m in memberships]
managers = db.query(User).filter(User.id.in_(manager_ids)).all()
return [{"id": m.id, "name": m.name, "email": m.email} for m in managers]
@router.post("/{office_id}/managers")
def add_office_manager(office_id: str, data: AddManagerRequest, db: Session = Depends(get_db), user=Depends(require_admin)):
"""Add manager to office (admin only)"""
if not db.query(Office).filter(Office.id == office_id).first():
raise HTTPException(status_code=404, detail="Office not found")
manager = db.query(User).filter(User.id == data.user_id).first()
if not manager:
raise HTTPException(status_code=404, detail="User not found")
if manager.role != "manager":
raise HTTPException(status_code=400, detail="User must have manager role")
existing = db.query(OfficeMembership).filter(
OfficeMembership.office_id == office_id,
OfficeMembership.user_id == data.user_id
).first()
if existing:
raise HTTPException(status_code=400, detail="Manager already assigned to office")
membership = OfficeMembership(
id=str(uuid.uuid4()),
office_id=office_id,
user_id=data.user_id,
created_at=datetime.utcnow().isoformat()
)
db.add(membership)
db.commit()
return {"message": "Manager added to office"}
@router.delete("/{office_id}/managers/{manager_id}")
def remove_office_manager(office_id: str, manager_id: str, db: Session = Depends(get_db), user=Depends(require_admin)):
"""Remove manager from office (admin only)"""
membership = db.query(OfficeMembership).filter(
OfficeMembership.office_id == office_id,
OfficeMembership.user_id == manager_id
).first()
if not membership:
raise HTTPException(status_code=404, detail="Manager not assigned to office")
db.delete(membership)
db.commit()
return {"message": "Manager removed from office"}
# Legacy redirect for /api/offices/managers/list -> /api/managers
@router.get("/managers/list")
def list_managers_legacy(db: Session = Depends(get_db), user=Depends(require_manager_or_admin)):
"""Get all managers with their managed offices and parking quota (legacy endpoint)"""
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