feat(voucher): 增加券码适用商品类型功能

- 在 VoucherBatchCreateReq、VoucherBatchResp 和 VoucherInfo 中添加适用商品类型列表字段
- 在 PriceVoucherBatchConfig 中添加适用商品类型列表字段,并使用 ProductTypeListTypeHandler 进行 JSON 序列化和反序列化
- 实现 ProductTypeListTypeHandler 以处理商品类型列表的 JSON 序列化和反序列化
- 更新 VoucherBatchServiceImpl 和 VoucherServiceImpl 以支持适用商品类型的筛选和计算
This commit is contained in:
2025-08-30 15:31:35 +08:00
parent 57b087a4fb
commit 966568156c
7 changed files with 182 additions and 7 deletions

View File

@@ -1,10 +1,12 @@
package com.ycwl.basic.pricing.dto; package com.ycwl.basic.pricing.dto;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.enums.VoucherDiscountType; import com.ycwl.basic.pricing.enums.VoucherDiscountType;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 券码信息DTO * 券码信息DTO
@@ -81,4 +83,10 @@ public class VoucherInfo {
* 不可用原因 * 不可用原因
*/ */
private String unavailableReason; private String unavailableReason;
/**
* 适用商品类型列表
* null表示适用所有商品类型
*/
private List<ProductType> applicableProducts;
} }

View File

@@ -1,8 +1,10 @@
package com.ycwl.basic.pricing.dto.req; package com.ycwl.basic.pricing.dto.req;
import com.ycwl.basic.pricing.enums.ProductType;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
@Data @Data
public class VoucherBatchCreateReq { public class VoucherBatchCreateReq {
@@ -12,4 +14,10 @@ public class VoucherBatchCreateReq {
private Integer discountType; private Integer discountType;
private BigDecimal discountValue; private BigDecimal discountValue;
private Integer totalCount; private Integer totalCount;
/**
* 适用商品类型列表
* null或空列表表示适用所有商品类型
*/
private List<ProductType> applicableProducts;
} }

View File

@@ -1,9 +1,11 @@
package com.ycwl.basic.pricing.dto.resp; package com.ycwl.basic.pricing.dto.resp;
import com.ycwl.basic.pricing.enums.ProductType;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
@Data @Data
public class VoucherBatchResp { public class VoucherBatchResp {
@@ -22,4 +24,10 @@ public class VoucherBatchResp {
private String statusName; private String statusName;
private Date createTime; private Date createTime;
private Long createBy; private Long createBy;
/**
* 适用商品类型列表
* null表示适用所有商品类型
*/
private List<ProductType> applicableProducts;
} }

View File

@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.handler.ProductTypeListTypeHandler;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 券码批次配置实体 * 券码批次配置实体
@@ -44,6 +47,13 @@ public class PriceVoucherBatchConfig {
*/ */
private BigDecimal discountValue; private BigDecimal discountValue;
/**
* 适用商品类型列表(JSON数组)
* null表示适用所有商品类型
*/
@TableField(typeHandler = ProductTypeListTypeHandler.class)
private List<ProductType> applicableProducts;
/** /**
* 总券码数量 * 总券码数量
*/ */

View File

@@ -0,0 +1,94 @@
package com.ycwl.basic.pricing.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.pricing.enums.ProductType;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 商品类型列表类型处理器
* 用于处理券码适用商品类型的JSON序列化和反序列化
*/
@Slf4j
public class ProductTypeListTypeHandler extends BaseTypeHandler<List<ProductType>> {
private final ObjectMapper objectMapper = new ObjectMapper();
private final TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<ProductType> parameter, JdbcType jdbcType) throws SQLException {
try {
// 将ProductType枚举转换为字符串列表
List<String> codeList = parameter.stream()
.map(ProductType::getCode)
.toList();
String json = objectMapper.writeValueAsString(codeList);
ps.setString(i, json);
log.debug("序列化商品类型列表: {}", json);
} catch (JsonProcessingException e) {
log.error("序列化商品类型列表失败", e);
throw new SQLException("序列化商品类型列表失败", e);
}
}
@Override
public List<ProductType> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json, columnName);
}
@Override
public List<ProductType> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json, "columnIndex:" + columnIndex);
}
@Override
public List<ProductType> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json, "columnIndex:" + columnIndex);
}
private List<ProductType> parseJson(String json, String source) {
if (json == null || json.trim().isEmpty()) {
log.debug("从{}获取的JSON为空,返回null表示适用所有商品类型", source);
return null; // null表示适用所有商品类型
}
try {
List<String> codeList = objectMapper.readValue(json, typeReference);
if (codeList == null || codeList.isEmpty()) {
log.debug("从{}反序列化得到空列表,返回null表示适用所有商品类型", source);
return null;
}
List<ProductType> result = new ArrayList<>();
for (String code : codeList) {
try {
ProductType productType = ProductType.fromCode(code);
result.add(productType);
} catch (IllegalArgumentException e) {
log.warn("从{}反序列化时发现未知商品类型代码: {}", source, code);
// 忽略未知的商品类型代码,继续处理其他类型
}
}
log.debug("从{}反序列化商品类型列表成功,数量: {}", source, result.size());
return result.isEmpty() ? null : result;
} catch (JsonProcessingException e) {
log.error("从{}反序列化商品类型列表失败,JSON: {}", source, json, e);
// 解析失败时返回null,表示适用所有商品类型
return null;
}
}
}

View File

