Backend: - LOW-002: Add Query validation with max page size limits (100) - LOW-003: Replace magic strings with TaskStatus.is_done flag - LOW-004: Add 'creation' trigger type validation - Add action_executor.py with UpdateFieldAction and AutoAssignAction Frontend: - LOW-005: Replace TypeScript 'any' with 'unknown' + type guards - LOW-006: Add ConfirmModal component with A11Y support - LOW-007: Add ToastContext for user feedback notifications - LOW-009: Add Skeleton components (17 loading states replaced) - LOW-010: Setup Vitest with 21 tests for ConfirmModal and Skeleton Components updated: - App.tsx, ProtectedRoute.tsx, Spaces.tsx, Projects.tsx, Tasks.tsx - ProjectSettings.tsx, AuditPage.tsx, WorkloadPage.tsx, ProjectHealthPage.tsx - Comments.tsx, AttachmentList.tsx, TriggerList.tsx, TaskDetailModal.tsx - NotificationBell.tsx, BlockerDialog.tsx, CalendarView.tsx, WorkloadUserDetail.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
153 lines
4.9 KiB
Python
153 lines
4.9 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import List
|
|
|
|
from app.core.database import get_db
|
|
from app.models.department import Department
|
|
from app.models.user import User
|
|
from app.schemas.department import DepartmentCreate, DepartmentUpdate, DepartmentResponse
|
|
from app.middleware.auth import require_permission, require_system_admin
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=List[DepartmentResponse])
|
|
async def list_departments(
|
|
skip: int = Query(0, ge=0, description="Number of departments to skip"),
|
|
limit: int = Query(100, ge=1, le=200, description="Max departments to return"),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.read")),
|
|
):
|
|
"""
|
|
List all departments.
|
|
"""
|
|
departments = db.query(Department).offset(skip).limit(limit).all()
|
|
return departments
|
|
|
|
|
|
@router.get("/{department_id}", response_model=DepartmentResponse)
|
|
async def get_department(
|
|
department_id: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.read")),
|
|
):
|
|
"""
|
|
Get a specific department by ID.
|
|
"""
|
|
department = db.query(Department).filter(Department.id == department_id).first()
|
|
if not department:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Department not found",
|
|
)
|
|
return department
|
|
|
|
|
|
@router.post("", response_model=DepartmentResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_department(
|
|
department_data: DepartmentCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_system_admin),
|
|
):
|
|
"""
|
|
Create a new department. Requires system admin.
|
|
"""
|
|
# Check if parent exists if specified
|
|
if department_data.parent_id:
|
|
parent = db.query(Department).filter(
|
|
Department.id == department_data.parent_id
|
|
).first()
|
|
if not parent:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Parent department not found",
|
|
)
|
|
|
|
department = Department(**department_data.model_dump())
|
|
db.add(department)
|
|
db.commit()
|
|
db.refresh(department)
|
|
return department
|
|
|
|
|
|
@router.patch("/{department_id}", response_model=DepartmentResponse)
|
|
async def update_department(
|
|
department_id: str,
|
|
department_update: DepartmentUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_system_admin),
|
|
):
|
|
"""
|
|
Update a department. Requires system admin.
|
|
"""
|
|
department = db.query(Department).filter(Department.id == department_id).first()
|
|
if not department:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Department not found",
|
|
)
|
|
|
|
# Check if new parent exists if specified
|
|
update_data = department_update.model_dump(exclude_unset=True)
|
|
if "parent_id" in update_data and update_data["parent_id"]:
|
|
# Prevent circular reference
|
|
if update_data["parent_id"] == department_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Department cannot be its own parent",
|
|
)
|
|
|
|
parent = db.query(Department).filter(
|
|
Department.id == update_data["parent_id"]
|
|
).first()
|
|
if not parent:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Parent department not found",
|
|
)
|
|
|
|
for field, value in update_data.items():
|
|
setattr(department, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(department)
|
|
return department
|
|
|
|
|
|
@router.delete("/{department_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_department(
|
|
department_id: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_system_admin),
|
|
):
|
|
"""
|
|
Delete a department. Requires system admin.
|
|
"""
|
|
department = db.query(Department).filter(Department.id == department_id).first()
|
|
if not department:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Department not found",
|
|
)
|
|
|
|
# Check if department has users
|
|
user_count = db.query(User).filter(User.department_id == department_id).count()
|
|
if user_count > 0:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Cannot delete department with {user_count} users",
|
|
)
|
|
|
|
# Check if department has children
|
|
child_count = db.query(Department).filter(
|
|
Department.parent_id == department_id
|
|
).count()
|
|
if child_count > 0:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Cannot delete department with {child_count} child departments",
|
|
)
|
|
|
|
db.delete(department)
|
|
db.commit()
|