Files
zkxw/backend/app/api/presign_order.py
T
zlwuandCursor d66e55af65 fix: 小程序新增样本发微信通知,并放宽部分 Web 菜单强校验。
移动端协议保存后调度样本采集服务号通知;收款确认/储存证等写接口改为运营端登录即可。同步日期字段规范化与 OA 日期选择器复开修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 16:02:47 +08:00

449 lines
16 KiB
Python

"""预签约订单 / 意向金转账 / 转正式 API"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, ConfigDict, Field, field_validator
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.data_scope import get_user_dept_id
from app.core.menu_guard import require_admin_or_super_admin
from app.core.response import error_response, success_response
from app.crud import presign_order as po_svc
from app.models.models import SysUser
router = APIRouter(prefix="/web/admin", tags=["细胞存储-预签约订单"])
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)
class PresignOrderBody(BaseModel):
model_config = ConfigDict(extra="ignore", populate_by_name=True)
order_date: Optional[str] = Field(None, alias="orderDate")
protocol_no: Optional[str] = Field(None, alias="protocolNo")
mother_name: Optional[str] = Field(None, alias="motherName")
expected_delivery_date: Optional[str] = Field(None, alias="expectedDeliveryDate")
region_id: Optional[str] = Field(None, alias="regionId")
hospital_id: Optional[str] = Field(None, alias="hospitalId")
sample_collect_time: Optional[str] = Field(None, alias="sampleCollectTime")
pay_mode: Optional[str] = Field(None, alias="payMode")
contract_amount: Optional[float] = Field(None, alias="contractAmount")
collector_user_id: Optional[str] = Field(None, alias="collectorUserId")
sales_user_id: Optional[str] = Field(None, alias="salesUserId")
region_manager_user_id: Optional[str] = Field(None, alias="regionManagerUserId")
order_status: Optional[str] = Field(None, alias="orderStatus")
remark: Optional[str] = None
sample_type_ids: Optional[List[str]] = Field(None, alias="sampleTypeIds")
transfers: Optional[List[Dict[str, Any]]] = None
@field_validator("order_date", "expected_delivery_date")
@classmethod
def validate_ymd(cls, value: Optional[str]) -> Optional[str]:
if value in (None, ""):
return value
text = value.strip()
if len(text) != 8 or not text.isdigit():
raise ValueError("日期必须使用 YYYYMMDD 格式")
try:
datetime.strptime(text, "%Y%m%d")
except ValueError as exc:
raise ValueError("日期必须是有效的 YYYYMMDD") from exc
return text
class PresignTransferBody(BaseModel):
model_config = ConfigDict(extra="ignore", populate_by_name=True)
transfer_amount: Optional[float] = Field(None, alias="transferAmount")
transfer_time: Optional[str] = Field(None, alias="transferTime")
transfer_type: Optional[str] = Field(None, alias="transferType")
transfer_attachments: Optional[str] = Field(None, alias="transferAttachments")
remark: Optional[str] = None
class FinanceConfirmBody(BaseModel):
model_config = ConfigDict(extra="ignore", populate_by_name=True)
finance_confirm_attachments: Optional[str] = Field(
None, alias="financeConfirmAttachments"
)
finance_remark: Optional[str] = Field(None, alias="financeRemark")
@router.get("/presign-orders")
async def list_presign_orders(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
orderStatus: Optional[str] = Query(None, alias="orderStatus"),
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
items, total = po_svc.list_presign_orders(
db,
tenant_no=tenant_no,
page=page,
page_size=pageSize,
keyword=keyword,
order_status=orderStatus,
user_info=user,
)
return success_response(
{"list": items, "total": total, "page": page, "pageSize": pageSize}
)
except PermissionError as e:
return error_response(403, str(e))
@router.get("/presign-orders/{order_id}")
async def get_presign_order(
order_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
data = po_svc.get_presign_order(db, order_id=order_id, tenant_no=tenant_no)
if not data:
return error_response(404, "预签约订单不存在")
return success_response(data)
except PermissionError as e:
return error_response(403, str(e))
@router.post("/presign-orders")
async def create_presign_order(
body: PresignOrderBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
user_id = _get_current_user_id(user)
dumped = body.model_dump(by_alias=False)
obj = po_svc.create_presign_order(
db,
create_dept_id=get_user_dept_id(db, user_id) or "",
tenant_no=tenant_no,
user_id=user_id,
fields=dumped,
sample_type_ids=body.sample_type_ids,
transfers=body.transfers,
)
return success_response(
po_svc.order_to_dict(db, obj, tenant_no, with_children=True),
message="创建成功",
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.put("/presign-orders/{order_id}")
async def update_presign_order(
order_id: str,
body: PresignOrderBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
user_id = _get_current_user_id(user)
dumped = body.model_dump(by_alias=False, exclude_unset=True)
sample_type_ids = dumped.pop("sample_type_ids", None)
dumped.pop("transfers", None)
obj = po_svc.update_presign_order(
db,
create_dept_id=get_user_dept_id(db, user_id) or "",
order_id=order_id,
tenant_no=tenant_no,
user_id=user_id,
fields=dumped,
sample_type_ids=sample_type_ids,
)
return success_response(
po_svc.order_to_dict(db, obj, tenant_no, with_children=True),
message="保存成功",
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.delete("/presign-orders/{order_id}")
async def delete_presign_order(
order_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
user_id = _get_current_user_id(user)
po_svc.soft_delete_presign_order(
db, order_id=order_id, tenant_no=tenant_no, user_id=user_id
)
return success_response(None, message="删除成功")
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.post("/presign-orders/{order_id}/convert")
async def convert_presign_order(
order_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
user_id = _get_current_user_id(user)
data = po_svc.convert_to_protocol(
db, order_id=order_id, tenant_no=tenant_no, user_id=user_id,
create_dept_id=get_user_dept_id(db, user_id) or "",
)
return success_response(data, message="已转正式协议")
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.post("/presign-orders/{order_id}/transfers")
async def create_presign_transfer(
order_id: str,
body: PresignTransferBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
user_id = _get_current_user_id(user)
obj = po_svc.add_transfer(
db,
create_dept_id=get_user_dept_id(db, user_id) or "",
presign_order_id=order_id,
tenant_no=tenant_no,
user_id=user_id,
fields=body.model_dump(by_alias=False),
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="添加成功"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.put("/presign-transfers/{transfer_id}")
async def update_presign_transfer(
transfer_id: str,
body: PresignTransferBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(
db, transfer_id, tenant_no, user, transfer=True
)
user_id = _get_current_user_id(user)
obj = po_svc.update_transfer_draft(
db,
transfer_id=transfer_id,
tenant_no=tenant_no,
user_id=user_id,
fields=body.model_dump(by_alias=False, exclude_unset=True),
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="保存成功"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.post("/presign-transfers/{transfer_id}/submit-receipt-finance")
async def submit_presign_transfer_finance(
transfer_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(
db, transfer_id, tenant_no, user, transfer=True
)
user_id = _get_current_user_id(user)
obj = po_svc.submit_receipt_finance(
db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="已提交财务确认"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.post("/presign-transfers/{transfer_id}/withdraw")
async def withdraw_presign_transfer(
transfer_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(
db, transfer_id, tenant_no, user, transfer=True
)
user_id = _get_current_user_id(user)
obj = po_svc.withdraw_receipt(
db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="已撤回"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.get("/presign-transfers/receipt-confirm")
async def list_presign_receipt_confirm(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
items, total = po_svc.list_presign_transfers_for_finance(
db,
pay_status="jfqrz",
tenant_no=tenant_no,
keyword=keyword,
page=page,
page_size=pageSize,
user_info=user,
)
return success_response(
{"list": items, "total": total, "page": page, "pageSize": pageSize}
)
except PermissionError as e:
return error_response(403, str(e))
@router.get("/presign-transfers/receipt-confirmed")
async def list_presign_receipt_confirmed(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
items, total = po_svc.list_presign_transfers_for_finance(
db,
pay_status="yjf",
tenant_no=tenant_no,
keyword=keyword,
page=page,
page_size=pageSize,
user_info=user,
)
return success_response(
{"list": items, "total": total, "page": page, "pageSize": pageSize}
)
except PermissionError as e:
return error_response(403, str(e))
@router.post("/presign-transfers/{transfer_id}/receipt-confirm")
async def confirm_presign_transfer(
transfer_id: str,
body: Optional[FinanceConfirmBody] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(
db, transfer_id, tenant_no, user, transfer=True
)
user_id = _get_current_user_id(user)
obj = po_svc.confirm_receipt(
db,
transfer_id=transfer_id,
tenant_no=tenant_no,
user_id=user_id,
finance_confirm_attachments=body.finance_confirm_attachments if body else None,
finance_remark=body.finance_remark if body else None,
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="确认收款成功"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))
@router.post("/presign-transfers/{transfer_id}/receipt-reject")
async def reject_presign_transfer(
transfer_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
po_svc.assert_presign_data_access(
db, transfer_id, tenant_no, user, transfer=True
)
user_id = _get_current_user_id(user)
obj = po_svc.reject_receipt(
db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
)
return success_response(
po_svc._transfer_to_dict(db, obj, tenant_no), message="已退回"
)
except PermissionError as e:
return error_response(403, str(e))
except ValueError as e:
return error_response(400, str(e))