@@ -190,6 +190,7 @@ public class VoucherBatchServiceImpl implements VoucherBatchService {
resp.setStatusName(batch.getStatus() == 1 ? "启用" : "禁用"); resp.setStatusName(batch.getStatus() == 1 ? "启用" : "禁用");
resp.setAvailableCount(batch.getTotalCount() - batch.getClaimedCount()); resp.setAvailableCount(batch.getTotalCount() - batch.getClaimedCount());
resp.setApplicableProducts(batch.getApplicableProducts());
return resp; return resp;
} }

View File

@@ -3,6 +3,7 @@ package com.ycwl.basic.pricing.service.impl;
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;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.entity.PriceVoucherBatchConfig; import com.ycwl.basic.pricing.entity.PriceVoucherBatchConfig;
import com.ycwl.basic.pricing.entity.PriceVoucherCode; import com.ycwl.basic.pricing.entity.PriceVoucherCode;
import com.ycwl.basic.pricing.enums.VoucherCodeStatus; import com.ycwl.basic.pricing.enums.VoucherCodeStatus;
@@ -197,32 +198,40 @@ public class VoucherServiceImpl implements IVoucherService {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
// 筛选适用的商品
List<ProductItem> applicableItems = filterApplicableProducts(context.getProducts(), voucherInfo.getApplicableProducts());
if (applicableItems.isEmpty()) {
log.debug("券码 {} 没有适用的商品", voucherInfo.getVoucherCode());
return BigDecimal.ZERO;
}
return switch (discountType) { return switch (discountType) {
case FREE_ALL -> { case FREE_ALL -> {
// 全场免费,返回当前总金额 // 全场免费,计算适用商品的总金额
yield context.getCurrentAmount() != null ? context.getCurrentAmount() : BigDecimal.ZERO; BigDecimal applicableAmount = calculateApplicableAmount(applicableItems, context);
yield applicableAmount;
} }
case REDUCE_PRICE -> { case REDUCE_PRICE -> {
// 商品降价,每个商品减免固定金额 // 商品降价,每个适用商品减免固定金额
if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0) { if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0) {
yield BigDecimal.ZERO; yield BigDecimal.ZERO;
} }
BigDecimal totalDiscount = BigDecimal.ZERO; BigDecimal totalDiscount = BigDecimal.ZERO;
for (ProductItem product : context.getProducts()) { for (ProductItem product : applicableItems) {
BigDecimal productDiscount = discountValue.multiply(BigDecimal.valueOf(product.getQuantity())); BigDecimal productDiscount = discountValue.multiply(BigDecimal.valueOf(product.getQuantity()));
totalDiscount = totalDiscount.add(productDiscount); totalDiscount = totalDiscount.add(productDiscount);
} }
yield totalDiscount; yield totalDiscount;
} }
case DISCOUNT -> { case DISCOUNT -> {
// 商品打折,按百分比计算 // 商品打折,按适用商品的金额比例计算
if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0 || if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0 ||
discountValue.compareTo(BigDecimal.valueOf(100)) >= 0) { discountValue.compareTo(BigDecimal.valueOf(100)) >= 0) {
yield BigDecimal.ZERO; yield BigDecimal.ZERO;
} }
BigDecimal currentAmount = context.getCurrentAmount() != null ? context.getCurrentAmount() : BigDecimal.ZERO; BigDecimal applicableAmount = calculateApplicableAmount(applicableItems, context);
BigDecimal discountRate = discountValue.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP); BigDecimal discountRate = discountValue.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
yield currentAmount.multiply(discountRate).setScale(2, RoundingMode.HALF_UP); yield applicableAmount.multiply(discountRate).setScale(2, RoundingMode.HALF_UP);
} }
}; };
} }
@@ -267,7 +276,44 @@ public class VoucherServiceImpl implements IVoucherService {
voucherInfo.setStatus(voucherCode.getStatus()); voucherInfo.setStatus(voucherCode.getStatus());
voucherInfo.setClaimedTime(voucherCode.getClaimedTime()); voucherInfo.setClaimedTime(voucherCode.getClaimedTime());
voucherInfo.setUsedTime(voucherCode.getUsedTime()); voucherInfo.setUsedTime(voucherCode.getUsedTime());
voucherInfo.setApplicableProducts(batchConfig.getApplicableProducts());
return voucherInfo; return voucherInfo;
} }
/**
* 筛选适用的商品
*/
private List<ProductItem> filterApplicableProducts(List<ProductItem> products, List<ProductType> applicableProducts) {
if (applicableProducts == null || applicableProducts.isEmpty()) {
// null或空列表表示适用所有商品类型
return products;
}
return products.stream()
.filter(product -> {
try {
ProductType productType = ProductType.fromCode(product.getProductType());
return applicableProducts.contains(productType);
} catch (IllegalArgumentException e) {
log.warn("未知的商品类型: {}", product.getProductType());
return false;
}
})
.collect(Collectors.toList());
}
/**
* 计算适用商品的总金额
*/
private BigDecimal calculateApplicableAmount(List<ProductItem> applicableItems, DiscountDetectionContext context) {
// 简单计算:基于商品数量和单价
// 在实际应用中可能需要更复杂的计算逻辑
BigDecimal totalAmount = BigDecimal.ZERO;
for (ProductItem item : applicableItems) {
BigDecimal itemAmount = item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity()));
totalAmount = totalAmount.add(itemAmount);
}
return totalAmount;
}
} }