Commit ba380ea615fb6d5f9fcf7a061a0858a88e65bf7a
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
Showing
44 changed files
with
1381 additions
and
780 deletions
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
| 1 | 1 | package cn.iocoder.yudao.module.product.api.sku.dto; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; | |
| 3 | 4 | import lombok.Data; |
| 4 | 5 | |
| 5 | 6 | import java.util.List; |
| ... | ... | @@ -25,7 +26,7 @@ public class ProductSkuRespDTO { |
| 25 | 26 | /** |
| 26 | 27 | * 属性数组 |
| 27 | 28 | */ |
| 28 | - private List<Property> properties; | |
| 29 | + private List<ProductPropertyValueDetailRespDTO> properties; | |
| 29 | 30 | /** |
| 30 | 31 | * 销售价格,单位:分 |
| 31 | 32 | */ |
| ... | ... | @@ -51,10 +52,6 @@ public class ProductSkuRespDTO { |
| 51 | 52 | */ |
| 52 | 53 | private Integer stock; |
| 53 | 54 | /** |
| 54 | - * 预警预存 | |
| 55 | - */ | |
| 56 | - private Integer warnStock; | |
| 57 | - /** | |
| 58 | 55 | * 商品重量,单位:kg 千克 |
| 59 | 56 | */ |
| 60 | 57 | private Double weight; |
| ... | ... | @@ -63,30 +60,4 @@ public class ProductSkuRespDTO { |
| 63 | 60 | */ |
| 64 | 61 | private Double volume; |
| 65 | 62 | |
| 66 | - /** | |
| 67 | - * 商品属性 | |
| 68 | - */ | |
| 69 | - @Data | |
| 70 | - public static class Property { | |
| 71 | - | |
| 72 | - /** | |
| 73 | - * 属性编号 | |
| 74 | - */ | |
| 75 | - private Long propertyId; | |
| 76 | - /** | |
| 77 | - * 属性名字 | |
| 78 | - */ | |
| 79 | - private String propertyName; | |
| 80 | - | |
| 81 | - /** | |
| 82 | - * 属性值编号 | |
| 83 | - */ | |
| 84 | - private Long valueId; | |
| 85 | - /** | |
| 86 | - * 属性值名字 | |
| 87 | - */ | |
| 88 | - private String valueName; | |
| 89 | - | |
| 90 | - } | |
| 91 | - | |
| 92 | 63 | } | ... | ... |
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java
| ... | ... | @@ -18,6 +18,9 @@ public class AppProductSpuDetailRespVO { |
| 18 | 18 | @Schema(description = "商品名称", required = true, example = "芋道") |
| 19 | 19 | private String name; |
| 20 | 20 | |
| 21 | + @Schema(description = "商品简介", required = true, example = "我是一个快乐简介") | |
| 22 | + private String introduction; | |
| 23 | + | |
| 21 | 24 | @Schema(description = "商品详情", required = true, example = "我是商品描述") |
| 22 | 25 | private String description; |
| 23 | 26 | ... | ... |
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/PriceCalculateReqDTO.java
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/price/PriceServiceTest.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.service.price; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.map.MapUtil; | |
| 4 | 3 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 5 | 4 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 6 | 5 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 7 | 6 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 8 | 7 | import cn.iocoder.yudao.module.promotion.api.price.dto.CouponMeetRespDTO; |
| 9 | 8 | import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO; |
| 10 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; | |
| 11 | 9 | import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; |
| 12 | -import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; | |
| 13 | -import cn.iocoder.yudao.module.promotion.enums.common.*; | |
| 10 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 14 | 11 | import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; |
| 15 | 12 | import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; |
| 16 | -import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService; | |
| 17 | 13 | import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService; |
| 18 | 14 | import org.junit.jupiter.api.Test; |
| 19 | 15 | import org.mockito.InjectMocks; |
| 20 | 16 | import org.mockito.Mock; |
| 21 | 17 | |
| 22 | -import java.util.LinkedHashMap; | |
| 23 | 18 | import java.util.List; |
| 24 | -import java.util.Map; | |
| 25 | -import java.util.Set; | |
| 26 | 19 | |
| 27 | 20 | import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; |
| 28 | 21 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; |
| 29 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | |
| 30 | 22 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| 31 | 23 | import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_VALID_TIME_NOT_NOW; |
| 32 | 24 | import static java.util.Arrays.asList; |
| ... | ... | @@ -47,413 +39,18 @@ public class PriceServiceTest extends BaseMockitoUnitTest { |
| 47 | 39 | private PriceServiceImpl priceService; |
| 48 | 40 | |
| 49 | 41 | @Mock |
| 50 | - private DiscountActivityService discountService; | |
| 51 | - @Mock | |
| 52 | 42 | private RewardActivityService rewardActivityService; |
| 53 | 43 | @Mock |
| 54 | 44 | private CouponService couponService; |
| 55 | 45 | @Mock |
| 56 | 46 | private ProductSkuApi productSkuApi; |
| 57 | 47 | |
| 58 | - @Test | |
| 59 | - public void testCalculatePrice_memberDiscount() { | |
| 60 | - // 准备参数 | |
| 61 | - // TODO 芋艿:userId = 1,实现 9 折;后续改成 mock | |
| 62 | - PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(1L) | |
| 63 | - .setItems(singletonList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2))); | |
| 64 | - // mock 方法(商品 SKU 信息) | |
| 65 | - ProductSkuRespDTO productSku = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100)); | |
| 66 | - when(productSkuApi.getSkuList(eq(asSet(10L)))).thenReturn(singletonList(productSku)); | |
| 67 | - | |
| 68 | - // 调用 | |
| 69 | - //PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); TODO 没有这个方法 | |
| 70 | - PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | |
| 71 | - // 断言 Order 部分 | |
| 72 | - PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | |
| 73 | - assertEquals(order.getTotalPrice(), 200); | |
| 74 | - assertEquals(order.getDiscountPrice(), 0); | |
| 75 | - assertEquals(order.getPointPrice(), 0); | |
| 76 | - assertEquals(order.getDeliveryPrice(), 0); | |
| 77 | - assertEquals(order.getPayPrice(), 180); | |
| 78 | - assertNull(order.getCouponId()); | |
| 79 | - // 断言 OrderItem 部分 | |
| 80 | - assertEquals(order.getItems().size(), 1); | |
| 81 | - PriceCalculateRespDTO.OrderItem orderItem = order.getItems().get(0); | |
| 82 | - assertEquals(orderItem.getSkuId(), 10L); | |
| 83 | - assertEquals(orderItem.getCount(), 2); | |
| 84 | - assertEquals(orderItem.getOriginalPrice(), 200); | |
| 85 | - assertEquals(orderItem.getOriginalUnitPrice(), 100); | |
| 86 | - assertEquals(orderItem.getDiscountPrice(), 20); | |
| 87 | - assertEquals(orderItem.getPayPrice(), 180); | |
| 88 | - assertEquals(orderItem.getOrderPartPrice(), 0); | |
| 89 | - assertEquals(orderItem.getOrderDividePrice(), 180); | |
| 90 | - // 断言 Promotion 部分 | |
| 91 | - assertEquals(priceCalculate.getPromotions().size(), 1); | |
| 92 | - PriceCalculateRespDTO.Promotion promotion = priceCalculate.getPromotions().get(0); | |
| 93 | - assertNull(promotion.getId()); | |
| 94 | - assertEquals(promotion.getName(), "会员折扣"); | |
| 95 | - assertEquals(promotion.getType(), PromotionTypeEnum.MEMBER.getType()); | |
| 96 | - //assertEquals(promotion.getLevel(), PromotionLevelEnum.SKU.getLevel()); TODO 没有这个枚举类 | |
| 97 | - assertEquals(promotion.getTotalPrice(), 200); | |
| 98 | - assertEquals(promotion.getDiscountPrice(), 20); | |
| 99 | - assertTrue(promotion.getMatch()); | |
| 100 | - assertEquals(promotion.getDescription(), "会员折扣:省 0.20 元"); | |
| 101 | - PriceCalculateRespDTO.PromotionItem promotionItem = promotion.getItems().get(0); | |
| 102 | - assertEquals(promotion.getItems().size(), 1); | |
| 103 | - assertEquals(promotionItem.getSkuId(), 10L); | |
| 104 | - assertEquals(promotionItem.getOriginalPrice(), 200); | |
| 105 | - assertEquals(promotionItem.getDiscountPrice(), 20); | |
| 106 | - } | |
| 107 | - | |
| 108 | - @Test | |
| 109 | - public void testCalculatePrice_discountActivity() { | |
| 110 | - // 准备参数 | |
| 111 | - PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId()) | |
| 112 | - .setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2), | |
| 113 | - new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3))); | |
| 114 | - // mock 方法(商品 SKU 信息) | |
| 115 | - ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100)); | |
| 116 | - ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50)); | |
| 117 | - when(productSkuApi.getSkuList(eq(asSet(10L, 20L)))).thenReturn(asList(productSku01, productSku02)); | |
| 118 | - // mock 方法(限时折扣 DiscountActivity 信息)TODO 没找到 DiscountProductDetailBO | |
| 119 | - //DiscountProductDetailBO discountProduct01 = randomPojo(DiscountProductDetailBO.class, o -> o.setActivityId(1000L) | |
| 120 | - // .setActivityName("活动 1000 号").setSkuId(10L) | |
| 121 | - // .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(40)); | |
| 122 | - //DiscountProductDetailBO discountProduct02 = randomPojo(DiscountProductDetailBO.class, o -> o.setActivityId(2000L) | |
| 123 | - // .setActivityName("活动 2000 号").setSkuId(20L) | |
| 124 | - // .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(60)); | |
| 125 | - //when(discountService.getMatchDiscountProductList(eq(asSet(10L, 20L)))).thenReturn( | |
| 126 | - // MapUtil.builder(10L, discountProduct01).put(20L, discountProduct02).map()); | |
| 127 | - | |
| 128 | - // 10L: 100 * 2 - 40 * 2 = 120 | |
| 129 | - // 20L:50 * 3 - 50 * 3 * 0.4 = 90 | |
| 130 | - | |
| 131 | - // 调用 | |
| 132 | - //PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); TODO 没有这个方法 | |
| 133 | - PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | |
| 134 | - // 断言 Order 部分 | |
| 135 | - PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | |
| 136 | - assertEquals(order.getTotalPrice(), 350); | |
| 137 | - assertEquals(order.getDiscountPrice(), 0); | |
| 138 | - assertEquals(order.getPointPrice(), 0); | |
| 139 | - assertEquals(order.getDeliveryPrice(), 0); | |
| 140 | - assertEquals(order.getPayPrice(), 210); | |
| 141 | - assertNull(order.getCouponId()); | |
| 142 | - // 断言 OrderItem 部分 | |
| 143 | - assertEquals(order.getItems().size(), 2); | |
| 144 | - PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0); | |
| 145 | - assertEquals(orderItem01.getSkuId(), 10L); | |
| 146 | - assertEquals(orderItem01.getCount(), 2); | |
| 147 | - assertEquals(orderItem01.getOriginalPrice(), 200); | |
| 148 | - assertEquals(orderItem01.getOriginalUnitPrice(), 100); | |
| 149 | - assertEquals(orderItem01.getDiscountPrice(), 80); | |
| 150 | - assertEquals(orderItem01.getPayPrice(), 120); | |
| 151 | - assertEquals(orderItem01.getOrderPartPrice(), 0); | |
| 152 | - assertEquals(orderItem01.getOrderDividePrice(), 120); | |
| 153 | - PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1); | |
| 154 | - assertEquals(orderItem02.getSkuId(), 20L); | |
| 155 | - assertEquals(orderItem02.getCount(), 3); | |
| 156 | - assertEquals(orderItem02.getOriginalPrice(), 150); | |
| 157 | - assertEquals(orderItem02.getOriginalUnitPrice(), 50); | |
| 158 | - assertEquals(orderItem02.getDiscountPrice(), 60); | |
| 159 | - assertEquals(orderItem02.getPayPrice(), 90); | |
| 160 | - assertEquals(orderItem02.getOrderPartPrice(), 0); | |
| 161 | - assertEquals(orderItem02.getOrderDividePrice(), 90); | |
| 162 | - // 断言 Promotion 部分 | |
| 163 | - assertEquals(priceCalculate.getPromotions().size(), 2); | |
| 164 | - PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0); | |
| 165 | - assertEquals(promotion01.getId(), 1000L); | |
| 166 | - assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 167 | - assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()); | |
| 168 | - //assertEquals(promotion01.getLevel(), PromotionLevelEnum.SKU.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 169 | - assertEquals(promotion01.getTotalPrice(), 200); | |
| 170 | - assertEquals(promotion01.getDiscountPrice(), 80); | |
| 171 | - assertTrue(promotion01.getMatch()); | |
| 172 | - assertEquals(promotion01.getDescription(), "限时折扣:省 0.80 元"); | |
| 173 | - PriceCalculateRespDTO.PromotionItem promotionItem01 = promotion01.getItems().get(0); | |
| 174 | - assertEquals(promotion01.getItems().size(), 1); | |
| 175 | - assertEquals(promotionItem01.getSkuId(), 10L); | |
| 176 | - assertEquals(promotionItem01.getOriginalPrice(), 200); | |
| 177 | - assertEquals(promotionItem01.getDiscountPrice(), 80); | |
| 178 | - PriceCalculateRespDTO.Promotion promotion02 = priceCalculate.getPromotions().get(1); | |
| 179 | - assertEquals(promotion02.getId(), 2000L); | |
| 180 | - assertEquals(promotion02.getName(), "活动 2000 号"); | |
| 181 | - assertEquals(promotion02.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()); | |
| 182 | - //assertEquals(promotion02.getLevel(), PromotionLevelEnum.SKU.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 183 | - assertEquals(promotion02.getTotalPrice(), 150); | |
| 184 | - assertEquals(promotion02.getDiscountPrice(), 60); | |
| 185 | - assertTrue(promotion02.getMatch()); | |
| 186 | - assertEquals(promotion02.getDescription(), "限时折扣:省 0.60 元"); | |
| 187 | - PriceCalculateRespDTO.PromotionItem promotionItem02 = promotion02.getItems().get(0); | |
| 188 | - assertEquals(promotion02.getItems().size(), 1); | |
| 189 | - assertEquals(promotionItem02.getSkuId(), 20L); | |
| 190 | - assertEquals(promotionItem02.getOriginalPrice(), 150); | |
| 191 | - assertEquals(promotionItem02.getDiscountPrice(), 60); | |
| 192 | - } | |
| 193 | - | |
| 194 | - /** | |
| 195 | - * 测试满减送活动,匹配的情况 | |
| 196 | - */ | |
| 197 | - @Test | |
| 198 | - public void testCalculatePrice_rewardActivity() { | |
| 199 | - // 准备参数 | |
| 200 | - PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId()) | |
| 201 | - .setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2), | |
| 202 | - new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3), | |
| 203 | - new PriceCalculateReqDTO.Item().setSkuId(30L).setCount(4))); | |
| 204 | - // mock 方法(商品 SKU 信息) | |
| 205 | - ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L)); | |
| 206 | - ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L)); | |
| 207 | - ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L)); | |
| 208 | - when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03)); | |
| 209 | - // mock 方法(限时折扣 DiscountActivity 信息) | |
| 210 | - RewardActivityDO rewardActivity01 = randomPojo(RewardActivityDO.class, o -> o.setId(1000L).setName("活动 1000 号") | |
| 211 | - .setProductSpuIds(asList(10L, 20L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) | |
| 212 | - .setRules(singletonList(new RewardActivityDO.Rule().setLimit(200).setDiscountPrice(70)))); | |
| 213 | - RewardActivityDO rewardActivity02 = randomPojo(RewardActivityDO.class, o -> o.setId(2000L).setName("活动 2000 号") | |
| 214 | - .setProductSpuIds(singletonList(30L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) | |
| 215 | - .setRules(asList(new RewardActivityDO.Rule().setLimit(1).setDiscountPrice(10), | |
| 216 | - new RewardActivityDO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个 | |
| 217 | - new RewardActivityDO.Rule().setLimit(10).setDiscountPrice(100)))); | |
| 218 | - Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>(); | |
| 219 | - matchRewardActivities.put(rewardActivity01, asSet(1L, 2L)); | |
| 220 | - matchRewardActivities.put(rewardActivity02, asSet(3L)); | |
| 221 | - // TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList | |
| 222 | - //when(rewardActivityService.getMatchRewardActivities(eq(asSet(1L, 2L, 3L)))).thenReturn(matchRewardActivities); | |
| 223 | - | |
| 224 | - // 调用 | |
| 225 | - //PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); TODO 没有这个方法 | |
| 226 | - PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | |
| 227 | - // 断言 Order 部分 | |
| 228 | - PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | |
| 229 | - assertEquals(order.getTotalPrice(), 470); | |
| 230 | - assertEquals(order.getDiscountPrice(), 130); | |
| 231 | - assertEquals(order.getPointPrice(), 0); | |
| 232 | - assertEquals(order.getDeliveryPrice(), 0); | |
| 233 | - assertEquals(order.getPayPrice(), 340); | |
| 234 | - assertNull(order.getCouponId()); | |
| 235 | - // 断言 OrderItem 部分 | |
| 236 | - assertEquals(order.getItems().size(), 3); | |
| 237 | - PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0); | |
| 238 | - assertEquals(orderItem01.getSkuId(), 10L); | |
| 239 | - assertEquals(orderItem01.getCount(), 2); | |
| 240 | - assertEquals(orderItem01.getOriginalPrice(), 200); | |
| 241 | - assertEquals(orderItem01.getOriginalUnitPrice(), 100); | |
| 242 | - assertEquals(orderItem01.getDiscountPrice(), 0); | |
| 243 | - assertEquals(orderItem01.getPayPrice(), 200); | |
| 244 | - assertEquals(orderItem01.getOrderPartPrice(), 40); | |
| 245 | - assertEquals(orderItem01.getOrderDividePrice(), 160); | |
| 246 | - PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1); | |
| 247 | - assertEquals(orderItem02.getSkuId(), 20L); | |
| 248 | - assertEquals(orderItem02.getCount(), 3); | |
| 249 | - assertEquals(orderItem02.getOriginalPrice(), 150); | |
| 250 | - assertEquals(orderItem02.getOriginalUnitPrice(), 50); | |
| 251 | - assertEquals(orderItem02.getDiscountPrice(), 0); | |
| 252 | - assertEquals(orderItem02.getPayPrice(), 150); | |
| 253 | - assertEquals(orderItem02.getOrderPartPrice(), 30); | |
| 254 | - assertEquals(orderItem02.getOrderDividePrice(), 120); | |
| 255 | - PriceCalculateRespDTO.OrderItem orderItem03 = order.getItems().get(2); | |
| 256 | - assertEquals(orderItem03.getSkuId(), 30L); | |
| 257 | - assertEquals(orderItem03.getCount(), 4); | |
| 258 | - assertEquals(orderItem03.getOriginalPrice(), 120); | |
| 259 | - assertEquals(orderItem03.getOriginalUnitPrice(), 30); | |
| 260 | - assertEquals(orderItem03.getDiscountPrice(), 0); | |
| 261 | - assertEquals(orderItem03.getPayPrice(), 120); | |
| 262 | - assertEquals(orderItem03.getOrderPartPrice(), 60); | |
| 263 | - assertEquals(orderItem03.getOrderDividePrice(), 60); | |
| 264 | - // 断言 Promotion 部分(第一个) | |
| 265 | - assertEquals(priceCalculate.getPromotions().size(), 2); | |
| 266 | - PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0); | |
| 267 | - assertEquals(promotion01.getId(), 1000L); | |
| 268 | - assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 269 | - assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 270 | - //assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 271 | - assertEquals(promotion01.getTotalPrice(), 350); | |
| 272 | - assertEquals(promotion01.getDiscountPrice(), 70); | |
| 273 | - assertTrue(promotion01.getMatch()); | |
| 274 | - assertEquals(promotion01.getDescription(), "满减送:省 0.70 元"); | |
| 275 | - assertEquals(promotion01.getItems().size(), 2); | |
| 276 | - PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 277 | - assertEquals(promotionItem011.getSkuId(), 10L); | |
| 278 | - assertEquals(promotionItem011.getOriginalPrice(), 200); | |
| 279 | - assertEquals(promotionItem011.getDiscountPrice(), 40); | |
| 280 | - PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 281 | - assertEquals(promotionItem012.getSkuId(), 20L); | |
| 282 | - assertEquals(promotionItem012.getOriginalPrice(), 150); | |
| 283 | - assertEquals(promotionItem012.getDiscountPrice(), 30); | |
| 284 | - // 断言 Promotion 部分(第二个) | |
| 285 | - PriceCalculateRespDTO.Promotion promotion02 = priceCalculate.getPromotions().get(1); | |
| 286 | - assertEquals(promotion02.getId(), 2000L); | |
| 287 | - assertEquals(promotion02.getName(), "活动 2000 号"); | |
| 288 | - assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 289 | - //assertEquals(promotion02.getLevel(), PromotionLevelEnum.ORDER.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 290 | - assertEquals(promotion02.getTotalPrice(), 120); | |
| 291 | - assertEquals(promotion02.getDiscountPrice(), 60); | |
| 292 | - assertTrue(promotion02.getMatch()); | |
| 293 | - assertEquals(promotion02.getDescription(), "满减送:省 0.60 元"); | |
| 294 | - PriceCalculateRespDTO.PromotionItem promotionItem02 = promotion02.getItems().get(0); | |
| 295 | - assertEquals(promotion02.getItems().size(), 1); | |
| 296 | - assertEquals(promotionItem02.getSkuId(), 30L); | |
| 297 | - assertEquals(promotionItem02.getOriginalPrice(), 120); | |
| 298 | - assertEquals(promotionItem02.getDiscountPrice(), 60); | |
| 299 | - } | |
| 300 | - | |
| 301 | 48 | /** |
| 302 | 49 | * 测试满减送活动,不匹配的情况 |
| 303 | 50 | */ |
| 304 | 51 | @Test |
| 305 | 52 | public void testCalculatePrice_rewardActivityNotMeet() { |
| 306 | - // 准备参数 | |
| 307 | - PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId()) | |
| 308 | - .setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2), | |
| 309 | - new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3))); | |
| 310 | - // mock 方法(商品 SKU 信息) | |
| 311 | - ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L)); | |
| 312 | - ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L)); | |
| 313 | - when(productSkuApi.getSkuList(eq(asSet(10L, 20L)))).thenReturn(asList(productSku01, productSku02)); | |
| 314 | - // mock 方法(限时折扣 DiscountActivity 信息) | |
| 315 | - RewardActivityDO rewardActivity01 = randomPojo(RewardActivityDO.class, o -> o.setId(1000L).setName("活动 1000 号") | |
| 316 | - .setProductSpuIds(asList(10L, 20L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) | |
| 317 | - .setRules(singletonList(new RewardActivityDO.Rule().setLimit(351).setDiscountPrice(70)))); | |
| 318 | - Map<RewardActivityDO, Set<Long>> matchRewardActivities = new LinkedHashMap<>(); | |
| 319 | - matchRewardActivities.put(rewardActivity01, asSet(1L, 2L)); | |
| 320 | - //TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList | |
| 321 | - //when(rewardActivityService.getMatchRewardActivities(eq(asSet(1L, 2L)))).thenReturn(matchRewardActivities); | |
| 322 | 53 | |
| 323 | - // 调用 | |
| 324 | - //PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); TODO 没有这个方法 | |
| 325 | - PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | |
| 326 | - // 断言 Order 部分 | |
| 327 | - PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | |
| 328 | - assertEquals(order.getTotalPrice(), 350); | |
| 329 | - assertEquals(order.getDiscountPrice(), 0); | |
| 330 | - assertEquals(order.getPointPrice(), 0); | |
| 331 | - assertEquals(order.getDeliveryPrice(), 0); | |
| 332 | - assertEquals(order.getPayPrice(), 350); | |
| 333 | - assertNull(order.getCouponId()); | |
| 334 | - // 断言 OrderItem 部分 | |
| 335 | - assertEquals(order.getItems().size(), 2); | |
| 336 | - PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0); | |
| 337 | - assertEquals(orderItem01.getSkuId(), 10L); | |
| 338 | - assertEquals(orderItem01.getCount(), 2); | |
| 339 | - assertEquals(orderItem01.getOriginalPrice(), 200); | |
| 340 | - assertEquals(orderItem01.getOriginalUnitPrice(), 100); | |
| 341 | - assertEquals(orderItem01.getDiscountPrice(), 0); | |
| 342 | - assertEquals(orderItem01.getPayPrice(), 200); | |
| 343 | - assertEquals(orderItem01.getOrderPartPrice(), 0); | |
| 344 | - assertEquals(orderItem01.getOrderDividePrice(), 200); | |
| 345 | - PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1); | |
| 346 | - assertEquals(orderItem02.getSkuId(), 20L); | |
| 347 | - assertEquals(orderItem02.getCount(), 3); | |
| 348 | - assertEquals(orderItem02.getOriginalPrice(), 150); | |
| 349 | - assertEquals(orderItem02.getOriginalUnitPrice(), 50); | |
| 350 | - assertEquals(orderItem02.getDiscountPrice(), 0); | |
| 351 | - assertEquals(orderItem02.getPayPrice(), 150); | |
| 352 | - assertEquals(orderItem02.getOrderPartPrice(), 0); | |
| 353 | - assertEquals(orderItem02.getOrderDividePrice(), 150); | |
| 354 | - // 断言 Promotion 部分 | |
| 355 | - assertEquals(priceCalculate.getPromotions().size(), 1); | |
| 356 | - PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0); | |
| 357 | - assertEquals(promotion01.getId(), 1000L); | |
| 358 | - assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 359 | - assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 360 | - //assertEquals(promotion01.getLevel(), PromotionLevelEnum.ORDER.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 361 | - assertEquals(promotion01.getTotalPrice(), 350); | |
| 362 | - assertEquals(promotion01.getDiscountPrice(), 0); | |
| 363 | - assertFalse(promotion01.getMatch()); | |
| 364 | - assertEquals(promotion01.getDescription(), "TODO"); // TODO 芋艿:后面再想想 | |
| 365 | - assertEquals(promotion01.getItems().size(), 2); | |
| 366 | - PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 367 | - assertEquals(promotionItem011.getSkuId(), 10L); | |
| 368 | - assertEquals(promotionItem011.getOriginalPrice(), 200); | |
| 369 | - assertEquals(promotionItem011.getDiscountPrice(), 0); | |
| 370 | - PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 371 | - assertEquals(promotionItem012.getSkuId(), 20L); | |
| 372 | - assertEquals(promotionItem012.getOriginalPrice(), 150); | |
| 373 | - assertEquals(promotionItem012.getDiscountPrice(), 0); | |
| 374 | - } | |
| 375 | - | |
| 376 | - @Test | |
| 377 | - public void testCalculatePrice_coupon() { | |
| 378 | - // 准备参数 | |
| 379 | - PriceCalculateReqDTO calculateReqDTO = new PriceCalculateReqDTO().setUserId(randomLongId()) | |
| 380 | - .setItems(asList(new PriceCalculateReqDTO.Item().setSkuId(10L).setCount(2), | |
| 381 | - new PriceCalculateReqDTO.Item().setSkuId(20L).setCount(3), | |
| 382 | - new PriceCalculateReqDTO.Item().setSkuId(30L).setCount(4))) | |
| 383 | - .setCouponId(1024L); | |
| 384 | - // mock 方法(商品 SKU 信息) | |
| 385 | - ProductSkuRespDTO productSku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(10L).setPrice(100).setSpuId(1L)); | |
| 386 | - ProductSkuRespDTO productSku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(20L).setPrice(50).setSpuId(2L)); | |
| 387 | - ProductSkuRespDTO productSku03 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(30L).setPrice(30).setSpuId(3L)); | |
| 388 | - when(productSkuApi.getSkuList(eq(asSet(10L, 20L, 30L)))).thenReturn(asList(productSku01, productSku02, productSku03)); | |
| 389 | - // mock 方法(优惠劵 Coupon 信息) | |
| 390 | - CouponDO coupon = randomPojo(CouponDO.class, o -> o.setId(1024L).setName("程序员节") | |
| 391 | - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)) | |
| 392 | - .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) | |
| 393 | - .setDiscountPercent(50).setDiscountLimitPrice(70)); | |
| 394 | - when(couponService.validCoupon(eq(1024L), eq(calculateReqDTO.getUserId()))).thenReturn(coupon); | |
| 395 | - | |
| 396 | - // 调用 | |
| 397 | - //PriceCalculateRespDTO priceCalculate = priceService.calculatePrice(calculateReqDTO); TODO 没有这个方法 | |
| 398 | - PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO(); | |
| 399 | - // 断言 Order 部分 | |
| 400 | - PriceCalculateRespDTO.Order order = priceCalculate.getOrder(); | |
| 401 | - assertEquals(order.getTotalPrice(), 470); | |
| 402 | - assertEquals(order.getDiscountPrice(), 0); | |
| 403 | - assertEquals(order.getPointPrice(), 0); | |
| 404 | - assertEquals(order.getDeliveryPrice(), 0); | |
| 405 | - assertEquals(order.getPayPrice(), 400); | |
| 406 | - assertEquals(order.getCouponId(), 1024L); | |
| 407 | - assertEquals(order.getCouponPrice(), 70); | |
| 408 | - // 断言 OrderItem 部分 | |
| 409 | - assertEquals(order.getItems().size(), 3); | |
| 410 | - PriceCalculateRespDTO.OrderItem orderItem01 = order.getItems().get(0); | |
| 411 | - assertEquals(orderItem01.getSkuId(), 10L); | |
| 412 | - assertEquals(orderItem01.getCount(), 2); | |
| 413 | - assertEquals(orderItem01.getOriginalPrice(), 200); | |
| 414 | - assertEquals(orderItem01.getOriginalUnitPrice(), 100); | |
| 415 | - assertEquals(orderItem01.getDiscountPrice(), 0); | |
| 416 | - assertEquals(orderItem01.getPayPrice(), 200); | |
| 417 | - assertEquals(orderItem01.getOrderPartPrice(), 40); | |
| 418 | - assertEquals(orderItem01.getOrderDividePrice(), 160); | |
| 419 | - PriceCalculateRespDTO.OrderItem orderItem02 = order.getItems().get(1); | |
| 420 | - assertEquals(orderItem02.getSkuId(), 20L); | |
| 421 | - assertEquals(orderItem02.getCount(), 3); | |
| 422 | - assertEquals(orderItem02.getOriginalPrice(), 150); | |
| 423 | - assertEquals(orderItem02.getOriginalUnitPrice(), 50); | |
| 424 | - assertEquals(orderItem02.getDiscountPrice(), 0); | |
| 425 | - assertEquals(orderItem02.getPayPrice(), 150); | |
| 426 | - assertEquals(orderItem02.getOrderPartPrice(), 30); | |
| 427 | - assertEquals(orderItem02.getOrderDividePrice(), 120); | |
| 428 | - PriceCalculateRespDTO.OrderItem orderItem03 = order.getItems().get(2); | |
| 429 | - assertEquals(orderItem03.getSkuId(), 30L); | |
| 430 | - assertEquals(orderItem03.getCount(), 4); | |
| 431 | - assertEquals(orderItem03.getOriginalPrice(), 120); | |
| 432 | - assertEquals(orderItem03.getOriginalUnitPrice(), 30); | |
| 433 | - assertEquals(orderItem03.getDiscountPrice(), 0); | |
| 434 | - assertEquals(orderItem03.getPayPrice(), 120); | |
| 435 | - assertEquals(orderItem03.getOrderPartPrice(), 0); | |
| 436 | - assertEquals(orderItem03.getOrderDividePrice(), 120); | |
| 437 | - // 断言 Promotion 部分 | |
| 438 | - assertEquals(priceCalculate.getPromotions().size(), 1); | |
| 439 | - PriceCalculateRespDTO.Promotion promotion01 = priceCalculate.getPromotions().get(0); | |
| 440 | - assertEquals(promotion01.getId(), 1024L); | |
| 441 | - assertEquals(promotion01.getName(), "程序员节"); | |
| 442 | - assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType()); | |
| 443 | - //assertEquals(promotion01.getLevel(), PromotionLevelEnum.COUPON.getLevel()); TODO PromotionLevelEnum 没有这个枚举类 | |
| 444 | - assertEquals(promotion01.getTotalPrice(), 350); | |
| 445 | - assertEquals(promotion01.getDiscountPrice(), 70); | |
| 446 | - assertTrue(promotion01.getMatch()); | |
| 447 | - assertEquals(promotion01.getDescription(), "优惠劵:省 0.70 元"); | |
| 448 | - assertEquals(promotion01.getItems().size(), 2); | |
| 449 | - PriceCalculateRespDTO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 450 | - assertEquals(promotionItem011.getSkuId(), 10L); | |
| 451 | - assertEquals(promotionItem011.getOriginalPrice(), 200); | |
| 452 | - assertEquals(promotionItem011.getDiscountPrice(), 40); | |
| 453 | - PriceCalculateRespDTO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 454 | - assertEquals(promotionItem012.getSkuId(), 20L); | |
| 455 | - assertEquals(promotionItem012.getOriginalPrice(), 150); | |
| 456 | - assertEquals(promotionItem012.getDiscountPrice(), 30); | |
| 457 | 54 | } |
| 458 | 55 | |
| 459 | 56 | @Test | ... | ... |
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.enums.delivery; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; |
| 4 | 5 | import lombok.AllArgsConstructor; |
| 5 | 6 | import lombok.Getter; |
| ... | ... | @@ -35,4 +36,8 @@ public enum DeliveryExpressChargeModeEnum implements IntArrayValuable { |
| 35 | 36 | return ARRAYS; |
| 36 | 37 | } |
| 37 | 38 | |
| 39 | + public static DeliveryExpressChargeModeEnum valueOf(Integer value) { | |
| 40 | + return ArrayUtil.firstMatch(chargeMode -> chargeMode.getType().equals(value), DeliveryExpressChargeModeEnum.values()); | |
| 41 | + } | |
| 42 | + | |
| 38 | 43 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryExpressTemplateController.java
| ... | ... | @@ -58,7 +58,7 @@ public class DeliveryExpressTemplateController { |
| 58 | 58 | @Operation(summary = "获得快递运费模板") |
| 59 | 59 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 60 | 60 | @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") |
| 61 | - public CommonResult<DeliveryExpressTemplateRespVO> getDeliveryExpressTemplate(@RequestParam("id") Long id) { | |
| 61 | + public CommonResult<DeliveryExpressTemplateDetailRespVO> getDeliveryExpressTemplate(@RequestParam("id") Long id) { | |
| 62 | 62 | return success(deliveryExpressTemplateService.getDeliveryExpressTemplate(id)); |
| 63 | 63 | } |
| 64 | 64 | |
| ... | ... | @@ -66,7 +66,7 @@ public class DeliveryExpressTemplateController { |
| 66 | 66 | @Operation(summary = "获得快递运费模板列表") |
| 67 | 67 | @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048") |
| 68 | 68 | @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") |
| 69 | - public CommonResult<List<DeliveryExpressTemplateSimpleRespVO>> getDeliveryExpressTemplateList(@RequestParam("ids") Collection<Long> ids) { | |
| 69 | + public CommonResult<List<DeliveryExpressTemplateRespVO>> getDeliveryExpressTemplateList(@RequestParam("ids") Collection<Long> ids) { | |
| 70 | 70 | List<DeliveryExpressTemplateDO> list = deliveryExpressTemplateService.getDeliveryExpressTemplateList(ids); |
| 71 | 71 | return success(DeliveryExpressTemplateConvert.INSTANCE.convertList(list)); |
| 72 | 72 | } |
| ... | ... | @@ -83,7 +83,7 @@ public class DeliveryExpressTemplateController { |
| 83 | 83 | @GetMapping("/page") |
| 84 | 84 | @Operation(summary = "获得快递运费模板分页") |
| 85 | 85 | @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") |
| 86 | - public CommonResult<PageResult<DeliveryExpressTemplateSimpleRespVO>> getDeliveryExpressTemplatePage(@Valid DeliveryExpressTemplatePageReqVO pageVO) { | |
| 86 | + public CommonResult<PageResult<DeliveryExpressTemplateRespVO>> getDeliveryExpressTemplatePage(@Valid DeliveryExpressTemplatePageReqVO pageVO) { | |
| 87 | 87 | PageResult<DeliveryExpressTemplateDO> pageResult = deliveryExpressTemplateService.getDeliveryExpressTemplatePage(pageVO); |
| 88 | 88 | return success(DeliveryExpressTemplateConvert.INSTANCE.convertPage(pageResult)); |
| 89 | 89 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateDetailRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.EqualsAndHashCode; | |
| 6 | +import lombok.ToString; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +@Schema(description = "管理后台 - 快递运费模板的详细 Response VO") | |
| 11 | +@Data | |
| 12 | +@EqualsAndHashCode(callSuper = true) | |
| 13 | +@ToString(callSuper = true) | |
| 14 | +public class DeliveryExpressTemplateDetailRespVO extends DeliveryExpressTemplateBaseVO { | |
| 15 | + | |
| 16 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "371") | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + @Schema(description = "运费模板运费设置", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 20 | + private List<ExpressTemplateChargeBaseVO> templateCharge; | |
| 21 | + | |
| 22 | + @Schema(description = "运费模板包邮区域", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 23 | + private List<ExpressTemplateFreeBaseVO> templateFree; | |
| 24 | + | |
| 25 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate; |
| 2 | 2 | |
| 3 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.EqualsAndHashCode; | |
| 6 | -import lombok.ToString; | |
| 4 | +import lombok.*; | |
| 5 | +import java.time.LocalDateTime; | |
| 7 | 6 | |
| 8 | -import java.util.List; | |
| 9 | - | |
| 10 | -/** | |
| 11 | - * @author jason | |
| 12 | - */ | |
| 13 | 7 | @Schema(description = "管理后台 - 快递运费模板 Response VO") |
| 14 | 8 | @Data |
| 15 | 9 | @EqualsAndHashCode(callSuper = true) |
| 16 | 10 | @ToString(callSuper = true) |
| 17 | 11 | public class DeliveryExpressTemplateRespVO extends DeliveryExpressTemplateBaseVO { |
| 18 | 12 | |
| 19 | - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "371") | |
| 13 | + @Schema(description = "编号,自增", required = true, example = "371") | |
| 20 | 14 | private Long id; |
| 21 | 15 | |
| 22 | - @Schema(description = "运费模板运费设置", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 23 | - private List<ExpressTemplateChargeBaseVO> templateCharge; | |
| 24 | - | |
| 25 | - @Schema(description = "运费模板包邮区域", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 26 | - private List<ExpressTemplateFreeBaseVO> templateFree; | |
| 16 | + @Schema(description = "创建时间", required = true) | |
| 17 | + private LocalDateTime createTime; | |
| 27 | 18 | |
| 28 | 19 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java deleted
| 1 | -package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate; | |
| 2 | - | |
| 3 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | -import lombok.*; | |
| 5 | -import java.time.LocalDateTime; | |
| 6 | - | |
| 7 | -// TODO @jason:simplae 是不是不用继承 DeliveryExpressTemplateBaseVO,直接 id name 属性就够了。 | |
| 8 | -// @芋艿 这里给列表显示用的。 需要显示计费方式和排序, 所以继承 DeliveryExpressTemplateBaseVO。 这是去掉了包邮区域和 区域运费列表 | |
| 9 | -@Schema(description = "管理后台 - 快递运费模板 精简 Response VO") | |
| 10 | -@Data | |
| 11 | -@EqualsAndHashCode(callSuper = true) | |
| 12 | -@ToString(callSuper = true) | |
| 13 | -public class DeliveryExpressTemplateSimpleRespVO extends DeliveryExpressTemplateBaseVO { | |
| 14 | - | |
| 15 | - @Schema(description = "编号,自增", required = true, example = "371") | |
| 16 | - private Long id; | |
| 17 | - | |
| 18 | - @Schema(description = "创建时间", required = true) | |
| 19 | - private LocalDateTime createTime; | |
| 20 | - | |
| 21 | -} |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http
| 1 | -### /trade-order/settlement 获得订单结算信息 | |
| 2 | -GET {{appApi}}/trade/order/settlement?cartIds=1 | |
| 1 | +### /trade-order/settlement 获得订单结算信息(基于商品) | |
| 2 | +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 | |
| 3 | 3 | Authorization: Bearer {{appToken}} |
| 4 | 4 | tenant-id: {{appTenentId}} |
| 5 | 5 | |
| 6 | -### /trade-order/confirm-create-order-info-from-cart 基于购物车,确认创建订单 | |
| 7 | -GET {{shop-api-base-url}}/trade-order/confirm-create-order-info-from-cart | |
| 8 | -Content-Type: application/x-www-form-urlencoded | |
| 9 | -Authorization: Bearer {{user-access-token}} | |
| 6 | +### /trade-order/settlement 获得订单结算信息(基于购物车) | |
| 7 | +GET {{appApi}}/trade/order/settlement?type=0&items[0].cartId=50&couponId=1 | |
| 8 | +Authorization: Bearer {{appToken}} | |
| 9 | +tenant-id: {{appTenentId}} | |
| 10 | 10 | |
| 11 | -### /trade-order/create 基于商品,创建订单 | |
| 11 | +### /trade-order/create 创建订单(基于商品) | |
| 12 | 12 | POST {{appApi}}/trade/order/create |
| 13 | 13 | Content-Type: application/json |
| 14 | 14 | Authorization: Bearer {{appToken}} |
| 15 | 15 | tenant-id: {{appTenentId}} |
| 16 | 16 | |
| 17 | 17 | { |
| 18 | + "type": 0, | |
| 18 | 19 | "addressId": 21, |
| 19 | - "remark": "我是备注", | |
| 20 | - "fromCart": false, | |
| 21 | 20 | "items": [ |
| 22 | 21 | { |
| 23 | - "skuId": 29, | |
| 24 | - "count": 1 | |
| 22 | + "skuId": 1, | |
| 23 | + "count": 2 | |
| 25 | 24 | } |
| 26 | - ] | |
| 25 | + ], | |
| 26 | + "remark": "我是备注" | |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | 29 | ### 获得订单交易的分页 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
| ... | ... | @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; |
| 6 | 6 | import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; |
| 7 | 7 | import cn.iocoder.yudao.module.product.api.property.ProductPropertyValueApi; |
| 8 | 8 | import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; |
| 9 | -import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; | |
| 10 | 9 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; |
| 11 | 10 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; |
| 12 | 11 | import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; |
| ... | ... | @@ -23,15 +22,14 @@ import org.springframework.validation.annotation.Validated; |
| 23 | 22 | import org.springframework.web.bind.annotation.*; |
| 24 | 23 | |
| 25 | 24 | import javax.annotation.Resource; |
| 26 | -import javax.servlet.http.HttpServletRequest; | |
| 27 | 25 | import javax.validation.Valid; |
| 28 | -import java.util.ArrayList; | |
| 29 | 26 | import java.util.HashMap; |
| 30 | 27 | import java.util.List; |
| 31 | 28 | import java.util.Map; |
| 32 | 29 | |
| 33 | 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 34 | 31 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 32 | +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; | |
| 35 | 33 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 36 | 34 | |
| 37 | 35 | @Tag(name = "用户 App - 交易订单") |
| ... | ... | @@ -53,92 +51,16 @@ public class AppTradeOrderController { |
| 53 | 51 | @GetMapping("/settlement") |
| 54 | 52 | @Operation(summary = "获得订单结算信息") |
| 55 | 53 | @PreAuthenticated |
| 56 | - public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder( | |
| 57 | - @Valid AppTradeOrderSettlementReqVO settlementReqVO) { | |
| 58 | - if (true) { | |
| 59 | - return success(tradeOrderService.settlementOrder(getLoginUserId(), settlementReqVO)); | |
| 60 | - } | |
| 61 | -// return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId)); | |
| 62 | - AppTradeOrderSettlementRespVO settlement = new AppTradeOrderSettlementRespVO(); | |
| 63 | - | |
| 64 | - AppTradeOrderSettlementRespVO.Price price = new AppTradeOrderSettlementRespVO.Price(); | |
| 65 | - price.setTotalPrice(1000); | |
| 66 | - price.setDeliveryPrice(200); | |
| 67 | - price.setCouponPrice(100); | |
| 68 | - price.setPointPrice(50); | |
| 69 | - price.setPayPrice(950); | |
| 70 | - | |
| 71 | - List<AppTradeOrderSettlementRespVO.Item> skus = new ArrayList<>(); | |
| 72 | - | |
| 73 | - AppTradeOrderSettlementRespVO.Item item1 = new AppTradeOrderSettlementRespVO.Item(); | |
| 74 | - item1.setCartId(1L); | |
| 75 | - item1.setSpuId(2048L); | |
| 76 | - item1.setSpuName("Apple iPhone 12"); | |
| 77 | - item1.setSkuId(1024); | |
| 78 | - item1.setPrice(500); | |
| 79 | - item1.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg"); | |
| 80 | - item1.setCount(2); | |
| 81 | - List<AppProductPropertyValueDetailRespVO> properties1 = new ArrayList<>(); | |
| 82 | - AppProductPropertyValueDetailRespVO property1 = new AppProductPropertyValueDetailRespVO(); | |
| 83 | - property1.setPropertyId(1L); | |
| 84 | - property1.setPropertyName("尺寸"); | |
| 85 | - property1.setValueId(2L); | |
| 86 | - property1.setValueName("大"); | |
| 87 | - properties1.add(property1); | |
| 88 | - item1.setProperties(properties1); | |
| 89 | - | |
| 90 | - AppTradeOrderSettlementRespVO.Item item2 = new AppTradeOrderSettlementRespVO.Item(); | |
| 91 | - item2.setCartId(2L); | |
| 92 | - item2.setSpuId(3072L); | |
| 93 | - item2.setSpuName("Samsung Galaxy S21"); | |
| 94 | - item2.setSkuId(2048); | |
| 95 | - item2.setPrice(800); | |
| 96 | - item2.setPicUrl("https://pro.crmeb.net/uploads/attach/2022/10/12/0c56f9abb80d2775fc1e80dbe4f8826a.jpg"); | |
| 97 | - item2.setCount(1); | |
| 98 | - List<AppProductPropertyValueDetailRespVO> properties2 = new ArrayList<>(); | |
| 99 | - AppProductPropertyValueDetailRespVO property2 = new AppProductPropertyValueDetailRespVO(); | |
| 100 | - property2.setPropertyId(10L); | |
| 101 | - property2.setPropertyName("颜色"); | |
| 102 | - property2.setValueId(20L); | |
| 103 | - property2.setValueName("白色"); | |
| 104 | - properties2.add(property2); | |
| 105 | - item2.setProperties(properties2); | |
| 106 | - | |
| 107 | - skus.add(item1); | |
| 108 | - skus.add(item2); | |
| 109 | - | |
| 110 | - settlement.setItems(skus); | |
| 111 | - settlement.setPrice(price); | |
| 112 | - | |
| 113 | - AppTradeOrderSettlementRespVO.Address address = new AppTradeOrderSettlementRespVO.Address(); | |
| 114 | - address.setId(1L); | |
| 115 | - address.setName("John"); | |
| 116 | - address.setMobile("18888888888"); | |
| 117 | - address.setProvinceId(1L); | |
| 118 | - address.setProvinceName("Beijing"); | |
| 119 | - address.setCityId(1L); | |
| 120 | - address.setCityName("Beijing"); | |
| 121 | - address.setDistrictId(1L); | |
| 122 | - address.setDistrictName("Chaoyang Distripct"); | |
| 123 | - address.setDetailAddress("No. 10, Xinzhong Street, Chaoyang District"); | |
| 124 | - address.setDefaulted(true); | |
| 125 | - settlement.setAddress(address); | |
| 126 | - | |
| 127 | - return success(settlement); | |
| 54 | + public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) { | |
| 55 | + return success(tradeOrderService.settlementOrder(getLoginUserId(), settlementReqVO)); | |
| 128 | 56 | } |
| 129 | 57 | |
| 130 | 58 | @PostMapping("/create") |
| 131 | 59 | @Operation(summary = "创建订单") |
| 132 | 60 | @PreAuthenticated |
| 133 | - public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO, | |
| 134 | - HttpServletRequest servletRequest) { | |
| 135 | - return success(1L); | |
| 136 | -// // 获取登录用户、用户 IP 地址 | |
| 137 | -// Long loginUserId = getLoginUserId(); | |
| 138 | -// String clientIp = ServletUtils.getClientIP(servletRequest); | |
| 139 | -// // 创建交易订单,预支付记录 | |
| 140 | -// Long orderId = tradeOrderService.createOrder(loginUserId, clientIp, createReqVO); | |
| 141 | -// return success(orderId); | |
| 61 | + public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO) { | |
| 62 | + Long orderId = tradeOrderService.createOrder(getLoginUserId(), getClientIP(), createReqVO); | |
| 63 | + return success(orderId); | |
| 142 | 64 | } |
| 143 | 65 | |
| 144 | 66 | @PostMapping("/update-paid") | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
| ... | ... | @@ -3,26 +3,9 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo; |
| 3 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 4 | import lombok.Data; |
| 5 | 5 | |
| 6 | -import javax.validation.constraints.NotEmpty; | |
| 7 | -import javax.validation.constraints.NotNull; | |
| 8 | -import java.util.List; | |
| 9 | - | |
| 10 | 6 | @Schema(description = "用户 App - 交易订单创建 Request VO") |
| 11 | 7 | @Data |
| 12 | -public class AppTradeOrderCreateReqVO { | |
| 13 | - | |
| 14 | - @Schema(description = "收件地址编号", required = true, example = "1") | |
| 15 | - @NotNull(message = "收件地址不能为空") | |
| 16 | - private Long addressId; | |
| 17 | - | |
| 18 | - @Schema(description = "优惠劵编号", example = "1024") | |
| 19 | - private Long couponId; | |
| 20 | - | |
| 21 | - @Schema(description = "购物车项的编号数组", required = true, example = "true") | |
| 22 | - @NotEmpty(message = "购物车项不能为空") | |
| 23 | - private List<Long> cartIds; | |
| 24 | - | |
| 25 | - // ========== 非 AppTradeOrderSettlementReqVO 字段 ========== | |
| 8 | +public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { | |
| 26 | 9 | |
| 27 | 10 | @Schema(description = "备注", example = "这个是我的订单哟") |
| 28 | 11 | private String remark; | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.controller.app.order.vo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 4 | +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; | |
| 5 | +import com.fasterxml.jackson.annotation.JsonIgnore; | |
| 3 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 7 | import lombok.Data; |
| 5 | 8 | |
| 6 | -import javax.validation.constraints.NotEmpty; | |
| 9 | +import javax.validation.Valid; | |
| 10 | +import javax.validation.constraints.AssertTrue; | |
| 11 | +import javax.validation.constraints.Min; | |
| 12 | +import javax.validation.constraints.NotNull; | |
| 7 | 13 | import java.util.List; |
| 8 | 14 | |
| 9 | 15 | @Schema(description = "用户 App - 交易订单结算 Request VO") |
| 10 | 16 | @Data |
| 11 | 17 | public class AppTradeOrderSettlementReqVO { |
| 12 | 18 | |
| 19 | + @NotNull(message = "交易类型不能为空") | |
| 20 | + @InEnum(value = TradeOrderTypeEnum.class, message = "交易类型必须是 {value}") | |
| 21 | + private Integer type; | |
| 22 | + | |
| 23 | + @Schema(description = "商品项数组", required = true) | |
| 24 | + @NotNull(message = "商品不能为空") | |
| 25 | + private List<Item> items; | |
| 26 | + | |
| 13 | 27 | @Schema(description = "收件地址编号", example = "1") |
| 14 | 28 | private Long addressId; |
| 15 | 29 | |
| 16 | 30 | @Schema(description = "优惠劵编号", example = "1024") |
| 17 | 31 | private Long couponId; |
| 18 | 32 | |
| 19 | - @Schema(description = "购物车项的编号数组", required = true, example = "true") | |
| 20 | - @NotEmpty(message = "购物车项不能为空") | |
| 21 | - private List<Long> cartIds; | |
| 33 | + @Data | |
| 34 | + @Schema(description = "用户 App - 商品项") | |
| 35 | + @Valid | |
| 36 | + public static class Item { | |
| 37 | + | |
| 38 | + @Schema(description = "商品 SKU 编号", example = "2048") | |
| 39 | + private Long skuId; | |
| 40 | + @Schema(description = "购买数量", example = "1") | |
| 41 | + @Min(value = 1, message = "购买数量最小值为 {value}") | |
| 42 | + private Integer count; | |
| 43 | + | |
| 44 | + @Schema(description = "购物车项的编号", example = "1024") | |
| 45 | + private Long cartId; | |
| 46 | + | |
| 47 | + @AssertTrue(message = "商品不正确") | |
| 48 | + @JsonIgnore | |
| 49 | + public boolean isValid() { | |
| 50 | + // 组合一:skuId + count 使用商品 SKU | |
| 51 | + if (skuId != null && count != null) { | |
| 52 | + return true; | |
| 53 | + } | |
| 54 | + // 组合二:cartId 使用购物车项 | |
| 55 | + return cartId != null; | |
| 56 | + } | |
| 57 | + | |
| 58 | + } | |
| 22 | 59 | |
| 23 | 60 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
| ... | ... | @@ -91,17 +91,17 @@ public class AppTradeOrderSettlementRespVO { |
| 91 | 91 | private String mobile; |
| 92 | 92 | |
| 93 | 93 | @Schema(description = "省份编号", required = true, example = "1") |
| 94 | - private Long provinceId; | |
| 94 | + private Integer provinceId; | |
| 95 | 95 | @Schema(description = "省份名字", required = true, example = "北京") |
| 96 | 96 | private String provinceName; |
| 97 | 97 | |
| 98 | 98 | @Schema(description = "城市编号", required = true, example = "1") |
| 99 | - private Long cityId; | |
| 99 | + private Integer cityId; | |
| 100 | 100 | @Schema(description = "城市名字", required = true, example = "北京") |
| 101 | 101 | private String cityName; |
| 102 | 102 | |
| 103 | 103 | @Schema(description = "地区编号", required = true, example = "1") |
| 104 | - private Long districtId; | |
| 104 | + private Integer districtId; | |
| 105 | 105 | @Schema(description = "地区名字", required = true, example = "朝阳区") |
| 106 | 106 | private String districtName; |
| 107 | 107 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java
| ... | ... | @@ -22,18 +22,18 @@ public interface DeliveryExpressTemplateConvert { |
| 22 | 22 | |
| 23 | 23 | DeliveryExpressTemplateDO convert(DeliveryExpressTemplateUpdateReqVO bean); |
| 24 | 24 | |
| 25 | - DeliveryExpressTemplateSimpleRespVO convert(DeliveryExpressTemplateDO bean); | |
| 25 | + DeliveryExpressTemplateRespVO convert(DeliveryExpressTemplateDO bean); | |
| 26 | 26 | |
| 27 | - DeliveryExpressTemplateRespVO convert2(DeliveryExpressTemplateDO bean); | |
| 27 | + DeliveryExpressTemplateDetailRespVO convert2(DeliveryExpressTemplateDO bean); | |
| 28 | 28 | |
| 29 | - List<DeliveryExpressTemplateSimpleRespVO> convertList(List<DeliveryExpressTemplateDO> list); | |
| 29 | + List<DeliveryExpressTemplateRespVO> convertList(List<DeliveryExpressTemplateDO> list); | |
| 30 | 30 | |
| 31 | - PageResult<DeliveryExpressTemplateSimpleRespVO> convertPage(PageResult<DeliveryExpressTemplateDO> page); | |
| 31 | + PageResult<DeliveryExpressTemplateRespVO> convertPage(PageResult<DeliveryExpressTemplateDO> page); | |
| 32 | 32 | |
| 33 | - default DeliveryExpressTemplateRespVO convert(DeliveryExpressTemplateDO bean, | |
| 34 | - List<DeliveryExpressTemplateChargeDO> chargeList, | |
| 35 | - List<DeliveryExpressTemplateFreeDO> freeList){ | |
| 36 | - DeliveryExpressTemplateRespVO respVO = convert2(bean); | |
| 33 | + default DeliveryExpressTemplateDetailRespVO convert(DeliveryExpressTemplateDO bean, | |
| 34 | + List<DeliveryExpressTemplateChargeDO> chargeList, | |
| 35 | + List<DeliveryExpressTemplateFreeDO> freeList){ | |
| 36 | + DeliveryExpressTemplateDetailRespVO respVO = convert2(bean); | |
| 37 | 37 | respVO.setTemplateCharge(convertTemplateChargeList(chargeList)); |
| 38 | 38 | respVO.setTemplateFree(convertTemplateFreeList(freeList)); |
| 39 | 39 | return respVO; | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
| ... | ... | @@ -3,29 +3,28 @@ package cn.iocoder.yudao.module.trade.convert.order; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 6 | +import cn.iocoder.yudao.framework.ip.core.Area; | |
| 6 | 7 | import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; |
| 7 | 8 | import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; |
| 8 | 9 | import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; |
| 9 | 10 | import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; |
| 10 | 11 | import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; |
| 11 | -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 12 | 12 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; |
| 13 | -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 14 | 13 | import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO; |
| 15 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; | |
| 16 | 14 | import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUserRespVO; |
| 17 | 15 | import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO; |
| 18 | 16 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDetailRespVO; |
| 19 | 17 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageItemRespVO; |
| 20 | 18 | import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; |
| 21 | -import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; | |
| 22 | -import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderDetailRespVO; | |
| 23 | -import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageItemRespVO; | |
| 19 | +import cn.iocoder.yudao.module.trade.controller.app.order.vo.*; | |
| 24 | 20 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; |
| 21 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 25 | 22 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 26 | 23 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 27 | 24 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; |
| 28 | 25 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; |
| 26 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 27 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 29 | 28 | import org.mapstruct.Mapper; |
| 30 | 29 | import org.mapstruct.Mapping; |
| 31 | 30 | import org.mapstruct.Mappings; |
| ... | ... | @@ -48,32 +47,32 @@ public interface TradeOrderConvert { |
| 48 | 47 | @Mapping(source = "createReqVO.couponId", target = "couponId"), |
| 49 | 48 | @Mapping(target = "remark", ignore = true), |
| 50 | 49 | @Mapping(source = "createReqVO.remark", target = "userRemark"), |
| 50 | + @Mapping(source = "createReqVO.type", target = "type"), | |
| 51 | + @Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"), | |
| 52 | + @Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"), | |
| 53 | + @Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"), | |
| 54 | + @Mapping(source = "calculateRespBO.price.couponPrice", target = "couponPrice"), | |
| 55 | + @Mapping(source = "calculateRespBO.price.pointPrice", target = "pointPrice"), | |
| 56 | + @Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice"), | |
| 51 | 57 | @Mapping(source = "address.name", target = "receiverName"), |
| 52 | 58 | @Mapping(source = "address.mobile", target = "receiverMobile"), |
| 53 | 59 | @Mapping(source = "address.areaId", target = "receiverAreaId"), |
| 54 | 60 | @Mapping(source = "address.detailAddress", target = "receiverDetailAddress"), |
| 55 | 61 | }) |
| 56 | 62 | TradeOrderDO convert(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO, |
| 57 | - PriceCalculateRespDTO.Order order, AddressRespDTO address); | |
| 63 | + TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address); | |
| 58 | 64 | |
| 59 | - @Mappings({ | |
| 60 | - @Mapping(target = "id", ignore = true), | |
| 61 | - @Mapping(source = "sku.spuId", target = "spuId"), | |
| 62 | - }) | |
| 63 | - TradeOrderItemDO convert(PriceCalculateRespDTO.OrderItem orderItem, ProductSkuRespDTO sku); | |
| 64 | - | |
| 65 | - default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, | |
| 66 | - List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) { | |
| 67 | - Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId); | |
| 68 | - return CollectionUtils.convertList(orderItems, orderItem -> { | |
| 69 | - TradeOrderItemDO tradeOrderItemDO = convert(orderItem, skuMap.get(orderItem.getSkuId())); | |
| 70 | - tradeOrderItemDO.setOrderId(tradeOrderDO.getId()); | |
| 71 | - tradeOrderItemDO.setUserId(tradeOrderDO.getUserId()); | |
| 72 | - tradeOrderItemDO.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); // 退款信息 | |
| 73 | -// tradeOrderItemDO.setCommented(false); | |
| 74 | - return tradeOrderItemDO; | |
| 65 | + default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) { | |
| 66 | + return CollectionUtils.convertList(calculateRespBO.getItems(), item -> { | |
| 67 | + TradeOrderItemDO orderItem = convert(item); | |
| 68 | + orderItem.setOrderId(tradeOrderDO.getId()); | |
| 69 | + orderItem.setUserId(tradeOrderDO.getUserId()); | |
| 70 | + orderItem.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); | |
| 71 | + orderItem.setCommentStatus(false); | |
| 72 | + return orderItem; | |
| 75 | 73 | }); |
| 76 | 74 | } |
| 75 | + TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item); | |
| 77 | 76 | |
| 78 | 77 | @Mapping(source = "userId" , target = "userId") |
| 79 | 78 | PriceCalculateReqDTO convert(AppTradeOrderCreateReqVO createReqVO, Long userId); |
| ... | ... | @@ -85,19 +84,20 @@ public interface TradeOrderConvert { |
| 85 | 84 | ProductSkuUpdateStockReqDTO.Item convert(TradeOrderItemDO bean); |
| 86 | 85 | List<ProductSkuUpdateStockReqDTO.Item> convertList(List<TradeOrderItemDO> list); |
| 87 | 86 | |
| 88 | - default PayOrderCreateReqDTO convert(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs, | |
| 89 | - List<ProductSpuRespDTO> spus, TradeOrderProperties tradeOrderProperties) { | |
| 87 | + default PayOrderCreateReqDTO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems, | |
| 88 | + TradePriceCalculateRespBO calculateRespBO, TradeOrderProperties orderProperties) { | |
| 90 | 89 | PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO() |
| 91 | - .setAppId(tradeOrderProperties.getAppId()).setUserIp(tradeOrderDO.getUserIp()); | |
| 90 | + .setAppId(orderProperties.getAppId()).setUserIp(order.getUserIp()); | |
| 92 | 91 | // 商户相关字段 |
| 93 | - createReqDTO.setMerchantOrderId(String.valueOf(tradeOrderDO.getId())); | |
| 94 | - String subject = spus.get(0).getName(); | |
| 95 | - if (spus.size() > 1) { | |
| 92 | + createReqDTO.setMerchantOrderId(String.valueOf(order.getId())); | |
| 93 | + String subject = calculateRespBO.getItems().get(0).getSpuName(); | |
| 94 | + if (calculateRespBO.getItems().size() > 1) { | |
| 96 | 95 | subject += " 等多件"; |
| 97 | 96 | } |
| 98 | 97 | createReqDTO.setSubject(subject); |
| 98 | + createReqDTO.setBody(subject); // TODO 芋艿:临时写死 | |
| 99 | 99 | // 订单相关字段 |
| 100 | - createReqDTO.setAmount(tradeOrderDO.getPayPrice()).setExpireTime(addTime(tradeOrderProperties.getExpireTime())); | |
| 100 | + createReqDTO.setAmount(order.getPayPrice()).setExpireTime(addTime(orderProperties.getExpireTime())); | |
| 101 | 101 | return createReqDTO; |
| 102 | 102 | } |
| 103 | 103 | |
| ... | ... | @@ -111,6 +111,7 @@ public interface TradeOrderConvert { |
| 111 | 111 | .collect(Collectors.toSet()); |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | + // TODO 芋艿:可简化 | |
| 114 | 115 | default PageResult<TradeOrderPageItemRespVO> convertPage(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems, |
| 115 | 116 | List<ProductPropertyValueDetailRespDTO> propertyValueDetails) { |
| 116 | 117 | Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId); |
| ... | ... | @@ -147,6 +148,7 @@ public interface TradeOrderConvert { |
| 147 | 148 | TradeOrderPageItemRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> items); |
| 148 | 149 | ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean); |
| 149 | 150 | |
| 151 | + // TODO 芋艿:可简化 | |
| 150 | 152 | default TradeOrderDetailRespVO convert(TradeOrderDO order, List<TradeOrderItemDO> orderItems, |
| 151 | 153 | List<ProductPropertyValueDetailRespDTO> propertyValueDetails, MemberUserRespDTO user) { |
| 152 | 154 | TradeOrderDetailRespVO orderVO = convert2(order, orderItems); |
| ... | ... | @@ -177,6 +179,7 @@ public interface TradeOrderConvert { |
| 177 | 179 | TradeOrderDetailRespVO convert2(TradeOrderDO order, List<TradeOrderItemDO> items); |
| 178 | 180 | MemberUserRespVO convert(MemberUserRespDTO bean); |
| 179 | 181 | |
| 182 | + // TODO 芋艿:可简化 | |
| 180 | 183 | default PageResult<AppTradeOrderPageItemRespVO> convertPage02(PageResult<TradeOrderDO> pageResult, List<TradeOrderItemDO> orderItems, |
| 181 | 184 | List<ProductPropertyValueDetailRespDTO> propertyValueDetails) { |
| 182 | 185 | Map<Long, List<TradeOrderItemDO>> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId); |
| ... | ... | @@ -241,4 +244,45 @@ public interface TradeOrderConvert { |
| 241 | 244 | |
| 242 | 245 | AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean); |
| 243 | 246 | |
| 247 | + default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, | |
| 248 | + List<TradeCartDO> cartList) { | |
| 249 | + TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO(); | |
| 250 | + reqBO.setUserId(userId).setType(settlementReqVO.getType()) | |
| 251 | + .setCouponId(settlementReqVO.getCouponId()).setAddressId(settlementReqVO.getAddressId()) | |
| 252 | + .setItems(new ArrayList<>(settlementReqVO.getItems().size())); | |
| 253 | + // 商品项的构建 | |
| 254 | + Map<Long, TradeCartDO> cartMap = convertMap(cartList, TradeCartDO::getId); | |
| 255 | + for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) { | |
| 256 | + // 情况一:skuId + count | |
| 257 | + if (item.getSkuId() != null) { | |
| 258 | + reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(item.getSkuId()).setCount(item.getCount()) | |
| 259 | + .setSelected(true)); // true 的原因,下单一定选中 | |
| 260 | + continue; | |
| 261 | + } | |
| 262 | + // 情况二:cartId | |
| 263 | + TradeCartDO cart = cartMap.get(item.getCartId()); | |
| 264 | + if (cart == null) { | |
| 265 | + continue; | |
| 266 | + } | |
| 267 | + reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(cart.getSkuId()).setCount(cart.getCount()) | |
| 268 | + .setCartId(item.getCartId()).setSelected(true)); // true 的原因,下单一定选中 | |
| 269 | + } | |
| 270 | + return reqBO; | |
| 271 | + } | |
| 272 | + | |
| 273 | + default AppTradeOrderSettlementRespVO convert(TradePriceCalculateRespBO calculate, AddressRespDTO address) { | |
| 274 | + AppTradeOrderSettlementRespVO respVO = convert0(calculate, address); | |
| 275 | + if (address != null) { | |
| 276 | + Area area = AreaUtils.getArea(address.getAreaId()); | |
| 277 | + respVO.getAddress().setDistrictId(area.getId()); | |
| 278 | + respVO.getAddress().setDistrictName(area.getName()); | |
| 279 | + respVO.getAddress().setCityId(area.getParent().getId()); | |
| 280 | + respVO.getAddress().setCityName(area.getParent().getName()); | |
| 281 | + respVO.getAddress().setProvinceId(area.getParent().getParent().getId()); | |
| 282 | + respVO.getAddress().setProvinceName(area.getParent().getParent().getName()); | |
| 283 | + } | |
| 284 | + return respVO; | |
| 285 | + } | |
| 286 | + AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, AddressRespDTO address); | |
| 287 | + | |
| 244 | 288 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java
| ... | ... | @@ -167,11 +167,24 @@ public class TradeOrderItemDO extends BaseDO { |
| 167 | 167 | */ |
| 168 | 168 | private Long propertyId; |
| 169 | 169 | /** |
| 170 | + * 属性名字 | |
| 171 | + * | |
| 172 | + * 关联 ProductPropertyDO 的 name 字段 | |
| 173 | + */ | |
| 174 | + private String propertyName; | |
| 175 | + | |
| 176 | + /** | |
| 170 | 177 | * 属性值编号 |
| 171 | 178 | * |
| 172 | 179 | * 关联 ProductPropertyValueDO 的 id 编号 |
| 173 | 180 | */ |
| 174 | 181 | private Long valueId; |
| 182 | + /** | |
| 183 | + * 属性值名字 | |
| 184 | + * | |
| 185 | + * 关联 ProductPropertyValueDO 的 name 字段 | |
| 186 | + */ | |
| 187 | + private String valueName; | |
| 175 | 188 | |
| 176 | 189 | } |
| 177 | 190 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java
| ... | ... | @@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper; |
| 12 | 12 | import java.util.Collection; |
| 13 | 13 | import java.util.List; |
| 14 | 14 | import java.util.Map; |
| 15 | +import java.util.Set; | |
| 15 | 16 | |
| 16 | 17 | @Mapper |
| 17 | 18 | public interface TradeCartMapper extends BaseMapperX<TradeCartDO> { |
| ... | ... | @@ -70,4 +71,10 @@ public interface TradeCartMapper extends BaseMapperX<TradeCartDO> { |
| 70 | 71 | update(updateObject, new LambdaQueryWrapper<TradeCartDO>().in(TradeCartDO::getId, ids)); |
| 71 | 72 | } |
| 72 | 73 | |
| 74 | + default List<TradeCartDO> selectListByUserId(Long userId, Set<Long> ids) { | |
| 75 | + return selectList(new LambdaQueryWrapper<TradeCartDO>() | |
| 76 | + .eq(TradeCartDO::getUserId, userId) | |
| 77 | + .in(TradeCartDO::getId, ids)); | |
| 78 | + } | |
| 79 | + | |
| 73 | 80 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java
| ... | ... | @@ -4,10 +4,13 @@ import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO |
| 4 | 4 | import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO; |
| 5 | 5 | import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO; |
| 6 | 6 | import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO; |
| 7 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 7 | 8 | |
| 8 | 9 | import javax.validation.Valid; |
| 9 | 10 | import java.util.Collection; |
| 11 | +import java.util.List; | |
| 10 | 12 | import java.util.Map; |
| 13 | +import java.util.Set; | |
| 11 | 14 | |
| 12 | 15 | /** |
| 13 | 16 | * 购物车 Service 接口 |
| ... | ... | @@ -68,6 +71,15 @@ public interface TradeCartService { |
| 68 | 71 | AppTradeCartListRespVO getCartList(Long userId); |
| 69 | 72 | |
| 70 | 73 | /** |
| 74 | + * 查询用户的购物车列表 | |
| 75 | + * | |
| 76 | + * @param userId 用户编号 | |
| 77 | + * @param ids 购物项的编号 | |
| 78 | + * @return 购物车列表 | |
| 79 | + */ | |
| 80 | + List<TradeCartDO> getCartList(Long userId, Set<Long> ids); | |
| 81 | + | |
| 82 | + /** | |
| 71 | 83 | * 获得用户的购物车商品 SPU 数量的 Map |
| 72 | 84 | * |
| 73 | 85 | * @param userId 用户编号 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java
| ... | ... | @@ -17,10 +17,7 @@ import org.springframework.transaction.annotation.Transactional; |
| 17 | 17 | import org.springframework.validation.annotation.Validated; |
| 18 | 18 | |
| 19 | 19 | import javax.annotation.Resource; |
| 20 | -import java.util.Collection; | |
| 21 | -import java.util.Comparator; | |
| 22 | -import java.util.List; | |
| 23 | -import java.util.Map; | |
| 20 | +import java.util.*; | |
| 24 | 21 | |
| 25 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 26 | 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| ... | ... | @@ -166,6 +163,14 @@ public class TradeCartServiceImpl implements TradeCartService { |
| 166 | 163 | return TradeCartConvert.INSTANCE.convertList(carts, spus, skus); |
| 167 | 164 | } |
| 168 | 165 | |
| 166 | + @Override | |
| 167 | + public List<TradeCartDO> getCartList(Long userId, Set<Long> ids) { | |
| 168 | + if (CollUtil.isEmpty(ids)) { | |
| 169 | + return Collections.emptyList(); | |
| 170 | + } | |
| 171 | + return cartMapper.selectListByUserId(userId, ids); | |
| 172 | + } | |
| 173 | + | |
| 169 | 174 | private void deleteCartIfSpuDeleted(List<TradeCartDO> carts, List<ProductSpuRespDTO> spus) { |
| 170 | 175 | // 如果 SPU 被删除,则删除购物车对应的商品。延迟删除 |
| 171 | 176 | carts.removeIf(cart -> { | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java
| ... | ... | @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.delivery; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateCreateReqVO; |
| 5 | +import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateDetailRespVO; | |
| 5 | 6 | import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO; |
| 6 | -import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateRespVO; | |
| 7 | 7 | import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateUpdateReqVO; |
| 8 | 8 | import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; |
| 9 | 9 | |
| ... | ... | @@ -46,7 +46,7 @@ public interface DeliveryExpressTemplateService { |
| 46 | 46 | * @param id 编号 |
| 47 | 47 | * @return 快递运费模板详情 |
| 48 | 48 | */ |
| 49 | - DeliveryExpressTemplateRespVO getDeliveryExpressTemplate(Long id); | |
| 49 | + DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id); | |
| 50 | 50 | |
| 51 | 51 | /** |
| 52 | 52 | * 获得快递运费模板列表 |
| ... | ... | @@ -70,4 +70,14 @@ public interface DeliveryExpressTemplateService { |
| 70 | 70 | * @return 快递运费模板分页 |
| 71 | 71 | */ |
| 72 | 72 | PageResult<DeliveryExpressTemplateDO> getDeliveryExpressTemplatePage(DeliveryExpressTemplatePageReqVO pageReqVO); |
| 73 | + | |
| 74 | + /** | |
| 75 | + * 校验快递运费模板 | |
| 76 | + * | |
| 77 | + * 如果校验不通过,抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常 | |
| 78 | + * | |
| 79 | + * @param templateId 模板编号 | |
| 80 | + * @return 快递运费模板 | |
| 81 | + */ | |
| 82 | + DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId); | |
| 73 | 83 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java
| ... | ... | @@ -185,7 +185,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla |
| 185 | 185 | } |
| 186 | 186 | |
| 187 | 187 | @Override |
| 188 | - public DeliveryExpressTemplateRespVO getDeliveryExpressTemplate(Long id) { | |
| 188 | + public DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id) { | |
| 189 | 189 | List<DeliveryExpressTemplateChargeDO> chargeList = expressTemplateChargeMapper.selectListByTemplateId(id); |
| 190 | 190 | List<DeliveryExpressTemplateFreeDO> freeList = expressTemplateFreeMapper.selectListByTemplateId(id); |
| 191 | 191 | DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(id); |
| ... | ... | @@ -207,4 +207,13 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla |
| 207 | 207 | return expressTemplateMapper.selectPage(pageReqVO); |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | + @Override | |
| 211 | + public DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId) { | |
| 212 | + DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(templateId); | |
| 213 | + if (template == null) { | |
| 214 | + throw exception(EXPRESS_TEMPLATE_NOT_EXISTS); | |
| 215 | + } | |
| 216 | + return template; | |
| 217 | + } | |
| 218 | + | |
| 210 | 219 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.order; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 4 | 5 | import cn.hutool.core.util.IdUtil; |
| 5 | 6 | import cn.hutool.core.util.ObjectUtil; |
| 6 | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.core.KeyValue; |
| 8 | 9 | import cn.iocoder.yudao.framework.common.enums.TerminalEnum; |
| 9 | 10 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 11 | 11 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 12 | 12 | import cn.iocoder.yudao.module.member.api.address.AddressApi; |
| 13 | 13 | import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; |
| ... | ... | @@ -18,15 +18,9 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; |
| 18 | 18 | import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; |
| 19 | 19 | import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; |
| 20 | 20 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 21 | -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 22 | 21 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; |
| 23 | -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; | |
| 24 | -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 25 | -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; | |
| 26 | 22 | import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; |
| 27 | 23 | import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; |
| 28 | -import cn.iocoder.yudao.module.promotion.api.price.PriceApi; | |
| 29 | -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; | |
| 30 | 24 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; |
| 31 | 25 | import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO; |
| 32 | 26 | 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 |
| 34 | 28 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; |
| 35 | 29 | import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; |
| 36 | 30 | import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; |
| 31 | +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO; | |
| 37 | 32 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 38 | 33 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 39 | 34 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; |
| 40 | 35 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; |
| 41 | 36 | import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants; |
| 37 | +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | |
| 42 | 38 | import cn.iocoder.yudao.module.trade.enums.order.*; |
| 43 | 39 | import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; |
| 40 | +import cn.iocoder.yudao.module.trade.service.cart.TradeCartService; | |
| 41 | +import cn.iocoder.yudao.module.trade.service.price.TradePriceService; | |
| 42 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 43 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 44 | 44 | import lombok.extern.slf4j.Slf4j; |
| 45 | 45 | import org.springframework.stereotype.Service; |
| 46 | 46 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -71,11 +71,12 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 71 | 71 | private TradeOrderItemMapper tradeOrderItemMapper; |
| 72 | 72 | |
| 73 | 73 | @Resource |
| 74 | - private PriceApi priceApi; | |
| 74 | + private TradeCartService tradeCartService; | |
| 75 | 75 | @Resource |
| 76 | - private ProductSkuApi productSkuApi; | |
| 76 | + private TradePriceService tradePriceService; | |
| 77 | + | |
| 77 | 78 | @Resource |
| 78 | - private ProductSpuApi productSpuApi; | |
| 79 | + private ProductSkuApi productSkuApi; | |
| 79 | 80 | @Resource |
| 80 | 81 | private PayOrderApi payOrderApi; |
| 81 | 82 | @Resource |
| ... | ... | @@ -92,76 +93,70 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 92 | 93 | |
| 93 | 94 | @Override |
| 94 | 95 | public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) { |
| 95 | - return null; | |
| 96 | + // 1. 获得收货地址 | |
| 97 | + AddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId()); | |
| 98 | + if (address != null) { | |
| 99 | + settlementReqVO.setAddressId(address.getId()); | |
| 100 | + } | |
| 101 | + | |
| 102 | + // 2. 计算价格 | |
| 103 | + TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO); | |
| 104 | + | |
| 105 | + // 3. 拼接返回 | |
| 106 | + return TradeOrderConvert.INSTANCE.convert(calculateRespBO, address); | |
| 107 | + } | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * 获得用户地址 | |
| 111 | + * | |
| 112 | + * @param userId 用户编号 | |
| 113 | + * @param addressId 地址编号 | |
| 114 | + * @return 地址 | |
| 115 | + */ | |
| 116 | + private AddressRespDTO getAddress(Long userId, Long addressId) { | |
| 117 | + if (addressId != null) { | |
| 118 | + return addressApi.getAddress(addressId, userId); | |
| 119 | + } | |
| 120 | + return addressApi.getDefaultAddress(userId); | |
| 121 | + } | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * 计算订单价格 | |
| 125 | + * | |
| 126 | + * @param userId 用户编号 | |
| 127 | + * @param settlementReqVO 结算信息 | |
| 128 | + * @return 订单价格 | |
| 129 | + */ | |
| 130 | + private TradePriceCalculateRespBO calculatePrice(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) { | |
| 131 | + // 1. 如果来自购物车,则获得购物车的商品 | |
| 132 | + List<TradeCartDO> cartList = tradeCartService.getCartList(userId, | |
| 133 | + convertSet(settlementReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId)); | |
| 134 | + | |
| 135 | + // 2. 计算价格 | |
| 136 | + TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList); | |
| 137 | + calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的 | |
| 138 | + "商品({}) 未设置为选中", item.getSkuId())); | |
| 139 | + return tradePriceService.calculatePrice(calculateReqBO); | |
| 96 | 140 | } |
| 97 | 141 | |
| 98 | 142 | @Override |
| 99 | 143 | @Transactional(rollbackFor = Exception.class) |
| 100 | 144 | public Long createOrder(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO) { |
| 101 | - // 商品 SKU 检查:可售状态、库存 | |
| 102 | -// List<ProductSkuRespDTO> skus = validateSkuSaleable(createReqVO.getItems()); // TODO 芋艿,临时关闭。 | |
| 103 | - List<ProductSkuRespDTO> skus = null; | |
| 104 | - // 商品 SPU 检查:可售状态 | |
| 105 | - List<ProductSpuRespDTO> spus = validateSpuSaleable(convertSet(skus, ProductSkuRespDTO::getSpuId)); | |
| 106 | - // 用户收件地址的校验 | |
| 145 | + // 1. 用户收件地址的校验 | |
| 107 | 146 | AddressRespDTO address = validateAddress(userId, createReqVO.getAddressId()); |
| 108 | 147 | |
| 109 | - // 价格计算 | |
| 110 | - PriceCalculateRespDTO priceResp = priceApi.calculatePrice(TradeOrderConvert.INSTANCE.convert(createReqVO, userId)); | |
| 148 | + // 2. 价格计算 | |
| 149 | + TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); | |
| 111 | 150 | |
| 112 | - // 插入 TradeOrderDO 订单 | |
| 113 | - TradeOrderDO tradeOrderDO = createTradeOrder(userId, userIp, createReqVO, priceResp.getOrder(), address); | |
| 114 | - // 插入 TradeOrderItemDO 订单项 | |
| 115 | - List<TradeOrderItemDO> tradeOrderItems = createTradeOrderItems(tradeOrderDO, priceResp.getOrder().getItems(), skus); | |
| 151 | + // 3.1 插入 TradeOrderDO 订单 | |
| 152 | + TradeOrderDO order = createTradeOrder(userId, userIp, createReqVO, calculateRespBO, address); | |
| 153 | + // 3.2 插入 TradeOrderItemDO 订单项 | |
| 154 | + List<TradeOrderItemDO> orderItems = createTradeOrderItems(order, calculateRespBO); | |
| 116 | 155 | |
| 117 | 156 | // 订单创建完后的逻辑 |
| 118 | - afterCreateTradeOrder(userId, createReqVO, tradeOrderDO, tradeOrderItems, spus); | |
| 157 | + afterCreateTradeOrder(userId, createReqVO, order, orderItems, calculateRespBO); | |
| 119 | 158 | // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! |
| 120 | - return tradeOrderDO.getId(); | |
| 121 | - } | |
| 122 | - | |
| 123 | -// /** | |
| 124 | -// * 校验商品 SKU 是否可出售 | |
| 125 | -// * | |
| 126 | -// * @param items 商品 SKU | |
| 127 | -// * @return 商品 SKU 数组 | |
| 128 | -// */ | |
| 129 | -// private List<ProductSkuRespDTO> validateSkuSaleable(List<Item> items) { | |
| 130 | -// List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(items, Item::getSkuId)); | |
| 131 | -// // SKU 不存在 | |
| 132 | -// if (items.size() != skus.size()) { | |
| 133 | -// throw exception(ORDER_CREATE_SKU_NOT_FOUND); | |
| 134 | -// } | |
| 135 | -// // 校验库存不足 | |
| 136 | -// Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId); | |
| 137 | -// items.forEach(item -> { | |
| 138 | -// ProductSkuRespDTO sku = skuMap.get(item.getSkuId()); | |
| 139 | -// if (item.getCount() > sku.getStock()) { | |
| 140 | -// throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH); | |
| 141 | -// } | |
| 142 | -// }); | |
| 143 | -// return skus; | |
| 144 | -// } | |
| 145 | - | |
| 146 | - /** | |
| 147 | - * 校验商品 SPU 是否可出售 | |
| 148 | - * | |
| 149 | - * @param spuIds 商品 SPU 编号数组 | |
| 150 | - * @return 商品 SPU 数组 | |
| 151 | - */ | |
| 152 | - private List<ProductSpuRespDTO> validateSpuSaleable(Set<Long> spuIds) { | |
| 153 | - List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(spuIds); | |
| 154 | - // SPU 不存在 | |
| 155 | - if (spus.size() != spuIds.size()) { | |
| 156 | - throw exception(ORDER_CREATE_SPU_NOT_FOUND); | |
| 157 | - } | |
| 158 | - // 校验是否存在禁用的 SPU | |
| 159 | - ProductSpuRespDTO spu = CollectionUtils.findFirst(spus, | |
| 160 | - spuDTO -> ObjectUtil.notEqual(ProductSpuStatusEnum.ENABLE.getStatus(), spuDTO.getStatus())); | |
| 161 | - if (spu != null) { | |
| 162 | - throw exception(ErrorCodeConstants.ORDER_CREATE_SPU_NOT_SALE); | |
| 163 | - } | |
| 164 | - return spus; | |
| 159 | + return order.getId(); | |
| 165 | 160 | } |
| 166 | 161 | |
| 167 | 162 | /** |
| ... | ... | @@ -173,33 +168,35 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 173 | 168 | */ |
| 174 | 169 | private AddressRespDTO validateAddress(Long userId, Long addressId) { |
| 175 | 170 | AddressRespDTO address = addressApi.getAddress(addressId, userId); |
| 176 | - if (Objects.isNull(address)) { | |
| 171 | + if (address == null) { | |
| 177 | 172 | throw exception(ErrorCodeConstants.ORDER_CREATE_ADDRESS_NOT_FOUND); |
| 178 | 173 | } |
| 179 | 174 | return address; |
| 180 | 175 | } |
| 181 | 176 | |
| 182 | 177 | private TradeOrderDO createTradeOrder(Long userId, String clientIp, AppTradeOrderCreateReqVO createReqVO, |
| 183 | - PriceCalculateRespDTO.Order order, AddressRespDTO address) { | |
| 184 | - TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, order, address); | |
| 185 | - tradeOrderDO.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的; | |
| 186 | - tradeOrderDO.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); | |
| 187 | - tradeOrderDO.setType(TradeOrderTypeEnum.NORMAL.getType()); | |
| 188 | - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); | |
| 189 | - tradeOrderDO.setProductCount(getSumValue(order.getItems(), PriceCalculateRespDTO.OrderItem::getCount, Integer::sum)); | |
| 190 | - tradeOrderDO.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源? | |
| 191 | - tradeOrderDO.setAdjustPrice(0).setPayed(false); // 支付信息 | |
| 192 | - tradeOrderDO.setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus()); // 物流信息 | |
| 193 | - tradeOrderDO.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); // 退款信息 | |
| 194 | - tradeOrderMapper.insert(tradeOrderDO); | |
| 195 | - return tradeOrderDO; | |
| 178 | + TradePriceCalculateRespBO calculateRespBO, AddressRespDTO address) { | |
| 179 | + TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, clientIp, createReqVO, calculateRespBO, address); | |
| 180 | + order.setNo(IdUtil.getSnowflakeNextId() + ""); // TODO @LeeYan9: 思考下, 怎么生成好点哈; 这个是会展示给用户的; | |
| 181 | + order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); | |
| 182 | + order.setType(TradeOrderTypeEnum.NORMAL.getType()); | |
| 183 | + order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()); | |
| 184 | + order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); | |
| 185 | + order.setTerminal(TerminalEnum.H5.getTerminal()); // todo 数据来源? | |
| 186 | + // 支付信息 | |
| 187 | + order.setAdjustPrice(0).setPayed(false); | |
| 188 | + // 物流信息 TODO 芋艿:暂时写死物流方式;应该是前端选择的 | |
| 189 | + order.setDeliveryType(DeliveryTypeEnum.EXPRESS.getMode()).setDeliveryStatus(TradeOrderDeliveryStatusEnum.UNDELIVERED.getStatus()); | |
| 190 | + // 退款信息 | |
| 191 | + order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0); | |
| 192 | + tradeOrderMapper.insert(order); | |
| 193 | + return order; | |
| 196 | 194 | } |
| 197 | 195 | |
| 198 | - private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, | |
| 199 | - List<PriceCalculateRespDTO.OrderItem> orderItems, List<ProductSkuRespDTO> skus) { | |
| 200 | - List<TradeOrderItemDO> tradeOrderItemDOs = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, orderItems, skus); | |
| 201 | - tradeOrderItemMapper.insertBatch(tradeOrderItemDOs); | |
| 202 | - return tradeOrderItemDOs; | |
| 196 | + private List<TradeOrderItemDO> createTradeOrderItems(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) { | |
| 197 | + List<TradeOrderItemDO> orderItems = TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); | |
| 198 | + tradeOrderItemMapper.insertBatch(orderItems); | |
| 199 | + return orderItems; | |
| 203 | 200 | } |
| 204 | 201 | |
| 205 | 202 | /** |
| ... | ... | @@ -210,12 +207,13 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 210 | 207 | * @param userId 用户编号 |
| 211 | 208 | * @param createReqVO 创建订单请求 |
| 212 | 209 | * @param tradeOrderDO 交易订单 |
| 210 | + * @param calculateRespBO 订单价格计算结果 | |
| 213 | 211 | */ |
| 214 | 212 | private void afterCreateTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, |
| 215 | - TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs, | |
| 216 | - List<ProductSpuRespDTO> spus) { | |
| 213 | + TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> orderItems, | |
| 214 | + TradePriceCalculateRespBO calculateRespBO) { | |
| 217 | 215 | // 下单时扣减商品库存 |
| 218 | - productSkuApi.updateSkuStock(new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(tradeOrderItemDOs))); | |
| 216 | + productSkuApi.updateSkuStock(new ProductSkuUpdateStockReqDTO(TradeOrderConvert.INSTANCE.convertList(orderItems))); | |
| 219 | 217 | |
| 220 | 218 | // 删除购物车商品 TODO 芋艿:待实现 |
| 221 | 219 | |
| ... | ... | @@ -228,20 +226,19 @@ public class TradeOrderServiceImpl implements TradeOrderService { |
| 228 | 226 | } |
| 229 | 227 | |
| 230 | 228 | // 生成预支付 |
| 231 | - createPayOrder(tradeOrderDO, tradeOrderItemDOs, spus); | |
| 229 | + createPayOrder(tradeOrderDO, orderItems, calculateRespBO); | |
| 232 | 230 | |
| 233 | 231 | // 增加订单日志 TODO 芋艿:待实现 |
| 234 | 232 | } |
| 235 | 233 | |
| 236 | - private void createPayOrder(TradeOrderDO tradeOrderDO, List<TradeOrderItemDO> tradeOrderItemDOs, | |
| 237 | - List<ProductSpuRespDTO> spus) { | |
| 234 | + private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) { | |
| 238 | 235 | // 创建支付单,用于后续的支付 |
| 239 | 236 | PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( |
| 240 | - tradeOrderDO, tradeOrderItemDOs, spus, tradeOrderProperties); | |
| 237 | + order, orderItems, calculateRespBO, tradeOrderProperties); | |
| 241 | 238 | Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO); |
| 242 | 239 | |
| 243 | 240 | // 更新到交易单上 |
| 244 | - tradeOrderMapper.updateById(new TradeOrderDO().setId(tradeOrderDO.getId()).setPayOrderId(payOrderId)); | |
| 241 | + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setPayOrderId(payOrderId)); | |
| 245 | 242 | } |
| 246 | 243 | |
| 247 | 244 | @Override | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java
| ... | ... | @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.price; |
| 3 | 3 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 4 | 4 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 5 | 5 | |
| 6 | +import javax.validation.Valid; | |
| 7 | + | |
| 6 | 8 | /** |
| 7 | 9 | * 价格计算 Service 接口 |
| 8 | 10 | * |
| ... | ... | @@ -16,6 +18,6 @@ public interface TradePriceService { |
| 16 | 18 | * @param calculateReqDTO 计算信息 |
| 17 | 19 | * @return 计算结果 |
| 18 | 20 | */ |
| 19 | - TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqDTO); | |
| 21 | + TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO); | |
| 20 | 22 | |
| 21 | 23 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
| ... | ... | @@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.trade.service.price; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 4 | 4 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 5 | +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; | |
| 6 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; | |
| 5 | 8 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 6 | 9 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 7 | 10 | import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; |
| 8 | 11 | import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper; |
| 9 | 12 | import lombok.extern.slf4j.Slf4j; |
| 10 | 13 | import org.springframework.stereotype.Service; |
| 14 | +import org.springframework.validation.annotation.Validated; | |
| 11 | 15 | |
| 12 | 16 | import javax.annotation.Resource; |
| 13 | 17 | import java.util.List; |
| ... | ... | @@ -15,8 +19,8 @@ import java.util.Map; |
| 15 | 19 | |
| 16 | 20 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 17 | 21 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 18 | -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; | |
| 19 | -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH; | |
| 22 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 23 | +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; | |
| 20 | 24 | import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL; |
| 21 | 25 | |
| 22 | 26 | /** |
| ... | ... | @@ -25,22 +29,28 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.PRICE_C |
| 25 | 29 | * @author 芋道源码 |
| 26 | 30 | */ |
| 27 | 31 | @Service |
| 32 | +@Validated | |
| 28 | 33 | @Slf4j |
| 29 | 34 | public class TradePriceServiceImpl implements TradePriceService { |
| 30 | 35 | |
| 31 | 36 | @Resource |
| 32 | 37 | private ProductSkuApi productSkuApi; |
| 33 | 38 | @Resource |
| 39 | + private ProductSpuApi productSpuApi; | |
| 40 | + | |
| 41 | + @Resource | |
| 34 | 42 | private List<TradePriceCalculator> priceCalculators; |
| 35 | 43 | |
| 36 | 44 | @Override |
| 37 | 45 | public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) { |
| 38 | - // 1. 获得商品 SKU 数组 | |
| 39 | - List<ProductSkuRespDTO> skuList = checkSkus(calculateReqBO); | |
| 46 | + // 1.1 获得商品 SKU 数组 | |
| 47 | + List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO); | |
| 48 | + // 1.2 获得商品 SPU 数组 | |
| 49 | + List<ProductSpuRespDTO> spuList = checkSpuList(skuList); | |
| 40 | 50 | |
| 41 | 51 | // 2.1 计算价格 |
| 42 | 52 | TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper |
| 43 | - .buildCalculateResp(calculateReqBO, skuList); | |
| 53 | + .buildCalculateResp(calculateReqBO, spuList, skuList); | |
| 44 | 54 | priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO)); |
| 45 | 55 | // 2.2 如果最终支付金额小于等于 0,则抛出业务异常 |
| 46 | 56 | if (calculateRespBO.getPrice().getPayPrice() <= 0) { |
| ... | ... | @@ -51,7 +61,7 @@ public class TradePriceServiceImpl implements TradePriceService { |
| 51 | 61 | return calculateRespBO; |
| 52 | 62 | } |
| 53 | 63 | |
| 54 | - private List<ProductSkuRespDTO> checkSkus(TradePriceCalculateReqBO reqBO) { | |
| 64 | + private List<ProductSkuRespDTO> checkSkuList(TradePriceCalculateReqBO reqBO) { | |
| 55 | 65 | // 获得商品 SKU 数组 |
| 56 | 66 | Map<Long, Integer> skuIdCountMap = convertMap(reqBO.getItems(), |
| 57 | 67 | TradePriceCalculateReqBO.Item::getSkuId, TradePriceCalculateReqBO.Item::getCount); |
| ... | ... | @@ -70,4 +80,20 @@ public class TradePriceServiceImpl implements TradePriceService { |
| 70 | 80 | return skus; |
| 71 | 81 | } |
| 72 | 82 | |
| 83 | + private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) { | |
| 84 | + // 获得商品 SPU 数组 | |
| 85 | + List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId)); | |
| 86 | + | |
| 87 | + // 校验商品 SPU | |
| 88 | + spus.forEach(spu -> { | |
| 89 | + if (spu == null) { | |
| 90 | + throw exception(SPU_NOT_EXISTS); | |
| 91 | + } | |
| 92 | + if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) { | |
| 93 | + throw exception(SPU_NOT_ENABLE); | |
| 94 | + } | |
| 95 | + }); | |
| 96 | + return spus; | |
| 97 | + } | |
| 98 | + | |
| 73 | 99 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateReqBO.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.price.bo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; | |
| 4 | +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | |
| 3 | 5 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; |
| 4 | 6 | import lombok.Data; |
| 5 | 7 | |
| ... | ... | @@ -21,7 +23,7 @@ public class TradePriceCalculateReqBO { |
| 21 | 23 | * |
| 22 | 24 | * 枚举 {@link TradeOrderTypeEnum} |
| 23 | 25 | */ |
| 24 | - private Integer orderType; | |
| 26 | + private Integer type; | |
| 25 | 27 | |
| 26 | 28 | /** |
| 27 | 29 | * 用户编号 |
| ... | ... | @@ -45,6 +47,21 @@ public class TradePriceCalculateReqBO { |
| 45 | 47 | private Long addressId; |
| 46 | 48 | |
| 47 | 49 | /** |
| 50 | + * 配送方式 | |
| 51 | + * | |
| 52 | + * 枚举 {@link DeliveryTypeEnum} | |
| 53 | + */ | |
| 54 | + private Integer deliveryType; | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 配送模板编号 | |
| 58 | + * | |
| 59 | + * 关联 {@link DeliveryExpressTemplateDO#getId()} | |
| 60 | + */ | |
| 61 | + // TODO @jason:运费模版,是不是每个 SKU 传入哈 | |
| 62 | + private Long templateId; | |
| 63 | + | |
| 64 | + /** | |
| 48 | 65 | * 商品 SKU 数组 |
| 49 | 66 | */ |
| 50 | 67 | @NotNull(message = "商品数组不能为空") |
| ... | ... | @@ -82,5 +99,4 @@ public class TradePriceCalculateReqBO { |
| 82 | 99 | private Boolean selected; |
| 83 | 100 | |
| 84 | 101 | } |
| 85 | - | |
| 86 | 102 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.price.bo; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; | |
| 3 | 4 | import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; |
| 4 | 5 | import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; |
| 5 | 6 | import lombok.Data; |
| ... | ... | @@ -23,7 +24,7 @@ public class TradePriceCalculateRespBO { |
| 23 | 24 | * |
| 24 | 25 | * 枚举 {@link TradeOrderTypeEnum} |
| 25 | 26 | */ |
| 26 | - private Integer orderType; | |
| 27 | + private Integer type; | |
| 27 | 28 | |
| 28 | 29 | /** |
| 29 | 30 | * 订单价格 |
| ... | ... | @@ -163,7 +164,26 @@ public class TradePriceCalculateRespBO { |
| 163 | 164 | */ |
| 164 | 165 | private Integer payPrice; |
| 165 | 166 | |
| 166 | - // TODO 芋艿:这里补充下基本信息,简单一点。 | |
| 167 | + // ========== 商品信息 ========== | |
| 168 | + /** | |
| 169 | + * 商品名 | |
| 170 | + */ | |
| 171 | + private String spuName; | |
| 172 | + /** | |
| 173 | + * 商品图片 | |
| 174 | + * | |
| 175 | + * 优先级:SKU.picUrl > SPU.picUrl | |
| 176 | + */ | |
| 177 | + private String picUrl; | |
| 178 | + /** | |
| 179 | + * 分类编号 | |
| 180 | + */ | |
| 181 | + private Long categoryId; | |
| 182 | + | |
| 183 | + /** | |
| 184 | + * 商品属性数组 | |
| 185 | + */ | |
| 186 | + private List<ProductPropertyValueDetailRespDTO> properties; | |
| 167 | 187 | |
| 168 | 188 | } |
| 169 | 189 | |
| ... | ... | @@ -190,12 +210,6 @@ public class TradePriceCalculateRespBO { |
| 190 | 210 | */ |
| 191 | 211 | private Integer type; |
| 192 | 212 | /** |
| 193 | - * 营销级别 | |
| 194 | - * | |
| 195 | - * 枚举 @link PromotionLevelEnum} TODO PromotionLevelEnum 没有这个枚举类 | |
| 196 | - */ | |
| 197 | - private Integer level; | |
| 198 | - /** | |
| 199 | 213 | * 计算时的原价(总),单位:分 |
| 200 | 214 | */ |
| 201 | 215 | private Integer totalPrice; | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
| ... | ... | @@ -101,7 +101,8 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator { |
| 101 | 101 | CouponRespDTO coupon) { |
| 102 | 102 | Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected; |
| 103 | 103 | if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) { |
| 104 | - matchPredicate = orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId()); | |
| 104 | + matchPredicate = matchPredicate // 额外加如下条件 | |
| 105 | + .and(orderItem -> coupon.getProductSpuIds().contains(orderItem.getSpuId())); | |
| 105 | 106 | } |
| 106 | 107 | return filterList(result.getItems(), matchPredicate); |
| 107 | 108 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price.calculator; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.member.api.address.AddressApi; | |
| 4 | +import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | |
| 6 | +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO; | |
| 8 | +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; | |
| 9 | +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO; | |
| 10 | +import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper; | |
| 11 | +import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper; | |
| 12 | +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; | |
| 13 | +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; | |
| 14 | +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; | |
| 15 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 16 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 17 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem; | |
| 18 | +import org.springframework.core.annotation.Order; | |
| 19 | +import org.springframework.stereotype.Component; | |
| 20 | + | |
| 21 | +import javax.annotation.Resource; | |
| 22 | +import java.util.HashMap; | |
| 23 | +import java.util.List; | |
| 24 | +import java.util.Map; | |
| 25 | +import java.util.Set; | |
| 26 | + | |
| 27 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 运费的 {@link TradePriceCalculator} 实现类 | |
| 31 | + * | |
| 32 | + * @author jason | |
| 33 | + */ | |
| 34 | +@Component | |
| 35 | +@Order(TradePriceCalculator.ORDER_DELIVERY) | |
| 36 | +public class TradeDeliveryPriceCalculator implements TradePriceCalculator { | |
| 37 | + | |
| 38 | + @Resource | |
| 39 | + private AddressApi addressApi; | |
| 40 | + @Resource | |
| 41 | + private ProductSkuApi productSkuApi; | |
| 42 | + | |
| 43 | + @Resource | |
| 44 | + private DeliveryExpressTemplateService deliveryExpressTemplateService; | |
| 45 | + // TODO @jason:走 Service 哈。Mapper 只允许自己的 Service 调用,保护好数据结构; | |
| 46 | + @Resource | |
| 47 | + private DeliveryExpressTemplateChargeMapper templateChargeMapper; | |
| 48 | + @Resource | |
| 49 | + private DeliveryExpressTemplateFreeMapper templateFreeMapper; | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | |
| 53 | + // 1.1 判断配送方式 | |
| 54 | + if (param.getDeliveryType() == null || DeliveryTypeEnum.PICK_UP.getMode().equals(param.getDeliveryType())) { | |
| 55 | + return; | |
| 56 | + } | |
| 57 | + | |
| 58 | + if (param.getTemplateId() == null || param.getAddressId() == null) { | |
| 59 | + return; | |
| 60 | + } | |
| 61 | + // 1.2 校验运费模板是否存在 | |
| 62 | + DeliveryExpressTemplateDO template = deliveryExpressTemplateService.validateDeliveryExpressTemplate(param.getTemplateId()); | |
| 63 | + | |
| 64 | + // 得到包邮配置 | |
| 65 | + List<DeliveryExpressTemplateFreeDO> expressTemplateFreeList = templateFreeMapper.selectListByTemplateId(template.getId()); | |
| 66 | + Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap = new HashMap<>(); | |
| 67 | + expressTemplateFreeList.forEach(item -> { | |
| 68 | + for (Integer areaId : item.getAreaIds()) { | |
| 69 | + // TODO 需要保证 areaId 不能重复 | |
| 70 | + if (!areaTemplateFreeMap.containsKey(areaId)) { | |
| 71 | + areaTemplateFreeMap.put(areaId, item); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + }); | |
| 75 | + // 得到快递运费配置 | |
| 76 | + List<DeliveryExpressTemplateChargeDO> expressTemplateChargeList = templateChargeMapper.selectListByTemplateId(template.getId()); | |
| 77 | + Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap = new HashMap<>(); | |
| 78 | + expressTemplateChargeList.forEach(item -> { | |
| 79 | + for (Integer areaId : item.getAreaIds()) { | |
| 80 | + // areaId 不能重复 | |
| 81 | + if (!areaTemplateChargeMap.containsKey(areaId)) { | |
| 82 | + areaTemplateChargeMap.put(areaId, item); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + }); | |
| 86 | + // 得到收件地址区域 | |
| 87 | + AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId()); | |
| 88 | + // 1.3 计算快递费用 | |
| 89 | + calculateDeliveryPrice(address.getAreaId(), template.getChargeMode(), | |
| 90 | + areaTemplateFreeMap, areaTemplateChargeMap, result); | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * 校验订单是否满足包邮条件 | |
| 95 | + * | |
| 96 | + * @param receiverAreaId 收件人地区的区域编号 | |
| 97 | + * @param chargeMode 配送计费方式 | |
| 98 | + * @param areaTemplateFreeMap 运费模板包邮区域设置 Map | |
| 99 | + * @param areaTemplateChargeMap 运费模板快递费用设置 Map | |
| 100 | + */ | |
| 101 | + private void calculateDeliveryPrice(Integer receiverAreaId, | |
| 102 | + Integer chargeMode, | |
| 103 | + Map<Integer, DeliveryExpressTemplateFreeDO> areaTemplateFreeMap, | |
| 104 | + Map<Integer, DeliveryExpressTemplateChargeDO> areaTemplateChargeMap, | |
| 105 | + TradePriceCalculateRespBO result) { | |
| 106 | + // 过滤出已选中的商品SKU | |
| 107 | + List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected); | |
| 108 | + Set<Long> skuIds = convertSet(selectedItem, OrderItem::getSkuId); | |
| 109 | + // 得到SKU 详情。得到 重量体积 | |
| 110 | + Map<Long, ProductSkuRespDTO> skuRespMap = convertMap(productSkuApi.getSkuList(skuIds), ProductSkuRespDTO::getId); | |
| 111 | + // 一个 spuId 可能对应多条订单商品 SKU | |
| 112 | + // TODO @jason:得确认下,按照 sku 算,还是 spu 算; | |
| 113 | + Map<Long, List<OrderItem>> spuIdItemMap = convertMultiMap(selectedItem, OrderItem::getSpuId); | |
| 114 | + // 依次计算每个 SPU 的快递运费 | |
| 115 | + for (Map.Entry<Long, List<OrderItem>> entry : spuIdItemMap.entrySet()) { | |
| 116 | + List<OrderItem> orderItems = entry.getValue(); | |
| 117 | + // 总件数, 总金额, 总重量, 总体积 | |
| 118 | + int totalCount = 0; | |
| 119 | + int totalPrice = 0; | |
| 120 | + double totalWeight = 0; | |
| 121 | + double totalVolume = 0; | |
| 122 | + for (OrderItem orderItem : orderItems) { | |
| 123 | + totalCount += orderItem.getCount(); | |
| 124 | + totalPrice += orderItem.getPrice(); // TODO jason:应该按照 payPrice? | |
| 125 | + ProductSkuRespDTO skuResp = skuRespMap.get(orderItem.getSkuId()); | |
| 126 | + if (skuResp != null) { | |
| 127 | + totalWeight = totalWeight + skuResp.getWeight(); // TODO @jason:* 数量 | |
| 128 | + totalVolume = totalVolume + skuResp.getVolume(); | |
| 129 | + } | |
| 130 | + } | |
| 131 | + // 优先判断是否包邮. 如果包邮不计算快递运费 | |
| 132 | + if (areaTemplateFreeMap.containsKey(receiverAreaId) && | |
| 133 | + checkExpressFree(chargeMode, totalCount, totalWeight, | |
| 134 | + totalVolume, totalPrice, areaTemplateFreeMap.get(receiverAreaId))) { | |
| 135 | + continue; | |
| 136 | + } | |
| 137 | + // 计算快递运费 | |
| 138 | + // TODO @jason:貌似也可以抽成 checkExpressFree 类似方法 | |
| 139 | + if (areaTemplateChargeMap.containsKey(receiverAreaId)) { | |
| 140 | + DeliveryExpressTemplateChargeDO templateCharge = areaTemplateChargeMap.get(receiverAreaId); | |
| 141 | + DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); | |
| 142 | + switch (chargeModeEnum) { | |
| 143 | + case PIECE: { | |
| 144 | + calculateExpressFeeBySpu(totalCount, templateCharge, orderItems); | |
| 145 | + break; | |
| 146 | + } | |
| 147 | + case WEIGHT: { | |
| 148 | + calculateExpressFeeBySpu(totalWeight, templateCharge, orderItems); | |
| 149 | + break; | |
| 150 | + } | |
| 151 | + case VOLUME: { | |
| 152 | + calculateExpressFeeBySpu(totalVolume, templateCharge, orderItems); | |
| 153 | + break; | |
| 154 | + } | |
| 155 | + } | |
| 156 | + } | |
| 157 | + } | |
| 158 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 159 | + } | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * 按 spu 来计算快递费用 | |
| 163 | + * | |
| 164 | + * @param total 总件数/总重量/总体积 | |
| 165 | + * @param templateCharge 快递运费配置 | |
| 166 | + * @param orderItems SKU 商品项目 | |
| 167 | + */ | |
| 168 | + private void calculateExpressFeeBySpu(double total, DeliveryExpressTemplateChargeDO templateCharge, List<OrderItem> orderItems) { | |
| 169 | + int deliveryPrice; | |
| 170 | + if (total <= templateCharge.getStartCount()) { | |
| 171 | + deliveryPrice = templateCharge.getStartPrice(); | |
| 172 | + } else { | |
| 173 | + double remainWeight = total - templateCharge.getStartCount(); | |
| 174 | + // 剩余重量/ 续件 = 续件的次数. 向上取整 | |
| 175 | + int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount()); | |
| 176 | + int extraPrice = templateCharge.getExtraPrice() * extraNum; | |
| 177 | + deliveryPrice = templateCharge.getStartPrice() + extraPrice; | |
| 178 | + } | |
| 179 | + // TODO @芋艿 分摊快递费用到 SKU. 是不是搞复杂了; | |
| 180 | + // TODO @jason:因为退费的时候,可能按照 SKU 考虑退费金额 | |
| 181 | + divideDeliveryPrice(deliveryPrice, orderItems); | |
| 182 | + } | |
| 183 | + | |
| 184 | + /** | |
| 185 | + * 快递运费分摊到每个 SKU 商品上 | |
| 186 | + * | |
| 187 | + * @param deliveryPrice 快递运费 | |
| 188 | + * @param orderItems SKU 商品 | |
| 189 | + */ | |
| 190 | + private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) { | |
| 191 | + int dividePrice = deliveryPrice / orderItems.size(); | |
| 192 | + for (OrderItem item : orderItems) { | |
| 193 | + // 更新快递运费 | |
| 194 | + item.setDeliveryPrice(dividePrice); | |
| 195 | + } | |
| 196 | + } | |
| 197 | + | |
| 198 | + /** | |
| 199 | + * 检查是否包邮 | |
| 200 | + * | |
| 201 | + * @param chargeMode 配送计费方式 | |
| 202 | + * @param totalCount 总件数 | |
| 203 | + * @param totalWeight 总重量 | |
| 204 | + * @param totalVolume 总体积 | |
| 205 | + * @param totalPrice 总金额 | |
| 206 | + * @param templateFree 包邮配置 | |
| 207 | + */ | |
| 208 | + private boolean checkExpressFree(Integer chargeMode, int totalCount, double totalWeight, | |
| 209 | + double totalVolume, int totalPrice, DeliveryExpressTemplateFreeDO templateFree) { | |
| 210 | + DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); | |
| 211 | + switch (chargeModeEnum) { | |
| 212 | + case PIECE: | |
| 213 | + // 两个条件都满足才包邮 | |
| 214 | + if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) { | |
| 215 | + return true; | |
| 216 | + } | |
| 217 | + break; | |
| 218 | + case WEIGHT: | |
| 219 | + // freeCount 是不是应该是 double ?? | |
| 220 | + if (totalWeight >= templateFree.getFreeCount() | |
| 221 | + && totalPrice >= templateFree.getFreePrice()) { | |
| 222 | + return true; | |
| 223 | + } | |
| 224 | + break; | |
| 225 | + case VOLUME: | |
| 226 | + if (totalVolume >= templateFree.getFreeCount() | |
| 227 | + && totalPrice >= templateFree.getFreePrice()) { | |
| 228 | + return true; | |
| 229 | + } | |
| 230 | + break; | |
| 231 | + } | |
| 232 | + return false; | |
| 233 | + } | |
| 234 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java
| ... | ... | @@ -53,10 +53,13 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato |
| 53 | 53 | Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice; |
| 54 | 54 | |
| 55 | 55 | // 3.1 记录优惠明细 |
| 56 | - TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 57 | - discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), | |
| 58 | - StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)), | |
| 59 | - newDiscountPrice); | |
| 56 | + if (orderItem.getSelected()) { | |
| 57 | + // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示 | |
| 58 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 59 | + discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(), | |
| 60 | + StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)), | |
| 61 | + newDiscountPrice); | |
| 62 | + } | |
| 60 | 63 | // 3.2 更新 SKU 优惠金额 |
| 61 | 64 | orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice); |
| 62 | 65 | TradePriceCalculatorHelper.recountPayPrice(orderItem); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java
| ... | ... | @@ -13,6 +13,12 @@ public interface TradePriceCalculator { |
| 13 | 13 | int ORDER_DISCOUNT_ACTIVITY = 10; |
| 14 | 14 | int ORDER_REWARD_ACTIVITY = 20; |
| 15 | 15 | int ORDER_COUPON = 30; |
| 16 | + /** | |
| 17 | + * 快递运费的计算 | |
| 18 | + * | |
| 19 | + * 放在各种营销活动、优惠劵后面 TODO | |
| 20 | + */ | |
| 21 | + int ORDER_DELIVERY = 40; | |
| 16 | 22 | |
| 17 | 23 | void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result); |
| 18 | 24 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 6 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 6 | 7 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 7 | 8 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 8 | 9 | |
| ... | ... | @@ -24,25 +25,42 @@ import static java.util.Collections.singletonList; |
| 24 | 25 | public class TradePriceCalculatorHelper { |
| 25 | 26 | |
| 26 | 27 | public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param, |
| 27 | - List<ProductSkuRespDTO> skuList) { | |
| 28 | + List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) { | |
| 28 | 29 | // 创建 PriceCalculateRespDTO 对象 |
| 29 | 30 | TradePriceCalculateRespBO result = new TradePriceCalculateRespBO(); |
| 30 | - result.setOrderType(param.getOrderType()); | |
| 31 | + result.setType(param.getType()); | |
| 32 | + result.setPromotions(new ArrayList<>()); | |
| 33 | + | |
| 31 | 34 | // 创建它的 OrderItem 属性 |
| 32 | - Map<Long, TradePriceCalculateReqBO.Item> skuItemMap = convertMap(param.getItems(), | |
| 33 | - TradePriceCalculateReqBO.Item::getSkuId); | |
| 34 | - result.setItems(new ArrayList<>(skuItemMap.size())); | |
| 35 | - skuList.forEach(sku -> { | |
| 36 | - TradePriceCalculateReqBO.Item skuItem = skuItemMap.get(sku.getId()); | |
| 37 | - TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem() | |
| 38 | - // SKU 字段 | |
| 39 | - .setSpuId(sku.getSpuId()).setSkuId(sku.getId()) | |
| 40 | - .setCount(skuItem.getCount()).setCartId(skuItem.getCartId()).setSelected(skuItem.getSelected()) | |
| 41 | - // 价格字段 | |
| 42 | - .setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * skuItem.getCount()) | |
| 43 | - .setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0); | |
| 35 | + result.setItems(new ArrayList<>(param.getItems().size())); | |
| 36 | + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId); | |
| 37 | + Map<Long, ProductSkuRespDTO> skuMap = convertMap(skuList, ProductSkuRespDTO::getId); | |
| 38 | + param.getItems().forEach(item -> { | |
| 39 | + ProductSkuRespDTO sku = skuMap.get(item.getSkuId()); | |
| 40 | + if (sku == null) { | |
| 41 | + return; | |
| 42 | + } | |
| 43 | + ProductSpuRespDTO spu = spuMap.get(sku.getSpuId()); | |
| 44 | + if (spu == null) { | |
| 45 | + return; | |
| 46 | + } | |
| 47 | + // 商品项 | |
| 48 | + TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem(); | |
| 44 | 49 | result.getItems().add(orderItem); |
| 50 | + orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId()) | |
| 51 | + .setCount(item.getCount()).setCartId(item.getCartId()).setSelected(item.getSelected()); | |
| 52 | + // sku 价格 | |
| 53 | + orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount()) | |
| 54 | + .setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0); | |
| 55 | + // sku 信息 | |
| 56 | + orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties()); | |
| 57 | + // spu 信息 | |
| 58 | + orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()); | |
| 59 | + if (orderItem.getPicUrl() == null) { | |
| 60 | + orderItem.setPicUrl(spu.getPicUrl()); | |
| 61 | + } | |
| 45 | 62 | }); |
| 63 | + | |
| 46 | 64 | // 创建它的 Price 属性 |
| 47 | 65 | result.setPrice(new TradePriceCalculateRespBO.Price()); |
| 48 | 66 | recountAllPrice(result); |
| ... | ... | @@ -87,6 +105,31 @@ public class TradePriceCalculatorHelper { |
| 87 | 105 | } |
| 88 | 106 | |
| 89 | 107 | /** |
| 108 | + * 重新计算每个订单项的支付金额 | |
| 109 | + * | |
| 110 | + * 【目前主要是单测使用】 | |
| 111 | + * | |
| 112 | + * @param orderItems 订单项数组 | |
| 113 | + */ | |
| 114 | + public static void recountPayPrice(List<TradePriceCalculateRespBO.OrderItem> orderItems) { | |
| 115 | + orderItems.forEach(orderItem -> { | |
| 116 | + if (orderItem.getDiscountPrice() == null) { | |
| 117 | + orderItem.setDiscountPrice(0); | |
| 118 | + } | |
| 119 | + if (orderItem.getDeliveryPrice() == null) { | |
| 120 | + orderItem.setDeliveryPrice(0); | |
| 121 | + } | |
| 122 | + if (orderItem.getCouponPrice() == null) { | |
| 123 | + orderItem.setCouponPrice(0); | |
| 124 | + } | |
| 125 | + if (orderItem.getPointPrice() == null) { | |
| 126 | + orderItem.setPointPrice(0); | |
| 127 | + } | |
| 128 | + recountPayPrice(orderItem); | |
| 129 | + }); | |
| 130 | + } | |
| 131 | + | |
| 132 | + /** | |
| 90 | 133 | * 计算已选中的订单项,总支付金额 |
| 91 | 134 | * |
| 92 | 135 | * @param orderItems 订单项数组 |
| ... | ... | @@ -94,7 +137,7 @@ public class TradePriceCalculatorHelper { |
| 94 | 137 | */ |
| 95 | 138 | public static Integer calculateTotalPayPrice(List<TradePriceCalculateRespBO.OrderItem> orderItems) { |
| 96 | 139 | return getSumValue(orderItems, |
| 97 | - orderItem -> orderItem.getSelected() ? orderItem.getPayPrice() : 0, // 未选中的情况下,不计算支付金额 | |
| 140 | + orderItem -> orderItem.getSelected() ? orderItem.getPayPrice() : 0, // 未选中的情况下,不计算支付金额 | |
| 98 | 141 | Integer::sum); |
| 99 | 142 | } |
| 100 | 143 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
| ... | ... | @@ -53,6 +53,9 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator |
| 53 | 53 | // 1.2 获得最大匹配的满减送活动的规则 |
| 54 | 54 | RewardActivityMatchRespDTO.Rule rule = getMaxMatchRewardActivityRule(rewardActivity, orderItems); |
| 55 | 55 | if (rule == null) { |
| 56 | + TradePriceCalculatorHelper.addNotMatchPromotion(result, orderItems, | |
| 57 | + rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(), | |
| 58 | + getRewardActivityNotMeetTip(rewardActivity)); | |
| 56 | 59 | return; |
| 57 | 60 | } |
| 58 | 61 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/TradeAfterSaleServiceTest.java
| ... | ... | @@ -69,8 +69,7 @@ public class TradeAfterSaleServiceTest extends BaseDbUnitTest { |
| 69 | 69 | .setApplyPicUrls(asList("https://www.baidu.com/1.png", "https://www.baidu.com/2.png")); |
| 70 | 70 | // mock 方法(交易订单项) |
| 71 | 71 | TradeOrderItemDO orderItem = randomPojo(TradeOrderItemDO.class, o -> { |
| 72 | - //o.setOrderId(111L).setUserId(userId).setOrderDividePrice(200); TODO DO 中没有 orderDividePrice 属性 | |
| 73 | - o.setOrderId(111L).setUserId(userId); | |
| 72 | + o.setOrderId(111L).setUserId(userId).setPayPrice(200); | |
| 74 | 73 | o.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); |
| 75 | 74 | }); |
| 76 | 75 | when(tradeOrderService.getOrderItem(eq(1024L), eq(1L))) | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceTest.java
| ... | ... | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi; |
| 8 | 8 | import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; |
| 9 | 9 | import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; |
| 10 | 10 | import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; |
| 11 | +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; | |
| 11 | 12 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 12 | 13 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 13 | 14 | import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; |
| ... | ... | @@ -101,10 +102,10 @@ public class TradeOrderServiceTest extends BaseDbUnitTest { |
| 101 | 102 | // mock 方法(商品 SKU 检查) |
| 102 | 103 | ProductSkuRespDTO sku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(1L).setSpuId(11L) |
| 103 | 104 | .setPrice(50).setStock(100) |
| 104 | - .setProperties(singletonList(new ProductSkuRespDTO.Property().setPropertyId(111L).setValueId(222L)))); | |
| 105 | + .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(111L).setValueId(222L)))); | |
| 105 | 106 | ProductSkuRespDTO sku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(2L).setSpuId(21L) |
| 106 | 107 | .setPrice(20).setStock(50)) |
| 107 | - .setProperties(singletonList(new ProductSkuRespDTO.Property().setPropertyId(333L).setValueId(444L))); | |
| 108 | + .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(333L).setValueId(444L))); | |
| 108 | 109 | when(productSkuApi.getSkuList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(sku01, sku02)); |
| 109 | 110 | // mock 方法(商品 SPU 检查) |
| 110 | 111 | ProductSpuRespDTO spu01 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(11L) |
| ... | ... | @@ -114,7 +115,7 @@ public class TradeOrderServiceTest extends BaseDbUnitTest { |
| 114 | 115 | when(productSpuApi.getSpuList(eq(asSet(11L, 21L)))).thenReturn(Arrays.asList(spu01, spu02)); |
| 115 | 116 | // mock 方法(用户收件地址的校验) |
| 116 | 117 | AddressRespDTO addressRespDTO = new AddressRespDTO().setId(10L).setUserId(userId).setName("芋艿") |
| 117 | - .setMobile("15601691300").setAreaId(3306L).setPostCode("85757").setDetailAddress("土豆村"); | |
| 118 | + .setMobile("15601691300").setAreaId(3306).setPostCode("85757").setDetailAddress("土豆村"); | |
| 118 | 119 | when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(addressRespDTO); |
| 119 | 120 | // mock 方法(价格计算) |
| 120 | 121 | PriceCalculateRespDTO.OrderItem priceOrderItem01 = new PriceCalculateRespDTO.OrderItem() | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; | |
| 6 | +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; | |
| 8 | +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; | |
| 9 | +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; | |
| 10 | +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; | |
| 11 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 12 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 13 | +import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; | |
| 14 | +import org.junit.jupiter.api.Test; | |
| 15 | +import org.mockito.InjectMocks; | |
| 16 | +import org.mockito.Mock; | |
| 17 | +import org.mockito.Mockito; | |
| 18 | + | |
| 19 | +import java.util.Arrays; | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 23 | +import static java.util.Collections.singletonList; | |
| 24 | +import static org.junit.jupiter.api.Assertions.*; | |
| 25 | +import static org.mockito.Mockito.when; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * {@link TradePriceServiceImpl} 的单元测试 | |
| 29 | + * | |
| 30 | + * @author 芋道源码 | |
| 31 | + */ | |
| 32 | +public class TradePriceServiceImplTest extends BaseMockitoUnitTest { | |
| 33 | + | |
| 34 | + @InjectMocks | |
| 35 | + private TradePriceServiceImpl tradePriceService; | |
| 36 | + | |
| 37 | + @Mock | |
| 38 | + private ProductSkuApi productSkuApi; | |
| 39 | + @Mock | |
| 40 | + private ProductSpuApi productSpuApi; | |
| 41 | + @Mock | |
| 42 | + private List<TradePriceCalculator> priceCalculators; | |
| 43 | + | |
| 44 | + @Test | |
| 45 | + public void testCalculatePrice() { | |
| 46 | + // 准备参数 | |
| 47 | + TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO() | |
| 48 | + .setType(TradeOrderTypeEnum.NORMAL.getType()).setUserId(10L) | |
| 49 | + .setCouponId(20L).setAddressId(30L) | |
| 50 | + .setItems(Arrays.asList( | |
| 51 | + new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true), | |
| 52 | + new TradePriceCalculateReqBO.Item().setSkuId(200L).setCount(3).setSelected(true), | |
| 53 | + new TradePriceCalculateReqBO.Item().setSkuId(300L).setCount(6).setCartId(233L).setSelected(false) | |
| 54 | + )); | |
| 55 | + // mock 方法 | |
| 56 | + List<ProductSkuRespDTO> skuList = Arrays.asList( | |
| 57 | + new ProductSkuRespDTO().setId(100L).setStock(500).setPrice(1000).setPicUrl("https://t.cn/1.png").setSpuId(1001L) | |
| 58 | + .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") | |
| 59 | + .setValueId(2L).setValueName("红色"))), | |
| 60 | + new ProductSkuRespDTO().setId(200L).setStock(400).setPrice(2000).setPicUrl("https://t.cn/2.png").setSpuId(1001L) | |
| 61 | + .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") | |
| 62 | + .setValueId(3L).setValueName("黄色"))), | |
| 63 | + new ProductSkuRespDTO().setId(300L).setStock(600).setPrice(3000).setPicUrl("https://t.cn/3.png").setSpuId(1001L) | |
| 64 | + .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") | |
| 65 | + .setValueId(4L).setValueName("黑色"))) | |
| 66 | + ); | |
| 67 | + when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList); | |
| 68 | + when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L)))) | |
| 69 | + .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L) | |
| 70 | + .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()))); | |
| 71 | + | |
| 72 | + // 调用 | |
| 73 | + TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculatePrice(calculateReqBO); | |
| 74 | + // 断言 | |
| 75 | + assertEquals(TradeOrderTypeEnum.NORMAL.getType(), calculateRespBO.getType()); | |
| 76 | + assertEquals(0, calculateRespBO.getPromotions().size()); | |
| 77 | + assertNull(calculateRespBO.getCouponId()); | |
| 78 | + // 断言:订单价格 | |
| 79 | + assertEquals(7000, calculateRespBO.getPrice().getTotalPrice()); | |
| 80 | + assertEquals(0, calculateRespBO.getPrice().getDiscountPrice()); | |
| 81 | + assertEquals(0, calculateRespBO.getPrice().getDeliveryPrice()); | |
| 82 | + assertEquals(0, calculateRespBO.getPrice().getCouponPrice()); | |
| 83 | + assertEquals(0, calculateRespBO.getPrice().getPointPrice()); | |
| 84 | + assertEquals(7000, calculateRespBO.getPrice().getPayPrice()); | |
| 85 | + // 断言:SKU 1 | |
| 86 | + assertEquals(1001L, calculateRespBO.getItems().get(0).getSpuId()); | |
| 87 | + assertEquals(100L, calculateRespBO.getItems().get(0).getSkuId()); | |
| 88 | + assertEquals(1, calculateRespBO.getItems().get(0).getCount()); | |
| 89 | + assertNull(calculateRespBO.getItems().get(0).getCartId()); | |
| 90 | + assertTrue(calculateRespBO.getItems().get(0).getSelected()); | |
| 91 | + assertEquals(1000, calculateRespBO.getItems().get(0).getPrice()); | |
| 92 | + assertEquals(0, calculateRespBO.getItems().get(0).getDiscountPrice()); | |
| 93 | + assertEquals(0, calculateRespBO.getItems().get(0).getDeliveryPrice()); | |
| 94 | + assertEquals(0, calculateRespBO.getItems().get(0).getCouponPrice()); | |
| 95 | + assertEquals(0, calculateRespBO.getItems().get(0).getPointPrice()); | |
| 96 | + assertEquals(1000, calculateRespBO.getItems().get(0).getPayPrice()); | |
| 97 | + assertEquals("小菜", calculateRespBO.getItems().get(0).getSpuName()); | |
| 98 | + assertEquals("https://t.cn/1.png", calculateRespBO.getItems().get(0).getPicUrl()); | |
| 99 | + assertEquals(666L, calculateRespBO.getItems().get(0).getCategoryId()); | |
| 100 | + assertEquals(skuList.get(0).getProperties(), calculateRespBO.getItems().get(0).getProperties()); | |
| 101 | + // 断言:SKU 2 | |
| 102 | + assertEquals(1001L, calculateRespBO.getItems().get(1).getSpuId()); | |
| 103 | + assertEquals(200L, calculateRespBO.getItems().get(1).getSkuId()); | |
| 104 | + assertEquals(3, calculateRespBO.getItems().get(1).getCount()); | |
| 105 | + assertNull(calculateRespBO.getItems().get(1).getCartId()); | |
| 106 | + assertTrue(calculateRespBO.getItems().get(1).getSelected()); | |
| 107 | + assertEquals(2000, calculateRespBO.getItems().get(1).getPrice()); | |
| 108 | + assertEquals(0, calculateRespBO.getItems().get(1).getDiscountPrice()); | |
| 109 | + assertEquals(0, calculateRespBO.getItems().get(1).getDeliveryPrice()); | |
| 110 | + assertEquals(0, calculateRespBO.getItems().get(1).getCouponPrice()); | |
| 111 | + assertEquals(0, calculateRespBO.getItems().get(1).getPointPrice()); | |
| 112 | + assertEquals(6000, calculateRespBO.getItems().get(1).getPayPrice()); | |
| 113 | + assertEquals("小菜", calculateRespBO.getItems().get(1).getSpuName()); | |
| 114 | + assertEquals("https://t.cn/2.png", calculateRespBO.getItems().get(1).getPicUrl()); | |
| 115 | + assertEquals(666L, calculateRespBO.getItems().get(1).getCategoryId()); | |
| 116 | + assertEquals(skuList.get(1).getProperties(), calculateRespBO.getItems().get(1).getProperties()); | |
| 117 | + // 断言:SKU 3 | |
| 118 | + assertEquals(1001L, calculateRespBO.getItems().get(2).getSpuId()); | |
| 119 | + assertEquals(300L, calculateRespBO.getItems().get(2).getSkuId()); | |
| 120 | + assertEquals(6, calculateRespBO.getItems().get(2).getCount()); | |
| 121 | + assertEquals(233L, calculateRespBO.getItems().get(2).getCartId()); | |
| 122 | + assertFalse(calculateRespBO.getItems().get(2).getSelected()); | |
| 123 | + assertEquals(3000, calculateRespBO.getItems().get(2).getPrice()); | |
| 124 | + assertEquals(0, calculateRespBO.getItems().get(2).getDiscountPrice()); | |
| 125 | + assertEquals(0, calculateRespBO.getItems().get(2).getDeliveryPrice()); | |
| 126 | + assertEquals(0, calculateRespBO.getItems().get(2).getCouponPrice()); | |
| 127 | + assertEquals(0, calculateRespBO.getItems().get(2).getPointPrice()); | |
| 128 | + assertEquals(18000, calculateRespBO.getItems().get(2).getPayPrice()); | |
| 129 | + assertEquals("小菜", calculateRespBO.getItems().get(2).getSpuName()); | |
| 130 | + assertEquals("https://t.cn/3.png", calculateRespBO.getItems().get(2).getPicUrl()); | |
| 131 | + assertEquals(666L, calculateRespBO.getItems().get(2).getCategoryId()); | |
| 132 | + assertEquals(skuList.get(2).getProperties(), calculateRespBO.getItems().get(2).getProperties()); | |
| 133 | + } | |
| 134 | + | |
| 135 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price.calculator; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; | |
| 5 | +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; | |
| 6 | +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; | |
| 7 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; | |
| 9 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | |
| 10 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 11 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 12 | +import org.junit.jupiter.api.Test; | |
| 13 | +import org.mockito.InjectMocks; | |
| 14 | +import org.mockito.Mock; | |
| 15 | + | |
| 16 | +import java.util.ArrayList; | |
| 17 | + | |
| 18 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 19 | +import static java.util.Arrays.asList; | |
| 20 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 21 | +import static org.junit.jupiter.api.Assertions.assertTrue; | |
| 22 | +import static org.mockito.ArgumentMatchers.eq; | |
| 23 | +import static org.mockito.Mockito.when; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * {@link TradeCouponPriceCalculator} 的单元测试类 | |
| 27 | + * | |
| 28 | + * @author 芋道源码 | |
| 29 | + */ | |
| 30 | +public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { | |
| 31 | + | |
| 32 | + @InjectMocks | |
| 33 | + private TradeCouponPriceCalculator tradeCouponPriceCalculator; | |
| 34 | + | |
| 35 | + @Mock | |
| 36 | + private CouponApi couponApi; | |
| 37 | + | |
| 38 | + @Test | |
| 39 | + public void testCalculate() { | |
| 40 | + // 准备参数 | |
| 41 | + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() | |
| 42 | + .setUserId(233L).setCouponId(1024L) | |
| 43 | + .setItems(asList( | |
| 44 | + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配优惠劵 | |
| 45 | + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配优惠劵 | |
| 46 | + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true), // 不匹配优惠劵 | |
| 47 | + new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 匹配优惠劵,但是未选中 | |
| 48 | + )); | |
| 49 | + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() | |
| 50 | + .setPrice(new TradePriceCalculateRespBO.Price()) | |
| 51 | + .setPromotions(new ArrayList<>()) | |
| 52 | + .setItems(asList( | |
| 53 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) | |
| 54 | + .setPrice(100).setSpuId(1L), | |
| 55 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) | |
| 56 | + .setPrice(50).setSpuId(2L), | |
| 57 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) | |
| 58 | + .setPrice(30).setSpuId(3L), | |
| 59 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false) | |
| 60 | + .setPrice(60).setSpuId(1L) | |
| 61 | + )); | |
| 62 | + // 保证价格被初始化上 | |
| 63 | + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); | |
| 64 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 65 | + | |
| 66 | + // mock 方法(优惠劵 Coupon 信息) | |
| 67 | + CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节") | |
| 68 | + .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L)) | |
| 69 | + .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) | |
| 70 | + .setDiscountPercent(50).setDiscountLimitPrice(70)); | |
| 71 | + when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon); | |
| 72 | + | |
| 73 | + // 调用 | |
| 74 | + tradeCouponPriceCalculator.calculate(param, result); | |
| 75 | + // 断言 | |
| 76 | + assertEquals(result.getCouponId(), 1024L); | |
| 77 | + // 断言:Price 部分 | |
| 78 | + TradePriceCalculateRespBO.Price price = result.getPrice(); | |
| 79 | + assertEquals(price.getTotalPrice(), 470); | |
| 80 | + assertEquals(price.getDiscountPrice(), 0); | |
| 81 | + assertEquals(price.getPointPrice(), 0); | |
| 82 | + assertEquals(price.getDeliveryPrice(), 0); | |
| 83 | + assertEquals(price.getCouponPrice(), 70); | |
| 84 | + assertEquals(price.getPayPrice(), 400); | |
| 85 | + // 断言:SKU 1 | |
| 86 | + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); | |
| 87 | + assertEquals(orderItem01.getSkuId(), 10L); | |
| 88 | + assertEquals(orderItem01.getCount(), 2); | |
| 89 | + assertEquals(orderItem01.getPrice(), 100); | |
| 90 | + assertEquals(orderItem01.getDiscountPrice(), 0); | |
| 91 | + assertEquals(orderItem01.getDeliveryPrice(), 0); | |
| 92 | + assertEquals(orderItem01.getCouponPrice(), 40); | |
| 93 | + assertEquals(orderItem01.getPointPrice(), 0); | |
| 94 | + assertEquals(orderItem01.getPayPrice(), 160); | |
| 95 | + // 断言:SKU 2 | |
| 96 | + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); | |
| 97 | + assertEquals(orderItem02.getSkuId(), 20L); | |
| 98 | + assertEquals(orderItem02.getCount(), 3); | |
| 99 | + assertEquals(orderItem02.getPrice(), 50); | |
| 100 | + assertEquals(orderItem02.getDiscountPrice(), 0); | |
| 101 | + assertEquals(orderItem02.getDeliveryPrice(), 0); | |
| 102 | + assertEquals(orderItem02.getCouponPrice(), 30); | |
| 103 | + assertEquals(orderItem02.getPointPrice(), 0); | |
| 104 | + assertEquals(orderItem02.getPayPrice(), 120); | |
| 105 | + // 断言:SKU 3 | |
| 106 | + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); | |
| 107 | + assertEquals(orderItem03.getSkuId(), 30L); | |
| 108 | + assertEquals(orderItem03.getCount(), 4); | |
| 109 | + assertEquals(orderItem03.getPrice(), 30); | |
| 110 | + assertEquals(orderItem03.getDiscountPrice(), 0); | |
| 111 | + assertEquals(orderItem03.getCouponPrice(), 0); | |
| 112 | + assertEquals(orderItem03.getPointPrice(), 0); | |
| 113 | + assertEquals(orderItem03.getPayPrice(), 120); | |
| 114 | + // 断言:SKU 4 | |
| 115 | + TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3); | |
| 116 | + assertEquals(orderItem04.getSkuId(), 40L); | |
| 117 | + assertEquals(orderItem04.getCount(), 5); | |
| 118 | + assertEquals(orderItem04.getPrice(), 60); | |
| 119 | + assertEquals(orderItem04.getDiscountPrice(), 0); | |
| 120 | + assertEquals(orderItem04.getCouponPrice(), 0); | |
| 121 | + assertEquals(orderItem04.getPointPrice(), 0); | |
| 122 | + assertEquals(orderItem04.getPayPrice(), 300); | |
| 123 | + // 断言:Promotion 部分 | |
| 124 | + assertEquals(result.getPromotions().size(), 1); | |
| 125 | + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); | |
| 126 | + assertEquals(promotion01.getId(), 1024L); | |
| 127 | + assertEquals(promotion01.getName(), "程序员节"); | |
| 128 | + assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType()); | |
| 129 | + assertEquals(promotion01.getTotalPrice(), 350); | |
| 130 | + assertEquals(promotion01.getDiscountPrice(), 70); | |
| 131 | + assertTrue(promotion01.getMatch()); | |
| 132 | + assertEquals(promotion01.getDescription(), "优惠劵:省 0.70 元"); | |
| 133 | + assertEquals(promotion01.getItems().size(), 2); | |
| 134 | + TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 135 | + assertEquals(promotionItem011.getSkuId(), 10L); | |
| 136 | + assertEquals(promotionItem011.getTotalPrice(), 200); | |
| 137 | + assertEquals(promotionItem011.getDiscountPrice(), 40); | |
| 138 | + TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 139 | + assertEquals(promotionItem012.getSkuId(), 20L); | |
| 140 | + assertEquals(promotionItem012.getTotalPrice(), 150); | |
| 141 | + assertEquals(promotionItem012.getDiscountPrice(), 30); | |
| 142 | + } | |
| 143 | + | |
| 144 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price.calculator; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; | |
| 5 | +import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; | |
| 6 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; | |
| 7 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 9 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 10 | +import org.junit.jupiter.api.Test; | |
| 11 | +import org.mockito.InjectMocks; | |
| 12 | +import org.mockito.Mock; | |
| 13 | + | |
| 14 | +import java.util.ArrayList; | |
| 15 | + | |
| 16 | +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 17 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 18 | +import static java.util.Arrays.asList; | |
| 19 | +import static org.junit.jupiter.api.Assertions.*; | |
| 20 | +import static org.mockito.ArgumentMatchers.eq; | |
| 21 | +import static org.mockito.Mockito.when; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * {@link TradeDiscountActivityPriceCalculator} 的单元测试类 | |
| 25 | + * | |
| 26 | + * @author 芋道源码 | |
| 27 | + */ | |
| 28 | +public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest { | |
| 29 | + | |
| 30 | + @InjectMocks | |
| 31 | + private TradeDiscountActivityPriceCalculator tradeDiscountActivityPriceCalculator; | |
| 32 | + | |
| 33 | + @Mock | |
| 34 | + private DiscountActivityApi discountActivityApi; | |
| 35 | + | |
| 36 | + @Test | |
| 37 | + public void testCalculate() { | |
| 38 | + // 准备参数 | |
| 39 | + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() | |
| 40 | + .setItems(asList( | |
| 41 | + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中 | |
| 42 | + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中 | |
| 43 | + )); | |
| 44 | + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() | |
| 45 | + .setPrice(new TradePriceCalculateRespBO.Price()) | |
| 46 | + .setPromotions(new ArrayList<>()) | |
| 47 | + .setItems(asList( | |
| 48 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) | |
| 49 | + .setPrice(100), | |
| 50 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false) | |
| 51 | + .setPrice(50) | |
| 52 | + )); | |
| 53 | + // 保证价格被初始化上 | |
| 54 | + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); | |
| 55 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 56 | + | |
| 57 | + // mock 方法(限时折扣活动) | |
| 58 | + when(discountActivityApi.getMatchDiscountProductList(eq(asSet(10L, 20L)))).thenReturn(asList( | |
| 59 | + randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(1000L) | |
| 60 | + .setActivityName("活动 1000 号").setSkuId(10L) | |
| 61 | + .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(40)), | |
| 62 | + randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(2000L) | |
| 63 | + .setActivityName("活动 2000 号").setSkuId(20L) | |
| 64 | + .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(60)) | |
| 65 | + )); | |
| 66 | + // 10L: 100 * 2 - 40 * 2 = 120 | |
| 67 | + // 20L:50 * 3 - 50 * 3 * 0.4 = 90 | |
| 68 | + | |
| 69 | + // 调用 | |
| 70 | + tradeDiscountActivityPriceCalculator.calculate(param, result); | |
| 71 | + // 断言:Price 部分 | |
| 72 | + TradePriceCalculateRespBO.Price price = result.getPrice(); | |
| 73 | + assertEquals(price.getTotalPrice(), 200); | |
| 74 | + assertEquals(price.getDiscountPrice(), 80); | |
| 75 | + assertEquals(price.getPointPrice(), 0); | |
| 76 | + assertEquals(price.getDeliveryPrice(), 0); | |
| 77 | + assertEquals(price.getCouponPrice(), 0); | |
| 78 | + assertEquals(price.getPayPrice(), 120); | |
| 79 | + assertNull(result.getCouponId()); | |
| 80 | + // 断言:SKU 1 | |
| 81 | + assertEquals(result.getItems().size(), 2); | |
| 82 | + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); | |
| 83 | + assertEquals(orderItem01.getSkuId(), 10L); | |
| 84 | + assertEquals(orderItem01.getCount(), 2); | |
| 85 | + assertEquals(orderItem01.getPrice(), 100); | |
| 86 | + assertEquals(orderItem01.getDiscountPrice(), 80); | |
| 87 | + assertEquals(orderItem01.getDeliveryPrice(), 0); | |
| 88 | + assertEquals(orderItem01.getCouponPrice(), 0); | |
| 89 | + assertEquals(orderItem01.getPointPrice(), 0); | |
| 90 | + assertEquals(orderItem01.getPayPrice(), 120); | |
| 91 | + // 断言:SKU 2 | |
| 92 | + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); | |
| 93 | + assertEquals(orderItem02.getSkuId(), 20L); | |
| 94 | + assertEquals(orderItem02.getCount(), 3); | |
| 95 | + assertEquals(orderItem02.getPrice(), 50); | |
| 96 | + assertEquals(orderItem02.getDiscountPrice(), 60); | |
| 97 | + assertEquals(orderItem02.getDeliveryPrice(), 0); | |
| 98 | + assertEquals(orderItem02.getCouponPrice(), 0); | |
| 99 | + assertEquals(orderItem02.getPointPrice(), 0); | |
| 100 | + assertEquals(orderItem02.getPayPrice(), 90); | |
| 101 | + // 断言:Promotion 部分 | |
| 102 | + assertEquals(result.getPromotions().size(), 1); | |
| 103 | + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); | |
| 104 | + assertEquals(promotion01.getId(), 1000L); | |
| 105 | + assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 106 | + assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()); | |
| 107 | + assertEquals(promotion01.getTotalPrice(), 200); | |
| 108 | + assertEquals(promotion01.getDiscountPrice(), 80); | |
| 109 | + assertTrue(promotion01.getMatch()); | |
| 110 | + assertEquals(promotion01.getDescription(), "限时折扣:省 0.80 元"); | |
| 111 | + TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0); | |
| 112 | + assertEquals(promotion01.getItems().size(), 1); | |
| 113 | + assertEquals(promotionItem01.getSkuId(), 10L); | |
| 114 | + assertEquals(promotionItem01.getTotalPrice(), 200); | |
| 115 | + assertEquals(promotionItem01.getDiscountPrice(), 80); | |
| 116 | + } | |
| 117 | + | |
| 118 | +} | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.trade.service.price.calculator; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; | |
| 5 | +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; | |
| 6 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; | |
| 7 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; | |
| 9 | +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; | |
| 10 | +import org.junit.jupiter.api.Test; | |
| 11 | +import org.mockito.InjectMocks; | |
| 12 | +import org.mockito.Mock; | |
| 13 | + | |
| 14 | +import java.util.ArrayList; | |
| 15 | + | |
| 16 | +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | |
| 17 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | |
| 18 | +import static java.util.Arrays.asList; | |
| 19 | +import static java.util.Collections.singletonList; | |
| 20 | +import static org.junit.jupiter.api.Assertions.*; | |
| 21 | +import static org.mockito.ArgumentMatchers.eq; | |
| 22 | +import static org.mockito.Mockito.when; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * {@link TradeRewardActivityPriceCalculator} 的单元测试类 | |
| 26 | + * | |
| 27 | + * @author 芋道源码 | |
| 28 | + */ | |
| 29 | +public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest { | |
| 30 | + | |
| 31 | + @InjectMocks | |
| 32 | + private TradeRewardActivityPriceCalculator tradeRewardActivityPriceCalculator; | |
| 33 | + | |
| 34 | + @Mock | |
| 35 | + private RewardActivityApi rewardActivityApi; | |
| 36 | + | |
| 37 | + @Test | |
| 38 | + public void testCalculate_match() { | |
| 39 | + // 准备参数 | |
| 40 | + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() | |
| 41 | + .setItems(asList( | |
| 42 | + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动 1 | |
| 43 | + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配活动 1 | |
| 44 | + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) // 匹配活动 2 | |
| 45 | + )); | |
| 46 | + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() | |
| 47 | + .setPrice(new TradePriceCalculateRespBO.Price()) | |
| 48 | + .setPromotions(new ArrayList<>()) | |
| 49 | + .setItems(asList( | |
| 50 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) | |
| 51 | + .setPrice(100).setSpuId(1L), | |
| 52 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) | |
| 53 | + .setPrice(50).setSpuId(2L), | |
| 54 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) | |
| 55 | + .setPrice(30).setSpuId(3L) | |
| 56 | + )); | |
| 57 | + // 保证价格被初始化上 | |
| 58 | + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); | |
| 59 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 60 | + | |
| 61 | + // mock 方法(限时折扣 DiscountActivity 信息) | |
| 62 | + when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList( | |
| 63 | + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") | |
| 64 | + .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) | |
| 65 | + .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))), | |
| 66 | + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") | |
| 67 | + .setSpuIds(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) | |
| 68 | + .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10), | |
| 69 | + new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个 | |
| 70 | + new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)))) | |
| 71 | + )); | |
| 72 | + | |
| 73 | + // 调用 | |
| 74 | + tradeRewardActivityPriceCalculator.calculate(param, result); | |
| 75 | + // 断言 Order 部分 | |
| 76 | + TradePriceCalculateRespBO.Price price = result.getPrice(); | |
| 77 | + assertEquals(price.getTotalPrice(), 470); | |
| 78 | + assertEquals(price.getDiscountPrice(), 130); | |
| 79 | + assertEquals(price.getPointPrice(), 0); | |
| 80 | + assertEquals(price.getDeliveryPrice(), 0); | |
| 81 | + assertEquals(price.getCouponPrice(), 0); | |
| 82 | + assertEquals(price.getPayPrice(), 340); | |
| 83 | + assertNull(result.getCouponId()); | |
| 84 | + // 断言:SKU 1 | |
| 85 | + assertEquals(result.getItems().size(), 3); | |
| 86 | + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); | |
| 87 | + assertEquals(orderItem01.getSkuId(), 10L); | |
| 88 | + assertEquals(orderItem01.getCount(), 2); | |
| 89 | + assertEquals(orderItem01.getPrice(), 100); | |
| 90 | + assertEquals(orderItem01.getDiscountPrice(), 40); | |
| 91 | + assertEquals(orderItem01.getDeliveryPrice(), 0); | |
| 92 | + assertEquals(orderItem01.getCouponPrice(), 0); | |
| 93 | + assertEquals(orderItem01.getPointPrice(), 0); | |
| 94 | + assertEquals(orderItem01.getPayPrice(), 160); | |
| 95 | + // 断言:SKU 2 | |
| 96 | + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); | |
| 97 | + assertEquals(orderItem02.getSkuId(), 20L); | |
| 98 | + assertEquals(orderItem02.getCount(), 3); | |
| 99 | + assertEquals(orderItem02.getPrice(), 50); | |
| 100 | + assertEquals(orderItem02.getDiscountPrice(), 30); | |
| 101 | + assertEquals(orderItem02.getDeliveryPrice(), 0); | |
| 102 | + assertEquals(orderItem02.getCouponPrice(), 0); | |
| 103 | + assertEquals(orderItem02.getPointPrice(), 0); | |
| 104 | + assertEquals(orderItem02.getPayPrice(), 120); | |
| 105 | + // 断言:SKU 3 | |
| 106 | + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); | |
| 107 | + assertEquals(orderItem03.getSkuId(), 30L); | |
| 108 | + assertEquals(orderItem03.getCount(), 4); | |
| 109 | + assertEquals(orderItem03.getPrice(), 30); | |
| 110 | + assertEquals(orderItem03.getDiscountPrice(), 60); | |
| 111 | + assertEquals(orderItem03.getDeliveryPrice(), 0); | |
| 112 | + assertEquals(orderItem03.getCouponPrice(), 0); | |
| 113 | + assertEquals(orderItem03.getPointPrice(), 0); | |
| 114 | + assertEquals(orderItem03.getPayPrice(), 60); | |
| 115 | + // 断言:Promotion 部分(第一个) | |
| 116 | + assertEquals(result.getPromotions().size(), 2); | |
| 117 | + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); | |
| 118 | + assertEquals(promotion01.getId(), 1000L); | |
| 119 | + assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 120 | + assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 121 | + assertEquals(promotion01.getTotalPrice(), 350); | |
| 122 | + assertEquals(promotion01.getDiscountPrice(), 70); | |
| 123 | + assertTrue(promotion01.getMatch()); | |
| 124 | + assertEquals(promotion01.getDescription(), "满减送:省 0.70 元"); | |
| 125 | + assertEquals(promotion01.getItems().size(), 2); | |
| 126 | + TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 127 | + assertEquals(promotionItem011.getSkuId(), 10L); | |
| 128 | + assertEquals(promotionItem011.getTotalPrice(), 200); | |
| 129 | + assertEquals(promotionItem011.getDiscountPrice(), 40); | |
| 130 | + TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 131 | + assertEquals(promotionItem012.getSkuId(), 20L); | |
| 132 | + assertEquals(promotionItem012.getTotalPrice(), 150); | |
| 133 | + assertEquals(promotionItem012.getDiscountPrice(), 30); | |
| 134 | + // 断言:Promotion 部分(第二个) | |
| 135 | + TradePriceCalculateRespBO.Promotion promotion02 = result.getPromotions().get(1); | |
| 136 | + assertEquals(promotion02.getId(), 2000L); | |
| 137 | + assertEquals(promotion02.getName(), "活动 2000 号"); | |
| 138 | + assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 139 | + assertEquals(promotion02.getTotalPrice(), 120); | |
| 140 | + assertEquals(promotion02.getDiscountPrice(), 60); | |
| 141 | + assertTrue(promotion02.getMatch()); | |
| 142 | + assertEquals(promotion02.getDescription(), "满减送:省 0.60 元"); | |
| 143 | + TradePriceCalculateRespBO.PromotionItem promotionItem02 = promotion02.getItems().get(0); | |
| 144 | + assertEquals(promotion02.getItems().size(), 1); | |
| 145 | + assertEquals(promotionItem02.getSkuId(), 30L); | |
| 146 | + assertEquals(promotionItem02.getTotalPrice(), 120); | |
| 147 | + assertEquals(promotionItem02.getDiscountPrice(), 60); | |
| 148 | + } | |
| 149 | + | |
| 150 | + @Test | |
| 151 | + public void testCalculate_notMatch() { | |
| 152 | + // 准备参数 | |
| 153 | + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() | |
| 154 | + .setItems(asList( | |
| 155 | + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), | |
| 156 | + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), | |
| 157 | + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) | |
| 158 | + )); | |
| 159 | + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() | |
| 160 | + .setPrice(new TradePriceCalculateRespBO.Price()) | |
| 161 | + .setPromotions(new ArrayList<>()) | |
| 162 | + .setItems(asList( | |
| 163 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) | |
| 164 | + .setPrice(100).setSpuId(1L), | |
| 165 | + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) | |
| 166 | + .setPrice(50).setSpuId(2L) | |
| 167 | + )); | |
| 168 | + // 保证价格被初始化上 | |
| 169 | + TradePriceCalculatorHelper.recountPayPrice(result.getItems()); | |
| 170 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 171 | + | |
| 172 | + // mock 方法(限时折扣 DiscountActivity 信息) | |
| 173 | + when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L)))).thenReturn(singletonList( | |
| 174 | + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") | |
| 175 | + .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) | |
| 176 | + .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70)))) | |
| 177 | + )); | |
| 178 | + | |
| 179 | + // 调用 | |
| 180 | + tradeRewardActivityPriceCalculator.calculate(param, result); | |
| 181 | + // 断言 Order 部分 | |
| 182 | + TradePriceCalculateRespBO.Price price = result.getPrice(); | |
| 183 | + assertEquals(price.getTotalPrice(), 350); | |
| 184 | + assertEquals(price.getDiscountPrice(), 0); | |
| 185 | + assertEquals(price.getPointPrice(), 0); | |
| 186 | + assertEquals(price.getDeliveryPrice(), 0); | |
| 187 | + assertEquals(price.getCouponPrice(), 0); | |
| 188 | + assertEquals(price.getPayPrice(), 350); | |
| 189 | + assertNull(result.getCouponId()); | |
| 190 | + // 断言:SKU 1 | |
| 191 | + assertEquals(result.getItems().size(), 2); | |
| 192 | + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); | |
| 193 | + assertEquals(orderItem01.getSkuId(), 10L); | |
| 194 | + assertEquals(orderItem01.getCount(), 2); | |
| 195 | + assertEquals(orderItem01.getPrice(), 100); | |
| 196 | + assertEquals(orderItem01.getDiscountPrice(), 0); | |
| 197 | + assertEquals(orderItem01.getDeliveryPrice(), 0); | |
| 198 | + assertEquals(orderItem01.getCouponPrice(), 0); | |
| 199 | + assertEquals(orderItem01.getPointPrice(), 0); | |
| 200 | + assertEquals(orderItem01.getPayPrice(), 200); | |
| 201 | + // 断言:SKU 2 | |
| 202 | + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); | |
| 203 | + assertEquals(orderItem02.getSkuId(), 20L); | |
| 204 | + assertEquals(orderItem02.getCount(), 3); | |
| 205 | + assertEquals(orderItem02.getPrice(), 50); | |
| 206 | + assertEquals(orderItem02.getDiscountPrice(), 0); | |
| 207 | + assertEquals(orderItem02.getDeliveryPrice(), 0); | |
| 208 | + assertEquals(orderItem02.getCouponPrice(), 0); | |
| 209 | + assertEquals(orderItem02.getPointPrice(), 0); | |
| 210 | + assertEquals(orderItem02.getPayPrice(), 150); | |
| 211 | + // 断言 Promotion 部分 | |
| 212 | + assertEquals(result.getPromotions().size(), 1); | |
| 213 | + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); | |
| 214 | + assertEquals(promotion01.getId(), 1000L); | |
| 215 | + assertEquals(promotion01.getName(), "活动 1000 号"); | |
| 216 | + assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); | |
| 217 | + assertEquals(promotion01.getTotalPrice(), 350); | |
| 218 | + assertEquals(promotion01.getDiscountPrice(), 0); | |
| 219 | + assertFalse(promotion01.getMatch()); | |
| 220 | + assertEquals(promotion01.getDescription(), "TODO"); // TODO 芋艿:后面再想想 | |
| 221 | + assertEquals(promotion01.getItems().size(), 2); | |
| 222 | + TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); | |
| 223 | + assertEquals(promotionItem011.getSkuId(), 10L); | |
| 224 | + assertEquals(promotionItem011.getTotalPrice(), 200); | |
| 225 | + assertEquals(promotionItem011.getDiscountPrice(), 0); | |
| 226 | + TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); | |
| 227 | + assertEquals(promotionItem012.getSkuId(), 20L); | |
| 228 | + assertEquals(promotionItem012.getTotalPrice(), 150); | |
| 229 | + assertEquals(promotionItem012.getDiscountPrice(), 0); | |
| 230 | + } | |
| 231 | + | |
| 232 | +} | ... | ... |
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java
| ... | ... | @@ -25,4 +25,9 @@ public class AddressApiImpl implements AddressApi { |
| 25 | 25 | return AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id)); |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | + @Override | |
| 29 | + public AddressRespDTO getDefaultAddress(Long userId) { | |
| 30 | + return AddressConvert.INSTANCE.convert02(addressService.getDefaultUserAddress(userId)); | |
| 31 | + } | |
| 32 | + | |
| 28 | 33 | } | ... | ... |
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/MemberAddressDO.java