将 market 模块,调整成 promotion 模块

This commit is contained in:
YunaiV
2022-10-30 17:37:07 +08:00
parent 829bbf58b5
commit e6acdf7098
64 changed files with 152 additions and 155 deletions

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.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");
}
}

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

View File

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

View File

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

View File

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

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.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 ==========
}

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

View File

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

View File

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

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,4 @@
/**
* TODO 占位
*/
package cn.iocoder.yudao.module.promotion.dal.mysql.discount;

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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