Commit 840cfad84aa45e70b1ec11f6ce7529b5f6e2a247

Authored by YunaiV
1 parent 30fd7996

【功能优化】商城:价格计算时,返回可用 + 不可用的优惠劵

Showing 21 changed files with 240 additions and 263 deletions
@@ -15,12 +15,12 @@ @@ -15,12 +15,12 @@
15 <!-- 各种 module 拓展 --> 15 <!-- 各种 module 拓展 -->
16 <module>yudao-module-system</module> 16 <module>yudao-module-system</module>
17 <module>yudao-module-infra</module> 17 <module>yudao-module-infra</module>
18 - <module>yudao-module-member</module> 18 +<!-- <module>yudao-module-member</module>-->
19 <!-- <module>yudao-module-bpm</module>--> 19 <!-- <module>yudao-module-bpm</module>-->
20 <!-- <module>yudao-module-report</module>--> 20 <!-- <module>yudao-module-report</module>-->
21 <!-- <module>yudao-module-mp</module>--> 21 <!-- <module>yudao-module-mp</module>-->
22 - <module>yudao-module-pay</module>  
23 - <module>yudao-module-mall</module> 22 +<!-- <module>yudao-module-pay</module>-->
  23 +<!-- <module>yudao-module-mall</module>-->
24 <!-- <module>yudao-module-crm</module>--> 24 <!-- <module>yudao-module-crm</module>-->
25 <!-- <module>yudao-module-erp</module>--> 25 <!-- <module>yudao-module-erp</module>-->
26 <!-- <module>yudao-module-ai</module>--> 26 <!-- <module>yudao-module-ai</module>-->
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.coupon; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.coupon;
2 2
3 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 3 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
5 -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;  
6 import jakarta.validation.Valid; 5 import jakarta.validation.Valid;
7 6
8 import java.util.List; 7 import java.util.List;
@@ -16,6 +15,15 @@ import java.util.Map; @@ -16,6 +15,15 @@ import java.util.Map;
16 public interface CouponApi { 15 public interface CouponApi {
17 16
18 /** 17 /**
  18 + * 获得用户的优惠劵列表
  19 + *
  20 + * @param userId 用户编号
  21 + * @param status 优惠劵状态
  22 + * @return 优惠劵列表
  23 + */
  24 + List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status);
  25 +
  26 + /**
19 * 使用优惠劵 27 * 使用优惠劵
20 * 28 *
21 * @param useReqDTO 使用请求 29 * @param useReqDTO 使用请求
@@ -30,14 +38,6 @@ public interface CouponApi { @@ -30,14 +38,6 @@ public interface CouponApi {
30 void returnUsedCoupon(Long id); 38 void returnUsedCoupon(Long id);
31 39
32 /** 40 /**
33 - * 校验优惠劵  
34 - *  
35 - * @param validReqDTO 校验请求  
36 - * @return 优惠劵  
37 - */  
38 - CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO);  
39 -  
40 - /**  
41 * 【管理员】给指定用户批量发送优惠券 41 * 【管理员】给指定用户批量发送优惠券
42 * 42 *
43 * @param giveCoupons key: 优惠劵模版编号,value:对应的数量 43 * @param giveCoupons key: 优惠劵模版编号,value:对应的数量
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/dto/CouponValidReqDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.coupon.dto;  
2 -  
3 -import lombok.Data;  
4 -  
5 -import jakarta.validation.constraints.NotNull;  
6 -  
7 -/**  
8 - * 优惠劵使用 Request DTO  
9 - *  
10 - * @author 芋道源码  
11 - */  
12 -@Data  
13 -public class CouponValidReqDTO {  
14 -  
15 - /**  
16 - * 优惠劵编号  
17 - */  
18 - @NotNull(message = "优惠劵编号不能为空")  
19 - private Long id;  
20 -  
21 - /**  
22 - * 用户编号  
23 - */  
24 - @NotNull(message = "用户编号不能为空")  
25 - private Long userId;  
26 -  
27 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
@@ -20,8 +20,6 @@ public interface ErrorCodeConstants { @@ -20,8 +20,6 @@ public interface ErrorCodeConstants {
20 ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在"); 20 ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
21 21
22 // ========== Coupon 相关 1-013-003-000 ============ 22 // ========== Coupon 相关 1-013-003-000 ============
23 - ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1_013_003_000, "优惠劵没有可使用的商品!");  
24 - ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1_013_003_001, "所结算的商品中未满足使用的金额");  
25 23
26 // ========== 优惠劵模板 1-013-004-000 ========== 24 // ========== 优惠劵模板 1-013-004-000 ==========
27 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在"); 25 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在");
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java
1 package cn.iocoder.yudao.module.promotion.api.coupon; 1 package cn.iocoder.yudao.module.promotion.api.coupon;
2 2
3 3
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 5 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; 6 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
6 -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;  
7 -import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;  
8 -import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;  
9 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; 7 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
10 import jakarta.annotation.Resource; 8 import jakarta.annotation.Resource;
11 import org.springframework.stereotype.Service; 9 import org.springframework.stereotype.Service;
@@ -27,6 +25,11 @@ public class CouponApiImpl implements CouponApi { @@ -27,6 +25,11 @@ public class CouponApiImpl implements CouponApi {
27 private CouponService couponService; 25 private CouponService couponService;
28 26
29 @Override 27 @Override
  28 + public List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status) {
  29 + return BeanUtils.toBean(couponService.getCouponList(userId, status), CouponRespDTO.class);
  30 + }
  31 +
  32 + @Override
30 public void useCoupon(CouponUseReqDTO useReqDTO) { 33 public void useCoupon(CouponUseReqDTO useReqDTO) {
31 couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(), 34 couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
32 useReqDTO.getOrderId()); 35 useReqDTO.getOrderId());
@@ -38,12 +41,6 @@ public class CouponApiImpl implements CouponApi { @@ -38,12 +41,6 @@ public class CouponApiImpl implements CouponApi {
38 } 41 }
39 42
40 @Override 43 @Override
41 - public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) {  
42 - CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId());  
43 - return CouponConvert.INSTANCE.convert(coupon);  
44 - }  
45 -  
46 - @Override  
47 public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) { 44 public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
48 return couponService.takeCouponsByAdmin(giveCoupons, userId); 45 return couponService.takeCouponsByAdmin(giveCoupons, userId);
49 } 46 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
@@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
8 -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*; 8 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
  9 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
  10 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponTakeReqVO;
