浏览代码

Merge branch 'feature/1.8.0-uniapp' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp

LAPTOP-CNV4CMCJ\cheng 2 年之前
父节点
当前提交
fb45f22533
共有 100 个文件被更改,包括 3046 次插入587 次删除
  1. 43 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java
  2. 31 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java
  3. 1 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java
  4. 9 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
  5. 27 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java
  6. 4 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
  7. 3 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
  8. 1 14
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
  9. 11 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
  10. 5 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
  11. 20 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
  12. 5 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
  13. 9 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/PriceCalculateRespDTO.java
  14. 24 4
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  15. 39 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java
  16. 4 13
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponTakeTypeEnum.java
  17. 5 0
      yudao-module-mall/yudao-module-promotion-biz/pom.xml
  18. 75 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java
  19. 9 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponTemplateController.java
  20. 103 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponBaseVO.java
  21. 18 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageItemRespVO.java
  22. 34 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java
  23. 23 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponRespVO.java
  24. 11 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateBaseVO.java
  25. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateCreateReqVO.java
  26. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java
  27. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateRespVO.java
  28. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateUpdateReqVO.java
  29. 24 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateUpdateStatusReqVO.java
  30. 87 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/DiscountActivityController.java
  31. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/package-info.java
  32. 82 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityBaseVO.java
  33. 25 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityCreateReqVO.java
  34. 21 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityDetailRespVO.java
  35. 27 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityPageReqVO.java
  36. 28 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityRespVO.java
  37. 31 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityUpdateReqVO.java
  38. 83 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java
  39. 99 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java
  40. 12 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityCreateReqVO.java
  41. 19 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityPageReqVO.java
  42. 22 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java
  43. 17 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityUpdateReqVO.java
  44. 21 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
  45. 3 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
  46. 102 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/DiscountActivityConvert.java
  47. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/package-info.java
  48. 2 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/price/PriceConvert.java
  49. 29 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java
  50. 40 41
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java
  51. 2 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java
  52. 13 19
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/discount/DiscountProductDO.java
  53. 21 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java
  54. 36 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
  55. 4 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
  56. 30 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountActivityMapper.java
  57. 26 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java
  58. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/package-info.java
  59. 38 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
  60. 17 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
  61. 65 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  62. 19 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java
  63. 19 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
  64. 86 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java
  65. 196 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java
  66. 0 25
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountService.java
  67. 0 29
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountServiceImpl.java
  68. 50 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/bo/DiscountProductDetailBO.java
  69. 50 37
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java
  70. 73 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java
  71. 169 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
  72. 0 23
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardService.java
  73. 0 26
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardServiceImpl.java
  74. 32 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/util/PromotionUtils.java
  75. 11 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/resources/mapper/coupon/CouponTemplateMapper.xml
  76. 29 18
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java
  77. 209 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java
  78. 41 36
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceTest.java
  79. 218 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java
  80. 5 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/clean.sql
  81. 106 1
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql
  82. 3 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  83. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
  84. 25 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
  85. 34 71
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java
  86. 6 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java
  87. 6 8
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  88. 32 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java
  89. 0 52
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/UserInfoDTO.java
  90. 12 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java
  91. 0 39
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/UserController.java
  92. 5 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/UserConvert.java
  93. 8 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  94. 19 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  95. 12 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  96. 3 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleUpdateStatusReqVO.java
  97. 1 0
      yudao-server/src/main/resources/application-local.yaml
  98. 0 54
      yudao-ui-admin/src/api/mall/CouponTemplete/CouponTemplete.js
  99. 9 0
      yudao-ui-admin/src/api/mall/product/sku.js
  100. 13 5
      yudao-ui-admin/src/api/mall/product/spu.js

+ 43 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java

@@ -1,14 +1,57 @@
 package cn.iocoder.yudao.module.product.controller.admin.sku;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
 @Api(tags = "管理后台 - 商品 sku")
 @RestController
 @RequestMapping("/product/sku")
 @Validated
 public class ProductSkuController {
 
+    @Resource
+    private ProductSkuService productSkuService;
+    @Resource
+    private ProductSpuService productSpuService;
+
+    @GetMapping("/get-option-list")
+    @ApiOperation("获得商品 SKU 选项的列表")
+//    @PreAuthorize("@ss.hasPermission('product:sku:query')")
+    public CommonResult<List<ProductSkuOptionRespVO>> getSkuOptionList() {
+        // 获得 SKU 列表
+        List<ProductSkuDO> skus = productSkuService.getSkuList();
+        if (CollUtil.isEmpty(skus)) {
+            return success(Collections.emptyList());
+        }
+
+        // 获得对应的 SPU 映射
+        Map<Long, ProductSpuDO> spuMap = productSpuService.getSpuMap(convertSet(skus, ProductSkuDO::getSpuId));
+        // 转换为返回结果
+        List<ProductSkuOptionRespVO> skuVOs = ProductSkuConvert.INSTANCE.convertList05(skus);
+        skuVOs.forEach(sku -> MapUtils.findAndThen(spuMap, sku.getSpuId(),
+                spu -> sku.setSpuId(spu.getId()).setSpuName(spu.getName())));
+        return success(skuVOs);
+    }
+
 }

+ 31 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel(value = "管理后台 - 商品 SKU 选项 Response VO", description = "用于前端 SELECT 选项")
+@Data
+public class ProductSkuOptionRespVO {
+
+    @ApiModelProperty(value = "主键", required = true, example = "1024")
+    private Long id;
+
+    @ApiModelProperty(value = "商品 SKU 名字", example = "红色")
+    private String name;
+
+    @ApiModelProperty(value = "销售价格", required = true, example = "100", notes = "单位:分")
+    private String price;
+
+    @ApiModelProperty(value = "库存", required = true, example = "100")
+    private Integer stock;
+
+    // ========== 商品 SPU 信息 ==========
+
+    @ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
+    private Long spuId;
+
+    @ApiModelProperty(value = "商品 SPU 名字", required = true, example = "iPhone 11")
+    private String spuName;
+
+}

+ 1 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java

@@ -9,7 +9,7 @@ import lombok.ToString;
 import java.util.Date;
 import java.util.List;
 
-@ApiModel("管理后台 - 商品sku Response VO")
+@ApiModel("管理后台 - 商品 SKU Response VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)

+ 9 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java

@@ -53,6 +53,7 @@ public class ProductSpuController {
         return success(true);
     }
 
+    // TODO 芋艿:修改接口
     @GetMapping("/get/detail")
     @ApiOperation("获得商品 SPU")
     @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@@ -79,6 +80,14 @@ public class ProductSpuController {
         return success(ProductSpuConvert.INSTANCE.convertList(list));
     }
 
+    @GetMapping("/get-simple-list")
+    @ApiOperation("获得商品 SPU 精简列表")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<List<ProductSpuSimpleRespVO>> getSpuSimpleList() {
+        List<ProductSpuDO> list = spuService.getSpuList();
+        return success(ProductSpuConvert.INSTANCE.convertList02(list));
+    }
+
     @GetMapping("/page")
     @ApiOperation("获得商品 SPU 分页")
     @PreAuthorize("@ss.hasPermission('product:spu:query')")

+ 27 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 商品 SPU 精简 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuSimpleRespVO extends ProductSpuBaseVO {
+
+    @ApiModelProperty(value = "主键", required = true, example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "商品名称", required = true, example = "芋道")
+    private String name;
+
+    @ApiModelProperty(value = " 最小价格,单位使用:分", required = true, example = "1024")
+    private Integer minPrice;
+
+    @ApiModelProperty(value = "最大价格,单位使用:分", required = true, example = "1024")
+    private Integer maxPrice;
+
+}

+ 4 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.convert.sku;
 
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuDetailRespVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
@@ -11,7 +12,7 @@ import org.mapstruct.factory.Mappers;
 import java.util.List;
 
 /**
- * 商品sku Convert
+ * 商品 SKU Convert
  *
  * @author 芋道源码
  */
@@ -36,4 +37,6 @@ public interface ProductSkuConvert {
 
     List<ProductSkuRespDTO> convertList04(List<ProductSkuDO> list);
 
+    List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus);
+
 }

+ 3 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java

@@ -36,5 +36,7 @@ public interface ProductSpuConvert {
     AppSpuPageRespVO convertAppResp(ProductSpuDO list);
 
     List<SpuInfoRespDTO> convertList2(List<ProductSpuDO> list);
-    
+
+    List<ProductSpuSimpleRespVO> convertList02(List<ProductSpuDO> list);
+
 }

+ 1 - 14
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java

@@ -97,6 +97,7 @@ public class ProductSkuDO extends BaseDO {
      * 商品属性
      */
     @Data
+    @NoArgsConstructor
     @AllArgsConstructor
     public static class Property {
 
@@ -130,19 +131,5 @@ public class ProductSkuDO extends BaseDO {
 
     }
 
-    // TODO ========== 待定字段:yv =========
-    // TODO brokerage:一级返佣
-    // TODO brokerage_two:二级返佣
-    // TODO pink_price:拼团价
-    // TODO pink_stock:拼团库存
-    // TODO seckill_price:秒杀价
-    // TODO seckill_stock:秒杀库存
-    // TODO integral:需要积分
-
-    // TODO ========== 待定字段:cf =========
-    // TODO type 活动显示排序 0=默认 1=秒 2=砍价 3=拼团
-    // TODO quota 活动限购数量
-    // TODO quota_show 活动限购数量显示
-
 }
 

+ 11 - 4
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java

@@ -14,22 +14,29 @@ import java.util.List;
 public interface ProductSkuService {
 
     /**
-     * 删除商品sku
+     * 删除商品 SKU
      *
      * @param id 编号
      */
     void deleteSku(Long id);
 
     /**
-     * 获得商品sku
+     * 获得商品 SKU 信息
      *
      * @param id 编号
-     * @return 商品sku
+     * @return 商品 SKU 信息
      */
     ProductSkuDO getSku(Long id);
 
     /**
-     * 获得商品sku列表
+     * 获得商品 SKU 列表
+     *
+     * @return 商品sku列表
+     */
+    List<ProductSkuDO> getSkuList();
+
+    /**
+     * 获得商品 SKU 列表
      *
      * @param ids 编号
      * @return 商品sku列表

+ 5 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java

@@ -61,6 +61,11 @@ public class ProductSkuServiceImpl implements ProductSkuService {
         return productSkuMapper.selectById(id);
     }
 
+    @Override
+    public List<ProductSkuDO> getSkuList() {
+        return productSkuMapper.selectList();
+    }
+
     @Override
     public List<ProductSkuDO> getSkuList(Collection<Long> ids) {
         return productSkuMapper.selectBatchIds(ids);

+ 20 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java

@@ -9,6 +9,9 @@ import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商品 SPU Service 接口
@@ -63,6 +66,23 @@ public interface ProductSpuService {
      */
     List<ProductSpuDO> getSpuList(Collection<Long> ids);
 
+    /**
+     * 获得商品 SPU 映射
+     *
+     * @param ids 编号数组
+     * @return 商品 SPU 映射
+     */
+    default Map<Long, ProductSpuDO> getSpuMap(Collection<Long> ids) {
+        return convertMap(getSpuList(ids), ProductSpuDO::getId);
+    }
+
+    /**
+     * 获得所有商品 SPU 列表
+     *
+     * @return 商品 SPU 列表
+     */
+    List<ProductSpuDO> getSpuList();
+
     /**
      * 获得商品 SPU 分页
      *

+ 5 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -176,6 +176,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         return productSpuMapper.selectBatchIds(ids);
     }
 
+    @Override
+    public List<ProductSpuDO> getSpuList() {
+        return productSpuMapper.selectList();
+    }
+
     @Override
     public PageResult<ProductSpuRespVO> getSpuPage(ProductSpuPageReqVO pageReqVO) {
         // 库存告警的 SPU 编号的集合

+ 9 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/PriceCalculateRespDTO.java

@@ -48,9 +48,17 @@ public class PriceCalculateRespDTO {
          * 商品原价(总),单位:分
          *
          * 基于 {@link OrderItem#getOriginalPrice()} 求和
+         *
          * 对应 taobao 的 trade.total_fee 字段
          */
         private Integer originalPrice;
+        /**
+         * 订单原价(总),单位:分
+         *
+         * 基于 {@link OrderItem#getPayPrice()} 求和
+         * 和 {@link #originalPrice} 的差异:去除商品级优惠
+         */
+        private Integer orderPrice;
         /**
          * 订单优惠(总),单位:分
          *
@@ -82,6 +90,7 @@ public class PriceCalculateRespDTO {
          * - {@link #couponPrice}
          * - {@link #pointPrice}
          * + {@link #deliveryPrice}
+         * - {@link #discountPrice}
          */
         private Integer payPrice;
         /**

+ 24 - 4
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -3,14 +3,19 @@ package cn.iocoder.yudao.module.promotion.enums;
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 
 /**
- * market 错误码枚举类
- * <p>
+ * promotion 错误码枚举类
+ *
  * market 系统,使用 1-003-000-000 段
  */
 public interface ErrorCodeConstants {
 
-    // ========== 促销活动相关 1003001000 ============ TODO 芋艿:看看是不是要删除掉
-    ErrorCode ACTIVITY_NOT_EXISTS = new ErrorCode(1003001000, "促销活动不存在");
+    // ========== 促销活动相关 1003001000 ============
+    ErrorCode DISCOUNT_ACTIVITY_NOT_EXISTS = new ErrorCode(1003001000, "限时折扣活动不存在");
+    ErrorCode DISCOUNT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1003006001, "存在商品参加了其它限时折扣活动");
+    ErrorCode DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1003006002, "限时折扣活动已关闭,不能修改");
+    ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1003006003, "限时折扣活动未关闭,不能删除");
+    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1003006004, "限时折扣活动已关闭,不能重复关闭");
+    ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1003006004, "限时折扣活动已结束,不能关闭");
 
     // ========== Banner 相关 1003002000 ============
     ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1003002000, "Banner 不存在");
@@ -23,4 +28,19 @@ public interface ErrorCodeConstants {
     ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1003004000, "优惠劵模板不存在");
     ErrorCode COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL = new ErrorCode(1003004001, "发放数量不能小于已领取数量({})");
 
+    // ========== 优惠劵模板 1003005000 ==========
+    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1003005000, "优惠劵不存在");
+    ErrorCode COUPON_DELETE_FAIL_USED = new ErrorCode(1003005001, "回收优惠劵失败,优惠劵已被使用");
+
+    // ========== 满减送活动 1003006000 ==========
+    ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1003006000, "满减送活动不存在");
+    ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1003006001, "存在商品参加了其它满减送活动");
+    ErrorCode REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1003006002, "满减送活动已关闭,不能修改");
+    ErrorCode REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1003006003, "满减送活动未关闭,不能删除");
+    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1003006004, "满减送活动已关闭,不能重复关闭");
+    ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1003006004, "满减送活动已结束,不能关闭");
+
+    // ========== Price 相关 1003007000 ============
+    ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1003007000, "支付价格计算异常,原因:价格小于等于 0");
+
 }

