from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import select from sqlalchemy.orm import Session, selectinload from app.database import get_db from app.deps import require_roles from app.models import MoldLockFeedback, Personnel, Role, SessionStatus, WorkSession, WorkSessionDevice from app.schemas import MoldLockFeedbackCreate, MoldLockFeedbackOut, MonitorLockOut, PageResponse from app.services.attendance_points import accessible_point_names from app.services.common import paginate from app.services.mold_locks import ( FEEDBACK_STATUS_PENDING, active_locks_for_points, current_unreleased_device, feedback_out, lock_out, release_lock, ) from app.timezone import now router = APIRouter(prefix="/api/monitor", tags=["monitor"]) def _lock_query(): return ( select(WorkSessionDevice) .options( selectinload(WorkSessionDevice.session).selectinload(WorkSession.devices), selectinload(WorkSessionDevice.session).selectinload(WorkSession.employee), ) ) def _feedback_query(): return select(MoldLockFeedback).options( selectinload(MoldLockFeedback.reporter), selectinload(MoldLockFeedback.occupied_person), selectinload(MoldLockFeedback.handler), selectinload(MoldLockFeedback.session_device), ) def _admin_point_names(db: Session, user: Personnel) -> list[str]: return accessible_point_names(db, user) def _get_admin_lock(db: Session, user: Personnel, lock_id: int) -> WorkSessionDevice: lock = db.scalar(_lock_query().where(WorkSessionDevice.id == lock_id)) if lock is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="占用记录不存在") if lock.attendance_point_name not in _admin_point_names(db, user): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无该考勤点监控权限") return lock def _get_admin_feedback(db: Session, user: Personnel, feedback_id: int) -> MoldLockFeedback: feedback = db.scalar(_feedback_query().where(MoldLockFeedback.id == feedback_id)) if feedback is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="反馈不存在") if feedback.attendance_point_name not in _admin_point_names(db, user): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无该考勤点监控权限") return feedback def _is_active_current_lock(lock: WorkSessionDevice) -> bool: if ( lock.session is None or lock.session.status not in [SessionStatus.active, SessionStatus.reporting] or lock.released_at is not None ): return False current = current_unreleased_device(lock.session) return bool(current and current.id == lock.id) @router.get("/locks", response_model=list[MonitorLockOut]) def list_active_locks( user: Personnel = Depends(require_roles(Role.admin)), db: Session = Depends(get_db), ) -> list[MonitorLockOut]: return [lock_out(db, lock) for lock in active_locks_for_points(db, _admin_point_names(db, user))] @router.post("/locks/{lock_id}/release") def force_release_lock( lock_id: int, user: Personnel = Depends(require_roles(Role.admin)), db: Session = Depends(get_db), ) -> dict[str, bool]: _get_admin_lock(db, user, lock_id) try: release_lock(db, lock_id, user.phone, "管理员强制解除占用") except ValueError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc db.commit() return {"ok": True} @router.get("/feedbacks", response_model=PageResponse) def list_feedbacks( box: str = Query("unread", pattern="^(unread|read)$"), page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=10), user: Personnel = Depends(require_roles(Role.admin)), db: Session = Depends(get_db), ) -> PageResponse: point_names = _admin_point_names(db, user) query = _feedback_query().where(MoldLockFeedback.attendance_point_name.in_(point_names)) if box == "read": query = query.where(MoldLockFeedback.read_at.is_not(None)).order_by( MoldLockFeedback.read_at.desc(), MoldLockFeedback.created_at.desc(), ) else: query = query.where(MoldLockFeedback.read_at.is_(None)).order_by(MoldLockFeedback.created_at.desc()) result = paginate(db, query, page, page_size) return PageResponse(**{**result, "rows": [feedback_out(db, row) for row in result["rows"]]}) @router.post("/feedbacks", response_model=MoldLockFeedbackOut) def create_feedback( payload: MoldLockFeedbackCreate, user: Personnel = Depends(require_roles(Role.worker)), db: Session = Depends(get_db), ) -> MoldLockFeedbackOut: lock = db.scalar(_lock_query().where(WorkSessionDevice.id == payload.lock_id)) if lock is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="占用记录不存在") if lock.attendance_point_name not in accessible_point_names(db, user): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无该考勤点反馈权限") if not _is_active_current_lock(lock): raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="该模具占用已解除") if lock.session.employee_phone == user.phone: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不能反馈自己的占用") existing = db.scalar( _feedback_query().where( MoldLockFeedback.session_device_id == lock.id, MoldLockFeedback.reporter_phone == user.phone, MoldLockFeedback.status == FEEDBACK_STATUS_PENDING, ) ) if existing is not None: return feedback_out(db, existing) feedback = MoldLockFeedback( attendance_point_name=lock.attendance_point_name, mold_name=lock.device_no, process_name=lock.process_name, session_device_id=lock.id, reporter_phone=user.phone, occupied_phone=lock.session.employee_phone, status=FEEDBACK_STATUS_PENDING, ) db.add(feedback) db.commit() feedback = db.scalar(_feedback_query().where(MoldLockFeedback.id == feedback.id)) if feedback is None: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="反馈保存失败") return feedback_out(db, feedback) @router.post("/feedbacks/{feedback_id}/read", response_model=MoldLockFeedbackOut) def mark_feedback_read( feedback_id: int, user: Personnel = Depends(require_roles(Role.admin)), db: Session = Depends(get_db), ) -> MoldLockFeedbackOut: feedback = _get_admin_feedback(db, user, feedback_id) if feedback.read_at is None: feedback.read_at = now() db.commit() feedback = _get_admin_feedback(db, user, feedback_id) return feedback_out(db, feedback) @router.post("/feedbacks/{feedback_id}/release", response_model=MoldLockFeedbackOut) def release_feedback_lock( feedback_id: int, user: Personnel = Depends(require_roles(Role.admin)), db: Session = Depends(get_db), ) -> MoldLockFeedbackOut: feedback = _get_admin_feedback(db, user, feedback_id) if feedback.read_at is None: feedback.read_at = now() try: release_lock(db, feedback.session_device_id, user.phone, "管理员通过反馈解除占用") except ValueError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc db.commit() feedback = _get_admin_feedback(db, user, feedback_id) return feedback_out(db, feedback)