feat(duty): register admin and public duty APIs with encrypt exclude

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-14 17:12:40 +08:00
1 parent 49cad98c84
commit 36fc7f286c
4 files changed
+151 -1

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
"""值班排班 · 管理端 API"""
from __future__ import annotations
from datetime import date, 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, 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.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))
+37
View File
@@ -0,0 +1,37 @@
"""值班排班 · 公开抢班 API(免登录)"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.response import error_response, success_response
from app.schemas.duty import DutyGrabBody
from app.services import duty_schedule as duty_svc
from app.services.duty_schedule import DutyBizError
router = APIRouter(prefix="/public/duty", tags=["值班排班-公开"])
@router.get("/{token}")
def public_page(token: str, db: Session = Depends(get_db)):
try:
data = duty_svc.get_public_page(db, token)
return success_response(data)
except DutyBizError as exc:
msg = str(exc)
if "无效" in msg:
raise HTTPException(status_code=404, detail=msg)
return error_response(400, msg)
@router.post("/{token}/grab")
def public_grab(token: str, body: DutyGrabBody, db: Session = Depends(get_db)):
try:
data = duty_svc.grab(db, token, body.shiftType, body.personName)
return success_response(data, message="报名成功")
except DutyBizError as exc:
msg = str(exc)
if "无效" in msg:
raise HTTPException(status_code=404, detail=msg)
return error_response(400, msg)
+2 -1
View File
@@ -178,7 +178,8 @@ class Settings(BaseSettings):
"/api/public/pl/inbound,"
"/api/public/pl/outbound,"
"/api/public/db/inventory,"
"/api/public/db/vehicle-plates"
"/api/public/db/vehicle-plates,"
"/api/public/duty"
)
class Config:
+5
View File
@@ -18,6 +18,7 @@ from app.models import models # noqa: F401
import app.models.bx_models # noqa: F401
import app.models.db_models # noqa: F401
import app.models.pl_models # noqa: F401
import app.models.duty_models # noqa: F401
from app.api import (
public_router,
menu_router,
@@ -38,6 +39,8 @@ from app.api.pl.public_io_order import (
)
from app.api.db.public_inventory import router as public_db_inventory_router
from app.api.db.public_vehicles import router as public_db_vehicles_router
from app.api.duty import router as duty_router
from app.api.public_duty import router as public_duty_router
@asynccontextmanager
@@ -161,6 +164,7 @@ app.include_router(public_pl_inbound_router, prefix=settings.API_V1_PREFIX)
app.include_router(public_pl_outbound_router, prefix=settings.API_V1_PREFIX)
app.include_router(public_db_inventory_router, prefix=settings.API_V1_PREFIX)
app.include_router(public_db_vehicles_router, prefix=settings.API_V1_PREFIX)
app.include_router(public_duty_router, prefix=settings.API_V1_PREFIX)
app.include_router(menu_router, prefix=settings.API_V1_PREFIX)
app.include_router(admin_router, prefix=settings.API_V1_PREFIX)
app.include_router(workflow_router, prefix=settings.API_V1_PREFIX)
@@ -169,6 +173,7 @@ app.include_router(crm_router, prefix=settings.API_V1_PREFIX)
app.include_router(bx_router, prefix=settings.API_V1_PREFIX)
app.include_router(db_router, prefix=settings.API_V1_PREFIX)
app.include_router(pl_router, prefix=settings.API_V1_PREFIX)
app.include_router(duty_router, prefix=settings.API_V1_PREFIX)
app.include_router(monitor_router, prefix=settings.API_V1_PREFIX)