mall+order: 订单活动特殊逻辑的抽离

This commit is contained in:
puhui999 2023-09-11 17:48:01 +08:00
parent 7fb455096c
commit 254d7828aa
23 changed files with 526 additions and 141 deletions

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
/**
* 拼团活动 Api 接口
*
* @author HUIHUI
*/
public interface CombinationApi {
/**
* 更新活动库存
*
* @param reqDTO 请求
*/
void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO);
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* 拼团活动更新活动库存 Request DTO
*
* @author HUIHUI
*/
@Data
public class CombinationActivityUpdateStockReqDTO {
@NotNull(message = "活动编号不能为空")
private Long activityId;
@NotNull(message = "购买数量不能为空")
private Integer count;
@NotNull(message = "活动商品不能为空")
private Item item;
@Data
@Valid
public static class Item {
@NotNull(message = "SPU 编号不能为空")
private Long spuId;
@NotNull(message = "SKU 编号活动商品不能为空")
private Long skuId;
@NotNull(message = "购买数量不能为空")
private Integer count;
}
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
// TODO @芋艿这块要在看看
@ -14,65 +13,25 @@ import javax.validation.constraints.NotNull;
@Data
public class CombinationRecordCreateReqDTO {
/**
* 拼团活动编号
*/
@NotNull(message = "拼团活动编号不能为空")
private Long activityId;
/**
* spu 编号
*/
@NotNull(message = "spu 编号不能为空")
private Long spuId;
/**
* sku 编号
*/
@NotNull(message = "sku 编号不能为空")
private Long skuId;
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
/**
* 团长编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
@NotNull(message = "团长编号不能为空")
private Long headId;
/**
* 商品名字
*/
@NotEmpty(message = "商品名字不能为空")
private String spuName;
/**
* 商品图片
*/
@NotEmpty(message = "商品图片不能为空")
private String picUrl;
/**
* 拼团商品单价
*/
@NotNull(message = "拼团商品单价不能为空")
private Integer combinationPrice;
/**
* 用户昵称
*/
@NotEmpty(message = "用户昵称不能为空")
private String nickname;
/**
* 用户头像
*/
@NotEmpty(message = "用户头像不能为空")
private String avatar;
/**
* 开团状态正在开团 拼团成功 拼团失败
*/
@NotNull(message = "开团状态不能为空")
private Integer status;
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.api.seckill.dto;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
@ -22,6 +23,7 @@ public class SeckillActivityUpdateStockReqDTO {
private Item item;
@Data
@Valid
public static class Item {
@NotNull(message = "SPU 编号不能为空")

View File

@ -65,18 +65,19 @@ public interface ErrorCodeConstants {
// ========== 拼团活动 1013010000 ==========
ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
ErrorCode COMBINATION_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013010001, "存在商品参加了其它拼团活动");
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE_NOT_UPDATE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013010004, "拼团不存在");
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010004, "拼团失败,原因:拼团活动已关闭");
// ========== 拼团记录 1013011000 ==========
ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1013011000, "拼团失败,已参与过该拼团");
ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013011001, "拼团失败,父拼团不存在");
ErrorCode COMBINATION_RECORD_USER_FULL = new ErrorCode(1013011002, "拼团失败,拼团人数已满");
ErrorCode COMBINATION_RECORD_FAILED_HAVE_JOINED = new ErrorCode(1013011003, "拼团失败,已参与其它拼团");
ErrorCode COMBINATION_RECORD_FAILED_TIME_END = new ErrorCode(1013011004, "拼团失败,活动已经结束");
ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1013011005, "拼团失败,单次限购超出");
ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1013011006, "拼团失败,单次限购超出");
ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013011000, "拼团不存在");
ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1013011001, "拼团失败,已参与过该拼团");
ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013011002, "拼团失败,父拼团不存在");
ErrorCode COMBINATION_RECORD_USER_FULL = new ErrorCode(1013011003, "拼团失败,拼团人数已满");
ErrorCode COMBINATION_RECORD_FAILED_HAVE_JOINED = new ErrorCode(1013011004, "拼团失败,已参与其它拼团");
ErrorCode COMBINATION_RECORD_FAILED_TIME_END = new ErrorCode(1013011005, "拼团失败,活动已经结束");
ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1013011006, "拼团失败,原因:单次限购超出");
ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1013011007, "拼团失败,原因:超出总购买次数");
// ========== 砍价活动 1013012000 ==========
ErrorCode BARGAIN_ACTIVITY_NOT_EXISTS = new ErrorCode(1013012000, "砍价活动不存在");