9 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; 11 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
10 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 12 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
11 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
@@ -15,13 +17,12 @@ import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService; @@ -15,13 +17,12 @@ import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
15 import io.swagger.v3.oas.annotations.Operation; 17 import io.swagger.v3.oas.annotations.Operation;
16 import io.swagger.v3.oas.annotations.Parameter; 18 import io.swagger.v3.oas.annotations.Parameter;
17 import io.swagger.v3.oas.annotations.tags.Tag; 19 import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
18 import org.springframework.validation.annotation.Validated; 22 import org.springframework.validation.annotation.Validated;
19 import org.springframework.web.bind.annotation.*; 23 import org.springframework.web.bind.annotation.*;
20 24
21 -import jakarta.annotation.Resource;  
22 -import jakarta.validation.Valid;  
23 import java.util.Collections; 25 import java.util.Collections;
24 -import java.util.List;  
25 26
26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 28 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -56,14 +57,6 @@ public class AppCouponController { @@ -56,14 +57,6 @@ public class AppCouponController {
56 return success(canTakeAgain); 57 return success(canTakeAgain);
57 } 58 }
58 59
59 - @GetMapping("/match-list")  
60 - @Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表")  
61 - public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {  
62 - // todo: 优化:优惠金额倒序  
63 - List<CouponDO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO);  
64 - return success(BeanUtils.toBean(list, AppCouponMatchRespVO.class));  
65 - }  
66 -  
67 @GetMapping("/page") 60 @GetMapping("/page")
68 @Operation(summary = "我的优惠劵列表") 61 @Operation(summary = "我的优惠劵列表")
69 @PreAuthenticated 62 @PreAuthenticated
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java deleted
1 -package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -  
6 -import jakarta.validation.constraints.NotEmpty;  
7 -import jakarta.validation.constraints.NotNull;  
8 -import java.util.List;  
9 -  
10 -@Schema(description = "用户 App - 优惠劵的匹配 Request VO")  
11 -@Data  
12 -public class AppCouponMatchReqVO {  
13 -  
14 - @Schema(description = "商品金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
15 - @NotNull(message = "商品金额不能为空")  
16 - private Integer price;  
17 -  
18 - @Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")  
19 - @NotEmpty(message = "商品 SPU 编号不能为空")  
20 - private List<Long> spuIds;  
21 -  
22 - @Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")  
23 - @NotEmpty(message = "商品 SKU 编号不能为空")  
24 - private List<Long> skuIds;  
25 -  
26 - @Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]")  
27 - @NotEmpty(message = "分类编号不能为空")  
28 - private List<Long> categoryIds;  
29 -  
30 -}  
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchRespVO.java deleted
1 -package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -  
6 -@Schema(description = "用户 App - 优惠劵 Response VO")  
7 -@Data  
8 -public class AppCouponMatchRespVO extends AppCouponRespVO {  
9 -  
10 - @Schema(description = "是否匹配", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")  
11 - private Boolean match;  
12 -  
13 - @Schema(description = "匹配条件的提示", example = "所结算商品没有符合条件的商品")  
14 - private String description;  
15 -  
16 -}  
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponRespVO.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -import jakarta.validation.constraints.Min;  
7 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
8 import java.util.List; 7 import java.util.List;
9 8
@@ -42,7 +41,6 @@ public class AppCouponRespVO { @@ -42,7 +41,6 @@ public class AppCouponRespVO {
42 private Integer discountPercent; 41 private Integer discountPercent;
43 42
44 @Schema(description = "优惠金额", example = "10") 43 @Schema(description = "优惠金额", example = "10")
45 - @Min(value = 0, message = "优惠金额需要大于等于 0")  
46 private Integer discountPrice; 44 private Integer discountPrice;
47 45
48 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用 46 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
@@ -4,9 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -4,9 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
7 -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;  
8 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
9 -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;  
10 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
11 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
12 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; 10 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
@@ -16,7 +14,6 @@ import org.mapstruct.factory.Mappers; @@ -16,7 +14,6 @@ import org.mapstruct.factory.Mappers;
16 14
17 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
18 import java.util.Collection; 16 import java.util.Collection;
19 -import java.util.List;  
20 17
21 /** 18 /**
22 * 优惠劵 Convert 19 * 优惠劵 Convert
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon; 1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 -import cn.hutool.core.util.StrUtil;  
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
10 -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;  
11 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
12 import com.github.yulichang.toolkit.MPJWrappers; 10 import com.github.yulichang.toolkit.MPJWrappers;
13 import org.apache.ibatis.annotations.Mapper; 11 import org.apache.ibatis.annotations.Mapper;
@@ -16,8 +14,6 @@ import java.time.LocalDateTime; @@ -16,8 +14,6 @@ import java.time.LocalDateTime;
16 import java.util.Collection; 14 import java.util.Collection;
17 import java.util.List; 15 import java.util.List;
18 import java.util.Map; 16 import java.util.Map;
19 -import java.util.function.Function;  
20 -import java.util.stream.Collectors;  
21 17
22 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
23 19
@@ -84,22 +80,6 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; { @@ -84,22 +80,6 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
84 return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias)); 80 return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias));
85 } 81 }
86 82
87 - default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(  
88 - Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) {  
89 - Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()  
90 - .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))  
91 - .collect(Collectors.joining(" OR "));  
92 - return selectList(new LambdaQueryWrapperX<CouponDO>()  
93 - .eq(CouponDO::getUserId, userId)  
94 - .eq(CouponDO::getStatus, status)  
95 - .le(CouponDO::getUsePrice, usePrice) // 价格小于等于,满足价格使用条件  
96 - .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()) // 商品范围一:全部  
97 - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) // 商品范围二:满足指定商品  
98 - .apply(productScopeValuesFindInSetFunc.apply(spuIds)))  
99 - .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) // 商品范围三:满足指定分类  
100 - .apply(productScopeValuesFindInSetFunc.apply(categoryIds)))));  
101 - }  
102 -  
103 default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) { 83 default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) {
104 return selectList(new LambdaQueryWrapperX<CouponDO>() 84 return selectList(new LambdaQueryWrapperX<CouponDO>()
105 .eq(CouponDO::getStatus, status) 85 .eq(CouponDO::getStatus, status)
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
@@ -30,15 +30,9 @@ public interface RewardActivityMapper extends BaseMapperX&lt;RewardActivityDO&gt; { @@ -30,15 +30,9 @@ public interface RewardActivityMapper extends BaseMapperX&lt;RewardActivityDO&gt; {
30 .orderByDesc(RewardActivityDO::getId)); 30 .orderByDesc(RewardActivityDO::getId));
31 } 31 }
32 32
33 - default List<RewardActivityDO> selectListByProductScopeAndStatus(Integer productScope, Integer status) {  
34 - return selectList(new LambdaQueryWrapperX<RewardActivityDO>()  
35 - .eq(RewardActivityDO::getProductScope, productScope)  
36 - .eq(RewardActivityDO::getStatus, status));  
37 - }  
38 -  
39 default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { 33 default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
40 Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() 34 Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
41 - .map(id -> StrUtil.format("FIND_IN_SET({}, product_spu_ids) ", id)) 35 + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id))
42 .collect(Collectors.joining(" OR ")); 36 .collect(Collectors.joining(" OR "));
43 return selectList(new QueryWrapper<RewardActivityDO>() 37 return selectList(new QueryWrapper<RewardActivityDO>()
44 .eq("status", status) 38 .eq("status", status)
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
7 -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;  
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
10 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; 9 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
@@ -19,26 +18,6 @@ import java.util.*; @@ -19,26 +18,6 @@ import java.util.*;
19 public interface CouponService { 18 public interface CouponService {
20 19
21 /** 20 /**
22 - * 校验优惠劵,包括状态、有限期  
23 - * <p>  
24 - * 1. 如果校验通过,则返回优惠劵信息  
25 - * 2. 如果校验不通过,则直接抛出业务异常  
26 - *  
27 - * @param id 优惠劵编号  
28 - * @param userId 用户编号  
29 - * @return 优惠劵信息  
30 - */  
31 - CouponDO validCoupon(Long id, Long userId);  
32 -  
33 - /**  
34 - * 校验优惠劵,包括状态、有限期  
35 - *  
36 - * @param coupon 优惠劵  
37 - * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同  
38 - */  
39 - void validCoupon(CouponDO coupon);  
40 -  
41 - /**  
42 * 使用优惠劵 21 * 使用优惠劵
43 * 22 *
44 * @param id 优惠劵编号 23 * @param id 优惠劵编号
@@ -172,15 +151,6 @@ public interface CouponService { @@ -172,15 +151,6 @@ public interface CouponService {
172 } 151 }
173 152
174 /** 153 /**
175 - * 获取用户匹配的优惠券列表  
176 - *  
177 - * @param userId 用户编号  
178 - * @param matchReqVO 匹配参数  
179 - * @return 优惠券列表  
180 - */  
181 - List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);  
182 -  
183 - /**  
184 * 获取用户是否可以领取优惠券 154 * 获取用户是否可以领取优惠券
185 * 155 *
186 * @param userId 用户编号 156 * @param userId 用户编号
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
12 import cn.iocoder.yudao.module.member.api.user.MemberUserApi; 12 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
13 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; 13 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
14 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; 14 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
15 -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;  
16 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; 15 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
17 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 16 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
18 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; 17 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
@@ -56,18 +55,9 @@ public class CouponServiceImpl implements CouponService { @@ -56,18 +55,9 @@ public class CouponServiceImpl implements CouponService {
56 private MemberUserApi memberUserApi; 55 private MemberUserApi memberUserApi;
57 56
58 @Override 57 @Override
59 - public CouponDO validCoupon(Long id, Long userId) {  
60 - CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);  
61 - if (coupon == null) {  
62 - throw exception(COUPON_NOT_EXISTS);  
63 - }  
64 - validCoupon(coupon);  
65 - return coupon;  
66 - }  
67 -  
68 - @Override  
69 - public void validCoupon(CouponDO coupon) { 58 + public void useCoupon(Long id, Long userId, Long orderId) {
70 // 校验状态 59 // 校验状态
  60 + CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
71 if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) { 61 if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
72 throw exception(COUPON_STATUS_NOT_UNUSED); 62 throw exception(COUPON_STATUS_NOT_UNUSED);
73 } 63 }
@@ -75,12 +65,6 @@ public class CouponServiceImpl implements CouponService { @@ -75,12 +65,6 @@ public class CouponServiceImpl implements CouponService {
75 if (!LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) { 65 if (!LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
76 throw exception(COUPON_VALID_TIME_NOT_NOW); 66 throw exception(COUPON_VALID_TIME_NOT_NOW);
77 } 67 }
78 - }  
79 -  
80 - @Override  
81 - public void useCoupon(Long id, Long userId, Long orderId) {  
82 - // 校验优惠劵  
83 - validCoupon(id, userId);  
84 68
85 // 更新状态 69 // 更新状态
86 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(), 70 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
@@ -355,16 +339,6 @@ public class CouponServiceImpl implements CouponService { @@ -355,16 +339,6 @@ public class CouponServiceImpl implements CouponService {
355 } 339 }
356 340
357 @Override 341 @Override
358 - public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {  
359 - List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,  
360 - CouponStatusEnum.UNUSED.getStatus(),  
361 - matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds());  
362 - // 兜底逻辑:如果 CouponExpireJob 未执行,status 未变成 EXPIRE ,但是 validEndTime 已经过期了,需要进行过滤  
363 - list.removeIf(coupon -> !LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime()));  
364 - return list;  
365 - }  
366 -  
367 - @Override  
368 public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) { 342 public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
369 // 1. 未登录时,都显示可以领取 343 // 1. 未登录时,都显示可以领取
370 Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true); 344 Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
@@ -61,7 +61,7 @@ public interface ErrorCodeConstants { @@ -61,7 +61,7 @@ public interface ErrorCodeConstants {
61 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); 61 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
62 ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量"); 62 ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
63 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配"); 63 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配");
64 - ErrorCode PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:优惠金额超过订单金额"); 64 + ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:{}」");
65 65
66 // ========== 物流 Express 模块 1-011-004-000 ========== 66 // ========== 物流 Express 模块 1-011-004-000 ==========
67 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); 67 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
2 2
3 import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; 3 import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 -import jakarta.validation.constraints.NotNull;  
6 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
7 import lombok.Data; 6 import lombok.Data;
8 import lombok.NoArgsConstructor; 7 import lombok.NoArgsConstructor;
9 8
  9 +import java.time.LocalDateTime;
10 import java.util.List; 10 import java.util.List;
11 11
12 @Schema(description = "用户 App - 交易订单结算信息 Response VO") 12 @Schema(description = "用户 App - 交易订单结算信息 Response VO")
@@ -19,6 +19,9 @@ public class AppTradeOrderSettlementRespVO { @@ -19,6 +19,9 @@ public class AppTradeOrderSettlementRespVO {
19 @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED) 19 @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
20 private List<Item> items; 20 private List<Item> items;
21 21
  22 + @Schema(description = "优惠劵数组", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + private List<Coupon> coupons; // 可用 + 不可用
  24 +
22 @Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED) 25 @Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED)
23 private Price price; 26 private Price price;
24 27
@@ -109,7 +112,6 @@ public class AppTradeOrderSettlementRespVO { @@ -109,7 +112,6 @@ public class AppTradeOrderSettlementRespVO {
109 private String mobile; 112 private String mobile;
110 113
111 @Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED) 114 @Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED)
112 - @NotNull(message = "地区编号不能为空")  
113 private Long areaId; 115 private Long areaId;
114 @Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区") 116 @Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区")
115 private String areaName; 117 private String areaName;
@@ -122,4 +124,43 @@ public class AppTradeOrderSettlementRespVO { @@ -122,4 +124,43 @@ public class AppTradeOrderSettlementRespVO {
122 124
123 } 125 }
124 126
  127 + @Schema(description = "优惠劵信息")
  128 + @Data
  129 + public static class Coupon {
  130 +
  131 + @Schema(description = "优惠劵编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  132 + private Long id;
  133 +
  134 + @Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
  135 + private String name;
  136 +
  137 + @Schema(description = "是否设置满多少金额可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 单位:分;0 - 不限制
  138 + private Integer usePrice;
  139 +
  140 + @Schema(description = "固定日期 - 生效开始时间")
  141 + private LocalDateTime validStartTime;
  142 +
  143 + @Schema(description = "固定日期 - 生效结束时间")
  144 + private LocalDateTime validEndTime;
  145 +
  146 + @Schema(description = "优惠类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  147 + private Integer discountType;
  148 +
  149 + @Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80
  150 + private Integer discountPercent;
  151 +
  152 + @Schema(description = "优惠金额", example = "10")
  153 + private Integer discountPrice;
  154 +
  155 + @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
  156 + private Integer discountLimitPrice;
  157 +
  158 + @Schema(description = "是否可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  159 + private Boolean match;
  160 +
  161 + @Schema(description = "不可用原因", example = "优惠劵已过期")
  162 + private String mismatchReason;
  163 +
  164 + }
  165 +
125 } 166 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
5 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 5 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
6 import lombok.Data; 6 import lombok.Data;
7 7
  8 +import java.time.LocalDateTime;
8 import java.util.List; 9 import java.util.List;
9 import java.util.Map; 10 import java.util.Map;
10 11
@@ -45,9 +46,13 @@ public class TradePriceCalculateRespBO { @@ -45,9 +46,13 @@ public class TradePriceCalculateRespBO {
45 private List<Promotion> promotions; 46 private List<Promotion> promotions;
46 47
47 /** 48 /**
48 - * 优惠劵编号 49 + * 使用的优惠劵编号
49 */ 50 */
50 private Long couponId; 51 private Long couponId;
  52 + /**
  53 + * 用户的优惠劵列表(可用 + 不可用)
  54 + */
  55 + private List<Coupon> coupons;
51 56
52 /** 57 /**
53 * 会员剩余积分 58 * 会员剩余积分
@@ -339,4 +344,62 @@ public class TradePriceCalculateRespBO { @@ -339,4 +344,62 @@ public class TradePriceCalculateRespBO {
339 344
340 } 345 }
341 346
  347 + /**
  348 + * 优惠劵信息
  349 + */
  350 + @Data
  351 + public static class Coupon {
  352 +
  353 + /**
  354 + * 优惠劵编号
  355 + */
  356 + private Long id;
  357 + /**
  358 + * 优惠劵名
  359 + */
  360 + private String name;
  361 +
  362 + /**
  363 + * 是否设置满多少金额可用,单位:分
  364 + */
  365 + private Integer usePrice;
  366 +
  367 + /**
  368 + * 生效开始时间
  369 + */
  370 + private LocalDateTime validStartTime;
  371 + /**
  372 + * 生效结束时间
  373 + */
  374 + private LocalDateTime validEndTime;
  375 +
  376 + /**
  377 + * 优惠类型
  378 + */
  379 + private Integer discountType;
  380 + /**
  381 + * 折扣百分比
  382 + */
  383 + private Integer discountPercent;
  384 + /**
  385 + * 优惠金额,单位:分
  386 + */
  387 + private Integer discountPrice;
  388 + /**
  389 + * 折扣上限,单位:分
  390 + */
  391 + private Integer discountLimitPrice;
  392 +
  393 + /**
  394 + * 是否匹配
  395 + */
  396 + private Boolean match;
  397 + /**
  398 + * 不匹配的原因
  399 + */
  400 + private String mismatchReason;
  401 +
  402 + }
  403 +
  404 +
342 } 405 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
1 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.lang.Assert;  
5 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
6 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; 8 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
8 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 9 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
9 -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;  
10 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; 10 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
11 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; 11 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
12 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; 12 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  13 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
13 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 14 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
14 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 16 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  17 +import jakarta.annotation.Resource;
16 import org.springframework.core.annotation.Order; 18 import org.springframework.core.annotation.Order;
17 import org.springframework.stereotype.Component; 19 import org.springframework.stereotype.Component;
18 20
19 -import jakarta.annotation.Resource;  
20 import java.util.List; 21 import java.util.List;
21 import java.util.function.Predicate; 22 import java.util.function.Predicate;
22 23
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; 26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
25 -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;  
26 -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU; 27 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_CAN_NOT_USE;
27 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER; 28 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
28 -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH;  
29 29
30 /** 30 /**
31 * 优惠劵的 {@link TradePriceCalculator} 实现类 31 * 优惠劵的 {@link TradePriceCalculator} 实现类
@@ -41,34 +41,37 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { @@ -41,34 +41,37 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
41 41
42 @Override 42 @Override
43 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 43 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
44 - // 1.1 校验优惠劵  
45 - if (param.getCouponId() == null) {  
46 - return;  
47 - }  
48 - CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()  
49 - .setId(param.getCouponId()).setUserId(param.getUserId()));  
50 - Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());  
51 - // 1.2 只有【普通】订单,才允许使用优惠劵 44 + // 只有【普通】订单,才允许使用优惠劵
52 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) { 45 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
53 - throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER); 46 + if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
  47 + throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
  48 + }
  49 + return;
54 } 50 }
55 51
56 - // 2.1 获得匹配的商品 SKU 数组  
57 - List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);  
58 - if (CollUtil.isEmpty(orderItems)) {  
59 - throw exception(COUPON_NO_MATCH_SPU); 52 + // 1.1 加载用户的优惠劵列表
  53 + List<CouponRespDTO> coupons = couponApi.getCouponListByUserId(param.getUserId(), CouponStatusEnum.UNUSED.getStatus());
  54 + coupons.removeIf(coupon -> LocalDateTimeUtils.beforeNow(coupon.getValidEndTime()));
  55 + // 1.2 计算优惠劵的使用条件
  56 + result.setCoupons(calculateCoupons(coupons, result));
  57 +
  58 + // 2. 校验优惠劵是否可用
  59 + if (param.getCouponId() == null) {
  60 + return;
60 } 61 }
61 - // 2.2 计算是否满足优惠劵的使用金额  
62 - Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);  
63 - if (totalPayPrice < coupon.getUsePrice()) {  
64 - throw exception(COUPON_NO_MATCH_MIN_PRICE); 62 + TradePriceCalculateRespBO.Coupon couponBO = CollUtil.findOne(result.getCoupons(), item -> item.getId().equals(param.getCouponId()));
  63 + CouponRespDTO coupon = CollUtil.findOne(coupons, item -> item.getId().equals(param.getCouponId()));
  64 + if (couponBO == null || coupon == null) {
  65 + throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, "优惠劵不存在");
  66 + }
  67 + if (Boolean.FALSE.equals(couponBO.getMatch())) {
  68 + throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, couponBO.getMismatchReason());
65 } 69 }
66 70
67 // 3.1 计算可以优惠的金额 71 // 3.1 计算可以优惠的金额
  72 + List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
  73 + Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
68 Integer couponPrice = getCouponPrice(coupon, totalPayPrice); 74 Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
69 - if (couponPrice <= totalPayPrice) {  
70 - throw exception(PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH);  
71 - }  
72 // 3.2 计算分摊的优惠金额 75 // 3.2 计算分摊的优惠金额
73 List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice); 76 List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);
74 77
@@ -76,7 +79,7 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { @@ -76,7 +79,7 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
76 result.setCouponId(param.getCouponId()); 79 result.setCouponId(param.getCouponId());
77 // 4.2 记录优惠明细 80 // 4.2 记录优惠明细
78 TradePriceCalculatorHelper.addPromotion(result, orderItems, 81 TradePriceCalculatorHelper.addPromotion(result, orderItems,
79 - param.getCouponId(), coupon.getName(), PromotionTypeEnum.COUPON.getType(), 82 + param.getCouponId(), couponBO.getName(), PromotionTypeEnum.COUPON.getType(),
80 StrUtil.format("优惠劵:省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)), 83 StrUtil.format("优惠劵:省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)),
81 divideCouponPrices); 84 divideCouponPrices);
82 // 4.3 更新 SKU 优惠金额 85 // 4.3 更新 SKU 优惠金额
@@ -88,6 +91,43 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { @@ -88,6 +91,43 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
88 TradePriceCalculatorHelper.recountAllPrice(result); 91 TradePriceCalculatorHelper.recountAllPrice(result);
89 } 92 }
90 93
  94 + /**
  95 + * 计算用户的优惠劵列表(可用 + 不可用)
  96 + *
  97 + * @param coupons 优惠劵
  98 + * @param result 计算结果
  99 + * @return 优惠劵列表
  100 + */
  101 + private List<TradePriceCalculateRespBO.Coupon> calculateCoupons(List<CouponRespDTO> coupons,
  102 + TradePriceCalculateRespBO result) {
  103 + return convertList(coupons, coupon -> {
  104 + TradePriceCalculateRespBO.Coupon matchCoupon = BeanUtils.toBean(coupon, TradePriceCalculateRespBO.Coupon.class);
  105 + // 1.1 优惠劵未到使用时间
  106 + if (LocalDateTimeUtils.afterNow(coupon.getValidStartTime())) {
  107 + return matchCoupon.setMatch(false).setMismatchReason("优惠劵未到使用时间");
  108 + }
  109 + // 1.2 优惠劵没有匹配的商品
  110 + List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
  111 + if (CollUtil.isEmpty(orderItems)) {
  112 + return matchCoupon.setMatch(false).setMismatchReason("优惠劵没有匹配的商品");
  113 + }
  114 + // 1.3 差 %1$,.2f 元可用优惠劵
  115 + Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
  116 + if (totalPayPrice < coupon.getUsePrice()) {
  117 + return matchCoupon.setMatch(false)
  118 + .setMismatchReason(String.format("差 %1$,.2f 元可用优惠劵", (coupon.getUsePrice() - totalPayPrice) / 100D));
  119 + }
  120 + // 1.4 优惠金额超过订单金额
  121 + Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
  122 + if (couponPrice >= totalPayPrice) {
  123 + return matchCoupon.setMatch(false).setMismatchReason("优惠金额超过订单金额");
  124 + }
  125 +
  126 + // 2. 满足条件
  127 + return matchCoupon.setMatch(true);
  128 + });
  129 + }
  130 +
91 private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) { 131 private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) {
92 if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价 132 if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
93 return coupon.getDiscountPrice(); 133 return coupon.getDiscountPrice();
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java
1 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
  3 +import cn.hutool.core.collection.ListUtil;
3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; 5 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
5 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; 6 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
6 -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;  
7 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; 7 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
8 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; 8 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
9 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; 9 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  10 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
10 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 11 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@@ -14,8 +15,10 @@ import org.junit.jupiter.api.Test; @@ -14,8 +15,10 @@ import org.junit.jupiter.api.Test;
14 import org.mockito.InjectMocks; 15 import org.mockito.InjectMocks;
15 import org.mockito.Mock; 16 import org.mockito.Mock;
16 17
  18 +import java.time.Duration;
17 import java.util.ArrayList; 19 import java.util.ArrayList;
18 20
  21 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
19 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 22 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
20 import static java.util.Arrays.asList; 23 import static java.util.Arrays.asList;
21 import static org.junit.jupiter.api.Assertions.assertEquals; 24 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -69,8 +72,10 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { @@ -69,8 +72,10 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
69 CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节") 72 CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节")
70 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) 73 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
71 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) 74 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
72 - .setDiscountPercent(50).setDiscountLimitPrice(70));  
73 - when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon); 75 + .setDiscountPercent(50).setDiscountLimitPrice(70))
  76 + .setValidStartTime(addTime(Duration.ofDays(1))).setValidEndTime(addTime(Duration.ofDays(2)));
  77 + when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus())))
  78 + .thenReturn(ListUtil.toList(coupon));
