Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Y
yudao-cloud
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
hblj
yudao-cloud
Commits
5122b68a
提交
5122b68a
authored
8月 14, 2020
作者:
YunaiV
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Price 价格服务的编写
上级
ed71f5e9
显示空白字符变更
内嵌
并排
正在显示
32 个修改的文件
包含
857 行增加
和
427 行删除
+857
-427
UsersCartController.java
.../mall/order/rest/controller/cart/UsersCartController.java
+1
-4
CartServiceImpl.java
...va/cn/iocoder/mall/order/biz/service/CartServiceImpl.java
+37
-334
ProductSkuDetailFieldEnum.java
...l/productservice/enums/sku/ProductSkuDetailFieldEnum.java
+28
-0
ProductSkuListQueryReqDTO.java
...productservice/rpc/sku/dto/ProductSkuListQueryReqDTO.java
+8
-0
ProductSpuRpc.java
...cn/iocoder/mall/productservice/rpc/spu/ProductSpuRpc.java
+1
-1
ProductSkuManager.java
...er/mall/productservice/manager/sku/ProductSkuManager.java
+7
-0
ProductSpuManager.java
...er/mall/productservice/manager/spu/ProductSpuManager.java
+1
-1
ProductSpuRpcImpl.java
...ocoder/mall/productservice/rpc/spu/ProductSpuRpcImpl.java
+1
-1
ProductSpuService.java
...er/mall/productservice/service/spu/ProductSpuService.java
+2
-1
CouponCardRespDTO.java
.../promotion/api/rpc/coupon/dto/card/CouponCardRespDTO.java
+3
-5
CouponTemplateRespDTO.java
...on/api/rpc/coupon/dto/template/CouponTemplateRespDTO.java
+4
-3
PriceRpc.java
...ava/cn/iocoder/mall/promotion/api/rpc/price/PriceRpc.java
+6
-6
PriceProductCalcReqDTO.java
...l/promotion/api/rpc/price/dto/PriceProductCalcReqDTO.java
+1
-1
PriceProductCalcRespDTO.java
.../promotion/api/rpc/price/dto/PriceProductCalcRespDTO.java
+28
-9
CouponCardConvert.java
...ll/promotionservice/convert/coupon/CouponCardConvert.java
+2
-8
CouponCardConvert.java
...omotionservice/convert/coupon/card/CouponCardConvert.java
+15
-0
CouponTemplateConvert.java
...ionservice/convert/coupon/card/CouponTemplateConvert.java
+13
-7
PriceManager.java
...der/mall/promotionservice/manager/price/PriceManager.java
+300
-37
package-info.java
...va/cn/iocoder/mall/promotionservice/rpc/package-info.java
+1
-0
PriceRpcImpl.java
...iocoder/mall/promotionservice/rpc/price/PriceRpcImpl.java
+24
-0
CouponCardService.java
...ll/promotionservice/service/coupon/CouponCardService.java
+34
-0
CouponService.java
...r/mall/promotionservice/service/coupon/CouponService.java
+3
-8
CouponTemplateService.java
...romotionservice/service/coupon/CouponTemplateService.java
+24
-0
application.yaml
...promotion-service-app/src/main/resources/application.yaml
+1
-1
package-info.java
...n/iocoder/mall/promotionservice/manager/package-info.java
+1
-0
PriceManagerTest.java
...mall/promotionservice/manager/price/PriceManagerTest.java
+30
-0
pom.xml
shop-web-app/pom.xml
+6
-0
CartController.java
...iocoder/mall/shopweb/controller/order/CartController.java
+7
-0
CartDetailVO.java
...r/mall/shopweb/controller/order/vo/cart/CartDetailVO.java
+213
-0
CartConvert.java
...va/cn/iocoder/mall/shopweb/convert/order/CartConvert.java
+11
-0
CartManager.java
.../iocoder/mall/shopweb/manager/order/cart/CartManager.java
+42
-0
application.yml
shop-web-app/src/main/resources/application.yml
+2
-0
没有找到文件。
order/order-rest/src/main/java/cn/iocoder/mall/order/rest/controller/cart/UsersCartController.java
浏览文件 @
5122b68a
...
...
@@ -38,10 +38,7 @@ public class UsersCartController {
// }
//
// @GetMapping("/list")
// public CommonResult<UsersCartDetailVO> list() { // TODO 芋艿,先暂用这个 VO 。等促销活动出来后,做调整
// return getCartDetail();
// }
//
// private CommonResult<UsersCartDetailVO> getCartDetail() {
// // 获得购物车中选中的
...
...
order/order-service-impl/src/main/java/cn/iocoder/mall/order/biz/service/CartServiceImpl.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
order
.
biz
.
service
;
import
cn.iocoder.common.framework.enums.CommonStatusEnum
;
import
cn.iocoder.common.framework.util.ServiceExceptionUtil
;
import
cn.iocoder.mall.order.api.CartService
;
import
cn.iocoder.mall.order.api.bo.CalcOrderPriceBO
;
import
cn.iocoder.mall.order.api.bo.CalcSkuPriceBO
;
import
cn.iocoder.mall.order.api.bo.CartItemBO
;
import
cn.iocoder.mall.order.api.constant.CartItemStatusEnum
;
import
cn.iocoder.mall.order.api.constant.OrderErrorCodeEnum
;
import
cn.iocoder.mall.order.api.dto.CalcOrderPriceDTO
;
import
cn.iocoder.mall.order.biz.convert.CartConvert
;
import
cn.iocoder.mall.order.biz.dao.CartMapper
;
import
cn.iocoder.mall.order.biz.dataobject.CartItemDO
;
import
cn.iocoder.mall.product.api.ProductSpuService
;
import
cn.iocoder.mall.product.api.bo.ProductSkuBO
;
import
cn.iocoder.mall.product.api.bo.ProductSkuDetailBO
;
import
cn.iocoder.mall.promotion.api.CouponService
;
import
cn.iocoder.mall.promotion.api.PromotionActivityService
;
import
cn.iocoder.mall.promotion.api.bo.CouponCardDetailBO
;
import
cn.iocoder.mall.promotion.api.bo.PromotionActivityBO
;
import
cn.iocoder.mall.promotion.api.enums.*
;
import
org.apache.dubbo.config.annotation.Reference
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.Assert
;
import
java.util.*
;
import
java.util.stream.Collectors
;
/**
* 购物车服务 Service 实现类
*/
@Service
@org
.
apache
.
dubbo
.
config
.
annotation
.
Service
(
validation
=
"true"
,
version
=
"${dubbo.provider.CartService.version}"
)
public
class
CartServiceImpl
implements
CartService
{
@Reference
(
validation
=
"true"
,
version
=
"${dubbo.consumer.PromotionActivityService.version}"
)
private
ProductSpuService
productSpuService
;
@Reference
(
validation
=
"true"
,
version
=
"${dubbo.consumer.PromotionActivityService.version}"
)
private
PromotionActivityService
promotionActivityService
;
@Reference
(
validation
=
"true"
,
version
=
"${dubbo.consumer.CouponService.version}"
)
private
CouponService
couponService
;
@Autowired
private
CartMapper
cartMapper
;
@Override
public
CalcOrderPriceBO
calcOrderPrice
(
CalcOrderPriceDTO
calcOrderPriceDTO
)
{
// 2. 计算【限时折扣】促销
// 3. 计算【满减送】促销
List
<
CalcOrderPriceBO
.
ItemGroup
>
itemGroups
=
groupByFullPrivilege
(
items
,
activityList
);
calcOrderPriceBO
.
setItemGroups
(
itemGroups
);
// 4. 计算优惠劵
if
(
calcOrderPriceDTO
.
getCouponCardId
()
!=
null
)
{
Integer
result
=
modifyPriceByCouponCard
(
calcOrderPriceDTO
.
getUserId
(),
calcOrderPriceDTO
.
getCouponCardId
(),
itemGroups
);
calcOrderPriceBO
.
setCouponCardDiscountTotal
(
result
);
}
// 5. 计算最终的价格
int
buyTotal
=
0
;
int
discountTotal
=
0
;
int
presentTotal
=
0
;
for
(
CalcOrderPriceBO
.
ItemGroup
itemGroup
:
calcOrderPriceBO
.
getItemGroups
())
{
buyTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
item
->
item
.
getSelected
()
?
item
.
getBuyTotal
()
:
0
).
sum
();
discountTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
item
->
item
.
getSelected
()
?
item
.
getDiscountTotal
()
:
0
).
sum
();
presentTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
item
->
item
.
getSelected
()
?
item
.
getPresentTotal
()
:
0
).
sum
();
}
Assert
.
isTrue
(
buyTotal
-
discountTotal
==
presentTotal
,
String
.
format
(
"价格合计( %d - %d == %d )不正确"
,
buyTotal
,
discountTotal
,
presentTotal
));
calcOrderPriceBO
.
setFee
(
new
CalcOrderPriceBO
.
Fee
(
buyTotal
,
discountTotal
,
0
,
presentTotal
));
// 返回
return
calcOrderPriceBO
;
}
@Override
@SuppressWarnings
(
"Duplicates"
)
public
CalcSkuPriceBO
calcSkuPrice
(
Integer
skuId
)
{
// 查询 SKU 是否合法
ProductSkuBO
sku
=
productSpuService
.
getProductSku
(
skuId
);
if
(
sku
==
null
||
CommonStatusEnum
.
DISABLE
.
getValue
().
equals
(
sku
.
getStatus
()))
{
// sku 被禁用
throw
ServiceExceptionUtil
.
exception
(
OrderErrorCodeEnum
.
CARD_ITEM_SKU_NOT_FOUND
.
getCode
());
}
// 查询促销活动
List
<
PromotionActivityBO
>
activityList
=
promotionActivityService
.
getPromotionActivityListBySpuId
(
sku
.
getSpuId
(),
Arrays
.
asList
(
PromotionActivityStatusEnum
.
WAIT
.
getValue
(),
PromotionActivityStatusEnum
.
RUN
.
getValue
()));
if
(
activityList
.
isEmpty
())
{
// 如果无促销活动,则直接返回默认结果即可
return
new
CalcSkuPriceBO
().
setOriginalPrice
(
sku
.
getPrice
()).
setBuyPrice
(
sku
.
getPrice
());
}
// 如果有促销活动,则开始做计算 TODO 芋艿,因为现在暂时只有限时折扣 + 满减送。所以写的比较简单先
PromotionActivityBO
fullPrivilege
=
findPromotionActivityByType
(
activityList
,
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
);
PromotionActivityBO
timeLimitedDiscount
=
findPromotionActivityByType
(
activityList
,
PromotionActivityTypeEnum
.
TIME_LIMITED_DISCOUNT
);
Integer
presentPrice
=
calcSkuPriceByTimeLimitDiscount
(
sku
,
timeLimitedDiscount
);
// 返回结果
return
new
CalcSkuPriceBO
().
setFullPrivilege
(
fullPrivilege
).
setTimeLimitedDiscount
(
timeLimitedDiscount
)
.
setOriginalPrice
(
sku
.
getPrice
()).
setBuyPrice
(
presentPrice
);
}
public
class
CartServiceImpl
{
private
List
<
CalcOrderPriceBO
.
ItemGroup
>
groupByFullPrivilege
(
List
<
CalcOrderPriceBO
.
Item
>
items
,
List
<
PromotionActivityBO
>
activityList
)
{
List
<
CalcOrderPriceBO
.
ItemGroup
>
itemGroups
=
new
ArrayList
<>();
// 获得所有满减送促销
List
<
PromotionActivityBO
>
fullPrivileges
=
activityList
.
stream
()
.
filter
(
activity
->
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()))
.
collect
(
Collectors
.
toList
());
// 基于满减送促销,进行分组
if
(!
fullPrivileges
.
isEmpty
())
{
items
=
new
ArrayList
<>(
items
);
// 因为下面会修改数组,进行浅拷贝,避免影响传入的 items 。
for
(
PromotionActivityBO
fullPrivilege
:
fullPrivileges
)
{
// 创建 fullPrivilege 对应的分组
CalcOrderPriceBO
.
ItemGroup
itemGroup
=
new
CalcOrderPriceBO
.
ItemGroup
()
.
setActivity
(
fullPrivilege
)
.
setItems
(
new
ArrayList
<>());
// 筛选商品到分组中
for
(
Iterator
<
CalcOrderPriceBO
.
Item
>
iterator
=
items
.
iterator
();
iterator
.
hasNext
();
)
{
CalcOrderPriceBO
.
Item
item
=
iterator
.
next
();
if
(!
isSpuMatchFullPrivilege
(
item
.
getSpu
().
getId
(),
fullPrivilege
))
{
continue
;
}
itemGroup
.
getItems
().
add
(
item
);
iterator
.
remove
();
}
// 如果匹配到,则添加到 itemGroups 中
if
(!
itemGroup
.
getItems
().
isEmpty
())
{
itemGroups
.
add
(
itemGroup
);
}
}
}
// 处理未参加活动的商品,形成一个分组
if
(!
items
.
isEmpty
())
{
itemGroups
.
add
(
new
CalcOrderPriceBO
.
ItemGroup
().
setItems
(
items
));
}
// 计算每个分组的价格
for
(
CalcOrderPriceBO
.
ItemGroup
itemGroup
:
itemGroups
)
{
itemGroup
.
setActivityDiscountTotal
(
calcSkuPriceByFullPrivilege
(
itemGroup
));
}
// 返回结果
return
itemGroups
;
}
private
Integer
modifyPriceByCouponCard
(
Integer
userId
,
Integer
couponCardId
,
List
<
CalcOrderPriceBO
.
ItemGroup
>
itemGroups
)
{
Assert
.
isTrue
(
couponCardId
!=
null
,
"优惠劵编号不能为空"
);
// 查询优惠劵
CouponCardDetailBO
couponCard
=
couponService
.
getCouponCardDetail
(
userId
,
couponCardId
);
// 获得匹配的商品
List
<
CalcOrderPriceBO
.
Item
>
items
=
new
ArrayList
<>();
if
(
RangeTypeEnum
.
ALL
.
getValue
().
equals
(
couponCard
.
getRangeType
()))
{
// totalPrice = spus.stream().mapToInt(spu -> spu.getPrice() * spu.getQuantity()).sum();
itemGroups
.
forEach
(
itemGroup
->
items
.
addAll
(
itemGroup
.
getItems
()));
}
else
if
(
RangeTypeEnum
.
PRODUCT_INCLUDE_PART
.
getValue
().
equals
(
couponCard
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(
couponCard
.
getRangeValues
().
contains
(
item
.
getSpu
().
getId
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
PRODUCT_EXCLUDE_PART
.
getValue
().
equals
(
couponCard
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(!
couponCard
.
getRangeValues
().
contains
(
item
.
getSpu
().
getId
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
CATEGORY_INCLUDE_PART
.
getValue
().
equals
(
couponCard
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(
couponCard
.
getRangeValues
().
contains
(
item
.
getSpu
().
getCid
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
CATEGORY_EXCLUDE_PART
.
getValue
().
equals
(
couponCard
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(!
couponCard
.
getRangeValues
().
contains
(
item
.
getSpu
().
getCid
()))
{
items
.
add
(
item
);
}
}));
}
// 判断是否符合条件
int
originalTotal
=
items
.
stream
().
mapToInt
(
CalcOrderPriceBO
.
Item
::
getPresentTotal
).
sum
();
// 此处,指的是以优惠劵视角的原价
if
(
originalTotal
==
0
||
originalTotal
<
couponCard
.
getPriceAvailable
())
{
throw
ServiceExceptionUtil
.
exception
(
PromotionErrorCodeConstants
.
COUPON_CARD_NOT_MATCH
.
getCode
());
// TODO 芋艿,这种情况,会出现错误码的提示,无法格式化出来。另外,这块的最佳实践,找人讨论下。
}
// 计算价格
// 获得到优惠信息,进行价格计算
int
presentTotal
;
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
couponCard
.
getPreferentialType
()))
{
// 减价
// 计算循环次数。这样,后续优惠的金额就是相乘了
presentTotal
=
originalTotal
-
couponCard
.
getPriceOff
();
Assert
.
isTrue
(
presentTotal
>
0
,
"计算后,价格为负数:"
+
presentTotal
);
}
else
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
couponCard
.
getPreferentialType
()))
{
// 打折
presentTotal
=
originalTotal
*
couponCard
.
getPercentOff
()
/
100
;
if
(
couponCard
.
getDiscountPriceLimit
()
!=
null
// 空,代表不限制优惠上限
&&
originalTotal
-
presentTotal
>
couponCard
.
getDiscountPriceLimit
())
{
presentTotal
=
originalTotal
-
couponCard
.
getDiscountPriceLimit
();
}
}
else
{
throw
new
IllegalArgumentException
(
String
.
format
(
"优惠劵(%s) 的优惠类型不正确"
,
couponCard
.
toString
()));
}
int
discountTotal
=
originalTotal
-
presentTotal
;
Assert
.
isTrue
(
discountTotal
>
0
,
"计算后,不产生优惠:"
+
discountTotal
);
// 按比例,拆分 presentTotal
splitDiscountPriceToItems
(
items
,
discountTotal
,
presentTotal
);
// 返回优惠金额
return
originalTotal
-
presentTotal
;
}
/**
* 计算指定 SKU 在限时折扣下的价格
*
* @param sku SKU
* @param timeLimitedDiscount 限时折扣促销。
* 传入的该活动,要保证该 SKU 在该促销下一定有优惠。
* @return 计算后的价格
*/
private
Integer
calcSkuPriceByTimeLimitDiscount
(
ProductSkuBO
sku
,
PromotionActivityBO
timeLimitedDiscount
)
{
if
(
timeLimitedDiscount
==
null
)
{
return
sku
.
getPrice
();
}
// 获得对应的优惠项
PromotionActivityBO
.
TimeLimitedDiscount
.
Item
item
=
timeLimitedDiscount
.
getTimeLimitedDiscount
().
getItems
().
stream
()
.
filter
(
item0
->
item0
.
getSpuId
().
equals
(
sku
.
getSpuId
()))
.
findFirst
().
orElse
(
null
);
if
(
item
==
null
)
{
throw
new
IllegalArgumentException
(
String
.
format
(
"折扣活动(%s) 不存在商品(%s) 的优惠配置"
,
timeLimitedDiscount
.
toString
(),
sku
.
toString
()));
}
// 计算价格
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
item
.
getPreferentialType
()))
{
// 减价
int
presentPrice
=
sku
.
getPrice
()
-
item
.
getPreferentialValue
();
return
presentPrice
>=
0
?
presentPrice
:
sku
.
getPrice
();
// 如果计算优惠价格小于 0 ,则说明无法使用优惠。
}
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
item
.
getPreferentialType
()))
{
// 打折
return
sku
.
getPrice
()
*
item
.
getPreferentialValue
()
/
100
;
}
throw
new
IllegalArgumentException
(
String
.
format
(
"折扣活动(%s) 的优惠类型不正确"
,
timeLimitedDiscount
.
toString
()));
}
private
Integer
calcSkuPriceByFullPrivilege
(
CalcOrderPriceBO
.
ItemGroup
itemGroup
)
{
if
(
itemGroup
.
getActivity
()
==
null
)
{
return
null
;
}
PromotionActivityBO
activity
=
itemGroup
.
getActivity
();
Assert
.
isTrue
(
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()),
"传入的必须的满减送活动必须是满减送"
);
// 获得优惠信息
List
<
CalcOrderPriceBO
.
Item
>
items
=
itemGroup
.
getItems
().
stream
().
filter
(
CalcOrderPriceBO
.
Item
::
getSelected
)
.
collect
(
Collectors
.
toList
());
Integer
itemCnt
=
items
.
stream
().
mapToInt
(
CalcOrderPriceBO
.
Item
::
getBuyQuantity
).
sum
();
Integer
originalTotal
=
items
.
stream
().
mapToInt
(
CalcOrderPriceBO
.
Item
::
getPresentTotal
).
sum
();
List
<
PromotionActivityBO
.
FullPrivilege
.
Privilege
>
privileges
=
activity
.
getFullPrivilege
().
getPrivileges
().
stream
()
.
filter
(
privilege
->
{
if
(
MeetTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
return
originalTotal
>=
privilege
.
getMeetValue
();
}
if
(
MeetTypeEnum
.
QUANTITY
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
return
itemCnt
>=
privilege
.
getMeetValue
();
}
throw
new
IllegalArgumentException
(
String
.
format
(
"满减送活动(%s) 的匹配(%s)不正确"
,
itemGroup
.
getActivity
().
toString
(),
privilege
.
toString
()));
}).
collect
(
Collectors
.
toList
());
// 获得不到优惠信息,返回原始价格
if
(
privileges
.
isEmpty
())
{
return
null
;
}
// 获得到优惠信息,进行价格计算
PromotionActivityBO
.
FullPrivilege
.
Privilege
privilege
=
privileges
.
get
(
privileges
.
size
()
-
1
);
Integer
presentTotal
;
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getPreferentialType
()))
{
// 减价
// 计算循环次数。这样,后续优惠的金额就是相乘了
Integer
cycleCount
=
1
;
if
(
activity
.
getFullPrivilege
().
getCycled
())
{
if
(
MeetTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
cycleCount
=
originalTotal
/
privilege
.
getMeetValue
();
}
else
if
(
MeetTypeEnum
.
QUANTITY
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
cycleCount
=
itemCnt
/
privilege
.
getMeetValue
();
}
}
presentTotal
=
originalTotal
-
cycleCount
*
privilege
.
getMeetValue
();
if
(
presentTotal
<
0
)
{
// 如果计算优惠价格小于 0 ,则说明无法使用优惠。
presentTotal
=
originalTotal
;
}
}
else
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
privilege
.
getPreferentialType
()))
{
// 打折
presentTotal
=
originalTotal
*
privilege
.
getPreferentialValue
()
/
100
;
}
else
{
throw
new
IllegalArgumentException
(
String
.
format
(
"满减送促销(%s) 的优惠类型不正确"
,
activity
.
toString
()));
}
int
discountTotal
=
originalTotal
-
presentTotal
;
if
(
discountTotal
==
0
)
{
return
null
;
}
// 按比例,拆分 presentTotal
// for (int i = 0; i < items.size(); i++) {
// CalcOrderPriceBO.Item item = items.get(i);
// Integer discountPart;
// if (i < items.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
// discountPart = (int) (discountTotal * (1.0D * item.getPresentTotal() / presentTotal));
// discountTotal -= discountPart;
// } else {
// discountPart = discountTotal;
// @Override
// @SuppressWarnings("Duplicates")
// public CalcSkuPriceBO calcSkuPrice(Integer skuId) {
// // 查询 SKU 是否合法
// ProductSkuBO sku = productSpuService.getProductSku(skuId);
// if (sku == null
// || CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
// throw ServiceExceptionUtil.exception(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode());
// }
// Assert.isTrue(discountPart > 0, "优惠金额必须大于 0");
// item.setDiscountTotal(item.getDiscountTotal() + discountPart);
// item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
// item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
// // 查询促销活动
// List<PromotionActivityBO> activityList = promotionActivityService.getPromotionActivityListBySpuId(sku.getSpuId(),
// Arrays.asList(PromotionActivityStatusEnum.WAIT.getValue(), PromotionActivityStatusEnum.RUN.getValue()));
// if (activityList.isEmpty()) { // 如果无促销活动,则直接返回默认结果即可
// return new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setBuyPrice(sku.getPrice());
// }
// // 如果有促销活动,则开始做计算 TODO 芋艿,因为现在暂时只有限时折扣 + 满减送。所以写的比较简单先
// PromotionActivityBO fullPrivilege = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.FULL_PRIVILEGE);
// PromotionActivityBO timeLimitedDiscount = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT);
// Integer presentPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
// // 返回结果
// return new CalcSkuPriceBO().setFullPrivilege(fullPrivilege).setTimeLimitedDiscount(timeLimitedDiscount)
// .setOriginalPrice(sku.getPrice()).setBuyPrice(presentPrice);
// }
//
//
//
//
// private PromotionActivityBO findPromotionActivityByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
// return activityList.stream()
// .filter(activity -> type.getValue().equals(activity.getActivityType()))
// .findFirst().orElse(null);
// }
//
// private List<PromotionActivityBO> findPromotionActivityListByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {
// return activityList.stream()
// .filter(activity -> type.getValue().equals(activity.getActivityType()))
// .collect(Collectors.toList());
// }
splitDiscountPriceToItems
(
items
,
discountTotal
,
presentTotal
);
// 返回优惠金额
return
originalTotal
-
presentTotal
;
}
private
void
splitDiscountPriceToItems
(
List
<
CalcOrderPriceBO
.
Item
>
items
,
Integer
discountTotal
,
Integer
presentTotal
)
{
for
(
int
i
=
0
;
i
<
items
.
size
();
i
++)
{
CalcOrderPriceBO
.
Item
item
=
items
.
get
(
i
);
Integer
discountPart
;
if
(
i
<
items
.
size
()
-
1
)
{
// 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
discountPart
=
(
int
)
(
discountTotal
*
(
1.0
D
*
item
.
getPresentTotal
()
/
presentTotal
));
discountTotal
-=
discountPart
;
}
else
{
discountPart
=
discountTotal
;
}
Assert
.
isTrue
(
discountPart
>
0
,
"优惠金额必须大于 0"
);
item
.
setDiscountTotal
(
item
.
getDiscountTotal
()
+
discountPart
);
item
.
setPresentTotal
(
item
.
getBuyTotal
()
-
item
.
getDiscountTotal
());
item
.
setPresentPrice
(
item
.
getPresentTotal
()
/
item
.
getBuyQuantity
());
}
}
private
PromotionActivityBO
findPromotionActivityByType
(
List
<
PromotionActivityBO
>
activityList
,
PromotionActivityTypeEnum
type
)
{
return
activityList
.
stream
()
.
filter
(
activity
->
type
.
getValue
().
equals
(
activity
.
getActivityType
()))
.
findFirst
().
orElse
(
null
);
}
private
List
<
PromotionActivityBO
>
findPromotionActivityListByType
(
List
<
PromotionActivityBO
>
activityList
,
PromotionActivityTypeEnum
type
)
{
return
activityList
.
stream
()
.
filter
(
activity
->
type
.
getValue
().
equals
(
activity
.
getActivityType
()))
.
collect
(
Collectors
.
toList
());
}
private
boolean
isSpuMatchFullPrivilege
(
Integer
spuId
,
PromotionActivityBO
activity
)
{
Assert
.
isTrue
(
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()),
"传入的必须的促销活动必须是满减送"
);
PromotionActivityBO
.
FullPrivilege
fullPrivilege
=
activity
.
getFullPrivilege
();
if
(
RangeTypeEnum
.
ALL
.
getValue
().
equals
(
fullPrivilege
.
getRangeType
()))
{
return
true
;
}
else
if
(
RangeTypeEnum
.
PRODUCT_INCLUDE_PART
.
getValue
().
equals
(
fullPrivilege
.
getRangeType
()))
{
return
fullPrivilege
.
getRangeValues
().
contains
(
spuId
);
}
else
{
throw
new
IllegalArgumentException
(
String
.
format
(
"促销活动(%s) 可用范围的类型是不正确"
,
activity
.
toString
()));
}
}
}
product-service-project/product-service-api/src/main/java/cn/iocoder/mall/productservice/enums/sku/ProductSkuDetailFieldEnum.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
productservice
.
enums
.
sku
;
import
cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO
;
/**
* 商品 SKU 明细的字段枚举
*
* @see ProductSkuRespDTO
*/
public
enum
ProductSkuDetailFieldEnum
{
SPU
(
"spu"
),
ATTR
(
"attr"
);
/**
* 字段
*/
private
final
String
field
;
ProductSkuDetailFieldEnum
(
String
field
)
{
this
.
field
=
field
;
}
public
String
getField
()
{
return
field
;
}
}
product-service-project/product-service-api/src/main/java/cn/iocoder/mall/productservice/rpc/sku/dto/ProductSkuListQueryReqDTO.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
productservice
.
rpc
.
sku
.
dto
;
import
cn.iocoder.mall.productservice.enums.sku.ProductSkuDetailFieldEnum
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
import
java.io.Serializable
;
import
java.util.Collection
;
import
java.util.Collections
;
/**
* 商品 SKU 列表查询 DTO
...
...
@@ -26,5 +28,11 @@ public class ProductSkuListQueryReqDTO implements Serializable {
*/
private
Integer
productSpuId
;
/**
* 额外返回字段
*
* @see ProductSkuDetailFieldEnum
*/
private
Collection
<
String
>
fields
=
Collections
.
emptySet
();
}
product-service-project/product-service-api/src/main/java/cn/iocoder/mall/productservice/rpc/spu/ProductSpuRpc.java
浏览文件 @
5122b68a
...
...
@@ -41,7 +41,7 @@ public interface ProductSpuRpc {
* @param productSpuIds 商品 SPU 编号列表
* @return 商品 SPU 列表
*/
CommonResult
<
List
<
ProductSpuRespDTO
>>
listProductSpus
(
List
<
Integer
>
productSpuIds
);
CommonResult
<
List
<
ProductSpuRespDTO
>>
listProductSpus
(
Collection
<
Integer
>
productSpuIds
);
/**
* 获得商品 SPU 分页
...
...
product-service-project/product-service-app/src/main/java/cn/iocoder/mall/productservice/manager/sku/ProductSkuManager.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
productservice
.
manager
.
sku
;
import
cn.iocoder.common.framework.util.CollectionUtils
;
import
cn.iocoder.mall.productservice.convert.sku.ProductSkuConvert
;
import
cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuListQueryReqDTO
;
import
cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO
;
...
...
@@ -8,6 +9,7 @@ import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuBO;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
java.util.Collections
;
import
java.util.List
;
/**
...
...
@@ -37,8 +39,13 @@ public class ProductSkuManager {
* @return 商品 SKU列表
*/
public
List
<
ProductSkuRespDTO
>
listProductSkus
(
ProductSkuListQueryReqDTO
queryReqDTO
)
{
// 获得商品 SKU 列表
List
<
ProductSkuBO
>
productSkuBOs
=
productSkuService
.
listProductSkus
(
ProductSkuConvert
.
INSTANCE
.
convert
(
queryReqDTO
));
if
(
CollectionUtils
.
isEmpty
(
productSkuBOs
))
{
return
Collections
.
emptyList
();
}
//
return
ProductSkuConvert
.
INSTANCE
.
convertList03
(
productSkuBOs
);
}
...
...
product-service-project/product-service-app/src/main/java/cn/iocoder/mall/productservice/manager/spu/ProductSpuManager.java
浏览文件 @
5122b68a
...
...
@@ -131,7 +131,7 @@ public class ProductSpuManager {
* @param productSpuIds 商品 SPU编号列表
* @return 商品 SPU列表
*/
public
List
<
ProductSpuRespDTO
>
listProductSpus
(
List
<
Integer
>
productSpuIds
)
{
public
List
<
ProductSpuRespDTO
>
listProductSpus
(
Collection
<
Integer
>
productSpuIds
)
{
List
<
ProductSpuBO
>
productSpuBOs
=
productSpuService
.
listProductSpus
(
productSpuIds
);
return
ProductSpuConvert
.
INSTANCE
.
convertList02
(
productSpuBOs
);
}
...
...
product-service-project/product-service-app/src/main/java/cn/iocoder/mall/productservice/rpc/spu/ProductSpuRpcImpl.java
浏览文件 @
5122b68a
...
...
@@ -38,7 +38,7 @@ public class ProductSpuRpcImpl implements ProductSpuRpc {
}
@Override
public
CommonResult
<
List
<
ProductSpuRespDTO
>>
listProductSpus
(
List
<
Integer
>
productSpuIds
)
{
public
CommonResult
<
List
<
ProductSpuRespDTO
>>
listProductSpus
(
Collection
<
Integer
>
productSpuIds
)
{
return
success
(
productSpuManager
.
listProductSpus
(
productSpuIds
));
}
...
...
product-service-project/product-service-app/src/main/java/cn/iocoder/mall/productservice/service/spu/ProductSpuService.java
浏览文件 @
5122b68a
...
...
@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
import
org.springframework.validation.annotation.Validated
;
import
javax.validation.Valid
;
import
java.util.Collection
;
import
java.util.List
;
import
static
cn
.
iocoder
.
mall
.
productservice
.
enums
.
ProductErrorCodeConstants
.
PRODUCT_SPU_NOT_EXISTS
;
...
...
@@ -76,7 +77,7 @@ public class ProductSpuService {
* @param productSpuIds 商品 SPU编号列表
* @return 商品 SPU列表
*/
public
List
<
ProductSpuBO
>
listProductSpus
(
List
<
Integer
>
productSpuIds
)
{
public
List
<
ProductSpuBO
>
listProductSpus
(
Collection
<
Integer
>
productSpuIds
)
{
List
<
ProductSpuDO
>
productSpuDOs
=
productSpuMapper
.
selectBatchIds
(
productSpuIds
);
return
ProductSpuConvert
.
INSTANCE
.
convertList
(
productSpuDOs
);
}
...
...
promotion-service-project/promotion-service-ap
p/src/main/java/cn/iocoder/mall/promotionservice/service/coupon/bo/CouponCardB
O.java
→
promotion-service-project/promotion-service-ap
i/src/main/java/cn/iocoder/mall/promotion/api/rpc/coupon/dto/card/CouponCardRespDT
O.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotion
service
.
service
.
coupon
.
bo
;
package
cn
.
iocoder
.
mall
.
promotion
.
api
.
rpc
.
coupon
.
dto
.
card
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
...
...
@@ -7,11 +7,11 @@ import java.io.Serializable;
import
java.util.Date
;
/**
* 优惠劵
B
O
* 优惠劵
Response DT
O
*/
@Data
@Accessors
(
chain
=
true
)
public
class
CouponCard
B
O
implements
Serializable
{
public
class
CouponCard
RespDT
O
implements
Serializable
{
// ========== 基本信息 BEGIN ==========
/**
...
...
@@ -100,8 +100,6 @@ public class CouponCardBO implements Serializable {
*/
private
Date
usedTime
;
// TODO 芋艿,后续要加优惠劵的使用日志,因为下单后,可能会取消。
// ========== 使用情况 END ==========
/**
...
...
promotion-service-project/promotion-service-ap
p/src/main/java/cn/iocoder/mall/promotionservice/service/coupon/bo/CouponTemplateB
O.java
→
promotion-service-project/promotion-service-ap
i/src/main/java/cn/iocoder/mall/promotion/api/rpc/coupon/dto/template/CouponTemplateRespDT
O.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotion
service
.
service
.
coupon
.
bo
;
package
cn
.
iocoder
.
mall
.
promotion
.
api
.
rpc
.
coupon
.
dto
.
template
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
import
java.io.Serializable
;
import
java.util.Date
;
import
java.util.List
;
/**
* 优惠劵(码)模板 BO
*/
@Data
@Accessors
(
chain
=
true
)
public
class
CouponTemplate
B
O
implements
Serializable
{
public
class
CouponTemplate
RespDT
O
implements
Serializable
{
// ========== 基本信息 BEGIN ==========
/**
...
...
@@ -85,7 +86,7 @@ public class CouponTemplateBO implements Serializable {
/**
* 指定商品 / 分类列表,使用逗号分隔商品编号
*/
private
String
rangeValues
;
private
List
<
Integer
>
rangeValues
;
/**
* 生效日期类型
*
...
...
promotion-service-project/promotion-service-api/src/main/java/cn/iocoder/mall/promotion/api/rpc/price/PriceRpc.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotion
.
api
.
rpc
.
price
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
import
javax.validation.constraints.NotNull
;
import
java.util.List
;
import
cn.iocoder.common.framework.vo.CommonResult
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO
;
/**
* 价格 Rpc 接口,提供价格计算的功能
*/
public
class
PriceRpc
{
public
interface
PriceRpc
{
CommonResult
<
PriceProductCalcRespDTO
>
calcProductPrice
(
PriceProductCalcReqDTO
calcReqDTO
);
}
promotion-service-project/promotion-service-api/src/main/java/cn/iocoder/mall/promotion/api/rpc/price/dto/PriceProductCalcReqDTO.java
浏览文件 @
5122b68a
...
...
@@ -11,7 +11,7 @@ import java.util.List;
* 商品价格计算 Request DTO
*/
@Data
@Accessors
@Accessors
(
chain
=
true
)
public
class
PriceProductCalcReqDTO
implements
Serializable
{
/**
...
...
promotion-service-project/promotion-service-api/src/main/java/cn/iocoder/mall/promotion/api/rpc/price/dto/PriceProductCalcRespDTO.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotion
.
api
.
rpc
.
price
.
dto
;
import
cn.iocoder.mall.promotion.api.enums.PromotionActivityTypeEnum
;
import
cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
...
...
@@ -48,11 +50,14 @@ public class PriceProductCalcRespDTO implements Serializable {
@Accessors
(
chain
=
true
)
public
static
class
ItemGroup
{
// /**
// * 优惠活动
// */
// // TODO 芋艿,目前只会有【满减送】的情况,未来有新的促销方式,可能需要改成数组
// private PromotionActivityBO activity;
/**
* 优惠活动
*
* 目前会有满减送 {@link PromotionActivityTypeEnum#FULL_PRIVILEGE} 类型的活动
*
* // TODO 芋艿,目前只会有【满减送】的情况,未来有新的促销方式,可能需要改成数组
*/
private
PromotionActivityRespDTO
activity
;
/**
* 促销减少的金额
*
...
...
@@ -77,14 +82,28 @@ public class PriceProductCalcRespDTO implements Serializable {
@Accessors
(
chain
=
true
)
public
static
class
Item
{
/**
* 商品 SPU 编号
*/
private
Integer
spuId
;
/**
* 商品 SKU 编号
*/
private
Integer
skuId
;
/**
* 商品 Category 编号
*/
private
Integer
cid
;
/**
* 购买数量
*/
private
Integer
buyQuantity
;
// /**
// * 优惠活动
// */
// private PromotionActivityBO activity;
/**
* 优惠活动
*
* 目前会有限时折扣 {@link PromotionActivityTypeEnum#TIME_LIMITED_DISCOUNT} 类型的活动
*/
private
PromotionActivityRespDTO
activity
;
/**
* 原始单价,单位:分。
*/
...
...
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/convert/coupon/CouponCardConvert.java
浏览文件 @
5122b68a
...
...
@@ -4,8 +4,6 @@ import cn.iocoder.mall.promotion.api.rpc.coupon.dto.CouponCardDetailRespDTO;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.CouponCardReqDTO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardAvailableBO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardBO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardDetailBO
;
import
org.mapstruct.Mapper
;
import
org.mapstruct.Mappings
;
import
org.mapstruct.factory.Mappers
;
...
...
@@ -19,17 +17,13 @@ public interface CouponCardConvert {
// @Mappings({})
// CouponCardBO convertToBO(CouponCardDO banner);
//
@Mappings
({})
List
<
CouponCardBO
>
convertToBO
(
List
<
CouponCardDO
>
cardList
);
List
<
CouponCardReqDTO
>
convertToDTO
(
List
<
CouponCardDO
>
cardList
);
CouponCardReqDTO
convertToSingleDTO
(
CouponCardDO
card
);
@Mappings
({})
CouponCardBO
convert
(
CouponCardDO
card
);
@Mappings
({})
CouponCardDetailRespDTO
convert2
(
CouponCardDO
card
);
...
...
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/convert/coupon/card/CouponCardConvert.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
convert
.
coupon
.
card
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO
;
import
org.mapstruct.Mapper
;
import
org.mapstruct.factory.Mappers
;
@Mapper
public
interface
CouponCardConvert
{
CouponCardConvert
INSTANCE
=
Mappers
.
getMapper
(
CouponCardConvert
.
class
);
CouponCardRespDTO
convert
(
CouponCardDO
bean
);
}
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/convert/coupon/CouponTemplateConvert.java
→
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/convert/coupon/
card/
CouponTemplateConvert.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
convert
.
coupon
;
package
cn
.
iocoder
.
mall
.
promotionservice
.
convert
.
coupon
.
card
;
import
cn.iocoder.common.framework.util.StringUtils
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.*
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponTemplateDO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardTemplateAddBO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponCardTemplateUpdateBO
;
import
cn.iocoder.mall.promotionservice.service.coupon.bo.CouponTemplateBO
;
import
org.mapstruct.Mapper
;
import
org.mapstruct.Mapping
;
import
org.mapstruct.Mappings
;
import
org.mapstruct.Named
;
import
org.mapstruct.factory.Mappers
;
import
java.util.List
;
...
...
@@ -18,9 +21,7 @@ public interface CouponTemplateConvert {
// @Mappings({})
// CouponTemplateBO convertToBO(CouponTemplateDO banner);
//
@Mappings
({})
List
<
CouponTemplateBO
>
convertToBO
(
List
<
CouponTemplateDO
>
templateList
);
List
<
CouponTemplateReqDTO
>
convertToDTO
(
List
<
CouponTemplateDO
>
templateList
);
...
...
@@ -42,7 +43,12 @@ public interface CouponTemplateConvert {
@Mappings
({})
CouponTemplateDO
convert
(
CouponCardTemplateUpdateBO
template
);
@Mappings
({})
CouponTemplateBO
convert
(
CouponTemplateDO
template
);
@Mapping
(
source
=
"rangeValues"
,
target
=
"rangeValues"
,
qualifiedByName
=
"translateStringToIntList"
)
CouponTemplateRespDTO
convert
(
CouponTemplateDO
bean
);
@Named
(
"translateStringToIntList"
)
default
List
<
Integer
>
translateStringToIntList
(
String
str
)
{
return
StringUtils
.
splitToInt
(
str
,
","
);
}
}
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/manager/price/PriceManager.java
浏览文件 @
5122b68a
...
...
@@ -6,22 +6,27 @@ import cn.iocoder.common.framework.vo.CommonResult;
import
cn.iocoder.mall.productservice.rpc.sku.ProductSkuRpc
;
import
cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuListQueryReqDTO
;
import
cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO
;
import
cn.iocoder.mall.promotion.api.enums.PromotionActivityStatusEnum
;
import
cn.iocoder.mall.productservice.rpc.spu.ProductSpuRpc
;
import
cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO
;
import
cn.iocoder.mall.promotion.api.enums.*
;
import
cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO
;
import
cn.iocoder.mall.promotionservice.service.activity.PromotionActivityService
;
import
cn.iocoder.mall.promotionservice.service.coupon.CouponCardService
;
import
cn.iocoder.mall.promotionservice.service.coupon.CouponTemplateService
;
import
org.apache.dubbo.config.annotation.DubboReference
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.Assert
;
import
org.springframework.validation.annotation.Validated
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.*
;
import
java.util.stream.Collectors
;
import
static
cn
.
iocoder
.
mall
.
promotion
.
api
.
enums
.
PromotionErrorCodeConstants
.
PRICE_PRODUCT_SKU_NOT_EXISTS
;
import
static
cn
.
iocoder
.
mall
.
promotion
.
api
.
enums
.
PromotionErrorCodeConstants
.
*
;
@Service
@Validated
...
...
@@ -29,9 +34,15 @@ public class PriceManager {
@DubboReference
(
version
=
"${dubbo.consumer.ProductSkuRpc.version}"
)
private
ProductSkuRpc
productSkuRpc
;
@DubboReference
(
version
=
"${dubbo.consumer.ProductSpuRpc.version}"
)
private
ProductSpuRpc
productSpuRpc
;
@Autowired
private
PromotionActivityService
promotionActivityService
;
@Autowired
private
CouponCardService
couponCardService
;
@Autowired
private
CouponTemplateService
couponTemplateService
;
public
PriceProductCalcRespDTO
calcProductPrice
(
PriceProductCalcReqDTO
calcReqDTO
)
{
// TODO 芋艿,补充一些表单校验。例如说,需要传入用户编号。
...
...
@@ -47,28 +58,54 @@ public class PriceManager {
// TODO 库存相关
// 查询促销活动
List
<
PromotionActivityRespDTO
>
activityRespDTOs
=
promotionActivityService
.
listPromotionActivitiesBySpuIds
(
calcProductItemDTOMap
.
keySet
(),
Collections
.
singleton
(
PromotionActivityStatusEnum
.
RUN
.
getValue
()));
CollectionUtils
.
convertSet
(
listProductSkusResult
.
getData
(),
ProductSkuRespDTO:
:
getSpuId
),
Collections
.
singleton
(
PromotionActivityStatusEnum
.
RUN
.
getValue
()));
// 拼装结果(主要是计算价格)
PriceProductCalcRespDTO
calcRespDTO
=
new
PriceProductCalcRespDTO
();
// 1. 创建初始的每一项的数组
List
<
PriceProductCalcRespDTO
.
Item
>
calcItemRespDTOs
=
initCalcOrderPriceItems
(
List
<
PriceProductCalcRespDTO
.
Item
>
calcItemRespDTOs
=
this
.
initCalcOrderPriceItems
(
listProductSkusResult
.
getData
(),
calcProductItemDTOMap
);
// 2. 计算【限时折扣】促销
// modifyPriceByTimeLimitDiscount(items, activityList
);
this
.
modifyPriceByTimeLimitDiscount
(
calcItemRespDTOs
,
activityRespDTOs
);
// 3. 计算【满减送】促销
// 4. 计算优惠劵
List
<
PriceProductCalcRespDTO
.
ItemGroup
>
itemGroups
=
this
.
groupByFullPrivilege
(
calcItemRespDTOs
,
activityRespDTOs
);
calcRespDTO
.
setItemGroups
(
itemGroups
);
// 4. 计算优惠劵 TODO 芋艿:未详细测试;
if
(
calcReqDTO
.
getCouponCardId
()
!=
null
)
{
Integer
result
=
this
.
modifyPriceByCouponCard
(
calcReqDTO
.
getUserId
(),
calcReqDTO
.
getCouponCardId
(),
itemGroups
);
calcRespDTO
.
setCouponCardDiscountTotal
(
result
);
}
// 5. 计算最终的价格
return
null
;
int
buyTotal
=
0
;
int
discountTotal
=
0
;
int
presentTotal
=
0
;
for
(
PriceProductCalcRespDTO
.
ItemGroup
itemGroup
:
calcRespDTO
.
getItemGroups
())
{
buyTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getBuyTotal
).
sum
();
discountTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getDiscountTotal
).
sum
();
presentTotal
+=
itemGroup
.
getItems
().
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getPresentTotal
).
sum
();
}
Assert
.
isTrue
(
buyTotal
-
discountTotal
==
presentTotal
,
String
.
format
(
"价格合计( %d - %d == %d )不正确"
,
buyTotal
,
discountTotal
,
presentTotal
));
calcRespDTO
.
setFee
(
new
PriceProductCalcRespDTO
.
Fee
(
buyTotal
,
discountTotal
,
0
,
presentTotal
));
return
calcRespDTO
;
}
private
List
<
PriceProductCalcRespDTO
.
Item
>
initCalcOrderPriceItems
(
List
<
ProductSkuRespDTO
>
skus
,
Map
<
Integer
,
PriceProductCalcReqDTO
.
Item
>
calcProductItemDTOMap
)
{
// 获得商品分类 Map
CommonResult
<
List
<
ProductSpuRespDTO
>>
listProductSpusResult
=
productSpuRpc
.
listProductSpus
(
CollectionUtils
.
convertSet
(
skus
,
ProductSkuRespDTO:
:
getSpuId
));
listProductSpusResult
.
checkError
();
Map
<
Integer
,
Integer
>
spuIdCategoryIdMap
=
CollectionUtils
.
convertMap
(
listProductSpusResult
.
getData
(),
// SPU 编号与 Category 编号的映射
ProductSpuRespDTO:
:
getId
,
ProductSpuRespDTO:
:
getCid
);
// 生成商品列表
List
<
PriceProductCalcRespDTO
.
Item
>
items
=
new
ArrayList
<>();
for
(
ProductSkuRespDTO
sku
:
skus
)
{
PriceProductCalcRespDTO
.
Item
item
=
new
PriceProductCalcRespDTO
.
Item
();
items
.
add
(
item
);
// 将
是否选中,购物数量
,复制到 item 中
// 将
基本信息
,复制到 item 中
PriceProductCalcReqDTO
.
Item
calcOrderItem
=
calcProductItemDTOMap
.
get
(
sku
.
getId
());
item
.
setSpuId
(
sku
.
getSpuId
()).
setSkuId
(
sku
.
getId
());
item
.
setCid
(
spuIdCategoryIdMap
.
get
(
sku
.
getSpuId
()));
item
.
setBuyQuantity
(
calcOrderItem
.
getQuantity
());
// 计算初始价格
item
.
setOriginPrice
(
sku
.
getPrice
());
...
...
@@ -81,30 +118,256 @@ public class PriceManager {
return
items
;
}
// private void modifyPriceByTimeLimitDiscount(List<CalcOrderPriceBO.Item> items, List<PromotionActivityBO> activityList) {
// for (CalcOrderPriceBO.Item item : items) {
// // 获得符合条件的限时折扣
// PromotionActivityBO timeLimitedDiscount = activityList.stream()
// .filter(activity -> PromotionActivityTypeEnum.TIME_LIMITED_DISCOUNT.getValue().equals(activity.getActivityType())
// && activity.getTimeLimitedDiscount().getItems().stream().anyMatch(item0 -> item0.getSpuId().equals(item.getSpu().getId())))
// .findFirst().orElse(null);
// if (timeLimitedDiscount == null) {
// continue;
// }
// // 计算价格
// ProductSkuBO sku = new ProductSkuBO().setId(item.getId()).setSpuId(item.getSpu().getId()).setPrice(item.getPrice());
// Integer newPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
// if (newPrice.equals(item.getPrice())) {
// continue;
// }
// // 设置优惠
// item.setActivity(timeLimitedDiscount);
// // 设置价格
// item.setBuyPrice(newPrice);
// item.setBuyTotal(newPrice * item.getBuyQuantity());
// item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
// item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
// }
// }
private
void
modifyPriceByTimeLimitDiscount
(
List
<
PriceProductCalcRespDTO
.
Item
>
items
,
List
<
PromotionActivityRespDTO
>
activityList
)
{
for
(
PriceProductCalcRespDTO
.
Item
item
:
items
)
{
// 获得符合条件的限时折扣
PromotionActivityRespDTO
timeLimitedDiscount
=
activityList
.
stream
()
.
filter
(
activity
->
PromotionActivityTypeEnum
.
TIME_LIMITED_DISCOUNT
.
getValue
().
equals
(
activity
.
getActivityType
())
&&
activity
.
getTimeLimitedDiscount
().
getItems
().
stream
().
anyMatch
(
item0
->
item0
.
getSpuId
().
equals
(
item
.
getSpuId
())))
.
findFirst
().
orElse
(
null
);
if
(
timeLimitedDiscount
==
null
)
{
continue
;
}
// 计算价格
Integer
newBuyPrice
=
calcSkuPriceByTimeLimitDiscount
(
item
,
timeLimitedDiscount
);
if
(
newBuyPrice
.
equals
(
item
.
getBuyPrice
()))
{
// 未优惠
continue
;
}
// 设置优惠
item
.
setActivity
(
timeLimitedDiscount
);
// 设置价格
item
.
setBuyPrice
(
newBuyPrice
);
item
.
setBuyTotal
(
newBuyPrice
*
item
.
getBuyQuantity
());
item
.
setPresentTotal
(
item
.
getBuyTotal
()
-
item
.
getDiscountTotal
());
item
.
setPresentPrice
(
item
.
getPresentTotal
()
/
item
.
getBuyQuantity
());
}
}
/**
* 计算指定 SKU 在限时折扣下的价格
*
* @param sku SKU
* @param timeLimitedDiscount 限时折扣促销。
* 传入的该活动,要保证该 SKU 在该促销下一定有优惠。
* @return 计算后的价格
*/
private
Integer
calcSkuPriceByTimeLimitDiscount
(
PriceProductCalcRespDTO
.
Item
sku
,
PromotionActivityRespDTO
timeLimitedDiscount
)
{
if
(
timeLimitedDiscount
==
null
)
{
return
sku
.
getBuyPrice
();
}
// 获得对应的优惠项
PromotionActivityRespDTO
.
TimeLimitedDiscount
.
Item
item
=
timeLimitedDiscount
.
getTimeLimitedDiscount
().
getItems
().
stream
()
.
filter
(
item0
->
item0
.
getSpuId
().
equals
(
sku
.
getSpuId
()))
.
findFirst
().
orElse
(
null
);
if
(
item
==
null
)
{
throw
new
IllegalArgumentException
(
String
.
format
(
"折扣活动(%s) 不存在商品(%s) 的优惠配置"
,
timeLimitedDiscount
.
toString
(),
sku
.
toString
()));
}
// 计算价格
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
item
.
getPreferentialType
()))
{
// 减价
int
presentPrice
=
sku
.
getBuyPrice
()
-
item
.
getPreferentialValue
();
return
presentPrice
>=
0
?
presentPrice
:
sku
.
getBuyPrice
();
// 如果计算优惠价格小于 0 ,则说明无法使用优惠。
}
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
item
.
getPreferentialType
()))
{
// 打折
return
sku
.
getBuyPrice
()
*
item
.
getPreferentialValue
()
/
100
;
}
throw
new
IllegalArgumentException
(
String
.
format
(
"折扣活动(%s) 的优惠类型不正确"
,
timeLimitedDiscount
.
toString
()));
}
private
List
<
PriceProductCalcRespDTO
.
ItemGroup
>
groupByFullPrivilege
(
List
<
PriceProductCalcRespDTO
.
Item
>
items
,
List
<
PromotionActivityRespDTO
>
activityList
)
{
List
<
PriceProductCalcRespDTO
.
ItemGroup
>
itemGroups
=
new
ArrayList
<>();
// 获得所有满减送促销
List
<
PromotionActivityRespDTO
>
fullPrivileges
=
activityList
.
stream
()
.
filter
(
activity
->
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()))
.
collect
(
Collectors
.
toList
());
// 基于满减送促销,进行分组
if
(!
fullPrivileges
.
isEmpty
())
{
items
=
new
ArrayList
<>(
items
);
// 因为下面会修改数组,进行浅拷贝,避免影响传入的 items 。
for
(
PromotionActivityRespDTO
fullPrivilege
:
fullPrivileges
)
{
// 创建 fullPrivilege 对应的分组
PriceProductCalcRespDTO
.
ItemGroup
itemGroup
=
new
PriceProductCalcRespDTO
.
ItemGroup
()
.
setActivity
(
fullPrivilege
)
.
setItems
(
new
ArrayList
<>());
// 筛选商品到分组中
for
(
Iterator
<
PriceProductCalcRespDTO
.
Item
>
iterator
=
items
.
iterator
();
iterator
.
hasNext
();
)
{
PriceProductCalcRespDTO
.
Item
item
=
iterator
.
next
();
if
(!
isSpuMatchFullPrivilege
(
item
.
getSpuId
(),
fullPrivilege
))
{
continue
;
}
itemGroup
.
getItems
().
add
(
item
);
iterator
.
remove
();
}
// 如果匹配到,则添加到 itemGroups 中
if
(!
itemGroup
.
getItems
().
isEmpty
())
{
itemGroups
.
add
(
itemGroup
);
}
}
}
// 处理未参加活动的商品,形成一个分组
if
(!
items
.
isEmpty
())
{
itemGroups
.
add
(
new
PriceProductCalcRespDTO
.
ItemGroup
().
setItems
(
items
));
}
// 计算每个分组的价格
for
(
PriceProductCalcRespDTO
.
ItemGroup
itemGroup
:
itemGroups
)
{
itemGroup
.
setActivityDiscountTotal
(
calcSkuPriceByFullPrivilege
(
itemGroup
));
}
// 返回结果
return
itemGroups
;
}
private
boolean
isSpuMatchFullPrivilege
(
Integer
spuId
,
PromotionActivityRespDTO
activity
)
{
Assert
.
isTrue
(
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()),
"传入的必须的促销活动必须是满减送"
);
PromotionActivityRespDTO
.
FullPrivilege
fullPrivilege
=
activity
.
getFullPrivilege
();
if
(
RangeTypeEnum
.
ALL
.
getValue
().
equals
(
fullPrivilege
.
getRangeType
()))
{
return
true
;
}
else
if
(
RangeTypeEnum
.
PRODUCT_INCLUDE_PART
.
getValue
().
equals
(
fullPrivilege
.
getRangeType
()))
{
return
fullPrivilege
.
getRangeValues
().
contains
(
spuId
);
}
throw
new
IllegalArgumentException
(
String
.
format
(
"促销活动(%s) 可用范围的类型是不正确"
,
activity
.
toString
()));
}
private
Integer
calcSkuPriceByFullPrivilege
(
PriceProductCalcRespDTO
.
ItemGroup
itemGroup
)
{
if
(
itemGroup
.
getActivity
()
==
null
)
{
return
null
;
}
PromotionActivityRespDTO
activity
=
itemGroup
.
getActivity
();
Assert
.
isTrue
(
PromotionActivityTypeEnum
.
FULL_PRIVILEGE
.
getValue
().
equals
(
activity
.
getActivityType
()),
"传入的必须的满减送活动必须是满减送"
);
// 获得优惠信息
List
<
PriceProductCalcRespDTO
.
Item
>
items
=
itemGroup
.
getItems
();
Integer
itemCnt
=
items
.
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getBuyQuantity
).
sum
();
Integer
originalTotal
=
items
.
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getPresentTotal
).
sum
();
List
<
PromotionActivityRespDTO
.
FullPrivilege
.
Privilege
>
privileges
=
activity
.
getFullPrivilege
().
getPrivileges
().
stream
()
.
filter
(
privilege
->
{
if
(
MeetTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
return
originalTotal
>=
privilege
.
getMeetValue
();
}
if
(
MeetTypeEnum
.
QUANTITY
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
return
itemCnt
>=
privilege
.
getMeetValue
();
}
throw
new
IllegalArgumentException
(
String
.
format
(
"满减送活动(%s) 的匹配(%s)不正确"
,
itemGroup
.
getActivity
().
toString
(),
privilege
.
toString
()));
}).
collect
(
Collectors
.
toList
());
// 获得不到优惠信息,返回原始价格
if
(
privileges
.
isEmpty
())
{
return
null
;
}
// 获得到优惠信息,进行价格计算
PromotionActivityRespDTO
.
FullPrivilege
.
Privilege
privilege
=
privileges
.
get
(
privileges
.
size
()
-
1
);
Integer
presentTotal
;
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getPreferentialType
()))
{
// 减价
// 计算循环次数。这样,后续优惠的金额就是相乘了
Integer
cycleCount
=
1
;
if
(
activity
.
getFullPrivilege
().
getCycled
())
{
if
(
MeetTypeEnum
.
PRICE
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
cycleCount
=
originalTotal
/
privilege
.
getMeetValue
();
}
else
if
(
MeetTypeEnum
.
QUANTITY
.
getValue
().
equals
(
privilege
.
getMeetType
()))
{
cycleCount
=
itemCnt
/
privilege
.
getMeetValue
();
}
}
presentTotal
=
originalTotal
-
cycleCount
*
privilege
.
getMeetValue
();
if
(
presentTotal
<
0
)
{
// 如果计算优惠价格小于 0 ,则说明无法使用优惠。
presentTotal
=
originalTotal
;
}
}
else
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
privilege
.
getPreferentialType
()))
{
// 打折
presentTotal
=
originalTotal
*
privilege
.
getPreferentialValue
()
/
100
;
}
else
{
throw
new
IllegalArgumentException
(
String
.
format
(
"满减送促销(%s) 的优惠类型不正确"
,
activity
.
toString
()));
}
int
discountTotal
=
originalTotal
-
presentTotal
;
if
(
discountTotal
==
0
)
{
return
null
;
}
// 按比例,拆分 presentTotal
splitDiscountPriceToItems
(
items
,
discountTotal
,
presentTotal
);
// 返回优惠金额
return
originalTotal
-
presentTotal
;
}
private
Integer
modifyPriceByCouponCard
(
Integer
userId
,
Integer
couponCardId
,
List
<
PriceProductCalcRespDTO
.
ItemGroup
>
itemGroups
)
{
Assert
.
isTrue
(
couponCardId
!=
null
,
"优惠劵编号不能为空"
);
// 查询优惠劵
CouponCardRespDTO
couponCard
=
couponCardService
.
getCouponCard
(
userId
,
couponCardId
);
if
(
couponCard
==
null
)
{
throw
ServiceExceptionUtil
.
exception
(
COUPON_CARD_NOT_EXISTS
);
}
CouponTemplateRespDTO
couponTemplate
=
couponTemplateService
.
getCouponTemplate
(
couponCardId
);
if
(
couponTemplate
==
null
)
{
throw
ServiceExceptionUtil
.
exception
(
COUPON_TEMPLATE_NOT_EXISTS
);
}
// 获得匹配的商品
List
<
PriceProductCalcRespDTO
.
Item
>
items
=
new
ArrayList
<>();
if
(
RangeTypeEnum
.
ALL
.
getValue
().
equals
(
couponTemplate
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
addAll
(
itemGroup
.
getItems
()));
}
else
if
(
RangeTypeEnum
.
PRODUCT_INCLUDE_PART
.
getValue
().
equals
(
couponTemplate
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(
couponTemplate
.
getRangeValues
().
contains
(
item
.
getSpuId
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
PRODUCT_EXCLUDE_PART
.
getValue
().
equals
(
couponTemplate
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(!
couponTemplate
.
getRangeValues
().
contains
(
item
.
getSpuId
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
CATEGORY_INCLUDE_PART
.
getValue
().
equals
(
couponTemplate
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(
couponTemplate
.
getRangeValues
().
contains
(
item
.
getCid
()))
{
items
.
add
(
item
);
}
}));
}
else
if
(
RangeTypeEnum
.
CATEGORY_EXCLUDE_PART
.
getValue
().
equals
(
couponTemplate
.
getRangeType
()))
{
itemGroups
.
forEach
(
itemGroup
->
items
.
forEach
(
item
->
{
if
(!
couponTemplate
.
getRangeValues
().
contains
(
item
.
getCid
()))
{
items
.
add
(
item
);
}
}));
}
// 判断是否符合条件
int
originalTotal
=
items
.
stream
().
mapToInt
(
PriceProductCalcRespDTO
.
Item
::
getPresentTotal
).
sum
();
// 此处,指的是以优惠劵视角的原价
if
(
originalTotal
==
0
||
originalTotal
<
couponCard
.
getPriceAvailable
())
{
throw
ServiceExceptionUtil
.
exception
(
COUPON_CARD_NOT_MATCH
);
// TODO 芋艿,这种情况,会出现错误码的提示,无法格式化出来。另外,这块的最佳实践,找人讨论下。
}
// 计算价格
// 获得到优惠信息,进行价格计算
int
presentTotal
;
if
(
PreferentialTypeEnum
.
PRICE
.
getValue
().
equals
(
couponCard
.
getPreferentialType
()))
{
// 减价
// 计算循环次数。这样,后续优惠的金额就是相乘了
presentTotal
=
originalTotal
-
couponCard
.
getPriceOff
();
Assert
.
isTrue
(
presentTotal
>
0
,
"计算后,价格为负数:"
+
presentTotal
);
}
else
if
(
PreferentialTypeEnum
.
DISCOUNT
.
getValue
().
equals
(
couponCard
.
getPreferentialType
()))
{
// 打折
presentTotal
=
originalTotal
*
couponCard
.
getPercentOff
()
/
100
;
if
(
couponCard
.
getDiscountPriceLimit
()
!=
null
// 空,代表不限制优惠上限
&&
originalTotal
-
presentTotal
>
couponCard
.
getDiscountPriceLimit
())
{
presentTotal
=
originalTotal
-
couponCard
.
getDiscountPriceLimit
();
}
}
else
{
throw
new
IllegalArgumentException
(
String
.
format
(
"优惠劵(%s) 的优惠类型不正确"
,
couponCard
.
toString
()));
}
int
discountTotal
=
originalTotal
-
presentTotal
;
Assert
.
isTrue
(
discountTotal
>
0
,
"计算后,不产生优惠:"
+
discountTotal
);
// 按比例,拆分 presentTotal
splitDiscountPriceToItems
(
items
,
discountTotal
,
presentTotal
);
// 返回优惠金额
return
originalTotal
-
presentTotal
;
}
private
void
splitDiscountPriceToItems
(
List
<
PriceProductCalcRespDTO
.
Item
>
items
,
Integer
discountTotal
,
Integer
presentTotal
)
{
for
(
int
i
=
0
;
i
<
items
.
size
();
i
++)
{
PriceProductCalcRespDTO
.
Item
item
=
items
.
get
(
i
);
Integer
discountPart
;
if
(
i
<
items
.
size
()
-
1
)
{
// 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
discountPart
=
(
int
)
(
discountTotal
*
(
1.0
D
*
item
.
getPresentTotal
()
/
presentTotal
));
discountTotal
-=
discountPart
;
}
else
{
discountPart
=
discountTotal
;
}
Assert
.
isTrue
(
discountPart
>
0
,
"优惠金额必须大于 0"
);
item
.
setDiscountTotal
(
item
.
getDiscountTotal
()
+
discountPart
);
item
.
setPresentTotal
(
item
.
getBuyTotal
()
-
item
.
getDiscountTotal
());
item
.
setPresentPrice
(
item
.
getPresentTotal
()
/
item
.
getBuyQuantity
());
}
}
}
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/rpc/package-info.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
rpc
;
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/rpc/price/PriceRpcImpl.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
rpc
.
price
;
import
cn.iocoder.common.framework.vo.CommonResult
;
import
cn.iocoder.mall.promotion.api.rpc.price.PriceRpc
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO
;
import
cn.iocoder.mall.promotionservice.manager.price.PriceManager
;
import
org.apache.dubbo.config.annotation.DubboService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
static
cn
.
iocoder
.
common
.
framework
.
vo
.
CommonResult
.
success
;
@DubboService
public
class
PriceRpcImpl
implements
PriceRpc
{
@Autowired
private
PriceManager
priceManager
;
@Override
public
CommonResult
<
PriceProductCalcRespDTO
>
calcProductPrice
(
PriceProductCalcReqDTO
calcReqDTO
)
{
return
success
(
priceManager
.
calcProductPrice
(
calcReqDTO
));
}
}
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/service/coupon/CouponCardService.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
service
.
coupon
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.card.CouponCardRespDTO
;
import
cn.iocoder.mall.promotionservice.convert.coupon.card.CouponCardConvert
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponCardMapper
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.validation.annotation.Validated
;
import
java.util.Objects
;
/**
* 优惠劵 Service
*/
@Service
@Validated
public
class
CouponCardService
{
@Autowired
private
CouponCardMapper
couponCardMapper
;
public
CouponCardRespDTO
getCouponCard
(
Integer
userId
,
Integer
couponCardId
)
{
CouponCardDO
card
=
couponCardMapper
.
selectById
(
couponCardId
);
if
(
card
==
null
)
{
return
null
;
}
if
(!
Objects
.
equals
(
userId
,
card
.
getUserId
()))
{
return
null
;
}
return
CouponCardConvert
.
INSTANCE
.
convert
(
card
);
}
}
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/service/coupon/CouponService.java
浏览文件 @
5122b68a
...
...
@@ -5,7 +5,7 @@ import cn.iocoder.common.framework.util.DateUtil;
import
cn.iocoder.mall.promotion.api.enums.*
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.*
;
import
cn.iocoder.mall.promotionservice.convert.coupon.CouponCardConvert
;
import
cn.iocoder.mall.promotionservice.convert.coupon.CouponTemplateConvert
;
import
cn.iocoder.mall.promotionservice.convert.coupon.
card.
CouponTemplateConvert
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponCardDO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.dataobject.coupon.CouponTemplateDO
;
import
cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponCardMapper
;
...
...
@@ -31,11 +31,6 @@ public class CouponService {
// ========== 优惠劵(码)模板 ==========
public
cn
.
iocoder
.
mall
.
promotionservice
.
service
.
coupon
.
bo
.
CouponTemplateBO
getCouponTemplate
(
Integer
couponTemplateId
)
{
CouponTemplateDO
template
=
couponTemplateMapper
.
selectById
(
couponTemplateId
);
return
CouponTemplateConvert
.
INSTANCE
.
convert
(
template
);
}
public
CouponTemplatePageRespDTO
getCouponTemplatePage
(
CouponTemplatePageReqDTO
couponTemplatePageDTO
)
{
CouponTemplatePageRespDTO
couponTemplatePageBO
=
new
CouponTemplatePageRespDTO
();
// 查询分页数据
...
...
@@ -51,7 +46,7 @@ public class CouponService {
return
couponTemplatePageBO
;
}
public
cn
.
iocoder
.
mall
.
promotionservice
.
service
.
coupon
.
bo
.
CouponTemplateBO
addCouponCardTemplate
(
CouponCardTemplateAddBO
couponCardTemplateAddDTO
)
{
public
Integer
addCouponCardTemplate
(
CouponCardTemplateAddBO
couponCardTemplateAddDTO
)
{
// 校验生效日期相关
checkCouponTemplateDateType
(
couponCardTemplateAddDTO
.
getDateType
(),
couponCardTemplateAddDTO
.
getValidStartTime
(),
couponCardTemplateAddDTO
.
getValidEndTime
(),
...
...
@@ -68,7 +63,7 @@ public class CouponService {
template
.
setCreateTime
(
new
Date
());
couponTemplateMapper
.
insert
(
template
);
// 返回成功
return
CouponTemplateConvert
.
INSTANCE
.
convert
(
template
);
return
template
.
getId
(
);
}
public
Boolean
updateCouponCodeTemplate
(
CouponCodeTemplateUpdateBO
couponCodeTemplateUpdateDTO
)
{
...
...
promotion-service-project/promotion-service-app/src/main/java/cn/iocoder/mall/promotionservice/service/coupon/CouponTemplateService.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
service
.
coupon
;
import
cn.iocoder.mall.promotion.api.rpc.coupon.dto.template.CouponTemplateRespDTO
;
import
cn.iocoder.mall.promotionservice.convert.coupon.card.CouponTemplateConvert
;
import
cn.iocoder.mall.promotionservice.dal.mysql.mapper.coupon.CouponTemplateMapper
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.validation.annotation.Validated
;
/**
* 优惠劵模板 Service
*/
@Service
@Validated
public
class
CouponTemplateService
{
@Autowired
private
CouponTemplateMapper
couponTemplateMapper
;
public
CouponTemplateRespDTO
getCouponTemplate
(
Integer
couponCardId
)
{
return
CouponTemplateConvert
.
INSTANCE
.
convert
(
couponTemplateMapper
.
selectById
(
couponCardId
));
}
}
promotion-service-project/promotion-service-app/src/main/resources/application.yaml
浏览文件 @
5122b68a
...
...
@@ -41,7 +41,7 @@ dubbo:
version
:
1.0.0
ProductSkuRpc
:
version
:
1.0.0
ProductSpu
Service
:
ProductSpu
Rpc
:
version
:
1.0.0
# RocketMQ 配置项
...
...
promotion-service-project/promotion-service-app/src/test/java/cn/iocoder/mall/promotionservice/manager/package-info.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
manager
;
promotion-service-project/promotion-service-app/src/test/java/cn/iocoder/mall/promotionservice/manager/price/PriceManagerTest.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
promotionservice
.
manager
.
price
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner
;
import
java.util.Arrays
;
@RunWith
(
SpringJUnit4ClassRunner
.
class
)
@SpringBootTest
(
webEnvironment
=
SpringBootTest
.
WebEnvironment
.
NONE
)
public
class
PriceManagerTest
{
@Autowired
private
PriceManager
priceManager
;
@Test
public
void
testCalcProductPrice
()
{
PriceProductCalcReqDTO
calcReqDTO
=
new
PriceProductCalcReqDTO
();
PriceProductCalcReqDTO
.
Item
item01
=
new
PriceProductCalcReqDTO
.
Item
(
33
,
2
);
// 满足满减送的商品
PriceProductCalcReqDTO
.
Item
item02
=
new
PriceProductCalcReqDTO
.
Item
(
34
,
2
);
// 满足限时折扣的商品
calcReqDTO
.
setItems
(
Arrays
.
asList
(
item01
,
item02
));
PriceProductCalcRespDTO
calcRespDTO
=
priceManager
.
calcProductPrice
(
calcReqDTO
);
System
.
out
.
println
(
calcRespDTO
);
}
}
shop-web-app/pom.xml
浏览文件 @
5122b68a
...
...
@@ -72,6 +72,12 @@
<artifactId>
order-service-api
</artifactId>
<version>
1.0-SNAPSHOT
</version>
</dependency>
<dependency>
<!-- 营销服务 -->
<groupId>
cn.iocoder.mall
</groupId>
<artifactId>
promotion-service-api
</artifactId>
<version>
1.0-SNAPSHOT
</version>
</dependency>
<dependency>
<!-- 系统服务 -->
<groupId>
cn.iocoder.mall
</groupId>
...
...
shop-web-app/src/main/java/cn/iocoder/mall/shopweb/controller/order/CartController.java
浏览文件 @
5122b68a
...
...
@@ -2,6 +2,7 @@ package cn.iocoder.mall.shopweb.controller.order;
import
cn.iocoder.common.framework.vo.CommonResult
;
import
cn.iocoder.mall.security.user.core.context.UserSecurityContextHolder
;
import
cn.iocoder.mall.shopweb.controller.order.vo.cart.CartDetailVO
;
import
cn.iocoder.mall.shopweb.manager.order.cart.CartManager
;
import
io.swagger.annotations.Api
;
import
io.swagger.annotations.ApiImplicitParam
;
...
...
@@ -40,4 +41,10 @@ public class CartController {
return
success
(
cartManager
.
sumCartItemQuantity
(
UserSecurityContextHolder
.
getUserId
()));
}
@GetMapping
(
"/get-detail"
)
@ApiOperation
(
"查询用户的购物车的商品列表"
)
public
CommonResult
<
CartDetailVO
>
getCartDetail
()
{
return
success
(
cartManager
.
getCartDetail
());
}
}
shop-web-app/src/main/java/cn/iocoder/mall/shopweb/controller/order/vo/cart/CartDetailVO.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
shopweb
.
controller
.
order
.
vo
.
cart
;
import
cn.iocoder.mall.promotion.api.rpc.activity.dto.PromotionActivityRespDTO
;
import
cn.iocoder.mall.shopweb.controller.product.vo.attr.ProductAttrKeyValueRespVO
;
import
io.swagger.annotations.ApiModel
;
import
lombok.Data
;
import
lombok.experimental.Accessors
;
import
java.util.List
;
@ApiModel
(
value
=
"用户的购物车明细 VO"
)
@Data
@Accessors
(
chain
=
true
)
public
class
CartDetailVO
{
/**
* 商品分组数组
*/
private
List
<
ItemGroup
>
itemGroups
;
/**
* 费用
*/
private
Fee
fee
;
/**
* 商品分组
*
* 多个商品,参加同一个活动,从而形成分组。
*/
@Data
@Accessors
(
chain
=
true
)
public
static
class
ItemGroup
{
/**
* 优惠活动
*/
private
PromotionActivityRespDTO
activity
;
// TODO 芋艿,偷懒
/**
* 促销减少的金额
*
* 1. 若未参与促销活动,或不满足促销条件,返回 null
* 2. 该金额,已经分摊到每个 Item 的 discountTotal ,需要注意。
*/
private
Integer
activityDiscountTotal
;
/**
* 商品数组
*/
private
List
<
Sku
>
items
;
}
@Data
@Accessors
(
chain
=
true
)
public
static
class
Sku
{
// SKU 自带信息
/**
* sku 编号
*/
private
Integer
id
;
/**
* SPU 信息
*/
private
Spu
spu
;
/**
* 图片地址
*/
private
String
picURL
;
/**
* 规格值数组
*/
private
List
<
ProductAttrKeyValueRespVO
>
attrs
;
// TODO 后面改下
/**
* 价格,单位:分
*/
private
Integer
price
;
/**
* 库存数量
*/
private
Integer
quantity
;
// 非 SKU 自带信息
/**
* 购买数量
*/
private
Integer
buyQuantity
;
/**
* 是否选中
*/
private
Boolean
selected
;
/**
* 优惠活动
*/
private
PromotionActivityRespDTO
activity
;
// TODO 芋艿,偷懒
/**
* 原始单价,单位:分。
*/
private
Integer
originPrice
;
/**
* 购买单价,单位:分
*/
private
Integer
buyPrice
;
/**
* 最终价格,单位:分。
*/
private
Integer
presentPrice
;
/**
* 购买总金额,单位:分
*
* 用途类似 {@link #presentTotal}
*/
private
Integer
buyTotal
;
/**
* 优惠总金额,单位:分。
*/
private
Integer
discountTotal
;
/**
* 最终总金额,单位:分。
*
* 注意,presentPrice * quantity 不一定等于 presentTotal 。
* 因为,存在无法整除的情况。
* 举个例子,presentPrice = 8.33 ,quantity = 3 的情况,presentTotal 有可能是 24.99 ,也可能是 25 。
* 所以,需要存储一个该字段。
*/
private
Integer
presentTotal
;
}
@Data
@Accessors
(
chain
=
true
)
public
static
class
Spu
{
/**
* SPU 编号
*/
private
Integer
id
;
// ========== 基本信息 =========
/**
* SPU 名字
*/
private
String
name
;
/**
* 分类编号
*/
private
Integer
cid
;
/**
* 商品主图地址
*
* 数组,以逗号分隔
*
* 建议尺寸:800*800像素,你可以拖拽图片调整顺序,最多上传15张
*/
private
List
<
String
>
picUrls
;
}
/**
* 费用(合计)
*/
@Data
@Accessors
(
chain
=
true
)
public
static
class
Fee
{
/**
* 购买总价
*/
private
Integer
buyTotal
;
/**
* 优惠总价
*
* 注意,满多少元包邮,不算在优惠中。
*/
private
Integer
discountTotal
;
/**
* 邮费
*/
private
Integer
postageTotal
;
/**
* 最终价格
*
* 计算公式 = 总价 - 优惠总价 + 邮费
*/
private
Integer
presentTotal
;
public
Fee
()
{
}
public
Fee
(
Integer
buyTotal
,
Integer
discountTotal
,
Integer
postageTotal
,
Integer
presentTotal
)
{
this
.
buyTotal
=
buyTotal
;
this
.
discountTotal
=
discountTotal
;
this
.
postageTotal
=
postageTotal
;
this
.
presentTotal
=
presentTotal
;
}
}
/**
* 邮费信息 TODO 芋艿,未完成
*/
@Data
@Accessors
(
chain
=
true
)
public
static
class
Postage
{
/**
* 需要满足多少钱,可以包邮。单位:分
*/
private
Integer
threshold
;
}
}
shop-web-app/src/main/java/cn/iocoder/mall/shopweb/convert/order/CartConvert.java
0 → 100644
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
shopweb
.
convert
.
order
;
import
org.mapstruct.Mapper
;
import
org.mapstruct.factory.Mappers
;
@Mapper
public
interface
CartConvert
{
CartConvert
INSTANCE
=
Mappers
.
getMapper
(
CartConvert
.
class
);
}
shop-web-app/src/main/java/cn/iocoder/mall/shopweb/manager/order/cart/CartManager.java
浏览文件 @
5122b68a
package
cn
.
iocoder
.
mall
.
shopweb
.
manager
.
order
.
cart
;
import
cn.iocoder.common.framework.util.CollectionUtils
;
import
cn.iocoder.common.framework.vo.CommonResult
;
import
cn.iocoder.mall.orderservice.rpc.cart.CartRpc
;
import
cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemAddReqDTO
;
import
cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemListReqDTO
;
import
cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemRespDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.PriceRpc
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcReqDTO
;
import
cn.iocoder.mall.promotion.api.rpc.price.dto.PriceProductCalcRespDTO
;
import
cn.iocoder.mall.shopweb.controller.order.vo.cart.CartDetailVO
;
import
org.apache.dubbo.config.annotation.DubboReference
;
import
org.springframework.stereotype.Service
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* 购物车 Manager
*/
...
...
@@ -14,6 +25,8 @@ public class CartManager {
@DubboReference
(
version
=
"${dubbo.consumer.ProductCategoryRpc.version}"
)
private
CartRpc
cartRpc
;
@DubboReference
(
version
=
"${dubbo.consumer.PriceRpc.version}"
)
private
PriceRpc
priceRpc
;
/**
* 添加商品到购物车
...
...
@@ -40,4 +53,33 @@ public class CartManager {
return
sumCartItemQuantityResult
.
getData
();
}
/**
* 查询用户的购物车的商品列表
*
* @return 商品列表
*/
public
CartDetailVO
getCartDetail
(
Integer
userId
)
{
// 获得购物车中选中的
CommonResult
<
List
<
CartItemRespDTO
>>
listCartItemsResult
=
cartRpc
.
listCartItems
(
new
CartItemListReqDTO
().
setUserId
(
userId
));
listCartItemsResult
.
checkError
();
// 购物车为空时,构造空的 UsersOrderConfirmCreateVO 返回
if
(
CollectionUtils
.
isEmpty
(
listCartItemsResult
.
getData
()))
{
CartDetailVO
result
=
new
CartDetailVO
();
result
.
setItemGroups
(
Collections
.
emptyList
());
result
.
setFee
(
new
CartDetailVO
.
Fee
(
0
,
0
,
0
,
0
));
return
result
;
}
// 计算选中的商品价格
CommonResult
<
PriceProductCalcRespDTO
>
calcProductPriceResult
=
priceRpc
.
calcProductPrice
(
new
PriceProductCalcReqDTO
().
setUserId
(
userId
)
.
setItems
(
listCartItemsResult
.
getData
().
stream
()
.
filter
(
CartItemRespDTO:
:
getSelected
)
.
map
(
cartItem
->
new
PriceProductCalcReqDTO
.
Item
(
cartItem
.
getSkuId
(),
cartItem
.
getQuantity
()))
.
collect
(
Collectors
.
toList
())));
calcProductPriceResult
.
checkError
();
// 拼接结果
// 执行数据拼装
return
null
;
}
}
shop-web-app/src/main/resources/application.yml
浏览文件 @
5122b68a
...
...
@@ -39,6 +39,8 @@ dubbo:
version
:
1.0.0
SearchProductRpc
:
version
:
1.0.0
PriceRpc
:
version
:
1.0.0
# Swagger 配置项
swagger
:
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论