更新一下

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,7 +1,7 @@
# 7月迭代技术方案标准评审稿
> 状态:待评审
> 最后更新2026-07-17
> 最后更新2026-07-20
> 分支:`Iteration/7-11`
> 系统:`junhong_cmp_fiber` 及配套后台、代理端、C 端前端
> 负责人:待指定
@@ -38,7 +38,7 @@
- 套餐临期企业微信消息推送;本期先完成站内通知和防重记录。
- 全仓 MVC/贫血模型一次性重构。
- 为旧退款和线下充值审批接口建设长期兼容层。
- 后端提供 Excel 模板下载接口;模板由前端静态资源随版本发布。
- 后端提供批量导入模板下载接口;CSV 模板由前端静态资源随版本发布。
- 微信/支付宝自动退款,以及基于套餐规则的自动限速。
- 运营商解除实名后自动回滚本地实名状态;第一版只保留防腐层入口和集成记录。
- 禅道草稿 #41“代理查询限制”和 #51“不同品类资产换货”;草稿不进入本期开发、工时和验收。
@@ -52,18 +52,18 @@
| D-03 | 不建设本地审批流;节点、审批人、意见和审批附件全部由企微模板及审批详情负责 | 18、20、21 |
| D-04 | 本系统保存提交业务快照、本地业务资料和企微审批详情快照,审批人能够看到审批对象、备注和附件 | 18、20、21 |
| D-05 | 平台员工不建立钱包信用额度,信用额度只属于代理主钱包 | 17、19 |
| D-06 | 批量订购支付方式整批统一,Excel 不包含支付方式 | 19 |
| D-07 | 角色级导出字段权限本期落地,服务端取角色授权与用户选择的交集 | 14 |
| D-08 | Gateway 只按 `cardNo` 手动设置/取消限速;设备入口先解析当前绑定卡,不做套餐自动限速 | 10 |
| D-06 | 批量订购使用 CSV支付方式整批统一,CSV 不包含支付方式 | 19 |
| D-07 | 角色级导出字段权限本期落地;前端不选择、不提交字段,服务端按代码字段目录与当前账号有效角色授权并集自动解析全部导出列 | 14 |
| D-08 | Gateway 只按 `cardNo` 手动设置固定限速等级或恢复不限速;设备入口先解析当前绑定卡,不做套餐自动限速 | 10 |
| D-09 | 需求16整体移出本期不创建相关表、API、流程、页面和迁移 | 16 |
| D-10 | 退款金额在发起时固定;非代理钱包退款由财务人工处理,代理钱包仅回退原扣款主钱包;线下充值企微通过后自动入账 | 20、21 |
| D-11 | 采用停机发布同时切换数据库、API、Worker 和前端,旧审批接口同步下线 | 20、21 |
| D-12 | 企微模板 ID 和控件 ID 均按不可变版本映射;编辑模板前暂停场景,发布新映射后原子切换 | 企微审批 |
| D-13 | 设备批量分配代理与套餐系列拆为两个独立命令 | 08 |
| D-13 | 设备批量分配代理与套餐系列拆为两个独立 CSV 命令,文件先直传私有对象存储,业务接口只接收 `file_key` 和唯一目标 | 08 |
| D-14 | 排队套餐使用购买时长快照推算预计最终到期时间;资产详情、临期和导出共用实时 Query定时任务仅发送通知 | 06、11、22 |
| D-15 | 行业卡是否需要实名由运营商 `realname_link_type` 决定,`card_category` 不参与实名复机和轮询判断 | 01、02、数据同步 |
| D-15 | 卡是否需要实名由运营商 `realname_link_type` 决定,`card_category` 不参与实名复机判断;本期不重新设计现有周期轮询资格 | 01、02、数据同步 |
| D-16 | 数据同步保留轮询兜底关键业务事件按立即、3 分钟、5 分钟触发;超频不建立退避状态 | 数据同步 |
| D-17 | 企微账号由已登录平台员工扫码自助绑定,普通运营不录入 `userid` | 企微审批 |
| D-17 | 企微发起身份按账号类型分流:平台/超级管理员必须扫码绑定并使用本人 `userid`,代理使用部署配置中的固定企微账号代提交;真实业务提交人始终独立进入审批表单、通知和审计 | 企微审批 |
| D-18 | 全系统审计本次一次性切换到 Audit Event + Integration Log多视角 API 和前端同时发布 | 全局 |
| D-19 | 代理在线充值最低 100 元,支持微信 Native 和支付宝 PreCreate支付成功直接入主钱包且不审批 | 21、新增充值 |
| D-20 | 资产层只展示一个预计最终到期时间;当前套餐和全部排队主套餐共同参与推算,临期也使用同一结果 | 06、11、22 |
@@ -72,10 +72,11 @@
| D-23 | 代理主钱包现金可用余额降至 100 元及以下时,向代理主账号和店铺业务员发送一次站内通知;信用额度不参与预警计算 | 禅道 #97 |
| D-24 | 客户角色只提供新建店铺的默认信用配置;修改角色配置不更新任何已有店铺,已有店铺额度只能单独调整 | 17 |
| D-25 | 代理系列套餐授权复用现有批量接口;前端批量选择,已授权套餐明确标记并置灰 | 禅道 #43 |
| D-26 | 一张退款单只对应一条企微审批;企微拒绝同时终结审批和退款单,原单不可修改或重提,后续仍需退款时必须重新创建退款单 | 20、企微审批 |
### 1.4 本期边界
本期包含企业微信审批、账号扫码绑定、回调与轮询补偿;不再保留“先做站内审批、以后切企微”的中间态。套餐临期的企业微信消息推送仍属于后续扩展,本期只实现站内通知及外部渠道可扩展边界。
本期包含企业微信审批、平台账号扫码绑定、代理固定企微账号代提交、回调与轮询补偿;不再保留“先做站内审批、以后切企微”的中间态。套餐临期的企业微信消息推送仍属于后续扩展,本期只实现站内通知及外部渠道可扩展边界。
---
@@ -252,8 +253,8 @@ sequenceDiagram
| 类型 | 接收人 |
|------|--------|
| `wecom.approval.approved/rejected/cancelled` | 申请人账号 |
| `wecom.approval.revoked_after_approved` | 申请人和财务角色账号 |
| `wecom.approval.approved/rejected/cancelled` | 真实业务提交人账号 |
| `wecom.approval.revoked_after_approved` | 真实业务提交人和财务角色账号 |
| `wecom.template.invalid` | 平台超管 |
| `package.expiring` | 代理/企业相关账号 |
| `card_sync.failed` | 平台运维角色 |
@@ -281,18 +282,18 @@ API
企业微信负责模板、审批节点、审批人、会签/或签、通过、驳回、撤销、审批意见、审批附件和企业微信端待办。本系统不保存本地审批任务,也不提供审批按钮,只负责:
- 创建退款和平台员工线下充值业务单。
- 创建退款(允许现有平台/超级管理员/代理发起)和平台员工线下充值业务单。
- 保存提交时业务快照和本地业务资料,上传企微附件副本。
- 以稳定场景码映射企微模板版本和控件 ID。
- 保存 `sp_no`、审批状态、审批人/意见/附件详情快照。
- 接收回调并以 `getapprovaldetail` 查询作为状态权威来源。
- 审批终态后幂等执行退款或线下充值业务处理。
Viper 配置包含 `corp_id``agent_id``agent_secret`、回调 Token/EncodingAESKey、审批回调路径、账号绑定回调 URL、2 分钟审批轮询和 10 分钟模板验证间隔。Secret 只允许环境变量覆盖,后台只返回“是否配置”和最近连通结果。Access Token 缓存在 RedisTTL 使用 `expires_in-300秒` 并通过锁避免并发刷新。
Viper 配置包含 `corp_id``agent_id``agent_secret`、回调 Token/EncodingAESKey、审批回调路径、账号绑定回调 URL、代理代提交固定成员 `agent_approval_creator_userid`、2 分钟审批轮询和 10 分钟模板验证间隔。Secret 和代理代提交身份只允许通过部署配置提供,后台只返回“是否就绪”、固定成员显示名和最近连通结果,不提供在线修改固定 `userid`。Access Token 缓存在 RedisTTL 使用 `expires_in-300秒` 并通过锁避免并发刷新。
```mermaid
sequenceDiagram
actor Staff as 平台员工
actor Submitter as 业务提交人
participant App as Refund/Recharge Application
participant DB as PostgreSQL
participant Outbox as Outbox
@@ -301,10 +302,10 @@ sequenceDiagram
participant Sync as Approval Sync
participant Biz as 业务终态用例
Staff->>App: 创建退款/线下充值
Submitter->>App: 创建退款/线下充值
App->>DB: 业务单+企微实例(submitting)
App->>Outbox: SubmissionRequested
Worker->>WeCom: 上传附件并applyevent
Worker->>WeCom: 按账号类型解析userid并applyevent
WeCom-->>Worker: sp_no
WeCom->>Sync: 加密状态回调
Sync->>WeCom: getapprovaldetail
@@ -337,34 +338,25 @@ sequenceDiagram
退款至少映射店铺、退款单号、申请金额、原因、附件和提交人;线下充值至少映射店铺、充值单号、金额、备注、附件和提交人。后台每 10 分钟验证启用版本,模板不可访问或 fingerprint 变化时暂停场景并发送系统告警。
#### 3.3.3 系统账号扫码绑定企微成员
场景处于暂停中、已暂停,或当前模板版本失效时,新的退款/线下充值创建请求必须在写入任何业务单、审批实例或 Outbox 之前失败,并返回明确的“审批场景当前不可用”。前端保留用户已经填写的表单,待场景恢复后由用户重新提交;后端不为失败请求保留待补提的孤儿业务单。暂停前已经成功创建的审批实例继续接收回调、执行 2 分钟兜底同步和终态业务处理,不受场景暂停影响。
普通运营不录入 `userid`。员工先登录本系统,再点击“绑定企业微信”,后端生成一次性 `state` 和企微 Web 登录 URL扫码回调后使用自建应用 Access Token 调用 `auth/getuserinfo` 获取 `userid`
#### 3.3.3 平台账号绑定、代理固定代提交与真实业务提交人
```text
系统登录账号
→ 创建5分钟绑定会话
→ 打开企微Web登录二维码
→ 回调code+state
→ 原子消费state
→ 换取userid并校验企业成员
→ 保存一对一绑定
```
企微 `applyevent.creator_userid` 按当前登录账号类型解析:平台账号和超级管理员必须使用其系统账号扫码绑定的本人企微 `userid`;代理账号发起退款时使用部署配置中的 `agent_approval_creator_userid` 代提交。退款或线下充值的真实业务提交人始终取当前登录账号,并独立保存账号 ID、名称、角色和店铺快照。
约束:
- `state` 与前端查询 `session_id` 分开存 Redis`state` 单次使用,绑定结果会话保留 10 分钟
- 回调不接受 `account_id`,目标账号只能来自服务端绑定会话
- `account_id``wecom_userid` 均唯一;成员已绑定其他账号时拒绝覆盖
- 自助解绑和管理员强制解绑不影响历史审批快照,但会阻止新审批
- 管理员只查看绑定状态和强制解绑,不提供 `userid` 输入框
- 企微自建应用可信域名和可见范围必须覆盖需要发起审批的平台员工
`tb_account_wecom_mapping` 保存账号、`userid`、成员名称、CorpID、AgentID、绑定来源和验证时间。
- 平台/超级管理员通过 5 分钟一次性会话扫码绑定;`account_id``wecom_userid` 均一对一唯一,普通运营不手工录入 `userid`
- 平台/超级管理员未绑定、绑定失效或成员不可用时,拒绝其新申请并原地提供绑定入口;代理账号不要求绑定企微
- 代理代提交固定成员必须属于当前 CorpID、处于可用状态并在自建应用可见范围内未配置或失效时拒绝代理新申请。以上校验都在任何业务单、审批实例或 Outbox 落库前完成
- 企微模板中的 `submitter` 必填字段始终写入真实业务提交人名称和账号标识;代理审批单不能只展示固定代提交账号
- 审批实例同时保存真实业务提交人快照、本次实际使用的企微 `userid` 快照及身份来源 `self_binding/agent_proxy`;列表、详情、通知、权限与审计中的“提交人/申请人”均指真实业务提交人
- 平台账号换绑、解绑以及代理固定成员变更只影响新审批;历史实例保留提交时身份快照,不改写历史记录
- 后台账号绑定列表只允许查看和强制解绑;代理固定身份只展示就绪状态及成员显示名,不提供在线修改 `userid`
#### 3.3.4 审批实例与状态同步
`tb_wecom_approval_instance` 保存:业务类型/ID/编号、轮次、场景、模板版本和映射快照、创建人本地账号与企微 `userid``sp_no`、提交业务快照、企微详情和审批人快照、业务处理结果、轮询时间、乐观锁版本
`tb_wecom_approval_instance` 的每条记录表示一次独立审批申请,保存:业务类型/ID/编号、场景、模板版本和映射快照、真实业务提交人账号及显示快照、实际企微发起 `userid` 快照、身份来源 `self_binding/agent_proxy``sp_no`、提交业务快照、企微详情和审批人快照、业务处理结果、轮询时间、乐观锁版本。退款和线下充值业务表使用 `approval_instance_id` 明确指向唯一审批申请,审批实例以 `(biz_type, biz_id)` 唯一约束保证一张业务单只有一条审批;不使用 `round_no`,也不存在从同一业务单推算“当前轮次”的逻辑
状态:
@@ -378,16 +370,45 @@ sequenceDiagram
| 通过后撤销 | `sp_status=6` |
| 已删除 | `sp_status=7` |
`applyevent` 请求已发送但响应超时必须标记“提交结果未知”,禁止盲目重试产生重复审批。管理员在企微核对后选择“确认未创建并重新提交”或手工绑定已有 `sp_no`,后者记录高风险审计。
`applyevent` 请求已发送但响应超时必须标记“提交结果未知”,禁止盲目重试产生重复审批。异常恢复同时提供两个受控动作:
- “绑定已有 `sp_no`”:先调用 `getapprovaldetail`,核对企业、模板版本、实例保存的实际企微发起 `userid`、身份来源、业务场景、真实业务提交人字段和提交业务快照;全部匹配后才允许绑定,禁止只校验编号存在。
- “确认企微未创建并重新发送”:保留原未知尝试和 Integration Log在同一审批申请下记录新的技术提交尝试它不是一条新的业务审批申请。企微已经拒绝或进入其他明确业务终态后原业务单不得再次发送后续确有业务需要时只能重新创建新的业务单和审批申请。
两个动作只允许超级管理员或具备独立“企微审批异常恢复”权限的平台账号执行,并记录操作人、依据、审批申请、技术尝试或绑定 `sp_no`、校验结果和前后状态的高风险 Audit Event。
回调只负责验签、解密、保存 Integration Log 并触发统一 `SyncApprovalStatus`。审批中实例每 2 分钟兜底查询,回调和轮询共用状态同步用例;首次进入终态时同事务写业务 Outbox`business_processed_at` 保证资金动作只执行一次。
#### 3.3.5 DDD 边界与 API
#### 3.3.5 权限边界
企微能力拆分授权,禁止用一个泛化权限同时覆盖配置和异常操作:
| 能力 | 超级管理员 | 平台账号 | 代理账号 |
|------|------------|----------|----------|
| 查询连接、场景和模板版本 | 允许 | 不允许 | 不允许 |
| 暂停/恢复场景、读取/发布模板映射 | 允许 | 不允许 | 不允许 |
| 管理平台账号绑定列表、强制解绑 | 允许 | 不允许 | 不允许 |
| 查询、重新绑定、解绑本人企微 | 允许 | 仅本人 | 不提供绑定能力 |
| 审批运行列表和详情 | 允许 | 仅具备独立“企微审批运营”权限 | 不允许 |
| 立即同步审批详情 | 允许 | 仅具备独立“企微审批运营”权限 | 不允许 |
| 提交未知异常恢复 | 允许 | 仅具备独立“企微审批异常恢复”权限 | 不允许 |
| 业务退款详情中的审批区块 | 按业务查看权限 | 按业务查看权限 | 按现有店铺层级数据范围 |
平台账号的“企微审批运营”权限只允许查看运行记录和触发 `getapprovaldetail` 同步,不包含场景、模板、账号绑定管理或提交未知恢复。所有接口以后端角色与权限校验为准,前端隐藏入口不能替代鉴权。
审批详情按查看主体投影,禁止把平台内部审批资料随业务详情或导出泄露给代理:
- 代理在其店铺层级数据范围内只能看到真实业务提交人、审批状态、状态更新时间、业务处理结果,以及代理自己提交的退款资料和业务凭证。
- 代理不得看到企微审批人名单、内部审批意见和审批人在企微上传的附件。
- 平台账号和超级管理员在具备对应退款查看权限时可查看完整审批详情,包括审批人、意见、时间线和审批附件。
- 审批附件详情、受保护下载解析与导出必须复用同一主体权限投影;不得因持有 `attachment_ref` 或历史导出文件而绕过当前权限。
#### 3.3.6 DDD 边界与 API
```text
domain/wecomapproval 场景、模板版本、审批状态和终态幂等
application/wecomapproval 发布模板、提交审批、回调、同步和异常恢复
domain/wecomidentity 系统账号与企微成员一对一绑定
domain/wecomidentity 平台系统账号与企微成员一对一绑定
application/wecomidentity 创建绑定会话、完成绑定和解绑
infrastructure/adapter/wecom Token、模板、附件、审批、身份和回调加解密
query/wecomapproval 审批运行列表、详情和业务摘要
@@ -402,7 +423,7 @@ query/wecomapproval 审批运行列表、详情和业务摘要
| POST | `/api/admin/wecom/approval-templates/inspect` | 读取企微模板 |
| POST | `/api/admin/wecom/approval-templates/publish` | 发布控件映射版本 |
| GET | `/api/admin/wecom/approval-templates` | 模板版本列表 |
| POST | `/api/admin/wecom/account-binding/sessions` | 当前账号创建扫码绑定会话 |
| POST | `/api/admin/wecom/account-binding/sessions` | 当前平台账号创建扫码绑定会话 |
| GET | `/api/admin/wecom/account-binding/sessions/{session_id}` | 查询扫码绑定结果 |
| GET/DELETE | `/api/admin/wecom/account-binding/me` | 查询或解绑自己 |
| GET | `/api/admin/wecom/account-bindings` | 超管查询绑定列表 |
@@ -411,8 +432,10 @@ query/wecomapproval 审批运行列表、详情和业务摘要
| GET/POST | `/api/callback/wecom/approval` | 企微审批回调校验和事件 |
| GET | `/api/admin/wecom/approvals*` | 审批运行列表和详情 |
| POST | `/api/admin/wecom/approvals/{id}/sync` | 立即同步详情 |
| POST | `/api/admin/wecom/approvals/{id}/bind-sp-no` | 提交结果未知时校验并绑定已有审批单 |
| POST | `/api/admin/wecom/approvals/{id}/confirm-not-created-and-resend` | 确认企微未创建后重新发送同一审批申请 |
### 3.4 数据同步触发与轮询优化
### 3.4 卡状态公共写入、事件触发与运营商回调
#### 3.4.1 三条自动通道
@@ -420,7 +443,7 @@ query/wecomapproval 审批运行列表、详情和业务摘要
```mermaid
flowchart LR
Polling[活跃/不活跃轮询] --> Request[RequestCardObservation]
Polling[现有周期轮询] --> Request[RequestCardObservation]
Event[关键业务埋点] --> Series[立即/3分钟/5分钟]
Series --> Request
Manual[现有手动刷新] --> Request
@@ -434,11 +457,11 @@ flowchart LR
ACL --> Integration
```
- 轮询始终兜底,只保留 `enable_polling` 总开关,按活跃/不活跃使用不同间隔
- 现有周期轮询继续兜底,其 PostgreSQL 配置、Redis 分片队列、同步类型间隔、卡级开关、失败重排、并发控制和监控保持现状;本期只把查询成功后的状态应用收口到公共用例
- 业务事件不是新接口,而是在查询资产、获取实名链接、停复机、支付、套餐激活、切卡、重启等用例成功边界埋点。
- 每个事件序列默认创建立即、3 分钟、5 分钟三个无自动重试任务;达到预期状态后剩余任务提前完成。
- 现有手动刷新接口保持响应契约并直接同步,不再额外创建阶梯任务。
- Gateway 超频只记录当前 `rate_limited`,不维护 `blocked_until` 或 30/60/120 秒退避后续阶梯任务和轮询保持原计划。
- 事件阶梯任务遇到 Gateway 超频只记录当前 `rate_limited`,不为事件序列维护 `blocked_until` 或 30/60/120 秒退避后续阶梯任务和现有周期轮询保持各自原计划。
#### 3.4.2 实名判断与状态应用
@@ -454,11 +477,9 @@ flowchart LR
`ApplyCardObservation` 是唯一允许写入实名、流量和网络状态的入口:加载并锁定卡聚合,应用标准观测值,状态变化时同事务保存领域事件和 Audit Event外部调用始终写 Integration Log。旧轮询 Handler、手动实名修改和刷新 Service 必须改为调用该用例,不保留双实现。
#### 3.4.3 活跃调频和请求协调
#### 3.4.3 事件序列请求协调
卡维护活跃级别、最后活跃时间/场景和最后上游变化时间。C 端/后台/OpenAPI 查询、实名、停复机、支付、套餐、切卡、回调和轮询发现变化均标记活跃;持续无业务活动且轮询无变化后转为不活跃。
第一版建议 `inactive_after=30m`;活跃卡沿用现有实名/流量/状态默认间隔,不活跃卡三类查询初始均为 15 分钟。上线前按生产卡量只读测算 QPS 后再调整,数值进入轮询配置而不是写死代码。
本期不引入活跃/不活跃卡、不增加活跃状态字段、不调整周期轮询间隔或 QPS也不合并现有多套轮询配置。以下协调规则只作用于业务事件产生的 `0/3/5` 观测序列:
重复控制拆为:
@@ -472,7 +493,7 @@ flowchart LR
ICCID 必须按原始长度精确路由19 位只查 `iccid_19`20 位只查 `iccid_20`,禁止截断、补位或跨列降级。发布前检查 `iccid_19` 重复并建立未删除数据范围内的部分唯一索引。解除实名回调第一版只写 Integration Log 并返回约定成功报文,不修改卡实名状态。
本需求不新增同步按钮显式同步 API。资产详情只增加自动轮询状态和“查看同步轨迹”,跳转 `/operations/audit?tab=integrations&resource_type=iot_card&resource_key=...`
本需求不新增同步按钮显式同步 API、活跃状态或下次轮询字段。资产详情只增加“查看同步轨迹”,跳转 `/operations/audit?tab=integrations&resource_type=iot_card&resource_key=...`;现有手动刷新和轮询监控页面保持原契约
### 3.5 全局多视角审计
@@ -532,8 +553,8 @@ POST /api/admin/audit/exports
| 模块/页面 | 建议路由 | 主要能力 |
|-----------|----------|----------|
| 企微审批运行 | `/operations/wecom-approvals` | 状态、业务单号、模板版本、异常恢复和详情抽屉 |
| 企微配置 | `/system/wecom` | 连接状态、模板映射版本、账号绑定和异常审批 |
| 账号企微绑定 | 当前用户个人中心 | 扫码绑定、查看成员名称、重新绑定和解绑 |
| 企微配置 | `/system/wecom` | 连接状态、代理固定代提交账号状态、模板映射版本、平台账号绑定和异常审批 |
| 账号企微绑定 | 当前平台用户个人中心 | 扫码绑定、查看成员名称、重新绑定和解绑;代理不展示绑定入口 |
| 站内消息 | `/notifications` | 未读数、列表、已读和受控业务跳转 |
| 全局审计 | `/operations/audit` | 全局、人员、资源、链路、资金、风险和外部集成多视角 |
| 系统配置 | `/settings/system-config` | 受控单选、复选和开关 |
@@ -575,17 +596,19 @@ stateDiagram-v2
- 网络错误不等于业务失败,展示“状态获取失败,点击重试”。
- 必须展示总数、成功数、失败数和部分成功状态。
- 页面刷新后根据任务 ID 恢复进度。
- Excel 模板由前端静态资源提供;后端仍严格校验表头、版本和内容。
- 批量文件模板由前端静态资源提供;设备批量分配和批量订购均只接受 CSV但各自使用独立表头契约。后端仍严格校验表头、编码、文件大小和内容。
### 4.4 企业微信审批交互
- 删除本系统待我审批、流程节点配置、审批人配置、通过/驳回/退回按钮,审批操作全部在企业微信完成。
- 退款和线下充值创建成功后先显示“正在提交企业微信审批”,取得 `sp_no` 后显示“企业微信审批中”。
- 业务详情企微审批区块展示审批单号、状态、模板版本、申请人、审批人、意见、审批附件、状态时间线和本地业务处理结果。
- 平台/超级管理员的业务详情企微区块展示审批单号、状态、模板版本、真实业务提交人、审批人、意见、审批附件、状态时间线和本地业务处理结果;代理视图只展示真实业务提交人、审批状态、状态时间和业务处理结果,不返回平台内部审批人、意见或审批附件
- “立即同步”只调用 `getapprovaldetail`,不提供任何本地审批动作。
- 通过后撤销且资金动作已执行时使用高风险异常提示,明确说明不会自动冲正。
- 未绑定企微账号时,创建页原地展示“绑定企业微信”按钮;扫码成功后继续当前表单,不要求用户先跳转配置页
- 平台/超级管理员未绑定企微时,创建页原地展示“绑定企业微信”按钮,绑定成功后继续当前表单;代理不展示绑定入口,后端使用固定企微账号代提交
- 代理固定代提交账号未配置、失效或不在应用可见范围时,代理创建页展示后端返回的场景不可用原因并保留当前表单。
- 模板发布使用业务字段与企微控件的可视化映射,不向运营暴露 JSON场景暂停时创建页提前展示维护原因。
- `/system/wecom` 仅超级管理员可访问;平台账号只在个人中心维护本人绑定,具备“企微审批运营”权限时才显示审批运行页;代理只在自己有权查看的退款详情中查看审批区块。
### 4.5 审批与业务处理状态
@@ -601,7 +624,8 @@ stateDiagram-v2
### 4.6 权限与敏感数据
- 企微配置、账号绑定、导出字段、审计视角和数据范围均以后端为准。
- 企微连接、平台账号绑定与代理固定代提交账号状态、导出字段、审计视角和数据范围均以后端为准。
- 审批附件的详情展示、下载解析和导出沿用审批详情主体权限:代理提交的业务凭证可按业务权限访问,平台内部审批附件仅平台/超级管理员按权限访问。
- 通知跳转使用前端受控 `ref_type` 路由表,不接受任意 URL。
- 退款凭证、充值凭证、身份证、营业执照和审批附件均走对象存储,不保存永久公开地址。
- 角色导出字段配置只控制列,不扩大已有店铺或企业数据范围。
@@ -621,7 +645,7 @@ stateDiagram-v2
| 需求 06/11 套餐到期时间 | 资产层只展示当前生效及排队主套餐全部接续后的预计最终到期时间;当前套餐自身到期时间只保留在套餐明细 | 资产详情 Query 按队列顺序使用购买时长快照推演;无套餐返回 `null`,等待未知实名激活时返回不可预计状态 | 文案使用“预计套餐到期时间”;高亮和临期统一使用最终剩余天数,不维护第二套资产汇总字段 |
| 需求 07 实名筛选 | 卡按自身实名状态;设备任一有效绑定卡实名即视为设备实名 | 设备增加 `real_name_status` 快照;实名变化、绑定、解绑、换卡均刷新旧/新设备 | 卡和设备列表增加全部/已实名/未实名筛选 |
| 需求 12 换货显示与搜索 | 卡的新旧资产标识统一快照 ICCID设备维持设备号历史数据不回填 | 创建快照逻辑修正;列表增加 `old_asset_keyword/new_asset_keyword`,支持 ICCID、接入号、虚拟号 | 搜索框拆为旧资产和新资产;验收大结果集查询性能 |
| 需求 13 列表字段 | 提交人写业务快照;企微审批摘要动态读取 | 换货、退款、充值增加 `submitter_name`;按本页企微实例批量查询状态和审批人摘要,禁止 N+1 | 展示企微状态、当前审批人摘要、处理状态和历史审批标识 |
| 需求 13 列表字段 | 提交人写业务快照;企微审批摘要动态读取 | 换货、退款、充值增加 `submitter_name`;按本页企微实例批量查询状态,平台视角可查询审批人摘要,代理视角摘要固定为空,禁止 N+1 | 展示企微状态和处理状态;当前审批人仅平台/超级管理员按业务权限展示 |
| 需求 15 下架套餐续费 | 禁用套餐始终不可购买;下架套餐只允许资产所有人基于有效历史使用记录续费,禁止代理代购 | 复用统一套餐可售策略;后端根据资产和登录主体判定续费资格 | C 端当前套餐旁展示“续费”,复用购买流程;新购入口隐藏,后台代购禁用 |
### 5.2 需求 02H5 实名与充值顺序配置
@@ -699,18 +723,18 @@ PATCH /api/admin/shop-package-allocations/{id}/expiry-base
### 5.4 需求 08设备批量分配
“分配代理”和“分配套餐系列”是两个业务命令,不允许一个任务同时修改两个字段。两者复用 Excel 解析、任务轮询和失败明细基础设施,但每个任务只携带一种 `operation_type`一个目标值。
“分配代理”和“分配套餐系列”是两个业务命令,不允许一个任务同时修改两个字段。两者只接受单列 UTF-8 CSV复用任务轮询和失败明细基础设施但使用两个独立创建接口且每个任务只携带一个目标值。
```mermaid
sequenceDiagram
actor User as 平台员工
actor User as 平台或代理
participant Web as 设备管理页
participant API as Batch Allocation API
participant DB as PostgreSQL
participant Worker as Asynq Worker
User->>Web: 选择一种分配操作并上传 Excel
Web->>API: POST batch-assign-shop 或 batch-assign-series
User->>Web: 选择一种操作并上传 CSV 到对象存储
Web->>API: 提交 file_key 和 shop_id 或 series_id
API->>DB: 保存任务和文件 Key
API-->>Web: task_id
Worker->>DB: 分批查询、条件更新、记录失败
@@ -722,8 +746,12 @@ sequenceDiagram
处理规则:
- 设备号去重后批量查询,禁止逐行查询。
- CSV 固定一列“设备号”,支持虚拟号或 IMEI 精确匹配;不接受 Excel、multipart 文件字节、号段或模糊搜索。
- 前端复用受控预签名上传,业务接口只接收 `file_key` 和对应目标 IDAsynq 载荷只传结构化 `task_id`
- `assign_shop`:已属于其他代理的设备失败并提示先回收;已属于目标代理的按幂等成功。
- `assign_shop`:平台只能分配平台库存设备;代理只能把自己名下设备分配给直属下级。
- `assign_series`:已属于目标套餐系列的按幂等成功;不修改 `shop_id`
- `assign_series`:代理只能操作自己名下设备并选择自己当前有效授权的系列。
- 临时本地路径和文件字节不得进入 Asynq 载荷。
- 模板由前端提供,后端不实现下载接口。
- 限制:文件最大 10MB、最多 1000 行、Worker 每批 200 条、失败明细最多保存 1000 条。
@@ -739,7 +767,9 @@ sequenceDiagram
| 设备 | 微信、钱包 |
- C 端支付页只展示接口返回的允许方式。
- 创建订单和支付接口必须再次校验,不能依赖前端隐藏
- 创建订单`payment_method` 必填,后端校验后固化为订单不可变快照;强充在创建流程中立即按该方式拉起渠道,钱包不能用于给自身强充
- 后续支付接口不允许重新选择或替换 `payment_method`,只能执行订单快照方式,并在真正支付前按最新资产配置再次校验。需要换方式时先取消待支付订单再重新创建。
- 普通资产钱包充值只展示并接受资产允许集合与 `wechat|alipay` 的交集,不能把 `wallet` 当成充值渠道。
- 配置异常时使用上述安全默认值并记录错误,不得放开全部方式。
- 后台配置使用复选框,不直接编辑 JSON。
@@ -757,17 +787,155 @@ flowchart TD
Binding --> Exists{当前卡有效?}
Exists -->|否| Reject[拒绝操作并记录原因]
Exists -->|是| ICCID
ICCID --> Gateway[SetSpeedLimit cardNo, speedKbps]
ICCID --> Gateway[SetSpeedLimit cardNo, speedLevel]
Gateway --> Audit[记录操作审计和 Gateway 结果]
```
#### 5.6.1 CMP 业务契约
数据和契约:
- 应用层只接收 `speed_kbps`:正数为设置,`0` 为取消;内部单位固定为 `kbps`
- 应用层只接收语义化 `speed_level`,前端只能从固定档位中选择,禁止输入任意速率,也不得接触 Gateway 渠道 `code`
- 统一接口:`POST /api/admin/assets/{identifier}/speed-limit`。资产为设备时解析当前绑定卡,不存在当前卡则拒绝,绝不把设备 IMEI 传给 Gateway。
- Gateway 端口只有 `SetSpeedLimit(cardNo, speedKbps)`;取消仍调用同一端口,适配器按上游最终契约转换取消参数
- Gateway 端口只有 `SetSpeedLimit(cardNo, speedLevel)`Infrastructure Adapter 根据 Gateway 账户和运营商把业务等级映射为上游字符串 `code`,映射不得进入 Handler、前端或领域模型
- “恢复不限速”和“限到 0kbps”是两个不同业务等级必须分别映射为上游 `code=-1``code=0`,禁止继续用数值 `0` 表示取消限速。
- 上游说明只有广电和电信直接支持限速接口,联通通过通信计划调整,移动不支持限速。调用前必须按卡的运营商能力校验;不支持或缺少账户档位映射时返回明确业务错误,不得猜测 `code` 或盲目调用。
- 不新增 `tb_package.speed_limit_kbps``SpeedLimitApplyRequested` Outbox 或自动补偿 Worker。本期也不宣称能展示 Gateway 当前实际限速,除非上游另提供查询接口。
- 卡详情和设备详情都提供设置/取消入口;设备入口明确显示“当前使用卡 ICCID”。每次操作记录资产、最终 `cardNo`目标值、操作人、请求结果和错误摘要。
- 卡详情和设备详情都提供固定档位选择及恢复不限速入口;设备入口明确显示“当前使用卡 ICCID”。每次操作记录资产、最终 `cardNo`业务 `speed_level`、实际发送的渠道 `code`、Gateway 返回的 `appliedSpeed/channelRawValue`、操作人、请求结果和错误摘要。
CMP 固定业务等级如下。枚举名称属于本系统稳定契约;展示文案和上游默认 `code` 仅用于表达当前已知映射,实际调用仍须按 Gateway 账户和运营商查找映射。
| `speed_level` | 展示文案 | 当前上游默认 `code` |
|---------------|----------|----------------------|
| `unlimited` | 恢复不限速 | `-1` |
| `zero_kbps` | 限到 0kbps | `0` |
| `limit_128_kbps` | 128Kbps | `1` |
| `limit_512_kbps` | 512Kbps | `2` |
| `limit_1_mbps` | 1Mbps | `3` |
| `limit_2_mbps` | 2Mbps | `4` |
| `limit_10_mbps` | 10Mbps | `5` |
| `limit_20_mbps` | 20Mbps | `6` |
| `limit_50_mbps` | 50Mbps | `7` |
| `limit_100_mbps` | 100Mbps | `8` |
#### 5.6.2 Gateway 上游接口文档
上游正式环境地址为 `https://open.whjhft.com/openapi`,限速接口为 `POST /flow-card/speedLimit`。统一 Gateway 客户端发送时使用 `params` 包装业务参数:
```json
{
"params": {
"cardNo": "89861124221081232235",
"code": "-1"
}
}
```
以下为上游提供的原始 OpenAPI 文档。文档中的成功响应 `example` 实际是错误页面文案,不是有效 JSON实现和自动化测试不得据此构造成功响应响应字段以 schema 及真实环境联调结果为准。
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/flow-card/speedLimit:
post:
summary: 流量卡限速接口
deprecated: false
description: |
只有广电接口和电信接口存在限速 联通是通过通信计划调整 不同账户相同速率的编码不一致 移动无限速
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| cardNo | string | 是 | 流量卡ICCID号码 |
| code | string | 是 | 限速档位,取值见下表 |
**code 档位对照表:**
| code | 速率 |
|------|------|
| -1 | 限速恢复(取消限速) |
| 0 | 0kbps |
| 1 | 128Kbps |
| 2 | 512Kbps |
| 3 | 1Mbps |
| 4 | 2Mbps |
| 5 | 10Mbps |
| 6 | 20Mbps |
| 7 | 50Mbps |
| 8 | 100Mbps |
tags:
- 流量卡
- flow-card
parameters: []
requestBody:
content:
application/json:
schema:
type: object
properties: {}
example:
params:
cardNo: '89861124221081232235'
code: '-1'
required: true
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
description: 200成功
msg:
type: string
data:
type: object
properties:
appliedSpeed:
type: string
description: 实际限速值
channelRawValue:
type: string
description: 对应运营商过去的值
iccid:
type: string
required:
- appliedSpeed
- channelRawValue
- iccid
x-apifox-orders:
- appliedSpeed
- channelRawValue
- iccid
required:
- code
- msg
- data
x-apifox-orders:
- code
- msg
- data
example: 抱歉,您访问的页面不存在。
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 流量卡
x-apifox-status: developing
x-run-in-apifox: https://app.apifox.com/web/project/6930706/apis/api-487496525-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers:
- url: https://open.whjhft.com/openapi
description: 正式环境
security: []
```
### 5.7 需求 14统一导出与字段权限
@@ -787,18 +955,17 @@ flowchart TD
```text
POST /api/admin/export-tasks
body: scene + format + query + fields
body: scene + format + query
```
字段权限:
```text
resolved_fields = 用户申请字段
∩ 角色授权字段并集
resolved_fields = 角色授权字段并集
∩ 场景支持字段
```
新增 `tb_role_export_field_permission(role_id, scene, field_key)`,三列唯一。超级管理员拥有全部字段;普通角色授权并集计算。数据行范围继续使用现有权限,字段授权不能扩大数据范围。
新增 `tb_role_export_field_permission(role_id, scene, field_key)`,三列唯一。超级管理员拥有代码目录内全部字段;普通账号从当前有效角色授权并集计算。数据行范围继续使用现有权限,字段授权不能扩大菜单、接口、场景或数据范围。前端不得提交 `fields`,也不提供导出字段选择器;账号获授权的代码目录字段全部导出。
API
@@ -810,11 +977,32 @@ PUT /api/admin/roles/{role_id}/export-fields
- 创建任务时快照最终字段和表头Worker 不能因后续角色变化扩大字段。
- 权限解析失败时拒绝导出,不能回退到全字段。
- 普通账号解析成功但最终字段为空时不创建任务;查询当前账号字段时返回空数组,前端据此禁用导出入口。
- 不存在 `default_selected``required` 或后端自动补列。角色配置是代码目录内字段能否导出的权威来源密码、密钥、Token、对象存储 Key、企微 `media_id` 等永久禁止导出的字段不得注册进代码目录。
- Count 和 Fetch 必须使用相同权限及查询条件。
- 退款和充值审批摘要按本批实例批量查询,禁止 N+1。
- 导出审批附件只输出数量,不输出对象 Key 或永久 URL
- 退款/充值业务凭证和企微审批附件可按字段权限导出受保护的稳定业务链接,但字段权限不能突破审批主体可见性:代理可导出其有权查看的业务凭证,不能导出或解析平台内部企微审批附件;不得输出对象 Key、企微 `media_id`、当前预签名 URL 或公开 Bucket URL。平台审批摘要仍可保留附件数量
- `scene=iot_card` 支持按预计最终到期时间筛选 30 天内资产;该导出条件独立于页面 15 天临期定义,复用需求 06/11/22 的最终到期 Query。
受保护附件访问流程:
```text
导出单元格中的后台前端绝对 URL
→ /export-attachments/{attachment_ref}
→ 未登录时进入后台登录并保存当前站内返回地址
→ 登录成功返回附件落地页
→ 前端携带 Bearer Token 请求 GET /api/admin/attachments/{attachment_ref}/download-url
→ 后端按附件所关联退款单、充值单或审批业务重新校验当前查看权限、数据范围和附件种类可见性
→ 返回短期 download_url、expires_at、file_name
→ 前端在当前页跳转短期地址,浏览器打开或下载私有文件
```
- `attachment_ref` 是不可变、不可枚举的稳定引用;业务附件被替换时旧引用不得改指新文件。
- 附件仍保存在私有 Bucket。稳定的是后台前端落地页地址不是对象存储地址每次访问都重新鉴权并生成短期地址。
- 无权与引用不存在对外使用统一错误,避免探测;角色或数据范围在导出后被收回时,旧导出文件中的链接也不能继续下载。
- 企微审批附件必须先保存到本地私有对象存储并建立稳定引用,不能依赖临时 `media_id`。历史确无本地附件事实时输出空;匹配记录仍处于应有附件但尚未本地化的异常状态时,导出任务明确失败。
- 前端附件落地页必须覆盖登录恢复、加载中、无权限/不存在、对象文件不可用、解析失败和成功跳转状态;不得把后端 API URL直接写进导出文件因为浏览器无法为普通文件链接自动附加后台 Bearer Token。
### 5.8 需求 22套餐临期提醒
临期定义:按 `Asia/Shanghai` 自然日计算,资产当前生效主套餐与全部排队主套餐连续接续后的**预计最终剩余天数**为 `015` 天。已过期资产不按 0 天计入临期;没有生效套餐且队首仍等待无法确定时间的实名激活时,不伪造到期日期,也不进入临期。需求 06、11、22 共用同一个最终到期 Query。
@@ -906,19 +1094,13 @@ cash_available = balance - frozen_balance
#### 5.9.5 #43 代理系列套餐批量授权
现有 `PUT /api/admin/shop-series-grants/{id}/packages` 已接受套餐数组,继续作为批量写接口。新增套餐候选 Query
不新增套餐候选 API。首次授权使用现有 `GET /api/admin/packages?series_id=...` 选择套餐,并由 `POST /api/admin/shop-series-grants` 在同一事务创建系列授权和至少一条套餐授权;不允许创建没有套餐的空系列授权。
```text
GET /api/admin/shop-series-grants/{id}/package-options
```
后续管理并行读取现有套餐列表和 `GET /api/admin/shop-series-grants/{id}`,由前端按 `package_id` 合并:已授权项置灰,未授权项可多选,存量已授权但当前不再可售/可见的项目仍通过授权详情只读展示。
返回 `package_id`、名称、编码、公司成本价、当前代理授权成本价、建议售价和 `is_authorized`。首次授权系列和后续管理套餐都使用同一候选列表:
保留 `PUT /api/admin/shop-series-grants/{id}/packages`,但请求必须用 `operation_type=authorize|update_cost|remove` 明确表达一种批量命令,单次最多 100 个套餐、事务内全成全败。新增授权同价重复幂等,不同价重复冲突;调价和移除不能伪装成新增授权。前后端同批切换,不保留旧的混合新增/改价/移除语义。
- 已授权套餐显示“已授权”并置灰,不可重复选择
- 未授权套餐支持复选框多选并一次提交。
- 后端对重复套餐按幂等处理,不能依赖前端置灰保证一致性。
- `company_cost_price``authorized_cost_price``suggested_retail_price` 分字段返回,禁止继续使用含义不明确的单一 `cost_price` 展示。
- 套餐候选属于 Query批量授权属于轻量 Application 事务脚本,不为此创建空洞聚合。
页面按调用视角分别标注上级当前成本价、目标代理授权成本价和建议零售价;平台视角的上级成本才是公司成本。所有系列、套餐、价格和直属下级权限由后端重新校验,不能依赖前端置灰
---
@@ -942,6 +1124,9 @@ GET /api/admin/shop-series-grants/{id}/package-options
- 修改角色默认信用配置只影响以后新建的店铺,不更新任何已有店铺。
- 已有店铺通过独立资金接口直接修改实际额度,之后也不跟随角色变化。
- 店铺后续增加其他角色不改变钱包信用额度,避免多角色组合影响资金事实。
- 代理账号不能调整自己或任何下级代理的实际信用额度;现有店铺管理权限和数据范围不推导信用额度修改权。
- 实际额度只能由超级管理员,或具备独立信用额度管理权限的平台账号修改。角色默认模板也只允许超级管理员或具备相应角色管理权限的平台账号配置。
- 信用额度不设置产品层固定上限;接口仍须使用分为单位的 `int64` 安全范围并拒绝负数和算术溢出。
#### 不变量
@@ -998,7 +1183,7 @@ CHECK (
|------|------|
| 修改角色默认额度 | 只更新角色模板和审计,不扫描、不修改任何已有店铺钱包 |
| 创建店铺 | 在创建事务中读取默认角色模板并初始化代理主钱包实际额度 |
| 修改额度 | 校验权限、加载主钱包、校验新可用金额、按 `version` 条件更新、写信用变更审计 |
| 修改额度 | 拒绝代理账号;校验平台独立权限、加载主钱包、校验新可用金额、按 `version` 条件更新、写信用变更审计 |
| 钱包扣款 | 使用有效额度计算可用金额,同事务更新余额、版本和资金流水 |
| 查询/导出 | Query 返回 `credit_enabled``credit_limit``available_balance``is_in_debt``debt_amount` |
@@ -1011,6 +1196,8 @@ GET /api/admin/shops/fund-summary 返回信用和可用金额
角色页面显示“新建代理默认信用额度”,并明确提示“修改后不会影响已有店铺”。店铺资金页面独立显示和修改实际信用额度。前端只展示接口返回的可用金额,不自行重新计算;额度调整弹框显示修改前后金额预览,并发冲突时刷新最新钱包版本。
资金概况中的 `is_in_debt` 表示 `balance < 0``debt_amount = max(-balance, 0)`;冻结金额只影响现金可用金额和总可用金额,不直接记为欠款。额度调整只改变资金边界,不伪造一条金额为零的钱包交易流水;变更前后值进入全局 Audit Event。
### 6.3 需求 18多人审批业务映射
多人审批由企业微信模板负责,本系统不解析审批角色、账号、部门或会签规则:
@@ -1020,25 +1207,28 @@ GET /api/admin/shops/fund-summary 返回信用和可用金额
| 多级、会签、或签 | 在企微模板中配置,本系统使用 `use_template_approver=1` |
| “部门领导→财务” | 可以作为企微模板中的组织规则,本系统不建立部门模型也不推导审批人 |
| 当前节点待办 | 由企业微信自身提醒,本系统不重复生成站内待审批任务 |
| 通过/驳回/撤销通知 | 状态同步后向申请人生成站内结果通知 |
| 通过/驳回/撤销通知 | 状态同步后向真实业务提交人生成站内结果通知 |
| 意见和附件 | 从 `getapprovaldetail` 保存审批详情快照并在业务详情只读展示 |
| 历史版本 | 本地保存模板 ID、控件映射和提交快照历史实例不受新模板影响 |
平台员工必须先完成系统账号与企微成员扫码绑定,发起审批使用绑定的 `userid`;普通运营不查询或录入成员 ID
平台/超级管理员必须扫码绑定并使用本人企微 `userid` 发起;代理退款使用部署配置中的固定企微成员代提交。两类路径都把真实业务提交人写入模板 `submitter` 字段和本地快照,通知与审计均以真实业务提交人为准
### 6.4 需求 19批量订购套餐
#### 规则和流程
- 支付方式整批选择 `offline``agent_wallet`Excel 不包含支付方式。
- 支付方式整批选择 `offline``agent_wallet`CSV 不包含支付方式。
- 混合支付必须拆成不同批次。
- 模板字段:资产类型、资产标识、套餐编码、套餐名称;实际匹配使用套餐编码
- 批次不选择代理、不接收 `shop_id`;同一 CSV 可以包含不同代理的资产。Worker 逐行以资产当前归属解析结算代理,套餐授权、成本价、钱包和数据权限均以该行解析结果为准
- 创建、查询批次和查看明细只允许超级管理员,或具备独立“批量订购套餐”权限的平台账号;代理和企业账号无页面及 API 权限。平台身份只授予发起批次的能力,不跳过任何逐行资产、结算代理、套餐授权、成本价或钱包校验。
- CSV 固定 UTF-8允许 BOM模板字段资产类型、资产标识、套餐编码、套餐名称实际匹配使用套餐编码套餐名称只供人工核对。不接受 Excel。
- 同一 CSV 内按“资产类型 + 标准化资产标识 + 套餐编码”识别重复业务行:首次出现的行正常处理,后续重复行记为失败并返回首次出现的行号;同一资产订购不同套餐不视为重复。本期不通过复制相同行表达购买多份,未来需要多份时增加明确数量字段。
- 任务允许部分成功,每行是独立、可重试、可审计的业务单元。
- 文件最大 10MB、最多 1000 行;失败明细最多保存 1000 条。
```mermaid
flowchart TD
Submit[选择代理、支付方式、凭证并上传] --> Task[创建任务]
Submit[选择支付方式CSV及凭证直传私有对象存储] --> Task[提交file_key和voucher_keys创建任务]
Task --> Parse[解析并持久化逐行明细]
Parse --> Item{处理下一行}
Item -->|钱包| Wallet[锁钱包并校验有效可用金额]
@@ -1055,8 +1245,8 @@ flowchart TD
| 表 | 关键字段 |
|----|----------|
| `tb_bulk_purchase_task` | 任务号、`request_id`、文件 Key、代理、支付方式、凭证快照、金额和数量汇总、状态、处理租约 |
| `tb_bulk_purchase_item` | 行号、资产、套餐快照、金额、状态、订单 ID、错误码、错误原因、幂等键 |
| `tb_bulk_purchase_task` | 任务号、`request_id`、文件 Key、支付方式、凭证快照、涉及代理数、金额和数量汇总、状态、处理租约 |
| `tb_bulk_purchase_item` | 行号、资产、结算代理快照、套餐快照、金额、状态、订单 ID、错误码、错误原因、幂等键 |
任务状态:`1=待处理, 2=处理中, 3=完成, 4=部分成功, 5=失败`
明细状态:`1=待处理, 2=处理中, 3=成功, 4=失败`
@@ -1068,17 +1258,22 @@ flowchart TD
- 行幂等键:`bulk_purchase:{task_id}:{row_no}`
- 钱包可用金额统一使用 `credit_enabled` 后的有效信用额度,禁止直接无条件加 `credit_limit`
- 钱包余额、版本、订单、资金流水和明细成功状态在同一事务。
- 钱包支付逐行扣该资产当前所属代理的主钱包;资产无代理归属、归属异常或该代理没有有效主钱包时只将该行记为失败。同一批次可依次锁定不同代理钱包,不存在整批共享钱包。
- 单行失败不回滚其他成功行,任务统计从明细表重新聚合。
- 批量订购必须复用需求 15 的套餐可售策略,不能绕过下架续费限制。
- 任务详情和明细只能由上述有权平台主体读取;不得通过猜测 `task_id` 向代理或企业账号暴露跨代理资产、钱包或失败原因。
API
```text
POST /api/admin/storage/upload-url
POST /api/admin/bulk-purchases
GET /api/admin/bulk-purchases/{task_id}
GET /api/admin/bulk-purchases/{task_id}/items
```
CSV 先使用 `purpose=bulk_purchase` 获取预签名地址并直传私有对象存储;线下凭证使用附件用途直传。`POST /api/admin/bulk-purchases` 只接收 JSON`request_id``payment_method``file_key``voucher_keys`,不接收 `shop_id`、multipart 或文件字节。创建任务前校验对象存在、归属当前上传主体、扩展名/Content-Type 和大小Worker 按稳定 `file_key` 下载解析。
线下凭证仅作为本批次业务资料和审计快照,本期不校验跨批次唯一性或建立财务核销规则。
### 6.5 需求 20退款审批
@@ -1111,17 +1306,11 @@ sequenceDiagram
- 微信、支付宝、线下等非代理钱包支付由财务在系统外人工退款后再通过企微;企微通过即表示人工退款已确认,本系统不调用渠道退款 API也不再提供本地 `manual-complete` 二次确认。
- 代理钱包支付订单在企微通过后按原扣款流水定位原代理主钱包,幂等回溯并写退款流水;可自然冲减负余额。
- 个人客户或资产钱包不自动回款,现有 `BuyerTypePersonal` 自动资产钱包退款分支在迁移时删除或隔离。
- 驳回时本地状态改为已拒绝;撤销/删除时改为已撤销,可修改业务资料后重新申请
- 驳回时本地状态改为已拒绝,审批和该退款单同时终结;原退款单不可编辑、不可再次提交。业务人员纠正驳回原因后仍需退款时,重新走 `POST /api/admin/refunds` 创建新退款单。撤销/删除时进入异常状态和人工处置,同样不开放原单重新提交
- 通过后撤销且资金已执行时不自动冲正,记录 `critical` 审计、站内告警并人工处理;资金尚未执行时终止后续任务。
- 原退款业务单级 `approve/reject/return` 路由下线,不存在本地审批动作 API。
重新申请:
```text
POST /api/admin/refunds/{id}/resubmit
```
只允许已驳回、已撤销或已删除且尚未完成退款的申请按原退款规则修改申请金额、凭证和原因,并创建 `round_no + 1` 的企微实例;订单、资产快照、提交人和历史审批不可修改。
一张退款单只对应一条企微审批申请。企微同意或拒绝后,该审批申请和退款单均形成不可变终态;本期下线既有 `POST /api/admin/refunds/{id}/resubmit`,不提供任何原退款单编辑或重提接口。拒绝后再次退款属于新的业务事实:前端重新进入退款创建流程,用户根据拒绝原因重新填写金额、凭证和原因,后端生成新的退款 ID、退款单号、业务快照、提交人快照和企微审批申请。新旧退款单只因指向同一订单或资产而具有关联不继承审批节点、意见、附件、状态或企微发起身份已拒绝退款不计入该订单或资产的活跃退款但仍须阻止与其他活跃退款并存。企微意外返回撤销、删除或通过后撤销时只进入异常处置不作为创建新退款的自动放行依据。
前端只读展示企微审批状态、意见/附件和业务处理结果,不显示本地审批或人工退款确认按钮。
@@ -1205,14 +1394,14 @@ POST /api/admin/agent-recharges/{id}/reject
- 检查同一设备多条 `is_current=true` 绑定并先修复。
- 检查未删除卡 `iccid_19` 重复,解决冲突后才能建立部分唯一索引。
- 检查 `realname_link_type!=none` 但资产 `realname_policy=none` 的冲突数据并明确修正结果。
- 使用生产卡量测算活跃/不活跃轮询间隔对应的 Gateway QPS 和最长兜底延迟
- 记录现有轮询配置、队列深度、卡级开关和监控基线,发布后验证调度行为未被公共写入改造改变
- 核对存量退款的支付方式和买家类型;仅将代理钱包订单接入自动回退,个人资产钱包订单不得误入该处理器。
- 统计待审批退款、平台员工线下充值和历史终态记录数量。
- 轮换用户 demo 中泄露的企微 Secret、Token 和 EncodingAESKey配置可信域名、应用可见范围和回调地址。
- 发布并验证退款、线下充值企微模板控件映射要求会发起审批的平台员完成扫码绑定。
- 发布并验证退款、线下充值企微模板控件映射,验证代理固定代提交成员可用,并要求会发起审批的平台/超级管理员完成扫码绑定。
- 验证微信 Native、支付宝 PreCreate 配置和回调地址,确认代理充值支付渠道可用。
- 盘点旧账号、资产、轮询日志的写入口和查询入口,确认统一审计切换清单。
- 初始化角色导出字段的最小权限集,禁止默认放开敏感字段
- 停机窗口内由业务使用超级管理员配置普通角色导出字段并抽样验证;不运行默认授权迁移,永久禁止导出的字段不进入代码目录
### 7.2 迁移清单
@@ -1221,7 +1410,7 @@ POST /api/admin/agent-recharges/{id}/reject
| 新建 | `tb_system_config` | 受控动态配置 |
| 新建 | `tb_notification` | 分类、级别、受控跳转和接收人幂等 |
| 新建 | `tb_wecom_approval_scene``tb_wecom_approval_template_version` | 稳定场景和不可变模板映射版本 |
| 新建 | `tb_wecom_approval_instance``tb_account_wecom_mapping` | 企微审批镜像账号扫码绑定 |
| 新建 | `tb_wecom_approval_instance``tb_account_wecom_mapping` | 企微审批镜像、平台账号扫码绑定、真实业务提交人和企微发起身份快照 |
| 新建 | `tb_audit_event``tb_audit_event_resource` | 全局不可变业务审计和多资源关联 |
| 新建 | `tb_integration_log` | Gateway、运营商、企微和支付渠道交互 |
| 新建/确认 | `tb_outbox_event` | 可靠事件投递 |
@@ -1230,8 +1419,7 @@ POST /api/admin/agent-recharges/{id}/reject
| 新建 | `tb_expiry_push_record` | 15/7/3 天临期通知防重 |
| 新建 | `tb_bulk_purchase_task``tb_bulk_purchase_item` | 批量订购任务和逐行明细 |
| 修改 | `tb_device` | 实名状态快照 |
| 修改 | `tb_iot_card`/轮询调度状态 | 活跃级别、最后活跃/上游变化信息;高频调度心跳不写核心表 |
| 修改 | `tb_polling_config` | 收口为全局活跃/不活跃间隔,不再按卡类别形成轮询资格 |
| 修改 | 现有实名/流量/网络轮询 Handler | 保持调度不变,只将查询结果交给公共卡状态写入用例 |
| 修改 | `tb_iot_card.iccid_19` | 未删除数据范围 Partial Unique Index |
| 修改 | `tb_shop_package_allocation``tb_package_usage` | 生效条件、周期和时长快照 |
| 修改 | `tb_exchange_order` | 提交人快照、资产标识快照和新旧资产查询索引 |
@@ -1239,7 +1427,7 @@ POST /api/admin/agent-recharges/{id}/reject
| 修改 | `tb_shop` | 可空平台业务员字段 `business_owner_account_id` |
| 修改 | `tb_role` | 新建店铺默认信用开关和额度模板 |
| 修改 | `tb_agent_wallet` | 实际信用开关、额度和 CHECK 约束 |
| 修改 | `tb_refund_request` | 提交人、企微实例轮次、撤销状态和终态处理结果 |
| 修改 | `tb_refund_request` | 提交人、唯一企微审批申请引用、撤销状态和终态处理结果 |
| 修改 | `tb_agent_recharge_record` | 提交人、企微实例、支付状态和入账处理结果 |
| 修改 | `tb_payment` | 支持 `order_type=agent_recharge` 和创建时支付配置快照 |
@@ -1251,7 +1439,7 @@ POST /api/admin/agent-recharges/{id}/reject
flowchart LR
M[进入维护模式并停止写入] --> DB[执行增量迁移]
DB --> Deploy[同时发布 API、Relay/Worker、前端]
Deploy --> WeCom[发布企微模板映射和扫码绑定]
Deploy --> WeCom[发布企微模板映射、验证代理固定账号并完成平台账号绑定]
WeCom --> Backfill[提交存量待审批单到企微]
Backfill --> Verify[人工验证核心链路]
Verify --> Open[解除维护模式]
@@ -1262,8 +1450,8 @@ flowchart LR
1. 停止订单、退款、充值、资产状态和配置相关写入,暂停旧 Worker。
2. 创建企微、审计、通知、批量任务、权限和同步调度所需表/索引,并增加业务关联字段。
3. 同时发布 API、Relay/Worker 和前端,统一切换 Audit Writer、Integration Log 和 Access Log 脱敏。
4. 发布两个企微模板映射版本,验证连接、回调状态查询和员工扫码绑定
5. 使用一次性 Application 命令为存量待审批退款和平台员工线下充值创建企微实例;未绑定创建人的记录进入迁移待处理列表。
4. 发布两个企微模板映射版本,验证连接、代理固定代提交账号、平台账号扫码绑定、回调状态查询。
5. 使用一次性 Application 命令为存量待审批记录创建企微实例:代理创建的退款使用固定代提交账号;平台/超级管理员创建的记录仅在创建人已绑定企微时提交,未绑定或真实创建人缺失的记录进入迁移待处理列表。
6. 历史终态记录保留 `approval_source=legacy`,不得伪造企微实例或审批时间线。
7. 验证数据同步三通道、19/20 位 ICCID 回调、代理微信/支付宝扫码充值和统一审计查询。
8. 确认旧退款审批、充值 `offline-pay/reject`、旧审计写入口和重复卡状态写逻辑不可访问。
@@ -1287,7 +1475,7 @@ flowchart LR
| 编号 | 已确认结论 |
|------|------------|
| C-01 | 限速内部单位固定 `kbps`;本期仅人工设置/取消Gateway 取消参数仅由适配器处理。 |
| C-01 | 限速使用固定语义化 `speed_level`;前端不得输入任意速率或渠道 `code`;恢复不限速与限到 0kbps 严格区分,上游编码由适配器按 Gateway 账户和运营商映射。 |
| C-02 | 实名策略批量单次最多 500 条,事务内全成全败。 |
| C-03 | 设备批量分配文件最大 10MB、最多 1000 行、每批 200 条、失败明细最多 1000 条,且一任务只做一种分配操作。 |
| C-04 | 批量订购线下凭证仅保存业务资料快照,不校验跨批次唯一性或财务核销。 |
@@ -1301,7 +1489,7 @@ flowchart LR
| C-12 | H5 不增加全局实名策略默认值,新建卡/设备继续默认 `after_order`。 |
| C-13 | 行业卡实名由 `realname_link_type` 决定19/20 位 ICCID 精确查对应列,不截断、不补位、不跨列降级。 |
| C-14 | 事件同步固定立即、3 分钟、5 分钟三次Gateway 超频不退避,不删除后续任务。 |
| C-15 | 企微账号只允许扫码绑定一个 `userid`能覆盖绑定到第二个系统账号。 |
| C-15 | 平台/超级管理员企微账号只允许扫码绑定一个 `userid`得绑定多个系统账号;代理审批使用部署配置中的固定企微账号代提交,真实业务提交人始终独立保存和展示。 |
| C-16 | 代理在线充值最低 100 元,支付成功直接入主钱包,不因金额大进入审批。 |
| C-17 | 全局审计本次停止旧表新写入;历史只读投影,不双写、不在线回填。 |
| C-18 | 角色默认信用配置只作用于以后新建的店铺;修改角色不更新已有店铺。 |
@@ -1309,7 +1497,7 @@ flowchart LR
| C-20 | 临期只发站内通知3 天内使用红色,且只在临期独立列表置顶。 |
| C-21 | 换货新资产继承旧资产店铺;旧资产保留原归属,已属于其他店铺的新资产不得换入。 |
Gateway 的取消限速具体报文不是产品决策:上线前由上游接口契约确定,业务层始终只传 `speed_kbps=0`
Gateway 上游已确认使用 `POST /flow-card/speedLimit`,请求核心参数为 `cardNo + code` 并由统一客户端包装在 `params` 中。业务层始终只传语义化 `speed_level``code=-1` 表示恢复不限速,`code=0` 表示限到 0kbps二者禁止混用。不同 Gateway 账户相同速率编码可能不同,实施前必须完成账户/运营商映射和真实环境联调
### 8.1 主要运行风险
@@ -1318,11 +1506,12 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 企微模板失效 | 模板或控件 ID 编辑后变化 | 场景暂停、模板版本、定期验证和原子切换 |
| 企微提交重复 | `applyevent` 超时后盲目重试 | 提交结果未知状态和人工绑定 `sp_no` |
| 审批回调漏失 | 网络或企微回调异常 | 审批中实例每 2 分钟查询详情兜底 |
| 账号绑定冲突 | 同一企微成员绑定多个账号 | `userid` 唯一约束、扫码会话和强制解绑审计 |
| 平台账号绑定冲突 | 同一企微成员绑定多个平台账号 | `userid` 唯一约束、扫码会话和强制解绑审计 |
| 代理固定代提交账号失效 | 成员离职、停用或移出应用可见范围 | 周期验证、提交前就绪校验、暂停代理新申请和系统告警 |
| 重复代理钱包回退或入账 | Outbox/Asynq 至少一次投递 | 稳定业务幂等键、资金流水唯一业务号、处理租约 |
| 通过后撤销 | 企微通过且资金已执行后撤销 | 不自动冲正critical 审计、通知和人工处理 |
| 支付重复回调 | 微信/支付宝多次通知 | 支付单状态条件、钱包流水唯一键和业务幂等键 |
| 同步请求放大 | 查询、轮询和业务事件同时请求同一卡 | 同场景合并、单互斥、运营商最小间隔和活跃调频 |
| 同步请求放大 | 查询、轮询和业务事件同时请求同一卡 | 事件序列按同场景合并、单次请求互斥、运营商最小间隔;现有周期轮询调度保持不变 |
| ICCID 错配 | 20 位截断或 19 位补位命中错误卡 | 双列精确路由和 `iccid_19` 部分唯一索引 |
| 审计数据量过大 | 高频轮询和全局写操作持续增长 | 时间索引、分批清理、Integration Log 短保留和后续月分区 |
| 批量重复下单 | 重复提交或并发 Worker | 创建请求幂等、任务/明细条件领取、行级唯一键 |
@@ -1345,8 +1534,8 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
- Outbox 待投递数、最早积压时间和失败次数。
- 企微 Token 刷新、模板失效、提交结果未知、回调失败、待审批轮询积压和业务处理失败。
- 账号扫码绑定成功/失败/冲突未绑定申请人数量。
- 活跃/不活跃卡数量、各同步类型 QPS、Gateway 超频、连续失败和事件序列完成率。
- 平台账号扫码绑定成功/失败/冲突未绑定平台申请人数、代理固定代提交账号验证结果和提交失败数量。
- 现有轮询队列/配置基线、各同步类型 QPS、Gateway 超频、连续失败和事件序列完成率。
- Audit Event/Integration Log 写入失败、增长速度、清理积压和敏感读取次数。
- 微信/支付宝预下单、回调失败、已支付未入账和重复回调数量。
- 批量任务成功/失败/部分成功数量。
@@ -1358,8 +1547,8 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 范围 | 必测场景 |
|------|----------|
| 企微审批 | 模板发布/失效、扫码绑定、提交、回调、2分钟轮询、驳回/撤销/删除、提交未知恢复、意见附件和业务快照 |
| 数据同步 | 活跃调频、0/3/5 阶梯、同场景合并、单互斥、超频不退避、19/20位回调、解除实名忽略、旧入口收口 |
| 企微审批 | 模板发布/失效、平台账号扫码绑定、代理固定账号有效/失效、两类发起身份与真实业务提交人展示、提交、回调、2分钟轮询、驳回/撤销/删除、提交未知恢复、意见附件和业务快照 |
| 数据同步 | 现有轮询调度回归、0/3/5 阶梯、同场景合并、单次请求互斥、事件超频不退避、19/20位回调、解除实名忽略、旧入口收口 |
| 全局审计 | 关键事务失败回滚、人员/资源/请求/资金/风险/集成视角、历史投影、脱敏、旧表停止新写入 |
| 钱包 | 普通余额扣款、信用扣款、额度降低失败、并发版本冲突、负余额回充 |
| 角色默认额度 | 新建店铺继承默认值、修改角色不影响已有店铺、店铺独立调额 |
@@ -1367,14 +1556,14 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 批量订购 | 钱包/线下两种支付、部分成功、重复提交、Worker 重试、失败明细、模板错误 |
| 退款 | 金额发起时固定、财务人工退款企微确认、代理钱包回退原主钱包、个人资产钱包不误入、通过后撤销不冲正 |
| 充值 | 微信/支付宝最低100元扫码、重复回调不重复入账、已支付恢复、在线不审批、线下企微通过自动入账且无操作密码 |
| 导出 | 普通角色、敏感字段角色、超级管理员、权限为空、审批摘要批量查询 |
| 限速 | 单卡、设备当前卡、无当前卡、设置、取消、Gateway 失败和审计记录 |
| 导出 | 普通角色字段并集、超级管理员全代码目录、权限为空、行权限、附件受保护链接、审批摘要和附件批量查询 |
| 限速 | 单卡、设备当前卡、无/多当前卡、固定档位、恢复不限速、限到0kbps、运营商能力、Gateway 失败/结果未知和审计记录 |
| 临期 | 当前与排队套餐最终到期推算、等待实名不可预计、15/7/3 天、3天红色和临期页置顶、漏跑补发、列表/详情/C端一致 |
| 换货补充 | 新资产继承店铺、其他店铺资产拒绝、旧资产保留归属、前代/后代标识和受控跳转 |
| 系列授权 | 首次和后续批量选择、已授权置灰、重复提交幂等、三类价格含义正确 |
| 发布 | 存量退款和充值回填、历史 `legacy`、旧路由不可访问、Worker 暂停后恢复 |
验收使用接口调用、PostgreSQL 数据核对、日志检查和页面操作,不以自动化测试作为本项目交付前提
验收同时使用自动化测试、真实接口调用、PostgreSQL 数据核对、日志检查和页面操作;资金、状态机、权限、异步幂等和第三方 Adapter 必须有可重复的自动化公共行为测试,真实第三方联调与人工验收不能被测试替代
---
@@ -1383,8 +1572,8 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
```text
1. 增量迁移、事务管理、Outbox、统一审计模型和 Access Log 脱敏
2. 站内通知与审计多视角 Query/前端
3. 卡状态领域、轮询活跃调频、事件阶梯和运营商回调防腐层
4. 企微连接、模板版本、账号扫码绑定、提交/回调/轮询
3. 卡状态公共写入、现有轮询接入、事件阶梯和运营商回调防腐层
4. 企微连接、模板版本、平台账号扫码绑定、代理固定代提交、提交/回调/轮询
5. 退款和平台员工线下充值接入企微,代理微信/支付宝扫码充值
6. 钱包信用额度、业务员和余额预警、批量订购、导出权限、设备批量分配、系列套餐批量授权、限速和临期提醒
7. 换货归属与换货链、其他查询和显示修复、存量回填及停机发布演练
@@ -1413,8 +1602,8 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 工作包 | 后端人日 | 前端人日 | 快速交付说明 |
|--------|----------|----------|--------------|
| 迁移、Outbox、全局审计和站内通知 | 2 | 1.52 | 复用现有日志、列表和抽屉组件,先完成核心多视角 |
| 数据同步领域收口、活跃轮询和运营商回调 | 23 | 0.51 | 后端为主,前端只补状态和审计跳转 |
| 企微模板、扫码绑定、提交/回调/轮询 | 23 | 1.52 | 复用企微官方页面和现有业务详情布局 |
| 数据同步领域收口、事件触发和运营商回调 | 23 | 0.51 | 现有轮询调度不改,前端只补审计轨迹跳转 |
| 企微模板、平台绑定、代理固定代提交、提交/回调/轮询 | 23 | 1.52 | 复用企微官方页面和现有业务详情布局 |
| 退款、线下充值终态和代理扫码充值 | 23 | 1.52 | 复用现有钱包、支付回调和充值页面 |
| 信用、业务员预警、换货、系列授权、批量、导出、限速和临期 | 2.53.5 | 2.53 | 系列授权和换货链已有后端基础,只补增量能力 |
| 联调、数据核对、回填和停机发布 | 11.5 | 0.51 | 随开发持续联调,最后集中验证核心链路 |
@@ -1428,8 +1617,8 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
```text
第 12 天迁移、Outbox、审计/通知骨架,前端同步搭建页面框架
第 35 天:数据同步收口、活跃轮询、回调防腐层和审计外部集成视角
第 47 天:企微模板、扫码绑定、审批提交/回调/轮询及前端配置页面
第 35 天:数据同步写入收口、事件阶梯、回调防腐层和审计外部集成视角
第 47 天:企微模板、平台账号扫码绑定、代理固定代提交、审批提交/回调/轮询及前端配置页面
第 69 天:退款、线下充值终态、代理微信/支付宝扫码充值
第 812 天:信用、业务员预警、换货归属与标识、系列批量授权、批量、导出、限速和临期
第 1214 天:全链路联调、数据核对、旧入口清理和存量回填演练
@@ -1445,7 +1634,7 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 本期范围与移出项 | 待评审 | | |
| 渐进式 DDD 边界 | 待评审 | | |
| 数据同步三通道与运营商回调 | 待评审 | | |
| 企业微信审批、模板版本账号绑定 | 待评审 | | |
| 企业微信审批、模板版本、平台账号绑定与代理固定代提交 | 待评审 | | |
| 全局多视角审计与历史投影 | 待评审 | | |
| 站内通知与受控跳转 | 待评审 | | |
| 资金与信用额度 | 待评审 | | |
@@ -1455,4 +1644,4 @@ Gateway 的取消限速具体报文不是产品决策:上线前由上游接口
| 前端交互和权限 | 待评审 | | |
| 停机发布和回滚 | 待评审 | | |
评审通过条件:第八章约束全部纳入实施任务;除 Gateway 上游取消参数和真实第三方联调结果外,不存在需要实施人员自行猜测的数据模型、接口、状态语义或资金规则。
评审通过条件:第八章约束全部纳入实施任务;除真实第三方联调结果和不同 Gateway 账户/运营商的档位映射配置外,不存在需要实施人员自行猜测的数据模型、接口、状态语义或资金规则。

