mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-08-22 06:01:53 +08:00
将 market 模块,调整成 promotion 模块
This commit is contained in:
72
yudao-module-mall/yudao-module-promotion-biz/pom.xml
Normal file
72
yudao-module-mall/yudao-module-promotion-biz/pom.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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-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>
|
@@ -0,0 +1 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.discount;
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
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.*;
|
||||
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.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - Banner 管理")
|
||||
@RestController
|
||||
@RequestMapping("/market/banner")
|
||||
@Validated
|
||||
public class BannerController {
|
||||
|
||||
@Resource
|
||||
private BannerService bannerService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation("创建 Banner")
|
||||
@PreAuthorize("@ss.hasPermission('market:banner:create')")
|
||||
public CommonResult<Long> createBanner(@Valid @RequestBody BannerCreateReqVO createReqVO) {
|
||||
return success(bannerService.createBanner(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@ApiOperation("更新 Banner")
|
||||
@PreAuthorize("@ss.hasPermission('market:banner:update')")
|
||||
public CommonResult<Boolean> updateBanner(@Valid @RequestBody BannerUpdateReqVO updateReqVO) {
|
||||
bannerService.updateBanner(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ApiOperation("删除 Banner")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
|
||||
@PreAuthorize("@ss.hasPermission('market:banner:delete')")
|
||||
public CommonResult<Boolean> deleteBanner(@RequestParam("id") Long id) {
|
||||
bannerService.deleteBanner(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation("获得 Banner")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
|
||||
@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")
|
||||
@ApiOperation("获得 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));
|
||||
}
|
||||
|
||||
}
|
@@ -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.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Banner Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
* @author xia
|
||||
*/
|
||||
@Data
|
||||
public class BannerBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "标题", required = true)
|
||||
@NotNull(message = "标题不能为空")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty(value = "跳转链接", required = true)
|
||||
@NotNull(message = "跳转链接不能为空")
|
||||
private String url;
|
||||
|
||||
@ApiModelProperty(value = "图片地址", required = true)
|
||||
@NotNull(message = "图片地址不能为空")
|
||||
private String picUrl;
|
||||
|
||||
@ApiModelProperty(value = "排序", required = true)
|
||||
@NotNull(message = "排序不能为空")
|
||||
private Integer sort;
|
||||
|
||||
@ApiModelProperty(value = "状态", required = true)
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@ApiModelProperty(value = "备注")
|
||||
private String memo;
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* @author xia
|
||||
*/
|
||||
@ApiModel("管理后台 - Banner 创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BannerCreateReqVO extends BannerBaseVO {
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
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.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
/**
|
||||
* @author xia
|
||||
*/
|
||||
@ApiModel("管理后台 - Banner 分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BannerPageReqVO extends PageParam {
|
||||
|
||||
@ApiModelProperty(value = "标题")
|
||||
private String title;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "状态")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
private Date[] createTime;
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author xia
|
||||
*/
|
||||
@ApiModel("管理后台 - Banner Response VO")
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class BannerRespVO extends BannerBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "banner编号", required = true)
|
||||
@NotNull(message = "banner编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.banner.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author xia
|
||||
*/
|
||||
@ApiModel("管理后台 - Banner更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BannerUpdateReqVO extends BannerBaseVO {
|
||||
|
||||
@ApiModelProperty(value = "banner 编号", required = true)
|
||||
@NotNull(message = "banner 编号不能为空")
|
||||
private Long id;
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.discount;
|
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.app;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "用户 App - 营销")
|
||||
@RestController
|
||||
@RequestMapping("/market/test")
|
||||
@Validated
|
||||
public class AppMarketTestController {
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation("获取 market 信息")
|
||||
public CommonResult<String> get() {
|
||||
return success("true");
|
||||
}
|
||||
}
|
@@ -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.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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")
|
||||
@Api(tags = "用户APP- 首页Banner")
|
||||
@Validated
|
||||
public class AppBannerController {
|
||||
|
||||
@Resource
|
||||
private BannerService bannerService;
|
||||
|
||||
// TODO @xia:新建一个 AppBannerRespVO,只返回必要的字段。status 要过滤下。然后 sort 下结果
|
||||
@GetMapping("/list")
|
||||
@ApiOperation("获得banner列表")
|
||||
@PreAuthorize("@ss.hasPermission('market:banner:query')")
|
||||
public CommonResult<List<BannerRespVO>> getBannerList() {
|
||||
List<BannerDO> list = bannerService.getBannerList();
|
||||
return success(BannerConvert.INSTANCE.convertList(list));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Banner Convert
|
||||
*
|
||||
* @author xia
|
||||
*/
|
||||
@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);
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.promotion.convert.discount;
|
@@ -0,0 +1,44 @@
|
||||
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.PriceCalculateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
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()).setPayPrice(order.getOriginalPrice());
|
||||
});
|
||||
return priceCalculate;
|
||||
}
|
||||
|
||||
}
|
@@ -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 芋艿 点击次数。&& 其他数据相关
|
||||
|
||||
}
|
@@ -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.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
// TODO 芋艿:待完善该实体
|
||||
/**
|
||||
* 优惠劵 DO
|
||||
*/
|
||||
@TableName("coupon")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class CouponDO extends BaseDO {
|
||||
|
||||
// ========== 基本信息 BEGIN ==========
|
||||
/**
|
||||
* 优惠劵编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 优惠劵(码)分组编号,{@link CouponTemplateDO} 的 id
|
||||
*/
|
||||
private Integer templateId;
|
||||
/**
|
||||
* 优惠劵名
|
||||
*
|
||||
* 冗余自 {@link CouponTemplateDO} 的 title
|
||||
*
|
||||
* TODO 芋艿,暂时不考虑冗余的更新
|
||||
*/
|
||||
private String title;
|
||||
// /**
|
||||
// * 核销码
|
||||
// */
|
||||
// private String verifyCode;
|
||||
/**
|
||||
* 优惠码状态
|
||||
*
|
||||
* 1-未使用
|
||||
* 2-已使用
|
||||
* 3-已失效
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
// ========== 基本信息 END ==========
|
||||
|
||||
// ========== 领取情况 BEGIN ==========
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 领取类型
|
||||
*
|
||||
* 1 - 用户主动领取
|
||||
* 2 - 后台自动发放
|
||||
*/
|
||||
private Integer takeType;
|
||||
// ========== 领取情况 END ==========
|
||||
|
||||
// ========== 使用规则 BEGIN ==========
|
||||
/**
|
||||
* 是否设置满多少金额可用,单位:分
|
||||
*/
|
||||
private Integer priceAvailable;
|
||||
/**
|
||||
* 生效开始时间
|
||||
*/
|
||||
private Date validStartTime;
|
||||
/**
|
||||
* 生效结束时间
|
||||
*/
|
||||
private Date validEndTime;
|
||||
/**
|
||||
* 商品范围
|
||||
*
|
||||
* 枚举 {@link PromotionProductScopeEnum}
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<Long> spuIds;
|
||||
// ========== 使用规则 END ==========
|
||||
|
||||
// ========== 使用效果 BEGIN ==========
|
||||
/**
|
||||
* 优惠类型
|
||||
*
|
||||
* 1-代金卷
|
||||
* 2-折扣卷
|
||||
*/
|
||||
private Integer preferentialType;
|
||||
/**
|
||||
* 折扣
|
||||
*/
|
||||
private Integer percentOff;
|
||||
/**
|
||||
* 优惠金额,单位:分。
|
||||
*/
|
||||
private Integer priceOff;
|
||||
/**
|
||||
* 折扣上限,仅在 {@link #preferentialType} 等于 2 时生效。
|
||||
*
|
||||
* 例如,折扣上限为 20 元,当使用 8 折优惠券,订单金额为 1000 元时,最高只可折扣 20 元,而非 80 元。
|
||||
*/
|
||||
private Integer discountPriceLimit;
|
||||
// ========== 使用效果 END ==========
|
||||
|
||||
// ========== 使用情况 BEGIN ==========
|
||||
// /**
|
||||
// * 使用订单号
|
||||
// */
|
||||
// private Integer usedOrderId; // TODO 芋艿,暂时不考虑这个字段
|
||||
// /**
|
||||
// * 订单中优惠面值,单位:分
|
||||
// */
|
||||
// private Integer usedPrice; // TODO 芋艿,暂时不考虑这个字段
|
||||
/**
|
||||
* 使用时间
|
||||
*/
|
||||
private Date usedTime;
|
||||
|
||||
// TODO 芋艿,后续要加优惠劵的使用日志,因为下单后,可能会取消。
|
||||
|
||||
// ========== 使用情况 END ==========
|
||||
|
||||
}
|
@@ -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.util.Date;
|
||||
|
||||
/**
|
||||
* 限时折扣活动 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 Date startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.dataobject.discount;
|
||||
|
||||
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 java.util.Date;
|
||||
|
||||
/**
|
||||
* 限时折扣商品 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;
|
||||
/**
|
||||
* 限时折扣活动的名字
|
||||
*
|
||||
* 冗余 {@link DiscountActivityDO#getName()}
|
||||
*/
|
||||
private String activityName;
|
||||
/**
|
||||
* 商品 SPU 编号
|
||||
*
|
||||
* 关联 ProductSpuDO 的 id 编号
|
||||
*/
|
||||
private Long spuId;
|
||||
/**
|
||||
* 商品 SKU 编号
|
||||
*
|
||||
* 关联 ProductSkuDO 的 id 编号
|
||||
*/
|
||||
private Long skuId;
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
/**
|
||||
* 销售价格,单位:分
|
||||
*
|
||||
* 冗余 ProductSkuDO 的 price 字段
|
||||
*/
|
||||
private Integer originalPrice;
|
||||
/**
|
||||
* 优惠价格,单位:分
|
||||
*/
|
||||
private Integer promotionPrice;
|
||||
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
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.JacksonTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
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 Date startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 条件类型
|
||||
*
|
||||
* 枚举 {@link PromotionConditionTypeEnum}
|
||||
*/
|
||||
private Integer conditionType;
|
||||
/**
|
||||
* 商品范围
|
||||
*
|
||||
* 枚举 {@link PromotionProductScopeEnum}
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<Long> spuIds;
|
||||
/**
|
||||
* 优惠规则的数组
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<Rule> rules;
|
||||
|
||||
/**
|
||||
* 优惠规则
|
||||
*/
|
||||
@Data
|
||||
public static class Rule {
|
||||
|
||||
/**
|
||||
* 优惠门槛
|
||||
*
|
||||
* 1. 满 N 元,单位:分
|
||||
* 2. 满 N 件
|
||||
*/
|
||||
private Integer limit;
|
||||
/**
|
||||
* 优惠价格,单位:分
|
||||
*/
|
||||
private Integer discountPrice;
|
||||
/**
|
||||
* 是否包邮
|
||||
*/
|
||||
private Boolean freeDelivery;
|
||||
/**
|
||||
* 赠送的积分
|
||||
*/
|
||||
private Integer point;
|
||||
/**
|
||||
* 赠送的优惠劵编号的数组
|
||||
*/
|
||||
private List<Long> couponIds;
|
||||
/**
|
||||
* 赠送的优惠卷数量的数组
|
||||
*/
|
||||
private List<Integer> couponCounts;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
|
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* promotion 模块,我们放营销业务。
|
||||
* 例如说:营销活动、banner、优惠券等等
|
||||
*
|
||||
* 1. Controller URL:以 /promotion/ 开头,避免和其它 Module 冲突
|
||||
* 2. DataObject 表名:以 promotion_ 开头,方便在数据库中区分
|
||||
*/
|
||||
package cn.iocoder.yudao.module.promotion;
|
@@ -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);
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.coupon;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
|
||||
/**
|
||||
* 优惠劵 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface CouponService {
|
||||
|
||||
/**
|
||||
* 校验优惠劵,包括状态、有限期
|
||||
*
|
||||
* 1. 如果校验通过,则返回优惠劵信息
|
||||
* 2. 如果校验不通过,则直接抛出业务异常
|
||||
*
|
||||
* @param id 优惠劵编号
|
||||
* @param userId 用户编号
|
||||
* @return 优惠劵信息
|
||||
*/
|
||||
CouponDO validCoupon(Long id, Long userId);
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.coupon;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* 优惠劵 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class CouponServiceImpl implements CouponService {
|
||||
|
||||
// TODO 芋艿:待实现
|
||||
@Override
|
||||
public CouponDO validCoupon(Long id, Long userId) {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.discount;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 限时折扣 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface DiscountService {
|
||||
|
||||
/**
|
||||
* 基于指定 SKU 编号数组,获得匹配的限时折扣商品
|
||||
*
|
||||
* 注意,匹配的条件,仅仅是日期符合,并且处于开启状态
|
||||
*
|
||||
* @param skuIds SKU 编号数组
|
||||
* @return 匹配的限时折扣商品
|
||||
*/
|
||||
Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds);
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.discount;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 限时折扣 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class DiscountServiceImpl implements DiscountService {
|
||||
|
||||
// TODO 芋艿:待实现
|
||||
@Override
|
||||
public Map<Long, DiscountProductDO> getMatchDiscountProducts(Collection<Long> skuIds) {
|
||||
Map<Long, DiscountProductDO> products = new HashMap<>();
|
||||
products.put(1L, new DiscountProductDO().setPromotionPrice(100));
|
||||
products.put(2L, new DiscountProductDO().setPromotionPrice(50));
|
||||
return products;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.price;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
|
||||
|
||||
/**
|
||||
* 价格计算 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PriceService {
|
||||
|
||||
/**
|
||||
* 计算商品的价格
|
||||
*
|
||||
* @param calculateReqDTO 价格请求
|
||||
* @return 价格相应
|
||||
*/
|
||||
PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
|
||||
|
||||
}
|
@@ -0,0 +1,487 @@
|
||||
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.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.price.PriceConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
|
||||
import cn.iocoder.yudao.module.promotion.service.discount.DiscountService;
|
||||
import cn.iocoder.yudao.module.promotion.service.reward.RewardService;
|
||||
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import com.google.common.base.Suppliers;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
|
||||
import static 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
|
||||
public class PriceServiceImpl implements PriceService {
|
||||
|
||||
@Resource
|
||||
private DiscountService discountService;
|
||||
@Resource
|
||||
private RewardService rewardService;
|
||||
@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);
|
||||
// 计算【优惠劵】促销 TODO 待实现
|
||||
return priceCalculate;
|
||||
}
|
||||
|
||||
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> memberDiscountSupplier = getMemberDiscountSupplier(userId);
|
||||
Map<Long, DiscountProductDO> discountProducts = discountService.getMatchDiscountProducts(
|
||||
convertSet(priceCalculate.getOrder().getItems(), PriceCalculateRespDTO.OrderItem::getSkuId));
|
||||
|
||||
// 处理每个 SKU 的优惠
|
||||
priceCalculate.getOrder().getItems().forEach(orderItem -> {
|
||||
// 获取该 SKU 的优惠信息
|
||||
Double memberDiscount = memberDiscountSupplier.get();
|
||||
DiscountProductDO discountProduct = discountProducts.get(orderItem.getSkuId());
|
||||
if (discountProduct != null // 假设优惠价格更贵,则认为没优惠
|
||||
&& discountProduct.getPromotionPrice() >= orderItem.getOriginalUnitPrice()) {
|
||||
discountProduct = null;
|
||||
}
|
||||
if (memberDiscount == null && discountProduct == null) {
|
||||
return;
|
||||
}
|
||||
// 计算价格,判断选择哪个折扣
|
||||
Integer memberPrice = memberDiscount != null ? (int) (orderItem.getPayPrice() * memberDiscount / 100) : null;
|
||||
Integer promotionPrice = discountProduct != null ? discountProduct.getPromotionPrice() * orderItem.getCount() : 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 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,
|
||||
DiscountProductDO 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> getMemberDiscountSupplier(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 = rewardService.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;
|
||||
}
|
||||
|
||||
// 分摊金额
|
||||
// TODO 芋艿:limit 不能超过最大价格
|
||||
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.getPriceAvailable()) {
|
||||
throw exception(COUPON_NO_MATCH_MIN_PRICE);
|
||||
}
|
||||
|
||||
// 计算可以优惠的金额
|
||||
priceCalculate.getOrder().setCouponId(couponId);
|
||||
Integer couponPrice = getCouponPrice(coupon, originPrice);
|
||||
// 分摊金额
|
||||
// TODO 芋艿:limit 不能超过最大价格
|
||||
List<Integer> couponPartPrices = dividePrice(orderItems, couponPrice);
|
||||
// 记录优惠明细
|
||||
addPromotion(priceCalculate, orderItems, coupon.getId(), coupon.getTitle(),
|
||||
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.getSpuIds().contains(orderItem.getSpuId()));
|
||||
}
|
||||
|
||||
private Integer getCouponPrice(CouponDO coupon, Integer originPrice) {
|
||||
// TODO 芋艿 getPreferentialType 的枚举判断
|
||||
if (coupon.getPreferentialType().equals(1)) { // 减价
|
||||
return coupon.getPriceOff();
|
||||
} else if (coupon.getPreferentialType().equals(2)) { // 打折
|
||||
Integer couponPrice = originPrice * coupon.getPercentOff() / 100;
|
||||
return coupon.getDiscountPriceLimit() == null ? couponPrice
|
||||
: Math.min(couponPrice, coupon.getDiscountPriceLimit()); // 优惠上限
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", coupon.toString()));
|
||||
}
|
||||
|
||||
// ========== 其它相对通用的方法 ==========
|
||||
|
||||
/**
|
||||
* 添加单个 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) {
|
||||
int diffPayPrice = orderItem.getPayPrice() - newPayPrice;
|
||||
// 设置 OrderItem 价格相关字段
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + diffPayPrice);
|
||||
orderItem.setPayPrice(newPayPrice);
|
||||
orderItem.setOrderDividePrice(orderItem.getPayPrice() - orderItem.getOrderPartPrice());
|
||||
// 设置 Order 相关相关字段
|
||||
priceCalculate.getOrder().setPayPrice(priceCalculate.getOrder().getPayPrice() - diffPayPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.reward;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 满减送 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface RewardService {
|
||||
|
||||
/**
|
||||
* 基于指定的 SPU 编号数组,获得它们匹配的满减送活动
|
||||
*
|
||||
* @param spuIds SPU 编号数组
|
||||
* @return 满减送活动,与对应的 SPU 编号的映射。即,value 就是 SPU 编号的集合
|
||||
*/
|
||||
Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds);
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.reward;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 满减送 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class RewardServiceImpl implements RewardService {
|
||||
|
||||
// TODO 芋艿:待实现
|
||||
@Override
|
||||
public Map<RewardActivityDO, Set<Long>> getMatchRewardActivities(Set<Long> spuIds) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,446 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.price;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionLevelEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
|
||||
import cn.iocoder.yudao.module.promotion.service.discount.DiscountService;
|
||||
import cn.iocoder.yudao.module.promotion.service.reward.RewardService;
|
||||
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link PriceServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class PriceServiceTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private PriceServiceImpl priceService;
|
||||
|
||||
@Mock
|
||||
private DiscountService discountService;
|
||||
@Mock
|
||||
private RewardService rewardService;
|
||||
@Mock
|
||||
private CouponService couponService;
|
||||
@Mock
|
||||
private ProductSkuApi productSkuApi;
|
||||
|
||||
@Test
|
||||
public void testCalculatePrice_memberDiscount() {
|
||||
// 准备参数
|
||||
// TODO 芋艿:userId = 1,实现 9 折;后续改成 mock
|
||||
PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(1L)
|
||||
.setItems(singletonList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2)));
|
||||
// mock 方法(商品 SKU 信息)
|
||||
ProductSkuRespDTO productSku = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100));
|
||||
when(productSkuApi.getSkuList(eq(asSet(10L)))).thenReturn(singletonList(productSku));
|
||||
|
||||
// 调用
|
||||
PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
|
||||
// 断言 Order 部分
|
||||
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
|
||||
assertEquals(order.getOriginalPrice(), 200);
|
||||
assertEquals(order.getDiscountPrice(), 0);
|
||||
assertEquals(order.getPointPrice(), 0);
|
||||
assertEquals(order.getDeliveryPrice(), 0);
|
||||
assertEquals(order.getPayPrice(), 180);
|
||||
assertNull(order.getCouponId());
|
||||
// 断言 OrderItem 部分
|
||||
assertEquals(order.getItems().size(), 1);
|
||||
PriceCalculateRespDTO.OrderItem orderItem = order.getItems().get(0);
|
||||
assertEquals(orderItem.getSkuId(), 10L);
|
||||
assertEquals(orderItem.getCount(), 2);
|
||||
assertEquals(orderItem.getOriginalPrice(), 200);
|
||||
assertEquals(orderItem.getOriginalUnitPrice(), 100);
|
||||
assertEquals(orderItem.getDiscountPrice(), 20);
|
||||
assertEquals(orderItem.getPayPrice(), 180);
|
||||
assertEquals(orderItem.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem.getOrderDividePrice(), 180);
|
||||
// 断言 Promotion 部分
|
||||
assertEquals(priceCalculate.getPromotions().size(), 1);
|
||||
PriceCalculateRespDTO.Promotion promotion = priceCalculate.getPromotions().get(0);
|
||||
assertNull(promotion.getId());
|
||||
assertEquals(promotion.getName(), "会员折扣");
|
||||
assertEquals(promotion.getType(), PromotionTypeEnum.MEMBER.getType());
|
||||
assertEquals(promotion.getLevel(), PromotionLevelEnum.SKU.getLevel());
|
||||
assertEquals(promotion.getOriginalPrice(), 200);
|
||||
assertEquals(promotion.getDiscountPrice(), 20);
|
||||
assertTrue(promotion.getMeet());
|
||||
assertEquals(promotion.getMeetTip(), "会员折扣:省 0.20 元");
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem = promotion.getItems().get(0);
|
||||
assertEquals(promotion.getItems().size(), 1);
|
||||
assertEquals(promotionItem.getSkuId(), 10L);
|
||||
assertEquals(promotionItem.getOriginalPrice(), 200);
|
||||
assertEquals(promotionItem.getDiscountPrice(), 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalculatePrice_discountActivity() {
|
||||
// 准备参数
|
||||
PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId())
|
||||
.setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3)));
|
||||
// mock 方法(商品 SKU 信息)
|
||||
ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100));
|
||||
ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50));
|
||||
when(productSkuApi.getSkuList(eq(asSet(10L, 20L)))).thenReturn(asList(productSku01, productSku02));
|
||||
// mock 方法(限时折扣 DiscountActivity 信息)
|
||||
DiscountProductDO discountProduct01 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(1000L).setActivityName("活动 1000 号")
|
||||
.setSkuId(10L).setPromotionPrice(80));
|
||||
DiscountProductDO discountProduct02 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(2000L).setActivityName("活动 2000 号")
|
||||
.setSkuId(20L).setPromotionPrice(40));
|
||||
when(discountService.getMatchDiscountProducts(eq(asSet(10L, 20L)))).thenReturn(
|
||||
MapUtil.builder(10L, discountProduct01).put(20L, discountProduct02).map());
|
||||
|
||||
// 调用
|
||||
PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
|
||||
// 断言 Order 部分
|
||||
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
|
||||
assertEquals(order.getOriginalPrice(), 350);
|
||||
assertEquals(order.getDiscountPrice(), 0);
|
||||
assertEquals(order.getPointPrice(), 0);
|
||||
assertEquals(order.getDeliveryPrice(), 0);
|
||||
assertEquals(order.getPayPrice(), 280);
|
||||
assertNull(order.getCouponId());
|
||||
// 断言 OrderItem 部分
|
||||
assertEquals(order.getItems().size(), 2);
|
||||
PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getOriginalPrice(), 200);
|
||||
assertEquals(orderItem01.getOriginalUnitPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 40);
|
||||
assertEquals(orderItem01.getPayPrice(), 160);
|
||||
assertEquals(orderItem01.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem01.getOrderDividePrice(), 160);
|
||||
PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getOriginalPrice(), 150);
|
||||
assertEquals(orderItem02.getOriginalUnitPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 30);
|
||||
assertEquals(orderItem02.getPayPrice(), 120);
|
||||
assertEquals(orderItem02.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem02.getOrderDividePrice(), 120);
|
||||
// 断言 Promotion 部分
|
||||
assertEquals(priceCalculate.getPromotions().size(), 2);
|
||||
PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 1000L);
|
||||
assertEquals(promotion01.getName(), "活动 1000 号");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType());
|
||||
assertEquals(promotion01.getLevel(), PromotionLevelEnum.SKU.getLevel());
|
||||
assertEquals(promotion01.getOriginalPrice(), 200);
|
||||
assertEquals(promotion01.getDiscountPrice(), 40);
|
||||
assertTrue(promotion01.getMeet());
|
||||
assertEquals(promotion01.getMeetTip(), "限时折扣:省 0.40 元");
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
|
||||
assertEquals(promotion01.getItems().size(), 1);
|
||||
assertEquals(promotionItem01.getSkuId(), 10L);
|
||||
assertEquals(promotionItem01.getOriginalPrice(), 200);
|
||||
assertEquals(promotionItem01.getDiscountPrice(), 40);
|
||||
PriceCalculateRespDTO.Promotion promotion02 = priceCalculate.getPromotions().get(1);
|
||||
assertEquals(promotion02.getId(), 2000L);
|
||||
assertEquals(promotion02.getName(), "活动 2000 号");
|
||||
assertEquals(promotion02.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType());
|
||||
assertEquals(promotion02.getLevel(), PromotionLevelEnum.SKU.getLevel());
|
||||
assertEquals(promotion02.getOriginalPrice(), 150);
|
||||
assertEquals(promotion02.getDiscountPrice(), 30);
|
||||
assertTrue(promotion02.getMeet());
|
||||
assertEquals(promotion02.getMeetTip(), "限时折扣:省 0.30 元");
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem02 = promotion02.getItems().get(0);
|
||||
assertEquals(promotion02.getItems().size(), 1);
|
||||
assertEquals(promotionItem02.getSkuId(), 20L);
|
||||
assertEquals(promotionItem02.getOriginalPrice(), 150);
|
||||
assertEquals(promotionItem02.getDiscountPrice(), 30);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试满减送活动,匹配的情况
|
||||
*/
|
||||
@Test
|
||||
public void testCalculatePrice_rewardActivity() {
|
||||
// 准备参数
|
||||
PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId())
|
||||
.setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(30L).setCount(4)));
|
||||
// mock 方法(商品 SKU 信息)
|
||||
ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L));
|
||||
ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L));
|
||||
ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L));
|
||||
when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03));
|
||||
// mock 方法(限时折扣 DiscountActivity 信息)
|
||||
RewardActivityDO rewardActivity01 = randomPojo(RewardActivityDO.class, o -> o.setId(1000L).setName("活动 1000 号")
|
||||
.setSpuIds(asList(10L, 20L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setRules(singletonList(new RewardActivityDO.Rule().setLimit(200).setDiscountPrice(70))));
|
||||
RewardActivityDO rewardActivity02 = randomPojo(RewardActivityDO.class, o -> o.setId(2000L).setName("活动 2000 号")
|
||||
.setSpuIds(singletonList(30L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType())
|
||||
.setRules(asList(new RewardActivityDO.Rule().setLimit(1).setDiscountPrice(10),
|
||||
new RewardActivityDO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个
|
||||
new RewardActivityDO.Rule().setLimit(10).setDiscountPrice(100))));
|
||||
Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>();
|
||||
matchRewardActivities.put(rewardActivity01, asSet(1L, 2L));
|
||||
matchRewardActivities.put(rewardActivity02, asSet(3L));
|
||||
when(rewardService.getMatchRewardActivities(eq(asSet(1L, 2L, 3L)))).thenReturn(matchRewardActivities);
|
||||
|
||||
// 调用
|
||||
PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
|
||||
// 断言 Order 部分
|
||||
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
|
||||
assertEquals(order.getOriginalPrice(), 470);
|
||||
assertEquals(order.getDiscountPrice(), 130);
|
||||
assertEquals(order.getPointPrice(), 0);
|
||||
assertEquals(order.getDeliveryPrice(), 0);
|
||||
assertEquals(order.getPayPrice(), 340);
|
||||
assertNull(order.getCouponId());
|
||||
// 断言 OrderItem 部分
|
||||
assertEquals(order.getItems().size(), 3);
|
||||
PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getOriginalPrice(), 200);
|
||||
assertEquals(orderItem01.getOriginalUnitPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem01.getPayPrice(), 200);
|
||||
assertEquals(orderItem01.getOrderPartPrice(), 40);
|
||||
assertEquals(orderItem01.getOrderDividePrice(), 160);
|
||||
PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getOriginalPrice(), 150);
|
||||
assertEquals(orderItem02.getOriginalUnitPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem02.getPayPrice(), 150);
|
||||
assertEquals(orderItem02.getOrderPartPrice(), 30);
|
||||
assertEquals(orderItem02.getOrderDividePrice(), 120);
|
||||
PriceCalculateRespDTO.OrderItem orderItem03 = order.getItems().get(2);
|
||||
assertEquals(orderItem03.getSkuId(), 30L);
|
||||
assertEquals(orderItem03.getCount(), 4);
|
||||
assertEquals(orderItem03.getOriginalPrice(), 120);
|
||||
assertEquals(orderItem03.getOriginalUnitPrice(), 30);
|
||||
assertEquals(orderItem03.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem03.getPayPrice(), 120);
|
||||
assertEquals(orderItem03.getOrderPartPrice(), 60);
|
||||
assertEquals(orderItem03.getOrderDividePrice(), 60);
|
||||
// 断言 Promotion 部分(第一个)
|
||||
assertEquals(priceCalculate.getPromotions().size(), 2);
|
||||
PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 1000L);
|
||||
assertEquals(promotion01.getName(), "活动 1000 号");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
|
||||
assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel());
|
||||
assertEquals(promotion01.getOriginalPrice(), 350);
|
||||
assertEquals(promotion01.getDiscountPrice(), 70);
|
||||
assertTrue(promotion01.getMeet());
|
||||
assertEquals(promotion01.getMeetTip(), "满减送:省 0.70 元");
|
||||
assertEquals(promotion01.getItems().size(), 2);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
|
||||
assertEquals(promotionItem011.getSkuId(), 10L);
|
||||
assertEquals(promotionItem011.getOriginalPrice(), 200);
|
||||
assertEquals(promotionItem011.getDiscountPrice(), 40);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
|
||||
assertEquals(promotionItem012.getSkuId(), 20L);
|
||||
assertEquals(promotionItem012.getOriginalPrice(), 150);
|
||||
assertEquals(promotionItem012.getDiscountPrice(), 30);
|
||||
// 断言 Promotion 部分(第二个)
|
||||
PriceCalculateRespDTO.Promotion promotion02 = priceCalculate.getPromotions().get(1);
|
||||
assertEquals(promotion02.getId(), 2000L);
|
||||
assertEquals(promotion02.getName(), "活动 2000 号");
|
||||
assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
|
||||
assertEquals(promotion02.getLevel(), PromotionLevelEnum.ORDER.getLevel());
|
||||
assertEquals(promotion02.getOriginalPrice(), 120);
|
||||
assertEquals(promotion02.getDiscountPrice(), 60);
|
||||
assertTrue(promotion02.getMeet());
|
||||
assertEquals(promotion02.getMeetTip(), "满减送:省 0.60 元");
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem02 = promotion02.getItems().get(0);
|
||||
assertEquals(promotion02.getItems().size(), 1);
|
||||
assertEquals(promotionItem02.getSkuId(), 30L);
|
||||
assertEquals(promotionItem02.getOriginalPrice(), 120);
|
||||
assertEquals(promotionItem02.getDiscountPrice(), 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试满减送活动,不匹配的情况
|
||||
*/
|
||||
@Test
|
||||
public void testCalculatePrice_rewardActivityNotMeet() {
|
||||
// 准备参数
|
||||
PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId())
|
||||
.setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3)));
|
||||
// mock 方法(商品 SKU 信息)
|
||||
ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L));
|
||||
ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L));
|
||||
when(productSkuApi.getSkuList(eq(asSet(10L, 20L)))).thenReturn(asList(productSku01, productSku02));
|
||||
// mock 方法(限时折扣 DiscountActivity 信息)
|
||||
RewardActivityDO rewardActivity01 = randomPojo(RewardActivityDO.class, o -> o.setId(1000L).setName("活动 1000 号")
|
||||
.setSpuIds(asList(10L, 20L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
|
||||
.setRules(singletonList(new RewardActivityDO.Rule().setLimit(351).setDiscountPrice(70))));
|
||||
Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>();
|
||||
matchRewardActivities.put(rewardActivity01, asSet(1L, 2L));
|
||||
when(rewardService.getMatchRewardActivities(eq(asSet(1L, 2L)))).thenReturn(matchRewardActivities);
|
||||
|
||||
// 调用
|
||||
PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
|
||||
// 断言 Order 部分
|
||||
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
|
||||
assertEquals(order.getOriginalPrice(), 350);
|
||||
assertEquals(order.getDiscountPrice(), 0);
|
||||
assertEquals(order.getPointPrice(), 0);
|
||||
assertEquals(order.getDeliveryPrice(), 0);
|
||||
assertEquals(order.getPayPrice(), 350);
|
||||
assertNull(order.getCouponId());
|
||||
// 断言 OrderItem 部分
|
||||
assertEquals(order.getItems().size(), 2);
|
||||
PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getOriginalPrice(), 200);
|
||||
assertEquals(orderItem01.getOriginalUnitPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem01.getPayPrice(), 200);
|
||||
assertEquals(orderItem01.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem01.getOrderDividePrice(), 200);
|
||||
PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getOriginalPrice(), 150);
|
||||
assertEquals(orderItem02.getOriginalUnitPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem02.getPayPrice(), 150);
|
||||
assertEquals(orderItem02.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem02.getOrderDividePrice(), 150);
|
||||
// 断言 Promotion 部分
|
||||
assertEquals(priceCalculate.getPromotions().size(), 1);
|
||||
PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 1000L);
|
||||
assertEquals(promotion01.getName(), "活动 1000 号");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
|
||||
assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel());
|
||||
assertEquals(promotion01.getOriginalPrice(), 350);
|
||||
assertEquals(promotion01.getDiscountPrice(), 0);
|
||||
assertFalse(promotion01.getMeet());
|
||||
assertEquals(promotion01.getMeetTip(), "TODO"); // TODO 芋艿:后面再想想
|
||||
assertEquals(promotion01.getItems().size(), 2);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
|
||||
assertEquals(promotionItem011.getSkuId(), 10L);
|
||||
assertEquals(promotionItem011.getOriginalPrice(), 200);
|
||||
assertEquals(promotionItem011.getDiscountPrice(), 0);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
|
||||
assertEquals(promotionItem012.getSkuId(), 20L);
|
||||
assertEquals(promotionItem012.getOriginalPrice(), 150);
|
||||
assertEquals(promotionItem012.getDiscountPrice(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalculatePrice_coupon() {
|
||||
// 准备参数
|
||||
PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId())
|
||||
.setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3),
|
||||
new PriceCalculateReqDTO.Item().setSkuId(30L).setCount(4)))
|
||||
.setCouponId(1024L);
|
||||
// mock 方法(商品 SKU 信息)
|
||||
ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L));
|
||||
ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L));
|
||||
ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L));
|
||||
when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03));
|
||||
// mock 方法(优惠劵 Coupon 信息)
|
||||
CouponDO coupon = randomPojo(CouponDO.class, o -> o.setId(1024L).setTitle("程序员节")
|
||||
.setProductScope(PromotionProductScopeEnum.SPU.getScope()).setSpuIds(asList(1L, 2L))
|
||||
.setPriceAvailable(350).setPreferentialType(2).setPercentOff(50).setDiscountPriceLimit(70));
|
||||
when(couponService.validCoupon(eq(1024L), eq(calculateReqDTO.getUserId()))).thenReturn(coupon);
|
||||
|
||||
// 200 + 150; 350
|
||||
//
|
||||
|
||||
// 调用
|
||||
PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO);
|
||||
// 断言 Order 部分
|
||||
PriceCalculateRespDTO.Order order = priceCalculate.getOrder();
|
||||
assertEquals(order.getOriginalPrice(), 470);
|
||||
assertEquals(order.getDiscountPrice(), 0);
|
||||
assertEquals(order.getPointPrice(), 0);
|
||||
assertEquals(order.getDeliveryPrice(), 0);
|
||||
assertEquals(order.getPayPrice(), 400);
|
||||
assertEquals(order.getCouponId(), 1024L);
|
||||
assertEquals(order.getCouponPrice(), 70);
|
||||
// 断言 OrderItem 部分
|
||||
assertEquals(order.getItems().size(), 3);
|
||||
PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getOriginalPrice(), 200);
|
||||
assertEquals(orderItem01.getOriginalUnitPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem01.getPayPrice(), 200);
|
||||
assertEquals(orderItem01.getOrderPartPrice(), 40);
|
||||
assertEquals(orderItem01.getOrderDividePrice(), 160);
|
||||
PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getOriginalPrice(), 150);
|
||||
assertEquals(orderItem02.getOriginalUnitPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem02.getPayPrice(), 150);
|
||||
assertEquals(orderItem02.getOrderPartPrice(), 30);
|
||||
assertEquals(orderItem02.getOrderDividePrice(), 120);
|
||||
PriceCalculateRespDTO.OrderItem orderItem03 = order.getItems().get(2);
|
||||
assertEquals(orderItem03.getSkuId(), 30L);
|
||||
assertEquals(orderItem03.getCount(), 4);
|
||||
assertEquals(orderItem03.getOriginalPrice(), 120);
|
||||
assertEquals(orderItem03.getOriginalUnitPrice(), 30);
|
||||
assertEquals(orderItem03.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem03.getPayPrice(), 120);
|
||||
assertEquals(orderItem03.getOrderPartPrice(), 0);
|
||||
assertEquals(orderItem03.getOrderDividePrice(), 120);
|
||||
// 断言 Promotion 部分
|
||||
assertEquals(priceCalculate.getPromotions().size(), 1);
|
||||
PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 1024L);
|
||||
assertEquals(promotion01.getName(), "程序员节");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType());
|
||||
assertEquals(promotion01.getLevel(), PromotionLevelEnum.COUPON.getLevel());
|
||||
assertEquals(promotion01.getOriginalPrice(), 350);
|
||||
assertEquals(promotion01.getDiscountPrice(), 70);
|
||||
assertTrue(promotion01.getMeet());
|
||||
assertEquals(promotion01.getMeetTip(), "优惠劵:省 0.70 元");
|
||||
assertEquals(promotion01.getItems().size(), 2);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
|
||||
assertEquals(promotionItem011.getSkuId(), 10L);
|
||||
assertEquals(promotionItem011.getOriginalPrice(), 200);
|
||||
assertEquals(promotionItem011.getDiscountPrice(), 40);
|
||||
PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
|
||||
assertEquals(promotionItem012.getSkuId(), 20L);
|
||||
assertEquals(promotionItem012.getOriginalPrice(), 150);
|
||||
assertEquals(promotionItem012.getDiscountPrice(), 30);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
spring:
|
||||
main:
|
||||
lazy-initialization: true # 开启懒加载,加快速度
|
||||
banner-mode: off # 单元测试,禁用 Banner
|
||||
|
||||
--- #################### 数据库相关配置 ####################
|
||||
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
druid:
|
||||
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
|
||||
initial-size: 1 # 单元测试,配置为 1,提升启动速度
|
||||
sql:
|
||||
init:
|
||||
schema-locations: classpath:/sql/create_tables.sql
|
||||
|
||||
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
|
||||
redis:
|
||||
host: 127.0.0.1 # 地址
|
||||
port: 16379 # 端口(单元测试,使用 16379 端口)
|
||||
database: 0 # 数据库索引
|
||||
|
||||
mybatis:
|
||||
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
||||
--- #################### 配置中心相关配置 ####################
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
# Lock4j 配置项(单元测试,禁用 Lock4j)
|
||||
|
||||
# Resilience4j 配置项
|
||||
|
||||
--- #################### 监控相关配置 ####################
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
# 芋道配置项,设置当前项目所有自定义的配置
|
||||
yudao:
|
||||
info:
|
||||
base-package: cn.iocoder.yudao.module
|
@@ -0,0 +1,4 @@
|
||||
<configuration>
|
||||
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
</configuration>
|
@@ -0,0 +1 @@
|
||||
DELETE FROM "market_activity";
|
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE IF NOT EXISTS "market_activity" (
|
||||
"id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"title" varchar(50) NOT NULL,
|
||||
"activity_type" tinyint(4) NOT NULL,
|
||||
"status" tinyint(4) NOT NULL,
|
||||
"start_time" datetime NOT NULL,
|
||||
"end_time" datetime NOT NULL,
|
||||
"invalid_time" datetime,
|
||||
"delete_time" datetime,
|
||||
"time_limited_discount" varchar(2000),
|
||||
"full_privilege" varchar(2000),
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint(20) NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '促销活动';
|
Reference in New Issue
Block a user