duty.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """值班排班 · 管理端 API"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Optional
  5. from fastapi import APIRouter, Depends, Query
  6. from sqlalchemy.orm import Session
  7. from app.core.database import get_db
  8. from app.core.menu_guard import require_admin_or_super_admin
  9. from app.core.response import error_response, success_response
  10. from app.crud import user as user_crud
  11. from app.schemas.duty import DutyDaySaveBody, DutyDictEnsureBody, DutyLinkBody
  12. from app.services import duty_schedule as duty_svc
  13. from app.services.duty_schedule import DutyBizError
  14. router = APIRouter(prefix="/web/admin/duty", tags=["值班排班"])
  15. def _enrich_assign_names(db: Session, body: DutyDaySaveBody) -> DutyDaySaveBody:
  16. """指定用户时补全 personName(用 nick_name)。"""
  17. for slot in body.slots:
  18. for item in slot.assignments or []:
  19. if item.source != "assign":
  20. continue
  21. uid = (item.userId or "").strip()
  22. if not uid:
  23. continue
  24. if (item.personName or "").strip():
  25. continue
  26. u = user_crud.get_user(db, uid)
  27. if u:
  28. item.personName = (u.nick_name or "").strip() or uid
  29. return body
  30. @router.get("")
  31. def month_summary(
  32. year: int = Query(..., ge=2000, le=2100),
  33. month: int = Query(..., ge=1, le=12),
  34. _user: dict = Depends(require_admin_or_super_admin),
  35. db: Session = Depends(get_db),
  36. ):
  37. tenant_no = _user.get("tenant_no", "") or ""
  38. data = duty_svc.get_month_summary(db, tenant_no, year, month)
  39. return success_response(data)
  40. @router.get("/day")
  41. def day_detail(
  42. dutyDate: str = Query(..., description="YYYY-MM-DD"),
  43. _user: dict = Depends(require_admin_or_super_admin),
  44. db: Session = Depends(get_db),
  45. ):
  46. tenant_no = _user.get("tenant_no", "") or ""
  47. try:
  48. d = datetime.strptime(dutyDate.strip(), "%Y-%m-%d").date()
  49. except ValueError:
  50. return error_response(400, "日期格式无效")
  51. return success_response(duty_svc.get_day_detail(db, tenant_no, d))
  52. @router.get("/users/options")
  53. def duty_user_options(
  54. _user: dict = Depends(require_admin_or_super_admin),
  55. db: Session = Depends(get_db),
  56. ):
  57. """值班可安排人员:仅「值班人员」角色下的启用用户。"""
  58. tenant_no = _user.get("tenant_no", "") or ""
  59. rows = duty_svc.list_duty_staff_users(db, tenant_no)
  60. out = [{"label": r["label"], "value": r["id"]} for r in rows]
  61. return success_response(out)
  62. @router.post("/dict/ensure")
  63. def ensure_duty_dict(
  64. body: DutyDictEnsureBody,
  65. _user: dict = Depends(require_admin_or_super_admin),
  66. db: Session = Depends(get_db),
  67. ):
  68. tenant_no = _user.get("tenant_no", "") or ""
  69. user_id = _user.get("user_id") or _user.get("id", "") or ""
  70. try:
  71. data = duty_svc.ensure_dict_data(
  72. db,
  73. tenant_no=tenant_no,
  74. dict_type=body.dictType,
  75. label=body.label,
  76. user_id=user_id,
  77. preferred_value=body.preferredValue,
  78. )
  79. db.commit()
  80. return success_response(data, message="已保存")
  81. except DutyBizError as exc:
  82. return error_response(400, str(exc))
  83. @router.post("/day")
  84. def save_day(
  85. body: DutyDaySaveBody,
  86. _user: dict = Depends(require_admin_or_super_admin),
  87. db: Session = Depends(get_db),
  88. ):
  89. tenant_no = _user.get("tenant_no", "") or ""
  90. try:
  91. body = _enrich_assign_names(db, body)
  92. data = duty_svc.save_day(db, tenant_no, _user, body)
  93. return success_response(data, message="保存成功")
  94. except DutyBizError as exc:
  95. return error_response(400, str(exc))
  96. @router.post("/day/{day_id}/link")
  97. def set_link(
  98. day_id: str,
  99. body: DutyLinkBody,
  100. _user: dict = Depends(require_admin_or_super_admin),
  101. db: Session = Depends(get_db),
  102. ):
  103. tenant_no = _user.get("tenant_no", "") or ""
  104. user_id = _user.get("user_id") or _user.get("id", "")
  105. try:
  106. data = duty_svc.set_link_status(db, tenant_no, day_id, body.linkStatus, operator_id=user_id)
  107. return success_response(data, message="已更新链接状态")
  108. except DutyBizError as exc:
  109. return error_response(400, str(exc))
  110. @router.delete("/assignments/{assignment_id}")
  111. def delete_assignment(
  112. assignment_id: str,
  113. _user: dict = Depends(require_admin_or_super_admin),
  114. db: Session = Depends(get_db),
  115. ):
  116. tenant_no = _user.get("tenant_no", "") or ""
  117. user_id = _user.get("user_id") or _user.get("id", "")
  118. try:
  119. duty_svc.soft_delete_assignment(db, tenant_no, assignment_id, operator_id=user_id)
  120. return success_response(None, message="已删除")
  121. except DutyBizError as exc:
  122. return error_response(400, str(exc))