Refactor to manager-centric model, add team calendar for all users

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
This commit is contained in:
Stefano Manfredi
2025-12-02 13:30:04 +00:00
parent 2ad8ba3424
commit 7168fa4b72
30 changed files with 1016 additions and 910 deletions

View File

@@ -2,8 +2,8 @@
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.
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
@@ -13,7 +13,7 @@ import uuid
from database.connection import get_db
from database.models import (
Office, User, OfficeMembership,
User,
ManagerClosingDay, ManagerWeeklyClosingDay,
ParkingGuarantee, ParkingExclusion
)
@@ -52,14 +52,12 @@ class ManagerSettingsUpdate(BaseModel):
# 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"""
"""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:
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 []
managed_user_count = db.query(User).filter(User.manager_id == manager.id).count()
result.append({
"id": manager.id,
@@ -67,7 +65,7 @@ def list_managers(db: Session = Depends(get_db), user=Depends(require_manager_or
"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]
"managed_user_count": managed_user_count
})
return result
@@ -75,14 +73,12 @@ def list_managers(db: Session = Depends(get_db), user=Depends(require_manager_or
@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"""
"""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")
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 []
managed_user_count = db.query(User).filter(User.manager_id == manager_id).count()
return {
"id": manager.id,
@@ -90,7 +86,7 @@ def get_manager_details(manager_id: str, db: Session = Depends(get_db), user=Dep
"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]
"managed_user_count": managed_user_count
}
@@ -131,19 +127,16 @@ def update_manager_settings(manager_id: str, data: ManagerSettingsUpdate, db: Se
@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"""
"""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")
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]
# 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