Files
ShiErFeng/shierfeng-fastapi-backend/module_business/entity/vo/master_vo.py
T
yujiangandClaude Opus 5.5 3050ccdc4e Feat: 后端新增基础资料与业务管理只读汇总接口
为 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>
2026-10-18 05:03:10 -07:00

475 lines
19 KiB
Python

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, le=100, 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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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, le=100, 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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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, le=100, 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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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, le=100, 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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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 TunnelModel(BaseModel):
"""隧道主数据写入及展示模型。"""
model_config = ConfigDict(
alias_generator=to_camel,
from_attributes=True,
populate_by_name=True,
extra='forbid',
)
tunnel_id: int | None = Field(default=None, gt=0, description='隧道ID')
site_id: int = Field(gt=0, description='现场ID')
tunnel_code: str = Field(min_length=1, max_length=64, pattern=r'\S', description='隧道编号')
tunnel_name: str = Field(min_length=1, max_length=100, pattern=r'\S', description='隧道名称')
status: Literal['0', '1'] = Field(default='0', description='状态(0正常 1停用)')
current_trace_no: str | None = Field(default=None, max_length=64, description='当前物料溯源号')
current_trace_started_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='备注')
class TunnelPageQueryModel(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='每页记录数')
site_id: int | None = Field(default=None, gt=0, description='现场ID')
tunnel_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='隧道编号')
tunnel_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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
class DeleteTunnelModel(BaseModel):
"""删除隧道模型。"""
model_config = ConfigDict(
alias_generator=to_camel,
from_attributes=True,
populate_by_name=True,
extra='forbid',
)
tunnel_ids: list[PositiveId] = Field(min_length=1, description='需要删除的隧道ID')
_validate_unique_ids = field_validator('tunnel_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, le=100, 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='状态')
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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(strict=True, 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='更新时间')
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: Literal[100] = Field(description='容量包数(固定 100 包)')
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, le=100, 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='资产状态')
flow_group: Literal['unstocked', 'warehouse', 'transit', 'site', 'returning'] | None = Field(
default=None, description='当前流转环节'
)
ids: str | None = Field(default=None, description='主键ID列表(英文逗号分隔)')
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(description='用户ID列表')
_validate_unique_ids = field_validator('user_ids')(_ensure_unique_ids)
class BusinessUserOptionModel(BaseModel):
"""仓库或客户现场可分配用户的最小只读模型。"""
model_config = ConfigDict(
alias_generator=to_camel,
from_attributes=True,
populate_by_name=True,
extra='forbid',
)
user_id: PositiveId = Field(description='用户ID')
user_name: str = Field(min_length=1, max_length=30, pattern=r'\S', description='用户账号')
nick_name: str | None = Field(default=None, max_length=30, description='用户昵称')
assigned: bool = Field(description='是否已分配')