新增接口

This commit is contained in:
2026-08-18 16:15:46 +08:00
parent d256f6d176
commit 247d7d9f6e
20 changed files with 523 additions and 16178 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-18

View File

@@ -0,0 +1,45 @@
## Context
历史记录在 `tb_refund_request``tb_agent_recharge_record` 中保持待审批但 `approval_instance_id` 为空。现有新建用例已经在单个事务中创建通用审批实例、企业微信上下文、审批提交 Outbox并回填该关联两个业务表的 `approval_instance_id` 均有唯一索引,通用审批实例还以业务类型和业务 ID 唯一。
## Goals / Non-Goals
**Goals:**
- 以最小增量复用现有审批创建和可靠提交链路补发历史记录。
- 以原创建账号构造企业微信发起人及审批快照。
- 使并发请求和重复请求均不会形成第二张审批单。
**Non-Goals:**
- 不批量扫描或自动补发历史记录。
- 不改变既有审批终态、企业微信提交重试或人工审批接口。
- 不新增迁移、重置既有审批关联,或为提交失败创建第二张审批单。
## Decisions
### 在各业务审批创建用例中增加历史记录发起入口
退款和线下代理充值分别新增面向既有记录的 Application 用例入口,复用各自已有的快照构造、提交人校验、通用审批 `Prepare`/`CreateInTx`、审计及 DTO 组装逻辑。Handler 只解析路径 ID 并调用服务Service 负责加载完整业务事实和调用 Application。
选择按业务保留两个小入口,而不引入跨退款/充值的通用“历史审批补发器”:二者的资格条件、快照和关联事实不同,现有两个创建用例已是最短复用边界。
### 以事务内条件更新和既有唯一约束保证一次性
发起前可在事务外执行审批渠道预检;事务内必须重新读取或条件更新业务记录,要求 `status=待审批 AND approval_instance_id IS NULL`,再创建通用审批及渠道上下文/Outbox并回填 `approval_instance_id`。任一环节失败回滚,不消耗发起资格;成功提交后,由业务表关联唯一索引和通用审批业务唯一索引共同拒绝并发的第二次创建。
不增加“已尝试”字段:用户确认以成功创建审批实例作为一次性边界,已有唯一关联就是持久化且可恢复的事实源。
### 发起人和授权语义
企业微信发起人固定为业务记录 `Creator`,不使用点击接口的账号;该账号不可用时失败关闭。接口沿用各自当前路由组的账号类型授权,不扩大既有退款或代理充值管理入口的访问范围。返回值沿用现有详情 DTO 的审批摘要字段,避免新增响应类型。
## Risks / Trade-offs
- [原创建账号已禁用或未绑定企业微信] → 不创建任何审批事实并返回错误;维护者修复账号/绑定后可再次操作。
- [两个请求同时发起] → 事务条件和数据库唯一约束确保仅一个提交成功,调用方对另一个请求按冲突处理。
- [提交 Outbox 后企微调用结果未知] → 沿用已有结果未知恢复流程,禁止通过本接口重建审批。
## Migration Plan
1. 发布 API 与 Worker 均包含该版本的应用代码,确保 Outbox 消费者已注册。
2. 维护者在生产环境按发布运行说明,通过列表筛选待审批历史记录后逐单调用新接口,并核对返回的审批摘要与审计/Outbox 事实。
3. 如需回滚,仅停止暴露新路由并回滚应用二进制;已成功创建的审批实例继续由既有 Worker 流程处理,不删除审批关联或重新发起。

View File

@@ -0,0 +1,28 @@
## Why
七月迭代上线前已创建且仍待审批的退款申请、员工线下代充值申请未关联通用审批实例,无法进入企业微信审批流。需要由管理员按单主动补发,同时避免同一业务重复创建审批单。
## What Changes
- 为待审批且尚未关联审批实例的历史退款申请新增主动发起企业微信审批接口。
- 为待审批、线下支付且尚未关联审批实例的历史代理充值申请新增主动发起企业微信审批接口。
- 主动发起时复用原业务创建人作为企业微信审批发起人;原创建人不可用或审批场景不可用时不创建审批实例。
- 在同一事务创建通用审批实例、企业微信上下文、提交 Outbox 并回填业务记录的 `approval_instance_id`,以该唯一关联保证成功创建后不可再次发起。
- 仅在业务保持待审批状态时允许主动发起;已关联审批实例、非线下充值或非待审批记录均拒绝。
## Capabilities
### New Capabilities
- 无。
### Modified Capabilities
- `order-refund-exchange`: 退款申请可对历史待审批且未关联审批实例的记录主动创建一次企业微信审批。
- `agent-funds-commission`: 历史待审批线下代理充值申请可主动创建一次企业微信审批。
## Impact
- 路由、退款与代理充值 Handler/Service以及审批创建 Application 用例。
- 新增两个后台 API 并同步 OpenAPI 文档生成入口。
- 复用现有通用审批、企业微信审批上下文、Outbox、审计和既有 `approval_instance_id` 唯一索引;不新增外部依赖或数据库表结构。