+ 39 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponStatusEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.promotion.enums.coupon;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 优惠劵状态枚举
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum CouponStatusEnum implements IntArrayValuable {
+
+    UNUSED(1, "未使用"),
+    USED(2, "已使用"),
+    EXPIRE(3, "已过期"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CouponStatusEnum::getStatus).toArray();
+
+    /**
+     * 值
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 4 - 13
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/coupon/CouponTakeTypeEnum.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.enums.coupon;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
 
 import java.util.Arrays;
 
@@ -9,6 +11,8 @@ import java.util.Arrays;
  *
  * @author 芋道源码
  */
+@AllArgsConstructor
+@Getter
 public enum CouponTakeTypeEnum implements IntArrayValuable {
 
     BY_USER(1, "直接领取"), // 用户可在首页、每日领劵直接领取
@@ -26,19 +30,6 @@ public enum CouponTakeTypeEnum implements IntArrayValuable {
      */
     private final String name;
 
-    CouponTakeTypeEnum(Integer value, String name) {
-        this.value = value;
-        this.name = name;
-    }
-
-    public Integer getValue() {
-        return value;
-    }
-
-    public String getName() {
-        return name;
-    }
-
     @Override
     public int[] array() {
         return ARRAYS;

+ 5 - 0
yudao-module-mall/yudao-module-promotion-biz/pom.xml

@@ -29,6 +29,11 @@
             <artifactId>yudao-module-product-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-member-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 业务组件 -->
         <dependency>

+ 75 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Api(tags = "管理后台 - 优惠劵")
+@RestController
+@RequestMapping("/promotion/coupon")
+@Validated
+public class CouponController {
+
+    @Resource
+    private CouponService couponService;
+    @Resource
+    private MemberUserApi memberUserApi;
+
+//    @GetMapping("/get")
+//    @ApiOperation("获得优惠劵")
+//    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+//    @PreAuthorize("@ss.hasPermission('promotion:coupon:query')")
+//    public CommonResult<CouponRespVO> getCoupon(@RequestParam("id") Long id) {
+//        CouponDO coupon = couponService.getCoupon(id);
+//        return success(CouponConvert.INSTANCE.convert(coupon));
+//    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("回收优惠劵")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:coupon:delete')")
+    public CommonResult<Boolean> deleteCoupon(@RequestParam("id") Long id) {
+        couponService.deleteCoupon(id);
+        return success(true);
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得优惠劵分页")
+    @PreAuthorize("@ss.hasPermission('promotion:coupon:query')")
+    public CommonResult<PageResult<CouponPageItemRespVO>> getCouponPage(@Valid CouponPageReqVO pageVO) {
+        PageResult<CouponDO> pageResult = couponService.getCouponPage(pageVO);
+        PageResult<CouponPageItemRespVO> pageResulVO = CouponConvert.INSTANCE.convertPage(pageResult);
+        if (CollUtil.isEmpty(pageResulVO.getList())) {
+            return success(pageResulVO);
+        }
+        // 读取用户信息,进行拼接
+        Set<Long> userIds = convertSet(pageResult.getList(), CouponDO::getUserId);
+        Map<Long, UserRespDTO> userMap = memberUserApi.getUserMap(userIds);
+        pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
+                userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
+        return success(pageResulVO);
+    }
+
+}

+ 9 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponTemplateController.java

@@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplatePageReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateRespVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.*;
 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
@@ -45,6 +42,14 @@ public class CouponTemplateController {
         return success(true);
     }
 
+    @PutMapping("/update-status")
+    @ApiOperation("更新优惠劵模板状态")
+    @PreAuthorize("@ss.hasPermission('promotion:coupon-template:update')")
+    public CommonResult<Boolean> updateCouponTemplateStatus(@Valid @RequestBody CouponTemplateUpdateStatusReqVO reqVO) {
+        couponTemplateService.updateCouponTemplateStatus(reqVO.getId(), reqVO.getStatus());
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @ApiOperation("删除优惠劵模板")
     @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)

+ 103 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponBaseVO.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
+
+/**
+* 优惠劵 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class CouponBaseVO {
+
+    // ========== 基本信息 BEGIN ==========
+    @ApiModelProperty(value = "优惠劵模板编号", required = true, example = "1024")
+    @NotNull(message = "优惠劵模板编号不能为空")
+    private Integer templateId;
+
+    @ApiModelProperty(value = "优惠劵名", required = true, example = "春节送送送")
+    @NotNull(message = "优惠劵名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "优惠码状态", required = true, example = "1", notes = "参见 CouponStatusEnum 枚举")
+    private Integer status;
+
+    // ========== 基本信息 END ==========
+
+    // ========== 领取情况 BEGIN ==========
+    @ApiModelProperty(value = "用户编号", required = true, example = "1")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @ApiModelProperty(value = "领取方式", required = true, example = "1", notes = "参见 CouponTakeTypeEnum 枚举类")
+    @NotNull(message = "领取方式不能为空")
+    private Integer takeType;
+    // ========== 领取情况 END ==========
+
+    // ========== 使用规则 BEGIN ==========
+    @ApiModelProperty(value = "是否设置满多少金额可用", required = true, example = "100", notes = "单位:分;0 - 不限制")
+    @NotNull(message = "是否设置满多少金额可用不能为空")
+    private Integer usePrice;
+
+    @ApiModelProperty(value = "固定日期 - 生效开始时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+    private Date validStartTime;
+
+    @ApiModelProperty(value = "固定日期 - 生效结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+    private Date validEndTime;
+
+    @ApiModelProperty(value = "商品范围", required = true, example = "1", notes = "参见 PromotionProductScopeEnum 枚举类")
+    @NotNull(message = "商品范围不能为空")
+    @InEnum(PromotionProductScopeEnum.class)
+    private Integer productScope;
+
+    @ApiModelProperty(value = "商品 SPU 编号的数组", example = "1,3")
+    private List<Long> productSpuIds;
+    // ========== 使用规则 END ==========
+
+    // ========== 使用效果 BEGIN ==========
+    @ApiModelProperty(value = "优惠类型", required = true, example = "1", notes = "参见 PromotionDiscountTypeEnum 枚举")
+    @NotNull(message = "优惠类型不能为空")
+    @InEnum(PromotionDiscountTypeEnum.class)
+    private Integer discountType;
+
+    @ApiModelProperty(value = "折扣百分比", example = "80", notes = "例如说,80% 为 80")
+    private Integer discountPercent;
+
+    @ApiModelProperty(value = "优惠金额", example = "10", notes = "单位:分")
+    @Min(value = 0, message = "优惠金额需要大于等于 0")
+    private Integer discountPrice;
+
+    @ApiModelProperty(value = "折扣上限", example = "100", notes = "单位:分,仅在 discountType 为 PERCENT 使用")
+    private Integer discountLimitPrice;
+    // ========== 使用效果 END ==========
+
+    // ========== 使用情况 BEGIN ==========
+
+    @ApiModelProperty(value = "使用订单号", example = "4096")
+    private Long useOrderId;
+
+    @ApiModelProperty(value = "使用时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+    private Date useTime;
+
+    // ========== 使用情况 END ==========
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageItemRespVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 优惠劵分页的每一项 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponPageItemRespVO extends CouponRespVO {
+
+    @ApiModelProperty(value = "用户昵称", example = "老芋艿")
+    private String nickname;
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponPageReqVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 优惠劵分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "优惠劵模板编号", example = "2048")
+    private Long templateId;
+
+    @ApiModelProperty(value = "优惠码状态", example = "1", notes = "参见 CouponStatusEnum 枚举")
+    private Integer status;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+    @ApiModelProperty(value = "用户昵称", example = "芋艿", notes = "模糊匹配")
+    private String nickname;
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/coupon/CouponRespVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 优惠劵 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CouponRespVO extends CouponBaseVO {
+
+    @ApiModelProperty(value = "优惠劵编号", required = true, example = "1024")
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 11 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/CouponTemplateBaseVO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateBaseVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo;
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -19,6 +20,7 @@ import java.util.List;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
 /**
 * 优惠劵模板 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -62,10 +64,12 @@ public class CouponTemplateBaseVO {
 
     @ApiModelProperty(value = "固定日期 - 生效开始时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
     private Date validStartTime;
 
     @ApiModelProperty(value = "固定日期 - 生效结束时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
     private Date validEndTime;
 
     @ApiModelProperty(value = "领取日期 - 开始天数")
@@ -82,15 +86,14 @@ public class CouponTemplateBaseVO {
     private Integer discountType;
 
     @ApiModelProperty(value = "折扣百分比", example = "80", notes = "例如说,80% 为 80")
-    @Min(value = 1)
     private Integer discountPercent;
 
     @ApiModelProperty(value = "优惠金额", example = "10", notes = "单位:分")
-    @Min(value = 1)
+    @Min(value = 0, message = "优惠金额需要大于等于 0")
     private Integer discountPrice;
 
     @ApiModelProperty(value = "折扣上限", example = "100", notes = "单位:分,仅在 discountType 为 PERCENT 使用")
-    private Integer discountPriceLimit;
+    private Integer discountLimitPrice;
 
     @AssertTrue(message = "商品 SPU 编号的数组不能为空")
     @JsonIgnore
@@ -127,11 +130,11 @@ public class CouponTemplateBaseVO {
                 || fixedEndTerm != null;
     }
 
-    @AssertTrue(message = "折扣百分比不能为空")
+    @AssertTrue(message = "折扣百分比需要大于等于 1,小于等于 99")
     @JsonIgnore
     public boolean isDiscountPercentValid() {
         return ObjectUtil.notEqual(discountType, PromotionDiscountTypeEnum.PERCENT.getType())
-                || discountPercent != null;
+                || (discountPercent != null && discountPercent >= 1 && discountPercent<= 99);
     }
 
     @AssertTrue(message = "优惠金额不能为空")
@@ -143,9 +146,9 @@ public class CouponTemplateBaseVO {
 
     @AssertTrue(message = "折扣上限不能为空")
     @JsonIgnore
-    public boolean isDiscountPriceLimit() {
+    public boolean isDiscountLimitPriceValid() {
         return ObjectUtil.notEqual(discountType, PromotionDiscountTypeEnum.PERCENT.getType())
-                || discountPriceLimit != null;
+                || discountLimitPrice != null;
     }
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/CouponTemplateCreateReqVO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateCreateReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo;
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import io.swagger.annotations.ApiModel;
 import lombok.Data;

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/CouponTemplatePageReqVO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplatePageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo;
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/CouponTemplateRespVO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo;
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/CouponTemplateUpdateReqVO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateUpdateReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo;
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 24 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/vo/template/CouponTemplateUpdateStatusReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 优惠劵模板更新状态 Request VO")
+@Data
+public class CouponTemplateUpdateStatusReqVO {
+
+    @ApiModelProperty(value = "优惠劵模板编号", required = true, example = "1024")
+    @NotNull(message = "优惠劵模板编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 CommonStatusEnum 枚举")
+    @NotNull(message = "状态不能为空")
+    @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
+    private Integer status;
+
+}

+ 87 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/DiscountActivityController.java

@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.*;
+import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 限时折扣活动")
+@RestController
+@RequestMapping("/promotion/discount-activity")
+@Validated
+public class DiscountActivityController {
+
+    @Resource
+    private DiscountActivityService discountActivityService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建限时折扣活动")
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:create')")
+    public CommonResult<Long> createDiscountActivity(@Valid @RequestBody DiscountActivityCreateReqVO createReqVO) {
+        return success(discountActivityService.createDiscountActivity(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新限时折扣活动")
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:update')")
+    public CommonResult<Boolean> updateDiscountActivity(@Valid @RequestBody DiscountActivityUpdateReqVO updateReqVO) {
+        discountActivityService.updateDiscountActivity(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/close")
+    @ApiOperation("关闭限时折扣活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:close')")
+    public CommonResult<Boolean> closeRewardActivity(@RequestParam("id") Long id) {
+        discountActivityService.closeRewardActivity(id);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除限时折扣活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:delete')")
+    public CommonResult<Boolean> deleteDiscountActivity(@RequestParam("id") Long id) {
+        discountActivityService.deleteDiscountActivity(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得限时折扣活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:query')")
+    public CommonResult<DiscountActivityDetailRespVO> getDiscountActivity(@RequestParam("id") Long id) {
+        DiscountActivityDO discountActivity = discountActivityService.getDiscountActivity(id);
+        if (discountActivity == null) {
+            return success(null);
+        }
+        // 拼接结果
+        List<DiscountProductDO> discountProducts = discountActivityService.getDiscountProductsByActivityId(id);
+        return success(DiscountActivityConvert.INSTANCE.convert(discountActivity, discountProducts));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得限时折扣活动分页")
+    @PreAuthorize("@ss.hasPermission('promotion:discount-activity:query')")
+    public CommonResult<PageResult<DiscountActivityRespVO>> getDiscountActivityPage(@Valid DiscountActivityPageReqVO pageVO) {
+        PageResult<DiscountActivityDO> pageResult = discountActivityService.getDiscountActivityPage(pageVO);
+        return success(DiscountActivityConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 0 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/package-info.java

@@ -1,4 +0,0 @@
-/**
- * TODO 占位
- */
-package cn.iocoder.yudao.module.promotion.controller.admin.discount;

+ 82 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityBaseVO.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 限时折扣活动 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class DiscountActivityBaseVO {
+
+    @ApiModelProperty(value = "活动标题", required = true, example = "一个标题")
+    @NotNull(message = "活动标题不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "开始时间", required = true)
+    @NotNull(message = "开始时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date startTime;
+
+    @ApiModelProperty(value = "结束时间", required = true)
+    @NotNull(message = "结束时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date endTime;
+
+    @ApiModelProperty(value = "备注", example = "我是备注")
+    private String remark;
+
+    @ApiModel("商品")
+    @Data
+    public static class Product {
+
+        @ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
+        @NotNull(message = "商品 SPU 编号不能为空")
+        private Long spuId;
+
+        @ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1")
+        @NotNull(message = "商品 SKU 编号不能为空")
+        private Long skuId;
+
+        @ApiModelProperty(value = "优惠类型", required = true, example = "1", notes = "参见 PromotionDiscountTypeEnum 枚举")
+        @NotNull(message = "优惠类型不能为空")
+        @InEnum(PromotionDiscountTypeEnum.class)
+        private Integer discountType;
+
+        @ApiModelProperty(value = "折扣百分比", example = "80", notes = "例如说,80% 为 80")
+        private Integer discountPercent;
+
+        @ApiModelProperty(value = "优惠金额", example = "10", notes = "单位:分")
+        @Min(value = 0, message = "优惠金额需要大于等于 0")
+        private Integer discountPrice;
+
+        @AssertTrue(message = "折扣百分比需要大于等于 1,小于等于 99")
+        @JsonIgnore
+        public boolean isDiscountPercentValid() {
+            return ObjectUtil.notEqual(discountType, PromotionDiscountTypeEnum.PERCENT.getType())
+                    || (discountPercent != null && discountPercent >= 1 && discountPercent<= 99);
+        }
+
+        @AssertTrue(message = "优惠金额不能为空")
+        @JsonIgnore
+        public boolean isDiscountPriceValid() {
+            return ObjectUtil.notEqual(discountType, PromotionDiscountTypeEnum.PRICE.getType())
+                    || discountPrice != null;
+        }
+
+    }
+}

+ 25 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityCreateReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+@ApiModel("管理后台 - 限时折扣活动创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityCreateReqVO extends DiscountActivityBaseVO {
+
+    /**
+     * 商品列表
+     */
+    @NotEmpty(message = "商品列表不能为空")
+    @Valid
+    private List<Product> products;
+
+}

+ 21 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityDetailRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+@ApiModel("管理后台 - 限时折扣活动的详细 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityDetailRespVO extends DiscountActivityRespVO {
+
+    /**
+     * 商品列表
+     */
+    private List<Product> products;
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityPageReqVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 限时折扣活动分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "活动标题", example = "一个标题")
+    private String name;
+
+    @ApiModelProperty(value = "活动状态", example = "1")
+    private Integer status;
+
+    @ApiModelProperty(value = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date[] createTime;
+
+}

+ 28 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+@ApiModel("管理后台 - 限时折扣活动 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityRespVO extends DiscountActivityBaseVO {
+
+    @ApiModelProperty(value = "活动编号", required = true, example = "1024")
+    private Long id;
+
+    @ApiModelProperty(value = "活动状态", required = true, example = "1")
+    @NotNull(message = "活动状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 31 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityUpdateReqVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("管理后台 - 限时折扣活动更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityUpdateReqVO extends DiscountActivityBaseVO {
+
+    @ApiModelProperty(value = "活动编号", required = true, example = "1024")
+    @NotNull(message = "活动编号不能为空")
+    private Long id;
+
+    /**
+     * 商品列表
+     */
+    @NotEmpty(message = "商品列表不能为空")
+    @Valid
+    private List<DiscountActivityCreateReqVO.Product> products;
+
+}

+ 83 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/RewardActivityController.java

@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 满减送活动")
+@RestController
+@RequestMapping("/promotion/reward-activity")
+@Validated
+public class RewardActivityController {
+
+    @Resource
+    private RewardActivityService rewardActivityService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建满减送活动")
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:create')")
+    public CommonResult<Long> createRewardActivity(@Valid @RequestBody RewardActivityCreateReqVO createReqVO) {
+        return success(rewardActivityService.createRewardActivity(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新满减送活动")
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:update')")
+    public CommonResult<Boolean> updateRewardActivity(@Valid @RequestBody RewardActivityUpdateReqVO updateReqVO) {
+        rewardActivityService.updateRewardActivity(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/close")
+    @ApiOperation("关闭满减送活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:close')")
+    public CommonResult<Boolean> closeRewardActivity(@RequestParam("id") Long id) {
+        rewardActivityService.closeRewardActivity(id);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除满减送活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:delete')")
+    public CommonResult<Boolean> deleteRewardActivity(@RequestParam("id") Long id) {
+        rewardActivityService.deleteRewardActivity(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得满减送活动")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
+    public CommonResult<RewardActivityRespVO> getRewardActivity(@RequestParam("id") Long id) {
+        RewardActivityDO rewardActivity = rewardActivityService.getRewardActivity(id);
+        return success(RewardActivityConvert.INSTANCE.convert(rewardActivity));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得满减送活动分页")
+    @PreAuthorize("@ss.hasPermission('promotion:reward-activity:query')")
+    public CommonResult<PageResult<RewardActivityRespVO>> getRewardActivityPage(@Valid RewardActivityPageReqVO pageVO) {
+        PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(pageVO);
+        return success(RewardActivityConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 99 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityBaseVO.java

@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.Valid;
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.Future;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 满减送活动 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class RewardActivityBaseVO {
+
+    @ApiModelProperty(value = "活动标题", required = true, example = "满啦满啦")
+    @NotNull(message = "活动标题不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "开始时间", required = true)
+    @NotNull(message = "开始时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date startTime;
+
+    @ApiModelProperty(value = "结束时间", required = true)
+    @NotNull(message = "结束时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Future(message = "结束时间必须大于当前时间")
+    private Date endTime;
+
+    @ApiModelProperty(value = "备注", example = "biubiubiu")
+    private String remark;
+
+    @ApiModelProperty(value = "条件类型", required = true, example = "1")
+    @NotNull(message = "条件类型不能为空")
+    @InEnum(value = PromotionConditionTypeEnum.class, message = "条件类型必须是 {value}")
+    private Integer conditionType;
+
+    @ApiModelProperty(value = "商品范围", required = true, example = "1")
+    @NotNull(message = "商品范围不能为空")
+    @InEnum(value = PromotionConditionTypeEnum.class, message = "商品范围必须是 {value}")
+    private Integer productScope;
+
+    @ApiModelProperty(value = "商品 SPU 编号的数组", example = "1,2,3")
+    private List<Long> productSpuIds;
+
+    /**
+     * 优惠规则的数组
+     */
+    @Valid // 校验下子对象
+    private List<Rule> rules;
+
+    @ApiModel("优惠规则")
+    @Data
+    public static class Rule {
+
+        @ApiModelProperty(value = "优惠门槛", required = true, example = "100", notes = "1. 满 N 元,单位:分; 2. 满 N 件")
+        @Min(value = 1L, message = "优惠门槛必须大于等于 1")
+        private Integer limit;
+
+        @ApiModelProperty(value = "优惠价格", required = true, example = "100", notes = "单位:分")
+        @Min(value = 1L, message = "优惠价格必须大于等于 1")
+        private Integer discountPrice;
+
+        @ApiModelProperty(value = "是否包邮", required = true, example = "true")
+        private Boolean freeDelivery;
+
+        @ApiModelProperty(value = "赠送的积分", required = true, example = "100")
+        @Min(value = 1L, message = "赠送的积分必须大于等于 1")
+        private Integer point;
+
+        @ApiModelProperty(value = "赠送的优惠劵编号的数组", example = "1,2,3")
+        private List<Long> couponIds;
+
+        @ApiModelProperty(value = "赠送的优惠卷数量的数组", example = "1,2,3")
+        private List<Integer> couponCounts;
+
+        @AssertTrue(message = "优惠劵和数量必须一一对应")
+        @JsonIgnore
+        public boolean isCouponCountsValid() {
+            return CollUtil.size(couponCounts) == CollUtil.size(couponCounts);
+        }
+
+    }
+
+}

+ 12 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityCreateReqVO.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 满减送活动创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class RewardActivityCreateReqVO extends RewardActivityBaseVO {
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityPageReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@ApiModel("管理后台 - 满减送活动分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class RewardActivityPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "活动标题", example = "满啦满啦")
+    private String name;
+
+    @ApiModelProperty(value = "活动状态", example = "1")
+    private Integer status;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 满减送活动 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class RewardActivityRespVO extends RewardActivityBaseVO {
+
+    @ApiModelProperty(value = "活动编号", required = true, example = "1024")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动状态", required = true, example = "1")
+    private Integer status;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityUpdateReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.reward.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 满减送活动更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class RewardActivityUpdateReqVO extends RewardActivityBaseVO {
+
+    @ApiModelProperty(value = "活动编号", required = true, example = "1024")
+    @NotNull(message = "活动编号不能为空")
+    private Long id;
+
+}

+ 21 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.promotion.convert.coupon;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 优惠劵 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface CouponConvert {
+
+    CouponConvert INSTANCE = Mappers.getMapper(CouponConvert.class);
+
+    PageResult<CouponPageItemRespVO> convertPage(PageResult<CouponDO> page);
+
+}

+ 3 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.promotion.convert.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateRespVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;

+ 102 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/DiscountActivityConvert.java

@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.promotion.convert.discount;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.*;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
+import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 限时折扣活动 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface DiscountActivityConvert {
+
+    DiscountActivityConvert INSTANCE = Mappers.getMapper(DiscountActivityConvert.class);
+
+    DiscountActivityDO convert(DiscountActivityCreateReqVO bean);
+
+    DiscountActivityDO convert(DiscountActivityUpdateReqVO bean);
+
+    DiscountActivityRespVO convert(DiscountActivityDO bean);
+
+    List<DiscountActivityRespVO> convertList(List<DiscountActivityDO> list);
+
+    PageResult<DiscountActivityRespVO> convertPage(PageResult<DiscountActivityDO> page);
+
+    DiscountProductDetailBO convert(DiscountProductDO product);
+
+    default List<DiscountProductDetailBO> convertList(List<DiscountProductDO> products, Map<Long, DiscountActivityDO> activityMap) {
+        return CollectionUtils.convertList(products, product -> {
+            DiscountProductDetailBO detail = convert(product);
+            MapUtils.findAndThen(activityMap, product.getActivityId(), activity -> {
+                detail.setActivityName(activity.getName());
+            });
+            return detail;
+        });
+    }
+
+    DiscountProductDO convert(DiscountActivityBaseVO.Product bean);
+
+    DiscountActivityDetailRespVO convert(DiscountActivityDO activity, List<DiscountProductDO> products);
+
+    // =========== 比较是否相等 ==========
+    /**
+     * 比较两个限时折扣商品是否相等
+     *
+     * @param productDO 数据库中的商品
+     * @param productVO 前端传入的商品
+     * @return 是否匹配
+     */
+    @SuppressWarnings("DuplicatedCode")
+    default boolean isEquals(DiscountProductDO productDO, DiscountActivityBaseVO.Product productVO) {
+        if (ObjectUtil.notEqual(productDO.getSpuId(), productVO.getSpuId())
+                || ObjectUtil.notEqual(productDO.getSkuId(), productVO.getSkuId())
+                || ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType())) {
+            return false;
+        }
+        if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PRICE.getType())) {
+            return ObjectUtil.equal(productDO.getDiscountPrice(), productVO.getDiscountPrice());
+        }
+        if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PERCENT.getType())) {
+            return ObjectUtil.equal(productDO.getDiscountPercent(), productVO.getDiscountPercent());
+        }
+        return true;
+    }
+
+    /**
+     * 比较两个限时折扣商品是否相等
+     * 注意,比较时忽略 id 编号
+     *
+     * @param productDO 商品 1
+     * @param productVO 商品 2
+     * @return 是否匹配
+     */
+    @SuppressWarnings("DuplicatedCode")
+    default boolean isEquals(DiscountProductDO productDO, DiscountProductDO productVO) {
+        if (ObjectUtil.notEqual(productDO.getSpuId(), productVO.getSpuId())
+                || ObjectUtil.notEqual(productDO.getSkuId(), productVO.getSkuId())
+                || ObjectUtil.notEqual(productDO.getDiscountType(), productVO.getDiscountType())) {
+            return false;
+        }
+        if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PRICE.getType())) {
+            return ObjectUtil.equal(productDO.getDiscountPrice(), productVO.getDiscountPrice());
+        }
+        if (productDO.getDiscountType().equals(PromotionDiscountTypeEnum.PERCENT.getType())) {
+            return ObjectUtil.equal(productDO.getDiscountPercent(), productVO.getDiscountPercent());
+        }
+        return true;
+    }
+
+}

+ 0 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/package-info.java

@@ -1,4 +0,0 @@
-/**
- * TODO 占位
- */
-package cn.iocoder.yudao.module.promotion.convert.discount;

+ 2 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/price/PriceConvert.java

@@ -36,7 +36,8 @@ public interface PriceConvert {
             orderItem.setPayPrice(orderItem.getOriginalPrice()).setOrderDividePrice(orderItem.getOriginalPrice());
             priceCalculate.getOrder().getItems().add(orderItem);
             // 补充价格信息到 Order 中
-            order.setOriginalPrice(order.getOriginalPrice() + orderItem.getOriginalPrice()).setPayPrice(order.getOriginalPrice());
+            order.setOriginalPrice(order.getOriginalPrice() + orderItem.getOriginalPrice())
+                    .setOrderPrice(order.getOriginalPrice()).setPayPrice(order.getOriginalPrice());
         });
         return priceCalculate;
     }

+ 29 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/reward/RewardActivityConvert.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.promotion.convert.reward;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 满减送活动 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface RewardActivityConvert {
+
+    RewardActivityConvert INSTANCE = Mappers.getMapper(RewardActivityConvert.class);
+
+    RewardActivityDO convert(RewardActivityCreateReqVO bean);
+
+    RewardActivityDO convert(RewardActivityUpdateReqVO bean);
+
+    RewardActivityRespVO convert(RewardActivityDO bean);
+
+    PageResult<RewardActivityRespVO> convertPage(PageResult<RewardActivityDO> page);
+
+}

+ 40 - 41
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java

@@ -1,11 +1,14 @@
 package cn.iocoder.yudao.module.promotion.dal.dataobject.coupon;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -14,9 +17,11 @@ import java.util.List;
 
 /**
  * 优惠劵 DO
+ *
+ * @author 芋道源码
  */
 @TableName(value = "promotion_coupon", autoResultMap = true)
-@KeySequence("promotion_coupo_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@KeySequence("promotion_coupon_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 public class CouponDO extends BaseDO {
@@ -35,21 +40,13 @@ public class CouponDO extends BaseDO {
     /**
      * 优惠劵名
      *
-     * 冗余自 {@link CouponTemplateDO} 的 title
-     *
-     * TODO 芋艿,暂时不考虑冗余的更新
+     * 冗余 {@link CouponTemplateDO#getName()}
      */
-    private String title;
-//    /**
-//     * 核销码
-//     */
-//    private String verifyCode;
+    private String name;
     /**
      * 优惠码状态
      *
-     * 1-未使用
-     * 2-已使用
-     * 3-已失效
+     * 枚举 {@link CouponStatusEnum}
      */
     private Integer status;
 
@@ -58,13 +55,14 @@ public class CouponDO extends BaseDO {
     // ========== 领取情况 BEGIN ==========
     /**
      * 用户编号
+     *
+     * 关联 MemberUserDO 的 id 字段
      */
-    private Integer userId;
+    private Long userId;
     /**
      * 领取类型
      *
-     * 1 - 用户主动领取
-     * 2 - 后台自动发放
+     * 枚举 {@link CouponTakeTypeEnum}
      */
     private Integer takeType;
     // ========== 领取情况 END ==========
@@ -72,8 +70,10 @@ public class CouponDO extends BaseDO {
     // ========== 使用规则 BEGIN ==========
     /**
      * 是否设置满多少金额可用,单位:分
+     *
+     * 冗余 {@link CouponTemplateDO#getUsePrice()}
      */
-    private Integer priceAvailable;
+    private Integer usePrice;
     /**
      * 生效开始时间
      */
@@ -90,50 +90,49 @@ public class CouponDO extends BaseDO {
     private Integer productScope;
     /**
      * 商品 SPU 编号的数组
+     *
+     * 冗余 {@link CouponTemplateDO#getProductSpuIds()}
      */
-    @TableField(typeHandler = JacksonTypeHandler.class)
-    private List<Long> spuIds;
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> productSpuIds;
     // ========== 使用规则 END ==========
 
     // ========== 使用效果 BEGIN ==========
     /**
-     * 优惠类型
+     * 折扣类型
      *
-     * 1-代金卷
-     * 2-折扣卷
+     * 冗余 {@link CouponTemplateDO#getDiscountType()}
      */
-    private Integer preferentialType;
+    private Integer discountType;
     /**
-     * 折扣
+     * 折扣百分比
+     *
+     * 冗余 {@link CouponTemplateDO#getDiscountPercent()}
      */
-    private Integer percentOff;
+    private Integer discountPercent;
     /**
-     * 优惠金额,单位:分。
+     * 优惠金额,单位:分
+     *
+     * 冗余 {@link CouponTemplateDO#getDiscountPrice()}
      */
-    private Integer priceOff;
+    private Integer discountPrice;
     /**
-     * 折扣上限,仅在 {@link #preferentialType} 等于 2 时生效。
+     * 折扣上限,仅在 {@link #discountType} 等于 {@link PromotionDiscountTypeEnum#PERCENT} 时生效
      *
-     * 例如,折扣上限为 20 元,当使用 8 折优惠券,订单金额为 1000 元时,最高只可折扣 20 元,而非 80  元。
+     * 冗余 {@link CouponTemplateDO#getDiscountLimitPrice()}
      */
-    private Integer discountPriceLimit;
+    private Integer discountLimitPrice;
     // ========== 使用效果 END ==========
 
     // ========== 使用情况 BEGIN ==========
-//    /**
-//     * 使用订单号
-//     */
-//    private Integer usedOrderId; // TODO 芋艿,暂时不考虑这个字段
-//    /**
-//     * 订单中优惠面值,单位:分
-//     */
-//    private Integer usedPrice; // TODO 芋艿,暂时不考虑这个字段
+    /**
+     * 使用订单号
+     */
+    private Long useOrderId;
     /**
      * 使用时间
      */
-    private Date usedTime;
-
-    // TODO 芋艿,后续要加优惠劵的使用日志,因为下单后,可能会取消。
+    private Date useTime;
 
     // ========== 使用情况 END ==========
 

+ 2 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java

@@ -21,6 +21,8 @@ import java.util.List;
  * 优惠劵模板 DO
  *
  * 当用户领取时,会生成 {@link CouponDO} 优惠劵
+ *
+ * @author 芋道源码
  */
 @TableName(value = "promotion_coupon_template", autoResultMap = true)
 @KeySequence("promotion_coupon_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。

+ 13 - 19
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/discount/DiscountProductDO.java

@@ -1,14 +1,13 @@
 package cn.iocoder.yudao.module.promotion.dal.dataobject.discount;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.util.Date;
-
 /**
  * 限时折扣商品 DO
  *
@@ -31,12 +30,6 @@ public class DiscountProductDO extends BaseDO {
      * 关联 {@link DiscountActivityDO#getId()}
      */
     private Long activityId;
-    /**
-     * 限时折扣活动的名字
-     *
-     * 冗余 {@link DiscountActivityDO#getName()}
-     */
-    private String activityName;
     /**
      * 商品 SPU 编号
      *
@@ -49,23 +42,24 @@ public class DiscountProductDO extends BaseDO {
      * 关联 ProductSkuDO 的 id 编号
      */
     private Long skuId;
+
     /**
-     * 开始时间
-     */
-    private Date startTime;
-    /**
-     * 结束时间
+     * 折扣类型
+     *
+     * 枚举 {@link PromotionDiscountTypeEnum}
      */
-    private Date endTime;
+    private Integer discountType;
     /**
-     * 销售价格,单位:分
+     * 折扣百分比
      *
-     * 冗余 ProductSkuDO 的 price 字段
+     * 例如,80% 为 80
      */
-    private Integer originalPrice;
+    private Integer discountPercent;
     /**
-     * 优惠价格,单位:分
+     * 优惠金额,单位:分
+     *
+     * 当 {@link #discountType} 为 {@link PromotionDiscountTypeEnum#PRICE} 生效
      */
-    private Integer promotionPrice;
+    private Integer discountPrice;
 
 }

+ 21 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/reward/RewardActivityDO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
 
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
@@ -8,10 +10,11 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
 
@@ -68,19 +71,19 @@ public class RewardActivityDO extends BaseDO {
     /**
      * 商品 SPU 编号的数组
      */
-    @TableField(typeHandler = JacksonTypeHandler.class)
+    @TableField(typeHandler = LongListTypeHandler.class)
     private List<Long> productSpuIds;
     /**
      * 优惠规则的数组
      */
-    @TableField(typeHandler = JacksonTypeHandler.class)
+    @TableField(typeHandler = RuleTypeHandler.class)
     private List<Rule> rules;
 
     /**
      * 优惠规则
      */
     @Data
-    public static class Rule {
+    public static class Rule implements Serializable {
 
         /**
          * 优惠门槛
@@ -112,5 +115,19 @@ public class RewardActivityDO extends BaseDO {
 
     }
 
+    // TODO @芋艿:可以找一些新的思路
+    public static class RuleTypeHandler extends AbstractJsonTypeHandler<List<Rule>> {
+
+        @Override
+        protected List<Rule> parse(String json) {
+            return JsonUtils.parseArray(json, Rule.class);
+        }
+
+        @Override
+        protected String toJson(List<Rule> obj) {
+            return JsonUtils.toJsonString(obj);
+        }
+
+    }
 
 }

+ 36 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+
+/**
+ * 优惠劵 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface CouponMapper extends BaseMapperX<CouponDO> {
+
+    default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO, Collection<Long> userIds) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<CouponDO>()
+                .eqIfPresent(CouponDO::getTemplateId, reqVO.getTemplateId())
+                .eqIfPresent(CouponDO::getStatus, reqVO.getStatus())
+                .inIfPresent(CouponDO::getUserId, userIds)
+                .betweenIfPresent(CouponDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(CouponDO::getId));
+    }
+
+    default int delete(Long id, Collection<Integer> whereStatuses) {
+        return update(null, new LambdaUpdateWrapper<CouponDO>()
+                .eq(CouponDO::getId, id).in(CouponDO::getStatus, whereStatuses)
+                .set(CouponDO::getDeleted, 1));
+    }
+
+}

+ 4 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java

@@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * 优惠劵模板 Mapper
@@ -24,4 +25,6 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
                 .orderByDesc(CouponTemplateDO::getId));
     }
 
+    void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount);
+
 }

+ 30 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountActivityMapper.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 限时折扣活动 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface DiscountActivityMapper extends BaseMapperX<DiscountActivityDO> {
+
+    default PageResult<DiscountActivityDO> selectPage(DiscountActivityPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<DiscountActivityDO>()
+                .likeIfPresent(DiscountActivityDO::getName, reqVO.getName())
+                .eqIfPresent(DiscountActivityDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(DiscountActivityDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(DiscountActivityDO::getId));
+    }
+
+}

+ 26 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 限时折扣商城 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
+
+    default List<DiscountProductDO> selectListBySkuId(Collection<Long> skuIds) {
+        return selectList(DiscountProductDO::getSkuId, skuIds);
+    }
+
+    default List<DiscountProductDO> selectListByActivityId(Long activityId) {
+        return selectList(DiscountProductDO::getActivityId, activityId);
+    }
+
+}

+ 0 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/package-info.java

@@ -1,4 +0,0 @@
-/**
- * TODO 占位
- */
-package cn.iocoder.yudao.module.promotion.dal.mysql.discount;

+ 38 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.reward;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 满减送活动 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
+
+    default PageResult<RewardActivityDO> selectPage(RewardActivityPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<RewardActivityDO>()
+                .likeIfPresent(RewardActivityDO::getName, reqVO.getName())
+                .eqIfPresent(RewardActivityDO::getStatus, reqVO.getStatus())
+                .orderByDesc(RewardActivityDO::getId));
+    }
+
+    default List<RewardActivityDO> selectListByStatus(Collection<Integer> statuses) {
+        return selectList(RewardActivityDO::getStatus, statuses);
+    }
+
+    default List<RewardActivityDO> selectListByProductScopeAndStatus(Integer productScope, Integer status) {
+        return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
+                .eq(RewardActivityDO::getProductScope, productScope)
+                .eq(RewardActivityDO::getStatus, status));
+    }
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 
 /**
@@ -21,4 +23,19 @@ public interface CouponService {
      */
     CouponDO validCoupon(Long id, Long userId);
 
+    /**
+     * 获得优惠劵分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 优惠劵分页
+     */
+    PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
+
+    /**
+     * 回收优惠劵
+     *
+     * @param id 优惠劵编号
+     */
+    void deleteCoupon(Long id);
+
 }

+ 65 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java

@@ -1,9 +1,27 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import javax.annotation.Resource;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_DELETE_FAIL_USED;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NOT_EXISTS;
+import static java.util.Arrays.asList;
+
 /**
  * 优惠劵 Service 实现类
  *
@@ -13,9 +31,56 @@ import org.springframework.validation.annotation.Validated;
 @Validated
 public class CouponServiceImpl implements CouponService {
 
+    @Resource
+    private CouponTemplateService couponTemplateService;
+
+    @Resource
+    private CouponMapper couponMapper;
+
+    @Resource
+    private MemberUserApi memberUserApi;
+
     // TODO 芋艿:待实现
     @Override
     public CouponDO validCoupon(Long id, Long userId) {
         return null;
     }
+
+    @Override
+    public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
+        // 获得用户编号
+        Set<Long> userIds = null;
+        if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
+            userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
+                    UserRespDTO::getId);
+            if (CollUtil.isEmpty(userIds)) {
+                return PageResult.empty();
+            }
+        }
+        // 分页查询
+        return couponMapper.selectPage(pageReqVO, userIds);
+    }
+
+    @Override
+    @Transactional
+    public void deleteCoupon(Long id) {
+        // 校验存在
+        validateCouponExists(id);
+
+        // 更新优惠劵
+        int deleteCount = couponMapper.delete(id,
+                asList(CouponStatusEnum.UNUSED.getStatus(), CouponStatusEnum.EXPIRE.getStatus()));
+        if (deleteCount == 0) {
+            throw exception(COUPON_DELETE_FAIL_USED);
+        }
+        // 减少优惠劵模板的领取数量 -1
+        couponTemplateService.updateCouponTemplateTakeCount(id, -1);
+    }
+
+    private void validateCouponExists(Long id) {
+        if (couponMapper.selectById(id) == null) {
+            throw exception(COUPON_NOT_EXISTS);
+        }
+    }
+
 }

+ 19 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplatePageReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 
 import javax.validation.Valid;
@@ -30,6 +30,14 @@ public interface CouponTemplateService {
      */
     void updateCouponTemplate(@Valid CouponTemplateUpdateReqVO updateReqVO);
 
+    /**
+     * 更新优惠劵模板的状态
+     *
+     * @param id 编号
+     * @param status 状态
+     */
+    void updateCouponTemplateStatus(Long id, Integer status);
+
     /**
      * 删除优惠劵模板
      *
@@ -53,4 +61,12 @@ public interface CouponTemplateService {
      */
     PageResult<CouponTemplateDO> getCouponTemplatePage(CouponTemplatePageReqVO pageReqVO);
 
+    /**
+     * 更新优惠劵模板的领取数量
+     *
+     * @param id 优惠劵模板编号
+     * @param incrCount 增加数量
+     */
+    void updateCouponTemplateTakeCount(Long id, int incrCount);
+
 }

+ 19 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java

@@ -1,9 +1,10 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplatePageReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
@@ -30,7 +31,8 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
     @Override
     public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
         // 插入
-        CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO);
+        CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO)
+                .setStatus(CommonStatusEnum.ENABLE.getStatus());
         couponTemplateMapper.insert(couponTemplate);
         // 返回
         return couponTemplate.getId();
@@ -50,6 +52,14 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
         couponTemplateMapper.updateById(updateObj);
     }
 
+    @Override
+    public void updateCouponTemplateStatus(Long id, Integer status) {
+        // 校验存在
+        validateCouponTemplateExists(id);
+        // 更新
+        couponTemplateMapper.updateById(new CouponTemplateDO().setId(id).setStatus(status));
+    }
+
     @Override
     public void deleteCouponTemplate(Long id) {
         // 校验存在
@@ -76,4 +86,9 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
         return couponTemplateMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void updateCouponTemplateTakeCount(Long id, int incrCount) {
+        couponTemplateMapper.updateTakeCount(id, incrCount);
+    }
+
 }

+ 86 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java

@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.promotion.service.discount;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 限时折扣 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface DiscountActivityService {
+
+    /**
+     * 基于指定 SKU 编号数组,获得匹配的限时折扣商品
+     *
+     * 注意,匹配的条件,仅仅是日期符合,并且处于开启状态
+     *
+     * @param skuIds SKU 编号数组
+     * @return 匹配的限时折扣商品
+     */
+    Map<Long, DiscountProductDetailBO> getMatchDiscountProducts(Collection<Long> skuIds);
+
+    /**
+     * 创建限时折扣活动
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createDiscountActivity(@Valid DiscountActivityCreateReqVO createReqVO);
+
+    /**
+     * 更新限时折扣活动
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateDiscountActivity(@Valid DiscountActivityUpdateReqVO updateReqVO);
+
+    /**
+     * 关闭限时折扣活动
+     *
+     * @param id 编号
+     */
+    void closeRewardActivity(Long id);
+
+    /**
+     * 删除限时折扣活动
+     *
+     * @param id 编号
+     */
+    void deleteDiscountActivity(Long id);
+
+    /**
+     * 获得限时折扣活动
+     *
+     * @param id 编号
+     * @return 限时折扣活动
+     */
+    DiscountActivityDO getDiscountActivity(Long id);
+
+    /**
+     * 获得限时折扣活动分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 限时折扣活动分页
+     */
+    PageResult<DiscountActivityDO> getDiscountActivityPage(DiscountActivityPageReqVO pageReqVO);
+
+    /**
+     * 获得活动编号,对应对应的商品列表
+     *
+     * @param activityId 活动编号
+     * @return 活动的商品列表
+     */
+    List<DiscountProductDO> getDiscountProductsByActivityId(Long activityId);
+
+}

+ 196 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java

@@ -0,0 +1,196 @@
+package cn.iocoder.yudao.module.promotion.service.discount;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountActivityMapper;
+import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
+import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
+import static java.util.Arrays.asList;
+
+/**
+ * 限时折扣 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class DiscountActivityServiceImpl implements DiscountActivityService {
+
+    @Resource
+    private DiscountActivityMapper discountActivityMapper;
+    @Resource
+    private DiscountProductMapper discountProductMapper;
+
+    @Override
+    public Map<Long, DiscountProductDetailBO> getMatchDiscountProducts(Collection<Long> skuIds) {
+        List<DiscountProductDetailBO> discountProducts = getRewardProductListBySkuIds(skuIds, singleton(PromotionActivityStatusEnum.RUN.getStatus()));
+        return convertMap(discountProducts, DiscountProductDetailBO::getSkuId);
+    }
+
+    @Override
+    public Long createDiscountActivity(DiscountActivityCreateReqVO createReqVO) {
+        // 校验商品是否冲突
+        validateDiscountActivityProductConflicts(null, createReqVO.getProducts());
+
+        // 插入活动
+        DiscountActivityDO discountActivity = DiscountActivityConvert.INSTANCE.convert(createReqVO)
+                .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getStartTime(), createReqVO.getEndTime()));
+        discountActivityMapper.insert(discountActivity);
+        // 插入商品
+        List<DiscountProductDO> discountProducts = convertList(createReqVO.getProducts(),
+                product -> DiscountActivityConvert.INSTANCE.convert(product).setActivityId(discountActivity.getId()));
+        discountProductMapper.insertBatch(discountProducts);
+        // 返回
+        return discountActivity.getId();
+    }
+
+    @Override
+    public void updateDiscountActivity(DiscountActivityUpdateReqVO updateReqVO) {
+        // 校验存在
+        DiscountActivityDO discountActivity = validateDiscountActivityExists(updateReqVO.getId());
+        if (discountActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢
+            throw exception(DISCOUNT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
+        }
+        // 校验商品是否冲突
+        validateDiscountActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
+
+        // 更新活动
+        DiscountActivityDO updateObj = DiscountActivityConvert.INSTANCE.convert(updateReqVO)
+                .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getStartTime(), updateReqVO.getEndTime()));
+        discountActivityMapper.updateById(updateObj);
+        // 更新商品
+        updateDiscountProduct(updateReqVO);
+    }
+
+    private void updateDiscountProduct(DiscountActivityUpdateReqVO updateReqVO) {
+        List<DiscountProductDO> dbDiscountProducts = discountProductMapper.selectListByActivityId(updateReqVO.getId());
+        // 计算要删除的记录
+        List<Long> deleteIds = convertList(dbDiscountProducts, DiscountProductDO::getId,
+                discountProductDO -> updateReqVO.getProducts().stream()
+                        .noneMatch(product -> DiscountActivityConvert.INSTANCE.isEquals(discountProductDO, product)));
+        if (CollUtil.isNotEmpty(deleteIds)) {
+            discountProductMapper.deleteBatchIds(deleteIds);
+        }
+        // 计算新增的记录
+        List<DiscountProductDO> newDiscountProducts = convertList(updateReqVO.getProducts(),
+                product -> DiscountActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId()));
+        newDiscountProducts.removeIf(product -> dbDiscountProducts.stream().anyMatch(
+                dbProduct -> DiscountActivityConvert.INSTANCE.isEquals(dbProduct, product))); // 如果匹配到,说明是更新的
+        if (CollectionUtil.isNotEmpty(newDiscountProducts)) {
+            discountProductMapper.insertBatch(newDiscountProducts);
+        }
+    }
+
+    /**
+     * 校验商品是否冲突
+     *
+     * @param id 编号
+     * @param products 商品列表
+     */
+    private void validateDiscountActivityProductConflicts(Long id, List<DiscountActivityBaseVO.Product> products) {
+        if (CollUtil.isEmpty(products)) {
+            return;
+        }
+        // 查询商品参加的活动
+        List<DiscountProductDetailBO> discountActivityProductList = getRewardProductListBySkuIds(
+                convertSet(products, DiscountActivityBaseVO.Product::getSkuId),
+                asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus()));
+        if (id != null) { // 排除自己这个活动
+            discountActivityProductList.removeIf(product -> id.equals(product.getActivityId()));
+        }
+        // 如果非空,则说明冲突
+        if (CollUtil.isNotEmpty(discountActivityProductList)) {
+            throw exception(DISCOUNT_ACTIVITY_SPU_CONFLICTS);
+        }
+    }
+
+    private List<DiscountProductDetailBO> getRewardProductListBySkuIds(Collection<Long> skuIds,
+                                                                       Collection<Integer> statuses) {
+        // 查询商品
+        List<DiscountProductDO> products = discountProductMapper.selectListBySkuId(skuIds);
+        if (CollUtil.isEmpty(products)) {
+            return new ArrayList<>(0);
+        }
+
+        // 查询活动
+        List<DiscountActivityDO> activities = discountActivityMapper.selectBatchIds(skuIds);
+        activities.removeIf(activity -> !statuses.contains(activity.getStatus())); // 移除不满足 statuses 状态的
+        Map<Long, DiscountActivityDO> activityMap = CollectionUtils.convertMap(activities, DiscountActivityDO::getId);
+
+        // 移除不满足活动的商品
+        products.removeIf(product -> !activityMap.containsKey(product.getActivityId()));
+        return DiscountActivityConvert.INSTANCE.convertList(products, activityMap);
+    }
+
+    @Override
+    public void closeRewardActivity(Long id) {
+        // 校验存在
+        DiscountActivityDO dbDiscountActivity = validateDiscountActivityExists(id);
+        if (dbDiscountActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢
+            throw exception(DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
+        }
+        if (dbDiscountActivity.getStatus().equals(PromotionActivityStatusEnum.END.getStatus())) { // 已关闭的活动,不能关闭噢
+            throw exception(DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_END);
+        }
+
+        // 更新
+        DiscountActivityDO updateObj = new DiscountActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
+        discountActivityMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteDiscountActivity(Long id) {
+        // 校验存在
+        DiscountActivityDO discountActivity = validateDiscountActivityExists(id);
+        if (!discountActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 未关闭的活动,不能删除噢
+            throw exception(DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED);
+        }
+
+        // 删除
+        discountActivityMapper.deleteById(id);
+    }
+
+    private DiscountActivityDO validateDiscountActivityExists(Long id) {
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(id);
+        if (discountActivity == null) {
+            throw exception(DISCOUNT_ACTIVITY_NOT_EXISTS);
+        }
+        return discountActivity;
+    }
+
+    @Override
+    public DiscountActivityDO getDiscountActivity(Long id) {
+        return discountActivityMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<DiscountActivityDO> getDiscountActivityPage(DiscountActivityPageReqVO pageReqVO) {
+        return discountActivityMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<DiscountProductDO> getDiscountProductsByActivityId(Long activityId) {
+        return discountProductMapper.selectListByActivityId(activityId);
+    }
+
+}

+ 0 - 25
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountService.java

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.module.promotion.service.discount;
-
-import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * 限时折扣 Service 接口
- *
- * @author 芋道源码
- */
-public interface DiscountService {
-
-    /**
-     * 基于指定 SKU 编号数组,获得匹配的限时折扣商品
-     *
-     * 注意,匹配的条件,仅仅是日期符合,并且处于开启状态
-     *
-     * @param skuIds SKU 编号数组
-     * @return 匹配的限时折扣商品
-     */
-    Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds);
-
-}

+ 0 - 29
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountServiceImpl.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.promotion.service.discount;
-
-import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 限时折扣 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class DiscountServiceImpl implements DiscountService {
-
-    // TODO 芋艿:待实现
-    @Override
-    public Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds) {
-        Map<Long, DiscountProductDO> products = new HashMap<>();
-        products.put(1L, new DiscountProductDO().setPromotionPrice(100));
-        products.put(2L, new DiscountProductDO().setPromotionPrice(50));
-        return products;
-    }
-
-}

+ 50 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/bo/DiscountProductDetailBO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.promotion.service.discount.bo;
+
+import lombok.Data;
+
+/**
+ * 限时折扣活动商品 BO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class DiscountProductDetailBO {
+
+    // ========== DiscountProductDO 字段 ==========
+
+    /**
+     * 编号,主键自增
+     */
+    private Long id;
+    /**
+     * 限时折扣活动的编号
+     */
+    private Long activityId;
+    /**
+     * 商品 SPU 编号
+     */
+    private Long spuId;
+    /**
+     * 商品 SKU 编号
+     */
+    private Long skuId;
+    /**
+     * 折扣类型
+     */
+    private Integer discountType;
+    /**
+     * 折扣百分比
+     */
+    private Integer discountPercent;
+    /**
+     * 优惠金额,单位:分
+     */
+    private Integer discountPrice;
+
+    // ========== DiscountActivityDO 字段 ==========
+    /**
+     * 活动标题
+     */
+    private String activityName;
+
+}

+ 50 - 37
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java

@@ -4,22 +4,20 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
 import cn.iocoder.yudao.module.promotion.convert.price.PriceConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.*;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
-import cn.iocoder.yudao.module.promotion.service.discount.DiscountService;
-import cn.iocoder.yudao.module.promotion.service.reward.RewardService;
-import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
-import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
+import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
+import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
 import com.google.common.base.Suppliers;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -33,9 +31,8 @@ import java.util.function.Supplier;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
-import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
-import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 import static java.util.Collections.singletonList;
 
 /**
@@ -54,12 +51,13 @@ import static java.util.Collections.singletonList;
  */
 @Service
 @Validated
+@Slf4j
 public class PriceServiceImpl implements PriceService {
 
     @Resource
-    private DiscountService discountService;
+    private DiscountActivityService discountService;
     @Resource
-    private RewardService rewardService;
+    private RewardActivityService rewardActivityService;
     @Resource
     private CouponService couponService;
 
@@ -79,7 +77,13 @@ public class PriceServiceImpl implements PriceService {
         calculatePriceForOrderLevel(calculateReqDTO.getUserId(), priceCalculate);
         // 计算优惠劵级别的价格
         calculatePriceForCouponLevel(calculateReqDTO.getUserId(), calculateReqDTO.getCouponId(), priceCalculate);
-        // 计算【优惠劵】促销 TODO 待实现
+
+        // 如果最终支付金额小于等于 0,则抛出业务异常
+        if (priceCalculate.getOrder().getPayPrice() <= 0) {
+            log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]",
+                    calculateReqDTO, priceCalculate);
+            throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
+        }
         return priceCalculate;
     }
 
@@ -115,24 +119,20 @@ public class PriceServiceImpl implements PriceService {
     private void calculatePriceForSkuLevel(Long userId, PriceCalculateRespDTO priceCalculate) {
         // 获取 SKU 级别的所有优惠信息
         Supplier<Double> memberDiscountPercentSupplier = getMemberDiscountPercentSupplier(userId);
-        Map<Long, DiscountProductDO> discountProducts = discountService.getMatchDiscountProducts(
+        Map<Long, DiscountProductDetailBO> discountProducts = discountService.getMatchDiscountProducts(
                 convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSkuId));
 
         // 处理每个 SKU 的优惠
         priceCalculate.getOrder().getItems().forEach(orderItem -> {
             // 获取该 SKU 的优惠信息
             Double memberDiscountPercent = memberDiscountPercentSupplier.get();
-            DiscountProductDO discountProduct = discountProducts.get(orderItem.getSkuId());
-            if (discountProduct != null // 假设优惠价格更贵,则认为没优惠
-                    && discountProduct.getPromotionPrice() >= orderItem.getOriginalUnitPrice()) {
-                discountProduct = null;
-            }
+            DiscountProductDetailBO discountProduct = discountProducts.get(orderItem.getSkuId());
             if (memberDiscountPercent == null && discountProduct == null) {
                 return;
             }
             // 计算价格,判断选择哪个折扣
             Integer memberPrice = memberDiscountPercent != null ? (int) (orderItem.getPayPrice() * memberDiscountPercent / 100) : null;
-            Integer promotionPrice = discountProduct != null ? discountProduct.getPromotionPrice() * orderItem.getCount() : null;
+            Integer promotionPrice = discountProduct != null ? getDiscountProductPrice(discountProduct, orderItem) : null;
             if (memberPrice == null) {
                 calculatePriceByDiscountActivity(priceCalculate, orderItem, discountProduct, promotionPrice);
             } else if (promotionPrice == null) {
@@ -145,6 +145,19 @@ public class PriceServiceImpl implements PriceService {
         });
     }
 
+    private Integer getDiscountProductPrice(DiscountProductDetailBO discountProduct,
+                                            PriceCalculateRespDTO.OrderItem orderItem) {
+        Integer price = orderItem.getPayPrice();
+        if (PromotionDiscountTypeEnum.PRICE.getType().equals(discountProduct.getDiscountType())) { // 减价
+            price -= discountProduct.getDiscountPrice() * orderItem.getCount();
+        } else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discountProduct.getDiscountType())) { // 打折
+            price = price * discountProduct.getDiscountPercent() / 100;
+        } else {
+            throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discountProduct));
+        }
+        return price;
+    }
+
     private void calculatePriceByMemberDiscount(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem,
                                                 Integer memberPrice) {
         // 记录优惠明细
@@ -156,7 +169,7 @@ public class PriceServiceImpl implements PriceService {
     }
 
     private void calculatePriceByDiscountActivity(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem,
-                                                  DiscountProductDO discountProduct, Integer promotionPrice) {
+                                                  DiscountProductDetailBO discountProduct, Integer promotionPrice) {
         // 记录优惠明细
         addPromotion(priceCalculate, orderItem, discountProduct.getActivityId(), discountProduct.getActivityName(),
                 PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), PromotionLevelEnum.SKU.getLevel(), promotionPrice,
@@ -191,7 +204,7 @@ public class PriceServiceImpl implements PriceService {
     private void calculatePriceForOrderLevel(Long userId, PriceCalculateRespDTO priceCalculate) {
         // 获取 SKU 级别的所有优惠信息
         Set<Long> spuIds = convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSpuId);
-        Map<RewardActivityDO, Set<Long>> rewardActivities = rewardService.getMatchRewardActivities(spuIds);
+        Map<RewardActivityDO, Set<Long>> rewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
 
         // 处理满减送活动
         if (CollUtil.isNotEmpty(rewardActivities)) {
@@ -216,7 +229,6 @@ public class PriceServiceImpl implements PriceService {
         }
 
         // 分摊金额
-        // TODO 芋艿:limit 不能超过最大价格
         List<Integer> discountPartPrices = dividePrice(orderItems, rule.getDiscountPrice());
         // 记录优惠明细
         addPromotion(priceCalculate, orderItems, rewardActivity.getId(), rewardActivity.getName(),
@@ -283,7 +295,7 @@ public class PriceServiceImpl implements PriceService {
         // 计算是否满足优惠劵的使用金额
         Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
         assert originPrice != null;
-        if (originPrice < coupon.getPriceAvailable()) {
+        if (originPrice < coupon.getUsePrice()) {
             throw exception(COUPON_NO_MATCH_MIN_PRICE);
         }
 
@@ -291,10 +303,9 @@ public class PriceServiceImpl implements PriceService {
         priceCalculate.getOrder().setCouponId(couponId);
         Integer couponPrice = getCouponPrice(coupon, originPrice);
         // 分摊金额
-        // TODO 芋艿:limit 不能超过最大价格
         List<Integer> couponPartPrices = dividePrice(orderItems, couponPrice);
         // 记录优惠明细
-        addPromotion(priceCalculate, orderItems, coupon.getId(), coupon.getTitle(),
+        addPromotion(priceCalculate, orderItems, coupon.getId(), coupon.getName(),
                 PromotionTypeEnum.COUPON.getType(), PromotionLevelEnum.COUPON.getLevel(), couponPartPrices,
                 true, StrUtil.format("优惠劵:省 {} 元", formatPrice(couponPrice)));
         // 修改 SKU 的分摊
@@ -309,19 +320,18 @@ public class PriceServiceImpl implements PriceService {
             return priceCalculate.getOrder().getItems();
         }
         return CollectionUtils.filterList(priceCalculate.getOrder().getItems(),
-                orderItem -> coupon.getSpuIds().contains(orderItem.getSpuId()));
+                orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId()));
     }
 
     private Integer getCouponPrice(CouponDO coupon, Integer originPrice) {
-        // TODO 芋艿 getPreferentialType 的枚举判断
-        if (coupon.getPreferentialType().equals(1)) { // 减价
-            return coupon.getPriceOff();
-        } else if (coupon.getPreferentialType().equals(2)) { // 打折
-            Integer couponPrice = originPrice * coupon.getPercentOff() / 100;
-            return coupon.getDiscountPriceLimit() == null ? couponPrice
-                    : Math.min(couponPrice, coupon.getDiscountPriceLimit()); // 优惠上限
+        if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
+            return coupon.getDiscountPrice();
+        } else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(coupon.getDiscountType())) { // 打折
+            int couponPrice = originPrice * coupon.getDiscountPercent() / 100;
+            return coupon.getDiscountLimitPrice() == null ? couponPrice
+                    : Math.min(couponPrice, coupon.getDiscountLimitPrice()); // 优惠上限
         }
-        throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", coupon.toString()));
+        throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", coupon));
     }
 
     // ========== 其它相对通用的方法 ==========
@@ -410,13 +420,16 @@ public class PriceServiceImpl implements PriceService {
      */
     private void modifyOrderItemPayPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer newPayPrice,
                                          PriceCalculateRespDTO priceCalculate) {
+        // diffPayPrice 等于额外增加的商品级的优惠
         int diffPayPrice = orderItem.getPayPrice() - newPayPrice;
         // 设置 OrderItem 价格相关字段
         orderItem.setDiscountPrice(orderItem.getDiscountPrice() + diffPayPrice);
         orderItem.setPayPrice(newPayPrice);
         orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice());
         // 设置 Order 相关相关字段
-        priceCalculate.getOrder().setPayPrice(priceCalculate.getOrder().getPayPrice() - diffPayPrice);
+        PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
+        order.setPayPrice(order.getPayPrice() - diffPayPrice);
+        order.setOrderPrice(order.getOrderPrice() - diffPayPrice);
     }
 
     /**

+ 73 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.promotion.service.reward;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+
+import javax.validation.Valid;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 满减送活动 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface RewardActivityService {
+
+    /**
+     * 创建满减送活动
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createRewardActivity(@Valid RewardActivityCreateReqVO createReqVO);
+
+    /**
+     * 更新满减送活动
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateRewardActivity(@Valid RewardActivityUpdateReqVO updateReqVO);
+
+    /**
+     * 关闭满减送活动
+     *
+     * @param id 活动编号
+     */
+    void closeRewardActivity(Long id);
+
+    /**
+     * 删除满减送活动
+     *
+     * @param id 编号
+     */
+    void deleteRewardActivity(Long id);
+
+    /**
+     * 获得满减送活动
+     *
+     * @param id 编号
+     * @return 满减送活动
+     */
+    RewardActivityDO getRewardActivity(Long id);
+
+    /**
+     * 获得满减送活动分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 满减送活动分页
+     */
+    PageResult<RewardActivityDO> getRewardActivityPage(RewardActivityPageReqVO pageReqVO);
+
+    /**
+     * 基于指定的 SPU 编号数组,获得它们匹配的满减送活动
+     *
+     * @param spuIds SPU 编号数组
+     * @return 满减送活动,与对应的 SPU 编号的映射。即,value 就是 SPU 编号的集合
+     */
+    Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds);
+
+}

+ 169 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java

@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.module.promotion.service.reward;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.reward.RewardActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singleton;
+
+/**
+ * 满减送活动 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class RewardActivityServiceImpl implements RewardActivityService {
+
+    @Resource
+    private RewardActivityMapper rewardActivityMapper;
+
+    @Override
+    public Long createRewardActivity(RewardActivityCreateReqVO createReqVO) {
+        // 校验商品是否冲突
+        validateRewardActivitySpuConflicts(null, createReqVO.getProductSpuIds());
+
+        // 插入
+        RewardActivityDO rewardActivity = RewardActivityConvert.INSTANCE.convert(createReqVO)
+                .setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getStartTime(), createReqVO.getEndTime()));
+        rewardActivityMapper.insert(rewardActivity);
+        // 返回
+        return rewardActivity.getId();
+    }
+
+    @Override
+    public void updateRewardActivity(RewardActivityUpdateReqVO updateReqVO) {
+        // 校验存在
+        RewardActivityDO dbRewardActivity = validateRewardActivityExists(updateReqVO.getId());
+        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢
+            throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
+        }
+        // 校验商品是否冲突
+        validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds());
+
+        // 更新
+        RewardActivityDO updateObj = RewardActivityConvert.INSTANCE.convert(updateReqVO)
+                .setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getStartTime(), updateReqVO.getEndTime()));
+        rewardActivityMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void closeRewardActivity(Long id) {
+        // 校验存在
+        RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
+        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能关闭噢
+            throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
+        }
+        if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.END.getStatus())) { // 已关闭的活动,不能关闭噢
+            throw exception(REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END);
+        }
+
+        // 更新
+        RewardActivityDO updateObj = new RewardActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
+        rewardActivityMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteRewardActivity(Long id) {
+        // 校验存在
+        RewardActivityDO dbRewardActivity = validateRewardActivityExists(id);
+        if (!dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 未关闭的活动,不能删除噢
+            throw exception(REWARD_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED);
+        }
+
+        // 删除
+        rewardActivityMapper.deleteById(id);
+    }
+
+    private RewardActivityDO validateRewardActivityExists(Long id) {
+        RewardActivityDO activity = rewardActivityMapper.selectById(id);
+        if (activity == null) {
+            throw exception(REWARD_ACTIVITY_NOT_EXISTS);
+        }
+        return activity;
+    }
+
+    /**
+     * 校验商品参加的活动是否冲突
+     *
+     * @param id 活动编号
+     * @param spuIds 商品 SPU 编号数组
+     */
+    private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
+        if (CollUtil.isEmpty(spuIds)) {
+            return;
+        }
+        // 查询商品参加的活动
+        List<RewardActivityDO> rewardActivityList = getRewardActivityListBySpuIds(spuIds,
+                asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus()));
+        if (id != null) { // 排除自己这个活动
+            rewardActivityList.removeIf(activity -> id.equals(activity.getId()));
+        }
+        // 如果非空,则说明冲突
+        if (CollUtil.isNotEmpty(rewardActivityList)) {
+            throw exception(REWARD_ACTIVITY_SPU_CONFLICTS);
+        }
+    }
+
+    /**
+     * 获得商品参加的满减送活动的数组
+     *
+     * @param spuIds 商品 SPU 编号数组
+     * @param statuses 活动状态数组
+     * @return 商品参加的满减送活动的数组
+     */
+    private List<RewardActivityDO> getRewardActivityListBySpuIds(Collection<Long> spuIds,
+                                                                 Collection<Integer> statuses) {
+        List<RewardActivityDO> list = rewardActivityMapper.selectListByStatus(statuses);
+        return CollUtil.filter(list, activity -> CollUtil.containsAny(activity.getProductSpuIds(), spuIds));
+    }
+
+    @Override
+    public RewardActivityDO getRewardActivity(Long id) {
+        return rewardActivityMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<RewardActivityDO> getRewardActivityPage(RewardActivityPageReqVO pageReqVO) {
+        return rewardActivityMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds) {
+        // 如果有全局活动,则直接选择它
+        List<RewardActivityDO> allActivities = rewardActivityMapper.selectListByProductScopeAndStatus(
+                PromotionProductScopeEnum.ALL.getScope(), PromotionActivityStatusEnum.RUN.getStatus());
+        if (CollUtil.isNotEmpty(allActivities)) {
+            return MapUtil.builder(allActivities.get(0), spuIds).build();
+        }
+
+        // 查询某个活动参加的活动
+        List<RewardActivityDO> productActivityList = getRewardActivityListBySpuIds(spuIds,
+                singleton(PromotionActivityStatusEnum.RUN.getStatus()));
+        return convertMap(productActivityList, activity -> activity,
+                rewardActivityDO -> intersectionDistinct(rewardActivityDO.getProductSpuIds(), spuIds)); // 求交集返回
+    }
+
+}

+ 0 - 23
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardService.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.promotion.service.reward;
-
-import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * 满减送 Service 接口
- *
- * @author 芋道源码
- */
-public interface RewardService {
-
-    /**
-     * 基于指定的 SPU 编号数组,获得它们匹配的满减送活动
-     *
-     * @param spuIds SPU 编号数组
-     * @return 满减送活动,与对应的 SPU 编号的映射。即,value 就是 SPU 编号的集合
-     */
-    Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds);
-
-}

+ 0 - 26
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardServiceImpl.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.promotion.service.reward;
-
-import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * 满减送 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class RewardServiceImpl implements RewardService {
-
-    // TODO 芋艿:待实现
-    @Override
-    public Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds) {
-        return Collections.emptyMap();
-    }
-
-}

+ 32 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/util/PromotionUtils.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.promotion.util;
+
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+
+import java.util.Date;
+
+/**
+ * 活动工具类
+ *
+ * @author 芋道源码
+ */
+public class PromotionUtils {
+
+    /**
+     * 根据时间,计算活动状态
+     *
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @return 活动状态
+     */
+    public static Integer calculateActivityStatus(Date startTime, Date endTime) {
+        if (DateUtils.beforeNow(endTime)) {
+            return PromotionActivityStatusEnum.END.getStatus();
+        }
+        if (DateUtils.afterNow(startTime)) {
+            return PromotionActivityStatusEnum.WAIT.getStatus();
+        }
+        return PromotionActivityStatusEnum.RUN.getStatus();
+    }
+
+}

+ 11 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/resources/mapper/coupon/CouponTemplateMapper.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper">
+
+    <update id="updateTakeCount">
+        UPDATE promotion_coupon_template
+        SET take_count = take_count + #{incrCount}
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 29 - 18
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java

@@ -1,19 +1,24 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplatePageReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.CouponTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
-import org.junit.jupiter.api.Disabled;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.Date;
 
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -39,7 +44,10 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testCreateCouponTemplate_success() {
         // 准备参数
-        CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class);
+        CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class,
+                o -> o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope())
+                        .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType())
+                        .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType()));
 
         // 调用
         Long couponTemplateId = couponTemplateService.createCouponTemplate(reqVO);
@@ -58,6 +66,10 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
         // 准备参数
         CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> {
             o.setId(dbCouponTemplate.getId()); // 设置更新的 ID
+            // 其它通用字段
+            o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope())
+                    .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType())
+                    .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType());
         });
 
         // 调用
@@ -100,30 +112,29 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGetCouponTemplatePage() {
        // mock 数据
        CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class, o -> { // 等会查询到
-           o.setName(null);
-           o.setStatus(null);
-           o.setDiscountType(null);
-           o.setCreateTime(null);
+           o.setName("芋艿");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+           o.setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType());
+           o.setCreateTime(buildTime(2022, 2, 2));
        });
        couponTemplateMapper.insert(dbCouponTemplate);
        // 测试 name 不匹配
-       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setName(null)));
+       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setName("土豆")));
        // 测试 status 不匹配
-       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setStatus(null)));
+       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
        // 测试 type 不匹配
-       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setDiscountType(null)));
+       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType())));
        // 测试 createTime 不匹配
