为 Web 端改版提供名称、统计和看板数据。只新增只读查询、扩展列表与详情 返回字段;写接口、托盘状态机和幂等键规则均未改动,未新增权限标识。 - 基础资料:列表补充名称与汇总,客户详情返回现场树,托盘详情返回最近流转, 托盘列表支持按流转阶段筛选和二维码精确匹配;新增现场、产线、隧道的上级 资料选项接口;Excel 导入支持 preview 只校验不写入 - 发货出库:列表支持 keyword、receiptState 筛选并补充名称、托盘数、包数和 菌种构成;详情明细补充托盘号、菌种、批次 - 首次入库:列表与详情补充名称、托盘数、托盘当前状态;新增 /summary - 托盘退回:列表支持 keyword 并补充名称与托盘汇总,详情补充托盘号和隧道; 新增 /summary、/site-options - 工厂装填:列表支持 keyword 并补充托盘号、仓库、操作人;新增 /summary、 /batches、/batches/detail - 客户现场使用:列表支持 keyword、fifoSkipped 并补充名称和被跳过的先到托盘号; 新增 /site-options、/site-board 以及 usage-fifo 的 /queue - 先到先用 DAO 抽出公共候选条件与排序,建议查询生成的 SQL 与改动前一致 - 新增各汇总 DAO 的单元测试,同步更新路由权限与模型字段契约测试 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
908 lines
47 KiB
Python
908 lines
47 KiB
Python
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')
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号(只读展示)')
|
|
flow_state: PalletFlowState | None = Field(default=None, description='托盘当前流转状态(只读展示)')
|
|
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 InboundListItemModel(InboundModel):
|
|
"""首次入库单列表行:在单据字段外附带只读的名称与托盘数。"""
|
|
|
|
warehouse_name: str | None = Field(default=None, max_length=100, description='入库仓库名称')
|
|
creator_name: str | None = Field(default=None, max_length=30, description='创建人昵称')
|
|
confirmer_name: str | None = Field(default=None, max_length=30, description='确认人昵称')
|
|
pallet_count: int = Field(default=0, strict=True, ge=0, description='托盘数')
|
|
|
|
|
|
class InboundWarehouseCountModel(BaseModel):
|
|
"""按仓库统计的托盘数。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
warehouse_id: int = Field(gt=0, description='仓库ID')
|
|
warehouse_name: str | None = Field(default=None, max_length=100, description='仓库名称')
|
|
pallet_count: int = Field(strict=True, ge=0, description='托盘数')
|
|
|
|
|
|
class InboundOverviewModel(BaseModel):
|
|
"""首次入库页面概况(只读)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
draft_count: int = Field(strict=True, ge=0, description='待确认草稿单数')
|
|
draft_pallet_count: int = Field(strict=True, ge=0, description='待确认草稿托盘数')
|
|
oldest_draft_at: datetime | None = Field(default=None, description='最早草稿创建时间')
|
|
month_start: date = Field(description='本月统计起始日期')
|
|
month_confirmed_count: int = Field(strict=True, ge=0, description='本月已确认单数')
|
|
month_confirmed_pallet_count: int = Field(strict=True, ge=0, description='本月已确认入库托盘数')
|
|
unstocked_pallet_count: int = Field(strict=True, ge=0, description='待首次入库托盘数(资产正常且未入库)')
|
|
empty_pallet_count: int = Field(strict=True, ge=0, description='授权仓库空托盘总数')
|
|
empty_by_warehouse: list[InboundWarehouseCountModel] = 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 FillingListItemModel(FillingModel):
|
|
"""托盘装填记录列表行:在记录字段外附带只读的托盘编号、仓库与操作人名称。"""
|
|
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号')
|
|
warehouse_name: str | None = Field(default=None, max_length=100, description='仓库名称')
|
|
operator_name: str | None = Field(default=None, max_length=30, description='操作人昵称')
|
|
|
|
|
|
class FillingBatchModel(BaseModel):
|
|
"""按菌种 + 生产批次聚合的装填汇总(只读)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
strain_id: int = Field(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 = Field(min_length=1, 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='最近一次装填的包装规格')
|
|
pallet_count: int = Field(strict=True, ge=0, description='已装填托盘数')
|
|
package_sum: int = Field(strict=True, ge=0, description='已装填包数')
|
|
last_operated_at: datetime | None = Field(default=None, description='最近装填时间')
|
|
last_operator_name: str | None = Field(default=None, max_length=30, description='最近装填操作人昵称')
|
|
|
|
|
|
class FillingBatchStateCountModel(BaseModel):
|
|
"""批次托盘按当前去向统计。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
state: str = Field(
|
|
min_length=1,
|
|
max_length=32,
|
|
description='托盘当前流转状态;装填周期已结束(物料用完)时为 CONSUMED',
|
|
)
|
|
pallet_count: int = Field(strict=True, ge=0, description='托盘数')
|
|
|
|
|
|
class FillingBatchRecordModel(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, description='装填单号')
|
|
pallet_id: int = Field(gt=0, description='托盘ID')
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号')
|
|
warehouse_name: str | None = Field(default=None, max_length=100, description='装填仓库名称')
|
|
operator_name: str | None = Field(default=None, max_length=30, description='操作人昵称')
|
|
operated_at: datetime = Field(description='装填时间')
|
|
|
|
|
|
class FillingBatchDetailModel(FillingBatchModel):
|
|
"""生产批次详情:批次汇总、托盘当前去向与最近装填记录。"""
|
|
|
|
distribution: list[FillingBatchStateCountModel] = Field(
|
|
default_factory=list, description='托盘当前去向,托盘数从多到少'
|
|
)
|
|
recent: list[FillingBatchRecordModel] = Field(default_factory=list, description='最近 10 条装填记录')
|
|
|
|
|
|
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='当前库存剩余包数(只读,非确认快照)',
|
|
)
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号(只读展示)')
|
|
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='生产批次(只读展示)')
|
|
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 ShipmentStrainSummaryModel(BaseModel):
|
|
"""发货单内菌种构成。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
strain_name: str = Field(min_length=1, max_length=100, description='菌种名称或编码')
|
|
pallet_count: int = Field(strict=True, gt=0, description='托盘数')
|
|
|
|
|
|
class FillingOverviewModel(BaseModel):
|
|
"""工厂装填页面概况(只读)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
today_pallet_count: int = Field(strict=True, ge=0, description='今日装填托盘数')
|
|
today_package_sum: int = Field(strict=True, ge=0, description='今日装填包数')
|
|
today_batch_count: int = Field(strict=True, ge=0, description='今日涉及的生产批次数')
|
|
month_start: date = Field(description='本月统计起始日期')
|
|
month_pallet_count: int = Field(strict=True, ge=0, description='本月装填托盘数')
|
|
month_batch_count: int = Field(strict=True, ge=0, description='本月涉及的生产批次数')
|
|
month_top_strains: list[ShipmentStrainSummaryModel] = Field(
|
|
default_factory=list,
|
|
description='本月装填最多的菌种(最多 3 个)',
|
|
)
|
|
empty_pallet_count: int = Field(strict=True, ge=0, description='授权仓库可装填空托盘总数')
|
|
empty_by_warehouse: list[InboundWarehouseCountModel] = Field(
|
|
default_factory=list,
|
|
description='授权仓库空托盘按仓库分布,托盘数从多到少',
|
|
)
|
|
near_expiry_days: int = Field(strict=True, gt=0, description='临期判定天数')
|
|
near_expiry_pallet_count: int = Field(
|
|
strict=True, ge=0, description='有效期在临期天数内(含已过期)且仍在仓的有料托盘数'
|
|
)
|
|
near_expiry_batch_count: int = Field(strict=True, ge=0, description='上述托盘涉及的生产批次数')
|
|
nearest_expiry_batch_no: str | None = Field(default=None, max_length=64, description='最早到期的在仓批次')
|
|
nearest_expiry_date: date | None = Field(default=None, description='最早到期的在仓批次有效期')
|
|
|
|
|
|
class ShipmentListItemModel(ShipmentModel):
|
|
"""整托发货单列表行:在单据字段外附带只读的名称与汇总。"""
|
|
|
|
source_warehouse_name: str | None = Field(default=None, max_length=100, description='来源仓库名称')
|
|
target_site_name: str | None = Field(default=None, max_length=100, description='目标现场名称')
|
|
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
|
creator_name: str | None = Field(default=None, max_length=30, description='创建人昵称')
|
|
pallet_count: int = Field(default=0, strict=True, ge=0, description='托盘数')
|
|
package_qty: int | None = Field(
|
|
default=None,
|
|
strict=True,
|
|
ge=0,
|
|
description='包数:已确认为确认快照之和,草稿为当前可发余量之和',
|
|
)
|
|
strains: list[ShipmentStrainSummaryModel] = 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 UsageListItemModel(UsageModel):
|
|
"""客户现场使用记录列表行:附带只读的托盘、现场、隧道、产线与操作人名称。"""
|
|
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号')
|
|
site_name: str | None = Field(default=None, max_length=100, description='现场名称')
|
|
tunnel_code: str | None = Field(default=None, max_length=64, description='隧道编号')
|
|
tunnel_name: str | None = Field(default=None, max_length=100, description='隧道名称')
|
|
line_name: str | None = Field(default=None, max_length=100, description='产线名称')
|
|
operator_name: str | None = Field(default=None, max_length=30, description='操作人昵称')
|
|
skipped_pallet_code: str | None = Field(
|
|
default=None, max_length=64, 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='申请时剩余包数快照')
|
|
pallet_code: str | None = Field(default=None, max_length=64, description='托盘编号(只读展示)')
|
|
tunnel_name: str | None = Field(default=None, max_length=100, 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 ReturnListItemModel(ReturnModel):
|
|
"""托盘退回单列表行:在单据字段外附带只读的名称与托盘汇总。"""
|
|
|
|
source_site_name: str | None = Field(default=None, max_length=100, description='来源现场名称')
|
|
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
|
target_warehouse_name: str | None = Field(default=None, max_length=100, description='目标仓库名称')
|
|
requester_name: str | None = Field(default=None, max_length=30, description='申请人昵称')
|
|
confirmer_name: str | None = Field(default=None, max_length=30, description='确认人昵称')
|
|
pallet_count: int = Field(default=0, strict=True, ge=0, description='托盘数')
|
|
package_sum: int = Field(default=0, strict=True, ge=0, description='申请时剩余包数快照之和')
|
|
filled_count: int = Field(default=0, strict=True, ge=0, description='申请时仍有余量的托盘数')
|
|
|
|
|
|
class ReturnOverviewModel(BaseModel):
|
|
"""托盘退回页面概况(只读)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
pending_count: int = Field(strict=True, ge=0, description='待到仓核对单数')
|
|
pending_pallet_count: int = Field(strict=True, ge=0, description='待到仓核对托盘数(即退回中托盘)')
|
|
pending_filled_count: int = Field(strict=True, ge=0, description='待核对托盘中仍有余量的托盘数')
|
|
pending_package_sum: int = Field(strict=True, ge=0, description='待核对托盘剩余包数之和')
|
|
oldest_pending_at: datetime | None = Field(default=None, description='最早一张待核对单的申请时间')
|
|
month_start: date = Field(description='本月统计起始日期')
|
|
month_confirmed_count: int = Field(strict=True, ge=0, description='本月已确认单数')
|
|
month_confirmed_pallet_count: int = Field(strict=True, ge=0, description='本月已确认托盘数')
|
|
month_filled_pallet_count: int = Field(strict=True, ge=0, description='本月确认后回到有料库存的托盘数')
|
|
month_package_sum: int = Field(strict=True, ge=0, description='本月确认带回的剩余包数')
|
|
month_avg_confirm_hours: float | None = Field(default=None, ge=0, 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='装填单号')
|
|
keyword: 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 FillingBatchPageQueryModel(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='每页记录数')
|
|
keyword: str | None = Field(
|
|
default=None,
|
|
min_length=1,
|
|
max_length=64,
|
|
pattern=r'\S',
|
|
description='关键字:匹配生产批次、菌种编码或名称',
|
|
)
|
|
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
|
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 FillingBatchDetailQueryModel(BaseModel):
|
|
"""生产批次详情查询模型。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
strain_id: int = Field(gt=0, description='菌种ID')
|
|
batch_no: str = Field(min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
|
|
|
|
|
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='发货单号')
|
|
keyword: 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='单据状态')
|
|
receipt_state: Literal['PENDING', 'RECEIVED', 'LEGACY'] | 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='使用记录号')
|
|
keyword: str | None = Field(
|
|
default=None,
|
|
min_length=1,
|
|
max_length=64,
|
|
pattern=r'\S',
|
|
description='关键字:匹配使用记录号或托盘编号',
|
|
)
|
|
fifo_skipped: bool | None = Field(default=None, description='为 true 时只看跳过先到托盘的记录')
|
|
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='退回单号')
|
|
keyword: 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 ReturnSiteOptionModel(BusinessOptionModel):
|
|
"""退回来源现场选项,附带所属客户名称。"""
|
|
|
|
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
|
|
|
|
|
class UsageSiteOptionModel(BusinessOptionModel):
|
|
"""现场使用页的现场选项,附带客户名称与启用隧道、产线数。"""
|
|
|
|
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
|
tunnel_count: int = Field(default=0, strict=True, ge=0, description='启用隧道数')
|
|
line_count: int = Field(default=0, strict=True, ge=0, description='启用产线数')
|
|
|
|
|
|
class UsageBoardTunnelModel(BaseModel):
|
|
"""隧道看板中的一条隧道(不含内部溯源号)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
tunnel_id: int = Field(gt=0, description='隧道ID')
|
|
tunnel_code: str = Field(min_length=1, max_length=64, description='隧道编号')
|
|
tunnel_name: str = Field(min_length=1, max_length=100, description='隧道名称')
|
|
in_use: bool = Field(description='是否有正在使用的物料批')
|
|
pallet_count: int = Field(strict=True, ge=0, description='在隧道中仍有余量的托盘数')
|
|
remaining_packages: int = Field(strict=True, ge=0, description='在隧道中托盘的剩余包数')
|
|
started_at: datetime | None = Field(default=None, description='本轮物料批开始时间')
|
|
last_used_at: datetime | None = Field(default=None, description='最近一次登记使用时间')
|
|
last_line_name: str | None = Field(default=None, max_length=100, description='最近一次使用的产线名称')
|
|
|
|
|
|
class UsageLinePackagesModel(BaseModel):
|
|
"""按产线统计的用量。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
line_id: int | None = Field(default=None, gt=0, description='产线ID;未指定产线时为空')
|
|
line_name: str | None = Field(default=None, max_length=100, description='产线名称')
|
|
used_packages: int = Field(strict=True, ge=0, description='使用包数')
|
|
|
|
|
|
class UsageSiteBoardModel(BaseModel):
|
|
"""客户现场看板:库存、今日用量、隧道与先到先用概况(只读)。"""
|
|
|
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
|
|
|
site_id: int = Field(gt=0, description='现场ID')
|
|
site_code: str | None = Field(default=None, max_length=64, description='现场编码')
|
|
site_name: str | None = Field(default=None, max_length=100, description='现场名称')
|
|
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
|
pallet_count: int = Field(strict=True, ge=0, description='现场未使用与使用中的托盘数')
|
|
package_sum: int = Field(strict=True, ge=0, description='上述托盘的剩余包数')
|
|
unused_pallet_count: int = Field(strict=True, ge=0, description='现场未使用托盘数')
|
|
partial_pallet_count: int = Field(strict=True, ge=0, description='现场使用中托盘数')
|
|
empty_pallet_count: int = Field(strict=True, ge=0, description='现场已用完、待退回的托盘数')
|
|
today_usage_count: int = Field(strict=True, ge=0, description='今日登记次数')
|
|
today_used_packages: int = Field(strict=True, ge=0, description='今日使用包数')
|
|
today_by_line: list[UsageLinePackagesModel] = Field(default_factory=list, description='今日用量按产线,从多到少')
|
|
tunnel_count: int = Field(strict=True, ge=0, description='启用隧道数')
|
|
active_tunnel_count: int = Field(strict=True, ge=0, description='正在使用的隧道数')
|
|
today_fifo_skip_count: int = Field(strict=True, ge=0, description='今日跳过先到托盘的使用次数')
|
|
week_fifo_skip_count: int = Field(strict=True, ge=0, description='近 7 天跳过先到托盘的使用次数')
|
|
tunnels: list[UsageBoardTunnelModel] = Field(default_factory=list, description='启用隧道,按编号排序')
|
|
lines: list[BusinessOptionModel] = Field(default_factory=list, 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='当前接口允许的业务动作')
|