Эх сурвалжийг харах

promotion:增加限时折扣的基础代码

YunaiV 2 жил өмнө
parent
commit
d4b7f4aaa0
28 өөрчлөгдсөн 1139 нэмэгдсэн , 113 устгасан
  1. 10 5
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  2. 83 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/DiscountActivityController.java
  3. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/package-info.java
  4. 56 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityBaseVO.java
  5. 23 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityCreateReqVO.java
  6. 27 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityPageReqVO.java
  7. 28 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityRespVO.java
  8. 28 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityUpdateReqVO.java
  9. 49 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/DiscountActivityConvert.java
  10. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/discount/package-info.java
  11. 1 23
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/discount/DiscountProductDO.java
  12. 30 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountActivityMapper.java
  13. 22 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java
  14. 0 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/package-info.java
  15. 7 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
  16. 76 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java
  17. 169 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java
  18. 0 25
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountService.java
  19. 0 29
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountServiceImpl.java
  20. 42 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/bo/DiscountProductDetailBO.java
  21. 6 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceImpl.java
  22. 10 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
  23. 137 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java
  24. 6 6
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceTest.java
  25. 2 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/clean.sql
  26. 29 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql
  27. 52 0
      yudao-ui-admin/src/api/mall/promotion/discountActivity.js
  28. 246 0
      yudao-ui-admin/src/views/mall/promotion/discountActivity/index.vue

+ 10 - 5
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 不存在");
@@ -29,7 +34,7 @@ public interface ErrorCodeConstants {
 
     // ========== 满减送活动 1003006000 ==========
     ErrorCode REWARD_ACTIVITY_NOT_EXISTS = new ErrorCode(1003006000, "满减送活动不存在");
-    ErrorCode REWARD_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1003006001, "商品({}) 已经参加满减送活动({})");
+    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, "满减送活动已关闭,不能重复关闭");

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

@@ -0,0 +1,83 @@
+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.DiscountActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityRespVO;
+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.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 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<DiscountActivityRespVO> getDiscountActivity(@RequestParam("id") Long id) {
+        DiscountActivityDO discountActivity = discountActivityService.getDiscountActivity(id);
+        return success(DiscountActivityConvert.INSTANCE.convert(discountActivity));
+    }
+
+    @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;

+ 56 - 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,56 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
+
+import io.swagger.annotations.ApiModel;
+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 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 = "1000")
+        @NotNull(message = "折扣价格不能为空")
+        @Min(value = 1, message = "折扣价格必须大于 0")
+        private Integer discountPrice;
+
+    }
+}

+ 23 - 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,23 @@
+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.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("管理后台 - 限时折扣活动创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DiscountActivityCreateReqVO extends DiscountActivityBaseVO {
+
+    /**
+     * 商品列表
+     */
+    @NotNull(message = "商品列表不能为空")
+    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;
+
+}

+ 28 - 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,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.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;
+
+    /**
+     * 商品列表
+     */
+    @NotNull(message = "商品列表不能为空")
+    private List<DiscountActivityCreateReqVO.Product> products;
+
+}

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

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.promotion.convert.discount;
+
+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.DiscountActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityRespVO;
+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 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;
+        });
+    }
+}

+ 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;

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

@@ -7,8 +7,6 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.util.Date;
-
 /**
  * 限时折扣商品 DO
  *
@@ -31,12 +29,6 @@ public class DiscountProductDO extends BaseDO {
      * 关联 {@link DiscountActivityDO#getId()}
      */
     private Long activityId;
-    /**
-     * 限时折扣活动的名字
-     *
-     * 冗余 {@link DiscountActivityDO#getName()}
-     */
-    private String activityName;
     /**
      * 商品 SPU 编号
      *
@@ -49,23 +41,9 @@ public class DiscountProductDO extends BaseDO {
      * 关联 ProductSkuDO 的 id 编号
      */
     private Long skuId;
-    /**
-     * 开始时间
-     */
-    private Date startTime;
-    /**
-     * 结束时间
-     */
-    private Date endTime;
-    /**
-     * 销售价格,单位:分
-     *
-     * 冗余 ProductSkuDO 的 price 字段
-     */
-    private Integer originalPrice;
     /**
      * 优惠价格,单位:分
      */
-    private Integer promotionPrice;
+    private Integer discountPrice;
 
 }

+ 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));
+    }
+
+}

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

@@ -0,0 +1,22 @@
+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);
+    }
+
+}

+ 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;

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

@@ -7,6 +7,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi
 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
  *
