Files
jsowell-charger-web/docs/PRD-积分兑换洗车券功能.md
2026-03-03 12:27:06 +08:00

369 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PRD优惠券功能
**版本**: v1.1
**日期**: 2026-03-03
**项目**: 万车充运营管理平台
**状态**: 草稿
---
## 变更记录
| 版本 | 日期 | 变更内容 |
|------|------|---------|
| v1.0 | 2026-03-03 | 初稿:积分兑换洗车券功能 |
| v1.1 | 2026-03-03 | 新增:券创建权限体系(平台人员 vs 运营商人员);新增充电折扣券规划;更新数据库设计与业务规则 |
---
## 一、背景与目标
### 背景
平台现有积分系统(`member_points_info`)支持充电消费奖励积分,但积分使用场景单一(仅支持抵扣充电费用)。为提升用户留存与活跃度,引入优惠券体系,首期以「积分兑换洗车券」为切入点,同时建立支持平台与运营商双层管理的券权限体系,为后续充电折扣券等功能奠定基础。
### 目标
- 为用户提供积分消耗渠道,提升积分价值感知
- 通过洗车权益与充电绑定,增强用户充电粘性
- 建立可扩展的优惠券基础架构,支持后续更多券类型
- 建立平台与运营商双层券管理权限,支持精细化运营
### 成功指标
- 积分兑换率(兑换用户数 / 持有积分用户数)≥ 15%
- 洗车券核销率 ≥ 60%
- 功能上线后次月充电频次同比提升 ≥ 5%
---
## 二、用户角色
| 角色 | 类型 | 说明 |
|------|------|------|
| **平台管理员** | 创券方 | 平台运营人员,可创建全平台范围或指定运营商范围的优惠券 |
| **运营商管理员** | 创券方 | 运营商后台人员,只能创建本运营商范围内可用的优惠券 |
| **会员用户** | 使用方 | 通过充电获得积分,在 App/小程序中兑换并使用券 |
| **合作洗车商** | 核销方 | 扫码核销洗车券(可通过独立 H5 页面或 API 对接) |
---
## 三、券可用范围权限设计
### 3.1 核心规则
| 创建人角色 | 可设置的最大可用范围 | 说明 |
|-----------|-------------------|------|
| **平台管理员** | 全平台 | 可选:全平台 / 指定运营商 / 指定站点 |
| **运营商管理员** | 本运营商 | 可选:本运营商全部站点 / 指定站点(仅限本运营商下) |
### 3.2 可用范围枚举
| 范围值 | 说明 | 可创建角色 |
|--------|------|----------|
| `1` - 全平台 | 所有运营商、所有站点均可使用 | 仅平台管理员 |
| `2` - 指定运营商 | 仅指定运营商下的站点可使用 | 平台管理员(可多选);运营商管理员(固定为本运营商) |
| `3` - 指定站点 | 仅指定站点可使用 | 平台管理员;运营商管理员(仅限本运营商下的站点) |
### 3.3 业务规则
1. 运营商管理员创建券时,`scope_type` 最高只能选 `2`(本运营商),**不能选** `1`(全平台)
2. 运营商管理员创建的指定站点券,`scope_merchant_id` 强制写入本运营商 ID不可修改
3. 平台管理员可查看所有运营商的券;运营商管理员只能查看和管理本运营商创建的券
4. 用户兑换或领取券时,系统根据券的 `scope_type` 及关联数据校验该用户是否符合领取资格(如充电站是否在券可用范围内)
5. 充电折扣券使用时额外校验:本次充电所在站点是否在券的可用范围内
---
## 四、功能范围(首期 v1.0
### 4.1 功能清单
| 模块 | 功能点 | 优先级 |
|------|--------|--------|
| 券模板管理 | 平台管理员创建/编辑/下架洗车券模板 | P0 |
| 券模板管理 | 运营商管理员创建/编辑/下架洗车券模板 | P0 |
| 券模板管理 | 设置券可用范围(全平台 / 指定运营商 / 指定站点) | P0 |
| 券模板管理 | 设置积分兑换比例N 积分 = 1 张券) | P0 |
| 券模板管理 | 设置券有效期(固定日期 / 领取后 N 天) | P0 |
| 积分兑换 | 用户查看可兑换券列表 | P0 |
| 积分兑换 | 用户用积分兑换洗车券 | P0 |
| 积分兑换 | 兑换记录查询 | P0 |
| 我的券包 | 查看持有的洗车券(未使用/已使用/已过期) | P0 |
| 券核销 | 展示券码(二维码 / 核销码) | P0 |
| 券核销 | 合作商扫码核销 | P0 |
| 数据看板 | 兑换量、核销量、积分消耗统计 | P1 |
| 运营配置 | 单用户每日/每月兑换上限 | P1 |
| 运营配置 | 券库存上限控制 | P1 |
### 4.2 首期不包含
- 现金购买券
- 券转赠
- 充电折扣券(规划于 v1.2
- 第三方洗车平台 API 对接(仅本地核销)
---
## 五、业务流程
### 5.1 创建券模板流程
```
管理员进入券模板管理页
→ 选择券类型(首期仅洗车券)
→ 系统根据登录角色自动限制可用范围选项
├─ 平台管理员:可选 全平台 / 指定运营商 / 指定站点
└─ 运营商管理员:可选 本运营商 / 本运营商下指定站点
→ 填写积分兑换价格、库存、有效期、兑换限额等配置
→ 提交 → 系统二次校验 scope 权限边界
→ 保存,默认下架状态,手动上架后对用户可见
```
### 5.2 积分兑换流程
```
用户进入积分商城
→ 系统查询该用户可见的洗车券列表
(过滤逻辑:券为上架状态 + 未过期 + 有库存 + 用户所在运营商/站点在券可用范围内)
→ 用户查看洗车券兑换入口(展示所需积分、库存、有效期)
→ 确认兑换
→ 系统校验:积分是否足够 + 库存是否充足 + 兑换限额检查
→ 原子操作:扣减积分 + 生成用户券记录
→ 写入积分变更日志member_points_record类型=积分兑换消耗)
→ 返回兑换成功,展示券码
```
### 5.3 洗车券核销流程
```
用户出示洗车券二维码
→ 洗车商扫码 → 调用核销接口
→ 系统校验:券是否有效 + 是否已使用 + 是否在有效期内
→ 更新券状态为「已使用」,记录核销时间、核销门店
→ 核销成功响应
```
---
## 六、数据库设计
### 6.1 券模板表 `coupon_template`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint PK | 主键 |
| `name` | varchar(50) | 券名称,如"洗车券" |
| `type` | tinyint | 券类型1=洗车券 2=充电折扣券 |
| `creator_type` | tinyint | 创建人类型1=平台管理员 2=运营商管理员 |
| `creator_merchant_id` | bigint | 创建人所属运营商 ID平台管理员为 NULL |
| `scope_type` | tinyint | 可用范围1=全平台 2=指定运营商 3=指定站点 |
| `points_cost` | int | 兑换所需积分(洗车券用) |
| `discount_rate` | decimal(4,2) | 折扣比例(充电折扣券用,如 0.85 表示 85 折) |
| `stock_total` | int | 总库存(-1 表示不限制) |
| `stock_remain` | int | 剩余库存 |
| `validity_type` | tinyint | 有效期类型1=固定日期 2=领取后N天 |
| `valid_start_time` | datetime | 固定有效期开始validity_type=1 |
| `valid_end_time` | datetime | 固定有效期结束validity_type=1 |
| `valid_days` | int | 领取后有效天数validity_type=2 |
| `daily_limit` | int | 单用户每日兑换上限0=不限) |
| `monthly_limit` | int | 单用户每月兑换上限0=不限) |
| `status` | tinyint | 状态0=下架 1=上架 |
| `description` | varchar(500) | 券使用说明 |
| `create_by` | varchar(64) | 创建人账号 |
| `create_time` | datetime | 创建时间 |
| `update_time` | datetime | 更新时间 |
| `del_flag` | char(1) | 删除标志 |
### 6.2 券可用范围明细表 `coupon_template_scope`
> 用于存储 `scope_type=2`(指定运营商)或 `scope_type=3`(指定站点)时的具体关联数据
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint PK | 主键 |
| `template_id` | bigint | 关联券模板 ID |
| `scope_type` | tinyint | 范围类型2=运营商 3=站点 |
| `scope_id` | bigint | 运营商 ID 或站点 ID |
### 6.3 用户券表 `member_coupon`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint PK | 主键 |
| `coupon_no` | varchar(32) | 券编号(唯一,用于核销) |
| `template_id` | bigint | 关联券模板 ID |
| `member_id` | bigint | 关联会员 ID |
| `coupon_type` | tinyint | 券类型快照1=洗车券 2=充电折扣券 |
| `points_cost` | int | 兑换时消耗的积分(快照,折扣券为 0 |
| `discount_rate` | decimal(4,2) | 折扣比例快照(洗车券为 NULL |
| `status` | tinyint | 状态0=未使用 1=已使用 2=已过期 |
| `source` | tinyint | 来源1=积分兑换 |
| `exchange_time` | datetime | 兑换时间 |
| `expire_time` | datetime | 过期时间 |
| `use_time` | datetime | 核销时间 |
| `use_store_id` | bigint | 核销门店/站点 ID |
| `use_operator` | varchar(64) | 核销操作人 |
| `create_time` | datetime | 创建时间 |
| `del_flag` | char(1) | 删除标志 |
### 6.4 依赖现有表
- `member_points_info`:积分余额更新
- `member_points_record`:新增消耗类型(`type=3` 积分兑换)
- `pile_merchant_info`:运营商信息(校验 creator_merchant_id
- `pile_station_info`:站点信息(校验 scope 范围)
---
## 七、接口设计
### App/小程序端
| 接口 | 方法 | 说明 |
|------|------|------|
| `/coupon/template/list` | GET | 获取用户可兑换券模板列表(按用户所属范围过滤) |
| `/coupon/exchange` | POST | 积分兑换入参templateId |
| `/coupon/my/list` | GET | 我的券包列表入参status |
| `/coupon/my/detail/{couponNo}` | GET | 券详情(含核销二维码) |
### 管理后台
| 接口 | 方法 | 说明 |
|------|------|------|
| `/admin/coupon/template/list` | GET | 券模板列表(运营商管理员仅返回本运营商数据) |
| `/admin/coupon/template/add` | POST | 新增券模板(含 scope 权限校验) |
| `/admin/coupon/template/edit` | PUT | 编辑券模板 |
| `/admin/coupon/template/changeStatus` | PUT | 上下架 |
| `/admin/coupon/record/list` | GET | 兑换记录查询(运营商管理员仅查本运营商数据) |
| `/admin/coupon/verify` | POST | 核销券入参couponNo |
| `/admin/coupon/stats` | GET | 数据统计(运营商管理员仅查本运营商数据) |
---
## 八、关键业务规则
1. **原子性保障**:扣减积分与生成券记录必须在同一事务中完成,防止积分扣了但券没生成
2. **库存防超卖**:使用数据库乐观锁(`UPDATE ... WHERE stock_remain >= 1`)或 Redis 原子操作控制库存
3. **幂等兑换**:前端提交带客户端请求 ID防止重复点击导致多次扣分
4. **券码安全**`coupon_no` 使用 UUID 或带校验位的随机串,不可预测
5. **过期处理**Quartz 定时任务每日凌晨扫描并更新过期券状态
6. **积分变更日志**:兑换成功后写入 `member_points_record`,确保积分流水可追溯
7. **Scope 权限隔离**
- 运营商管理员提交创建/编辑接口时,后端强制校验 `scope_type <= 2`,且 `creator_merchant_id` 必须与登录账号所属运营商一致,防止越权
- 运营商管理员只能查看和操作本运营商创建的券模板,列表接口自动按 `creator_merchant_id` 过滤
8. **折扣券使用校验**v1.2 实现):充电结算时校验充电站是否在折扣券的 `scope` 范围内,不在范围内的券不可抵扣
---
## 九、非功能需求
| 项目 | 要求 |
|------|------|
| 性能 | 兑换接口 P99 < 500ms |
| 并发 | 支持同一券模板 100 QPS 并发兑换 |
| 可靠性 | 积分扣减与券生成保证原子性 |
| 安全 | 核销接口需鉴权;创券接口需强制 scope 边界校验,防止运营商越权 |
---
## 十、数据库建表语句
```sql
-- ----------------------------
-- 券模板表
-- ----------------------------
CREATE TABLE `coupon_template` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(50) NOT NULL COMMENT '券名称,如洗车券',
`type` TINYINT NOT NULL DEFAULT 1 COMMENT '券类型1=洗车券 2=充电折扣券',
`creator_type` TINYINT NOT NULL DEFAULT 1 COMMENT '创建人类型1=平台管理员 2=运营商管理员',
`creator_merchant_id` BIGINT DEFAULT NULL COMMENT '创建人所属运营商ID平台管理员为NULL',
`scope_type` TINYINT NOT NULL DEFAULT 1 COMMENT '可用范围1=全平台 2=指定运营商 3=指定站点',
`points_cost` INT DEFAULT NULL COMMENT '兑换所需积分(洗车券使用)',
`discount_rate` DECIMAL(4, 2) DEFAULT NULL COMMENT '折扣比例充电折扣券使用如0.85表示85折',
`stock_total` INT NOT NULL DEFAULT -1 COMMENT '总库存,-1表示不限制',
`stock_remain` INT NOT NULL DEFAULT -1 COMMENT '剩余库存,-1表示不限制',
`validity_type` TINYINT NOT NULL DEFAULT 2 COMMENT '有效期类型1=固定日期 2=领取后N天',
`valid_start_time` DATETIME DEFAULT NULL COMMENT '固定有效期开始时间validity_type=1时有效',
`valid_end_time` DATETIME DEFAULT NULL COMMENT '固定有效期结束时间validity_type=1时有效',
`valid_days` INT DEFAULT NULL COMMENT '领取后有效天数validity_type=2时有效',
`daily_limit` INT NOT NULL DEFAULT 0 COMMENT '单用户每日兑换上限0=不限',
`monthly_limit` INT NOT NULL DEFAULT 0 COMMENT '单用户每月兑换上限0=不限',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态0=下架 1=上架',
`description` VARCHAR(500) DEFAULT NULL COMMENT '券使用说明',
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建人账号',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志0=存在 2=删除',
PRIMARY KEY (`id`),
KEY `idx_creator_merchant` (`creator_merchant_id`, `status`, `del_flag`),
KEY `idx_scope_type` (`scope_type`, `status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '优惠券模板表';
-- ----------------------------
-- 券可用范围明细表scope_type=2 或 3 时使用)
-- ----------------------------
CREATE TABLE `coupon_template_scope` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`template_id` BIGINT NOT NULL COMMENT '关联券模板ID',
`scope_type` TINYINT NOT NULL COMMENT '范围类型2=运营商 3=站点',
`scope_id` BIGINT NOT NULL COMMENT '运营商ID 或 站点ID',
PRIMARY KEY (`id`),
KEY `idx_template_id` (`template_id`),
UNIQUE KEY `uk_template_scope` (`template_id`, `scope_type`, `scope_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '优惠券可用范围明细表';
-- ----------------------------
-- 用户券表
-- ----------------------------
CREATE TABLE `member_coupon` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`coupon_no` VARCHAR(32) NOT NULL COMMENT '券编号(唯一,用于核销)',
`template_id` BIGINT NOT NULL COMMENT '关联券模板ID',
`member_id` BIGINT NOT NULL COMMENT '关联会员ID',
`coupon_type` TINYINT NOT NULL COMMENT '券类型快照1=洗车券 2=充电折扣券',
`points_cost` INT NOT NULL DEFAULT 0 COMMENT '兑换时消耗的积分快照折扣券为0',
`discount_rate` DECIMAL(4, 2) DEFAULT NULL COMMENT '折扣比例快照洗车券为NULL',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态0=未使用 1=已使用 2=已过期',
`source` TINYINT NOT NULL DEFAULT 1 COMMENT '来源1=积分兑换',
`exchange_time` DATETIME DEFAULT NULL COMMENT '兑换时间',
`expire_time` DATETIME NOT NULL COMMENT '过期时间',
`use_time` DATETIME DEFAULT NULL COMMENT '核销时间',
`use_store_id` BIGINT DEFAULT NULL COMMENT '核销门店/站点ID',
`use_operator` VARCHAR(64) DEFAULT NULL COMMENT '核销操作人',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志0=存在 2=删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_coupon_no` (`coupon_no`),
KEY `idx_member_id` (`member_id`, `status`),
KEY `idx_template_id` (`template_id`),
KEY `idx_expire_time` (`expire_time`, `status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '会员优惠券表';
```
> **索引说明**
> - `coupon_template.idx_creator_merchant`:运营商管理员查看本运营商券模板列表的高频查询
> - `coupon_template_scope.uk_template_scope`:防止同一模板下重复添加相同范围记录
> - `member_coupon.idx_expire_time`:供 Quartz 定时任务扫描过期券使用
> - `member_coupon.uk_coupon_no`:唯一索引防止重复券码、保证核销安全
---
## 十一、后续迭代规划
| 阶段 | 功能 |
|------|------|
| v1.1 | 第三方洗车平台 API 对接(自动核销) |
| v1.2 | 充电折扣券:运营商/平台创建折扣券,充电结算时自动抵扣,校验站点范围 |
| v1.3 | 运营活动:充电满额赠券、节日送券 |
| v2.0 | 积分商城完整体验(多商品、排行榜) |