Add duty_time_range dictionary with day/night presets, parse typed ranges on ensure, and replace time pickers with allow-create select in the schedule dialog. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
4.7 KiB
Python
143 lines
4.7 KiB
Python
"""值班排班 · 管理端 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))
|