@@ -22,4 +25,8 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
                 .orderByDesc(RewardActivityDO::getId));
     }
 
+    default List<RewardActivityDO> selectListByStatus(Collection<Integer> statuses) {
+        return selectList(RewardActivityDO::getStatus, statuses);
+    }
+
 }

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

@@ -0,0 +1,76 @@
+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 javax.validation.Valid;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * 限时折扣 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface DiscountActivityService {
+
+    /**
+     * 基于指定 SKU 编号数组,获得匹配的限时折扣商品
+     *
+     * 注意,匹配的条件,仅仅是日期符合,并且处于开启状态
+     *
+     * @param skuIds SKU 编号数组
+     * @return 匹配的限时折扣商品
+     */
+    Map<Long, DiscountProductDO> 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);
+
+}

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

@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.module.promotion.service.discount;
+
+import cn.hutool.core.collection.CollUtil;
+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.convertSet;
+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;
+
+    // TODO 芋艿:待实现
+    @Override
+    public Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds) {
+        Map<Long, DiscountProductDO> products = new HashMap<>();
+        products.put(1L, new DiscountProductDO().setDiscountPrice(100));
+        products.put(2L, new DiscountProductDO().setDiscountPrice(50));
+        return products;
+    }
+
+    @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);
+        // 返回
+        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);
+    }
+
+
+    /**
+     * 校验商品是否冲突
+     *
+     * @param id 编号
+     * @param products 商品列表
+     */
+    private void validateDiscountActivityProductConflicts(Long id, List<DiscountActivityBaseVO.Product> products) {
+        if (CollUtil.isEmpty(products)) {
+            return;
+        }
+        // 查询商品参加的活动
+        List<DiscountProductDetailBO> discountActivityProductList = getRewardActivityListBySkuIds(
+                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> getRewardActivityListBySkuIds(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);
+    }
+
+}

+ 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;
-    }
-
-}

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

@@ -0,0 +1,42 @@
+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 discountPrice;
+
+    // ========== DiscountActivityDO 字段 ==========
+    /**
+     * 活动标题
+     */
+    private String activityName;
+
+}

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

@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProduct
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 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.discount.DiscountActivityService;
 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.reward.RewardActivityService;