View File

@@ -112,8 +112,8 @@ FE/BE 研发需求开发完成
| #40 下架套餐续费 | **标题:**C端当前套餐续费入口。<br>**页面:**当前套餐旁显示续费;下架套餐不出现在新购列表。 | **标题:**下架套餐续费资格。<br>**接口:**`GET /api/c/v1/asset/packages` 返回 `can_purchase/purchase_mode/disabled_reason``POST /api/c/v1/orders/create` 强校验资产所有人和历史使用记录。 |
| #38 代理信用额度 | **标题:**角色默认信用和店铺实际额度管理。<br>**页面:**客户角色配置新建默认额度并提示不影响存量;店铺资金页单独调整实际额度。 | **标题:**代理主钱包信用额度。<br>**接口:**`PUT /api/admin/roles/{id}/default-credit``PUT /api/admin/shops/{id}/credit-limit``GET /api/admin/shops/fund-summary`。<br>**入参:**开关、额度、钱包版本。<br>**返回:**额度、可用金额、欠款和版本。 |
| #37 企微审核流转 | **标题:**企微配置、账号绑定和审批详情。<br>**页面:**企微配置页、个人扫码绑定、审批运行列表;业务详情只读展示意见和附件。 | **标题:**企业微信审批接入。<br>**接口:**`GET /api/admin/wecom/status``POST /api/admin/wecom/account-binding/sessions``GET /api/admin/wecom/approvals``POST /api/admin/wecom/approvals/{id}/sync`。<br>**业务详情:**统一返回 `approval` 对象。 |
| #36 批量订购套餐 | **标题:**批量订购上传、支付、进度和失败明细。<br>**页面:**选择代理和整批支付方式上传Excel及线下凭证展示部分成功。 | **标题:**批量订购任务。<br>**接口:**`POST /api/admin/bulk-purchases``GET /api/admin/bulk-purchases/{task_id}``GET /api/admin/bulk-purchases/{task_id}/items`。<br>**入参:**代理、支付方式、文件、凭证。<br>**返回:**任务和逐行结果。 |
| #35 退款审核 | **标题:**退款企微审批和退款处理状态。<br>**页面:**创建时上传备注附件;详情展示审批、人工退款说明和业务处理结果。 | **标题:**退款企微终态处理。<br>**接口:**`POST /api/admin/refunds``GET /api/admin/refunds/{id}``POST /api/admin/refunds/{id}/resubmit`。<br>**返回:**退款数据、`approval``processing_status`。代理钱包通过后幂等回溯。 |
| #36 批量订购套餐 | **标题:**批量订购上传、支付、进度和失败明细。<br>**页面:**仅超管/具备独立权限的平台账号可用选择整批支付方式CSV及线下凭证先直传对象存储不选择代理同一CSV可含不同代理资产不接受Excel。 | **标题:**批量订购任务。<br>**接口:**`POST /api/admin/storage/upload-url``POST /api/admin/bulk-purchases``GET /api/admin/bulk-purchases/{task_id}``GET /api/admin/bulk-purchases/{task_id}/items`。<br>**权限:**超管或独立“批量订购套餐”权限;代理/企业拒绝。<br>**入参:**支付方式、`file_key``voucher_keys`,无`shop_id`。<br>**判重:**同文件相同资产类型、标准化标识和套餐编码仅首行处理,后续行失败。<br>**返回:**任务和逐行结算代理结果。 |
| #35 退款审核 | **标题:**退款企微审批和退款处理状态。<br>**页面:**创建时上传备注附件;详情展示审批、人工退款说明和业务处理结果;拒绝后重新退款必须新建退款单。 | **标题:**退款企微终态处理。<br>**接口:**`POST /api/admin/refunds``GET /api/admin/refunds/{id}`;下线原 `approve/reject/return/resubmit`。<br>**返回:**退款数据、`approval``processing_status`一单一审批,拒绝终结原单;代理钱包通过后幂等回溯。 |
| #34 充值审核流程 | **标题:**代理扫码充值与员工线下充值审批。<br>**页面:**在线充值展示支付方式、二维码和支付状态;线下充值展示只读企微审批状态。 | **标题:**代理在线充值和员工线下审批。<br>**接口:**`GET /api/admin/agent-recharges/payment-methods``POST /api/admin/agent-recharges``GET /api/admin/agent-recharges/{id}/payment-status``GET /api/admin/agent-recharges/{id}`。<br>**返回:**二维码、过期时间、支付/审批/入账状态。 |
| #33 套餐临期提醒 | **标题:**临期列表、各端高亮和续费入口。<br>**页面:**临期页3天内置顶普通资产列表只高亮代理首页显示数量C端显示续费按钮。 | **标题:**预计最终到期临期Query和站内通知。<br>**接口:**`GET /api/admin/expiring-assets`、资产列表/详情增加临期字段、`GET /api/c/v1/asset/info`、通知接口。<br>**返回:**最终到期、剩余天数、颜色节点15/7/3天通知防重。 |
@@ -197,7 +197,7 @@ FE/BE 研发需求开发完成
| INT-01 | 资产实名、复机与状态同步联调 | #94#73#62#53 | FE 11.5h / BE 11.5h,已含 | 实名策略接口、同步任务和H5页面完成 | 不同运营商实名能力、先实名/先购买、0/3/5同步、回调后状态和筛选一致 |
| INT-02 | 换货完整链路联调 | #98#86#57#45 | FE 0.51h / BE 0.51h已含 | 换货接口、详情和列表页面完成 | 退款拦截、新资产继承店铺、完成换货、列表搜索、前代/后代跳转 |
| INT-03 | 套餐授权、购买、续费、到期与临期联调 | #55#46#43#40#33 | FE 11.5h / BE 11.5h,已含 | 套餐快照、授权候选和各端到期字段完成 | 批量授权、购买生效条件、下架续费、排队套餐最终到期、临期高亮和通知 |
| INT-04 | Excel批量任务与导出联调 | #49#36#42 | FE 11.5h / BE 11.5h,已含 | 上传、任务详情、Worker和导出场景完成 | 模板校验、部分成功、失败明细、任务恢复、字段权限和文件下载 |
| INT-04 | CSV批量任务与导出联调 | #49#36#42 | FE 11.5h / BE 11.5h,已含 | 上传、任务详情、Worker和导出场景完成 | 模板校验、部分成功、失败明细、任务恢复、字段权限和文件下载 |
| INT-05 | 支付、代理钱包、信用和余额预警联调 | #48#38#34#36#96#97 | FE 11.5h / BE 11.5h,已含 | 支付配置、钱包领域和业务员接口完成 | 支付方式限制、扫码充值、信用扣款、角色默认额度、店铺调额、100元预警 |
| INT-06 | 企业微信审批、退款和线下充值联调 | #37#35#34#44 | FE 1.52h / BE 1.52h已含 | 企微模板、绑定、回调、轮询和业务详情完成 | 扫码绑定、发起审批、意见附件、通过/驳回/撤销、退款/充值终态和列表摘要 |
| INT-07 | Gateway卡限速联调 | #47 | FE 0.51h / BE 0.51h已含 | Gateway联调配置和卡/设备入口完成 | 单卡限速、设备解析当前卡、取消限速、无当前卡、失败审计 |

