mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-08-09 07:41:53 +08:00
Merge remote-tracking branch 'origin/feature/mall_product' into brokerate
# Conflicts: # yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionTypeEnum.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderItemDO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java # yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java
This commit is contained in:
@@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.*;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.log.TradeAfterSaleLogRespVO;
|
||||
import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.service.AfterSaleLogService;
|
||||
import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
@@ -87,13 +87,13 @@ public class TradeAfterSaleController {
|
||||
// 拼接数据
|
||||
MemberUserRespDTO user = memberUserApi.getUser(afterSale.getUserId());
|
||||
// 获取售后日志
|
||||
List<TradeAfterSaleLogRespDTO> logs = afterSaleLogService.getLog(afterSale.getId());
|
||||
List<TradeAfterSaleLogRespVO> logs = afterSaleLogService.getLog(afterSale.getId());
|
||||
// TODO 方便测试看效果,review 后移除
|
||||
if (logs == null) {
|
||||
logs = new ArrayList<>();
|
||||
}
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
TradeAfterSaleLogRespDTO respVO = new TradeAfterSaleLogRespDTO();
|
||||
TradeAfterSaleLogRespVO respVO = new TradeAfterSaleLogRespVO();
|
||||
respVO.setId((long) i);
|
||||
respVO.setUserId((long) i);
|
||||
respVO.setUserType(i % 2 == 0 ? 2 : 1);
|
||||
|
@@ -156,7 +156,7 @@ public class AppTradeOrderController {
|
||||
@Operation(summary = "删除交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
public CommonResult<Boolean> deleteOrder(@RequestParam("id") Long id) {
|
||||
// TODO @芋艿:未实现,mock 用
|
||||
tradeOrderUpdateService.deleteOrder(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
@@ -82,6 +82,9 @@ public class AppTradeOrderSettlementRespVO {
|
||||
@Schema(description = "积分抵扣的金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
|
||||
private Integer pointPrice;
|
||||
|
||||
@Schema(description = "VIP 减免金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
|
||||
private Integer vipPrice;
|
||||
|
||||
@Schema(description = "实际支付金额(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "450")
|
||||
private Integer payPrice;
|
||||
|
||||
|
@@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.TradeAfterSaleLogDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
@@ -68,10 +67,10 @@ public interface TradeAfterSaleConvert {
|
||||
|
||||
PageResult<AppTradeAfterSaleRespVO> convertPage02(PageResult<TradeAfterSaleDO> page);
|
||||
|
||||
List<TradeAfterSaleLogRespDTO> convertList(List<TradeAfterSaleLogDO> list);
|
||||
|
||||
List<TradeAfterSaleLogRespVO> convertList(List<TradeAfterSaleLogDO> list);
|
||||
|
||||
default TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, TradeOrderDO order, List<TradeOrderItemDO> orderItems,
|
||||
MemberUserRespDTO user, List<TradeAfterSaleLogRespDTO> logs) {
|
||||
MemberUserRespDTO user, List<TradeAfterSaleLogRespVO> logs) {
|
||||
TradeAfterSaleDetailRespVO respVO = convert(afterSale, orderItems);
|
||||
// 处理用户信息
|
||||
respVO.setUser(convert(user));
|
||||
@@ -81,7 +80,8 @@ public interface TradeAfterSaleConvert {
|
||||
respVO.setLogs(convertList1(logs));
|
||||
return respVO;
|
||||
}
|
||||
List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespDTO> list);
|
||||
|
||||
List<TradeAfterSaleLogRespVO> convertList1(List<TradeAfterSaleLogRespVO> list);
|
||||
@Mapping(target = "id", source = "afterSale.id")
|
||||
TradeAfterSaleDetailRespVO convert(TradeAfterSaleDO afterSale, List<TradeOrderItemDO> orderItems);
|
||||
TradeOrderBaseVO convert(TradeOrderDO order);
|
||||
|
@@ -65,6 +65,7 @@ public interface TradeOrderConvert {
|
||||
@Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
|
||||
@Mapping(source = "calculateRespBO.price.couponPrice", target = "couponPrice"),
|
||||
@Mapping(source = "calculateRespBO.price.pointPrice", target = "pointPrice"),
|
||||
@Mapping(source = "calculateRespBO.price.vipPrice", target = "vipPrice"),
|
||||
@Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice")
|
||||
})
|
||||
TradeOrderDO convert(Long userId, String userIp, AppTradeOrderCreateReqVO createReqVO,
|
||||
|
@@ -168,6 +168,7 @@ public class TradeOrderDO extends BaseDO {
|
||||
* - {@link #discountPrice}
|
||||
* + {@link #deliveryPrice}
|
||||
* + {@link #adjustPrice}
|
||||
* - {@link #vipPrice}
|
||||
*/
|
||||
private Integer payPrice;
|
||||
|
||||
@@ -273,5 +274,9 @@ public class TradeOrderDO extends BaseDO {
|
||||
* 退还的使用的积分
|
||||
*/
|
||||
private Integer refundPoint;
|
||||
/**
|
||||
* VIP 减免金额,单位:分
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
|
||||
}
|
||||
|
@@ -126,6 +126,7 @@ public class TradeOrderItemDO extends BaseDO {
|
||||
* - {@link #discountPrice}
|
||||
* + {@link #deliveryPrice}
|
||||
* + {@link #adjustPrice}
|
||||
* - {@link #vipPrice}
|
||||
*/
|
||||
private Integer payPrice;
|
||||
|
||||
@@ -151,6 +152,10 @@ public class TradeOrderItemDO extends BaseDO {
|
||||
* 赠送的积分
|
||||
*/
|
||||
private Integer givePoint;
|
||||
/**
|
||||
* VIP 减免金额,单位:分
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
// TODO @芋艿:如果商品 vip 折扣时,到底是新增一个 vipPrice 记录优惠记录,还是 vipDiscountPrice,记录 vip 的优惠;还是直接使用 vipPrice;
|
||||
// 目前 crmeb 的选择,单独一个 vipPrice 记录优惠价格;感觉不一定合理,可以在看看有赞的;
|
||||
|
||||
|
@@ -1,59 +0,0 @@
|
||||
package cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
// TODO @puhui999:这个是不是应该搞成 vo 啊?
|
||||
/**
|
||||
* 贸易售后日志详情 DTO
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TradeAfterSaleLogRespDTO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20669")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22634")
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@NotNull(message = "用户类型不能为空")
|
||||
private Integer userType;
|
||||
|
||||
@Schema(description = "售后编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3023")
|
||||
@NotNull(message = "售后编号不能为空")
|
||||
private Long afterSaleId;
|
||||
|
||||
@Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25870")
|
||||
@NotNull(message = "订单编号不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23154")
|
||||
@NotNull(message = "订单项编号不能为空")
|
||||
private Long orderItemId;
|
||||
|
||||
@Schema(description = "售后状态(之前)", example = "2")
|
||||
private Integer beforeStatus;
|
||||
|
||||
@Schema(description = "售后状态(之后)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "售后状态(之后)不能为空")
|
||||
private Integer afterStatus;
|
||||
|
||||
@Schema(description = "操作明细", requiredMode = Schema.RequiredMode.REQUIRED, example = "维权完成,退款金额:¥37776.00")
|
||||
@NotNull(message = "操作明细不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.module.trade.framework.aftersalelog.core.service;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.log.TradeAfterSaleLogRespVO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogRespDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,6 +29,6 @@ public interface AfterSaleLogService {
|
||||
* @param afterSaleId 售后编号
|
||||
* @return 售后日志
|
||||
*/
|
||||
List<TradeAfterSaleLogRespDTO> getLog(Long afterSaleId);
|
||||
List<TradeAfterSaleLogRespVO> getLog(Long afterSaleId);
|
||||
|
||||
}
|
||||
|
@@ -63,6 +63,9 @@ public class TradeOrderLogAspect {
|
||||
Long userId = getUserId();
|
||||
// 1.2 订单信息
|
||||
Long orderId = ORDER_ID.get();
|
||||
if (orderId == null) { // 如果未设置,只有注解,说明不需要记录订单日志
|
||||
return;
|
||||
}
|
||||
Integer beforeStatus = BEFORE_STATUS.get();
|
||||
Integer afterStatus = AFTER_STATUS.get();
|
||||
Map<String, Object> exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap());
|
||||
|
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleDisagreeReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSalePageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.TradeAfterSaleRefuseReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.log.TradeAfterSaleLogRespVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSaleDeliveryReqVO;
|
||||
import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
|
||||
@@ -26,7 +27,6 @@ import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleWayEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.service.AfterSaleLogService;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
@@ -449,8 +449,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @puhui999:应该返回 do 哈。
|
||||
@Override
|
||||
public List<TradeAfterSaleLogRespDTO> getLog(Long afterSaleId) {
|
||||
public List<TradeAfterSaleLogRespVO> getLog(Long afterSaleId) {
|
||||
// TODO 不熟悉流程先这么滴
|
||||
List<TradeAfterSaleLogDO> saleLogDOs = tradeAfterSaleLogMapper.selectList(TradeAfterSaleLogDO::getAfterSaleId, afterSaleId);
|
||||
return TradeAfterSaleConvert.INSTANCE.convertList(saleLogDOs);
|
||||
|
@@ -106,7 +106,7 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 查询物流
|
||||
return getExpressTrackList(order);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 查询物流
|
||||
return getExpressTrackList(order);
|
||||
}
|
||||
|
||||
|
@@ -62,6 +62,22 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
void receiveOrder(Long userId, Long id);
|
||||
|
||||
/**
|
||||
* 【会员】取消订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param id 订单编号
|
||||
*/
|
||||
void cancelOrder(Long userId, Long id);
|
||||
|
||||
/**
|
||||
* 【会员】删除订单
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param id 订单编号
|
||||
*/
|
||||
void deleteOrder(Long userId, Long id);
|
||||
|
||||
/**
|
||||
* 【管理员】交易订单备注
|
||||
*
|
||||
@@ -117,11 +133,4 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 【会员】取消订单
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param id 订单编号
|
||||
*/
|
||||
void cancelOrder(Long userId, Long id);
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.*;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
|
||||
import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
|
||||
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
|
||||
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.cart.CartService;
|
||||
@@ -299,6 +300,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
// 5. 生成预支付
|
||||
createPayOrder(order, orderItems, calculateRespBO);
|
||||
|
||||
// 6. 插入订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
|
||||
|
||||
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
|
||||
}
|
||||
|
||||
@@ -482,6 +486,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_RECEIVE)
|
||||
public void receiveOrder(Long userId, Long id) {
|
||||
// 校验并获得交易订单(可收货)
|
||||
TradeOrderDO order = validateOrderReceivable(userId, id);
|
||||
@@ -492,7 +497,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
if (updateCount == 0) {
|
||||
throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
|
||||
}
|
||||
// TODO 芋艿:OrderLog
|
||||
|
||||
// TODO 芋艿:lili 发送订单变化的消息
|
||||
|
||||
@@ -500,7 +504,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
|
||||
// TODO 芋艿:销售佣金的记录;
|
||||
|
||||
// TODO 芋艿:获得积分;
|
||||
// 插入订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -541,41 +546,49 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
tradeOrderMapper.updateById(update);
|
||||
// TODO @芋艿:改价时,赠送的积分,要不要做改动???
|
||||
|
||||
// TODO @puhui999:应该是按照 payPrice 分配;并且要考虑取余问题;payPrice 也要考虑,item 里的
|
||||
// TODO:先按 adjustPrice 实现,没明白 payPrice 怎么搞哈哈哈
|
||||
// TODO @puhui999:就是对比新老 adjustPrice 的差值,然后计算补充的 adjustPrice 最终值;另外,可以不用区分 items.size 是不是 > 1 哈;应该是一致的逻辑;分摊的逻辑,有点类似 dividePrice 方法噢;
|
||||
// 5、更新 TradeOrderItem
|
||||
if (items.size() > 1) {
|
||||
// TradeOrderItemDO 需要做 adjustPrice 的分摊
|
||||
int price = reqVO.getAdjustPrice() / items.size();
|
||||
int remainderPrice = reqVO.getAdjustPrice() % items.size();
|
||||
List<TradeOrderItemDO> orders = new ArrayList<>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
// 把平摊后剩余的金额加到第一个订单项
|
||||
if (remainderPrice != 0 && i == 0) {
|
||||
orders.add(convertOrderItemPrice(items.get(i), price + remainderPrice));
|
||||
}
|
||||
orders.add(convertOrderItemPrice(items.get(i), price));
|
||||
}
|
||||
tradeOrderItemMapper.updateBatch(orders);
|
||||
} else {
|
||||
TradeOrderItemDO orderItem = items.get(0);
|
||||
TradeOrderItemDO updateItem = convertOrderItemPrice(orderItem, reqVO.getAdjustPrice());
|
||||
tradeOrderItemMapper.updateById(updateItem);
|
||||
// TradeOrderItemDO 需要做 adjustPrice 的分摊
|
||||
List<Integer> dividePrices = dividePrice(items, orderPayPrice);
|
||||
List<TradeOrderItemDO> updateItems = new ArrayList<>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
TradeOrderItemDO item = items.get(i);
|
||||
Integer adjustPrice = item.getPrice() - dividePrices.get(i); // 计算调整的金额
|
||||
updateItems.add(new TradeOrderItemDO().setId(item.getId()).setAdjustPrice(adjustPrice)
|
||||
.setPayPrice(item.getPayPrice() - adjustPrice));
|
||||
}
|
||||
tradeOrderItemMapper.updateBatch(updateItems);
|
||||
|
||||
|
||||
// 6、更新支付订单
|
||||
payOrderApi.updatePayOrderPrice(order.getPayOrderId(), update.getPayPrice());
|
||||
}
|
||||
|
||||
private TradeOrderItemDO convertOrderItemPrice(TradeOrderItemDO orderItem, Integer price) {
|
||||
TradeOrderItemDO newOrderItem = new TradeOrderItemDO();
|
||||
newOrderItem.setId(orderItem.getId());
|
||||
newOrderItem.setAdjustPrice(price);
|
||||
int payPrice = orderItem.getAdjustPrice() != null ? (orderItem.getPayPrice() - orderItem.getAdjustPrice())
|
||||
+ price : orderItem.getPayPrice() + price;
|
||||
newOrderItem.setPayPrice(payPrice);
|
||||
return newOrderItem;
|
||||
/**
|
||||
* 计算订单调价价格分摊
|
||||
*
|
||||
* @param items 订单项
|
||||
* @param orderPayPrice 订单支付金额
|
||||
* @return 分摊金额数组,和传入的 orderItems 一一对应
|
||||
*/
|
||||
private List<Integer> dividePrice(List<TradeOrderItemDO> items, Integer orderPayPrice) {
|
||||
Integer total = getSumValue(items, TradeOrderItemDO::getPrice, Integer::sum);
|
||||
assert total != null;
|
||||
// 遍历每一个,进行分摊
|
||||
List<Integer> prices = new ArrayList<>(items.size());
|
||||
int remainPrice = orderPayPrice;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
TradeOrderItemDO orderItem = items.get(i);
|
||||
int partPrice;
|
||||
if (i < items.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
|
||||
partPrice = (int) (orderPayPrice * (1.0D * orderItem.getPayPrice() / total));
|
||||
remainPrice -= partPrice;
|
||||
} else {
|
||||
partPrice = remainPrice;
|
||||
}
|
||||
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
|
||||
prices.add(partPrice);
|
||||
}
|
||||
return prices;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -671,6 +684,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_COMMENT)
|
||||
public Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO) {
|
||||
// 先通过订单项 ID,查询订单项是否存在
|
||||
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId);
|
||||
@@ -698,24 +712,27 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
|
||||
if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
|
||||
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE));
|
||||
// TODO 待实现:已完成评价,要不要写一条订单日志?目前 crmeb 会写,有赞可以研究下
|
||||
// 增加订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
|
||||
public void cancelOrder(Long userId, Long id) {
|
||||
// 校验存在
|
||||
// 1.1 校验存在
|
||||
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验状态
|
||||
// 1.2 校验状态
|
||||
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
|
||||
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// 1.更新 TradeOrderDO 状态为已取消
|
||||
// 2. 更新 TradeOrderDO 状态为已取消
|
||||
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
|
||||
new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
|
||||
.setCancelTime(LocalDateTime.now())
|
||||
@@ -724,22 +741,43 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来
|
||||
// 3. TODO 活动相关库存回滚需要活动 id,活动 id 怎么获取?app 端能否传过来;回复:从订单里拿呀
|
||||
tradeOrderHandlers.forEach(handler -> handler.rollback());
|
||||
|
||||
// 2.回滚库存
|
||||
// 4. 回滚库存
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
|
||||
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
|
||||
|
||||
// 3.回滚优惠券
|
||||
couponApi.returnUsedCoupon(order.getCouponId());
|
||||
// 5. 回滚优惠券
|
||||
if (order.getCouponId() > 0) {
|
||||
couponApi.returnUsedCoupon(order.getCouponId());
|
||||
}
|
||||
|
||||
// 4.回滚积分(抵扣的)
|
||||
// 6. 回滚积分(抵扣的)
|
||||
addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
|
||||
|
||||
// TODO 芋艿:OrderLog
|
||||
// 7. 增加订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
|
||||
}
|
||||
|
||||
// TODO 芋艿:lili 发送订单变化的消息
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE)
|
||||
public void deleteOrder(Long userId, Long id) {
|
||||
// 1.1 校验存在
|
||||
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
|
||||
if (order == null) {
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
// 1.2 校验状态
|
||||
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus())) {
|
||||
throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL);
|
||||
}
|
||||
// 2. 删除订单
|
||||
tradeOrderMapper.deleteById(id);
|
||||
|
||||
// 3. 记录日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,6 +804,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户积分
|
||||
* <p>
|
||||
* 目前是支付成功后,就会创建积分记录。
|
||||
* <p>
|
||||
* 业内还有两种做法,可以根据自己的业务调整:
|
||||
* 1. 确认收货后,才创建积分记录
|
||||
* 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param point 增加积分数量
|
||||
* @param bizType 业务编号
|
||||
* @param bizId 业务编号
|
||||
*/
|
||||
protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
|
||||
if (point != null && point > 0) {
|
||||
memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
|
||||
@@ -778,15 +830,27 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分销记录
|
||||
*
|
||||
* 目前是支付成功后,就会创建分销记录。
|
||||
*
|
||||
* 业内还有两种做法,可以根据自己的业务调整:
|
||||
* 1. 确认收货后,才创建分销记录
|
||||
* 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param orderId 订单编号
|
||||
*/
|
||||
@Async
|
||||
protected void addBrokerageAsync(Long userId, Long orderId) {
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
Assert.notNull(user);
|
||||
|
||||
// 每一个订单项,都会去生成分销记录
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
|
||||
List<BrokerageAddReqBO> list = convertList(orderItems,
|
||||
List<BrokerageAddReqBO> addList = convertList(orderItems,
|
||||
item -> TradeOrderConvert.INSTANCE.convert(user, item, productSkuApi.getSku(item.getSkuId())));
|
||||
brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, list);
|
||||
brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
|
||||
}
|
||||
|
||||
@Async
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.trade.service.order.bo;
|
||||
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -14,36 +15,73 @@ import javax.validation.constraints.NotNull;
|
||||
@Data
|
||||
public class TradeBeforeOrderCreateReqBO {
|
||||
|
||||
// TODO @puhui999:注释也写下哈;bo 还是写注释噢
|
||||
|
||||
/**
|
||||
* 订单类型
|
||||
*
|
||||
* 枚举 {@link TradeOrderTypeEnum}
|
||||
*/
|
||||
@NotNull(message = "订单类型不能为空")
|
||||
private Integer orderType;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* 关联 MemberUserDO 的 id 编号
|
||||
*/
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
// ========== 秒杀活动相关字段 ==========
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Schema(description = "秒杀活动编号", example = "1024")
|
||||
private Long seckillActivityId;
|
||||
|
||||
// ========== 拼团活动相关字段 ==========
|
||||
|
||||
/**
|
||||
* 拼团活动编号
|
||||
*/
|
||||
@Schema(description = "拼团活动编号", example = "1024")
|
||||
private Long combinationActivityId;
|
||||
|
||||
/**
|
||||
* 拼团团长编号
|
||||
*/
|
||||
@Schema(description = "拼团团长编号", example = "2048")
|
||||
private Long combinationHeadId;
|
||||
|
||||
// ========== 砍价活动相关字段 ==========
|
||||
|
||||
/**
|
||||
* 砍价活动编号
|
||||
*/
|
||||
@Schema(description = "砍价活动编号", example = "123")
|
||||
private Long bargainActivityId;
|
||||
|
||||
// ========== 活动购买商品相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商品 SPU 编号
|
||||
*
|
||||
* 关联 ProductSkuDO 的 spuId 编号
|
||||
*/
|
||||
@NotNull(message = "SPU 编号不能为空")
|
||||
private Long spuId;
|
||||
|
||||
/**
|
||||
* 商品 SKU 编号
|
||||
*
|
||||
* 关联 ProductSkuDO 的 id 编号
|
||||
*/
|
||||
@NotNull(message = "SKU 编号活动商品不能为空")
|
||||
private Long skuId;
|
||||
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 购买的商品数量
|
||||
*/
|
||||
@NotNull(message = "购买数量不能为空")
|
||||
private Integer count;
|
||||
|
||||
|
@@ -20,14 +20,17 @@ public class TradeBargainHandler implements TradeOrderHandler {
|
||||
@Resource
|
||||
private BargainActivityApi bargainActivityApi;
|
||||
|
||||
// TODO @puhui999:先临时写在这里;在价格计算时,如果是秒杀商品,需要校验如下条件:
|
||||
// 1. 商品存在、库存充足、单次限购;
|
||||
// 2. 活动进行中、时间段符合
|
||||
|
||||
@Override
|
||||
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
|
||||
// 如果是砍价订单
|
||||
if (ObjectUtil.notEqual(TradeOrderTypeEnum.BARGAIN.getType(), reqBO.getOrderType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 额外扣减砍价的库存
|
||||
// 扣减砍价活动的库存
|
||||
bargainActivityApi.updateBargainActivityStock(reqBO.getBargainActivityId(), reqBO.getCount());
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,12 @@ public interface TradeOrderHandler {
|
||||
*/
|
||||
void afterOrderCreate(TradeAfterOrderCreateReqBO reqBO);
|
||||
|
||||
// TODO @puhui999:这个搞成订单取消
|
||||
/**
|
||||
* 回滚
|
||||
*/
|
||||
void rollback();
|
||||
|
||||
// TODO @puhui999:再搞个订单项取消哈
|
||||
|
||||
}
|
||||
|
@@ -20,13 +20,17 @@ public class TradeSeckillHandler implements TradeOrderHandler {
|
||||
@Resource
|
||||
private SeckillActivityApi seckillActivityApi;
|
||||
|
||||
// TODO @puhui999:先临时写在这里;在价格计算时,如果是秒杀商品,需要校验如下条件:
|
||||
// 1. 商品存在、库存充足、单次限购;
|
||||
// 2. 活动进行中、时间段符合
|
||||
|
||||
@Override
|
||||
public void beforeOrderCreate(TradeBeforeOrderCreateReqBO reqBO) {
|
||||
// 如果是秒杀订单:额外扣减秒杀的库存;
|
||||
if (ObjectUtil.notEqual(TradeOrderTypeEnum.SECKILL.getType(), reqBO.getOrderType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 扣减秒杀活动的库存
|
||||
seckillActivityApi.updateSeckillStock(reqBO.getSeckillActivityId(), reqBO.getSkuId(), reqBO.getCount());
|
||||
}
|
||||
|
||||
|
@@ -94,6 +94,10 @@ public class TradePriceCalculateRespBO {
|
||||
* 对应 taobao 的 trade.point_fee 字段
|
||||
*/
|
||||
private Integer pointPrice;
|
||||
/**
|
||||
* VIP 减免金额,单位:分
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
/**
|
||||
* 最终购买金额(总),单位:分
|
||||
*
|
||||
@@ -102,6 +106,7 @@ public class TradePriceCalculateRespBO {
|
||||
* - {@link #pointPrice}
|
||||
* - {@link #discountPrice}
|
||||
* + {@link #deliveryPrice}
|
||||
* - {@link #vipPrice}
|
||||
*/
|
||||
private Integer payPrice;
|
||||
|
||||
@@ -167,6 +172,10 @@ public class TradePriceCalculateRespBO {
|
||||
* 使用的积分
|
||||
*/
|
||||
private Integer usePoint;
|
||||
/**
|
||||
* VIP 减免金额,单位:分
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
/**
|
||||
* 应付金额(总),单位:分
|
||||
*
|
||||
@@ -175,6 +184,7 @@ public class TradePriceCalculateRespBO {
|
||||
* - {@link #pointPrice}
|
||||
* - {@link #discountPrice}
|
||||
* + {@link #deliveryPrice}
|
||||
* - {@link #vipPrice}
|
||||
*/
|
||||
private Integer payPrice;
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
@@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -22,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
|
||||
|
||||
/**
|
||||
* 优惠劵的 {@link TradePriceCalculator} 实现类
|
||||
@@ -44,6 +47,10 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()
|
||||
.setId(param.getCouponId()).setUserId(param.getUserId()));
|
||||
Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());
|
||||
// 1.2 只有【普通】订单,才允许使用优惠劵
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
|
||||
}
|
||||
|
||||
// 2.1 获得匹配的商品 SKU 数组
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
|
@@ -64,7 +64,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
private void calculateByPickUp(TradePriceCalculateReqBO param) {
|
||||
if (param.getPickUpStoreId() == null) {
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
|
||||
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
|
||||
return;
|
||||
}
|
||||
DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
|
||||
if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
|
||||
@@ -77,7 +78,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
private void calculateExpress(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 得到收件地址区域
|
||||
if (param.getAddressId() == null) {
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_USER_ADDRESS_IS_EMPTY);
|
||||
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
|
||||
return;
|
||||
}
|
||||
AddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
|
||||
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
|
||||
|
@@ -1,11 +1,13 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -33,6 +35,10 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 获得 SKU 对应的限时折扣活动
|
||||
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
|
||||
|
@@ -0,0 +1,88 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
/**
|
||||
* 会员 VIP 折扣的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Order(TradePriceCalculator.ORDER_MEMBER_LEVEL)
|
||||
public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 1. 获得用户的会员等级
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
if (user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
return;
|
||||
}
|
||||
MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
if (level == null || level.getDiscountPercent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 计算每个 SKU 的优惠金额
|
||||
result.getItems().forEach(orderItem -> {
|
||||
// 2.1 计算优惠金额
|
||||
Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
|
||||
if (vipPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2 记录优惠明细
|
||||
if (orderItem.getSelected()) {
|
||||
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
vipPrice);
|
||||
}
|
||||
|
||||
// 2.3 更新 SKU 的优惠金额
|
||||
orderItem.setVipPrice(vipPrice);
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
});
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算会员 VIP 优惠价格
|
||||
*
|
||||
* @param price 原价
|
||||
* @param discountPercent 折扣
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
|
||||
if (discountPercent == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = price * discountPercent / 100;
|
||||
return price - newPrice;
|
||||
}
|
||||
|
||||
}
|
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
*/
|
||||
public interface TradePriceCalculator {
|
||||
|
||||
int ORDER_MEMBER_LEVEL = 5;
|
||||
int ORDER_DISCOUNT_ACTIVITY = 10;
|
||||
int ORDER_REWARD_ACTIVITY = 20;
|
||||
int ORDER_COUPON = 30;
|
||||
|
@@ -52,7 +52,7 @@ public class TradePriceCalculatorHelper {
|
||||
.setCount(item.getCount()).setCartId(item.getCartId()).setSelected(item.getSelected());
|
||||
// sku 价格
|
||||
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
|
||||
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0);
|
||||
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
|
||||
// sku 信息
|
||||
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties())
|
||||
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
|
||||
@@ -98,7 +98,7 @@ public class TradePriceCalculatorHelper {
|
||||
// 先重置
|
||||
TradePriceCalculateRespBO.Price price = result.getPrice();
|
||||
price.setTotalPrice(0).setDiscountPrice(0).setDeliveryPrice(0)
|
||||
.setCouponPrice(0).setPointPrice(0).setPayPrice(0);
|
||||
.setCouponPrice(0).setPointPrice(0).setVipPrice(0).setPayPrice(0);
|
||||
// 再合计 item
|
||||
result.getItems().forEach(item -> {
|
||||
if (!item.getSelected()) {
|
||||
@@ -109,6 +109,7 @@ public class TradePriceCalculatorHelper {
|
||||
price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());
|
||||
price.setCouponPrice(price.getCouponPrice() + item.getCouponPrice());
|
||||
price.setPointPrice(price.getPointPrice() + item.getPointPrice());
|
||||
price.setVipPrice(price.getVipPrice() + item.getVipPrice());
|
||||
price.setPayPrice(price.getPayPrice() + item.getPayPrice());
|
||||
});
|
||||
}
|
||||
@@ -132,7 +133,9 @@ public class TradePriceCalculatorHelper {
|
||||
- orderItem.getDiscountPrice()
|
||||
+ orderItem.getDeliveryPrice()
|
||||
- orderItem.getCouponPrice()
|
||||
- orderItem.getPointPrice());
|
||||
- orderItem.getPointPrice()
|
||||
- orderItem.getVipPrice()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +165,9 @@ public class TradePriceCalculatorHelper {
|
||||
if (orderItem.getGivePoint() == null) {
|
||||
orderItem.setGivePoint(0);
|
||||
}
|
||||
if (orderItem.getVipPrice() == null) {
|
||||
orderItem.setVipPrice(0);
|
||||
}
|
||||
recountPayPrice(orderItem);
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -32,6 +34,10 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 获得 SKU 对应的满减送活动
|
||||
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getMatchRewardActivityList(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSpuId));
|
||||
|
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -47,6 +48,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 匹配优惠劵,但是未选中
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
|
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -42,6 +43,7 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
|
@@ -0,0 +1,118 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link TradeMemberLevelPriceCalculator} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TradeMemberLevelPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private TradeMemberLevelPriceCalculator memberLevelPriceCalculator;
|
||||
|
||||
@Mock
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Mock
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Test
|
||||
public void testCalculate() {
|
||||
// 准备参数
|
||||
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
|
||||
.setUserId(1024L)
|
||||
.setItems(asList(
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
|
||||
.setPrice(100),
|
||||
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false)
|
||||
.setPrice(50)
|
||||
));
|
||||
// 保证价格被初始化上
|
||||
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
|
||||
// mock 方法(会员等级)
|
||||
when(memberUserApi.getUser(eq(1024L))).thenReturn(new MemberUserRespDTO().setLevelId(2048L));
|
||||
when(memberLevelApi.getMemberLevel(eq(2048L))).thenReturn(
|
||||
new MemberLevelRespDTO().setId(2048L).setName("VIP 会员").setDiscountPercent(60));
|
||||
|
||||
// 调用
|
||||
memberLevelPriceCalculator.calculate(param, result);
|
||||
// 断言:Price 部分
|
||||
TradePriceCalculateRespBO.Price price = result.getPrice();
|
||||
assertEquals(price.getTotalPrice(), 200);
|
||||
assertEquals(price.getDiscountPrice(), 0);
|
||||
assertEquals(price.getPointPrice(), 0);
|
||||
assertEquals(price.getDeliveryPrice(), 0);
|
||||
assertEquals(price.getCouponPrice(), 0);
|
||||
assertEquals(price.getVipPrice(), 80);
|
||||
assertEquals(price.getPayPrice(), 120);
|
||||
assertNull(result.getCouponId());
|
||||
// 断言:SKU 1
|
||||
assertEquals(result.getItems().size(), 2);
|
||||
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem01.getDeliveryPrice(), 0);
|
||||
assertEquals(orderItem01.getCouponPrice(), 0);
|
||||
assertEquals(orderItem01.getPointPrice(), 0);
|
||||
assertEquals(orderItem01.getVipPrice(), 80);
|
||||
assertEquals(orderItem01.getPayPrice(), 120);
|
||||
// 断言:SKU 2
|
||||
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem02.getDeliveryPrice(), 0);
|
||||
assertEquals(orderItem02.getCouponPrice(), 0);
|
||||
assertEquals(orderItem02.getPointPrice(), 0);
|
||||
assertEquals(orderItem02.getVipPrice(), 60);
|
||||
assertEquals(orderItem02.getPayPrice(), 90);
|
||||
// 断言:Promotion 部分
|
||||
assertEquals(result.getPromotions().size(), 1);
|
||||
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 2048L);
|
||||
assertEquals(promotion01.getName(), "VIP 会员");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.MEMBER_LEVEL.getType());
|
||||
assertEquals(promotion01.getTotalPrice(), 200);
|
||||
assertEquals(promotion01.getDiscountPrice(), 80);
|
||||
assertTrue(promotion01.getMatch());
|
||||
assertEquals(promotion01.getDescription(), "会员等级折扣:省 0.80 元");
|
||||
TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
|
||||
assertEquals(promotion01.getItems().size(), 1);
|
||||
assertEquals(promotionItem01.getSkuId(), 10L);
|
||||
assertEquals(promotionItem01.getTotalPrice(), 200);
|
||||
assertEquals(promotionItem01.getDiscountPrice(), 80);
|
||||
}
|
||||
|
||||
}
|
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -44,6 +45,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) // 匹配活动 2
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
@@ -157,6 +159,7 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true)
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
|
Reference in New Issue
Block a user