|
@@ -3,29 +3,27 @@ package cn.iocoder.yudao.module.trade.service.order;
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.text.StrBuilder;
|
|
|
import cn.hutool.core.util.IdUtil;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
|
|
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
|
|
-import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
|
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
|
|
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
|
|
-import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
|
|
|
-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.member.api.address.AddressApi;
|
|
|
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
|
|
|
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
|
|
|
import cn.iocoder.yudao.module.pay.api.order.PayOrderInfoCreateReqDTO;
|
|
|
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.sku.dto.SkuDecrementStockBatchReqDTO;
|
|
|
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
|
|
-import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
|
|
|
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
|
|
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
|
|
+import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
|
|
|
+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.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
|
|
|
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO.Item;
|
|
|
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
|
|
|
-import cn.iocoder.yudao.module.trade.convert.order.TradeOrderItemConvert;
|
|
|
-import cn.iocoder.yudao.module.trade.convert.pay.PayOrderConvert;
|
|
|
import cn.iocoder.yudao.module.trade.convert.price.PriceConvert;
|
|
|
-import cn.iocoder.yudao.module.trade.convert.sku.ProductSkuConvert;
|
|
|
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
|
|
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
|
|
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
|
|
@@ -43,6 +41,12 @@ import javax.annotation.Resource;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
|
|
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_SKU_NOT_SALE;
|
|
|
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_SPU_NOT_FOUND;
|
|
|
|
|
|
/**
|
|
|
* 交易订单 Service 实现类
|
|
@@ -70,66 +74,51 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|
|
|
private ProductSpuApi productSpuApi;
|
|
|
@Resource
|
|
|
private PayOrderApi payOrderApi;
|
|
|
+ @Resource
|
|
|
+ private AddressApi addressApi;
|
|
|
|
|
|
@Resource
|
|
|
private TradeOrderProperties tradeOrderProperties;
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
|
|
|
- List<Item> items = createReqVO.getItems();
|
|
|
- // 商品SKU检查 sku可售状态,库存
|
|
|
- List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
|
|
|
- Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);
|
|
|
- checkSaleableAndStockFromSpu(skuInfoMap, items);
|
|
|
-
|
|
|
- // 商品SPU检查 sku可售状态,库存
|
|
|
- List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpuList(CollectionUtils.convertSet(skuInfos, ProductSkuRespDTO::getSpuId));
|
|
|
- checkSaleableFromSpu(spuInfos);
|
|
|
+ public Long createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
|
|
|
+ // 商品 SKU 检查:可售状态、库存
|
|
|
+ List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems());
|
|
|
+ // 商品 SPU 检查:可售状态
|
|
|
+ List<ProductSpuRespDTO> spus = validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId));
|
|
|
+ // 用户收件地址的校验
|
|
|
+ AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId());
|
|
|
|
|
|
// 价格计算
|
|
|
- PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, loginUserId);
|
|
|
+ PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, userId);
|
|
|
PriceCalculateRespDTO priceResp = priceApi.calculatePrice(priceCalculateReqDTO);
|
|
|
- // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
|
|
|
|
|
|
- // 订单信息记录
|
|
|
- TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(createReqVO, priceResp.getOrder());
|
|
|
- fillTradeOrderInfoFromReqInfo(tradeOrderDO,createReqVO,loginUserId, clientIp); // TODO @LeeYan9: tradeOrderDO, createReqVO, loginUserId, clientIp
|
|
|
- tradeOrderMapper.insert(tradeOrderDO);
|
|
|
+ // 插入 TradeOrderDO 订单
|
|
|
+ TradeOrderDO tradeOrderDO = createTradeOrder(userId, clientIp, createReqVO, priceResp.getOrder(), address);
|
|
|
+ // 插入 TradeOrderItemDO 订单项
|
|
|
+ createTradeOrderItems(tradeOrderDO, priceResp.getOrder().getItems(), skus);
|
|
|
+
|
|
|
+ // 下单时扣减商品库存 TODO
|
|
|
+// List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
|
|
|
+// productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
|
|
|
+
|
|
|
+ // 删除购物车商品 TODO 芋艿:待实现
|
|
|
|
|
|
- // 订单项信息记录
|
|
|
- List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(priceResp.getOrder().getItems());
|
|
|
- // 填充订单项-SKU信息
|
|
|
- fillItemsInfoFromSkuAndOrder(tradeOrderDO, tradeOrderItems, skuInfoMap);
|
|
|
- tradeOrderItemMapper.insertBatch(tradeOrderItems);
|
|
|
+ // 扣减积分,抵扣金额 TODO 芋艿:待实现
|
|
|
|
|
|
- // TODO @LeeYan9: 先扣减库存哈; 可能会扣减失败; 毕竟 get 和 update 之间, 会有并发的可能性
|
|
|
- // 库存扣减
|
|
|
- List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
|
|
|
- productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
|
|
|
+ // 有使用优惠券时更新
|
|
|
+
|
|
|
+ // 增加订单日志 TODO 芋艿:待实现
|
|
|
|
|
|
// 构建预支付请求参数
|
|
|
// TODO @LeeYan9: 需要更新到订单上
|
|
|
- PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
|
|
|
- fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
|
|
|
+// PayOrderInfoCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
|
|
|
+// fillPayOrderInfoFromItems(payOrderCreateReqDTO, tradeOrderItems);
|
|
|
// 生成预支付
|
|
|
- return payOrderApi.createPayOrder(payOrderCreateReqDTO);
|
|
|
- }
|
|
|
-
|
|
|
- // TODO @LeeYan9: 填充就好, 不用 from 哈;
|
|
|
- private void fillTradeOrderInfoFromReqInfo(TradeOrderDO tradeOrderDO, AppTradeOrderCreateReqVO createReqVO,
|
|
|
- Long loginUserId, String clientIp) {
|
|
|
- tradeOrderDO.setUserId(loginUserId);
|
|
|
- tradeOrderDO.setUserIp(clientIp);
|
|
|
- tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
|
|
|
- tradeOrderDO.setStatus(TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
|
|
|
- tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
|
|
|
- tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
|
|
- tradeOrderDO.setProductCount(CollectionUtils.getSumValue(createReqVO.getItems(), Item::getCount,Integer::sum));
|
|
|
- // todo 地址&用户信息解析
|
|
|
|
|
|
- // todo 数据来源?
|
|
|
- tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal());
|
|
|
+ // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
|
|
|
+ return tradeOrderDO.getId();
|
|
|
}
|
|
|
|
|
|
private void fillPayOrderInfoFromItems(PayOrderInfoCreateReqDTO payOrderInfoCreateReqDTO,
|
|
@@ -157,7 +146,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|
|
|
payOrderInfoCreateReqDTO.setBody(StrUtils.maxLength(body.subString(1), 128));
|
|
|
}
|
|
|
|
|
|
- private void fillItemsInfoFromSkuAndOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItems,
|
|
|
+ private void xfillItemsInfoFromSkuAndOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItems,
|
|
|
Map<Long, ProductSkuRespDTO> spuInfos) {
|
|
|
for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
|
|
|
// 填充订单信息
|
|
@@ -176,32 +165,87 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void checkSaleableFromSpu(List<SpuInfoRespDTO> spuInfos) {
|
|
|
- SpuInfoRespDTO spu = CollectionUtils.findFirst(spuInfos,
|
|
|
- spuInfoDTO -> !Objects.equals(ProductSpuStatusEnum.ENABLE.getStatus(), spuInfoDTO.getStatus()));
|
|
|
- if (Objects.isNull(spu)) {
|
|
|
- throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SPU_NOT_SALE);
|
|
|
+ /**
|
|
|
+ * 校验商品 SKU 是否可出售
|
|
|
+ *
|
|
|
+ * @param items 商品 SKU
|
|
|
+ * @return 商品 SKU 数组
|
|
|
+ */
|
|
|
+ private List<ProductSkuRespDTO> validateSkuSaleable(List<Item> items) {
|
|
|
+ List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(items, Item::getSkuId));
|
|
|
+ // SKU 不存在
|
|
|
+ if (items.size() != skus.size()) {
|
|
|
+ throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_NOT_FOUND);
|
|
|
}
|
|
|
+ // 校验是否禁用 or 库存不足
|
|
|
+ Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
|
|
|
+ items.forEach(item -> {
|
|
|
+ ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
|
|
|
+ // SKU 禁用
|
|
|
+ if (ObjectUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), sku.getStatus())) {
|
|
|
+ throw exception(ORDER_CREATE_SKU_NOT_SALE);
|
|
|
+ }
|
|
|
+ // SKU 库存不足
|
|
|
+ if (item.getCount() > sku.getStock()) {
|
|
|
+ throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return skus;
|
|
|
}
|
|
|
|
|
|
- // TODO @LeeYan9: checkSpuXXX 会不会好点哈? ps: 这个貌似是 sku 哈
|
|
|
- private void checkSaleableAndStockFromSpu(Map<Long, ProductSkuRespDTO> skuInfoMap,
|
|
|
- List<Item> items) {
|
|
|
- // sku 不存在
|
|
|
- if (items.size() != skuInfoMap.size()) {
|
|
|
- throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
|
|
|
+ /**
|
|
|
+ * 校验商品 SPU 是否可出售
|
|
|
+ *
|
|
|
+ * @param spuIds 商品 SPU 编号数组
|
|
|
+ * @return 商品 SPU 数组
|
|
|
+ */
|
|
|
+ private List<ProductSpuRespDTO> validateSpuSaleable(Set<Long> spuIds) {
|
|
|
+ List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(spuIds);
|
|
|
+ // SPU 不存在
|
|
|
+ if (spus.size() != spuIds.size()) {
|
|
|
+ throw exception(ORDER_CREATE_SPU_NOT_FOUND);
|
|
|
}
|
|
|
- for (Item item : items) {
|
|
|
- ProductSkuRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
|
|
|
- // sku禁用
|
|
|
- if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
|
|
|
- throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);
|
|
|
- }
|
|
|
- // sku库存不足
|
|
|
- if (item.getCount() > skuInfoDTO.getStock()) {
|
|
|
- throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_STOCK_NOT_ENOUGH);
|
|
|
- }
|
|
|
+ // 校验是否存在禁用的 SPU
|
|
|
+ ProductSpuRespDTO spu = CollectionUtils.findFirst(spus,
|
|
|
+ spuDTO -> ObjectUtil.notEqual(ProductSpuStatusEnum.ENABLE.getStatus(), spuDTO.getStatus()));
|
|
|
+ if (Objects.isNull(spu)) {
|
|
|
+ throw exception(ErrorCodeConstants.ORDER_CREATE_SPU_NOT_SALE);
|
|
|
}
|
|
|
+ return spus;
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * 校验收件地址是否存在
|
|
|
+ *
|
|
|
+ * @param userId 用户编号
|
|
|
+ * @param addressId 收件地址编号
|
|
|
+ * @return 收件地址
|
|
|
+ */
|
|
|
+ private AddressRespDTO validateAddress(Long userId, Long addressId) {
|
|
|
+ AddressRespDTO address = addressApi.getAddress(userId, addressId);
|
|
|
+ if (Objects.isNull(address)) {
|
|
|
+ throw exception(ErrorCodeConstants.ORDER_CREATE_ADDRESS_NOT_FOUND);
|
|
|
+ }
|
|
|
+ return address;
|
|
|
}
|
|
|
+
|
|
|
+ private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO,
|
|
|
+ PriceCalculateRespDTO.Order order, AddressRespDTO address) {
|
|
|
+ TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, order, address);
|
|
|
+ tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
|
|
|
+ tradeOrderDO.setStatus(TradeOrderStatusEnum.WAITING_PAYMENT.getStatus());
|
|
|
+ tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType());
|
|
|
+ tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
|
|
+ tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum));
|
|
|
+ tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
|
|
|
+ tradeOrderMapper.insert(tradeOrderDO);
|
|
|
+ return tradeOrderDO;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void createTradeOrderItems(TradeOrderDO tradeOrderDO,
|
|
|
+ List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) {
|
|
|
+ List<TradeOrderItemDO> tradeOrderItemDOs = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, orderItems, skus);
|
|
|
+ tradeOrderItemMapper.insertBatch(tradeOrderItemDOs);
|
|
|
+ }
|
|
|
+
|
|
|
}
|