@@ -54,7 +54,7 @@ import static java.util.Collections.singletonList;
 public class PriceServiceImpl implements PriceService {
 
     @Resource
-    private DiscountService discountService;
+    private DiscountActivityService discountService;
     @Resource
     private RewardActivityService rewardActivityService;
     @Resource
@@ -121,7 +121,7 @@ public class PriceServiceImpl implements PriceService {
             Double memberDiscountPercent = memberDiscountPercentSupplier.get();
             DiscountProductDO discountProduct = discountProducts.get(orderItem.getSkuId());
             if (discountProduct != null // 假设优惠价格更贵,则认为没优惠
-                    && discountProduct.getPromotionPrice() >= orderItem.getOriginalUnitPrice()) {
+                    && discountProduct.getDiscountPrice() >= orderItem.getOriginalUnitPrice()) {
                 discountProduct = null;
             }
             if (memberDiscountPercent == null && discountProduct == null) {
@@ -129,7 +129,7 @@ public class PriceServiceImpl implements PriceService {
             }
             // 计算价格,判断选择哪个折扣
             Integer memberPrice = memberDiscountPercent != null ? (int) (orderItem.getPayPrice() * memberDiscountPercent / 100) : null;
-            Integer promotionPrice = discountProduct != null ? discountProduct.getPromotionPrice() * orderItem.getCount() : null;
+            Integer promotionPrice = discountProduct != null ? discountProduct.getDiscountPrice() * orderItem.getCount() : null;
             if (memberPrice == null) {
                 calculatePriceByDiscountActivity(priceCalculate, orderItem, discountProduct, promotionPrice);
             } else if (promotionPrice == null) {
@@ -155,7 +155,8 @@ public class PriceServiceImpl implements PriceService {
     private void calculatePriceByDiscountActivity(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem,
                                                   DiscountProductDO discountProduct, Integer promotionPrice) {
         // 记录优惠明细
-        addPromotion(priceCalculate, orderItem, discountProduct.getActivityId(), discountProduct.getActivityName(),
+        addPromotion(priceCalculate, orderItem, discountProduct.getActivityId(), null
+                /* TODO 芋艿:修复下 discountProduct.getActivityName()*/,
                 PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), PromotionLevelEnum.SKU.getLevel(), promotionPrice,
                 true, StrUtil.format("限时折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - promotionPrice)));
         // 修改 SKU 的优惠

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

@@ -14,13 +14,11 @@ 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 java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
+import static java.util.Arrays.asList;
 
 /**
  * 满减送活动 Service 实现类
@@ -54,6 +52,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
         if (dbRewardActivity.getStatus().equals(PromotionActivityStatusEnum.CLOSE.getStatus())) { // 已关闭的活动,不能修改噢
             throw exception(REWARD_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
         }
+        // 校验商品是否冲突
         validateRewardActivitySpuConflicts(updateReqVO.getId(), updateReqVO.getProductSpuIds());
 
         // 更新
@@ -109,8 +108,9 @@ public class RewardActivityServiceImpl implements RewardActivityService {
             return;
         }
         // 查询商品参加的活动
-        List<RewardActivityDO> rewardActivityList = getRewardActivityListBySpuIds(spuIds);
-        if (id != null) { // 排除活动
+        List<RewardActivityDO> rewardActivityList = getRewardActivityListBySpuIds(spuIds,
+                asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus()));
+        if (id != null) { // 排除自己这个活动
             rewardActivityList.removeIf(activity -> id.equals(activity.getId()));
         }
         // 如果非空,则说明冲突
@@ -123,10 +123,12 @@ public class RewardActivityServiceImpl implements RewardActivityService {
      * 获得商品参加的满减送活动的数组
      *
      * @param spuIds 商品 SPU 编号数组
+     * @param statuses 活动状态数组
      * @return 商品参加的满减送活动的数组
      */
-    private List<RewardActivityDO> getRewardActivityListBySpuIds(Collection<Long> spuIds) {
-        List<RewardActivityDO> list = rewardActivityMapper.selectList();
+    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));
     }
 

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

@@ -0,0 +1,137 @@
+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.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.mysql.discount.DiscountActivityMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
+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 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 org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link DiscountActivityServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(DiscountActivityServiceImpl.class)
+public class DiscountActivityServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private DiscountActivityServiceImpl discountActivityService;
+
+    @Resource
+    private DiscountActivityMapper discountActivityMapper;
+
+    @Test
+    public void testCreateDiscountActivity_success() {
+        // 准备参数
+        DiscountActivityCreateReqVO reqVO = randomPojo(DiscountActivityCreateReqVO.class, o -> {
+            // 用于触发进行中的状态
+            o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2)));
+        });
+
+        // 调用
+        Long discountActivityId = discountActivityService.createDiscountActivity(reqVO);
+        // 断言
+        assertNotNull(discountActivityId);
+        // 校验记录的属性是否正确
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(discountActivityId);
+        assertPojoEquals(reqVO, discountActivity);
+    }
+
+    @Test
+    public void testUpdateDiscountActivity_success() {
+        // mock 数据
+        DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class);
+        discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        DiscountActivityUpdateReqVO reqVO = randomPojo(DiscountActivityUpdateReqVO.class, o -> {
+            o.setId(dbDiscountActivity.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        discountActivityService.updateDiscountActivity(reqVO);
+        // 校验是否更新正确
+        DiscountActivityDO discountActivity = discountActivityMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, discountActivity);
+    }
+
+    @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);
+        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));
+    }
+
+}

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

@@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProduct
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 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.discount.DiscountActivityService;
 import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
@@ -41,7 +41,7 @@ public class PriceServiceTest extends BaseMockitoUnitTest {
     private PriceServiceImpl priceService;
 
     @Mock
-    private DiscountService discountService;
+    private DiscountActivityService discountService;
     @Mock
     private RewardActivityService rewardActivityService;
     @Mock
@@ -109,10 +109,10 @@ 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));
+        DiscountProductDO discountProduct01 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(1000L)/*.setActsivityName("活动 1000 号") TODO 芋艿:待完善 */
+                .setSkuId(10L).setDiscountPrice(80));
+        DiscountProductDO discountProduct02 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(2000L)/*.setActivityName("活动 2000 号") TODO 芋艿:待完善 */
+                .setSkuId(20L).setDiscountPrice(80));
         when(discountService.getMatchDiscountProducts(eq(asSet(10L, 20L)))).thenReturn(
                 MapUtil.builder(10L, discountProduct01).put(20L, discountProduct02).map());
 

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

@@ -2,3 +2,5 @@ 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";

+ 29 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql

