Merge remote-tracking branch 'yudao/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceTest.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
#	yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceTest.java
This commit is contained in:
puhui999
2023-06-03 20:55:45 +08:00
44 changed files with 1381 additions and 780 deletions

View File

@@ -58,7 +58,7 @@ public class DeliveryExpressTemplateController {
@Operation(summary = "获得快递运费模板")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')")
public CommonResult<DeliveryExpressTemplateRespVO> getDeliveryExpressTemplate(@RequestParam("id") Long id) {
public CommonResult<DeliveryExpressTemplateDetailRespVO> getDeliveryExpressTemplate(@RequestParam("id") Long id) {
return success(deliveryExpressTemplateService.getDeliveryExpressTemplate(id));
}
@@ -66,7 +66,7 @@ public class DeliveryExpressTemplateController {
@Operation(summary = "获得快递运费模板列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')")
public CommonResult<List<DeliveryExpressTemplateSimpleRespVO>> getDeliveryExpressTemplateList(@RequestParam("ids") Collection<Long> ids) {
public CommonResult<List<DeliveryExpressTemplateRespVO>> getDeliveryExpressTemplateList(@RequestParam("ids") Collection<Long> ids) {
List<DeliveryExpressTemplateDO> list = deliveryExpressTemplateService.getDeliveryExpressTemplateList(ids);
return success(DeliveryExpressTemplateConvert.INSTANCE.convertList(list));
}
@@ -83,7 +83,7 @@ public class DeliveryExpressTemplateController {
@GetMapping("/page")
@Operation(summary = "获得快递运费模板分页")
@PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')")
public CommonResult<PageResult<DeliveryExpressTemplateSimpleRespVO>> getDeliveryExpressTemplatePage(@Valid DeliveryExpressTemplatePageReqVO pageVO) {
public CommonResult<PageResult<DeliveryExpressTemplateRespVO>> getDeliveryExpressTemplatePage(@Valid DeliveryExpressTemplatePageReqVO pageVO) {
PageResult<DeliveryExpressTemplateDO> pageResult = deliveryExpressTemplateService.getDeliveryExpressTemplatePage(pageVO);
return success(DeliveryExpressTemplateConvert.INSTANCE.convertPage(pageResult));
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Schema(description = "管理后台 - 快递运费模板的详细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DeliveryExpressTemplateDetailRespVO extends DeliveryExpressTemplateBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "371")
private Long id;
@Schema(description = "运费模板运费设置", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ExpressTemplateChargeBaseVO> templateCharge;
@Schema(description = "运费模板包邮区域", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ExpressTemplateFreeBaseVO> templateFree;
}

View File

@@ -1,28 +1,19 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author jason
*/
@Schema(description = "管理后台 - 快递运费模板 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DeliveryExpressTemplateRespVO extends DeliveryExpressTemplateBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "371")
@Schema(description = "编号,自增", required = true, example = "371")
private Long id;
@Schema(description = "运费模板运费设置", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ExpressTemplateChargeBaseVO> templateCharge;
@Schema(description = "运费模板包邮区域", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ExpressTemplateFreeBaseVO> templateFree;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
// TODO @jasonsimplae 是不是不用继承 DeliveryExpressTemplateBaseVO直接 id name 属性就够了。
// @芋艿 这里给列表显示用的。 需要显示计费方式和排序, 所以继承 DeliveryExpressTemplateBaseVO。 这是去掉了包邮区域和 区域运费列表
@Schema(description = "管理后台 - 快递运费模板 精简 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DeliveryExpressTemplateSimpleRespVO extends DeliveryExpressTemplateBaseVO {
@Schema(description = "编号,自增", required = true, example = "371")
private Long id;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -1,29 +1,29 @@
### /trade-order/settlement 获得订单结算信息
GET {{appApi}}/trade/order/settlement?cartIds=1
### /trade-order/settlement 获得订单结算信息(基于商品)
GET {{appApi}}/trade/order/settlement?type=0&items[0].skuId=1&items[0].count=2&items[1].skuId=2&items[1].count=3&couponId=1
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
### /trade-order/confirm-create-order-info-from-cart 基于购物车,确认创建订单
GET {{shop-api-base-url}}/trade-order/confirm-create-order-info-from-cart
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{user-access-token}}
### /trade-order/settlement 获得订单结算信息(基于购物车)
GET {{appApi}}/trade/order/settlement?type=0&items[0].cartId=50&couponId=1
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
### /trade-order/create 基于商品,创建订单
### /trade-order/create 创建订单(基于商品)
POST {{appApi}}/trade/order/create
Content-Type: application/json
Authorization: Bearer {{appToken}}
tenant-id: {{appTenentId}}
{
"type": 0,
"addressId": 21,
"remark": "我是备注",
"fromCart": false,
"items": [
{
"skuId": 29,
"count": 1
"skuId": 1,
"count": 2
}
]
],
"remark": "我是备注"
}
### 获得订单交易的分页

View File

@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
@@ -23,15 +22,14 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 交易订单")
@@ -53,92 +51,16 @@ public class AppTradeOrderController {
@GetMapping("/settlement")
@Operation(summary = "获得订单结算信息")
@PreAuthenticated
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(
@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
if (true) {
return success(tradeOrderService.settlementOrder(getLoginUserId(), settlementReqVO));
}
// return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId));
AppTradeOrderSettlementRespVO settlement = new AppTradeOrderSettlementRespVO();
AppTradeOrderSettlementRespVO.Price price = new AppTradeOrderSettlementRespVO.Price();
price.setTotalPrice(1000);
price.setDeliveryPrice(200);
price.setCouponPrice(100);
price.setPointPrice(50);
price.setPayPrice(950);
List<AppTradeOrderSettlementRespVO.Item> skus = new ArrayList<>();
AppTradeOrderSettlementRespVO.Item item1 = new AppTradeOrderSettlementRespVO.Item();
item1.setCartId(1L);
item1.setSpuId(2048L);
item1.setSpuName("Apple iPhone 12");
item1.setSkuId(1024);
item1.setPrice(500);
item1.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg");
item1.setCount(2);
List<AppProductPropertyValueDetailRespVO> properties1 = new ArrayList<>();
AppProductPropertyValueDetailRespVO property1 = new AppProductPropertyValueDetailRespVO();
property1.setPropertyId(1L);
property1.setPropertyName("尺寸");
property1.setValueId(2L);
property1.setValueName("");
properties1.add(property1);
item1.setProperties(properties1);
AppTradeOrderSettlementRespVO.Item item2 = new AppTradeOrderSettlementRespVO.Item();
item2.setCartId(2L);
item2.setSpuId(3072L);
item2.setSpuName("Samsung Galaxy S21");
item2.setSkuId(2048);
item2.setPrice(800);
item2.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg");
item2.setCount(1);
List<AppProductPropertyValueDetailRespVO> properties2 = new ArrayList<>();
AppProductPropertyValueDetailRespVO property2 = new AppProductPropertyValueDetailRespVO();
property2.setPropertyId(10L);
property2.setPropertyName("颜色");
property2.setValueId(20L);
property2.setValueName("白色");
properties2.add(property2);
item2.setProperties(properties2);
skus.add(item1);
skus.add(item2);
settlement.setItems(skus);
settlement.setPrice(price);
AppTradeOrderSettlementRespVO.Address address = new AppTradeOrderSettlementRespVO.Address();
address.setId(1L);
address.setName("John");
address.setMobile("18888888888");
address.setProvinceId(1L);
address.setProvinceName("Beijing");
address.setCityId(1L);
address.setCityName("Beijing");
address.setDistrictId(1L);
address.setDistrictName("Chaoyang Distripct");
address.setDetailAddress("No. 10, Xinzhong Street, Chaoyang District");
address.setDefaulted(true);
settlement.setAddress(address);
return success(settlement);
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
return success(tradeOrderService.settlementOrder(getLoginUserId(), settlementReqVO));
}
@PostMapping("/create")
@Operation(summary = "创建订单")
@PreAuthenticated
public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
HttpServletRequest servletRequest) {
return success(1L);
// // 获取登录用户、用户 IP 地址
// Long loginUserId = getLoginUserId();
// String clientIp = ServletUtils.getClientIP(servletRequest);
// // 创建交易订单,预支付记录
// Long orderId = tradeOrderService.createOrder(loginUserId, clientIp, createReqVO);
// return success(orderId);
public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) {
Long orderId = tradeOrderService.createOrder(getLoginUserId(), getClientIP(), createReqVO);
return success(orderId);
}
@PostMapping("/update-paid")

View File

@@ -3,26 +3,9 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "用户 App - 交易订单创建 Request VO")
@Data
public class AppTradeOrderCreateReqVO {
@Schema(description = "收件地址编号", required = true, example = "1")
@NotNull(message = "收件地址不能为空")
private Long addressId;
@Schema(description = "优惠劵编号", example = "1024")
private Long couponId;
@Schema(description = "购物车项的编号数组", required = true, example = "true")
@NotEmpty(message = "购物车项不能为空")
private List<Long> cartIds;
// ========== 非 AppTradeOrderSettlementReqVO 字段 ==========
public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO {
@Schema(description = "备注", example = "这个是我的订单哟")
private String remark;

View File

@@ -1,23 +1,60 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "用户 App - 交易订单结算 Request VO")
@Data
public class AppTradeOrderSettlementReqVO {
@NotNull(message = "交易类型不能为空")
@InEnum(value = TradeOrderTypeEnum.class, message = "交易类型必须是 {value}")
private Integer type;
@Schema(description = "商品项数组", required = true)
@NotNull(message = "商品不能为空")
private List<Item> items;
@Schema(description = "收件地址编号", example = "1")
private Long addressId;
@Schema(description = "优惠劵编号", example = "1024")
private Long couponId;
@Schema(description = "购物车项的编号数组", required = true, example = "true")
@NotEmpty(message = "购物车项不能为空")
private List<Long> cartIds;
@Data
@Schema(description = "用户 App - 商品项")
@Valid
public static class Item {
@Schema(description = "商品 SKU 编号", example = "2048")
private Long skuId;
@Schema(description = "购买数量", example = "1")
@Min(value = 1, message = "购买数量最小值为 {value}")
private Integer count;
@Schema(description = "购物车项的编号", example = "1024")
private Long cartId;
@AssertTrue(message = "商品不正确")
@JsonIgnore
public boolean isValid() {
// 组合一skuId + count 使用商品 SKU
if (skuId != null && count != null) {
return true;
}
// 组合二cartId 使用购物车项
return cartId != null;
}
}
}

View File

@@ -91,17 +91,17 @@ public class AppTradeOrderSettlementRespVO {
private String mobile;
@Schema(description = "省份编号", required = true, example = "1")
private Long provinceId;
private Integer provinceId;
@Schema(description = "省份名字", required = true, example = "北京")
private String provinceName;
@Schema(description = "城市编号", required = true, example = "1")
private Long cityId;
private Integer cityId;
@Schema(description = "城市名字", required = true, example = "北京")
private String cityName;
@Schema(description = "地区编号", required = true, example = "1")
private Long districtId;
private Integer districtId;
@Schema(description = "地区名字", required = true, example = "朝阳区")
private String districtName;

View File

@@ -22,18 +22,18 @@ public interface DeliveryExpressTemplateConvert {
DeliveryExpressTemplateDO convert(DeliveryExpressTemplateUpdateReqVO bean);
DeliveryExpressTemplateSimpleRespVO convert(DeliveryExpressTemplateDO bean);
DeliveryExpressTemplateRespVO convert(DeliveryExpressTemplateDO bean);
DeliveryExpressTemplateRespVO convert2(DeliveryExpressTemplateDO bean);
DeliveryExpressTemplateDetailRespVO convert2(DeliveryExpressTemplateDO bean);
List<DeliveryExpressTemplateSimpleRespVO> convertList(List<DeliveryExpressTemplateDO> list);
List<DeliveryExpressTemplateRespVO> convertList(List<DeliveryExpressTemplateDO> list);
PageResult<DeliveryExpressTemplateSimpleRespVO> convertPage(PageResult<DeliveryExpressTemplateDO> page);
PageResult<DeliveryExpressTemplateRespVO> convertPage(PageResult<DeliveryExpressTemplateDO> page);
default DeliveryExpressTemplateRespVO convert(DeliveryExpressTemplateDO bean,
List<DeliveryExpressTemplateChargeDO> chargeList,
List<DeliveryExpressTemplateFreeDO> freeList){
DeliveryExpressTemplateRespVO respVO = convert2(bean);
default DeliveryExpressTemplateDetailRespVO convert(DeliveryExpressTemplateDO bean,
List<DeliveryExpressTemplateChargeDO> chargeList,
List<DeliveryExpressTemplateFreeDO> freeList){
DeliveryExpressTemplateDetailRespVO respVO = convert2(bean);
respVO.setTemplateCharge(convertTemplateChargeList(chargeList));
respVO.setTemplateFree(convertTemplateFreeList(freeList));
return respVO;

View File

@@ -3,29 +3,28 @@ package cn.iocoder.yudao.module.trade.convert.order;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
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.product.api.spu.dto.ProductSpuRespDTO;
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.admin.base.member.user.MemberUserRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageItemRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
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.enums.order.TradeOrderItemAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@@ -48,32 +47,32 @@ public interface TradeOrderConvert {
@Mapping(source = "createReqVO.couponId", target = "couponId"),
@Mapping(target = "remark", ignore = true),
@Mapping(source = "createReqVO.remark", target = "userRemark"),
@Mapping(source = "createReqVO.type", target = "type"),
@Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"),
@Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"),
@Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
@Mapping(source = "calculateRespBO.price.couponPrice", target = "couponPrice"),
@Mapping(source = "calculateRespBO.price.pointPrice", target = "pointPrice"),
@Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice"),
@Mapping(source = "address.name", target = "receiverName"),
@Mapping(source = "address.mobile", target = "receiverMobile"),
@Mapping(source = "address.areaId", target = "receiverAreaId"),
@Mapping(source = "address.detailAddress", target = "receiverDetailAddress"),
})
TradeOrderDO convert(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO,
PriceCalculateRespDTO.Order order, AddressRespDTO address);
TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(source = "sku.spuId", target = "spuId"),
})
TradeOrderItemDO convert(PriceCalculateRespDTO.OrderItem orderItem, ProductSkuRespDTO sku);
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO,
List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) {
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
return CollectionUtils.convertList(orderItems, orderItem -> {
TradeOrderItemDO tradeOrderItemDO = convert(orderItem, skuMap.get(orderItem.getSkuId()));
tradeOrderItemDO.setOrderId(tradeOrderDO.getId());
tradeOrderItemDO.setUserId(tradeOrderDO.getUserId());
tradeOrderItemDO.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); // 退款信息
// tradeOrderItemDO.setCommented(false);
return tradeOrderItemDO;
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
return CollectionUtils.convertList(calculateRespBO.getItems(), item -> {
TradeOrderItemDO orderItem = convert(item);
orderItem.setOrderId(tradeOrderDO.getId());
orderItem.setUserId(tradeOrderDO.getUserId());
orderItem.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
orderItem.setCommentStatus(false);
return orderItem;
});
}
TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item);
@Mapping(source = "userId" , target = "userId")
PriceCalculateReqDTO convert(AppTradeOrderCreateReqVO createReqVO, Long userId);
@@ -85,19 +84,20 @@ public interface TradeOrderConvert {
ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean);
List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list);
default PayOrderCreateReqDTO convert(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus, TradeOrderProperties tradeOrderProperties) {
default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) {
PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
.setAppId(tradeOrderProperties.getAppId()).setUserIp(tradeOrderDO.getUserIp());
.setAppId(orderProperties.getAppId()).setUserIp(order.getUserIp());
// 商户相关字段
createReqDTO.setMerchantOrderId(String.valueOf(tradeOrderDO.getId()));
String subject = spus.get(0).getName();
if (spus.size() > 1) {
createReqDTO.setMerchantOrderId(String.valueOf(order.getId()));
String subject = calculateRespBO.getItems().get(0).getSpuName();
if (calculateRespBO.getItems().size() > 1) {
subject += " 等多件";
}
createReqDTO.setSubject(subject);
createReqDTO.setBody(subject); // TODO 芋艿:临时写死
// 订单相关字段
createReqDTO.setAmount(tradeOrderDO.getPayPrice()).setExpireTime(addTime(tradeOrderProperties.getExpireTime()));
createReqDTO.setAmount(order.getPayPrice()).setExpireTime(addTime(orderProperties.getExpireTime()));
return createReqDTO;
}
@@ -111,6 +111,7 @@ public interface TradeOrderConvert {
.collect(Collectors.toSet());
}
// TODO 芋艿:可简化
default PageResult<TradeOrderPageItemRespVO> convertPage(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId);
@@ -147,6 +148,7 @@ public interface TradeOrderConvert {
TradeOrderPageItemRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> items);
ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
// TODO 芋艿:可简化
default TradeOrderDetailRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails, MemberUserRespDTO user) {
TradeOrderDetailRespVO orderVO = convert2(order, orderItems);
@@ -177,6 +179,7 @@ public interface TradeOrderConvert {
TradeOrderDetailRespVO convert2(TradeOrderDO order, List<TradeOrderItemDO> items);
MemberUserRespVO convert(MemberUserRespDTO bean);
// TODO 芋艿:可简化
default PageResult<AppTradeOrderPageItemRespVO> convertPage02(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems,
List<ProductPropertyValueDetailRespDTO> propertyValueDetails) {
Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId);
@@ -241,4 +244,45 @@ public interface TradeOrderConvert {
AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean);
default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO,
List<TradeCartDO> cartList) {
TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO();
reqBO.setUserId(userId).setType(settlementReqVO.getType())
.setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId())
.setItems(new ArrayList<>(settlementReqVO.getItems().size()));
// 商品项的构建
Map<Long, TradeCartDO> cartMap = convertMap(cartList, TradeCartDO::getId);
for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {
// 情况一skuId + count
if (item.getSkuId() != null) {
reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(item.getSkuId()).setCount(item.getCount())
.setSelected(true)); // true 的原因,下单一定选中
continue;
}
// 情况二cartId
TradeCartDO cart = cartMap.get(item.getCartId());
if (cart == null) {
continue;
}
reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(cart.getSkuId()).setCount(cart.getCount())
.setCartId(item.getCartId()).setSelected(true)); // true 的原因,下单一定选中
}
return reqBO;
}
default AppTradeOrderSettlementRespVO convert(TradePriceCalculateRespBO calculate, AddressRespDTO address) {
AppTradeOrderSettlementRespVO respVO = convert0(calculate, address);
if (address != null) {
Area area = AreaUtils.getArea(address.getAreaId());
respVO.getAddress().setDistrictId(area.getId());
respVO.getAddress().setDistrictName(area.getName());
respVO.getAddress().setCityId(area.getParent().getId());
respVO.getAddress().setCityName(area.getParent().getName());
respVO.getAddress().setProvinceId(area.getParent().getParent().getId());
respVO.getAddress().setProvinceName(area.getParent().getParent().getName());
}
return respVO;
}
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address);
}

View File

@@ -166,12 +166,25 @@ public class TradeOrderItemDO extends BaseDO {
* 关联 ProductPropertyDO 的 id 编号
*/
private Long propertyId;
/**
* 属性名字
*
* 关联 ProductPropertyDO 的 name 字段
*/
private String propertyName;
/**
* 属性值编号
*
* 关联 ProductPropertyValueDO 的 id 编号
*/
private Long valueId;
/**
* 属性值名字
*
* 关联 ProductPropertyValueDO 的 name 字段
*/
private String valueName;
}

View File

@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Mapper
public interface TradeCartMapper extends BaseMapperX<TradeCartDO> {
@@ -70,4 +71,10 @@ public interface TradeCartMapper extends BaseMapperX<TradeCartDO> {
update(updateObject, new LambdaQueryWrapper<TradeCartDO>().in(TradeCartDO::getId, ids));
}
default List<TradeCartDO> selectListByUserId(Long userId, Set<Long> ids) {
return selectList(new LambdaQueryWrapper<TradeCartDO>()
.eq(TradeCartDO::getUserId, userId)
.in(TradeCartDO::getId, ids));
}
}

View File

@@ -4,10 +4,13 @@ import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 购物车 Service 接口
@@ -67,6 +70,15 @@ public interface TradeCartService {
*/
AppTradeCartListRespVO getCartList(Long userId);
/**
* 查询用户的购物车列表
*
* @param userId 用户编号
* @param ids 购物项的编号
* @return 购物车列表
*/
List<TradeCartDO> getCartList(Long userId, Set<Long> ids);
/**
* 获得用户的购物车商品 SPU 数量的 Map
*

View File

@@ -17,10 +17,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -166,6 +163,14 @@ public class TradeCartServiceImpl implements TradeCartService {
return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
}
@Override
public List<TradeCartDO> getCartList(Long userId, Set<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return cartMapper.selectListByUserId(userId, ids);
}
private void deleteCartIfSpuDeleted(List<TradeCartDO> carts, List<ProductSpuRespDTO> spus) {
// 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
carts.removeIf(cart -> {

View File

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.delivery;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateDetailRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateRespVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateUpdateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
@@ -46,7 +46,7 @@ public interface DeliveryExpressTemplateService {
* @param id 编号
* @return 快递运费模板详情
*/
DeliveryExpressTemplateRespVO getDeliveryExpressTemplate(Long id);
DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id);
/**
* 获得快递运费模板列表
@@ -70,4 +70,14 @@ public interface DeliveryExpressTemplateService {
* @return 快递运费模板分页
*/
PageResult<DeliveryExpressTemplateDO> getDeliveryExpressTemplatePage(DeliveryExpressTemplatePageReqVO pageReqVO);
/**
* 校验快递运费模板
*
* 如果校验不通过,抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常
*
* @param templateId 模板编号
* @return 快递运费模板
*/
DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
}

View File

@@ -185,7 +185,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
}
@Override
public DeliveryExpressTemplateRespVO getDeliveryExpressTemplate(Long id) {
public DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id) {
List<DeliveryExpressTemplateChargeDO> chargeList = expressTemplateChargeMapper.selectListByTemplateId(id);
List<DeliveryExpressTemplateFreeDO> freeList = expressTemplateFreeMapper.selectListByTemplateId(id);
DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(id);
@@ -207,4 +207,13 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
return expressTemplateMapper.selectPage(pageReqVO);
}
@Override
public DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId) {
DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(templateId);
if (template == null) {
throw exception(EXPRESS_TEMPLATE_NOT_EXISTS);
}
return template;
}
}

View File

@@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
@@ -18,15 +18,9 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
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.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
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.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
import cn.iocoder.yudao.module.promotion.api.price.PriceApi;
import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
@@ -34,13 +28,19 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageRe
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
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.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.*;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -71,12 +71,13 @@ public class TradeOrderServiceImpl implements TradeOrderService {
private TradeOrderItemMapper tradeOrderItemMapper;
@Resource
private PriceApi priceApi;
private TradeCartService tradeCartService;
@Resource
private TradePriceService tradePriceService;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private PayOrderApi payOrderApi;
@Resource
private AddressApi addressApi;
@@ -92,76 +93,70 @@ public class TradeOrderServiceImpl implements TradeOrderService {
@Override
public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
return null;
// 1. 获得收货地址
AddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId());
if (address != null) {
settlementReqVO.setAddressId(address.getId());
}
// 2. 计算价格
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO);
// 3. 拼接返回
return TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
}
/**
* 获得用户地址
*
* @param userId 用户编号
* @param addressId 地址编号
* @return 地址
*/
private AddressRespDTO getAddress(Long userId, Long addressId) {
if (addressId != null) {
return addressApi.getAddress(addressId, userId);
}
return addressApi.getDefaultAddress(userId);
}
/**
* 计算订单价格
*
* @param userId 用户编号
* @param settlementReqVO 结算信息
* @return 订单价格
*/
private TradePriceCalculateRespBO calculatePrice(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
// 1. 如果来自购物车,则获得购物车的商品
List<TradeCartDO> cartList = tradeCartService.getCartList(userId,
convertSet(settlementReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId));
// 2. 计算价格
TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList);
calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的
"商品({}) 未设置为选中", item.getSkuId()));
return tradePriceService.calculatePrice(calculateReqBO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) {
// 商品 SKU 检查:可售状态、库存
// List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems()); // TODO 芋艿,临时关闭。
List<ProductSkuRespDTO> skus = null;
// 商品 SPU 检查:可售状态
List<ProductSpuRespDTO> spus = validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId));
// 用户收件地址的校验
// 1. 用户收件地址的校验
AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId());
// 价格计算
PriceCalculateRespDTO priceResp = priceApi.calculatePrice(TradeOrderConvert.INSTANCE.convert(createReqVO, userId));
// 2. 价格计算
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO);
// 插入 TradeOrderDO 订单
TradeOrderDO tradeOrderDO = createTradeOrder(userId, userIp, createReqVO, priceResp.getOrder(), address);
// 插入 TradeOrderItemDO 订单项
List<TradeOrderItemDO> tradeOrderItems = createTradeOrderItems(tradeOrderDO, priceResp.getOrder().getItems(), skus);
// 3.1 插入 TradeOrderDO 订单
TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO, address);
// 3.2 插入 TradeOrderItemDO 订单项
List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO);
// 订单创建完后的逻辑
afterCreateTradeOrder(userId, createReqVO, tradeOrderDO, tradeOrderItems, spus);
afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO);
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
return tradeOrderDO.getId();
}
// /**
// * 校验商品 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(ORDER_CREATE_SKU_NOT_FOUND);
// }
// // 校验库存不足
// Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
// items.forEach(item -> {
// ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
// if (item.getCount() > sku.getStock()) {
// throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
// }
// });
// return skus;
// }
/**
* 校验商品 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);
}
// 校验是否存在禁用的 SPU
ProductSpuRespDTO spu = CollectionUtils.findFirst(spus,
spuDTO -> ObjectUtil.notEqual(ProductSpuStatusEnum.ENABLE.getStatus(), spuDTO.getStatus()));
if (spu != null) {
throw exception(ErrorCodeConstants.ORDER_CREATE_SPU_NOT_SALE);
}
return spus;
return order.getId();
}
/**
@@ -173,33 +168,35 @@ public class TradeOrderServiceImpl implements TradeOrderService {
*/
private AddressRespDTO validateAddress(Long userId, Long addressId) {
AddressRespDTO address = addressApi.getAddress(addressId, userId);
if (Objects.isNull(address)) {
if (address == null) {
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.UNPAID.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 数据来源?
tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息
tradeOrderDO.setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus()); // 物流信息
tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息
tradeOrderMapper.insert(tradeOrderDO);
return tradeOrderDO;
TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address) {
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address);
order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的;
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
order.setType(TradeOrderTypeEnum.NORMAL.getType());
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源?
// 支付信息
order.setAdjustPrice(0).setPayed(false);
// 物流信息 TODO 芋艿:暂时写死物流方式;应该是前端选择的
order.setDeliveryType(DeliveryTypeEnum.EXPRESS.getMode()).setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus());
// 退款信息
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
tradeOrderMapper.insert(order);
return order;
}
private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO,
List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) {
List<TradeOrderItemDO> tradeOrderItemDOs = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, orderItems, skus);
tradeOrderItemMapper.insertBatch(tradeOrderItemDOs);
return tradeOrderItemDOs;
private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
tradeOrderItemMapper.insertBatch(orderItems);
return orderItems;
}
/**
@@ -210,12 +207,13 @@ public class TradeOrderServiceImpl implements TradeOrderService {
* @param userId 用户编号
* @param createReqVO 创建订单请求
* @param tradeOrderDO 交易订单
* @param calculateRespBO 订单价格计算结果
*/
private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus) {
TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems,
TradePriceCalculateRespBO calculateRespBO) {
// 下单时扣减商品库存
productSkuApi.updateSkuStock(new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(tradeOrderItemDOs)));
productSkuApi.updateSkuStock(new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(orderItems)));
// 删除购物车商品 TODO 芋艿:待实现
@@ -228,20 +226,19 @@ public class TradeOrderServiceImpl implements TradeOrderService {
}
// 生成预支付
createPayOrder(tradeOrderDO, tradeOrderItemDOs, spus);
createPayOrder(tradeOrderDO, orderItems, calculateRespBO);
// 增加订单日志 TODO 芋艿:待实现
}
private void createPayOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs,
List<ProductSpuRespDTO> spus) {
private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
// 创建支付单,用于后续的支付
PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
tradeOrderDO, tradeOrderItemDOs, spus, tradeOrderProperties);
order, orderItems, calculateRespBO, tradeOrderProperties);
Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO);
// 更新到交易单上
tradeOrderMapper.updateById(new TradeOrderDO().setId(tradeOrderDO.getId()).setPayOrderId(payOrderId));
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setPayOrderId(payOrderId));
}
@Override

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.price;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import javax.validation.Valid;
/**
* 价格计算 Service 接口
*
@@ -16,6 +18,6 @@ public interface TradePriceService {
* @param calculateReqDTO 计算信息
* @return 计算结果
*/
TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqDTO);
TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
}

