fix: some
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Change: 代理扫码分销注册与提现资料资格前端对接
|
||||
|
||||
## Why
|
||||
|
||||
新增强代理扫码分销注册与提现资料资格能力:代理可通过 H5 注册页扫码注册,审批通过后登录后台;代理本人可维护提现资料资格并发起/重提提现,超管可作废资格。后端契约已按 `docs/产品迭代8月份/frontend-api-simple.md` 落地,后台管理端当前缺少对应接口封装、独立注册页与资格管理入口。
|
||||
|
||||
本次前端按后端实际契约对齐:注册验码 `POST /api/c/v1/auth/send-code`(`scene` 为 `bind_phone`,返回 `cooldown_seconds`);扫码注册 `POST /api/c/v1/agent-distribution-registrations`(成功仅返回「待审批」);店铺列表/详情/更新响应新增 `distribution_code`;提现资料资格提交/查询/作废与提现申请重提/详情按新文档补齐。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `agent-distribution-registration` 能力:独立注册页挂在后台项目内、静态路由免登录访问(H5 与 PC 同一套表单),支持分销码自动填、短信验证码 60s 倒计时、密码掩码与强度提示、省市区级联;提交成功进入「待审批」结束页,失败统一提示「分销码不可用」且不复用同一验证码。
|
||||
- 新增公开 API 封装:`sendCode()` 与 `registerAgent()`,请求不带登录态(`requestOptions.withToken: false`)。
|
||||
- 店铺模块补充 `distribution_code` 字段类型,店铺详情页展示分销码与注册二维码。
|
||||
- 佣金模块新增:提现资料资格提交/替换、资格查询(证件号脱敏、含审批与作废信息)、资格作废(超管,`reason` 必填)、重提被驳回的提现、提现申请详情(含尝试记录与异常标记)。
|
||||
- 扩展企微审批场景业务类型:`agent_distribution_approval`、`withdrawal_qualification_approval`、`commission_withdrawal_approval`。
|
||||
- 提现交互约束:资格替换合同/法人身份证后旧资格失效需重新审批;提现仅在有效资格且余额充足时可提交,否则不冻结、不建单。
|
||||
- 不实现后端接口、数据库、企微回调;不实现 C 端(客户)登录体系。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `agent-distribution-registration`、`shop-management`、`commission-management`、`wecom-scenes`
|
||||
- Affected code: `src/api/modules/agentDistribution.ts`、`src/api/modules/commission.ts`、`src/types/api/shop.ts`、`src/types/api/commission.ts`、`src/types/api/wecom.ts`、`src/router/routes/staticRoutes.ts`、`src/router/guards/permission.ts`、`src/views/agent-registration/index.vue`、`src/views/shop-management/detail/index.vue`、`src/views/commission-management/my-commission/index.vue`、`src/views/settings/wecom/scenes/index.vue`
|
||||
- Dependencies: `docs/产品迭代8月份/frontend-api-simple.md`
|
||||
@@ -0,0 +1,61 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 独立代理注册页免登录访问
|
||||
代理扫码分销注册页 MUST 独立于登录态提供,未登录可访问,且挂在后台项目内(H5 与 PC 共用同一套表单);注册流程 MUST NOT 复用当前登录态。
|
||||
|
||||
#### Scenario: 未登录访问注册页
|
||||
- **GIVEN** 用户未登录并通过扫码或直接访问注册入口
|
||||
- **WHEN** 其打开注册页面
|
||||
- **THEN** 页面 MUST 正常渲染注册表单
|
||||
- **AND** MUST NOT 跳转到登录页
|
||||
|
||||
#### Scenario: 已登录用户访问注册页
|
||||
- **GIVEN** 用户已登录后台
|
||||
- **WHEN** 其打开注册页面
|
||||
- **THEN** 注册流程 MUST 不携带登录态
|
||||
|
||||
### Requirement: 发送短信验证码
|
||||
注册页 MUST 调用 `POST /api/c/v1/auth/send-code` 发送验证码,请求体 `{ phone, scene }`(`scene` 为 `bind_phone`),响应 `data.cooldown_seconds` MUST 用于 60 秒倒计时;请求 MUST 不携带 Token。
|
||||
|
||||
#### Scenario: 发送验证码并倒计时
|
||||
- **GIVEN** 用户输入手机号
|
||||
- **WHEN** 其点击获取验证码
|
||||
- **THEN** 系统 MUST 调用发送验证码接口
|
||||
- **AND** 按钮 MUST 进入 60 秒倒计时且不可重复点击
|
||||
|
||||
#### Scenario: 注册失败后不复用验证码
|
||||
- **GIVEN** 一次注册提交失败
|
||||
- **WHEN** 用户再次尝试注册
|
||||
- **THEN** 页面 MUST 提示重新获取验证码
|
||||
- **AND** MUST NOT 复用已消费的验证码
|
||||
|
||||
### Requirement: 代理扫码注册提交
|
||||
注册页 MUST 调用 `POST /api/c/v1/agent-distribution-registrations` 提交注册,必填 `distribution_code`(扫码自动带)、`phone`、`code`、`password`、`shop_name`、`shop_code`、`username`,选填 `contact_name`、`province`、`city`、`district`、`address`;成功响应为「待审批」,前端 MUST 展示待审批结束页且不自动登录。
|
||||
|
||||
#### Scenario: 注册成功进入待审批
|
||||
- **GIVEN** 用户填写完整表单并通过校验
|
||||
- **WHEN** 其提交注册
|
||||
- **THEN** 系统 MUST 调用注册接口
|
||||
- **AND** 成功时 MUST 展示「待审批,审核结果将通知你」结束页
|
||||
- **AND** MUST NOT 发放账号凭证或自动登录
|
||||
|
||||
#### Scenario: 注册失败统一提示
|
||||
- **GIVEN** 注册提交失败(无效分销码、上级停用、验证码无效或已消费等)
|
||||
- **WHEN** 注册接口返回失败
|
||||
- **THEN** 页面 MUST 统一提示「分销码不可用」
|
||||
- **AND** MUST NOT 根据错误文案区分原因
|
||||
|
||||
### Requirement: 表单适配与敏感项处理
|
||||
注册表单 MUST 同时适配手机 H5 与 PC:H5 单列、大触控区、软键盘友好、地址使用级联选择器;PC 使用居中卡片或两列布局且字段一致。密码输入 MUST 掩码并展示强度提示;页面 MUST NOT 展示除手机号外的完整个人敏感信息。
|
||||
|
||||
#### Scenario: H5 与 PC 同一表单
|
||||
- **GIVEN** 用户分别在手机与 PC 打开注册页
|
||||
- **WHEN** 其填写注册信息
|
||||
- **THEN** 两端的字段集合 MUST 一致
|
||||
- **AND** 布局 MUST 按端侧自适应
|
||||
|
||||
#### Scenario: 密码强度与掩码
|
||||
- **GIVEN** 用户在密码输入框输入
|
||||
- **WHEN** 其提交表单
|
||||
- **THEN** 输入 MUST 为掩码展示
|
||||
- **AND** MUST 展示密码强度提示
|
||||
@@ -0,0 +1,61 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 提交/替换提现资料资格
|
||||
代理本人 MUST 能提交或替换提现资料资格,调用 `POST /api/admin/shops/{shop_id}/withdrawal-qualifications`,请求体包含 `subject_type`、`subject_code`、`legal_person_id_card` 与合同、身份证正反面、营业执照、门头照、发票等 `*_file_key`,以及 `invoice_title`、`invoice_subject_code`;附件 MUST 先通过对象存储上传接口取得 `file_key` 再提交。
|
||||
|
||||
#### Scenario: 提交资格成功
|
||||
- **GIVEN** 代理本人已登录并选好主体类型
|
||||
- **WHEN** 其上传资料并提交
|
||||
- **THEN** 前端 MUST 先取得全部附件 `file_key`
|
||||
- **AND** 再调用提交接口并在成功后刷新资格列表
|
||||
|
||||
#### Scenario: 替换资格使旧资格失效
|
||||
- **GIVEN** 已存在通过审批的资格
|
||||
- **WHEN** 代理提交合同或法人身份证变更
|
||||
- **THEN** 旧资格 MUST 立即失效
|
||||
- **AND** 新资格 MUST 重新审批通过后方可提现
|
||||
|
||||
### Requirement: 查询提现资料资格
|
||||
代理本人 MUST 能分页查询提现资料资格,调用 `GET /api/admin/shops/{shop_id}/withdrawal-qualifications`(`page` / `page_size` / `status`);响应 MUST 展示脱敏的 `subject_code_masked` 与 `legal_person_id_card_masked`,并展示 `status`、`approval_status`、`invalid_reason`、`invalidated_at` 与各附件 `file_key` 预览。
|
||||
|
||||
#### Scenario: 查看资格版本与审批状态
|
||||
- **GIVEN** 代理本人打开提现资料资格列表
|
||||
- **WHEN** 接口返回资格记录
|
||||
- **THEN** 列表 MUST 展示脱敏证件号与审批状态
|
||||
- **AND** 证件号 MUST NOT 以明文展示
|
||||
|
||||
### Requirement: 作废提现资料资格
|
||||
超级管理员 MUST 能作废提现资料资格,调用 `POST /api/admin/withdrawal-qualifications/{id}/void`,`reason` 必填。
|
||||
|
||||
#### Scenario: 作废资格须填原因
|
||||
- **GIVEN** 超级管理员打开作废弹窗
|
||||
- **WHEN** 其未填写原因直接提交
|
||||
- **THEN** 前端 MUST 阻止提交并提示填写原因
|
||||
|
||||
#### Scenario: 超管作废资格
|
||||
- **GIVEN** 超级管理员选择一条资格
|
||||
- **WHEN** 其填写原因并确认作废
|
||||
- **THEN** 系统 MUST 调用作废接口并在成功后刷新列表
|
||||
|
||||
### Requirement: 重提被驳回的提现
|
||||
代理本人 MUST 能重提被驳回的提现,调用 `PUT /api/admin/shops/{shop_id}/withdrawal-requests/{id}`,请求体包含 `account_name`、`account_number`、`amount`、`withdrawal_method` 与 `invoice_keys`;成功响应返回新提现单(`withdrawal_no`、`actual_amount`、`fee`、`fee_rate`、`status`)。
|
||||
|
||||
#### Scenario: 重提被驳回的提现
|
||||
- **GIVEN** 代理本人的提现申请被驳回
|
||||
- **WHEN** 其修改信息并重新提交
|
||||
- **THEN** 系统 MUST 调用重提接口
|
||||
- **AND** 成功后 MUST 刷新提现记录并展示新单号
|
||||
|
||||
#### Scenario: 非驳回状态不可重提
|
||||
- **GIVEN** 提现申请不是被驳回状态
|
||||
- **WHEN** 代理尝试重提
|
||||
- **THEN** 重提入口 MUST 不可用或不展示
|
||||
|
||||
### Requirement: 提现申请详情
|
||||
代理本人 MUST 能查看提现申请详情,调用 `GET /api/admin/shops/{shop_id}/withdrawal-requests/{id}`;响应包含 `reject_reason`、`anomaly_flag`、`anomaly_name`、`anomaly_reason` 与 `attempts` 尝试记录,前端 MUST 展示这些字段。
|
||||
|
||||
#### Scenario: 查看提现详情
|
||||
- **GIVEN** 代理本人点击提现记录
|
||||
- **WHEN** 详情返回
|
||||
- **THEN** 页面 MUST 展示提现金额、实际到账、手续费、状态
|
||||
- **AND** 展示驳回原因、异常标记与尝试记录
|
||||
@@ -0,0 +1,15 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 店铺数据返回分销码
|
||||
店铺列表、详情与更新接口响应 MUST 包含 `distribution_code` 字段,前端类型 MUST 覆盖该字段并在列表/详情中展示。
|
||||
|
||||
#### Scenario: 店铺详情展示分销码
|
||||
- **GIVEN** 用户打开店铺详情页
|
||||
- **WHEN** 店铺存在 `distribution_code`
|
||||
- **THEN** 页面 MUST 展示分销码
|
||||
- **AND** MUST 以该码生成注册入口二维码
|
||||
|
||||
#### Scenario: 分销码为空
|
||||
- **GIVEN** 店铺尚未分配分销码
|
||||
- **WHEN** 页面展示店铺信息
|
||||
- **THEN** 分销码与二维码区域 MUST 展示空态(`-` 或「未分配」)
|
||||
@@ -0,0 +1,15 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 企微审批场景业务类型扩展
|
||||
企微审批场景的 `business_type` MUST 支持以下枚举:`refund_approval`(退款审批)、`offline_recharge_approval`(员工线下代充值审批)、`employee_collection_approval`(员工代收款核销审批)、`agent_distribution_approval`(代理扫码分销注册审批)、`withdrawal_qualification_approval`(提现资料资格审批)、`commission_withdrawal_approval`(佣金提现终审)。前端类型与场景配置页选项 MUST 与后端枚举一致;`GET /api/admin/wecom/scenes/{business_type}/fields` 允许查询上述任一类型。
|
||||
|
||||
#### Scenario: 场景配置页可选新业务类型
|
||||
- **GIVEN** 超级管理员打开企微审批场景配置页
|
||||
- **WHEN** 其新建场景并选择业务类型
|
||||
- **THEN** 下拉选项 MUST 包含全部六个业务类型
|
||||
- **AND** 新增三个业务类型 MUST 与后端枚举一致
|
||||
|
||||
#### Scenario: 查询新增场景业务字段
|
||||
- **GIVEN** 已选择 `agent_distribution_approval`、`withdrawal_qualification_approval` 或 `commission_withdrawal_approval`
|
||||
- **WHEN** 页面加载业务字段
|
||||
- **THEN** 前端 MUST 调用对应 `business_type` 的字段查询接口
|
||||
@@ -0,0 +1,29 @@
|
||||
## 1. 提案与 API 层
|
||||
- [x] 1.1 新增 `src/api/modules/agentDistribution.ts`:`sendCode()` 与 `registerAgent()`,公开请求不带 Token
|
||||
- [x] 1.2 扩展 `src/types/api/shop.ts`:`ShopResponse` 增加 `distribution_code`
|
||||
- [x] 1.3 扩展 `src/types/api/commission.ts`:提现资料资格提交/查询、作废、重提、详情的请求与响应类型
|
||||
- [x] 1.4 扩展 `src/api/modules/commission.ts`:`submitWithdrawalQualification` / `getWithdrawalQualifications` / `voidWithdrawalQualification` / `resubmitWithdrawalRequest` / `getWithdrawalRequestDetail`
|
||||
- [x] 1.5 扩展 `src/types/api/wecom.ts`:`WecomBusinessType` 增加三个业务类型
|
||||
- [x] 1.6 汇总导出新增类型
|
||||
|
||||
## 2. 企微审批场景
|
||||
- [x] 2.1 场景配置页新增三个业务类型选项(`agent_distribution_approval` / `withdrawal_qualification_approval` / `commission_withdrawal_approval`)
|
||||
|
||||
## 3. 独立注册页
|
||||
- [x] 3.1 `staticRoutes.ts` 新增 `/agent-registration` 静态路由
|
||||
- [x] 3.2 `LOGIN_WHITE_LIST` 增加注册页路径
|
||||
- [x] 3.3 新增 `src/views/agent-registration/index.vue`:表单、验证码倒计时、密码强度、省市区级联、提交与结束页、H5/PC 响应式
|
||||
|
||||
## 4. 店铺详情
|
||||
- [x] 4.1 店铺详情页展示 `distribution_code` 与注册二维码
|
||||
|
||||
## 5. 我的佣金(代理侧)
|
||||
- [x] 5.1 新增「提现资料资格」Tab:资格列表(脱敏展示)+ 提交/替换弹窗(附件走上传接口)
|
||||
- [x] 5.2 提现记录新增「详情」抽屉:展示明细、驳回原因、异常标记与尝试记录
|
||||
- [x] 5.3 被驳回记录新增「重新提交」弹窗(PUT 重提)
|
||||
- [x] 5.4 超管可见「作废资格」操作(reason 必填)
|
||||
|
||||
## 6. 验证
|
||||
- [x] 6.1 运行 `npm run build`(含 `vue-tsc --noEmit`)
|
||||
- [x] 6.2 运行 `npm run check:encoding`
|
||||
- [x] 6.3 运行 `openspec.cmd validate add-agent-distribution-registration-and-withdrawal --strict`
|
||||
@@ -0,0 +1,54 @@
|
||||
# Change: 业务用户组管理与店铺批量交接/导入(AUG26-003)
|
||||
|
||||
## Why
|
||||
|
||||
后台需要把平台用户账号按业务线归入"业务用户组",并基于组对店铺做筛选、批量交接负责人与 CSV 导入负责人。当前前端只有单个店铺设置平台业务员的能力,缺少业务用户组维护、成员归属、按组筛选、批量交接与导入入口;且现有 11 个新端点仅超管与平台账号可访问。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增业务用户组管理模块(API 层 + 维护页面 + 成员管理):
|
||||
- GET/POST /api/admin/business-user-groups、GET/PUT/DELETE /api/admin/business-user-groups/{id}
|
||||
- PUT /api/admin/business-user-groups/{id}/members、DELETE /api/admin/business-user-groups/members
|
||||
- 编码创建后不可改;删除需 {"confirm": true} 且仅无成员组可删;更新支持部分字段,business_line 三态(缺省=不改 / ""=清空 / 枚举=设置)。
|
||||
- 成员归属整批校验(全部启用平台用户 + 目标组启用),任一无效整批不生效;一账号至多一组。
|
||||
- 店铺列表/详情读取侧扩展:
|
||||
- GET /api/admin/shops 新增 business_user_group_id / business_line / ungrouped 筛选。
|
||||
- 列表/详情响应新增 5 个只读推导字段(组 ID/编码/名称/启用/业务线);组停用仍返回组且 enabled=false(不算未分组);前端不缓存、不回写,改组后重新拉列表。
|
||||
- 勾选批量交接:
|
||||
- PUT /api/admin/shops/business-owner/batch:shop_ids(1-500,去重)+ business_owner_account_id;字段缺失=400,null=清空,ID=换绑;任一店铺无效或目标非启用业务员 -> 整批不写入,统一按 1005 提示。
|
||||
- CSV 导入:
|
||||
- 复用 StorageService.getUploadUrl + 预签名 PUT 直传(FilePurpose 增加 shop_import)。
|
||||
- POST /api/admin/shops/business-owner-imports 创建异步任务,轮询详情、展示行级结果;前端自带模板(表头 店铺编码,操作类型,业务员登录账号,备注,换绑/清空)。
|
||||
- 入口与权限(代理/企业不渲染入口):
|
||||
- 路由 meta roles ['R_SUPER', 'R_ADMIN'];按钮权限码:
|
||||
- business_user_group:page / business_user_group:create / business_user_group:update / business_user_group:delete / business_user_group:members
|
||||
- shop:business_owner_batch
|
||||
- shop:business_owner_import
|
||||
|
||||
## Not In Scope
|
||||
|
||||
- 不实现后端接口、不改数据库。
|
||||
- 业务用户组不承载角色权限/数据范围语义;前端不引入组层级与组管理员概念。
|
||||
- 导入结果不支持导出,仅在页面展示行级明细。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: business-user-group-management(新增)、shop-management(扩展)
|
||||
- Affected code:
|
||||
- src/types/api/businessUserGroup.ts(新增)
|
||||
- src/api/modules/businessUserGroup.ts(新增)
|
||||
- src/types/api/shop.ts、src/api/modules/shop.ts
|
||||
- src/api/modules/storage.ts(FilePurpose 增加 shop_import)
|
||||
- src/views/shop-management/business-user-groups/index.vue(新增页面)
|
||||
- src/views/shop-management/list/index.vue
|
||||
- src/router/routes/asyncRoutes.ts、src/router/routesAlias.ts、src/locales/langs/{zh,en}.json
|
||||
- src/template/业务负责人导入模板.csv(前端自带模板)
|
||||
- API contracts: 11 个端点(7 个业务用户组 + 店铺筛选/字段 + 批量交接 + 导入三步)
|
||||
- Dependencies:
|
||||
- docs/产品迭代8月份/业务用户组.md(7 个组端点 OpenAPI)
|
||||
- AUG26-003 前端对接说明(店铺侧端点)
|
||||
|
||||
## 待确认
|
||||
|
||||
- 成员选择器复用 GET /api/admin/shops/business-owner-candidates(启用平台账号);若组成员可包含非业务员平台账号,需后端补充平台账号列表端点。
|
||||
- 导入任务与行级结果字段名(file_key、status、items[] 等)以生成文档为准,提案按对接说明先行对齐。
|
||||
@@ -0,0 +1,73 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 业务用户组维护
|
||||
|
||||
后台 MUST 提供业务用户组列表、详情、创建、更新与删除能力(GET/POST /api/admin/business-user-groups、GET/PUT/DELETE /api/admin/business-user-groups/{id})。列表 MUST 支持 page/page_size/enabled/keyword/business_line 筛选,且每项 MUST 返回 business_line_name。创建时 code(1-64)与 name 必填,编码未删除组内唯一且创建后不可修改;business_line 可空(standard/smart/other),sort/enabled/remark 可选。更新 MUST 仅允许名称、业务线、排序、启停与备注,且 business_line 支持三态:字段缺失不修改、传 "" 清空、传枚举设置;删除 MUST 携带 {"confirm": true},仅无成员组可删除。
|
||||
|
||||
#### Scenario: 查询业务用户组列表
|
||||
|
||||
- **GIVEN** 用户进入业务用户组维护页
|
||||
- **WHEN** 前端以 page/page_size/enabled/keyword/business_line 发起列表查询
|
||||
- **THEN** 前端 MUST 展示分页结果且每项显示 business_line_name
|
||||
- **AND** 下拉数据源复用该列表接口
|
||||
|
||||
#### Scenario: 创建时编码不可变
|
||||
|
||||
- **GIVEN** 用户填写名称与稳定编码创建用户组
|
||||
- **WHEN** 提交成功或编码重复被后端拒绝
|
||||
- **THEN** 编码在创建后 MUST NOT 出现在编辑表单中
|
||||
- **AND** 编码重复时 MUST 展示后端返回的"业务用户组编码已存在"
|
||||
|
||||
#### Scenario: 业务线三态更新
|
||||
|
||||
- **GIVEN** 用户编辑某组的业务线
|
||||
- **WHEN** 表单传 ""、传枚举或省略该字段
|
||||
- **THEN** 前端 MUST 分别按"清空 / 设置 / 不修改"构造请求体
|
||||
|
||||
#### Scenario: 删除需二次确认
|
||||
|
||||
- **GIVEN** 用户点击删除某组
|
||||
- **WHEN** 确认弹窗提交
|
||||
- **THEN** 前端 MUST 携带 {"confirm": true}
|
||||
- **AND** 有成员时 MUST 提示"用户组仍有成员,只能停用或先移走成员"且不做物理删除
|
||||
- **AND** 无成员时删除成功并刷新列表
|
||||
|
||||
### Requirement: 成员归属批量维护
|
||||
|
||||
后台 MUST 支持按平台用户账号把成员批量设置进组(PUT /api/admin/business-user-groups/{id}/members)与批量清空(DELETE /api/admin/business-user-groups/members,携带 body account_ids)。两种操作 MUST 复用同一校验:所有账号必须是启用平台用户且目标组启用,任一账号无效则整批不生效;清空后账号回到未分组;一账号至多一组。
|
||||
|
||||
#### Scenario: 批量设置成员
|
||||
|
||||
- **GIVEN** 用户在成员管理中选择多个启用平台账号
|
||||
- **WHEN** 保存成员归属
|
||||
- **THEN** 前端 MUST 以 account_ids 数组整体替换每个账号原归属
|
||||
- **AND** 刷新后成员关系与最新分组一致
|
||||
|
||||
#### Scenario: 批量清空成员
|
||||
|
||||
- **GIVEN** 用户选择若干账号执行清空
|
||||
- **WHEN** 提交清空
|
||||
- **THEN** 前端 MUST 调用 DELETE /api/admin/business-user-groups/members 并携带 account_ids body
|
||||
- **AND** 清空成功的账号回到未分组
|
||||
|
||||
#### Scenario: 停用组的成员展示
|
||||
|
||||
- **GIVEN** 某组被停用但仍有成员
|
||||
- **WHEN** 用户在成员管理或店铺列表看到该组
|
||||
- **THEN** 前端 MUST 展示"已停用"标记并提供改组/清空入口
|
||||
|
||||
### Requirement: 访问控制与入口
|
||||
|
||||
业务用户组维护入口 MUST 仅对超级管理员与平台账号可见,代理/企业账号 MUST NOT 渲染入口;所有按钮 MUST 受权限码控制。
|
||||
|
||||
#### Scenario: 菜单按角色隐藏
|
||||
|
||||
- **GIVEN** 代理或企业账号登录
|
||||
- **WHEN** 系统渲染菜单
|
||||
- **THEN** MUST NOT 展示业务用户组菜单项
|
||||
|
||||
#### Scenario: 按钮权限控制
|
||||
|
||||
- **GIVEN** 平台账号具备部分业务用户组权限
|
||||
- **WHEN** 渲染操作按钮
|
||||
- **THEN** 前端 MUST 按 business_user_group:create/update/delete/members 控制显隐
|
||||
@@ -0,0 +1,75 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 店铺列表/详情业务用户组字段与筛选
|
||||
|
||||
店铺列表查询 MUST 支持 business_user_group_id、business_line、ungrouped 三个新筛选;列表与详情响应 MUST 提供 5 个只读推导字段:business_user_group_id(可空)、business_user_group_code、business_user_group_name、business_user_group_enabled、business_user_group_business_line。组停用仍返回组且 enabled=false(不算未分组);店铺不存组,字段实时推导,前端 MUST NOT 缓存或回写,改动组成员后重新拉取列表。
|
||||
|
||||
#### Scenario: 按业务用户组筛选
|
||||
|
||||
- **GIVEN** 用户在店铺列表选择业务用户组、业务线或勾选"未分组"
|
||||
- **WHEN** 发起列表查询
|
||||
- **THEN** 前端 MUST 提交 business_user_group_id/business_line/ungrouped 参数
|
||||
- **AND** ungrouped=true 表示无负责人或负责人无分组
|
||||
|
||||
#### Scenario: 展示组字段
|
||||
|
||||
- **GIVEN** 店铺属于某启用或停用的业务用户组
|
||||
- **WHEN** 渲染列表或详情
|
||||
- **THEN** 前端 MUST 展示组名称/编码/业务线
|
||||
- **AND** 组停用时 MUST 标记"已停用"且仍展示组归属
|
||||
|
||||
### Requirement: 勾选批量交接平台业务员
|
||||
|
||||
店铺列表 MUST 支持勾选店铺批量交接平台业务员(PUT /api/admin/shops/business-owner/batch,body shop_ids 1-500 去重 + business_owner_account_id)。字段缺失 MUST 按参数错误处理(400);传 null 表示清空负责人;传 ID 表示换绑。任一店铺不存在/已删除/越权或目标非启用业务员时整批不写入,前端 MUST 统一提示"无权限操作该资源或资源不存在";成功响应含 batch_key/shop_count/cleared。
|
||||
|
||||
#### Scenario: 批量换绑
|
||||
|
||||
- **GIVEN** 用户勾选 1-500 家店铺并选择启用业务员
|
||||
- **WHEN** 提交批量交接
|
||||
- **THEN** 前端 MUST 提交 shop_ids(去重)与 business_owner_account_id
|
||||
- **AND** 成功后展示 shop_count 并刷新列表
|
||||
|
||||
#### Scenario: 批量清空负责人
|
||||
|
||||
- **GIVEN** 用户勾选店铺并选择"清空负责人"
|
||||
- **WHEN** 提交批量交接
|
||||
- **THEN** 前端 MUST 显式提交 business_owner_account_id: null
|
||||
- **AND** MUST NOT 省略该字段
|
||||
|
||||
#### Scenario: 整批失败统一提示
|
||||
|
||||
- **GIVEN** 批量交接请求因任一店铺或目标账号不满足条件而被后端拒绝
|
||||
- **WHEN** 前端收到 1005
|
||||
- **THEN** 前端 MUST 展示统一文案且不写入任何店铺
|
||||
|
||||
### Requirement: 业务负责人 CSV 导入
|
||||
|
||||
店铺列表 MUST 支持通过 CSV 导入批量交接/清空业务负责人。流程 MUST 为:前端自带模板(表头 店铺编码,操作类型,业务员登录账号,备注,操作类型仅"换绑"或"清空")-> POST /api/admin/storage/upload-url(purpose=shop_import)拿预签名 URL 直传(15 分钟有效)-> POST /api/admin/shops/business-owner-imports(file_key)创建异步任务 -> 轮询 GET /api/admin/shops/business-owner-imports/{id} 展示结果。行级失败不等于任务失败:任务 status=3 也可能有失败行,MUST 按 items[] 展示明细;表头/编码不符为任务级失败,items 为空数组,只展示 error_message。
|
||||
|
||||
#### Scenario: 下载模板并上传
|
||||
|
||||
- **GIVEN** 用户进入导入界面
|
||||
- **WHEN** 点击下载模板或选择文件
|
||||
- **THEN** 前端 MUST 提供表头完全一致的模板文件
|
||||
- **AND** 上传后 MUST 以 purpose=shop_import 获取上传地址并直传
|
||||
|
||||
#### Scenario: 创建任务并轮询
|
||||
|
||||
- **GIVEN** 文件上传成功拿到 file_key
|
||||
- **WHEN** 提交创建导入任务
|
||||
- **THEN** 前端 MUST 携带 file_key 调用创建接口
|
||||
- **AND** MUST 轮询任务详情直到 status 为已完成(3)或失败(4)
|
||||
|
||||
#### Scenario: 展示行级失败明细
|
||||
|
||||
- **GIVEN** 任务完成但存在失败行
|
||||
- **WHEN** 前端渲染结果
|
||||
- **THEN** 前端 MUST 展示 total_count/success_count/fail_count
|
||||
- **AND** MUST 按 items[] 展示各行的 line/shop_code/operation_type/status/reason
|
||||
- **AND** 失败行保留原负责人
|
||||
|
||||
#### Scenario: 任务级失败提示
|
||||
|
||||
- **GIVEN** 表头或编码不符导致任务级失败
|
||||
- **WHEN** 前端渲染结果
|
||||
- **THEN** 前端 MUST 只展示 error_message 且明细为空
|
||||
34
openspec/changes/add-business-user-group-management/tasks.md
Normal file
34
openspec/changes/add-business-user-group-management/tasks.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## 1. API Contract 层
|
||||
|
||||
- [x] 1.1 新增 src/types/api/businessUserGroup.ts:组项/分页/创建/更新/删除/成员设置与结果类型
|
||||
- [x] 1.2 新增 src/api/modules/businessUserGroup.ts:封装 7 个组端点(DELETE 带 body,需请求层支持)
|
||||
- [x] 1.3 扩展 src/types/api/shop.ts:列表筛选 business_user_group_id/business_line/ungrouped,响应只读组字段,批量交接与导入任务/行级结果类型
|
||||
- [x] 1.4 扩展 src/api/modules/shop.ts:batchUpdateBusinessOwner、createBusinessOwnerImport、getBusinessOwnerImportDetail、getBusinessOwnerImportList
|
||||
- [x] 1.5 扩展 src/api/modules/storage.ts:FilePurpose 增加 shop_import
|
||||
|
||||
## 2. 业务用户组页面
|
||||
|
||||
- [x] 2.1 新增 src/views/shop-management/business-user-groups/index.vue:列表(筛选/分页/启停/业务线)
|
||||
- [x] 2.2 新建/编辑弹窗:code 创建后不可改、business_line 三态、名称/排序/备注/启停
|
||||
- [x] 2.3 删除:二次确认(confirm=true),有成员时按后端提示处理
|
||||
- [x] 2.4 成员管理(对话框/抽屉):成员选择器(复用 business-owner-candidates)+ 批量设置 + 批量清空;停用组保留成员并标记"已停用"
|
||||
|
||||
## 3. 店铺列表扩展
|
||||
|
||||
- [x] 3.1 列表搜索区新增业务用户组下拉与业务线筛选、ungrouped 勾选(数据源来自组列表接口)
|
||||
- [x] 3.2 列表/详情展示组字段(停用组标记,不缓存不回写)
|
||||
- [x] 3.3 批量交接弹窗:勾选店铺 + 选择业务员/清空(显式 null),整批失败统一 1005 文案
|
||||
- [x] 3.4 导入入口:模板下载、文件校验/上传(purpose=shop_import)、创建任务、轮询并展示行级明细
|
||||
- [x] 3.5 导入任务列表(状态筛选)
|
||||
|
||||
## 4. 路由/菜单/权限/i18n
|
||||
|
||||
- [x] 4.1 新增 /shop-management/business-user-groups 路由与菜单,roles ['R_SUPER', 'R_ADMIN']
|
||||
- [x] 4.2 按钮权限编码落地(business_user_group*/shop:business_owner_batch/shop:business_owner_import)
|
||||
- [x] 4.3 中英文案 menus.shopManagement.businessUserGroups 等
|
||||
|
||||
## 5. 验证
|
||||
|
||||
- [x] 5.1 运行 npm run build(含 vue-tsc --noEmit)
|
||||
- [x] 5.2 运行 npm run check:encoding
|
||||
- [x] 5.3 运行 openspec validate add-business-user-group-management --strict
|
||||
60
openspec/changes/add-commission-clawback-records/proposal.md
Normal file
60
openspec/changes/add-commission-clawback-records/proposal.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Change: 退款佣金回扣改为独立回溯明细(前端适配)
|
||||
|
||||
## Why
|
||||
|
||||
后端调整了退款佣金回扣口径:退款不再把原佣金整单置为「已失效」,而是原佣金行保持「已发放」不变,另新增一条独立负数、不可提现的「回溯明细」行。对应前端接口说明(简短版)要求:
|
||||
|
||||
- 佣金记录列表新增 `status` 查询参数(含 5 回溯)与行级 `source` 字段(`original` / `clawback`)。
|
||||
- 新增佣金记录详情接口 `GET /api/admin/shops/{shop_id}/commission-records/{id}?source=clawback`。
|
||||
- 新增导出场景 `scene=commission_record`(后端已进白名单)。
|
||||
- 两个必改点:列表行 key 必须用 `source + ':' + id`(原佣金与回溯 ID 空间独立,同一页会重复);渲染需支持负数金额(标红)与「不可提现」标识。
|
||||
|
||||
前端当前实现基于旧语义(`ShopCommissionRecordItem` 无 `source`,列表行 key 直接用 `id`,退款后原行被视为已失效),需按新口径适配。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 类型层:`ShopCommissionRecordItem` 新增 `source`、`original_commission_id`、`refund_id`、`refund_no`、`withdrawable`、`clawback_records`、`clawback_total_amount`;新增回溯摘要与详情类型;`CommissionRecordQueryParams.status` 支持 5(回溯)。
|
||||
- API 层:新增 `getShopCommissionRecordDetail(shopId, id, source?)`,`source` 省略时按原佣金处理。
|
||||
- 列表页(代理侧「我的佣金」与管理侧「代理资金概览 > 佣金明细」):行 key 改为 `source + ':' + id`;回溯行展示「回溯」状态与「不可提现」标识;负数金额与可为负的 `balance_after` 标红;`status` 筛选项新增「回溯」;详情入口必须携带该行 `source`。
|
||||
- 详情展示:原佣金详情展示 `clawback_records` 摘要与 `clawback_total_amount`;回溯详情展示来源 `original_commission`;越权/不存在统一提示「佣金明细不存在」,不做存在性判断。
|
||||
- 导出:`ExportTaskScene` 增加 `commission_record`;新增导出佣金记录场景页、路由、菜单与 i18n;佣金记录列表导出入口只提交受支持的筛选 key(`shop_id` / `status` / `commission_source` / `order_no`)。
|
||||
- 字段兜底:后端新增字段为 `omitempty`,前端取 `?? []` / `?? 0`,金额单位为分。
|
||||
|
||||
## Not In Scope
|
||||
|
||||
- 不实现后端接口、不改数据库、不改审批实例数据。
|
||||
- 既有字段无改名/改类型;`commission-stats`、`commission-daily-stats` 口径未变,本次不动。
|
||||
- 时间筛选属另一 Change(`add-export-time-filter-standards`),本次不涉及。
|
||||
- 语义提醒:退款不再写 `status=4`,旧假设「退款后原行变已失效」已失效,需在文案/注释体现。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `commission-management`、`export-task-management`
|
||||
- Affected code:
|
||||
- `src/types/api/commission.ts`
|
||||
- `src/api/modules/commission.ts`
|
||||
- `src/views/commission-management/my-commission/index.vue`
|
||||
- `src/views/commission-management/agent-fund-overview/index.vue`
|
||||
- `src/types/api/exportTask.ts`
|
||||
- `src/config/constants/exportTask.ts`
|
||||
- `src/views/asset-management/export-task-management/export-commission-record/index.vue`
|
||||
- `src/router/routes/asyncRoutes.ts`、`src/router/routesAlias.ts`、`src/locales/langs/{zh,en}.json`
|
||||
- Dependencies:
|
||||
- `docs/产品迭代8月份/frontend-api-simple.md`(后端契约)
|
||||
- 后端已把 `scene=commission_record` 加入导出场景白名单
|
||||
- 需确认导出场景权限编码与场景页是否必需(见 Open Questions)
|
||||
- Breaking changes:
|
||||
- 列表行 key 由 `id` 改为 `source + ':' + id`;未携带 `source` 的详情请求会命中原佣金行。
|
||||
|
||||
## 接口确认结果(来自 OpenAPI)
|
||||
|
||||
- 详情响应 `DtoShopCommissionRecordDetailResp`:`source`、`record`(`DtoShopCommissionRecordItem`)、`clawback_records`(仅原佣金返回)、`original_commission`(仅回溯详情返回)。
|
||||
- `DtoShopCommissionRecordItem` 新增 `source`、`released_at`、`withdrawable`(boolean,nullable,仅回溯行返回且恒 false)、`original_commission_id`、`refund_id`、`refund_no`、`clawback_records`、`clawback_total_amount`。
|
||||
- `DtoShopCommissionClawbackItem`:`id`、`amount`(恒负)、`balance_after`(可为负)、`original_commission_id`、`refund_id`、`refund_no`、`status`(5 回溯)、`status_name`、`withdrawable`、`created_at`。
|
||||
- 详情接口越权与不存在返回同一结果,前端统一提示「佣金明细不存在」。
|
||||
- 已确认需要新增「导出佣金记录」菜单页。
|
||||
|
||||
## 待确认(已按仓库既有约定实现,如需调整请告知)
|
||||
|
||||
1. 导出场景权限编码:按既有约定实现为 `export_task:commission_record_detail` / `export_task:commission_record_download`。
|
||||
2. 佣金记录列表导出按钮权限:按 `agent_wallet_transaction:export` 的同级约定实现为 `commission_record:export`。
|
||||
@@ -0,0 +1,48 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 佣金记录列表回溯行与状态筛选
|
||||
佣金记录列表(`GET /api/admin/shops/{shop_id}/commission-records`)MUST 支持 `status` 查询参数(1 已冻结 / 2 解冻中 / 3 已发放 / 4 已失效 / 5 回溯 / 99 待人工修正),且 MUST NOT 提供 `source` 查询参数;`status=5` 即等价于只看回溯行。
|
||||
|
||||
#### Scenario: 只看回溯行
|
||||
- **GIVEN** 用户在佣金记录列表选择状态「回溯」
|
||||
- **WHEN** 前端发起列表查询
|
||||
- **THEN** 前端 MUST 只传 `status=5`
|
||||
- **AND** 前端 MUST NOT 传 `source` 参数
|
||||
|
||||
### Requirement: 佣金记录行新增字段与兜底
|
||||
列表响应的每一行 MUST 支持 `source`(`original` / `clawback`);回溯行 MUST 支持 `original_commission_id`、`refund_id`、`refund_no`、`withdrawable`(恒 `false`)与可为负数的 `amount`、`balance_after`;原佣金行 MUST 支持 `clawback_records` 摘要与 `clawback_total_amount`(负值)。后端 `omitempty` 缺省字段前端 MUST 以 `?? []` / `?? 0` 兜底,金额单位为分。
|
||||
|
||||
#### Scenario: 缺省字段兜底
|
||||
- **GIVEN** 列表响应中的原佣金行未返回 `clawback_records`
|
||||
- **WHEN** 前端渲染该行
|
||||
- **THEN** 前端 MUST 按空数组处理并正常渲染
|
||||
- **AND** 前端 MUST NOT 因缺省字段报错或渲染 `undefined`
|
||||
|
||||
### Requirement: 列表行标识与负数渲染
|
||||
列表 MUST 使用 `source + ':' + id` 作为行 key(原佣金与回溯 ID 空间独立,同一页可能出现相同 `id`);回溯行 MUST 展示「不可提现」标识;负数 `amount` 与 `balance_after` MUST 标红显示;任何跳转详情的入口 MUST 携带该行的 `source`,缺省按原佣金处理。
|
||||
|
||||
#### Scenario: 同一页出现重复 id
|
||||
- **GIVEN** 同一页列表中同时存在原佣金行与回溯行且 `id` 相同
|
||||
- **WHEN** 前端渲染表格
|
||||
- **THEN** 前端 MUST 以 `source + ':' + id` 区分两行
|
||||
- **AND** 前端 MUST NOT 出现行复用或渲染错位
|
||||
|
||||
#### Scenario: 负数金额标红
|
||||
- **GIVEN** 某回溯行 `amount` 为负值
|
||||
- **WHEN** 前端渲染金额列
|
||||
- **THEN** 前端 MUST 标红展示该负值
|
||||
|
||||
### Requirement: 佣金记录详情接口封装
|
||||
前端 MUST 提供 `GET /api/admin/shops/{shop_id}/commission-records/{id}` 的详情封装,并支持可选 `source`:`source=clawback` 时返回回溯详情,`source` 省略时按原佣金处理。原佣金详情 MUST 返回 `clawback_records`;回溯详情 MUST 返回 `original_commission`;越权与不存在 MUST 返回同一结果(佣金明细不存在),前端 MUST NOT 用该接口做存在性判断。
|
||||
|
||||
#### Scenario: 打开回溯行详情
|
||||
- **GIVEN** 用户在列表点击某条回溯行
|
||||
- **WHEN** 前端请求详情
|
||||
- **THEN** 前端 MUST 携带 `source=clawback` 与该行 `id`
|
||||
- **AND** 详情 MUST 展示来源 `original_commission`
|
||||
|
||||
#### Scenario: 越权统一提示
|
||||
- **GIVEN** 详情接口返回「佣金明细不存在」
|
||||
- **WHEN** 前端渲染详情
|
||||
- **THEN** 前端 MUST 展示统一不存在提示
|
||||
- **AND** 前端 MUST NOT 区分越权与不存在两种原因
|
||||
@@ -0,0 +1,19 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 佣金记录导出场景
|
||||
前端 MUST 支持导出场景 `scene=commission_record`(后端已进白名单),并在导出管理中提供该场景的任务列表入口;创建导出任务时 MUST 提交固定 `scene=commission_record`。导出粒度为佣金记录:原佣金与回溯各占一行,金额与余额同样按分转元、负数带 `-` 展示。
|
||||
|
||||
#### Scenario: 场景页固定查询
|
||||
- **GIVEN** 用户打开导出佣金记录页面
|
||||
- **WHEN** 页面查询导出任务列表
|
||||
- **THEN** 前端 MUST 调用 `GET /api/admin/export-tasks` 且带 `scene=commission_record`
|
||||
- **AND** 用户 MUST NOT 能在该页面切换到其他场景
|
||||
|
||||
### Requirement: 佣金记录导出筛选受限
|
||||
佣金记录列表发起导出时,`query` MUST 只包含后端支持的筛选 key:`shop_id`、`status`、`commission_source`、`order_no`;MUST NOT 提交 `iccid`、`virtual_no` 及时间筛选,导出弹窗 MUST NOT 直接复用列表全部条件。
|
||||
|
||||
#### Scenario: 列表筛选含不支持的 key
|
||||
- **GIVEN** 佣金记录列表当前筛选包含 `iccid` 与时间范围
|
||||
- **WHEN** 用户确认创建导出任务
|
||||
- **THEN** 提交的 `query` MUST NOT 包含 `iccid`、`virtual_no` 或时间字段
|
||||
- **AND** 提交的 `query` MUST 仅保留 `shop_id`、`status`、`commission_source`、`order_no` 中已填写的项
|
||||
27
openspec/changes/add-commission-clawback-records/tasks.md
Normal file
27
openspec/changes/add-commission-clawback-records/tasks.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## 1. 类型与 API 层(commission-api)
|
||||
- [x] 1.1 `CommissionStatus` 补充 5(回溯);修正 `CommissionRecordQueryParams.status` 过期注释并支持回溯值
|
||||
- [x] 1.2 `ShopCommissionRecordItem` 新增 `source`、`original_commission_id`、`refund_id`、`refund_no`、`withdrawable`、`clawback_records`、`clawback_total_amount`
|
||||
- [x] 1.3 新增回溯摘要与佣金记录详情类型(原佣金详情含 `clawback_records`,回溯详情含 `original_commission`)
|
||||
- [x] 1.4 `CommissionService` 新增 `getShopCommissionRecordDetail(shopId, id, source?)`
|
||||
|
||||
## 2. 列表页(commission-records-list)
|
||||
- [x] 2.1 行 key 改为 `source + ':' + id`(`my-commission` 与 `agent-fund-overview` 佣金明细)
|
||||
- [x] 2.2 回溯行展示「回溯」状态与「不可提现」标识
|
||||
- [x] 2.3 负数金额与可为负的 `balance_after` 标红渲染
|
||||
- [x] 2.4 `status` 筛选项新增「回溯」
|
||||
- [x] 2.5 列表新增详情入口,且请求必须携带该行 `source`
|
||||
|
||||
## 3. 详情展示(commission-record-detail)
|
||||
- [x] 3.1 原佣金详情展示 `clawback_records` 摘要与 `clawback_total_amount`
|
||||
- [x] 3.2 回溯详情展示来源 `original_commission`
|
||||
- [x] 3.3 越权/不存在统一提示「佣金明细不存在」,不作为存在性判断依据
|
||||
|
||||
## 4. 导出(commission-record-export)
|
||||
- [x] 4.1 `ExportTaskScene` 增加 `commission_record` 并补场景配置
|
||||
- [x] 4.2 新增导出佣金记录场景页、路由、菜单与中英文案
|
||||
- [x] 4.3 佣金记录列表新增导出入口,`query` 仅提交 `shop_id` / `status` / `commission_source` / `order_no`
|
||||
|
||||
## 5. 验证
|
||||
- [x] 5.1 运行 `npm run build`(含 `vue-tsc --noEmit`)
|
||||
- [x] 5.2 运行 `npm run check:encoding`
|
||||
- [x] 5.3 运行 `openspec.cmd validate add-commission-clawback-records --strict`
|
||||
@@ -44,5 +44,5 @@
|
||||
## 7. Verification
|
||||
|
||||
- [x] 7.1 运行 `npm run build`(含 `vue-tsc --noEmit`)与 `npm run check:encoding`,确保类型与编码通过。
|
||||
- [ ] 7.2 校验普通员工与超管的菜单、列与操作可见性差异。
|
||||
- [ ] 7.3 校验金额分/元转换、附件预签名展示与 503 错误兜底。
|
||||
- [x] 7.2 校验普通员工与超管的菜单、列与操作可见性差异。 — 已核验:应用/账单/收款方式页 v-permission 按钮、bills 关闭操作 hasAuth(billClose)+canCloseBill、表单 isActing=isSuperAdmin 控制代办字段
|
||||
- [x] 7.3 校验金额分/元转换、附件预签名展示与 503 错误兜底。 — 已核验:ApplicationFormDialog 用 fenToYuan/yuanToFen;detail.vue 用 PaymentVoucherDialog 预览凭证;getErrorMessage 提取后端 msg(含 503 未配置场景)兜底
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Design: H5 运营弹窗配置管理(管理后台)
|
||||
|
||||
## 页面结构
|
||||
|
||||
- 设置管理下新增「H5运营弹窗配置」入口(仅超管/平台):
|
||||
- 列表页 `src/views/settings/h5-popup-configuration/index.vue`
|
||||
- 创建/编辑表单弹窗(复用 ArtForm + ArtSearchBar/ArtTable 模式)
|
||||
- 详情以弹窗/抽屉展示完整配置
|
||||
- 分页、筛选、表格沿用 ArtTable / ArtSearchBar / ArtTableHeader 与统一响应结构(`code/msg/timestamp/data`、分页 `items/page/size/total`)。
|
||||
|
||||
## 接口与类型
|
||||
|
||||
- 新增 `src/api/modules/h5PopupConfiguration.ts` 与 `src/types/api/h5PopupConfiguration.ts`:
|
||||
- `GET /api/admin/h5-popup-configurations`(page/page_size/enabled,倒序分页)
|
||||
- `POST /api/admin/h5-popup-configurations`(创建)
|
||||
- `GET /api/admin/h5-popup-configurations/{id}`(详情)
|
||||
- `PUT /api/admin/h5-popup-configurations/{id}`(更新,整表提交,版本 +1)
|
||||
- `POST /api/admin/h5-popup-configurations/{id}/enable`、`{id}/disable`(请求体 `{ id }`,返回更新后配置)
|
||||
- 枚举映射常量集中放置:`pages`(home/asset_detail/package_purchase/asset_wallet_recharge)、`frequency`(once/daily)、`action_type`(package_purchase/asset_wallet_recharge,空字符串 = 无)、`card_types`(CMCC/CUCC/CTCC/CBN)。
|
||||
|
||||
## 表单约定
|
||||
|
||||
- `pages` 多选必填(空数组非法);`title`(1–100)/`content`(1–2000)/`frequency`/起止时间必填。
|
||||
- 范围三选器(店铺/设备类型/卡类型)空数组 = 全量,回显「全部」。
|
||||
- 起止时间用 datetimerange,提交转 ISO 8601(`starts_at`/`ends_at`),`ends_at` 不得早于 `starts_at`。
|
||||
- `priority` 数字输入(0–1000000);`enabled` 开关;`action_type` 下拉(含「无」选项,提交空字符串清除受控动作)。
|
||||
- 编辑表单整表提交;编辑保存成功后提示「每次更新版本递增,旧版本通知保留原快照,新版本可向原命中客户按频率重新投放一次」。
|
||||
- 启用/停用为行操作(停用需二次确认),以接口返回的更新后配置刷新当前行。
|
||||
|
||||
## 权限与可见性
|
||||
|
||||
- 权限码:`h5_popup_configuration:list`(菜单/列表)、`:create`、`:update`、`:enable`、`:disable`。
|
||||
- 路由 `meta.permissions` + 按钮 `v-permission`/`usePermission`,仅超管/平台可见;代理/企业/个人由后端 403 兜底并原文透传失败文案。
|
||||
- 不提供删除入口(文档无 DELETE 接口)。
|
||||
|
||||
## 待确认
|
||||
|
||||
- 权限编码以后端菜单配置为准(前端默认 `h5_popup_configuration:list/create/update/enable/disable`)。
|
||||
@@ -0,0 +1,46 @@
|
||||
# Change: 新增 H5 运营弹窗配置管理(管理后台)
|
||||
|
||||
## Why
|
||||
|
||||
H5 端(个人客户)需要在首页、资产详情、套餐购买、资产钱包充值等页面投放运营/风险弹窗,弹窗文案、命中页面、投放范围与生效规则需要后台配置。当前后台没有任何弹窗配置入口,运营只能走数据库操作。
|
||||
|
||||
本 Change 为管理后台新增「H5 运营弹窗配置」管理能力:配置标题与正文、命中页面、投放范围(店铺/设备类型/卡类型)、优先级、投放频率、生效时间与启停。
|
||||
|
||||
H5 端契约(`GET /api/c/v1/popup-candidates`、`POST /api/c/v1/risk-exchanges/{asset_id}/address`、通知列表/未读/已读接口)不在本 Change 范围,需求方已明确不处理 C 端。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 6 个后台接口(仅超管/平台,代理/企业/个人 403):
|
||||
- `GET /api/admin/h5-popup-configurations`:配置列表,`page`(1–10000)/ `page_size`(1–100)/ `enabled` 筛选,按最近更新时间倒序分页返回 `items/page/size/total`。
|
||||
- `POST /api/admin/h5-popup-configurations`:创建;`title`/`content`/`pages`/`frequency`/`starts_at`/`ends_at` 必填;`priority`/`enabled`/`action_type`/`shop_ids`/`device_types`/`card_types` 可选(范围集合空数组 = 全量)。
|
||||
- `GET /api/admin/h5-popup-configurations/{id}`:详情(含 `version`、`frequency_text`、`enabled_text`、`creator`/`updater`)。
|
||||
- `PUT /api/admin/h5-popup-configurations/{id}`:更新;成功即版本 +1,新版本可向原命中客户按频率重新投放一次,旧版本已投放通知的内容与快照不被改写;除 `id` 外全部字段可选,**不传保持原值**;`action_type` 传空字符串 = 清除受控动作;`shop_ids`/`device_types`/`card_types` 传空数组 = 改为全量;`pages` 传空数组非法(页面必选);`enabled` 不传保持原值(启停刷新最近更新时间);`ends_at` 不得早于 `starts_at`;`content` 1–2000 字符、`title` 1–100 字符、`priority` 0–1000000。
|
||||
- `POST /api/admin/h5-popup-configurations/{id}/enable`:启用,参与候选匹配,刷新 `updated_at`(影响同优先级排序),返回更新后的配置。
|
||||
- `POST /api/admin/h5-popup-configurations/{id}/disable`:停用,停止新投放,历史通知在展示期内仍可见;刷新 `updated_at`,返回更新后的配置。
|
||||
- 新增「H5 运营弹窗配置」管理页(设置管理下,仅超管/平台可见):分页列表 + 创建/编辑表单 + 详情 + 行操作启用/停用。
|
||||
- 前端契约约定:
|
||||
- 范围集合 `shop_ids` / `device_types` / `card_types` 空数组 = 全量,列表与编辑回显「全部」。
|
||||
- 枚举:`pages` = home / asset_detail / package_purchase / asset_wallet_recharge;`frequency` = once / daily;`action_type` = package_purchase / asset_wallet_recharge(空字符串 = 无受控动作);`card_types` = CMCC / CUCC / CTCC / CBN。
|
||||
- 无「删除」接口,页面不提供删除入口;停用不清数据,列表保留历史配置。
|
||||
- 编辑表单整表提交(所有字段都传,未传语义仅在部分更新时生效);范围清空传空数组、动作清除传空字符串、页面集合不可为空。
|
||||
- 权限编码由前端确定:`h5_popup_configuration:list` / `:create` / `:update` / `:enable` / `:disable`。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs:
|
||||
- `h5-popup-configuration-management`
|
||||
- Affected code:
|
||||
- `src/api/modules/h5PopupConfiguration.ts`(新增)
|
||||
- `src/types/api/h5PopupConfiguration.ts`(新增)
|
||||
- `src/api/modules/index.ts`、`src/types/api/index.ts`
|
||||
- `src/config/constants/`(权限码常量)
|
||||
- `src/router/routesAlias.ts`、`src/router/routes/asyncRoutes.ts`(设置管理下新增菜单与路由)
|
||||
- `src/locales/langs/zh.json`、`src/locales/langs/en.json`
|
||||
- `src/views/settings/h5-popup-configuration/`(列表、表单弹窗、详情)
|
||||
- Dependencies:
|
||||
- 后端按 `docs/产品迭代8月份/通知.md` 提供上述 6 接口,Bearer JWT 鉴权,代理/企业/个人 403。
|
||||
- Breaking changes:
|
||||
- 无;全部为新增页面、接口模块与类型。
|
||||
- 待确认:
|
||||
- 权限编码(前端默认 `h5_popup_configuration:list/create/update/enable/disable`)以后端菜单配置为准。
|
||||
- 列表分页上限与「按最近更新时间倒序」以文档为准,联调核对后端行为。
|
||||
@@ -0,0 +1,117 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: H5 运营弹窗配置列表
|
||||
|
||||
后台 MUST 提供 H5 运营弹窗配置的分页列表(GET /api/admin/h5-popup-configurations),支持 `page`(1–10000)/ `page_size`(1–100)/ `enabled` 筛选,按最近更新时间倒序返回 `items/page/size/total`;列表项 MUST 包含标题、命中页面、范围(店铺/设备类型/卡类型)、优先级、频率(`frequency_text`)、启停(`enabled_text`)、受控动作、生效起止时间、版本、创建/更新人与时间。
|
||||
|
||||
#### Scenario: 按启停状态筛选并分页
|
||||
|
||||
- **GIVEN** 超管/平台账号进入「H5运营弹窗配置」列表
|
||||
- **WHEN** 以 page/page_size/enabled 发起查询
|
||||
- **THEN** 前端 MUST 展示分页结果(后端按最近更新时间倒序)
|
||||
- **AND** 切换 enabled 筛选后按新条件重新请求
|
||||
|
||||
#### Scenario: 范围空数组显示全部
|
||||
|
||||
- **GIVEN** 某条配置的 shop_ids/device_types/card_types 为空数组
|
||||
- **THEN** 列表 MUST 在范围列展示「全部」
|
||||
|
||||
#### Scenario: 越权访问
|
||||
|
||||
- **GIVEN** 代理/企业/个人账号访问列表接口或直达路由
|
||||
- **THEN** 后端 MUST 返回 403,前端 MUST NOT 渲染入口
|
||||
- **AND** 失败文案 MUST 按后端返回原文透传
|
||||
|
||||
### Requirement: 创建弹窗配置
|
||||
|
||||
后台 MUST 支持创建 H5 运营弹窗配置(POST /api/admin/h5-popup-configurations),`title`/`content`/`pages`/`frequency`/`starts_at`/`ends_at` 必填;`priority`/`enabled`/`action_type`/`shop_ids`/`device_types`/`card_types` 可选,范围集合空数组 = 全量。
|
||||
|
||||
#### Scenario: 必填校验
|
||||
|
||||
- **GIVEN** 用户提交创建表单
|
||||
- **WHEN** title/content/pages/frequency/starts_at/ends_at 任一缺失
|
||||
- **THEN** 前端 MUST 拦截并提示必填,MUST NOT 发送请求
|
||||
|
||||
#### Scenario: 范围集合为空数组表示全量
|
||||
|
||||
- **GIVEN** 用户未选择店铺/设备类型/卡类型范围
|
||||
- **THEN** 请求体对应数组 MUST 传空数组,后端按全量投放处理
|
||||
|
||||
### Requirement: 弹窗配置详情
|
||||
|
||||
后台 MUST 提供弹窗配置详情(GET /api/admin/h5-popup-configurations/{id}),返回完整配置含 `version`、`frequency_text`、`enabled_text`、`creator`/`updater`。
|
||||
|
||||
#### Scenario: 查看详情
|
||||
|
||||
- **GIVEN** 用户点击列表行「详情」
|
||||
- **WHEN** 请求详情成功
|
||||
- **THEN** 前端 MUST 展示完整配置要素(含版本与启停/频率中文名)
|
||||
|
||||
### Requirement: 更新弹窗配置
|
||||
|
||||
后台 MUST 支持更新弹窗配置(PUT /api/admin/h5-popup-configurations/{id}),成功即版本 +1;新版本可向原命中客户按频率重新投放一次,旧版本已投放通知的内容与快照不被改写。除 `id` 外所有字段均可选,**不传保持原值**;`action_type` 传空字符串 = 清除受控动作;`shop_ids`/`device_types`/`card_types` 传空数组 = 改为全量;`pages` 传空数组非法(页面必选);`enabled` 不传保持原值(启停刷新最近更新时间);`ends_at` 不得早于 `starts_at`;`content` 1–2000 字符、`title` 1–100 字符、`priority` 0–1000000。
|
||||
|
||||
#### Scenario: 更新成功版本递增并重新投放
|
||||
|
||||
- **GIVEN** 用户保存编辑
|
||||
- **WHEN** 更新成功
|
||||
- **THEN** 前端 MUST 提示「每次更新版本递增,旧版本通知保留原快照,新版本可向原命中客户按频率重新投放一次」并刷新列表
|
||||
- **AND** 列表/详情版本号较更新前 +1,旧版本已投放通知内容不被改写
|
||||
|
||||
#### Scenario: 范围清空表示改为全量
|
||||
|
||||
- **GIVEN** 编辑时清空某范围集合
|
||||
- **THEN** 提交对应数组 MUST 为空数组,后端按全量处理
|
||||
|
||||
#### Scenario: 受控动作清除
|
||||
|
||||
- **GIVEN** 编辑时把 action_type 从 package_purchase 切换为「无」
|
||||
- **THEN** 请求体 action_type MUST 传空字符串,后端清除受控动作
|
||||
|
||||
#### Scenario: 页面集合不得为空
|
||||
|
||||
- **GIVEN** 编辑时清空全部命中页面
|
||||
- **THEN** 前端 MUST 拦截提示命中页面必选,MUST NOT 传空 pages 数组
|
||||
|
||||
#### Scenario: 未传字段保持原值
|
||||
|
||||
- **GIVEN** 请求体省略某字段(如 enabled)
|
||||
- **THEN** 后端 MUST 保持原值;前端编辑表单整表提交,范围清空按空数组、动作清除按空字符串处理
|
||||
|
||||
### Requirement: 启用与停用弹窗配置
|
||||
|
||||
后台 MUST 支持启用/停用弹窗配置(POST /api/admin/h5-popup-configurations/{id}/enable、/{id}/disable,请求体 `{ id }`,返回更新后的配置),停用停止新投放且历史通知在展示期内仍可见,启用参与候选匹配;两者均刷新最近更新时间(影响同优先级排序)且不递增版本。
|
||||
|
||||
#### Scenario: 停用需二次确认
|
||||
|
||||
- **GIVEN** 用户点击某启用的配置「停用」
|
||||
- **WHEN** 二次确认提交
|
||||
- **THEN** 前端 MUST 调用 disable 并提示「停用后停止新投放,历史通知在展示期内仍可见」
|
||||
- **AND** 以返回的更新后配置刷新当前行(状态为停用、最近更新时间更新、版本不变)
|
||||
|
||||
#### Scenario: 启用
|
||||
|
||||
- **GIVEN** 用户点击某停用的配置「启用」
|
||||
- **THEN** 前端 MUST 调用 enable 并提示「启用后参与候选匹配」
|
||||
- **AND** 以返回的更新后配置刷新当前行(状态为启用、最近更新时间更新、版本不变)
|
||||
|
||||
### Requirement: 角色权限与交互约定
|
||||
|
||||
弹窗配置入口 MUST 仅对超级管理员与平台账号可见;前端权限码固定为 `h5_popup_configuration:list`(菜单/列表)、`:create`、`:update`、`:enable`、`:disable`;页面 MUST NOT 提供删除入口(文档无 DELETE 接口);停用不清数据,列表保留历史配置。
|
||||
|
||||
#### Scenario: 入口按角色隐藏
|
||||
|
||||
- **GIVEN** 代理/企业/个人账号登录
|
||||
- **WHEN** 系统渲染菜单
|
||||
- **THEN** MUST NOT 展示「H5运营弹窗配置」菜单项
|
||||
|
||||
#### Scenario: 按钮权限码控制
|
||||
|
||||
- **GIVEN** 账号具备列表权限但缺 enable/disable 权限
|
||||
- **THEN** 前端 MUST 只渲染具备权限的操作按钮
|
||||
|
||||
#### Scenario: 不提供删除入口
|
||||
|
||||
- **GIVEN** 用户查看列表或详情
|
||||
- **THEN** 页面 MUST NOT 出现删除操作,仅提供启用/停用
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
## 1. Contract Confirmation
|
||||
|
||||
- [x] 1.1 权限编码前端确定:`h5_popup_configuration:list` / `:create` / `:update` / `:enable` / `:disable`(以后端菜单配置为准)。
|
||||
- [x] 1.2 更新接口语义按文档:不传保持原值;`action_type` 空串 = 清除;范围空数组 = 改全量;`pages` 空数组非法;`enabled` 不传保持原值;`ends_at` 不得早于 `starts_at`;`content` 1–2000、`title` 1–100、`priority` 0–1000000;更新成功版本 +1 且旧版本通知不被改写。
|
||||
- [x] 1.3 `enable`/`disable` 请求体 `{ id }`,返回更新后的 DtoH5PopupConfigurationResponse。
|
||||
- [x] 1.4 列表 `page` 1–10000、`page_size` 1–100、`enabled` 筛选,按最近更新时间倒序分页。
|
||||
|
||||
## 2. API And Types
|
||||
|
||||
- [ ] 2.1 新增 `src/api/modules/h5PopupConfiguration.ts`:列表、创建、详情、更新、启用、停用 6 个接口。
|
||||
- [ ] 2.2 新增 `src/types/api/h5PopupConfiguration.ts`:请求/响应 DTO(含 pages/frequency/action_type/card_types 枚举、范围集合、version、creator/updater 等;更新语义按「不传保持原值/空数组=全量/空串=清除」注释)。
|
||||
- [ ] 2.3 在 `src/api/modules/index.ts`、`src/types/api/index.ts` 导出新模块/类型。
|
||||
- [ ] 2.4 对齐统一响应 `code/msg/timestamp/data` 与分页 `items/page/size/total`。
|
||||
|
||||
## 3. List Page
|
||||
|
||||
- [ ] 3.1 设置管理下新增「H5运营弹窗配置」菜单与路由(仅超管/平台可见,权限码 `h5_popup_configuration:list`)。
|
||||
- [ ] 3.2 列表:`page`/`page_size`/`enabled` 筛选,分页展示,按后端倒序。
|
||||
- [ ] 3.3 列:标题、命中页面、范围(店铺/设备类型/卡类型,空数组显示「全部」)、优先级、频率、启停状态、受控动作、生效起止、版本、创建/更新人与时间。
|
||||
- [ ] 3.4 行操作:详情、编辑、启用/停用(停用需二次确认);无删除入口。
|
||||
|
||||
## 4. Create / Edit / Detail
|
||||
|
||||
- [ ] 4.1 创建表单:`title`/`content`/`pages` 多选/`frequency`/起止时间必填校验(pages 非空);`priority`/`enabled`/`action_type`/范围可选。
|
||||
- [ ] 4.2 范围集合 `shop_ids`/`device_types`/`card_types` 空数组 = 全量;编辑读回空数组显示「全部」。
|
||||
- [ ] 4.3 编辑表单整表提交;保存成功提示「每次更新版本递增,旧版本通知保留原快照,新版本可向原命中客户按频率重新投放一次」。
|
||||
- [ ] 4.4 编辑支持 `action_type` 清空(「无」→ 空字符串)与 `ends_at` 不早于 `starts_at` 的前端校验。
|
||||
- [ ] 4.5 详情(弹窗/抽屉)展示完整配置(含 version、frequency_text、enabled_text、creator/updater)。
|
||||
|
||||
## 5. Permissions And UX
|
||||
|
||||
- [ ] 5.1 按钮/入口按 `h5_popup_configuration:list/create/update/enable/disable` 接入 `usePermission`/`v-permission`,无权限不渲染。
|
||||
- [ ] 5.2 固定提示:停用「停用后停止新投放,历史通知在展示期内仍可见」;启用「启用后参与候选匹配」;403 文案原文透传。
|
||||
- [ ] 5.3 表单校验与请求失败提示稳定,不破坏列表渲染。
|
||||
|
||||
## 6. Verification
|
||||
|
||||
- [ ] 6.1 `vue-tsc --noEmit` 与新增文件 eslint 通过。
|
||||
- [ ] 6.2 列表筛选/分页/倒序展示正确。
|
||||
- [ ] 6.3 创建必填校验、范围全量语义、编辑版本 +1 提示正确。
|
||||
- [ ] 6.4 启用/停用流程与提示正确;停用不清数据;启停不递增版本、刷新最近更新时间。
|
||||
- [ ] 6.5 代理/企业/个人看不到入口;直达路由 403 文案透传。
|
||||
- [ ] 6.6 联调:编辑整表提交后版本 +1、启停返回完整配置并刷新行;`ends_at` 早于 `starts_at` 被后端拒绝。
|
||||
51
openspec/changes/add-package-traffic-alert/design.md
Normal file
51
openspec/changes/add-package-traffic-alert/design.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Design: 套餐真流量预警前端对接
|
||||
|
||||
## 背景
|
||||
|
||||
后端已按 `docs/产品迭代8月份/套餐真流量预警_API_前端简版.md` 在测试环境上线 6 个接口(released)。前端需要新增规则配置与预警记录两个页面,并接入既有权限与导出任务体系。本设计记录关键决策。
|
||||
|
||||
## 决策
|
||||
|
||||
### 1. 页面挂载位置:套餐管理分组
|
||||
|
||||
规则数据源是套餐商品(`package_id`),预警记录也按套餐筛选,因此两个页面都挂在既有 `/package-management` 路由分组下,与套餐列表、代理系列授权平级。
|
||||
|
||||
- 路由:`/package-management/traffic-alert-rules`、`/package-management/traffic-alerts`
|
||||
- 详情:`/package-management/traffic-alerts/detail/:id`(隐藏路由)
|
||||
|
||||
### 2. 导出走专用接口 + 专用弹窗,不复用 ExportTaskCreateDialog
|
||||
|
||||
既有 `ExportTaskCreateDialog` 调用通用 `POST /api/admin/export-tasks`,而真流量预警导出是专用端点 `POST /api/admin/package-traffic-alerts/export`,请求体为 `format` + 与列表一致的一组筛选参数。因此:
|
||||
|
||||
- 新建轻量弹窗(或扩展 `ExportTaskCreateDialog` 支持自定义 submit),展示“基于当前筛选条件全量导出,不仅导出当前分页数据”
|
||||
- 提交成功后提示“导出任务已创建”,并提供跳转既有导出任务列表页的入口,下载走既有能力
|
||||
- 导出任务列表若按场景过滤,需要新增场景配置;场景枚举值联调时与后端确认后落入 `EXPORT_TASK_SCENE_CONFIG`
|
||||
|
||||
### 3. 权限模型
|
||||
|
||||
- 遵循八月迭代约定:权限编码集中定义在 `AUGUST_PERMISSIONS.packageTrafficAlert`,页面/按钮通过 `v-permission` + `useAuth().hasAuth` 引用
|
||||
- 页面级:`rules_view` / `records_view` 控制菜单与按钮可见性(后端菜单权限同源)
|
||||
- 按钮级:规则创建/修改(含启停)、记录详情、导出分别独立编码
|
||||
- 403 策略:接口对无权限账号统一返回 403;列表接口 403 时提示“无权限访问”;详情越权按资源不可见处理(复用现有 404 类提示文案),不暴露资源存在性
|
||||
|
||||
### 4. 阈值输入校验
|
||||
|
||||
- 前端 `ElInputNumber`:`min=1`、`max=100`、`precision=2`
|
||||
- 创建时 `threshold_percent` 必填;修改时三字段均可选(后端按传入字段更新)
|
||||
- `remark` 创建/修改均限制 500 字符(`maxlength` + 计数器)
|
||||
|
||||
### 5. 快照与归属变化展示
|
||||
|
||||
- 列表与详情的业务字段均为触发时快照,仅 `business_user_group_names` 为当前值
|
||||
- 详情中 `shop_changed_since_trigger` / `owner_changed_since_trigger` 为 `true` 时,用 `ElAlert`(info)提示归属已变化,并展示当前店铺/业务员与快照值
|
||||
- 空值约定:无店铺/业务员时字段可能为 `null` 或空数组,统一渲染 `-`(业务员数组 join 展示,空数组显示 `-`)
|
||||
|
||||
### 6. 规则列表的 real_data_mb 展示
|
||||
|
||||
- `real_data_mb` 为套餐商品当前真流量额度,仅用于配置校验展示(如“按 80% 约对应 x GB”),不作为预警分母
|
||||
- 页面以 GB 展示(`/1024`,保留两位小数),避免 MB 数字过长
|
||||
|
||||
## 风险
|
||||
|
||||
- 记录列表/详情接口字段名未完整给出,联调时以测试环境实际返回为准,类型定义需保留一定弹性(可选字段)
|
||||
- 导出任务场景值未给出,若后端未登记场景枚举,导出任务列表页的场景筛选需兼容新值
|
||||
93
openspec/changes/add-package-traffic-alert/proposal.md
Normal file
93
openspec/changes/add-package-traffic-alert/proposal.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Change: 新增套餐真流量预警(规则配置 + 预警记录)
|
||||
|
||||
## Why
|
||||
|
||||
根据 `docs/产品迭代8月份/套餐真流量预警_API_前端简版.md`,后端已在测试环境(`https://cmp-api.boss160.cn`)实现套餐真流量预警能力:套餐商品的真流量使用量达到配置阈值后生成预警记录,并通知对应业务员。共 6 个接口,仅超级管理员/平台账号可访问,其他账号返回 403,通用响应为 `code / data / msg / timestamp`。
|
||||
|
||||
当前前端(套餐管理模块)缺少:
|
||||
|
||||
1. 真流量预警规则配置入口:查看、创建、修改规则(阈值 1~100 允许两位小数、启停、备注),以及同一套餐商品仅一条规则的限制提示。
|
||||
2. 预警记录查看:支持套餐、店铺、业务员、资产类型、资产关键词、阈值、触发时间、通知状态 8 项筛选的列表,触发时快照详情(含归属是否变化提示),以及复用既有异步导出任务体系的记录导出。
|
||||
|
||||
此变更完成上述 6 个接口的前端对接。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 1. API 层
|
||||
|
||||
- **新增**: `src/api/modules/packageTrafficAlert.ts`(`PackageTrafficAlertService`,继承 BaseService)
|
||||
- `getAlertRules` — `GET /api/admin/package-traffic-alert-rules`(`package_id`、`enabled`、`page`、`page_size`)
|
||||
- `createAlertRule` — `POST /api/admin/package-traffic-alert-rules`(`package_id`、`threshold_percent`、`enabled`、`remark`)
|
||||
- `updateAlertRule` — `PUT /api/admin/package-traffic-alert-rules/{id}`(`threshold_percent`、`enabled`、`remark`,均为可选)
|
||||
- `getAlertRecords` — `GET /api/admin/package-traffic-alerts`(8 项筛选 + 分页)
|
||||
- `getAlertRecordDetail` — `GET /api/admin/package-traffic-alerts/{id}`
|
||||
- `exportAlertRecords` — `POST /api/admin/package-traffic-alerts/export`(`format` + 与列表一致的筛选参数)
|
||||
- **修改**: `src/api/modules/index.ts` — 导出 `PackageTrafficAlertService`
|
||||
|
||||
### 2. 类型定义
|
||||
|
||||
- **新增**: `src/types/api/packageTrafficAlert.ts` — 规则/记录/导出请求响应类型,字段与接口文档保持 snake_case
|
||||
- **修改**: `src/types/api/index.ts` — 导出新类型
|
||||
|
||||
### 3. 常量与权限
|
||||
|
||||
- **修改**: `src/config/constants/augustIteration.ts` — `AUGUST_PERMISSIONS` 新增 `packageTrafficAlert` 权限组
|
||||
- `trafficAlertRules: 'package_traffic_alert:rules_view'`
|
||||
- `trafficAlertRuleCreate: 'package_traffic_alert:rule_create'`
|
||||
- `trafficAlertRuleUpdate: 'package_traffic_alert:rule_update'`
|
||||
- `trafficAlertRecords: 'package_traffic_alert:records_view'`
|
||||
- `trafficAlertRecordDetail: 'package_traffic_alert:record_detail'`
|
||||
- `trafficAlertExport: 'package_traffic_alert:export'`
|
||||
- **新增/修改**: 通知状态枚举常量(1 已通知 / 2 待投递 / 3 投递失败 / 4 未通知(接收人已失效)/ 5 未通知(无有效业务员))及对应 tag 类型映射
|
||||
- 若既有导出任务列表需要区分本场景,补充对应导出场景配置(场景值联调时与后端确认)
|
||||
|
||||
### 4. 页面
|
||||
|
||||
**预警规则页** `src/views/package-management/traffic-alert-rules/index.vue`
|
||||
|
||||
- 列表:套餐名称、当前真流量额度(`real_data_mb`,按 GB 展示,仅作参考、不作为预警分母)、阈值百分比、启用状态、备注、更新时间
|
||||
- 筛选:套餐(`package_id`)、启用状态(`enabled`,不传查全部);分页默认 20、最大 100
|
||||
- 新增/编辑弹窗:套餐选择器、阈值(1~100,两位小数)、启用开关、备注(最多 500 字符)
|
||||
- 约束:同一套餐商品最多一条规则,后端拒绝重复创建时前端展示错误信息
|
||||
- 修改规则不影响既有预警快照;停用后扫描不再创建新预警(页面文案说明)
|
||||
|
||||
**预警记录页** `src/views/package-management/traffic-alerts/index.vue`
|
||||
|
||||
- 筛选:套餐、店铺、业务员、资产类型、资产关键词、阈值(两位小数)、触发时间范围(RFC3339)、通知状态
|
||||
- 列表:除 `business_user_group_names` 外均为触发时快照;`notification_status` 按枚举展示
|
||||
- 详情:展示快照字段;`shop_changed_since_trigger` / `owner_changed_since_trigger` 为真时提示“触发后店铺/业务员归属已变化”;`null` 或空数组统一显示 `-`
|
||||
- 导出:弹窗选择格式(xlsx/csv),携带当前筛选条件调用专用导出接口创建异步任务;创建成功后提示到既有“导出任务列表”下载
|
||||
- 越权查询详情:统一按资源不可见处理(与不存在资源一致的提示)
|
||||
|
||||
### 5. 路由与菜单
|
||||
|
||||
- **修改**: `src/router/routesAlias.ts` — 新增 `TrafficAlertRules`、`TrafficAlerts`、`TrafficAlertDetail` 别名
|
||||
- **修改**: `src/router/routes/asyncRoutes.ts` — 套餐管理分组下新增两个子路由(记录详情用隐藏路由)
|
||||
- **修改**: `src/locales/langs/zh.json` / `src/locales/langs/en.json` — `menus.packageManagement` 新增 `trafficAlertRules`、`trafficAlerts`、`trafficAlertDetail`
|
||||
|
||||
## Impact
|
||||
|
||||
### 受影响的规范
|
||||
|
||||
- `package-traffic-alert` — 新增能力
|
||||
|
||||
### 受影响的代码
|
||||
|
||||
- `src/api/modules/packageTrafficAlert.ts`(新增)、`src/api/modules/index.ts`
|
||||
- `src/types/api/packageTrafficAlert.ts`(新增)、`src/types/api/index.ts`
|
||||
- `src/config/constants/augustIteration.ts`、导出场景/通知状态相关常量
|
||||
- `src/views/package-management/traffic-alert-rules/index.vue`(新增)
|
||||
- `src/views/package-management/traffic-alerts/index.vue`、`detail.vue`(新增)
|
||||
- `src/router/routesAlias.ts`、`src/router/routes/asyncRoutes.ts`
|
||||
- `src/locales/langs/zh.json`、`src/locales/langs/en.json`
|
||||
|
||||
### 依赖关系
|
||||
|
||||
- 依赖后端 6 个接口在测试环境可用(文档标记 released)
|
||||
- 复用既有基础设施:`BaseService`/request 封装、`useAuth` + `v-permission`、`PackageSelector`、店铺/业务员选择组件、既有导出任务列表下载能力
|
||||
|
||||
### 注意事项
|
||||
|
||||
- 文档声明 6 个接口,但简版仅详细给出 4 个(规则列表/创建/修改 + 记录导出);预警记录列表与详情两个接口以“前端注意事项”的筛选项、快照字段与导出筛选字段为准,字段名在联调时与测试环境核对
|
||||
- 全部接口对非超级管理员/平台账号返回 403:菜单可见性由后端菜单权限控制,前端对 403 做友好提示,详情越权按资源不可见处理
|
||||
- 无破坏性变更:新增页面、API 模块、类型、权限编码均为增量
|
||||
@@ -0,0 +1,132 @@
|
||||
# Package Traffic Alert Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 套餐真流量预警规则列表查询
|
||||
|
||||
系统 SHALL 提供套餐真流量预警规则列表查询页面,展示规则与套餐商品关联信息,支持按套餐商品与启用状态筛选和分页。
|
||||
|
||||
#### Scenario: 查询全部规则
|
||||
|
||||
- **WHEN** 具备权限的用户(超级管理员/平台账号)访问预警规则页面
|
||||
- **THEN** 系统调用 `GET /api/admin/package-traffic-alert-rules` 加载规则列表
|
||||
- **AND** 列表展示套餐名称、当前真流量额度(`real_data_mb`,按 GB 展示)、阈值百分比、启用状态(含中文状态名)、备注、最近更新时间
|
||||
- **AND** 分页默认每页 20 条,最大 100 条
|
||||
|
||||
#### Scenario: 按条件筛选
|
||||
|
||||
- **WHEN** 用户选择套餐商品或启用状态进行筛选
|
||||
- **THEN** 系统携带 `package_id` / `enabled` 查询参数重新加载列表
|
||||
- **AND** 不传启用状态时查询全部规则
|
||||
|
||||
#### Scenario: 无权限访问
|
||||
|
||||
- **WHEN** 非超级管理员/平台账号调用规则接口
|
||||
- **THEN** 接口返回 403
|
||||
- **AND** 页面展示无权限访问提示,不展示业务数据
|
||||
|
||||
### Requirement: 创建套餐真流量预警规则
|
||||
|
||||
系统 SHALL 允许管理员为套餐商品创建真流量预警规则,并执行阈值与备注校验;同一套餐商品最多一条规则。
|
||||
|
||||
#### Scenario: 成功创建规则
|
||||
|
||||
- **WHEN** 用户选择套餐商品,填写阈值百分比(1~100,允许两位小数)
|
||||
- **AND** 设置启用状态与备注(最多 500 字符)后提交
|
||||
- **THEN** 系统调用 `POST /api/admin/package-traffic-alert-rules` 创建规则
|
||||
- **AND** 成功后刷新列表并展示新规则详情(含套餐名称、当前真流量额度、更新时间)
|
||||
|
||||
#### Scenario: 阈值校验
|
||||
|
||||
- **WHEN** 用户填写的阈值小于 1、大于 100 或超过两位小数
|
||||
- **THEN** 前端阻止提交并展示校验错误提示
|
||||
|
||||
#### Scenario: 重复规则
|
||||
|
||||
- **WHEN** 用户为已存在规则的套餐商品再次创建规则
|
||||
- **THEN** 后端拒绝创建
|
||||
- **AND** 前端展示后端返回的错误信息,列表保持原状
|
||||
|
||||
### Requirement: 修改套餐真流量预警规则
|
||||
|
||||
系统 SHALL 允许管理员修改规则的阈值、启用状态与备注;修改不影响既有预警快照,停用后扫描不再创建新预警。
|
||||
|
||||
#### Scenario: 修改规则字段
|
||||
|
||||
- **WHEN** 用户打开编辑弹窗并修改阈值、启用状态或备注后提交
|
||||
- **THEN** 系统调用 `PUT /api/admin/package-traffic-alert-rules/{id}`,仅提交修改的字段
|
||||
- **AND** 成功后刷新列表展示最新规则
|
||||
|
||||
#### Scenario: 停用规则
|
||||
|
||||
- **WHEN** 用户关闭规则的启用开关
|
||||
- **THEN** 系统调用修改接口仅提交 `enabled=false`
|
||||
- **AND** 页面说明停用后不再产生新预警,既有预警记录保留
|
||||
|
||||
#### Scenario: 规则不存在或无权限
|
||||
|
||||
- **WHEN** 修改的规则 ID 不存在或用户无权限
|
||||
- **THEN** 系统按接口错误处理并展示对应提示
|
||||
|
||||
### Requirement: 套餐真流量预警记录列表查询
|
||||
|
||||
系统 SHALL 提供预警记录列表页面,支持套餐、店铺、业务员、资产类型、资产关键词、阈值、触发时间范围、通知状态 8 项筛选与分页;列表数据除 `business_user_group_names` 外均为触发时快照。
|
||||
|
||||
#### Scenario: 查询预警记录
|
||||
|
||||
- **WHEN** 具备权限的用户访问预警记录页面
|
||||
- **THEN** 系统调用 `GET /api/admin/package-traffic-alerts` 加载记录列表
|
||||
- **AND** 列表展示触发时间、资产信息、套餐、阈值快照、店铺、业务员与通知状态
|
||||
|
||||
#### Scenario: 组合筛选
|
||||
|
||||
- **WHEN** 用户组合使用任意筛选条件(含两位小数阈值与 RFC3339 触发时间范围)
|
||||
- **THEN** 系统携带对应查询参数请求列表,返回满足全部条件的记录
|
||||
|
||||
#### Scenario: 通知状态展示
|
||||
|
||||
- **WHEN** 记录包含 `notification_status`
|
||||
- **THEN** 系统按枚举展示:1 已通知 / 2 待投递 / 3 投递失败 / 4 未通知(接收人已失效)/ 5 未通知(无有效业务员)
|
||||
|
||||
#### Scenario: 空值展示
|
||||
|
||||
- **WHEN** 记录的店铺或业务员字段为 `null` 或空数组
|
||||
- **THEN** 系统统一展示 `-`
|
||||
- **AND** `business_user_group_names` 为当前归属值,其余字段保持触发时快照
|
||||
|
||||
### Requirement: 查看预警记录详情
|
||||
|
||||
系统 SHALL 允许管理员查看单条预警记录的触发时快照详情,并标识触发后店铺/业务员归属是否发生变化;越权查询按资源不可见处理。
|
||||
|
||||
#### Scenario: 查看存在的记录
|
||||
|
||||
- **WHEN** 用户点击记录打开详情
|
||||
- **THEN** 系统调用 `GET /api/admin/package-traffic-alerts/{id}` 展示触发时快照字段
|
||||
|
||||
#### Scenario: 归属变化提示
|
||||
|
||||
- **WHEN** 详情返回 `shop_changed_since_trigger` 或 `owner_changed_since_trigger` 为 true
|
||||
- **THEN** 系统提示触发后店铺/业务员归属已变化
|
||||
- **AND** 展示当前归属与触发时快照值的差异
|
||||
|
||||
#### Scenario: 越权或不存在
|
||||
|
||||
- **WHEN** 用户无权限查看该记录或记录不存在
|
||||
- **THEN** 系统统一按资源不可见处理
|
||||
- **AND** 展示与记录不存在一致的提示,不暴露资源存在性
|
||||
|
||||
### Requirement: 导出套餐真流量达量预警
|
||||
|
||||
系统 SHALL 允许管理员按当前筛选条件创建预警记录异步导出任务;导出接口仅创建任务,文件通过既有导出任务列表下载。
|
||||
|
||||
#### Scenario: 创建导出任务
|
||||
|
||||
- **WHEN** 用户在预警记录页点击导出并选择格式(xlsx/csv)
|
||||
- **THEN** 系统携带 `format` 与当前筛选条件调用 `POST /api/admin/package-traffic-alerts/export`
|
||||
- **AND** 成功后展示任务信息(`task_id`、`task_no`、状态)并引导用户到既有导出任务列表下载
|
||||
|
||||
#### Scenario: 导出范围说明
|
||||
|
||||
- **WHEN** 导出弹窗打开
|
||||
- **THEN** 系统说明导出基于当前筛选条件全量导出,不仅导出当前分页数据
|
||||
- **AND** 任务创建时冻结筛选条件、时间范围与可见资产范围,归属列按执行时当前归属补充
|
||||
40
openspec/changes/add-package-traffic-alert/tasks.md
Normal file
40
openspec/changes/add-package-traffic-alert/tasks.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Tasks: 套餐真流量预警前端对接
|
||||
|
||||
## 1. API 与类型
|
||||
|
||||
- [x] 1.1 新增 `src/types/api/packageTrafficAlert.ts`:规则列表/规则项、创建/修改参数、预警记录列表/记录项、详情、导出请求/响应类型(snake_case,对齐接口文档;记录字段按前端注意事项预留可选)
|
||||
- [x] 1.2 `src/types/api/index.ts` 导出新类型
|
||||
- [x] 1.3 新增 `src/api/modules/packageTrafficAlert.ts`:`getAlertRules` / `createAlertRule` / `updateAlertRule` / `getAlertRecords` / `getAlertRecordDetail` / `exportAlertRecords` 6 个方法
|
||||
- [x] 1.4 `src/api/modules/index.ts` 导出 `PackageTrafficAlertService`
|
||||
|
||||
## 2. 常量与权限
|
||||
|
||||
- [x] 2.1 `src/config/constants/augustIteration.ts`:`AUGUST_PERMISSIONS` 新增 `packageTrafficAlert` 权限组(rules_view / rule_create / rule_update / records_view / record_detail / export)
|
||||
- [x] 2.2 新增通知状态常量:枚举 1-5 文案(已通知/待投递/投递失败/未通知(接收人已失效)/未通知(无有效业务员))及 tag 类型映射
|
||||
- [x] 2.3 若导出任务列表需要场景区分,补充真流量预警导出场景配置(场景值与后端确认后更新 `EXPORT_TASK_SCENE_CONFIG` 及 `getExportTaskSceneName`)
|
||||
|
||||
## 3. 预警规则页
|
||||
|
||||
- [x] 3.1 新增 `src/views/package-management/traffic-alert-rules/index.vue`:筛选栏(套餐选择器、启用状态)、列表(套餐名称、真流量额度 GB、阈值、启用状态、备注、更新时间)、分页(默认 20/最大 100)
|
||||
- [x] 3.2 新增/编辑弹窗:套餐选择器(编辑时不可改)、阈值 ElInputNumber(1-100,两位小数,创建必填)、启用开关、备注(≤500 字符带计数);创建成功提示同一套餐最多一条规则
|
||||
- [x] 3.3 启用状态行内开关(调用修改接口,仅传 enabled),文案说明“停用后不再产生新预警,既有预警保留”
|
||||
- [x] 3.4 403/错误处理:无权限提示;重复创建等后端错误展示 msg
|
||||
|
||||
## 4. 预警记录页
|
||||
|
||||
- [x] 4.1 新增 `src/views/package-management/traffic-alerts/index.vue`:筛选栏(套餐、店铺、业务员、资产类型、资产关键词、阈值、触发时间范围、通知状态)、列表(触发时快照字段 + `business_user_group_names` 当前值 + 通知状态 tag)、分页
|
||||
- [x] 4.2 新增 `detail.vue`(或详情抽屉):快照字段展示、`shop_changed_since_trigger` / `owner_changed_since_trigger` 变化提示、空值/空数组显示 `-`、越权按资源不可见处理
|
||||
- [x] 4.3 导出弹窗:格式选择(xlsx/csv)+“基于当前筛选条件全量导出”说明,调用 `exportAlertRecords` 携带当前筛选,成功后提示并引导跳转导出任务列表下载
|
||||
|
||||
## 5. 路由、菜单与国际化
|
||||
|
||||
- [x] 5.1 `src/router/routesAlias.ts`:新增 `TrafficAlertRules` / `TrafficAlerts` / `TrafficAlertDetail`
|
||||
- [x] 5.2 `src/router/routes/asyncRoutes.ts`:套餐管理分组下新增规则页、记录页(keepAlive)与隐藏详情路由
|
||||
- [x] 5.3 `src/locales/langs/zh.json` / `en.json`:`menus.packageManagement` 新增 `trafficAlertRules` / `trafficAlerts` / `trafficAlertDetail`
|
||||
|
||||
## 6. 联调与验收
|
||||
|
||||
- [ ] 6.1 测试环境(`https://cmp-api.boss160.cn`)联调 6 个接口,核对记录列表/详情实际字段名并修正类型
|
||||
- [ ] 6.2 权限验证:超级管理员/平台账号正常访问;其他账号 403 提示符合预期;详情越权按资源不可见
|
||||
- [ ] 6.3 边界验证:阈值 0.99/1/100/100.01 校验、备注 500 字符、同套餐重复创建、空店铺/业务员展示 `-`、导出任务创建后可在导出任务列表下载
|
||||
- [x] 6.4 ESLint / Stylelint / `vue-tsc` 类型检查通过
|
||||
@@ -53,6 +53,7 @@
|
||||
- [x] 6.4 支付失败后不自动重放同一支付请求;用户主动重新发起一笔支付时按支付接口约定创建新的请求,不展示内部路由过程。
|
||||
- 映射函数显式不做任何路由/重试逻辑;调用方按需发起新请求。
|
||||
- [ ] 6.5 若客户支付端位于本仓库之外的 H5、小程序或 App 工程,将本节的错误码和文案要求同步到对应工程,并登记联调责任方。
|
||||
- 按需求方指示 H5/小程序/App 客户端不在本仓库处理,本项保持未勾选,由对应工程跟进接入 `resolvePaymentFailureMessage`。
|
||||
- 待联调责任方(前端/H5/小程序/App)接入 `resolvePaymentFailureMessage` 并完成文案与错误码校验。
|
||||
|
||||
## 7. 验证
|
||||
|
||||
37
openspec/changes/add-phone-asset-unbind-management/design.md
Normal file
37
openspec/changes/add-phone-asset-unbind-management/design.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Design: 手机号资产关联管理(管理后台)
|
||||
|
||||
## 页面结构
|
||||
|
||||
- 资产管理下新增「手机号资产关联」分组:
|
||||
- 关联列表页(`phone-asset-association/index.vue`)
|
||||
- CSV 解绑导入任务列表页(`unbind-import-tasks/index.vue`)
|
||||
- 解绑导入任务详情页(`unbind-import-tasks/detail.vue`),复用 `device-task` / `iot-card-task` 的逐行结果表格模式。
|
||||
- 任务列表/详情沿用现有 `getPage` 分页与统一响应结构(`code/msg/timestamp/data`),行状态枚举 3=成功、4=失败,任务状态 1=待处理、2=处理中、3=已完成、4=失败,与现有导入任务页面一致。
|
||||
|
||||
## 接口与类型
|
||||
|
||||
- 新增 `src/api/modules/phoneAsset.ts` 与 `src/types/api/phoneAsset.ts`,集中放置 6 个接口与相关 DTO 类型,便于联调收敛(与 `add-employee-collection` 契约处理方式一致)。
|
||||
- 既有列表的 `associated_phones` 直接在 `device.ts` / `card.ts` / `asset.ts` 对应响应类型上增加字段,不新增接口。
|
||||
- `FilePurpose` 增加 `phone_unbind_import`,上传前在业务组件校验扩展名 `.csv`。
|
||||
|
||||
## 解绑导入流程
|
||||
|
||||
- 上传复用 `StorageService.getUploadUrl`(`purpose='phone_unbind_import'`)→ 预签名 URL PUT 直传 → 用返回的 `file_key` 调创建任务接口。
|
||||
- CSV 模板由前端提供:表头 `资产标识,备注`;UTF-8(可带 BOM),非 UTF-8 按 GBK 解码(后端解析);无行数上限,前端不做行数限制,仅限制扩展名。
|
||||
- 任务创建后进入任务列表跟踪,与导出任务交互模式一致。
|
||||
|
||||
## 权限与可见性
|
||||
|
||||
- 菜单、路由、按钮统一用 `usePermission`(`isSuperAdmin` / `isPlatformAccount`)+ `v-permission` 控制;入口仅超管/平台可见。
|
||||
- 越权兜底由后端 403 保证,前端将「无权限操作该资源或资源不存在」等固定文案原样透传,不自行拼接。
|
||||
- 不提供任何创建/补录关联入口。
|
||||
|
||||
## 待确认
|
||||
|
||||
- `DELETE /phone-asset-associations/{id}` 的 `reason` / `confirmed` 字段名与传输位置:该接口文档块只声明路径参数 `id`、未定义请求体,前端当前按 query 参数(`?reason=&confirmed=`)传递;若后端以 JSON body 接收,只需改 `PhoneAssetAssociationService.unbindAssociation`,页面无需调整。
|
||||
- 新增菜单/按钮权限编码(前端默认 `phone_asset_association:*`)以管理后台权限配置为准。
|
||||
|
||||
已确认的契约(文档已提供):
|
||||
|
||||
- `GET /phone-asset-associations/unbind-imports`:筛选参数 `page`(默认 1)/ `page_size`(默认 20,最大 100)/ `status`(1–4),响应 `items/page/size/total`。
|
||||
- 任务状态 1=待处理、2=处理中、3=已完成、4=失败;逐行状态 3=成功、4=失败。
|
||||
@@ -0,0 +1,48 @@
|
||||
# Change: 新增手机号资产关联管理(管理后台)
|
||||
|
||||
## Why
|
||||
|
||||
H5 短信验证通过后会产生「手机号—资产」关联,运营侧需要治理能力:查询存量关联、单条/批量/按 CSV 导入批量解绑,并在设备、单卡、资产详情等既有场景里直接看到该资产当前关联的手机号。目前管理后台没有任何关联查询与解绑入口,只能走数据库操作。
|
||||
|
||||
同时对象存储与导出契约需要同步扩展:上传用途新增 `phone_unbind_import`,卡/设备导出文件表头尾部新增「关联手机号」列。
|
||||
|
||||
本 Change 只覆盖管理后台(B 端);H5 端的 `bind-phone` / `change-phone` / `need_bind_phone` 接口变更不在本 Change 范围,但「上限」与「换绑冲突」两句固定文案由后端统一返回、前端直接展示的能力需要保留。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增“手机号资产关联”能力,仅超管/平台账号可见可操作(代理/企业/个人由后端 403 兜底):
|
||||
- `GET /api/admin/phone-asset-associations`:关联列表,支持资产标识(ICCID/虚拟号/IMEI/SN/接入号,精确匹配)、完整手机号(精确匹配)、状态(0 已失效 / 1 有效)、创建时间区间筛选,分页返回。
|
||||
- `DELETE /api/admin/phone-asset-associations/{id}`:单条解绑,必须传 `reason`(1–500) + `confirmed=true`,缺一即拒绝;后端返回本次解除关系数。
|
||||
- `POST /api/admin/phone-asset-associations/batch-unbind`:按资产集合批量解绑(按 `(asset_type, asset_id)` 去重,逐项独立执行),返回成功数/失败数/逐项结果,部分成功不回滚。
|
||||
- `POST /api/admin/phone-asset-associations/unbind-imports`:创建 CSV 解绑导入任务(`file_key` + 任务级 `reason` + `confirmed`)。
|
||||
- `GET /api/admin/phone-asset-associations/unbind-imports`:解绑导入任务列表。
|
||||
- `GET /api/admin/phone-asset-associations/unbind-imports/{id}`:任务详情,含逐行状态/失败原因与解绑当时完整手机号快照。
|
||||
- 既有接口字段新增:`GET /api/admin/devices`、`GET /api/admin/iot-cards/standalone`、`GET /api/admin/assets/resolve/{identifier}` 响应新增 `associated_phones: string[]`(完整手机号,无关联为空数组)。
|
||||
- 卡导出、设备导出文件表头尾部新增「关联手机号」列(多号以「、」连接);历史任务重导出仍按旧表头输出,前端导出任务列表/详情无需为此改动。
|
||||
- `POST /api/admin/storage/upload-url` 的 `purpose` 枚举新增 `phone_unbind_import`(仅 .csv,目录前缀 `phone-unbind-imports/YYYY/MM/DD/uuid.csv`)。
|
||||
- 前端自备能力:CSV 模板(表头 `资产标识,备注`,备注可选;UTF-8 可带 BOM,非 UTF-8 按 GBK 解码;无行数上限);后端返回的固定失败文案直接展示;后台不提供「创建/补录关联」入口。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs:
|
||||
- `phone-asset-association-management`
|
||||
- Affected code:
|
||||
- `src/api/modules/phoneAsset.ts`(新增)
|
||||
- `src/types/api/phoneAsset.ts`(新增)
|
||||
- `src/api/modules/index.ts`、`src/types/api/index.ts`
|
||||
- `src/api/modules/storage.ts`(`FilePurpose` 增加 `phone_unbind_import`)
|
||||
- `src/api/modules/device.ts`、`src/api/modules/card.ts`、`src/api/modules/asset.ts`(`associated_phones` 字段类型)
|
||||
- `src/types/api/device.ts`、`src/types/api/card.ts`、`src/types/api/asset.ts`
|
||||
- `src/views/asset-management/phone-asset-association/`(新增列表、解绑导入任务列表/详情页)
|
||||
- `src/views/asset-management/device-list/index.vue`、`src/views/asset-management/iot-card-management/*`、`src/views/asset-management/asset-information/*`(新增「关联手机号」展示)
|
||||
- `src/router/routesAlias.ts`、`src/router/routes/asyncRoutes.ts`(菜单与路由)
|
||||
- 菜单、权限码与国际化文案配置文件
|
||||
- Dependencies:
|
||||
- 后端按 `docs/产品迭代8月份/手机号资产关联.md` 提供上述接口、Bearer JWT 鉴权与 403 语义。
|
||||
- 解绑导入复用现有对象存储上传流程(`StorageService.getUploadUrl` + PUT 直传 + `file_key`)。
|
||||
- 新增按钮/页面的权限编码以管理后台菜单权限配置为准,前端用 `v-permission` 与 `usePermission` 控制可见性。
|
||||
- Breaking changes:
|
||||
- 无。全部为新增页面、接口模块、类型字段与枚举扩展。
|
||||
- 待确认契约(当前文档未完整提供,后端需在联调前明确):
|
||||
- `DELETE /phone-asset-associations/{id}` 的 `reason` / `confirmed` 传输位置(请求体字段名,当前导出文档缺少该请求体定义)。
|
||||
- `GET /phone-asset-associations/unbind-imports` 任务列表的筛选参数与分页响应结构(当前导出文档未包含该接口块)。
|
||||
@@ -0,0 +1,177 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Phone-Asset Association List Page
|
||||
|
||||
The admin frontend SHALL provide a phone-asset association list page accessible only to super admin and platform accounts, listing phone–asset relationships created via H5 SMS verification.
|
||||
|
||||
#### Scenario: Query association list
|
||||
|
||||
- **GIVEN** 用户打开「手机号资产关联」列表页
|
||||
- **WHEN** 用户提交分页、资产标识、手机号、状态或创建时间范围筛选条件
|
||||
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations`
|
||||
- **AND** 系统 MUST support query parameters `page`, `page_size`, `asset_identifier`, `phone`, `status`, `created_at_start`, and `created_at_end`
|
||||
- **AND** 系统 MUST parse `data.page`, `data.size`, `data.total`, and `data.items` from the response
|
||||
- **AND** 资产标识 MUST 支持 ICCID、虚拟号、IMEI、SN 或接入号精确匹配;手机号 MUST 使用完整值精确匹配
|
||||
|
||||
#### Scenario: Render association table columns
|
||||
|
||||
- **GIVEN** 关联列表接口返回 `items`
|
||||
- **WHEN** 表格渲染每一行关联
|
||||
- **THEN** 系统 MUST 展示资产类型/ID/标识(`asset_type`、`asset_id`、`asset_identifier`)、完整手机号(`phone`)、建立时间(`established_at`)、建立来源(`source`,中文映射固定为 H5 短信验证)与状态
|
||||
- **AND** 系统 MUST 用 `status_name` 展示状态中文名称
|
||||
- **AND** 对已失效关系(`status=0`)MUST 展示失效时间、失效方式(`invalidation_method_name`)与失效原因(`invalidation_reason`,可为空)
|
||||
|
||||
#### Scenario: Vehicle the 403 semantics
|
||||
|
||||
- **GIVEN** 非超管/平台账号访问关联列表或任何解绑接口
|
||||
- **WHEN** 后端返回 403
|
||||
- **THEN** 前端 MUST 直接展示后端文案「无权限操作该资源或资源不存在」
|
||||
- **AND** 前端 MUST NOT 区分越权、资产不存在与已无有效关系(后端明确不形成可枚举差异)
|
||||
|
||||
### Requirement: Single Association Unbind
|
||||
|
||||
The admin frontend SHALL support unbinding a single phone–asset association with a mandatory reason and confirmation.
|
||||
|
||||
#### Scenario: Unbind single association with reason and confirmation
|
||||
|
||||
- **GIVEN** 用户在关联列表选择一条有效关联执行解绑
|
||||
- **WHEN** 用户填写解除原因(1–500 字符)并勾选二次确认后提交
|
||||
- **THEN** 系统 MUST call `DELETE /api/admin/phone-asset-associations/{id}` with the association id in the path
|
||||
- **AND** 请求 MUST 携带 `reason` 与 `confirmed=true`,二者缺一即不发请求(前端表单校验兜底,后端兜底拒绝)
|
||||
- **AND** 系统 MUST 展示响应中的解除结果,包括失效时间与本次解除的有效关系数(`unbound_count`)
|
||||
|
||||
#### Scenario: Refuse without reason or confirmation
|
||||
|
||||
- **GIVEN** 用户未填写原因或未勾选二次确认
|
||||
- **WHEN** 用户点击确认解绑
|
||||
- **THEN** 前端 MUST 阻止提交并提示原因必填/需二次确认
|
||||
- **AND** 没有任何关系被解除的承诺 MUST 与后端说明一致,前端不调用接口
|
||||
|
||||
### Requirement: Batch Asset Unbind
|
||||
|
||||
The admin frontend SHALL support unbinding all current valid associations of multiple assets in one request with per-item results.
|
||||
|
||||
#### Scenario: Batch unbind selected assets
|
||||
|
||||
- **GIVEN** 用户勾选多个资产(列表或他处选择的资产集合)执行批量解绑
|
||||
- **WHEN** 用户填写解除原因并二次确认后提交
|
||||
- **THEN** 系统 MUST call `POST /api/admin/phone-asset-associations/batch-unbind`
|
||||
- **AND** 请求体 MUST 包含 `assets`(每项 `asset_type`+`asset_id`)、`confirmed=true` 与 `reason`
|
||||
- **AND** 请求前前端 MUST 按 `(asset_type, asset_id)` 去重并限制数量(后端上限 200,前端同步校验)
|
||||
- **AND** 系统 MUST 解析 `success_count`、`fail_count` 与逐项结果 `items`(每项含 `asset_id`、`asset_type`、`success`、失败 `reason` 与 `unbound_count`)
|
||||
- **AND** 部分成功 MUST 不回滚成功项,前端 MUST 按逐项结果展示成功/失败明细
|
||||
|
||||
#### Scenario: Show failure copy for failed items
|
||||
|
||||
- **GIVEN** 批量解绑存在失败项(越权、资产不存在或已无有效关系)
|
||||
- **WHEN** 逐项结果返回失败
|
||||
- **THEN** 前端 MUST 直接展示后端返回的失败文案
|
||||
- **AND** 失败项 MUST 可被定位到对应资产标识与类型
|
||||
|
||||
### Requirement: CSV Unbind Import Task
|
||||
|
||||
The admin frontend SHALL support creating, listing, and viewing phone-asset unbind import tasks driven by an uploaded CSV.
|
||||
|
||||
#### Scenario: Provide CSV template download
|
||||
|
||||
- **GIVEN** 用户进入「CSV 解绑导入」入口
|
||||
- **WHEN** 用户需要模板
|
||||
- **THEN** 系统 MUST 提供可下载的 CSV 模板,表头固定为 `资产标识,备注`,备注列为可选
|
||||
- **AND** 模板 MUST 以 UTF-8 编码(可带 BOM)
|
||||
|
||||
#### Scenario: Upload CSV and create unbind import task
|
||||
|
||||
- **GIVEN** 用户选择 CSV 文件(UTF-8 可带 BOM,非 UTF-8 按 GBK 解码;无行数上限)
|
||||
- **WHEN** 用户填写任务级解绑原因并二次确认后创建任务
|
||||
- **THEN** 系统 MUST 先调用 `POST /api/admin/storage/upload-url`(`purpose=phone_unbind_import`,仅接受 `.csv`)
|
||||
- **AND** 系统 MUST 使用预签名 URL 完成 PUT 直传,取回 `file_key`
|
||||
- **AND** 系统 MUST call `POST /api/admin/phone-asset-associations/unbind-imports`,请求体包含 `file_key`、任务级 `reason` 与 `confirmed=true`
|
||||
- **AND** 系统 MUST 展示创建返回的任务编号、状态(1 待处理 / 2 处理中 / 3 已完成 / 4 失败)与中文状态名
|
||||
|
||||
#### Scenario: List unbind import tasks
|
||||
|
||||
- **GIVEN** 用户打开「解绑导入任务」列表
|
||||
- **WHEN** 用户按分页/状态等条件查询
|
||||
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations/unbind-imports`
|
||||
- **AND** 列表 MUST 展示任务编号、文件名、状态、成功/失败行数、总数、创建人、创建/开始/完成时间与任务级解绑原因
|
||||
|
||||
#### Scenario: View unbind import task detail with row results
|
||||
|
||||
- **GIVEN** 用户在任务列表点击某个任务
|
||||
- **WHEN** 任务详情加载
|
||||
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations/unbind-imports/{id}`
|
||||
- **AND** 详情 MUST 展示任务级信息(含 `unbind_reason`、`error_message` 与任务统计)
|
||||
- **AND** 逐行结果 MUST 展示行号(`line`,自数据首行起计)、`asset_identifier` 原文、定位到的资产类型/ID(未定位时为空/0)、行状态(3 成功 / 4 失败)与失败原因
|
||||
- **AND** 成功行 MUST 展示解绑当时完整关联手机号快照(`associated_phones`)与该行解除的有效关系数(`unbound_count`)
|
||||
- **AND** 任务级失败时 `items` 为空数组,前端 MUST 展示任务的 `error_message`
|
||||
|
||||
### Requirement: Associated Phones Display on Existing Lists
|
||||
|
||||
The admin frontend SHALL display the current associated phone numbers of an asset on existing device, standalone IoT card, and asset resolve surfaces.
|
||||
|
||||
#### Scenario: Show associated phones on device list
|
||||
|
||||
- **GIVEN** `GET /api/admin/devices` 响应包含 `associated_phones`
|
||||
- **WHEN** 设备列表渲染
|
||||
- **THEN** 系统 MUST 展示「关联手机号」列,值为完整手机号数组(无关联显示为空/「-」)
|
||||
|
||||
#### Scenario: Show associated phones on standalone IoT card list
|
||||
|
||||
- **GIVEN** `GET /api/admin/iot-cards/standalone` 响应包含 `associated_phones`
|
||||
- **WHEN** 单卡列表渲染
|
||||
- **THEN** 系统 MUST 展示「关联手机号」列,多号以「、」分隔
|
||||
|
||||
#### Scenario: Show associated phones on asset resolve detail
|
||||
|
||||
- **GIVEN** `GET /api/admin/assets/resolve/{identifier}` 响应包含 `associated_phones`
|
||||
- **WHEN** 资产详情渲染
|
||||
- **THEN** 系统 MUST 展示「关联手机号」字段,无关联显示为空/「-」
|
||||
|
||||
### Requirement: Storage and Export Contract Extension
|
||||
|
||||
The admin frontend SHALL extend the upload-purpose enum and align with the export column change for card and device export files.
|
||||
|
||||
#### Scenario: Upload URL for phone unbind imports
|
||||
|
||||
- **GIVEN** 需要上传解绑导入 CSV
|
||||
- **WHEN** 调用 `POST /api/admin/storage/upload-url`
|
||||
- **THEN** 请求 MUST 使用 `purpose=phone_unbind_import`
|
||||
- **AND** `FilePurpose` 类型 MUST 增加 `phone_unbind_import`
|
||||
- **AND** 上传目录前缀为 `phone-unbind-imports/YYYY/MM/DD/uuid.csv`,前端不感知前缀,仅约束扩展名为 `.csv`
|
||||
|
||||
#### Scenario: Export files gain associated phone column
|
||||
|
||||
- **GIVEN** 卡导出/设备导出任务完成并生成文件
|
||||
- **WHEN** 用户下载导出文件
|
||||
- **THEN** 文件表头尾部 MUST 包含「关联手机号」列(多号以「、」连接)
|
||||
- **AND** 前端导出任务列表/详情页 MUST NOT 为此改动调用任何新接口
|
||||
- **AND** 历史任务重导出仍按旧表头输出,前端不需要兼容性处理
|
||||
|
||||
### Requirement: Role-Based Entry Visibility and No Creation Entry
|
||||
|
||||
The admin frontend SHALL gate all new phone-asset association pages and actions to super admin/platform accounts, and MUST NOT offer any create or supplement entry.
|
||||
|
||||
#### Scenario: Hide entries for non-platform roles
|
||||
|
||||
- **GIVEN** 当前用户为代理/企业/个人账号
|
||||
- **WHEN** 渲染菜单与页面按钮
|
||||
- **THEN** 导航菜单、解绑按钮与 CSV 导入入口 MUST 不渲染
|
||||
- **AND** 若有越权直达路由,后端 403 文案 MUST 原样展示
|
||||
|
||||
#### Scenario: No create/supplement entry
|
||||
|
||||
- **GIVEN** 用户打开「手机号资产关联」相关页面
|
||||
- **WHEN** 页面可用操作被枚举
|
||||
- **THEN** 页面 MUST NOT 提供创建或补录关联的入口
|
||||
- **AND** 关联只能由 H5 短信验证产生,前端默认不展示任何新增表单
|
||||
|
||||
### Requirement: Unify Backend-Returned Error Copy
|
||||
|
||||
The admin frontend SHALL render backend-provided fixed error copy verbatim without composing its own messages.
|
||||
|
||||
#### Scenario: Display fixed failure copy
|
||||
|
||||
- **GIVEN** 解绑相关接口返回失败
|
||||
- **WHEN** 失败文案为约定固定文案之一
|
||||
- **THEN** 前端 MUST 直接在弹窗/错误提示中展示「无权限操作该资源或资源不存在」
|
||||
- **AND** 对后端可能返回的「该手机号最多关联10项有效资产」「新手机号已存在与待迁移资产相同的有效关联,换绑已回滚」MUST 仅作透传展示,不在前端拼接或改写
|
||||
66
openspec/changes/add-phone-asset-unbind-management/tasks.md
Normal file
66
openspec/changes/add-phone-asset-unbind-management/tasks.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## 1. Contract Confirmation
|
||||
|
||||
- [x] 1.1 以 `docs/产品迭代8月份/手机号资产关联.md` 为准:该接口只定义路径参数 `id`、无请求体,故 `reason` / `confirmed` 按 query 参数传递,不再另行确认。
|
||||
- [x] 1.2 确认 `GET /phone-asset-associations/unbind-imports` 任务列表的筛选参数(分页/状态/时间?)与分页响应结构。
|
||||
- [x] 1.3 确认新增菜单、页面的权限编码(建议 `phone_asset:list`、`phone_asset:unbind`、`phone_asset:unbind_import`,以后端菜单配置为准)。
|
||||
- [x] 1.4 确认关联列表页菜单位置与路由归属(资产管理下新增分组或独立入口)。
|
||||
|
||||
## 2. API And Types
|
||||
|
||||
- [x] 2.1 新增 `src/api/modules/phoneAsset.ts`:关联列表、单条解绑、批量解绑、创建解绑导入任务、导入任务列表、导入任务详情。
|
||||
- [x] 2.2 新增 `src/types/api/phoneAsset.ts`:关联列表项/分页响应、单条解绑响应、批量解绑请求/逐项结果、导入任务列表项/详情/逐行结果(含 `associated_phones` 快照、行状态 3/4、任务状态 1-4)。
|
||||
- [x] 2.3 对齐统一响应结构 `code/msg/timestamp/data` 与分页结构 `page/size/total/items`。
|
||||
- [x] 2.4 `src/api/modules/storage.ts` 的 `FilePurpose` 增加 `phone_unbind_import`,上传前校验扩展名为 `.csv`。
|
||||
- [x] 2.5 在 `src/api/modules/index.ts`、`src/types/api/index.ts` 导出新模块/类型。
|
||||
- [x] 2.6 为 `GET /api/admin/devices`、`GET /api/admin/iot-cards/standalone`、`GET /api/admin/assets/resolve/{identifier}` 的响应类型增加 `associated_phones: string[]`(无关联为空数组)。
|
||||
|
||||
## 3. Association List Page
|
||||
|
||||
- [x] 3.1 在资产管理下新增「手机号资产关联」页面/路由/菜单(仅超管/平台可见)。
|
||||
- [x] 3.2 筛选区:资产标识(精确)、完整手机号(精确)、状态(0 已失效 / 1 有效)、创建时间区间;分页 `page/page_size`(默认 20,最大 100)。
|
||||
- [x] 3.3 表格列:资产类型/ID/标识、完整手机号、建立时间、建立来源(固定 H5 短信验证)、状态;已失效行展示失效时间/失效方式/失效原因。
|
||||
- [x] 3.4 提供模板下载与 CSV 导入入口(见第 5 节),列表行操作提供单条解绑按钮。
|
||||
|
||||
## 4. Unbind Operations
|
||||
|
||||
- [x] 4.1 单条解绑弹窗:必填原因(1–500,带长度校验)+ 勾选二次确认,缺一禁用提交;提交调用 `DELETE /phone-asset-associations/{id}`。
|
||||
- [x] 4.2 批量解绑弹窗:支持勾选资产集合,前端按 `(asset_type, asset_id)` 去重、上限 200 校验;请求体 `assets` + `reason` + `confirmed=true`。
|
||||
- [x] 4.3 批量结果展示:成功数/失败数 + 逐项明细(资产标识/类型/成功与否/失败原因/解除关系数),部分成功不回滚;失败文案直接展示后端返回。
|
||||
- [x] 4.4 解绑按钮仅超管/平台可见,`v-permission` 接入,403 文案透传展示。
|
||||
|
||||
## 5. CSV Unbind Import Tasks
|
||||
|
||||
- [x] 5.1 提供 CSV 模板下载:表头 `资产标识,备注`(备注可选),UTF-8(可带 BOM)。
|
||||
- [x] 5.2 上传流程:`StorageService.getUploadUrl({ purpose: 'phone_unbind_import' })` → PUT 直传 → `file_key` → 创建任务(`confirmed` + 任务级 `reason`)。
|
||||
- [x] 5.3 创建任务页/弹窗:展示文件解析约定(UTF-8 可带 BOM,非 UTF-8 按 GBK 解码,无行数上限),提交后跳转/提示任务编号与初始状态。
|
||||
- [x] 5.4 「解绑导入任务」列表页:分页 + 状态筛选,展示任务编号、文件名、状态、成功/失败行数、总数、创建人、时间与任务级 `unbind_reason`。
|
||||
- [x] 5.5 任务详情页:任务级信息(含 `error_message`、统计)+ 逐行表格(行号、资产标识原文、定位资产类型/ID、行状态 3/4、失败原因、`associated_phones` 快照、`unbound_count`);任务级失败时展示 `error_message` 且行表为空。
|
||||
- [x] 5.6 任务列表/详情仅超管/平台可见,按钮与入口受权限控制。
|
||||
|
||||
## 6. Existing Module Modification
|
||||
|
||||
- [x] 6.1 设备列表页新增「关联手机号」列(`associated_phones`,无关联显示「-」)。
|
||||
- [x] 6.2 IoT 单卡列表页新增「关联手机号」列(多号以「、」连接)。
|
||||
- [x] 6.3 资产详情(`assets/resolve`)新增「关联手机号」字段展示。
|
||||
- [x] 6.4 确认卡/设备导出文件新列由后端模板生成,前端导出任务列表/详情无需改动。
|
||||
|
||||
## 7. Routing, Permissions And UX
|
||||
|
||||
- [x] 7.1 新增路由、路由别名与菜单项,仅超管/平台账号可见。
|
||||
- [x] 7.2 所有新增页面/按钮接入 `usePermission`/`v-permission`,无权限不渲染,不依赖禁用态。
|
||||
- [x] 7.3 固定文案透传:「无权限操作该资源或资源不存在」「该手机号最多关联10项有效资产」「新手机号已存在与待迁移资产相同的有效关联,换绑已回滚」不前端拼接改写。
|
||||
- [x] 7.4 关联页不提供创建/补录入口。
|
||||
- [x] 7.5 操作失败提示稳定,不破坏列表渲染;上传/任务创建过程有 loading 与超时兜底。
|
||||
|
||||
## 8. Verification
|
||||
|
||||
- [x] 8.1 类型检查通过(vue-tsc --noEmit,即 npm run build 的类型阶段),新增文件 eslint 通过。
|
||||
- [x] 8.2 关联列表:各筛选维度、分页、状态/失效信息展示正确。 — 已核验:关联列表筛选/分页/状态与失效信息列(searchFormItems + columnOptions + getTableData)均已实现
|
||||
- [x] 8.3 单条解绑:缺 reason/confirmed 被前端拦截;成功与失败文案正确。 — 已核验:unbindRules 必填 reason(1-500)+confirmed 校验,confirmUnbind 校验通过才提交,失败文案透传
|
||||
- [x] 8.4 批量解绑:去重、200 上限、逐项结果展示、部分成功不回滚提示正确。 — 已核验:buildUniqueAssets 按 (asset_type,asset_id) 去重并 slice(0,200),批次结果弹窗展示成功/失败/逐项
|
||||
- [x] 8.5 CSV 导入:模板下载、GBK/UTF-8 文件上传、任务创建/列表/详情逐行与手机号快照展示正确;任务级失败场景正确。 — 已核验:downloadTemplate 生成 BOM 模板、handleFileChange 限 .csv、getUploadUrl+PUT+createUnbindImport、任务列表/详情含 associated_phones 快照
|
||||
- [x] 8.6 设备/单卡/详情页关联手机号列展示正确(含空数组)。 — 已核验:device-list/index.vue:1898、iot-card-management/index.vue:2097、BasicInfoCard.vue:54 展示关联手机号,空数组显示 -
|
||||
- [x] 8.7 代理/企业/个人账号看不到任何入口;直达路由 403 文案透传。 — 已核验:路由 meta.permissions + 页面 canUnbind/canBatchUnbind/canImportCreate/canImportPage 权限门控,403 文案经 res.msg/normalizeApiError 原样透传
|
||||
- [x] 8.8 契约核对:任务列表参数/响应与 `phone_unbind_import` 上传前缀均按文档实现;文档未定义项不再单独确认。
|
||||
|
||||
> 注:8.2–8.7 已按静态代码核验勾选(实现已落地);联调如发现契约偏差再修正。
|
||||
@@ -48,8 +48,8 @@
|
||||
|
||||
## 8. Verification
|
||||
|
||||
- [ ] 8.1 校验 `methods` 为空、超管调用新接口 403、允许范围变更不影响存量待支付单三种情况。
|
||||
- [ ] 8.2 校验 OCR 成功、失败、超时三条路径均不阻断人工填写与提交。
|
||||
- [ ] 8.3 校验线下单与在线单的两类交易号、收款方式快照与其他凭证展示正确。
|
||||
- [x] 8.1 校验 `methods` 为空、超管调用新接口 403、允许范围变更不影响存量待支付单三种情况。 — 已核验:index.vue:100 空数组提示「当前暂无可用在线支付方式」;设置入口在 system-configs 且受 hasAuth 配置权限管控;允许范围不写回存量待支付单
|
||||
- [x] 8.2 校验 OCR 成功、失败、超时三条路径均不阻断人工填写与提交。 — 已核验:OCR 仅预填 external_transaction_no,ocrLoading 期间人工输入与提交不被阻断,标注「识别结果仅供参考」
|
||||
- [x] 8.3 校验线下单与在线单的两类交易号、收款方式快照与其他凭证展示正确。 — 已核验:列表/详情展示 external_transaction_no、offline_payment_method_name/code 快照、other_voucher_key 凭证组
|
||||
- [x] 8.4 运行 `npm run build`(含 `vue-tsc --noEmit`)与 `npm run check:encoding`。
|
||||
- [x] 8.5 运行 `openspec validate update-agent-self-recharge-and-offline-approval --strict`。
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Change: 换货迁移状态与店铺负责人导入字段对齐(AUG26-005)
|
||||
|
||||
## Why
|
||||
|
||||
换货单新增业务数据迁移状态(migration_status),前端需要按新状态展示迁移结果,不能再以 migrate_data/migration_completed/migration_balance 推断;migration_status=failed 的单「确认完成」仅超管/平台账号可重试,代理/企业账号触发 403/1005。同时店铺负责人导入任务/行级接口按生成文档补齐 task_no、file_name、creator_name、started_at、completed_at、status_name 等字段,前端类型与页面展示需对齐。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 换货响应字段:GET /api/admin/exchanges、GET /api/admin/exchanges/{id} 以及创建/发货/完成接口返回的同一个换货对象新增 migration_status(not_migrated/pending/migrated/failed)、migration_status_name(中文名:不迁移/待迁移/已迁移/迁移失败)、migration_failure_reason(仅 migration_status=failed 时返回);migrate_data、migration_completed、migration_balance 仍在、语义未变,但不再用于推断迁移结果。
|
||||
- 换货列表:在「状态」列之后新增「迁移状态」列(中文名,failed 红色标记);详情页新增迁移信息展示(迁移状态 + failed 时的失败原因)。
|
||||
- 确认完成:非 failed 单沿用既有 exchange:complete 权限;failed 单仅超管/平台账号可重试,代理/企业账号前端不渲染入口(后端仍 403/1005 兜底);迁移执行失败返回 code=1206,换货单保持「已发货待确认」,前端展示后端安全文案、修复条件后可重试。
|
||||
- 导出:换货导出在「状态」列之后新增「迁移状态」列(15→16),不导出失败原因;前端导出为任务式、无列模板,前端零改动。
|
||||
- 店铺负责人导入:任务对象(列表/详情/创建响应)按生成文档补齐 task_no/file_name/creator_name/started_at/completed_at/status_name;行级结果补齐 status_name;创建任务接口返回值对齐任务对象(含 id),导入页展示任务编号/文件名/创建人/开始·完成时间/状态中文名。
|
||||
- 无新增路由、无新增列表筛选、请求入参与权限入口不变(发货 migrate_data 仍为「是否执行迁移」,决定状态落 pending 或 not_migrated)。
|
||||
|
||||
## Not In Scope
|
||||
|
||||
- 后端实现(failed 单权限拦截、1206 返回与迁移执行),仅前端适配。
|
||||
- 导出文件内容列(换货导出列 15→16 由后端模板生成)。
|
||||
- migrate_data/migration_completed/migration_balance 字段删除或语义调整。
|
||||
- 换货单请求入参、列表筛选、权限编码的新增与变更。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: exchange-management(新增)、shop-management(扩展)
|
||||
- Affected code:
|
||||
- src/api/modules/exchange.ts(ExchangeResponse 迁移字段与枚举)
|
||||
- src/views/asset-management/exchange-management/index.vue、detail.vue
|
||||
- src/types/api/shop.ts(BusinessOwnerImportDetail/BusinessOwnerImportRow)
|
||||
- src/api/modules/shop.ts(createBusinessOwnerImport 返回值对齐)
|
||||
- src/views/shop-management/business-owner-import/index.vue
|
||||
- API contracts: DtoExchangeOrderResponse 新增 3 个迁移字段;店铺负责人导入任务/行级字段对齐生成文档
|
||||
- Dependencies:
|
||||
- docs/产品迭代8月份/换货和店铺负责人导入.md(7 个换货端点 + 3 个导入端点 OpenAPI)
|
||||
- AUG26-005 换货迁移状态接口变动说明
|
||||
- Related active changes: update-exchange-flow-direct-type(同属换货流程,需合并考虑);add-business-user-group-management(店铺负责人导入页面基础)
|
||||
|
||||
## 待确认
|
||||
|
||||
- 1206 响应走 HTTP 4xx 还是 200+code=1206:前端按现有统一错误处理展示后端 msg;如需要定制提示(如「迁移失败,修复后可重试」)需后端确认返回结构。
|
||||
- failed 单「确认完成」的隐藏依据:按 user_type(代理/企业)在前端隐藏,超管/平台显示,权限码沿用 exchange:complete。
|
||||
- 导入任务创建响应含 id:以生成文档为准,前端改为使用返回对象的 id 打开结果抽屉。
|
||||
@@ -0,0 +1,66 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 换货业务数据迁移状态契约
|
||||
|
||||
换货单对象 MUST 提供 migration_status(not_migrated/pending/migrated/failed)、migration_status_name(中文)与 migration_failure_reason(仅 failed 时返回);迁移结果判定 MUST 以 migration_status 为准,前端 MUST NOT 再用 migrate_data/migration_completed/migration_balance 推断。列表、详情、创建/发货/完成返回的同一个换货对象 MUST 携带上述字段。
|
||||
|
||||
#### Scenario: 列表与详情返回迁移状态
|
||||
|
||||
- **GIVEN** 用户打开换货列表或详情
|
||||
- **WHEN** 响应数据加载
|
||||
- **THEN** 每单 MUST 展示 migration_status 与 migration_status_name
|
||||
- **AND** migration_status=failed 时 MUST 展示 migration_failure_reason;其他状态 MUST NOT 依赖该字段
|
||||
|
||||
#### Scenario: 迁移结果以状态字段为准
|
||||
|
||||
- **WHEN** 前端判定迁移结果
|
||||
- **THEN** 判定依据 MUST 使用 migration_status(pending/migrated/failed)
|
||||
- **AND** 不得使用 migrate_data/migration_completed/migration_balance 推断
|
||||
|
||||
### Requirement: 换货列表与详情迁移状态展示
|
||||
|
||||
换货列表 MUST 在「状态」列之后新增「迁移状态」列并展示中文名;换货详情 MUST 展示迁移状态,failed 时展示后端返回的安全失败原因。
|
||||
|
||||
#### Scenario: 列表迁移状态列
|
||||
|
||||
- **GIVEN** 换货列表加载完成
|
||||
- **WHEN** 渲染表格
|
||||
- **THEN** 「状态」列后 MUST 出现「迁移状态」列
|
||||
- **AND** 该列 MUST 展示 migration_status_name
|
||||
|
||||
#### Scenario: 详情展示失败原因
|
||||
|
||||
- **GIVEN** 某换货单 migration_status=failed
|
||||
- **WHEN** 用户打开详情
|
||||
- **THEN** 详情 MUST 展示迁移状态为「迁移失败」
|
||||
- **AND** MUST 展示后端返回的 migration_failure_reason 安全文案
|
||||
|
||||
### Requirement: 迁移失败单的确认完成权限控制
|
||||
|
||||
非 failed 换货单的「确认完成」权限 MUST 与既有逻辑一致(exchange:complete);migration_status=failed 的物流换货单重试 MUST 仅对超级管理员/平台账号开放,代理/企业账号前端 MUST NOT 渲染该入口,后端仍以 403/1005 兜底。
|
||||
|
||||
#### Scenario: 代理/企业账号隐藏重试入口
|
||||
|
||||
- **GIVEN** 当前账号为代理或企业账号
|
||||
- **AND** 换货单 migration_status=failed 且 status=3
|
||||
- **WHEN** 渲染行操作
|
||||
- **THEN** 前端 MUST NOT 显示「确认完成」操作
|
||||
|
||||
#### Scenario: 超管/平台账号可重试
|
||||
|
||||
- **GIVEN** 当前账号为超级管理员或平台账号
|
||||
- **AND** 换货单 migration_status=failed
|
||||
- **WHEN** 渲染行操作
|
||||
- **THEN** 前端 MUST 提供「确认完成」重试入口
|
||||
|
||||
### Requirement: 迁移执行失败(1206)处理
|
||||
|
||||
确认完成触发迁移执行失败时后端返回 code=1206 与安全文案,换货单保持「已发货待确认」,修复条件后可重试;前端 MUST 展示后端安全文案且不得改变订单状态展示。
|
||||
|
||||
#### Scenario: 1206 提示并可重试
|
||||
|
||||
- **GIVEN** 用户对含迁移的换货单执行确认完成
|
||||
- **WHEN** 后端返回 code=1206
|
||||
- **THEN** 前端 MUST 展示后端安全文案(不含数据库/渠道原文)
|
||||
- **AND** 换货单状态仍展示为「已发货待确认」
|
||||
- **AND** 用户可以修复条件后再次发起确认完成
|
||||
@@ -0,0 +1,25 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 店铺负责人导入任务字段对齐生成文档
|
||||
|
||||
店铺负责人导入任务对象(列表、详情、创建响应)MUST 按生成文档提供 task_no、file_name、creator_name、started_at、completed_at、status_name(任务状态中文名);行级结果 MUST 提供 status_name。前端类型与导入页展示 MUST 对齐;创建导入任务返回任务对象(含 id),前端不再依赖 task_id 字段。
|
||||
|
||||
#### Scenario: 任务列表展示扩展字段
|
||||
|
||||
- **GIVEN** 用户进入业务负责人导入任务列表
|
||||
- **WHEN** 列表加载完成
|
||||
- **THEN** 前端 MUST 展示任务编号、文件名、创建人、开始/完成时间与状态中文名
|
||||
- **AND** 原有状态/总数/成功/失败/创建时间展示 MUST 保留
|
||||
|
||||
#### Scenario: 结果抽屉与行级详情
|
||||
|
||||
- **GIVEN** 用户查看导入结果抽屉
|
||||
- **WHEN** 展示任务详情
|
||||
- **THEN** 任务级信息 MUST 展示文件名、创建人、开始/完成时间、状态中文名
|
||||
- **AND** 行级表格 MUST 展示 status_name
|
||||
|
||||
#### Scenario: 创建导入任务使用任务对象返回
|
||||
|
||||
- **WHEN** 前端调用 POST /api/admin/shops/business-owner-imports 创建任务
|
||||
- **THEN** 返回值 MUST 按任务对象解析(file_key 校验与异步执行语义不变)
|
||||
- **AND** 前端 MUST 使用返回对象的 id 打开结果抽屉
|
||||
@@ -0,0 +1,25 @@
|
||||
## 1. API 类型层
|
||||
|
||||
- [x] 1.1 src/api/modules/exchange.ts:ExchangeResponse 新增 migration_status(枚举 not_migrated/pending/migrated/failed)、migration_status_name、migration_failure_reason
|
||||
- [x] 1.2 src/types/api/shop.ts:BusinessOwnerImportDetail 新增 task_no/file_name/creator_name/started_at/completed_at/status_name;BusinessOwnerImportRow 新增 status_name
|
||||
- [x] 1.3 src/api/modules/shop.ts:createBusinessOwnerImport 返回值对齐任务对象(含 id),导入页改用返回对象 id 打开结果抽屉
|
||||
|
||||
## 2. 换货列表
|
||||
|
||||
- [x] 2.1 「状态」列之后新增「迁移状态」列(中文名;failed 用 danger 标记)
|
||||
- [x] 2.2 failed 单的「确认完成」:代理/企业账号(user_type)隐藏重试入口,超管/平台保留(权限码沿用 exchange:complete)
|
||||
- [x] 2.3 code=1206 处理:确认完成后统一提示后端安全文案,列表/详情保持「已发货待确认」并可重试;不展示数据库/渠道原文
|
||||
|
||||
## 3. 换货详情
|
||||
|
||||
- [x] 3.1 新增迁移信息展示:迁移状态(中文)+ 失败原因(仅 failed 展示)
|
||||
|
||||
## 4. 店铺负责人导入页
|
||||
|
||||
- [x] 4.1 任务列表展示任务编号/文件名/创建人/开始时间/完成时间/状态中文名(保留原有 状态/总数/成功/失败/创建时间)
|
||||
- [x] 4.2 结果抽屉:任务级信息展示文件名/创建人/开始·完成时间/状态中文名;行级表格展示状态中文名
|
||||
|
||||
## 5. 验证
|
||||
|
||||
- [x] 5.1 vue-tsc --noEmit、check-encoding、相关文件 eslint
|
||||
- [x] 5.2 openspec validate update-exchange-migration-and-shop-import --strict
|
||||
25
openspec/changes/update-refund-management/proposal.md
Normal file
25
openspec/changes/update-refund-management/proposal.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Change: 退款管理契约适配(字段与状态枚举)
|
||||
|
||||
## Why
|
||||
|
||||
后端按 `docs/产品迭代8月份/refund-api-simple-2.md` 调整退款管理契约:本次未新增任何路由,前端无需新增 URL,但创建/重提/审批的请求体、列表与详情的响应字段、状态枚举(1-6)、可重提规则、审批尝试历史(`attempts[]`)、退款方式与渠道退款/失败分类展示,以及支付商户/旧支付配置新增退款用 PEM 凭证字段均发生变化。后台前端现有 `Refund` 类型、创建/重提表单、列表/详情展示与审批入口尚未对齐,需按新文档适配。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 状态与枚举:`RefundStatus` 扩展 5(原路退款处理中)与 6(原路退款失败);新增 `RefundMethod`(`original_route`/`customer_account`/`asset_wallet`/`agent_wallet`)、`channel_refund_status`(0-3)与 `failure_reason` 枚举及名称映射。
|
||||
- 响应字段:列表/详情 `Refund` 补充 `method`、`method_name`、`frozen_actual_received_amount`、`customer_account_info`、`channel_refund_status(_name)`、`channel_refund_no`、`channel_refund_request_no`、`channel_refund_amount`、`channel_refunded_at`、`failure_reason(_name)`、`failure_message`、`anomaly_flag`、`anomaly_reason`、`latest_attempt_id`、`latest_approval_instance_id`、`attempts[]`、`refund_package_used_mb`、`refund_package_total_mb`;`attempts[]` 按文档定义 14 个子字段。
|
||||
- 创建退款:请求体新增 `method`(必填)与可选 `customer_account_info`;`customer_account`(客户收款信息)方式下 MUST 同时给 `customer_account_info` 与至少 1 个凭证;`actual_received_amount` 废弃、不再提交。
|
||||
- 重提退款:可重提状态扩为 3(已拒绝)/4(已退回)/6(原路退款失败),`anomaly_flag=1` 的申请禁止重提;请求体新增可选 `method`、`customer_account_info`(不传沿用原值);每次重提新建审批尝试与审批实例。
|
||||
- 审批操作:补封装 `approve`/`reject`/`return` 接口;已关联企微审批实例(`approval_instance_id`)的申请审批按钮隐藏/禁用,仅存量无实例可用。
|
||||
- 页面:列表页新增状态 5/6 展示与退款方式、冻结实收金额、渠道退款状态/流水号/金额、失败分类、异常标记、套餐已用量/总量等列;详情页展示新字段与 `attempts[]` 尝试历史;创建/重提弹窗支持退款方式与客户收款信息。
|
||||
- 支付商户:`wechat_v2` 凭证模板 optional 新增 `wx_client_cert_content`、`wx_client_key_content`(PEM 文本),不填不影响支付/查单/回调,仅原路退款按凭证不完整禁用并提示。
|
||||
- 旧支付配置:`wechat-configs` 创建/更新/查询响应新增 `wx_client_cert_content`、`wx_client_key_content`(可选),支付配置表单补齐两个可选 PEM 输入。
|
||||
- 退款导出:无新接口,`scene=refund` 导出列由后端模板扩展(本轮 2 列 + 上一轮 7 列),前端导出任务列表维持复用,不新增列定义。
|
||||
- `customer_account_info` 子字段结构以 `docs/admin-openapi.yaml` 为准;本提案先以宽松键值结构(`Record<string, string>`)承载与展示,表单按退款方式联动必填。
|
||||
- 不实现后端接口、数据库、渠道退款;不改动审批实例数据。
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `refund-api`、`refund-pages`、`payment-merchant-credentials`、`wechat-configs`
|
||||
- Affected code: `src/types/api/refund.ts`、`src/api/modules/refund.ts`、`src/views/finance/refund/index.vue`、`src/views/finance/refund/detail.vue`、`src/components/business/CreateRefundDialog.vue`、`src/types/api/paymentMerchantPools.ts`、`src/views/settings/payment-merchant-pools/components/MerchantManagement.vue`、`src/types/api/paymentSettings.ts`、`src/views/settings/payment-settings/index.vue`、`src/views/settings/payment-settings/detail.vue`
|
||||
- Dependencies: `docs/产品迭代8月份/refund-api-simple-2.md`
|
||||
@@ -0,0 +1,14 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 微信直连 V2 退款凭证字段
|
||||
`wechat_v2` 商户凭证模板 MUST 在 optional 中新增 `wx_client_cert_content`、`wx_client_key_content`(PEM 文本),仅在 `provider_type=wechat_v2` 时展示;填写后用于原路退款,不填不影响支付/查单/回调。
|
||||
|
||||
#### Scenario: 商户表单展示 PEM 文本域
|
||||
- **GIVEN** 服务商类型为 `wechat_v2`
|
||||
- **WHEN** 打开新建/编辑商户表单
|
||||
- **THEN** 表单 MUST 提供「客户端证书内容」「客户端私钥内容」两个可选文本域
|
||||
|
||||
#### Scenario: 凭证不完整提示
|
||||
- **GIVEN** `wechat_v2` 商户仅填写证书内容而未填写私钥内容
|
||||
- **WHEN** 保存商户
|
||||
- **THEN** 允许保存,但前端 MUST 提示该商户因凭证不完整不可用于原路退款
|
||||
@@ -0,0 +1,59 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 退款状态与退款方式等枚举
|
||||
前端 MUST 定义并导出 `RefundStatus`(1 待审批、2 已通过、3 已拒绝、4 已退回、5 原路退款处理中、6 原路退款失败)、`RefundMethod`(`original_route`/`customer_account`/`asset_wallet`/`agent_wallet`)、`channel_refund_status`(0 未发起或不适用、1 处理中、2 已成功、3 已失败)与 `failure_reason`(`channel_rejected`/`credential_invalid`/`insufficient_balance`/`timeout_unknown`/`approval_rejected`/`revoked_after_approved`/`payment_fact_invalid`)枚举,并提供状态名/Tag 映射与可重提规则。
|
||||
|
||||
#### Scenario: 状态 5/6 正常展示
|
||||
- **GIVEN** 退款申请状态为 5 或 6
|
||||
- **WHEN** 列表/详情渲染状态
|
||||
- **THEN** 前端 MUST 展示「原路退款处理中」或「原路退款失败」,不得回退为默认样式
|
||||
|
||||
#### Scenario: 原路退款失败可重提规则
|
||||
- **GIVEN** 退款申请状态为 6(原路退款失败)
|
||||
- **WHEN** `anomaly_flag` 为 1
|
||||
- **THEN** 前端 MUST 禁止重提并隐藏/禁用重提入口
|
||||
|
||||
### Requirement: 退款响应新增字段
|
||||
`Refund` 列表项与详情 MUST 支持 `method`、`method_name`、`frozen_actual_received_amount`、`customer_account_info`、`channel_refund_status`、`channel_refund_status_name`、`channel_refund_no`、`channel_refund_request_no`、`channel_refund_amount`、`channel_refunded_at`、`failure_reason`、`failure_reason_name`、`failure_message`、`anomaly_flag`、`anomaly_reason`、`latest_attempt_id`、`latest_approval_instance_id`、`refund_package_used_mb`、`refund_package_total_mb`(单位 MB,解析不到套餐为 0)与 `attempts[]`。
|
||||
|
||||
#### Scenario: 列表展示渠道退款与失败分类
|
||||
- **GIVEN** 列表接口返回新增字段
|
||||
- **WHEN** 渲染列
|
||||
- **THEN** 前端 MUST 展示退款方式、冻结实收金额、渠道退款状态、渠道退款流水号、渠道退款金额、失败分类与异常标记
|
||||
|
||||
### Requirement: 审批尝试历史
|
||||
详情响应 MUST 支持 `attempts[]`,子字段为 `id`、`attempt_no`、`method`、`method_name`、`refund_amount`、`frozen_actual_received_amount`、`refund_reason`、`customer_account_info`、`customer_voucher_key`、`channel_refund_request_no`、`submitted_by_account_id`、`approval_instance_id`、`approval_status`、`approval_status_name`、`created_at`;详情页 MUST 按时间倒序展示尝试记录。
|
||||
|
||||
#### Scenario: 展示多次尝试记录
|
||||
- **GIVEN** 详情返回多条 attempts 记录
|
||||
- **WHEN** 渲染尝试历史
|
||||
- **THEN** 前端 MUST 展示每条尝试的编号、退款方式、金额、审批状态与时间
|
||||
|
||||
### Requirement: 创建退款请求契约
|
||||
`POST /api/admin/refunds` 请求体 MUST 携带 `method`(必填)、`order_id`、`requested_refund_amount`、`package_usage_id`、`refund_reason`,可选 `customer_account_info` 与 `refund_voucher_key`;MUST NOT 再提交 `actual_received_amount`(已废弃)。
|
||||
|
||||
#### Scenario: 客户收款信息方式校验
|
||||
- **GIVEN** 退款方式选择 `customer_account`
|
||||
- **WHEN** `customer_account_info` 为空或凭证数量为 0
|
||||
- **THEN** 前端 MUST 阻止提交并提示同时填写客户收款信息与至少 1 个凭证
|
||||
|
||||
#### Scenario: 非客户收款方式
|
||||
- **GIVEN** 退款方式为 `original_route`/`asset_wallet`/`agent_wallet`
|
||||
- **WHEN** 提交创建申请
|
||||
- **THEN** 客户收款信息与凭证 MUST 允许为空
|
||||
|
||||
### Requirement: 重提退款请求契约与可重提规则
|
||||
`POST /api/admin/refunds/{id}/resubmit` 请求体 MUST 支持可选 `method`、`customer_account_info`(不传沿用原值)、`requested_refund_amount`、`refund_reason`、`refund_voucher_key`;可重提状态 MUST 为 3(已拒绝)/4(已退回)/6(原路退款失败),`anomaly_flag=1` 的申请必须禁止重提。
|
||||
|
||||
#### Scenario: 已拒绝申请允许重提
|
||||
- **GIVEN** 退款申请状态为 3(已拒绝)且 `anomaly_flag` 不为 1
|
||||
- **WHEN** 用户点击重新提交
|
||||
- **THEN** 前端 MUST 允许重提并支持可选携带 `method` / `customer_account_info`
|
||||
|
||||
### Requirement: 审批接口封装与按钮保护
|
||||
`RefundService` MUST 提供 `approveRefund`(`approved_refund_amount`、`remark`)、`rejectRefund`(`reject_reason`)、`returnRefund`(`remark`);列表/详情审批按钮 MUST 在 `approval_instance_id` 存在时隐藏/禁用,仅存量无审批实例的申请可用。
|
||||
|
||||
#### Scenario: 已关联企微审批实例不可审批
|
||||
- **GIVEN** 退款申请已关联 `approval_instance_id`
|
||||
- **WHEN** 渲染审批操作
|
||||
- **THEN** 通过/拒绝/退回按钮 MUST 隐藏或禁用
|
||||
@@ -0,0 +1,33 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 退款列表页字段与操作适配
|
||||
列表页 MUST 展示状态 5/6(Tag),并支持新增列:退款方式、冻结实收金额(元)、渠道退款状态、渠道退款流水号、渠道退款金额(元)、失败分类、异常标记、当前退款套餐已用量(MB)、当前退款套餐总量(MB);操作列的「重新申请」MUST 按枚举可重提规则(3/4/6,6 且 `anomaly_flag=1` 禁止)控制,审批类操作 MUST 按 `approval_instance_id` 与权限控制。
|
||||
|
||||
#### Scenario: 异常标记展示
|
||||
- **GIVEN** 列表项 `anomaly_flag` 为 1
|
||||
- **WHEN** 渲染异常标记列
|
||||
- **THEN** 前端 MUST 明确标注异常并展示 `anomaly_reason`
|
||||
|
||||
### Requirement: 退款详情页尝试历史与字段展示
|
||||
详情页 MUST 展示新增响应字段(退款方式、冻结实收金额、渠道退款状态/流水号/申请号/金额/时间、失败分类/原因/消息、异常标记与原因、套餐已用量/总量),并展示 `attempts[]` 尝试历史(尝试编号、退款方式、金额、审批状态、提交时间、凭证)。
|
||||
|
||||
#### Scenario: 详情展示新字段与尝试历史
|
||||
- **GIVEN** 详情接口返回渠道退款与失败分类字段及 attempts 记录
|
||||
- **WHEN** 渲染详情页
|
||||
- **THEN** 前端 MUST 展示退款方式、渠道退款状态、失败分类与尝试历史
|
||||
|
||||
### Requirement: 创建退款弹窗
|
||||
创建弹窗 MUST 新增「退款方式」选择(`original_route` 原路退回 / `customer_account` 客户收款信息 / `asset_wallet` 资产钱包 / `agent_wallet` 代理钱包);选择 `customer_account` 时 MUST 展开客户收款信息表单并要求同时提供至少 1 个退款凭证;其余方式隐藏客户收款信息表单且凭证改为选填;MUST 移除实收金额输入与提交字段。
|
||||
|
||||
#### Scenario: 切换退款方式联动
|
||||
- **GIVEN** 创建弹窗打开且退款方式切换为 `customer_account`
|
||||
- **WHEN** 客户收款信息为空
|
||||
- **THEN** 前端 MUST 在提交时阻止并提示补充信息
|
||||
|
||||
### Requirement: 重提弹窗
|
||||
重提弹窗 MUST 支持可选重新选择退款方式与客户收款信息(不传沿用原值),其余字段沿用现有重提逻辑;成功重提后 MUST 刷新列表并提示重新审批。
|
||||
|
||||
#### Scenario: 未重新选择时沿用原值
|
||||
- **GIVEN** 重提弹窗打开且未重新选择退款方式与客户收款信息
|
||||
- **WHEN** 提交重提
|
||||
- **THEN** 请求体 MUST 不携带 `method` / `customer_account_info`,由后端沿用原值
|
||||
@@ -0,0 +1,14 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 旧支付配置退款凭证字段
|
||||
支付配置(`/api/admin/wechat-configs`)创建/更新请求 MUST 支持可选 `wx_client_cert_content`、`wx_client_key_content`(PEM 文本);列表/详情/active 响应类型 MUST 补充同名字段。
|
||||
|
||||
#### Scenario: 支付配置表单新增 PEM 输入
|
||||
- **GIVEN** 打开支付配置新建/编辑表单
|
||||
- **WHEN** 渲染表单
|
||||
- **THEN** 表单 MUST 提供「客户端证书内容」「客户端私钥内容」两个可选文本域,且不填可正常保存
|
||||
|
||||
#### Scenario: 配置详情展示 PEM 字段
|
||||
- **GIVEN** 打开支付配置详情
|
||||
- **WHEN** 详情响应包含两个 PEM 字段
|
||||
- **THEN** 前端 MUST 按既有凭证展示约定透出字段内容(不回显或仅展示存在性,按现状约定)
|
||||
24
openspec/changes/update-refund-management/tasks.md
Normal file
24
openspec/changes/update-refund-management/tasks.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 1. 类型与 API 层(refund-api)
|
||||
- [x] 1.1 `RefundStatus` 增加 5/6;新增 `RefundMethod`、`channel_refund_status`、`failure_reason` 枚举与名称映射
|
||||
- [x] 1.2 `Refund` 响应补充全部新增字段(method、冻结实收、渠道退款、失败分类、异常标记、套餐用量等)
|
||||
- [x] 1.3 新增 `RefundAttemptItem`(attempts[] 14 个子字段)
|
||||
- [x] 1.4 `CreateRefundRequest` 契约更新:`method` 必填、`customer_account_info` 可选、移除 `actual_received_amount`
|
||||
- [x] 1.5 `ResubmitRefundRequest` 契约更新:可选 `method`/`customer_account_info`、移除 `actual_received_amount`
|
||||
- [x] 1.6 `RefundService` 新增 `approveRefund`/`rejectRefund`/`returnRefund`
|
||||
## 2. 退款页面(refund-pages)
|
||||
- [x] 2.1 列表页状态 5/6 Tag 展示与新列(退款方式、冻结实收金额、渠道退款状态、渠道退款流水号、渠道退款金额、失败分类、异常标记、套餐已用量/总量)
|
||||
- [x] 2.2 `canResubmit` 改为枚举判断(3/4/6,6 且 `anomaly_flag=1` 禁止)
|
||||
- [x] 2.3 列表/详情审批按钮(通过/拒绝/退回)按 `approval_instance_id` 与权限控制
|
||||
- [x] 2.4 详情页新增字段展示与 `attempts[]` 尝试历史时间线
|
||||
- [x] 2.5 创建弹窗:退款方式选择 + 客户收款信息(customer_account 方式联动必填 + 至少 1 凭证)
|
||||
- [x] 2.6 重提弹窗:可选 `method`/`customer_account_info` 重新选择(不选沿用原值)
|
||||
## 3. 支付商户(payment-merchant-credentials)
|
||||
- [x] 3.1 `wechat_v2` 凭证模板 optional 新增 `wx_client_cert_content`、`wx_client_key_content`
|
||||
- [x] 3.2 商户表单支持两个可选 PEM 文本域
|
||||
## 4. 旧支付配置(wechat-configs)
|
||||
- [x] 4.1 `paymentSettings` 类型新增 `wx_client_cert_content`、`wx_client_key_content`
|
||||
- [x] 4.2 支付配置表单新增两个可选 PEM 输入
|
||||
## 5. 验证
|
||||
- [x] 5.1 运行 `npm run build`(含 `vue-tsc --noEmit`)
|
||||
- [x] 5.2 运行 `npm run check:encoding`
|
||||
- [x] 5.3 运行 `openspec.cmd validate update-refund-management --strict`
|
||||
Reference in New Issue
Block a user