You've already forked FrameTour-BE
Merge branch 'refs/heads/price_inquery'
Some checks failed
ZhenTu-BE/pipeline/head There was a failure building this commit
Some checks failed
ZhenTu-BE/pipeline/head There was a failure building this commit
This commit is contained in:
@@ -114,9 +114,54 @@ public class VoucherInfo {
|
|||||||
* 剩余可使用次数
|
* 剩余可使用次数
|
||||||
*/
|
*/
|
||||||
private Integer remainingUseCount;
|
private Integer remainingUseCount;
|
||||||
|
/**
|
||||||
|
* 是否还能使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseMore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最后使用时间
|
* 最后使用时间
|
||||||
*/
|
*/
|
||||||
private Date lastUsedTime;
|
private Date lastUsedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期开始时间
|
||||||
|
*/
|
||||||
|
private Date validStartTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期结束时间
|
||||||
|
*/
|
||||||
|
private Date validEndTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定时间是否在有效期内
|
||||||
|
* @param checkTime 待检查的时间
|
||||||
|
* @return true-在有效期内,false-不在有效期内
|
||||||
|
*/
|
||||||
|
public boolean isWithinValidTimeRange(Date checkTime) {
|
||||||
|
if (checkTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查开始时间
|
||||||
|
if (validStartTime != null && checkTime.before(validStartTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查结束时间
|
||||||
|
if (validEndTime != null && checkTime.after(validEndTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前时间是否在有效期内
|
||||||
|
* @return true-在有效期内,false-不在有效期内
|
||||||
|
*/
|
||||||
|
public boolean isWithinValidTimeRange() {
|
||||||
|
return isWithinValidTimeRange(new Date());
|
||||||
|
}
|
||||||
}
|
}
|
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.Min;
|
|||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,4 +71,14 @@ public class VoucherBatchCreateReqV2 {
|
|||||||
* 两次使用间隔小时数(NULL表示无间隔限制)
|
* 两次使用间隔小时数(NULL表示无间隔限制)
|
||||||
*/
|
*/
|
||||||
private Integer useIntervalHours;
|
private Integer useIntervalHours;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期开始时间(NULL表示无开始时间限制)
|
||||||
|
*/
|
||||||
|
private Date validStartTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期结束时间(NULL表示无结束时间限制)
|
||||||
|
*/
|
||||||
|
private Date validEndTime;
|
||||||
}
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -30,4 +30,14 @@ public class VoucherBatchResp {
|
|||||||
* null表示适用所有商品类型
|
* null表示适用所有商品类型
|
||||||
*/
|
*/
|
||||||
private List<ProductType> applicableProducts;
|
private List<ProductType> applicableProducts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期开始时间
|
||||||
|
*/
|
||||||
|
private Date validStartTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期结束时间
|
||||||
|
*/
|
||||||
|
private Date validEndTime;
|
||||||
}
|
}
|
@@ -24,4 +24,30 @@ public class VoucherCodeResp {
|
|||||||
private String discountTypeName;
|
private String discountTypeName;
|
||||||
private String discountDescription;
|
private String discountDescription;
|
||||||
private BigDecimal discountValue;
|
private BigDecimal discountValue;
|
||||||
|
|
||||||
|
// 可重复使用券码相关字段
|
||||||
|
/**
|
||||||
|
* 当前使用次数
|
||||||
|
*/
|
||||||
|
private Integer currentUseCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大使用次数
|
||||||
|
*/
|
||||||
|
private Integer maxUseCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剩余使用次数
|
||||||
|
*/
|
||||||
|
private Integer remainingUseCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否还能使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseMore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后使用时间
|
||||||
|
*/
|
||||||
|
private Date lastUsedTime;
|
||||||
}
|
}
|
@@ -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<ProductType> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -47,6 +47,11 @@ public class VoucherUsageRecordResp {
|
|||||||
*/
|
*/
|
||||||
private String batchName;
|
private String batchName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用序号(该券码的第几次使用)
|
||||||
|
*/
|
||||||
|
private Integer usageSequence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用时间
|
* 使用时间
|
||||||
*/
|
*/
|
||||||
|
@@ -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> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
@@ -115,6 +115,49 @@ public class PriceVoucherBatchConfig {
|
|||||||
|
|
||||||
private Date deletedAt;
|
private Date deletedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期开始时间(NULL表示无开始时间限制)
|
||||||
|
*/
|
||||||
|
@TableField("valid_start_time")
|
||||||
|
private Date validStartTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期结束时间(NULL表示无结束时间限制)
|
||||||
|
*/
|
||||||
|
@TableField("valid_end_time")
|
||||||
|
private Date validEndTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前时间是否在有效期内
|
||||||
|
* @return true-在有效期内,false-不在有效期内
|
||||||
|
*/
|
||||||
|
public boolean isWithinValidTimeRange() {
|
||||||
|
return isWithinValidTimeRange(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定时间是否在有效期内
|
||||||
|
* @param checkTime 待检查的时间
|
||||||
|
* @return true-在有效期内,false-不在有效期内
|
||||||
|
*/
|
||||||
|
public boolean isWithinValidTimeRange(Date checkTime) {
|
||||||
|
if (checkTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查开始时间
|
||||||
|
if (validStartTime != null && checkTime.before(validStartTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查结束时间
|
||||||
|
if (validEndTime != null && checkTime.after(validEndTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取适用商品类型列表
|
* 获取适用商品类型列表
|
||||||
*/
|
*/
|
||||||
|
@@ -44,6 +44,11 @@ public class PriceVoucherUsageRecord {
|
|||||||
*/
|
*/
|
||||||
private Long batchId;
|
private Long batchId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用序号(该券码的第几次使用)
|
||||||
|
*/
|
||||||
|
private Integer usageSequence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用时间
|
* 使用时间
|
||||||
*/
|
*/
|
||||||
|
@@ -58,17 +58,6 @@ public interface PriceVoucherCodeMapper extends BaseMapper<PriceVoucherCode> {
|
|||||||
@Param("faceId") Long faceId,
|
@Param("faceId") Long faceId,
|
||||||
@Param("claimedTime") LocalDateTime claimedTime);
|
@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查询券码列表(支持分页)
|
* 根据批次ID查询券码列表(支持分页)
|
||||||
* @param batchId 批次ID
|
* @param batchId 批次ID
|
||||||
|
@@ -33,6 +33,17 @@ public interface PriceVoucherUsageRecordMapper extends BaseMapper<PriceVoucherUs
|
|||||||
*/
|
*/
|
||||||
@Select("SELECT * FROM price_voucher_usage_record WHERE voucher_code = #{voucherCode} AND deleted = 0 ORDER BY use_time DESC")
|
@Select("SELECT * FROM price_voucher_usage_record WHERE voucher_code = #{voucherCode} AND deleted = 0 ORDER BY use_time DESC")
|
||||||
List<PriceVoucherUsageRecord> selectByVoucherCode(@Param("voucherCode") String voucherCode);
|
List<PriceVoucherUsageRecord> selectByVoucherCode(@Param("voucherCode") String voucherCode);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据券码ID和用户ID查询使用记录
|
||||||
|
*
|
||||||
|
* @param voucherCodeId 券码ID
|
||||||
|
* @param faceId 用户faceId
|
||||||
|
* @return 使用记录列表
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_voucher_usage_record WHERE voucher_code_id = #{voucherCodeId} AND face_id = #{faceId} AND deleted = 0 ORDER BY use_time DESC")
|
||||||
|
List<PriceVoucherUsageRecord> selectByVoucherCodeAndFaceId(@Param("voucherCodeId") Long voucherCodeId, @Param("faceId") Long faceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户和景区查询使用记录
|
* 根据用户和景区查询使用记录
|
||||||
|
@@ -2,6 +2,9 @@ package com.ycwl.basic.pricing.service;
|
|||||||
|
|
||||||
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
||||||
import com.ycwl.basic.pricing.dto.VoucherInfo;
|
import com.ycwl.basic.pricing.dto.VoucherInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherBatchOverviewResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherDetailResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherUsageSummaryResp;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -61,6 +64,28 @@ public interface IVoucherService {
|
|||||||
*/
|
*/
|
||||||
BigDecimal calculateVoucherDiscount(VoucherInfo voucherInfo, DiscountDetectionContext context);
|
BigDecimal calculateVoucherDiscount(VoucherInfo voucherInfo, DiscountDetectionContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取券码详细信息
|
||||||
|
* @param voucherCode 券码
|
||||||
|
* @param faceId 用户人脸ID(可选)
|
||||||
|
* @return 券码详细信息
|
||||||
|
*/
|
||||||
|
VoucherDetailResp getVoucherDetail(String voucherCode, Long faceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取券码使用统计摘要
|
||||||
|
* @param voucherCode 券码
|
||||||
|
* @return 券码使用统计摘要
|
||||||
|
*/
|
||||||
|
VoucherUsageSummaryResp getVoucherUsageSummary(String voucherCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取批次券码概览
|
||||||
|
* @param batchId 批次ID
|
||||||
|
* @return 批次概览信息
|
||||||
|
*/
|
||||||
|
VoucherBatchOverviewResp getBatchOverview(Long batchId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最优的券码(如果用户有多个可用券码)
|
* 获取最优的券码(如果用户有多个可用券码)
|
||||||
* @param faceId 用户faceId
|
* @param faceId 用户faceId
|
||||||
|
@@ -118,6 +118,13 @@ public class VoucherBatchServiceImpl implements VoucherBatchService {
|
|||||||
throw new BizException(400, "使用间隔小时数不能为负数");
|
throw new BizException(400, "使用间隔小时数不能为负数");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证时间范围参数
|
||||||
|
if (req.getValidStartTime() != null && req.getValidEndTime() != null) {
|
||||||
|
if (req.getValidStartTime().after(req.getValidEndTime())) {
|
||||||
|
throw new BizException(400, "有效期开始时间不能晚于结束时间");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PriceVoucherBatchConfig batch = new PriceVoucherBatchConfig();
|
PriceVoucherBatchConfig batch = new PriceVoucherBatchConfig();
|
||||||
BeanUtils.copyProperties(req, batch);
|
BeanUtils.copyProperties(req, batch);
|
||||||
|
|
||||||
|
@@ -49,74 +49,79 @@ public class VoucherCodeServiceImpl implements VoucherCodeService {
|
|||||||
private VoucherBatchService voucherBatchService;
|
private VoucherBatchService voucherBatchService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generateVoucherCodes(Long batchId, Long scenicId, Integer count) {
|
public void generateVoucherCodes(Long batchId, Long scenicId, Integer count) {
|
||||||
List<PriceVoucherCode> codes = new ArrayList<>();
|
List<PriceVoucherCode> codes = new ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
PriceVoucherCode code = new PriceVoucherCode();
|
PriceVoucherCode code = new PriceVoucherCode();
|
||||||
code.setBatchId(batchId);
|
code.setBatchId(batchId);
|
||||||
code.setScenicId(scenicId);
|
code.setScenicId(scenicId);
|
||||||
code.setCode(generateVoucherCode());
|
code.setCode(generateVoucherCode());
|
||||||
code.setStatus(VoucherCodeStatus.UNCLAIMED.getCode());
|
code.setStatus(VoucherCodeStatus.UNCLAIMED.getCode());
|
||||||
code.setCreateTime(new Date());
|
code.setCurrentUseCount(0); // 初始化使用次数为0
|
||||||
code.setDeleted(0);
|
code.setCreateTime(new Date());
|
||||||
codes.add(code);
|
code.setDeleted(0);
|
||||||
}
|
codes.add(code);
|
||||||
|
|
||||||
for (PriceVoucherCode code : codes) {
|
|
||||||
voucherCodeMapper.insert(code);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
for (PriceVoucherCode code : codes) {
|
||||||
@Transactional
|
voucherCodeMapper.insert(code);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public Page<VoucherCodeResp> queryCodeList(VoucherCodeQueryReq req) {
|
public Page<VoucherCodeResp> queryCodeList(VoucherCodeQueryReq req) {
|
||||||
@@ -165,26 +170,31 @@ public class VoucherCodeServiceImpl implements VoucherCodeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void markCodeAsUsed(Long codeId, String remark) {
|
public void markCodeAsUsed(Long codeId, String remark) {
|
||||||
PriceVoucherCode code = voucherCodeMapper.selectById(codeId);
|
PriceVoucherCode code = voucherCodeMapper.selectById(codeId);
|
||||||
if (code == null || code.getDeleted() == 1) {
|
if (code == null || code.getDeleted() == 1) {
|
||||||
throw new BizException(404, "券码不存在");
|
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
||||||
Integer count = voucherCodeMapper.countByFaceIdAndScenicId(faceId, scenicId);
|
Integer count = voucherCodeMapper.countByFaceIdAndScenicId(faceId, scenicId);
|
||||||
@@ -257,6 +267,24 @@ public class VoucherCodeServiceImpl implements VoucherCodeService {
|
|||||||
resp.setDiscountTypeName(discountType.getName());
|
resp.setDiscountTypeName(discountType.getName());
|
||||||
resp.setDiscountDescription(discountType.getDescription());
|
resp.setDiscountDescription(discountType.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置可重复使用相关字段
|
||||||
|
Integer currentUseCount = code.getCurrentUseCount() != null ? code.getCurrentUseCount() : 0;
|
||||||
|
resp.setCurrentUseCount(currentUseCount);
|
||||||
|
resp.setMaxUseCount(batch.getMaxUseCount());
|
||||||
|
resp.setLastUsedTime(code.getLastUsedTime());
|
||||||
|
|
||||||
|
// 计算剩余使用次数
|
||||||
|
if (batch.getMaxUseCount() != null) {
|
||||||
|
int remaining = batch.getMaxUseCount() - currentUseCount;
|
||||||
|
resp.setRemainingUseCount(Math.max(0, remaining));
|
||||||
|
|
||||||
|
// 计算是否还能使用
|
||||||
|
resp.setCanUseMore(currentUseCount < batch.getMaxUseCount());
|
||||||
|
} else {
|
||||||
|
resp.setRemainingUseCount(null); // 无限制
|
||||||
|
resp.setCanUseMore(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VoucherCodeStatus status = VoucherCodeStatus.getByCode(code.getStatus());
|
VoucherCodeStatus status = VoucherCodeStatus.getByCode(code.getStatus());
|
||||||
|
@@ -1,5 +1,34 @@
|
|||||||
package com.ycwl.basic.pricing.service.impl;
|
package com.ycwl.basic.pricing.service.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
||||||
|
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||||
|
import com.ycwl.basic.pricing.dto.VoucherInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherDetailResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherUsageSummaryResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.VoucherBatchOverviewResp;
|
||||||
|
import com.ycwl.basic.pricing.enums.ProductType;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceVoucherBatchConfig;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceVoucherCode;
|
||||||
|
import com.ycwl.basic.pricing.enums.VoucherCodeStatus;
|
||||||
|
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceVoucherBatchConfigMapper;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceVoucherCodeMapper;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceVoucherUsageRecordMapper;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceVoucherUsageRecord;
|
||||||
|
import com.ycwl.basic.pricing.service.IVoucherService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
||||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||||
import com.ycwl.basic.pricing.dto.VoucherInfo;
|
import com.ycwl.basic.pricing.dto.VoucherInfo;
|
||||||
@@ -56,13 +85,27 @@ public class VoucherServiceImpl implements IVoucherService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VoucherInfo voucherInfo = buildVoucherInfo(voucherCodeEntity, batchConfig);
|
||||||
|
|
||||||
|
// 检查券码批次的时间范围,提供详细的时间范围信息
|
||||||
|
if (!batchConfig.isWithinValidTimeRange()) {
|
||||||
|
voucherInfo.setAvailable(false);
|
||||||
|
Date now = new Date();
|
||||||
|
if (batchConfig.getValidStartTime() != null && now.before(batchConfig.getValidStartTime())) {
|
||||||
|
voucherInfo.setUnavailableReason("券码尚未生效");
|
||||||
|
} else if (batchConfig.getValidEndTime() != null && now.after(batchConfig.getValidEndTime())) {
|
||||||
|
voucherInfo.setUnavailableReason("券码已过期");
|
||||||
|
} else {
|
||||||
|
voucherInfo.setUnavailableReason("券码不在有效期内");
|
||||||
|
}
|
||||||
|
return voucherInfo;
|
||||||
|
}
|
||||||
|
|
||||||
// 验证景区匹配
|
// 验证景区匹配
|
||||||
if (scenicId != null && !scenicId.equals(voucherCodeEntity.getScenicId())) {
|
if (scenicId != null && !scenicId.equals(voucherCodeEntity.getScenicId())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
VoucherInfo voucherInfo = buildVoucherInfo(voucherCodeEntity, batchConfig);
|
|
||||||
|
|
||||||
// 检查券码状态和可用性,包含完整的重复使用权限验证
|
// 检查券码状态和可用性,包含完整的重复使用权限验证
|
||||||
if (VoucherCodeStatus.UNCLAIMED.getCode().equals(voucherCodeEntity.getStatus())) {
|
if (VoucherCodeStatus.UNCLAIMED.getCode().equals(voucherCodeEntity.getStatus())) {
|
||||||
// 未领取状态,也需要检查用户的重复使用权限
|
// 未领取状态,也需要检查用户的重复使用权限
|
||||||
@@ -152,78 +195,82 @@ public class VoucherServiceImpl implements IVoucherService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记券码为已使用(支持可重复使用)
|
* 标记券码为已使用(支持可重复使用)
|
||||||
*
|
*
|
||||||
* @param voucherCode 券码
|
* @param voucherCode 券码
|
||||||
* @param remark 使用备注
|
* @param remark 使用备注
|
||||||
* @param orderId 订单ID
|
* @param orderId 订单ID
|
||||||
* @param discountAmount 优惠金额
|
* @param discountAmount 优惠金额
|
||||||
* @param faceId 人脸ID
|
* @param faceId 人脸ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void markVoucherAsUsed(String voucherCode, String remark, String orderId, BigDecimal discountAmount, Long faceId) {
|
public void markVoucherAsUsed(String voucherCode, String remark, String orderId, BigDecimal discountAmount, Long faceId) {
|
||||||
if (!StringUtils.hasText(voucherCode)) {
|
if (!StringUtils.hasText(voucherCode)) {
|
||||||
return;
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
public boolean canClaimVoucher(Long faceId, Long scenicId) {
|
||||||
if (faceId == null || scenicId == null) {
|
if (faceId == null || scenicId == null) {
|
||||||
@@ -336,6 +383,232 @@ public class VoucherServiceImpl implements IVoucherService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VoucherDetailResp getVoucherDetail(String voucherCode, Long faceId) {
|
||||||
|
if (!StringUtils.hasText(voucherCode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceVoucherCode voucherCodeEntity = voucherCodeMapper.selectByCode(voucherCode);
|
||||||
|
if (voucherCodeEntity == null || voucherCodeEntity.getDeleted() == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceVoucherBatchConfig batchConfig = voucherBatchConfigMapper.selectById(voucherCodeEntity.getBatchId());
|
||||||
|
if (batchConfig == null || batchConfig.getDeleted() == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoucherDetailResp detail = new VoucherDetailResp();
|
||||||
|
detail.setVoucherCode(voucherCode);
|
||||||
|
|
||||||
|
// 设置批次信息
|
||||||
|
VoucherDetailResp.BatchInfo batchInfo = new VoucherDetailResp.BatchInfo();
|
||||||
|
batchInfo.setBatchId(batchConfig.getId());
|
||||||
|
batchInfo.setBatchName(batchConfig.getBatchName());
|
||||||
|
batchInfo.setDiscountType(batchConfig.getDiscountType());
|
||||||
|
batchInfo.setDiscountValue(batchConfig.getDiscountValue());
|
||||||
|
batchInfo.setApplicableProducts(batchConfig.getApplicableProducts());
|
||||||
|
detail.setBatchInfo(batchInfo);
|
||||||
|
|
||||||
|
// 设置使用信息
|
||||||
|
VoucherDetailResp.UsageInfo usageInfo = new VoucherDetailResp.UsageInfo();
|
||||||
|
Integer currentUseCount = voucherCodeEntity.getCurrentUseCount() != null ? voucherCodeEntity.getCurrentUseCount() : 0;
|
||||||
|
usageInfo.setMaxUseCount(batchConfig.getMaxUseCount());
|
||||||
|
usageInfo.setCurrentUseCount(currentUseCount);
|
||||||
|
usageInfo.setMaxUsePerUser(batchConfig.getMaxUsePerUser());
|
||||||
|
usageInfo.setUseIntervalHours(batchConfig.getUseIntervalHours());
|
||||||
|
|
||||||
|
// 计算剩余使用次数和是否还能使用
|
||||||
|
if (batchConfig.getMaxUseCount() != null) {
|
||||||
|
int remaining = batchConfig.getMaxUseCount() - currentUseCount;
|
||||||
|
usageInfo.setRemainingUseCount(Math.max(0, remaining));
|
||||||
|
usageInfo.setCanUseMore(currentUseCount < batchConfig.getMaxUseCount());
|
||||||
|
} else {
|
||||||
|
usageInfo.setRemainingUseCount(null);
|
||||||
|
usageInfo.setCanUseMore(true);
|
||||||
|
}
|
||||||
|
detail.setUsageInfo(usageInfo);
|
||||||
|
|
||||||
|
// 设置用户信息
|
||||||
|
if (faceId != null) {
|
||||||
|
VoucherDetailResp.UserInfo userInfo = new VoucherDetailResp.UserInfo();
|
||||||
|
userInfo.setFaceId(faceId);
|
||||||
|
|
||||||
|
// 计算该用户使用此券码的次数
|
||||||
|
List<PriceVoucherUsageRecord> userUsageRecords = usageRecordMapper.selectByVoucherCodeAndFaceId(voucherCodeEntity.getId(), faceId);
|
||||||
|
userInfo.setUserUsageCount(userUsageRecords.size());
|
||||||
|
|
||||||
|
if (voucherCodeEntity.getLastUsedTime() != null) {
|
||||||
|
userInfo.setLastUsedTime(voucherCodeEntity.getLastUsedTime());
|
||||||
|
|
||||||
|
// 计算下次可用时间
|
||||||
|
if (batchConfig.getUseIntervalHours() != null) {
|
||||||
|
Date nextAvailableTime = new Date(voucherCodeEntity.getLastUsedTime().getTime() +
|
||||||
|
batchConfig.getUseIntervalHours() * 60 * 60 * 1000L);
|
||||||
|
userInfo.setNextAvailableTime(nextAvailableTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detail.setUserInfo(userInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置状态信息
|
||||||
|
VoucherDetailResp.StatusInfo statusInfo = new VoucherDetailResp.StatusInfo();
|
||||||
|
statusInfo.setStatus(voucherCodeEntity.getStatus());
|
||||||
|
VoucherCodeStatus statusEnum = VoucherCodeStatus.getByCode(voucherCodeEntity.getStatus());
|
||||||
|
if (statusEnum != null) {
|
||||||
|
statusInfo.setStatusName(statusEnum.getName());
|
||||||
|
}
|
||||||
|
statusInfo.setClaimedTime(voucherCodeEntity.getClaimedTime());
|
||||||
|
detail.setStatusInfo(statusInfo);
|
||||||
|
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VoucherUsageSummaryResp getVoucherUsageSummary(String voucherCode) {
|
||||||
|
if (!StringUtils.hasText(voucherCode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceVoucherCode voucherCodeEntity = voucherCodeMapper.selectByCode(voucherCode);
|
||||||
|
if (voucherCodeEntity == null || voucherCodeEntity.getDeleted() == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceVoucherBatchConfig batchConfig = voucherBatchConfigMapper.selectById(voucherCodeEntity.getBatchId());
|
||||||
|
if (batchConfig == null || batchConfig.getDeleted() == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoucherUsageSummaryResp summary = new VoucherUsageSummaryResp();
|
||||||
|
summary.setVoucherCode(voucherCode);
|
||||||
|
summary.setBatchId(batchConfig.getId());
|
||||||
|
summary.setBatchName(batchConfig.getBatchName());
|
||||||
|
|
||||||
|
// 获取使用记录
|
||||||
|
List<PriceVoucherUsageRecord> usageRecords = usageRecordMapper.selectByVoucherCodeId(voucherCodeEntity.getId());
|
||||||
|
summary.setTotalUsageRecords(usageRecords.size());
|
||||||
|
|
||||||
|
// 计算独立用户数
|
||||||
|
long uniqueUsers = usageRecords.stream()
|
||||||
|
.mapToLong(PriceVoucherUsageRecord::getFaceId)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
summary.setUniqueUsers((int) uniqueUsers);
|
||||||
|
|
||||||
|
// 设置使用统计
|
||||||
|
VoucherUsageSummaryResp.UsageStatistics statistics = new VoucherUsageSummaryResp.UsageStatistics();
|
||||||
|
Integer currentUseCount = voucherCodeEntity.getCurrentUseCount() != null ? voucherCodeEntity.getCurrentUseCount() : 0;
|
||||||
|
statistics.setMaxUseCount(batchConfig.getMaxUseCount());
|
||||||
|
statistics.setCurrentUseCount(currentUseCount);
|
||||||
|
|
||||||
|
if (batchConfig.getMaxUseCount() != null) {
|
||||||
|
int remaining = batchConfig.getMaxUseCount() - currentUseCount;
|
||||||
|
statistics.setRemainingUseCount(Math.max(0, remaining));
|
||||||
|
statistics.setCanUseMore(currentUseCount < batchConfig.getMaxUseCount());
|
||||||
|
|
||||||
|
// 计算使用率
|
||||||
|
if (batchConfig.getMaxUseCount() > 0) {
|
||||||
|
BigDecimal usageRate = new BigDecimal(currentUseCount)
|
||||||
|
.divide(new BigDecimal(batchConfig.getMaxUseCount()), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(new BigDecimal(100));
|
||||||
|
statistics.setUsageRate(usageRate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statistics.setRemainingUseCount(null);
|
||||||
|
statistics.setCanUseMore(true);
|
||||||
|
statistics.setUsageRate(null);
|
||||||
|
}
|
||||||
|
summary.setUsageStatistics(statistics);
|
||||||
|
|
||||||
|
// 设置使用时间轴
|
||||||
|
List<VoucherUsageSummaryResp.UsageTimeline> timeline = usageRecords.stream()
|
||||||
|
.map(record -> {
|
||||||
|
VoucherUsageSummaryResp.UsageTimeline item = new VoucherUsageSummaryResp.UsageTimeline();
|
||||||
|
item.setUseTime(record.getUseTime());
|
||||||
|
item.setFaceId(record.getFaceId());
|
||||||
|
item.setOrderId(record.getOrderId());
|
||||||
|
item.setDiscountAmount(record.getDiscountAmount());
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
summary.setUsageTimeline(timeline);
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VoucherBatchOverviewResp getBatchOverview(Long batchId) {
|
||||||
|
if (batchId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceVoucherBatchConfig batchConfig = voucherBatchConfigMapper.selectById(batchId);
|
||||||
|
if (batchConfig == null || batchConfig.getDeleted() == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoucherBatchOverviewResp overview = new VoucherBatchOverviewResp();
|
||||||
|
overview.setBatchId(batchId);
|
||||||
|
overview.setBatchName(batchConfig.getBatchName());
|
||||||
|
overview.setTotalCodes(batchConfig.getTotalCount());
|
||||||
|
overview.setClaimedCodes(batchConfig.getClaimedCount());
|
||||||
|
|
||||||
|
// 查询批次下的券码统计
|
||||||
|
List<PriceVoucherCode> allCodes = voucherCodeMapper.selectByBatchId(batchId);
|
||||||
|
|
||||||
|
// 计算已使用券码数(至少使用过一次)
|
||||||
|
int usedCodes = (int) allCodes.stream()
|
||||||
|
.filter(code -> code.getCurrentUseCount() != null && code.getCurrentUseCount() > 0)
|
||||||
|
.count();
|
||||||
|
overview.setUsedCodes(usedCodes);
|
||||||
|
|
||||||
|
// 计算已达到最大使用次数的券码数
|
||||||
|
int exhaustedCodes = 0;
|
||||||
|
if (batchConfig.getMaxUseCount() != null) {
|
||||||
|
exhaustedCodes = (int) allCodes.stream()
|
||||||
|
.filter(code -> code.getCurrentUseCount() != null &&
|
||||||
|
code.getCurrentUseCount() >= batchConfig.getMaxUseCount())
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
overview.setExhaustedCodes(exhaustedCodes);
|
||||||
|
|
||||||
|
// 设置总使用记录数
|
||||||
|
overview.setTotalUsageRecords(batchConfig.getUsedCount());
|
||||||
|
|
||||||
|
// 计算平均每个券码使用次数
|
||||||
|
if (usedCodes > 0) {
|
||||||
|
BigDecimal averageUsage = new BigDecimal(batchConfig.getUsedCount())
|
||||||
|
.divide(new BigDecimal(usedCodes), 2, RoundingMode.HALF_UP);
|
||||||
|
overview.setAverageUsagePerCode(averageUsage);
|
||||||
|
} else {
|
||||||
|
overview.setAverageUsagePerCode(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算重复使用率
|
||||||
|
if (usedCodes > 0) {
|
||||||
|
int repeatUsedCodes = (int) allCodes.stream()
|
||||||
|
.filter(code -> code.getCurrentUseCount() != null && code.getCurrentUseCount() > 1)
|
||||||
|
.count();
|
||||||
|
BigDecimal repeatUsageRate = new BigDecimal(repeatUsedCodes)
|
||||||
|
.divide(new BigDecimal(usedCodes), 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(new BigDecimal(100));
|
||||||
|
overview.setRepeatUsageRate(repeatUsageRate);
|
||||||
|
} else {
|
||||||
|
overview.setRepeatUsageRate(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置可重复使用配置
|
||||||
|
VoucherBatchOverviewResp.RepeatableSettings settings = new VoucherBatchOverviewResp.RepeatableSettings();
|
||||||
|
settings.setMaxUseCount(batchConfig.getMaxUseCount());
|
||||||
|
settings.setMaxUsePerUser(batchConfig.getMaxUsePerUser());
|
||||||
|
settings.setUseIntervalHours(batchConfig.getUseIntervalHours());
|
||||||
|
overview.setRepeatableSettings(settings);
|
||||||
|
|
||||||
|
return overview;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VoucherInfo getBestVoucher(Long faceId, Long scenicId, DiscountDetectionContext context) {
|
public VoucherInfo getBestVoucher(Long faceId, Long scenicId, DiscountDetectionContext context) {
|
||||||
List<VoucherInfo> availableVouchers = getAvailableVouchers(faceId, scenicId);
|
List<VoucherInfo> availableVouchers = getAvailableVouchers(faceId, scenicId);
|
||||||
@@ -459,6 +732,10 @@ public class VoucherServiceImpl implements IVoucherService {
|
|||||||
voucherInfo.setUseIntervalHours(batchConfig.getUseIntervalHours());
|
voucherInfo.setUseIntervalHours(batchConfig.getUseIntervalHours());
|
||||||
voucherInfo.setLastUsedTime(voucherCode.getLastUsedTime());
|
voucherInfo.setLastUsedTime(voucherCode.getLastUsedTime());
|
||||||
|
|
||||||
|
// 设置时间范围信息
|
||||||
|
voucherInfo.setValidStartTime(batchConfig.getValidStartTime());
|
||||||
|
voucherInfo.setValidEndTime(batchConfig.getValidEndTime());
|
||||||
|
|
||||||
// 计算剩余可使用次数
|
// 计算剩余可使用次数
|
||||||
if (batchConfig.getMaxUseCount() != null) {
|
if (batchConfig.getMaxUseCount() != null) {
|
||||||
int remaining = batchConfig.getMaxUseCount() - currentUseCount;
|
int remaining = batchConfig.getMaxUseCount() - currentUseCount;
|
||||||
@@ -467,6 +744,13 @@ public class VoucherServiceImpl implements IVoucherService {
|
|||||||
voucherInfo.setRemainingUseCount(null); // 无限次
|
voucherInfo.setRemainingUseCount(null); // 无限次
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算是否还能使用
|
||||||
|
boolean canUseMore = true;
|
||||||
|
if (batchConfig.getMaxUseCount() != null) {
|
||||||
|
canUseMore = currentUseCount < batchConfig.getMaxUseCount();
|
||||||
|
}
|
||||||
|
voucherInfo.setCanUseMore(canUseMore);
|
||||||
|
|
||||||
return voucherInfo;
|
return voucherInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,8 @@
|
|||||||
<result column="face_id" property="faceId" jdbcType="BIGINT"/>
|
<result column="face_id" property="faceId" jdbcType="BIGINT"/>
|
||||||
<result column="claimed_time" property="claimedTime" jdbcType="TIMESTAMP"/>
|
<result column="claimed_time" property="claimedTime" jdbcType="TIMESTAMP"/>
|
||||||
<result column="used_time" property="usedTime" jdbcType="TIMESTAMP"/>
|
<result column="used_time" property="usedTime" jdbcType="TIMESTAMP"/>
|
||||||
|
<result column="current_use_count" property="currentUseCount" jdbcType="INTEGER"/>
|
||||||
|
<result column="last_used_time" property="lastUsedTime" jdbcType="TIMESTAMP"/>
|
||||||
<result column="remark" property="remark" jdbcType="VARCHAR"/>
|
<result column="remark" property="remark" jdbcType="VARCHAR"/>
|
||||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
|
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, batch_id, scenic_id, code, status, face_id, claimed_time, used_time,
|
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
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectByCode" resultMap="BaseResultMap">
|
<select id="selectByCode" resultMap="BaseResultMap">
|
||||||
@@ -69,16 +71,7 @@
|
|||||||
AND deleted = 0
|
AND deleted = 0
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<update id="useVoucher">
|
|
||||||
UPDATE price_voucher_code
|
|
||||||
SET status = 2,
|
|
||||||
used_time = #{usedTime},
|
|
||||||
remark = #{remark},
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE code = #{code}
|
|
||||||
AND (status = 1 OR status = 0)
|
|
||||||
AND deleted = 0
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="selectByBatchId" resultMap="BaseResultMap">
|
<select id="selectByBatchId" resultMap="BaseResultMap">
|
||||||
SELECT <include refid="Base_Column_List"/>
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
Reference in New Issue
Block a user