功能:新增托盘业务接口模型
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
from datetime import datetime
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
PositiveId = Annotated[int, Field(gt=0)]
|
||||
|
||||
|
||||
def _ensure_unique_ids(value: list[int]) -> list[int]:
|
||||
if len(value) != len(set(value)):
|
||||
raise ValueError('ID列表不能包含重复项')
|
||||
return value
|
||||
|
||||
|
||||
class StrainModel(BaseModel):
|
||||
"""菌种主数据写入及展示模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
||||
strain_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='菌种编码')
|
||||
strain_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='菌种名称')
|
||||
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
|
||||
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 StrainPageQueryModel(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, description='每页记录数')
|
||||
strain_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='菌种编码')
|
||||
strain_name: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
pattern=r'\S',
|
||||
description='菌种名称',
|
||||
)
|
||||
status: Literal['0', '1'] | None = Field(default=None, description='状态')
|
||||
|
||||
|
||||
class DeleteStrainModel(BaseModel):
|
||||
"""删除菌种模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
strain_ids: list[PositiveId] = Field(min_length=1, description='需要删除的菌种ID')
|
||||
|
||||
_validate_unique_ids = field_validator('strain_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class CustomerModel(BaseModel):
|
||||
"""客户主数据写入及展示模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
customer_id: int | None = Field(default=None, gt=0, description='客户ID')
|
||||
customer_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='客户编码')
|
||||
customer_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='客户名称')
|
||||
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
|
||||
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 CustomerPageQueryModel(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, description='每页记录数')
|
||||
customer_code: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=64,
|
||||
pattern=r'\S',
|
||||
description='客户编码',
|
||||
)
|
||||
customer_name: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
pattern=r'\S',
|
||||
description='客户名称',
|
||||
)
|
||||
status: Literal['0', '1'] | None = Field(default=None, description='状态')
|
||||
|
||||
|
||||
class DeleteCustomerModel(BaseModel):
|
||||
"""删除客户模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
customer_ids: list[PositiveId] = Field(min_length=1, description='需要删除的客户ID')
|
||||
|
||||
_validate_unique_ids = field_validator('customer_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class SiteModel(BaseModel):
|
||||
"""客户现场主数据写入及展示模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
site_id: int | None = Field(default=None, gt=0, description='现场ID')
|
||||
customer_id: int = Field(gt=0, description='客户ID')
|
||||
site_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='现场编码')
|
||||
site_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='现场名称')
|
||||
address: str | None = Field(default=None, max_length=500, description='现场地址')
|
||||
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
|
||||
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 SitePageQueryModel(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, description='每页记录数')
|
||||
customer_id: int | None = Field(default=None, gt=0, description='客户ID')
|
||||
site_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='现场编码')
|
||||
site_name: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
pattern=r'\S',
|
||||
description='现场名称',
|
||||
)
|
||||
status: Literal['0', '1'] | None = Field(default=None, description='状态')
|
||||
|
||||
|
||||
class DeleteSiteModel(BaseModel):
|
||||
"""删除客户现场模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
site_ids: list[PositiveId] = Field(min_length=1, description='需要删除的现场ID')
|
||||
|
||||
_validate_unique_ids = field_validator('site_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class LineModel(BaseModel):
|
||||
"""生产线主数据写入及展示模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
line_id: int | None = Field(default=None, gt=0, description='产线ID')
|
||||
site_id: int = Field(gt=0, description='现场ID')
|
||||
line_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='产线编码')
|
||||
line_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='产线名称')
|
||||
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
|
||||
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 LinePageQueryModel(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, description='每页记录数')
|
||||
site_id: int | None = Field(default=None, gt=0, description='现场ID')
|
||||
line_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='产线编码')
|
||||
line_name: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
pattern=r'\S',
|
||||
description='产线名称',
|
||||
)
|
||||
status: Literal['0', '1'] | None = Field(default=None, description='状态')
|
||||
|
||||
|
||||
class DeleteLineModel(BaseModel):
|
||||
"""删除生产线模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
line_ids: list[PositiveId] = Field(min_length=1, description='需要删除的产线ID')
|
||||
|
||||
_validate_unique_ids = field_validator('line_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class WarehouseModel(BaseModel):
|
||||
"""仓库主数据写入及展示模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID')
|
||||
warehouse_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='仓库编码')
|
||||
warehouse_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='仓库名称')
|
||||
address: str | None = Field(default=None, max_length=500, description='仓库地址')
|
||||
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
|
||||
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 WarehousePageQueryModel(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, description='每页记录数')
|
||||
warehouse_code: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=64,
|
||||
pattern=r'\S',
|
||||
description='仓库编码',
|
||||
)
|
||||
warehouse_name: str | None = Field(
|
||||
default=None,
|
||||
min_length=1,
|
||||
max_length=100,
|
||||
pattern=r'\S',
|
||||
description='仓库名称',
|
||||
)
|
||||
status: Literal['0', '1'] | None = Field(default=None, description='状态')
|
||||
|
||||
|
||||
class DeleteWarehouseModel(BaseModel):
|
||||
"""删除仓库模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
warehouse_ids: list[PositiveId] = Field(min_length=1, description='需要删除的仓库ID')
|
||||
|
||||
_validate_unique_ids = field_validator('warehouse_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class PalletModel(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 = Field(min_length=1, max_length=128, pattern=r'\S', description='二维码固定标识')
|
||||
capacity_packages: int = Field(gt=0, description='容量包数')
|
||||
asset_status: Literal['0', '1'] = Field(description='资产状态(0正常 1停用)')
|
||||
remark: str | None = Field(default=None, max_length=500, 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='更新时间')
|
||||
can_remove: bool | None = Field(default=None, description='是否允许删除')
|
||||
|
||||
|
||||
class PalletCreateModel(BaseModel):
|
||||
"""新增托盘模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
capacity_packages: int = Field(strict=True, gt=0, description='容量包数')
|
||||
remark: str | None = Field(default=None, max_length=500, description='备注')
|
||||
|
||||
|
||||
class PalletEditModel(BaseModel):
|
||||
"""编辑托盘可变属性模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
capacity_packages: int = Field(strict=True, gt=0, description='容量包数')
|
||||
asset_status: Literal['0', '1'] = Field(description='资产状态(0正常 1停用)')
|
||||
remark: str | None = Field(default=None, max_length=500, description='备注')
|
||||
|
||||
|
||||
class PalletPageQueryModel(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, description='每页记录数')
|
||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
asset_status: Literal['0', '1'] | None = Field(default=None, description='资产状态')
|
||||
|
||||
|
||||
class DeletePalletModel(BaseModel):
|
||||
"""删除托盘模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
pallet_ids: list[PositiveId] = Field(min_length=1, description='需要删除的托盘ID')
|
||||
|
||||
_validate_unique_ids = field_validator('pallet_ids')(_ensure_unique_ids)
|
||||
|
||||
|
||||
class UserAssignModel(BaseModel):
|
||||
"""用户数据范围分配模型。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
from_attributes=True,
|
||||
populate_by_name=True,
|
||||
extra='forbid',
|
||||
)
|
||||
|
||||
user_ids: list[PositiveId] = Field(min_length=1, description='用户ID列表')
|
||||
|
||||
_validate_unique_ids = field_validator('user_ids')(_ensure_unique_ids)
|
||||
@@ -0,0 +1,463 @@
|
||||
from datetime import 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
|
||||
|
||||
|
||||
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='生产批次')
|
||||
package_spec: str = Field(min_length=1, max_length=64, pattern=r'\S', description='包装规格')
|
||||
initial_packages: int = Field(strict=True, gt=0, description='初始包数')
|
||||
idempotency_key: str = Field(min_length=1, max_length=64, pattern=r'\S', description='幂等键')
|
||||
|
||||
|
||||
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='二维码固定标识')
|
||||
line_id: int = Field(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='幂等键')
|
||||
|
||||
|
||||
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='生产批次')
|
||||
package_spec: str = Field(min_length=1, max_length=64, pattern=r'\S', description='包装规格')
|
||||
initial_packages: int = Field(ge=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, 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='确认时间')
|
||||
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')
|
||||
line_id: int = Field(gt=0, description='产线ID')
|
||||
used_packages: int = Field(ge=0, description='本次使用包数')
|
||||
remaining_packages_after: int = Field(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='发生时间')
|
||||
|
||||
|
||||
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='申请时活动装填记录快照')
|
||||
remaining_packages_snapshot: int = Field(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, 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='结束时间')
|
||||
|
||||
|
||||
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, 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='结束时间')
|
||||
|
||||
|
||||
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, 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='结束时间')
|
||||
|
||||
|
||||
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, 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')
|
||||
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='结束时间')
|
||||
|
||||
|
||||
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, 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='结束时间')
|
||||
|
||||
|
||||
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='托盘编号')
|
||||
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')
|
||||
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='生产批次')
|
||||
package_spec: str | None = Field(default=None, max_length=64, description='包装规格')
|
||||
capacity_packages: int = Field(gt=0, description='容量包数')
|
||||
remaining_packages: int = Field(ge=0, description='当前剩余包数')
|
||||
allowed_actions: list[NonBlankAction] = Field(description='当前接口允许的业务动作')
|
||||
@@ -0,0 +1,245 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
from module_business.domain.enums import DocumentStatus, PalletEventType, PalletFlowState, ReturnStatus
|
||||
|
||||
|
||||
class PalletInventoryReportQueryModel(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, 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='结束时间')
|
||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
flow_state: PalletFlowState | None = Field(default=None, description='托盘流转状态')
|
||||
warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID')
|
||||
site_id: int | None = Field(default=None, gt=0, description='现场ID')
|
||||
line_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='生产批次')
|
||||
|
||||
|
||||
class PalletInventoryReportModel(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='托盘编号')
|
||||
flow_state: PalletFlowState = Field(description='托盘流转状态')
|
||||
warehouse_id: int | None = Field(default=None, gt=0, description='当前仓库ID')
|
||||
warehouse_name: str | None = Field(default=None, max_length=100, description='当前仓库名称')
|
||||
site_id: int | None = Field(default=None, gt=0, description='当前现场ID')
|
||||
site_name: str | None = Field(default=None, max_length=100, description='当前现场名称')
|
||||
line_id: int | None = Field(default=None, gt=0, description='当前产线ID')
|
||||
line_name: str | None = Field(default=None, max_length=100, description='当前产线名称')
|
||||
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='当前生产批次')
|
||||
initial_packages: int | None = Field(default=None, ge=0, description='初始包数')
|
||||
remaining_packages: int = Field(ge=0, description='当前剩余包数')
|
||||
updated_at: datetime = Field(description='库存更新时间')
|
||||
|
||||
|
||||
class FillingReportQueryModel(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, 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='结束时间')
|
||||
warehouse_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='托盘编号')
|
||||
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='生产批次')
|
||||
|
||||
|
||||
class FillingReportModel(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='装填单号')
|
||||
operated_at: datetime = Field(description='装填时间')
|
||||
warehouse_id: int = Field(gt=0, description='仓库ID')
|
||||
warehouse_name: str | None = Field(default=None, max_length=100, description='仓库名称')
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
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='生产批次')
|
||||
package_spec: str = Field(min_length=1, max_length=64, pattern=r'\S', description='包装规格')
|
||||
initial_packages: int = Field(ge=0, description='初始包数')
|
||||
operator_id: int = Field(gt=0, description='操作人ID')
|
||||
operator_name: str | None = Field(default=None, max_length=100, description='操作人名称')
|
||||
|
||||
|
||||
class ShipmentReportQueryModel(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, 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='结束时间')
|
||||
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')
|
||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
status: DocumentStatus | None = Field(default=None, description='单据状态')
|
||||
|
||||
|
||||
class ShipmentReportModel(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='发货单号')
|
||||
confirmed_at: datetime = Field(description='确认发货时间')
|
||||
source_warehouse_id: int = Field(gt=0, description='来源仓库ID')
|
||||
source_warehouse_name: str | None = Field(default=None, max_length=100, description='来源仓库名称')
|
||||
target_site_id: int = Field(gt=0, description='目标现场ID')
|
||||
target_site_name: str | None = Field(default=None, max_length=100, description='目标现场名称')
|
||||
customer_id: int = Field(gt=0, description='客户ID')
|
||||
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
active_filling_id: int | None = Field(default=None, gt=0, description='发货时活动装填记录ID')
|
||||
package_qty: int = Field(ge=0, description='发货时包数快照')
|
||||
|
||||
|
||||
class ConsumptionReportQueryModel(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, 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='结束时间')
|
||||
customer_id: int | None = Field(default=None, gt=0, description='客户ID')
|
||||
site_id: int | None = Field(default=None, gt=0, description='现场ID')
|
||||
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='托盘编号')
|
||||
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='生产批次')
|
||||
|
||||
|
||||
class ConsumptionReportModel(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='使用记录号')
|
||||
occurred_at: datetime = Field(description='使用发生时间')
|
||||
customer_id: int = Field(gt=0, description='客户ID')
|
||||
customer_name: str | None = Field(default=None, max_length=100, description='客户名称')
|
||||
site_id: int = Field(gt=0, description='现场ID')
|
||||
site_name: str | None = Field(default=None, max_length=100, description='现场名称')
|
||||
line_id: int = Field(gt=0, description='产线ID')
|
||||
line_name: str | None = Field(default=None, max_length=100, description='产线名称')
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
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, pattern=r'\S', description='生产批次')
|
||||
used_packages: int = Field(ge=0, description='本次使用包数')
|
||||
remaining_packages_after: int = Field(ge=0, description='使用后剩余包数')
|
||||
operator_id: int = Field(gt=0, description='操作人ID')
|
||||
operator_name: str | None = Field(default=None, max_length=100, description='操作人名称')
|
||||
|
||||
|
||||
class ReturnReportQueryModel(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, 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='结束时间')
|
||||
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')
|
||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
status: ReturnStatus | None = Field(default=None, description='退回状态')
|
||||
|
||||
|
||||
class ReturnReportModel(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='退回单号')
|
||||
status: ReturnStatus = Field(description='退回状态')
|
||||
requested_at: datetime = Field(description='申请时间')
|
||||
confirmed_at: datetime | None = Field(default=None, description='确认时间')
|
||||
source_site_id: int = Field(gt=0, description='来源现场ID')
|
||||
source_site_name: str | None = Field(default=None, max_length=100, description='来源现场名称')
|
||||
target_warehouse_id: int = Field(gt=0, description='目标仓库ID')
|
||||
target_warehouse_name: str | None = Field(default=None, max_length=100, description='目标仓库名称')
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
active_filling_id_snapshot: int | None = Field(default=None, gt=0, description='活动装填记录快照')
|
||||
remaining_packages_snapshot: int = Field(ge=0, description='剩余包数快照')
|
||||
|
||||
|
||||
class PalletTraceReportQueryModel(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, 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='结束时间')
|
||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
event_type: PalletEventType | None = Field(default=None, description='事件类型')
|
||||
|
||||
|
||||
class PalletTraceReportModel(BaseModel):
|
||||
"""托盘追溯报表行模型。"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||
|
||||
event_id: int = Field(gt=0, description='事件ID')
|
||||
event_seq: int = Field(gt=0, description='托盘内事件序号')
|
||||
event_type: PalletEventType = Field(description='事件类型')
|
||||
source_type: str = Field(min_length=1, max_length=32, pattern=r'\S', description='来源类型')
|
||||
source_id: int = Field(gt=0, description='来源记录ID')
|
||||
pallet_id: int = Field(gt=0, description='托盘ID')
|
||||
pallet_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||
before_state: PalletFlowState = Field(description='变更前状态')
|
||||
after_state: PalletFlowState = Field(description='变更后状态')
|
||||
before_warehouse_id: int | None = Field(default=None, gt=0, description='变更前仓库ID')
|
||||
before_warehouse_name: str | None = Field(default=None, max_length=100, description='变更前仓库名称')
|
||||
before_site_id: int | None = Field(default=None, gt=0, description='变更前现场ID')
|
||||
before_site_name: str | None = Field(default=None, max_length=100, description='变更前现场名称')
|
||||
before_line_id: int | None = Field(default=None, gt=0, description='变更前产线ID')
|
||||
before_line_name: str | None = Field(default=None, max_length=100, description='变更前产线名称')
|
||||
after_warehouse_id: int | None = Field(default=None, gt=0, description='变更后仓库ID')
|
||||
after_warehouse_name: str | None = Field(default=None, max_length=100, description='变更后仓库名称')
|
||||
after_site_id: int | None = Field(default=None, gt=0, description='变更后现场ID')
|
||||
after_site_name: str | None = Field(default=None, max_length=100, description='变更后现场名称')
|
||||
after_line_id: int | None = Field(default=None, gt=0, description='变更后产线ID')
|
||||
after_line_name: str | None = Field(default=None, max_length=100, description='变更后产线名称')
|
||||
before_remaining_packages: int = Field(ge=0, description='变更前剩余包数')
|
||||
after_remaining_packages: int = Field(ge=0, description='变更后剩余包数')
|
||||
operator_id: int = Field(gt=0, description='操作人ID')
|
||||
operator_name: str | None = Field(default=None, max_length=100, description='操作人名称')
|
||||
occurred_at: datetime = Field(description='发生时间')
|
||||
@@ -0,0 +1,785 @@
|
||||
import unittest
|
||||
|
||||
from pydantic import ValidationError
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
from module_business.entity.vo.master_vo import (
|
||||
CustomerModel,
|
||||
CustomerPageQueryModel,
|
||||
DeleteCustomerModel,
|
||||
DeleteLineModel,
|
||||
DeletePalletModel,
|
||||
DeleteSiteModel,
|
||||
DeleteStrainModel,
|
||||
DeleteWarehouseModel,
|
||||
LineModel,
|
||||
LinePageQueryModel,
|
||||
PalletCreateModel,
|
||||
PalletEditModel,
|
||||
PalletModel,
|
||||
PalletPageQueryModel,
|
||||
SiteModel,
|
||||
SitePageQueryModel,
|
||||
StrainModel,
|
||||
StrainPageQueryModel,
|
||||
UserAssignModel,
|
||||
WarehouseModel,
|
||||
WarehousePageQueryModel,
|
||||
)
|
||||
from module_business.entity.vo.operation_vo import (
|
||||
BusinessOptionModel,
|
||||
ConfirmCommandModel,
|
||||
FillingCreateModel,
|
||||
FillingModel,
|
||||
FillingPageQueryModel,
|
||||
InboundCreateModel,
|
||||
InboundItemModel,
|
||||
InboundModel,
|
||||
InboundPageQueryModel,
|
||||
InboundScanQueryModel,
|
||||
InboundUpdateModel,
|
||||
PalletScanResponseModel,
|
||||
ReturnConfirmModel,
|
||||
ReturnItemModel,
|
||||
ReturnModel,
|
||||
ReturnPageQueryModel,
|
||||
ReturnRequestModel,
|
||||
ReturnScanQueryModel,
|
||||
ShipmentCreateModel,
|
||||
ShipmentItemModel,
|
||||
ShipmentModel,
|
||||
ShipmentPageQueryModel,
|
||||
ShipmentScanQueryModel,
|
||||
ShipmentUpdateModel,
|
||||
UsageCreateModel,
|
||||
UsageModel,
|
||||
UsagePageQueryModel,
|
||||
)
|
||||
from module_business.entity.vo.report_vo import (
|
||||
ConsumptionReportModel,
|
||||
ConsumptionReportQueryModel,
|
||||
FillingReportModel,
|
||||
FillingReportQueryModel,
|
||||
PalletInventoryReportModel,
|
||||
PalletInventoryReportQueryModel,
|
||||
PalletTraceReportModel,
|
||||
PalletTraceReportQueryModel,
|
||||
ReturnReportModel,
|
||||
ReturnReportQueryModel,
|
||||
ShipmentReportModel,
|
||||
ShipmentReportQueryModel,
|
||||
)
|
||||
|
||||
MASTER_MODEL_FIELDS = {
|
||||
StrainModel: {
|
||||
'strain_id',
|
||||
'strain_code',
|
||||
'strain_name',
|
||||
'status',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
StrainPageQueryModel: {'page_num', 'page_size', 'strain_code', 'strain_name', 'status'},
|
||||
DeleteStrainModel: {'strain_ids'},
|
||||
CustomerModel: {
|
||||
'customer_id',
|
||||
'customer_code',
|
||||
'customer_name',
|
||||
'status',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
CustomerPageQueryModel: {'page_num', 'page_size', 'customer_code', 'customer_name', 'status'},
|
||||
DeleteCustomerModel: {'customer_ids'},
|
||||
SiteModel: {
|
||||
'site_id',
|
||||
'customer_id',
|
||||
'site_code',
|
||||
'site_name',
|
||||
'address',
|
||||
'status',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
SitePageQueryModel: {'page_num', 'page_size', 'customer_id', 'site_code', 'site_name', 'status'},
|
||||
DeleteSiteModel: {'site_ids'},
|
||||
LineModel: {
|
||||
'line_id',
|
||||
'site_id',
|
||||
'line_code',
|
||||
'line_name',
|
||||
'status',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
LinePageQueryModel: {'page_num', 'page_size', 'site_id', 'line_code', 'line_name', 'status'},
|
||||
DeleteLineModel: {'line_ids'},
|
||||
WarehouseModel: {
|
||||
'warehouse_id',
|
||||
'warehouse_code',
|
||||
'warehouse_name',
|
||||
'address',
|
||||
'status',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
WarehousePageQueryModel: {'page_num', 'page_size', 'warehouse_code', 'warehouse_name', 'status'},
|
||||
DeleteWarehouseModel: {'warehouse_ids'},
|
||||
PalletModel: {
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'qr_token',
|
||||
'capacity_packages',
|
||||
'asset_status',
|
||||
'remark',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'can_remove',
|
||||
},
|
||||
PalletCreateModel: {'pallet_code', 'capacity_packages', 'remark'},
|
||||
PalletEditModel: {'pallet_id', 'capacity_packages', 'asset_status', 'remark'},
|
||||
PalletPageQueryModel: {'page_num', 'page_size', 'pallet_code', 'asset_status'},
|
||||
DeletePalletModel: {'pallet_ids'},
|
||||
UserAssignModel: {'user_ids'},
|
||||
}
|
||||
|
||||
OPERATION_MODEL_FIELDS = {
|
||||
InboundCreateModel: {'warehouse_id', 'pallet_ids'},
|
||||
InboundUpdateModel: {'inbound_id', 'warehouse_id', 'pallet_ids'},
|
||||
ConfirmCommandModel: {'idempotency_key'},
|
||||
FillingCreateModel: {
|
||||
'qr_token',
|
||||
'warehouse_id',
|
||||
'strain_id',
|
||||
'batch_no',
|
||||
'package_spec',
|
||||
'initial_packages',
|
||||
'idempotency_key',
|
||||
},
|
||||
ShipmentCreateModel: {'source_warehouse_id', 'target_site_id', 'pallet_ids'},
|
||||
ShipmentUpdateModel: {'shipment_id', 'source_warehouse_id', 'target_site_id', 'pallet_ids'},
|
||||
UsageCreateModel: {'qr_token', 'line_id', 'used_packages', 'idempotency_key'},
|
||||
ReturnRequestModel: {'source_site_id', 'target_warehouse_id', 'pallet_ids', 'idempotency_key'},
|
||||
ReturnConfirmModel: {'scanned_pallet_ids', 'idempotency_key'},
|
||||
InboundItemModel: {'inbound_item_id', 'inbound_id', 'pallet_id', 'create_by', 'create_time'},
|
||||
InboundModel: {
|
||||
'inbound_id',
|
||||
'inbound_no',
|
||||
'warehouse_id',
|
||||
'status',
|
||||
'confirm_idempotency_key',
|
||||
'confirm_request_hash',
|
||||
'confirmed_by',
|
||||
'confirmed_at',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'items',
|
||||
},
|
||||
FillingModel: {
|
||||
'filling_id',
|
||||
'filling_no',
|
||||
'pallet_id',
|
||||
'warehouse_id',
|
||||
'strain_id',
|
||||
'strain_code_snapshot',
|
||||
'strain_name_snapshot',
|
||||
'batch_no',
|
||||
'package_spec',
|
||||
'initial_packages',
|
||||
'active_slot',
|
||||
'closed_at',
|
||||
'idempotency_key',
|
||||
'request_hash',
|
||||
'operator_id',
|
||||
'operated_at',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
},
|
||||
ShipmentItemModel: {
|
||||
'shipment_item_id',
|
||||
'shipment_id',
|
||||
'pallet_id',
|
||||
'active_filling_id',
|
||||
'package_qty',
|
||||
'create_by',
|
||||
'create_time',
|
||||
},
|
||||
ShipmentModel: {
|
||||
'shipment_id',
|
||||
'shipment_no',
|
||||
'source_warehouse_id',
|
||||
'target_site_id',
|
||||
'status',
|
||||
'confirm_idempotency_key',
|
||||
'confirm_request_hash',
|
||||
'confirmed_by',
|
||||
'confirmed_at',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'items',
|
||||
},
|
||||
UsageModel: {
|
||||
'usage_id',
|
||||
'usage_no',
|
||||
'pallet_id',
|
||||
'active_filling_id',
|
||||
'site_id',
|
||||
'line_id',
|
||||
'used_packages',
|
||||
'remaining_packages_after',
|
||||
'idempotency_key',
|
||||
'request_hash',
|
||||
'operator_id',
|
||||
'occurred_at',
|
||||
},
|
||||
ReturnItemModel: {
|
||||
'return_item_id',
|
||||
'return_id',
|
||||
'pallet_id',
|
||||
'active_filling_id_snapshot',
|
||||
'remaining_packages_snapshot',
|
||||
'create_by',
|
||||
'create_time',
|
||||
},
|
||||
ReturnModel: {
|
||||
'return_id',
|
||||
'return_no',
|
||||
'source_site_id',
|
||||
'target_warehouse_id',
|
||||
'status',
|
||||
'request_idempotency_key',
|
||||
'request_hash',
|
||||
'confirm_idempotency_key',
|
||||
'confirm_request_hash',
|
||||
'requested_by',
|
||||
'requested_at',
|
||||
'confirmed_by',
|
||||
'confirmed_at',
|
||||
'create_by',
|
||||
'create_time',
|
||||
'update_by',
|
||||
'update_time',
|
||||
'remark',
|
||||
'items',
|
||||
},
|
||||
InboundPageQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'inbound_no',
|
||||
'warehouse_id',
|
||||
'status',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
},
|
||||
FillingPageQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'filling_no',
|
||||
'warehouse_id',
|
||||
'strain_id',
|
||||
'batch_no',
|
||||
'pallet_code',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
},
|
||||
ShipmentPageQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'shipment_no',
|
||||
'source_warehouse_id',
|
||||
'target_site_id',
|
||||
'status',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
},
|
||||
UsagePageQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'usage_no',
|
||||
'site_id',
|
||||
'line_id',
|
||||
'pallet_code',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
},
|
||||
ReturnPageQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'return_no',
|
||||
'source_site_id',
|
||||
'target_warehouse_id',
|
||||
'status',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
},
|
||||
InboundScanQueryModel: {'warehouse_id'},
|
||||
ShipmentScanQueryModel: {'source_warehouse_id', 'target_site_id'},
|
||||
ReturnScanQueryModel: {'mode', 'return_id'},
|
||||
BusinessOptionModel: {'id', 'code', 'name'},
|
||||
PalletScanResponseModel: {
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'flow_state',
|
||||
'warehouse_id',
|
||||
'site_id',
|
||||
'current_line_id',
|
||||
'active_filling_id',
|
||||
'strain_id',
|
||||
'strain_code',
|
||||
'strain_name',
|
||||
'batch_no',
|
||||
'package_spec',
|
||||
'capacity_packages',
|
||||
'remaining_packages',
|
||||
'allowed_actions',
|
||||
},
|
||||
}
|
||||
|
||||
REPORT_MODEL_FIELDS = {
|
||||
PalletInventoryReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'pallet_code',
|
||||
'flow_state',
|
||||
'warehouse_id',
|
||||
'site_id',
|
||||
'line_id',
|
||||
'strain_id',
|
||||
'batch_no',
|
||||
},
|
||||
PalletInventoryReportModel: {
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'flow_state',
|
||||
'warehouse_id',
|
||||
'warehouse_name',
|
||||
'site_id',
|
||||
'site_name',
|
||||
'line_id',
|
||||
'line_name',
|
||||
'strain_id',
|
||||
'strain_code',
|
||||
'strain_name',
|
||||
'batch_no',
|
||||
'initial_packages',
|
||||
'remaining_packages',
|
||||
'updated_at',
|
||||
},
|
||||
FillingReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'warehouse_id',
|
||||
'pallet_code',
|
||||
'strain_id',
|
||||
'batch_no',
|
||||
},
|
||||
FillingReportModel: {
|
||||
'filling_id',
|
||||
'filling_no',
|
||||
'operated_at',
|
||||
'warehouse_id',
|
||||
'warehouse_name',
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'strain_id',
|
||||
'strain_code_snapshot',
|
||||
'strain_name_snapshot',
|
||||
'batch_no',
|
||||
'package_spec',
|
||||
'initial_packages',
|
||||
'operator_id',
|
||||
'operator_name',
|
||||
},
|
||||
ShipmentReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'shipment_no',
|
||||
'source_warehouse_id',
|
||||
'target_site_id',
|
||||
'pallet_code',
|
||||
'status',
|
||||
},
|
||||
ShipmentReportModel: {
|
||||
'shipment_id',
|
||||
'shipment_no',
|
||||
'confirmed_at',
|
||||
'source_warehouse_id',
|
||||
'source_warehouse_name',
|
||||
'target_site_id',
|
||||
'target_site_name',
|
||||
'customer_id',
|
||||
'customer_name',
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'active_filling_id',
|
||||
'package_qty',
|
||||
},
|
||||
ConsumptionReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'customer_id',
|
||||
'site_id',
|
||||
'line_id',
|
||||
'pallet_code',
|
||||
'strain_id',
|
||||
'batch_no',
|
||||
},
|
||||
ConsumptionReportModel: {
|
||||
'usage_id',
|
||||
'usage_no',
|
||||
'occurred_at',
|
||||
'customer_id',
|
||||
'customer_name',
|
||||
'site_id',
|
||||
'site_name',
|
||||
'line_id',
|
||||
'line_name',
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'strain_id',
|
||||
'strain_code',
|
||||
'strain_name',
|
||||
'batch_no',
|
||||
'used_packages',
|
||||
'remaining_packages_after',
|
||||
'operator_id',
|
||||
'operator_name',
|
||||
},
|
||||
ReturnReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'return_no',
|
||||
'source_site_id',
|
||||
'target_warehouse_id',
|
||||
'pallet_code',
|
||||
'status',
|
||||
},
|
||||
ReturnReportModel: {
|
||||
'return_id',
|
||||
'return_no',
|
||||
'status',
|
||||
'requested_at',
|
||||
'confirmed_at',
|
||||
'source_site_id',
|
||||
'source_site_name',
|
||||
'target_warehouse_id',
|
||||
'target_warehouse_name',
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'active_filling_id_snapshot',
|
||||
'remaining_packages_snapshot',
|
||||
},
|
||||
PalletTraceReportQueryModel: {
|
||||
'page_num',
|
||||
'page_size',
|
||||
'begin_time',
|
||||
'end_time',
|
||||
'pallet_code',
|
||||
'event_type',
|
||||
},
|
||||
PalletTraceReportModel: {
|
||||
'event_id',
|
||||
'event_seq',
|
||||
'event_type',
|
||||
'source_type',
|
||||
'source_id',
|
||||
'pallet_id',
|
||||
'pallet_code',
|
||||
'before_state',
|
||||
'after_state',
|
||||
'before_warehouse_id',
|
||||
'before_warehouse_name',
|
||||
'before_site_id',
|
||||
'before_site_name',
|
||||
'before_line_id',
|
||||
'before_line_name',
|
||||
'after_warehouse_id',
|
||||
'after_warehouse_name',
|
||||
'after_site_id',
|
||||
'after_site_name',
|
||||
'after_line_id',
|
||||
'after_line_name',
|
||||
'before_remaining_packages',
|
||||
'after_remaining_packages',
|
||||
'operator_id',
|
||||
'operator_name',
|
||||
'occurred_at',
|
||||
},
|
||||
}
|
||||
|
||||
WRITE_MODELS = (
|
||||
StrainModel,
|
||||
DeleteStrainModel,
|
||||
CustomerModel,
|
||||
DeleteCustomerModel,
|
||||
SiteModel,
|
||||
DeleteSiteModel,
|
||||
LineModel,
|
||||
DeleteLineModel,
|
||||
WarehouseModel,
|
||||
DeleteWarehouseModel,
|
||||
PalletCreateModel,
|
||||
PalletEditModel,
|
||||
DeletePalletModel,
|
||||
UserAssignModel,
|
||||
InboundCreateModel,
|
||||
InboundUpdateModel,
|
||||
ConfirmCommandModel,
|
||||
FillingCreateModel,
|
||||
ShipmentCreateModel,
|
||||
ShipmentUpdateModel,
|
||||
UsageCreateModel,
|
||||
ReturnRequestModel,
|
||||
ReturnConfirmModel,
|
||||
)
|
||||
|
||||
PAGE_QUERY_MODELS = (
|
||||
StrainPageQueryModel,
|
||||
CustomerPageQueryModel,
|
||||
SitePageQueryModel,
|
||||
LinePageQueryModel,
|
||||
WarehousePageQueryModel,
|
||||
PalletPageQueryModel,
|
||||
InboundPageQueryModel,
|
||||
FillingPageQueryModel,
|
||||
ShipmentPageQueryModel,
|
||||
UsagePageQueryModel,
|
||||
ReturnPageQueryModel,
|
||||
PalletInventoryReportQueryModel,
|
||||
FillingReportQueryModel,
|
||||
ShipmentReportQueryModel,
|
||||
ConsumptionReportQueryModel,
|
||||
ReturnReportQueryModel,
|
||||
PalletTraceReportQueryModel,
|
||||
)
|
||||
|
||||
|
||||
class BusinessVoContractTest(unittest.TestCase):
|
||||
def test_all_models_have_exact_fields_and_camel_case_aliases(self) -> None:
|
||||
expected_models = MASTER_MODEL_FIELDS | OPERATION_MODEL_FIELDS | REPORT_MODEL_FIELDS
|
||||
|
||||
for model_class, expected_fields in expected_models.items():
|
||||
with self.subTest(model=model_class.__name__):
|
||||
self.assertEqual(expected_fields, set(model_class.model_fields))
|
||||
self.assertTrue(model_class.model_config.get('from_attributes'))
|
||||
self.assertTrue(model_class.model_config.get('populate_by_name'))
|
||||
self.assertIs(to_camel, model_class.model_config.get('alias_generator'))
|
||||
aliases = {name: field.alias for name, field in model_class.model_fields.items()}
|
||||
self.assertEqual({name: to_camel(name) for name in expected_fields}, aliases)
|
||||
|
||||
def test_write_models_forbid_extra_fields(self) -> None:
|
||||
for model_class in WRITE_MODELS:
|
||||
with self.subTest(model=model_class.__name__):
|
||||
self.assertEqual('forbid', model_class.model_config.get('extra'))
|
||||
|
||||
def test_page_queries_have_positive_defaults(self) -> None:
|
||||
for model_class in PAGE_QUERY_MODELS:
|
||||
with self.subTest(model=model_class.__name__):
|
||||
model = model_class.model_validate({})
|
||||
self.assertEqual((1, 10), (model.page_num, model.page_size))
|
||||
with self.assertRaises(ValidationError):
|
||||
model_class.model_validate({'pageNum': 0})
|
||||
with self.assertRaises(ValidationError):
|
||||
model_class.model_validate({'pageSize': 0})
|
||||
|
||||
def test_filling_accepts_camel_case_and_positive_packages(self) -> None:
|
||||
model = FillingCreateModel.model_validate(
|
||||
{
|
||||
'qrToken': 'qr-12345678',
|
||||
'warehouseId': 1,
|
||||
'strainId': 2,
|
||||
'batchNo': 'B20260724',
|
||||
'packageSpec': '20kg/包',
|
||||
'initialPackages': 10,
|
||||
'idempotencyKey': 'fill-key-1',
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(10, model.initial_packages)
|
||||
self.assertEqual(10, model.model_dump(by_alias=True)['initialPackages'])
|
||||
|
||||
def test_usage_rejects_zero_packages(self) -> None:
|
||||
with self.assertRaises(ValidationError):
|
||||
UsageCreateModel.model_validate(
|
||||
{
|
||||
'qrToken': 'qr-12345678',
|
||||
'lineId': 3,
|
||||
'usedPackages': 0,
|
||||
'idempotencyKey': 'usage-key-1',
|
||||
}
|
||||
)
|
||||
|
||||
def test_usage_rejects_non_integer_packages(self) -> None:
|
||||
for used_packages in (1.5, '1', True):
|
||||
with self.subTest(used_packages=used_packages), self.assertRaises(ValidationError):
|
||||
UsageCreateModel.model_validate(
|
||||
{
|
||||
'qrToken': 'qr-12345678',
|
||||
'lineId': 3,
|
||||
'usedPackages': used_packages,
|
||||
'idempotencyKey': 'usage-key-1',
|
||||
}
|
||||
)
|
||||
|
||||
def test_other_package_inputs_are_strict_positive_integers(self) -> None:
|
||||
cases = (
|
||||
(
|
||||
PalletCreateModel,
|
||||
{'palletCode': 'PLT-001', 'capacityPackages': 10, 'remark': None},
|
||||
'capacityPackages',
|
||||
),
|
||||
(
|
||||
FillingCreateModel,
|
||||
{
|
||||
'qrToken': 'qr-12345678',
|
||||
'warehouseId': 1,
|
||||
'strainId': 2,
|
||||
'batchNo': 'B20260724',
|
||||
'packageSpec': '20kg/包',
|
||||
'initialPackages': 10,
|
||||
'idempotencyKey': 'fill-key-1',
|
||||
},
|
||||
'initialPackages',
|
||||
),
|
||||
)
|
||||
|
||||
for model_class, payload, field_name in cases:
|
||||
for invalid_value in (0, 1.5, '1', True):
|
||||
with self.subTest(model=model_class.__name__, value=invalid_value), self.assertRaises(ValidationError):
|
||||
model_class.model_validate(payload | {field_name: invalid_value})
|
||||
|
||||
def test_id_lists_reject_empty_non_positive_and_duplicate_values(self) -> None:
|
||||
cases = (
|
||||
(DeleteStrainModel, {}, 'strainIds'),
|
||||
(DeleteCustomerModel, {}, 'customerIds'),
|
||||
(DeleteSiteModel, {}, 'siteIds'),
|
||||
(DeleteLineModel, {}, 'lineIds'),
|
||||
(DeleteWarehouseModel, {}, 'warehouseIds'),
|
||||
(DeletePalletModel, {}, 'palletIds'),
|
||||
(UserAssignModel, {}, 'userIds'),
|
||||
(InboundCreateModel, {'warehouseId': 1}, 'palletIds'),
|
||||
(InboundUpdateModel, {'inboundId': 1, 'warehouseId': 1}, 'palletIds'),
|
||||
(ShipmentCreateModel, {'sourceWarehouseId': 1, 'targetSiteId': 2}, 'palletIds'),
|
||||
(
|
||||
ShipmentUpdateModel,
|
||||
{'shipmentId': 1, 'sourceWarehouseId': 1, 'targetSiteId': 2},
|
||||
'palletIds',
|
||||
),
|
||||
(
|
||||
ReturnRequestModel,
|
||||
{'sourceSiteId': 1, 'targetWarehouseId': 2, 'idempotencyKey': 'return-key-1'},
|
||||
'palletIds',
|
||||
),
|
||||
(ReturnConfirmModel, {'idempotencyKey': 'return-confirm-key-1'}, 'scannedPalletIds'),
|
||||
)
|
||||
|
||||
for model_class, payload, field_name in cases:
|
||||
for invalid_value in ([], [0], [1, 1]):
|
||||
with self.subTest(model=model_class.__name__, value=invalid_value), self.assertRaises(ValidationError):
|
||||
model_class.model_validate(payload | {field_name: invalid_value})
|
||||
|
||||
def test_pallet_edit_forbids_identity_fields(self) -> None:
|
||||
valid_payload = {
|
||||
'palletId': 1,
|
||||
'capacityPackages': 20,
|
||||
'assetStatus': '0',
|
||||
'remark': None,
|
||||
}
|
||||
|
||||
for field_name in ('palletCode', 'qrToken'):
|
||||
with self.subTest(field=field_name), self.assertRaises(ValidationError):
|
||||
PalletEditModel.model_validate(valid_payload | {field_name: 'immutable'})
|
||||
|
||||
def test_return_scan_mode_controls_return_id(self) -> None:
|
||||
request_model = ReturnScanQueryModel.model_validate({'mode': 'request'})
|
||||
confirm_model = ReturnScanQueryModel.model_validate({'mode': 'confirm', 'returnId': 9})
|
||||
|
||||
self.assertIsNone(request_model.return_id)
|
||||
self.assertEqual(9, confirm_model.return_id)
|
||||
for payload in (
|
||||
{'mode': 'confirm'},
|
||||
{'mode': 'request', 'returnId': 9},
|
||||
{'mode': 'other'},
|
||||
):
|
||||
with self.subTest(payload=payload), self.assertRaises(ValidationError):
|
||||
ReturnScanQueryModel.model_validate(payload)
|
||||
|
||||
def test_required_business_strings_reject_whitespace(self) -> None:
|
||||
with self.assertRaises(ValidationError):
|
||||
ConfirmCommandModel.model_validate({'idempotencyKey': ' '})
|
||||
with self.assertRaises(ValidationError):
|
||||
StrainModel.model_validate({'strainCode': '\t', 'strainName': '菌种 A', 'status': '0'})
|
||||
with self.assertRaises(ValidationError):
|
||||
FillingCreateModel.model_validate(
|
||||
{
|
||||
'qrToken': 'qr-12345678',
|
||||
'warehouseId': 1,
|
||||
'strainId': 2,
|
||||
'batchNo': ' ',
|
||||
'packageSpec': '20kg/包',
|
||||
'initialPackages': 10,
|
||||
'idempotencyKey': 'fill-key-1',
|
||||
}
|
||||
)
|
||||
|
||||
def test_business_ids_must_be_positive(self) -> None:
|
||||
for model_class, payload in (
|
||||
(InboundScanQueryModel, {'warehouseId': 0}),
|
||||
(ShipmentScanQueryModel, {'sourceWarehouseId': 1, 'targetSiteId': -1}),
|
||||
(BusinessOptionModel, {'id': 0, 'code': 'WH-01', 'name': '一号仓'}),
|
||||
):
|
||||
with self.subTest(model=model_class.__name__), self.assertRaises(ValidationError):
|
||||
model_class.model_validate(payload)
|
||||
|
||||
def test_non_negative_output_quantities_are_validated(self) -> None:
|
||||
with self.assertRaises(ValidationError):
|
||||
PalletScanResponseModel.model_validate(
|
||||
{
|
||||
'palletId': 1,
|
||||
'palletCode': 'PLT-001',
|
||||
'flowState': 'AT_SITE_PARTIAL',
|
||||
'capacityPackages': 20,
|
||||
'remainingPackages': -1,
|
||||
'allowedActions': [],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user