""" 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