View File

@@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.trade.service.price;
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.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
@@ -15,8 +19,8 @@ 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.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
/**
@@ -25,22 +29,28 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_C
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class TradePriceServiceImpl implements TradePriceService {
@Resource
private ProductSkuApi productSkuApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private List<TradePriceCalculator> priceCalculators;
@Override
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
// 1. 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSkus(calculateReqBO);
// 1.1 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
// 1.2 获得商品 SPU 数组
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
// 2.1 计算价格
TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper
.buildCalculateResp(calculateReqBO, skuList);
.buildCalculateResp(calculateReqBO, spuList, skuList);
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
// 2.2 如果最终支付金额小于等于 0则抛出业务异常
if (calculateRespBO.getPrice().getPayPrice() <= 0) {
@@ -51,7 +61,7 @@ public class TradePriceServiceImpl implements TradePriceService {
return calculateRespBO;
}
private List<ProductSkuRespDTO> checkSkus(TradePriceCalculateReqBO reqBO) {
private List<ProductSkuRespDTO> checkSkuList(TradePriceCalculateReqBO reqBO) {
// 获得商品 SKU 数组
Map<Long, Integer> skuIdCountMap = convertMap(reqBO.getItems(),
TradePriceCalculateReqBO.Item::getSkuId, TradePriceCalculateReqBO.Item::getCount);
@@ -70,4 +80,20 @@ public class TradePriceServiceImpl implements TradePriceService {
return skus;
}
private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
// 获得商品 SPU 数组
List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
// 校验商品 SPU
spus.forEach(spu -> {
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE);
}
});
return spus;
}
}

View File

@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data;
@@ -21,7 +23,7 @@ public class TradePriceCalculateReqBO {
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer orderType;
private Integer type;
/**
* 用户编号
@@ -44,6 +46,21 @@ public class TradePriceCalculateReqBO {
*/
private Long addressId;
/**
* 配送方式
*
* 枚举 {@link DeliveryTypeEnum}
*/
private Integer deliveryType;
/**
* 配送模板编号
*
* 关联 {@link DeliveryExpressTemplateDO#getId()}
*/
// TODO @jason运费模版是不是每个 SKU 传入哈
private Long templateId;
/**
* 商品 SKU 数组
*/
@@ -82,5 +99,4 @@ public class TradePriceCalculateReqBO {
private Boolean selected;
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data;
@@ -23,7 +24,7 @@ public class TradePriceCalculateRespBO {
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer orderType;
private Integer type;
/**
* 订单价格
@@ -163,7 +164,26 @@ public class TradePriceCalculateRespBO {
*/
private Integer payPrice;
// TODO 芋艿:这里补充下基本信息,简单一点。
// ========== 商品信息 ==========
/**
* 商品名
*/
private String spuName;
/**
* 商品图片
*
* 优先级SKU.picUrl > SPU.picUrl
*/
private String picUrl;
/**
* 分类编号
*/
private Long categoryId;
/**
* 商品属性数组
*/
private List<ProductPropertyValueDetailRespDTO> properties;
}
@@ -189,12 +209,6 @@ public class TradePriceCalculateRespBO {
* 枚举 {@link PromotionTypeEnum}
*/
private Integer type;
/**
* 营销级别
*
* 枚举 @link PromotionLevelEnum} TODO PromotionLevelEnum 没有这个枚举类
*/
private Integer level;
/**
* 计算时的原价(总),单位:分
*/

View File

@@ -101,7 +101,8 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
CouponRespDTO coupon) {
Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected;
if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) {
matchPredicate = orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId());
matchPredicate = matchPredicate // 额外加如下条件
.and(orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId()));
}
return filterList(result.getItems(), matchPredicate);
}

