18 KiB
运费结算完善 — 设计文档
日期: 2026-06-25
状态: 已确认(2026-06-25)
前置:docs/运输计划管理-需求说明文档.md(§10、BR-26~29、BR-40,BR-27 由本设计 supersede)、docs/superpowers/specs/2026-06-25-pl-plan-archive-design.md
界面模式: P08 页内 Tab + P02 状态驱动列表 + P22 行内审批 + P13 财务确认发放
目标
完善【运输管理 → 运费结算】模块:按业务阶段拆分为 四个 Tab,支持从未结算计划组单、结算单多级审批(对齐合同管理)、审批完成后财务独立 Tab 登记发放,最终进入已结算归档态。
已确认决策(2026-06-25)
| # | 决策 | 说明 |
|---|---|---|
| D1 | 四 Tab 独立 | 未结算计划 / 结算单(草稿+审批中)/ 待财务发放 / 已结算 — 四个 Tab 各自独立,不用子筛选合并 |
| D2 | 审批对齐合同管理 | 提交时选审批人 + 顺序/会签/或签;独立审批记录表;审批按钮仅在「待我审批」时出现在行操作列 |
| D3 | 财务发放独立 Tab | approved 状态的结算单进入 Tab3「待财务发放」;财务在此 Tab 做发放登记(附件+备注),完成后 confirmed 进入 Tab4 |
| D4 | 组单以计划展示、运输落库 | Tab1 列表以运输计划维度展示(可含多车牌);生成结算单时落库关联 transport_id(复用 pl_freight_settlement_transport) |
| D5 | 未结算准入:已归档 + 有运费 | Tab1 仅展示 archive_status=archived 且 has_freight=1、且运输单尚未被非草稿结算单占用的计划 |
| D6 | Supersede BR-27 | 原「不走工作流、计划专员 C 直接确认」作废;改为合同式多级审批 + 财务发放两阶段 |
非目标(本期不做)
- 接入通用 Python 工作流引擎(OpenSpec workflow 目标设计尚未落地)
- 结算单驳回后自动拆单/释放部分运输单(驳回整单回到草稿,关联运输单一并释放)
- 邮件/短信通知审批人(仅系统内操作)
- 运费结算报表导出
- 按归属公司隔离数据(延续 BR-51)
一、页面结构(四 Tab)
1.1 路由与菜单
| 项 | 值 |
|---|---|
| 菜单名 | 运费结算(沿用) |
| Hash | /#/pl/freight |
| 组件 | web-admin/src/views/pl/freight/index.vue(重构为 Tab 容器 + 子面板) |
| menuCode | pl:freight:list |
1.2 Tab 定义
┌─────────────┬─────────────┬───────────────┬─────────────┐
│ 未结算计划 │ 结算单 │ 待财务发放 │ 已结算 │
│ Tab unsettled│ Tab bills │ Tab disburse │ Tab settled │
└─────────────┴─────────────┴───────────────┴─────────────┘
| Tab | name |
标题 | 结算状态 filter | 主要角色 |
|---|---|---|---|---|
| 1 | unsettled |
未结算计划 | —(查计划,非结算单) | 计划专员 |
| 2 | bills |
结算单 | pending, approving |
计划专员、审批人 |
| 3 | disburse |
待财务发放 | approved |
财务 |
| 4 | settled |
已结算 | confirmed |
全员只读 |
Tab 切换不走路由 query;默认激活 Tab1。各 Tab 独立筛选、分页、列表刷新。
1.3 权限按钮(新增/调整)
| 权限码 | 用途 |
|---|---|
pl:freight:list |
菜单 + 四 Tab 列表(已有) |
pl:freight:query |
查看结算单/计划详情 |
pl:freight:create |
Tab1 多选组单 |
pl:freight:edit |
Tab2 草稿编辑、发票信息 |
pl:freight:submit |
Tab2 提交审批 |
pl:freight:approve |
Tab2 行内审批(后端校验是否当前节点审批人) |
pl:freight:withdraw |
Tab2 撤回审批(提交人) |
pl:freight:disburse |
Tab3 财务发放登记 |
pl:freight:disburse:list |
Tab3 列表(可选:与 disburse 合并,无 list 则 finance 角色仅见 Tab3 需菜单子权限设计 — 实现时 Tab3/4 仍受 list 控制,按钮用 disburse) |
二、Tab1 — 未结算计划
2.1 数据范围(D5)
同时满足:
pl_plan.archive_status = 'archived'pl_plan.has_freight = 1pl_plan.follow_status != 'cancelled'- 计划已关联运输单(
transport_id非空)且pl_transport.freight_amount > 0 - 该
transport_id未出现在任何settlement_status NOT IN ('pending')且del_flag=0的结算单关联中
(若仅在pending草稿结算单中,视为「占用中」:Tab1 不展示,Tab2 草稿可编辑/删除后释放)
2.2 列表列(建议)
| 列 | 来源 |
|---|---|
| 多选 | type="selection" |
| 计划编号 | planNo |
| 计划日期 | planDate |
| 单据类型 | planType Tag |
| 客户/供应商 | customerName / supplierName |
| 车牌号 | licensePlate |
| 运输公司 | transportCompany |
| 运费(元) | freightAmount |
| 归档时间 | updateTime 或归档变更记录 |
筛选:计划编号、日期范围、客户、车牌号、单据类型(与运输计划列表 subset 对齐)。
2.3 工具栏与操作
| 控件 | 规则 |
|---|---|
| 已选 N 项 | 最前 |
| 生成结算单 | 唯一主色按钮;至少选 1 条;调用 POST /transport/freight/ |
| 查看 | 行操作,打开计划只读查看(PlPlanViewDialog 或跳转 plan detail) |
组单规则:
- 一次多选生成 一张 结算单
- 后端校验:所有 plan 的 transport 均未锁定;租户一致
total_freight = sum(transport.freight_amount)- 创建后跳转/激活 Tab2,定位到新结算单
三、Tab2 — 结算单(草稿 + 审批中)
3.1 数据范围
settlement_status IN ('pending', 'approving')
可选 Tab 内筛选:结算单号、创建时间、提交人、状态(草稿/审批中)。
3.2 列表列
| 列 | 说明 |
|---|---|
| 结算单号 | 链接打开查看弹窗 |
| 运费合计 | totalFreight |
| 关联计划数 | 聚合 count |
| 车牌摘要 | 多车牌逗号分隔,tooltip 全量 |
| 结算状态 | Tag:pending 草稿 / approving 审批中 |
| 当前审批人 | approving 时展示当前节点审批人姓名 |
| 创建人 / 时间 |
3.3 行操作(操作列 fixed="right",min-width ≥ 280)
| 状态 | 查看 | 编辑 | 提交审批 | 撤回 | 审批 |
|---|---|---|---|---|---|
pending |
✅ | ✅(创建人或有 edit 权限) | ✅ | — | — |
approving |
✅ | ❌ | — | ✅(提交人) | ✅ 仅 canApprove=true |
约束(D2):
- 禁止在列表顶栏放批量审批
- 「审批」按钮必须带
<el-icon>,与规范一致 canApprove由列表/详情 API 返回(后端can_user_approve_settlement)
3.4 草稿编辑弹窗
- 只读:结算单号、合计
- 可编辑:发票号、开票日期、发票金额、备注(
FreightSettlementUpdate已有字段) - 明细区:关联计划/运输表格(只读);本期不支持草稿中增删关联行(需删单重建)
- 删除草稿:软删结算单 + 释放 transport 占用(新增
DELETE /freight/{id},仅pending)
3.5 提交审批弹窗
复用/抽取合同 ApprovalDialog 交互(web-admin/src/views/crm/contract/components/ApprovalDialog.vue):
| 字段 | 说明 |
|---|---|
| 审批类型 | 顺序 / 会签 / 或签 |
| 审批人 | 多选远程搜索用户 |
| 提交说明 | 可选 |
提交后:pending → approving,创建 pl_freight_settlement_approval 记录。
3.6 审批弹窗
- 审批意见(必填/选填与合同一致)
- 通过 / 驳回
- 驳回:
approving → pending,审批记录置 rejected,释放为可重新编辑草稿(审批记录保留历史)
3.7 查看弹窗
- Hero:结算单号 + 状态 Tag + 运费合计
- Section:发票信息、关联运输/计划明细表
- Section:审批流时间线(对齐合同
ContractDetailDialog审批区) - Section:汇款凭证(
confirmed后在 Tab4 查看;Tab2 仅已发放前为空)
四、Tab3 — 待财务发放
4.1 数据范围
settlement_status = 'approved'
4.2 列表列
| 列 | 说明 |
|---|---|
| 结算单号 | |
| 运费合计 | |
| 审批完成时间 | 最后一节点 approval_time |
| 提交人 | |
| 关联计划数 / 车牌摘要 | |
| 状态 | Tag:待发放 |
4.3 行操作
| 操作 | 权限 | 说明 |
|---|---|---|
| 查看 | query | 只读详情 + 完整审批流 |
| 发放登记 | disburse | 打开财务确认弹窗 |
无审批按钮。
4.4 发放登记弹窗(P13 财务确认)
┌─────────────────────────────────────────────┐
│ 财务发放登记 · {settlementNo} [×] │
├─────────────────────────────────────────────┤
│ [只读] 结算单号、运费合计、关联明细摘要 │
│ [只读] 审批流时间线(已通过) │
├─────────────────────────────────────────────┤
│ 汇款日期 * │
│ 汇款金额 * │
│ 汇款凭证 * 附件卡片上传(多文件) │
│ 发放备注 textarea │
├─────────────────────────────────────────────┤
│ 取消 │ 确认发放 │
└─────────────────────────────────────────────┘
提交逻辑(单事务):
- 校验
settlement_status == approved - 写入
pl_remittance_voucher(voucher_urlsJSON 数组、remittance_date、remittance_amount、remark) settlement_status → confirmedupdate_by当前财务用户
五、Tab4 — 已结算
5.1 数据范围
settlement_status = 'confirmed'
5.2 列表列
在 Tab3 基础上增加:汇款日期、汇款金额、财务确认人、确认时间。
5.3 行操作
仅 查看(含汇款凭证预览/下载)。
六、状态机
┌──────────┐
组单 │ pending │ 草稿 (Tab2)
───────────────▶│ │
└────┬─────┘
│ submit
▼
┌──────────┐
│approving │ 审批中 (Tab2)
└────┬─────┘
reject │ │ all approved
┌─────┘ └─────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ pending │ │ approved │ 待财务发放 (Tab3)
└──────────┘ └────┬─────┘
│ disburse
▼
┌──────────┐
│confirmed │ 已结算 (Tab4)
└──────────┘
6.1 字典 pl_settlement_status(更新)
| dict_value | dict_label | tag_type | 所在 Tab |
|---|---|---|---|
pending |
草稿 | info | Tab2 |
approving |
审批中 | warning | Tab2 |
approved |
待发放 | primary | Tab3 |
confirmed |
已结算 | success | Tab4 |
rejected |
已驳回 | danger | 仅审批记录,结算单回到 pending |
移除或废弃旧标签「已提交」「已审核」与 API 中 submitted 的直接终态跳转。
6.2 运费锁定(与归档模块一致)
以下状态的结算单关联 transport 锁定运费修改(transport_has_locked_freight_settlement):
approving, approved, confirmed
pending 草稿不锁定(删单可释放)。
七、后端设计
7.1 新表 pl_freight_settlement_approval
| 字段 | 类型 | 说明 |
|---|---|---|
| id | varchar(64) | PK |
| settlement_id | varchar(64) | 结算单 ID |
| step | int | 步骤序号 |
| approver_id | varchar(64) | 审批人 |
| approver_name | varchar(64) | 冗余 |
| approval_type | varchar(20) | sequential / parallel / or |
| approval_result | varchar(20) | pending / approved / rejected |
| approval_opinion | varchar(500) | |
| approval_time | datetime | |
| is_current | tinyint | 当前节点 |
| tenant_no + 公共字段 |
索引:(settlement_id, del_flag), (approver_id, approval_result, is_current)
7.2 Service 层(新建 pl_freight_settlement_service.py)
| 函数 | 职责 |
|---|---|
list_unsettled_plans |
Tab1 查询 |
create_settlement_from_transports |
组单 + 占用校验 |
submit_settlement_approval |
创建审批链,pending→approving |
approve_settlement_step |
单步通过/驳回 + 推进 |
withdraw_settlement_approval |
撤回 |
can_user_approve_settlement |
当前用户是否可审 |
disburse_settlement |
财务发放 + voucher |
delete_draft_settlement |
软删草稿 |
CRUD 参考:backend/app/crud/crm/contract_approval.py(勿直接依赖 CRM 表)。
7.3 API 清单
| Method | Path | 说明 |
|---|---|---|
| GET | /transport/freight/unsettled-plans |
Tab1 分页列表 |
| GET | /transport/freight/ |
Tab2/3/4;增加 tab 或 settlementStatus 多值 |
| POST | /transport/freight/ |
组单 { planIds } 或 { transportIds }(推荐 planIds,服务端解析 transport) |
| GET | /transport/freight/{id} |
详情 + transports + plans 摘要 + canApprove |
| PUT | /transport/freight/{id} |
更新草稿 |
| DELETE | /transport/freight/{id} |
删除草稿 |
| POST | /transport/freight/{id}/submit-approval |
提交审批 body 同合同 |
| POST | /transport/freight/approval/{approvalId} |
通过/驳回 |
| POST | /transport/freight/{id}/withdraw-approval |
撤回 |
| GET | /transport/freight/{id}/approval-flow |
审批时间线 |
| POST | /transport/freight/{id}/disburse |
财务发放 body: remittanceDate, amount, voucherUrls, remark |
废弃/替换:
POST /{id}/submit→ 由submit-approval替代POST /{id}/approve→ 由approval/{id}替代POST /voucher→ 合并进disburse或内部调用
列表项返回 canApprove: boolean(当前用户 + 当前 settlement)。
7.4 审批规则(对齐合同)
| 类型 | 规则 |
|---|---|
| sequential | 按选择顺序,仅 is_current=1 且 pending 的一人可审 |
| parallel | 所有 pending 节点需全部通过 |
| or | 任一人通过即通过 |
全部通过后:settlement_status → approved,进入 Tab3。
八、前端文件规划
| 文件 | 动作 |
|---|---|
views/pl/freight/index.vue |
重构为 Tab 容器 |
views/pl/freight/components/UnsettledPlanPanel.vue |
Tab1 |
views/pl/freight/components/SettlementBillPanel.vue |
Tab2 |
views/pl/freight/components/DisbursePanel.vue |
Tab3 |
views/pl/freight/components/SettledPanel.vue |
Tab4 |
components/business/pl-freight-settlement-view-dialog/index.vue |
查看弹窗 |
components/business/pl-freight-settlement-form-dialog/index.vue |
草稿编辑 |
components/business/pl-freight-approval-dialog/index.vue |
提交/审批(可 fork 合同 ApprovalDialog) |
components/business/pl-freight-disburse-dialog/index.vue |
财务发放 |
api/pl/index.ts |
扩展 freightApi |
九、与现有模块衔接
| 模块 | 衔接点 |
|---|---|
| 运输计划归档 | Tab1 要求已归档;归档弹窗改运费仍受结算锁定约束 |
| 合同管理 | 审批 UX、API 语义对齐 |
| 运输计划列表 | 不新增结算入口,结算统一在 freight 模块 |
| 需求文档 | 实现后更新 §10.1、BR-27、BR-40、附录 C |
十、验收清单
| # | 场景 | 预期 |
|---|---|---|
| V1 | 四 Tab 切换 | 各自列表/筛选/分页独立正常 |
| V2 | Tab1 仅已归档有运费且未占用计划 | 不符合不出现 |
| V3 | 多选组单 | 生成一张草稿,出现在 Tab2,Tab1 对应行消失 |
| V4 | Tab2 草稿编辑发票 | 保存成功 |
| V5 | 提交审批 | 进入 approving,审批流生成 |
| V6 | 非当前审批人 | Tab2 无审批按钮 |
| V7 | 当前审批人 | 行操作列出现审批,通过后流转 |
| V8 | 全部审批通过 | 结算单出现在 Tab3,Tab2 消失 |
| V9 | Tab3 发放登记 | 上传凭证+备注,进入 Tab4 |
| V10 | Tab4 查看 | 只读,含凭证与审批流 |
| V11 | 审批中锁定 | 归档页不可改关联 plan 运费 |
| V12 | 驳回 | 回 Tab2 草稿,可再提交 |
| V13 | 权限 | 无 disburse 不见 Tab3 发放按钮 |
十一、实施阶段建议
| Phase | 范围 |
|---|---|
| P1 | 四 Tab 骨架 + Tab1 未结算 + 组单 + Tab4 只读列表 |
| P2 | Tab2 草稿 CRUD + 查看弹窗 |
| P3 | 审批表 + 提交/审批/撤回 + canApprove 行按钮 |
| P4 | Tab3 财务发放 + voucher + 状态 confirmed |
| P5 | 菜单权限 SQL、字典更新、归档锁定联调、文档修订 |
十二、Open Questions(实现前默认可按本文)
| # | 问题 | 默认 |
|---|---|---|
| Q1 | Tab1 是否必须已归档 | 是(D5) |
| Q2 | 草稿能否删单 | 是,仅 pending |
| Q3 | 驳回后审批历史 | 保留记录,结算单回 pending |
| Q4 | 发放金额是否必须等于运费合计 | 允许差异,备注必填当不等 |