74 79
75 // 调用 80 // 调用
76 tradeCouponPriceCalculator.calculate(param, result); 81 tradeCouponPriceCalculator.calculate(param, result);
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java
@@ -144,7 +144,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { @@ -144,7 +144,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
144 // 调用,并断言 144 // 调用,并断言
145 assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId), 145 assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),
146 new ErrorCode(401, "刷新令牌已过期")); 146 new ErrorCode(401, "刷新令牌已过期"));
147 - assertEquals(0, oauth2RefreshTokenMapper.selectCount()); 147 + assertEquals(0, oauth2AccessTokenMapper.selectCount());
148 } 148 }
149 149
150 @Test 150 @Test
yudao-server/pom.xml
@@ -33,11 +33,11 @@ @@ -33,11 +33,11 @@
33 </dependency> 33 </dependency>
34 34
35 <!-- 会员中心。默认注释,保证编译速度 --> 35 <!-- 会员中心。默认注释,保证编译速度 -->
36 - <dependency>  
37 - <groupId>cn.iocoder.boot</groupId>  
38 - <artifactId>yudao-module-member-biz</artifactId>  
39 - <version>${revision}</version>  
40 - </dependency> 36 +<!-- <dependency>-->
  37 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  38 +<!-- <artifactId>yudao-module-member-biz</artifactId>-->
  39 +<!-- <version>${revision}</version>-->
  40 +<!-- </dependency>-->
