Files
zkxw/backend/app/api/sample_storage_cert_qc.py
T
zlwuandCursor 529f36eac6 feat(cell-storage): 储存证质检字段级质检/生产权限
按方案 A 增加租户级 ACL(字段+人员),详情返回可编辑字段,保存改为服务端 merge;弹窗 footer 提供权限配置入口。关联 #93。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-11 16:13:20 +08:00

177 lines
5.7 KiB
Python

"""样本储存证质检信息 API"""
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.menu_guard import (
require_any_menu_code,
require_strict_any_menu_code,
require_strict_menu_code,
strict_user_has_any_menu_perm,
strict_user_has_menu_perm,
user_has_menu_perm,
)
from app.core.response import error_response, success_response
from app.crud import sample_storage_cert_qc as svc
from app.crud import sample_storage_cert_qc_acl as acl_svc
from app.models.models import SysUser
from app.services.sample_storage_cert_qc import ACL_CONFIG_MENU
router = APIRouter(prefix="/web/admin", tags=["储存证质检信息"])
# 市场 / 质检 / 生产三端共用读写
READ_MENUS = (
"biz:qcWorkOrder:list",
"biz:myQcTask:list",
"production:work-order:list",
"production:my-task:list",
"cell-storage:sample-info:list",
)
WRITE_MENUS = READ_MENUS
def _get_tenant_no(db: Session, user: dict) -> str:
user_id = user.get("user_id")
if not user_id:
raise PermissionError("用户身份信息缺失,无法确定租户")
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
if not sys_user or not sys_user.tenant_no:
raise PermissionError("用户租户信息缺失")
return sys_user.tenant_no
def _get_current_user_id(user: dict) -> str:
user_id = user.get("user_id")
if not user_id:
raise PermissionError("用户身份信息缺失")
return str(user_id)
def _can_configure_acl(db: Session, user: dict) -> bool:
return user_has_menu_perm(db, user, ACL_CONFIG_MENU) or strict_user_has_menu_perm(
db, user, ACL_CONFIG_MENU
)
class UpsertBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
payload: Dict[str, Any] = Field(default_factory=dict)
qc_work_order_id: Optional[str] = Field(None, alias="qcWorkOrderId")
class AclUpsertBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
field_keys: List[str] = Field(default_factory=list, alias="fieldKeys")
user_ids: List[str] = Field(default_factory=list, alias="userIds")
@router.get("/sample-storage-cert-qc/{sample_code}")
async def get_sample_storage_cert_qc(
sample_code: str,
user: dict = Depends(require_any_menu_code(*READ_MENUS)),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
user_id = _get_current_user_id(user)
detail = svc.get_detail(db, tenant_no, sample_code)
if detail is None:
return error_response(404, "样本不存在")
has_legacy_write = strict_user_has_any_menu_perm(db, user, WRITE_MENUS)
svc.attach_acl_context(
db,
detail,
tenant_no=tenant_no,
user_id=user_id,
has_legacy_write=has_legacy_write,
can_configure_acl=_can_configure_acl(db, user),
)
return success_response(detail)
@router.put("/sample-storage-cert-qc/{sample_code}")
async def put_sample_storage_cert_qc(
sample_code: str,
body: UpsertBody,
user: dict = Depends(require_strict_any_menu_code(*WRITE_MENUS)),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
user_id = _get_current_user_id(user)
has_legacy_write = strict_user_has_any_menu_perm(db, user, WRITE_MENUS)
# 先用空 sampleTypeKey 解析严格模式名单;类型在 upsert 内再收窄
preview = svc.get_detail(db, tenant_no, sample_code)
if preview is None:
return error_response(404, "样本不存在")
editable = acl_svc.resolve_editable_field_keys(
db,
tenant_no=tenant_no,
user_id=user_id,
sample_type_key=preview.get("sampleTypeKey"),
has_legacy_write=has_legacy_write,
)
try:
detail = svc.upsert(
db,
tenant_no=tenant_no,
user_id=user_id,
sample_code=sample_code,
payload=body.payload,
qc_work_order_id=body.qc_work_order_id,
editable_field_keys=editable,
)
svc.attach_acl_context(
db,
detail,
tenant_no=tenant_no,
user_id=user_id,
has_legacy_write=has_legacy_write,
can_configure_acl=_can_configure_acl(db, user),
)
return success_response(detail, message="保存成功")
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.get("/sample-storage-cert-qc-acl/{scope}")
async def get_sample_storage_cert_qc_acl(
scope: str,
user: dict = Depends(require_strict_menu_code(ACL_CONFIG_MENU, allow_super_admin=True)),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
try:
detail = acl_svc.get_acl_detail(db, tenant_no, scope)
return success_response(detail)
except ValueError as e:
return error_response(400, str(e))
@router.put("/sample-storage-cert-qc-acl/{scope}")
async def put_sample_storage_cert_qc_acl(
scope: str,
body: AclUpsertBody,
user: dict = Depends(require_strict_menu_code(ACL_CONFIG_MENU, allow_super_admin=True)),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
user_id = _get_current_user_id(user)
try:
detail = acl_svc.upsert_acl(
db,
tenant_no=tenant_no,
user_id=user_id,
scope=scope,
field_keys=body.field_keys,
user_ids=body.user_ids,
)
return success_response(detail, message="保存成功")
except ValueError as e:
return error_response(400, str(e))