适配 mall 模块的 openapi

This commit is contained in:
YunaiV
2023-02-04 13:06:54 +08:00
parent 71cb5fdcb8
commit 6bf5eb0133
339 changed files with 21163 additions and 11 deletions

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-mall</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>yudao-module-promotion-biz</artifactId>
<name>${project.artifactId}</name>
<description>
market模块主要实现营销相关功能
例如营销活动、banner广告、优惠券、优惠码等功能。
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-promotion-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<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>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.api.coupon;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 优惠劵 API 实现类
*
* @author 芋道源码
*/
@Service
public class CouponApiImpl implements CouponApi {
@Resource
private CouponService couponService;
@Override
public void useCoupon(CouponUseReqDTO useReqDTO) {
couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
useReqDTO.getOrderId());
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.yudao.module.promotion.api.discount;

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.promotion.api.price;
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.service.price.PriceService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 价格 API 实现类
*
* @author 芋道源码
*/
@Service
public class PriceApiImpl implements PriceApi {
@Resource
private PriceService priceService;
@Override
public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
return priceService.calculatePrice(calculateReqDTO);
}
}

View File

@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.banner.BannerConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import cn.iocoder.yudao.module.promotion.service.banner.BannerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - Banner 管理")
@RestController
@RequestMapping("/market/banner")
@Validated
public class BannerController {
@Resource
private BannerService bannerService;
@PostMapping("/create")
@Operation(summary = "创建 Banner")
@PreAuthorize("@ss.hasPermission('market:banner:create')")
public CommonResult<Long> createBanner(@Valid @RequestBody BannerCreateReqVO createReqVO) {
return success(bannerService.createBanner(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新 Banner")
@PreAuthorize("@ss.hasPermission('market:banner:update')")
public CommonResult<Boolean> updateBanner(@Valid @RequestBody BannerUpdateReqVO updateReqVO) {
bannerService.updateBanner(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除 Banner")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('market:banner:delete')")
public CommonResult<Boolean> deleteBanner(@RequestParam("id") Long id) {
bannerService.deleteBanner(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得 Banner")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('market:banner:query')")
public CommonResult<BannerRespVO> getBanner(@RequestParam("id") Long id) {
BannerDO banner = bannerService.getBanner(id);
return success(BannerConvert.INSTANCE.convert(banner));
}
@GetMapping("/page")
@Operation(summary = "获得 Banner 分页")
@PreAuthorize("@ss.hasPermission('market:banner:query')")
public CommonResult<PageResult<BannerRespVO>> getBannerPage(@Valid BannerPageReqVO pageVO) {
PageResult<BannerDO> pageResult = bannerService.getBannerPage(pageVO);
return success(BannerConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* Banner Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
* @author xia
*/
@Data
public class BannerBaseVO {
@Schema(description = "标题", required = true)
@NotNull(message = "标题不能为空")
private String title;
@Schema(description = "跳转链接", required = true)
@NotNull(message = "跳转链接不能为空")
private String url;
@Schema(description = "图片地址", required = true)
@NotNull(message = "图片地址不能为空")
private String picUrl;
@Schema(description = "排序", required = true)
@NotNull(message = "排序不能为空")
private Integer sort;
@Schema(description = "状态", required = true)
@NotNull(message = "状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "备注")
private String memo;
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author xia
*/
@Schema(description = "管理后台 - Banner 创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BannerCreateReqVO extends BannerBaseVO {
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* @author xia
*/
@Schema(description = "管理后台 - Banner 分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BannerPageReqVO extends PageParam {
@Schema(description = "标题")
private String title;
@Schema(description = "状态")
@InEnum(CommonStatusEnum.class)
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* @author xia
*/
@Schema(description = "管理后台 - Banner Response VO")
@Data
@ToString(callSuper = true)
public class BannerRespVO extends BannerBaseVO {
@Schema(description = "banner编号", required = true)
@NotNull(message = "banner编号不能为空")
private Long id;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
/**
* @author xia
*/
@Schema(description = "管理后台 - Banner更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BannerUpdateReqVO extends BannerBaseVO {
@Schema(description = "banner 编号", required = true)
@NotNull(message = "banner 编号不能为空")
private Long id;
}

View File

@@ -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.MemberUserRespDTO;
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.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - 优惠劵")
@RestController
@RequestMapping("/promotion/coupon")
@Validated
public class CouponController {
@Resource
private CouponService couponService;
@Resource
private MemberUserApi memberUserApi;
// @GetMapping("/get")
// @Operation(summary = "获得优惠劵")
// @Parameter(name = "id", description = "编号", required = true, example = "1024")
// @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")
@Operation(summary = "回收优惠劵")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:coupon:delete')")
public CommonResult<Boolean> deleteCoupon(@RequestParam("id") Long id) {
couponService.deleteCoupon(id);
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得优惠劵分页")
@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, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
return success(pageResulVO);
}
}

View File

@@ -0,0 +1,79 @@
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.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;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - 优惠劵模板")
@RestController
@RequestMapping("/promotion/coupon-template")
@Validated
public class CouponTemplateController {
@Resource
private CouponTemplateService couponTemplateService;
@PostMapping("/create")
@Operation(summary = "创建优惠劵模板")
@PreAuthorize("@ss.hasPermission('promotion:coupon-template:create')")
public CommonResult<Long> createCouponTemplate(@Valid @RequestBody CouponTemplateCreateReqVO createReqVO) {
return success(couponTemplateService.createCouponTemplate(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新优惠劵模板")
@PreAuthorize("@ss.hasPermission('promotion:coupon-template:update')")
public CommonResult<Boolean> updateCouponTemplate(@Valid @RequestBody CouponTemplateUpdateReqVO updateReqVO) {
couponTemplateService.updateCouponTemplate(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新优惠劵模板状态")
@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")
@Operation(summary = "删除优惠劵模板")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:coupon-template:delete')")
public CommonResult<Boolean> deleteCouponTemplate(@RequestParam("id") Long id) {
couponTemplateService.deleteCouponTemplate(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得优惠劵模板")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:coupon-template:query')")
public CommonResult<CouponTemplateRespVO> getCouponTemplate(@RequestParam("id") Long id) {
CouponTemplateDO couponTemplate = couponTemplateService.getCouponTemplate(id);
return success(CouponTemplateConvert.INSTANCE.convert(couponTemplate));
}
@GetMapping("/page")
@Operation(summary = "获得优惠劵模板分页")
@PreAuthorize("@ss.hasPermission('promotion:coupon-template:query')")
public CommonResult<PageResult<CouponTemplateRespVO>> getCouponTemplatePage(@Valid CouponTemplatePageReqVO pageVO) {
PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(pageVO);
return success(CouponTemplateConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -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.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
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 ==========
@Schema(description = "优惠劵模板编号", required = true, example = "1024")
@NotNull(message = "优惠劵模板编号不能为空")
private Integer templateId;
@Schema(description = "优惠劵名", required = true, example = "春节送送送")
@NotNull(message = "优惠劵名不能为空")
private String name;
@Schema(description = "优惠码状态", required = true, example = "1")
private Integer status;
// ========== 基本信息 END ==========
// ========== 领取情况 BEGIN ==========
@Schema(description = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "领取方式", required = true, example = "1")
@NotNull(message = "领取方式不能为空")
private Integer takeType;
// ========== 领取情况 END ==========
// ========== 使用规则 BEGIN ==========
@Schema(description = "是否设置满多少金额可用", required = true, example = "100") // 单位0 - 不限制
@NotNull(message = "是否设置满多少金额可用不能为空")
private Integer usePrice;
@Schema(description = "固定日期 - 生效开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime validStartTime;
@Schema(description = "固定日期 - 生效结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime validEndTime;
@Schema(description = "商品范围", required = true, example = "1")
@NotNull(message = "商品范围不能为空")
@InEnum(PromotionProductScopeEnum.class)
private Integer productScope;
@Schema(description = "商品 SPU 编号的数组", example = "1,3")
private List<Long> productSpuIds;
// ========== 使用规则 END ==========
// ========== 使用效果 BEGIN ==========
@Schema(description = "优惠类型", required = true, example = "1")
@NotNull(message = "优惠类型不能为空")
@InEnum(PromotionDiscountTypeEnum.class)
private Integer discountType;
@Schema(description = "折扣百分比", example = "80") // 例如说80% 为 80
private Integer discountPercent;
@Schema(description = "优惠金额", example = "10")
@Min(value = 0, message = "优惠金额需要大于等于 0")
private Integer discountPrice;
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
private Integer discountLimitPrice;
// ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ==========
@Schema(description = "使用订单号", example = "4096")
private Long useOrderId;
@Schema(description = "使用时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime useTime;
// ========== 使用情况 END ==========
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,154 @@
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
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.v3.oas.annotations.media.Schema;
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.time.LocalDateTime;
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 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class CouponTemplateBaseVO {
@Schema(description = "优惠劵名", required = true, example = "春节送送送")
@NotNull(message = "优惠劵名不能为空")
private String name;
@Schema(description = "发行总量", required = true, example = "1024") // -1 - 则表示不限制发放数量
@NotNull(message = "发行总量不能为空")
private Integer totalCount;
@Schema(description = "每人限领个数", required = true, example = "66") // -1 - 则表示不限制
@NotNull(message = "每人限领个数不能为空")
private Integer takeLimitCount;
@Schema(description = "领取方式", required = true, example = "1")
@NotNull(message = "领取方式不能为空")
private Integer takeType;
@Schema(description = "是否设置满多少金额可用", required = true, example = "100") // 单位0 - 不限制
@NotNull(message = "是否设置满多少金额可用不能为空")
private Integer usePrice;
@Schema(description = "商品范围", required = true, example = "1")
@NotNull(message = "商品范围不能为空")
@InEnum(PromotionProductScopeEnum.class)
private Integer productScope;
@Schema(description = "商品 SPU 编号的数组", example = "1,3")
private List<Long> productSpuIds;
@Schema(description = "生效日期类型", required = true, example = "1")
@NotNull(message = "生效日期类型不能为空")
@InEnum(CouponTemplateValidityTypeEnum.class)
private Integer validityType;
@Schema(description = "固定日期 - 生效开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime validStartTime;
@Schema(description = "固定日期 - 生效结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime validEndTime;
@Schema(description = "领取日期 - 开始天数")
@Min(value = 0L, message = "开始天数必须大于 0")
private Integer fixedStartTerm;
@Schema(description = "领取日期 - 结束天数")
@Min(value = 1L, message = "开始天数必须大于 1")
private Integer fixedEndTerm;
@Schema(description = "优惠类型", required = true, example = "1")
@NotNull(message = "优惠类型不能为空")
@InEnum(PromotionDiscountTypeEnum.class)
private Integer discountType;
@Schema(description = "折扣百分比", example = "80") // 例如说80% 为 80
private Integer discountPercent;
@Schema(description = "优惠金额", example = "10")
@Min(value = 0, message = "优惠金额需要大于等于 0")
private Integer discountPrice;
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
private Integer discountLimitPrice;
@AssertTrue(message = "商品 SPU 编号的数组不能为空")
@JsonIgnore
public boolean isProductSpuIdsValid() {
return Objects.equals(productScope, PromotionProductScopeEnum.ALL.getScope()) // 全部范围时,可以为空
|| CollUtil.isNotEmpty(productSpuIds);
}
@AssertTrue(message = "生效开始时间不能为空")
@JsonIgnore
public boolean isValidStartTimeValid() {
return ObjectUtil.notEqual(validityType, CouponTemplateValidityTypeEnum.DATE.getType())
|| validStartTime != null;
}
@AssertTrue(message = "生效结束时间不能为空")
@JsonIgnore
public boolean isValidEndTimeValid() {
return ObjectUtil.notEqual(validityType, CouponTemplateValidityTypeEnum.DATE.getType())
|| validEndTime != null;
}
@AssertTrue(message = "开始天数不能为空")
@JsonIgnore
public boolean isFixedStartTermValid() {
return ObjectUtil.notEqual(validityType, CouponTemplateValidityTypeEnum.TERM.getType())
|| fixedStartTerm != null;
}
@AssertTrue(message = "结束天数不能为空")
@JsonIgnore
public boolean isFixedEndTermValid() {
return ObjectUtil.notEqual(validityType, CouponTemplateValidityTypeEnum.TERM.getType())
|| fixedEndTerm != null;
}
@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;
}
@AssertTrue(message = "折扣上限不能为空")
@JsonIgnore
public boolean isDiscountLimitPriceValid() {
return ObjectUtil.notEqual(discountType, PromotionDiscountTypeEnum.PERCENT.getType())
|| discountLimitPrice != null;
}
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 优惠劵模板创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CouponTemplateCreateReqVO extends CouponTemplateBaseVO {
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 优惠劵模板分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CouponTemplatePageReqVO extends PageParam {
@Schema(description = "优惠劵名", example = "你好")
private String name;
@Schema(description = "状态", example = "1")
private Integer status;
@Schema(description = "优惠类型", example = "1")
private Integer discountType;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,34 @@
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.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 优惠劵模板 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CouponTemplateRespVO extends CouponTemplateBaseVO {
@Schema(description = "模板编号", required = true, example = "1024")
private Long id;
@Schema(description = "状态", required = true, example = "1")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "领取优惠券的数量", required = true, example = "1024")
private Integer takeCount;
@Schema(description = "使用优惠券的次数", required = true, example = "2048")
private Integer useCount;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 优惠劵模板更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CouponTemplateUpdateReqVO extends CouponTemplateBaseVO {
@Schema(description = "模板编号", required = true, example = "1024")
@NotNull(message = "模板编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,23 @@
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.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 优惠劵模板更新状态 Request VO")
@Data
public class CouponTemplateUpdateStatusReqVO {
@Schema(description = "优惠劵模板编号", required = true, example = "1024")
@NotNull(message = "优惠劵模板编号不能为空")
private Long id;
@Schema(description = "状态", required = true, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
private Integer status;
}

View File

@@ -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.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - 限时折扣活动")
@RestController
@RequestMapping("/promotion/discount-activity")
@Validated
public class DiscountActivityController {
@Resource
private DiscountActivityService discountActivityService;
@PostMapping("/create")
@Operation(summary = "创建限时折扣活动")
@PreAuthorize("@ss.hasPermission('promotion:discount-activity:create')")
public CommonResult<Long> createDiscountActivity(@Valid @RequestBody DiscountActivityCreateReqVO createReqVO) {
return success(discountActivityService.createDiscountActivity(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新限时折扣活动")
@PreAuthorize("@ss.hasPermission('promotion:discount-activity:update')")
public CommonResult<Boolean> updateDiscountActivity(@Valid @RequestBody DiscountActivityUpdateReqVO updateReqVO) {
discountActivityService.updateDiscountActivity(updateReqVO);
return success(true);
}
@PutMapping("/close")
@Operation(summary = "关闭限时折扣活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:discount-activity:close')")
public CommonResult<Boolean> closeRewardActivity(@RequestParam("id") Long id) {
discountActivityService.closeRewardActivity(id);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除限时折扣活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:discount-activity:delete')")
public CommonResult<Boolean> deleteDiscountActivity(@RequestParam("id") Long id) {
discountActivityService.deleteDiscountActivity(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得限时折扣活动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@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")
@Operation(summary = "获得限时折扣活动分页")
@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));
}
}

View File

@@ -0,0 +1,81 @@
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.v3.oas.annotations.media.Schema;
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.time.LocalDateTime;
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 {
@Schema(description = "活动标题", required = true, example = "一个标题")
@NotNull(message = "活动标题不能为空")
private String name;
@Schema(description = "开始时间", required = true)
@NotNull(message = "开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "结束时间", required = true)
@NotNull(message = "结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime endTime;
@Schema(description = "备注", example = "我是备注")
private String remark;
@Schema(description = "商品")
@Data
public static class Product {
@Schema(description = "商品 SPU 编号", required = true, example = "1")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SKU 编号", required = true, example = "1")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@Schema(description = "优惠类型", required = true, example = "1")
@NotNull(message = "优惠类型不能为空")
@InEnum(PromotionDiscountTypeEnum.class)
private Integer discountType;
@Schema(description = "折扣百分比", example = "80") // 例如说80% 为 80
private Integer discountPercent;
@Schema(description = "优惠金额", example = "10")
@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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.promotion.controller.admin.discount.vo;
import io.swagger.v3.oas.annotations.media.Schema;
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;
@Schema(description = "管理后台 - 限时折扣活动更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DiscountActivityUpdateReqVO extends DiscountActivityBaseVO {
@Schema(description = "活动编号", required = true, example = "1024")
@NotNull(message = "活动编号不能为空")
private Long id;
/**
* 商品列表
*/
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<DiscountActivityCreateReqVO.Product> products;
}

View File

@@ -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.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - 满减送活动")
@RestController
@RequestMapping("/promotion/reward-activity")
@Validated
public class RewardActivityController {
@Resource
private RewardActivityService rewardActivityService;
@PostMapping("/create")
@Operation(summary = "创建满减送活动")
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:create')")
public CommonResult<Long> createRewardActivity(@Valid @RequestBody RewardActivityCreateReqVO createReqVO) {
return success(rewardActivityService.createRewardActivity(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新满减送活动")
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:update')")
public CommonResult<Boolean> updateRewardActivity(@Valid @RequestBody RewardActivityUpdateReqVO updateReqVO) {
rewardActivityService.updateRewardActivity(updateReqVO);
return success(true);
}
@PutMapping("/close")
@Operation(summary = "关闭满减送活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:close')")
public CommonResult<Boolean> closeRewardActivity(@RequestParam("id") Long id) {
rewardActivityService.closeRewardActivity(id);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除满减送活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:reward-activity:delete')")
public CommonResult<Boolean> deleteRewardActivity(@RequestParam("id") Long id) {
rewardActivityService.deleteRewardActivity(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得满减送活动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@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")
@Operation(summary = "获得满减送活动分页")
@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));
}
}

View File

@@ -0,0 +1,98 @@
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.v3.oas.annotations.media.Schema;
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.time.LocalDateTime;
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 {
@Schema(description = "活动标题", required = true, example = "满啦满啦")
@NotNull(message = "活动标题不能为空")
private String name;
@Schema(description = "开始时间", required = true)
@NotNull(message = "开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "结束时间", required = true)
@NotNull(message = "结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Future(message = "结束时间必须大于当前时间")
private LocalDateTime endTime;
@Schema(description = "备注", example = "biubiubiu")
private String remark;
@Schema(description = "条件类型", required = true, example = "1")
@NotNull(message = "条件类型不能为空")
@InEnum(value = PromotionConditionTypeEnum.class, message = "条件类型必须是 {value}")
private Integer conditionType;
@Schema(description = "商品范围", required = true, example = "1")
@NotNull(message = "商品范围不能为空")
@InEnum(value = PromotionConditionTypeEnum.class, message = "商品范围必须是 {value}")
private Integer productScope;
@Schema(description = "商品 SPU 编号的数组", example = "1,2,3")
private List<Long> productSpuIds;
/**
* 优惠规则的数组
*/
@Valid // 校验下子对象
private List<Rule> rules;
@Schema(description = "优惠规则")
@Data
public static class Rule {
@Schema(description = "优惠门槛", required = true, example = "100") // 1. 满 N 元,单位:分; 2. 满 N 件
@Min(value = 1L, message = "优惠门槛必须大于等于 1")
private Integer limit;
@Schema(description = "优惠价格", required = true, example = "100")
@Min(value = 1L, message = "优惠价格必须大于等于 1")
private Integer discountPrice;
@Schema(description = "是否包邮", required = true, example = "true")
private Boolean freeDelivery;
@Schema(description = "赠送的积分", required = true, example = "100")
@Min(value = 1L, message = "赠送的积分必须大于等于 1")
private Integer point;
@Schema(description = "赠送的优惠劵编号的数组", example = "1,2,3")
private List<Long> couponIds;
@Schema(description = "赠送的优惠卷数量的数组", example = "1,2,3")
private List<Integer> couponCounts;
@AssertTrue(message = "优惠劵和数量必须一一对应")
@JsonIgnore
public boolean isCouponCountsValid() {
return CollUtil.size(couponCounts) == CollUtil.size(couponCounts);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity.SeckillActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 秒杀活动")
@RestController
@RequestMapping("/promotion/seckill-activity")
@Validated
public class SeckillActivityController {
@Resource
private SeckillActivityService seckillActivityService;
@PostMapping("/create")
@Operation(summary = "创建秒杀活动")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:create')")
public CommonResult<Long> createSeckillActivity(@Valid @RequestBody SeckillActivityCreateReqVO createReqVO) {
return success(seckillActivityService.createSeckillActivity(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新秒杀活动")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:update')")
public CommonResult<Boolean> updateSeckillActivity(@Valid @RequestBody SeckillActivityUpdateReqVO updateReqVO) {
seckillActivityService.updateSeckillActivity(updateReqVO);
return success(true);
}
@PutMapping("/close")
@Operation(summary = "关闭秒杀活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:close')")
public CommonResult<Boolean> closeSeckillActivity(@RequestParam("id") Long id) {
seckillActivityService.closeSeckillActivity(id);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除秒杀活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:delete')")
public CommonResult<Boolean> deleteSeckillActivity(@RequestParam("id") Long id) {
seckillActivityService.deleteSeckillActivity(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得秒杀活动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
if (seckillActivity == null) {
return success(null);
}
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity,seckillProducts));
}
@GetMapping("/list")
@Operation(summary = "获得秒杀活动列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<List<SeckillActivityRespVO>> getSeckillActivityList(@RequestParam("ids") Collection<Long> ids) {
List<SeckillActivityDO> list = seckillActivityService.getSeckillActivityList(ids);
return success(SeckillActivityConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得秒杀活动分页")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckilltime.SeckillTimeConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import cn.iocoder.yudao.module.promotion.service.seckill.seckilltime.SeckillTimeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
@Tag(name = "管理后台 - 秒杀时段")
@RestController
@RequestMapping("/promotion/seckill-time")
@Validated
public class SeckillTimeController {
@Resource
private SeckillTimeService seckillTimeService;
@PostMapping("/create")
@Operation(summary = "创建秒杀时段")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:create')")
public CommonResult<Long> createSeckillTime(@Valid @RequestBody SeckillTimeCreateReqVO createReqVO) {
return success(seckillTimeService.createSeckillTime(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新秒杀时段")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:update')")
public CommonResult<Boolean> updateSeckillTime(@Valid @RequestBody SeckillTimeUpdateReqVO updateReqVO) {
seckillTimeService.updateSeckillTime(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除秒杀时段")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:delete')")
public CommonResult<Boolean> deleteSeckillTime(@RequestParam("id") Long id) {
seckillTimeService.deleteSeckillTime(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得秒杀时段")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:query')")
public CommonResult<SeckillTimeRespVO> getSeckillTime(@RequestParam("id") Long id) {
SeckillTimeDO seckillTime = seckillTimeService.getSeckillTime(id);
return success(SeckillTimeConvert.INSTANCE.convert(seckillTime));
}
@GetMapping("/list")
@Operation(summary = "获得所有秒杀时段列表")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:query')")
public CommonResult<List<SeckillTimeRespVO>> getSeckillTimeList() {
List<SeckillTimeDO> list = seckillTimeService.getSeckillTimeList();
return success(SeckillTimeConvert.INSTANCE.convertList(list));
}
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
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 SeckillActivityBaseVO {
@Schema(description = "秒杀活动名称", required = true, example = "晚九点限时秒杀")
@NotNull(message = "秒杀活动名称不能为空")
private String name;
@Schema(description = "活动开始时间", required = true)
@NotNull(message = "活动开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime startTime;
@Schema(description = "活动结束时间", required = true)
@NotNull(message = "活动结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime endTime;
@Schema(description = "商品")
@Data
public static class Product {
@Schema(description = "商品 SPU 编号", required = true, example = "1")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SKU 编号", required = true, example = "1")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@Schema(description = "秒杀金额", required = true, example = "12.00")
@NotNull(message = "秒杀金额不能为空")
private Integer seckillPrice;
@Schema(description = "秒杀库存", example = "80")
@Min(value = 0, message = "秒杀库存需要大于等于 0")
private Integer stock;
@Schema(description = "每人限购", example = "10") // 如果为 0 则不限购
@Min(value = 0, message = "每人限购需要大于等于 0")
private Integer limitBuyCount;
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
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;
@Schema(description = "管理后台 - 秒杀活动创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityCreateReqVO extends SeckillActivityBaseVO {
@Schema(description = "备注", example = "限时秒杀活动")
private String remark;
@Schema(description = "排序", required = true, example = "1")
@NotNull(message = "排序不能为空")
private Integer sort;
@Schema(description = "秒杀时段id", required = true, example = "1,3")
@NotEmpty(message = "参与场次不能为空")
private List<Long> timeIds;
/**
* 商品列表
*/
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<Product> products;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Schema(description = "管理后台 - 秒杀活动的详细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityDetailRespVO extends SeckillActivityRespVO {
/**
* 商品列表
*/
private List<Product> products;
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
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;
@Schema(description = "管理后台 - 秒杀活动分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityPageReqVO extends PageParam {
@Schema(description = "秒杀活动名称", example = "晚九点限时秒杀")
private String name;
@Schema(description = "活动状态", example = "进行中")
private Integer status;
@Schema(description = "秒杀时段id", example = "1")
private Long timeId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 秒杀活动 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityRespVO extends SeckillActivityBaseVO {
@Schema(description = "秒杀活动id", required = true, example = "1")
private Long id;
@Schema(description = "付款订单数", required = true, example = "1")
private Integer orderCount;
@Schema(description = "付款人数", required = true, example = "1")
private Integer userCount;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
@Schema(description = "秒杀时段id", required = true, example = "1,3")
private List<Long> timeIds;
@Schema(description = "排序", required = true, example = "1")
private Integer sort;
@Schema(description = "备注", example = "限时秒杀活动")
private String remark;
@Schema(description = "活动状态", example = "进行中")
private Integer status;
}

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
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;
@Schema(description = "管理后台 - 秒杀活动更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityUpdateReqVO extends SeckillActivityBaseVO {
@Schema(description = "秒杀活动编号", required = true, example = "224")
@NotNull(message = "秒杀活动编号不能为空")
private Long id;
@Schema(description = "备注", example = "限时秒杀活动")
private String remark;
@Schema(description = "排序", required = true, example = "1")
@NotNull(message = "排序不能为空")
private Integer sort;
@Schema(description = "秒杀时段id", required = true, example = "1,3")
@NotEmpty(message = "秒杀时段id不能为空")
private List<Long> timeIds;
/**
* 商品列表
*/
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<Product> products;
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalTime;
/**
* 秒杀时段 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SeckillTimeBaseVO {
@Schema(description = "秒杀时段名称", required = true, example = "上午场")
@NotNull(message = "秒杀时段名称不能为空")
private String name;
@Schema(description = "开始时间点", required = true, example = "16:30:40")
@NotNull(message = "开始时间点不能为空")
private LocalTime startTime;
@Schema(description = "结束时间点", required = true, example = "16:30:40")
@NotNull(message = "结束时间点不能为空")
private LocalTime endTime;
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 秒杀时段创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeCreateReqVO extends SeckillTimeBaseVO {
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalTime;
@Schema(description = "管理后台 - 秒杀时段分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimePageReqVO extends PageParam {
@Schema(description = "秒杀时段名称", example = "上午场")
private String name;
@Schema(description = "开始时间点", example = "16:30:40")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime;
@Schema(description = "结束时间点", example = "16:30:40")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime endTime;
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 秒杀时段 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeRespVO extends SeckillTimeBaseVO {
@Schema(description = "编号", required = true, example = "1")
private Long id;
@Schema(description = "秒杀活动数量", required = true, example = "1")
private Integer seckillActivityCount;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 秒杀时段更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeUpdateReqVO extends SeckillTimeBaseVO {
@Schema(description = "编号", required = true, example = "1")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.promotion.controller.app;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 App - 营销")
@RestController
@RequestMapping("/market/test")
@Validated
public class AppMarketTestController {
@GetMapping("/get")
@Operation(summary = "获取 market 信息")
public CommonResult<String> get() {
return success("true");
}
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.controller.app.banner;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerRespVO;
import cn.iocoder.yudao.module.promotion.convert.banner.BannerConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import cn.iocoder.yudao.module.promotion.service.banner.BannerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
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.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* @author: XIA
*/
@RestController
@RequestMapping("/market/banner")
@Tag(name = "用户APP- 首页Banner")
@Validated
public class AppBannerController {
@Resource
private BannerService bannerService;
// TODO @xia新建一个 AppBannerRespVO只返回必要的字段。status 要过滤下。然后 sort 下结果
@GetMapping("/list")
@Operation(summary = "获得banner列表")
@PreAuthorize("@ss.hasPermission('market:banner:query')")
public CommonResult<List<BannerRespVO>> getBannerList() {
List<BannerDO> list = bannerService.getBannerList();
return success(BannerConvert.INSTANCE.convertList(list));
}
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.promotion.convert.banner;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface BannerConvert {
BannerConvert INSTANCE = Mappers.getMapper(BannerConvert.class);
List<BannerRespVO> convertList(List<BannerDO> list);
PageResult<BannerRespVO> convertPage(PageResult<BannerDO> pageResult);
BannerRespVO convert(BannerDO banner);
BannerDO convert(BannerCreateReqVO createReqVO);
BannerDO convert(BannerUpdateReqVO updateReqVO);
}

View File

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

View File

@@ -0,0 +1,29 @@
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.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;
/**
* 优惠劵模板 Convert
*
* @author 芋道源码
*/
@Mapper
public interface CouponTemplateConvert {
CouponTemplateConvert INSTANCE = Mappers.getMapper(CouponTemplateConvert.class);
CouponTemplateDO convert(CouponTemplateCreateReqVO bean);
CouponTemplateDO convert(CouponTemplateUpdateReqVO bean);
CouponTemplateRespVO convert(CouponTemplateDO bean);
PageResult<CouponTemplateRespVO> convertPage(PageResult<CouponTemplateDO> page);
}

View File

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

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.promotion.convert.price;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
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.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Mapper
public interface PriceConvert {
PriceConvert INSTANCE = Mappers.getMapper(PriceConvert.class);
default PriceCalculateRespDTO convert(PriceCalculateReqDTO calculateReqDTO, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象
PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO();
// 创建它的 Order 属性
PriceCalculateRespDTO.Order order = new PriceCalculateRespDTO.Order().setOriginalPrice(0).setDiscountPrice(0)
.setCouponPrice(0).setPointPrice(0).setDeliveryPrice(0).setPayPrice(0)
.setItems(new ArrayList<>()).setCouponId(calculateReqDTO.getCouponId());
priceCalculate.setOrder(order).setPromotions(new ArrayList<>());
// 创建它的 OrderItem 属性
Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
skuList.forEach(sku -> {
Integer count = skuIdCountMap.get(sku.getId());
PriceCalculateRespDTO.OrderItem orderItem = new PriceCalculateRespDTO.OrderItem()
.setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count)
.setOriginalUnitPrice(sku.getPrice()).setOriginalPrice(sku.getPrice() * count)
.setDiscountPrice(0).setOrderPartPrice(0);
orderItem.setPayPrice(orderItem.getOriginalPrice()).setOrderDividePrice(orderItem.getOriginalPrice());
priceCalculate.getOrder().getItems().add(orderItem);
// 补充价格信息到 Order 中
order.setOriginalPrice(order.getOriginalPrice() + orderItem.getOriginalPrice())
.setOrderPrice(order.getOriginalPrice()).setPayPrice(order.getOriginalPrice());
});
return priceCalculate;
}
CouponMeetRespDTO convert(CouponDO coupon);
}

View File

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

View File

@@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
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.module.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 秒杀活动 Convert
*
* @author 芋道源码
*/
@Mapper
public interface SeckillActivityConvert {
SeckillActivityConvert INSTANCE = Mappers.getMapper(SeckillActivityConvert.class);
SeckillProductDO convert(SeckillActivityBaseVO.Product product);
SeckillActivityDO convert(SeckillActivityCreateReqVO bean);
default String map(Long[] value) {
return value.toString();
}
SeckillActivityDO convert(SeckillActivityUpdateReqVO bean);
SeckillActivityRespVO convert(SeckillActivityDO bean);
List<SeckillActivityRespVO> convertList(List<SeckillActivityDO> list);
PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
@Mappings({@Mapping(target = "products", source = "seckillProducts")})
SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts);
/**
* 比较两个秒杀商品对象是否相等
*
* @param productDO 数据库中的商品
* @param productVO 前端传入的商品
* @return 是否匹配
*/
default boolean isEquals(SeckillProductDO productDO, SeckillActivityBaseVO.Product productVO) {
return ObjectUtil.equals(productDO.getSpuId(), productVO.getSpuId())
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
&& ObjectUtil.equals(productDO.getStock(), productVO.getStock())
&& ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
}
/**
* 比较两个秒杀商品对象是否相等
*
* @param productDO 商品1
* @param productVO 商品2
* @return 是否匹配
*/
default boolean isEquals(SeckillProductDO productDO, SeckillProductDO productVO) {
return ObjectUtil.equals(productDO.getSpuId(), productVO.getSpuId())
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
&& ObjectUtil.equals(productDO.getStock(), productVO.getStock())
&& ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
}
default List<SeckillProductDO> convertList(List<SeckillActivityBaseVO.Product> products, SeckillActivityDO seckillActivity) {
return CollectionUtils.convertList(products, product -> convert(product)
.setActivityId(seckillActivity.getId()).setTimeIds(seckillActivity.getTimeIds()));
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.promotion.convert.seckill.seckilltime;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
/**
* 秒杀时段 Convert
*
* @author 芋道源码
*/
@Mapper
public interface SeckillTimeConvert {
SeckillTimeConvert INSTANCE = Mappers.getMapper(SeckillTimeConvert.class);
SeckillTimeDO convert(SeckillTimeCreateReqVO bean);
SeckillTimeDO convert(SeckillTimeUpdateReqVO bean);
SeckillTimeRespVO convert(SeckillTimeDO bean);
List<SeckillTimeRespVO> convertList(List<SeckillTimeDO> list);
PageResult<SeckillTimeRespVO> convertPage(PageResult<SeckillTimeDO> page);
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.banner;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* banner DO
*
* @author xia
*/
@TableName("market_banner")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BannerDO extends BaseDO {
/**
* 编号
*/
private Long id;
/**
* 标题
*/
private String title;
/**
* 跳转链接
*/
private String url;
/**
* 图片链接
*/
private String picUrl;
/**
* 排序
*/
private Integer sort;
/**
* 状态 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String memo;
// TODO 芋艿 点击次数。&& 其他数据相关
}

View File

@@ -0,0 +1,139 @@
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 lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
/**
* 优惠劵 DO
*
* @author 芋道源码
*/
@TableName(value = "promotion_coupon", autoResultMap = true)
@KeySequence("promotion_coupon_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class CouponDO extends BaseDO {
// ========== 基本信息 BEGIN ==========
/**
* 优惠劵编号
*/
private Long id;
/**
* 优惠劵模板编号
*
* 关联 {@link CouponTemplateDO#getId()}
*/
private Integer templateId;
/**
* 优惠劵名
*
* 冗余 {@link CouponTemplateDO#getName()}
*/
private String name;
/**
* 优惠码状态
*
* 枚举 {@link CouponStatusEnum}
*/
private Integer status;
// ========== 基本信息 END ==========
// ========== 领取情况 BEGIN ==========
/**
* 用户编号
*
* 关联 MemberUserDO 的 id 字段
*/
private Long userId;
/**
* 领取类型
*
* 枚举 {@link CouponTakeTypeEnum}
*/
private Integer takeType;
// ========== 领取情况 END ==========
// ========== 使用规则 BEGIN ==========
/**
* 是否设置满多少金额可用,单位:分
*
* 冗余 {@link CouponTemplateDO#getUsePrice()}
*/
private Integer usePrice;
/**
* 生效开始时间
*/
private LocalDateTime validStartTime;
/**
* 生效结束时间
*/
private LocalDateTime validEndTime;
/**
* 商品范围
*
* 枚举 {@link PromotionProductScopeEnum}
*/
private Integer productScope;
/**
* 商品 SPU 编号的数组
*
* 冗余 {@link CouponTemplateDO#getProductSpuIds()}
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> productSpuIds;
// ========== 使用规则 END ==========
// ========== 使用效果 BEGIN ==========
/**
* 折扣类型
*
* 冗余 {@link CouponTemplateDO#getDiscountType()}
*/
private Integer discountType;
/**
* 折扣百分比
*
* 冗余 {@link CouponTemplateDO#getDiscountPercent()}
*/
private Integer discountPercent;
/**
* 优惠金额,单位:分
*
* 冗余 {@link CouponTemplateDO#getDiscountPrice()}
*/
private Integer discountPrice;
/**
* 折扣上限,仅在 {@link #discountType} 等于 {@link PromotionDiscountTypeEnum#PERCENT} 时生效
*
* 冗余 {@link CouponTemplateDO#getDiscountLimitPrice()}
*/
private Integer discountLimitPrice;
// ========== 使用效果 END ==========
// ========== 使用情况 BEGIN ==========
/**
* 使用订单号
*/
private Long useOrderId;
/**
* 使用时间
*/
private LocalDateTime useTime;
// ========== 使用情况 END ==========
}

View File

@@ -0,0 +1,162 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.coupon;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
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.CouponTakeTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
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 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class CouponTemplateDO extends BaseDO {
// ========== 基本信息 BEGIN ==========
/**
* 模板编号,自增唯一
*/
@TableId
private Long id;
/**
* 优惠劵名
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
// ========== 基本信息 END ==========
// ========== 领取规则 BEGIN ==========
/**
* 发放数量
*
* -1 - 则表示不限制发放数量
*/
private Integer totalCount;
/**
* 每人限领个数
*
* -1 - 则表示不限制
*/
private Integer takeLimitCount;
/**
* 领取方式
*
* 枚举 {@link CouponTakeTypeEnum}
*/
private Integer takeType;
// ========== 领取规则 END ==========
// ========== 使用规则 BEGIN ==========
/**
* 是否设置满多少金额可用,单位:分
*
* 0 - 不限制
* 大于 0 - 多少金额可用
*/
private Integer usePrice;
/**
* 商品范围
*
* 枚举 {@link PromotionProductScopeEnum}
*/
private Integer productScope;
/**
* 商品 SPU 编号的数组
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> productSpuIds;
/**
* 生效日期类型
*
* 枚举 {@link CouponTemplateValidityTypeEnum}
*/
private Integer validityType;
/**
* 固定日期 - 生效开始时间
*
* 当 {@link #validityType} 为 {@link CouponTemplateValidityTypeEnum#DATE}
*/
private LocalDateTime validStartTime;
/**
* 固定日期 - 生效结束时间
*
* 当 {@link #validityType} 为 {@link CouponTemplateValidityTypeEnum#DATE}
*/
private LocalDateTime validEndTime;
/**
* 领取日期 - 开始天数
*
* 当 {@link #validityType} 为 {@link CouponTemplateValidityTypeEnum#TERM}
*/
private Integer fixedStartTerm;
/**
* 领取日期 - 结束天数
*
* 当 {@link #validityType} 为 {@link CouponTemplateValidityTypeEnum#TERM}
*/
private Integer fixedEndTerm;
// ========== 使用规则 END ==========
// ========== 使用效果 BEGIN ==========
/**
* 折扣类型
*
* 枚举 {@link PromotionDiscountTypeEnum}
*/
private Integer discountType;
/**
* 折扣百分比
*
* 例如80% 为 80
*/
private Integer discountPercent;
/**
* 优惠金额,单位:分
*
* 当 {@link #discountType} 为 {@link PromotionDiscountTypeEnum#PRICE} 生效
*/
private Integer discountPrice;
/**
* 折扣上限,仅在 {@link #discountType} 等于 {@link PromotionDiscountTypeEnum#PERCENT} 时生效
*
* 例如,折扣上限为 20 元,当使用 8 折优惠券,订单金额为 1000 元时,最高只可折扣 20 元,而非 80 元。
*/
private Integer discountLimitPrice;
// ========== 使用效果 END ==========
// ========== 统计信息 BEGIN ==========
/**
* 领取优惠券的数量
*/
private Integer takeCount;
/**
* 使用优惠券的次数
*/
private Integer useCount;
// ========== 统计信息 END ==========
}

View File

@@ -0,0 +1,55 @@
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.PromotionActivityStatusEnum;
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.time.LocalDateTime;
/**
* 限时折扣活动 DO
*
* 一个活动下,可以有 {@link DiscountProductDO} 商品;
* 一个商品,在指定时间段内,只能属于一个活动;
*
* @author 芋道源码
*/
@TableName(value = "promotion_discount_activity", autoResultMap = true)
@KeySequence("promotion_discount_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DiscountActivityDO extends BaseDO {
/**
* 活动编号,主键自增
*/
@TableId
private Long id;
/**
* 活动标题
*/
private String name;
/**
* 状态
*
* 枚举 {@link PromotionActivityStatusEnum}
*/
private Integer status;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,65 @@
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;
/**
* 限时折扣商品 DO
*
* @author 芋道源码
*/
@TableName(value = "promotion_discount_product", autoResultMap = true)
@KeySequence("promotion_discount_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DiscountProductDO extends BaseDO {
/**
* 编号,主键自增
*/
@TableId
private Long id;
/**
* 限时折扣活动的编号
*
* 关联 {@link DiscountActivityDO#getId()}
*/
private Long activityId;
/**
* 商品 SPU 编号
*
* 关联 ProductSpuDO 的 id 编号
*/
private Long spuId;
/**
* 商品 SKU 编号
*
* 关联 ProductSkuDO 的 id 编号
*/
private Long skuId;
/**
* 折扣类型
*
* 枚举 {@link PromotionDiscountTypeEnum}
*/
private Integer discountType;
/**
* 折扣百分比
*
* 例如80% 为 80
*/
private Integer discountPercent;
/**
* 优惠金额,单位:分
*
* 当 {@link #discountType} 为 {@link PromotionDiscountTypeEnum#PRICE} 生效
*/
private Integer discountPrice;
}

View File

@@ -0,0 +1,133 @@
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;
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.AbstractJsonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 满减送活动 DO
*
* @author 芋道源码
*/
@TableName(value = "promotion_reward_activity", autoResultMap = true)
@KeySequence("promotion_reward_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class RewardActivityDO extends BaseDO {
/**
* 活动编号,主键自增
*/
@TableId
private Long id;
/**
* 活动标题
*/
private String name;
/**
* 状态
*
* 枚举 {@link PromotionActivityStatusEnum}
*/
private Integer status;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 备注
*/
private String remark;
/**
* 条件类型
*
* 枚举 {@link PromotionConditionTypeEnum}
*/
private Integer conditionType;
/**
* 商品范围
*
* 枚举 {@link PromotionProductScopeEnum}
*/
private Integer productScope;
/**
* 商品 SPU 编号的数组
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> productSpuIds;
/**
* 优惠规则的数组
*/
@TableField(typeHandler = RuleTypeHandler.class)
private List<Rule> rules;
/**
* 优惠规则
*/
@Data
public static class Rule implements Serializable {
/**
* 优惠门槛
*
* 1. 满 N 元,单位:分
* 2. 满 N 件
*/
private Integer limit;
/**
* 优惠价格,单位:分
*/
private Integer discountPrice;
/**
* 是否包邮
*/
private Boolean freeDelivery;
/**
* 赠送的积分
*/
private Integer point;
/**
* 赠送的优惠劵编号的数组
*/
private List<Long> couponIds;
/**
* 赠送的优惠卷数量的数组
*/
private List<Integer> couponCounts;
}
// 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);
}
}
}

View File

@@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity;
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 com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
/**
* 秒杀活动 DO
*
* @author halfninety
*/
@TableName(value = "promotion_seckill_activity", autoResultMap = true)
@KeySequence("promotion_seckill_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityDO extends BaseDO {
/**
* 秒杀活动编号
*/
@TableId
private Long id;
/**
* 秒杀活动名称
*/
private String name;
/**
* 活动状态
* <p>
* 枚举 {@link PromotionActivityStatusEnum 对应的类}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 活动开始时间
*/
private LocalDateTime startTime;
/**
* 活动结束时间
*/
private LocalDateTime endTime;
/**
* 排序
*/
private Integer sort;
/**
* 秒杀时段 id
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> timeIds;
/**
* 付款订单数
*/
private Integer orderCount;
/**
* 付款人数
*/
private Integer userCount;
/**
* 订单实付金额,单位:分
*/
private Long totalPrice;
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
/**
* 秒杀参与商品
*
* @author halfninety
* @TableName promotion_seckill_product
*/
@TableName(value = "promotion_seckill_product", autoResultMap = true)
@KeySequence("promotion_seckill_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillProductDO extends BaseDO {
/**
* 秒杀参与商品编号
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 秒杀活动id
*/
private Long activityId;
/**
* 秒杀时段id
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> timeIds;
/**
* 商品id
*/
private Long spuId;
/**
* 商品sku_id
*/
private Long skuId;
/**
* 秒杀金额
*/
private Integer seckillPrice;
/**
* 秒杀库存
*/
private Integer stock;
/**
* 每人限购
*/
private Integer limitBuyCount;
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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 lombok.ToString;
import java.time.LocalTime;
/**
* 秒杀时段 DO
*
* @author 芋道源码
*/
@TableName("promotion_seckill_time")
@KeySequence("promotion_seckill_time_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 秒杀时段名称
*/
private String name;
/**
* 开始时间点
*/
private LocalTime startTime;
/**
* 结束时间点
*/
private LocalTime endTime;
/**
* 秒杀活动数量
*/
private Integer seckillActivityCount;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.banner;
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.banner.vo.BannerPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import org.apache.ibatis.annotations.Mapper;
/**
* Banner Mapper
*
* @author xia
*/
@Mapper
public interface BannerMapper extends BaseMapperX<BannerDO> {
default PageResult<BannerDO> selectPage(BannerPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BannerDO>()
.likeIfPresent(BannerDO::getTitle, reqVO.getTitle())
.eqIfPresent(BannerDO::getStatus, reqVO.getStatus())
.betweenIfPresent(BannerDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(BannerDO::getSort));
}
}

View File

@@ -0,0 +1,52 @@
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;
import java.util.List;
/**
* 优惠劵 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 List<CouponDO> selectListByUserIdAndStatus(Long userId, Integer status) {
return selectList(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getUserId, userId).eq(CouponDO::getStatus, status));
}
default CouponDO selectByIdAndUserId(Long id, Long userId) {
return selectOne(new LambdaQueryWrapperX<CouponDO>()
.eq(CouponDO::getId, id).eq(CouponDO::getUserId, userId));
}
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));
}
default int updateByIdAndStatus(Long id, Integer status, CouponDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<CouponDO>()
.eq(CouponDO::getId, id).eq(CouponDO::getStatus, status));
}
}

View File

@@ -0,0 +1,30 @@
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.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
*
* @author 芋道源码
*/
@Mapper
public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
.likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
.eqIfPresent(CouponTemplateDO::getStatus, reqVO.getStatus())
.eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType())
.betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(CouponTemplateDO::getId));
}
void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.util.ObjectUtil;
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.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 秒杀活动 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
default PageResult<SeckillActivityDO> selectPage(SeckillActivityPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
.likeIfPresent(SeckillActivityDO::getName, reqVO.getName())
.eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus())
.betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime())
.apply(ObjectUtil.isNotNull(reqVO.getTimeId()),"FIND_IN_SET(" + reqVO.getTimeId() + ",time_ids) > 0")
.orderByDesc(SeckillActivityDO::getId));
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 秒杀活动商品 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
default List<SeckillProductDO> selectListByActivityId(Long id) {
return selectList(SeckillProductDO::getActivityId, id);
}
default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) {
return selectList(SeckillProductDO::getSkuId, skuIds);
}
default void updateTimeIdsByActivityId(Long id, List<Long> timeIds) {
new LambdaUpdateChainWrapper<>(this)
.set(SeckillProductDO::getTimeIds, CollUtil.join(timeIds, ","))
.eq(SeckillProductDO::getActivityId, id)
.update();
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckilltime;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalTime;
import java.util.Collection;
import java.util.List;
/**
* 秒杀时段 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillTimeMapper extends BaseMapperX<SeckillTimeDO> {
default List<SeckillTimeDO> selectListByTime(LocalTime time) {
return selectList(SeckillTimeDO::getStartTime, SeckillTimeDO::getEndTime, time);
}
default List<SeckillTimeDO> selectListByTime(LocalTime startTime, LocalTime endTime) {
return selectList(new LambdaQueryWrapper<SeckillTimeDO>()
.ge(SeckillTimeDO::getStartTime, startTime)
.le(SeckillTimeDO::getEndTime, endTime));
}
default void updateActivityCount(Collection<Long> ids, String type, Integer count) {
new LambdaUpdateChainWrapper<>(this)
.in(SeckillTimeDO::getId, ids)
.setSql("`seckill_activity_count` = `seckill_activity_count` " + type + count)
.update();
}
}

View File

@@ -0,0 +1,6 @@
/**
* 属于 promotion 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.promotion.framework;

View File

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.promotion.framework.web.config;
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* promotion 模块的 web 组件的 Configuration
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
public class PromotionWebConfiguration {
/**
* promotion 模块的 API 分组
*/
@Bean
public GroupedOpenApi promotionGroupedOpenApi() {
return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("promotion");
}
}

View File

@@ -0,0 +1,4 @@
/**
* promotion 模块的 web 配置
*/
package cn.iocoder.yudao.module.promotion.framework.web;

View File

@@ -0,0 +1,8 @@
/**
* promotion 模块,我们放营销业务。
* 例如说营销活动、banner、优惠券等等
*
* 1. Controller URL以 /promotion/ 开头,避免和其它 Module 冲突
* 2. DataObject 表名:以 promotion_ 开头,方便在数据库中区分
*/
package cn.iocoder.yudao.module.promotion;

View File

@@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.promotion.service.banner;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import javax.validation.Valid;
import java.util.List;
/**
* 首页 Banner Service 接口
*
* @author xia
*/
public interface BannerService {
/**
* 创建 Banner
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createBanner(@Valid BannerCreateReqVO createReqVO);
/**
* 更新 Banner
*
* @param updateReqVO 更新信息
*/
void updateBanner(@Valid BannerUpdateReqVO updateReqVO);
/**
* 删除 Banner
*
* @param id 编号
*/
void deleteBanner(Long id);
/**
* 获得 Banner
*
* @param id 编号
* @return Banner
*/
BannerDO getBanner(Long id);
/**
* 获得所有 Banner列表
* @return Banner列表
*/
List<BannerDO> getBannerList();
/**
* 获得 Banner 分页
*
* @param pageReqVO 分页查询
* @return Banner分页
*/
PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO);
}

View File

@@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.promotion.service.banner;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.banner.vo.BannerUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.banner.BannerConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.banner.BannerDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.banner.BannerMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BANNER_NOT_EXISTS;
/**
* 首页 banner 实现类
*
* @author xia
*/
@Service
@Validated
public class BannerServiceImpl implements BannerService {
@Resource
private BannerMapper bannerMapper;
@Override
public Long createBanner(BannerCreateReqVO createReqVO) {
// 插入
BannerDO banner = BannerConvert.INSTANCE.convert(createReqVO);
bannerMapper.insert(banner);
// 返回
return banner.getId();
}
@Override
public void updateBanner(BannerUpdateReqVO updateReqVO) {
// 校验存在
this.validateBannerExists(updateReqVO.getId());
// 更新
BannerDO updateObj = BannerConvert.INSTANCE.convert(updateReqVO);
bannerMapper.updateById(updateObj);
}
@Override
public void deleteBanner(Long id) {
// 校验存在
this.validateBannerExists(id);
// 删除
bannerMapper.deleteById(id);
}
private void validateBannerExists(Long id) {
if (bannerMapper.selectById(id) == null) {
throw exception(BANNER_NOT_EXISTS);
}
}
@Override
public BannerDO getBanner(Long id) {
return bannerMapper.selectById(id);
}
@Override
public List<BannerDO> getBannerList() {
return bannerMapper.selectList();
}
@Override
public PageResult<BannerDO> getBannerPage(BannerPageReqVO pageReqVO) {
return bannerMapper.selectPage(pageReqVO);
}
}

View File

@@ -0,0 +1,70 @@
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;
import java.util.List;
/**
* 优惠劵 Service 接口
*
* @author 芋道源码
*/
public interface CouponService {
/**
* 校验优惠劵,包括状态、有限期
*
* 1. 如果校验通过,则返回优惠劵信息
* 2. 如果校验不通过,则直接抛出业务异常
*
* @param id 优惠劵编号
* @param userId 用户编号
* @return 优惠劵信息
*/
CouponDO validCoupon(Long id, Long userId);
/**
* 校验优惠劵,包括状态、有限期
*
* @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
*
* @param coupon 优惠劵
*/
void validCoupon(CouponDO coupon);
/**
* 获得优惠劵分页
*
* @param pageReqVO 分页查询
* @return 优惠劵分页
*/
PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO);
/**
* 使用优惠劵
*
* @param id 优惠劵编号
* @param userId 用户编号
* @param orderId 订单编号
*/
void useCoupon(Long id, Long userId, Long orderId);
/**
* 回收优惠劵
*
* @param id 优惠劵编号
*/
void deleteCoupon(Long id);
/**
* 获得用户的优惠劵列表
*
* @param userId 用户编号
* @param status 优惠劵状态
* @return 优惠劵列表
*/
List<CouponDO> getCouponList(Long userId, Integer status);
}

View File

@@ -0,0 +1,123 @@
package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
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.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
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.time.LocalDateTime;
import java.util.List;
import java.util.Set;
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 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CouponServiceImpl implements CouponService {
@Resource
private CouponTemplateService couponTemplateService;
@Resource
private CouponMapper couponMapper;
@Resource
private MemberUserApi memberUserApi;
@Override
public CouponDO validCoupon(Long id, Long userId) {
CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
if (coupon == null) {
throw exception(COUPON_NOT_EXISTS);
}
validCoupon(coupon);
return coupon;
}
@Override
public void validCoupon(CouponDO coupon) {
// 校验状态
if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
throw exception(COUPON_STATUS_NOT_UNUSED);
}
// 校验有效期;为避免定时器没跑,实际优惠劵已经过期
if (LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
throw exception(COUPON_VALID_TIME_NOT_NOW);
}
}
@Override
public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
// 获得用户编号
Set<Long> userIds = null;
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
MemberUserRespDTO::getId);
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
}
// 分页查询
return couponMapper.selectPage(pageReqVO, userIds);
}
@Override
public void useCoupon(Long id, Long userId, Long orderId) {
// 校验优惠劵
validCoupon(id, userId);
// 更新状态
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
new CouponDO().setStatus(CouponStatusEnum.USED.getStatus())
.setUseOrderId(orderId).setUseTime(LocalDateTime.now()));
if (updateCount == 0) {
throw exception(COUPON_STATUS_NOT_UNUSED);
}
}
@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);
}
@Override
public List<CouponDO> getCouponList(Long userId, Integer status) {
return couponMapper.selectListByUserIdAndStatus(userId, status);
}
private void validateCouponExists(Long id) {
if (couponMapper.selectById(id) == null) {
throw exception(COUPON_NOT_EXISTS);
}
}
}

View File

@@ -0,0 +1,72 @@
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.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;
/**
* 优惠劵模板 Service 接口
*
* @author 芋道源码
*/
public interface CouponTemplateService {
/**
* 创建优惠劵模板
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createCouponTemplate(@Valid CouponTemplateCreateReqVO createReqVO);
/**
* 更新优惠劵模板
*
* @param updateReqVO 更新信息
*/
void updateCouponTemplate(@Valid CouponTemplateUpdateReqVO updateReqVO);
/**
* 更新优惠劵模板的状态
*
* @param id 编号
* @param status 状态
*/
void updateCouponTemplateStatus(Long id, Integer status);
/**
* 删除优惠劵模板
*
* @param id 编号
*/
void deleteCouponTemplate(Long id);
/**
* 获得优惠劵模板
*
* @param id 编号
* @return 优惠劵模板
*/
CouponTemplateDO getCouponTemplate(Long id);
/**
* 获得优惠劵模板分页
*
* @param pageReqVO 分页查询
* @return 优惠劵模板分页
*/
PageResult<CouponTemplateDO> getCouponTemplatePage(CouponTemplatePageReqVO pageReqVO);
/**
* 更新优惠劵模板的领取数量
*
* @param id 优惠劵模板编号
* @param incrCount 增加数量
*/
void updateCouponTemplateTakeCount(Long id, int incrCount);
}

View File

@@ -0,0 +1,94 @@
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.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;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
/**
* 优惠劵模板 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CouponTemplateServiceImpl implements CouponTemplateService {
@Resource
private CouponTemplateMapper couponTemplateMapper;
@Override
public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
// 插入
CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO)
.setStatus(CommonStatusEnum.ENABLE.getStatus());
couponTemplateMapper.insert(couponTemplate);
// 返回
return couponTemplate.getId();
}
@Override
public void updateCouponTemplate(CouponTemplateUpdateReqVO updateReqVO) {
// 校验存在
CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId());
// 校验发放数量不能过小
if (updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
}
// 更新
CouponTemplateDO updateObj = CouponTemplateConvert.INSTANCE.convert(updateReqVO);
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) {
// 校验存在
validateCouponTemplateExists(id);
// 删除
couponTemplateMapper.deleteById(id);
}
private CouponTemplateDO validateCouponTemplateExists(Long id) {
CouponTemplateDO couponTemplate = couponTemplateMapper.selectById(id);
if (couponTemplate == null) {
throw exception(COUPON_TEMPLATE_NOT_EXISTS);
}
return couponTemplate;
}
@Override
public CouponTemplateDO getCouponTemplate(Long id) {
return couponTemplateMapper.selectById(id);
}
@Override
public PageResult<CouponTemplateDO> getCouponTemplatePage(CouponTemplatePageReqVO pageReqVO) {
return couponTemplateMapper.selectPage(pageReqVO);
}
@Override
public void updateCouponTemplateTakeCount(Long id, int incrCount) {
couponTemplateMapper.updateTakeCount(id, incrCount);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.promotion.service.price;
import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import java.util.List;
/**
* 价格计算 Service 接口
*
* @author 芋道源码
*/
public interface PriceService {
/**
* 计算商品的价格
*
* @param calculateReqDTO 价格请求
* @return 价格响应
*/
PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
/**
* 获得优惠劵的匹配信息列表
*
* @param calculateReqDTO 价格请求
* @return 价格响应
*/
List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO);
}

View File

@@ -0,0 +1,547 @@
package cn.iocoder.yudao.module.promotion.service.price;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
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.CouponMeetRespDTO;
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.reward.RewardActivityDO;
import cn.iocoder.yudao.module.promotion.enums.common.*;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
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;
import javax.annotation.Resource;
import java.util.*;
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.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Collections.singletonList;
/**
* 价格计算 Service 实现类
*
* 优惠计算顺序min(限时折扣, 会员折扣) > 满减送 > 优惠券。
* 参考文档:
* 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-60384">有赞文档:限时折扣、满减送、优惠券哪个优先计算?</a>
*
* TODO 芋艿:进一步完善
* 1. 限时折扣:指定金额、减免金额、折扣
* 2. 满减送:循环、折扣
* 3. 优惠劵:待定
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class PriceServiceImpl implements PriceService {
@Resource
private DiscountActivityService discountService;
@Resource
private RewardActivityService rewardActivityService;
@Resource
private CouponService couponService;
@Resource
private ProductSkuApi productSkuApi;
@Override
public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
// 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSkus(calculateReqDTO);
// 初始化 PriceCalculateRespDTO 对象
PriceCalculateRespDTO priceCalculate = PriceConvert.INSTANCE.convert(calculateReqDTO, skuList);
// 计算商品级别的价格
calculatePriceForSkuLevel(calculateReqDTO.getUserId(), priceCalculate);
// 计算订单级别的价格
calculatePriceForOrderLevel(calculateReqDTO.getUserId(), priceCalculate);
// 计算优惠劵级别的价格
calculatePriceForCouponLevel(calculateReqDTO.getUserId(), calculateReqDTO.getCouponId(), priceCalculate);
// 如果最终支付金额小于等于 0则抛出业务异常
if (priceCalculate.getOrder().getPayPrice() <= 0) {
log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]",
calculateReqDTO, priceCalculate);
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
}
return priceCalculate;
}
@Override
public List<CouponMeetRespDTO> getMeetCouponList(PriceCalculateReqDTO calculateReqDTO) {
// 先计算一轮价格
PriceCalculateRespDTO priceCalculate = calculatePrice(calculateReqDTO);
// 获得用户的待使用优惠劵
List<CouponDO> couponList = couponService.getCouponList(calculateReqDTO.getUserId(), CouponStatusEnum.UNUSED.getStatus());
if (CollUtil.isEmpty(couponList)) {
return Collections.emptyList();
}
// 获得优惠劵的匹配信息
return CollectionUtils.convertList(couponList, coupon -> {
CouponMeetRespDTO couponMeetRespDTO = PriceConvert.INSTANCE.convert(coupon);
try {
// 校验优惠劵
couponService.validCoupon(coupon);
// 获得匹配的商品 SKU 数组
List<PriceCalculateRespDTO.OrderItem> orderItems = getMatchCouponOrderItems(priceCalculate, coupon);
if (CollUtil.isEmpty(orderItems)) {
return couponMeetRespDTO.setMeet(false).setMeetTip("所结算商品没有符合条件的商品");
}
// 计算是否满足优惠劵的使用金额
Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
assert originPrice != null;
if (originPrice < coupon.getUsePrice()) {
return couponMeetRespDTO.setMeet(false)
// .setMeetTip(String.format("差 %s 元可用优惠劵", formatPrice(coupon.getUsePrice() - originPrice)));
.setMeetTip("所结算的商品中未满足使用的金额");
}
} catch (ServiceException serviceException) {
couponMeetRespDTO.setMeet(false);
if (serviceException.getCode().equals(COUPON_VALID_TIME_NOT_NOW.getCode())) {
couponMeetRespDTO.setMeetTip("优惠劵未到使用时间");
} else {
log.error("[getMeetCouponList][calculateReqDTO({}) 获得优惠劵匹配信息异常]", calculateReqDTO, serviceException);
couponMeetRespDTO.setMeetTip("优惠劵不满足使用条件");
}
return couponMeetRespDTO;
}
// 满足
return couponMeetRespDTO.setMeet(true);
});
}
private List<ProductSkuRespDTO> checkSkus(PriceCalculateReqDTO calculateReqDTO) {
// 获得商品 SKU 数组
Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(skuIdCountMap.keySet());
// 校验商品 SKU
skus.forEach(sku -> {
Integer count = skuIdCountMap.get(sku.getId());
if (count == null) {
throw exception(SKU_NOT_EXISTS);
}
// 不校验库存不足,避免购物车场景,商品无货的情况
});
return skus;
}
// ========== 计算商品级别的价格 ==========
/**
* 计算商品级别的价格,例如说:
* 1. 会员折扣
* 2. 限时折扣 {@link cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO}
*
* 其中,会员折扣、限时折扣取最低价
*
* @param userId 用户编号
* @param priceCalculate 价格计算的结果
*/
private void calculatePriceForSkuLevel(Long userId, PriceCalculateRespDTO priceCalculate) {
// 获取 SKU 级别的所有优惠信息
Supplier<Double> memberDiscountPercentSupplier = getMemberDiscountPercentSupplier(userId);
Map<Long, DiscountProductDetailBO> discountProducts = discountService.getMatchDiscountProducts(
convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSkuId));
// 处理每个 SKU 的优惠
priceCalculate.getOrder().getItems().forEach(orderItem -> {
// 获取该 SKU 的优惠信息
Double memberDiscountPercent = memberDiscountPercentSupplier.get();
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 ? getDiscountProductPrice(discountProduct, orderItem) : null;
if (memberPrice == null) {
calculatePriceByDiscountActivity(priceCalculate, orderItem, discountProduct, promotionPrice);
} else if (promotionPrice == null) {
calculatePriceByMemberDiscount(priceCalculate, orderItem, memberPrice);
} else if (memberPrice < promotionPrice) {
calculatePriceByDiscountActivity(priceCalculate, orderItem, discountProduct, promotionPrice);
} else {
calculatePriceByMemberDiscount(priceCalculate, orderItem, memberPrice);
}
});
}
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) {
// 记录优惠明细
addPromotion(priceCalculate, orderItem, null, PromotionTypeEnum.MEMBER.getName(),
PromotionTypeEnum.MEMBER.getType(), PromotionLevelEnum.SKU.getLevel(), memberPrice,
true, StrUtil.format("会员折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - memberPrice)));
// 修改 SKU 的优惠
modifyOrderItemPayPrice(orderItem, memberPrice, priceCalculate);
}
private void calculatePriceByDiscountActivity(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem,
DiscountProductDetailBO discountProduct, Integer promotionPrice) {
// 记录优惠明细
addPromotion(priceCalculate, orderItem, discountProduct.getActivityId(), discountProduct.getActivityName(),
PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), PromotionLevelEnum.SKU.getLevel(), promotionPrice,
true, StrUtil.format("限时折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - promotionPrice)));
// 修改 SKU 的优惠
modifyOrderItemPayPrice(orderItem, promotionPrice, priceCalculate);
}
// TODO 芋艿:提前实现
private Supplier<Double> getMemberDiscountPercentSupplier(Long userId) {
return Suppliers.memoize(() -> {
if (userId == 1) {
return 90d;
}
if (userId == 2) {
return 80d;
}
return null; // 无优惠
});
}
// ========== 计算商品级别的价格 ==========
/**
* 计算订单级别的价格,例如说:
* 1. 满减送 {@link cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO}
*
* @param userId 用户编号
* @param priceCalculate 价格计算的结果
*/
@SuppressWarnings("unused")
private void calculatePriceForOrderLevel(Long userId, PriceCalculateRespDTO priceCalculate) {
// 获取 SKU 级别的所有优惠信息
Set<Long> spuIds = convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSpuId);
Map<RewardActivityDO, Set<Long>> rewardActivities = rewardActivityService.getMatchRewardActivities(spuIds);
// 处理满减送活动
if (CollUtil.isNotEmpty(rewardActivities)) {
rewardActivities.forEach((rewardActivity, activitySpuIds) -> {
List<PriceCalculateRespDTO.OrderItem> orderItems = CollectionUtils.filterList(priceCalculate.getOrder().getItems(),
orderItem -> CollUtil.contains(activitySpuIds, orderItem.getSpuId()));
calculatePriceByRewardActivity(priceCalculate, orderItems, rewardActivity);
});
}
}
private void calculatePriceByRewardActivity(PriceCalculateRespDTO priceCalculate, List<PriceCalculateRespDTO.OrderItem> orderItems,
RewardActivityDO rewardActivity) {
// 获得最大匹配的满减送活动的规则
RewardActivityDO.Rule rule = getLastMatchRewardActivityRule(rewardActivity, orderItems);
if (rule == null) {
// 获取不到的情况下,记录不满足的优惠明细
addNotMeetPromotion(priceCalculate, orderItems, rewardActivity.getId(), rewardActivity.getName(),
PromotionTypeEnum.REWARD_ACTIVITY.getType(), PromotionLevelEnum.ORDER.getLevel(),
getRewardActivityNotMeetTip(rewardActivity));
return;
}
// 分摊金额
List<Integer> discountPartPrices = dividePrice(orderItems, rule.getDiscountPrice());
// 记录优惠明细
addPromotion(priceCalculate, orderItems, rewardActivity.getId(), rewardActivity.getName(),
PromotionTypeEnum.REWARD_ACTIVITY.getType(), PromotionLevelEnum.ORDER.getLevel(), discountPartPrices,
true, StrUtil.format("满减送:省 {} 元", formatPrice(rule.getDiscountPrice())));
// 修改 SKU 的分摊
for (int i = 0; i < orderItems.size(); i++) {
modifyOrderItemOrderPartPriceFromDiscountPrice(orderItems.get(i), discountPartPrices.get(i), priceCalculate);
}
}
/**
* 获得最大匹配的满减送活动的规则
*
* @param rewardActivity 满减送活动
* @param orderItems 商品项
* @return 匹配的活动规则
*/
private RewardActivityDO.Rule getLastMatchRewardActivityRule(RewardActivityDO rewardActivity,
List<PriceCalculateRespDTO.OrderItem> orderItems) {
Integer count = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getCount, Integer::sum);
// price 的计算逻辑,使用 orderDividePrice 的原因,主要考虑分摊后,这个才是该 SKU 当前真实的支付总价
Integer price = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
assert count != null && price != null;
for (int i = rewardActivity.getRules().size() - 1; i >= 0; i--) {
RewardActivityDO.Rule rule = rewardActivity.getRules().get(i);
if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())
&& price >= rule.getLimit()) {
return rule;
}
if (PromotionConditionTypeEnum.COUNT.getType().equals(rewardActivity.getConditionType())
&& count >= rule.getLimit()) {
return rule;
}
}
return null;
}
/**
* 获得满减送活动部匹配时的提示
*
* @param rewardActivity 满减送活动
* @return 提示
*/
private String getRewardActivityNotMeetTip(RewardActivityDO rewardActivity) {
return "TODO"; // TODO 芋艿:后面再想想
}
// ========== 计算优惠劵级别的价格 ==========
private void calculatePriceForCouponLevel(Long userId, Long couponId, PriceCalculateRespDTO priceCalculate) {
// 校验优惠劵
if (couponId == null) {
return;
}
CouponDO coupon = couponService.validCoupon(couponId, userId);
// 获得匹配的商品 SKU 数组
List<PriceCalculateRespDTO.OrderItem> orderItems = getMatchCouponOrderItems(priceCalculate, coupon);
if (CollUtil.isEmpty(orderItems)) {
throw exception(COUPON_NO_MATCH_SPU);
}
// 计算是否满足优惠劵的使用金额
Integer originPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
assert originPrice != null;
if (originPrice < coupon.getUsePrice()) {
throw exception(COUPON_NO_MATCH_MIN_PRICE);
}
// 计算可以优惠的金额
priceCalculate.getOrder().setCouponId(couponId);
Integer couponPrice = getCouponPrice(coupon, originPrice);
// 分摊金额
List<Integer> couponPartPrices = dividePrice(orderItems, couponPrice);
// 记录优惠明细
addPromotion(priceCalculate, orderItems, coupon.getId(), coupon.getName(),
PromotionTypeEnum.COUPON.getType(), PromotionLevelEnum.COUPON.getLevel(), couponPartPrices,
true, StrUtil.format("优惠劵:省 {} 元", formatPrice(couponPrice)));
// 修改 SKU 的分摊
for (int i = 0; i < orderItems.size(); i++) {
modifyOrderItemOrderPartPriceFromCouponPrice(orderItems.get(i), couponPartPrices.get(i), priceCalculate);
}
}
private List<PriceCalculateRespDTO.OrderItem> getMatchCouponOrderItems(PriceCalculateRespDTO priceCalculate,
CouponDO coupon) {
if (PromotionProductScopeEnum.ALL.getScope().equals(coupon.getProductScope())) {
return priceCalculate.getOrder().getItems();
}
return CollectionUtils.filterList(priceCalculate.getOrder().getItems(),
orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId()));
}
private Integer getCouponPrice(CouponDO coupon, Integer originPrice) {
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));
}
// ========== 其它相对通用的方法 ==========
/**
* 添加单个 OrderItem 的营销明细
*
* @param priceCalculate 价格计算结果
* @param orderItem 单个订单商品 SKU
* @param id 营销编号
* @param name 营销名字
* @param type 营销类型
* @param level 营销级别
* @param newPayPrice 新的单实付金额(总)
* @param meet 是否满足优惠条件
* @param meetTip 满足条件的提示
*/
private void addPromotion(PriceCalculateRespDTO priceCalculate, PriceCalculateRespDTO.OrderItem orderItem,
Long id, String name, Integer type, Integer level,
Integer newPayPrice, Boolean meet, String meetTip) {
// 创建营销明细 Item
// TODO 芋艿orderItem.getPayPrice() 要不要改成 orderDividePrice同时newPayPrice 要不要改成直接传递 discountPrice
PriceCalculateRespDTO.PromotionItem promotionItem = new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId())
.setOriginalPrice(orderItem.getPayPrice()).setDiscountPrice(orderItem.getPayPrice() - newPayPrice);
// 创建营销明细
PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion()
.setId(id).setName(name).setType(type).setLevel(level)
.setOriginalPrice(promotionItem.getOriginalPrice()).setDiscountPrice(promotionItem.getDiscountPrice())
.setItems(singletonList(promotionItem)).setMeet(meet).setMeetTip(meetTip);
priceCalculate.getPromotions().add(promotion);
}
/**
* 添加多个 OrderItem 的营销明细
*
* @param priceCalculate 价格计算结果
* @param orderItems 多个订单商品 SKU
* @param id 营销编号
* @param name 营销名字
* @param type 营销类型
* @param level 营销级别
* @param discountPrices 多个订单商品 SKU 的优惠价格(总),和 orderItems 一一对应
* @param meet 是否满足优惠条件
* @param meetTip 满足条件的提示
*/
private void addPromotion(PriceCalculateRespDTO priceCalculate, List<PriceCalculateRespDTO.OrderItem> orderItems,
Long id, String name, Integer type, Integer level,
List<Integer> discountPrices, Boolean meet, String meetTip) {
// 创建营销明细 Item
List<PriceCalculateRespDTO.PromotionItem> promotionItems = new ArrayList<>(discountPrices.size());
for (int i = 0; i < orderItems.size(); i++) {
PriceCalculateRespDTO.OrderItem orderItem = orderItems.get(i);
promotionItems.add(new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId())
.setOriginalPrice(orderItem.getPayPrice()).setDiscountPrice(discountPrices.get(i)));
}
// 创建营销明细
PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion()
.setId(id).setName(name).setType(type).setLevel(level)
.setOriginalPrice(getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum))
.setDiscountPrice(getSumValue(discountPrices, value -> value, Integer::sum))
.setItems(promotionItems).setMeet(meet).setMeetTip(meetTip);
priceCalculate.getPromotions().add(promotion);
}
private void addNotMeetPromotion(PriceCalculateRespDTO priceCalculate, List<PriceCalculateRespDTO.OrderItem> orderItems,
Long id, String name, Integer type, Integer level, String meetTip) {
// 创建营销明细 Item
List<PriceCalculateRespDTO.PromotionItem> promotionItems = CollectionUtils.convertList(orderItems,
orderItem -> new PriceCalculateRespDTO.PromotionItem().setSkuId(orderItem.getSkuId())
.setOriginalPrice(orderItem.getOrderDividePrice()).setDiscountPrice(0));
// 创建营销明细
Integer originalPrice = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
PriceCalculateRespDTO.Promotion promotion = new PriceCalculateRespDTO.Promotion()
.setId(id).setName(name).setType(type).setLevel(level)
.setOriginalPrice(originalPrice).setDiscountPrice(0)
.setItems(promotionItems).setMeet(false).setMeetTip(meetTip);
priceCalculate.getPromotions().add(promotion);
}
/**
* 修改 OrderItem 的 payPrice 价格,同时会修改 Order 的 payPrice 价格
*
* @param orderItem 订单商品 SKU
* @param newPayPrice 新的 payPrice 价格
* @param priceCalculate 价格计算结果
*/
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 相关相关字段
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
order.setPayPrice(order.getPayPrice() - diffPayPrice);
order.setOrderPrice(order.getOrderPrice() - diffPayPrice);
}
/**
* 修改 OrderItem 的 orderPartPrice 价格,同时会修改 Order 的 discountPrice 价格
*
* 本质:分摊 Order 的 discountPrice 价格,到对应的 OrderItem 的 orderPartPrice 价格中
*
* @param orderItem 订单商品 SKU
* @param addOrderPartPrice 新增的 discountPrice 价格
* @param priceCalculate 价格计算结果
*/
private void modifyOrderItemOrderPartPriceFromDiscountPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer addOrderPartPrice,
PriceCalculateRespDTO priceCalculate) {
// 设置 OrderItem 价格相关字段
orderItem.setOrderPartPrice(orderItem.getOrderPartPrice() + addOrderPartPrice);
orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice());
// 设置 Order 相关相关字段
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
order.setDiscountPrice(order.getDiscountPrice() + addOrderPartPrice);
order.setPayPrice(order.getPayPrice() - addOrderPartPrice);
}
/**
* 修改 OrderItem 的 orderPartPrice 价格,同时会修改 Order 的 couponPrice 价格
*
* 本质:分摊 Order 的 couponPrice 价格,到对应的 OrderItem 的 orderPartPrice 价格中
*
* @param orderItem 订单商品 SKU
* @param addOrderPartPrice 新增的 couponPrice 价格
* @param priceCalculate 价格计算结果
*/
private void modifyOrderItemOrderPartPriceFromCouponPrice(PriceCalculateRespDTO.OrderItem orderItem, Integer addOrderPartPrice,
PriceCalculateRespDTO priceCalculate) {
// 设置 OrderItem 价格相关字段
orderItem.setOrderPartPrice(orderItem.getOrderPartPrice() + addOrderPartPrice);
orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice());
// 设置 Order 相关相关字段
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
order.setCouponPrice(order.getCouponPrice() + addOrderPartPrice);
order.setPayPrice(order.getPayPrice() - addOrderPartPrice);
}
private List<Integer> dividePrice(List<PriceCalculateRespDTO.OrderItem> orderItems, Integer price) {
List<Integer> prices = new ArrayList<>(orderItems.size());
Integer total = getSumValue(orderItems, PriceCalculateRespDTO.OrderItem::getOrderDividePrice, Integer::sum);
assert total != null;
int remainPrice = price;
// 遍历每一个,进行分摊
for (int i = 0; i < orderItems.size(); i++) {
PriceCalculateRespDTO.OrderItem orderItem = orderItems.get(i);
int partPrice;
if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
partPrice = (int) (price * (1.0D * orderItem.getOrderDividePrice() / total));
remainPrice -= partPrice;
} else {
partPrice = remainPrice;
}
Assert.isTrue(partPrice > 0, "分摊金额必须大于 0");
prices.add(partPrice);
}
return prices;
}
private String formatPrice(Integer price) {
return String.format("%.2f", price / 100d);
}
}

View File

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

View File

@@ -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)); // 求交集返回
}
}

View File

@@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
/**
* 秒杀活动 Service 接口
*
* @author halfninety
*/
public interface SeckillActivityService {
/**
* 创建秒杀活动
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSeckillActivity(@Valid SeckillActivityCreateReqVO createReqVO);
/**
* 更新秒杀活动
*
* @param updateReqVO 更新信息
*/
void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
/**
* 关闭秒杀活动
*
* @param id 编号
*/
void closeSeckillActivity(Long id);
/**
* 删除秒杀活动
*
* @param id 编号
*/
void deleteSeckillActivity(Long id);
/**
* 获得秒杀活动
*
* @param id 编号
* @return 秒杀活动
*/
SeckillActivityDO getSeckillActivity(Long id);
/**
* 获得秒杀活动列表
*
* @param ids 编号
* @return 秒杀活动列表
*/
List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids);
/**
* 获得秒杀活动分页
*
* @param pageReqVO 分页查询
* @return 秒杀活动分页
*/
PageResult<SeckillActivityDO> getSeckillActivityPage(SeckillActivityPageReqVO pageReqVO);
/**
* 通过活动编号获取活动商品
*
* @param id 活动编号
* @return 活动商品列表
*/
List<SeckillProductDO> getSeckillProductListByActivityId(Long id);
}

View File

@@ -0,0 +1,226 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
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.seckill.vo.activity.SeckillActivityBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.service.seckill.seckilltime.SeckillTimeService;
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 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 实现类
*
* @author halfninety
*/
@Service
@Validated
public class SeckillActivityServiceImpl implements SeckillActivityService {
@Resource
private SeckillActivityMapper seckillActivityMapper;
@Resource
private SeckillProductMapper seckillProductMapper;
@Resource
private SeckillTimeService seckillTimeService;
@Override
public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
// 校验商品是否冲突
validateSeckillActivityProductConflicts(null, createReqVO.getProducts());
// 校验秒杀时段是否存在
seckillTimeService.validateSeckillTimeExists(createReqVO.getTimeIds());
// 插入秒杀活动
SeckillActivityDO seckillActivity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getStartTime(), createReqVO.getEndTime()));
seckillActivityMapper.insert(seckillActivity);
// 插入商品
List<SeckillProductDO> productDOS = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), seckillActivity);
seckillProductMapper.insertBatch(productDOS);
// 更新秒杀时段的秒杀活动数量
seckillTimeService.sekillActivityCountIncr(createReqVO.getTimeIds());
return seckillActivity.getId();
}
@Override
public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
// 校验存在
SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId());
if (PromotionActivityStatusEnum.CLOSE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
}
// 校验商品是否冲突
validateSeckillActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
// 更新活动
SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getStartTime(), updateReqVO.getEndTime()));
seckillActivityMapper.updateById(updateObj);
// 更新商品
updateSeckillProduct(updateReqVO);
// 更新秒杀时段的秒杀活动数量
updateSeckillTimeActivityCount(seckillActivity, updateReqVO.getTimeIds());
}
/**
* 更新秒杀时段的秒杀活动数量
*
* @param seckillActivity 查询出的秒杀活动
* @param updateTimeIds 更新后的秒杀时段id列表
*/
private void updateSeckillTimeActivityCount(SeckillActivityDO seckillActivity, List<Long> updateTimeIds) {
// 查询出 timeIds
List<Long> existsTimeIds = seckillActivity.getTimeIds();
// 需要减少的时间段
Collection<Long> reduceIds = CollUtil.filterNew(existsTimeIds, existsTimeId -> !updateTimeIds.contains(existsTimeId));
// 需要添加的时间段
updateTimeIds.removeIf(existsTimeIds::contains);
// 更新减少时间段和增加时间段
if (CollUtil.isNotEmpty(updateTimeIds)) {
seckillTimeService.sekillActivityCountIncr(updateTimeIds);
}
if (CollUtil.isNotEmpty(reduceIds)) {
seckillTimeService.sekillActivityCountDecr(reduceIds);
}
}
/**
* 更新秒杀商品
* 后台查出的数据和前台查出的数据进行遍历,
* 1. 对前台数据进行遍历:如果不存在于后台的 sku 中需要新增
* 2. 对后台数据进行遍历:如果不存在于前台的 sku 中需要删除
* 3. 最后对当前活动商品全部更新更新秒杀时段id列表
*
* @param updateReqVO 更新的请求VO
*/
private void updateSeckillProduct(SeckillActivityUpdateReqVO updateReqVO) {
List<SeckillProductDO> seckillProductDOS = seckillProductMapper.selectListByActivityId(updateReqVO.getId());
List<SeckillActivityBaseVO.Product> products = updateReqVO.getProducts();
// 计算需要删除的数据
List<Long> deleteIds = CollectionUtils.convertList(seckillProductDOS, SeckillProductDO::getId,
seckillProductDO -> products.stream()
.noneMatch(product -> SeckillActivityConvert.INSTANCE.isEquals(seckillProductDO, product)));
if (CollUtil.isNotEmpty(deleteIds)) {
seckillProductMapper.deleteBatchIds(deleteIds);
}
// 计算需要新增的数据
List<SeckillProductDO> newSeckillProductDOs = CollectionUtils.convertList(products,
product -> SeckillActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId()));
newSeckillProductDOs.removeIf(product -> seckillProductDOS.stream()
.anyMatch(seckillProduct -> SeckillActivityConvert.INSTANCE.isEquals(seckillProduct, product)));
if (CollUtil.isNotEmpty(newSeckillProductDOs)) {
seckillProductMapper.insertBatch(newSeckillProductDOs);
}
//全量更新当前活动商品的秒杀时段id列表timeIds
seckillProductMapper.updateTimeIdsByActivityId(updateReqVO.getId(), updateReqVO.getTimeIds());
}
/**
* 校验商品是否冲突
*
* @param id 秒杀活动编号
* @param products 商品列表
*/
private void validateSeckillActivityProductConflicts(Long id, List<SeckillActivityBaseVO.Product> products) {
if (CollUtil.isEmpty(products)) {
return;
}
List<SeckillProductDO> seckillProductDOS = seckillProductMapper
.selectListBySkuIds(CollectionUtils.convertSet(products, SeckillActivityBaseVO.Product::getSkuId));
if (CollUtil.isEmpty(seckillProductDOS)) {
return;
}
List<SeckillActivityDO> seckillActivityDOS = seckillActivityMapper
.selectBatchIds(CollectionUtils.convertSet(seckillProductDOS, SeckillProductDO::getActivityId));
if (id != null) { // 排除自己这个活动
seckillActivityDOS.removeIf(item -> id.equals(item.getId()));
}
// 排除不满足 status 的活动
List<Integer> statuses = asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus());
seckillActivityDOS.removeIf(item -> !statuses.contains(item.getStatus()));
// 如果非空,则说明冲突
if (CollUtil.isNotEmpty(seckillActivityDOS)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
}
@Override
public void closeSeckillActivity(Long id) {
// 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id);
if (PromotionActivityStatusEnum.CLOSE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
}
if (PromotionActivityStatusEnum.END.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END);
}
// 更新
SeckillActivityDO updateObj = new SeckillActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
seckillActivityMapper.updateById(updateObj);
}
@Override
public void deleteSeckillActivity(Long id) {
// 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id);
List<Integer> statuses = asList(PromotionActivityStatusEnum.CLOSE.getStatus(), PromotionActivityStatusEnum.END.getStatus());
if (!statuses.contains(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
}
// 更新秒杀时段的秒杀活动数量
seckillTimeService.sekillActivityCountDecr(seckillActivity.getTimeIds());
// 删除
seckillActivityMapper.deleteById(id);
}
private SeckillActivityDO validateSeckillActivityExists(Long id) {
SeckillActivityDO seckillActivity = seckillActivityMapper.selectById(id);
if (seckillActivity == null) {
throw exception(SECKILL_ACTIVITY_NOT_EXISTS);
}
return seckillActivity;
}
@Override
public SeckillActivityDO getSeckillActivity(Long id) {
return seckillActivityMapper.selectById(id);
}
@Override
public List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids) {
return seckillActivityMapper.selectBatchIds(ids);
}
@Override
public PageResult<SeckillActivityDO> getSeckillActivityPage(SeckillActivityPageReqVO pageReqVO) {
return seckillActivityMapper.selectPage(pageReqVO);
}
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Long id) {
return seckillProductMapper.selectListByActivityId(id);
}
}

View File

@@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckilltime;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 秒杀时段 Service 接口
*
* @author halfninety
*/
public interface SeckillTimeService {
/**
* 创建秒杀时段
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSeckillTime(@Valid SeckillTimeCreateReqVO createReqVO);
/**
* 更新秒杀时段
*
* @param updateReqVO 更新信息
*/
void updateSeckillTime(@Valid SeckillTimeUpdateReqVO updateReqVO);
/**
* 删除秒杀时段
*
* @param id 编号
*/
void deleteSeckillTime(Long id);
/**
* 获得秒杀时段
*
* @param id 编号
* @return 秒杀时段
*/
SeckillTimeDO getSeckillTime(Long id);
/**
* 获得所有秒杀时段列表
*
* @return 所有秒杀时段列表
*/
List<SeckillTimeDO> getSeckillTimeList();
/**
* 校验秒杀时段是否存在
*
* @param timeIds 秒杀时段id集合
*/
void validateSeckillTimeExists(Collection<Long> timeIds);
/**
* 秒杀时段列表的秒杀活动数量加 1
*
* @param ids 秒杀时段id列表
*/
void sekillActivityCountIncr(Collection<Long> ids);
/**
* 秒杀时段列表的秒杀活动数量减 1
*
* @param ids 秒杀时段id列表
*/
void sekillActivityCountDecr(Collection<Long> ids);
}

View File

@@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckilltime;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckilltime.SeckillTimeConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckilltime.SeckillTimeMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_TIME_CONFLICTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_TIME_NOT_EXISTS;
/**
* 秒杀时段 Service 实现类
*
* @author halfninety
*/
@Service
@Validated
public class SeckillTimeServiceImpl implements SeckillTimeService {
@Resource
private SeckillTimeMapper seckillTimeMapper;
@Override
public Long createSeckillTime(SeckillTimeCreateReqVO createReqVO) {
// 校验时间段是否冲突
validateSeckillTimeConflict(null, createReqVO.getStartTime(), createReqVO.getEndTime());
// 插入
SeckillTimeDO seckillTime = SeckillTimeConvert.INSTANCE.convert(createReqVO);
seckillTimeMapper.insert(seckillTime);
// 返回
return seckillTime.getId();
}
@Override
public void updateSeckillTime(SeckillTimeUpdateReqVO updateReqVO) {
// 校验存在
this.validateSeckillTimeExists(updateReqVO.getId());
// 校验时间段是否冲突
validateSeckillTimeConflict(updateReqVO.getId(), updateReqVO.getStartTime(), updateReqVO.getEndTime());
// 更新
SeckillTimeDO updateObj = SeckillTimeConvert.INSTANCE.convert(updateReqVO);
seckillTimeMapper.updateById(updateObj);
}
@Override
public void deleteSeckillTime(Long id) {
// 校验存在
this.validateSeckillTimeExists(id);
// 删除
seckillTimeMapper.deleteById(id);
}
private void validateSeckillTimeExists(Long id) {
if (seckillTimeMapper.selectById(id) == null) {
throw exception(SECKILL_TIME_NOT_EXISTS);
}
}
/**
* 校验时间是否存在冲突
*
* @param startTime 开始时间
* @param endTime 结束时间
*/
private void validateSeckillTimeConflict(Long id, LocalTime startTime, LocalTime endTime) {
//查询开始时间,结束时间,是否在别人的时间段内
List<SeckillTimeDO> startTimeList = seckillTimeMapper.selectListByTime(startTime);
List<SeckillTimeDO> endTimeList = seckillTimeMapper.selectListByTime(endTime);
//查询自己时间段内是否有时间段
List<SeckillTimeDO> startEndTimeList = seckillTimeMapper.selectListByTime(startTime, endTime);
if (id != null) {
//移除自己
startTimeList.removeIf(seckillTime -> Objects.equals(seckillTime.getId(), id));
endTimeList.removeIf(seckillTime -> Objects.equals(seckillTime.getId(), id));
startEndTimeList.removeIf(seckillTime -> Objects.equals(seckillTime.getId(), id));
}
if (CollUtil.isNotEmpty(startTimeList) || CollUtil.isNotEmpty(endTimeList)
|| CollUtil.isNotEmpty(startEndTimeList)) {
throw exception(SECKILL_TIME_CONFLICTS);
}
}
@Override
public SeckillTimeDO getSeckillTime(Long id) {
return seckillTimeMapper.selectById(id);
}
@Override
public List<SeckillTimeDO> getSeckillTimeList() {
return seckillTimeMapper.selectList();
}
@Override
public void validateSeckillTimeExists(Collection<Long> timeIds) {
if (CollUtil.isEmpty(timeIds)) {
throw exception(SECKILL_TIME_NOT_EXISTS);
}
if (seckillTimeMapper.selectBatchIds(timeIds).size() != timeIds.size()) {
throw exception(SECKILL_TIME_NOT_EXISTS);
}
}
@Override
public void sekillActivityCountIncr(Collection<Long> ids) {
seckillTimeMapper.updateActivityCount(ids, "+", 1);
}
@Override
public void sekillActivityCountDecr(Collection<Long> ids) {
seckillTimeMapper.updateActivityCount(ids, "-", 1);
}
}

View File

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

View File

@@ -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>

View File

@@ -0,0 +1,147 @@
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.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 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.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.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.COUPON_TEMPLATE_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link CouponTemplateServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(CouponTemplateServiceImpl.class)
public class CouponTemplateServiceImplTest extends BaseDbUnitTest {
@Resource
private CouponTemplateServiceImpl couponTemplateService;
@Resource
private CouponTemplateMapper couponTemplateMapper;
@Test
public void testCreateCouponTemplate_success() {
// 准备参数
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);
// 断言
assertNotNull(couponTemplateId);
// 校验记录的属性是否正确
CouponTemplateDO couponTemplate = couponTemplateMapper.selectById(couponTemplateId);
assertPojoEquals(reqVO, couponTemplate);
}
@Test
public void testUpdateCouponTemplate_success() {
// mock 数据
CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class);
couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据
// 准备参数
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());
});
// 调用
couponTemplateService.updateCouponTemplate(reqVO);
// 校验是否更新正确
CouponTemplateDO couponTemplate = couponTemplateMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, couponTemplate);
}
@Test
public void testUpdateCouponTemplate_notExists() {
// 准备参数
CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_EXISTS);
}
@Test
public void testDeleteCouponTemplate_success() {
// mock 数据
CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class);
couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbCouponTemplate.getId();
// 调用
couponTemplateService.deleteCouponTemplate(id);
// 校验数据不存在了
assertNull(couponTemplateMapper.selectById(id));
}
@Test
public void testDeleteCouponTemplate_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> couponTemplateService.deleteCouponTemplate(id), COUPON_TEMPLATE_NOT_EXISTS);
}
@Test
public void testGetCouponTemplatePage() {
// mock 数据
CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class, o -> { // 等会查询到
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("土豆")));
// 测试 status 不匹配
couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 type 不匹配
couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType())));
// 测试 createTime 不匹配
couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setCreateTime(buildTime(2022, 1, 1))));
// 准备参数
CouponTemplatePageReqVO reqVO = new CouponTemplatePageReqVO();
reqVO.setName("芋艿");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType());
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2022, 2, 1), buildTime(2022, 2, 3)}));
// 调用
PageResult<CouponTemplateDO> pageResult = couponTemplateService.getCouponTemplatePage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbCouponTemplate, pageResult.getList().get(0));
}
}

Some files were not shown because too many files have changed in this diff Show More