纠正对公转账代收人契约说明

这个提交包含在:
yaosutu 2026-07-21 16:40:57 +08:00
父节点 39acbeb0e8
当前提交 33d0999a68
共有 2 个文件被更改,包括 412 次插入10 次删除

查看文件

@ -197,16 +197,7 @@ GET /v3/admin/order/2075415307597357058/payment/manual-receipt/options
"channel": "BANK_TRANSFER",
"channelText": "银行转账",
"allowedPayTypes": ["DEPOSIT", "FULL"],
"collectors": [
{
"collectorType": "COMPANY_ACCOUNT",
"collectorId": null,
"collectorName": "公司账户",
"collectorRole": "COMPANY_ACCOUNT",
"collectorRoleText": "公司账户",
"defaultSelected": true
}
]
"collectors": []
},
{
"channel": "DRIVER_CASH",

查看文件

@ -0,0 +1,411 @@
# 📝【契约纠正·管理后台】对公转账不需要代收人 (#5120)
> **变更性质**:现有接口契约澄清 + 历史文档示例纠错|**端类型**:管理后台|**更新日期**2026-07-21
>
> 本次没有发布新的后端字段、枚举或行为变更;下文说明接口已有的稳定契约。
## 1. 接口背景
管理后台在“登记线下收款”中选择“对公转账”后仍显示“代收人”,与当前接口契约不一致。对公转账不由某位员工代收,只需填写转账流水号;代收人仅在“报账人收款”渠道下需要选择。
2026-07-10 的历史通知曾在示例中给 `BANK_TRANSFER.collectors` 放入“公司账户”对象,该示例与实际响应不符,本次一并纠正为空数组。
## 2. 变更清单
| # | 方法 | 路径 | 通知类型 | 说明 |
|---|---|---|---|---|
| 1 | GET | `/v3/admin/order/{orderId}/payment/manual-receipt/options` | 契约澄清 | `BANK_TRANSFER.collectors` 始终为 `[]`;各渠道使用各自的候选项 |
| 2 | POST | `/v3/admin/order/{orderId}/payment/manual-receipt` | 契约澄清 | `collectorStaffId` 仅对 `DRIVER_CASH` 条件必填;`BANK_TRANSFER` 条件必填 `transferRef` |
| 3 | 文档 | `2026-07/10_4884_线下收款代收人-修改接口-管理后台.md` | 示例纠错 | 将对公转账的错误 `collectors` 对象改为 `[]` |
## 3. 接口详情
### 3.1 查询线下收款选项
- **方法与路径**`GET /v3/admin/order/{orderId}/payment/manual-receipt/options`
- **使用场景**:打开登记线下收款表单时,查询当前订单可用的渠道、款项类型、代收人和收款方式。
- **认证**:需要管理后台 JWT。
- **幂等性**:幂等,只读查询。
- **限流**:无接口专属限流规则。
### 3.2 登记线下收款
- **方法与路径**`POST /v3/admin/order/{orderId}/payment/manual-receipt`
- **使用场景**:按 options 当前返回的可用渠道和款项类型登记一笔线下收款。
- **认证**:需要管理后台 JWT。
- **幂等性**:非幂等,每次成功请求会新增一条收款记录。
- **限流**:无接口专属限流规则。
## 4. 接口入参
### 4.1 路径参数(两个接口通用)
| 字段 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| `orderId` | Path | String(Long) | 是 | 订单 ID,按字符串处理 |
GET 接口无 Query 参数、无请求体。
### 4.2 POST 请求体
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
| `channel` | String | 是 | 收款渠道 | `DRIVER_CASH` / `BANK_TRANSFER` / `CONSULTANT_COLLECTION` |
| `payType` | String | 是 | 款项类型 | 必须取 options 中当前渠道的 `allowedPayTypes` |
| `amount` | Decimal | 是 | 收款金额 | 最小 `0.01`,不能超过当前可收余额 |
| `receivedAt` | String(LocalDateTime) | 否 | 收款时间 | `yyyy-MM-dd'T'HH:mm:ss`;不传默认当前时间 |
| `transferRef` | String | 条件必填 | 对公转账流水号 | `BANK_TRANSFER` 必填,其他渠道不使用 |
| `receiptMethod` | String | 否 | 收款方式 | 取当前渠道 `receiptMethods[].value``BANK_TRANSFER` 为空 |
| `collectorStaffId` | String(Long) | 条件必填 | 代收人 assignmentId | **仅 `DRIVER_CASH` 必填**,且必须取当前渠道 `collectors[].collectorId` |
| `collectorType` | String | 否 | 实际代收人类型 | 不传时按 `channel` 推导;如传入,必须与渠道匹配 |
| `voucherUrls` | Array<String> | 否 | 凭证图片 URL 列表 | 可为空数组或不传 |
| `remark` | String | 否 | 备注 | 最长 500 字 |
### 4.3 渠道联动必填矩阵
| `channel` | `collectorStaffId` | `collectorType` | `transferRef` | 代收人规则 |
|---|---|---|---|---|
| `BANK_TRANSFER` | 不需要;误传也不作为员工代收人处理 | 可不传;如传只能为 `COMPANY_ACCOUNT` | **必填** | 不选择任何员工,公司账户是收款归属而非代收人候选项 |
| `CONSULTANT_COLLECTION` | 不需要 | 可不传;如传只能为 `CONSULTANT` | 不需要 | 使用订单定制师,不使用员工选择器 |
| `DRIVER_CASH` | **必填** | 可不传;如传只能为 `ORDER_STAFF` | 不需要 | 仅能选当前订单 options 返回的有效报账人 |
## 5. 出参(响应)
### 5.1 通用响应包装
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` | Integer | `200` 表示成功,其他值为业务错误码 |
| `message` | String | 结果或错误说明 |
| `data` | Object/null | 业务数据;失败时通常为 `null` |
| `success` | Boolean | 是否成功 |
### 5.2 GET options 的 `data`
| 字段 | 类型 | 说明 |
|---|---|---|
| `channels` | Array<ChannelOption> | 当前订单的线下收款渠道列表 |
| `channels[].channel` | String | 渠道枚举值 |
| `channels[].channelText` | String | 渠道展示文案 |
| `channels[].allowedPayTypes` | Array<String> | 当前订单状态下该渠道允许的款项类型;以本次响应为准 |
| `channels[].disabled` | Boolean | `true` 表示当前不可提交该渠道 |
| `channels[].disabledReason` | String/null | 禁用原因;可用时为 `null` |
| `channels[].collectors` | Array<CollectorOption> | **该渠道自己的代收人候选列表**`BANK_TRANSFER``[]` |
| `channels[].receiptMethods` | Array<OptionItem> | 该渠道可选收款方式;`BANK_TRANSFER``[]` |
| `collectors[].collectorType` | String | 代收人类型 |
| `collectors[].collectorId` | String(Long) | `ORDER_STAFF` 为 assignmentId,`CONSULTANT` 为管理员 ID |
| `collectors[].collectorName` | String | 代收人姓名 |
| `collectors[].collectorRole` | String | 代收人角色值 |
| `collectors[].collectorRoleText` | String | 代收人角色文案 |
| `collectors[].defaultSelected` | Boolean | 是否默认选中 |
| `receiptMethods[].value` | String | 收款方式值 |
| `receiptMethods[].label` | String | 收款方式文案 |
| `receiptMethods[].defaultSelected` | Boolean | 是否默认选中 |
### 5.3 POST 的 `data`
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String(Long) | 收款凭据 ID |
| `orderId` | String(Long) | 订单 ID |
| `channel` / `channelLabel` | String | 收款渠道值 / 文案 |
| `payType` / `payTypeLabel` | String | 款项类型值 / 文案 |
| `amount` | Decimal | 本次收款金额 |
| `receivedAt` | String(LocalDateTime) | 收款时间 |
| `collectorStaffId` / `collectorStaffName` | String(Long)/String/null | 仅 `DRIVER_CASH` 有值 |
| `collectorType` | String | 实际代收人类型 |
| `collectorAdminId` | String(Long)/null | `CONSULTANT_COLLECTION` 为定制师管理员 ID |
| `collectorName` / `collectorRole` | String | 代收归属快照名称 / 角色 |
| `transferRef` | String/null | 对公转账流水号,仅 `BANK_TRANSFER` 有值 |
| `receiptMethod` / `receiptMethodLabel` | String/null | 收款方式值 / 文案;`BANK_TRANSFER` 为空 |
| `voucherUrls` | Array<String> | 凭证图片 URL 列表 |
| `remark` | String/null | 备注 |
| `operatorName` | String | 登记人姓名 |
| `createTime` | String(LocalDateTime) | 登记时间 |
| `voided` | Boolean | 是否已撤销;新登记为 `false` |
| `voidedByName` / `voidedAt` / `voidReason` | String/null | 撤销信息;新登记时为 `null` |
| `paidAmountAfter` | Decimal | 登记后订单累计已付金额 |
| `payStatusAfter` | String | 登记后订单支付状态 |
## 6. 枚举 / 数据字典
### 6.1 `channel`
**所属字段**`channel` / `channels[].channel`**类型**String
| 值 | 中文 | 说明 |
|---|---|---|
| `BANK_TRANSFER` | 对公转账 | 无员工代收人,必须填 `transferRef` |
| `CONSULTANT_COLLECTION` | 定制师代收 | 使用订单定制师,不传 `collectorStaffId` |
| `DRIVER_CASH` | 报账人收款 | 仅允许尾款,必须从本渠道 `collectors` 选择代收人 |
### 6.2 `payType`
**所属字段**`payType` / `channels[].allowedPayTypes[]`**类型**String
| 值 | 中文 | 说明 |
|---|---|---|
| `DEPOSIT` | 订金 | 是否可登记以 options 当前返回为准 |
| `FULL` | 全款 | 是否可登记以 options 当前返回为准 |
| `BALANCE` | 尾款 | 是否可登记以 options 当前返回为准;`DRIVER_CASH` 只允许此值 |
> `BANK_TRANSFER` 的通用契约可支持 `DEPOSIT` / `FULL` / `BALANCE`,但具体订单当次能提交哪些值,必须以 options 的 `allowedPayTypes` 为准,不要将某个实例的 `BALANCE` 硬编码为全局规则。
### 6.3 `collectorType`
**所属字段**`collectorType` / `collectors[].collectorType`**类型**String
| 值 | 中文 | 匹配渠道 |
|---|---|---|
| `COMPANY_ACCOUNT` | 公司账户 | `BANK_TRANSFER` |
| `CONSULTANT` | 定制师 | `CONSULTANT_COLLECTION` |
| `ORDER_STAFF` | 订单工作人员 | `DRIVER_CASH` |
### 6.4 `receiptMethod`
**所属字段**`receiptMethod` / `receiptMethods[].value`**类型**String
| 值 | 中文 | 说明 |
|---|---|---|
| `WECHAT_TRANSFER` | 微信转账 | 人员代收渠道的当前默认字典值 |
| `CASH` | 现金收款 | 人员代收渠道的当前默认字典值 |
`BANK_TRANSFER.receiptMethods=[]`;该字典可扩展,实际可选值以 options 当次返回为准。
### 6.5 `payStatusAfter`
**所属字段**POST 响应 `payStatusAfter`**类型**String
| 值 | 中文 | 说明 |
|---|---|---|
| `UNPAID` | 未付款 | 尚未完成有效收款 |
| `DEPOSIT_PAID` | 已付订金 | 订金已收 |
| `FULLY_PAID` | 已付全款 | 应收金额已收齐 |
## 7. 错误码
| code | message / 含义 | 触发场景 |
|---|---|---|
| `520011` | 支付类型无效或与订单状态不匹配 | `payType` 不在当前 options 允许范围内 |
| `520401` | 收款渠道非法 | `channel` 不在三个渠道枚举中 |
| `520402` | 对公转账渠道必须填写转账流水号 | `BANK_TRANSFER` 未传 `transferRef` |
| `520403` | 报账人收款渠道必须指定代收人 | `DRIVER_CASH` 未传 `collectorStaffId` |
| `520404` | 代收人不属于本订单人员 | `collectorStaffId` 不是本订单有效人员 |
| `520407` | 订单已取消,不允许登记线下收款 | 已取消订单提交 POST |
| `520408` | 收款金额必须大于 0 | `amount < 0.01` |
| `520409` | 线下收款代收人类型非法 | `collectorType``channel` 不匹配 |
| `520410` | 报账人收款只能登记尾款 | `DRIVER_CASH` 提交 `DEPOSIT``FULL` |
| `520411` | 报账人收款必须选择本订单报账人 | 选中的订单人员不是报账人 |
| `520412` | 订单没有可用定制师,不能登记定制师代收 | `CONSULTANT_COLLECTION` 无可用定制师 |
| `520413` | 本次收款金额超过当前可收余额 | `amount` 大于当前可收金额 |
## 8. 示例(典型 + 边界 + 异常)
### 8.1 典型:查询选项,对公转账无代收人
**请求**
```http
GET /v3/admin/order/2079454953641836546/payment/manual-receipt/options
Authorization: Bearer <admin-jwt>
无请求体
```
**响应**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"channels": [
{
"channel": "CONSULTANT_COLLECTION",
"channelText": "定制师代收",
"allowedPayTypes": ["BALANCE"],
"disabled": false,
"disabledReason": null,
"collectors": [
{
"collectorType": "CONSULTANT",
"collectorId": "2037350531801993218",
"collectorName": "张三",
"collectorRole": "CONSULTANT",
"collectorRoleText": "定制师",
"defaultSelected": true
}
],
"receiptMethods": [
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
]
},
{
"channel": "BANK_TRANSFER",
"channelText": "对公转账",
"allowedPayTypes": ["BALANCE"],
"disabled": false,
"disabledReason": null,
"collectors": [],
"receiptMethods": []
},
{
"channel": "DRIVER_CASH",
"channelText": "报账人收款",
"allowedPayTypes": [],
"disabled": true,
"disabledReason": "本订单暂无可代收报账人",
"collectors": [],
"receiptMethods": [
{"value": "WECHAT_TRANSFER", "label": "微信转账", "defaultSelected": true},
{"value": "CASH", "label": "现金收款", "defaultSelected": false}
]
}
]
},
"success": true
}
```
### 8.2 边界:对公转账不传代收人
**场景说明**`collectorStaffId``collectorType` 都不传;仅提交 options 当前允许的款项类型与对公转账流水号。
**请求**
```http
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"channel": "BANK_TRANSFER",
"payType": "BALANCE",
"amount": 100.00,
"transferRef": "BANK202607210001",
"voucherUrls": [],
"remark": "客户对公转账"
}
```
**响应**
```json
{
"code": 200,
"message": "操作成功",
"data": {
"id": "2079600000000000001",
"orderId": "2079454953641836546",
"channel": "BANK_TRANSFER",
"channelLabel": "对公转账",
"payType": "BALANCE",
"payTypeLabel": "尾款",
"amount": 100.00,
"receivedAt": "2026-07-21T15:30:00",
"collectorStaffId": null,
"collectorStaffName": null,
"collectorType": "COMPANY_ACCOUNT",
"collectorAdminId": null,
"collectorName": "公司账户",
"collectorRole": "COMPANY_ACCOUNT",
"transferRef": "BANK202607210001",
"receiptMethod": null,
"receiptMethodLabel": null,
"voucherUrls": [],
"remark": "客户对公转账",
"operatorName": "管理员",
"createTime": "2026-07-21T15:30:00",
"voided": false,
"voidedByName": null,
"voidedAt": null,
"voidReason": null,
"paidAmountAfter": 1600.00,
"payStatusAfter": "DEPOSIT_PAID"
},
"success": true
}
```
### 8.3 异常:报账人收款未选择代收人
**请求**
```http
POST /v3/admin/order/2079454953641836546/payment/manual-receipt
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"channel": "DRIVER_CASH",
"payType": "BALANCE",
"amount": 100.00,
"receiptMethod": "CASH"
}
```
**响应**
```json
{
"code": 520403,
"message": "报账人收款渠道必须指定代收人",
"data": null,
"success": false
}
```
## 9. 业务边界
- `collectors` 是每个 channel 自己的候选列表,不是所有渠道共用的必选列表。
- `BANK_TRANSFER``collectors=[]`,不传 `collectorStaffId`,必须传 `transferRef`;即使误传 `collectorStaffId`,响应中员工代收人 ID 仍为空。
- `CONSULTANT_COLLECTION`:不要提交 `collectorStaffId`;当订单没有可用定制师时,渠道禁用。
- `DRIVER_CASH`:仅允许 `BALANCE`,必须传当前订单有效报账人的 assignmentId。
- options 的 `allowedPayTypes` 随订单状态和可收余额变化。待支付订单中,对公转账/定制师代收可返回 `DEPOSIT``FULL`;非待支付且仍有可收余额时可返回 `BALANCE`
- 渠道 `disabled=true``allowedPayTypes=[]` 时,当前不可提交该渠道。
- 已取消订单、无可收余额的订单不能登记线下收款。
## 10. 修改前后对比
> 本节对比的是“错误理解 / 错误文档示例”与“正确的现有契约”,不表示后端今日发布了新的接口变更。
| 项目 | 错误理解 / 历史错误示例 | 正确契约 |
|---|---|---|
| 对公转账的代收人候选 | `BANK_TRANSFER.collectors` 含“公司账户”对象 | `BANK_TRANSFER.collectors=[]` |
| `collectorStaffId` 字段 | 所有渠道都要选代收人,或该字段已从后端删除 | 字段仍保留,**仅 `DRIVER_CASH` 条件必填** |
| 对公转账必填项 | 代收人 | `transferRef` 转账流水号 |
| 定制师代收 | 复用员工代收人选择器并传 `collectorStaffId` | 不要传 `collectorStaffId`,使用订单定制师 |
| 对公转账款项类型 | 固定只能是某一种款项 | 以 options 当前返回的 `allowedPayTypes` 为准 |
## 11. 影响评估 / 回滚
### 11.1 影响评估
- **是否破坏向后兼容**:否。后端字段、枚举和行为没有变更。
- **前端是否必须同步上线**:是。已有页面在 `BANK_TRANSFER` 下显示代收人,需要按正确契约纠正。
### 11.2 回滚说明
- 本次仅修正通知文档,不涉及后端接口回滚。
- 若前端回滚渠道联动修正,对公转账将再次错误显示代收人。
## 12. 注意事项
- 选中 `BANK_TRANSFER` 时,隐藏代收人选择器,并清空从其他渠道切换前残留的 `collectorStaffId`
- 选中 `BANK_TRANSFER` 时,显示并校验 `transferRef`,不要根据统一响应结构中“存在 `collectors` 字段”就认定代收人必选。
- 仅 `DRIVER_CASH``collectorStaffId` 设为必填,候选项取当前 channel 的 `collectors`
- `CONSULTANT_COLLECTION` 不要复用 `DRIVER_CASH` 的员工代收人校验。
- 不要把测试订单中 `BANK_TRANSFER.allowedPayTypes=["BALANCE"]` 固化为全局规则;每次均以 options 返回为准。
## 13. 关联 / 联系人
### 13.1 关联
- **Issue**[#5120](https://git.1814.love:8443/wx/HL/issues/5120)
- **后端 PR**:无(本次无后端代码变更)
- **后端 commit**:无(本次无后端代码变更)
### 13.2 联系人
- **后端负责人**:腰苏图