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:
@@ -10,8 +10,8 @@ from sqlalchemy.orm import Session
|
||||
import uuid
|
||||
|
||||
from database.connection import get_db
|
||||
from database.models import UserPresence, User, DailyParkingAssignment, OfficeMembership, Office
|
||||
from utils.auth_middleware import get_current_user, require_manager_or_admin, check_manager_access_to_user
|
||||
from database.models import UserPresence, User, DailyParkingAssignment
|
||||
from utils.auth_middleware import get_current_user, require_manager_or_admin
|
||||
from services.parking import handle_presence_change, get_spot_display_name
|
||||
|
||||
router = APIRouter(prefix="/api/presence", tags=["presence"])
|
||||
@@ -70,6 +70,20 @@ def parse_date(date_str: str) -> datetime:
|
||||
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
|
||||
|
||||
|
||||
def check_manager_access(current_user: User, target_user: User, db: Session):
|
||||
"""Check if current_user has access to target_user"""
|
||||
if current_user.role == "admin":
|
||||
return True
|
||||
|
||||
if current_user.role == "manager":
|
||||
# Manager can access users they manage
|
||||
if target_user.manager_id == current_user.id:
|
||||
return True
|
||||
raise HTTPException(status_code=403, detail="User is not managed by you")
|
||||
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
|
||||
|
||||
def _mark_presence_for_user(
|
||||
user_id: str,
|
||||
date: str,
|
||||
@@ -111,12 +125,18 @@ def _mark_presence_for_user(
|
||||
db.refresh(presence)
|
||||
|
||||
# Handle parking assignment
|
||||
if old_status != status and target_user.office_id:
|
||||
# Use manager_id if user has one, or user's own id if they are a manager
|
||||
parking_manager_id = target_user.manager_id
|
||||
if not parking_manager_id and target_user.role == "manager":
|
||||
# Manager is part of their own team for parking purposes
|
||||
parking_manager_id = target_user.id
|
||||
|
||||
if old_status != status and parking_manager_id:
|
||||
try:
|
||||
handle_presence_change(
|
||||
user_id, date,
|
||||
old_status or "absent", status,
|
||||
target_user.office_id, db
|
||||
parking_manager_id, db
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Warning: Parking handler failed: {e}")
|
||||
@@ -177,12 +197,17 @@ def _bulk_mark_presence(
|
||||
results.append(presence)
|
||||
|
||||
# Handle parking for each date
|
||||
if old_status != status and target_user.office_id:
|
||||
# Use manager_id if user has one, or user's own id if they are a manager
|
||||
parking_manager_id = target_user.manager_id
|
||||
if not parking_manager_id and target_user.role == "manager":
|
||||
parking_manager_id = target_user.id
|
||||
|
||||
if old_status != status and parking_manager_id:
|
||||
try:
|
||||
handle_presence_change(
|
||||
user_id, date_str,
|
||||
old_status or "absent", status,
|
||||
target_user.office_id, db
|
||||
parking_manager_id, db
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -216,12 +241,17 @@ def _delete_presence(
|
||||
db.delete(presence)
|
||||
db.commit()
|
||||
|
||||
if target_user.office_id:
|
||||
# Use manager_id if user has one, or user's own id if they are a manager
|
||||
parking_manager_id = target_user.manager_id
|
||||
if not parking_manager_id and target_user.role == "manager":
|
||||
parking_manager_id = target_user.id
|
||||
|
||||
if parking_manager_id:
|
||||
try:
|
||||
handle_presence_change(
|
||||
user_id, date,
|
||||
old_status, "absent",
|
||||
target_user.office_id, db
|
||||
parking_manager_id, db
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -274,7 +304,7 @@ def admin_mark_presence(data: AdminPresenceMarkRequest, db: Session = Depends(ge
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
check_manager_access_to_user(current_user, target_user, db)
|
||||
check_manager_access(current_user, target_user, db)
|
||||
return _mark_presence_for_user(data.user_id, data.date, data.status, db, target_user)
|
||||
|
||||
|
||||
@@ -285,7 +315,7 @@ def admin_mark_bulk_presence(data: AdminBulkPresenceRequest, db: Session = Depen
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
check_manager_access_to_user(current_user, target_user, db)
|
||||
check_manager_access(current_user, target_user, db)
|
||||
return _bulk_mark_presence(
|
||||
data.user_id, data.start_date, data.end_date,
|
||||
data.status, data.days, db, target_user
|
||||
@@ -299,34 +329,42 @@ def admin_delete_presence(user_id: str, date: str, db: Session = Depends(get_db)
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
check_manager_access_to_user(current_user, target_user, db)
|
||||
check_manager_access(current_user, target_user, db)
|
||||
return _delete_presence(user_id, date, db, target_user)
|
||||
|
||||
|
||||
@router.get("/team")
|
||||
def get_team_presences(start_date: str, end_date: str, manager_id: str = None, db: Session = Depends(get_db), current_user=Depends(require_manager_or_admin)):
|
||||
"""Get team presences with parking info for managers/admins, filtered by manager"""
|
||||
def get_team_presences(start_date: str, end_date: str, manager_id: str = None, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
||||
"""Get team presences with parking info, filtered by manager.
|
||||
- Admins can see all teams
|
||||
- Managers see their own team
|
||||
- Employees can only see their own team (read-only view)
|
||||
"""
|
||||
parse_date(start_date)
|
||||
parse_date(end_date)
|
||||
|
||||
# Get users based on permissions and manager filter
|
||||
if manager_id:
|
||||
# Filter by specific manager's offices
|
||||
managed_office_ids = [m.office_id for m in db.query(OfficeMembership).filter(
|
||||
OfficeMembership.user_id == manager_id
|
||||
).all()]
|
||||
if not managed_office_ids:
|
||||
return []
|
||||
users = db.query(User).filter(User.office_id.in_(managed_office_ids)).all()
|
||||
# Note: Manager is part of their own team (for parking assignment purposes)
|
||||
if current_user.role == "employee":
|
||||
# Employees can only see their own team (users with same manager_id + the manager)
|
||||
if not current_user.manager_id:
|
||||
return [] # No manager assigned, no team to show
|
||||
users = db.query(User).filter(
|
||||
(User.manager_id == current_user.manager_id) | (User.id == current_user.manager_id)
|
||||
).all()
|
||||
elif manager_id:
|
||||
# Filter by specific manager (for admins/managers) - include the manager themselves
|
||||
users = db.query(User).filter(
|
||||
(User.manager_id == manager_id) | (User.id == manager_id)
|
||||
).all()
|
||||
elif current_user.role == "admin":
|
||||
# Admin sees all users
|
||||
users = db.query(User).all()
|
||||
else:
|
||||
# Manager sees only users in their managed offices
|
||||
managed_ids = [m.office_id for m in current_user.managed_offices]
|
||||
if not managed_ids:
|
||||
return []
|
||||
users = db.query(User).filter(User.office_id.in_(managed_ids)).all()
|
||||
# Manager sees their team + themselves
|
||||
users = db.query(User).filter(
|
||||
(User.manager_id == current_user.id) | (User.id == current_user.id)
|
||||
).all()
|
||||
|
||||
# Batch query presences and parking for all users
|
||||
user_ids = [u.id for u in users]
|
||||
@@ -358,30 +396,21 @@ def get_team_presences(start_date: str, end_date: str, manager_id: str = None, d
|
||||
"spot_display_name": spot_display_name
|
||||
})
|
||||
|
||||
# Build office and managed offices lookups
|
||||
offices_lookup = {o.id: o.name for o in db.query(Office).all()}
|
||||
managed_offices_lookup = {}
|
||||
for m in db.query(OfficeMembership).all():
|
||||
if m.user_id not in managed_offices_lookup:
|
||||
managed_offices_lookup[m.user_id] = []
|
||||
managed_offices_lookup[m.user_id].append(offices_lookup.get(m.office_id, m.office_id))
|
||||
# Build manager lookup for display
|
||||
manager_ids = list(set(u.manager_id for u in users if u.manager_id))
|
||||
managers = db.query(User).filter(User.id.in_(manager_ids)).all() if manager_ids else []
|
||||
manager_lookup = {m.id: m.name for m in managers}
|
||||
|
||||
# Build response
|
||||
result = []
|
||||
for user in users:
|
||||
user_presences = [p for p in presences if p.user_id == user.id]
|
||||
|
||||
# For managers, show managed offices; for others, show their office
|
||||
if user.role == "manager" and user.id in managed_offices_lookup:
|
||||
office_display = ", ".join(managed_offices_lookup[user.id])
|
||||
else:
|
||||
office_display = offices_lookup.get(user.office_id)
|
||||
|
||||
result.append({
|
||||
"id": user.id,
|
||||
"name": user.name,
|
||||
"office_id": user.office_id,
|
||||
"office_name": office_display,
|
||||
"manager_id": user.manager_id,
|
||||
"manager_name": manager_lookup.get(user.manager_id),
|
||||
"presences": [{"date": p.date, "status": p.status} for p in user_presences],
|
||||
"parking_dates": parking_lookup.get(user.id, []),
|
||||
"parking_info": parking_info_lookup.get(user.id, [])
|
||||
@@ -397,7 +426,7 @@ def get_user_presences(user_id: str, start_date: str = None, end_date: str = Non
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
check_manager_access_to_user(current_user, target_user, db)
|
||||
check_manager_access(current_user, target_user, db)
|
||||
|
||||
query = db.query(UserPresence).filter(UserPresence.user_id == user_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user