Add ensure-copy-seq API and miniprogram clipboard prefix so each plan gets a tenant-scoped fixed serial on first copy; cover allocation with unit tests and refresh the backend graph. Co-authored-by: Cursor <cursoragent@cursor.com>
5.8 KiB
运输计划「复制发群」固定序号设计
日期: 2026-10-09
状态: 已确认
范围:pl_plan.copy_seq_no+POST /api/transport/driver-plan/{planId}/ensure-copy-seq+ 小程序司机运输计划单「复制发群」文案
前置:docs/superpowers/specs/2026-09-28-mini-pl-driver-plan-copy-wechat-design.md
1. 目标
在司机运输计划单「复制发群」纯文本中,于「计划编号」上方增加固定流水「序号」。租户内永不回收;历史按创建时间回填;新建计划不在创建时发号,首次点击复制时发号并落库。
2. 已确认决策
| # | 项 | 决策 |
|---|---|---|
| D1 | 号段范围 | 按 tenant_no 永久累加 1、2、3… |
| D2 | 回收 | 作废 / 软删 不回收号;回填与 MAX 均含 del_flag=1 |
| D3 | 展示面 | 仅「复制发群」文案;列表/详情/Web 管理端不展示 |
| D4 | 发号时机 | 创建时 NULL;首次「复制发群」调用后端 ensure 时分配并写库 |
| D5 | 历史 | 迁移按租户 create_time ASC, id ASC 一次性补号;含软删 |
| D6 | 无号兜底 | 文案写 序号:—(正常流程点复制会先 ensure,几乎用不到) |
| D7 | 格式 | 序号:{n},纯数字不补零 |
| D8 | 详情副作用 | 禁止在普通 GET 详情里发号;专用 POST ensure-copy-seq |
3. 非目标
- 不在 Web 管理端 / 小程序列表卡片 / 回执页 UI 展示该序号
- 不改变装货方、送货方、实际装/送货量等既有复制规则
- 不按日/月重置号段
- 创建计划路径(
create_plan/create_plan_full/create_from_contract等)不分配copy_seq_no
4. 数据模型
4.1 字段
pl_plan 新增:
| 列 | 类型 | 空 | 说明 |
|---|---|---|---|
copy_seq_no |
INT |
YES | 复制发群序号(租户内固定流水);未首次复制前为 NULL(历史回填后有值) |
建议索引:KEY idx_pl_plan_tenant_copy_seq (tenant_no, copy_seq_no)(便于 MAX)。
4.2 历史回填
- 分组:
tenant_no - 排序:
create_time ASC, id ASC - 范围:含
del_flag = 1 - 赋值:组内从 1 起连续编号写入
copy_seq_no - 幂等:已非空的行不覆盖(便于脚本重跑)
4.3 运行时发号
在事务内:
- 按与司机详情相同的可见性解析计划;不可见则失败。
SELECT … FROM pl_plan WHERE id=? FOR UPDATE(或等价行锁)。- 若
copy_seq_no已有值 → 原样返回。 - 否则计算
next = COALESCE(MAX(copy_seq_no) WHERE tenant_no = ?, 含软删), 0) + 1
写回当前行并commit,返回next。
同一计划并发首次复制只得到一个号;不同计划不撞号。
5. API
POST /api/transport/driver-plan/{planId}/ensure-copy-seq
| 项 | 约定 |
|---|---|
| 鉴权 | JWT;与 GET /transport/driver-plan/{planId} 一致(须关联驾押人员且可看该计划) |
| 成功体 | { "copySeqNo": <int> }(camelCase / by_alias 与现网一致) |
| 失败 | 未关联人员 / 计划不可见 → 与详情同等 404 或业务错误码 |
| 加密排除 | 前缀已在 /api/transport/driver-plan,一般无需改配置 |
详情 GET 可不强制返回 copySeqNo;小程序以 ensure 结果为准合并进复制上下文。
6. 复制文案
在既有模板最上方增加一行:
序号:12
计划编号:TP2026xxxx
装货方:…
送货方#1:…
…
规则:
- 有号:
序号:{copySeqNo} - 无号:
序号:— - 其余字段规则不变(见 2026-09-28 设计)
7. 小程序交互
列表「复制发群」
showLoadingGET /transport/driver-plan/{planId}取详情POST .../ensure-copy-seq→ 将copySeqNo合并进详情对象(如page.copySeqNo)buildDriverPlanCopyText→setClipboardDatahideLoading;成功「已复制」;任一步失败「复制失败」/「计划无效」
说明:若 ensure 失败,仍可用详情尝试复制(序号行为 —),或整单失败——推荐整单失败并 toast「复制失败」,避免发出无序号的半成品(与「点复制就会有号」预期一致)。实现时采用:ensure 失败则中止复制。
回执保存成功 →「复制发群」
同样:ensure → 合并 → 复制;成功仍 navigateBack;失败 toast 且留在回执页。
8. 实现结构
| 位置 | 职责 |
|---|---|
docs/sql/migrations/2026-10-09-pl-plan-copy-seq-no.sql(名可微调) |
ALTER + 回填 |
backend/app/models/pl_models.py |
PlPlan.copy_seq_no |
backend/app/services/pl_driver_plan_service.py(或邻近 service) |
ensure_copy_seq_no |
backend/app/api/pl/driver_plan.py |
新路由 |
backend/app/schemas/pl/… |
响应模型(如需) |
miniprogram/api/pl.js |
driverPlanApi.ensureCopySeq |
miniprogram/utils/plDriverPlanCopy.js |
首行序号 |
miniprogram/pages/pl/driver-plan/list.vue |
handleCopy 调 ensure |
miniprogram/pages/pl/driver-plan/detail.vue |
保存成功复制路径调 ensure |
9. 测试要点
- 历史回填后:最旧计划为 1,同租户连续;软删行也占号
- 新计划创建后
copy_seq_no为NULL;首次复制后为MAX+1且再次复制不变 - 两客户端同时首次复制同一计划:只出现一个号
- 两计划几乎同时首次复制:号不重复
- 复制文案首行为
序号:n,其后为原「计划编号」块 - ensure 失败时不写入剪贴板(或明确 toast),列表/详情 UI 仍无序号展示
- 不可见计划 / 未绑定驾押人员:ensure 与 GET 详情一致失败
10. 与加油登记序号的差异
加油登记 seq_no 为按 create_time 现算、不落库。本需求为 落库固定流水,且新单在首次复制时分配,不可套用加油登记的纯计算方案。