From 966568156cb15b31a9694e36ddfd65012518be7a Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sat, 30 Aug 2025 15:31:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(voucher):=20=E5=A2=9E=E5=8A=A0=E5=88=B8?= =?UTF-8?q?=E7=A0=81=E9=80=82=E7=94=A8=E5=95=86=E5=93=81=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 VoucherBatchCreateReq、VoucherBatchResp 和 VoucherInfo 中添加适用商品类型列表字段 - 在 PriceVoucherBatchConfig 中添加适用商品类型列表字段,并使用 ProductTypeListTypeHandler 进行 JSON 序列化和反序列化 - 实现 ProductTypeListTypeHandler 以处理商品类型列表的 JSON 序列化和反序列化 - 更新 VoucherBatchServiceImpl 和 VoucherServiceImpl 以支持适用商品类型的筛选和计算 --- .../ycwl/basic/pricing/dto/VoucherInfo.java | 8 ++ .../dto/req/VoucherBatchCreateReq.java | 8 ++ .../pricing/dto/resp/VoucherBatchResp.java | 8 ++ .../entity/PriceVoucherBatchConfig.java | 10 ++ .../handler/ProductTypeListTypeHandler.java | 94 +++++++++++++++++++ .../service/impl/VoucherBatchServiceImpl.java | 1 + .../service/impl/VoucherServiceImpl.java | 60 ++++++++++-- 7 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/pricing/handler/ProductTypeListTypeHandler.java diff --git a/src/main/java/com/ycwl/basic/pricing/dto/VoucherInfo.java b/src/main/java/com/ycwl/basic/pricing/dto/VoucherInfo.java index b4e80b8..1492af2 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/VoucherInfo.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/VoucherInfo.java @@ -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 applicableProducts; } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/req/VoucherBatchCreateReq.java b/src/main/java/com/ycwl/basic/pricing/dto/req/VoucherBatchCreateReq.java index 8582fea..54ca4dc 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/req/VoucherBatchCreateReq.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/req/VoucherBatchCreateReq.java @@ -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 applicableProducts; } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchResp.java b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchResp.java index 7c75781..684d31e 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchResp.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/resp/VoucherBatchResp.java @@ -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 applicableProducts; } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherBatchConfig.java b/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherBatchConfig.java index 49e4724..368cfb8 100644 --- a/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherBatchConfig.java +++ b/src/main/java/com/ycwl/basic/pricing/entity/PriceVoucherBatchConfig.java @@ -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 applicableProducts; + /** * 总券码数量 */ diff --git a/src/main/java/com/ycwl/basic/pricing/handler/ProductTypeListTypeHandler.java b/src/main/java/com/ycwl/basic/pricing/handler/ProductTypeListTypeHandler.java new file mode 100644 index 0000000..f14c9a2 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/handler/ProductTypeListTypeHandler.java @@ -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> { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TypeReference> typeReference = new TypeReference>() {}; + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + try { + // 将ProductType枚举转换为字符串列表 + List 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 getNullableResult(ResultSet rs, String columnName) throws SQLException { + String json = rs.getString(columnName); + return parseJson(json, columnName); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String json = rs.getString(columnIndex); + return parseJson(json, "columnIndex:" + columnIndex); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String json = cs.getString(columnIndex); + return parseJson(json, "columnIndex:" + columnIndex); + } + + private List parseJson(String json, String source) { + if (json == null || json.trim().isEmpty()) { + log.debug("从{}获取的JSON为空,返回null表示适用所有商品类型", source); + return null; // null表示适用所有商品类型 + } + + try { + List codeList = objectMapper.readValue(json, typeReference); + if (codeList == null || codeList.isEmpty()) { + log.debug("从{}反序列化得到空列表,返回null表示适用所有商品类型", source); + return null; + } + + List 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; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherBatchServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherBatchServiceImpl.java index 805fc5b..8ce020e 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherBatchServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherBatchServiceImpl.java @@ -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; } 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 b983b39..049d51d 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 @@ -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 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 filterApplicableProducts(List products, List 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 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; + } } \ No newline at end of file