41 41
42 <!-- 数据报表。默认注释,保证编译速度 --> 42 <!-- 数据报表。默认注释,保证编译速度 -->
43 <!-- <dependency>--> 43 <!-- <dependency>-->
@@ -52,11 +52,11 @@ @@ -52,11 +52,11 @@
52 <!-- <version>${revision}</version>--> 52 <!-- <version>${revision}</version>-->
53 <!-- </dependency>--> 53 <!-- </dependency>-->
54 <!-- 支付服务。默认注释,保证编译速度 --> 54 <!-- 支付服务。默认注释,保证编译速度 -->
55 - <dependency>  
56 - <groupId>cn.iocoder.boot</groupId>  
57 - <artifactId>yudao-module-pay-biz</artifactId>  
58 - <version>${revision}</version>  
59 - </dependency> 55 +<!-- <dependency>-->
  56 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  57 +<!-- <artifactId>yudao-module-pay-biz</artifactId>-->
  58 +<!-- <version>${revision}</version>-->
  59 +<!-- </dependency>-->
60 60
61 <!-- 微信公众号模块。默认注释,保证编译速度 --> 61 <!-- 微信公众号模块。默认注释,保证编译速度 -->
62 <!-- <dependency>--> 62 <!-- <dependency>-->
@@ -66,26 +66,26 @@ @@ -66,26 +66,26 @@
66 <!-- </dependency>--> 66 <!-- </dependency>-->
67 67
68 <!-- 商城相关模块。默认注释,保证编译速度--> 68 <!-- 商城相关模块。默认注释,保证编译速度-->
69 - <dependency>  
70 - <groupId>cn.iocoder.boot</groupId>  
71 - <artifactId>yudao-module-promotion-biz</artifactId>  
72 - <version>${revision}</version>  
73 - </dependency>  
74 - <dependency>  
75 - <groupId>cn.iocoder.boot</groupId>  
76 - <artifactId>yudao-module-product-biz</artifactId>  
77 - <version>${revision}</version>  
78 - </dependency>  
79 - <dependency>  
80 - <groupId>cn.iocoder.boot</groupId>  
81 - <artifactId>yudao-module-trade-biz</artifactId>  
82 - <version>${revision}</version>  
83 - </dependency>  
84 - <dependency>  
85 - <groupId>cn.iocoder.boot</groupId>  
86 - <artifactId>yudao-module-statistics-biz</artifactId>  
87 - <version>${revision}</version>  
88 - </dependency> 69 +<!-- <dependency>-->
  70 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  71 +<!-- <artifactId>yudao-module-promotion-biz</artifactId>-->
  72 +<!-- <version>${revision}</version>-->
  73 +<!-- </dependency>-->
  74 +<!-- <dependency>-->
  75 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  76 +<!-- <artifactId>yudao-module-product-biz</artifactId>-->
  77 +<!-- <version>${revision}</version>-->
  78 +<!-- </dependency>-->
  79 +<!-- <dependency>-->
  80 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  81 +<!-- <artifactId>yudao-module-trade-biz</artifactId>-->
  82 +<!-- <version>${revision}</version>-->
  83 +<!-- </dependency>-->
  84 +<!-- <dependency>-->
  85 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  86 +<!-- <artifactId>yudao-module-statistics-biz</artifactId>-->
  87 +<!-- <version>${revision}</version>-->
  88 +<!-- </dependency>-->
89 89
90 <!-- CRM 相关模块。默认注释,保证编译速度 --> 90 <!-- CRM 相关模块。默认注释,保证编译速度 -->
91 <!-- <dependency>--> 91 <!-- <dependency>-->