前后端:商品确认页,增加优惠劵接入

后端:修改了价格计算的逻辑
This commit is contained in:
YunaiV 2019-04-18 22:46:27 +08:00
parent 8962631b6a
commit 7277ecb2d4
34 changed files with 550 additions and 143 deletions

View File

@ -1 +0,0 @@
package cn.iocoder.mall.admin;

View File

@ -16,7 +16,6 @@ export function getProductRecommendList() {
url: '/promotion-api/users/product_recommend/list',
method: 'get',
params: {
id,
}
});
}

View File

@ -6,6 +6,7 @@ import App from './App.vue';
import VueLazyload from 'vue-lazyload'
import components from './config/components.js';
import { Dialog } from 'vant';
import { CouponCell, CouponList } from 'vant';
import { formatDate } from './utils/date.js';
@ -13,6 +14,7 @@ Vue.use(components);
Vue.use(VueLazyload);
Vue.use(Dialog);
Vue.use(CouponCell).use(CouponList);
new Vue({
router,

View File

@ -133,13 +133,14 @@ export default {
}
return text;
},
formatItemGroupDiscountPriceText() {
let price = 0;
for (let i in this.itemGroups) {
let itemGroup = this.itemGroups[i];
price += itemGroup.fee.discountTotal;
}
return price > 0 ? '立减 ' + price / 100.0 + ' 元' : '';
// let price = 0;
// for (let i in this.itemGroups) {
// let itemGroup = this.itemGroups[i];
// price += itemGroup.activityDiscountTotal || 0;
// }
return this.fee.discountTotal > 0 ? '立减 ' + this.fee.discountTotal / 100.0 + ' 元' : '';
},
calCheckedItemIds() {
@ -226,7 +227,7 @@ export default {
return {
...item.spu,
quantity: item.buyQuantity,
price: item.discountPrice || item.price,
price: item.buyPrice || item.price,
sku: {
...item,
spu: undefined,

View File

@ -9,8 +9,8 @@
<!-- TODO 这里需要优化下芋艿 -->
<van-cell-group>
<van-cell>
<div v-if="calSkuPriceResult.originalPrice && calSkuPriceResult.originalPrice !== calSkuPriceResult.presentPrice">
<span class="goods-price">{{ formatPrice(calSkuPriceResult.presentPrice) }}</span>
<div v-if="calSkuPriceResult.originalPrice && calSkuPriceResult.originalPrice !== calSkuPriceResult.buyPrice">
<span class="goods-price">{{ formatPrice(calSkuPriceResult.buyPrice) }}</span>
<span class="goods-market-price">{{ formatPrice(calSkuPriceResult.originalPrice) }}</span>
</div>
<div v-else>
@ -283,7 +283,7 @@
for (let i in this.vanSku.list) {
let sku = this.vanSku.list[i];
if (sku.id === skuId) {
sku.price = data.presentPrice;
sku.price = data.buyPrice;
break;
}
}

View File

@ -43,21 +43,36 @@
/>
</van-cell-group>
<div style="height:15px;"></div>
<van-cell-group class="total">
<van-cell title="优惠券" is-link value="抵扣¥5.00"/>
</van-cell-group>
<!-- 优惠券单元格 -->
<van-coupon-cell
:coupons="coupons"
:chosen-coupon="chosenCoupon"
@click="showCouponPopup = true"
/>
<!-- 优惠券列表 -->
<van-popup v-model="showCouponPopup" position="bottom">
<van-coupon-list
:coupons="coupons"
:chosen-coupon="chosenCoupon"
:disabled-coupons="disabledCoupons"
@change="onCouponChange"
@exchange="onCouponExchange"
/>
</van-popup>
<div style="height:15px;"></div>
<van-cell-group class="total">
<van-cell title="商品总额" :value="fee.originalTotal"/>
<van-cell title="运费" :value="+ fee.postageTotal"/>
<van-cell title="折扣" :value="- fee.discountTotal"/>
<van-cell title="实付金额" :value="fee.presentTotal" style="font-weight: 700;"/>
<van-cell title="商品总额" :value="fee.buyTotal / 100.0"/>
<van-cell title="运费" :value="+ fee.postageTotal / 100.0"/>
<van-cell title="折扣" :value="- fee.discountTotal / 100.0"/>
<van-cell title="实付金额" :value="fee.presentTotal / 100.0" style="font-weight: 700;"/>
</van-cell-group>
<div style="height:50px;"></div>
<van-submit-bar
:price="3050"
:price="fee.presentTotal"
button-text="提交订单"
label='实付金额:'
@submit="onSubmit"
@ -75,7 +90,8 @@
createOrderFromCart
} from '../../api/order';
import {GetDefaultAddress} from '../../api/user';
import orderStore from '../../store/order'
import orderStore from '../../store/order';
import { Dialog } from 'vant';
export default {
data() {
@ -89,6 +105,8 @@
addressData: {
},
// +
itemGroups: [],
fee: {
originalTotal: undefined,
@ -96,12 +114,36 @@
postageTotal: undefined,
presentTotal: undefined,
},
products: [],
// products: [], //
//
showCouponPopup: false,
coupons: [],
disabledCoupons: [],
chosenCoupon: -1,
};
},
methods: {
onCouponChange(a, b, c) {
debugger;
},
onCouponExchange(a, b, c) {
Dialog.alert({
title: '系统提示',
message: '暂未开发', // TODO
});
},
onSubmit() {
const userAddressId = this.addressData.id;
if (!userAddressId) {
Dialog.alert({
title: '系统提示',
message: '请选择收获地址',
});
return;
}
const remark = '';
if (this.from === 'direct_order') {
@ -116,7 +158,7 @@
remark,
}).then(result => {
if (result) {
const { orderNo } = result;
// const { orderNo } = result;
this.$router.push({ //
path:`/order/success`, //
query:{ //pushquery使
@ -128,7 +170,7 @@
} else if (this.from === 'cart') {
createOrderFromCart(userAddressId, remark).then(result => {
if (result) {
const { orderNo } = result;
// const { orderNo } = result;
this.$router.push({ //
path:`/order/success`, //
query:{ //pushquery使
@ -140,16 +182,34 @@
}
},
convertProduct(item) {
// debugger;
return {
...item.spu,
quantity: item.buyQuantity,
price: item.price,
price: item.buyPrice || item.price,
sku: {
...item,
spu: undefined,
}
};
},
convertCouponList(cards) {
let newCards = [];
for (let i in cards) {
let card = cards[i];
newCards.push({
id: card.id,
name: card.title,
condition: '满 ' + card.priceAvailable / 100.0 + ' 元可用',
startAt: card.validStartTime / 1000,
endAt: card.validEndTime / 1000,
// description: '',
reason: card.unavailableReason,
value: card.preferentialType === 1 ? card.priceOff : card.percentOff,
valueDesc: card.preferentialType === 1 ? card.priceOff / 100 : card.percentOff / 10.0,
unitDesc: card.preferentialType === 1 ? '元' : '折'
})
}
return newCards;
}
},
mounted: function() {
@ -166,11 +226,19 @@
getOrderConfirmCreateOrder(this.skuId, this.quantity).then(data => {
this.itemGroups = data.itemGroups;
this.fee = data.fee;
//
})
} else if (this.from === 'cart') {
getCartConfirmCreateOrder().then(data => {
this.itemGroups = data.itemGroups;
this.fee = data.fee;
//
this.coupons = this.convertCouponList(data.couponCards.filter(function (element) {
return element.available;
}));
this.disabledCoupons = this.convertCouponList(data.couponCards.filter(function (element) {
return !element.available;
}));
})
}
},

View File

@ -33,7 +33,7 @@
<span class="amountSign"></span>
</div>
<div class="condition">
<span> {{item.priceAvailable}} 元可用</span>
<span> {{item.priceAvailable / 100.0}} 元可用</span>
</div>
</div>
</div>

View File

@ -11,6 +11,8 @@ import cn.iocoder.mall.order.application.convert.CartConvert;
import cn.iocoder.mall.order.application.vo.UsersCalcSkuPriceVO;
import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*;
@ -28,6 +30,8 @@ public class UsersCartController {
private CartService cartService;
@Reference(validation = "true")
private OrderService orderService;
@Reference(validation = "true")
private CouponService couponService;
@PostMapping("add")
public CommonResult<Integer> add(@RequestParam("skuId") Integer skuId,
@ -102,8 +106,9 @@ public class UsersCartController {
@GetMapping("/confirm_create_order")
public CommonResult<UsersOrderConfirmCreateVO> getConfirmCreateOrder() {
Integer userId = UserSecurityContextHolder.getContext().getUserId();
// 获得购物车中选中的
List<CartItemBO> cartItems = cartService.list(UserSecurityContextHolder.getContext().getUserId(), true).getData();
List<CartItemBO> cartItems = cartService.list(userId, true).getData();
// 购物车为空时构造空的 UsersOrderConfirmCreateVO 返回
if (cartItems.isEmpty()) {
UsersOrderConfirmCreateVO result = new UsersOrderConfirmCreateVO();
@ -116,8 +121,13 @@ public class UsersCartController {
if (calcOrderPriceResult.isError()) {
return CommonResult.error(calcOrderPriceResult);
}
// 获得优惠劵
CalcOrderPriceBO calcOrderPrice = calcOrderPriceResult.getData();
List<CouponCardAvailableBO> couponCards = couponService.getCouponCardList(userId,
CartConvert.INSTANCE.convertList(calcOrderPrice.getItemGroups())).getData();
// 执行数据拼装
return CommonResult.success(CartConvert.INSTANCE.convert(calcOrderPriceResult.getData()));
return CommonResult.success(CartConvert.INSTANCE.convert(calcOrderPrice)
.setCouponCards(couponCards));
}
private CommonResult<CalcOrderPriceBO> list0(List<CartItemBO> cartItems) {

View File

@ -5,9 +5,14 @@ import cn.iocoder.mall.order.api.bo.CalcSkuPriceBO;
import cn.iocoder.mall.order.application.vo.UsersCalcSkuPriceVO;
import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.promotion.api.dto.CouponCardSpuDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Mapper
public interface CartConvert {
@ -19,4 +24,17 @@ public interface CartConvert {
UsersCalcSkuPriceVO convert2(CalcSkuPriceBO calcSkuPriceBO);
default List<CouponCardSpuDTO> convertList(List<CalcOrderPriceBO.ItemGroup> itemGroups) {
List<CouponCardSpuDTO> items = new ArrayList<>();
itemGroups.forEach(itemGroup -> items.addAll(itemGroup.getItems().stream().map(
item -> new CouponCardSpuDTO()
.setSpuId(item.getSpu().getId())
.setSkuId(item.getId())
.setCategoryId(item.getSpu().getCid())
.setPrice(item.getBuyPrice())
.setQuantity(item.getBuyQuantity()))
.collect(Collectors.toList())));
return items;
}
}

View File

@ -29,6 +29,6 @@ public class UsersCalcSkuPriceVO {
/**
* 最终价格单位
*/
private Integer presentPrice;
private Integer buyPrice;
}

View File

@ -36,25 +36,16 @@ public class UsersCartDetailVO {
*/
private PromotionActivityBO activity; // TODO 芋艿偷懒
/**
* 优惠活动是否生效
* 促销减少的金额
*
* 多个商品参与某个活动因为并发达到条件所以会存在未生效的情况所以一共有三种情况
*
* 1. activity 非空activityEffectEffective true参与活动且生效
* 2. activity 非空activityEffectEffective false 参与活动并未生效
* 3. activity 为空activityEffectEffective 为空并未参与活动
* 1. 若未参与促销活动或不满足促销条件返回 null
* 2. 该金额已经分摊到每个 Item discountTotal 需要注意
*/
private Boolean activityEffectEffective;
private Integer activityDiscountTotal;
/**
* 商品数组
*/
private List<Sku> items;
/**
* 费用
*
* TODO 芋艿这里先偷懒postageTotal 字段用不到
*/
private Fee fee;
}
@ -103,15 +94,36 @@ public class UsersCartDetailVO {
*/
private PromotionActivityBO activity;
/**
* 折扣价
* 原始单价单位
*/
private Integer discountPrice;
private Integer originPrice;
/**
* 费用
*
* TODO 芋艿这里先偷懒postageTotal 字段用不到
* 购买单价单位
*/
private Fee fee;
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;
}
@ -152,9 +164,9 @@ public class UsersCartDetailVO {
public static class Fee {
/**
* 总价
* 购买总价
*/
private Integer originalTotal;
private Integer buyTotal;
/**
* 优惠总价
*
@ -175,8 +187,8 @@ public class UsersCartDetailVO {
public Fee() {
}
public Fee(Integer originalTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.originalTotal = originalTotal;
public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.buyTotal = buyTotal;
this.discountTotal = discountTotal;
this.postageTotal = postageTotal;
this.presentTotal = presentTotal;

View File

@ -1,6 +1,7 @@
package cn.iocoder.mall.order.application.vo;
import cn.iocoder.mall.product.api.bo.ProductAttrAndValuePairBO;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO;
import cn.iocoder.mall.promotion.api.bo.PromotionActivityBO;
import lombok.Data;
import lombok.experimental.Accessors;
@ -19,6 +20,10 @@ public class UsersOrderConfirmCreateVO {
* 费用
*/
private Fee fee;
/**
* 优惠劵列表 TODO 芋艿后续改改
*/
private List<CouponCardAvailableBO> couponCards;
/**
* 商品分组
@ -79,9 +84,36 @@ public class UsersOrderConfirmCreateVO {
*/
private Integer buyQuantity;
/**
* 折扣价
* 原始单价单位
*/
private Integer discountPrice;
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;
}
@ -122,9 +154,9 @@ public class UsersOrderConfirmCreateVO {
public static class Fee {
/**
* 总价
* 购买总价
*/
private Integer originalTotal;
private Integer buyTotal;
/**
* 优惠总价
*
@ -145,8 +177,8 @@ public class UsersOrderConfirmCreateVO {
public Fee() {
}
public Fee(Integer originalTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.originalTotal = originalTotal;
public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.buyTotal = buyTotal;
this.discountTotal = discountTotal;
this.postageTotal = postageTotal;
this.presentTotal = presentTotal;

View File

@ -20,6 +20,8 @@ public class CalcOrderPriceBO {
private List<ItemGroup> itemGroups;
/**
* 邮费信息
*
* TODO 芋艿暂时未弄
*/
private Postage postage;
/**
@ -42,25 +44,22 @@ public class CalcOrderPriceBO {
// TODO 芋艿目前只会有满减送的情况未来有新的促销方式可能需要改成数组
private PromotionActivityBO activity;
/**
* 优惠活动是否生效
* 促销减少的金额
*
* 多个商品参与某个活动因为并发达到条件所以会存在未生效的情况所以一共有三种情况
*
* 1. activity 非空activityEffectEffective true参与活动且生效
* 2. activity 非空activityEffectEffective false 参与活动并未生效
* 3. activity 为空activityEffectEffective 为空并未参与活动
* 1. 若未参与促销活动或不满足促销条件返回 null
* 2. 该金额已经分摊到每个 Item discountTotal 需要注意
*/
private Boolean activityEffectEffective;
private Integer activityDiscountTotal;
/**
* 商品数组
*/
private List<Item> items;
/**
* 费用
*
* TODO 芋艿这里先偷懒postageTotal 字段用不到
*/
private Fee fee;
// /**
// * 费用
// *
// * TODO 芋艿这里先偷懒postageTotal 字段用不到
// */
// private Fee fee; // 注释原因不用这里了
}
@ -81,15 +80,36 @@ public class CalcOrderPriceBO {
*/
private PromotionActivityBO activity;
/**
* 费用
*
* TODO 芋艿这里先偷懒postageTotal 字段用不到
* 原始单价单位
*/
private Fee fee;
private Integer originPrice;
/**
* 折扣价
* 购买单价单位
*/
private Integer discountPrice;
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;
}
@ -101,9 +121,9 @@ public class CalcOrderPriceBO {
public static class Fee {
/**
* 总价
* 购买总价
*/
private Integer originalTotal;
private Integer buyTotal;
/**
* 优惠总价
*
@ -124,8 +144,8 @@ public class CalcOrderPriceBO {
public Fee() {
}
public Fee(Integer originalTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.originalTotal = originalTotal;
public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {
this.buyTotal = buyTotal;
this.discountTotal = discountTotal;
this.postageTotal = postageTotal;
this.presentTotal = presentTotal;

View File

@ -24,8 +24,8 @@ public class CalcSkuPriceBO {
*/
private Integer originalPrice;
/**
* 最终价格单位
* 购买价格单位
*/
private Integer presentPrice;
private Integer buyPrice;
}

View File

@ -49,18 +49,62 @@ public class OrderItemDO extends DeletableDO {
*/
private Integer quantity;
/**
* ()
* 商品成交单()
*/
@Deprecated
private Integer price;
/**
* 支付金额实付金额
*/
@Deprecated
private Integer payAmount;
/**
* 物流金额 ()
*/
@Deprecated
private Integer logisticsPrice;
/**
* 原始单价单位
*/
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;
// 如上字段举个例子
// 假设购买三个 quantity = 3
// originPrice = 15
// 使用限时折扣单品优惠8 buyPrice = 12
// 开始算总的价格
// buyTotal = buyPrice * quantity = 12 * 3 = 36
// discountTotal 假设有满减送分组优惠 20 10 并且使用优惠劵满 1.01 1 discountTotal = 10 + 1 = 11
// presentTotal = buyTotal - discountTotal = 24 - 11 = 13
// 最终 presentPrice = presentTotal / quantity = 13 / 3 = 4.33
///
/// 时间信息

View File

@ -0,0 +1,46 @@
package cn.iocoder.mall.order.biz.dataobject;
/**
* 订单优惠明细
*/
// TODO 芋艿 后续在完善
public class OrderPreferentialDO {
/**
* 编号
*/
private Integer id;
/**
* 类型
*
* 1 - 促销活动
* 2 - 优惠劵
*/
private Integer type;
// TODO 芋艿 优惠劵编号 or 促销活动编号
/**
* 订单编号
*/
private Integer orderId;
/**
* 商品 SPU 编号
*/
private Integer spuId;
/**
* 商品 SKU 编号
*/
private Integer skuId;
/**
* 商品数量
*/
private Integer quantity;
/**
* 传入时的价格
*/
private Integer originTotal;
/**
* 总优惠价格
*/
private Integer discountTotal;
}

View File

@ -1,5 +0,0 @@
/**
* @author Sin
* @time 2019-03-16 13:49
*/
package cn.iocoder.mall.order.biz;

View File

@ -178,17 +178,17 @@ public class CartServiceImpl implements CartService {
List<CalcOrderPriceBO.ItemGroup> itemGroups = groupByFullPrivilege(items, activityList);
calcOrderPriceBO.setItemGroups(itemGroups);
// 4. 计算最终的价格
Integer originalTotal = 0;
Integer presentTotal = 0;
Integer discountTotal = 0;
int buyTotal = 0;
int discountTotal = 0;
int presentTotal = 0;
for (CalcOrderPriceBO.ItemGroup itemGroup : calcOrderPriceBO.getItemGroups()) {
originalTotal += itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getFee().getOriginalTotal() : 0).sum();
discountTotal += itemGroup.getFee().getDiscountTotal() + itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getFee().getDiscountTotal() : 0).sum();
presentTotal += itemGroup.getFee().getPresentTotal();
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(originalTotal - discountTotal == presentTotal,
String.format("价格合计( %d - %d == %d )不正确", originalTotal, discountTotal, presentTotal));
calcOrderPriceBO.setFee(new CalcOrderPriceBO.Fee(originalTotal, discountTotal, 0, presentTotal));
Assert.isTrue(buyTotal - discountTotal == presentTotal,
String.format("价格合计( %d - %d == %d )不正确", buyTotal, discountTotal, presentTotal));
calcOrderPriceBO.setFee(new CalcOrderPriceBO.Fee(buyTotal, discountTotal, 0, presentTotal));
// 返回
return CommonResult.success(calcOrderPriceBO);
}
@ -215,7 +215,7 @@ public class CartServiceImpl implements CartService {
// 如果无促销活动则直接返回默认结果即可
List<PromotionActivityBO> activityList = activityListResult.getData();
if (activityList.isEmpty()) {
return CommonResult.success(new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setPresentPrice(sku.getPrice()));
return CommonResult.success(new CalcSkuPriceBO().setOriginalPrice(sku.getPrice()).setBuyPrice(sku.getPrice()));
}
// 如果有促销活动则开始做计算 TODO 芋艿因为现在暂时只有限时折扣 + 满减送所以写的比较简单先
PromotionActivityBO fullPrivilege = findPromotionActivityByType(activityList, PromotionActivityTypeEnum.FULL_PRIVILEGE);
@ -223,7 +223,7 @@ public class CartServiceImpl implements CartService {
Integer presentPrice = calcSkuPriceByTimeLimitDiscount(sku, timeLimitedDiscount);
// 返回结果
return CommonResult.success(new CalcSkuPriceBO().setFullPrivilege(fullPrivilege).setTimeLimitedDiscount(timeLimitedDiscount)
.setOriginalPrice(sku.getPrice()).setPresentPrice(presentPrice));
.setOriginalPrice(sku.getPrice()).setBuyPrice(presentPrice));
}
private List<CalcOrderPriceBO.Item> initCalcOrderPriceItems(List<ProductSkuDetailBO> skus,
@ -237,10 +237,12 @@ public class CartServiceImpl implements CartService {
item.setSelected(calcOrderItem.getSelected());
item.setBuyQuantity(calcOrderItem.getQuantity());
// 计算初始价格
CalcOrderPriceBO.Fee fee = new CalcOrderPriceBO.Fee(0, 0, 0, 0);
fee.setOriginalTotal(item.getPrice() * item.getBuyQuantity());
fee.setPresentTotal(fee.getOriginalTotal());
item.setFee(fee);
item.setOriginPrice(sku.getPrice());
item.setBuyPrice(sku.getPrice());
item.setPresentPrice(sku.getPrice());
item.setBuyTotal(sku.getPrice() * calcOrderItem.getQuantity());
item.setDiscountTotal(0);
item.setPresentTotal(item.getBuyTotal());
}
return items;
}
@ -264,10 +266,10 @@ public class CartServiceImpl implements CartService {
// 设置优惠
item.setActivity(timeLimitedDiscount);
// 设置价格
item.setDiscountPrice(newPrice);
CalcOrderPriceBO.Fee fee = item.getFee();
fee.setDiscountTotal(fee.getDiscountTotal() + (item.getPrice() - newPrice) * item.getBuyQuantity());
fee.setPresentTotal(fee.getOriginalTotal() - fee.getDiscountTotal() + fee.getPostageTotal());
item.setBuyPrice(newPrice);
item.setBuyTotal(newPrice * item.getBuyQuantity());
item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
}
}
@ -302,14 +304,11 @@ public class CartServiceImpl implements CartService {
}
// 处理未参加活动的商品形成一个分组
if (!items.isEmpty()) {
CalcOrderPriceBO.ItemGroup itemGroup = new CalcOrderPriceBO.ItemGroup()
.setItems(items);
itemGroups.add(itemGroup);
itemGroups.add(new CalcOrderPriceBO.ItemGroup().setItems(items));
}
// 计算每个分组的价格
for (CalcOrderPriceBO.ItemGroup itemGroup : itemGroups) {
itemGroup.setFee(calcSkuPriceByFullPrivilege(itemGroup));
itemGroup.setActivityEffectEffective(itemGroup.getFee().getDiscountTotal() > 0);
itemGroup.setActivityDiscountTotal(calcSkuPriceByFullPrivilege(itemGroup));
}
// 返回结果
return itemGroups;
@ -346,17 +345,18 @@ public class CartServiceImpl implements CartService {
throw new IllegalArgumentException(String.format("折扣活动(%s) 的优惠类型不正确", timeLimitedDiscount.toString()));
}
private CalcOrderPriceBO.Fee calcSkuPriceByFullPrivilege(CalcOrderPriceBO.ItemGroup itemGroup) {
private Integer calcSkuPriceByFullPrivilege(CalcOrderPriceBO.ItemGroup itemGroup) {
if (itemGroup.getActivity() == null) {
Integer originalTotal = itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getFee().getPresentTotal() : 0).sum();
return new CalcOrderPriceBO.Fee(originalTotal, 0, 0, originalTotal);
return null;
}
PromotionActivityBO activity = itemGroup.getActivity();
Assert.isTrue(PromotionActivityTypeEnum.FULL_PRIVILEGE.getValue().equals(activity.getActivityType()),
"传入的必须的满减送活动必须是满减送");
// 获得优惠信息
Integer itemCnt = itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getBuyQuantity() : 0).sum();
Integer originalTotal = itemGroup.getItems().stream().mapToInt(item -> item.getSelected() ? item.getFee().getPresentTotal() : 0).sum();
List<CalcOrderPriceBO.Item> items = itemGroup.getItems().stream().filter(item -> !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())) {
@ -369,7 +369,7 @@ public class CartServiceImpl implements CartService {
}).collect(Collectors.toList());
// 获得不到优惠信息返回原始价格
if (privileges.isEmpty()) {
return new CalcOrderPriceBO.Fee(originalTotal, 0, 0, originalTotal);
return null;
}
// 获得到优惠信息进行价格计算
PromotionActivityBO.FullPrivilege.Privilege privilege = privileges.get(privileges.size() - 1);
@ -393,7 +393,26 @@ public class CartServiceImpl implements CartService {
} else {
throw new IllegalArgumentException(String.format("满减送促销(%s) 的优惠类型不正确", activity.toString()));
}
return new CalcOrderPriceBO.Fee(originalTotal, originalTotal - presentTotal, 0, presentTotal);
Integer 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;
}
Assert.isTrue(discountPart > 0, "优惠金额必须大于 0");
item.setDiscountTotal(item.getDiscountTotal() + discountPart);
item.setPresentTotal(item.getBuyTotal() - item.getDiscountTotal());
item.setPresentPrice(item.getPresentTotal() / item.getBuyQuantity());
}
return originalTotal - presentTotal;
}
private PromotionActivityBO findPromotionActivityByType(List<PromotionActivityBO> activityList, PromotionActivityTypeEnum type) {

View File

@ -212,6 +212,8 @@ public class OrderServiceImpl implements OrderService {
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_GET_GOODS_INFO_INCORRECT.getCode());
}
//
// 设置 orderItem
Map<Integer, ProductSkuDetailBO> productSpuBOMap = productResult.getData()
@ -293,7 +295,7 @@ public class OrderServiceImpl implements OrderService {
.setDeleted(DeletedStatusEnum.DELETED_NO.getValue())
.setCreateTime(new Date())
.setUpdateTime(null);
orderItemMapper.insert(orderItemDO);
orderItemMapper.insert(orderItemDO); // TODO 芋艿需要改成一次性插入
});
// 创建预订单

View File

@ -1 +0,0 @@
package cn.iocoder.mall.pay.biz.mq;

View File

@ -1 +0,0 @@
package cn.iocoder.mall.product;

View File

@ -2,13 +2,12 @@ package cn.iocoder.mall.promotion.api;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.promotion.api.bo.CouponCardBO;
import cn.iocoder.mall.promotion.api.bo.CouponCardPageBO;
import cn.iocoder.mall.promotion.api.bo.CouponTemplateBO;
import cn.iocoder.mall.promotion.api.bo.CouponTemplatePageBO;
import cn.iocoder.mall.promotion.api.bo.*;
import cn.iocoder.mall.promotion.api.constant.CouponTemplateStatusEnum;
import cn.iocoder.mall.promotion.api.dto.*;
import java.util.List;
public interface CouponService {
// ========== 优惠劵模板 ==========
@ -93,6 +92,17 @@ public interface CouponService {
*/
CommonResult<Boolean> cancelUseCouponCard(Integer userId, Integer couponCardId);
/**
* 获得用户所有优惠劵并标明是否可用
*
* 注意spus 是作为条件判断优惠劵是否可用
*
* @param userId 用户编号
* @param spus 匹配的商品/分类
* @return 优惠劵列表
*/
CommonResult<List<CouponCardAvailableBO>> getCouponCardList(Integer userId, List<CouponCardSpuDTO> spus);
// ========== 优惠码 ==========
/**

View File

@ -0,0 +1,24 @@
package cn.iocoder.mall.promotion.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 可用优惠劵 BO
*
* 注意如果优惠劵不可用标记 available = false 并写明 unavailableReason 原因
*/
@Data
@Accessors(chain = true)
public class CouponCardAvailableBO extends CouponCardBO {
/**
* 是否可用
*/
private Boolean available;
/**
* 不可用原因
*/
private String unavailableReason;
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.mall.promotion.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 优惠劵商品 DTO
*
* 主要用于 {@link cn.iocoder.mall.promotion.api.CouponService#getCouponCardList(Integer, List)}
*/
@Data
@Accessors(chain = true)
public class CouponCardSpuDTO implements Serializable {
/**
* 商品 SPU 编号
*/
private Integer spuId;
/**
* 商品 SKU 编号
*/
private Integer skuId;
/**
* 分类编号
*/
private Integer categoryId;
/**
* 价格
*/
private Integer price;
/**
* 数量
*/
private Integer quantity;
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.mall.promotion.biz.convert;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO;
import cn.iocoder.mall.promotion.api.bo.CouponCardBO;
import cn.iocoder.mall.promotion.biz.dataobject.CouponCardDO;
import org.mapstruct.Mapper;
@ -22,4 +23,7 @@ public interface CouponCardConvert {
@Mappings({})
CouponCardBO convert(CouponCardDO card);
@Mappings({})
CouponCardAvailableBO convert2(CouponCardDO card, boolean x); // TODO 芋艿临时用来解决 mapstruct 无法正确匹配方法的问题
}

View File

@ -11,6 +11,9 @@ public interface CouponCardMapper {
CouponCardDO selectById(@Param("id") Integer id);
List<CouponCardDO> selectListByUserIdAndStatus(@Param("userId") Integer userId,
@Param("status") Integer status);
List<CouponCardDO> selectListByPage(@Param("userId") Integer userId,
@Param("status") Integer status,
@Param("offset") Integer offset,

View File

@ -4,6 +4,7 @@ import cn.iocoder.mall.promotion.biz.dataobject.CouponTemplateDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
@Repository
@ -11,6 +12,8 @@ public interface CouponTemplateMapper {
CouponTemplateDO selectById(@Param("id") Integer id);
List<CouponTemplateDO> selectListByIds(@Param("ids") Collection<Integer> ids);
List<CouponTemplateDO> selectListByPage(@Param("type") Integer type,
@Param("title") String title,
@Param("status") Integer status,

View File

@ -1 +0,0 @@
package cn.iocoder.mall.promotion.biz;

View File

@ -3,12 +3,10 @@ package cn.iocoder.mall.promotion.biz.service;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.util.DateUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.StringUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.promotion.api.bo.CouponCardBO;
import cn.iocoder.mall.promotion.api.bo.CouponCardPageBO;
import cn.iocoder.mall.promotion.api.bo.CouponTemplateBO;
import cn.iocoder.mall.promotion.api.bo.CouponTemplatePageBO;
import cn.iocoder.mall.promotion.api.bo.*;
import cn.iocoder.mall.promotion.api.constant.*;
import cn.iocoder.mall.promotion.api.dto.*;
import cn.iocoder.mall.promotion.biz.convert.CouponCardConvert;
@ -22,8 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Calendar;
import java.util.Date;
import java.util.*;
import java.util.stream.Collectors;
@Service // 实际上不用添加添加的原因是必须 Spring 报错提示
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
@ -252,6 +250,23 @@ public class CouponServiceImpl implements CouponService {
return null;
}
@Override
public CommonResult<List<CouponCardAvailableBO>> getCouponCardList(Integer userId, List<CouponCardSpuDTO> spus) {
// 查询用户未使用的优惠劵列表
List<CouponCardDO> cards = couponCardMapper.selectListByUserIdAndStatus(userId, CouponCardStatusEnum.UNUSED.getValue());
Map<Integer, CouponTemplateDO> templates = couponTemplateMapper.selectListByIds(cards.stream().map(CouponCardDO::getTemplateId).collect(Collectors.toSet()))
.stream().collect(Collectors.toMap(CouponTemplateDO::getId, template -> template));
// 逐个判断是否可用
List<CouponCardAvailableBO> availableCards = cards.stream().map(card -> {
CouponCardAvailableBO availableCard = CouponCardConvert.INSTANCE.convert2(card, true);
availableCard.setUnavailableReason(isMatch(card, templates.get(card.getTemplateId()), spus));
availableCard.setAvailable(availableCard.getUnavailableReason() == null);
return availableCard;
}).collect(Collectors.toList());
// 返回结果
return CommonResult.success(availableCards);
}
private void setCouponCardValidTime(CouponCardDO card, CouponTemplateDO template) {
if (CouponTemplateDateTypeEnum.FIXED_DATE.getValue().equals(template.getDateType())) {
card.setValidStartTime(template.getValidStartTime()).setValidEndTime(template.getValidEndTime());
@ -263,6 +278,35 @@ public class CouponServiceImpl implements CouponService {
}
}
// 如果匹配则返回 null 即可
private String isMatch(CouponCardDO card, CouponTemplateDO template, List<CouponCardSpuDTO> spus) {
int totalPrice = 0;
if (RangeTypeEnum.ALL.getValue().equals(template.getRangeType())) {
totalPrice = spus.stream().mapToInt(spu -> spu.getPrice() * spu.getQuantity()).sum();
} else if (RangeTypeEnum.PRODUCT_INCLUDE_PART.getValue().equals(template.getRangeType())) {
List<Integer> spuIds = StringUtil.splitToInt(template.getRangeValues(), ",");
totalPrice = spus.stream().mapToInt(spu -> spuIds.contains(spu.getSpuId()) ? spu.getPrice() * spu.getQuantity() : 0).sum();
} else if (RangeTypeEnum.PRODUCT_EXCLUDE_PART.getValue().equals(template.getRangeType())) {
List<Integer> spuIds = StringUtil.splitToInt(template.getRangeValues(), ",");
totalPrice = spus.stream().mapToInt(spu -> !spuIds.contains(spu.getSpuId()) ? spu.getPrice() * spu.getQuantity() : 0).sum();
} else if (RangeTypeEnum.CATEGORY_INCLUDE_PART.getValue().equals(template.getRangeType())) {
List<Integer> spuIds = StringUtil.splitToInt(template.getRangeValues(), ",");
totalPrice = spus.stream().mapToInt(spu -> spuIds.contains(spu.getCategoryId()) ? spu.getPrice() * spu.getQuantity() : 0).sum();
} else if (RangeTypeEnum.CATEGORY_EXCLUDE_PART.getValue().equals(template.getRangeType())) {
List<Integer> spuIds = StringUtil.splitToInt(template.getRangeValues(), ",");
totalPrice = spus.stream().mapToInt(spu -> !spuIds.contains(spu.getCategoryId()) ? spu.getPrice() * spu.getQuantity() : 0).sum();
}
// 总价为 0 说明优惠劵丫根不匹配
if (totalPrice == 0) {
return "优惠劵不匹配";
}
// 如果不满足金额
if (totalPrice < card.getPriceAvailable()) {
return String.format("差 %1$,.2f 元可用优惠劵", (card.getPriceAvailable() - totalPrice) / 100D);
}
return null;
}
// ========== 优惠码 ==========
@Override

View File

@ -33,6 +33,20 @@
WHERE id = #{id}
</select>
<select id="selectListByUserIdAndStatus" resultType="CouponCardDO">
SELECT
<include refid="FIELDS" />
FROM coupon_card
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
<select id="selectListByPage" resultType="CouponCardDO">
SELECT
<include refid="FIELDS" />

View File

@ -34,6 +34,16 @@
WHERE id = #{id}
</select>
<select id="selectListByIds" resultType="CouponTemplateDO">
SELECT
<include refid="FIELDS"/>
FROM coupon_template
WHERE id IN
<foreach item="id" collection="ids" separator="," open="(" close=")" index="">
#{id}
</foreach>
</select>
<select id="selectListByPage" resultType="CouponTemplateDO">
SELECT
<include refid="FIELDS" />

View File

@ -1 +0,0 @@
package cn.iocoder.mall.promotion.biz;

View File

@ -1,6 +0,0 @@
/**
* 提供 SDK 给其它服务使用如下功能
*
* 1. 通过 {@link cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor} 拦截器实现需要登陆 URL 的鉴权
*/
package cn.iocoder.mall.user.sdk;

View File

@ -1 +0,0 @@
package cn.iocoder.mall.user.biz;