View File

@@ -0,0 +1,21 @@
## ADDED Requirements
### Requirement: 历史待审批线下代理充值可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/agent-recharges/{id}/trigger-approval`,使具有既有代理充值管理访问权限的后台账号可为历史线下代理充值申请主动创建企业微信审批。系统 MUST 仅在线下充值记录处于待审批状态且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该充值记录的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及充值记录的审批实例关联,并返回更新后的充值申请审批摘要。
#### Scenario: 主动发起历史线下代理充值审批成功
- **GIVEN** 线下代理充值申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信线下充值审批场景均可用
- **WHEN** 有既有代理充值管理访问权限的后台账号请求 `POST /api/admin/agent-recharges/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 在线、非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的充值记录不是线下充值、不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一充值审批
- **WHEN** 两个请求同时为同一符合条件的线下代理充值申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 充值申请原创建账号不可用,或企业微信线下充值审批场景不可用
- **THEN** 系统返回相应错误,充值申请保持未关联审批实例,修复条件后可再次发起

View File

@@ -0,0 +1,21 @@
## ADDED Requirements
### Requirement: 历史待审批退款可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/refunds/{id}/trigger-approval`,使具有既有退款管理访问权限的后台账号可为历史退款申请主动创建企业微信审批。系统 MUST 仅在退款申请状态为待审批且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该退款申请的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及退款申请的审批实例关联,并返回更新后的退款申请审批摘要。
#### Scenario: 主动发起历史退款审批成功
- **GIVEN** 退款申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信退款审批场景均可用
- **WHEN** 有既有退款管理访问权限的后台账号请求 `POST /api/admin/refunds/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的退款申请不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一退款审批
- **WHEN** 两个请求同时为同一符合条件的退款申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 退款申请原创建账号不可用,或企业微信退款审批场景不可用
- **THEN** 系统返回相应错误,退款申请保持未关联审批实例,修复条件后可再次发起

View File

@@ -0,0 +1,16 @@
## 1. 审批补发用例
- [x] 1.1 在退款审批 Application 中实现历史待审批退款的主动发起:加载原创建人和订单事实、复用既有审批快照与 `Prepare`/`CreateInTx` 链路,并在同一事务内按待审批且未关联审批实例的条件回填关联和审计。
- [x] 1.2 在线下代理充值 Application 中实现历史待审批充值的主动发起校验线下支付、待审批和未关联审批实例加载原创建人、店铺和钱包事实并复用既有审批创建、快照、Outbox 与审计链路。
- [x] 1.3 在退款和代理充值 Service 中接入补发用例,复核既有路由权限与资源查询范围,向调用方返回包含审批摘要的既有 DTO将并发或已关联审批实例映射为状态冲突。
## 2. HTTP 入口与文档
- [x] 2.1 在退款 Handler 和路由注册 `POST /api/admin/refunds/{id}/trigger-approval`,完成路径 ID 绑定并交由 Service 处理。
- [x] 2.2 在代理充值 Handler 和路由注册 `POST /api/admin/agent-recharges/{id}/trigger-approval`,完成路径 ID 绑定并交由 Service 处理。
- [x] 2.3 同步 `cmd/api/docs.go``cmd/gendocs/main.go` 所依赖的路由元数据,确保两个接口及其响应模型生成到 OpenAPI 文档。
## 3. 验证
- [x] 3.1 以隔离环境或最小可运行验证覆盖:两个符合资格的历史记录各只创建一次审批实例和提交 Outbox非待审批、已关联、在线充值、原创建人/场景不可用及并发重复请求不创建第二实例。
- [ ] 3.2 执行 `gofmt -w`(变更的 Go 文件)、`go build ./cmd/api ./cmd/worker``go run cmd/gendocs/main.go``openspec validate --all``./scripts/context-health.sh`

View File

@@ -97,13 +97,38 @@
- **WHEN** 当前账号请求资金概况列表但未提供 `shop_id`
- **THEN** 系统继续按既有分页、数据范围、店铺名称和主账号用户名条件返回结果
### Requirement: 历史待审批线下代理充值可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/agent-recharges/{id}/trigger-approval`,使具有既有代理充值管理访问权限的后台账号可为历史线下代理充值申请主动创建企业微信审批。系统 MUST 仅在线下充值记录处于待审批状态且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该充值记录的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及充值记录的审批实例关联,并返回更新后的充值申请审批摘要。
#### Scenario: 主动发起历史线下代理充值审批成功
- **GIVEN** 线下代理充值申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信线下充值审批场景均可用
- **WHEN** 有既有代理充值管理访问权限的后台账号请求 `POST /api/admin/agent-recharges/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 在线、非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的充值记录不是线下充值、不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一充值审批
- **WHEN** 两个请求同时为同一符合条件的线下代理充值申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 充值申请原创建账号不可用,或企业微信线下充值审批场景不可用
- **THEN** 系统返回相应错误,充值申请保持未关联审批实例,修复条件后可再次发起
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
### 代理预充值
`GET /api/admin/agent-recharges`(查询代理充值订单列表);`POST /api/admin/agent-recharges`(创建代理充值订单);`GET /api/admin/agent-recharges/{id}`(查询代理充值订单详情);`POST /api/admin/agent-recharges/{id}/offline-pay`(确认线下充值);`GET /api/admin/agent-recharges/{id}/payment-status`(查询代理充值本地支付与到账状态);`POST /api/admin/agent-recharges/{id}/reject`(驳回代理充值订单);`GET /api/admin/agent-recharges/payment-methods`(查询代理在线充值可用支付方式)。
`GET /api/admin/agent-recharges`(查询代理充值订单列表);`POST /api/admin/agent-recharges`(创建代理充值订单);`GET /api/admin/agent-recharges/{id}`(查询代理充值订单详情);`POST /api/admin/agent-recharges/{id}/trigger-approval`(补发历史线下代理充值审批);`POST /api/admin/agent-recharges/{id}/offline-pay`(确认线下充值);`GET /api/admin/agent-recharges/{id}/payment-status`(查询代理充值本地支付与到账状态);`POST /api/admin/agent-recharges/{id}/reject`(驳回代理充值订单);`GET /api/admin/agent-recharges/payment-methods`(查询代理在线充值可用支付方式)。
### 代理商资金管理

View File

@@ -38,6 +38,31 @@
- **WHEN** 该代理查询退款列表或退款申请详情
- **THEN** 系统返回空列表或不存在,且不泄露任何退款申请
### Requirement: 历史待审批退款可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/refunds/{id}/trigger-approval`,使具有既有退款管理访问权限的后台账号可为历史退款申请主动创建企业微信审批。系统 MUST 仅在退款申请状态为待审批且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该退款申请的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及退款申请的审批实例关联,并返回更新后的退款申请审批摘要。
#### Scenario: 主动发起历史退款审批成功
- **GIVEN** 退款申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信退款审批场景均可用
- **WHEN** 有既有退款管理访问权限的后台账号请求 `POST /api/admin/refunds/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的退款申请不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一退款审批
- **WHEN** 两个请求同时为同一符合条件的退款申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 退款申请原创建账号不可用,或企业微信退款审批场景不可用
- **THEN** 系统返回相应错误,退款申请保持未关联审批实例,修复条件后可再次发起
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
@@ -48,7 +73,7 @@
### 退款管理
`GET /api/admin/refunds`(退款申请列表);`POST /api/admin/refunds`(创建退款申请);`GET /api/admin/refunds/{id}`(退款申请详情);`POST /api/admin/refunds/{id}/approve`(审批通过退款申请);`POST /api/admin/refunds/{id}/reject`(审批拒绝退款申请);`POST /api/admin/refunds/{id}/resubmit`(重新提交退款申请);`POST /api/admin/refunds/{id}/return`(退回退款申请)。
`GET /api/admin/refunds`(退款申请列表);`POST /api/admin/refunds`(创建退款申请);`GET /api/admin/refunds/{id}`(退款申请详情);`POST /api/admin/refunds/{id}/trigger-approval`(补发历史退款审批);`POST /api/admin/refunds/{id}/approve`(审批通过退款申请);`POST /api/admin/refunds/{id}/reject`(审批拒绝退款申请);`POST /api/admin/refunds/{id}/resubmit`(重新提交退款申请);`POST /api/admin/refunds/{id}/return`(退回退款申请)。
### 换货管理