-       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setCreateTime(null)));
+       couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setCreateTime(buildTime(2022, 1, 1))));
        // 准备参数
        CouponTemplatePageReqVO reqVO = new CouponTemplatePageReqVO();
-       reqVO.setName(null);
-       reqVO.setStatus(null);
-       reqVO.setDiscountType(null);
-       reqVO.setCreateTime((new Date[]{}));
+       reqVO.setName("芋艿");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       reqVO.setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType());
+       reqVO.setCreateTime((new Date[]{buildTime(2022, 2, 1), buildTime(2022, 2, 3)}));
 
        // 调用
        PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(reqVO);

+ 209 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java

@@ -0,0 +1,209 @@
+package cn.iocoder.yudao.module.promotion.service.discount;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountActivityMapper;
+import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DISCOUNT_ACTIVITY_NOT_EXISTS;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link DiscountActivityServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(DiscountActivityServiceImpl.class)
+public class DiscountActivityServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private DiscountActivityServiceImpl discountActivityService;
+
+    @Resource
+    private DiscountActivityMapper discountActivityMapper;
+    @Resource
+    private DiscountProductMapper discountProductMapper;
+
+    @Test
+    public void testCreateDiscountActivity_success() {
+        // 准备参数
+        DiscountActivityCreateReqVO reqVO = randomPojo(DiscountActivityCreateReqVO.class, o -> {
+            // 用于触发进行中的状态
+            o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2)));
+            // 设置商品
+            o.setProducts(asList(new DiscountActivityBaseVO.Product().setSpuId(1L).setSkuId(2L)
+                            .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3),
+                    new DiscountActivityBaseVO.Product().setSpuId(10L).setSkuId(20L)
+                            .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30)));
+        });
+
+        // 调用
+        Long discountActivityId = discountActivityService.createDiscountActivity(reqVO);
+        // 断言
+        assertNotNull(discountActivityId);
+        // 校验活动
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(discountActivityId);
+        assertPojoEquals(reqVO, discountActivity);
+        assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        // 校验商品
+        List<DiscountProductDO> discountProducts = discountProductMapper.selectList(DiscountProductDO::getActivityId, discountActivity.getId());
+        assertEquals(discountProducts.size(), reqVO.getProducts().size());
+        for (int i = 0; i < reqVO.getProducts().size(); i++) {
+            DiscountActivityBaseVO.Product product = reqVO.getProducts().get(i);
+            DiscountProductDO discountProduct = discountProducts.get(i);
+            assertEquals(discountProduct.getActivityId(), discountActivity.getId());
+            assertEquals(discountProduct.getSpuId(), product.getSpuId());
+            assertEquals(discountProduct.getSkuId(), product.getSkuId());
+            assertEquals(discountProduct.getDiscountType(), product.getDiscountType());
+            assertEquals(discountProduct.getDiscountPrice(), product.getDiscountPrice());
+            assertEquals(discountProduct.getDiscountPercent(), product.getDiscountPercent());
+        }
+    }
+
+    @Test
+    public void testUpdateDiscountActivity_success() {
+        // mock 数据(商品)
+        DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class);
+        discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据
+        // mock 数据(活动)
+        DiscountProductDO dbDiscountProduct01 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(dbDiscountActivity.getId())
+                .setSpuId(1L).setSkuId(2L).setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3).setDiscountPercent(null));
+        DiscountProductDO dbDiscountProduct02 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(dbDiscountActivity.getId())
+                .setSpuId(10L).setSkuId(20L).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30).setDiscountPrice(null));
+        discountProductMapper.insert(dbDiscountProduct01);
+        discountProductMapper.insert(dbDiscountProduct02);
+        // 准备参数
+        DiscountActivityUpdateReqVO reqVO = randomPojo(DiscountActivityUpdateReqVO.class, o -> {
+            o.setId(dbDiscountActivity.getId()); // 设置更新的 ID
+            // 用于触发进行中的状态
+            o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2)));
+            // 设置商品
+            o.setProducts(asList(new DiscountActivityBaseVO.Product().setSpuId(1L).setSkuId(2L)
+                            .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3).setDiscountPercent(null),
+                    new DiscountActivityBaseVO.Product().setSpuId(100L).setSkuId(200L)
+                            .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30).setDiscountPrice(null)));
+        });
+
+        // 调用
+        discountActivityService.updateDiscountActivity(reqVO);
+        // 校验活动
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, discountActivity);
+        assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        // 校验商品
+        List<DiscountProductDO> discountProducts = discountProductMapper.selectList(DiscountProductDO::getActivityId, discountActivity.getId());
+        assertEquals(discountProducts.size(), reqVO.getProducts().size());
+        for (int i = 0; i < reqVO.getProducts().size(); i++) {
+            DiscountActivityBaseVO.Product product = reqVO.getProducts().get(i);
+            DiscountProductDO discountProduct = discountProducts.get(i);
+            assertEquals(discountProduct.getActivityId(), discountActivity.getId());
+            assertEquals(discountProduct.getSpuId(), product.getSpuId());
+            assertEquals(discountProduct.getSkuId(), product.getSkuId());
+            assertEquals(discountProduct.getDiscountType(), product.getDiscountType());
+            assertEquals(discountProduct.getDiscountPrice(), product.getDiscountPrice());
+            assertEquals(discountProduct.getDiscountPercent(), product.getDiscountPercent());
+        }
+    }
+
+    @Test
+    public void testCloseDiscountActivity() {
+        // mock 数据
+        DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class,
+                o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
+        discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbDiscountActivity.getId();
+
+        // 调用
+        discountActivityService.closeRewardActivity(id);
+        // 校验状态
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(id);
+        assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus());
+    }
+
+    @Test
+    public void testUpdateDiscountActivity_notExists() {
+        // 准备参数
+        DiscountActivityUpdateReqVO reqVO = randomPojo(DiscountActivityUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> discountActivityService.updateDiscountActivity(reqVO), DISCOUNT_ACTIVITY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteDiscountActivity_success() {
+        // mock 数据
+        DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class,
+                o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()));
+        discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbDiscountActivity.getId();
+
+        // 调用
+        discountActivityService.deleteDiscountActivity(id);
+       // 校验数据不存在了
+       assertNull(discountActivityMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteDiscountActivity_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> discountActivityService.deleteDiscountActivity(id), DISCOUNT_ACTIVITY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGetDiscountActivityPage() {
+       // mock 数据
+       DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class, o -> { // 等会查询到
+           o.setName("芋艿");
+           o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus());
+           o.setCreateTime(buildTime(2021, 1, 15));
+       });
+       discountActivityMapper.insert(dbDiscountActivity);
+       // 测试 name 不匹配
+       discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setName("土豆")));
+       // 测试 status 不匹配
+       discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setStatus(PromotionActivityStatusEnum.END.getStatus())));
+       // 测试 createTime 不匹配
+       discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setCreateTime(buildTime(2021, 2, 10))));
+       // 准备参数
+       DiscountActivityPageReqVO reqVO = new DiscountActivityPageReqVO();
+       reqVO.setName("芋艿");
+       reqVO.setStatus(PromotionActivityStatusEnum.WAIT.getStatus());
+       reqVO.setCreateTime((new Date[]{buildTime(2021, 1, 1), buildTime(2021, 1, 31)}));
+
+       // 调用
+       PageResult<DiscountActivityDO> pageResult = discountActivityService.getDiscountActivityPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbDiscountActivity, pageResult.getList().get(0));
+    }
+
+}

