From 1506ae95b82eb869e74c7d42c98cc76a387dd362 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 16 Sep 2025 20:54:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(pricing):=20=E5=A2=9E=E5=8A=A0=E5=88=B8?= =?UTF-8?q?=E7=A0=81=E9=87=8D=E5=A4=8D=E4=BD=BF=E7=94=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 PriceVoucherUsageRecord 和 VoucherUsageRecordResp 中添加 usageSequence 字段,用于记录券码的使用序号- 更新 PriceVoucherCode 实体和相关 mapper,增加 currentUseCount 和 lastUsedTime 字段 - 修改 VoucherCodeServiceImpl 和 VoucherServiceImpl 中的券码使用逻辑,支持重复使用 - 新增VoucherBatchOverviewResp、VoucherDetailResp、VoucherUsageSummaryResp 和 VoucherValidationResp 等新的响应 DTO 类,用于提供券码批次概览、详情、使用统计和验证等功能 --- .../dto/resp/VoucherBatchOverviewResp.java | 80 ++++++++ .../pricing/dto/resp/VoucherDetailResp.java | 142 ++++++++++++++ .../dto/resp/VoucherUsageRecordResp.java | 5 + .../dto/resp/VoucherUsageSummaryResp.java | 100 ++++++++++ .../dto/resp/VoucherValidationResp.java | 33 ++++ .../entity/PriceVoucherUsageRecord.java | 5 + .../mapper/PriceVoucherCodeMapper.java | 11 -- .../service/impl/VoucherCodeServiceImpl.java | 176 +++++++++--------- .../service/impl/VoucherServiceImpl.java | 144 +++++++------- .../mapper/PriceVoucherCodeMapper.xml | 15 +- 10 files changed, 536 insertions(+), 175 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchOverviewResp.java create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherDetailResp.java create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageSummaryResp.java create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherValidationResp.java diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchOverviewResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchOverviewResp.java new file mode 100644 index 00000000..7644f1a9 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchOverviewResp.java @@ -0,0 +1,80 @@ +package com.ycwl.basic.pricing.dto.resp; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 券码批次概览响应 + */ +@Data +public class VoucherBatchOverviewResp { + + /** + * 批次ID + */ + private Long batchId; + + /** + * 批次名称 + */ + private String batchName; + + /** + * 券码总数 + */ + private Integer totalCodes; + + /** + * 已领取券码数 + */ + private Integer claimedCodes; + + /** + * 已使用券码数(至少使用过一次) + */ + private Integer usedCodes; + + /** + * 已达到最大使用次数的券码数 + */ + private Integer exhaustedCodes; + + /** + * 总使用记录数(支持重复使用) + */ + private Integer totalUsageRecords; + + /** + * 平均每个券码使用次数 + */ + private BigDecimal averageUsagePerCode; + + /** + * 重复使用率 (重复使用的券码数/已使用券码数 * 100) + */ + private BigDecimal repeatUsageRate; + + /** + * 可重复使用设置 + */ + private RepeatableSettings repeatableSettings; + + @Data + public static class RepeatableSettings { + /** + * 每个券码最大使用次数 + */ + private Integer maxUseCount; + + /** + * 每个用户最大使用次数 + */ + private Integer maxUsePerUser; + + /** + * 使用间隔小时数 + */ + private Integer useIntervalHours; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherDetailResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherDetailResp.java new file mode 100644 index 00000000..62ec1d7d --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherDetailResp.java @@ -0,0 +1,142 @@ +package com.ycwl.basic.pricing.dto.resp; + +import com.ycwl.basic.pricing.enums.ProductType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 券码详情响应 + */ +@Data +public class VoucherDetailResp { + + /** + * 券码 + */ + private String voucherCode; + + /** + * 批次信息 + */ + private BatchInfo batchInfo; + + /** + * 使用信息 + */ + private UsageInfo usageInfo; + + /** + * 用户信息 + */ + private UserInfo userInfo; + + /** + * 状态信息 + */ + private StatusInfo statusInfo; + + @Data + public static class BatchInfo { + /** + * 批次ID + */ + private Long batchId; + + /** + * 批次名称 + */ + private String batchName; + + /** + * 优惠类型 (0: 赠品兑换, 1: 金额抵扣, 2: 百分比折扣) + */ + private Integer discountType; + + /** + * 优惠值 + */ + private BigDecimal discountValue; + + /** + * 适用商品类型 + */ + private List applicableProducts; + } + + @Data + public static class UsageInfo { + /** + * 最大使用次数 + */ + private Integer maxUseCount; + + /** + * 当前已使用次数 + */ + private Integer currentUseCount; + + /** + * 剩余使用次数 + */ + private Integer remainingUseCount; + + /** + * 每个用户最大使用次数 + */ + private Integer maxUsePerUser; + + /** + * 使用间隔小时数 + */ + private Integer useIntervalHours; + + /** + * 是否还能使用 + */ + private Boolean canUseMore; + } + + @Data + public static class UserInfo { + /** + * 用户人脸ID + */ + private Long faceId; + + /** + * 该用户已使用此券码的次数 + */ + private Integer userUsageCount; + + /** + * 最后使用时间 + */ + private Date lastUsedTime; + + /** + * 下次可用时间 + */ + private Date nextAvailableTime; + } + + @Data + public static class StatusInfo { + /** + * 状态码 + */ + private Integer status; + + /** + * 状态名称 + */ + private String statusName; + + /** + * 领取时间 + */ + private Date claimedTime; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageRecordResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageRecordResp.java index 59b75a90..f9324710 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageRecordResp.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageRecordResp.java @@ -47,6 +47,11 @@ public class VoucherUsageRecordResp { */ private String batchName; + /** + * 使用序号(该券码的第几次使用) + */ + private Integer usageSequence; + /** * 使用时间 */ diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageSummaryResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageSummaryResp.java new file mode 100644 index 00000000..811687a2 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherUsageSummaryResp.java @@ -0,0 +1,100 @@ +package com.ycwl.basic.pricing.dto.resp; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 券码使用统计摘要响应 + */ +@Data +public class VoucherUsageSummaryResp { + + /** + * 券码 + */ + private String voucherCode; + + /** + * 批次ID + */ + private Long batchId; + + /** + * 批次名称 + */ + private String batchName; + + /** + * 总使用记录数(重复使用产生多条记录) + */ + private Integer totalUsageRecords; + + /** + * 使用过的独立用户数 + */ + private Integer uniqueUsers; + + /** + * 使用统计 + */ + private UsageStatistics usageStatistics; + + /** + * 使用时间轴 + */ + private List usageTimeline; + + @Data + public static class UsageStatistics { + /** + * 最大可使用次数 + */ + private Integer maxUseCount; + + /** + * 当前已使用次数 + */ + private Integer currentUseCount; + + /** + * 剩余使用次数 + */ + private Integer remainingUseCount; + + /** + * 使用率 + */ + private BigDecimal usageRate; + + /** + * 是否还能使用 + */ + private Boolean canUseMore; + } + + @Data + public static class UsageTimeline { + /** + * 使用时间 + */ + private Date useTime; + + /** + * 用户人脸ID + */ + private Long faceId; + + /** + * 订单ID + */ + private String orderId; + + /** + * 优惠金额 + */ + private BigDecimal discountAmount; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherValidationResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherValidationResp.java new file mode 100644 index 00000000..8cbfcccc --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherValidationResp.java @@ -0,0 +1,33 @@ +package com.ycwl.basic.pricing.dto.resp; + +import com.ycwl.basic.pricing.dto.VoucherInfo; +import lombok.Builder; +import lombok.Data; + +/** + * 券码验证响应 + */ +@Data +@Builder +public class VoucherValidationResp { + + /** + * 券码 + */ + private String voucherCode; + + /** + * 是否可用 + */ + private Boolean available; + + /** + * 不可用原因 + */ + private String unavailableReason; + + /** + * 券码详细信息 + */ + private VoucherInfo voucherInfo; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherUsageRecord.java b/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherUsageRecord.java index b8c8c937..288851ef 100644 --- a/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherUsageRecord.java +++ b/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherUsageRecord.java @@ -44,6 +44,11 @@ public class PriceVoucherUsageRecord { */ private Long batchId; + /** + * 使用序号(该券码的第几次使用) + */ + private Integer usageSequence; + /** * 使用时间 */ diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceVoucherCodeMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceVoucherCodeMapper.java index d7b00483..2ed8a99f 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceVoucherCodeMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceVoucherCodeMapper.java @@ -58,17 +58,6 @@ public interface PriceVoucherCodeMapper extends BaseMapper { @Param("faceId") Long faceId, @Param("claimedTime") LocalDateTime claimedTime); - /** - * 使用券码(更新状态为已使用) - * @param code 券码 - * @param usedTime 使用时间 - * @param remark 使用备注 - * @return 影响行数 - */ - int useVoucher(@Param("code") String code, - @Param("usedTime") LocalDateTime usedTime, - @Param("remark") String remark); - /** * 根据批次ID查询券码列表(支持分页) * @param batchId 批次ID diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherCodeServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherCodeServiceImpl.java index b4077399..818d01db 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherCodeServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherCodeServiceImpl.java @@ -49,74 +49,79 @@ public class VoucherCodeServiceImpl implements VoucherCodeService { private VoucherBatchService voucherBatchService; @Override - public void generateVoucherCodes(Long batchId, Long scenicId, Integer count) { - List 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 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 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 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 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); diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherServiceImpl.java index a2860d93..70d796f8 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherServiceImpl.java @@ -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) { diff --git a/src/main/resources/mapper/PriceVoucherCodeMapper.xml b/src/main/resources/mapper/PriceVoucherCodeMapper.xml index d3bc5293..ea20b164 100644 --- a/src/main/resources/mapper/PriceVoucherCodeMapper.xml +++ b/src/main/resources/mapper/PriceVoucherCodeMapper.xml @@ -11,6 +11,8 @@ + + @@ -20,7 +22,7 @@ id, batch_id, scenic_id, code, status, face_id, claimed_time, used_time, - remark, create_time, update_time, deleted, deleted_at + current_use_count, last_used_time, remark, create_time, update_time, deleted, deleted_at SELECT