refactor(outbound): 出库单据与领用物料合并成一张表

这两者本来就是同一件事(这台设备对应 MOM 的哪些出库单、领了哪些料),
却因为粒度不同被拆成两张表、界面上两张卡:用户要面对两个入口两个删除按钮,
还会问「我在那边挂的怎么这边看不见」。更糟的是**单据级那张没有 mom_line_id,
挂上去的料根本报不了废**。

- 新建 product_outbound_materials,统一到**明细级**(只有它带 mom_line_id,
  而报废要用它定位)。单据级信息(申请单号/备注/撤回)作为冗余列落在每条明细上。
  task_id 改为可空 —— 任务只是溯源信息,不再是组织维度,展示/报废/删除按设备走。
- 接口从 7 个收敛成 3 个(GET/POST/DELETE /products/{id}/outbound-materials,
  外加整单删 by-order)。任务级那套连同 TaskResponse.outbound_materials 一起删掉:
  保留第二个入口只会让「同一个东西两个地方」重新长出来。
- MOM 回调存档改为按 outbound_no 去 MOM **现查明细**逐行落 —— 不查的话
  这台设备「领了什么料」永远是空的,也就报不了废。查不到时退化成单据级存档,
  宁可显示「有这张单但看不到明细」,也不要静默丢掉这张单。
- 扫码响应补 outbound_materials(附「谁挂上去的」中文名,服务端解析)。
  ⚠️ 依赖 task_tree_loader 的 selectinload —— 异步 session 下懒加载会
  MissingGreenlet。
- 前端两张卡合并成一张:按出库单号分组、点开看明细,明细行才有报废/删除。
This commit is contained in:
2026-09-23 15:18:06 +08:00
parent 551819e0e3
commit 5d5aea1015
15 changed files with 2030 additions and 15 deletions

View File

@ -64,9 +64,23 @@ async def create_task_endpoint(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
"""创建任务"""
return await task_service.create_task(db, data)
"""创建任务
`mom_line_ids` 非空时,会在**同一事务**里把对应的 MOM 出库明细挂到新任务上,
所以不存在「任务建好了但物料没挂上」的中间态。
"""
return await task_service.create_task(
db, data, operator_id=current_user.get("username"),
)
# 注:原先这里有「任务挂载 MOM 出库物料」的三个端点
# GET/POST /tasks/{id}/outbound-materials、DELETE .../{material_id})。
# 物料已统一为**设备级**,这三个端点连同 task_service 里的实现一起删除 ——
# 挂载/查看/删除/报废一律走:
# GET/POST /products/{id}/outbound-materials
# DELETE /products/{id}/outbound-materials/{material_id}
# 保留任务级入口只会让「同一个东西两个地方」重新长出来。
@router.patch("/{task_id}", response_model=TaskResponse)
async def update_task_endpoint(