+ 41 - 36
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceTest.java

@@ -2,20 +2,17 @@ package cn.iocoder.yudao.module.promotion.service.price;
 
 import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
-import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.*;
 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
-import cn.iocoder.yudao.module.promotion.service.discount.DiscountService;
-import cn.iocoder.yudao.module.promotion.service.reward.RewardService;
-import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
-import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
+import cn.iocoder.yudao.module.promotion.service.discount.bo.DiscountProductDetailBO;
+import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -44,9 +41,9 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
     private PriceServiceImpl priceService;
 
     @Mock
-    private DiscountService discountService;
+    private DiscountActivityService discountService;
     @Mock
-    private RewardService rewardService;
+    private RewardActivityService rewardActivityService;
     @Mock
     private CouponService couponService;
     @Mock
@@ -67,6 +64,7 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         // 断言 Order 部分
         PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
         assertEquals(order.getOriginalPrice(), 200);
+        assertEquals(order.getOrderPrice(), 180);
         assertEquals(order.getDiscountPrice(), 0);
         assertEquals(order.getPointPrice(), 0);
         assertEquals(order.getDeliveryPrice(), 0);
@@ -112,22 +110,28 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50));
         when(productSkuApi.getSkuList(eq(asSet(10L, 20L)))).thenReturn(asList(productSku01, productSku02));
         // mock 方法(限时折扣 DiscountActivity 信息)
