You've already forked FrameTour-BE
feat(voucher): 增加券码适用商品类型功能
- 在 VoucherBatchCreateReq、VoucherBatchResp 和 VoucherInfo 中添加适用商品类型列表字段 - 在 PriceVoucherBatchConfig 中添加适用商品类型列表字段,并使用 ProductTypeListTypeHandler 进行 JSON 序列化和反序列化 - 实现 ProductTypeListTypeHandler 以处理商品类型列表的 JSON 序列化和反序列化 - 更新 VoucherBatchServiceImpl 和 VoucherServiceImpl 以支持适用商品类型的筛选和计算
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 券码信息DTO
|
||||
@@ -81,4 +83,10 @@ public class VoucherInfo {
|
||||
* 不可用原因
|
||||
*/
|
||||
private String unavailableReason;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -1,8 +1,10 @@
|
||||
package com.ycwl.basic.pricing.dto.req;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VoucherBatchCreateReq {
|
||||
@@ -12,4 +14,10 @@ public class VoucherBatchCreateReq {
|
||||
private Integer discountType;
|
||||
private BigDecimal discountValue;
|
||||
private Integer totalCount;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null或空列表表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
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 VoucherBatchResp {
|
||||
@@ -22,4 +24,10 @@ public class VoucherBatchResp {
|
||||
private String statusName;
|
||||
private Date createTime;
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
private List<ProductType> applicableProducts;
|
||||
}
|
@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.handler.ProductTypeListTypeHandler;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 券码批次配置实体
|
||||
@@ -44,6 +47,13 @@ public class PriceVoucherBatchConfig {
|
||||
*/
|
||||
private BigDecimal discountValue;
|
||||
|
||||
/**
|
||||
* 适用商品类型列表(JSON数组)
|
||||
* null表示适用所有商品类型
|
||||
*/
|
||||
@TableField(typeHandler = ProductTypeListTypeHandler.class)
|
||||
private List<ProductType> applicableProducts;
|
||||
|
||||
/**
|
||||
* 总券码数量
|
||||
*/
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -190,6 +190,7 @@ public class VoucherBatchServiceImpl implements VoucherBatchService {
|
||||
|
||||
resp.setStatusName(batch.getStatus() == 1 ? "启用" : "禁用");
|
||||
resp.setAvailableCount(batch.getTotalCount() - batch.getClaimedCount());
|
||||
resp.setApplicableProducts(batch.getApplicableProducts());
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ 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.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.entity.PriceVoucherBatchConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceVoucherCode;
|
||||
import com.ycwl.basic.pricing.enums.VoucherCodeStatus;
|
||||
@@ -197,32 +198,40 @@ public class VoucherServiceImpl implements IVoucherService {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 筛选适用的商品
|
||||
List<ProductItem> applicableItems = filterApplicableProducts(context.getProducts(), voucherInfo.getApplicableProducts());
|
||||
if (applicableItems.isEmpty()) {
|
||||
log.debug("券码 {} 没有适用的商品", voucherInfo.getVoucherCode());
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
return switch (discountType) {
|
||||
case FREE_ALL -> {
|
||||
// 全场免费,返回当前总金额
|
||||
yield context.getCurrentAmount() != null ? context.getCurrentAmount() : BigDecimal.ZERO;
|
||||
// 全场免费,计算适用商品的总金额
|
||||
BigDecimal applicableAmount = calculateApplicableAmount(applicableItems, context);
|
||||
yield applicableAmount;
|
||||
}
|
||||
case REDUCE_PRICE -> {
|
||||
// 商品降价,每个商品减免固定金额
|
||||
// 商品降价,每个适用商品减免固定金额
|
||||
if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
yield BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal totalDiscount = BigDecimal.ZERO;
|
||||
for (ProductItem product : context.getProducts()) {
|
||||
for (ProductItem product : applicableItems) {
|
||||
BigDecimal productDiscount = discountValue.multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
totalDiscount = totalDiscount.add(productDiscount);
|
||||
}
|
||||
yield totalDiscount;
|
||||
}
|
||||
case DISCOUNT -> {
|
||||
// 商品打折,按百分比计算
|
||||
// 商品打折,按适用商品的金额比例计算
|
||||
if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0 ||
|
||||
discountValue.compareTo(BigDecimal.valueOf(100)) >= 0) {
|
||||
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);
|
||||
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.setClaimedTime(voucherCode.getClaimedTime());
|
||||
voucherInfo.setUsedTime(voucherCode.getUsedTime());
|
||||
voucherInfo.setApplicableProducts(batchConfig.getApplicableProducts());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user