@@ -91,3 +91,32 @@ CREATE TABLE IF NOT EXISTS "promotion_reward_activity" (
    "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_price" int NOT NULL,
+     "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 '限时折扣活动';

+ 52 - 0
yudao-ui-admin/src/api/mall/promotion/discountActivity.js

@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 创建限时折扣活动
+export function createDiscountActivity(data) {
+  return request({
+    url: '/promotion/discount-activity/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新限时折扣活动
+export function updateDiscountActivity(data) {
+  return request({
+    url: '/promotion/discount-activity/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 关闭限时折扣活动
+export function closeDiscountActivity(id) {
+  return request({
+    url: '/promotion/discount-activity/close?id=' + id,
+    method: 'put'
+  })
+}
+
+// 删除限时折扣活动
+export function deleteDiscountActivity(id) {
+  return request({
+    url: '/promotion/discount-activity/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得限时折扣活动
+export function getDiscountActivity(id) {
+  return request({
+    url: '/promotion/discount-activity/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得限时折扣活动分页
+export function getDiscountActivityPage(query) {
+  return request({
+    url: '/promotion/discount-activity/page',
+    method: 'get',
+    params: query
+  })
+}

+ 246 - 0
yudao-ui-admin/src/views/mall/promotion/discountActivity/index.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="活动名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入活动名称" clearable @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="活动状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择活动状态" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)"
+                       :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['promotion:discount-activity:create']">新增</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="活动名称" align="center" prop="name" />
+      <el-table-column label="活动时间" align="center" prop="startTime" width="240">
+        <template slot-scope="scope">
+          <div>开始:{{ parseTime(scope.row.startTime) }}</div>
+          <div>结束:{{ parseTime(scope.row.endTime) }}</div>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :type="DICT_TYPE.PROMOTION_ACTIVITY_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type"
+                     v-hasPermi="['promotion:discount-activity:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleClose(scope.row)"
+                     v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type &&
+                            scope.row.status !== PromotionActivityStatusEnum.END.type"
+                     v-hasPermi="['promotion:discount-activity:close']">关闭</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-if="scope.row.status === PromotionActivityStatusEnum.CLOSE.type"
+                     v-hasPermi="['promotion:discount-activity:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="600px" v-dialogDrag append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="活动名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入活动名称" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+        <el-form-item label="活动时间" prop="startAndEndTime">
+          <el-date-picker clearable v-model="form.startAndEndTime" type="datetimerange" :default-time="['00:00:00', '23:59:59']"
+                          value-format="timestamp" placeholder="选择开始时间" style="width: 480px" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { createDiscountActivity, updateDiscountActivity, deleteDiscountActivity, getDiscountActivity, getDiscountActivityPage } from "@/api/mall/promotion/discountActivity";
+import {PromotionActivityStatusEnum, PromotionProductScopeEnum} from "@/utils/constants";
+import {closeRewardActivity} from "@/api/mall/promotion/rewardActivity";
+
+export default {
+  name: "DiscountActivity",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 限时折扣活动列表
+      list: [],
+      // 弹出层名称
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: null,
+        status: null,
+        createTime: [],
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [{ required: true, message: "活动名称不能为空", trigger: "blur" }],
+        startAndEndTime: [{ required: true, message: "活动时间不能为空", trigger: "blur" }],
+      },
+      // 商品列表
+      productSpus: [], // TODO 芋艿:需要重新写下
+      // 如下的变量,主要为了 v-if 判断可以使用到
+      PromotionProductScopeEnum: PromotionProductScopeEnum,
+      PromotionActivityStatusEnum: PromotionActivityStatusEnum,
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 执行查询
+      getDiscountActivityPage(this.queryParams).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        startAndEndTime: undefined,
+        startTime: undefined,
+        endTime: undefined,
+        remark: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加限时折扣活动";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getDiscountActivity(id).then(response => {
+        this.form = response.data;
+        this.form.startAndEndTime = [response.data.startTime, response.data.endTime];
+        this.open = true;
+        this.title = "修改限时折扣活动";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        this.form.startTime = this.form.startAndEndTime[0];
+        this.form.endTime = this.form.startAndEndTime[1];
+        // 修改的提交
+        if (this.form.id != null) {
+          updateDiscountActivity(this.form).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createDiscountActivity(this.form).then(response => {
+          this.$modal.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认删除限时折扣活动编号为"' + id + '"的数据项?').then(function() {
+          return deleteDiscountActivity(id);
+        }).then(() => {
+          this.getList();
+          this.$modal.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 关闭按钮操作 */
+    handleClose(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认关闭限时折扣活动编号为"' + id + '"的数据项?').then(function() {
+        return closeRewardActivity(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("关闭成功");
+      }).catch(() => {});
+    }
+  }
+};
+</script>