-        DiscountProductDO discountProduct01 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(1000L).setActivityName("活动 1000 号")
-                .setSkuId(10L).setPromotionPrice(80));
-        DiscountProductDO discountProduct02 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(2000L).setActivityName("活动 2000 号")
-                .setSkuId(20L).setPromotionPrice(40));
+        DiscountProductDetailBO discountProduct01 = randomPojo(DiscountProductDetailBO.class, o -> o.setActivityId(1000L)
+                .setActivityName("活动 1000 号").setSkuId(10L)
+                .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(40));
+        DiscountProductDetailBO discountProduct02 = randomPojo(DiscountProductDetailBO.class, o -> o.setActivityId(2000L)
+                .setActivityName("活动 2000 号").setSkuId(20L)
+                .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(60));
         when(discountService.getMatchDiscountProducts(eq(asSet(10L, 20L)))).thenReturn(
                 MapUtil.builder(10L, discountProduct01).put(20L, discountProduct02).map());
 
+        // 10L: 100 * 2 - 40 * 2 = 120
+        // 20L:50 * 3 - 50 * 3 * 0.4 = 90
+
         // 调用
         PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
         // 断言 Order 部分
         PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
         assertEquals(order.getOriginalPrice(), 350);
+        assertEquals(order.getOrderPrice(), 210);
         assertEquals(order.getDiscountPrice(), 0);
         assertEquals(order.getPointPrice(), 0);
         assertEquals(order.getDeliveryPrice(), 0);