View File

@@ -0,0 +1,234 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
/**
* 运费的 {@link TradePriceCalculator} 实现类
*
* @author jason
*/
@Component
@Order(TradePriceCalculator.ORDER_DELIVERY)
public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
@Resource
private AddressApi addressApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private DeliveryExpressTemplateService deliveryExpressTemplateService;
// TODO @jason走 Service 哈。Mapper 只允许自己的 Service 调用,保护好数据结构;
@Resource
private DeliveryExpressTemplateChargeMapper templateChargeMapper;
@Resource
private DeliveryExpressTemplateFreeMapper templateFreeMapper;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1.1 判断配送方式
if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) {
return;
}
if (param.getTemplateId() == null || param.getAddressId() == null) {
return;
}
// 1.2 校验运费模板是否存在
DeliveryExpressTemplateDO template = deliveryExpressTemplateService.validateDeliveryExpressTemplate(param.getTemplateId());
// 得到包邮配置
List<DeliveryExpressTemplateFreeDO> expressTemplateFreeList = templateFreeMapper.selectListByTemplateId(template.getId());
Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap = new HashMap<>();
expressTemplateFreeList.forEach(item -> {
for (Integer areaId : item.getAreaIds()) {
// TODO 需要保证 areaId 不能重复
if (!areaTemplateFreeMap.containsKey(areaId)) {
areaTemplateFreeMap.put(areaId, item);
}
}
});
// 得到快递运费配置
List<DeliveryExpressTemplateChargeDO> expressTemplateChargeList = templateChargeMapper.selectListByTemplateId(template.getId());
Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap = new HashMap<>();
expressTemplateChargeList.forEach(item -> {
for (Integer areaId : item.getAreaIds()) {
// areaId 不能重复
if (!areaTemplateChargeMap.containsKey(areaId)) {
areaTemplateChargeMap.put(areaId, item);
}
}
});
// 得到收件地址区域
AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
// 1.3 计算快递费用
calculateDeliveryPrice(address.getAreaId(), template.getChargeMode(),
areaTemplateFreeMap, areaTemplateChargeMap, result);
}
/**
* 校验订单是否满足包邮条件
*
* @param receiverAreaId 收件人地区的区域编号
* @param chargeMode 配送计费方式
* @param areaTemplateFreeMap 运费模板包邮区域设置 Map
* @param areaTemplateChargeMap 运费模板快递费用设置 Map
*/
private void calculateDeliveryPrice(Integer receiverAreaId,
Integer chargeMode,
Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap,
Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap,
TradePriceCalculateRespBO result) {
// 过滤出已选中的商品SKU
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
Set<Long> skuIds = convertSet(selectedItem, OrderItem::getSkuId);
// 得到SKU 详情。得到 重量体积
Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId);
// 一个 spuId 可能对应多条订单商品 SKU
// TODO @jason得确认下按照 sku 算,还是 spu 算;
Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedItem, OrderItem::getSpuId);
// 依次计算每个 SPU 的快递运费
for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) {
List<OrderItem> orderItems = entry.getValue();
// 总件数, 总金额, 总重量, 总体积
int totalCount = 0;
int totalPrice = 0;
double totalWeight = 0;
double totalVolume = 0;
for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount();
totalPrice += orderItem.getPrice(); // TODO jason应该按照 payPrice
ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId());
if (skuResp != null) {
totalWeight = totalWeight + skuResp.getWeight(); // TODO @jason* 数量
totalVolume = totalVolume + skuResp.getVolume();
}
}
// 优先判断是否包邮. 如果包邮不计算快递运费
if (areaTemplateFreeMap.containsKey(receiverAreaId) &&
checkExpressFree(chargeMode, totalCount, totalWeight,
totalVolume, totalPrice, areaTemplateFreeMap.get(receiverAreaId))) {
continue;
}
// 计算快递运费
// TODO @jason貌似也可以抽成 checkExpressFree 类似方法
if (areaTemplateChargeMap.containsKey(receiverAreaId)) {
DeliveryExpressTemplateChargeDO templateCharge = areaTemplateChargeMap.get(receiverAreaId);
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE: {
calculateExpressFeeBySpu(totalCount, templateCharge, orderItems);
break;
}
case WEIGHT: {
calculateExpressFeeBySpu(totalWeight, templateCharge, orderItems);
break;
}
case VOLUME: {
calculateExpressFeeBySpu(totalVolume, templateCharge, orderItems);
break;
}
}
}
}
TradePriceCalculatorHelper.recountAllPrice(result);
}
/**
* 按 spu 来计算快递费用
*
* @param total 总件数/总重量/总体积
* @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/
private void calculateExpressFeeBySpu(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) {
int deliveryPrice;
if (total <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice();
} else {
double remainWeight = total - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice;
}
// TODO @芋艿 分摊快递费用到 SKU. 是不是搞复杂了;
// TODO @jason因为退费的时候可能按照 SKU 考虑退费金额
divideDeliveryPrice(deliveryPrice, orderItems);
}
/**
* 快递运费分摊到每个 SKU 商品上
*
* @param deliveryPrice 快递运费
* @param orderItems SKU 商品
*/
private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
int dividePrice = deliveryPrice / orderItems.size();
for (OrderItem item : orderItems) {
// 更新快递运费
item.setDeliveryPrice(dividePrice);
}
}
/**
* 检查是否包邮
*
* @param chargeMode 配送计费方式
* @param totalCount 总件数
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param totalPrice 总金额
* @param templateFree 包邮配置
*/
private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight,
double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) {
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE:
// 两个条件都满足才包邮
if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case WEIGHT:
// freeCount 是不是应该是 double ??
if (totalWeight >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case VOLUME:
if (totalVolume >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
}
return false;
}
}

