You've already forked FrameTour-BE
feat(pricing): 增加券码重复使用功能并优化相关数据结构
- 在 PriceVoucherUsageRecord 和 VoucherUsageRecordResp 中添加 usageSequence 字段,用于记录券码的使用序号- 更新 PriceVoucherCode 实体和相关 mapper,增加 currentUseCount 和 lastUsedTime 字段 - 修改 VoucherCodeServiceImpl 和 VoucherServiceImpl 中的券码使用逻辑,支持重复使用 - 新增VoucherBatchOverviewResp、VoucherDetailResp、VoucherUsageSummaryResp 和 VoucherValidationResp 等新的响应 DTO 类,用于提供券码批次概览、详情、使用统计和验证等功能
This commit is contained in:
@@ -49,74 +49,79 @@ public class VoucherCodeServiceImpl implements VoucherCodeService {
|
||||
private VoucherBatchService voucherBatchService;
|
||||
|
||||
@Override
|
||||
public void generateVoucherCodes(Long batchId, Long scenicId, Integer count) {
|
||||
List<PriceVoucherCode> codes = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
PriceVoucherCode code = new PriceVoucherCode();
|
||||
code.setBatchId(batchId);
|
||||
code.setScenicId(scenicId);
|
||||
code.setCode(generateVoucherCode());
|
||||
code.setStatus(VoucherCodeStatus.UNCLAIMED.getCode());
|
||||
code.setCreateTime(new Date());
|
||||
code.setDeleted(0);
|
||||
codes.add(code);
|
||||
}
|
||||
|
||||
for (PriceVoucherCode code : codes) {
|
||||
voucherCodeMapper.insert(code);
|
||||
}
|
||||
public void generateVoucherCodes(Long batchId, Long scenicId, Integer count) {
|
||||
List<PriceVoucherCode> codes = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
PriceVoucherCode code = new PriceVoucherCode();
|
||||
code.setBatchId(batchId);
|
||||
code.setScenicId(scenicId);
|
||||
code.setCode(generateVoucherCode());
|
||||
code.setStatus(VoucherCodeStatus.UNCLAIMED.getCode());
|
||||
code.setCurrentUseCount(0); // 初始化使用次数为0
|
||||
code.setCreateTime(new Date());
|
||||
code.setDeleted(0);
|
||||
codes.add(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public VoucherCodeResp claimVoucher(VoucherClaimReq req) {
|
||||
if (req.getScenicId() == null) {
|
||||
throw new BizException(400, "景区ID不能为空");
|
||||
}
|
||||
if (req.getFaceId() == null) {
|
||||
throw new BizException(400, "用户faceId不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(req.getCode())) {
|
||||
throw new BizException(400, "券码不能为空");
|
||||
}
|
||||
|
||||
// 验证券码是否存在且未被领取
|
||||
LambdaQueryWrapper<PriceVoucherCode> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(PriceVoucherCode::getCode, req.getCode())
|
||||
.eq(PriceVoucherCode::getScenicId, req.getScenicId())
|
||||
.eq(PriceVoucherCode::getDeleted, 0);
|
||||
|
||||
PriceVoucherCode voucherCode = voucherCodeMapper.selectOne(wrapper);
|
||||
if (voucherCode == null) {
|
||||
throw new BizException(400, "券码不存在或不属于该景区");
|
||||
}
|
||||
|
||||
if (!Objects.equals(voucherCode.getStatus(), VoucherCodeStatus.UNCLAIMED.getCode())) {
|
||||
throw new BizException(400, "券码已被领取或已使用");
|
||||
}
|
||||
|
||||
if (!canClaimVoucher(req.getFaceId(), req.getScenicId())) {
|
||||
throw new BizException(400, "该用户在此景区已领取过券码");
|
||||
}
|
||||
|
||||
// 获取券码所属批次
|
||||
PriceVoucherBatchConfig batch = voucherBatchMapper.selectById(voucherCode.getBatchId());
|
||||
if (batch == null || batch.getDeleted() == 1) {
|
||||
throw new BizException(400, "券码批次不存在");
|
||||
}
|
||||
|
||||
// 更新券码状态
|
||||
voucherCode.setFaceId(req.getFaceId());
|
||||
voucherCode.setStatus(VoucherCodeStatus.CLAIMED_UNUSED.getCode());
|
||||
voucherCode.setClaimedTime(new Date());
|
||||
|
||||
voucherCodeMapper.updateById(voucherCode);
|
||||
|
||||
voucherBatchService.updateBatchClaimedCount(batch.getId());
|
||||
|
||||
return convertToResp(voucherCode, batch);
|
||||
for (PriceVoucherCode code : codes) {
|
||||
voucherCodeMapper.insert(code);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public VoucherCodeResp claimVoucher(VoucherClaimReq req) {
|
||||
if (req.getScenicId() == null) {
|
||||
throw new BizException(400, "景区ID不能为空");
|
||||
}
|
||||
if (req.getFaceId() == null) {
|
||||
throw new BizException(400, "用户faceId不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(req.getCode())) {
|
||||
throw new BizException(400, "券码不能为空");
|
||||
}
|
||||
|
||||
// 验证券码是否存在且未被领取
|
||||
LambdaQueryWrapper<PriceVoucherCode> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(PriceVoucherCode::getCode, req.getCode())
|
||||
.eq(PriceVoucherCode::getScenicId, req.getScenicId())
|
||||
.eq(PriceVoucherCode::getDeleted, 0);
|
||||
|
||||
PriceVoucherCode voucherCode = voucherCodeMapper.selectOne(wrapper);
|
||||
if (voucherCode == null) {
|
||||
throw new BizException(400, "券码不存在或不属于该景区");
|
||||
}
|
||||
|
||||
if (!Objects.equals(voucherCode.getStatus(), VoucherCodeStatus.UNCLAIMED.getCode())) {
|
||||
throw new BizException(400, "券码已被领取或已使用");
|
||||
}
|
||||
|
||||
if (!canClaimVoucher(req.getFaceId(), req.getScenicId())) {
|
||||
throw new BizException(400, "该用户在此景区已领取过券码");
|
||||
}
|
||||
|
||||
// 获取券码所属批次
|
||||
PriceVoucherBatchConfig batch = voucherBatchMapper.selectById(voucherCode.getBatchId());
|
||||
if (batch == null || batch.getDeleted() == 1) {
|
||||
throw new BizException(400, "券码批次不存在");
|
||||
}
|
||||
|
||||
// 更新券码状态
|
||||
voucherCode.setFaceId(req.getFaceId());
|
||||
voucherCode.setStatus(VoucherCodeStatus.CLAIMED_UNUSED.getCode());
|
||||
voucherCode.setClaimedTime(new Date());
|
||||
// 确保currentUseCount被初始化
|
||||
if (voucherCode.getCurrentUseCount() == null) {
|
||||
voucherCode.setCurrentUseCount(0);
|
||||
}
|
||||
|
||||
voucherCodeMapper.updateById(voucherCode);
|
||||
|
||||
voucherBatchService.updateBatchClaimedCount(batch.getId());
|
||||
|
||||
return convertToResp(voucherCode, batch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<VoucherCodeResp> queryCodeList(VoucherCodeQueryReq req) {
|
||||
@@ -165,26 +170,31 @@ public class VoucherCodeServiceImpl implements VoucherCodeService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void markCodeAsUsed(Long codeId, String remark) {
|
||||
PriceVoucherCode code = voucherCodeMapper.selectById(codeId);
|
||||
if (code == null || code.getDeleted() == 1) {
|
||||
throw new BizException(404, "券码不存在");
|
||||
}
|
||||
|
||||
if (!Objects.equals(code.getStatus(), VoucherCodeStatus.CLAIMED_UNUSED.getCode())) {
|
||||
throw new BizException(400, "券码状态异常,无法使用");
|
||||
}
|
||||
|
||||
code.setStatus(VoucherCodeStatus.USED.getCode());
|
||||
code.setUsedTime(new Date());
|
||||
code.setRemark(remark);
|
||||
|
||||
voucherCodeMapper.updateById(code);
|
||||
|
||||
voucherBatchService.updateBatchUsedCount(code.getBatchId());
|
||||
@Transactional
|
||||
public void markCodeAsUsed(Long codeId, String remark) {
|
||||
PriceVoucherCode code = voucherCodeMapper.selectById(codeId);
|
||||
if (code == null || code.getDeleted() == 1) {
|
||||
throw new BizException(404, "券码不存在");
|
||||
}
|
||||
|
||||
if (!Objects.equals(code.getStatus(), VoucherCodeStatus.CLAIMED_UNUSED.getCode())) {
|
||||
throw new BizException(400, "券码状态异常,无法使用");
|
||||
}
|
||||
|
||||
// 更新使用计数和时间
|
||||
Integer currentUseCount = code.getCurrentUseCount() != null ? code.getCurrentUseCount() : 0;
|
||||
code.setCurrentUseCount(currentUseCount + 1);
|
||||
code.setLastUsedTime(new Date());
|
||||
|
||||
code.setStatus(VoucherCodeStatus.USED.getCode());
|
||||
code.setUsedTime(new Date());
|
||||
code.setRemark(remark);
|
||||
|
||||
voucherCodeMapper.updateById(code);
|
||||
|
||||
voucherBatchService.updateBatchUsedCount(code.getBatchId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
||||
Integer count = voucherCodeMapper.countByFaceIdAndScenicId(faceId, scenicId);
|
||||
|
||||
@@ -181,78 +181,82 @@ public class VoucherServiceImpl implements IVoucherService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记券码为已使用(支持可重复使用)
|
||||
*
|
||||
* @param voucherCode 券码
|
||||
* @param remark 使用备注
|
||||
* @param orderId 订单ID
|
||||
* @param discountAmount 优惠金额
|
||||
* @param faceId 人脸ID
|
||||
*/
|
||||
@Override
|
||||
public void markVoucherAsUsed(String voucherCode, String remark, String orderId, BigDecimal discountAmount, Long faceId) {
|
||||
if (!StringUtils.hasText(voucherCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PriceVoucherCode voucherCodeEntity = voucherCodeMapper.selectByCode(voucherCode);
|
||||
if (voucherCodeEntity == null || voucherCodeEntity.getDeleted() == 1) {
|
||||
log.warn("券码不存在或已删除: {}", voucherCode);
|
||||
return;
|
||||
}
|
||||
|
||||
PriceVoucherBatchConfig batchConfig = voucherBatchConfigMapper.selectById(voucherCodeEntity.getBatchId());
|
||||
if (batchConfig == null || batchConfig.getDeleted() == 1) {
|
||||
log.warn("券码批次不存在或已删除: batchId={}", voucherCodeEntity.getBatchId());
|
||||
return;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// 创建使用记录
|
||||
PriceVoucherUsageRecord usageRecord = new PriceVoucherUsageRecord();
|
||||
usageRecord.setVoucherCodeId(voucherCodeEntity.getId());
|
||||
usageRecord.setVoucherCode(voucherCode);
|
||||
usageRecord.setFaceId(faceId != null ? faceId : voucherCodeEntity.getFaceId());
|
||||
usageRecord.setScenicId(voucherCodeEntity.getScenicId());
|
||||
usageRecord.setBatchId(voucherCodeEntity.getBatchId());
|
||||
usageRecord.setUseTime(now);
|
||||
usageRecord.setOrderId(orderId);
|
||||
usageRecord.setDiscountAmount(discountAmount);
|
||||
usageRecord.setRemark(remark);
|
||||
usageRecord.setCreateTime(now);
|
||||
usageRecord.setDeleted(0);
|
||||
|
||||
usageRecordMapper.insert(usageRecord);
|
||||
|
||||
// 更新券码使用次数和状态
|
||||
Integer currentUseCount = voucherCodeEntity.getCurrentUseCount() != null ?
|
||||
voucherCodeEntity.getCurrentUseCount() + 1 : 1;
|
||||
voucherCodeEntity.setCurrentUseCount(currentUseCount);
|
||||
voucherCodeEntity.setLastUsedTime(now);
|
||||
|
||||
// 检查是否达到最大使用次数
|
||||
Integer maxUseCount = batchConfig.getMaxUseCount();
|
||||
if (maxUseCount != null && currentUseCount >= maxUseCount) {
|
||||
if (maxUseCount == 1) {
|
||||
// 兼容原有逻辑,单次使用设为USED状态
|
||||
voucherCodeEntity.setStatus(VoucherCodeStatus.USED.getCode());
|
||||
voucherCodeEntity.setUsedTime(now);
|
||||
} else {
|
||||
// 多次使用达到上限设为CLAIMED_EXHAUSTED状态
|
||||
voucherCodeEntity.setStatus(VoucherCodeStatus.CLAIMED_EXHAUSTED.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
voucherCodeMapper.updateById(voucherCodeEntity);
|
||||
|
||||
// 更新批次统计(使用记录数,不是使用券码数)
|
||||
voucherBatchConfigMapper.updateUsedCount(voucherCodeEntity.getBatchId(), 1);
|
||||
|
||||
log.info("券码使用记录已创建: code={}, useCount={}, maxUseCount={}, faceId={}",
|
||||
voucherCode, currentUseCount, maxUseCount, faceId);
|
||||
* 标记券码为已使用(支持可重复使用)
|
||||
*
|
||||
* @param voucherCode 券码
|
||||
* @param remark 使用备注
|
||||
* @param orderId 订单ID
|
||||
* @param discountAmount 优惠金额
|
||||
* @param faceId 人脸ID
|
||||
*/
|
||||
@Override
|
||||
public void markVoucherAsUsed(String voucherCode, String remark, String orderId, BigDecimal discountAmount, Long faceId) {
|
||||
if (!StringUtils.hasText(voucherCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PriceVoucherCode voucherCodeEntity = voucherCodeMapper.selectByCode(voucherCode);
|
||||
if (voucherCodeEntity == null || voucherCodeEntity.getDeleted() == 1) {
|
||||
log.warn("券码不存在或已删除: {}", voucherCode);
|
||||
return;
|
||||
}
|
||||
|
||||
PriceVoucherBatchConfig batchConfig = voucherBatchConfigMapper.selectById(voucherCodeEntity.getBatchId());
|
||||
if (batchConfig == null || batchConfig.getDeleted() == 1) {
|
||||
log.warn("券码批次不存在或已删除: batchId={}", voucherCodeEntity.getBatchId());
|
||||
return;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// 计算新的使用次数和序号
|
||||
Integer currentUseCount = voucherCodeEntity.getCurrentUseCount() != null ?
|
||||
voucherCodeEntity.getCurrentUseCount() : 0;
|
||||
Integer newUseCount = currentUseCount + 1;
|
||||
|
||||
// 创建使用记录
|
||||
PriceVoucherUsageRecord usageRecord = new PriceVoucherUsageRecord();
|
||||
usageRecord.setVoucherCodeId(voucherCodeEntity.getId());
|
||||
usageRecord.setVoucherCode(voucherCode);
|
||||
usageRecord.setFaceId(faceId != null ? faceId : voucherCodeEntity.getFaceId());
|
||||
usageRecord.setScenicId(voucherCodeEntity.getScenicId());
|
||||
usageRecord.setBatchId(voucherCodeEntity.getBatchId());
|
||||
usageRecord.setUsageSequence(newUseCount); // 设置使用序号,表示这是该券码的第几次使用
|
||||
usageRecord.setUseTime(now);
|
||||
usageRecord.setOrderId(orderId);
|
||||
usageRecord.setDiscountAmount(discountAmount);
|
||||
usageRecord.setRemark(remark);
|
||||
usageRecord.setCreateTime(now);
|
||||
usageRecord.setDeleted(0);
|
||||
|
||||
usageRecordMapper.insert(usageRecord);
|
||||
|
||||
// 更新券码使用次数和状态
|
||||
voucherCodeEntity.setCurrentUseCount(newUseCount);
|
||||
voucherCodeEntity.setLastUsedTime(now);
|
||||
|
||||
// 检查是否达到最大使用次数
|
||||
Integer maxUseCount = batchConfig.getMaxUseCount();
|
||||
if (maxUseCount != null && newUseCount >= maxUseCount) {
|
||||
if (maxUseCount == 1) {
|
||||
// 兼容原有逻辑,单次使用设为USED状态
|
||||
voucherCodeEntity.setStatus(VoucherCodeStatus.USED.getCode());
|
||||
voucherCodeEntity.setUsedTime(now);
|
||||
} else {
|
||||
// 多次使用达到上限设为CLAIMED_EXHAUSTED状态
|
||||
voucherCodeEntity.setStatus(VoucherCodeStatus.CLAIMED_EXHAUSTED.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
voucherCodeMapper.updateById(voucherCodeEntity);
|
||||
|
||||
// 更新批次统计(使用记录数,不是使用券码数)
|
||||
voucherBatchConfigMapper.updateUsedCount(voucherCodeEntity.getBatchId(), 1);
|
||||
|
||||
log.info("券码使用记录已创建: code={}, useCount={}, maxUseCount={}, faceId={}, sequence={}",
|
||||
voucherCode, newUseCount, maxUseCount, faceId, newUseCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
||||
if (faceId == null || scenicId == null) {
|
||||
|
||||
Reference in New Issue
Block a user