from datetime import date, datetime from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic.alias_generators import to_camel from module_business.domain.enums import DocumentStatus, PalletFlowState, ReturnStatus PositiveId = Annotated[int, Field(gt=0)] NonBlankAction = Annotated[str, Field(min_length=1, max_length=64, pattern=r'\S')] def _ensure_unique_ids(value: list[int]) -> list[int]: if len(value) != len(set(value)): raise ValueError('托盘ID列表不能包含重复项') return value def _normalize_fifo_reason(value: str | None) -> str | None: return (value.strip() or None) if isinstance(value, str) else value def _parse_page_query_date(value: str) -> datetime: try: parsed = datetime.strptime(value, '%Y-%m-%d') except ValueError as exc: raise ValueError('日期必须为有效的 YYYY-MM-DD 格式') from exc if parsed.strftime('%Y-%m-%d') != value: raise ValueError('日期必须为有效的 YYYY-MM-DD 格式') return parsed def _validate_page_query_date_range(value: BaseModel) -> BaseModel: begin_value = value.begin_time end_value = value.end_time begin_date = _parse_page_query_date(begin_value) if begin_value else None end_date = _parse_page_query_date(end_value) if end_value else None if end_date and end_date.date() == datetime.max.date(): raise ValueError('结束日期必须早于 9999-12-31') if begin_date and end_date and begin_date > end_date: raise ValueError('开始日期不能晚于结束日期') return value class InboundCreateModel(BaseModel): """首次入库草稿新增模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) warehouse_id: int = Field(gt=0, description='仓库ID') pallet_ids: list[PositiveId] = Field(min_length=1, description='托盘ID列表') _validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids) class InboundUpdateModel(BaseModel): """首次入库草稿更新模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) inbound_id: int = Field(gt=0, description='入库单ID') warehouse_id: int = Field(gt=0, description='仓库ID') pallet_ids: list[PositiveId] = Field(min_length=1, description='托盘ID列表') _validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids) class ConfirmCommandModel(BaseModel): """单据确认命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='确认幂等键') class FillingCreateModel(BaseModel): """托盘装填命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) qr_token: str = Field(min_length=1, max_length=128, pattern=r'\S', description='二维码固定标识') warehouse_id: int = Field(gt=0, description='仓库ID') strain_id: int = Field(gt=0, description='菌种ID') batch_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='生产批次') production_date: date = Field(description='发货来源生产日期') expiry_date: date = Field(description='有效期至') package_spec: str = Field(min_length=1, max_length=64, pattern=r'\S', description='包装规格') initial_packages: Literal[100] = Field(description='初始包数(固定 100 包)') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键') @model_validator(mode='after') def validate_material_dates(self) -> 'FillingCreateModel': if self.expiry_date < self.production_date: raise ValueError('有效期不能早于生产日期') return self class ShipmentCreateModel(BaseModel): """整托发货草稿新增模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) source_warehouse_id: int = Field(gt=0, description='来源仓库ID') target_site_id: int = Field(gt=0, description='目标现场ID') pallet_ids: list[PositiveId] = Field(min_length=1, description='托盘ID列表') _validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids) class ShipmentUpdateModel(BaseModel): """整托发货草稿更新模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) shipment_id: int = Field(gt=0, description='发货单ID') source_warehouse_id: int = Field(gt=0, description='来源仓库ID') target_site_id: int = Field(gt=0, description='目标现场ID') pallet_ids: list[PositiveId] = Field(min_length=1, description='托盘ID列表') _validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids) class UsageCreateModel(BaseModel): """客户现场使用命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) qr_token: str = Field(min_length=1, max_length=128, pattern=r'\S', description='二维码固定标识') tunnel_id: int = Field(gt=0, description='隧道ID') line_id: int | None = Field(default=None, gt=0, description='产线ID(可选)') used_packages: int = Field(strict=True, gt=0, description='本次使用包数') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键') fifo_skip_reason: str | None = Field(default=None, max_length=500, description='跳过先到托盘原因') _normalize_reason = field_validator('fifo_skip_reason', mode='before')(_normalize_fifo_reason) class TunnelEntryCreateModel(BaseModel): """托盘扫码入隧命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) qr_token: str = Field(min_length=1, max_length=128, pattern=r'\S', description='二维码固定标识') tunnel_id: int = Field(gt=0, description='隧道ID') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键') fifo_skip_reason: str | None = Field(default=None, max_length=500, description='跳过先到托盘原因') _normalize_reason = field_validator('fifo_skip_reason', mode='before')(_normalize_fifo_reason) class TunnelEntrySuccessResponseModel(BaseModel): """托盘扫码入隧成功响应模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) pallet_id: int = Field(gt=0, description='托盘ID') tunnel_id: int = Field(gt=0, description='隧道ID') trace_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='物料溯源号') class UsageSuccessResponseModel(BaseModel): """客户现场使用成功响应模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) usage_id: int = Field(gt=0, description='使用记录ID') remaining_packages: int = Field(strict=True, ge=0, description='使用后剩余包数') flow_state: PalletFlowState = Field(description='使用后托盘流转状态') class ReturnRequestModel(BaseModel): """托盘退回申请命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) source_site_id: int = Field(gt=0, description='来源现场ID') target_warehouse_id: int = Field(gt=0, description='目标仓库ID') pallet_ids: list[PositiveId] = Field(min_length=1, description='托盘ID列表') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='申请幂等键') _validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids) class ReturnConfirmModel(BaseModel): """托盘退回确认命令模型。""" model_config = ConfigDict( alias_generator=to_camel, from_attributes=True, populate_by_name=True, extra='forbid', ) scanned_pallet_ids: list[PositiveId] = Field(min_length=1, description='已扫描托盘ID列表') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='确认幂等键') _validate_unique_ids = field_validator('scanned_pallet_ids')(_ensure_unique_ids) class InboundItemModel(BaseModel): """首次入库单托盘明细模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) inbound_item_id: int = Field(gt=0, description='入库明细ID') inbound_id: int = Field(gt=0, description='入库单ID') pallet_id: int = Field(gt=0, description='托盘ID') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') class InboundModel(BaseModel): """首次入库单展示模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) inbound_id: int = Field(gt=0, description='入库单ID') inbound_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='入库单号') warehouse_id: int = Field(gt=0, description='仓库ID') status: DocumentStatus = Field(description='单据状态') confirm_idempotency_key: str | None = Field(default=None, max_length=64, description='确认幂等键') confirm_request_hash: str | None = Field(default=None, max_length=64, description='确认请求摘要') confirmed_by: int | None = Field(default=None, gt=0, description='确认人ID') confirmed_at: datetime | None = Field(default=None, description='确认时间') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') update_by: str | None = Field(default=None, max_length=64, description='更新者') update_time: datetime | None = Field(default=None, description='更新时间') remark: str | None = Field(default=None, max_length=500, description='备注') items: list[InboundItemModel] = Field(default_factory=list, description='入库托盘明细') class FillingModel(BaseModel): """托盘装填记录展示模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) filling_id: int = Field(gt=0, description='装填记录ID') filling_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='装填单号') pallet_id: int = Field(gt=0, description='托盘ID') warehouse_id: int = Field(gt=0, description='仓库ID') strain_id: int = Field(gt=0, description='菌种ID') strain_code_snapshot: str = Field(min_length=1, max_length=64, pattern=r'\S', description='菌种编码快照') strain_name_snapshot: str = Field(min_length=1, max_length=100, pattern=r'\S', description='菌种名称快照') batch_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='生产批次') production_date: date | None = Field(default=None, description='生产日期;历史未登记时为空') expiry_date: date | None = Field(default=None, description='有效期至;历史未登记时为空') package_spec: str = Field(min_length=1, max_length=64, pattern=r'\S', description='包装规格') initial_packages: int = Field(strict=True, gt=0, description='初始包数') active_slot: Literal['ACTIVE'] | None = Field(default=None, description='活动装填槽') closed_at: datetime | None = Field(default=None, description='装填周期关闭时间') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键') request_hash: str = Field(min_length=1, max_length=64, pattern=r'\S', description='请求摘要') operator_id: int = Field(gt=0, description='操作人ID') operated_at: datetime = Field(description='操作时间') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') update_by: str | None = Field(default=None, max_length=64, description='更新者') update_time: datetime | None = Field(default=None, description='更新时间') remark: str | None = Field(default=None, max_length=500, description='备注') class ShipmentItemModel(BaseModel): """整托发货明细模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) shipment_item_id: int = Field(gt=0, description='发货明细ID') shipment_id: int = Field(gt=0, description='发货单ID') pallet_id: int = Field(gt=0, description='托盘ID') active_filling_id: int | None = Field(default=None, gt=0, description='确认时活动装填记录ID') package_qty: int | None = Field(default=None, strict=True, gt=0, description='确认时包数快照') remaining_packages: int | None = Field( default=None, strict=True, ge=0, description='当前库存剩余包数(只读,非确认快照)', ) create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') class ShipmentModel(BaseModel): """整托发货单展示模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) shipment_id: int = Field(gt=0, description='发货单ID') shipment_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='发货单号') source_warehouse_id: int = Field(gt=0, description='来源仓库ID') target_site_id: int = Field(gt=0, description='目标现场ID') status: DocumentStatus = Field(description='单据状态') confirm_idempotency_key: str | None = Field(default=None, max_length=64, description='确认幂等键') confirm_request_hash: str | None = Field(default=None, max_length=64, description='确认请求摘要') confirmed_by: int | None = Field(default=None, gt=0, description='确认人ID') confirmed_at: datetime | None = Field(default=None, description='确认时间') receipt_state: Literal['PENDING', 'RECEIVED', 'LEGACY'] | None = Field(default=None, description='收货核对状态') received_by: int | None = Field(default=None, gt=0, description='收货核对人') received_at: datetime | None = Field(default=None, description='实际收货核对时间') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') update_by: str | None = Field(default=None, max_length=64, description='更新者') update_time: datetime | None = Field(default=None, description='更新时间') remark: str | None = Field(default=None, max_length=500, description='备注') items: list[ShipmentItemModel] = Field(default_factory=list, description='发货托盘明细') class UsageModel(BaseModel): """客户现场使用记录展示模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) usage_id: int = Field(gt=0, description='使用记录ID') usage_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='使用记录号') pallet_id: int = Field(gt=0, description='托盘ID') active_filling_id: int = Field(gt=0, description='活动装填记录ID') site_id: int = Field(gt=0, description='现场ID') tunnel_id: int | None = Field(default=None, gt=0, description='隧道ID') trace_no: str | None = Field(default=None, max_length=64, description='物料溯源号') batch_no_snapshot: str | None = Field(default=None, max_length=64, description='生产批次快照') line_id: int | None = Field(default=None, gt=0, description='产线ID(可选)') used_packages: int = Field(strict=True, gt=0, description='本次使用包数') remaining_packages_after: int = Field(strict=True, ge=0, description='使用后剩余包数') idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键') request_hash: str = Field(min_length=1, max_length=64, pattern=r'\S', description='请求摘要') operator_id: int = Field(gt=0, description='操作人ID') occurred_at: datetime = Field(description='发生时间') fifo_skip_reason: str | None = Field(default=None, max_length=500, description='跳过先到托盘原因') class ReturnItemModel(BaseModel): """托盘退回明细模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) return_item_id: int = Field(gt=0, description='退回明细ID') return_id: int = Field(gt=0, description='退回单ID') pallet_id: int = Field(gt=0, description='托盘ID') active_filling_id_snapshot: int | None = Field(default=None, gt=0, description='申请时活动装填记录快照') tunnel_id_snapshot: int | None = Field(default=None, gt=0, description='申请时隧道ID快照') trace_no_snapshot: str | None = Field(default=None, max_length=64, description='申请时物料溯源号快照') batch_no_snapshot: str | None = Field(default=None, max_length=64, description='申请时生产批次快照') remaining_packages_snapshot: int = Field(strict=True, ge=0, description='申请时剩余包数快照') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') class ReturnModel(BaseModel): """托盘退回单展示模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) return_id: int = Field(gt=0, description='退回单ID') return_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='退回单号') source_site_id: int = Field(gt=0, description='来源现场ID') target_warehouse_id: int = Field(gt=0, description='目标仓库ID') status: ReturnStatus = Field(description='退回状态') request_idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='申请幂等键') request_hash: str = Field(min_length=1, max_length=64, pattern=r'\S', description='申请请求摘要') confirm_idempotency_key: str | None = Field(default=None, max_length=64, description='确认幂等键') confirm_request_hash: str | None = Field(default=None, max_length=64, description='确认请求摘要') requested_by: int = Field(gt=0, description='申请人ID') requested_at: datetime = Field(description='申请时间') confirmed_by: int | None = Field(default=None, gt=0, description='确认人ID') confirmed_at: datetime | None = Field(default=None, description='确认时间') create_by: str | None = Field(default=None, max_length=64, description='创建者') create_time: datetime | None = Field(default=None, description='创建时间') update_by: str | None = Field(default=None, max_length=64, description='更新者') update_time: datetime | None = Field(default=None, description='更新时间') remark: str | None = Field(default=None, max_length=500, description='备注') items: list[ReturnItemModel] = Field(default_factory=list, description='退回托盘明细') class InboundPageQueryModel(BaseModel): """首次入库分页查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) page_num: int = Field(default=1, gt=0, description='当前页码') page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') inbound_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='入库单号') warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID') status: DocumentStatus | None = Field(default=None, description='单据状态') begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间') end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间') _validate_date_range = model_validator(mode='after')(_validate_page_query_date_range) class FillingPageQueryModel(BaseModel): """托盘装填分页查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) page_num: int = Field(default=1, gt=0, description='当前页码') page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') filling_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='装填单号') warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID') strain_id: int | None = Field(default=None, gt=0, description='菌种ID') batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次') pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号') begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间') end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间') _validate_date_range = model_validator(mode='after')(_validate_page_query_date_range) class ShipmentPageQueryModel(BaseModel): """整托发货分页查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) page_num: int = Field(default=1, gt=0, description='当前页码') page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') shipment_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='发货单号') source_warehouse_id: int | None = Field(default=None, gt=0, description='来源仓库ID') target_site_id: int | None = Field(default=None, gt=0, description='目标现场ID') status: DocumentStatus | None = Field(default=None, description='单据状态') begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间') end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间') _validate_date_range = model_validator(mode='after')(_validate_page_query_date_range) class UsagePageQueryModel(BaseModel): """客户现场使用分页查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) page_num: int = Field(default=1, gt=0, description='当前页码') page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') usage_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='使用记录号') site_id: int | None = Field(default=None, gt=0, description='现场ID') tunnel_id: int | None = Field(default=None, gt=0, description='隧道ID') trace_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='物料溯源号') line_id: int | None = Field(default=None, gt=0, description='产线ID') pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号') begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间') end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间') _validate_date_range = model_validator(mode='after')(_validate_page_query_date_range) class ReturnPageQueryModel(BaseModel): """托盘退回分页查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) page_num: int = Field(default=1, gt=0, description='当前页码') page_size: int = Field(default=10, gt=0, le=100, description='每页记录数') return_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='退回单号') source_site_id: int | None = Field(default=None, gt=0, description='来源现场ID') target_warehouse_id: int | None = Field(default=None, gt=0, description='目标仓库ID') status: ReturnStatus | None = Field(default=None, description='退回状态') begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间') end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间') _validate_date_range = model_validator(mode='after')(_validate_page_query_date_range) class InboundScanQueryModel(BaseModel): """首次入库扫码查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) warehouse_id: int = Field(gt=0, description='仓库ID') class ShipmentScanQueryModel(BaseModel): """整托发货扫码查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) source_warehouse_id: int = Field(gt=0, description='来源仓库ID') target_site_id: int = Field(gt=0, description='目标现场ID') class ReturnScanQueryModel(BaseModel): """托盘退回扫码查询模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) mode: Literal['request', 'confirm'] = Field(description='扫码模式') return_id: int | None = Field(default=None, gt=0, description='退回单ID') @model_validator(mode='after') def validate_return_id(self) -> 'ReturnScanQueryModel': if self.mode == 'confirm' and self.return_id is None: raise ValueError('确认退回扫码时必须提供退回单ID') if self.mode == 'request' and self.return_id is not None: raise ValueError('申请退回扫码时不能提供退回单ID') return self class BusinessOptionModel(BaseModel): """业务下拉选项模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) id: int = Field(gt=0, description='业务主键') code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='业务编码') name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='业务名称') class PalletScanResponseModel(BaseModel): """业务扫码托盘响应模型。""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True) pallet_id: int = Field(gt=0, description='托盘ID') pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号') qr_token: str | None = Field(default=None, min_length=1, max_length=128, description='解析后的固定二维码标识,写入时使用') flow_state: PalletFlowState = Field(description='托盘流转状态') warehouse_id: int | None = Field(default=None, gt=0, description='当前仓库ID') site_id: int | None = Field(default=None, gt=0, description='当前现场ID') tunnel_id: int | None = Field(default=None, gt=0, description='当前隧道ID') trace_no: str | None = Field(default=None, max_length=64, description='当前物料溯源号') current_line_id: int | None = Field(default=None, gt=0, description='当前绑定产线ID') active_filling_id: int | None = Field(default=None, gt=0, description='当前活动装填记录ID') strain_id: int | None = Field(default=None, gt=0, description='菌种ID') strain_code: str | None = Field(default=None, max_length=64, description='菌种编码') strain_name: str | None = Field(default=None, max_length=100, description='菌种名称') batch_no: str | None = Field(default=None, max_length=64, description='生产批次') production_date: date | None = Field(default=None, description='生产日期') expiry_date: date | None = Field(default=None, description='有效期至') package_spec: str | None = Field(default=None, max_length=64, description='包装规格') capacity_packages: int = Field(strict=True, gt=0, description='容量包数') remaining_packages: int = Field(strict=True, ge=0, description='当前剩余包数') allowed_actions: list[NonBlankAction] = Field(description='当前接口允许的业务动作')