View File

@@ -157,10 +157,11 @@
页面入口:店铺新建、店铺编辑、店铺列表、店铺详情。
页面结构:
1. 新建编辑表单增加“平台业务员”可搜索下拉,可为空。
1. 超级管理员和平台账号的新建编辑表单增加“平台业务员”可搜索下拉,可为空;代理账号只读展示,不出现选择或清空控件
2. 候选项展示账号名和手机号摘要,只显示启用的平台账号。
3. 店铺列表增加业务员列和业务员筛选项。
4. 店铺详情展示业务员名称和手机号摘要。
5. 代理创建下级店铺时提示“默认继承直属上级当前业务员”;继承由后端保证。
接口约定:
- POST /api/admin/shops、PUT /api/admin/shops/{id} 增加 business_owner_account_id:int64|null。
@@ -168,7 +169,7 @@
- 业务员候选复用 GET /api/admin/accounts?account_type=platform&status=1items至少返回 account_id、account_name、phone_masked。
- 列表和详情返回 business_owner_account_id:int64|null、business_owner_name:string、business_owner_phone_masked:string。
交互规则:不出现分销、佣金或发展层级文案;已停用业务员仍可在历史详情显示,但编辑候选中不可选。
交互规则:不出现分销、佣金或发展层级文案;代理不能通过构造请求修改业务员;已停用业务员仍可在历史详情显示,但编辑候选中不可选;上级店铺后续变更不自动级联既有下级
完成标准:创建、编辑、筛选、详情展示一致,并覆盖清空业务员和无可选账号状态。
```
@@ -197,6 +198,8 @@
2. 字段不参与店铺层级、数据权限、佣金或分销关系计算。
3. 账号停用或删除后保留历史关联,通知时跳过不可用账号。
4. 变更前后值写入审计。
5. 代理创建下级店铺时由服务端复制所选直属上级店铺当时的业务员;代理请求只要出现该字段即拒绝。
6. 只有超级管理员和平台账号可以显式设置、清空或更换;父店铺后续变更不向既有子孙店铺级联。
完成标准:创建、修改、清空和筛选均生效,不能绑定代理账号或停用平台账号。
```
@@ -214,24 +217,22 @@
**描述**
```markdown
目标:让运营能看懂资产当前轮询策略和最近同步情况,不新增第二个同步按钮。
目标:让运营通过统一审计查看业务事件和回调的同步轨迹;保留现有手动刷新和轮询展示,不新增第二个同步按钮或活跃轮询字段
预计工时前端23小时。
页面入口:卡详情、设备详情、全局审计外部集成页。
页面结构:
1. 详情页同步区域展示轮询是否启用、活跃级别、最后活跃时间、最后活跃场景下次轮询时间
2. 保留现有手动刷新按钮
3. 增加“查看同步轨迹”,跳转全局审计并自动带入资产筛选
4. 同步轨迹按立即、3分钟、5分钟连续展示结果。
1. 保留现有手动刷新按钮及现有轮询状态展示,不新增活跃级别、最后活跃场景下次轮询字段
2. 增加“查看同步轨迹”,跳转全局审计并自动带入资产筛选
3. 同步轨迹按立即、3分钟、5分钟连续展示结果并显示运营商回调和现有周期轮询的Integration Log
接口约定:
- GET /api/admin/assets/resolve/{identifier} 返回 polling:{enabled:bool,activity_level:string,last_activity_at:string|null,last_activity_scene:string,next_poll_at:string|null}。
- 现有手动刷新接口保持原契约。
- GET /api/admin/audit/integrations 支持 resource_type、resource_key、correlation_id 查询。
交互规则rate_limited 只展示本次失败,不显示自动退避倒计时;无下次轮询时显示“-”
交互规则:事件序列的rate_limited只展示本次失败不显示自动退避倒计时前端不根据事件轨迹推算或展示新的周期轮询时间
完成标准:卡和设备均能展示同步状态并跳转到过滤后的轨迹页,加载、空记录和失败状态完整。
```
@@ -241,28 +242,28 @@
**标题**
```text
[BE][UR#94] 资产状态同步DDD收口与轮询优化
[BE][UR#94] 卡状态公共写入、事件触发与运营商实名回调
```
**描述**
```markdown
目标:将轮询、业务事件、手动刷新和运营商回调统一进入卡状态应用用例。
目标:将现有轮询查询结果、业务事件、手动刷新和运营商回调统一进入卡状态应用用例,同时保持现有轮询调度不变
预计工时后端79小时。
规则:
1. 只保留全局 enable_polling 开关,按活跃和不活跃卡使用不同轮询间隔
1. 现有tb_polling_config、Redis分片队列、各同步类型间隔、卡级开关、失败重排、并发控制和监控全部保持现状只替换查询成功后的状态写入及联动
2. 关键业务成功边界创建立即、3分钟、5分钟三个无自动重试任务达到预期状态后后续任务提前完成。
3. Gateway 超频只记录 rate_limited不做 blocked_until 或指数退避。
4. ApplyCardObservation 是实名、流量和网络状态的唯一写入口。
5. 行业卡是否实名由运营商 realname_link_type 决定。
接口:不新增显式同步接口;资产详情 Query 返回 polling;同步轨迹进入统一 Integration Log 和审计 Query。
接口:不新增显式同步接口或活跃轮询字段;现有手动刷新保持原契约同步轨迹进入统一Integration Log和审计Query;只为已掌握真实报文的移动/电信实名成功及联通解除实名增加回调Translator
架构:迁移到 card Domain、同步 Application、Gateway Adapter 和 Query旧轮询及刷新逻辑改为调用统一用例。
完成标准:三条自动通道和手动刷新结果一致,事件序列可追踪,旧逻辑不再直接修改卡状态。
完成标准:现有轮询、事件序列、手动刷新和回调结果一致,事件序列可追踪,旧逻辑不再直接修改卡状态;改造前后轮询配置、下次入队、失败重排和监控统计保持一致
```
## UR#86 资产详情中换货标识
@@ -677,18 +678,19 @@
页面结构:
1. 两个独立按钮:批量分配代理、批量分配套餐系列。
2. 两个弹框都包含前端静态模板下载、目标选择、Excel上传和提交按钮。
2. 两个弹框都包含前端静态CSV模板下载、目标选择、CSV上传和提交按钮不接受Excel
3. 创建成功后进入任务进度区,展示状态、总数、成功数、失败数和失败明细。
4. 页面刷新后根据 task_id 恢复进度。
接口约定:
- POST /api/admin/devices/batch-assign-shopmultipart包含 file、shop_id、request_id
- POST /api/admin/devices/batch-assign-seriesmultipart包含 file、series_id、request_id。
- 前端先调用现有对象存储预签名上传接口purpose=device_batch_allocation将UTF-8 CSV直传私有对象存储
- POST /api/admin/devices/batch-assign-shopJSON body={file_key,shop_id}
- POST /api/admin/devices/batch-assign-seriesJSON body={file_key,series_id}。
- GET /api/admin/devices/batch-allocation/{task_id} 返回 task_id、operation_type、status、status_name、total_count、success_count、failed_count、failed_items。
交互规则一个任务只能选择一种操作任务处理中按2、3、5秒后最大10秒轮询页面不可见时暂停。
完成标准:两个入口互不混淆,部分成功和失败原因可查看,模板由前端静态文件提供。
完成标准:两个入口互不混淆,平台代理权限与现有同步用例一致,长设备号按文本预览,部分成功和失败原因可查看,CSV模板由前端静态文件提供。
```
### 后端研发需求
@@ -710,12 +712,13 @@
规则:
1. operation_type 只能是 assign_shop 或 assign_series一个任务只修改一个目标字段。
2. 文件最大10MB、最多1000行、Worker每批200条。
2. 只接受单列UTF-8 CSV固定表头“设备号”文件最大10MB、最多1000行、Worker每批200条。
3. 设备号去重后批量查询失败明细最多保存1000条。
4. 已属于目标值按幂等成功assign_shop 遇到其他代理资产失败assign_series 不修改 shop_id
5. Asynq载荷只传结构化ID和对象存储Key不传本地路径或文件字节
4. 平台只能分配平台库存代理只能把自己名下设备分给直属下级。assign_series时代理只能操作自己设备并使用自己当前授权系列
5. 已属于目标值按幂等成功assign_shop遇到其他代理资产失败assign_series不修改shop_id
6. 业务接口只接收file_key不接收multipart字节Asynq载荷只传结构化task_id不传对象Key、本地路径或文件字节。
完成标准:任务支持部分成功进度恢复和request_id防重,两种命令的数据边界清晰。
完成标准:任务支持部分成功进度恢复Worker重试幂等平台及代理数据边界正确,两种命令的数据边界清晰。
```
## UR#48 不同资产使用不同支付方式
@@ -744,10 +747,10 @@
接口约定:
- GET /api/c/v1/asset/info 返回 allowed_payment_methods:string[],值为 alipay、wechat、wallet。
- POST /api/c/v1/orders/create POST /api/c/v1/orders/{id}/pay 入参 payment_method:string
- POST /api/c/v1/orders/create 必须提交payment_method:string并固化POST /api/c/v1/orders/{id}/pay 不允许选择或修改payment_method,只提交固定方式执行所需附加参数
- GET /api/admin/system/config?module=payment 查询配置PUT /api/admin/system/config/{config_key} 保存卡和设备允许的支付方式。
交互规则:钱包是否可选完全使用后端返回;创建或支付时后端拒绝的方式直接展示错误,不由前端兜底放行
交互规则:钱包是否可选完全使用后端返回;创建成功后展示订单已选方式且不提供切换。需要换方式时先取消待支付订单再重新创建;强充由创建接口按所选微信/支付宝立即拉起,钱包不能用于给自身强充
完成标准:卡、设备、配置异常和无可用方式四类场景展示正确。
```
@@ -763,13 +766,13 @@
**描述**
```markdown
目标:按资产类型返回允许支付方式,在订单创建及支付阶段强校验。
目标:按资产类型返回允许支付方式,在订单创建时决定并固化支付方式,并在后续实际支付时再次强校验。
预计工时后端23小时。
接口GET /api/c/v1/asset/info 增加 allowed_payment_methods订单创建和支付接口校验 payment_method后台复用系统配置接口。
规则:卡默认支付宝和钱包;设备默认微信和钱包;钱包始终允许;配置异常时使用安全默认值并记录错误,不能放开全部方式。
规则:卡默认支付宝和钱包;设备默认微信和钱包;钱包始终允许;配置异常时使用安全默认值并记录错误,不能放开全部方式。普通订单创建保存不可变payment_method/pay只能读取订单快照执行管理员后来禁用该方式时拒绝支付不自动换渠道。强充在创建流程立即使用所选方式wallet强充拒绝。
完成标准:前端隐藏不能绕过后端校验,订单创建和支付使用同一策略,配置变更写审计。
```
@@ -794,14 +797,14 @@
页面入口:卡详情、设备详情。
页面结构:
1.设置限速”弹框包含 speed_kbps 正整数输入框和确认按钮
2.取消限速”使用独立确认操作,提交 speed_kbps=0
1.手动限速”弹框只展示后端返回的固定语义档位不允许输入任意速率或上游code
2.恢复不限速”提交speed_level=unlimited“限到0kbps”提交speed_level=zero_kbps两者严格区分
3. 设备详情必须显示本次实际作用的当前卡ICCID无当前卡时禁用操作并展示原因。
4. 不展示“Gateway当前实际限速”除非接口未来明确返回查询结果。
接口约定POST /api/admin/assets/{identifier}/speed-limitbody={speed_kbps:int};返回 asset_type、asset_identifier、card_no、speed_kbps、result、message。
接口约定POST /api/admin/assets/{identifier}/speed-limitbody={speed_level:string}返回asset_type、asset_identifier、card_no、speed_level、channel_code、applied_speed、channel_raw_value、result、message。
交互规则:单位固定展示kbps提交中禁用按钮;失败保留输入值;成功后展示后端结果
交互规则:提交中禁用按钮;失败保留所选档位;中国电信/广电按后端能力展示,联通/移动或映射缺失时禁用并展示原因;结果未知不能显示成功
完成标准:卡限速、设备解析当前卡、取消限速和无当前卡四类场景完整。
```
@@ -821,13 +824,13 @@
预计工时后端23小时。
接口POST /api/admin/assets/{identifier}/speed-limit入参 speed_kbps正数为设置0为取消
接口POST /api/admin/assets/{identifier}/speed-limit入参为固定语义枚举speed_level恢复不限速与限到0kbps是不同值前端和业务层不得接触渠道code
规则资产为卡时读取ICCID资产为设备时解析 is_current=true 的有效当前卡;不存在当前卡拒绝绝不把设备号传给Gateway。取消仍调用同一Gateway端口由适配器转换上游取消参数
规则资产为卡时读取ICCID资产为设备时解析is_current=true的唯一有效当前卡;不存在或多条当前卡拒绝绝不把设备号传给Gateway。仅中国电信和中国广电直连POST /flow-card/speedLimitAdapter按Gateway账号+运营商+speed_level映射code缺失映射不猜测。外部副作用关闭自动重试超时等结果不明记录unknown
非目标不建立套餐固定限速不因激活、到期、停机或切卡自动限速不增加自动补偿Worker。
完成标准返回最终card_no和调用结果,每次请求记录操作审计及Integration Log。
完成标准返回最终card_no、语义档位和规范化调用结果;电信、广电、联通、移动、映射缺失、明确失败和结果未知均有正确行为;每次请求记录操作审计及Integration Log。
```
## UR#46 资产信息详情字段新增
@@ -960,7 +963,7 @@
接口约定GET /api/admin/refunds、GET /api/admin/agent-recharges、GET /api/admin/exchanges 的items增加 submitter_name、approval_source、approval_status、approval_status_name、current_approver_summary、processing_status、processing_status_name。
交互规则approval_source=none时审批列显示“-”legacy只读展示wecom展示企微状态。审批人摘要使用省略和悬浮完整文本。
交互规则approval_source=none时审批列显示“-”legacy只读展示wecom展示企微状态。平台/超级管理员按业务权限展示当前审批人摘要,长摘要使用省略和悬浮完整文本;代理不展示审批人列,接口摘要固定为空
完成标准:三类列表字段和空值规则一致,分页切换不会额外逐行请求审批详情。
```
@@ -982,7 +985,7 @@
接口:扩展现有三类列表返回 submitter_name、approval_source、approval_status_name、current_approver_summary、processing_status_name。
规则提交人保存业务快照企微摘要按当前页实例ID批量查询禁止N+1历史本地审批返回approval_source=legacy无审批返回none。
规则提交人保存业务快照企微摘要按当前页实例ID批量查询禁止N+1历史本地审批返回approval_source=legacy无审批返回none。当前审批人属于平台内部信息,仅平台/超级管理员按业务权限返回代理响应中的current_approver_summary固定为空。
完成标准:列表查询次数稳定,历史数据可读,审批摘要与详情状态一致。
```
@@ -1008,13 +1011,15 @@
页面结构:
1. 两个入口复用同一套餐候选表格。
2. 列包含套餐名称、编码、公司成本价、当前授权成本价、建议售价、授权状态
3. is_authorized=true 的行显示“已授权”、复选框置灰且不可全选
4. 未授权套餐支持多选并一次提交
2. 首次授权读取现有套餐列表后续管理再读取现有系列授权详情前端按package_id合并不新增候选接口
3. 列按当前调用视角区分上级当前成本价、目标代理授权成本价、建议售价和授权状态;平台代理视角不得都误标为公司成本
4. is_authorized=true的行在新增模式置灰授权、调价、移除分别使用独立操作模式
5. 未授权套餐支持多选并一次提交。
接口约定:
- GET /api/admin/shop-series-grants/{id}/package-options 返回 items:{package_id,package_name,package_code,company_cost_price,authorized_cost_price,suggested_retail_price,is_authorized}
- PUT /api/admin/shop-series-grants/{id}/packagesbody={package_ids:int64[]}。
- 首次授权复用GET /api/admin/packages?series_id=...和POST /api/admin/shop-series-grantsPOST必须同时提交至少一个packages项
- 后续管理复用GET /api/admin/packages?series_id=...与GET /api/admin/shop-series-grants/{id}。
- PUT /api/admin/shop-series-grants/{id}/packagesbody={operation_type:authorize|update_cost|remove,packages:[{package_id,cost_price?}]}。
交互规则:三类价格按分转元;提交成功后重新加载候选列表;空候选和全部已授权状态有明确提示。
@@ -1026,25 +1031,25 @@
**标题**
```text
[BE][UR#43] 系列套餐候选Query与重复授权幂等
[BE][UR#43] 首次套餐授权与后续批量命令明确化
```
**描述**
```markdown
目标:复用现有批量授权接口,新增可供前端一次选择的套餐候选Query。
目标:首次授权继续同时创建系列与套餐;后续复用现有批量接口,并把新增、调价、移除拆成明确命令;不新增候选Query。
预计工时后端0.51小时。
接口GET /api/admin/shop-series-grants/{id}/package-options复用 PUT /api/admin/shop-series-grants/{id}/packages。
接口:复用GET /api/admin/packages、GET /api/admin/shop-series-grants/{id}、POST /api/admin/shop-series-grants和PUT /api/admin/shop-series-grants/{id}/packages。
返回package_id、名称、编码、company_cost_price、authorized_cost_price、suggested_retail_price、is_authorized
首次授权POST中的packages改为必填且至少1项系列与套餐在同一事务全成全败不允许空系列授权
规则:重复套餐按幂等处理;后端不依赖前端置灰;查询遵守代理、系列套餐可见范围;三个价格字段语义分开
后续规则:PUT必须提交operation_type=authorize|update_cost|remove一次只执行一种命令、最多100项、事务内全成全败authorize同价重复幂等、不同价重复冲突不能静默改价所有系列套餐、直属下级与价格边界由后端校验
架构候选列表走Query批量授权走轻量Application事务脚本
读取:前端组合现有套餐列表和授权详情;已授权但当前不在普通列表中的存量项仍从详情只读展示。成本字段按操作者视角准确命名
完成标准:首次和后续授权共用契约,重复提交不生成重复关系
完成标准:首次授权不会产生空系列;新增、调价和移除无语义混用;重复提交不生成重复关系或静默覆盖并发价格;前后端同批切换
```
## UR#42 导出功能
@@ -1054,33 +1059,47 @@
**标题**
```text
[FE][UR#42] 导出字段选择权限配置与任务进度
[FE][UR#42] 自动权限导出、角色字段配置与受保护附件访问
```
**描述**
```markdown
目标:用户按权限选择导出字段管理员可给角色配置各场景可导出字段。
目标:用户按当前查询条件直接导出角色已授权的全部字段管理员可给角色配置各场景可导出字段;导出文件中的退款/充值凭证和企微审批附件可通过后台登录态安全访问
预计工时:前端34小时
预计工时:按本冻结范围重新评估原34小时估算未包含受保护附件落地页不能继续沿用
页面入口:卡、钱包流水、套餐、退款、换货、充值、临期列表的导出弹框;角色权限配置页。
页面入口:卡、设备、订单、钱包流水、套餐、退款、换货、充值、临期列表的导出入口;角色权限配置页;后台附件落地页 `/export-attachments/:attachment_ref`
页面结构:
1. 导出弹框先加载当前scene可选字段使用复选框选择未授权字段不展示
2. 支持xlsx/csv格式及当前列表查询条件
3. 创建后展示任务状态、进度、失败原因和下载按钮。
4. 角色配置按scene分组展示字段复选框并保存
1. 列表导出只选择xlsx/csv格式并沿用当前查询条件不展示字段复选框实际列完全由后端权限解析
2. 页面可加载当前scene最终字段返回空数组时禁用导出并提示“当前角色未配置该场景的导出字段”
3. 创建后展示任务状态、进度、失败原因、取消和下载按钮页面刷新后按任务ID恢复轮询
4. 角色配置按scene分组展示代码支持字段全量保存该角色单个scene的字段允许清空保存后重新读取服务端结果
5. 新增受保护附件落地页,展示加载中、登录恢复、无权限/不存在、文件不可用、解析失败和成功跳转状态。
接口约定:
- GET /api/admin/export-fields?scene= 返回 fields:{field_key,label,selected_by_default}[]
- POST /api/admin/export-tasksbody={scene,format,query,fields:string[]},返回 task_id。
- GET /api/admin/export-fields?scene= 返回当前账号最终会导出的 fields:{key,label}[];它不是字段选择候选集
- POST /api/admin/export-tasksbody={scene,format,query},返回 task_id前端不得提交fields
- GET /api/admin/export-tasks/{id} 返回 status、status_name、progress、download_url、error_message。
- GET/PUT /api/admin/roles/{role_id}/export-fields 查询和保存场景字段。
- GET/PUT /api/admin/roles/{role_id}/export-fields 查询代码目录及保存角色场景字段PUT按单个scene全量替换空数组表示收回全部字段。
- GET /api/admin/attachments/{attachment_ref}/download-url 返回 data:{download_url,expires_at,file_name}请求必须携带后台当前Bearer Token。
交互规则:下载地址为空时禁用下载;任务轮询复用统一异步任务规则;字段为空时禁止提交。
导出流程:
1. 用户在列表页发起导出前端提交scene、format和当前筛选条件分页参数不进入导出条件。
2. 后端创建任务后前端进入现有任务进度流程download_url为空时禁用任务文件下载。
3. 导出文件中的附件地址是后台前端绝对地址 `{admin_frontend_base_url}/export-attachments/{attachment_ref}`,不是后端接口地址或对象存储地址。
完成标准:字段权限、任务恢复、失败重试和文件下载流程完整。
附件访问流程:
1. 浏览器打开附件落地页;未登录时保存当前站内路径并进入后台登录,登录成功后返回该附件页。
2. 页面从路由读取attachment_ref使用Bearer Token调用附件解析接口禁止把attachment_ref替换为file_key也禁止接收外部returnUrl。
3. 后端根据附件关联的退款、充值或审批业务重新检查当前账号权限,成功返回短期私有对象地址。
4. 前端在当前页跳转download_url避免异步后新开窗口被浏览器拦截地址过期时用户重新打开稳定落地页即可重新解析。
5. 401按后台统一登录失效流程处理并保留当前附件页无权与不存在展示统一不可访问状态对象文件不可用或解析失败展示可重试错误不展示file_key、media_id或底层存储错误。
交互规则:用户不能选择导出字段;角色权限后来变化不改变已经创建任务的文件列,但打开附件时始终按当前权限重新鉴权;任务失败时展示后端原因,不自动重复创建任务。
完成标准平台、不同层级代理分别验证最终列和数据行范围无字段状态、任务刷新恢复、CSV/XLSX下载、附件未登录返回、当前有权、权限后来收回、引用不存在和对象不可用流程完整。后端接口发布后前端只需按上述固定契约接入不再等待字段选择或永久对象URL方案。
```
### 后端研发需求
@@ -1088,23 +1107,29 @@
**标题**
```text
[BE][UR#42] 统一导出场景与角色字段权限
[BE][UR#42] 统一导出角色字段权限与受保护附件解析
```
**描述**
```markdown
目标:复用现有导出任务体系,增加本期场景和角色字段级权限。
目标:复用现有导出任务体系,增加本期场景和角色字段级权限,并为退款/充值业务凭证和企微审批附件提供稳定引用、当前权限复核及短期私有下载地址解析
预计工时:后端46小时
预计工时:按本冻结范围重新评估原46小时估算未包含全部新增场景、附件本地化与鉴权解析不能继续沿用
接口GET /api/admin/export-fieldsPOST /api/admin/export-tasksGET /api/admin/export-tasks/{id}GET/PUT /api/admin/roles/{role_id}/export-fields。
接口GET /api/admin/export-fieldsPOST /api/admin/export-tasksGET /api/admin/export-tasks/{id}GET/PUT /api/admin/roles/{role_id}/export-fieldsGET /api/admin/attachments/{attachment_ref}/download-url
规则:最终字段=用户申请字段∩角色授权字段并集∩场景支持字段;超级管理员拥有全部字段;字段授权不能扩大数据行范围;创建任务时快照字段表头;权限解析失败直接拒绝
规则:最终字段=当前账号有效角色授权字段并集∩场景代码支持字段;超级管理员拥有全部代码目录字段前端不传fields账号获授权字段全部导出字段授权不能扩大菜单、接口、场景或数据行范围;创建任务时快照字段表头、查询条件和数据范围Worker不重读实时角色权限权限解析失败或普通账号最终字段为空时不创建任务
场景:iot_card、agent_wallet_transaction、package、refund、exchange、agent_recharge、expiring_asset。审批附件只导出数量不导出对象Key或永久URL
场景:扩展device、iot_card、order并增加agent_wallet_transaction、package、refund、exchange、agent_recharge、expiring_asset。agent_recharge保留支付方式、不导出支付通道
完成标准Count与Fetch条件一致审批摘要批量查询导出任务可恢复且无越权字段
字段目录密码、密钥、Token、对象Key、企微media_id等永不允许导出的内容不注册对已注册字段角色配置是权威不再增加主观敏感字段层。发布不迁移普通角色默认授权停机窗口由业务使用超级管理员完成配置后再恢复服务
附件规则:退款/充值业务凭证与企微审批附件按字段权限导出稳定前端落地页URL但字段授权不能突破审批主体可见性代理可导出业务范围内的退款凭证不能导出或解析平台内部企微审批附件。每个本地附件使用不可变、不可枚举attachment_ref私有file_key不出后端。企微附件须先保存到本地私有对象存储不得把临时media_id或预签名URL写入导出文件。
解析流程前端携带Bearer Token请求attachment_ref后端解析其关联业务在每次访问时重新校验当前业务查看权限、数据范围和附件种类可见性成功返回短期download_url、expires_at、file_name。无权与不存在返回统一错误权限后来收回后旧导出链接也不可下载已知引用的代理也不能解析企微内部审批附件。
完成标准Count与Fetch条件一致审批摘要和附件引用批量查询无N+1字段/表头/权限快照可恢复且无越权列或越权行导出文件不含file_key、media_id或预签名URL前后端按未登录、有权、权限收回、引用不存在、对象不存在和附件未本地化完成联调。
```
## UR#40 套餐设计
@@ -1185,15 +1210,16 @@
2. 店铺资金页展示现金余额、冻结金额、实际信用额度、可用金额、欠款金额和版本。
3. 店铺实际额度使用独立调整弹框,展示修改前后金额预览。
4. 平台员工角色不展示信用配置。
5. 代理账号即使能查看自己或下级代理资金,也不展示实际额度修改入口;平台账号只有取得独立信用额度管理权限后才展示。
接口约定:
- PUT /api/admin/roles/{id}/default-creditbody={credit_enabled:bool,credit_limit:int64}。
- PUT /api/admin/shops/{id}/credit-limitbody={credit_enabled:bool,credit_limit:int64,version:int64}。
- GET /api/admin/shops/fund-summary 返回 balance、frozen_balance、credit_enabled、credit_limit、available_balance、is_in_debt、debt_amount、version。
交互规则:金额不在前端重新计算;并发冲突时刷新最新资金概况;关闭信用时额度输入归零。
交互规则:金额不在前端重新计算;并发冲突时刷新最新资金概况;关闭信用时额度输入归零;不设置产品层固定额度上限,只执行分/元精确转换和接口整数安全范围校验。欠款只按接口is_in_debt/debt_amount展示冻结金额不由前端换算为欠款
完成标准:角色默认、创建店铺初始化、已有店铺调额和并发冲突提示完整。
完成标准:角色默认、创建店铺初始化、已有店铺调额、代理禁止调额、平台有/无独立权限、降低额度失败和并发冲突提示完整。
```
### 后端研发需求
@@ -1217,9 +1243,11 @@
规则修改角色不更新已有店铺店铺后续角色变化不影响钱包余额、冻结、信用、版本和资金流水在同一事务维护调额按version乐观锁更新。
权限代理账号不得调整自己或任何下级代理额度不能复用CanManageShop或普通店铺管理权限推导调额权只有超级管理员或具备独立信用额度管理权限的平台账号可以修改实际额度。角色默认模板只允许超级管理员或具备相应角色管理权限的平台账号配置。信用额度不设产品层固定上限但必须在int64范围内并拒绝负数与算术溢出。
架构钱包资金规则迁入Wallet Domain查询走资金Query。
完成标准:扣款、冻结、解冻、充值、退款回充和调额都维护同一不变量并写资金审计。
完成标准:扣款、冻结、解冻、充值、退款回充和调额都维护同一不变量并写资金审计代理调额请求始终拒绝额度变更写Audit Event但不伪造金额为0的钱包流水
```
## UR#37 审核流转
@@ -1229,34 +1257,40 @@
**标题**
```text
[FE][UR#37] 企微配置账号绑定与审批只读详情
[FE][UR#37] 平台企微绑定、代理固定代提交与审批只读详情
```
**描述**
```markdown
目标:使用企业微信完成审批,本系统负责配置、账号扫码绑定、状态查看和异常恢复。
目标:平台/超级管理员绑定本人企微发起审批,代理使用固定企微账号代提交;本系统负责配置、状态查看和异常恢复,页面展示的申请人始终是本系统真实业务提交人
预计工时前端68.5小时,包含多人审批业务映射展示。
预计工时前端68.5小时,包含平台账号扫码绑定、代理固定代提交状态和多人审批业务映射展示。
页面入口:/system/wecom、个人中心、/operations/wecom-approvals、退款和线下充值详情。
页面入口:/system/wecom、平台用户个人中心、/operations/wecom-approvals、退款和线下充值详情。
页面结构:
1. 企微配置页:连接状态、审批场景状态、模板版本列表、模板读取和业务字段到控件的可视化映射发布。
2. 个人中心:绑定状态、成员名称、扫码绑定、重新绑定、解绑;不提供userid输入框
3. 审批运行页业务类型、业务单号、sp_no、状态、模板版本、申请人、更新时间和异常标识;详情抽屉展示审批人、意见、附件、时间线和业务处理结果。
4. 业务详情复用统一approval区块只读展示不提供通过、驳回、退回按钮。
1. 企微配置页:连接状态、代理固定代提交账号就绪状态与成员显示名、审批场景状态、模板版本列表、模板读取和业务字段到控件的可视化映射发布不提供固定userid在线修改入口
2. 平台用户个人中心:绑定状态、成员名称、扫码绑定、重新绑定、解绑;代理账号不展示绑定入口
3. 审批运行页业务类型、业务单号、sp_no、状态、模板版本、真实业务提交人、企微发起身份来源、更新时间和异常标识;平台详情抽屉展示审批人、意见、附件、时间线和业务处理结果。
4. 业务详情复用统一approval区块只读展示不提供通过、驳回、退回按钮;代理视图只展示真实业务提交人、审批状态、状态时间和业务处理结果,不展示平台内部审批人、意见或审批附件
接口约定:
- GET /api/admin/wecom/status。
- PUT /api/admin/wecom/approval-scenes/{scene_code}/status。
- POST /api/admin/wecom/approval-templates/inspect、POST /api/admin/wecom/approval-templates/publish、GET /api/admin/wecom/approval-templates。
- POST /api/admin/wecom/account-binding/sessions返回 session_id、login_url、expires_atGET /api/admin/wecom/account-binding/sessions/{session_id} 查询结果GET/DELETE /api/admin/wecom/account-binding/me。
- POST /api/admin/wecom/account-binding/sessions返回session_id、login_url、expires_atGET /api/admin/wecom/account-binding/sessions/{session_id}查询结果GET/DELETE /api/admin/wecom/account-binding/me。
- GET /api/admin/wecom/approvals、GET /api/admin/wecom/approvals/{id}、POST /api/admin/wecom/approvals/{id}/sync。
- POST /api/admin/wecom/approvals/{id}/bind-sp-no提交sp_no和必填恢复原因由后端完成全量业务快照核对后绑定。
- POST /api/admin/wecom/approvals/{id}/confirm-not-created-and-resend提交必填恢复原因仅用于人工确认企微未创建后重新发送同一审批申请。
交互规则:扫码绑定页面轮询会话;未绑定创建审批时原地提供绑定入口;立即同步只拉取企微详情;通过后撤销且业务已执行时显示高风险提示。
交互规则:平台/超级管理员未绑定时原地提供绑定入口,绑定成功后继续当前表单;代理不要求绑定,固定代提交账号不可用时按后端错误保留表单。审批列表和详情的“申请人”展示真实业务提交人,并可只读标识企微发起身份来源;立即同步只拉取企微详情;通过后撤销且业务已执行时显示高风险提示。
完成标准:配置、绑定列表、详情、同步和异常状态均有加载、空、失败及权限状态
权限约定:/system/wecom、场景/模板维护、账号绑定列表和强制解绑仅超级管理员可见;平台账号只能管理本人绑定,具备独立“企微审批运营”权限后才显示审批运行页和立即同步;“企微审批运营”不包含模板配置、绑定管理或异常恢复。代理不展示企微配置、绑定和运行页,只在其业务数据范围内的退款详情查看审批区块
信息可见性:代理自己提交的退款资料和业务凭证仍按退款查看权限展示,但企微审批人、内部意见及审批人上传的附件仅平台/超级管理员按退款查看权限访问。详情接口、受保护附件下载和导出执行同一主体权限投影不能通过attachment_ref或历史导出链接绕过当前权限。
完成标准:配置、平台绑定、代理固定身份状态、列表、详情、同步和异常状态均有加载、空、失败及权限状态。
```
### 后端研发需求
@@ -1264,7 +1298,7 @@
**标题**
```text
[BE][UR#37] 企业微信审批模板账号绑定回调与补偿
[BE][UR#37] 企业微信审批模板、平台绑定、代理代提交、回调与补偿
```
**描述**
@@ -1272,16 +1306,20 @@
```markdown
目标:以稳定业务场景码接入企微审批,替代本地审批流。
预计工时后端911.5小时,包含多人审批场景映射。
预计工时后端911.5小时,包含平台账号扫码绑定、代理固定代提交和多人审批场景映射。
接口:实现企微状态、场景暂停恢复、模板读取发布、扫码绑定绑定管理、审批列表详情、立即同步及企微回调接口。
接口:实现企微状态、场景暂停恢复、模板读取发布、平台账号扫码绑定绑定管理、审批列表详情、立即同步及企微回调接口。
规则:
1. 场景码固定为 refund_approval、offline_recharge_approval模板ID和控件ID按不可变版本映射。
2. 模板编辑前暂停场景,发布新映射后恢复;历史实例保留模板和提交快照。
3. 系统账号通过5分钟一次性会话扫码绑定企微useridaccount_id和userid均唯一
3. applyevent发起身份按账号类型分流平台/超级管理员必须使用当前账号扫码绑定的本人userid代理使用部署配置中的固定企微userid。真实业务提交人以本地账号和显示快照独立保存并写入模板必填submitter字段审批实例保存实际userid与self_binding/agent_proxy来源列表、详情、通知、权限和审计均以真实业务提交人为准
4. 回调只验签解密并触发统一SyncApprovalStatusgetapprovaldetail为状态权威来源审批中实例每2分钟兜底轮询。
5. 首次终态同事务写业务Outboxbusiness_processed_at保证只执行一次。
6. applyevent已发出但结果未知时禁止自动重试超级管理员或具备独立异常恢复权限的平台账号可选择“校验并绑定已有sp_no”或“确认未创建后重新发送”。重新发送属于同一审批申请的技术尝试不创建业务审批轮次绑定前必须核对企业、模板、实例中的实际企微userid及来源、业务场景、真实业务提交人字段和业务快照两种动作均保留原尝试并写高风险审计。
7. 场景暂停或模板失效时,新退款/线下充值在任何业务单、审批实例和Outbox落库前直接拒绝前端保留表单恢复后由用户重新提交。暂停前已经创建的审批继续回调、轮询同步和处理终态不受暂停影响。
8. 权限必须拆分:场景暂停恢复、模板读取发布、绑定列表和强制解绑仅超级管理员;平台账号只能管理本人绑定;审批运行列表/详情/立即同步要求独立“企微审批运营”权限;提交未知恢复要求独立“企微审批异常恢复”权限;代理无公共企微管理接口权限,只能按现有业务数据范围查看退款详情中的审批摘要。
9. 审批详情按主体投影:代理只能读取真实业务提交人、审批状态/时间、业务处理结果及其业务范围内的退款资料和业务凭证;审批人、内部意见和审批人上传附件仅平台/超级管理员按业务查看权限读取。附件解析和导出必须复用当前主体权限,禁止旁路访问。
架构使用wecomapproval、wecomidentity Domain/Application外部API和加解密进入WeCom Adapter列表详情走Query。
@@ -1301,24 +1339,27 @@
**描述**
```markdown
目标:运营按一个代理和一种支付方式批量导入套餐订单,并查看逐行结果。
目标:运营按一种支付方式批量导入套餐订单同一CSV可包含不同代理的资产系统逐行解析结算代理并展示结果。
预计工时前端34小时。
页面入口:批量订购套餐页或现有订单页批量入口。
权限:仅超级管理员或具备独立“批量订购套餐”权限的平台账号展示入口并调用创建、任务详情和明细接口;代理和企业账号不展示入口,后端仍必须独立拒绝。
页面结构:
1. 选择代理、整批支付方式(线下或代理钱包)上传Excel线下支付时上传整批凭证。
2. 模板下载使用前端静态文件。
1. 选择整批支付方式(线下或代理钱包)上传CSV不选择代理线下支付时上传整批凭证不接受Excel
2. CSV模板下载使用前端静态文件编码为UTF-8并允许BOM
3. 创建后展示任务号、状态、总数、成功数、失败数、金额汇总和失败明细表。
4. 失败明细包含行号、资产、套餐编码、错误原因支持按任务ID恢复页面。
接口约定:
- POST /api/admin/bulk-purchasesmultipart包含 shop_id、payment_method、file、voucher_file、request_id
- CSV先调用POST /api/admin/storage/upload-urlpurpose=bulk_purchase使用返回的upload_url直传后取得file_key线下凭证按附件用途直传取得voucher_keys
- POST /api/admin/bulk-purchases只接收JSONrequest_id、payment_method、file_key、voucher_keys不接收shop_id、multipart或文件字节。
- GET /api/admin/bulk-purchases/{task_id} 返回任务汇总。
- GET /api/admin/bulk-purchases/{task_id}/items?page=&size=&status= 返回逐行结果。
交互规则:一个批次不能混合支付方式;部分成功视为任务终态;钱包余额不足只影响对应行或后续行,不回滚已成功行。
交互规则:一个批次不能混合支付方式;同一CSV允许不同代理资产代理归属由后端逐行解析前端不提交或猜测部分成功视为任务终态;某代理钱包余额不足只影响使用该钱包的相应行,不回滚已成功行。
完成标准:上传、进度恢复、部分成功、失败筛选和凭证展示完整。
```
@@ -1340,9 +1381,9 @@
接口POST /api/admin/bulk-purchasesGET /api/admin/bulk-purchases/{task_id}GET /api/admin/bulk-purchases/{task_id}/items。
规则整批选择offline或agent_wallet模板按套餐编码匹配文件最大10MB、最多1000行request_id唯一返回原任务行幂等键为bulk_purchase:{task_id}:{row_no}。
权限仅超级管理员或具备独立“批量订购套餐”权限的平台账号可创建和读取任务代理、企业账号一律拒绝。规则整批选择offline或agent_wallet不接收shop_id同一CSV可以包含不同代理资产逐行以资产当前归属解析结算代理CSV和凭证先直传私有对象存储业务接口只接收稳定file_key/voucher_keys仅接受UTF-8 CSV允许BOM不接受Excel模板按套餐编码匹配文件最大10MB、最多1000行同一文件按“资产类型+标准化资产标识+套餐编码”判重,首行正常处理、后续重复行失败并指出首行号,同一资产的不同套餐不算重复request_id唯一返回原任务行幂等键为bulk_purchase:{task_id}:{row_no}。
钱包行事务:锁主钱包,按信用不变量校验,订单、扣款、流水和明细成功状态同事务;单行失败不回滚其他行;任务统计从明细重新聚合。线下凭证只做本批资料,不校验跨批唯一。
钱包行事务:锁该行资产所属代理的主钱包,按信用不变量校验,订单、扣款、流水、结算代理快照和明细成功状态同事务;无代理归属、无有效主钱包或余额不足只失败对应行,单行失败不回滚其他行;任务统计从明细重新聚合。线下凭证只做本批资料,不校验跨批唯一。
完成标准Worker处理租约、重复消费、进程中断恢复和部分成功均不产生重复订单或重复扣款。
```
@@ -1376,11 +1417,10 @@
接口约定:
- POST /api/admin/refunds 创建退款。
- GET /api/admin/refunds/{id} 返回退款数据、approval对象和processing_status。
- POST /api/admin/refunds/{id}/resubmit仅已驳回、已撤销或已删除可重新申请。
- attachments使用现有对象存储上传结果提交结构为 {file_key,file_name,file_size}[]。
- approval结构至少包含 source、sp_no、status、status_name、template_version、applicant、approvers、comments、attachments、timeline、business_process_result。
交互规则:非代理钱包由财务在系统外人工退款后再在企微通过;重新申请可改金额、凭证和原因,不可改订单、资产和提交人快照
交互规则:非代理钱包由财务在系统外人工退款后再在企微通过。企微拒绝后当前退款单终结详情只读且不提供编辑或重提业务人员处理拒绝原因后仍需退款时重新进入创建退款流程并填写金额、凭证和原因成功后展示新的退款ID、退款单号和审批信息
完成标准:审批中、通过处理中、处理成功、驳回、撤销和通过后撤销异常状态均展示明确。
```
@@ -1400,14 +1440,14 @@
预计工时后端45小时。
接口POST /api/admin/refundsGET /api/admin/refunds/{id}POST /api/admin/refunds/{id}/resubmit下线原approve/reject/return路由。
接口POST /api/admin/refundsGET /api/admin/refunds/{id}下线原approve/reject/return/resubmit路由。
规则:
1. 非代理钱包支付由财务人工退款企微通过代表人工退款已确认不调用渠道退款API。
2. 代理钱包订单通过后按原扣款流水幂等回溯原代理主钱包并写退款流水。
3. 个人客户或资产钱包不自动回款。
4. 驳回更新为已拒绝;撤销/删除更新为已撤销通过后撤销且资金已执行不自动冲正记录critical审计。
5. 重新申请创建round_no+1企微实例并保留历史。
5. 一张退款单只创建一条企微审批申请业务表保存唯一approval_instance_id并由(biz_type,biz_id)唯一约束防重。正常结果只有同意或拒绝任一结果产生后审批与退款单同时完结拒绝后原退款单不可修改、不可重提。若仍需退款必须重新调用创建接口生成新的退款ID、退款单号、业务快照、提交人快照和企微审批旧单只保留历史事实,已拒绝退款不阻止新建,但仍需阻止存在其他活跃退款时重复创建。撤销、删除或通过后撤销只作外部异常处置,不作为自动放行新退款的依据
完成标准:审批状态与业务处理状态分离,重复终态不重复回款,失败任务可可靠重试。
```
@@ -1778,12 +1818,12 @@
完成标准后台、C端、临期列表、通知和导出使用同一套餐生命周期结果。
```
### INT-04 Excel批量任务与导出联调
### INT-04 CSV批量任务与导出联调
**标题**
```text
[INT-04] Excel批量任务与导出联调
[INT-04] CSV批量任务与导出联调
```
**描述**
@@ -1837,9 +1877,9 @@
参与工时参考前端1.52小时、后端1.52小时从关联FE/BE研发需求工时中拆出不新增总工时。
进入条件:模板映射、扫码绑定、审批提交、回调、2分钟轮询、退款和线下充值终态处理完成。
进入条件:模板映射、平台账号绑定、代理固定代提交账号验证、审批提交、回调、2分钟轮询、退款和线下充值终态处理完成。
联调范围:账号扫码绑定未绑定拦截、模板发布、发起审批、意见附件、通过/驳回/撤销/删除、回调重复、立即同步、退款人工处理、代理钱包回溯、线下充值入账和列表审批摘要。
联调范围:平台账号扫码绑定未绑定拦截、代理固定代提交账号有效/失效拦截、两类企微发起身份、真实业务提交人展示、模板发布、发起审批、意见附件、通过/驳回/撤销/删除、回调重复、立即同步、退款人工处理、代理钱包回溯、线下充值入账和列表审批摘要。
完成标准:本系统无审批按钮,企微状态与业务处理状态分离,终态重复同步不重复执行资金动作。
```
@@ -1861,7 +1901,7 @@
进入条件统一限速接口、卡和设备详情入口、Gateway联调配置完成。
联调范围:单卡设置、设备解析当前卡、speed_kbps单位、0取消限速、设备无当前卡、Gateway失败及审计记录。
联调范围:单卡固定speed_level、设备解析当前卡、恢复不限速与限到0kbps区分、设备无当前卡、电信/广电档位映射、联通/移动不支持、Gateway失败/结果未知及审计记录。
完成标准Gateway收到的cardNo始终为卡ICCID设备号不会被发送上游结果在页面和审计中可追踪。
```

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

View File

@@ -14,10 +14,10 @@
6. 回调是主通道,每 2 分钟查询审批详情作为待审批单兜底。
7. 回调与轮询必须进入同一个状态同步用例,业务终态处理只能执行一次。
8. 企微模板 ID 和控件 ID 都可能因管理员编辑模板而变化,必须使用稳定业务场景码、不可变模板版本和控件映射快照。
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application旧业务单级通过、驳回、退回和确认入账接口下线不保留两套审批入口。
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application旧业务单级通过、驳回、退回、重新提交和确认入账接口下线,不保留两套审批入口。
10. 系统无法从旧模板 ID 自动发现编辑后生成的新模板 ID模板变更必须先暂停业务场景再发布新映射并原子切换避免继续向旧模板提交。
11. 退款仍为财务人工退款,本系统不调用微信、支付宝等支付渠道退款接口;只有代理钱包支付订单自动回溯原扣款代理主钱包,不向个人客户或资产钱包自动回款。
12. 系统账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`管理端只查看状态和强制解绑
12. 平台账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`代理账号不绑定企微,统一使用部署配置中的固定企微账号代提交。两类路径都独立保存真实业务提交人
## 二、系统边界
@@ -45,7 +45,7 @@
```mermaid
sequenceDiagram
actor User as 平台员工
actor User as 业务提交人
participant API as Refund/Recharge Application
participant DB as PostgreSQL
participant Outbox as Outbox
@@ -60,7 +60,7 @@ sequenceDiagram
API->>Outbox: 同事务写 SubmissionRequested
API-->>User: 返回业务单和提交中状态
Outbox->>Worker: 投递提交任务
Worker->>WeCom: 上传附件、applyevent
Worker->>WeCom: 按账号类型选择企微身份并applyevent
WeCom-->>Worker: sp_no
Worker->>DB: 保存 sp_no状态改为 pending
@@ -87,6 +87,7 @@ wecom:
callback_encoding_aes_key: ""
callback_path: "/api/callback/wecom/approval"
account_binding_redirect_url: "https://后台域名/api/callback/wecom/account-binding"
agent_approval_creator_userid: ""
approval_poll_interval: 2m
template_verify_interval: 10m
request_timeout: 10s
@@ -101,14 +102,15 @@ JUNHONG_WECOM_AGENT_SECRET
JUNHONG_WECOM_CALLBACK_TOKEN
JUNHONG_WECOM_CALLBACK_ENCODING_AES_KEY
JUNHONG_WECOM_ACCOUNT_BINDING_REDIRECT_URL
JUNHONG_WECOM_AGENT_APPROVAL_CREATOR_USERID
```
后台只能查询“是否已配置、最近连通时间、最近错误”,不能返回 Secret、TokenEncodingAESKey。
后台只能查询“是否已配置、最近连通时间、最近错误”以及代理固定代提交成员是否就绪和成员显示名,不能返回 Secret、TokenEncodingAESKey 或原始固定 `userid`
企微自建应用还必须配置:
- Web 登录回调可信域名与 `account_binding_redirect_url` 域名一致。
- 应用可见范围覆盖需要发起退款或线下充值审批的平台员工,否则扫码时企微会提示无权限
- 应用可见范围覆盖需要本人发起审批的平台/超级管理员以及代理固定代提交成员;代理业务账号本身不要求成为企微成员
- `CorpID``AgentID`、用于换取身份的 Access Token 必须属于同一个自建应用配置。
用户提供的 demo 中已经出现完整密钥正式接入前必须在企业微信后台轮换并禁止将新值写入代码、Markdown、日志或审计快照。
@@ -277,15 +279,15 @@ shop_name, recharge_no, amount, remark, attachment, submitter
提交审批前,如果 `last_verified_at` 超过验证间隔Worker 先同步验证一次。验证失败不调用 `applyevent`
## 六、内部账号与企微成员绑定
## 六、平台账号绑定、代理固定代提交与真实业务提交人
创建人必须有明确的企微 `userid`,不使用固定手机号冒充所有申请人,也不要求运营人员进入企微后台查找成员 ID
企微发起身份按账号类型解析:平台账号和超级管理员必须有本人企微 `userid`;代理账号发起退款时使用部署配置中的固定企微成员 `agent_approval_creator_userid`。两类路径都把当前登录账号作为真实业务提交人写入业务快照和模板 `submitter` 字段
### 6.1 绑定流程
```mermaid
sequenceDiagram
actor User as 已登录平台员
actor User as 已登录平台/超级管理
participant FE as 管理后台
participant API as WeComIdentity Application
participant Redis as Redis绑定会话
@@ -351,7 +353,7 @@ value:
created_at
```
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话。
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话;代理账号不提供绑定会话
- `state` 使用密码学安全随机数,回调时通过 Lua 原子读取并删除;后续成功或失败结果写入独立的 `session_id` 会话。
- 回调不接受前端传入的 `account_id`,绑定目标只能来自服务端会话。
- 同一账号再次创建会话时,旧会话立即失效。
@@ -377,7 +379,7 @@ CREATE TABLE tb_account_wecom_mapping (
);
```
- 平台员创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态。
- 平台/超级管理员创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态;代理创建退款不检查个人绑定,改用固定代提交身份
- `account_id``wecom_userid` 都是一对一唯一;企微成员已绑定其他账号时拒绝覆盖,必须先由超级管理员解除旧绑定。
- 重新绑定同一账号必须再次扫码,成功后在事务内替换旧身份并记录前后值审计。
- 自助解绑不影响历史审批;历史实例继续使用提交时保存的 `creator_wecom_userid` 快照,新审批在重新绑定前禁止创建。
@@ -387,6 +389,20 @@ CREATE TABLE tb_account_wecom_mapping (
普通运营界面不提供手工录入 `userid`。超级管理员仅能查看、强制解绑并要求员工重新扫码,不提供直接改写成员 ID 的入口。
代理固定代提交身份只通过部署配置维护。其 `userid` 必须属于当前 CorpID、处于启用状态且在自建应用可见范围内后台只返回是否就绪及成员显示名不返回或修改原始 `userid`。固定身份未配置或失效时,代理新申请必须在业务单、审批实例和 Outbox 落库前拒绝。固定身份变更只影响新审批,历史实例保留原发起身份快照。
### 6.4 权限边界
- 连接状态、场景状态、模板版本、模板读取/发布、场景暂停/恢复仅超级管理员可访问。
- 平台账号只能查询、重新绑定和解绑自己的企微身份;绑定列表和强制解绑仅超级管理员可访问。
- 审批运行列表、详情和“立即同步”仅超级管理员或具备独立“企微审批运营”权限的平台账号访问。
- “企微审批运营”不包含场景/模板维护、账号绑定管理和提交结果未知恢复。
- 提交结果未知恢复仅超级管理员或具备独立“企微审批异常恢复”权限的平台账号访问。
- 代理不访问企微配置、绑定和审批运行接口,只能按现有店铺层级数据范围查看退款详情中的只读审批摘要。
- 所有接口均执行后端鉴权,前端隐藏按钮或路由不构成权限控制。
审批内容继续按主体最小化投影:代理只读取真实业务提交人、审批状态、状态时间、业务处理结果,以及其业务范围内的退款资料和业务凭证;企微审批人、内部意见和审批人上传附件只允许具备对应业务查看权限的平台账号或超级管理员读取。审批详情、附件解析和导出必须共享同一权限判定,历史导出中的稳定附件引用也要在每次访问时重新校验当前权限。
## 七、审批实例
```sql
@@ -395,13 +411,13 @@ CREATE TABLE tb_wecom_approval_instance (
biz_type VARCHAR(64) NOT NULL,
biz_id BIGINT NOT NULL,
biz_no VARCHAR(64) NOT NULL DEFAULT '',
round_no INT NOT NULL DEFAULT 1,
scene_code VARCHAR(64) NOT NULL,
template_version_id BIGINT NOT NULL,
template_id_snapshot VARCHAR(128) NOT NULL,
control_mapping_snapshot JSONB NOT NULL,
creator_account_id BIGINT NOT NULL,
creator_wecom_userid VARCHAR(128) NOT NULL,
creator_identity_source VARCHAR(32) NOT NULL,
sp_no VARCHAR(128),
status INT NOT NULL DEFAULT 0,
submitted_snapshot JSONB NOT NULL,
@@ -418,7 +434,7 @@ CREATE TABLE tb_wecom_approval_instance (
version BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (biz_type, biz_id, round_no)
UNIQUE (biz_type, biz_id)
);
CREATE UNIQUE INDEX uq_wecom_approval_sp_no
@@ -440,7 +456,7 @@ CREATE UNIQUE INDEX uq_wecom_approval_sp_no
| 7 | 提交失败 | 明确未创建审批 |
| 8 | 提交结果未知 | 请求超时,无法确认是否创建 |
`submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key不保存临时 `media_id`、签名 URL 或敏感密钥。
每条实例表示一张业务单唯一的审批申请,不使用 `round_no`。业务表通过 `approval_instance_id` 明确引用该申请,`(biz_type, biz_id)` 唯一约束保证退款或线下充值业务单不能产生第二条审批申请。`creator_account_id` 是真实业务提交人;`creator_wecom_userid` 是本次实际调用企微的成员快照,`creator_identity_source` 固定为 `self_binding``agent_proxy``submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key不保存临时 `media_id`、签名 URL 或敏感密钥。
## 八、DDD 设计
@@ -459,7 +475,7 @@ internal/
│ ├── sync_approval_status.go
│ ├── handle_callback.go
│ ├── poll_pending.go
│ └── resubmit_approval.go
│ └── recover_unknown_submission.go
├── domain/wecomidentity/
│ ├── binding.go 账号与企微成员一对一绑定规则
│ └── repository.go
@@ -512,7 +528,7 @@ Worker 处理:
5. 逐个调用 `media/upload` 获取临时 `media_id`
6. 按控件映射构建 `apply_data`
7. 使用模板审批人配置,固定 `use_template_approver=1`
8. 调用 `applyevent`
8. 按实例身份来源使用平台账号绑定的本人 `userid` 或代理固定代提交 `userid` 调用 `applyevent`;不得在 Worker 执行时重新按账号当前状态选择身份
9. 保存 `sp_no`,状态变为审批中并释放租约。
附件的本地对象存储 Key 是权威记录;企微 `media_id` 只是提交期间使用的临时值。
@@ -524,7 +540,7 @@ Worker 处理:
- 收到明确 `errcode != 0`:状态改为提交失败,可修正配置后重新提交。
- 建连失败且确认请求未发送:允许自动重试。
- 请求已发送但响应超时/连接中断:状态改为提交结果未知,不自动再次调用 `applyevent`
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新提交”或“绑定已有 sp_no”。
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新发送”或“绑定已有 sp_no”。重新发送只是同一审批申请的新技术尝试,不创建新的业务审批申请;旧尝试继续保留在 Integration Log。
## 十、回调和轮询
@@ -594,18 +610,12 @@ AND (last_polled_at IS NULL OR last_polled_at <= NOW() - INTERVAL '2 minutes')
|---|---|
| 通过 | 记录人工退款完成;代理钱包支付订单回溯原扣款代理主钱包;更新订单状态,随后异步佣金回扣和资产处理 |
| 驳回 | 退款状态改为已拒绝,保存审批详情摘要 |
| 撤销/删除 | 退款状态改为已撤销;允许申请人修改后创建下一轮审批 |
| 撤销/删除 | 非正常业务结果,记录异常、停止自动业务处理并进入人工处置;不开放正常重新提交入口 |
| 通过后撤销 | 已退款则不冲正,记录严重异常并通知财务;尚未执行则阻止退款 |
`POST /api/admin/refunds/{id}/approve``reject``return` 下线。
`POST /api/admin/refunds/{id}/approve``reject``return``resubmit` 全部下线。
重新申请使用:
```http
POST /api/admin/refunds/{id}/resubmit
```
它只允许已驳回、已撤销或已删除且业务尚未退款的申请,修改业务资料后创建 `round_no + 1` 的企微审批。
一张退款单只产生一条审批申请,正常业务结果只有同意或拒绝,任一结果产生后审批和退款单即同时完结。企微拒绝时保存拒绝原因和审批详情,原退款单进入不可编辑、不可重提的已拒绝终态。业务人员纠正问题后若仍需退款,必须重新调用 `POST /api/admin/refunds`,系统生成新的退款 ID、退款单号、业务快照、提交人快照和企微审批申请新单不继承旧审批节点、意见、附件、状态或企微发起身份。旧单只作为历史与审计事实保留。创建校验不把已拒绝退款视为活跃退款但存在其他审批中或处理未完成的退款时仍拒绝新建。撤销、删除和通过后撤销属于外部异常防御不作为自动放行新退款的依据。
审批通过后的业务 Worker 处理失败时,审批实例保持“已通过”,`business_process_result=failed`,由任务重试;前端明确区分“审批已通过”和“退款终态处理失败”。只有退款终态事务成功后才写 `business_processed_at`
@@ -633,10 +643,10 @@ POST /api/admin/refunds/{id}/resubmit
### 12.1 基础配置状态
```http
GET /api/admin/wecom/config/status
GET /api/admin/wecom/status
```
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误,不返回任何密钥
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误、代理固定代提交成员是否就绪及成员显示名,不返回任何密钥或原始固定 `userid`
### 12.2 审批场景状态
@@ -735,9 +745,10 @@ GET /api/admin/wecom/approvals
GET /api/admin/wecom/approvals/{id}
POST /api/admin/wecom/approvals/{id}/sync
POST /api/admin/wecom/approvals/{id}/bind-sp-no
POST /api/admin/wecom/approvals/{id}/confirm-not-created-and-resend
```
`bind-sp-no` 仅用于提交结果未知的人工恢复,必须写高等级审计日志。
`bind-sp-no` 请求包含 `sp_no` 和必填恢复原因;后端调用 `getapprovaldetail` 完成企业、模板、发起身份、业务场景、真实业务提交人和业务快照核对后才能绑定。`confirm-not-created-and-resend` 请求包含必填恢复原因,只在操作人已在企微确认未创建审批时重新发送同一审批申请。两者仅用于提交结果未知的人工恢复,必须二次确认并写高等级审计日志。
### 12.8 业务详情响应
@@ -752,7 +763,6 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
"status": 2,
"status_name": "已通过",
"template_name": "退款审批",
"round_no": 1,
"creator_name": "张三",
"approvers": [],
"submitted_at": "2026-07-15T10:00:00+08:00",
@@ -773,16 +783,18 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
### 13.2 业务详情
退款和充值详情增加只读“企业微信审批”区块:
退款和充值详情增加只读“企业微信审批”区块。平台/超级管理员在具备业务查看权限时展示
- 审批单号。
- 当前状态和状态更新时间。
- 模板名称和模板版本。
- 申请人。
- 真实业务提交人;代理代提交时可只读标识“企微由固定账号代提交”,但不能把固定账号展示为业务申请人。
- 审批人、审批结果、意见和时间线。
- 业务处理结果。
- “立即同步”图标按钮,仅触发 `getapprovaldetail`,不提供审批按钮。
代理退款详情只展示真实业务提交人、审批状态、状态时间和业务处理结果,以及其原有业务权限允许查看的退款资料和业务凭证;接口不向代理返回审批人、内部审批意见和审批人上传附件。
通过后撤销且业务已执行时使用红色异常状态条,明确显示“资金动作未自动冲正,需人工处理”。
### 13.3 企微配置页
@@ -793,7 +805,7 @@ Tab
1. **连接状态**:只显示配置状态和最近连通结果。
2. **审批模板**:按业务场景展示当前版本、模板 ID、验证状态和历史版本。
3. **账号绑定**:查看平台员绑定状态、企微名称和最近验证时间;不允许编辑 userid。
3. **账号绑定**:查看平台/超级管理员绑定状态、企微名称和最近验证时间;不允许编辑 userid;代理固定代提交账号只在连接状态中展示就绪状态和成员显示名
4. **异常审批**:提交未知、模板失效、终态业务处理失败、通过后撤销。
模板发布交互:
@@ -814,10 +826,10 @@ Tab
账号绑定交互:
- 当前用户个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间。
- 平台/超级管理员个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间;代理个人中心不展示企微绑定能力
- 点击“绑定企业微信”后打开官方企微二维码窗口,原页面显示 5 分钟倒计时。
- 绑定成功后窗口自动关闭,原页面刷新绑定状态;失败时显示企微返回的可操作原因。
- 创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页。
- 平台/超级管理员创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页;代理创建退款不检查个人绑定
- 管理员的账号绑定列表只提供筛选、查看和强制解绑;不提供 userid 输入框。
### 13.4 审批运行页
@@ -829,10 +841,10 @@ Tab
页面用于运行监控,不提供审批动作:
- 按业务类型、企微状态、提交状态和业务处理结果筛选。
- 查询 `sp_no`、业务单号、申请人和模板版本。
- 查询 `sp_no`、业务单号、真实业务提交人、企微发起身份来源和模板版本。
- 查看企微审批详情快照和本地业务处理结果。
- 对审批中记录执行“立即同步”。
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新提交”
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新发送”;后者仍属于同一审批申请的技术恢复
- 对业务处理失败记录查看错误和任务重试状态。
## 十四、Token、日志和限流
@@ -852,11 +864,11 @@ Tab
1. 在企微自建应用配置 Web 登录可信域名和平台员工可见范围。
2. 创建企微模板版本、账号绑定和审批实例表。
3. 发布企微身份 Adapter、账号绑定回调、审批回调、轮询 Worker 和业务终态用例。
4. 要求会发起审批的平台员工完成扫码绑定
4. 要求会发起审批的平台/超级管理员完成扫码绑定,并验证代理固定代提交成员可用
5. 发布并验证退款、线下充值两个模板映射版本。
6. 下线本地审批动作路由和前端按钮。
7. 存量已通过/已拒绝记录保留原业务审批快照,展示 `approval.source=legacy`
8. 存量待审批退款和线下充值仅在创建人已绑定企微时提交;未绑定记录进入迁移待处理列表。
8. 存量待审批记录按创建人类型处理:代理创建的退款使用固定代提交成员;平台/超级管理员创建的退款和线下充值仅在创建人已绑定企微时提交;未绑定或创建人缺失的记录进入迁移待处理列表。
9. 提交失败的存量记录进入异常审批页面,不允许继续走旧接口处理。
## 十六、人工验证
@@ -872,7 +884,8 @@ Tab
9. 回调和轮询同时到达时,只有一个处理器执行业务动作。
10. 附件始终保留本地对象存储 Key企微 media_id 失效不影响历史资料下载。
11. Secret、Token、EncodingAESKey 和附件内容不出现在 API、日志和审计详情中。
12. 已登录平台员扫码后自动获得企微 userid不需要人工查询或录入成员 ID。
12. 已登录平台/超级管理员扫码后自动获得本人企微 userid不需要人工查询或录入成员 ID;代理不展示绑定入口并使用固定账号代提交
13. 绑定 `state` 过期、重复回调、非企业成员扫码时均不会产生绑定。
14. 同一企微 userid 尝试绑定第二个系统账号时被拒绝,原绑定不被覆盖。
15. 自助解绑和管理员强制解绑不影响历史审批快照,但会阻止该账号发起新审批。
16. 同一份退款分别由平台账号和代理账号创建时,前者使用本人绑定身份,后者使用固定代提交身份;两份审批单都正确展示真实业务提交人,通知和审计不会把固定账号当成代理本人。