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)