6a2e43f829
1、将药物发货新建和编辑从独立页面迁移到抽屉,详情页支持权限控制和停用中心只读状态。 2、补充药物发货状态字段校验,统一 PENDING、IN_TRANSIT、SIGNED、EXCEPTION 状态及演示数据。 3、为物资设备增加详情页和编辑抽屉,复用通用附件上传并按项目权限控制操作入口。 4、将启动会和培训授权编辑迁移到抽屉,详情页按中心过滤培训记录并移除旧独立编辑路由。
121 lines
3.5 KiB
Python
121 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import date, datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, ConfigDict, model_validator
|
|
|
|
|
|
SHIPMENT_STATUSES = {"PENDING", "IN_TRANSIT", "SIGNED", "EXCEPTION"}
|
|
COMPLETED_SHIPMENT_STATUS = "SIGNED"
|
|
EXCEPTION_SHIPMENT_STATUS = "EXCEPTION"
|
|
PENDING_SHIPMENT_STATUS = "PENDING"
|
|
|
|
|
|
def shipment_requires_execution_details(status: str | None) -> bool:
|
|
return status != PENDING_SHIPMENT_STATUS
|
|
|
|
|
|
def shipment_requires_receive_date(status: str | None) -> bool:
|
|
return status == COMPLETED_SHIPMENT_STATUS
|
|
|
|
|
|
def shipment_requires_remark(status: str | None) -> bool:
|
|
return status == EXCEPTION_SHIPMENT_STATUS
|
|
|
|
|
|
def validate_drug_shipment_required_fields(
|
|
status: str | None,
|
|
ship_date: date | None,
|
|
receive_date: date | None,
|
|
quantity: int | None,
|
|
batch_no: str | None,
|
|
carrier: str | None,
|
|
tracking_no: str | None,
|
|
remark: str | None,
|
|
) -> None:
|
|
if status not in SHIPMENT_STATUSES:
|
|
raise ValueError("无效状态")
|
|
if shipment_requires_execution_details(status):
|
|
missing_execution_fields = [
|
|
label
|
|
for label, value in (
|
|
("寄出/回收日期", ship_date),
|
|
("数量", quantity),
|
|
("批号", batch_no),
|
|
("快递公司", carrier),
|
|
("单号", tracking_no),
|
|
)
|
|
if value is None or (isinstance(value, str) and not value.strip())
|
|
]
|
|
if missing_execution_fields:
|
|
raise ValueError(f"发运信息缺失:{', '.join(missing_execution_fields)}")
|
|
if shipment_requires_receive_date(status) and receive_date is None:
|
|
raise ValueError("已签收时必须填写接收日期")
|
|
if shipment_requires_remark(status) and not (remark or "").strip():
|
|
raise ValueError("异常状态必须填写备注")
|
|
|
|
|
|
class DrugShipmentCreate(BaseModel):
|
|
direction: str
|
|
center_id: uuid.UUID
|
|
site_name: Optional[str] = None
|
|
ship_date: Optional[date] = None
|
|
receive_date: Optional[date] = None
|
|
quantity: Optional[int] = None
|
|
batch_no: Optional[str] = None
|
|
carrier: Optional[str] = None
|
|
tracking_no: Optional[str] = None
|
|
status: str
|
|
remark: Optional[str] = None
|
|
|
|
@model_validator(mode="after")
|
|
def validate_status_required_fields(self):
|
|
validate_drug_shipment_required_fields(
|
|
self.status,
|
|
self.ship_date,
|
|
self.receive_date,
|
|
self.quantity,
|
|
self.batch_no,
|
|
self.carrier,
|
|
self.tracking_no,
|
|
self.remark,
|
|
)
|
|
return self
|
|
|
|
|
|
class DrugShipmentUpdate(BaseModel):
|
|
direction: Optional[str] = None
|
|
center_id: Optional[uuid.UUID] = None
|
|
site_name: Optional[str] = None
|
|
ship_date: Optional[date] = None
|
|
receive_date: Optional[date] = None
|
|
quantity: Optional[int] = None
|
|
batch_no: Optional[str] = None
|
|
carrier: Optional[str] = None
|
|
tracking_no: Optional[str] = None
|
|
status: Optional[str] = None
|
|
remark: Optional[str] = None
|
|
|
|
|
|
class DrugShipmentRead(BaseModel):
|
|
id: uuid.UUID
|
|
study_id: uuid.UUID
|
|
direction: str
|
|
center_id: Optional[uuid.UUID]
|
|
site_name: str
|
|
ship_date: Optional[date]
|
|
receive_date: Optional[date]
|
|
quantity: Optional[int]
|
|
batch_no: Optional[str]
|
|
carrier: Optional[str]
|
|
tracking_no: Optional[str]
|
|
status: str
|
|
remark: Optional[str]
|
|
created_by: Optional[uuid.UUID]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|