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;
|
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;
|
||||||
}
|
}
|
@@ -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;
|
||||||
}
|
}
|
@@ -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;
|
||||||
}
|
}
|
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总券码数量
|
* 总券码数量
|
||||||
*/
|
*/
|
||||||
|
@@ -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.setStatusName(batch.getStatus() == 1 ? "启用" : "禁用");
|
||||||
resp.setAvailableCount(batch.getTotalCount() - batch.getClaimedCount());
|
resp.setAvailableCount(batch.getTotalCount() - batch.getClaimedCount());
|
||||||
|
resp.setApplicableProducts(batch.getApplicableProducts());
|
||||||
|
|
||||||
return resp;
|
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.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;
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user