View File

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

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import javax.annotation.Resource;
/**
* 拼团活动 Api 接口实现类
*
* @author HUIHUI
*/
public class CombinationApiImpl implements CombinationApi {
@Resource
private CombinationActivityService activityService;
@Override
public void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO) {
activityService.validateCombination(reqDTO);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.promotion.service.combination;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
@ -72,4 +73,11 @@ public interface CombinationActivityService {
*/
List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds);
/**
* 更新拼图活动库存
*
* @param reqDTO 请求
*/
void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO);
}

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
@ -16,8 +17,11 @@ import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationProductMapper;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -28,8 +32,8 @@ import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -48,11 +52,15 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
private CombinationActivityMapper combinationActivityMapper;
@Resource
private CombinationProductMapper combinationProductMapper;
@Resource
private CombinationRecordService combinationRecordService;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private TradeOrderApi tradeOrderApi;
@Override
@Transactional(rollbackFor = Exception.class)
@ -97,7 +105,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
* 校验拼团商品是否都存在
*
* @param spuId 商品 SPU 编号
* @param products 秒杀商品
* @param products 拼团商品
*/
private void validateProductExists(Long spuId, List<CombinationProductBaseVO> products) {
// 1. 校验商品 spu 是否存在
@ -123,7 +131,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
CombinationActivityDO activityDO = validateCombinationActivityExists(updateReqVO.getId());
// 校验状态
if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE_NOT_UPDATE);
}
// 校验商品冲突
validateProductConflict(updateReqVO.getSpuId(), updateReqVO.getId());
@ -205,4 +213,30 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
return combinationProductMapper.selectListByActivityIds(activityIds);
}
@Override
public void validateCombination(CombinationActivityUpdateStockReqDTO reqDTO) {
// 1校验拼团活动是否存在
CombinationActivityDO activity = validateCombinationActivityExists(reqDTO.getActivityId());
// 1.1校验活动是否开启
if (ObjectUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
}
// 1.2校验是否超出单次限购数量
if (activity.getSingleLimitCount() < reqDTO.getCount()) {
throw exception(COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED);
}
// 1.3校验是否超出总限购数量
List<CombinationRecordDO> recordList = combinationRecordService.getRecordListByUserIdAndActivityId(getLoginUserId(), reqDTO.getActivityId());
if (CollUtil.isNotEmpty(recordList)) {
// 过滤出拼团成功的
List<Long> skuIds = convertList(recordList, CombinationRecordDO::getSkuId,
item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
Integer countSum = tradeOrderApi.getOrderItemCountSumByOrderIdAndSkuId(convertList(recordList, CombinationRecordDO::getOrderId,
item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
if (activity.getTotalLimitCount() < countSum) {
throw exception(COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED);
}
}
}
}

View File

@ -2,12 +2,19 @@ package cn.iocoder.yudao.module.promotion.service.combination;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -32,10 +39,18 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Resource
private CombinationActivityService combinationActivityService;
@Resource
private CombinationRecordMapper recordMapper;
@Resource
private MemberUserApi memberUserApi;
@Resource
@Lazy
private ProductSpuApi productSpuApi;
@Resource
@Lazy
private ProductSkuApi productSkuApi;
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
@ -102,12 +117,12 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
// 1.5 父拼团是否存在,是否已经满了
if (reqDTO.getHeadId() != null) {
// 查询进行中的父拼团
CombinationRecordDO recordDO1 = recordMapper.selectOneByHeadId(reqDTO.getHeadId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
if (recordDO1 == null) {
CombinationRecordDO record = recordMapper.selectOneByHeadId(reqDTO.getHeadId(), CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
if (record == null) {
throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
}
// 校验拼团是否满足要求
if (ObjectUtil.equal(recordDO1.getUserCount(), recordDO1.getUserSize())) {
if (ObjectUtil.equal(record.getUserCount(), record.getUserSize())) {
throw exception(COMBINATION_RECORD_USER_FULL);
}
}
@ -117,6 +132,13 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
record.setVirtualGroup(false);
record.setExpireTime(record.getStartTime().plusHours(activity.getLimitDuration()));
record.setUserSize(activity.getUserSize());
MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
record.setNickname(user.getNickname());
record.setAvatar(user.getAvatar());
ProductSpuRespDTO spu = productSpuApi.getSpu(record.getSpuId());
record.setSpuName(spu.getName());
ProductSkuRespDTO sku = productSkuApi.getSku(record.getSkuId());
record.setPicUrl(sku.getPicUrl());
recordMapper.insert(record);
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.trade.api.order;
import java.util.Collection;
/**
* 订单 API 接口
*
@ -16,4 +18,13 @@ public interface TradeOrderApi {
*/
Long validateOrder(Long userId, Long orderItemId);
/**
* 获取订单项商品购买数量总和
*
* @param orderIds 订单编号
* @param skuIds sku 编号
* @return 订单项商品购买数量总和
*/
Integer getOrderItemCountSumByOrderIdAndSkuId(Collection<Long> orderIds, Collection<Long> skuIds);
}

View File

@ -6,6 +6,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_ITEM_NOT_FOUND;
@ -32,4 +33,9 @@ public class TradeOrderApiImpl implements TradeOrderApi {
return item.getOrderId();
}
@Override
public Integer getOrderItemCountSumByOrderIdAndSkuId(Collection<Long> orderIds, Collection<Long> skuIds) {
return tradeOrderQueryService.getOrderItemCountSumByOrderIdAndSkuId(orderIds, skuIds);
}
}

View File

@ -14,7 +14,9 @@ import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDT
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
@ -31,6 +33,8 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEn
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.mapstruct.Mapper;
@ -253,21 +257,15 @@ public interface TradeOrderConvert {
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address);
@Mappings({
@Mapping(target = "activityId", source = "createReqVO.combinationActivityId"),
@Mapping(target = "spuId", source = "orderItem.spuId"),
@Mapping(target = "skuId", source = "orderItem.skuId"),
@Mapping(target = "userId", source = "order.userId"),
@Mapping(target = "orderId", source = "order.id"),
@Mapping(target = "headId", source = "createReqVO.combinationHeadId"),
@Mapping(target = "spuName", source = "orderItem.spuName"),
@Mapping(target = "picUrl", source = "orderItem.picUrl"),
@Mapping(target = "combinationPrice", source = "orderItem.payPrice"),
@Mapping(target = "nickname", source = "user.nickname"),
@Mapping(target = "avatar", source = "user.avatar"),
@Mapping(target = "status", ignore = true)
@Mapping(target = "activityId", source = "afterOrderCreateReqBO.combinationActivityId"),
@Mapping(target = "spuId", source = "afterOrderCreateReqBO.spuId"),
@Mapping(target = "skuId", source = "afterOrderCreateReqBO.skuId"),
@Mapping(target = "orderId", source = "afterOrderCreateReqBO.orderId"),
@Mapping(target = "userId", source = "afterOrderCreateReqBO.userId"),
@Mapping(target = "headId", source = "afterOrderCreateReqBO.combinationHeadId"),
@Mapping(target = "combinationPrice", source = "afterOrderCreateReqBO.payPrice"),
})
CombinationRecordCreateReqDTO convert(TradeOrderDO order, TradeOrderItemDO orderItem,
AppTradeOrderCreateReqVO createReqVO, MemberUserRespDTO user);
CombinationRecordCreateReqDTO convert(TradeAfterOrderCreateReqBO afterOrderCreateReqBO);
List<AppOrderExpressTrackRespDTO> convertList02(List<ExpressTrackRespDTO> list);
@ -283,4 +281,24 @@ public interface TradeOrderConvert {
.setFirstFixedPrice(sku.getFirstBrokerageRecord())
.setSecondFixedPrice(sku.getSecondBrokerageRecord());
}
@Mapping(target = "activityId", source = "reqBO.seckillActivityId")
SeckillActivityUpdateStockReqDTO convert(TradeBeforeOrderCreateReqBO reqBO);
@Mapping(target = "activityId", source = "reqBO.combinationActivityId")
CombinationActivityUpdateStockReqDTO convert1(TradeBeforeOrderCreateReqBO reqBO);
TradeBeforeOrderCreateReqBO convert(AppTradeOrderCreateReqVO createReqVO);
@Mappings({
@Mapping(target = "combinationActivityId", source = "createReqVO.combinationActivityId"),
@Mapping(target = "combinationHeadId", source = "createReqVO.combinationHeadId"),
@Mapping(target = "spuId", source = "orderItem.spuId"),
@Mapping(target = "skuId", source = "orderItem.skuId"),
@Mapping(target = "orderId", source = "tradeOrderDO.id"),
@Mapping(target = "userId", source = "userId"),
@Mapping(target = "payPrice", source = "tradeOrderDO.payPrice"),
})
TradeAfterOrderCreateReqBO convert(Long userId, AppTradeOrderCreateReqVO createReqVO, TradeOrderDO tradeOrderDO, TradeOrderItemDO orderItem);
}

View File

@ -29,7 +29,7 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
default List<TradeOrderItemDO> selectListByOrderIdAnSkuId(Collection<Long> orderIds, Collection<Long> skuIds) {
return selectList(new LambdaQueryWrapperX<TradeOrderItemDO>()
.in(TradeOrderItemDO::getOrderId, orderIds)
.eq(TradeOrderItemDO::getSkuId, skuIds));
.in(TradeOrderItemDO::getSkuId, skuIds));
}
default TradeOrderItemDO selectByIdAndUserId(Long orderItemId, Long loginUserId) {

View File

@ -119,4 +119,13 @@ public interface TradeOrderQueryService {
*/
List<TradeOrderItemDO> getOrderItemListByOrderId(Collection<Long> orderIds);
/**
* 获取订单项商品购买数量总和
*
* @param orderIds 订单编号
* @param skuIds sku 编号
* @return 订单项商品购买数量总和
*/
Integer getOrderItemCountSumByOrderIdAndSkuId(Collection<Long> orderIds, Collection<Long> skuIds);
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
@ -167,4 +168,10 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
return tradeOrderItemMapper.selectListByOrderId(orderIds);
}
@Override
public Integer getOrderItemCountSumByOrderIdAndSkuId(Collection<Long> orderIds, Collection<Long> skuIds) {
List<TradeOrderItemDO> tradeOrderItems = tradeOrderItemMapper.selectListByOrderIdAnSkuId(orderIds, skuIds);
return CollectionUtils.getSumValue(tradeOrderItems, TradeOrderItemDO::getCount, Integer::sum);
}
}

View File

@ -13,8 +13,6 @@ import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
@ -24,15 +22,10 @@ import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@ -59,6 +52,8 @@ import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
import cn.iocoder.yudao.module.trade.service.message.bo.TradeOrderMessageWhenDeliveryOrderReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@ -95,6 +90,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private TradeOrderItemMapper tradeOrderItemMapper;
@Resource
private TradeOrderNoRedisDAO orderNoRedisDAO;
@Resource
private List<TradeOrderHandler> orderHandlers;
@Resource
private CartService cartService;
@ -118,12 +115,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Resource
private BargainRecordApi bargainRecordApi;
@Resource
private SeckillActivityApi seckillActivityApi;
@Resource
private BargainActivityApi bargainActivityApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberPointApi memberPointApi;
@ -188,7 +179,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
// 1. 价格计算
// 1执行订单创建前置处理器
TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO);
beforeOrderCreateReqBO.setOrderType(validateActivity(createReqVO));
beforeOrderCreateReqBO.setCount(CollectionUtils.getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum));
orderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO));
// 2. 价格计算
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
// 2.1 插入 TradeOrderDO 订单
@ -198,37 +195,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 3. 订单创建完后的逻辑
afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
// 3.1 拼团的特殊逻辑
// TODO @puhui999这个逻辑先抽个小方法未来要通过设计模式把这些拼团之类的逻辑抽象出去
// 拼团
if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
createCombinationRecord(userId, createReqVO, orderItems, order);
}
// 3.2 秒杀的特殊逻辑
if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), order.getType())) {
}
// 3.3 砍价的特殊逻辑
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
return order;
}
private void createCombinationRecord(Long userId, AppTradeOrderCreateReqVO createReqVO, List<TradeOrderItemDO> orderItems, TradeOrderDO order) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
List<CombinationRecordRespDTO> recordRespDTOS = combinationRecordApi.getRecordListByUserIdAndActivityId(userId, createReqVO.getCombinationActivityId());
// TODO 拼团一次应该只能选择一种规格的商品
TradeOrderItemDO orderItemDO = orderItems.get(0);
if (CollUtil.isNotEmpty(recordRespDTOS)) {
List<Long> skuIds = convertList(recordRespDTOS, CombinationRecordRespDTO::getSkuId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus()));
List<TradeOrderItemDO> tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderIdAnSkuId(convertList(recordRespDTOS,
CombinationRecordRespDTO::getOrderId, item -> ObjectUtil.equals(item.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus())), skuIds);
combinationRecordApi.validateCombinationLimitCount(createReqVO.getCombinationActivityId(),
CollectionUtils.getSumValue(tradeOrderItemDOS, TradeOrderItemDO::getCount, Integer::sum), orderItemDO.getCount());
}
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, orderItemDO, createReqVO, user));
}
// TODO @puhui999订单超时自动取消
@ -310,13 +281,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
TradePriceCalculateRespBO calculateRespBO) {
Integer count = getSumValue(orderItems, TradeOrderItemDO::getCount, Integer::sum);
// 1如果是秒杀商品额外扣减秒杀的库存
if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), tradeOrderDO.getType())) {
seckillActivityApi.updateSeckillStock(getSeckillActivityUpdateStockReqDTO(createReqVO, orderItems, count));
}
// 2如果是砍价活动额外扣减砍价的库存
bargainActivityApi.updateBargainActivityStock(createReqVO.getBargainActivityId(), count);
// 执行订单创建后置处理器
orderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0))));
// 扣减积分 TODO 芋艿待实现需要前置
// 这个是不是应该放到支付成功之后如果支付后的话可能积分可以重复使用哈资源类都要预扣
@ -341,19 +307,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 增加订单日志 TODO 芋艿待实现
}
private SeckillActivityUpdateStockReqDTO getSeckillActivityUpdateStockReqDTO(AppTradeOrderCreateReqVO createReqVO, List<TradeOrderItemDO> orderItems, Integer count) {
SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
updateStockReqDTO.setCount(count);
// 秒杀活动只能选择一个商品
TradeOrderItemDO item = orderItems.get(0);
SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
item1.setSpuId(item.getSpuId());
item1.setSkuId(item.getSkuId());
item1.setCount(item.getCount());
updateStockReqDTO.setItem(item1);
return updateStockReqDTO;
}
private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
// 创建支付单用于后续的支付
@ -768,6 +721,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
}
// TODO 活动相关库存回滚需要活动 id活动 id 怎么获取app 端能否传过来
orderHandlers.forEach(handler -> handler.rollbackStock());
// 2.回滚库存
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.trade.service.order.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 订单创建之后 Request BO
*
* @author HUIHUI
*/
@Data
public class TradeAfterOrderCreateReqBO {
// ========== 拼团活动相关字段 ==========
@Schema(description = "拼团活动编号", example = "1024")
private Long combinationActivityId;
@Schema(description = "拼团团长编号", example = "2048")
private Long combinationHeadId;
@NotNull(message = "SPU 编号不能为空")
private Long spuId;
@NotNull(message = "SKU 编号活动商品不能为空")
private Long skuId;
@NotNull(message = "订单编号不能为空")
private Long orderId;
@NotNull(message = "用户编号不能为空")
private Long userId;
@NotNull(message = "支付金额不能为空")
private Integer payPrice;
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.trade.service.order.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* 订单创建之前 Request BO
*
* @author HUIHUI
*/
@Data
public class TradeBeforeOrderCreateReqBO {
@NotNull(message = "订单类型不能为空")
private Integer orderType;
// ========== 秒杀活动相关字段 ==========
@Schema(description = "秒杀活动编号", example = "1024")
private Long seckillActivityId;
// ========== 拼团活动相关字段 ==========
@Schema(description = "拼团活动编号", example = "1024")
private Long combinationActivityId;
@Schema(description = "拼团团长编号", example = "2048")
private Long combinationHeadId;
@Schema(description = "砍价活动编号", example = "123")
private Long bargainActivityId;
@NotNull(message = "购买数量不能为空")
private Integer count;
@NotNull(message = "活动商品不能为空")
private Item item;
@Data
@Valid
public static class Item {
@NotNull(message = "SPU 编号不能为空")
private Long spuId;
@NotNull(message = "SKU 编号活动商品不能为空")
private Long skuId;
@NotNull(message = "购买数量不能为空")
private Integer count;
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 砍价订单 handler 实现类
*
* @author HUIHUI
*/
@Component
public class TradeBargainHandler implements TradeOrderHandler {
@Resource
private BargainActivityApi bargainActivityApi;
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
// 如果是秒杀订单
if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), reqBO.getOrderType())) {
return;
}
// 额外扣减砍价的库存
bargainActivityApi.updateBargainActivityStock(reqBO.getBargainActivityId(), reqBO.getCount());
}
@Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
}
@Override
public void rollbackStock() {
}
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationApi;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 拼团订单 handler 接口实现类
*
* @author HUIHUI
*/
@Component
public class TradeCombinationHandler implements TradeOrderHandler {
@Resource
private CombinationApi combinationApi;
@Resource
private CombinationRecordApi combinationRecordApi;
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
// 如果是拼团订单
if (ObjectUtil.notEqual(TradeOrderTypeEnum.COMBINATION.getType(), reqBO.getOrderType())) {
return;
}
// 校验是否满足拼团活动相关限制
combinationApi.validateCombination(TradeOrderConvert.INSTANCE.convert1(reqBO));
}
@Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
// 创建砍价记录
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO));
}
@Override
public void rollbackStock() {
}
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
/**
* 订单活动特殊逻辑处理器 handler 接口
*
* @author HUIHUI
*/
public interface TradeOrderHandler {
/**
* 订单创建前
*
* @param reqBO 请求
*/
void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO);
/**
* 订单创建后
*
* @param reqBO 请求
*/
void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
/**
* 回滚活动相关库存
*/
void rollbackStock();
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeAfterOrderCreateReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.TradeBeforeOrderCreateReqBO;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 秒杀订单 handler 实现类
*
* @author HUIHUI
*/
@Component
public class TradeSeckillHandler implements TradeOrderHandler {
@Resource
private SeckillActivityApi seckillActivityApi;
@Override
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
// 如果是秒杀订单额外扣减秒杀的库存
if (ObjectUtil.notEqual(TradeOrderTypeEnum.SECKILL.getType(), reqBO.getOrderType())) {
return;
}
seckillActivityApi.updateSeckillStock(TradeOrderConvert.INSTANCE.convert(reqBO));
}
@Override
public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) {
}
@Override
public void rollbackStock() {
}
}