-        assertEquals(order.getPayPrice(), 280);
+        assertEquals(order.getPayPrice(), 210);
         assertNull(order.getCouponId());
         // 断言 OrderItem 部分
         assertEquals(order.getItems().size(), 2);
@@ -136,19 +140,19 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         assertEquals(orderItem01.getCount(), 2);
         assertEquals(orderItem01.getOriginalPrice(), 200);
         assertEquals(orderItem01.getOriginalUnitPrice(), 100);
-        assertEquals(orderItem01.getDiscountPrice(), 40);
-        assertEquals(orderItem01.getPayPrice(), 160);
+        assertEquals(orderItem01.getDiscountPrice(), 80);
+        assertEquals(orderItem01.getPayPrice(), 120);
         assertEquals(orderItem01.getOrderPartPrice(), 0);
-        assertEquals(orderItem01.getOrderDividePrice(), 160);
+        assertEquals(orderItem01.getOrderDividePrice(), 120);
         PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
         assertEquals(orderItem02.getSkuId(), 20L);
         assertEquals(orderItem02.getCount(), 3);
         assertEquals(orderItem02.getOriginalPrice(), 150);
         assertEquals(orderItem02.getOriginalUnitPrice(), 50);
-        assertEquals(orderItem02.getDiscountPrice(), 30);
-        assertEquals(orderItem02.getPayPrice(), 120);
+        assertEquals(orderItem02.getDiscountPrice(), 60);
+        assertEquals(orderItem02.getPayPrice(), 90);
         assertEquals(orderItem02.getOrderPartPrice(), 0);
-        assertEquals(orderItem02.getOrderDividePrice(), 120);
+        assertEquals(orderItem02.getOrderDividePrice(), 90);
         // 断言 Promotion 部分
         assertEquals(priceCalculate.getPromotions().size(), 2);
         PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
@@ -157,28 +161,28 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType());
         assertEquals(promotion01.getLevel(), PromotionLevelEnum.SKU.getLevel());
         assertEquals(promotion01.getOriginalPrice(), 200);
-        assertEquals(promotion01.getDiscountPrice(), 40);
+        assertEquals(promotion01.getDiscountPrice(), 80);
         assertTrue(promotion01.getMeet());
-        assertEquals(promotion01.getMeetTip(), "限时折扣:省 0.40 元");
+        assertEquals(promotion01.getMeetTip(), "限时折扣:省 0.80 元");
         PriceCalculateRespDTO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
         assertEquals(promotion01.getItems().size(), 1);
         assertEquals(promotionItem01.getSkuId(), 10L);
         assertEquals(promotionItem01.getOriginalPrice(), 200);
-        assertEquals(promotionItem01.getDiscountPrice(), 40);
+        assertEquals(promotionItem01.getDiscountPrice(), 80);
         PriceCalculateRespDTO.Promotion promotion02 = priceCalculate.getPromotions().get(1);
         assertEquals(promotion02.getId(), 2000L);
         assertEquals(promotion02.getName(), "活动 2000 号");
         assertEquals(promotion02.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType());
         assertEquals(promotion02.getLevel(), PromotionLevelEnum.SKU.getLevel());
         assertEquals(promotion02.getOriginalPrice(), 150);
-        assertEquals(promotion02.getDiscountPrice(), 30);
+        assertEquals(promotion02.getDiscountPrice(), 60);
         assertTrue(promotion02.getMeet());
-        assertEquals(promotion02.getMeetTip(), "限时折扣:省 0.30 元");
+        assertEquals(promotion02.getMeetTip(), "限时折扣:省 0.60 元");
         PriceCalculateRespDTO.PromotionItem promotionItem02 = promotion02.getItems().get(0);
         assertEquals(promotion02.getItems().size(), 1);
         assertEquals(promotionItem02.getSkuId(), 20L);
         assertEquals(promotionItem02.getOriginalPrice(), 150);
