Commit 8811ce0200b291977a34635bb26fed8d143ffede
Committed by
Gitee
!637 完善优惠券功能
Merge pull request !637 from 疯狂的世界/feature/mall_product
Showing
40 changed files
with
633 additions
and
354 deletions
sql/mysql/ruoyi-vue-pro.sql
| ... | ... | @@ -1188,8 +1188,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st |
| 1188 | 1188 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1166, 2, '折扣', '2', 'promotion_discount_type', 0, 'primary', '', '优惠类型 - 折扣', '1', '2022-11-01 12:46:51', '1', '2022-11-01 12:50:08', b'0'); |
| 1189 | 1189 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1167, 1, '固定日期', '1', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 固定日期', '1', '2022-11-02 00:07:34', '1', '2022-11-04 00:07:49', b'0'); |
| 1190 | 1190 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1168, 2, '领取之后', '2', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 领取之后', '1', '2022-11-02 00:07:54', '1', '2022-11-04 00:07:52', b'0'); |
| 1191 | -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用卷', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0'); | |
| 1192 | -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品卷', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0'); | |
| 1191 | +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1169, 1, '通用券', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-01 23:42:49', b'0'); | |
| 1192 | +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1170, 2, '商品券', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-01 23:42:54', b'0'); | |
| 1193 | 1193 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1171, 1, '已领取', '1', 'promotion_coupon_status', 0, 'primary', '', '优惠劵的状态 - 已领取', '1', '2022-11-04 00:15:08', '1', '2022-11-04 19:16:04', b'0'); |
| 1194 | 1194 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1172, 2, '已使用', '2', 'promotion_coupon_status', 0, 'success', '', '优惠劵的状态 - 已使用', '1', '2022-11-04 00:15:21', '1', '2022-11-04 19:16:08', b'0'); |
| 1195 | 1195 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1173, 3, '已过期', '3', 'promotion_coupon_status', 0, 'info', '', '优惠劵的状态 - 已过期', '1', '2022-11-04 00:15:43', '1', '2022-11-04 19:16:12', b'0'); |
| ... | ... | @@ -1284,7 +1284,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st |
| 1284 | 1284 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1355, 5, '抽奖奖励', '5', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', b'0'); |
| 1285 | 1285 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1356, 1, '快递发货', '1', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:04:55', '1', '2023-08-23 00:04:55', b'0'); |
| 1286 | 1286 | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1357, 2, '用户自提', '2', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:05:05', '1', '2023-08-23 00:05:05', b'0'); |
| 1287 | -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类卷', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0'); | |
| 1287 | +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1358, 3, '品类券', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-01 23:43:07', b'0'); | |
| 1288 | 1288 | COMMIT; |
| 1289 | 1289 | |
| 1290 | 1290 | -- ---------------------------- | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/dto/RewardActivityMatchRespDTO.java
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionProductScopeEnum.java
| ... | ... | @@ -15,9 +15,9 @@ import java.util.Arrays; |
| 15 | 15 | @AllArgsConstructor |
| 16 | 16 | public enum PromotionProductScopeEnum implements IntArrayValuable { |
| 17 | 17 | |
| 18 | - ALL(1, "通用卷"), // 全部商品 | |
| 19 | - SPU(2, "商品卷"), // 指定商品 | |
| 20 | - CATEGORY(3, "品类卷"), // 指定商品 | |
| 18 | + ALL(1, "通用券"), // 全部商品 | |
| 19 | + SPU(2, "商品券"), // 指定商品 | |
| 20 | + CATEGORY(3, "品类券"), // 指定品类 | |
| 21 | 21 | ; |
| 22 | 22 | |
| 23 | 23 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray(); | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/pom.xml
| ... | ... | @@ -47,6 +47,10 @@ |
| 47 | 47 | </dependency> |
| 48 | 48 | <dependency> |
| 49 | 49 | <groupId>cn.iocoder.boot</groupId> |
| 50 | + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | |
| 51 | + </dependency> | |
| 52 | + <dependency> | |
| 53 | + <groupId>cn.iocoder.boot</groupId> | |
| 50 | 54 | <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId> |
| 51 | 55 | </dependency> |
| 52 | 56 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 5 | +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -8,6 +10,7 @@ import lombok.ToString; |
| 8 | 10 | import org.springframework.format.annotation.DateTimeFormat; |
| 9 | 11 | |
| 10 | 12 | import java.time.LocalDateTime; |
| 13 | +import java.util.Collection; | |
| 11 | 14 | |
| 12 | 15 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| 13 | 16 | |
| ... | ... | @@ -21,6 +24,7 @@ public class CouponPageReqVO extends PageParam { |
| 21 | 24 | private Long templateId; |
| 22 | 25 | |
| 23 | 26 | @Schema(description = "优惠码状态", example = "1") |
| 27 | + @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}") | |
| 24 | 28 | private Integer status; |
| 25 | 29 | |
| 26 | 30 | @Schema(description = "创建时间") |
| ... | ... | @@ -30,4 +34,7 @@ public class CouponPageReqVO extends PageParam { |
| 30 | 34 | @Schema(description = "用户昵称", example = "芋艿") |
| 31 | 35 | private String nickname; |
| 32 | 36 | |
| 37 | + @Schema(description = "用户编号", example = "1") | |
| 38 | + private Collection<Long> userIds; | |
| 39 | + | |
| 33 | 40 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 5 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 5 | 6 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| 6 | 7 | import io.swagger.v3.oas.annotations.media.Schema; |
| 7 | 8 | import lombok.Data; |
| ... | ... | @@ -33,8 +34,14 @@ public class CouponTemplatePageReqVO extends PageParam { |
| 33 | 34 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| 34 | 35 | private LocalDateTime[] createTime; |
| 35 | 36 | |
| 36 | - @Schema(description = "可以领取的类型", example = "[1,2, 3]") | |
| 37 | + @Schema(description = "可以领取的类型", example = "[1, 2, 3]") | |
| 37 | 38 | @InEnum(value = CouponTakeTypeEnum.class, message = "可以领取的类型,必须是 {value}") |
| 38 | 39 | private List<Integer> canTakeTypes; |
| 39 | 40 | |
| 41 | + @Schema(description = "商品范围", example = "1") | |
| 42 | + @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}") | |
| 43 | + private Integer productScope; | |
| 44 | + | |
| 45 | + @Schema(description = "商品范围编号", example = "1") | |
| 46 | + private Long productScopeValue; | |
| 40 | 47 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
| ... | ... | @@ -84,7 +84,7 @@ public class RewardActivityBaseVO { |
| 84 | 84 | @Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3") |
| 85 | 85 | private List<Long> couponIds; |
| 86 | 86 | |
| 87 | - @Schema(description = "赠送的优惠卷数量的数组", example = "1,2,3") | |
| 87 | + @Schema(description = "赠送的优惠券数量的数组", example = "1,2,3") | |
| 88 | 88 | private List<Integer> couponCounts; |
| 89 | 89 | |
| 90 | 90 | @AssertTrue(message = "优惠劵和数量必须一一对应") | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.controller.app.coupon; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.map.MapUtil; | |
| 3 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 7 | import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; |
| 6 | -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO; | |
| 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; | |
| 9 | -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO; | |
| 10 | -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO; | |
| 8 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*; | |
| 9 | +import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; | |
| 10 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; | |
| 11 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; | |
| 12 | +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; | |
| 11 | 13 | import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; |
| 14 | +import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService; | |
| 12 | 15 | import io.swagger.v3.oas.annotations.Operation; |
| 16 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 13 | 17 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 14 | 18 | import org.springframework.validation.annotation.Validated; |
| 15 | 19 | import org.springframework.web.bind.annotation.*; |
| 16 | 20 | |
| 17 | 21 | import javax.annotation.Resource; |
| 18 | -import java.time.LocalDateTime; | |
| 19 | -import java.util.ArrayList; | |
| 22 | +import javax.validation.Valid; | |
| 23 | +import java.util.Collections; | |
| 20 | 24 | import java.util.List; |
| 21 | -import java.util.Random; | |
| 22 | 25 | |
| 23 | 26 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 24 | 27 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| ... | ... | @@ -31,73 +34,40 @@ public class AppCouponController { |
| 31 | 34 | |
| 32 | 35 | @Resource |
| 33 | 36 | private CouponService couponService; |
| 37 | + @Resource | |
| 38 | + private CouponTemplateService couponTemplateService; | |
| 34 | 39 | |
| 35 | - // TODO 芋艿:待实现 | |
| 36 | 40 | @PostMapping("/take") |
| 37 | 41 | @Operation(summary = "领取优惠劵") |
| 38 | - public CommonResult<Long> takeCoupon(@RequestBody AppCouponTemplatePageReqVO pageReqVO) { | |
| 39 | - return success(1L); | |
| 42 | + @Parameter(name = "templateId", description = "优惠券模板编号", required = true, example = "1024") | |
| 43 | + public CommonResult<Boolean> takeCoupon(@Valid @RequestBody AppCouponTakeReqVO reqVO) { | |
| 44 | + Long userId = getLoginUserId(); | |
| 45 | + // 领取 | |
| 46 | + couponService.takeCoupon(reqVO.getTemplateId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.USER); | |
| 47 | + // 检查是否可以继续领取 | |
| 48 | + CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(reqVO.getTemplateId()); | |
| 49 | + boolean canTakeAgain = true; | |
| 50 | + if (couponTemplate.getTakeLimitCount() != null && couponTemplate.getTakeLimitCount() > 0) { | |
| 51 | + Integer takeCount = MapUtil.getInt(couponService.getTakeCountMapByTemplateIds( | |
| 52 | + Collections.singleton(reqVO.getTemplateId()), userId), reqVO.getTemplateId(), 0); | |
| 53 | + canTakeAgain = takeCount < couponTemplate.getTakeLimitCount(); | |
| 54 | + } | |
| 55 | + return success(canTakeAgain); | |
| 40 | 56 | } |
| 41 | 57 | |
| 42 | - // TODO 芋艿:待实现 | |
| 43 | 58 | @GetMapping("/match-list") |
| 44 | 59 | @Operation(summary = "获得匹配指定商品的优惠劵列表") |
| 45 | 60 | public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) { |
| 46 | - List<AppCouponMatchRespVO> list = new ArrayList<>(); | |
| 47 | - Random random = new Random(); | |
| 48 | - for (int i = 0; i < 10; i++) { | |
| 49 | - AppCouponMatchRespVO vo = new AppCouponMatchRespVO(); | |
| 50 | - vo.setId(i + 1L); | |
| 51 | - vo.setName("优惠劵" + (i + 1)); | |
| 52 | - vo.setUsePrice(random.nextInt(100) * 100); | |
| 53 | - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10))); | |
| 54 | - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10)); | |
| 55 | - vo.setDiscountType(random.nextInt(2) + 1); | |
| 56 | - if (vo.getDiscountType() == 1) { | |
| 57 | - vo.setDiscountPercent(null); | |
| 58 | - vo.setDiscountPrice(random.nextInt(50) * 100); | |
| 59 | - vo.setDiscountLimitPrice(null); | |
| 60 | - } else { | |
| 61 | - vo.setDiscountPercent(random.nextInt(90) + 10); | |
| 62 | - vo.setDiscountPrice(null); | |
| 63 | - vo.setDiscountLimitPrice(random.nextInt(200) * 100); | |
| 64 | - } | |
| 65 | - vo.setMatch(random.nextBoolean()); | |
| 66 | - if (!vo.getMatch()) { | |
| 67 | - vo.setDescription("不符合条件噢"); | |
| 68 | - } | |
| 69 | - list.add(vo); | |
| 70 | - } | |
| 71 | - return success(list); | |
| 61 | + // todo: 优惠金额倒序 | |
| 62 | + return success(CouponConvert.INSTANCE.convertList(couponService.getMatchCouponList(getLoginUserId(), matchReqVO))); | |
| 72 | 63 | } |
| 73 | 64 | |
| 74 | - // TODO 芋艿:待实现 | |
| 75 | 65 | @GetMapping("/page") |
| 76 | 66 | @Operation(summary = "优惠劵列表", description = "我的优惠劵") |
| 77 | 67 | public CommonResult<PageResult<AppCouponRespVO>> takeCoupon(AppCouponPageReqVO pageReqVO) { |
| 78 | - List<AppCouponRespVO> list = new ArrayList<>(); | |
| 79 | - Random random = new Random(); | |
| 80 | - for (int i = 0; i < 10; i++) { | |
| 81 | - AppCouponRespVO vo = new AppCouponRespVO(); | |
| 82 | - vo.setId(i + 1L); | |
| 83 | - vo.setName("优惠劵" + (i + 1)); | |
| 84 | - vo.setStatus(pageReqVO.getStatus()); | |
| 85 | - vo.setUsePrice(random.nextInt(100) * 100); | |
| 86 | - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10))); | |
| 87 | - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10)); | |
| 88 | - vo.setDiscountType(random.nextInt(2) + 1); | |
| 89 | - if (vo.getDiscountType() == 1) { | |
| 90 | - vo.setDiscountPercent(null); | |
| 91 | - vo.setDiscountPrice(random.nextInt(50) * 100); | |
| 92 | - vo.setDiscountLimitPrice(null); | |
| 93 | - } else { | |
| 94 | - vo.setDiscountPercent(random.nextInt(90) + 10); | |
| 95 | - vo.setDiscountPrice(null); | |
| 96 | - vo.setDiscountLimitPrice(random.nextInt(200) * 100); | |
| 97 | - } | |
| 98 | - list.add(vo); | |
| 99 | - } | |
| 100 | - return success(new PageResult<>(list, 20L)); | |
| 68 | + PageResult<CouponDO> pageResult = couponService.getCouponPage( | |
| 69 | + CouponConvert.INSTANCE.convert(pageReqVO, Collections.singleton(getLoginUserId()))); | |
| 70 | + return success(CouponConvert.INSTANCE.convertAppPage(pageResult)); | |
| 101 | 71 | } |
| 102 | 72 | |
| 103 | 73 | @GetMapping(value = "/get-unused-count") | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.controller.app.coupon; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ObjUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; | |
| 7 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 5 | 8 | import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO; |
| 6 | 9 | import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO; |
| 10 | +import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert; | |
| 11 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; | |
| 12 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 13 | +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; | |
| 14 | +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; | |
| 7 | 15 | import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService; |
| 8 | 16 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | -import io.swagger.v3.oas.annotations.Parameter; | |
| 10 | -import io.swagger.v3.oas.annotations.Parameters; | |
| 11 | 17 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 12 | 18 | import org.springframework.validation.annotation.Validated; |
| 13 | 19 | import org.springframework.web.bind.annotation.GetMapping; |
| 14 | 20 | import org.springframework.web.bind.annotation.RequestMapping; |
| 15 | -import org.springframework.web.bind.annotation.RequestParam; | |
| 16 | 21 | import org.springframework.web.bind.annotation.RestController; |
| 17 | 22 | |
| 18 | 23 | import javax.annotation.Resource; |
| 19 | -import java.time.LocalDateTime; | |
| 20 | -import java.util.ArrayList; | |
| 21 | -import java.util.List; | |
| 22 | -import java.util.Random; | |
| 24 | +import java.util.*; | |
| 23 | 25 | |
| 24 | 26 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 27 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 28 | +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; | |
| 25 | 29 | |
| 26 | 30 | @Tag(name = "用户 App - 优惠劵模板") |
| 27 | 31 | @RestController |
| ... | ... | @@ -31,84 +35,45 @@ public class AppCouponTemplateController { |
| 31 | 35 | |
| 32 | 36 | @Resource |
| 33 | 37 | private CouponTemplateService couponTemplateService; |
| 38 | + @Resource | |
| 39 | + private CouponService couponService; | |
| 34 | 40 | |
| 35 | - // TODO 芋艿:待实现 | |
| 36 | - @GetMapping("/list") | |
| 37 | - @Operation(summary = "获得优惠劵模版列表") | |
| 38 | - @Parameters({ | |
| 39 | - @Parameter(name = "spuId", description = "商品 SPU 编号"), // 目前主要给商品详情使用 | |
| 40 | - @Parameter(name = "useType", description = "使用类型"), | |
| 41 | - @Parameter(name = "count", description = "数量", required = true) | |
| 42 | - }) | |
| 43 | - public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(@RequestParam(value = "spuId", required = false) Long spuId, | |
| 44 | - @RequestParam(value = "useType", required = false) Integer useType, | |
| 45 | - @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) { | |
| 46 | - List<AppCouponTemplateRespVO> list = new ArrayList<>(); | |
| 47 | - Random random = new Random(); | |
| 48 | - for (int i = 0; i < 10; i++) { | |
| 49 | - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO(); | |
| 50 | - vo.setId(i + 1L); | |
| 51 | - vo.setName("优惠劵" + (i + 1)); | |
| 52 | - vo.setTakeLimitCount(random.nextInt(10) + 1); | |
| 53 | - vo.setUsePrice(random.nextInt(100) * 100); | |
| 54 | - vo.setValidityType(random.nextInt(2) + 1); | |
| 55 | - if (vo.getValidityType() == 1) { | |
| 56 | - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10))); | |
| 57 | - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10)); | |
| 58 | - } else { | |
| 59 | - vo.setFixedStartTerm(random.nextInt(10)); | |
| 60 | - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1); | |
| 61 | - } | |
| 62 | - vo.setDiscountType(random.nextInt(2) + 1); | |
| 63 | - if (vo.getDiscountType() == 1) { | |
| 64 | - vo.setDiscountPercent(null); | |
| 65 | - vo.setDiscountPrice(random.nextInt(50) * 100); | |
| 66 | - vo.setDiscountLimitPrice(null); | |
| 67 | - } else { | |
| 68 | - vo.setDiscountPercent(random.nextInt(90) + 10); | |
| 69 | - vo.setDiscountPrice(null); | |
| 70 | - vo.setDiscountLimitPrice(random.nextInt(200) * 100); | |
| 71 | - } | |
| 72 | - vo.setTakeStatus(random.nextBoolean()); | |
| 73 | - list.add(vo); | |
| 74 | - } | |
| 75 | - return success(list); | |
| 76 | - } | |
| 41 | + @Resource | |
| 42 | + private ProductSpuApi productSpuApi; | |
| 77 | 43 | |
| 78 | - // TODO 芋艿:待实现;和 getCouponTemplateList 类似 | |
| 79 | 44 | @GetMapping("/page") |
| 80 | 45 | @Operation(summary = "获得优惠劵模版分页") |
| 81 | 46 | public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) { |
| 82 | - List<AppCouponTemplateRespVO> list = new ArrayList<>(); | |
| 83 | - Random random = new Random(); | |
| 84 | - for (int i = 0; i < 10; i++) { | |
| 85 | - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO(); | |
| 86 | - vo.setId(i + 1L); | |
| 87 | - vo.setName("优惠劵" + (i + 1)); | |
| 88 | - vo.setTakeLimitCount(random.nextInt(10) + 1); | |
| 89 | - vo.setUsePrice(random.nextInt(100) * 100); | |
| 90 | - vo.setValidityType(random.nextInt(2) + 1); | |
| 91 | - if (vo.getValidityType() == 1) { | |
| 92 | - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10))); | |
| 93 | - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10)); | |
| 94 | - } else { | |
| 95 | - vo.setFixedStartTerm(random.nextInt(10)); | |
| 96 | - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1); | |
| 97 | - } | |
| 98 | - vo.setDiscountType(random.nextInt(2) + 1); | |
| 99 | - if (vo.getDiscountType() == 1) { | |
| 100 | - vo.setDiscountPercent(null); | |
| 101 | - vo.setDiscountPrice(random.nextInt(50) * 100); | |
| 102 | - vo.setDiscountLimitPrice(null); | |
| 103 | - } else { | |
| 104 | - vo.setDiscountPercent(random.nextInt(90) + 10); | |
| 105 | - vo.setDiscountPrice(null); | |
| 106 | - vo.setDiscountLimitPrice(random.nextInt(200) * 100); | |
| 107 | - } | |
| 108 | - vo.setTakeStatus(random.nextBoolean()); | |
| 109 | - list.add(vo); | |
| 47 | + // 1.1 处理查询条件:商品范围编号 | |
| 48 | + Long productScopeValue = getaProductScopeValue(pageReqVO); | |
| 49 | + // 1.2 处理查询条件:领取方式=直接领取 | |
| 50 | + List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue()); | |
| 51 | + // 2. 分页查询 | |
| 52 | + PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage( | |
| 53 | + CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue)); | |
| 54 | + // 3.1 领取数量 | |
| 55 | + Map<Long, Integer> couponTakeCountMap = new HashMap<>(0); | |
| 56 | + Long userId = getLoginUserId(); | |
| 57 | + if (userId != null) { | |
| 58 | + List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId, | |
| 59 | + t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的 | |
| 60 | + couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId); | |
| 61 | + } | |
| 62 | + // 3.2 拼接返回 | |
| 63 | + return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap)); | |
| 64 | + } | |
| 65 | + | |
| 66 | + private Long getaProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) { | |
| 67 | + Long productScopeValue = pageReqVO.getSpuId(); | |
| 68 | + if (pageReqVO.getProductScope() == null || Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope())) { | |
| 69 | + // 通用券:清除商品范围 | |
| 70 | + productScopeValue = null; | |
| 71 | + } else if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) { | |
| 72 | + // 品类券:查询商品的品类 | |
| 73 | + productScopeValue = Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId())) | |
| 74 | + .map(ProductSpuRespDTO::getCategoryId).orElse(null); | |
| 110 | 75 | } |
| 111 | - return success(new PageResult<>(list, 20L)); | |
| 76 | + return productScopeValue; | |
| 112 | 77 | } |
| 113 | 78 | |
| 114 | 79 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java
| ... | ... | @@ -15,15 +15,15 @@ public class AppCouponMatchReqVO { |
| 15 | 15 | @NotNull(message = "商品金额不能为空") |
| 16 | 16 | private Integer price; |
| 17 | 17 | |
| 18 | - @Schema(description = "商品 SPU 编号的数组", required = true, example = "[1, 2]") | |
| 18 | + @Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]") | |
| 19 | 19 | @NotEmpty(message = "商品 SPU 编号不能为空") |
| 20 | 20 | private List<Long> spuIds; |
| 21 | 21 | |
| 22 | - @Schema(description = "商品 SKU 编号的数组", required = true, example = "[1, 2]") | |
| 22 | + @Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]") | |
| 23 | 23 | @NotEmpty(message = "商品 SKU 编号不能为空") |
| 24 | 24 | private List<Long> skuIds; |
| 25 | 25 | |
| 26 | - @Schema(description = "分类编号的数组", required = true, example = "[10, 20]") | |
| 26 | + @Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]") | |
| 27 | 27 | @NotEmpty(message = "分类编号不能为空") |
| 28 | 28 | private List<Long> categoryIds; |
| 29 | 29 | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 5 | +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -13,6 +15,7 @@ import lombok.ToString; |
| 13 | 15 | public class AppCouponPageReqVO extends PageParam { |
| 14 | 16 | |
| 15 | 17 | @Schema(description = "优惠劵状态", example = "1") |
| 18 | + @InEnum(value = CouponStatusEnum.class, message = "优惠劵状态,必须是 {value}") | |
| 16 | 19 | private Integer status; |
| 17 | 20 | |
| 18 | 21 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 5 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | import lombok.EqualsAndHashCode; |
| ... | ... | @@ -12,8 +14,10 @@ import lombok.ToString; |
| 12 | 14 | @ToString(callSuper = true) |
| 13 | 15 | public class AppCouponTemplatePageReqVO extends PageParam { |
| 14 | 16 | |
| 15 | - @Schema(description = "使用类型", example = "1") | |
| 16 | - // TODO 芋艿:这里要限制下枚举的使用 | |
| 17 | - private Integer useType; | |
| 17 | + @Schema(description = "商品范围", example = "1") | |
| 18 | + @InEnum(value = PromotionProductScopeEnum.class, message = "商品范围,必须是 {value}") | |
| 19 | + private Integer productScope; | |
| 18 | 20 | |
| 21 | + @Schema(description = "商品标号", example = "1") | |
| 22 | + private Long spuId; | |
| 19 | 23 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
| ... | ... | @@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.promotion.convert.coupon; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; |
| 5 | 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; | |
| 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; | |
| 9 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO; | |
| 6 | 10 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 7 | 11 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; |
| 8 | 12 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; |
| ... | ... | @@ -11,6 +15,8 @@ import org.mapstruct.Mapper; |
| 11 | 15 | import org.mapstruct.factory.Mappers; |
| 12 | 16 | |
| 13 | 17 | import java.time.LocalDateTime; |
| 18 | +import java.util.Collection; | |
| 19 | +import java.util.List; | |
| 14 | 20 | |
| 15 | 21 | /** |
| 16 | 22 | * 优惠劵 Convert |
| ... | ... | @@ -49,4 +55,10 @@ public interface CouponConvert { |
| 49 | 55 | } |
| 50 | 56 | return couponDO; |
| 51 | 57 | } |
| 58 | + | |
| 59 | + CouponPageReqVO convert(AppCouponPageReqVO pageReqVO, Collection<Long> userIds); | |
| 60 | + | |
| 61 | + PageResult<AppCouponRespVO> convertAppPage(PageResult<CouponDO> pageResult); | |
| 62 | + | |
| 63 | + List<AppCouponMatchRespVO> convertList(List<CouponDO> list); | |
| 52 | 64 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.convert.coupon; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.map.MapUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 5 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO; |
| 6 | +import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; | |
| 5 | 7 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO; |
| 6 | 8 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO; |
| 9 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO; | |
| 10 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO; | |
| 7 | 11 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; |
| 8 | 12 | import org.mapstruct.Mapper; |
| 9 | 13 | import org.mapstruct.factory.Mappers; |
| 10 | 14 | |
| 15 | +import java.util.List; | |
| 16 | +import java.util.Map; | |
| 17 | + | |
| 11 | 18 | /** |
| 12 | 19 | * 优惠劵模板 Convert |
| 13 | 20 | * |
| ... | ... | @@ -26,4 +33,25 @@ public interface CouponTemplateConvert { |
| 26 | 33 | |
| 27 | 34 | PageResult<CouponTemplateRespVO> convertPage(PageResult<CouponTemplateDO> page); |
| 28 | 35 | |
| 36 | + CouponTemplatePageReqVO convert(AppCouponTemplatePageReqVO pageReqVO, List<Integer> canTakeTypes, Integer productScope, Long productScopeValue); | |
| 37 | + | |
| 38 | + PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult); | |
| 39 | + | |
| 40 | + default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) { | |
| 41 | + PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult); | |
| 42 | + if (MapUtil.isEmpty(couponTakeCountMap)) { | |
| 43 | + return result; | |
| 44 | + } | |
| 45 | + | |
| 46 | + for (AppCouponTemplateRespVO vo : result.getList()) { | |
| 47 | + // 每人领取数量无限制 | |
| 48 | + if (vo.getTakeLimitCount() == -1) { | |
| 49 | + vo.setTakeStatus(false); | |
| 50 | + continue; | |
| 51 | + } | |
| 52 | + // 检查已领取数量是否超过限领数量 | |
| 53 | + vo.setTakeStatus(MapUtil.getInt(couponTakeCountMap, vo.getId(), 0) >= vo.getTakeLimitCount()); | |
| 54 | + } | |
| 55 | + return result; | |
| 56 | + } | |
| 29 | 57 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.dal.mysql.coupon; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.bean.BeanUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 6 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 7 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 8 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; |
| 7 | 9 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 10 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 11 | +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO; | |
| 8 | 12 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 13 | +import com.github.yulichang.toolkit.MPJWrappers; | |
| 9 | 14 | import org.apache.ibatis.annotations.Mapper; |
| 10 | 15 | |
| 16 | +import java.time.LocalDateTime; | |
| 11 | 17 | import java.util.Collection; |
| 12 | 18 | import java.util.List; |
| 19 | +import java.util.function.Function; | |
| 20 | +import java.util.stream.Collectors; | |
| 13 | 21 | |
| 14 | 22 | /** |
| 15 | 23 | * 优惠劵 Mapper |
| ... | ... | @@ -19,11 +27,11 @@ import java.util.List; |
| 19 | 27 | @Mapper |
| 20 | 28 | public interface CouponMapper extends BaseMapperX<CouponDO> { |
| 21 | 29 | |
| 22 | - default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO, Collection<Long> userIds) { | |
| 30 | + default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO) { | |
| 23 | 31 | return selectPage(reqVO, new LambdaQueryWrapperX<CouponDO>() |
| 24 | 32 | .eqIfPresent(CouponDO::getTemplateId, reqVO.getTemplateId()) |
| 25 | 33 | .eqIfPresent(CouponDO::getStatus, reqVO.getStatus()) |
| 26 | - .inIfPresent(CouponDO::getUserId, userIds) | |
| 34 | + .inIfPresent(CouponDO::getUserId, reqVO.getUserIds()) | |
| 27 | 35 | .betweenIfPresent(CouponDO::getCreateTime, reqVO.getCreateTime()) |
| 28 | 36 | .orderByDesc(CouponDO::getId)); |
| 29 | 37 | } |
| ... | ... | @@ -62,4 +70,36 @@ public interface CouponMapper extends BaseMapperX<CouponDO> { |
| 62 | 70 | ); |
| 63 | 71 | } |
| 64 | 72 | |
| 73 | + default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) { | |
| 74 | + return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class) | |
| 75 | + .select(CouponDO::getTemplateId) | |
| 76 | + .selectCount(CouponDO::getId, CouponTakeCountBO::getCount) | |
| 77 | + .eq(CouponDO::getUserId, userId) | |
| 78 | + .in(CouponDO::getTemplateId, templateIds) | |
| 79 | + .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class); | |
| 80 | + } | |
| 81 | + | |
| 82 | + default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope( | |
| 83 | + Long userId, Integer status, Integer usePrice, List<Long> spuIds, List<Long> categoryIds) { | |
| 84 | + | |
| 85 | + Function<List<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream() | |
| 86 | + .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) | |
| 87 | + .collect(Collectors.joining(" OR ")); | |
| 88 | + return selectList(new LambdaQueryWrapperX<CouponDO>() | |
| 89 | + .eq(CouponDO::getUserId, userId) | |
| 90 | + .eq(CouponDO::getStatus, status) | |
| 91 | + .le(CouponDO::getUsePrice, usePrice) | |
| 92 | + .and(w -> w.eq(CouponDO::getProductScope, PromotionProductScopeEnum.ALL.getScope()) | |
| 93 | + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) | |
| 94 | + .apply(productScopeValuesFindInSetFunc.apply(spuIds))) | |
| 95 | + .or(ww -> ww.eq(CouponDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) | |
| 96 | + .apply(productScopeValuesFindInSetFunc.apply(categoryIds))))); | |
| 97 | + } | |
| 98 | + | |
| 99 | + default List<CouponDO> selectListByStatusAndValidEndTimeLe(Integer status, LocalDateTime validEndTime) { | |
| 100 | + return selectList(new LambdaQueryWrapperX<CouponDO>() | |
| 101 | + .eq(CouponDO::getStatus, status) | |
| 102 | + .le(CouponDO::getValidEndTime, validEndTime) | |
| 103 | + ); | |
| 104 | + } | |
| 65 | 105 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
| ... | ... | @@ -39,6 +39,9 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> { |
| 39 | 39 | .eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus()) |
| 40 | 40 | .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) |
| 41 | 41 | .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime()) |
| 42 | + .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope()) | |
| 43 | + .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", | |
| 44 | + reqVO.getProductScopeValue())) | |
| 42 | 45 | .and(canTakeConsumer != null, canTakeConsumer) |
| 43 | 46 | .orderByDesc(CouponTemplateDO::getId)); |
| 44 | 47 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/CouponExpireJob.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.promotion.job; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; | |
| 6 | +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +import javax.annotation.Resource; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 优惠券过期 Job | |
| 13 | + * | |
| 14 | + * @author owen | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +@TenantJob | |
| 18 | +public class CouponExpireJob implements JobHandler { | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private CouponService couponService; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public String execute(String param) { | |
| 25 | + int count = couponService.expireCoupon(); | |
| 26 | + return StrUtil.format("过期优惠券 {} 个", count); | |
| 27 | + } | |
| 28 | + | |
| 29 | +} | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
| ... | ... | @@ -3,12 +3,18 @@ package cn.iocoder.yudao.module.promotion.service.coupon; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; |
| 6 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO; | |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 7 | 8 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| 9 | +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO; | |
| 8 | 10 | |
| 11 | +import java.util.Collection; | |
| 9 | 12 | import java.util.List; |
| 13 | +import java.util.Map; | |
| 10 | 14 | import java.util.Set; |
| 11 | 15 | |
| 16 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 17 | + | |
| 12 | 18 | /** |
| 13 | 19 | * 优惠劵 Service 接口 |
| 14 | 20 | * |
| ... | ... | @@ -18,11 +24,11 @@ public interface CouponService { |
| 18 | 24 | |
| 19 | 25 | /** |
| 20 | 26 | * 校验优惠劵,包括状态、有限期 |
| 21 | - * | |
| 27 | + * <p> | |
| 22 | 28 | * 1. 如果校验通过,则返回优惠劵信息 |
| 23 | 29 | * 2. 如果校验不通过,则直接抛出业务异常 |
| 24 | 30 | * |
| 25 | - * @param id 优惠劵编号 | |
| 31 | + * @param id 优惠劵编号 | |
| 26 | 32 | * @param userId 用户编号 |
| 27 | 33 | * @return 优惠劵信息 |
| 28 | 34 | */ |
| ... | ... | @@ -31,9 +37,8 @@ public interface CouponService { |
| 31 | 37 | /** |
| 32 | 38 | * 校验优惠劵,包括状态、有限期 |
| 33 | 39 | * |
| 34 | - * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同 | |
| 35 | - * | |
| 36 | 40 | * @param coupon 优惠劵 |
| 41 | + * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同 | |
| 37 | 42 | */ |
| 38 | 43 | void validCoupon(CouponDO coupon); |
| 39 | 44 | |
| ... | ... | @@ -124,4 +129,39 @@ public interface CouponService { |
| 124 | 129 | takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER); |
| 125 | 130 | } |
| 126 | 131 | |
| 132 | + /** | |
| 133 | + * 统计会员领取优惠券的数量 | |
| 134 | + * | |
| 135 | + * @param templateIds 优惠券模板编号列表 | |
| 136 | + * @param userId 用户编号 | |
| 137 | + * @return 领取优惠券的数量 | |
| 138 | + */ | |
| 139 | + default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) { | |
| 140 | + return convertMap(getTakeCountListByTemplateIds(templateIds, userId), CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount); | |
| 141 | + } | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * 统计会员领取优惠券的数量 | |
| 145 | + * | |
| 146 | + * @param templateIds 优惠券模板编号列表 | |
| 147 | + * @param userId 用户编号 | |
| 148 | + * @return 领取优惠券的数量 | |
| 149 | + */ | |
| 150 | + List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId); | |
| 151 | + | |
| 152 | + /** | |
| 153 | + * 获取用户匹配的优惠券列表 | |
| 154 | + * | |
| 155 | + * @param userId 用户编号 | |
| 156 | + * @param matchReqVO 匹配参数 | |
| 157 | + * @return 优惠券列表 | |
| 158 | + */ | |
| 159 | + List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO); | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * 过期优惠券 | |
| 163 | + * | |
| 164 | + * @return 过期数量 | |
| 165 | + */ | |
| 166 | + int expireCoupon(); | |
| 127 | 167 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
| ... | ... | @@ -2,15 +2,18 @@ package cn.iocoder.yudao.module.promotion.service.coupon; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollStreamUtil; |
| 4 | 4 | import cn.hutool.core.collection.CollUtil; |
| 5 | +import cn.hutool.core.collection.ListUtil; | |
| 5 | 6 | import cn.hutool.core.map.MapUtil; |
| 6 | 7 | import cn.hutool.core.util.ObjectUtil; |
| 7 | 8 | import cn.hutool.core.util.StrUtil; |
| 9 | +import cn.hutool.extra.spring.SpringUtil; | |
| 8 | 10 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | 11 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 10 | 12 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; |
| 11 | 13 | import cn.iocoder.yudao.module.member.api.user.MemberUserApi; |
| 12 | 14 | import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; |
| 13 | 15 | import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; |
| 16 | +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO; | |
| 14 | 17 | import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; |
| 15 | 18 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 16 | 19 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; |
| ... | ... | @@ -18,12 +21,15 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper; |
| 18 | 21 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; |
| 19 | 22 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; |
| 20 | 23 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; |
| 24 | +import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO; | |
| 25 | +import lombok.extern.slf4j.Slf4j; | |
| 21 | 26 | import org.springframework.stereotype.Service; |
| 22 | 27 | import org.springframework.transaction.annotation.Transactional; |
| 23 | 28 | import org.springframework.validation.annotation.Validated; |
| 24 | 29 | |
| 25 | 30 | import javax.annotation.Resource; |
| 26 | 31 | import java.time.LocalDateTime; |
| 32 | +import java.util.Collection; | |
| 27 | 33 | import java.util.List; |
| 28 | 34 | import java.util.Map; |
| 29 | 35 | import java.util.Set; |
| ... | ... | @@ -39,6 +45,7 @@ import static java.util.Arrays.asList; |
| 39 | 45 | * |
| 40 | 46 | * @author 芋道源码 |
| 41 | 47 | */ |
| 48 | +@Slf4j | |
| 42 | 49 | @Service |
| 43 | 50 | @Validated |
| 44 | 51 | public class CouponServiceImpl implements CouponService { |
| ... | ... | @@ -77,16 +84,16 @@ public class CouponServiceImpl implements CouponService { |
| 77 | 84 | @Override |
| 78 | 85 | public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) { |
| 79 | 86 | // 获得用户编号 |
| 80 | - Set<Long> userIds = null; | |
| 81 | 87 | if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { |
| 82 | - userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), | |
| 88 | + Set<Long> userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), | |
| 83 | 89 | MemberUserRespDTO::getId); |
| 84 | 90 | if (CollUtil.isEmpty(userIds)) { |
| 85 | 91 | return PageResult.empty(); |
| 86 | 92 | } |
| 93 | + pageReqVO.setUserIds(userIds); | |
| 87 | 94 | } |
| 88 | 95 | // 分页查询 |
| 89 | - return couponMapper.selectPage(pageReqVO, userIds); | |
| 96 | + return couponMapper.selectPage(pageReqVO); | |
| 90 | 97 | } |
| 91 | 98 | |
| 92 | 99 | @Override |
| ... | ... | @@ -175,6 +182,57 @@ public class CouponServiceImpl implements CouponService { |
| 175 | 182 | couponTemplateService.updateCouponTemplateTakeCount(templateId, userIds.size()); |
| 176 | 183 | } |
| 177 | 184 | |
| 185 | + @Override | |
| 186 | + public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) { | |
| 187 | + if (CollUtil.isEmpty(templateIds)) { | |
| 188 | + return ListUtil.empty(); | |
| 189 | + } | |
| 190 | + return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds); | |
| 191 | + } | |
| 192 | + | |
| 193 | + @Override | |
| 194 | + public List<CouponDO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) { | |
| 195 | + return couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId, CouponStatusEnum.UNUSED.getStatus(), | |
| 196 | + matchReqVO.getPrice(), matchReqVO.getSpuIds(), matchReqVO.getCategoryIds()); | |
| 197 | + } | |
| 198 | + | |
| 199 | + @Override | |
| 200 | + public int expireCoupon() { | |
| 201 | + // 1. 查询待过期的优惠券 | |
| 202 | + List<CouponDO> list = couponMapper.selectListByStatusAndValidEndTimeLe( | |
| 203 | + CouponStatusEnum.UNUSED.getStatus(), LocalDateTime.now()); | |
| 204 | + if (CollUtil.isEmpty(list)) { | |
| 205 | + return 0; | |
| 206 | + } | |
| 207 | + | |
| 208 | + // 2. 遍历执行 | |
| 209 | + int count = 0; | |
| 210 | + for (CouponDO coupon : list) { | |
| 211 | + try { | |
| 212 | + boolean success = getSelf().expireCoupon(coupon); | |
| 213 | + if (success) { | |
| 214 | + count++; | |
| 215 | + } | |
| 216 | + } catch (Exception e) { | |
| 217 | + log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId(), e); | |
| 218 | + } | |
| 219 | + } | |
| 220 | + return count; | |
| 221 | + } | |
| 222 | + | |
| 223 | + private boolean expireCoupon(CouponDO coupon) { | |
| 224 | + // 更新记录状态 | |
| 225 | + CouponDO updateObj = new CouponDO().setStatus(CouponStatusEnum.EXPIRE.getStatus()); | |
| 226 | + int updateRows = couponMapper.updateByIdAndStatus(coupon.getId(), CouponStatusEnum.UNUSED.getStatus(), updateObj); | |
| 227 | + if (updateRows == 0) { | |
| 228 | + log.error("[expireCoupon][coupon({}) 更新为已过期失败]", coupon.getId()); | |
| 229 | + return false; | |
| 230 | + } | |
| 231 | + | |
| 232 | + log.info("[expireCoupon][coupon({}) 更新为已过期成功]", coupon.getId()); | |
| 233 | + return true; | |
| 234 | + } | |
| 235 | + | |
| 178 | 236 | /** |
| 179 | 237 | * 校验优惠券是否可以领取 |
| 180 | 238 | * |
| ... | ... | @@ -211,7 +269,7 @@ public class CouponServiceImpl implements CouponService { |
| 211 | 269 | /** |
| 212 | 270 | * 过滤掉达到领取上线的用户 |
| 213 | 271 | * |
| 214 | - * @param userIds 用户编号数组 | |
| 272 | + * @param userIds 用户编号数组 | |
| 215 | 273 | * @param couponTemplate 优惠劵模版 |
| 216 | 274 | */ |
| 217 | 275 | private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) { |
| ... | ... | @@ -228,4 +286,12 @@ public class CouponServiceImpl implements CouponService { |
| 228 | 286 | userIds.removeIf(userId -> MapUtil.getInt(userTakeCountMap, userId, 0) >= couponTemplate.getTakeLimitCount()); |
| 229 | 287 | } |
| 230 | 288 | |
| 289 | + /** | |
| 290 | + * 获得自身的代理对象,解决 AOP 生效问题 | |
| 291 | + * | |
| 292 | + * @return 自己 | |
| 293 | + */ | |
| 294 | + private CouponServiceImpl getSelf() { | |
| 295 | + return SpringUtil.getBean(getClass()); | |
| 296 | + } | |
| 231 | 297 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.promotion.service.coupon.bo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 优惠券领取数量 BO | |
| 7 | + * | |
| 8 | + * @author owen | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class CouponTakeCountBO { | |
| 12 | + /** | |
| 13 | + * 优惠劵模板编号 | |
| 14 | + */ | |
| 15 | + private Long templateId; | |
| 16 | + /** | |
| 17 | + * 领取数量 | |
| 18 | + */ | |
| 19 | + private Integer count; | |
| 20 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java
| ... | ... | @@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; |
| 10 | 10 | import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; |
| 11 | 11 | import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; |
| 12 | 12 | import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; |
| 13 | -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService; | |
| 14 | -import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO; | |
| 15 | 13 | import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService; |
| 16 | 14 | import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; |
| 15 | +import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService; | |
| 16 | +import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO; | |
| 17 | 17 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO; |
| 18 | 18 | import io.swagger.v3.oas.annotations.Operation; |
| 19 | 19 | import io.swagger.v3.oas.annotations.Parameter; |
| ... | ... | @@ -92,20 +92,17 @@ public class BrokerageUserController { |
| 92 | 92 | Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId); |
| 93 | 93 | // 查询用户信息 |
| 94 | 94 | Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds); |
| 95 | - // TODO @疯狂:看看下面两个 getBrokerageUserCountByBindUserId、getWithdrawSummaryByUserId 有没可能一次性出结果,不然 n 次有点太花性能了; | |
| 96 | 95 | // 合计分佣订单 |
| 97 | - Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds, | |
| 98 | - userId -> userId, | |
| 99 | - userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId, | |
| 100 | - BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus())); | |
| 96 | + Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId( | |
| 97 | + userIds, BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()); | |
| 98 | + // TODO @芋艿:看看下面 getBrokerageUserCountByBindUserId 有没可能一次性出结果,不然 n 次有点太花性能了; | |
| 101 | 99 | // 合计推广用户数量 |
| 102 | 100 | Map<Long, Long> brokerageUserCountMap = convertMap(userIds, |
| 103 | 101 | userId -> userId, |
| 104 | 102 | userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null)); |
| 105 | 103 | // 合计提现 |
| 106 | - Map<Long, UserWithdrawSummaryBO> withdrawMap = convertMap(userIds, | |
| 107 | - userId -> userId, | |
| 108 | - userId -> brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS)); | |
| 104 | + Map<Long, UserWithdrawSummaryBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(userIds, | |
| 105 | + BrokerageWithdrawStatusEnum.AUDIT_SUCCESS); | |
| 109 | 106 | // 拼接返回 |
| 110 | 107 | return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap, |
| 111 | 108 | userOrderSummaryMap, withdrawMap)); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
| ... | ... | @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.*; |
| 27 | 27 | import javax.annotation.Resource; |
| 28 | 28 | import javax.validation.Valid; |
| 29 | 29 | import java.time.LocalDateTime; |
| 30 | +import java.util.Collections; | |
| 30 | 31 | import java.util.Map; |
| 31 | 32 | import java.util.Optional; |
| 32 | 33 | |
| ... | ... | @@ -76,23 +77,25 @@ public class AppBrokerageUserController { |
| 76 | 77 | @Operation(summary = "获得个人分销统计") |
| 77 | 78 | @PreAuthenticated |
| 78 | 79 | public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() { |
| 79 | - Long userId = getLoginUserId(); | |
| 80 | - // TODO @疯狂:后面这种,要不也改成 convert;感觉 controller 这样更容易看到整体;核心其实是 86、8/87、9/90、9/91 这阶段 | |
| 81 | - // 统计 yesterdayPrice、withdrawPrice、firstBrokerageUserCount、secondBrokerageUserCount 字段 | |
| 80 | + // 查询当前登录用户信息 | |
| 81 | + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(getLoginUserId()); | |
| 82 | + // 统计用户昨日的佣金 | |
| 82 | 83 | LocalDateTime yesterday = LocalDateTime.now().minusDays(1); |
| 83 | 84 | LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday); |
| 84 | 85 | LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday); |
| 85 | - AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO() | |
| 86 | - .setYesterdayPrice(brokerageRecordService.getSummaryPriceByUserId(userId, BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime)) | |
| 87 | - .setWithdrawPrice(Optional.ofNullable(brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS)) | |
| 88 | - .map(UserWithdrawSummaryBO::getPrice).orElse(0)) | |
| 89 | - .setBrokeragePrice(0).setFrozenPrice(0) | |
| 90 | - .setFirstBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 1)) | |
| 91 | - .setSecondBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 2)); | |
| 92 | - // 设置 brokeragePrice、frozenPrice 字段 | |
| 93 | - Optional.ofNullable(brokerageUserService.getBrokerageUser(userId)) | |
| 94 | - .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice())); | |
| 95 | - return success(respVO); | |
| 86 | + Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(), | |
| 87 | + BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime); | |
| 88 | + // 统计用户提现的佣金 | |
| 89 | + Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryByUserId(Collections.singleton(brokerageUser.getId()), | |
| 90 | + BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream() | |
| 91 | + .findFirst().map(UserWithdrawSummaryBO::getPrice).orElse(0); | |
| 92 | + // 统计分销用户数量(一级) | |
| 93 | + Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 1); | |
| 94 | + // 统计分销用户数量(二级) | |
| 95 | + Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 2); | |
| 96 | + | |
| 97 | + // 拼接返回 | |
| 98 | + return success(BrokerageUserConvert.INSTANCE.convert(yesterdayPrice, withdrawPrice, firstBrokerageUserCount, secondBrokerageUserCount, brokerageUser)); | |
| 96 | 99 | } |
| 97 | 100 | |
| 98 | 101 | @GetMapping("/rank-page-by-user-count") | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
| ... | ... | @@ -37,6 +37,8 @@ public class AppTradeOrderSettlementRespVO { |
| 37 | 37 | |
| 38 | 38 | // ========== SPU 信息 ========== |
| 39 | 39 | |
| 40 | + @Schema(description = "品类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 41 | + private Long categoryId; | |
| 40 | 42 | @Schema(description = "SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| 41 | 43 | private Long spuId; |
| 42 | 44 | @Schema(description = "SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "Apple iPhone 12") | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.convert.brokerage; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.core.util.ObjUtil; | |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 6 | import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; |
| 6 | 7 | import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserRespVO; |
| 8 | +import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserMySummaryRespVO; | |
| 7 | 9 | import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO; |
| 8 | 10 | import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; |
| 9 | 11 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO; |
| ... | ... | @@ -71,4 +73,18 @@ public interface BrokerageUserConvert { |
| 71 | 73 | |
| 72 | 74 | void copyTo(MemberUserRespDTO from, @MappingTarget AppBrokerageUserRankByUserCountRespVO to); |
| 73 | 75 | |
| 76 | + default AppBrokerageUserMySummaryRespVO convert(Integer yesterdayPrice, Integer withdrawPrice, | |
| 77 | + Long firstBrokerageUserCount, Long secondBrokerageUserCount, | |
| 78 | + BrokerageUserDO brokerageUser) { | |
| 79 | + AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO() | |
| 80 | + .setYesterdayPrice(ObjUtil.defaultIfNull(yesterdayPrice, 0)) | |
| 81 | + .setWithdrawPrice(ObjUtil.defaultIfNull(withdrawPrice, 0)) | |
| 82 | + .setBrokeragePrice(0).setFrozenPrice(0) | |
| 83 | + .setFirstBrokerageUserCount(ObjUtil.defaultIfNull(firstBrokerageUserCount, 0L)) | |
| 84 | + .setSecondBrokerageUserCount(ObjUtil.defaultIfNull(secondBrokerageUserCount, 0L)); | |
| 85 | + // 设置 brokeragePrice、frozenPrice 字段 | |
| 86 | + Optional.ofNullable(brokerageUser) | |
| 87 | + .ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice())); | |
| 88 | + return respVO; | |
| 89 | + } | |
| 74 | 90 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.dal.mysql.brokerage; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.bean.BeanUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| ... | ... | @@ -9,12 +10,15 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO; |
| 9 | 10 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO; |
| 10 | 11 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 11 | 12 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 13 | +import com.github.yulichang.toolkit.MPJWrappers; | |
| 12 | 14 | import org.apache.ibatis.annotations.Mapper; |
| 13 | 15 | import org.apache.ibatis.annotations.Param; |
| 14 | 16 | import org.apache.ibatis.annotations.Select; |
| 15 | 17 | |
| 16 | 18 | import java.time.LocalDateTime; |
| 19 | +import java.util.Collection; | |
| 17 | 20 | import java.util.List; |
| 21 | +import java.util.Map; | |
| 18 | 22 | |
| 19 | 23 | /** |
| 20 | 24 | * 佣金记录 Mapper |
| ... | ... | @@ -52,11 +56,27 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> { |
| 52 | 56 | BrokerageRecordDO::getUserId, userId); |
| 53 | 57 | } |
| 54 | 58 | |
| 55 | - @Select("SELECT COUNT(1), SUM(price) FROM trade_brokerage_record " + | |
| 56 | - "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} AND deleted = FALSE") | |
| 57 | - UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId, | |
| 58 | - @Param("bizType") Integer bizType, | |
| 59 | - @Param("status") Integer status); | |
| 59 | + default List<UserBrokerageSummaryBO> selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(Collection<Long> userIds, | |
| 60 | + Integer bizType, | |
| 61 | + Integer status) { | |
| 62 | + List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(BrokerageRecordDO.class) | |
| 63 | + .select(BrokerageRecordDO::getUserId) | |
| 64 | + .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount) | |
| 65 | + .selectSum(BrokerageRecordDO::getPrice) | |
| 66 | + .in(BrokerageRecordDO::getUserId, userIds) | |
| 67 | + .eq(BrokerageRecordDO::getBizId, bizType) | |
| 68 | + .eq(BrokerageRecordDO::getStatus, status) | |
| 69 | + .groupBy(BrokerageRecordDO::getUserId)); | |
| 70 | + return BeanUtil.copyToList(list, UserBrokerageSummaryBO.class); | |
| 71 | +// return selectJoinList(UserBrokerageSummaryBO.class, MPJWrappers.lambdaJoin(BrokerageRecordDO.class) | |
| 72 | +// .select(BrokerageRecordDO::getUserId) | |
| 73 | +// .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount) | |
| 74 | +// .selectSum(BrokerageRecordDO::getPrice) | |
| 75 | +// .in(BrokerageRecordDO::getUserId, userIds) | |
| 76 | +// .eq(BrokerageRecordDO::getBizId, bizType) | |
| 77 | +// .eq(BrokerageRecordDO::getStatus, status) | |
| 78 | +// .groupBy(BrokerageRecordDO::getUserId)); | |
| 79 | + } | |
| 60 | 80 | |
| 61 | 81 | @Select("SELECT SUM(price) FROM trade_brokerage_record " + |
| 62 | 82 | "WHERE user_id = #{userId} AND biz_type = #{bizType} " + | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.dal.mysql.brokerage; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.bean.BeanUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| ... | ... | @@ -7,9 +8,12 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.Brok |
| 7 | 8 | import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO; |
| 8 | 9 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO; |
| 9 | 10 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 11 | +import com.github.yulichang.wrapper.MPJLambdaWrapper; | |
| 10 | 12 | import org.apache.ibatis.annotations.Mapper; |
| 11 | -import org.apache.ibatis.annotations.Param; | |
| 12 | -import org.apache.ibatis.annotations.Select; | |
| 13 | + | |
| 14 | +import java.util.Collection; | |
| 15 | +import java.util.List; | |
| 16 | +import java.util.Map; | |
| 13 | 17 | |
| 14 | 18 | /** |
| 15 | 19 | * 佣金提现 Mapper |
| ... | ... | @@ -37,9 +41,23 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO |
| 37 | 41 | .eq(BrokerageWithdrawDO::getStatus, status)); |
| 38 | 42 | } |
| 39 | 43 | |
| 40 | - @Select("SELECT COUNT(1) AS count, SUM(price) AS price FROM trade_brokerage_withdraw " + | |
| 41 | - "WHERE user_id = #{userId} AND status = #{status} AND deleted = FALSE") | |
| 42 | - UserWithdrawSummaryBO selectCountAndSumPriceByUserIdAndStatus(@Param("userId") Long userId, | |
| 43 | - @Param("status") Integer status); | |
| 44 | + default List<UserWithdrawSummaryBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds, Integer status) { | |
| 45 | + List<Map<String, Object>> list = selectMaps(new MPJLambdaWrapper<BrokerageWithdrawDO>() | |
| 46 | + .select(BrokerageWithdrawDO::getUserId) | |
| 47 | + .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount) | |
| 48 | + .selectSum(BrokerageWithdrawDO::getPrice) | |
| 49 | + .in(BrokerageWithdrawDO::getUserId, userIds) | |
| 50 | + .eq(BrokerageWithdrawDO::getStatus, status) | |
| 51 | + .groupBy(BrokerageWithdrawDO::getUserId)); | |
| 52 | + return BeanUtil.copyToList(list, UserWithdrawSummaryBO.class); | |
| 53 | + // selectJoinList有BUG,会与租户插件冲突:解析SQL时,发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW | |
| 54 | +// return selectJoinList(UserWithdrawSummaryBO.class, new MPJLambdaWrapper<BrokerageWithdrawDO>() | |
| 55 | +// .select(BrokerageWithdrawDO::getUserId) | |
| 56 | +// .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount) | |
| 57 | +// .selectSum(BrokerageWithdrawDO::getPrice) | |
| 58 | +// .in(BrokerageWithdrawDO::getUserId, userIds) | |
| 59 | +// .eq(BrokerageWithdrawDO::getStatus, status) | |
| 60 | +// .groupBy(BrokerageWithdrawDO::getUserId)); | |
| 61 | + } | |
| 44 | 62 | |
| 45 | 63 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceImpl.java
| ... | ... | @@ -85,7 +85,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 85 | 85 | |
| 86 | 86 | @Override |
| 87 | 87 | public TradeAfterSaleDO getAfterSale(Long userId, Long id) { |
| 88 | - return tradeAfterSaleMapper.selectByIdAndUserId(id, userId); | |
| 88 | + return tradeAfterSaleMapper.selectByIdAndUserId(id, userId); | |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | 91 | @Override |
| ... | ... | @@ -167,9 +167,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 167 | 167 | tradeAfterSaleMapper.insert(afterSale); |
| 168 | 168 | |
| 169 | 169 | // 更新交易订单项的售后状态 |
| 170 | - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(orderItem.getId(), | |
| 171 | - TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), | |
| 172 | - afterSale.getId(), null); | |
| 170 | + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCreate(orderItem.getId(), afterSale.getId()); | |
| 173 | 171 | |
| 174 | 172 | // 记录售后日志 |
| 175 | 173 | createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(), |
| ... | ... | @@ -219,8 +217,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 219 | 217 | // TODO 发送售后消息 |
| 220 | 218 | |
| 221 | 219 | // 更新交易订单项的售后状态为【未申请】 |
| 222 | - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(), | |
| 223 | - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); | |
| 220 | + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); | |
| 224 | 221 | } |
| 225 | 222 | |
| 226 | 223 | /** |
| ... | ... | @@ -313,8 +310,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 313 | 310 | // TODO 发送售后消息 |
| 314 | 311 | |
| 315 | 312 | // 更新交易订单项的售后状态为【未申请】 |
| 316 | - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(), | |
| 317 | - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); | |
| 313 | + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); | |
| 318 | 314 | } |
| 319 | 315 | |
| 320 | 316 | /** |
| ... | ... | @@ -360,9 +356,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 360 | 356 | // TODO 发送售后消息 |
| 361 | 357 | |
| 362 | 358 | // 更新交易订单项的售后状态为【已完成】 |
| 363 | - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(), | |
| 364 | - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), | |
| 365 | - null, afterSale.getRefundPrice()); | |
| 359 | + tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice()); | |
| 366 | 360 | } |
| 367 | 361 | |
| 368 | 362 | private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) { |
| ... | ... | @@ -403,8 +397,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa |
| 403 | 397 | // TODO 发送售后消息 |
| 404 | 398 | |
| 405 | 399 | // 更新交易订单项的售后状态为【未申请】 |
| 406 | - tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(), | |
| 407 | - TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); | |
| 400 | + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); | |
| 408 | 401 | } |
| 409 | 402 | |
| 410 | 403 | @Override | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
| ... | ... | @@ -12,7 +12,11 @@ import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO |
| 12 | 12 | |
| 13 | 13 | import javax.validation.Valid; |
| 14 | 14 | import java.time.LocalDateTime; |
| 15 | +import java.util.Collection; | |
| 15 | 16 | import java.util.List; |
| 17 | +import java.util.Map; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 16 | 20 | |
| 17 | 21 | /** |
| 18 | 22 | * 佣金记录 Service 接口 |
| ... | ... | @@ -76,12 +80,24 @@ public interface BrokerageRecordService { |
| 76 | 80 | /** |
| 77 | 81 | * 汇总用户佣金 |
| 78 | 82 | * |
| 79 | - * @param userId 用户编号 | |
| 83 | + * @param userIds 用户编号 | |
| 84 | + * @param bizType 业务类型 | |
| 85 | + * @param status 佣金状态 | |
| 86 | + * @return 用户佣金汇总 | |
| 87 | + */ | |
| 88 | + List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status); | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 汇总用户佣金 | |
| 92 | + * | |
| 93 | + * @param userIds 用户编号 | |
| 80 | 94 | * @param bizType 业务类型 |
| 81 | 95 | * @param status 佣金状态 |
| 82 | 96 | * @return 用户佣金汇总 |
| 83 | 97 | */ |
| 84 | - UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status); | |
| 98 | + default Map<Long, UserBrokerageSummaryBO> getUserBrokerageSummaryMapByUserId(Collection<Long> userIds, Integer bizType, Integer status) { | |
| 99 | + return convertMap(getUserBrokerageSummaryByUserId(userIds, bizType, status), UserBrokerageSummaryBO::getUserId); | |
| 100 | + } | |
| 85 | 101 | |
| 86 | 102 | /** |
| 87 | 103 | * 获得用户佣金合计 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
| ... | ... | @@ -36,10 +36,7 @@ import org.springframework.validation.annotation.Validated; |
| 36 | 36 | |
| 37 | 37 | import javax.annotation.Resource; |
| 38 | 38 | import java.time.LocalDateTime; |
| 39 | -import java.util.ArrayList; | |
| 40 | -import java.util.List; | |
| 41 | -import java.util.Objects; | |
| 42 | -import java.util.Optional; | |
| 39 | +import java.util.*; | |
| 43 | 40 | |
| 44 | 41 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 45 | 42 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; |
| ... | ... | @@ -235,9 +232,8 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { |
| 235 | 232 | } |
| 236 | 233 | |
| 237 | 234 | @Override |
| 238 | - public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) { | |
| 239 | - UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status); | |
| 240 | - return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0); | |
| 235 | + public List<UserBrokerageSummaryBO> getUserBrokerageSummaryByUserId(Collection<Long> userIds, Integer bizType, Integer status) { | |
| 236 | + return brokerageRecordMapper.selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(userIds, bizType, status); | |
| 241 | 237 | } |
| 242 | 238 | |
| 243 | 239 | @Override |
| ... | ... | @@ -338,7 +334,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { |
| 338 | 334 | if (BooleanUtil.isTrue(spu.getSubCommissionType())) { |
| 339 | 335 | fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); |
| 340 | 336 | fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); |
| 341 | - // 3.2 全局分佣模式(根据商品价格比例计算) | |
| 337 | + // 3.2 全局分佣模式(根据商品价格比例计算) | |
| 342 | 338 | } else { |
| 343 | 339 | spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice); |
| 344 | 340 | spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.brokerage; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 5 | 4 | import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO; |
| 6 | 5 | import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO; |
| 7 | 6 | import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO; |
| ... | ... | @@ -111,21 +110,7 @@ public interface BrokerageUserService { |
| 111 | 110 | * @param registerTime 用户注册时间 |
| 112 | 111 | * @return 是否绑定 |
| 113 | 112 | */ |
| 114 | - default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) { | |
| 115 | - // 注册时间在30秒内的,都算新用户 | |
| 116 | - boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30)); | |
| 117 | - return bindBrokerageUser(userId, bindUserId, isNewUser); | |
| 118 | - } | |
| 119 | - | |
| 120 | - /** | |
| 121 | - * 【会员】绑定推广员 | |
| 122 | - * | |
| 123 | - * @param userId 用户编号 | |
| 124 | - * @param bindUserId 推广员编号 | |
| 125 | - * @param isNewUser 是否为新用户 | |
| 126 | - * @return 是否绑定 | |
| 127 | - */ | |
| 128 | - boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser); | |
| 113 | + boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime); | |
| 129 | 114 | |
| 130 | 115 | /** |
| 131 | 116 | * 获取用户是否有分销资格 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert; |
| 5 | 5 | import cn.hutool.core.util.ArrayUtil; |
| 6 | 6 | import cn.hutool.core.util.BooleanUtil; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 8 | 9 | import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; |
| 9 | 10 | import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO; |
| 10 | 11 | import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO; |
| ... | ... | @@ -154,7 +155,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { |
| 154 | 155 | } |
| 155 | 156 | |
| 156 | 157 | @Override |
| 157 | - public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) { | |
| 158 | + public boolean bindBrokerageUser(Long userId, Long bindUserId, LocalDateTime registerTime) { | |
| 158 | 159 | // 1. 获得分销用户 |
| 159 | 160 | boolean isNewBrokerageUser = false; |
| 160 | 161 | BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId); |
| ... | ... | @@ -164,7 +165,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { |
| 164 | 165 | } |
| 165 | 166 | |
| 166 | 167 | // 2.1 校验是否能绑定用户 |
| 167 | - boolean validated = isUserCanBind(brokerageUser, isNewUser); | |
| 168 | + boolean validated = isUserCanBind(brokerageUser, registerTime); | |
| 168 | 169 | if (!validated) { |
| 169 | 170 | return false; |
| 170 | 171 | } |
| ... | ... | @@ -222,7 +223,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { |
| 222 | 223 | return new PageResult<>(pageResult.getRecords(), pageResult.getTotal()); |
| 223 | 224 | } |
| 224 | 225 | |
| 225 | - private boolean isUserCanBind(BrokerageUserDO user, Boolean isNewUser) { | |
| 226 | + private boolean isUserCanBind(BrokerageUserDO user, LocalDateTime registerTime) { | |
| 226 | 227 | // 校验分销功能是否启用 |
| 227 | 228 | TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig(); |
| 228 | 229 | if (tradeConfig == null || !BooleanUtil.isTrue(tradeConfig.getBrokerageEnabled())) { |
| ... | ... | @@ -236,8 +237,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { |
| 236 | 237 | |
| 237 | 238 | // 校验分销关系绑定模式 |
| 238 | 239 | if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) { |
| 239 | - // TODO @疯狂:是不是把 isNewUser 挪到这里好点呀? | |
| 240 | - if (!BooleanUtil.isTrue(isNewUser)) { | |
| 240 | + // 判断是否为新用户:注册时间在30秒内的,都算新用户 | |
| 241 | + boolean isNotNewUser = LocalDateTimeUtils.beforeNow(registerTime.plusSeconds(30)); | |
| 242 | + if (isNotNewUser) { | |
| 241 | 243 | throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定 |
| 242 | 244 | } |
| 243 | 245 | } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) { | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
| ... | ... | @@ -7,6 +7,13 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawD |
| 7 | 7 | import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; |
| 8 | 8 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO; |
| 9 | 9 | |
| 10 | +import java.util.Collection; | |
| 11 | +import java.util.List; | |
| 12 | +import java.util.Map; | |
| 13 | +import java.util.Set; | |
| 14 | + | |
| 15 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 16 | + | |
| 10 | 17 | /** |
| 11 | 18 | * 佣金提现 Service 接口 |
| 12 | 19 | * |
| ... | ... | @@ -51,10 +58,20 @@ public interface BrokerageWithdrawService { |
| 51 | 58 | /** |
| 52 | 59 | * 汇总用户提现 |
| 53 | 60 | * |
| 54 | - * @param userId 用户编号 | |
| 55 | - * @param status 提现状态 | |
| 61 | + * @param userIds 用户编号 | |
| 62 | + * @param status 提现状态 | |
| 56 | 63 | * @return 用户提现汇总 |
| 57 | 64 | */ |
| 58 | - UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status); | |
| 65 | + List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status); | |
| 59 | 66 | |
| 67 | + /** | |
| 68 | + * 汇总用户提现 | |
| 69 | + * | |
| 70 | + * @param userIds 用户编号 | |
| 71 | + * @param status 提现状态 | |
| 72 | + * @return 用户提现汇总 | |
| 73 | + */ | |
| 74 | + default Map<Long, UserWithdrawSummaryBO> getWithdrawSummaryMapByUserId(Set<Long> userIds, BrokerageWithdrawStatusEnum status) { | |
| 75 | + return convertMap(getWithdrawSummaryByUserId(userIds, status), UserWithdrawSummaryBO::getUserId); | |
| 76 | + } | |
| 60 | 77 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
| ... | ... | @@ -26,6 +26,8 @@ import org.springframework.validation.annotation.Validated; |
| 26 | 26 | import javax.annotation.Resource; |
| 27 | 27 | import javax.validation.Validator; |
| 28 | 28 | import java.time.LocalDateTime; |
| 29 | +import java.util.Collection; | |
| 30 | +import java.util.List; | |
| 29 | 31 | import java.util.Map; |
| 30 | 32 | |
| 31 | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -142,9 +144,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { |
| 142 | 144 | } |
| 143 | 145 | |
| 144 | 146 | @Override |
| 145 | - public UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status) { | |
| 146 | - UserWithdrawSummaryBO summaryBO = brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userId, status.getStatus()); | |
| 147 | - return summaryBO != null ? summaryBO : new UserWithdrawSummaryBO(0, 0); | |
| 147 | + public List<UserWithdrawSummaryBO> getWithdrawSummaryByUserId(Collection<Long> userIds, BrokerageWithdrawStatusEnum status) { | |
| 148 | + return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus()); | |
| 148 | 149 | } |
| 149 | 150 | |
| 150 | 151 | /** | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserBrokerageSummaryBO.java
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/bo/UserWithdrawSummaryBO.java
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.order; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; | |
| 4 | -import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO; | |
| 5 | 3 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; |
| 6 | 4 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; |
| 5 | +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; | |
| 6 | +import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO; | |
| 7 | 7 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; |
| 8 | 8 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; |
| 10 | 10 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; |
| 11 | 11 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 12 | 12 | |
| 13 | +import javax.validation.constraints.NotNull; | |
| 14 | + | |
| 13 | 15 | /** |
| 14 | 16 | * 交易订单【写】Service 接口 |
| 15 | 17 | * |
| ... | ... | @@ -116,27 +118,28 @@ public interface TradeOrderUpdateService { |
| 116 | 118 | // =================== Order Item =================== |
| 117 | 119 | |
| 118 | 120 | /** |
| 119 | - * 更新交易订单项的售后状态 | |
| 121 | + * 当售后申请后,更新交易订单项的售后状态 | |
| 122 | + * | |
| 123 | + * @param id 交易订单项编号 | |
| 124 | + * @param afterSaleId 售后单编号 | |
| 125 | + */ | |
| 126 | + void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId); | |
| 127 | + | |
| 128 | + | |
| 129 | + /** | |
| 130 | + * 当售后完成后,更新交易订单项的售后状态 | |
| 120 | 131 | * |
| 121 | - * @param id 交易订单项编号 | |
| 122 | - * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常 | |
| 123 | - * @param newAfterSaleStatus 目标售后状态 | |
| 132 | + * @param id 交易订单项编号 | |
| 133 | + * @param refundPrice 退款金额 | |
| 124 | 134 | */ |
| 125 | - default void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) { | |
| 126 | - updateOrderItemAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, null, null); | |
| 127 | - } | |
| 135 | + void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice); | |
| 128 | 136 | |
| 129 | 137 | /** |
| 130 | - * 更新交易订单项的售后状态 | |
| 138 | + * 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态 | |
| 131 | 139 | * |
| 132 | - * @param id 交易订单项编号 | |
| 133 | - * @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常 | |
| 134 | - * @param newAfterSaleStatus 目标售后状态 | |
| 135 | - * @param afterSaleId 售后单编号;当订单项发起售后时,必须传递该字段 | |
| 136 | - * @param refundPrice 退款金额;当订单项退款成功时,必须传递该值 | |
| 140 | + * @param id 交易订单项编号 | |
| 137 | 141 | */ |
| 138 | - void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, | |
| 139 | - Long afterSaleId, Integer refundPrice); | |
| 142 | + void updateOrderItemWhenAfterSaleCancel(@NotNull Long id); | |
| 140 | 143 | |
| 141 | 144 | /** |
| 142 | 145 | * 【会员】创建订单项的评论 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
| ... | ... | @@ -74,10 +74,7 @@ import org.springframework.transaction.annotation.Transactional; |
| 74 | 74 | |
| 75 | 75 | import javax.annotation.Resource; |
| 76 | 76 | import java.time.LocalDateTime; |
| 77 | -import java.util.ArrayList; | |
| 78 | -import java.util.List; | |
| 79 | -import java.util.Objects; | |
| 80 | -import java.util.Set; | |
| 77 | +import java.util.*; | |
| 81 | 78 | |
| 82 | 79 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 83 | 80 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| ... | ... | @@ -363,7 +360,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 363 | 360 | |
| 364 | 361 | /** |
| 365 | 362 | * 校验交易订单满足被支付的条件 |
| 366 | - * | |
| 363 | + * <p> | |
| 367 | 364 | * 1. 交易订单未支付 |
| 368 | 365 | * 2. 支付单已支付 |
| 369 | 366 | * |
| ... | ... | @@ -416,7 +413,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 416 | 413 | |
| 417 | 414 | @Override |
| 418 | 415 | @Transactional(rollbackFor = Exception.class) |
| 419 | - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL) | |
| 416 | + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_DELIVERY) | |
| 420 | 417 | public void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO) { |
| 421 | 418 | // 1.1 校验并获得交易订单(可发货) |
| 422 | 419 | TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId()); |
| ... | ... | @@ -455,7 +452,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 455 | 452 | |
| 456 | 453 | /** |
| 457 | 454 | * 校验交易订单满足被发货的条件 |
| 458 | - * | |
| 455 | + * <p> | |
| 459 | 456 | * 1. 交易订单未发货 |
| 460 | 457 | * |
| 461 | 458 | * @param id 交易订单编号 |
| ... | ... | @@ -521,7 +518,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 521 | 518 | for (TradeOrderDO order : orders) { |
| 522 | 519 | try { |
| 523 | 520 | getSelf().receiveOrderBySystem(order); |
| 524 | - count ++; | |
| 521 | + count++; | |
| 525 | 522 | } catch (Throwable e) { |
| 526 | 523 | log.error("[receiveOrderBySystem][order({}) 自动收货订单异常]", order.getId(), e); |
| 527 | 524 | } |
| ... | ... | @@ -559,7 +556,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 559 | 556 | |
| 560 | 557 | /** |
| 561 | 558 | * 校验交易订单满足可售货的条件 |
| 562 | - * | |
| 559 | + * <p> | |
| 563 | 560 | * 1. 交易订单待收货 |
| 564 | 561 | * |
| 565 | 562 | * @param userId 用户编号 |
| ... | ... | @@ -612,7 +609,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 612 | 609 | for (TradeOrderDO order : orders) { |
| 613 | 610 | try { |
| 614 | 611 | getSelf().cancelOrderBySystem(order); |
| 615 | - count ++; | |
| 612 | + count++; | |
| 616 | 613 | } catch (Throwable e) { |
| 617 | 614 | log.error("[cancelOrderBySystem][order({}) 过期订单异常]", order.getId(), e); |
| 618 | 615 | } |
| ... | ... | @@ -634,7 +631,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 634 | 631 | /** |
| 635 | 632 | * 取消订单的核心实现 |
| 636 | 633 | * |
| 637 | - * @param order 订单 | |
| 634 | + * @param order 订单 | |
| 638 | 635 | * @param cancelType 取消类型 |
| 639 | 636 | */ |
| 640 | 637 | private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) { |
| ... | ... | @@ -754,68 +751,81 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 754 | 751 | |
| 755 | 752 | // =================== Order Item =================== |
| 756 | 753 | |
| 757 | - // TODO 疯狂:帮我重构下: | |
| 758 | - // 1. updateOrderItemAfterSaleStatus 拆分成三个方法:发起;同意;拒绝。原因是,职责更清晰,操作日志也更容易记录; | |
| 759 | - | |
| 760 | 754 | @Override |
| 761 | - @Transactional(rollbackFor = Exception.class) | |
| 762 | - public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, | |
| 763 | - Long afterSaleId, Integer refundPrice) { | |
| 764 | - // 如果退款成功,则 refundPrice 非空 | |
| 765 | - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()) | |
| 766 | - && refundPrice == null) { | |
| 767 | - throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id)); | |
| 768 | - } | |
| 769 | - // 如果退款发起,则 afterSaleId 非空 | |
| 770 | - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus()) | |
| 771 | - && afterSaleId == null) { | |
| 755 | + public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) { | |
| 756 | + if (afterSaleId == null) { | |
| 772 | 757 | throw new IllegalArgumentException(StrUtil.format("id({}) 退款发起,售后单编号不能为空", id)); |
| 773 | 758 | } |
| 774 | 759 | |
| 775 | 760 | // 更新订单项 |
| 776 | - int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId); | |
| 777 | - if (updateCount <= 0) { | |
| 778 | - throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL); | |
| 779 | - } | |
| 761 | + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), | |
| 762 | + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId); | |
| 763 | + } | |
| 780 | 764 | |
| 781 | - // 如果有退款金额,则需要更新订单 | |
| 765 | + @Override | |
| 766 | + @Transactional(rollbackFor = Exception.class) | |
| 767 | + public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) { | |
| 782 | 768 | if (refundPrice == null) { |
| 783 | - return; | |
| 769 | + throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id)); | |
| 784 | 770 | } |
| 785 | - // 计算总的退款金额 | |
| 771 | + | |
| 772 | + // 1. 更新订单项 | |
| 773 | + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), | |
| 774 | + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null); | |
| 775 | + | |
| 776 | + // 2. 计算总的退款金额、退回积分 | |
| 786 | 777 | TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id); |
| 787 | 778 | TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId()); |
| 788 | 779 | Integer orderRefundPrice = order.getRefundPrice() + refundPrice; |
| 789 | - // TODO @疯狂:809 到 817 改成:cancelOrderByAfterSale:相当于全部售后成功后,就是要取消胆子; | |
| 780 | + Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint(); | |
| 790 | 781 | if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单 |
| 791 | - tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) | |
| 792 | - .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice).setRefundPoint(order.getRefundPoint() + orderItem.getUsePoint()) | |
| 793 | - .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now())); | |
| 794 | - | |
| 795 | - // TODO 芋艿:记录订单日志 | |
| 796 | - | |
| 797 | - // TODO 芋艿:要不要退优惠劵 | |
| 798 | - | |
| 782 | + cancelOrderByAfterSale(order, orderRefundPrice, orderRefundPoint); | |
| 799 | 783 | } else { // 如果部分售后,则更新退款金额 |
| 800 | 784 | tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) |
| 801 | - .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice)); | |
| 785 | + .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()) | |
| 786 | + .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint)); | |
| 802 | 787 | } |
| 803 | 788 | |
| 804 | 789 | // TODO 芋艿:这块扣减规则,需要在考虑下 |
| 805 | - // 售后成功后,执行数据回滚逻辑 | |
| 806 | - if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) { | |
| 807 | - // TODO @疯狂:这里库存也要扣减下; | |
| 808 | - // 扣减用户积分(赠送的) | |
| 809 | - reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId); | |
| 810 | - // 增加用户积分(返还抵扣) | |
| 811 | - addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, afterSaleId); | |
| 812 | - // 扣减用户经验 | |
| 813 | - getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId); | |
| 814 | - // 更新分佣记录为已失效 | |
| 815 | - getSelf().cancelBrokerageAsync(order.getUserId(), id); | |
| 790 | + // 3.1 回滚数据:增加 SKU 库存 | |
| 791 | + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem))); | |
| 792 | + // 3.2 回滚数据:扣减用户积分(赠送的) | |
| 793 | + reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId()); | |
| 794 | + // 3.3 回滚数据:增加用户积分(返还抵扣) | |
| 795 | + addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId()); | |
| 796 | + // 3.4 回滚数据:扣减用户经验 | |
| 797 | + getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, orderItem.getAfterSaleId()); | |
| 798 | + // 3.5 回滚数据:更新分佣记录为已失效 | |
| 799 | + getSelf().cancelBrokerageAsync(order.getUserId(), id); | |
| 800 | + } | |
| 801 | + | |
| 802 | + @Override | |
| 803 | + public void updateOrderItemWhenAfterSaleCancel(Long id) { | |
| 804 | + // 更新订单项 | |
| 805 | + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), | |
| 806 | + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null); | |
| 807 | + } | |
| 808 | + | |
| 809 | + private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, | |
| 810 | + Long afterSaleId) { | |
| 811 | + // 更新订单项 | |
| 812 | + int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId); | |
| 813 | + if (updateCount <= 0) { | |
| 814 | + throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL); | |
| 816 | 815 | } |
| 817 | 816 | } |
| 818 | 817 | |
| 818 | + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL) | |
| 819 | + private void cancelOrderByAfterSale(TradeOrderDO order, Integer orderRefundPrice, Integer refundPoint) { | |
| 820 | + // 1. 更新订单 | |
| 821 | + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) | |
| 822 | + .setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()) | |
| 823 | + .setRefundPrice(orderRefundPrice).setRefundPoint(refundPoint) | |
| 824 | + .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now())); | |
| 825 | + // 2. 退还优惠券 | |
| 826 | + couponApi.returnUsedCoupon(order.getCouponId()); | |
| 827 | + } | |
| 828 | + | |
| 819 | 829 | |
| 820 | 830 | /** |
| 821 | 831 | * 判断指定订单的所有订单项,是不是都售后成功 |
| ... | ... | @@ -879,7 +889,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 879 | 889 | for (TradeOrderDO order : orders) { |
| 880 | 890 | try { |
| 881 | 891 | getSelf().createOrderItemCommentBySystemBySystem(order); |
| 882 | - count ++; | |
| 892 | + count++; | |
| 883 | 893 | } catch (Throwable e) { |
| 884 | 894 | log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e); |
| 885 | 895 | } |
| ... | ... | @@ -923,7 +933,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 923 | 933 | /** |
| 924 | 934 | * 创建订单项的评论的核心实现 |
| 925 | 935 | * |
| 926 | - * @param orderItem 订单项 | |
| 936 | + * @param orderItem 订单项 | |
| 927 | 937 | * @param createReqVO 评论内容 |
| 928 | 938 | * @return 评论编号 |
| 929 | 939 | */ |
| ... | ... | @@ -979,14 +989,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { |
| 979 | 989 | |
| 980 | 990 | /** |
| 981 | 991 | * 创建分销记录 |
| 982 | - * | |
| 992 | + * <p> | |
| 983 | 993 | * 目前是支付成功后,就会创建分销记录。 |
| 984 | - * | |
| 994 | + * <p> | |
| 985 | 995 | * 业内还有两种做法,可以根据自己的业务调整: |
| 986 | - * 1. 确认收货后,才创建分销记录 | |
| 987 | - * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻 | |
| 996 | + * 1. 确认收货后,才创建分销记录 | |
| 997 | + * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻 | |
| 988 | 998 | * |
| 989 | - * @param userId 用户编号 | |
| 999 | + * @param userId 用户编号 | |
| 990 | 1000 | * @param orderId 订单编号 |
| 991 | 1001 | */ |
| 992 | 1002 | @Async | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
| ... | ... | @@ -56,7 +56,6 @@ public class TradePointGiveCalculator implements TradePriceCalculator { |
| 56 | 56 | TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i); |
| 57 | 57 | // 商品可能赠送了积分,所以这里要加上 |
| 58 | 58 | orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i)); |
| 59 | - TradePriceCalculatorHelper.recountPayPrice(orderItem); // TODO @疯狂:这个应该不用调用哇?不影响支付金额 | |
| 60 | 59 | } |
| 61 | 60 | // 3.3 更新订单赠送积分 |
| 62 | 61 | TradePriceCalculatorHelper.recountAllGivePoint(result); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.price.calculator; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.BooleanUtil; |
| 4 | -import cn.hutool.core.util.NumberUtil; | |
| 5 | 4 | import cn.hutool.core.util.StrUtil; |
| 6 | 5 | import cn.iocoder.yudao.module.member.api.point.MemberPointApi; |
| 7 | 6 | import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO; |
| ... | ... | @@ -15,10 +14,11 @@ import org.springframework.core.annotation.Order; |
| 15 | 14 | import org.springframework.stereotype.Component; |
| 16 | 15 | |
| 17 | 16 | import javax.annotation.Resource; |
| 18 | -import java.math.RoundingMode; | |
| 19 | 17 | import java.util.List; |
| 20 | 18 | |
| 19 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 21 | 20 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| 21 | +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL; | |
| 22 | 22 | |
| 23 | 23 | // TODO @疯狂:搞个单测,嘿嘿; |
| 24 | 24 | /** |
| ... | ... | @@ -45,7 +45,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { |
| 45 | 45 | } |
| 46 | 46 | // 1.2 校验积分抵扣是否开启 |
| 47 | 47 | MemberPointConfigRespDTO config = memberPointApi.getConfig(); |
| 48 | - if (!checkDeductPointEnable(config)) { | |
| 48 | + if (!isDeductPointEnable(config)) { | |
| 49 | 49 | return; |
| 50 | 50 | } |
| 51 | 51 | // 1.3 校验用户积分余额 |
| ... | ... | @@ -55,7 +55,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | // 2.1 计算积分优惠金额 |
| 58 | - // TODO @疯狂:如果计算出来,优惠金额为 0,那是不是不用执行后续逻辑哈 | |
| 59 | 58 | int pointPrice = calculatePointPrice(config, user.getPoint(), result); |
| 60 | 59 | // 2.2 计算分摊的积分、抵扣金额 |
| 61 | 60 | List<TradePriceCalculateRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSelected); |
| ... | ... | @@ -77,18 +76,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { |
| 77 | 76 | TradePriceCalculatorHelper.recountAllPrice(result); |
| 78 | 77 | } |
| 79 | 78 | |
| 80 | - // TODO @疯狂:这个最好是 is 开头;因为 check 或者 validator,更多失败,会抛出异常; | |
| 81 | - private boolean checkDeductPointEnable(MemberPointConfigRespDTO config) { | |
| 82 | - // TODO @疯狂:这个要不直接写成 return config != null && config .... 多行这样一个形式; | |
| 83 | - if (config == null) { | |
| 84 | - return false; | |
| 85 | - } | |
| 86 | - if (!BooleanUtil.isTrue(config.getTradeDeductEnable())) { | |
| 87 | - return false; | |
| 88 | - } | |
| 89 | - | |
| 90 | - // 有没有配置:1 积分抵扣多少分 | |
| 91 | - return config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; | |
| 79 | + private boolean isDeductPointEnable(MemberPointConfigRespDTO config) { | |
| 80 | + return config != null && | |
| 81 | + !BooleanUtil.isTrue(config.getTradeDeductEnable()) && // 积分功能是否启用 | |
| 82 | + config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分 | |
| 92 | 83 | } |
| 93 | 84 | |
| 94 | 85 | private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) { |
| ... | ... | @@ -98,14 +89,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator { |
| 98 | 89 | } |
| 99 | 90 | // 积分优惠金额(分) |
| 100 | 91 | int pointPrice = usePoint * config.getTradeDeductUnitPrice(); |
| 101 | - // 0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分 | |
| 102 | - if (result.getPrice().getPayPrice() < pointPrice) { | |
| 103 | - pointPrice = result.getPrice().getPayPrice(); | |
| 104 | - // 反推需要扣除的积分 | |
| 105 | - usePoint = NumberUtil.toBigDecimal(pointPrice) | |
| 106 | - .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP) | |
| 107 | - .intValue(); | |
| 92 | + if (result.getPrice().getPayPrice() <= pointPrice) { | |
| 93 | + // 禁止0元购 | |
| 94 | + throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL); | |
| 108 | 95 | } |
| 96 | +// // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分 | |
| 97 | +// if (result.getPrice().getPayPrice() < pointPrice) { | |
| 98 | +// pointPrice = result.getPrice().getPayPrice(); | |
| 99 | +// // 反推需要扣除的积分 | |
| 100 | +// usePoint = NumberUtil.toBigDecimal(pointPrice) | |
| 101 | +// .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP) | |
| 102 | +// .intValue(); | |
| 103 | +// } | |
| 109 | 104 | // 记录使用的积分 |
| 110 | 105 | result.setUsePoint(usePoint); |
| 111 | 106 | return pointPrice; | ... | ... |