临时备份一次

This commit is contained in:
2026-07-13 12:01:18 +09:00
parent 5cdcdad534
commit 2e130b98f5
17 changed files with 3938 additions and 0 deletions

View 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 '发展人IDshop_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
→ 创建 RefundRequeststatus=1 待审批)
→ 调 SubmitApprovalUseCase 创建 ApprovalFlowbiz_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_idstatus 回到 1待审批
```
---
## 需求21充值审核流程
### 充值单现有状态
```
tb_agent_recharge_record1=待支付 2=已支付 3=已完成 4=已关闭 5=已退款
```
员工线下充值走审批流,需在现有状态基础上**新增"已退回"状态**
```sql
-- status 说明更新(不改原有值语义,追加新状态)
-- 6=已退回(审批人退回给提交人修改)
```
充值业务的状态语义:
- `1=待支付`:创建未支付(线下充值等待审批时也停在这里,由 approval_flow_id 判断是否在审批中)
- `2=已支付`:支付成功(线下充值审批通过后跳到已完成,不经过此状态)
- `3=已完成`:充值到账
- `4=已关闭`:取消/超时
- `5=已退款`:退款
- `6=已退回`:审批人退回给提交人修改
### 流程
**代理自行充值(不走审批)**
```
代理提交充值申请 → 系统生成收款码 → 代理扫码支付 → 回调自动充值到钱包
```
**员工线下代充值(走审批)**
```
POST /admin/agent-recharge-recordspayment_method=offline
→ 创建 AgentRechargeRecordstatus=1
→ 调 SubmitApprovalUseCase 创建 ApprovalFlowbiz_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_idstatus 回到 1待支付/待审批)
```