-        assertEquals(promotionItem02.getDiscountPrice(), 30);
+        assertEquals(promotionItem02.getDiscountPrice(), 60);
     }
 
     /**
@@ -208,13 +212,14 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>();
         matchRewardActivities.put(rewardActivity01, asSet(1L, 2L));
         matchRewardActivities.put(rewardActivity02, asSet(3L));
-        when(rewardService.getMatchRewardActivities(eq(asSet(1L, 2L, 3L)))).thenReturn(matchRewardActivities);
+        when(rewardActivityService.getMatchRewardActivities(eq(asSet(1L, 2L, 3L)))).thenReturn(matchRewardActivities);
 
         // 调用
         PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
         // 断言 Order 部分
         PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
         assertEquals(order.getOriginalPrice(), 470);
+        assertEquals(order.getOrderPrice(), 470);
         assertEquals(order.getDiscountPrice(), 130);
         assertEquals(order.getPointPrice(), 0);
         assertEquals(order.getDeliveryPrice(), 0);
@@ -305,13 +310,14 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
                 .setRules(singletonList(new RewardActivityDO.Rule().setLimit(351).setDiscountPrice(70))));
         Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>();
         matchRewardActivities.put(rewardActivity01, asSet(1L, 2L));
-        when(rewardService.getMatchRewardActivities(eq(asSet(1L, 2L)))).thenReturn(matchRewardActivities);
+        when(rewardActivityService.getMatchRewardActivities(eq(asSet(1L, 2L)))).thenReturn(matchRewardActivities);
 
         // 调用
         PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
         // 断言 Order 部分
         PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
         assertEquals(order.getOriginalPrice(), 350);
+        assertEquals(order.getOrderPrice(), 350);
         assertEquals(order.getDiscountPrice(), 0);
         assertEquals(order.getPointPrice(), 0);
         assertEquals(order.getDeliveryPrice(), 0);
@@ -373,19 +379,18 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
         ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L));
         when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03));
         // mock 方法(优惠劵 Coupon 信息)
-        CouponDO coupon = randomPojo(CouponDO.class, o -> o.setId(1024L).setTitle("程序员节")
-                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setSpuIds(asList(1L, 2L))
-                .setPriceAvailable(350).setPreferentialType(2).setPercentOff(50).setDiscountPriceLimit(70));
+        CouponDO coupon = randomPojo(CouponDO.class, o -> o.setId(1024L).setName("程序员节")
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))
+                .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
+                .setDiscountPercent(50).setDiscountLimitPrice(70));
         when(couponService.validCoupon(eq(1024L), eq(calculateReqDTO.getUserId()))).thenReturn(coupon);
 
-        // 200 + 150; 350
-        //
-
         // 调用
         PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
         // 断言 Order 部分
         PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
         assertEquals(order.getOriginalPrice(), 470);
+        assertEquals(order.getOrderPrice(), 470);
         assertEquals(order.getDiscountPrice(), 0);
         assertEquals(order.getPointPrice(), 0);
         assertEquals(order.getDeliveryPrice(), 0);

+ 218 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java

@@ -0,0 +1,218 @@
+package cn.iocoder.yudao.module.promotion.service.reward;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_NOT_EXISTS;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link RewardActivityServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(RewardActivityServiceImpl.class)
+public class RewardActivityServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private RewardActivityServiceImpl rewardActivityService;
+
+    @Resource
+    private RewardActivityMapper rewardActivityMapper;
+
+    @Test
+    public void testCreateRewardActivity_success() {
+        // 准备参数
+        RewardActivityCreateReqVO reqVO = randomPojo(RewardActivityCreateReqVO.class, o -> {
+            o.setConditionType(randomEle(PromotionConditionTypeEnum.values()).getType());
+            o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope());
+            // 用于触发进行中的状态
+            o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2)));
+        });
+
+        // 调用
+        Long rewardActivityId = rewardActivityService.createRewardActivity(reqVO);
+        // 断言
+        assertNotNull(rewardActivityId);
+        // 校验记录的属性是否正确
+        RewardActivityDO rewardActivity = rewardActivityMapper.selectById(rewardActivityId);
+        assertPojoEquals(reqVO, rewardActivity, "rules");
+        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        for (int i = 0; i < reqVO.getRules().size(); i++) {
+            assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
+        }
+    }
+
+    @Test
+    public void testUpdateRewardActivity_success() {
+        // mock 数据
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
+        rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class, o -> {
+            o.setId(dbRewardActivity.getId()); // 设置更新的 ID
+            o.setConditionType(randomEle(PromotionConditionTypeEnum.values()).getType());
+            o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope());
+            // 用于触发进行中的状态
+            o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2)));
+        });
+
+        // 调用
+        rewardActivityService.updateRewardActivity(reqVO);
+        // 校验是否更新正确
+        RewardActivityDO rewardActivity = rewardActivityMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, rewardActivity, "rules");
+        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus());
+        for (int i = 0; i < reqVO.getRules().size(); i++) {
+            assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i));
+        }
+    }
+
+    @Test
+    public void testCloseRewardActivity() {
+        // mock 数据
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()));
+        rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbRewardActivity.getId();
+
+        // 调用
+        rewardActivityService.closeRewardActivity(id);
+        // 校验状态
+        RewardActivityDO rewardActivity = rewardActivityMapper.selectById(id);
+        assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus());
+    }
+
+    @Test
+    public void testUpdateRewardActivity_notExists() {
+        // 准备参数
+        RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> rewardActivityService.updateRewardActivity(reqVO), REWARD_ACTIVITY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteRewardActivity_success() {
+        // mock 数据
+        RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()));
+        rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbRewardActivity.getId();
+
+        // 调用
+        rewardActivityService.deleteRewardActivity(id);
+       // 校验数据不存在了
+       assertNull(rewardActivityMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteRewardActivity_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> rewardActivityService.deleteRewardActivity(id), REWARD_ACTIVITY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGetRewardActivityPage() {
+       // mock 数据
+       RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到
+           o.setName("芋艿");
+           o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
+       });
+       rewardActivityMapper.insert(dbRewardActivity);
+       // 测试 name 不匹配
+       rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆")));
+       // 测试 status 不匹配
+       rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())));
+       // 准备参数
+       RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO();
+       reqVO.setName("芋艿");
+       reqVO.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
+
+       // 调用
+       PageResult<RewardActivityDO> pageResult = rewardActivityService.getRewardActivityPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules");
+    }
+
+    @Test
+    public void testGetRewardActivities_all() {
+        // mock 数据
+        RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
+                .setProductScope(PromotionProductScopeEnum.ALL.getScope()));
+        rewardActivityMapper.insert(allActivity);
+        RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
+        rewardActivityMapper.insert(productActivity);
+        // 准备参数
+        Set<Long> spuIds = asSet(1L, 2L);
+
+        // 调用
+        Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
+        // 断言
+        assertEquals(matchRewardActivities.size(), 1);
+        Map.Entry<RewardActivityDO, Set<Long>> next = matchRewardActivities.entrySet().iterator().next();
+        assertPojoEquals(next.getKey(), allActivity);
+        assertEquals(next.getValue(), spuIds);
+    }
+
+    @Test
+    public void testGetRewardActivities_product() {
+        // mock 数据
+        RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)));
+        rewardActivityMapper.insert(productActivity01);
+        RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus())
+                .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(singletonList(3L)));
+        rewardActivityMapper.insert(productActivity02);
+        // 准备参数
+        Set<Long> spuIds = asSet(1L, 2L, 3L);
+
+        // 调用
+        Map<RewardActivityDO, Set<Long>> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
+        // 断言
+        assertEquals(matchRewardActivities.size(), 2);
+        matchRewardActivities.forEach((activity, activitySpuIds) -> {
+            if (activity.getId().equals(productActivity01.getId())) {
+                assertPojoEquals(activity, productActivity01);
+                assertEquals(activitySpuIds, asSet(1L, 2L));
+            } else if (activity.getId().equals(productActivity02.getId())) {
+                assertPojoEquals(activity, productActivity02);
+                assertEquals(activitySpuIds, asSet(3L));
+            } else {
+                fail();
+            }
+        });
+    }
+
+}

+ 5 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/clean.sql

@@ -1 +1,6 @@
 DELETE FROM "market_activity";
+DELETE FROM "promotion_coupon_template";
+DELETE FROM "promotion_coupon";
+DELETE FROM "promotion_reward_activity";
+DELETE FROM "promotion_discount_activity";
+DELETE FROM "promotion_discount_product";

+ 106 - 1
yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql

@@ -16,4 +16,109 @@ CREATE TABLE IF NOT EXISTS "market_activity" (
     "deleted" bit NOT NULL DEFAULT FALSE,
     "tenant_id" bigint(20) NOT NULL,
     PRIMARY KEY ("id")
-    ) COMMENT '促销活动';
+    ) COMMENT '促销活动';
+
+CREATE TABLE IF NOT EXISTS "promotion_coupon_template" (
+   "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+   "name" varchar NOT NULL,
+   "status" int NOT NULL,
+   "total_count" int NOT NULL,
+   "take_limit_count" int NOT NULL,
+   "take_type" int NOT NULL,
+   "use_price" int NOT NULL,
+   "product_scope" int NOT NULL,
+   "product_spu_ids" varchar,
+   "validity_type" int NOT NULL,
+   "valid_start_time" datetime,
+   "valid_end_time" datetime,
+   "fixed_start_term" int,
+   "fixed_end_term" int,
+   "discount_type" int NOT NULL,
+   "discount_percent" int,
+   "discount_price" int,
+   "discount_limit_price" int,
+   "take_count" int NOT NULL DEFAULT 0,
+   "use_count" int NOT NULL DEFAULT 0,
+   "creator" varchar DEFAULT '',
+   "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+   "updater" varchar DEFAULT '',
+   "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+   "deleted" bit NOT NULL DEFAULT FALSE,
+   PRIMARY KEY ("id")
+) COMMENT '优惠劵模板';
+
+CREATE TABLE IF NOT EXISTS "promotion_coupon" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "template_id" bigint NOT NULL,
+    "name" varchar NOT NULL,
+    "status" int NOT NULL,
+    "user_id" bigint NOT NULL,
+    "take_type" int NOT NULL,
+    "useprice" int NOT NULL,
+    "valid_start_time" datetime NOT NULL,
+    "valid_end_time" datetime NOT NULL,
+    "product_scope" int NOT NULL,
+    "product_spu_ids" varchar,
+    "discount_type" int NOT NULL,
+    "discount_percent" int,
+    "discount_price" int,
+    "discount_limit_price" int,
+    "use_order_id" bigint,
+    "use_time" datetime,
+    "creator" varchar DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '优惠劵';
+
+CREATE TABLE IF NOT EXISTS "promotion_reward_activity" (
+   "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+   "name" varchar NOT NULL,
+   "status" int NOT NULL,
+   "start_time" datetime NOT NULL,
+   "end_time" datetime NOT NULL,
+   "remark" varchar,
+   "condition_type" int NOT NULL,
+   "product_scope" int NOT NULL,
+   "product_spu_ids" varchar,
+   "rules" varchar,
+   "creator" varchar DEFAULT '',
+   "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+   "updater" varchar DEFAULT '',
+   "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+   "deleted" bit NOT NULL DEFAULT FALSE,
+   PRIMARY KEY ("id")
+) COMMENT '满减送活动';
+
+CREATE TABLE IF NOT EXISTS "promotion_discount_activity" (
+     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+     "name" varchar NOT NULL,
+     "status" int NOT NULL,
+     "start_time" datetime NOT NULL,
+     "end_time" datetime NOT NULL,
+     "remark" varchar,
+     "creator" varchar DEFAULT '',
+     "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+     "updater" varchar DEFAULT '',
+     "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+     "deleted" bit NOT NULL DEFAULT FALSE,
+     PRIMARY KEY ("id")
+) COMMENT '限时折扣活动';
+
+CREATE TABLE IF NOT EXISTS "promotion_discount_product" (
+     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+     "activity_id" bigint NOT NULL,
+     "spu_id" bigint NOT NULL,
+     "sku_id" bigint NOT NULL,
+     "discount_type" int NOT NULL,
+     "discount_percent" int,
+     "discount_price" int,
+     "creator" varchar DEFAULT '',
+     "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+     "updater" varchar DEFAULT '',
+     "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+     "deleted" bit NOT NULL DEFAULT FALSE,
+     PRIMARY KEY ("id")
+) COMMENT '限时折扣活动';

+ 3 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java

@@ -43,13 +43,12 @@ public class AppTradeOrderController {
     @PreAuthenticated
     public CommonResult<Long> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
                                                HttpServletRequest servletRequest) {
-        // 获取登录用户
+        // 获取登录用户、用户 IP 地址
         Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
-        // 获取用户ip地址
         String clientIp = ServletUtil.getClientIP(servletRequest);
         // 创建交易订单,预支付记录
-        Long result = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
-        return CommonResult.success(result);
+        Long orderId = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
+        return CommonResult.success(orderId);
     }
 
     @GetMapping("/get")

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java

@@ -19,7 +19,7 @@ public class AppTradeOrderCreateReqVO {
     @ApiModelProperty(name = "优惠劵编号", example = "1024")
     private Long couponId;
 
-    @ApiModelProperty(name = "备注", example = "1024")
+    @ApiModelProperty(name = "备注", example = "这个是我的订单哟")
     private String remark;
 
     @ApiModelProperty(name = "是否来自购物车", required = true, example = "true", notes = "true - 来自购物车;false - 立即购买")

+ 25 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
 
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO.OrderItem;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
@@ -74,7 +75,7 @@ public class TradeOrderDO extends BaseDO {
     /**
      * 购买的商品数量
      */
-    private Integer productCount; // total_num
+    private Integer productCount;
     /**
      * 订单完成时间
      */
