更新一下

This commit is contained in:
2026-07-21 15:26:07 +09:00
parent 2823ff13bf
commit 4902a02c87
32 changed files with 4138 additions and 294 deletions

View File

@@ -1,6 +1,6 @@
# 需求15/16/18/19/20/21 技术方案
> 状态:原需求来源稿;其中本地审批流、审批页面操作密码方案已废弃,最终以企微审批方案和标准评审稿为准。
> 状态:原需求来源稿;其中本地审批流、审批页面操作密码以及基于原退款单 `resubmit` 的方案已废弃,最终以企微审批方案和标准评审稿为准。
> 评审建议:需求 15/16、需求 18/20/21、需求 19 分三组评审,不在一次会议中混合确认。
---
@@ -166,15 +166,15 @@ sequenceDiagram
评审结论:支付方式按**整批统一**设计:
- 页面选择 `offline``agent_wallet`Excel 不再重复填写支付方式。
- 页面选择 `offline``agent_wallet`CSV 不再重复填写支付方式。
- 混合支付拆成两个批次,避免一份凭证对应多种支付语义。
- Excel 不包含支付方式列,后端拒绝同一批次混合支付。
- CSV 不包含支付方式列,后端拒绝同一批次混合支付。
### 业务流程
```mermaid
flowchart TD
Start[员工选择代理和支付方式] --> Upload[上传 Excel]
Start[员工选择整批支付方式] --> Upload[上传可含不同代理资产的 CSV]
Upload --> Parse[解析并持久化逐行明细]
Parse --> Validate[校验资产、套餐、归属和重复行]
Validate --> Item{处理下一条有效明细}
@@ -193,6 +193,10 @@ flowchart TD
任务允许部分成功。每一行是独立、可重试、可审计的业务单元,不能只保存一段失败 JSON。
同一 CSV 内按“资产类型 + 标准化资产标识 + 套餐编码”判断重复:首次出现的行正常处理,后续重复行失败并记录首次出现的行号;同一资产订购不同套餐不算重复。本期不把重复行解释为购买多份,未来如有多份订购需求,应增加明确数量字段。
本页面和全部批量订购 API 只允许超级管理员,或具备独立“批量订购套餐”权限的平台账号访问;代理和企业账号一律禁止。平台权限不能替代逐行的资产归属、套餐授权、成本价和钱包业务校验。
### 数据库变更
```sql
@@ -205,7 +209,6 @@ CREATE TABLE tb_bulk_purchase_task (
updater BIGINT NOT NULL DEFAULT 0,
task_no VARCHAR(30) NOT NULL,
source_file_key VARCHAR(500) NOT NULL,
shop_id BIGINT NOT NULL,
operator_id BIGINT NOT NULL,
payment_method VARCHAR(20) NOT NULL,
voucher_keys JSONB NOT NULL DEFAULT '[]',
@@ -232,6 +235,8 @@ CREATE TABLE tb_bulk_purchase_item (
asset_type VARCHAR(20) NOT NULL,
asset_identifier VARCHAR(100) NOT NULL,
package_code VARCHAR(50) NOT NULL,
resolved_shop_id BIGINT,
resolved_shop_name VARCHAR(200) NOT NULL DEFAULT '',
package_name_snapshot VARCHAR(200) NOT NULL DEFAULT '',
package_id BIGINT,
amount BIGINT NOT NULL DEFAULT 0,
@@ -260,10 +265,10 @@ CREATE INDEX idx_bulk_purchase_item_task_status
### 处理与幂等
1. API 校验文件、代理、支付方式和凭证,将 Excel 保存到对象存储并创建任务,返回 `task_id`
2. Worker 根据 `source_file_key` 下载并解析 Excel,将每一行先写入明细表,再开始业务处理。
3. 同一任务按行顺序处理,避免对同一代理钱包制造不必要的乐观锁冲突
4. 每行使用独立事务。代理钱包支付时锁定钱包记录,校验 `balance - frozen_balance + credit_limit` 后,在同一事务扣款、创建订单、资金流水并更新明细
1. 前端通过对象存储预签名地址直传 CSV 和线下凭证,再由 API 校验 `file_key`、支付方式和 `voucher_keys` 并创建任务,返回 `task_id`;业务接口不接收 `shop_id` 或文件字节
2. Worker 根据 `source_file_key` 下载并解析 UTF-8 CSV允许 BOM,将每一行先写入明细表,再开始业务处理;不接受 Excel
3. 同一任务按行顺序处理;每行按资产当前归属解析结算代理,同一任务可以依次处理不同代理钱包。
4. 每行使用独立事务。代理钱包支付时锁定该行结算代理的主钱包,按有效信用额度校验总可用金额后,在同一事务扣款、创建订单、资金流水并更新明细;无归属、归属异常、无主钱包或余额不足只失败该行
5. `idempotency_key` 使用 `bulk_purchase:{task_id}:{row_no}`。Worker 重试时,已存在成功订单的明细直接跳过。
6. 单行失败不回滚已成功行;失败原因写结构化错误码和用户可见中文原因。
7. 任务汇总从明细表计算,不信任内存计数。
@@ -274,7 +279,7 @@ Asynq 载荷只传任务 ID不传文件字节或临时路径。源文件保
### API 设计
**前端静态 Excel 模板**
**前端静态 CSV 模板**
模板由前端项目随版本发布,后端不提供下载接口。按已确认的“整批统一支付方式”,模板字段为:
@@ -288,15 +293,17 @@ Asynq 载荷只传任务 ID不传文件字节或临时路径。源文件保
```text
POST /api/admin/bulk-purchases
Content-Type: multipart/form-data
Content-Type: application/json
shop_id: 123
payment_method: offline | agent_wallet
voucher_keys: ["key1","key2"]
file: <Excel文件>
{
"request_id": "01J...",
"payment_method": "offline",
"file_key": "bulk-purchase/2026/07/xxx.csv",
"voucher_keys": ["attachment/2026/07/key1"]
}
```
`offline``voucher_keys` 必填,`agent_wallet`忽略该字段
CSV 先调用 `POST /api/admin/storage/upload-url` 并使用独立 `purpose=bulk_purchase` 获取预签名地址后直传;线下凭证使用附件用途直传。`offline``voucher_keys` 必填,`agent_wallet`必须为空。创建任务前校验对象存在、归属当前上传主体、CSV 类型和 10MB 大小限制
**查询任务状态**
@@ -313,7 +320,7 @@ GET /api/admin/bulk-purchases/{task_id}/items?status=4&page=1&page_size=50
### 前端技术方案
- 页面分为“参数确认 → 文件上传 → 处理中 → 结果”四个稳定步骤,刷新页面后可根据任务 ID 恢复进度。
- 提交前展示代理、支付方式、凭证数量和文件名的二次确认;钱包支付额外展示当前可用余额,但最终以 Worker 扣款时校验为准
- 提交前展示支付方式、凭证数量和文件名的二次确认;不展示整批目标代理或单一钱包余额。任务结果按行展示后端解析的结算代理及金额
- 任务处理中展示总数、已处理数、成功数和失败数,轮询规则复用统一异步任务方案。
- 结果页默认显示失败明细,可切换全部/成功/失败,并可按资产标识搜索。
- 部分成功使用明确状态,不弹“全部成功”提示;再次上传失败行会创建新任务,不修改旧任务历史。
@@ -413,7 +420,9 @@ POST /api/admin/refunds/{id}/manual-complete
请求包含 `request_id`、可选 `remark` 和最多 5 个完成凭证。后端仅允许具备财务确认权限的账号对 `status=2 AND processing_status IN (0,3)` 的非代理钱包退款操作;实际金额沿用审批金额,不允许在确认时再次改价。确认记录操作人、时间、备注和凭证后将处理状态置为已完成。
### 退回后重新提交
### 退回后重新提交(已废弃,禁止实施)
> 本节是旧本地审批方案的历史记录。七月最终方案下线该接口;企微拒绝会终结原退款单,后续仍需退款时重新创建新退款单。
```
POST /api/admin/refunds/{id}/resubmit