450 lines
18 KiB
Markdown
450 lines
18 KiB
Markdown
# 运费结算完善 — 设计文档
|
||
|
||
> **日期**: 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)
|
||
|
||
同时满足:
|
||
|
||
1. `pl_plan.archive_status = 'archived'`
|
||
2. `pl_plan.has_freight = 1`
|
||
3. `pl_plan.follow_status != 'cancelled'`
|
||
4. 计划已关联运输单(`transport_id` 非空)且 `pl_transport.freight_amount > 0`
|
||
5. 该 `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 │
|
||
├─────────────────────────────────────────────┤
|
||
│ 取消 │ 确认发放 │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
提交逻辑(单事务):
|
||
|
||
1. 校验 `settlement_status == approved`
|
||
2. 写入 `pl_remittance_voucher`(`voucher_urls` JSON 数组、`remittance_date`、`remittance_amount`、`remark`)
|
||
3. `settlement_status → confirmed`
|
||
4. `update_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 | 发放金额是否必须等于运费合计 | **允许差异**,备注必填当不等 |
|