@@ -116,14 +117,25 @@ public class TradeOrderDO extends BaseDO {
      * 商品原价(总),单位:分
      *
      * 基于 {@link TradeOrderItemDO#getTotalOriginalPrice()} 求和
+     *
+     * 对应 taobao 的 trade.total_fee 字段
      */
     private Integer originalPrice;
+    /**
+     * 订单原价(总),单位:分
+     *
+     * 基于 {@link OrderItem#getPayPrice()} 求和
+     * 和 {@link #originalPrice} 的差异:去除商品级优惠
+     */
+    private Integer orderPrice;
     /**
      * 订单优惠(总),单位:分
      *
-     * 例如说:满减折扣;不包括优惠劵、商品优惠(TODO)
+     * 订单级优惠:对主订单的优惠,常见如:订单满 200 元减 10 元;订单满 80 包邮。
+     *
+     * 对应 taobao 的 order.discount_fee 字段
      */
-    private Integer promotionPrice;
+    private Integer discountPrice;
     /**
      * 运费金额,单位:分
      */
@@ -137,12 +149,12 @@ public class TradeOrderDO extends BaseDO {
     /**
      * 应付金额(总),单位:分
      *
-     * = {@link #originalPrice}
-     * + {@link #deliveryPrice}
-     * + {@link #adjustPrice}
-     * - {@link #promotionPrice}
+     * = {@link OrderItem#getPayPrice()} 求和
      * - {@link #couponPrice}
      * - {@link #pointPrice}
+     * + {@link #deliveryPrice}
+     * - {@link #discountPrice}
+     * + {@link #adjustPrice}
      */
     private Integer payPrice;
     /**
@@ -164,11 +176,11 @@ public class TradeOrderDO extends BaseDO {
      *
      * 关联 DeliveryTemplateDO 的 id 编号
      */
-    private Long deliveryTemplateId; // dvy_id
+    private Long deliveryTemplateId;
     /**
      * 物流公司单号
      */
-    private String expressNo; // dvy_flow_id
+    private String expressNo;
     /**
      * 发货状态
      *
@@ -227,10 +239,14 @@ public class TradeOrderDO extends BaseDO {
     private Long couponId;
     /**
      * 优惠劵减免金额,单位:分
+     *
+     * 对应 taobao 的 trade.coupon_fee 字段
      */
     private Integer couponPrice;
     /**
      * 积分抵扣的金额,单位:分
+     *
+     * 对应 taobao 的 trade.point_fee 字段
      */
     private Integer pointPrice;
 

+ 34 - 71
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java

@@ -71,7 +71,7 @@ public class TradeOrderItemDO extends BaseDO {
      */
     private Integer count;
     /**
-     * 是否评论
+     * 是否评论 TODO
      *
      * false - 未评论
      * true - 已评论
@@ -79,61 +79,61 @@ public class TradeOrderItemDO extends BaseDO {
     private Boolean commented;
 
     // ========== 价格 + 支付基本信息 ==========
+
     /**
-     * 商品原价(),单位:分
+     * 商品原价(),单位:分
      *
-     * 对应 ProductSkuDO 的 price 字段
+     * = {@link #originalUnitPrice} * {@link #getCount()}
      */
-    // like - original_price;niu - costPrice
     private Integer originalPrice;
     /**
-     * 商品原价(),单位:分
+     * 商品原价(),单位:分
      *
-     * = {@link #originalPrice} * {@link #count}
+     * 对应 ProductSkuDO 的 price 字段
+     * 对应 taobao 的 order.price 字段
      */
-    // like - total_price;niu - 暂无
-    private Integer totalOriginalPrice;
+    private Integer originalUnitPrice;
     /**
-     * 商品级优惠(总),单位:分
+     * 商品优惠(总),单位:分
+     *
+     * 商品级优惠:对单个商品的,常见如:商品原价的 8 折;商品原价的减 50 元
      *
-     * 例如说“限时折扣”:商品原价的 8 折;商品原价的减 50 元
+     * 对应 taobao 的 order.discount_fee 字段
      */
-    // taobao - order.discount_fee(子订单商品优惠)
-    private Integer totalPromotionPrice;
+    private Integer discountPrice;
     /**
-     * 最终购买金额(单),单位:分。
+     * 子订单实付金额,不算主订单分摊金额,单位:分
+     *
+     * = {@link #originalPrice}
+     * - {@link #discountPrice}
      *
-     * = {@link #totalPresentPrice} / {@link #count}
+     * 对应 taobao 的 order.payment 字段
      */
-    private Integer presentPrice;
+    private Integer payPrice;
+
     /**
-     * 最终购买金额(总),单位:分。
+     * 子订单分摊金额(总),单位:分
+     * 需要分摊 {@link TradeOrderDO#getDiscountPrice()}、{@link TradeOrderDO#getCouponPrice()}、{@link TradeOrderDO#getPointPrice()}
      *
-     * = {@link #totalOriginalPrice}
-     * - {@link #totalPromotionPrice}
+     * 对应 taobao 的 order.part_mjz_discount 字段
+     * 淘宝说明:子订单分摊优惠基础逻辑:一般正常优惠券和满减优惠按照子订单的金额进行分摊,特殊情况如果优惠券是指定商品使用的,只会分摊到对应商品子订单上不分摊。
      */
-    // like -  total_pay_price;niu - goods_money; taobao - order.payment(子订单实付金额,不算主订单分摊金额) | order.total_fee(子订单应付金额,参考使用)
-    private Integer totalPresentPrice;
-    // TODO 芋艿:part_mjz_discount(子订单分摊金额);本质上,totalOriginalPrice - totalPayPrice
+    private Integer orderPartPrice;
     /**
-     * 应付金额(总),单位:分
+     * 分摊后子订单实付金额(总),单位:分
+     *
+     * = {@link #payPrice}
+     * - {@link #orderPartPrice}
+     *
+     * 对应 taobao 的 divide_order_fee 字段
      */
-    // taobao - divide_order_fee (分摊后子订单实付金额);
-    private Integer totalPayPrice;
+    private Integer orderDividePrice;
 
     // ========== 营销基本信息 ==========
-//    /**
-//     * 积分抵扣的金额,单位:分
-//     */
-//    private Integer integralTotal; // like - integral_price;niu - point_money
-//    /**
-//     * 使用的积分
-//     */
-//    private Integer useIntegral; // niu - use_point
 
     // ========== 退款基本信息 ==========
     /**
-     * 退款状态
+     * 退款状态 TODO
      *
      * 枚举 {@link TradeOrderItemRefundStatusEnum}
      */
@@ -148,7 +148,7 @@ public class TradeOrderItemDO extends BaseDO {
     // presentTotal = buyTotal - discountTotal = 24 - 11 = 13
     // 最终 presentPrice = presentTotal / stock = 13 / 3 = 4.33
     /**
-     * 退款总金额,单位:分
+     * 退款总金额,单位:分 TODO
      */
     private Integer refundTotal;
 
@@ -173,42 +173,5 @@ public class TradeOrderItemDO extends BaseDO {
 
     }
 
-    // TODO 芋艿:basket_date 加入购物车时间;
-    // TODO 芋艿:distribution_card_no 推广员使用的推销卡号
-
-    // TODO 待确定:mf
-    // TODO give_integral:赠送积分
-    // TODO is_reply:是否评价,0-未评价,1-已评价
-    // TODO is_sub:是否单独分佣,0-否,1-是
-    // TODO vip_price:会员价
-    // TODO product_type:商品类型:0-普通,1-秒杀,2-砍价,3-拼团,4-视频号
-
-    // TODO 待确定:lf
-    // TODO integral_price:积分抵扣的金额
-    // TODO member_price:会员价格
-    // TODO is_member:是否为会员折扣;0-不是;1-是
-    // TODO member_discount:会员折扣(百分比)
-
-    // TODO goods_info 商品信息
-
-    // TODO integral_price:积分抵扣的金额
-
-    // TODO 待确定:niu
-    // TODO is_virtual '是否是虚拟商品'
-    // TODO goods_class '商品种类(1.实物 2.虚拟3.卡券)'
-    // TODO adjust_money ''调整金额''
-
-    // TODO is_fenxiao 是否分销,
-    // TODO adjust_money 是否分销,
-
-    // TODO delivery_status '配送状态'
-    // TODO delivery_no ''配送单号''
-    // TODO gift_flag '赠品标识'
-    // TODO gift_flag '赠品标识'
-
-    // TODO refund_status '退款状态'
-    // TODO refund_type '退款状态'
-    // TODO 一堆退款字段
-
 }
 

+ 6 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java

@@ -3,18 +3,20 @@ package cn.iocoder.yudao.module.trade.service.order;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
 
 /**
- * TODO @LeeYan9: 类注释
+ * 交易订单 Service 接口
+ *
  * @author LeeYan9
  * @since 2022-08-26
  */
 public interface TradeOrderService {
 
     /**
-     * 创建交易订单 TODO @LeeYan9: 方法注释, 和参数要空一行
+     * 创建交易订单
+     *
      * @param loginUserId 登录用户
-     * @param clientIp 用户ip地址 // TODO @LeeYan9: 中英文之间, 空一行哈
+     * @param clientIp 用户 IP 地址
      * @param createReqVO 创建交易订单请求模型
-     * @return 交易订单创建结果
+     * @return 交易订单的编号
      */
     Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO);
 }

+ 6 - 8
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java

@@ -45,14 +45,17 @@ import java.util.Map;
 import java.util.Objects;
 
 /**
- * TODO @LeeYan9: 注释
+ * 交易订单 Service 实现类
+ *
  * @author LeeYan9
  * @since 2022-08-26
  */
 @Service
 public class TradeOrderServiceImpl implements TradeOrderService {
 
-    // TODO @LeeYan9: 相同类型的, 可以放在一起,不用空行; 例如说 Mapper 和 API 和 Properties
+    // TODO LeeYan9: 静态变量, 需要在最前面哈; 另外, 静态变量的注释最好写下;
+    private static final String BLANK_PLACEHOLDER = " ";
+    private static final String MULTIPLIER_PLACEHOLDER = "x";
 
     @Resource
     private TradeOrderMapper tradeOrderMapper;
@@ -71,15 +74,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     @Resource
     private TradeOrderProperties tradeOrderProperties;
 
-    // TODO LeeYan9: 静态变量, 需要在最前面哈; 另外, 静态变量的注释最好写下;
-    private static final String BLANK_PLACEHOLDER = " ";
-    private static final String MULTIPLIER_PLACEHOLDER = "x";
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
-
-        List<Item> items = createReqVO.getItems(); // TODO @LeeYan9: 方法第一行, 不用空哈;
+        List<Item> items = createReqVO.getItems();
         // 商品SKU检查 sku可售状态,库存
         List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
         Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);

+ 32 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java

@@ -2,6 +2,12 @@ package cn.iocoder.yudao.module.member.api.user;
 
 import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
 /**
  * 会员用户的 API 接口
  *
@@ -17,4 +23,30 @@ public interface MemberUserApi {
      */
     UserRespDTO getUser(Long id);
 
+    /**
+     * 获得会员用户信息们
+     *
+     * @param ids 用户编号的数组
+     * @return 用户信息们
+     */
+    List<UserRespDTO> getUsers(Collection<Long> ids);
+
+    /**
+     * 获得会员用户 Map
+     *
+     * @param ids 用户编号的数组
+     * @return 会员用户 Map
+     */
+    default Map<Long, UserRespDTO> getUserMap(Collection<Long> ids) {
+        return convertMap(getUsers(ids), UserRespDTO::getId);
+    }
+
+    /**
+     * 基于用户昵称,模糊匹配用户列表
+     *
+     * @param nickname 用户昵称,模糊匹配
+     * @return 用户信息的列表
+     */
+    List<UserRespDTO> getUserListByNickname(String nickname);
+
 }

+ 0 - 52
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/UserInfoDTO.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.member.api.user.dto;
-
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import lombok.Data;
-
-import java.util.Date;
-
-/**
- * 用户信息 Response DTO
- *
- * @author 芋道源码
- */
-@Data
-public class UserInfoDTO {
-
-    /**
-     * 用户ID
-     */
-    private Long id;
-    /**
-     * 用户昵称
-     */
-    private String nickname;
-    /**
-     * 用户头像
-     */
-    private String avatar;
-    /**
-     * 帐号状态
-     *
-     * 枚举 {@link CommonStatusEnum}
-     */
-    private Integer status;
-
-    /**
-     * 手机
-     */
-    private String mobile;
-    /**
-     * 注册 IP
-     */
-    private String registerIp;
-    /**
-     * 最后登录IP
-     */
-    private String loginIp;
-    /**
-     * 最后登录时间
-     */
-    private Date loginDate;
-
-}

+ 12 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java

@@ -8,6 +8,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * 会员用户的 API 实现类
@@ -27,4 +29,14 @@ public class MemberUserApiImpl implements MemberUserApi {
         return UserConvert.INSTANCE.convert2(user);
     }
 
+    @Override
+    public List<UserRespDTO> getUsers(Collection<Long> ids) {
+        return UserConvert.INSTANCE.convertList2(userService.getUserList(ids));
+    }
+
+    @Override
+    public List<UserRespDTO> getUserListByNickname(String nickname) {
+        return UserConvert.INSTANCE.convertList2(userService.getUserListByNickname(nickname));
+    }
+
 }

+ 0 - 39
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/UserController.java

@@ -1,39 +0,0 @@
-package cn.iocoder.yudao.module.member.controller.admin.user;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.member.api.user.dto.UserInfoDTO;
-import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
-import cn.iocoder.yudao.module.member.convert.user.UserConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
-
-/**
- * @author Banging
- */
-@Slf4j
-@Api("用户管理")
-@RestController(value = "memberUserController")
-@RequestMapping("/user")
-public class UserController {
-
-    @Resource
-    private MemberUserService userService;
-
-    @ApiOperation(value = "用户信息获取",notes = "用户基本信息的获取")
-    @GetMapping("/{tel}")
-    public CommonResult<UserInfoDTO> getUserInfo(@PathVariable String tel){
-        MemberUserDO user = userService.getUserByMobile(tel);
-        return CommonResult.success(UserConvert.INSTANCE.convertInfo(user));
-    }
-}

+ 5 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/UserConvert.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.member.convert.user;
 
-import cn.iocoder.yudao.module.member.api.user.dto.UserInfoDTO;
 import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
+import java.util.List;
+
 @Mapper
 public interface UserConvert {
 
@@ -15,5 +16,7 @@ public interface UserConvert {
     AppUserInfoRespVO convert(MemberUserDO bean);
 
     UserRespDTO convert2(MemberUserDO bean);
-    UserInfoDTO convertInfo(MemberUserDO bean);
+
+    List<UserRespDTO> convertList2(List<MemberUserDO> list);
+
 }

+ 8 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java

@@ -1,9 +1,12 @@
 package cn.iocoder.yudao.module.member.dal.mysql.user;
 
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 /**
  * 会员 User Mapper
  *
@@ -16,4 +19,9 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
         return selectOne(MemberUserDO::getMobile, mobile);
     }
 
+    default List<MemberUserDO> selectListByNicknameLike(String nickname) {
+        return selectList(new LambdaQueryWrapperX<MemberUserDO>()
+                .likeIfPresent(MemberUserDO::getNickname, nickname));
+    }
+
 }

+ 19 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobile
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 
 import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * 会员用户 Service 接口
@@ -21,6 +23,15 @@ public interface MemberUserService {
      */
     MemberUserDO getUserByMobile(String mobile);
 
+    /**
+     * 基于用户昵称,模糊匹配用户列表
+     *
+     * @param nickname 用户昵称,模糊匹配
+     * @return 用户信息的列表
+     */
+    List<MemberUserDO> getUserListByNickname(String nickname);
+
+
     /**
      * 基于手机号创建用户。
      * 如果用户已经存在,则直接进行返回
@@ -47,6 +58,14 @@ public interface MemberUserService {
      */
     MemberUserDO getUser(Long id);
 
+    /**
+     * 通过用户 ID 查询用户们
+     *
+     * @param ids 用户 ID
+     * @return 用户对象信息数组
+     */
+    List<MemberUserDO> getUserList(Collection<Long> ids);
+
     /**
      * 修改用户昵称
      * @param userId 用户id

+ 12 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -19,7 +19,9 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.io.InputStream;
+import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -51,6 +53,11 @@ public class MemberUserServiceImpl implements MemberUserService {
         return memberUserMapper.selectByMobile(mobile);
     }
 
+    @Override
+    public List<MemberUserDO> getUserListByNickname(String nickname) {
+        return memberUserMapper.selectListByNicknameLike(nickname);
+    }
+
     @Override
     public MemberUserDO createUserIfAbsent(String mobile, String registerIp) {
         // 用户已经存在
@@ -86,6 +93,11 @@ public class MemberUserServiceImpl implements MemberUserService {
         return memberUserMapper.selectById(id);
     }
 
+    @Override
+    public List<MemberUserDO> getUserList(Collection<Long> ids) {
+        return memberUserMapper.selectBatchIds(ids);
+    }
+
     @Override
     public void updateUserNickname(Long userId, String nickname) {
         MemberUserDO user = this.checkUserExists(userId);

+ 3 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleUpdateStatusReqVO.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.system.controller.admin.permission.vo.role;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -16,7 +18,7 @@ public class RoleUpdateStatusReqVO {
 
     @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 CommonStatusEnum 枚举")
     @NotNull(message = "状态不能为空")
-//    @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
+    @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
     private Integer status;
 
 }

+ 1 - 0
yudao-server/src/main/resources/application-local.yaml

@@ -174,6 +174,7 @@ logging:
     cn.iocoder.yudao.module.tool.dal.mysql: debug
     cn.iocoder.yudao.module.member.dal.mysql: debug
     cn.iocoder.yudao.module.trade.dal.mysql: debug
+    cn.iocoder.yudao.module.promotion.dal.mysql: debug
 
 --- #################### 微信公众号、小程序相关配置 ####################
 wx:

+ 0 - 54
yudao-ui-admin/src/api/mall/CouponTemplete/CouponTemplete.js

@@ -1,54 +0,0 @@
-import request from '@/utils/request'
-
-// 创建优惠券模板
-export function create(data) {
-  return request({
-    url: '/coupon/template/create',
-    method: 'post',
-    data: data
-  })
-}
-
-// 更新优惠券模板
-export function update(data) {
-  return request({
-    url: '/coupon/template/update',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除优惠券模板
-export function deleteCouponTemplete (id) {
-  return request({
-    url: '/coupon/template/delete?id=' + id,
-    method: 'delete'
-  })
-}
-
-// 获得优惠券模板
-export function get(id) {
-  return request({
-    url: '/coupon/template/get?id=' + id,
-    method: 'get'
-  })
-}
-
-// 获得优惠券模板分页
-export function getPage(query) {
-  return request({
-    url: '/coupon/template/page',
-    method: 'get',
-    params: query
-  })
-}
-
-// 导出优惠券模板 Excel
-export function exportExcel(query) {
-  return request({
-    url: '/coupon/template/export-excel',
-    method: 'get',
-    params: query,
-    responseType: 'blob'
-  })
-}

+ 9 - 0
yudao-ui-admin/src/api/mall/product/sku.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 获得商品 SKU 选项的列表
+export function getSkuOptionList() {
+  return request({
+    url: '/product/sku/get-option-list',
+    method: 'get',
+  })
+}

+ 13 - 5
yudao-ui-admin/src/api/mall/product/spu.js

@@ -1,6 +1,6 @@
 import request from '@/utils/request'
 
-// 创建商品spu
+// 创建商品 SPU
 export function createSpu(data) {
   return request({
     url: '/product/spu/create',
@@ -9,7 +9,7 @@ export function createSpu(data) {
   })
 }
 
-// 更新商品spu
+// 更新商品 SPU
 export function updateSpu(data) {
   return request({
     url: '/product/spu/update',
@@ -18,7 +18,7 @@ export function updateSpu(data) {
   })
 }
 
-// 删除商品spu
+// 删除商品 SPU
 export function deleteSpu(id) {
   return request({
     url: '/product/spu/delete?id=' + id,
@@ -34,7 +34,7 @@ export function getSpu(id) {
   })
 }
 
-// 获得商品spu详情
+// 获得商品 SPU 详情
 export function getSpuDetail(id) {
   return request({
     url: '/product/spu/get/detail?id=' + id,
@@ -42,7 +42,7 @@ export function getSpuDetail(id) {
   })
 }
 
-// 获得商品spu分页
+// 获得商品 SPU 分页
 export function getSpuPage(query) {
   return request({
     url: '/product/spu/page',
@@ -50,3 +50,11 @@ export function getSpuPage(query) {
     params: query
   })
 }
+
+// 获得商品 SPU 精简列表
+export function getSpuSimpleList() {
+  return request({
+    url: '/product/spu/get-simple-list',
+    method: 'get',
+  })
+}

部分文件因为文件数量过多而无法显示