7.9 KiB
7.9 KiB
运输计划 / 库存列表导出 · 段次×产品行 — 设计文档
日期: 2026-10-15
状态: 待实现
方案: A — 完全对齐列表展平行(flattenPlanArchivePairRows)
范围: 运输计划列表 + 库存管理入库单/出库单(共用同一套导出逻辑)
1. 目标
列表导出 Excel 的行模型与列表 UI 一致:
- 一装多送时,能按段次拆成多行(先装货产品行,再各送货段产品行)
- 每行的「计划量 / 实际量」为本行所属段上的数量,不再把多段产品/客户压成一行
- 库存管理(
stockRole=inbound|outbound)与运输计划列表走同一后端导出构建函数
2. 已确认决策
| # | 项 | 决策 |
|---|---|---|
| D1 | 行结构 | 方案 A:段次×产品多行,对齐 flattenPlanArchivePairRows |
| D2 | 作用面 | 运输计划列表 + 入库单 + 出库单,共用逻辑 |
| D3 | 段次列 | 新增「段次」列:装货 / 送货 #1 / 送货 #2… |
| D4 | 客户/地址列 | 装货行只填装货侧;送货行只填对应送货段;不再用 / 拼接多送客户 |
| D5 | 计划级字段 | 计划编号等每行重复(Excel 不做合并单元格) |
| D6 | 归档导出 | 不改归档配对宽表(build_plan_archive_export_rows) |
| D7 | 文件名 / Sheet | 保持现有:入库单 / 出库单 / 计划列表 |
3. 非目标
- 不改列表 UI、合并单元格、斑马纹等展示逻辑
- 不改计划归档导出的配对宽表与「装货量+送货量同列」结构
- 不新增独立导出 API;仍走现有
plan列表导出入口 - 不做前端本地拼 Excel;行展开在后端完成
4. 现状问题
| 层 | 现状 | 问题 |
|---|---|---|
| 列表 | flattenPlanArchivePairRows:装货产品行 → 各送货段产品行;planQty/actualQty 取本段产品 |
正确 |
| 列表导出 | _build_plan_list_export_rows:按计划级 PlPlanProduct 展开;装/送客户、地址用 / 汇总整单 |
一装多送时一行;数量无法按段对照列表 |
5. 行模型
对每个计划,按 stops_by_plan(load_plan_stops_grouped)展开:
装货段.products[] → 行:段次=装货,产品/量=装货段产品
送货段1.products[] → 行:段次=送货 #1,产品/量=该送货段产品
送货段2.products[] → 行:段次=送货 #2,…
5.1 顺序与占位
- 先全部装货段产品行,再按送货
seq依次展开各送货段产品行(与列表一致)。 - 某侧无产品:仍输出该侧占位行(产品列、计划量、实际量为空),与列表「空产品仍占一行」行为一致。
- 无段结构(legacy):无 load/delivery 段时,回退为计划级
PlPlanProduct一行(段次为空),兼容旧数据。 - 筛选条件(含
stockRole、列头筛选等)仍由现有列表查询决定「导出哪些计划」;本设计只改单计划内行展开。
5.2 示例
计划 PO20261009016:装货 1 个产品、送货 2 段各 1 个产品 → 导出 3 行:
| 段次 | 装货客户 | 送货客户 | 产品 | 计划量 |
|---|---|---|---|---|
| 装货 | A | (空) | 尿素 | 30 |
| 送货 #1 | (空) | B1 | 尿素 | 20 |
| 送货 #2 | (空) | B2 | 尿素 | 10 |
6. 列语义
6.1 新增列
| field | 表头 | 取值 |
|---|---|---|
stopSegment |
段次 | 装货;送货 #{seq}(seq 缺省时用展开序号) |
默认导出列顺序:建议紧挨「计划编号」之后。前端可见列导出映射:若列表无独立段次 prop,仍由后端默认列包含 stopSegment;前端 planExportColumns 增加 { prop: 'stopSegment', label: '段次' }(或等价别名),以便「按可见列导出」时可选。
6.2 按行填写规则
| 列 | 装货行 | 送货行 |
|---|---|---|
| 计划级(编号、归属公司、跟进状态、计划日期、单据类型、车牌、人员、跟进人、供方、需方、司机、运输/归档状态、ERP、备注等) | 重复填计划值 | 同左 |
送货时间(deliveryTimes) |
空 | 本送货段日期(单值,不再整单 / 拼接) |
| 装货客户 / 装货地址时间 / 装货地磅 | 本装货段 | 空 |
| 送货客户 / 送货地址时间 / 送货地磅 | 空 | 本送货段 |
合同(productContract) |
本装货段合同号 | 本送货段合同号 |
| 产品名称 / 品牌 / 别名 / 型号规格 / 包装规格 / 单位 | 本行段产品(CRM 标准名/别名解析保持现有) | 同左 |
| 计划量 / 实际量 | 本行段产品 planQty;实际量语义对齐列表 getRowActualQty(装货读 inbound 侧实际量,送货读 outbound 侧实际量) |
同左 |
枚举中文标签(跟进状态、单据类型、运输/归档状态、归属公司等)与文件名/Sheet 标题逻辑保持现有实现。
6.3 字典与别名
- 单位等
dict_type列仍走现有export_rows_to_xlsx_response字典翻译。 _PLAN_EXPORT_COLUMN_ALIASES:如前端 prop 与 field 不一致则补映射;stopSegment本身可直接作为 prop。
7. 架构与落点
ListExportDropdown(运输计划 / 入库 / 出库)
→ POST 现有 plan 导出 API(带 filters + stockRole + columns)
→ 查询 plans(不变)
→ load_plan_stops_grouped
→ _build_plan_list_export_rows 【改为段次×产品展开】
→ PLAN_EXPORT_COLUMNS(+ stopSegment)
→ xlsx(filename/sheet 按 stockRole)
| 单元 | 职责 | 依赖 |
|---|---|---|
_build_plan_list_export_rows |
单计划 → 多行 dict;段次与侧栏字段按行写入 | stops_by_plan、CRM 别名 map、段产品 |
PLAN_EXPORT_COLUMNS |
列定义含 stopSegment |
— |
_apply_plan_list_export_stop_fields |
改为按单侧 stop填写,或拆成 load/delivery 专用 helper,避免整单拼接 | 单条 load 或单条 delivery |
web-admin plan/index.vue(及库存页复用) |
导出列清单增加段次;文件名已按 stockRole | 共用 PlanListPage |
tests/test_pl_plan_list_export.py |
一装多送行数、段次文案、侧栏字段空/非空、qty | — |
建议:段次展开逻辑可抽到 backend/app/services/pl/ 下小函数(如 iter_plan_list_export_segments),便于单测且与 plan.py 路由层解耦;若改动面小,也可先内联在 _build_plan_list_export_rows,以最小必要为准。
8. 实际量语义(与列表对齐)
列表:
getRowPlanQty← 本行PlanStopProduct.planQtygetRowActualQty←getStopArchiveActualQty:装货用inboundQty,送货用outboundQty
导出 MUST 使用同一语义读取段产品字段(后端 stop product 模型上的对应属性),不得再写计划级 PlPlanProduct.actual_qty 作为有段计划的实际量来源。
9. 测试要求
至少覆盖:
- 一装多送:1 装货产品 + 2 送货段各 1 产品 → 3 行;段次为
装货/送货 #1/送货 #2;各行planQty为段量;装货行deliveryCustomer为空、送货行loadCustomer为空。 - 单装单送:行为与列值正确;中文标签回归(跟进状态、单据类型等)。
- 空产品占位:送货段无产品仍有一行,产品/量为空。
- legacy:无 stops 时仍按计划级产品展开,段次为空。
- 文件名:
stockRole→入库单_/出库单_/计划列表_(现有用例保留)。 - 列过滤:请求
stopSegment时出现在导出列;别名 normalize 不破坏既有 prop。
10. 验收标准
- 对
PO20261009016类一装多送计划:导出 Excel 行数与列表展平行数一致(同筛选、同计划集合前提下)。 - 打开「入库单.xlsx / 出库单.xlsx / 计划列表.xlsx」均可看到「段次」及按段拆分的计划量/实际量。
- 计划归档导出行为不变(回归抽测或现有
test_pl_plan_archive_export全绿)。