"""值班排班 · 管理端 API""" from __future__ import annotations from datetime import datetime from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.core.database import get_db from app.core.menu_guard import require_admin_or_super_admin from app.core.response import error_response, success_response from app.crud import user as user_crud from app.schemas.duty import DutyDaySaveBody, DutyDictEnsureBody, DutyLinkBody from app.services import duty_schedule as duty_svc from app.services.duty_schedule import DutyBizError router = APIRouter(prefix="/web/admin/duty", tags=["值班排班"]) def _enrich_assign_names(db: Session, body: DutyDaySaveBody) -> DutyDaySaveBody: """指定用户时补全 personName(用 nick_name)。""" for slot in body.slots: for item in slot.assignments or []: if item.source != "assign": continue uid = (item.userId or "").strip() if not uid: continue if (item.personName or "").strip(): continue u = user_crud.get_user(db, uid) if u: item.personName = (u.nick_name or "").strip() or uid return body @router.get("") def month_summary( year: int = Query(..., ge=2000, le=2100), month: int = Query(..., ge=1, le=12), _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" data = duty_svc.get_month_summary(db, tenant_no, year, month) return success_response(data) @router.get("/day") def day_detail( dutyDate: str = Query(..., description="YYYY-MM-DD"), _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" try: d = datetime.strptime(dutyDate.strip(), "%Y-%m-%d").date() except ValueError: return error_response(400, "日期格式无效") return success_response(duty_svc.get_day_detail(db, tenant_no, d)) @router.get("/users/options") def duty_user_options( _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): """值班可安排人员:仅「值班人员」角色下的启用用户。""" tenant_no = _user.get("tenant_no", "") or "" rows = duty_svc.list_duty_staff_users(db, tenant_no) out = [{"label": r["label"], "value": r["id"]} for r in rows] return success_response(out) @router.post("/dict/ensure") def ensure_duty_dict( body: DutyDictEnsureBody, _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" user_id = _user.get("user_id") or _user.get("id", "") or "" try: data = duty_svc.ensure_dict_data( db, tenant_no=tenant_no, dict_type=body.dictType, label=body.label, user_id=user_id, preferred_value=body.preferredValue, ) db.commit() return success_response(data, message="已保存") except DutyBizError as exc: return error_response(400, str(exc)) @router.post("/day") def save_day( body: DutyDaySaveBody, _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" try: body = _enrich_assign_names(db, body) data = duty_svc.save_day(db, tenant_no, _user, body) return success_response(data, message="保存成功") except DutyBizError as exc: return error_response(400, str(exc)) @router.post("/day/{day_id}/link") def set_link( day_id: str, body: DutyLinkBody, _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" user_id = _user.get("user_id") or _user.get("id", "") try: data = duty_svc.set_link_status(db, tenant_no, day_id, body.linkStatus, operator_id=user_id) return success_response(data, message="已更新链接状态") except DutyBizError as exc: return error_response(400, str(exc)) @router.delete("/assignments/{assignment_id}") def delete_assignment( assignment_id: str, _user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _user.get("tenant_no", "") or "" user_id = _user.get("user_id") or _user.get("id", "") try: duty_svc.soft_delete_assignment(db, tenant_no, assignment_id, operator_id=user_id) return success_response(None, message="已删除") except DutyBizError as exc: return error_response(400, str(exc))