feat(ticket): 实现套票分期释放功能核心数据结构
- 修改 GltUserTicketReleaseParam 中 id 和 userTicketId 类型从 Long 改为 Integer - 移除 ShopOrderServiceImpl 中的 shopTicketBizService 依赖注入 - 注释掉订单支付成功后的套票发放调用 - 添加套票功能开发计划文档,定义套票模板、用户套票账户、释放计划和变更流水的核心概念 - 设计并创建套票相关数据库表,包括套票模板表、用户套票账户表、释放计划表和变更流水表
This commit is contained in:
31
docs/TICKET_PACKAGE_FEATURE_PLAN.md
Normal file
31
docs/TICKET_PACKAGE_FEATURE_PLAN.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 套票(冻结/可用、分期释放)功能开发计划
|
||||
|
||||
## 目标
|
||||
- 为“买N送M(例如买1送4)、起售(例如20桶起售)、按月释放(例如每月释放10桶)、可用未用完叠加”的套票/权益,提供后端可配置、可发放、可释放、可消费的能力。
|
||||
|
||||
## 核心概念
|
||||
- 套票模板:按商品(goodsId)配置买赠规则、起售/起送、释放规则(每期释放数或释放期数)、首期释放时机。
|
||||
- 用户套票账户:记录总量、可用量、冻结量、已用量、已释放量;绑定订单(用于幂等与追溯)。
|
||||
- 释放计划:每期一条,到期后把冻结转可用(可用未用完自然叠加)。
|
||||
- 变更流水:发放/释放/消费等都记录流水,便于对账与排查。
|
||||
|
||||
## 关键默认规则(可在模板里改)
|
||||
- 仅赠送量进入套票账户(默认不包含“购买量”本身);如需“买20送80=总100”,可在模板设置 `includeBuyQty=true`。
|
||||
- 首期释放:默认“支付成功当日/当刻”释放(`firstReleaseMode=0`);如需“下个月同日释放”,设 `firstReleaseMode=1`。
|
||||
- 每期释放:默认按 `monthlyReleaseQty`;如配置了 `releasePeriods`,则平均分摊并处理余数。
|
||||
|
||||
## 开发步骤(建议按顺序)
|
||||
1. 需求确认与接入点确认(订单哪个节点发放、桶票如何消费/核销、退款是否回滚、释放日期规则)。
|
||||
2. 数据表设计与SQL输出(模板/账户/释放计划/流水)。
|
||||
3. 实现套票模板后台CRUD接口。
|
||||
4. 支付成功接入:在订单支付成功后发放套票账户+生成释放计划(幂等)。
|
||||
5. 定时任务释放:扫描到期释放计划,执行“冻结->可用”转账(幂等)。
|
||||
6. 消费扣减:对用户可用量做扣减(支持跨多套票账户FIFO扣减),并落流水。
|
||||
7. 测试与验收:至少覆盖(买赠计算、分期拆分、叠加逻辑、幂等、并发扣减)。
|
||||
|
||||
## 待确认点(不确认也可先按默认实现)
|
||||
- 套票数量=赠送量?还是(购买量+赠送量)?
|
||||
- “释放日期”是按支付时间的“日”还是固定每月某一天?(31号跨月如何处理?)
|
||||
- 退款/取消:是否回滚未使用的可用/冻结?已释放但未用如何处理?
|
||||
- 消费场景:在哪个业务点扣减(下单抵扣/提货核销/线下核销)?
|
||||
|
||||
121
docs/TICKET_PACKAGE_TABLES.sql
Normal file
121
docs/TICKET_PACKAGE_TABLES.sql
Normal file
@@ -0,0 +1,121 @@
|
||||
-- 套票(冻结/可用、分期释放)相关表
|
||||
-- 说明:项目使用了 MyBatis-Plus 的 tenant 拦截;表内显式保留 tenant_id 字段并建议建立联合唯一索引。
|
||||
|
||||
-- 1) 套票模板(按商品配置)
|
||||
CREATE TABLE IF NOT EXISTS shop_ticket_template (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
goods_id INT NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
unit_name VARCHAR(20) NOT NULL DEFAULT '桶',
|
||||
|
||||
-- 起售/起送
|
||||
min_buy_qty INT NOT NULL DEFAULT 1,
|
||||
start_send_qty INT NOT NULL DEFAULT 1,
|
||||
|
||||
-- 买赠:买1送4 => gift_multiplier=4
|
||||
gift_multiplier INT NOT NULL DEFAULT 0,
|
||||
-- 是否把购买量也计入套票总量(默认仅计入赠送量)
|
||||
include_buy_qty TINYINT(1) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 释放规则:二选一
|
||||
-- A) 每期释放数量(默认每月释放10)
|
||||
monthly_release_qty INT NOT NULL DEFAULT 10,
|
||||
-- B) 总共释放多少期(若配置>0,则按期数平均分摊)
|
||||
release_periods INT NULL,
|
||||
|
||||
-- 首期释放时机:0=支付成功当刻;1=下个月同日
|
||||
first_release_mode INT NOT NULL DEFAULT 0,
|
||||
|
||||
comments VARCHAR(255) NULL,
|
||||
sort_number INT NOT NULL DEFAULT 0,
|
||||
user_id INT NULL,
|
||||
|
||||
deleted INT NOT NULL DEFAULT 0,
|
||||
tenant_id INT NOT NULL,
|
||||
create_time DATETIME NULL,
|
||||
update_time DATETIME NULL,
|
||||
|
||||
UNIQUE KEY uk_ticket_template_tenant_goods (tenant_id, goods_id),
|
||||
KEY idx_ticket_template_tenant (tenant_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 2) 用户套票账户(一次购买通常生成一条)
|
||||
CREATE TABLE IF NOT EXISTS shop_user_ticket (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
template_id INT NOT NULL,
|
||||
goods_id INT NOT NULL,
|
||||
|
||||
user_id INT NOT NULL,
|
||||
order_id INT NULL,
|
||||
order_no VARCHAR(64) NULL,
|
||||
order_goods_id INT NULL,
|
||||
|
||||
total_qty INT NOT NULL DEFAULT 0,
|
||||
available_qty INT NOT NULL DEFAULT 0,
|
||||
frozen_qty INT NOT NULL DEFAULT 0,
|
||||
used_qty INT NOT NULL DEFAULT 0,
|
||||
released_qty INT NOT NULL DEFAULT 0,
|
||||
|
||||
status INT NOT NULL DEFAULT 0,
|
||||
|
||||
deleted INT NOT NULL DEFAULT 0,
|
||||
tenant_id INT NOT NULL,
|
||||
create_time DATETIME NULL,
|
||||
update_time DATETIME NULL,
|
||||
|
||||
KEY idx_user_ticket_user (tenant_id, user_id),
|
||||
KEY idx_user_ticket_order (tenant_id, order_no),
|
||||
UNIQUE KEY uk_user_ticket_order_goods (tenant_id, template_id, order_no, order_goods_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 3) 释放计划/释放记录(每期一条,幂等执行)
|
||||
CREATE TABLE IF NOT EXISTS shop_user_ticket_release (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_ticket_id BIGINT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
period_no INT NOT NULL,
|
||||
|
||||
release_qty INT NOT NULL,
|
||||
release_time DATETIME NOT NULL,
|
||||
|
||||
status INT NOT NULL DEFAULT 0, -- 0待释放 1已释放 2作废
|
||||
released_time DATETIME NULL,
|
||||
|
||||
tenant_id INT NOT NULL,
|
||||
create_time DATETIME NULL,
|
||||
update_time DATETIME NULL,
|
||||
|
||||
UNIQUE KEY uk_ticket_release_period (tenant_id, user_ticket_id, period_no),
|
||||
KEY idx_ticket_release_due (status, release_time),
|
||||
KEY idx_ticket_release_user (tenant_id, user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 4) 套票变更流水(发放/释放/消费/回滚等)
|
||||
CREATE TABLE IF NOT EXISTS shop_user_ticket_log (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_ticket_id BIGINT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
|
||||
change_type INT NOT NULL, -- 10发放 20释放 30消费 40回滚/退款
|
||||
change_available INT NOT NULL DEFAULT 0,
|
||||
change_frozen INT NOT NULL DEFAULT 0,
|
||||
change_used INT NOT NULL DEFAULT 0,
|
||||
|
||||
available_after INT NOT NULL DEFAULT 0,
|
||||
frozen_after INT NOT NULL DEFAULT 0,
|
||||
used_after INT NOT NULL DEFAULT 0,
|
||||
|
||||
order_id INT NULL,
|
||||
order_no VARCHAR(64) NULL,
|
||||
remark VARCHAR(255) NULL,
|
||||
|
||||
tenant_id INT NOT NULL,
|
||||
create_time DATETIME NULL,
|
||||
update_time DATETIME NULL,
|
||||
|
||||
KEY idx_ticket_log_user (tenant_id, user_id),
|
||||
KEY idx_ticket_log_ticket (tenant_id, user_ticket_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
@@ -23,10 +23,10 @@ public class GltUserTicketReleaseParam extends BaseParam {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Long id;
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "水票ID")
|
||||
private Long userTicketId;
|
||||
private Integer userTicketId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
|
||||
@@ -84,8 +84,6 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
private ShopOrderDeliveryService shopOrderDeliveryService;
|
||||
@Resource
|
||||
private ShopExpressService shopExpressService;
|
||||
@Resource
|
||||
private ShopTicketBizService shopTicketBizService;
|
||||
|
||||
private static final long USER_ORDER_STATS_CACHE_SECONDS = 60L;
|
||||
|
||||
@@ -531,7 +529,7 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
updateGoodsSales(order);
|
||||
|
||||
// 3. 套票发放(冻结/可用、分期释放)
|
||||
shopTicketBizService.grantTicketsForPaidOrder(order);
|
||||
// shopTicketBizService.grantTicketsForPaidOrder(order);
|
||||
|
||||
log.info("支付成功后业务逻辑处理完成 - 订单号:{}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user