Commit 1b477aaa0d46985efff9f8891c762e54d7bf9c00
1 parent
c766f7da
trade:优化下单逻辑的实现
Showing
22 changed files
with
268 additions
and
110 deletions
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
| @@ -39,7 +39,7 @@ public interface ProductSkuApi { | @@ -39,7 +39,7 @@ public interface ProductSkuApi { | ||
| 39 | List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds); | 39 | List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds); |
| 40 | 40 | ||
| 41 | /** | 41 | /** |
| 42 | - * 更新 SKU 库存 | 42 | + * 更新 SKU 库存(增加 or 减少) |
| 43 | * | 43 | * |
| 44 | * @param updateStockReqDTO 更新请求 | 44 | * @param updateStockReqDTO 更新请求 |
| 45 | */ | 45 | */ |
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
| @@ -58,8 +58,9 @@ public interface ErrorCodeConstants { | @@ -58,8 +58,9 @@ public interface ErrorCodeConstants { | ||
| 58 | 58 | ||
| 59 | // ========== Price 相关 1011003000 ============ | 59 | // ========== Price 相关 1011003000 ============ |
| 60 | ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0"); | 60 | ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011003000, "支付价格计算异常,原因:价格小于等于 0"); |
| 61 | - ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空"); | 61 | + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003001, "计算快递运费异常,收件人地址编号为空"); |
| 62 | ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板"); | 62 | ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1011003002, "计算快递运费异常,找不到对应的运费模板"); |
| 63 | + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY = new ErrorCode(1011003003, "计算快递运费异常,自提点为空"); | ||
| 63 | 64 | ||
| 64 | // ========== 物流 Express 模块 1011004000 ========== | 65 | // ========== 物流 Express 模块 1011004000 ========== |
| 65 | ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在"); | 66 | ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1011004000, "快递公司不存在"); |
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryTypeEnum.java
| @@ -15,7 +15,6 @@ import java.util.Arrays; | @@ -15,7 +15,6 @@ import java.util.Arrays; | ||
| 15 | @AllArgsConstructor | 15 | @AllArgsConstructor |
| 16 | public enum DeliveryTypeEnum implements IntArrayValuable { | 16 | public enum DeliveryTypeEnum implements IntArrayValuable { |
| 17 | 17 | ||
| 18 | - NULL(0, "无需物流"), | ||
| 19 | EXPRESS(1, "快递发货"), | 18 | EXPRESS(1, "快递发货"), |
| 20 | PICK_UP(2, "用户自提"),; | 19 | PICK_UP(2, "用户自提"),; |
| 21 | 20 |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http
| @@ -15,7 +15,8 @@ Authorization: Bearer {{appToken}} | @@ -15,7 +15,8 @@ Authorization: Bearer {{appToken}} | ||
| 15 | tenant-id: {{appTenentId}} | 15 | tenant-id: {{appTenentId}} |
| 16 | 16 | ||
| 17 | { | 17 | { |
| 18 | - "type": 0, | 18 | + "pointStatus": true, |
| 19 | + "deliveryType": 1, | ||
| 19 | "addressId": 21, | 20 | "addressId": 21, |
| 20 | "items": [ | 21 | "items": [ |
| 21 | { | 22 | { |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
| 1 | package cn.iocoder.yudao.module.trade.controller.app.order; | 1 | package cn.iocoder.yudao.module.trade.controller.app.order; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.map.MapUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; | 5 | import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; |
| @@ -12,11 +11,8 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; | @@ -12,11 +11,8 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; | ||
| 12 | import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; | 11 | import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; |
| 13 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; | 12 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 14 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; | 13 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 15 | -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum; | ||
| 16 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; | 14 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; |
| 17 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; | 15 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; |
| 18 | -import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog; | ||
| 19 | -import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils; | ||
| 20 | import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; | 16 | import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; |
| 21 | import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; | 17 | import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; |
| 22 | import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; | 18 | import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; |
| @@ -65,10 +61,7 @@ public class AppTradeOrderController { | @@ -65,10 +61,7 @@ public class AppTradeOrderController { | ||
| 65 | @PostMapping("/create") | 61 | @PostMapping("/create") |
| 66 | @Operation(summary = "创建订单") | 62 | @Operation(summary = "创建订单") |
| 67 | @PreAuthenticated | 63 | @PreAuthenticated |
| 68 | - @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.TEST) | ||
| 69 | - public CommonResult<AppTradeOrderCreateRespVO> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) { | ||
| 70 | - TradeOrderLogUtils.setOrderInfo(10L, 1, 2, | ||
| 71 | - MapUtil.<String, Object>builder().put("nickname", "小明").put("thing", "种土豆").build()); | 64 | + public CommonResult<AppTradeOrderCreateRespVO> createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) { |
| 72 | TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO); | 65 | TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), getClientIP(), createReqVO); |
| 73 | return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId())); | 66 | return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId())); |
| 74 | } | 67 | } |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
| 1 | package cn.iocoder.yudao.module.trade.controller.app.order.vo; | 1 | package cn.iocoder.yudao.module.trade.controller.app.order.vo; |
| 2 | 2 | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | import lombok.Data; | 5 | import lombok.Data; |
| 5 | 6 | ||
| 7 | +import javax.validation.constraints.AssertTrue; | ||
| 8 | + | ||
| 6 | @Schema(description = "用户 App - 交易订单创建 Request VO") | 9 | @Schema(description = "用户 App - 交易订单创建 Request VO") |
| 7 | @Data | 10 | @Data |
| 8 | public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { | 11 | public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { |
| @@ -10,4 +13,10 @@ public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { | @@ -10,4 +13,10 @@ public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { | ||
| 10 | @Schema(description = "备注", example = "这个是我的订单哟") | 13 | @Schema(description = "备注", example = "这个是我的订单哟") |
| 11 | private String remark; | 14 | private String remark; |
| 12 | 15 | ||
| 16 | + @AssertTrue(message = "配送方式不能为空") | ||
| 17 | + @JsonIgnore | ||
| 18 | + public boolean isDeliveryTypeNotNull() { | ||
| 19 | + return getDeliveryType() != null; | ||
| 20 | + } | ||
| 21 | + | ||
| 13 | } | 22 | } |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
| @@ -30,7 +30,7 @@ public class AppTradeOrderSettlementReqVO { | @@ -30,7 +30,7 @@ public class AppTradeOrderSettlementReqVO { | ||
| 30 | private Boolean pointStatus; | 30 | private Boolean pointStatus; |
| 31 | 31 | ||
| 32 | // ========== 配送相关相关字段 ========== | 32 | // ========== 配送相关相关字段 ========== |
| 33 | - @Schema(description = "配送方式", required = true, example = "1") | 33 | + @Schema(description = "配送方式", example = "1") |
| 34 | @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确") | 34 | @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确") |
| 35 | private Integer deliveryType; | 35 | private Integer deliveryType; |
| 36 | 36 | ||
| @@ -62,6 +62,8 @@ public class AppTradeOrderSettlementReqVO { | @@ -62,6 +62,8 @@ public class AppTradeOrderSettlementReqVO { | ||
| 62 | @Schema(description = "砍价活动编号", example = "123") | 62 | @Schema(description = "砍价活动编号", example = "123") |
| 63 | private Long bargainActivityId; | 63 | private Long bargainActivityId; |
| 64 | 64 | ||
| 65 | + // TODO @puhui999:可以写个参数校验,如果 seckillActivityId 或 combinationActivityId 或 combinationHeadId 的情况,items 应该只有一个 | ||
| 66 | + | ||
| 65 | @Data | 67 | @Data |
| 66 | @Schema(description = "用户 App - 商品项") | 68 | @Schema(description = "用户 App - 商品项") |
| 67 | @Valid | 69 | @Valid |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
| @@ -56,6 +56,7 @@ public interface TradeOrderConvert { | @@ -56,6 +56,7 @@ public interface TradeOrderConvert { | ||
| 56 | 56 | ||
| 57 | @Mappings({ | 57 | @Mappings({ |
| 58 | @Mapping(target = "id", ignore = true), | 58 | @Mapping(target = "id", ignore = true), |
| 59 | + @Mapping(source = "userId", target = "userId"), | ||
| 59 | @Mapping(source = "createReqVO.couponId", target = "couponId"), | 60 | @Mapping(source = "createReqVO.couponId", target = "couponId"), |
| 60 | @Mapping(target = "remark", ignore = true), | 61 | @Mapping(target = "remark", ignore = true), |
| 61 | @Mapping(source = "createReqVO.remark", target = "userRemark"), | 62 | @Mapping(source = "createReqVO.remark", target = "userRemark"), |
| @@ -89,23 +90,16 @@ public interface TradeOrderConvert { | @@ -89,23 +90,16 @@ public interface TradeOrderConvert { | ||
| 89 | TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item); | 90 | TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item); |
| 90 | 91 | ||
| 91 | default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) { | 92 | default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) { |
| 92 | - return new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(list)); | 93 | + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item -> |
| 94 | + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount())); | ||
| 95 | + return new ProductSkuUpdateStockReqDTO(items); | ||
| 93 | } | 96 | } |
| 94 | - | ||
| 95 | - default ProductSkuUpdateStockReqDTO convertNegative(List<TradeOrderItemDO> list) { | ||
| 96 | - List<ProductSkuUpdateStockReqDTO.Item> items = TradeOrderConvert.INSTANCE.convertList(list); | ||
| 97 | - items.forEach(item -> item.setIncrCount(-item.getIncrCount())); | 97 | + default ProductSkuUpdateStockReqDTO convertNegative(List<AppTradeOrderSettlementReqVO.Item> list) { |
| 98 | + List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item -> | ||
| 99 | + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount())); | ||
| 98 | return new ProductSkuUpdateStockReqDTO(items); | 100 | return new ProductSkuUpdateStockReqDTO(items); |
| 99 | } | 101 | } |
| 100 | 102 | ||
| 101 | - List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list); | ||
| 102 | - | ||
| 103 | - @Mappings({ | ||
| 104 | - @Mapping(source = "skuId", target = "id"), | ||
| 105 | - @Mapping(source = "count", target = "incrCount"), | ||
| 106 | - }) | ||
| 107 | - ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean); | ||
| 108 | - | ||
| 109 | default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems, | 103 | default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems, |
| 110 | TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) { | 104 | TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) { |
| 111 | PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO() | 105 | PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO() |
| @@ -218,8 +212,9 @@ public interface TradeOrderConvert { | @@ -218,8 +212,9 @@ public interface TradeOrderConvert { | ||
| 218 | 212 | ||
| 219 | default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, | 213 | default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, |
| 220 | List<CartDO> cartList) { | 214 | List<CartDO> cartList) { |
| 221 | - TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO(); | ||
| 222 | - reqBO.setUserId(userId).setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId()) | 215 | + TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId) |
| 216 | + .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus()) | ||
| 217 | + .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId()) | ||
| 223 | .setItems(new ArrayList<>(settlementReqVO.getItems().size())); | 218 | .setItems(new ArrayList<>(settlementReqVO.getItems().size())); |
| 224 | // 商品项的构建 | 219 | // 商品项的构建 |
| 225 | Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); | 220 | Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
| @@ -251,12 +251,19 @@ public class TradeOrderDO extends BaseDO { | @@ -251,12 +251,19 @@ public class TradeOrderDO extends BaseDO { | ||
| 251 | * 对应 taobao 的 trade.coupon_fee 字段 | 251 | * 对应 taobao 的 trade.coupon_fee 字段 |
| 252 | */ | 252 | */ |
| 253 | private Integer couponPrice; | 253 | private Integer couponPrice; |
| 254 | - // TODO 芋艿:需要记录使用的积分; | 254 | + /** |
| 255 | + * 使用的积分 | ||
| 256 | + */ | ||
| 257 | + private Integer usePoint; | ||
| 255 | /** | 258 | /** |
| 256 | * 积分抵扣的金额,单位:分 | 259 | * 积分抵扣的金额,单位:分 |
| 257 | * | 260 | * |
| 258 | * 对应 taobao 的 trade.point_fee 字段 | 261 | * 对应 taobao 的 trade.point_fee 字段 |
| 259 | */ | 262 | */ |
| 260 | private Integer pointPrice; | 263 | private Integer pointPrice; |
| 264 | +// /** | ||
| 265 | +// * 奖励的积分 TODO 疯狂:可以使用这个字段哈; | ||
| 266 | +// */ | ||
| 267 | +// private Integer rewardPoint; | ||
| 261 | 268 | ||
| 262 | } | 269 | } |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
| @@ -47,6 +47,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; | @@ -47,6 +47,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; | ||
| 47 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | 47 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; |
| 48 | import cn.iocoder.yudao.module.trade.enums.order.*; | 48 | import cn.iocoder.yudao.module.trade.enums.order.*; |
| 49 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; | 49 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; |
| 50 | +import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog; | ||
| 50 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO; | 51 | import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO; |
| 51 | import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService; | 52 | import cn.iocoder.yudao.module.trade.service.brokerage.record.BrokerageRecordService; |
| 52 | import cn.iocoder.yudao.module.trade.service.cart.CartService; | 53 | import cn.iocoder.yudao.module.trade.service.cart.CartService; |
| @@ -182,32 +183,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -182,32 +183,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 182 | 183 | ||
| 183 | @Override | 184 | @Override |
| 184 | @Transactional(rollbackFor = Exception.class) | 185 | @Transactional(rollbackFor = Exception.class) |
| 186 | + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE) | ||
| 185 | public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { | 187 | public TradeOrderDO createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { |
| 186 | - // 1、执行订单创建前置处理器 | ||
| 187 | - // TODO @puhui999:最好也抽个 beforeOrderCreate 方法;不要 BO 各自处理参数岂不美哉? | ||
| 188 | - TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO); | ||
| 189 | - beforeOrderCreateReqBO.setOrderType(validateActivity(createReqVO)); | ||
| 190 | - beforeOrderCreateReqBO.setUserId(userId); | ||
| 191 | - beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum)); | ||
| 192 | - // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等 | ||
| 193 | - tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO)); | ||
| 194 | - | ||
| 195 | - // 2. 价格计算 | 188 | + // 0. 价格计算 |
| 196 | TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); | 189 | TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); |
| 197 | 190 | ||
| 191 | + // 1. 订单创建前的逻辑 | ||
| 192 | + beforeCreateTradeOrder(userId, createReqVO, calculateRespBO); | ||
| 193 | + | ||
| 198 | // 2.1 插入 TradeOrderDO 订单 | 194 | // 2.1 插入 TradeOrderDO 订单 |
| 199 | TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO); | 195 | TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO); |
| 200 | // 2.2 插入 TradeOrderItemDO 订单项 | 196 | // 2.2 插入 TradeOrderItemDO 订单项 |
| 201 | List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); | 197 | List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); |
| 202 | 198 | ||
| 203 | - // 3. 订单创建完后的逻辑 | 199 | + // 3. 订单创建后的逻辑 |
| 204 | afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); | 200 | afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); |
| 205 | - | ||
| 206 | - // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! | ||
| 207 | return order; | 201 | return order; |
| 208 | } | 202 | } |
| 209 | 203 | ||
| 210 | - | ||
| 211 | // TODO @puhui999:订单超时,自动取消; | 204 | // TODO @puhui999:订单超时,自动取消; |
| 212 | 205 | ||
| 213 | /** | 206 | /** |
| @@ -234,9 +227,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -234,9 +227,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 234 | address = validateAddress(userId, createReqVO.getAddressId()); | 227 | address = validateAddress(userId, createReqVO.getAddressId()); |
| 235 | } | 228 | } |
| 236 | TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); | 229 | TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); |
| 237 | - String no = orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX); | ||
| 238 | - order.setType(validateActivity(createReqVO)); | ||
| 239 | - order.setNo(no); | 230 | + order.setType(calculateRespBO.getType()); |
| 231 | + order.setNo(orderNoRedisDAO.generate(TradeOrderNoRedisDAO.TRADE_ORDER_NO_PREFIX)); | ||
| 240 | order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); | 232 | order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); |
| 241 | order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); | 233 | order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); |
| 242 | order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); | 234 | order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); |
| @@ -252,71 +244,80 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -252,71 +244,80 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 252 | return order; | 244 | return order; |
| 253 | } | 245 | } |
| 254 | 246 | ||
| 247 | + private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, | ||
| 248 | + TradePriceCalculateRespBO calculateRespBO) { | ||
| 249 | + List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); | ||
| 250 | + tradeOrderItemMapper.insertBatch(orderItems); | ||
| 251 | + return orderItems; | ||
| 252 | + } | ||
| 253 | + | ||
| 255 | /** | 254 | /** |
| 256 | - * 校验活动,并返回订单类型 | 255 | + * 订单创建前,执行前置逻辑 |
| 257 | * | 256 | * |
| 258 | - * @param createReqVO 请求参数 | ||
| 259 | - * @return 订单类型 | 257 | + * @param userId 用户编号 |
| 258 | + * @param createReqVO 创建订单请求 | ||
| 259 | + * @param calculateRespBO 订单价格计算结果 | ||
| 260 | */ | 260 | */ |
| 261 | - private Integer validateActivity(AppTradeOrderCreateReqVO createReqVO) { | ||
| 262 | - if (createReqVO.getSeckillActivityId() != null) { | ||
| 263 | - return TradeOrderTypeEnum.SECKILL.getType(); | ||
| 264 | - } | ||
| 265 | - if (createReqVO.getCombinationActivityId() != null) { | ||
| 266 | - return TradeOrderTypeEnum.COMBINATION.getType(); | ||
| 267 | - } | ||
| 268 | - // TODO 砍价敬请期待 | ||
| 269 | - return TradeOrderTypeEnum.NORMAL.getType(); | ||
| 270 | - } | 261 | + private void beforeCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, |
| 262 | + TradePriceCalculateRespBO calculateRespBO) { | ||
| 263 | + // 1. 执行订单创建前置处理器 | ||
| 264 | + TradeBeforeOrderCreateReqBO beforeOrderCreateReqBO = TradeOrderConvert.INSTANCE.convert(createReqVO); | ||
| 265 | + beforeOrderCreateReqBO.setOrderType(calculateRespBO.getType()); | ||
| 266 | + beforeOrderCreateReqBO.setUserId(userId); | ||
| 267 | + beforeOrderCreateReqBO.setCount(getSumValue(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCount, Integer::sum)); | ||
| 268 | + // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等 | ||
| 269 | + tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(beforeOrderCreateReqBO)); | ||
| 271 | 270 | ||
| 272 | - private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) { | ||
| 273 | - List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); | ||
| 274 | - tradeOrderItemMapper.insertBatch(orderItems); | ||
| 275 | - return orderItems; | 271 | + // 2. 下单时扣减商品库存 |
| 272 | + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(createReqVO.getItems())); | ||
| 276 | } | 273 | } |
| 277 | 274 | ||
| 278 | /** | 275 | /** |
| 279 | - * 执行创建完创建完订单后的逻辑 | 276 | + * 订单创建后,执行后置逻辑 |
| 280 | * | 277 | * |
| 281 | * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等 | 278 | * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等 |
| 282 | * | 279 | * |
| 283 | * @param userId 用户编号 | 280 | * @param userId 用户编号 |
| 284 | * @param createReqVO 创建订单请求 | 281 | * @param createReqVO 创建订单请求 |
| 285 | - * @param tradeOrderDO 交易订单 | 282 | + * @param order 交易订单 |
| 286 | * @param calculateRespBO 订单价格计算结果 | 283 | * @param calculateRespBO 订单价格计算结果 |
| 287 | */ | 284 | */ |
| 288 | private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, | 285 | private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, |
| 289 | - TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems, | 286 | + TradeOrderDO order, List<TradeOrderItemDO> orderItems, |
| 290 | TradePriceCalculateRespBO calculateRespBO) { | 287 | TradePriceCalculateRespBO calculateRespBO) { |
| 291 | - // 执行订单创建后置处理器 | ||
| 292 | - tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(TradeOrderConvert.INSTANCE.convert(userId, createReqVO, tradeOrderDO, orderItems.get(0)))); | ||
| 293 | - | ||
| 294 | - // 扣减积分 TODO 芋艿:待实现,需要前置; | ||
| 295 | - // 这个是不是应该放到支付成功之后?如果支付后的话,可能积分可以重复使用哈。资源类,都要预扣 | 288 | + // 1. 执行订单创建后置处理器 |
| 289 | + // TODO @puhui999:从通用性来说,应该不用 orderItems.get(0) | ||
| 290 | + tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate( | ||
| 291 | + TradeOrderConvert.INSTANCE.convert(userId, createReqVO, order, orderItems.get(0)))); | ||
| 296 | 292 | ||
| 297 | - // 有使用优惠券时更新 TODO 芋艿:需要前置; | 293 | + // 2. 有使用优惠券时更新 |
| 294 | + // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号 | ||
| 298 | if (createReqVO.getCouponId() != null) { | 295 | if (createReqVO.getCouponId() != null) { |
| 299 | couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId) | 296 | couponApi.useCoupon(new CouponUseReqDTO().setId(createReqVO.getCouponId()).setUserId(userId) |
| 300 | - .setOrderId(tradeOrderDO.getId())); | 297 | + .setOrderId(order.getId())); |
| 301 | } | 298 | } |
| 302 | 299 | ||
| 303 | - // 下单时扣减商品库存 | ||
| 304 | - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems)); | 300 | + // 3. 扣减积分 |
| 301 | + // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务 | ||
| 302 | + if (order.getUsePoint() != null && order.getUsePoint() > 0) { | ||
| 303 | + memberPointApi.reducePoint(userId, calculateRespBO.getUsePoint(), | ||
| 304 | + MemberPointBizTypeEnum.ORDER_USE.getType(), String.valueOf(order.getId())); | ||
| 305 | + } | ||
| 305 | 306 | ||
| 306 | - // 删除购物车商品 | 307 | + // 4. 删除购物车商品 |
| 307 | Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); | 308 | Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); |
| 308 | if (CollUtil.isNotEmpty(cartIds)) { | 309 | if (CollUtil.isNotEmpty(cartIds)) { |
| 309 | cartService.deleteCart(userId, cartIds); | 310 | cartService.deleteCart(userId, cartIds); |
| 310 | } | 311 | } |
| 311 | 312 | ||
| 312 | - // 生成预支付 | ||
| 313 | - createPayOrder(tradeOrderDO, orderItems, calculateRespBO); | 313 | + // 5. 生成预支付 |
| 314 | + createPayOrder(order, orderItems, calculateRespBO); | ||
| 314 | 315 | ||
| 315 | - // 增加订单日志 TODO 芋艿:待实现 | 316 | + // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! |
| 316 | } | 317 | } |
| 317 | 318 | ||
| 318 | - | ||
| 319 | - private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) { | 319 | + private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, |
| 320 | + TradePriceCalculateRespBO calculateRespBO) { | ||
| 320 | // 创建支付单,用于后续的支付 | 321 | // 创建支付单,用于后续的支付 |
| 321 | PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( | 322 | PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( |
| 322 | order, orderItems, calculateRespBO, tradeOrderProperties); | 323 | order, orderItems, calculateRespBO, tradeOrderProperties); |
| @@ -344,6 +345,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -344,6 +345,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 344 | } | 345 | } |
| 345 | // 校验活动 | 346 | // 校验活动 |
| 346 | // 1、拼团活动 | 347 | // 1、拼团活动 |
| 348 | + // TODO @puhui999:这块也抽象到 handler 里 | ||
| 347 | if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { | 349 | if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { |
| 348 | // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录 | 350 | // 更新拼团状态 TODO puhui999:订单支付失败或订单支付过期删除这条拼团记录 |
| 349 | combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now()); | 351 | combinationRecordApi.updateRecordStatusToInProgress(order.getUserId(), order.getId(), LocalDateTime.now()); |
| @@ -741,6 +743,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -741,6 +743,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 741 | couponApi.returnUsedCoupon(order.getCouponId()); | 743 | couponApi.returnUsedCoupon(order.getCouponId()); |
| 742 | 744 | ||
| 743 | // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择; | 745 | // 4.回滚积分:积分是支付成功后才增加的吧? 回复:每个项目不同,目前看下来,确认收货貌似更合适,我再看看其它项目的业务选择; |
| 746 | + // TODO @疯狂:有赞是可配置(支付 or 确认收货),我们按照支付好列;然后这里的退积分,指的是下单时的积分抵扣。 | ||
| 744 | 747 | ||
| 745 | // TODO 芋艿:OrderLog | 748 | // TODO 芋艿:OrderLog |
| 746 | 749 | ||
| @@ -773,17 +776,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | @@ -773,17 +776,18 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { | ||
| 773 | 776 | ||
| 774 | @Async | 777 | @Async |
| 775 | protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) { | 778 | protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) { |
| 776 | - int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType(); | 779 | + // TODO @疯狂:具体多少积分,需要分成 2 不分:1. 支付金额;2. 商品金额 |
| 780 | + int bizType = MemberPointBizTypeEnum.ORDER_REWARD.getType(); | ||
| 777 | memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId)); | 781 | memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId)); |
| 778 | } | 782 | } |
| 779 | 783 | ||
| 780 | @Async | 784 | @Async |
| 781 | protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) { | 785 | protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) { |
| 786 | + // TODO @疯狂:退款时,按照金额比例,退还积分;https://help.youzan.com/displaylist/detail_4_4-1-49185 | ||
| 782 | int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType(); | 787 | int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType(); |
| 783 | memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); | 788 | memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); |
| 784 | } | 789 | } |
| 785 | 790 | ||
| 786 | - | ||
| 787 | @Async | 791 | @Async |
| 788 | protected void addBrokerageAsync(Long userId, Long orderId) { | 792 | protected void addBrokerageAsync(Long userId, Long orderId) { |
| 789 | MemberUserRespDTO user = memberUserApi.getUser(userId); | 793 | MemberUserRespDTO user = memberUserApi.getUser(userId); |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
| @@ -37,6 +37,11 @@ public class TradeCombinationHandler implements TradeOrderHandler { | @@ -37,6 +37,11 @@ public class TradeCombinationHandler implements TradeOrderHandler { | ||
| 37 | 37 | ||
| 38 | @Override | 38 | @Override |
| 39 | public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) { | 39 | public void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO) { |
| 40 | + // TODO @puhui999:需要判断下; | ||
| 41 | + if (true) { | ||
| 42 | + return; | ||
| 43 | + } | ||
| 44 | + | ||
| 40 | // 创建砍价记录 | 45 | // 创建砍价记录 |
| 41 | combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO)); | 46 | combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(reqBO)); |
| 42 | } | 47 | } |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
| 1 | package cn.iocoder.yudao.module.trade.service.price.bo; | 1 | package cn.iocoder.yudao.module.trade.service.price.bo; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | 3 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; |
| 4 | -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; | ||
| 5 | import lombok.Data; | 4 | import lombok.Data; |
| 6 | 5 | ||
| 7 | import javax.validation.Valid; | 6 | import javax.validation.Valid; |
| @@ -18,13 +17,6 @@ import java.util.List; | @@ -18,13 +17,6 @@ import java.util.List; | ||
| 18 | public class TradePriceCalculateReqBO { | 17 | public class TradePriceCalculateReqBO { |
| 19 | 18 | ||
| 20 | /** | 19 | /** |
| 21 | - * 订单类型 | ||
| 22 | - * | ||
| 23 | - * 枚举 {@link TradeOrderTypeEnum} | ||
| 24 | - */ | ||
| 25 | - private Integer type; | ||
| 26 | - | ||
| 27 | - /** | ||
| 28 | * 用户编号 | 20 | * 用户编号 |
| 29 | * | 21 | * |
| 30 | * 对应 MemberUserDO 的 id 编号 | 22 | * 对应 MemberUserDO 的 id 编号 |
| @@ -37,20 +29,31 @@ public class TradePriceCalculateReqBO { | @@ -37,20 +29,31 @@ public class TradePriceCalculateReqBO { | ||
| 37 | * 对应 CouponDO 的 id 编号 | 29 | * 对应 CouponDO 的 id 编号 |
| 38 | */ | 30 | */ |
| 39 | private Long couponId; | 31 | private Long couponId; |
| 32 | + // TODO @疯狂:需要增加一个 PriceCalculator 实现积分扣减的计算;写回到 TradePriceCalculateRespBO 的 usePoint | ||
| 33 | + /** | ||
| 34 | + * 是否使用积分 | ||
| 35 | + */ | ||
| 36 | + @NotNull(message = "是否使用积分不能为空") | ||
| 37 | + private Boolean pointStatus; | ||
| 40 | 38 | ||
| 41 | /** | 39 | /** |
| 40 | + * 配送方式 | ||
| 41 | + * | ||
| 42 | + * 枚举 {@link DeliveryTypeEnum} | ||
| 43 | + */ | ||
| 44 | + private Integer deliveryType; | ||
| 45 | + /** | ||
| 42 | * 收货地址编号 | 46 | * 收货地址编号 |
| 43 | * | 47 | * |
| 44 | * 对应 MemberAddressDO 的 id 编号 | 48 | * 对应 MemberAddressDO 的 id 编号 |
| 45 | */ | 49 | */ |
| 46 | private Long addressId; | 50 | private Long addressId; |
| 47 | - | ||
| 48 | /** | 51 | /** |
| 49 | - * 配送方式 | 52 | + * 自提门店编号 |
| 50 | * | 53 | * |
| 51 | - * 枚举 {@link DeliveryTypeEnum} | 54 | + * 对应 PickUpStoreDO 的 id 编号 |
| 52 | */ | 55 | */ |
| 53 | - private Integer deliveryType; | 56 | + private Long pickUpStoreId; |
| 54 | 57 | ||
| 55 | /** | 58 | /** |
| 56 | * 商品 SKU 数组 | 59 | * 商品 SKU 数组 |
| @@ -58,6 +61,30 @@ public class TradePriceCalculateReqBO { | @@ -58,6 +61,30 @@ public class TradePriceCalculateReqBO { | ||
| 58 | @NotNull(message = "商品数组不能为空") | 61 | @NotNull(message = "商品数组不能为空") |
| 59 | private List<Item> items; | 62 | private List<Item> items; |
| 60 | 63 | ||
| 64 | + // ========== 秒杀活动相关字段 ========== | ||
| 65 | + /** | ||
| 66 | + * 秒杀活动编号 | ||
| 67 | + */ | ||
| 68 | + private Long seckillActivityId; | ||
| 69 | + | ||
| 70 | + // ========== 拼团活动相关字段 ========== | ||
| 71 | + // TODO @puhui999:是不是拼团记录的编号哈? | ||
| 72 | + /** | ||
| 73 | + * 拼团活动编号 | ||
| 74 | + */ | ||
| 75 | + private Long combinationActivityId; | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * 拼团团长编号 | ||
| 79 | + */ | ||
| 80 | + private Long combinationHeadId; | ||
| 81 | + | ||
| 82 | + // ========== 砍价活动相关字段 ========== | ||
| 83 | + /** | ||
| 84 | + * 砍价活动编号 | ||
| 85 | + */ | ||
| 86 | + private Long bargainActivityId; | ||
| 87 | + | ||
| 61 | /** | 88 | /** |
| 62 | * 商品 SKU | 89 | * 商品 SKU |
| 63 | */ | 90 | */ |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
| @@ -49,6 +49,11 @@ public class TradePriceCalculateRespBO { | @@ -49,6 +49,11 @@ public class TradePriceCalculateRespBO { | ||
| 49 | private Long couponId; | 49 | private Long couponId; |
| 50 | 50 | ||
| 51 | /** | 51 | /** |
| 52 | + * 使用的积分 | ||
| 53 | + */ | ||
| 54 | + private Integer usePoint; | ||
| 55 | + | ||
| 56 | + /** | ||
| 52 | * 订单价格 | 57 | * 订单价格 |
| 53 | */ | 58 | */ |
| 54 | @Data | 59 | @Data |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
| @@ -2,11 +2,14 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; | @@ -2,11 +2,14 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.lang.Assert; | 4 | import cn.hutool.core.lang.Assert; |
| 5 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 5 | import cn.iocoder.yudao.module.member.api.address.AddressApi; | 6 | import cn.iocoder.yudao.module.member.api.address.AddressApi; |
| 6 | import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; | 7 | import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; |
| 8 | +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; | ||
| 7 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; | 9 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; |
| 8 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | 10 | import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; |
| 9 | import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; | 11 | import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; |
| 12 | +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService; | ||
| 10 | import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; | 13 | import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; |
| 11 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | 14 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 12 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | 15 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| @@ -22,8 +25,7 @@ import java.util.Set; | @@ -22,8 +25,7 @@ import java.util.Set; | ||
| 22 | 25 | ||
| 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 26 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | 27 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 25 | -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY; | ||
| 26 | -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND; | 28 | +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; |
| 27 | 29 | ||
| 28 | /** | 30 | /** |
| 29 | * 运费的 {@link TradePriceCalculator} 实现类 | 31 | * 运费的 {@link TradePriceCalculator} 实现类 |
| @@ -37,20 +39,41 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { | @@ -37,20 +39,41 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { | ||
| 37 | 39 | ||
| 38 | @Resource | 40 | @Resource |
| 39 | private AddressApi addressApi; | 41 | private AddressApi addressApi; |
| 42 | + | ||
| 43 | + @Resource | ||
| 44 | + private DeliveryPickUpStoreService deliveryPickUpStoreService; | ||
| 40 | @Resource | 45 | @Resource |
| 41 | private DeliveryExpressTemplateService deliveryExpressTemplateService; | 46 | private DeliveryExpressTemplateService deliveryExpressTemplateService; |
| 42 | 47 | ||
| 43 | @Override | 48 | @Override |
| 44 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | 49 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { |
| 45 | - // TODO @芋艿:如果门店自提,需要校验是否开启; | ||
| 46 | - // 1.1 判断配送方式 | ||
| 47 | - if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) { | 50 | + if (param.getDeliveryType() == null) { |
| 48 | return; | 51 | return; |
| 49 | } | 52 | } |
| 53 | + if (DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) { | ||
| 54 | + calculateByPickUp(param, result); | ||
| 55 | + } else if (DeliveryTypeEnum.EXPRESS.getMode().equals(param.getDeliveryType())) { | ||
| 56 | + calculateExpress(param, result); | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | ||
| 61 | + if (param.getPickUpStoreId() == null) { | ||
| 62 | + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY); | ||
| 63 | + } | ||
| 64 | + DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId()); | ||
| 65 | + if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) { | ||
| 66 | + throw exception(PICK_UP_STORE_NOT_EXISTS); | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + // ========= 快递发货 ========== | ||
| 71 | + | ||
| 72 | + private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | ||
| 73 | + // 1. 得到收件地址区域 | ||
| 50 | if (param.getAddressId() == null) { | 74 | if (param.getAddressId() == null) { |
| 51 | - throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDR_IS_EMPTY); | 75 | + throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY); |
| 52 | } | 76 | } |
| 53 | - // 1.2 得到收件地址区域 | ||
| 54 | AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId()); | 77 | AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId()); |
| 55 | Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); | 78 | Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); |
| 56 | 79 | ||
| @@ -89,8 +112,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { | @@ -89,8 +112,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { | ||
| 89 | for (OrderItem orderItem : orderItems) { | 112 | for (OrderItem orderItem : orderItems) { |
| 90 | totalCount += orderItem.getCount(); | 113 | totalCount += orderItem.getCount(); |
| 91 | totalPrice += orderItem.getPayPrice(); | 114 | totalPrice += orderItem.getPayPrice(); |
| 92 | - totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount(); | ||
| 93 | - totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount(); | 115 | + if (orderItem.getWeight() != null) { |
| 116 | + totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount(); | ||
| 117 | + } | ||
| 118 | + if (orderItem.getVolume() != null) { | ||
| 119 | + totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount(); | ||
| 120 | + } | ||
| 94 | } | 121 | } |
| 95 | // 优先判断是否包邮. 如果包邮不计算快递运费 | 122 | // 优先判断是否包邮. 如果包邮不计算快递运费 |
| 96 | if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight, | 123 | if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight, |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price.calculator; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | ||
| 4 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.core.annotation.Order; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 使用积分的 {@link TradePriceCalculator} 实现类 | ||
| 11 | + * | ||
| 12 | + * @author owen | ||
| 13 | + */ | ||
| 14 | +@Component | ||
| 15 | +@Order(TradePriceCalculator.ORDER_POINT_USE) | ||
| 16 | +@Slf4j | ||
| 17 | +public class TradePointUsePriceCalculator implements TradePriceCalculator { | ||
| 18 | + | ||
| 19 | + @Override | ||
| 20 | + public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | ||
| 21 | + // TODO 疯狂:待实现,嘿嘿; | ||
| 22 | + if (param.getPointStatus()) { | ||
| 23 | + result.setUsePoint(10); | ||
| 24 | + } else { | ||
| 25 | + result.setUsePoint(0); | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | +} |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java
| @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | ||
| 6 | /** | 6 | /** |
| 7 | * 价格计算的计算器接口 | 7 | * 价格计算的计算器接口 |
| 8 | * | 8 | * |
| 9 | + * 优惠计算顺序: | ||
| 10 | + * 1. <a href="https://help.youzan.com/displaylist/detail_4_4-1-53316">积分抵现、会员价、优惠券、粉丝专享价、满减送哪个优先计算?</> | ||
| 11 | + * | ||
| 9 | * @author 芋道源码 | 12 | * @author 芋道源码 |
| 10 | */ | 13 | */ |
| 11 | public interface TradePriceCalculator { | 14 | public interface TradePriceCalculator { |
| @@ -13,12 +16,13 @@ public interface TradePriceCalculator { | @@ -13,12 +16,13 @@ public interface TradePriceCalculator { | ||
| 13 | int ORDER_DISCOUNT_ACTIVITY = 10; | 16 | int ORDER_DISCOUNT_ACTIVITY = 10; |
| 14 | int ORDER_REWARD_ACTIVITY = 20; | 17 | int ORDER_REWARD_ACTIVITY = 20; |
| 15 | int ORDER_COUPON = 30; | 18 | int ORDER_COUPON = 30; |
| 19 | + int ORDER_POINT_USE = 40; | ||
| 16 | /** | 20 | /** |
| 17 | * 快递运费的计算 | 21 | * 快递运费的计算 |
| 18 | * | 22 | * |
| 19 | * 放在各种营销活动、优惠劵后面 TODO | 23 | * 放在各种营销活动、优惠劵后面 TODO |
| 20 | */ | 24 | */ |
| 21 | - int ORDER_DELIVERY = 40; | 25 | + int ORDER_DELIVERY = 50; |
| 22 | 26 | ||
| 23 | void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result); | 27 | void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result); |
| 24 | 28 |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
| @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert; | @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert; | ||
| 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | 5 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 6 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | 6 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 7 | +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; | ||
| 7 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | 8 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 8 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | 9 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 9 | 10 | ||
| @@ -28,7 +29,7 @@ public class TradePriceCalculatorHelper { | @@ -28,7 +29,7 @@ public class TradePriceCalculatorHelper { | ||
| 28 | List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) { | 29 | List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) { |
| 29 | // 创建 PriceCalculateRespDTO 对象 | 30 | // 创建 PriceCalculateRespDTO 对象 |
| 30 | TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); | 31 | TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); |
| 31 | - result.setType(param.getType()); | 32 | + result.setType(getOrderType(param)); |
| 32 | result.setPromotions(new ArrayList<>()); | 33 | result.setPromotions(new ArrayList<>()); |
| 33 | 34 | ||
| 34 | // 创建它的 OrderItem 属性 | 35 | // 创建它的 OrderItem 属性 |
| @@ -70,6 +71,23 @@ public class TradePriceCalculatorHelper { | @@ -70,6 +71,23 @@ public class TradePriceCalculatorHelper { | ||
| 70 | } | 71 | } |
| 71 | 72 | ||
| 72 | /** | 73 | /** |
| 74 | + * 计算订单类型 | ||
| 75 | + * | ||
| 76 | + * @param param 计算参数 | ||
| 77 | + * @return 订单类型 | ||
| 78 | + */ | ||
| 79 | + private static Integer getOrderType(TradePriceCalculateReqBO param) { | ||
| 80 | + if (param.getSeckillActivityId() != null) { | ||
| 81 | + return TradeOrderTypeEnum.SECKILL.getType(); | ||
| 82 | + } | ||
| 83 | + if (param.getCombinationActivityId() != null) { | ||
| 84 | + return TradeOrderTypeEnum.COMBINATION.getType(); | ||
| 85 | + } | ||
| 86 | + // TODO 砍价敬请期待 | ||
| 87 | + return TradeOrderTypeEnum.NORMAL.getType(); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + /** | ||
| 73 | * 基于订单项,重新计算 price 总价 | 91 | * 基于订单项,重新计算 price 总价 |
| 74 | * | 92 | * |
| 75 | * @param result 计算结果 | 93 | * @param result 计算结果 |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java
| @@ -45,7 +45,7 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest { | @@ -45,7 +45,7 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest { | ||
| 45 | public void testCalculatePrice() { | 45 | public void testCalculatePrice() { |
| 46 | // 准备参数 | 46 | // 准备参数 |
| 47 | TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO() | 47 | TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO() |
| 48 | - .setType(TradeOrderTypeEnum.NORMAL.getType()).setUserId(10L) | 48 | + .setUserId(10L) |
| 49 | .setCouponId(20L).setAddressId(30L) | 49 | .setCouponId(20L).setAddressId(30L) |
| 50 | .setItems(Arrays.asList( | 50 | .setItems(Arrays.asList( |
| 51 | new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true), | 51 | new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true), |
yudao-module-member/yudao-module-member-api/pom.xml
| @@ -21,6 +21,13 @@ | @@ -21,6 +21,13 @@ | ||
| 21 | <groupId>cn.iocoder.boot</groupId> | 21 | <groupId>cn.iocoder.boot</groupId> |
| 22 | <artifactId>yudao-common</artifactId> | 22 | <artifactId>yudao-common</artifactId> |
| 23 | </dependency> | 23 | </dependency> |
| 24 | + | ||
| 25 | + <!-- 参数校验 --> | ||
| 26 | + <dependency> | ||
| 27 | + <groupId>org.springframework.boot</groupId> | ||
| 28 | + <artifactId>spring-boot-starter-validation</artifactId> | ||
| 29 | + <optional>true</optional> | ||
| 30 | + </dependency> | ||
| 24 | </dependencies> | 31 | </dependencies> |
| 25 | 32 | ||
| 26 | </project> | 33 | </project> |
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
| @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.api.point; | @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.api.point; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; | 3 | import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; |
| 4 | 4 | ||
| 5 | +import javax.validation.constraints.Min; | ||
| 6 | + | ||
| 5 | /** | 7 | /** |
| 6 | * 用户积分的 API 接口 | 8 | * 用户积分的 API 接口 |
| 7 | * | 9 | * |
| @@ -17,6 +19,18 @@ public interface MemberPointApi { | @@ -17,6 +19,18 @@ public interface MemberPointApi { | ||
| 17 | * @param bizType 业务类型 {@link MemberPointBizTypeEnum} | 19 | * @param bizType 业务类型 {@link MemberPointBizTypeEnum} |
| 18 | * @param bizId 业务编号 | 20 | * @param bizId 业务编号 |
| 19 | */ | 21 | */ |
| 20 | - void addPoint(Long userId, Integer point, Integer bizType, String bizId); | 22 | + void addPoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point, |
| 23 | + Integer bizType, String bizId); | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 减少用户积分 | ||
| 27 | + * | ||
| 28 | + * @param userId 用户编号 | ||
| 29 | + * @param point 积分 | ||
| 30 | + * @param bizType 业务类型 {@link MemberPointBizTypeEnum} | ||
| 31 | + * @param bizId 业务编号 | ||
| 32 | + */ | ||
| 33 | + void reducePoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point, | ||
| 34 | + Integer bizType, String bizId); | ||
| 21 | 35 | ||
| 22 | } | 36 | } |
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
| @@ -17,8 +17,10 @@ import java.util.Objects; | @@ -17,8 +17,10 @@ import java.util.Objects; | ||
| 17 | public enum MemberPointBizTypeEnum implements IntArrayValuable { | 17 | public enum MemberPointBizTypeEnum implements IntArrayValuable { |
| 18 | 18 | ||
| 19 | SIGN(1, "签到", "签到获得 {} 积分", true), | 19 | SIGN(1, "签到", "签到获得 {} 积分", true), |
| 20 | - ORDER_BUY(10, "订单消费", "下单获得 {} 积分", true), | ||
| 21 | - ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false); // 退回积分 | 20 | + ORDER_REWARD(10, "订单奖励", "下单获得 {} 积分", true), |
| 21 | + ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false), // 退回积分 | ||
| 22 | + ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 扣减积分 | ||
| 23 | + ; | ||
| 22 | 24 | ||
| 23 | /** | 25 | /** |
| 24 | * 类型 | 26 | * 类型 |
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
| @@ -31,4 +31,13 @@ public class MemberPointApiImpl implements MemberPointApi { | @@ -31,4 +31,13 @@ public class MemberPointApiImpl implements MemberPointApi { | ||
| 31 | memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId); | 31 | memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | + @Override | ||
| 35 | + public void reducePoint(Long userId, Integer point, Integer bizType, String bizId) { | ||
| 36 | + MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType); | ||
| 37 | + if (bizTypeEnum == null) { | ||
| 38 | + throw exception(POINT_RECORD_BIZ_NOT_SUPPORT); | ||
| 39 | + } | ||
| 40 | + memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId); | ||
| 41 | + } | ||
| 42 | + | ||
| 34 | } | 43 | } |