临时备份一次
This commit is contained in:
348
docs/7月迭代/需求15-16-18-19-20-21-复杂需求.md
Normal file
348
docs/7月迭代/需求15-16-18-19-20-21-复杂需求.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# 需求15/16/18/19/20/21 技术方案
|
||||
|
||||
---
|
||||
|
||||
## 需求15:套餐下架后允许续费
|
||||
|
||||
### 业务规则
|
||||
|
||||
- 下架套餐(`shelf_status=2`)不可被**新购**
|
||||
- 下架套餐**可以续费**(已在使用该套餐的客户)
|
||||
- 续费仅支持**客户自己购买**(不允许代理代购下架套餐给新客户)
|
||||
|
||||
### 后端
|
||||
|
||||
**当前逻辑**:下架套餐在购买时被拦截。
|
||||
|
||||
修改:在订单创建校验中,区分"新购"和"续费"场景:
|
||||
|
||||
```go
|
||||
// internal/service/order/service.go 或 client_order/service.go
|
||||
func (s *Service) validatePackageAvailability(
|
||||
ctx context.Context,
|
||||
pkg *model.Package,
|
||||
assetID uint,
|
||||
isRenewal bool,
|
||||
) error {
|
||||
if pkg.Status == constants.StatusDisabled { // 0=禁用,定义在 pkg/constants/constants.go
|
||||
return errors.New(errors.CodeForbidden, "套餐已禁用")
|
||||
}
|
||||
if pkg.ShelfStatus == constants.ShelfStatusOff && !isRenewal { // 2=下架
|
||||
return errors.New(errors.CodeForbidden, "套餐已下架,不可新购")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**isRenewal 判断**:查当前资产是否有该套餐的历史生效记录:
|
||||
|
||||
```go
|
||||
func (s *Service) isRenewal(ctx context.Context, assetType string, assetID uint, packageID uint) bool {
|
||||
count, _ := s.packageUsageStore.CountByAssetAndPackage(ctx, assetType, assetID, packageID)
|
||||
return count > 0
|
||||
}
|
||||
```
|
||||
|
||||
### 前端
|
||||
|
||||
C端续费页面:下架套餐不在"新购"列表中展示,但在"续费"入口中仍可展示。
|
||||
|
||||
后台代购时:下架套餐的"代购"按钮禁用,tooltip 提示"套餐已下架,不可代购"。
|
||||
|
||||
---
|
||||
|
||||
## 需求16:代理分销码与佣金提现
|
||||
|
||||
### 业务规则
|
||||
|
||||
**DST-001**:新建代理时自动建立分销归属关系(指定发展人)
|
||||
**DST-002**:员工可作为代理发展人进行标识
|
||||
**二维码**:代理或员工生成推广二维码 → 扫码进入H5填写信息 → 提交成为代理申请 → 平台审批
|
||||
|
||||
### 数据库变更
|
||||
|
||||
```sql
|
||||
-- 1. Shop 表新增发展人字段
|
||||
ALTER TABLE tb_shop
|
||||
ADD COLUMN referrer_type VARCHAR(20) DEFAULT NULL
|
||||
COMMENT '发展人类型 shop=代理介绍 admin=员工介绍',
|
||||
ADD COLUMN referrer_id BIGINT DEFAULT NULL
|
||||
COMMENT '发展人ID(shop_id 或 tb_account.id)',
|
||||
ADD COLUMN referrer_name VARCHAR(50) DEFAULT NULL
|
||||
COMMENT '发展人姓名快照';
|
||||
|
||||
-- 2. 分销码表
|
||||
CREATE TABLE tb_distribution_code (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
creator BIGINT NOT NULL DEFAULT 0,
|
||||
updater BIGINT NOT NULL DEFAULT 0,
|
||||
code VARCHAR(32) NOT NULL,
|
||||
owner_type VARCHAR(20) NOT NULL, -- shop=代理 | admin=员工
|
||||
owner_id BIGINT NOT NULL,
|
||||
owner_name VARCHAR(50) NOT NULL DEFAULT '',
|
||||
qr_code_url TEXT,
|
||||
use_count INT NOT NULL DEFAULT 0,
|
||||
status INT NOT NULL DEFAULT 1, -- 1=有效 0=禁用
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_distribution_code ON tb_distribution_code(code) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 3. 代理申请表(H5扫码提交)
|
||||
CREATE TABLE tb_agent_application (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
creator BIGINT NOT NULL DEFAULT 0,
|
||||
updater BIGINT NOT NULL DEFAULT 0,
|
||||
apply_no VARCHAR(30) NOT NULL,
|
||||
distribution_code VARCHAR(32) NOT NULL,
|
||||
referrer_type VARCHAR(20) NOT NULL,
|
||||
referrer_id BIGINT NOT NULL,
|
||||
applicant_name VARCHAR(50) NOT NULL,
|
||||
applicant_phone VARCHAR(20) NOT NULL,
|
||||
shop_name VARCHAR(100) NOT NULL,
|
||||
province VARCHAR(50),
|
||||
city VARCHAR(50),
|
||||
district VARCHAR(50),
|
||||
address VARCHAR(255),
|
||||
status INT NOT NULL DEFAULT 1, -- 1=待审批 2=已通过 3=已拒绝
|
||||
reject_reason TEXT,
|
||||
reviewed_by BIGINT,
|
||||
reviewed_at TIMESTAMPTZ
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_agent_application_no ON tb_agent_application(apply_no) WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
### API 设计
|
||||
|
||||
**生成分销码**:
|
||||
```
|
||||
POST /admin/distribution-codes
|
||||
body: { owner_type: "admin"|"shop", owner_id: 123 }
|
||||
```
|
||||
|
||||
**H5 扫码获取分销码信息**:
|
||||
```
|
||||
GET /app/distribution-codes/{code}
|
||||
```
|
||||
|
||||
**H5 提交代理申请**:
|
||||
```
|
||||
POST /app/agent-applications
|
||||
body: { distribution_code, applicant_name, applicant_phone, shop_name, ... }
|
||||
```
|
||||
|
||||
**后台代理申请列表/审批**:
|
||||
```
|
||||
GET /admin/agent-applications?status=1
|
||||
POST /admin/agent-applications/{id}/approve
|
||||
POST /admin/agent-applications/{id}/reject
|
||||
body: { reject_reason: "..." }
|
||||
```
|
||||
|
||||
审批通过时:自动创建 `tb_shop` + `tb_account`,设置 `referrer_type` 和 `referrer_id`。
|
||||
|
||||
**佣金提现(DST-003~007)**:
|
||||
|
||||
现有 commission 框架基础上新增材料上传字段,提现申请时一次性上传所有材料(对象存储 Key 列表)。
|
||||
|
||||
---
|
||||
|
||||
## 需求18:多人审批(APR-001~009)
|
||||
|
||||
### 依赖
|
||||
|
||||
基于 [审批流基础设施](./基础设施/审批流.md) 和 [站内消息](./基础设施/站内消息.md)。
|
||||
|
||||
APR-009(企微审批对接)= Phase 2。
|
||||
|
||||
### 实现要点
|
||||
|
||||
| 编号 | 需求 | 实现 |
|
||||
|------|------|------|
|
||||
| APR-001~003 | 充值/退款多级审核 | 见需求20/21 |
|
||||
| APR-004 | 审核环节:部门领导→财务 | 审批流固定两步:step=1 部门领导,step=2 财务 |
|
||||
| APR-005 | 待审核有消息提示 | 站内消息 `NotifyTypeApprovalPending` |
|
||||
| APR-006 | 上一级完成后才提示下一级 | `ApprovalFlowAggregate.Approve()` 触发 `ApprovalStepAdvancedEvent` |
|
||||
| APR-007 | 通过后通知申请人 | `ApprovalCompletedEvent` handler |
|
||||
| APR-008 | 驳回/退回后通知申请人含原因 | `ApprovalRejectedEvent` / `ApprovalReturnedEvent` handler |
|
||||
| APR-009 | 对接企微 | **Phase 2** |
|
||||
|
||||
### 业务表与审批流的关联
|
||||
|
||||
充值单、退款单接入审批流,各自新增 `approval_flow_id` 字段:
|
||||
|
||||
```sql
|
||||
ALTER TABLE tb_refund_request ADD COLUMN approval_flow_id BIGINT DEFAULT NULL
|
||||
COMMENT '当前审批流ID(已退回后重新提交会更新为新流程ID)';
|
||||
|
||||
ALTER TABLE tb_agent_recharge_record ADD COLUMN approval_flow_id BIGINT DEFAULT NULL
|
||||
COMMENT '当前审批流ID(已退回后重新提交会更新为新流程ID)';
|
||||
```
|
||||
|
||||
接入协议详见 [审批流文档 - 接入协议](./基础设施/审批流.md#六接入协议业务层如何接入审批流)。
|
||||
|
||||
---
|
||||
|
||||
## 需求19:批量订购套餐(BPO-001~008)
|
||||
|
||||
### 业务流程
|
||||
|
||||
1. 员工进入批量订购页面
|
||||
2. 选择代理 + 上传 Excel(字段:资产类型/资产标识/套餐编码/套餐名称/支付方式)
|
||||
3. 系统解析并校验每一行
|
||||
4. 支付方式:`offline=线下支付(默认成功)` / `agent_wallet=代理钱包支付`
|
||||
5. 代理钱包支付时从代理余额扣款(含信用额度)
|
||||
6. 线下支付时标记为已支付(不走钱包)
|
||||
7. 校验失败的行展示在页面,带失败原因
|
||||
8. **线下支付需上传整批次的支付凭证**
|
||||
|
||||
### 数据库变更
|
||||
|
||||
```sql
|
||||
CREATE TABLE tb_bulk_purchase_task (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
creator BIGINT NOT NULL DEFAULT 0,
|
||||
updater BIGINT NOT NULL DEFAULT 0,
|
||||
task_no VARCHAR(30) NOT NULL,
|
||||
shop_id BIGINT NOT NULL,
|
||||
operator_id BIGINT NOT NULL,
|
||||
payment_method VARCHAR(20) NOT NULL, -- offline | agent_wallet
|
||||
voucher_keys JSONB, -- 线下支付凭证列表
|
||||
total_count INT NOT NULL DEFAULT 0,
|
||||
success_count INT NOT NULL DEFAULT 0,
|
||||
fail_count INT NOT NULL DEFAULT 0,
|
||||
status INT NOT NULL DEFAULT 1, -- 1=处理中 2=已完成
|
||||
failed_items JSONB -- 失败明细
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_bulk_purchase_task_no ON tb_bulk_purchase_task(task_no) WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
> 失败明细存 JSONB(`failed_items`),与现有 DeviceImportTask 模式一致,无需单独明细表。
|
||||
|
||||
### API 设计
|
||||
|
||||
**下载 Excel 模板**:
|
||||
```
|
||||
GET /admin/bulk-purchases/template
|
||||
```
|
||||
|
||||
**上传并提交**:
|
||||
```
|
||||
POST /admin/bulk-purchases
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
字段:
|
||||
shop_id: 123
|
||||
payment_method: offline | agent_wallet
|
||||
voucher_keys: ["key1","key2"] (offline 时必填)
|
||||
file: <Excel文件>
|
||||
```
|
||||
|
||||
**查询任务状态**:
|
||||
```
|
||||
GET /admin/bulk-purchases/{task_id}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 需求20:退款审批
|
||||
|
||||
### 依赖
|
||||
|
||||
基于 [审批流基础设施](./基础设施/审批流.md)。
|
||||
|
||||
### 退款单现有状态(不变)
|
||||
|
||||
```
|
||||
1=待审批 2=已通过 3=已拒绝 4=已退回
|
||||
```
|
||||
|
||||
退款单本身的状态含义不变,审批进度由 `tb_approval_flow` 管理,两者通过 `approval_flow_id` 关联。
|
||||
|
||||
### 流程
|
||||
|
||||
```
|
||||
提交退款申请(POST /admin/refund-requests)
|
||||
→ 创建 RefundRequest(status=1 待审批)
|
||||
→ 调 SubmitApprovalUseCase 创建 ApprovalFlow(biz_type=refund)
|
||||
→ 把 approval_flow_id 写入 RefundRequest
|
||||
|
||||
审批人操作(POST /admin/approvals/{flow_id}/approve|reject|return)
|
||||
→ ApprovalCompletedEvent → RefundRequest.status=2(已通过)→ 触发实际退款
|
||||
→ ApprovalRejectedEvent → RefundRequest.status=3(已拒绝)
|
||||
→ ApprovalReturnedEvent → RefundRequest.status=4(已退回)→ 通知提交人可修改
|
||||
```
|
||||
|
||||
### 退回后重新提交
|
||||
|
||||
```
|
||||
PUT /admin/refund-requests/{id} 仅 status=4 时可编辑退款单内容
|
||||
POST /admin/refund-requests/{id}/resubmit
|
||||
→ 校验 status=4
|
||||
→ 新建 ApprovalFlow
|
||||
→ 更新 approval_flow_id,status 回到 1(待审批)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 需求21:充值审核流程
|
||||
|
||||
### 充值单现有状态
|
||||
|
||||
```
|
||||
tb_agent_recharge_record:1=待支付 2=已支付 3=已完成 4=已关闭 5=已退款
|
||||
```
|
||||
|
||||
员工线下充值走审批流,需在现有状态基础上**新增"已退回"状态**:
|
||||
|
||||
```sql
|
||||
-- status 说明更新(不改原有值语义,追加新状态)
|
||||
-- 6=已退回(审批人退回给提交人修改)
|
||||
```
|
||||
|
||||
充值业务的状态语义:
|
||||
- `1=待支付`:创建未支付(线下充值等待审批时也停在这里,由 approval_flow_id 判断是否在审批中)
|
||||
- `2=已支付`:支付成功(线下充值审批通过后跳到已完成,不经过此状态)
|
||||
- `3=已完成`:充值到账
|
||||
- `4=已关闭`:取消/超时
|
||||
- `5=已退款`:退款
|
||||
- `6=已退回`:审批人退回给提交人修改
|
||||
|
||||
### 流程
|
||||
|
||||
**代理自行充值(不走审批)**:
|
||||
|
||||
```
|
||||
代理提交充值申请 → 系统生成收款码 → 代理扫码支付 → 回调自动充值到钱包
|
||||
```
|
||||
|
||||
**员工线下代充值(走审批)**:
|
||||
|
||||
```
|
||||
POST /admin/agent-recharge-records(payment_method=offline)
|
||||
→ 创建 AgentRechargeRecord(status=1)
|
||||
→ 调 SubmitApprovalUseCase 创建 ApprovalFlow(biz_type=recharge)
|
||||
→ 把 approval_flow_id 写入记录
|
||||
|
||||
审批通过 → AgentRechargeRecord.status=3(已完成)→ 实际充值到钱包
|
||||
审批驳回 → AgentRechargeRecord.status=4(已关闭)
|
||||
审批退回 → AgentRechargeRecord.status=6(已退回)→ 通知提交人可修改
|
||||
```
|
||||
|
||||
### 退回后重新提交
|
||||
|
||||
```
|
||||
PUT /admin/agent-recharge-records/{id} 仅 status=6 时可编辑
|
||||
POST /admin/agent-recharge-records/{id}/resubmit
|
||||
→ 校验 status=6
|
||||
→ 新建 ApprovalFlow
|
||||
→ 更新 approval_flow_id,status 回到 1(待支付/待审批)
|
||||
```
|
||||
Reference in New Issue
Block a user