View File

@@ -53,10 +53,13 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice;
// 3.1 记录优惠明细
TradePriceCalculatorHelper.addPromotion(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
newDiscountPrice);
if (orderItem.getSelected()) {
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
TradePriceCalculatorHelper.addPromotion(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
newDiscountPrice);
}
// 3.2 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
TradePriceCalculatorHelper.recountPayPrice(orderItem);

View File

@@ -13,6 +13,12 @@ public interface TradePriceCalculator {
int ORDER_DISCOUNT_ACTIVITY = 10;
int ORDER_REWARD_ACTIVITY = 20;
int ORDER_COUPON = 30;
/**
* 快递运费的计算
*
* 放在各种营销活动、优惠劵后面 TODO
*/
int ORDER_DELIVERY = 40;
void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@@ -24,25 +25,42 @@ import static java.util.Collections.singletonList;
public class TradePriceCalculatorHelper {
public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param,
List<ProductSkuRespDTO> skuList) {
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
result.setOrderType(param.getOrderType());
result.setType(param.getType());
result.setPromotions(new ArrayList<>());
// 创建它的 OrderItem 属性
Map<Long, TradePriceCalculateReqBO.Item> skuItemMap = convertMap(param.getItems(),
TradePriceCalculateReqBO.Item::getSkuId);
result.setItems(new ArrayList<>(skuItemMap.size()));
skuList.forEach(sku -> {
TradePriceCalculateReqBO.Item skuItem = skuItemMap.get(sku.getId());
TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem()
// SKU 字段
.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(skuItem.getCount()).setCartId(skuItem.getCartId()).setSelected(skuItem.getSelected())
// 价格字段
.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * skuItem.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
result.setItems(new ArrayList<>(param.getItems().size()));
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skuList, ProductSkuRespDTO::getId);
param.getItems().forEach(item -> {
ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
if (sku == null) {
return;
}
ProductSpuRespDTO spu = spuMap.get(sku.getSpuId());
if (spu == null) {
return;
}
// 商品项
TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem();
result.getItems().add(orderItem);
orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(item.getCount()).setCartId(item.getCartId()).setSelected(item.getSelected());
// sku 价格
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
// sku 信息
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties());
// spu 信息
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId());
if (orderItem.getPicUrl() == null) {
orderItem.setPicUrl(spu.getPicUrl());
}
});
// 创建它的 Price 属性
result.setPrice(new TradePriceCalculateRespBO.Price());
recountAllPrice(result);
@@ -86,6 +104,31 @@ public class TradePriceCalculatorHelper {
- orderItem.getPointPrice());
}
/**
* 重新计算每个订单项的支付金额
*
* 【目前主要是单测使用】
*
* @param orderItems 订单项数组
*/
public static void recountPayPrice(List<TradePriceCalculateRespBO.OrderItem> orderItems) {
orderItems.forEach(orderItem -> {
if (orderItem.getDiscountPrice() == null) {
orderItem.setDiscountPrice(0);
}
if (orderItem.getDeliveryPrice() == null) {
orderItem.setDeliveryPrice(0);
}
if (orderItem.getCouponPrice() == null) {
orderItem.setCouponPrice(0);
}
if (orderItem.getPointPrice() == null) {
orderItem.setPointPrice(0);
}
recountPayPrice(orderItem);
});
}
/**
* 计算已选中的订单项,总支付金额
*
@@ -94,7 +137,7 @@ public class TradePriceCalculatorHelper {
*/
public static Integer calculateTotalPayPrice(List<TradePriceCalculateRespBO.OrderItem> orderItems) {
return getSumValue(orderItems,
orderItem -> orderItem.getSelected() ? orderItem.getPayPrice() : 0, // 未选中的情况下,不计算支付金额
orderItem -> orderItem.getSelected() ? orderItem.getPayPrice() : 0, // 未选中的情况下,不计算支付金额
Integer::sum);
}

View File

@@ -53,6 +53,9 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
// 1.2 获得最大匹配的满减送活动的规则
RewardActivityMatchRespDTO.Rule rule = getMaxMatchRewardActivityRule(rewardActivity, orderItems);
if (rule == null) {
TradePriceCalculatorHelper.addNotMatchPromotion(result, orderItems,
rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
getRewardActivityNotMeetTip(rewardActivity));
return;
}