Merge remote-tracking branch 'yudao/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
This commit is contained in:
puhui999
2023-09-30 11:35:51 +08:00
722 changed files with 5788 additions and 48800 deletions

View File

@@ -10,11 +10,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -88,27 +88,24 @@ public class BrokerageUserController {
// 分页查询
PageResult<BrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
// 涉及到的用户
Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
// 查询用户信息
Set<Long> userIds = convertSet(pageResult.getList(), BrokerageUserDO::getId);
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
// TODO @疯狂:看看下面两个 getBrokerageUserCountByBindUserId、getWithdrawSummaryByUserId 有没可能一次性出结果,不然 n 次有点太花性能了;
// 合计分佣订单
Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap = convertMap(userIds,
userId -> userId,
userId -> brokerageRecordService.getUserBrokerageSummaryByUserId(userId,
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus()));
// 合计推广用户数量
// 合计分佣的推广订单
Map<Long, UserBrokerageSummaryRespBO> brokerageOrderSummaryMap = brokerageRecordService.getUserBrokerageSummaryMapByUserId(
userIds, BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus());
// 合计分佣的推广用户
// TODO @疯狂:转成 map 批量读取
Map<Long, Long> brokerageUserCountMap = convertMap(userIds,
userId -> userId,
userId -> brokerageUserService.getBrokerageUserCountByBindUserId(userId, null));
// 合计提现
Map<Long, UserWithdrawSummaryBO> withdrawMap = convertMap(userIds,
userId -> userId,
userId -> brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS));
// 合计分佣的提现
// TODO @疯狂:如果未来支持了打款这个动作,可能 status 会不对;
Map<Long, BrokerageWithdrawSummaryRespBO> withdrawMap = brokerageWithdrawService.getWithdrawSummaryMapByUserId(
userIds, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS);
// 拼接返回
return success(BrokerageUserConvert.INSTANCE.convertPage(pageResult, userMap, brokerageUserCountMap,
userOrderSummaryMap, withdrawMap));
brokerageOrderSummaryMap, withdrawMap));
}
}

View File

@@ -21,25 +21,25 @@ public class BrokerageUserRespVO extends BrokerageUserBaseVO {
// ========== 用户信息 ==========
@Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.png")
private String avatar;
@Schema(description = "用户昵称", example = "李四")
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
private String nickname;
// ========== 推广信息 ==========
@Schema(description = "推广用户数量", example = "20019")
@Schema(description = "推广用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Integer brokerageUserCount;
@Schema(description = "推广订单数量", example = "20019")
@Schema(description = "推广订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Integer brokerageOrderCount;
@Schema(description = "推广订单金额", example = "20019")
@Schema(description = "推广订单金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Integer brokerageOrderPrice;
// ========== 提现信息 ==========
@Schema(description = "已提现金额", example = "20019")
@Schema(description = "已提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Integer withdrawPrice;
@Schema(description = "已提现次数", example = "20019")
@Schema(description = "已提现次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
private Integer withdrawCount;
}

View File

@@ -19,6 +19,19 @@ import java.util.List;
*/
@Data
public class TradeConfigBaseVO {
// ========== 售后相关 ==========
@Schema(description = "售后的退款理由", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "售后的退款理由不能为空")
private List<String> afterSaleRefundReasons;
@Schema(description = "售后的退货理由", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "售后的退货理由不能为空")
private List<String> afterSaleReturnReasons;
// ========== 配送相关 ==========
/**
* 是否启用全场包邮
*/
@@ -80,7 +93,7 @@ public class TradeConfigBaseVO {
private Integer brokerageFrozenDays;
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
@NotNull(message = "提现方式不能为空")
@NotEmpty(message = "提现方式不能为空")
@InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
private List<Integer> brokerageWithdrawTypes;

View File

@@ -73,7 +73,7 @@ public class TradeOrderController {
// 查询订单项
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id);
// orderLog
// TODO @puhui999orderLog
// 拼接数据
MemberUserRespDTO user = memberUserApi.getUser(order.getUserId());
return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, user));
@@ -120,4 +120,6 @@ public class TradeOrderController {
return success(true);
}
// TODO :核销逻辑
}

View File

@@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSa
import cn.iocoder.yudao.module.trade.convert.aftersale.TradeAfterSaleConvert;
import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleWayEnum;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.annotations.AfterSaleLog;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.util.AfterSaleLogUtils;
import cn.iocoder.yudao.module.trade.service.aftersale.TradeAfterSaleService;
@@ -21,9 +20,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -58,20 +54,9 @@ public class AppTradeAfterSaleController {
return success(afterSaleService.getApplyingAfterSaleCount(getLoginUserId()));
}
// TODO 芋艿:待实现
@GetMapping(value = "/get-reason-list")
@Operation(summary = "获得售后原因")
@Parameter(name = "way", description = "售后类型", required = true, example = "10")
public CommonResult<List<String>> getAfterSaleReasonList(@RequestParam("way") Integer way) {
if (Objects.equals(TradeAfterSaleWayEnum.REFUND.getWay(), way)) {
return success(Arrays.asList("不想要了", "商品质量问题", "商品描述不符"));
}
return success(Arrays.asList("不想要了", "商品质量问题", "商品描述不符", "商品错发漏发", "商品包装破损"));
}
@PostMapping(value = "/create")
@Operation(summary = "申请售后")
@AfterSaleLog(id = "#info.data", content = "'申请售后:售后编号['+#info.data+'],订单编号['+#createReqVO.orderItemId+'], '", operateType = AfterSaleOperateTypeEnum.APPLY)
@AfterSaleLog(id = "#info.data", content = "'申请售后:售后编号['+#info.data+'],订单编号['+#createReqVO.orderItemId+'], '", operateType = AfterSaleOperateTypeEnum.MEMBER_CREATE)
public CommonResult<Long> createAfterSale(@RequestBody AppTradeAfterSaleCreateReqVO createReqVO) {
AfterSaleLogUtils.setBeforeStatus(0);
AfterSaleLogUtils.setAfterStatus(TradeAfterSaleStatusEnum.APPLY.getStatus());

View File

@@ -45,7 +45,7 @@ public class AppBrokerageRecordController {
@GetMapping("/get-product-brokerage-price")
@Operation(summary = "获得商品的分销金额")
public CommonResult<AppBrokerageProductPriceRespVO> getProductBrokeragePrice(@RequestParam("spuId") Long spuId) {
return success(brokerageRecordService.calculateProductBrokeragePrice(spuId, getLoginUserId()));
return success(brokerageRecordService.calculateProductBrokeragePrice(getLoginUserId(), spuId));
}
}

View File

@@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@@ -41,6 +42,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
@Validated
@Slf4j
public class AppBrokerageUserController {
@Resource
private BrokerageUserService brokerageUserService;
@Resource
@@ -68,31 +70,32 @@ public class AppBrokerageUserController {
@Operation(summary = "绑定推广员")
@PreAuthenticated
public CommonResult<Boolean> bindBrokerageUser(@Valid @RequestBody AppBrokerageUserBindReqVO reqVO) {
MemberUserRespDTO user = memberUserApi.getUser(getLoginUserId());
return success(brokerageUserService.bindBrokerageUser(user.getId(), reqVO.getBindUserId(), user.getCreateTime()));
return success(brokerageUserService.bindBrokerageUser(getLoginUserId(), reqVO.getBindUserId()));
}
@GetMapping("/get-summary")
@Operation(summary = "获得个人分销统计")
@PreAuthenticated
public CommonResult<AppBrokerageUserMySummaryRespVO> getBrokerageUserSummary() {
Long userId = getLoginUserId();
// TODO @疯狂:后面这种,要不也改成 convert感觉 controller 这样更容易看到整体;核心其实是 86、8/87、9/90、9/91 这阶段
// 统计 yesterdayPrice、withdrawPrice、firstBrokerageUserCount、secondBrokerageUserCount 字段
// 查询当前登录用户信息
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(getLoginUserId());
// 统计用户昨日的佣金
LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
.setYesterdayPrice(brokerageRecordService.getSummaryPriceByUserId(userId, BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime))
.setWithdrawPrice(Optional.ofNullable(brokerageWithdrawService.getWithdrawSummaryByUserId(userId, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS))
.map(UserWithdrawSummaryBO::getPrice).orElse(0))
.setBrokeragePrice(0).setFrozenPrice(0)
.setFirstBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 1))
.setSecondBrokerageUserCount(brokerageUserService.getBrokerageUserCountByBindUserId(userId, 2));
// 设置 brokeragePrice、frozenPrice 字段
Optional.ofNullable(brokerageUserService.getBrokerageUser(userId))
.ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
return success(respVO);
Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
// 统计用户提现的佣金
Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(brokerageUser.getId()),
BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()
.findFirst().map(BrokerageWithdrawSummaryRespBO::getPrice).orElse(0);
// 统计分销用户数量(一级)
Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 1);
// 统计分销用户数量(二级)
Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 2);
// 拼接返回
return success(BrokerageUserConvert.INSTANCE.convert(yesterdayPrice, withdrawPrice, firstBrokerageUserCount, secondBrokerageUserCount, brokerageUser));
}
@GetMapping("/rank-page-by-user-count")

View File

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
@@ -32,14 +31,10 @@ public class AppBrokerageWithdrawController {
@Resource
private BrokerageWithdrawService brokerageWithdrawService;
@Resource
private DictDataApi dictDataApi;
@GetMapping("/page")
@Operation(summary = "获得分销提现分页")
@PreAuthenticated
public CommonResult<PageResult<AppBrokerageWithdrawRespVO>> getBrokerageWithdrawPage(AppBrokerageWithdrawPageReqVO pageReqVO) {
// 分页查询
PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(
BrokerageWithdrawConvert.INSTANCE.convert(pageReqVO, getLoginUserId()));
return success(BrokerageWithdrawConvert.INSTANCE.convertPage03(pageResult));
@@ -49,7 +44,7 @@ public class AppBrokerageWithdrawController {
@Operation(summary = "创建分销提现")
@PreAuthenticated
public CommonResult<Long> createBrokerageWithdraw(@RequestBody @Valid AppBrokerageWithdrawCreateReqVO createReqVO) {
return success(brokerageWithdrawService.createBrokerageWithdraw(createReqVO, getLoginUserId()));
return success(brokerageWithdrawService.createBrokerageWithdraw(getLoginUserId(), createReqVO));
}
}

View File

@@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
@Schema(description = "用户 App - 下级分销统计分页 Request VO")
@Data
@@ -19,7 +22,9 @@ public class AppBrokerageUserChildSummaryPageReqVO extends PageParam {
@Schema(description = "排序字段", example = "userCount")
private SortingField sortingField;
@Schema(description = "下级的级别", example = "1") // 1 - 直接下级2 - 间接下级
@Schema(description = "下级的级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 1 - 直接下级2 - 间接下级
@NotNull(message = "下级的级别不能为空")
@Range(min = 1, max = 2, message = "下级的级别只能是 {min} 或者 {max}")
private Integer level;
}

View File

@@ -9,6 +9,16 @@ import java.util.List;
@Data
public class AppTradeConfigRespVO {
// ========== 售后相关 ==========
@Schema(description = "售后的退款理由", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> afterSaleRefundReasons;
@Schema(description = "售后的退货理由", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> afterSaleReturnReasons;
// ========== 分销相关 ==========
@Schema(description = "分销海报地址数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> brokeragePosterUrls;

View File

@@ -8,10 +8,10 @@ import lombok.Data;
@Data
public class AppDeliveryConfigRespVO {
@Schema(description = "腾讯地图 KEY", required = true, example = "123456")
@Schema(description = "腾讯地图 KEY", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
private String tencentLbsKey;
@Schema(description = "是否开启自提", required = true, example = "true")
@Schema(description = "是否开启自提", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean pickUpEnable;
}

View File

@@ -7,10 +7,10 @@ import lombok.Data;
@Data
public class AppDeliveryExpressRespVO {
@Schema(description = "编号", required = true, example = "1")
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "门店名称", required = true, example = "顺丰")
@Schema(description = "门店名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "顺丰")
private String name;
}

View File

@@ -75,6 +75,7 @@ public class AppTradeOrderController {
}
// TODO @芋艿:如果拼团活动、秒杀活动、砍价活动时,是不是要额外在返回活动之类的信息;
// TODO @puhui999需要的
@GetMapping("/get-detail")
@Operation(summary = "获得交易订单")
@Parameter(name = "id", description = "交易订单编号")
@@ -140,7 +141,7 @@ public class AppTradeOrderController {
@Operation(summary = "确认交易订单收货")
@Parameter(name = "id", description = "交易订单编号")
public CommonResult<Boolean> receiveOrder(@RequestParam("id") Long id) {
tradeOrderUpdateService.receiveOrder(getLoginUserId(), id);
tradeOrderUpdateService.receiveOrderByMember(getLoginUserId(), id);
return success(true);
}
@@ -148,7 +149,7 @@ public class AppTradeOrderController {
@Operation(summary = "取消交易订单")
@Parameter(name = "id", description = "交易订单编号")
public CommonResult<Boolean> cancelOrder(@RequestParam("id") Long id) {
tradeOrderUpdateService.cancelOrder(getLoginUserId(), id);
tradeOrderUpdateService.cancelOrderByMember(getLoginUserId(), id);
return success(true);
}
@@ -173,7 +174,7 @@ public class AppTradeOrderController {
@PostMapping("/item/create-comment")
@Operation(summary = "创建交易订单项的评价")
public CommonResult<Long> createOrderItemComment(@RequestBody AppTradeOrderItemCommentCreateReqVO createReqVO) {
return success(tradeOrderUpdateService.createOrderItemComment(getLoginUserId(), createReqVO));
return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO));
}
}

View File

@@ -25,12 +25,12 @@ public class AppTradeOrderSettlementReqVO {
@Schema(description = "优惠劵编号", example = "1024")
private Long couponId;
@Schema(description = "是否使用积分", required = true, example = "true")
@Schema(description = "是否使用积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否使用积分不能为空")
private Boolean pointStatus;
// ========== 配送相关相关字段 ==========
@Schema(description = "配送方式", example = "1")
@Schema(description = "配送方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
private Integer deliveryType;

View File

@@ -13,7 +13,7 @@ import java.util.List;
@Data
public class AppTradeOrderSettlementRespVO {
@Schema(description = "交易类型", required = true, example = "1") // 对应 TradeOrderTypeEnum 枚举
@Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举
private Integer type = 1; // TODO 芋艿:改成计算
@Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -37,6 +37,8 @@ public class AppTradeOrderSettlementRespVO {
// ========== SPU 信息 ==========
@Schema(description = "品类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long categoryId;
@Schema(description = "SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long spuId;
@Schema(description = "SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "Apple iPhone 12")

View File

@@ -1,13 +1,17 @@
package cn.iocoder.yudao.module.trade.convert.brokerage;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserMySummaryRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@@ -30,29 +34,27 @@ public interface BrokerageUserConvert {
List<BrokerageUserRespVO> convertList(List<BrokerageUserDO> list);
PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> page, Map<Long, MemberUserRespDTO> userMap, Map<Long, Long> brokerageUserCountMap, Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap);
PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> page, Map<Long, MemberUserRespDTO> userMap, Map<Long, Long> brokerageUserCountMap, Map<Long, UserBrokerageSummaryRespBO> userOrderSummaryMap);
default PageResult<BrokerageUserRespVO> convertPage(PageResult<BrokerageUserDO> pageResult,
Map<Long, MemberUserRespDTO> userMap,
Map<Long, Long> brokerageUserCountMap,
Map<Long, UserBrokerageSummaryBO> userOrderSummaryMap,
Map<Long, UserWithdrawSummaryBO> withdrawMap) {
Map<Long, UserBrokerageSummaryRespBO> userOrderSummaryMap,
Map<Long, BrokerageWithdrawSummaryRespBO> withdrawMap) {
PageResult<BrokerageUserRespVO> result = convertPage(pageResult, userMap, brokerageUserCountMap, userOrderSummaryMap);
for (BrokerageUserRespVO userVO : result.getList()) {
// 用户信息
copyTo(userMap.get(userVO.getId()), userVO);
// 推广用户数量
userVO.setBrokerageUserCount(MapUtil.getInt(brokerageUserCountMap, userVO.getId(), 0));
// 推广订单数量、推广订单金额
Optional<UserBrokerageSummaryBO> orderSummaryOptional = Optional.ofNullable(userOrderSummaryMap.get(userVO.getId()));
userVO.setBrokerageOrderCount(orderSummaryOptional.map(UserBrokerageSummaryBO::getCount).orElse(0))
.setBrokerageOrderPrice(orderSummaryOptional.map(UserBrokerageSummaryBO::getPrice).orElse(0));
Optional<UserBrokerageSummaryRespBO> orderSummaryOptional = Optional.ofNullable(userOrderSummaryMap.get(userVO.getId()));
userVO.setBrokerageOrderCount(orderSummaryOptional.map(UserBrokerageSummaryRespBO::getCount).orElse(0))
.setBrokerageOrderPrice(orderSummaryOptional.map(UserBrokerageSummaryRespBO::getPrice).orElse(0));
// 已提现次数、已提现金额
Optional<UserWithdrawSummaryBO> withdrawSummaryOptional = Optional.ofNullable(withdrawMap.get(userVO.getId()));
userVO.setWithdrawCount(withdrawSummaryOptional.map(UserWithdrawSummaryBO::getCount).orElse(0))
.setWithdrawPrice(withdrawSummaryOptional.map(UserWithdrawSummaryBO::getPrice).orElse(0));
userVO.setWithdrawCount(0).setWithdrawPrice(0);
Optional<BrokerageWithdrawSummaryRespBO> withdrawSummaryOptional = Optional.ofNullable(withdrawMap.get(userVO.getId()));
userVO.setWithdrawCount(withdrawSummaryOptional.map(BrokerageWithdrawSummaryRespBO::getCount).orElse(0))
.setWithdrawPrice(withdrawSummaryOptional.map(BrokerageWithdrawSummaryRespBO::getPrice).orElse(0));
}
return result;
}
@@ -71,4 +73,25 @@ public interface BrokerageUserConvert {
void copyTo(MemberUserRespDTO from, @MappingTarget AppBrokerageUserRankByUserCountRespVO to);
default AppBrokerageUserMySummaryRespVO convert(Integer yesterdayPrice, Integer withdrawPrice,
Long firstBrokerageUserCount, Long secondBrokerageUserCount,
BrokerageUserDO brokerageUser) {
AppBrokerageUserMySummaryRespVO respVO = new AppBrokerageUserMySummaryRespVO()
.setYesterdayPrice(ObjUtil.defaultIfNull(yesterdayPrice, 0))
.setWithdrawPrice(ObjUtil.defaultIfNull(withdrawPrice, 0))
.setBrokeragePrice(0).setFrozenPrice(0)
.setFirstBrokerageUserCount(ObjUtil.defaultIfNull(firstBrokerageUserCount, 0L))
.setSecondBrokerageUserCount(ObjUtil.defaultIfNull(secondBrokerageUserCount, 0L));
// 设置 brokeragePrice、frozenPrice 字段
Optional.ofNullable(brokerageUser)
.ifPresent(user -> respVO.setBrokeragePrice(user.getBrokeragePrice()).setFrozenPrice(user.getFrozenPrice()));
return respVO;
}
default void copyTo(IPage<AppBrokerageUserChildSummaryRespVO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
for (AppBrokerageUserChildSummaryRespVO vo : pageResult.getRecords()) {
Optional.ofNullable(userMap.get(vo.getId())).ifPresent(user ->
vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
}
}
}

View File

@@ -90,7 +90,7 @@ public interface TradeOrderConvert {
default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount()));
new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(item.getCount()));
return new ProductSkuUpdateStockReqDTO(items);
}
default ProductSkuUpdateStockReqDTO convertNegative(List<AppTradeOrderSettlementReqVO.Item> list) {
@@ -110,7 +110,7 @@ public interface TradeOrderConvert {
createReqDTO.setSubject(subject);
createReqDTO.setBody(subject); // TODO 芋艿:临时写死
// 订单相关字段
createReqDTO.setPrice(order.getPayPrice()).setExpireTime(addTime(orderProperties.getExpireTime()));
createReqDTO.setPrice(order.getPayPrice()).setExpireTime(addTime(orderProperties.getPayExpireTime()));
return createReqDTO;
}
@@ -180,7 +180,7 @@ public interface TradeOrderConvert {
TradeOrderProperties tradeOrderProperties,
DeliveryExpressDO express) {
AppTradeOrderDetailRespVO orderVO = convert3(order, orderItems);
orderVO.setPayExpireTime(addTime(tradeOrderProperties.getExpireTime()));
orderVO.setPayExpireTime(addTime(tradeOrderProperties.getPayExpireTime()));
if (StrUtil.isNotEmpty(order.getPayChannelCode())) {
orderVO.setPayChannelName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CHANNEL_CODE, order.getPayChannelCode()));
}

View File

@@ -35,6 +35,19 @@ public class TradeConfigDO extends BaseDO {
@TableId
private Long id;
// ========== 售后相关 ==========
/**
* 售后的退款理由
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> afterSaleRefundReasons;
/**
* 售后的退货理由
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> afterSaleReturnReasons;
// ========== 配送相关 ==========
/**

View File

@@ -105,6 +105,8 @@ public class TradeOrderDO extends BaseDO {
*/
private Boolean commentStatus;
// TODO @疯狂:加一个推广人编号;
// ========== 价格 + 支付基本信息 ==========
// 价格文档 - 淘宝https://open.taobao.com/docV3.htm?docId=108471&docType=1

View File

@@ -146,18 +146,20 @@ public class TradeOrderItemDO extends BaseDO {
private Integer pointPrice;
/**
* 使用的积分
*
* 目的:用于后续取消或者售后订单时,需要归还赠送
*/
private Integer usePoint;
/**
* 赠送的积分
*
* 目的:用于后续取消或者售后订单时,需要扣减赠送
*/
private Integer givePoint;
/**
* VIP 减免金额,单位:分
*/
private Integer vipPrice;
// TODO @芋艿:如果商品 vip 折扣时,到底是新增一个 vipPrice 记录优惠记录,还是 vipDiscountPrice记录 vip 的优惠;还是直接使用 vipPrice
// 目前 crmeb 的选择,单独一个 vipPrice 记录优惠价格;感觉不一定合理,可以在看看有赞的;
// ========== 售后基本信息 ==========

View File

@@ -1,20 +1,24 @@
package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.BrokerageRecordPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByPriceRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.yulichang.toolkit.MPJWrappers;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 佣金记录 Mapper
@@ -52,11 +56,28 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
BrokerageRecordDO::getUserId, userId);
}
@Select("SELECT COUNT(1), SUM(price) FROM trade_brokerage_record " +
"WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} AND deleted = FALSE")
UserBrokerageSummaryBO selectCountAndSumPriceByUserIdAndBizTypeAndStatus(@Param("userId") Long userId,
@Param("bizType") Integer bizType,
@Param("status") Integer status);
default List<UserBrokerageSummaryRespBO> selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(Collection<Long> userIds,
Integer bizType,
Integer status) {
List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
.select(BrokerageRecordDO::getUserId)
.selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryRespBO::getCount)
.selectSum(BrokerageRecordDO::getPrice)
.in(BrokerageRecordDO::getUserId, userIds)
.eq(BrokerageRecordDO::getBizId, bizType)
.eq(BrokerageRecordDO::getStatus, status)
.groupBy(BrokerageRecordDO::getUserId));
return BeanUtil.copyToList(list, UserBrokerageSummaryRespBO.class);
// selectJoinList有BUG会与租户插件冲突解析SQL时发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW
// return selectJoinList(UserBrokerageSummaryBO.class, MPJWrappers.lambdaJoin(BrokerageRecordDO.class)
// .select(BrokerageRecordDO::getUserId)
// .selectCount(BrokerageRecordDO::getId, UserBrokerageSummaryBO::getCount)
// .selectSum(BrokerageRecordDO::getPrice)
// .in(BrokerageRecordDO::getUserId, userIds)
// .eq(BrokerageRecordDO::getBizId, bizType)
// .eq(BrokerageRecordDO::getStatus, status)
// .groupBy(BrokerageRecordDO::getUserId));
}
@Select("SELECT SUM(price) FROM trade_brokerage_record " +
"WHERE user_id = #{userId} AND biz_type = #{bizType} " +

View File

@@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
@@ -139,8 +139,11 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
@Param("endTime") LocalDateTime endTime);
IPage<AppBrokerageUserChildSummaryRespVO> selectSummaryPageByUserId(Page<?> page,
@Param("param") AppBrokerageUserChildSummaryPageReqVO param,
@Param("userId") Long userId);
@Param("ids") List<Long> ids,
@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("bindUserIds") List<Long> bindUserIds,
@Param("sortingField") SortingField sortingField);
default List<BrokerageUserDO> selectListByBindUserId(Long bindUserId) {
return selectList(BrokerageUserDO::getBindUserId, bindUserId);

View File

@@ -1,15 +1,19 @@
package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 佣金提现 Mapper
@@ -37,9 +41,23 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
.eq(BrokerageWithdrawDO::getStatus, status));
}
@Select("SELECT COUNT(1) AS count, SUM(price) AS price FROM trade_brokerage_withdraw " +
"WHERE user_id = #{userId} AND status = #{status} AND deleted = FALSE")
UserWithdrawSummaryBO selectCountAndSumPriceByUserIdAndStatus(@Param("userId") Long userId,
@Param("status") Integer status);
default List<BrokerageWithdrawSummaryRespBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds, Integer status) {
List<Map<String, Object>> list = selectMaps(new MPJLambdaWrapper<BrokerageWithdrawDO>()
.select(BrokerageWithdrawDO::getUserId)
.selectCount(BrokerageWithdrawDO::getId, BrokerageWithdrawSummaryRespBO::getCount)
.selectSum(BrokerageWithdrawDO::getPrice)
.in(BrokerageWithdrawDO::getUserId, userIds)
.eq(BrokerageWithdrawDO::getStatus, status)
.groupBy(BrokerageWithdrawDO::getUserId));
return BeanUtil.copyToList(list, BrokerageWithdrawSummaryRespBO.class);
// selectJoinList有BUG会与租户插件冲突解析SQL时发生异常 https://gitee.com/best_handsome/mybatis-plus-join/issues/I84GYW
// return selectJoinList(UserWithdrawSummaryBO.class, new MPJLambdaWrapper<BrokerageWithdrawDO>()
// .select(BrokerageWithdrawDO::getUserId)
// .selectCount(BrokerageWithdrawDO::getId, UserWithdrawSummaryBO::getCount)
// .selectSum(BrokerageWithdrawDO::getPrice)
// .in(BrokerageWithdrawDO::getUserId, userIds)
// .eq(BrokerageWithdrawDO::getStatus, status)
// .groupBy(BrokerageWithdrawDO::getUserId));
}
}

View File

@@ -38,4 +38,10 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
.eq(TradeOrderItemDO::getUserId, loginUserId));
}
default List<TradeOrderItemDO> selectListByOrderIdAndCommentStatus(Long orderId, Boolean commentStatus) {
return selectList(new LambdaQueryWrapperX<TradeOrderItemDO>()
.eq(TradeOrderItemDO::getOrderId, orderId)
.eq(TradeOrderItemDO::getCommentStatus, commentStatus));
}
}

View File

@@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
@Mapper
@@ -58,4 +60,25 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
.eq(TradeOrderDO::getId, orderId)
.eq(TradeOrderDO::getUserId, loginUserId));
}
default List<TradeOrderDO> selectListByStatusAndCreateTimeLt(Integer status, LocalDateTime createTime) {
return selectList(new LambdaUpdateWrapper<TradeOrderDO>()
.eq(TradeOrderDO::getStatus, status)
.lt(TradeOrderDO::getCreateTime, createTime));
}
default List<TradeOrderDO> selectListByStatusAndDeliveryTimeLt(Integer status, LocalDateTime deliveryTime) {
return selectList(new LambdaUpdateWrapper<TradeOrderDO>()
.eq(TradeOrderDO::getStatus, status)
.lt(TradeOrderDO::getDeliveryTime, deliveryTime));
}
default List<TradeOrderDO> selectListByStatusAndReceiveTimeLt(Integer status, LocalDateTime receive,
Boolean commentStatus) {
return selectList(new LambdaUpdateWrapper<TradeOrderDO>()
.eq(TradeOrderDO::getStatus, status)
.lt(TradeOrderDO::getReceiveTime, receive)
.eq(TradeOrderDO::getCommentStatus, commentStatus));
}
}

View File

@@ -21,7 +21,8 @@ public @interface AfterSaleLog {
/**
* 售后 ID
*/
String id();
@Deprecated
String id() default "";
/**
* 操作类型
@@ -31,6 +32,7 @@ public @interface AfterSaleLog {
/**
* 日志内容
*/
@Deprecated
String content() default "";
}

View File

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.framework.aftersalelog.core.aop;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLog;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.annotations.AfterSaleLog;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogCreateReqDTO;
@@ -96,7 +95,7 @@ public class AfterSaleLogAspect {
String id = MapUtil.getStr(spelMap, afterSaleLogPoint.id());
result.put(ID, id);
// 操作类型
String operateType = afterSaleLogPoint.operateType().description();
String operateType = afterSaleLogPoint.operateType().getContent();
result.put(OPERATE_TYPE, operateType);
// 日志内容
String content = MapUtil.getStr(spelMap, afterSaleLogPoint.content());

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.framework.aftersalelog.core.util;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.aop.AfterSaleLogAspect;
import java.util.Map;
/**
* 操作日志工具类
* 目前主要的作用,是提供给业务代码,记录操作明细和拓展字段
@@ -19,4 +21,13 @@ public class AfterSaleLogUtils {
AfterSaleLogAspect.setAfterStatus(status);
}
public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus) {
setAfterSaleInfo(id, beforeStatus, afterStatus, null);
}
public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus,
Map<String, Object> exts) {
// TODO 待实现
}
}

View File

@@ -28,6 +28,18 @@ public class TradeOrderProperties {
* 支付超时时间
*/
@NotNull(message = "支付超时时间不能为空")
private Duration expireTime;
private Duration payExpireTime;
/**
* 收货超时时间
*/
@NotNull(message = "收货超时时间不能为空")
private Duration receiveExpireTime;
/**
* 评论超时时间
*/
@NotNull(message = "评论超时时间不能为空")
private Duration commentExpireTime;
}

View File

@@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.trade.framework.order.core.annotations;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderOperateTypeEnum;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;

View File

@@ -31,6 +31,16 @@ import static java.util.Collections.emptyMap;
@Slf4j
public class TradeOrderLogAspect {
/**
* 用户编号
*
* 目前的使用场景:支付回调时,需要强制设置下用户编号
*/
private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();
/**
* 用户类型
*/
private static final ThreadLocal<Integer> USER_TYPE = new ThreadLocal<>();
/**
* 订单编号
*/
@@ -112,4 +122,9 @@ public class TradeOrderLogAspect {
EXTS.set(exts);
}
public static void setUserInfo(Long userId, Integer userType) {
USER_ID.set(userId);
USER_TYPE.set(userType);
}
}

View File

@@ -20,4 +20,8 @@ public class TradeOrderLogUtils {
TradeOrderLogAspect.setOrderInfo(id, beforeStatus, afterStatus, exts);
}
public static void setUserInfo(Long userId, Integer userType) {
TradeOrderLogAspect.setUserInfo(userId, userType);
}
}

View File

@@ -14,13 +14,13 @@ import javax.annotation.Resource;
* @author owen
*/
@Component
@TenantJob
public class BrokerageRecordUnfreezeJob implements JobHandler {
@Resource
private BrokerageRecordService brokerageRecordService;
@Override
@TenantJob
public String execute(String param) {
int count = brokerageRecordService.unfreezeRecord();
return StrUtil.format("解冻佣金 {} 个", count);

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.trade.job.order;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 交易订单的自动过期 Job
*
* @author 芋道源码
*/
@Component
public class TradeOrderAutoCancelJob implements JobHandler {
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@Override
@TenantJob
public String execute(String param) {
int count = tradeOrderUpdateService.cancelOrderBySystem();
return String.format("过期订单 %s 个", count);
}
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.trade.job.order;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 交易订单的自动评论 Job
*
* @author 芋道源码
*/
@Component
public class TradeOrderAutoCommentJob implements JobHandler {
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@Override
@TenantJob
public String execute(String param) {
int count = tradeOrderUpdateService.createOrderItemCommentBySystem();
return String.format("评论订单 %s 个", count);
}
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.trade.job.order;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 交易订单的自动收货 Job
*
* @author 芋道源码
*/
@Component
public class TradeOrderAutoReceiveJob implements JobHandler {
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@Override
@TenantJob
public String execute(String param) {
int count = tradeOrderUpdateService.receiveOrderBySystem();
return String.format("自动收货 %s 个", count);
}
}

View File

@@ -1,4 +0,0 @@
/**
* 占位文件,无特殊用途
*/
package cn.iocoder.yudao.module.trade.job;

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.trade.service.aftersale;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -17,18 +17,23 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppTradeAfterSa
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.aftersale.TradeAfterSaleLogDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
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.dal.mysql.aftersale.TradeAfterSaleLogMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.TradeAfterSaleMapper;
import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleStatusEnum;
import cn.iocoder.yudao.module.trade.enums.aftersale.TradeAfterSaleTypeEnum;
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.annotations.AfterSaleLog;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.dto.TradeAfterSaleLogCreateReqDTO;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.service.AfterSaleLogService;
import cn.iocoder.yudao.module.trade.framework.aftersalelog.core.util.AfterSaleLogUtils;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import lombok.extern.slf4j.Slf4j;
@@ -61,6 +66,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
private TradeOrderUpdateService tradeOrderUpdateService;
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Resource
private DeliveryExpressService deliveryExpressService;
@Resource
private TradeAfterSaleMapper tradeAfterSaleMapper;
@@ -85,7 +92,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
public TradeAfterSaleDO getAfterSale(Long userId, Long id) {
return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
return tradeAfterSaleMapper.selectByIdAndUserId(id, userId);
}
@Override
@@ -93,10 +100,9 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
return tradeAfterSaleMapper.selectById(id);
}
// TODO 芋艿:拼团失败,要不要发起售后的方式退款?还是走取消逻辑?
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_CREATE)
public Long createAfterSale(Long userId, AppTradeAfterSaleCreateReqVO createReqVO) {
// 第一步,前置校验
TradeOrderItemDO tradeOrderItem = validateOrderItemApplicable(userId, createReqVO);
@@ -119,12 +125,10 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
if (orderItem == null) {
throw exception(ORDER_ITEM_NOT_FOUND);
}
// 已申请售后,不允许再发起售后申请
if (!TradeOrderItemAfterSaleStatusEnum.isNone(orderItem.getAfterSaleStatus())) {
throw exception(AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED);
}
// 申请的退款金额,不能超过商品的价格
if (createReqVO.getRefundPrice() > orderItem.getPayPrice()) {
throw exception(AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR);
@@ -163,17 +167,14 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
afterSale.setOrderNo(order.getNo()); // 记录 orderNo 订单流水,方便后续检索
afterSale.setType(TradeOrderStatusEnum.isCompleted(order.getStatus())
? TradeAfterSaleTypeEnum.AFTER_SALE.getType() : TradeAfterSaleTypeEnum.IN_SALE.getType());
// TODO 退还积分
tradeAfterSaleMapper.insert(afterSale);
// 更新交易订单项的售后状态
tradeOrderUpdateService.updateOrderItemAfterSaleStatus(orderItem.getId(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
afterSale.getId(), null);
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCreate(orderItem.getId(), afterSale.getId());
// 记录售后日志
createAfterSaleLog(orderItem.getUserId(), UserTypeEnum.MEMBER.getValue(),
afterSale, null, afterSale.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), null,
TradeAfterSaleStatusEnum.APPLY.getStatus());
// TODO 发送售后消息
return afterSale;
@@ -181,6 +182,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_AGREE_APPLY)
public void agreeAfterSale(Long userId, Long id) {
// 校验售后单存在,并状态未审批
TradeAfterSaleDO afterSale = validateAfterSaleAuditable(id);
@@ -194,14 +196,14 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), newStatus);
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_DISAGREE_APPLY)
public void disagreeAfterSale(Long userId, TradeAfterSaleDisagreeReqVO auditReqVO) {
// 校验售后单存在,并状态未审批
TradeAfterSaleDO afterSale = validateAfterSaleAuditable(auditReqVO.getId());
@@ -213,14 +215,12 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setAuditReason(auditReqVO.getAuditReason()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), newStatus);
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
/**
@@ -249,6 +249,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_DELIVERY)
public void deliveryAfterSale(Long userId, AppTradeAfterSaleDeliveryReqVO deliveryReqVO) {
// 校验售后单存在,并状态未退货
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(deliveryReqVO.getId());
@@ -258,6 +259,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
if (ObjectUtil.notEqual(afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus())) {
throw exception(AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE);
}
DeliveryExpressDO express = deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId());
// 更新售后单的物流信息
updateAfterSaleStatus(afterSale.getId(), TradeAfterSaleStatusEnum.SELLER_AGREE.getStatus(), new TradeAfterSaleDO()
@@ -266,14 +268,17 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setDeliveryTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.MEMBER.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
TradeAfterSaleStatusEnum.BUYER_DELIVERY.getStatus(),
MapUtil.<String, Object>builder().put("expressName", express.getName())
.put("logisticsNo", deliveryReqVO.getLogisticsNo()).build());
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_AGREE_RECEIVE)
public void receiveAfterSale(Long userId, Long id) {
// 校验售后单存在,并状态为已退货
TradeAfterSaleDO afterSale = validateAfterSaleReceivable(id);
@@ -283,14 +288,15 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setStatus(TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus()).setReceiveTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
TradeAfterSaleStatusEnum.WAIT_REFUND.getStatus());
// TODO 发送售后消息
}
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_DISAGREE_RECEIVE)
public void refuseAfterSale(Long userId, TradeAfterSaleRefuseReqVO refuseReqVO) {
// 校验售后单存在,并状态为已退货
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(refuseReqVO.getId());
@@ -307,14 +313,14 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setReceiveReason(refuseReqVO.getRefuseMemo()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
TradeAfterSaleStatusEnum.SELLER_REFUSE.getStatus(),
MapUtil.of("reason", refuseReqVO.getRefuseMemo()));
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
/**
@@ -336,6 +342,7 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_REFUND)
public void refundAfterSale(Long userId, String userIp, Long id) {
// 校验售后单的状态,并状态待退款
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
@@ -354,15 +361,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setStatus(TradeAfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.ADMIN.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.COMPLETE.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
TradeAfterSaleStatusEnum.COMPLETE.getStatus());
// TODO 发送售后消息
// 更新交易订单项的售后状态为【已完成】
tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(),
null, afterSale.getRefundPrice());
tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
}
private void createPayRefund(String userIp, TradeAfterSaleDO afterSale) {
@@ -380,6 +385,8 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
}
@Override
@Transactional(rollbackFor = Exception.class)
@AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_CANCEL)
public void cancelAfterSale(Long userId, Long id) {
// 校验售后单的状态,并状态待退款
TradeAfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
@@ -397,14 +404,13 @@ public class TradeAfterSaleServiceImpl implements TradeAfterSaleService, AfterSa
.setStatus(TradeAfterSaleStatusEnum.BUYER_CANCEL.getStatus()));
// 记录售后日志
createAfterSaleLog(userId, UserTypeEnum.MEMBER.getValue(),
afterSale, afterSale.getStatus(), TradeAfterSaleStatusEnum.BUYER_CANCEL.getStatus());
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
TradeAfterSaleStatusEnum.BUYER_CANCEL.getStatus());
// TODO 发送售后消息
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemAfterSaleStatus(afterSale.getOrderItemId(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
@Override

View File

@@ -8,11 +8,15 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 佣金记录 Service 接口
@@ -57,6 +61,19 @@ public interface BrokerageRecordService {
*/
void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId, Integer brokeragePrice, String title);
/**
* 减少佣金【只针对自己】
*
* @param userId 会员编号
* @param bizType 业务类型
* @param bizId 业务编号
* @param brokeragePrice 佣金
* @param title 标题
*/
default void reduceBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId, Integer brokeragePrice, String title) {
addBrokerage(userId, bizType, bizId, -brokeragePrice, title);
}
/**
* 取消佣金:将佣金记录,状态修改为已失效
*
@@ -74,14 +91,29 @@ public interface BrokerageRecordService {
int unfreezeRecord();
/**
* 汇总用户佣金
* 按照 userId汇总每个用户佣金
*
* @param userId 用户编号
* @param userIds 用户编号
* @param bizType 业务类型
* @param status 佣金状态
* @return 用户佣金汇总
* @return 用户佣金汇总 List
*/
UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status);
List<UserBrokerageSummaryRespBO> getUserBrokerageSummaryListByUserId(Collection<Long> userIds,
Integer bizType, Integer status);
/**
* 按照 userId汇总每个用户的佣金
*
* @param userIds 用户编号
* @param bizType 业务类型
* @param status 佣金状态
* @return 用户佣金汇总 Map
*/
default Map<Long, UserBrokerageSummaryRespBO> getUserBrokerageSummaryMapByUserId(Collection<Long> userIds,
Integer bizType, Integer status) {
return convertMap(getUserBrokerageSummaryListByUserId(userIds, bizType, status),
UserBrokerageSummaryRespBO::getUserId);
}
/**
* 获得用户佣金合计
@@ -100,7 +132,8 @@ public interface BrokerageRecordService {
* @param pageReqVO 分页查询
* @return 排行榜分页
*/
PageResult<AppBrokerageUserRankByPriceRespVO> getBrokerageUserChildSummaryPageByPrice(AppBrokerageUserRankPageReqVO pageReqVO);
PageResult<AppBrokerageUserRankByPriceRespVO> getBrokerageUserChildSummaryPageByPrice(
AppBrokerageUserRankPageReqVO pageReqVO);
/**
* 获取用户的排名(基于佣金总数)
@@ -114,9 +147,10 @@ public interface BrokerageRecordService {
/**
* 计算商品被购买后,推广员可以得到的佣金
*
* @param spuId 商品编号
* @param userId 用户编号
* @param spuId 商品编号
* @return 用户佣金
*/
AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long spuId, Long userId);
AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long userId, Long spuId);
}

View File

@@ -26,7 +26,7 @@ import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
@@ -36,10 +36,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
@@ -234,25 +231,55 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
return count;
}
/**
* 解冻单条佣金记录
*
* @param record 佣金记录
* @return 解冻是否成功
*/
@Transactional(rollbackFor = Exception.class)
public boolean unfreezeRecord(BrokerageRecordDO record) {
// 更新记录状态
BrokerageRecordDO updateObj = new BrokerageRecordDO()
.setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus())
.setUnfreezeTime(LocalDateTime.now());
int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
if (updateRows == 0) {
log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId());
return false;
}
// 更新用户冻结佣金
brokerageUserService.updateFrozenPriceDecrAndPriceIncr(record.getUserId(), -record.getPrice());
log.info("[unfreezeRecord][record({}) 更新为已结算成功]", record.getId());
return true;
}
@Override
public UserBrokerageSummaryBO getUserBrokerageSummaryByUserId(Long userId, Integer bizType, Integer status) {
UserBrokerageSummaryBO summaryBO = brokerageRecordMapper.selectCountAndSumPriceByUserIdAndBizTypeAndStatus(userId, bizType, status);
return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
public List<UserBrokerageSummaryRespBO> getUserBrokerageSummaryListByUserId(Collection<Long> userIds,
Integer bizType, Integer status) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
return brokerageRecordMapper.selectCountAndSumPriceByUserIdInAndBizTypeAndStatus(userIds, bizType, status);
}
@Override
public Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId, bizType, beginTime, endTime);
return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId, bizType,
beginTime, endTime);
}
@Override
public PageResult<AppBrokerageUserRankByPriceRespVO> getBrokerageUserChildSummaryPageByPrice(AppBrokerageUserRankPageReqVO pageReqVO) {
IPage<AppBrokerageUserRankByPriceRespVO> pageResult = brokerageRecordMapper.selectSummaryPricePageGroupByUserId(MyBatisUtils.buildPage(pageReqVO),
IPage<AppBrokerageUserRankByPriceRespVO> pageResult = brokerageRecordMapper.selectSummaryPricePageGroupByUserId(
MyBatisUtils.buildPage(pageReqVO),
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
ArrayUtil.get(pageReqVO.getTimes(), 0), ArrayUtil.get(pageReqVO.getTimes(), 1));
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
// TODO @疯狂:这个要不我们先做精准的?先查询自己的推广金额,然后查询比该金额多的有多少人?
@Override
public Integer getUserRankByPrice(Long userId, LocalDateTime[] times) {
AppBrokerageUserRankPageReqVO pageParam = new AppBrokerageUserRankPageReqVO().setTimes(times);
@@ -289,26 +316,8 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
brokerageRecordMapper.insert(record);
}
@Transactional(rollbackFor = Exception.class)
public boolean unfreezeRecord(BrokerageRecordDO record) {
// 更新记录状态
BrokerageRecordDO updateObj = new BrokerageRecordDO()
.setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus())
.setUnfreezeTime(LocalDateTime.now());
int updateRows = brokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
if (updateRows == 0) {
log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId());
return false;
}
// 更新用户冻结佣金
brokerageUserService.updateFrozenPriceDecrAndPriceIncr(record.getUserId(), -record.getPrice());
log.info("[unfreezeRecord][record({}) 更新为已结算成功]", record.getId());
return true;
}
@Override
public AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long spuId, Long userId) {
public AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long userId, Long spuId) {
// 1. 构建默认的返回值
AppBrokerageProductPriceRespVO respVO = new AppBrokerageProductPriceRespVO().setEnabled(false)
.setBrokerageMinPrice(0).setBrokerageMaxPrice(0);
@@ -338,7 +347,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
if (BooleanUtil.isTrue(spu.getSubCommissionType())) {
fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice);
// 3.2 全局分佣模式(根据商品价格比例计算)
// 3.2 全局分佣模式(根据商品价格比例计算)
} else {
spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice);
spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice);

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
@@ -10,7 +9,6 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -108,24 +106,9 @@ public interface BrokerageUserService {
*
* @param userId 用户编号
* @param bindUserId 推广员编号
* @param registerTime 用户注册时间
* @return 是否绑定
*/
default boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId, @NotNull LocalDateTime registerTime) {
// 注册时间在30秒内的都算新用户
boolean isNewUser = LocalDateTimeUtils.afterNow(registerTime.minusSeconds(30));
return bindBrokerageUser(userId, bindUserId, isNewUser);
}
/**
* 【会员】绑定推广员
*
* @param userId 用户编号
* @param bindUserId 推广员编号
* @param isNewUser 是否为新用户
* @return 是否绑定
*/
boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser);
boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId);
/**
* 获取用户是否有分销资格

View File

@@ -4,18 +4,25 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
@@ -23,10 +30,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -47,6 +51,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
@Resource
private TradeConfigService tradeConfigService;
@Resource
private MemberUserApi memberUserApi;
@Override
public BrokerageUserDO getBrokerageUser(Long id) {
return brokerageUserMapper.selectById(id);
@@ -154,7 +161,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
}
@Override
public boolean bindBrokerageUser(Long userId, Long bindUserId, Boolean isNewUser) {
public boolean bindBrokerageUser(Long userId, Long bindUserId) {
// 1. 获得分销用户
boolean isNewBrokerageUser = false;
BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
@@ -164,7 +171,7 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
}
// 2.1 校验是否能绑定用户
boolean validated = isUserCanBind(brokerageUser, isNewUser);
boolean validated = isUserCanBind(brokerageUser);
if (!validated) {
return false;
}
@@ -218,11 +225,25 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
@Override
public PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId) {
IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(MyBatisUtils.buildPage(pageReqVO), pageReqVO, userId);
// 1.1 根据昵称过滤用户
List<Long> ids = StrUtil.isBlank(pageReqVO.getNickname())
? Collections.emptyList()
: convertList(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), MemberUserRespDTO::getId);
// 1.2 生成推广员编号列表
List<Long> bindUserIds = buildBindUserIdsByLevel(userId, pageReqVO.getLevel());
// 2. 分页查询
IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
MyBatisUtils.buildPage(pageReqVO), ids, BrokerageRecordBizTypeEnum.ORDER.getType(),
BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
);
// 3. 拼接数据并返回
List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
BrokerageUserConvert.INSTANCE.copyTo(pageResult, userMap);
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
private boolean isUserCanBind(BrokerageUserDO user, Boolean isNewUser) {
private boolean isUserCanBind(BrokerageUserDO user) {
// 校验分销功能是否启用
TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
if (tradeConfig == null || !BooleanUtil.isTrue(tradeConfig.getBrokerageEnabled())) {
@@ -236,8 +257,8 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
// 校验分销关系绑定模式
if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
// TODO @疯狂:是不是把 isNewUser 挪到这里好点呀?
if (!BooleanUtil.isTrue(isNewUser)) {
// 判断是否为新用户:注册时间在 30 秒内的,都算新用户
if (!isNewRegisterUser(user.getId())) {
throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
}
} else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
@@ -245,14 +266,29 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
throw exception(BROKERAGE_BIND_OVERRIDE); // 已绑定了推广人
}
}
return true;
}
/**
* 判断是否为新用户
* <p>
* 标准:注册时间在 30 秒内的,都算新用户
* <p>
* 疑问:为什么通过这样的方式实现?
* 回答:因为注册在 member 模块,希望它和 trade 模块解耦,所以只能用这种约定的逻辑。
*
* @param userId 用户编号
* @return 是否新用户
*/
private boolean isNewRegisterUser(Long userId) {
MemberUserRespDTO user = memberUserApi.getUser(userId);
return user != null && LocalDateTimeUtils.beforeNow(user.getCreateTime().plusSeconds(30));
}
private void validateCanBindUser(BrokerageUserDO user, Long bindUserId) {
// 校验要绑定的用户有无推广资格
BrokerageUserDO bindUser = brokerageUserMapper.selectById(bindUserId);
if (bindUser == null || !BooleanUtil.isTrue(bindUser.getBrokerageEnabled())) {
if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) {
throw exception(BROKERAGE_BIND_USER_NOT_ENABLED);
}
@@ -283,6 +319,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
* @return 绑定用户编号列表
*/
private List<Long> buildBindUserIdsByLevel(Long bindUserId, Integer level) {
if (bindUserId == null) {
return Collections.emptyList();
}
Assert.isTrue(level == null || level <= 2, "目前只支持 level 小于等于 2");
List<Long> bindUserIds = CollUtil.newArrayList();
if (level == null || level == 1) {

View File

@@ -5,7 +5,14 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.Brok
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 佣金提现 Service 接口
@@ -42,19 +49,32 @@ public interface BrokerageWithdrawService {
/**
* 【会员】创建佣金提现
*
* @param createReqVO 创建信息
* @param userId 会员用户编号
* @param createReqVO 创建信息
* @return 佣金提现编号
*/
Long createBrokerageWithdraw(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId);
Long createBrokerageWithdraw(Long userId, AppBrokerageWithdrawCreateReqVO createReqVO);
/**
* 汇总用户提现
* 按照 userId汇总每个用户提现
*
* @param userId 用户编号
* @param status 提现状态
* @return 用户提现汇总
* @param userIds 用户编号
* @param status 提现状态
* @return 用户提现汇总 List
*/
UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status);
List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
BrokerageWithdrawStatusEnum status);
/**
* 按照 userId汇总每个用户的提现
*
* @param userIds 用户编号
* @param status 提现状态
* @return 用户提现汇总 Map
*/
default Map<Long, BrokerageWithdrawSummaryRespBO> getWithdrawSummaryMapByUserId(Set<Long> userIds,
BrokerageWithdrawStatusEnum status) {
return convertMap(getWithdrawSummaryListByUserId(userIds, status), BrokerageWithdrawSummaryRespBO::getUserId);
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -17,7 +18,7 @@ import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserWithdrawSummaryBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -26,6 +27,9 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -65,29 +69,27 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
}
// 2. 更新
BrokerageWithdrawDO updateObj = new BrokerageWithdrawDO()
.setStatus(status.getStatus())
.setAuditReason(auditReason)
.setAuditTime(LocalDateTime.now());
int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(), updateObj);
int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(),
new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
if (rows == 0) {
throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
}
String templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE;
String templateCode;
if (BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.equals(status)) {
templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE;
// 3.1 通过时佣金转余额
if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
// todo
// todo 疯狂:
}
// TODO 疯狂:调用转账接口
} else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_REJECT;
// 3.2 驳回时需要退还用户佣金
brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
} else {
throw new IllegalArgumentException("不支持的提现状态");
throw new IllegalArgumentException("不支持的提现状态" + status);
}
// 4. 通知用户
@@ -96,10 +98,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
.put("price", MoneyUtils.fenToYuanStr(withdraw.getPrice()))
.put("reason", withdraw.getAuditReason())
.build();
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
.setUserId(withdraw.getUserId())
.setTemplateCode(templateCode).setTemplateParams(templateParams);
notifyMessageSendApi.sendSingleMessageToMember(reqDTO);
notifyMessageSendApi.sendSingleMessageToMember(new NotifySendSingleToUserReqDTO()
.setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams));
}
private BrokerageWithdrawDO validateBrokerageWithdrawExists(Integer id) {
@@ -122,29 +122,32 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createBrokerageWithdraw(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId) {
// 校验提现金额
public Long createBrokerageWithdraw(Long userId, AppBrokerageWithdrawCreateReqVO createReqVO) {
// 1.1 校验提现金额
TradeConfigDO tradeConfig = validateWithdrawPrice(createReqVO.getPrice());
// 校验提现参数
// 1.2 校验提现参数
createReqVO.validate(validator);
// 计算手续费
// 2.1 计算手续费
Integer feePrice = calculateFeePrice(createReqVO.getPrice(), tradeConfig.getBrokerageWithdrawFeePercent());
// 创建佣金提现记录
// 2.2 创建佣金提现记录
BrokerageWithdrawDO withdraw = BrokerageWithdrawConvert.INSTANCE.convert(createReqVO, userId, feePrice);
brokerageWithdrawMapper.insert(withdraw);
// 创建用户佣金记录
brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.WITHDRAW, String.valueOf(withdraw.getId()),
-createReqVO.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW.getTitle());
// 3. 创建用户佣金记录
// 注意佣金是否充足reduceBrokerage 已经进行校验
brokerageRecordService.reduceBrokerage(userId, BrokerageRecordBizTypeEnum.WITHDRAW, String.valueOf(withdraw.getId()),
createReqVO.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW.getTitle());
return withdraw.getId();
}
@Override
public UserWithdrawSummaryBO getWithdrawSummaryByUserId(Long userId, BrokerageWithdrawStatusEnum status) {
UserWithdrawSummaryBO summaryBO = brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userId, status.getStatus());
return summaryBO != null ? summaryBO : new UserWithdrawSummaryBO(0, 0);
public List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
BrokerageWithdrawStatusEnum status) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
}
/**

View File

@@ -5,14 +5,19 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户佣金提现合计 BO
* 佣金提现合计 BO
*
* @author owen
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserWithdrawSummaryBO {
public class BrokerageWithdrawSummaryRespBO {
/**
* 用户编号
*/
private Long userId;
/**
* 提现次数

View File

@@ -12,10 +12,14 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserBrokerageSummaryBO {
public class UserBrokerageSummaryRespBO {
/**
* 佣金数量
* 用户编号
*/
private Long userId;
/**
* 推广数量
*/
private Integer count;
/**

View File

@@ -26,7 +26,7 @@ import static java.util.Collections.emptyList;
/**
* 购物车 Service 实现类
*
* // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息;目前不支持的原因,前端界面需要前端 pr 支持下;
* // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息;目前不支持的原因,前端界面需要前端 pr 支持下;例如说:会员价格;
*
* @author 芋道源码
*/

View File

@@ -1,15 +1,17 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import javax.validation.constraints.NotNull;
/**
* 交易订单【写】Service 接口
*
@@ -60,15 +62,29 @@ public interface TradeOrderUpdateService {
* @param userId 用户编号
* @param id 订单编号
*/
void receiveOrder(Long userId, Long id);
void receiveOrderByMember(Long userId, Long id);
/**
* 【会员】取消订单
* 【系统】自动收货交易订单
*
* @return 收货数量
*/
int receiveOrderBySystem();
/**
* 【会员】取消交易订单
*
* @param userId 用户编号
* @param id 订单编号
*/
void cancelOrder(Long userId, Long id);
void cancelOrderByMember(Long userId, Long id);
/**
* 【系统】自动取消订单
*
* @return 取消数量
*/
int cancelOrderBySystem();
/**
* 【会员】删除订单
@@ -102,35 +118,42 @@ public interface TradeOrderUpdateService {
// =================== Order Item ===================
/**
* 更新交易订单项的售后状态
* 当售后申请后,更新交易订单项的售后状态
*
* @param id 交易订单项编号
* @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
* @param newAfterSaleStatus 目标售后状态
* @param id 交易订单项编号
* @param afterSaleId 售后单编号
*/
default void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus) {
updateOrderItemAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, null, null);
}
void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId);
/**
* 更新交易订单项的售后状态
* 当售后完成后,更新交易订单项的售后状态
*
* @param id 交易订单项编号
* @param oldAfterSaleStatus 当前售后状态;如果不符,更新后会抛出异常
* @param newAfterSaleStatus 目标售后状态
* @param afterSaleId 售后单编号;当订单项发起售后时,必须传递该字段
* @param refundPrice 退款金额;当订单项退款成功时,必须传递该值
* @param id 交易订单项编号
* @param refundPrice 退款金额
*/
void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
Long afterSaleId, Integer refundPrice);
void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice);
/**
* 创建订单项的评论
* 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态
*
* @param id 交易订单项编号
*/
void updateOrderItemWhenAfterSaleCancel(@NotNull Long id);
/**
* 【会员】创建订单项的评论
*
* @param userId 用户编号
* @param createReqVO 创建请求
* @return 得到评价 id
*/
Long createOrderItemComment(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
Long createOrderItemCommentByMember(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO);
/**
* 【系统】创建订单项的评论
*
* @return 被评论的订单数
*/
int createOrderItemCommentBySystem();
}

View File

@@ -2,13 +2,15 @@ package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
@@ -39,6 +41,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
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.dal.mysql.order.TradeOrderItemMapper;
@@ -62,6 +65,7 @@ import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.scheduling.annotation.Async;
@@ -70,13 +74,11 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.minusTime;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
@@ -321,39 +323,40 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY)
public void updateOrderPaid(Long id, Long payOrderId) {
// 校验并获得交易订单(可支付)
// 1. 校验并获得交易订单(可支付)
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
TradeOrderDO order = orderResult.getKey();
PayOrderRespDTO payOrder = orderResult.getValue();
// 更新 TradeOrderDO 状态为已支付,等待发货
// 2. 更新 TradeOrderDO 状态为已支付,等待发货
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()).setPayStatus(true)
.setPayTime(LocalDateTime.now()).setPayChannelCode(payOrder.getChannelCode()));
if (updateCount == 0) {
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 订单支付成功后
// 3、订单支付成功后
tradeOrderHandlers.forEach(tradeOrderHandler -> tradeOrderHandler.afterPayOrder(new TradeAfterPayOrderReqBO()
.setOrderId(order.getId()).setOrderType(order.getType()).setUserId(order.getUserId()).setPayTime(LocalDateTime.now())));
// TODO 芋艿:发送订单变化的消息
// TODO 芋艿:发送站内信
// TODO 芋艿OrderLog
// 增加用户积分(赠送)
addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_REWARD, order.getId());
// 增加用户经验
// 4.1 增加用户积分(赠送)
addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId());
// 4.2 增加用户经验
getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
// 增加用户佣金
// 4.3 增加用户佣金
getSelf().addBrokerageAsync(order.getUserId(), order.getId());
// 5. 记录订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
}
/**
* 校验交易订单满足被支付的条件
*
* <p>
* 1. 交易订单未支付
* 2. 支付单已支付
*
@@ -406,6 +409,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_DELIVERY)
public void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO) {
// 1.1 校验并获得交易订单(可发货)
TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId());
@@ -417,8 +421,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 2. 更新订单为已发货
TradeOrderDO updateOrderObj = new TradeOrderDO();
// 2.1 快递发货
DeliveryExpressDO express = null;
if (ObjectUtil.notEqual(deliveryReqVO.getLogisticsId(), TradeOrderDO.LOGISTICS_ID_NULL)) {
deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId());
express = deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId());
updateOrderObj.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo());
} else {
// 2.2 无需发货
@@ -431,18 +436,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED);
}
// TODO 芋艿:发送订单变化的消息
// 3. 记录订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus(),
MapUtil.<String, Object>builder().put("expressName", express != null ? express.getName() : "")
.put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build());
// 发送站内信
// 4. 发送站内信
tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO().setOrderId(order.getId())
.setUserId(order.getUserId()).setMessage(null));
// TODO 芋艿OrderLog
}
/**
* 校验交易订单满足被发货的条件
*
* <p>
* 1. 交易订单未发货
*
* @param id 交易订单编号
@@ -464,6 +470,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 订单类类型:砍价
if (Objects.equals(TradeOrderTypeEnum.BARGAIN.getType(), order.getType())) {
// 校验订单砍价是否成功
// TODO @puhui999砍价的话应该不用校验。因为是砍价成功后才可以下单
if (!bargainRecordApi.isBargainRecordSuccess(order.getUserId(), order.getId())) {
throw exception(ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS);
}
@@ -484,10 +491,54 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_RECEIVE)
public void receiveOrder(Long userId, Long id) {
public void receiveOrderByMember(Long userId, Long id) {
// 校验并获得交易订单(可收货)
TradeOrderDO order = validateOrderReceivable(userId, id);
// 收货订单
receiveOrder0(order);
}
@Override
public int receiveOrderBySystem() {
// 1. 查询过期的待支付订单
LocalDateTime expireTime = minusTime(tradeOrderProperties.getReceiveExpireTime());
List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndDeliveryTimeLt(
TradeOrderStatusEnum.DELIVERED.getStatus(), expireTime);
if (CollUtil.isEmpty(orders)) {
return 0;
}
// 2. 遍历执行,逐个取消
int count = 0;
for (TradeOrderDO order : orders) {
try {
getSelf().receiveOrderBySystem(order);
count++;
} catch (Throwable e) {
log.error("[receiveOrderBySystem][order({}) 自动收货订单异常]", order.getId(), e);
}
}
return count;
}
/**
* 自动收货单个订单
*
* @param order 订单
*/
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_RECEIVE)
public void receiveOrderBySystem(TradeOrderDO order) {
receiveOrder0(order);
}
/**
* 收货订单的核心实现
*
* @param order 订单
*/
private void receiveOrder0(TradeOrderDO order) {
// 更新 TradeOrderDO 状态为已完成
int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(LocalDateTime.now()));
@@ -495,114 +546,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED);
}
// TODO 芋艿lili 发送订单变化的消息
// TODO 芋艿lili 发送商品被购买完成的数据
// TODO 芋艿:销售佣金的记录;
// 插入订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
}
@Override
public void updateOrderRemark(TradeOrderRemarkReqVO reqVO) {
// 校验并获得交易订单
validateOrderExists(reqVO.getId());
// 更新
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(reqVO);
tradeOrderMapper.updateById(order);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) {
// 1、校验交易订单
TradeOrderDO order = validateOrderExists(reqVO.getId());
if (order.getPayStatus()) {
throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
}
// 2、校验订单项
List<TradeOrderItemDO> items = tradeOrderItemMapper.selectListByOrderId(order.getId());
if (CollUtil.isEmpty(items)) {
throw exception(ORDER_UPDATE_PRICE_FAIL_NOT_ITEM);
}
// 3、校验调价金额是否变化
if (ObjectUtil.equal(order.getAdjustPrice(), reqVO.getAdjustPrice())) {
throw exception(ORDER_UPDATE_PRICE_FAIL_EQUAL);
}
// 4、更新订单
TradeOrderDO update = new TradeOrderDO();
update.setId(order.getId());
update.setAdjustPrice(reqVO.getAdjustPrice());
int orderPayPrice = order.getAdjustPrice() != null ? (order.getPayPrice() - order.getAdjustPrice())
+ reqVO.getAdjustPrice() : order.getPayPrice() + reqVO.getAdjustPrice();
update.setPayPrice(orderPayPrice);
tradeOrderMapper.updateById(update);
// TODO @芋艿:改价时,赠送的积分,要不要做改动???
// 5、更新 TradeOrderItem
// 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());
}
/**
* 计算订单调价价格分摊
*
* @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
public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
// 校验交易订单
validateOrderExists(reqVO.getId());
// TODO 是否需要校验订单是否发货
// TODO 发货后是否支持修改收货地址
// 更新
TradeOrderDO update = TradeOrderConvert.INSTANCE.convert(reqVO);
tradeOrderMapper.updateById(update);
}
/**
* 校验交易订单满足可售货的条件
*
* <p>
* 1. 交易订单待收货
*
* @param userId 用户编号
@@ -622,103 +572,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return order;
}
// =================== Order Item ===================
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
Long afterSaleId, Integer refundPrice) {
// 如果退款成功,则 refundPrice 非空
if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())
&& refundPrice == null) {
throw new IllegalArgumentException(StrUtil.format("id({}) 退款成功,退款金额不能为空", id));
}
// 如果退款发起,则 afterSaleId 非空
if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus())
&& afterSaleId == null) {
throw new IllegalArgumentException(StrUtil.format("id({}) 退款发起,售后单编号不能为空", id));
}
// 更新订单项
int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
if (updateCount <= 0) {
throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
}
// 如果有退款金额,则需要更新订单
if (refundPrice == null) {
return;
}
// 计算总的退款金额
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
if (isAllOrderItemAfterSaleSuccess(order.getId())) { // 如果都售后成功,则需要取消订单
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setRefundStatus(TradeOrderRefundStatusEnum.ALL.getStatus()).setRefundPrice(orderRefundPrice).setRefundPoint(order.getRefundPoint() + orderItem.getUsePoint())
.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
// TODO 芋艿:记录订单日志
// TODO 芋艿:站内信?
} else { // 如果部分售后,则更新退款金额
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
}
// 售后成功后,执行数据回滚逻辑
if (Objects.equals(newAfterSaleStatus, TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())) {
// 扣减用户积分(赠送的)
reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, afterSaleId);
// 增加用户积分(返还抵扣)
addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, afterSaleId);
// 扣减用户经验
getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
// 更新分佣记录为已失效
getSelf().cancelBrokerageAsync(order.getUserId(), id);
}
}
@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);
if (orderItem == null) {
throw exception(ORDER_ITEM_NOT_FOUND);
}
// 校验订单相关状态
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderItem.getOrderId(), userId);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
}
if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) {
throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
}
// 1. 创建评价
ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItem);
Long comment = productCommentApi.createComment(productCommentCreateReqDTO);
// 2. 更新订单项评价状态
tradeOrderItemMapper.updateById(new TradeOrderItemDO().setId(orderItem.getId()).setCommentStatus(Boolean.TRUE));
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));
// 增加订单日志
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) {
public void cancelOrderByMember(Long userId, Long id) {
// 1.1 校验存在
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId);
if (order == null) {
@@ -729,34 +586,102 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
}
// 2. 更新 TradeOrderDO 状态为已取消
// 2. 取消订单
cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
}
@Override
public int cancelOrderBySystem() {
// 1. 查询过期的待支付订单
LocalDateTime expireTime = minusTime(tradeOrderProperties.getPayExpireTime());
List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndCreateTimeLt(
TradeOrderStatusEnum.UNPAID.getStatus(), expireTime);
if (CollUtil.isEmpty(orders)) {
return 0;
}
// 2. 遍历执行,逐个取消
int count = 0;
for (TradeOrderDO order : orders) {
try {
getSelf().cancelOrderBySystem(order);
count++;
} catch (Throwable e) {
log.error("[cancelOrderBySystem][order({}) 过期订单异常]", order.getId(), e);
}
}
return count;
}
/**
* 自动取消单个订单
*
* @param order 订单
*/
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
public void cancelOrderBySystem(TradeOrderDO order) {
cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
}
/**
* 取消订单的核心实现
*
* @param order 订单
* @param cancelType 取消类型
*/
private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
Long id = order.getId();
// 1. 更新 TradeOrderDO 状态为已取消
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
.setCancelTime(LocalDateTime.now())
.setCancelType(TradeOrderCancelTypeEnum.MEMBER_CANCEL.getType()));
.setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now()));
if (updateCount == 0) {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
}
// 2. TODO 活动相关库存回滚需要活动 id活动 id 怎么获取app 端能否传过来;回复:从订单里拿呀
tradeOrderHandlers.forEach(handler -> handler.rollback());
// 3. TODO 活动相关库存回滚需要活动 id活动 id 怎么获取app 端能否传过来;回复:从订单里拿呀
tradeOrderHandlers.forEach(handler -> handler.cancelOrder());
// 4. 回滚库存
// 3. 回滚库存
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
// 5. 回滚优惠券
if (order.getCouponId() > 0) {
// 4. 回滚优惠券
if (order.getCouponId() != null && order.getCouponId() > 0) {
couponApi.returnUsedCoupon(order.getCouponId());
}
// 6. 回滚积分(抵扣的)
// 5. 回滚积分(抵扣的)
addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
// 7. 增加订单日志
// 6. 增加订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
}
/**
* 如果金额全部被退款,则取消订单
* 如果还有未被退款的金额,则无需取消订单
*
* @param order 订单
* @param refundPrice 退款金额
*/
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_CANCEL_AFTER_SALE)
public void cancelOrderByAfterSale(TradeOrderDO order, Integer refundPrice) {
// 1. 更新订单
if (refundPrice < order.getPayPrice()) {
return;
}
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
.setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
// 2. 退还优惠券
couponApi.returnUsedCoupon(order.getCouponId());
}
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE)
@@ -777,6 +702,135 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
}
@Override
public void updateOrderRemark(TradeOrderRemarkReqVO reqVO) {
// 校验并获得交易订单
validateOrderExists(reqVO.getId());
// 更新
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(reqVO);
tradeOrderMapper.updateById(order);
}
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_UPDATE_PRICE)
public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) {
// 1.1 校验交易订单
TradeOrderDO order = validateOrderExists(reqVO.getId());
if (order.getPayStatus()) {
throw exception(ORDER_UPDATE_PRICE_FAIL_PAID);
}
// 1.2 校验调价金额是否变化
if (order.getAdjustPrice() > 0) {
throw exception(ORDER_UPDATE_PRICE_FAIL_ALREADY);
}
// 1.3 支付价格不能为 0
int newPayPrice = order.getPayPrice() + order.getAdjustPrice();
if (newPayPrice <= 0) {
throw exception(ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR);
}
// 2. 更新订单
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setAdjustPrice(reqVO.getAdjustPrice()).setPayPrice(newPayPrice));
// 3. 更新 TradeOrderItem需要做 adjustPrice 的分摊
List<TradeOrderItemDO> orderOrderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
List<Integer> dividePrices = TradePriceCalculatorHelper.dividePrice2(orderOrderItems, newPayPrice);
List<TradeOrderItemDO> updateItems = new ArrayList<>();
for (int i = 0; i < orderOrderItems.size(); i++) {
TradeOrderItemDO item = orderOrderItems.get(i);
updateItems.add(new TradeOrderItemDO().setId(item.getId()).setAdjustPrice(dividePrices.get(i))
.setPayPrice(item.getPayPrice() + dividePrices.get(i)));
}
tradeOrderItemMapper.updateBatch(updateItems);
// 4. 更新支付订单
payOrderApi.updatePayOrderPrice(order.getPayOrderId(), newPayPrice);
// 5. 记录订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus(),
MapUtil.<String, Object>builder().put("oldPayPrice", MoneyUtils.fenToYuanStr(order.getPayPrice()))
.put("newPayPrice", MoneyUtils.fenToYuanStr(newPayPrice)).build());
}
@Override
public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
// 校验交易订单
validateOrderExists(reqVO.getId());
// TODO @puhui999是否需要校验订单是否发货
// TODO 发货后是否支持修改收货地址
// 更新
tradeOrderMapper.updateById(TradeOrderConvert.INSTANCE.convert(reqVO));
// TODO @puhui999操作日志
}
// =================== Order Item ===================
@Override
public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) {
// 更新订单项
updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
// 1. 更新订单项
updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
// 2.1 更新订单的退款金额、积分
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ?
TradeOrderRefundStatusEnum.ALL.getStatus() // 如果都售后成功,则需要取消订单
: TradeOrderRefundStatusEnum.PART.getStatus();
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId())
.setRefundStatus(refundStatus)
.setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
// 2.2 如果全部退款,则进行取消订单
getSelf().cancelOrderByAfterSale(order, orderRefundPrice);
// TODO @puhui999活动相关的回滚
// 3. 回滚库存
productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
// 4.1 回滚积分:扣减用户积分(赠送的)
reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
// 4.2 回滚积分:增加用户积分(返还抵扣)
addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
// 5. 回滚经验:扣减用户经验
getSelf().reduceUserExperienceAsync(order.getUserId(), refundPrice, orderItem.getAfterSaleId());
// 6. 回滚佣金:更新分佣记录为已失效
getSelf().cancelBrokerageAsync(order.getUserId(), id);
}
@Override
public void updateOrderItemWhenAfterSaleCancel(Long id) {
// 更新订单项
updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
}
private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
Long afterSaleId) {
// 更新订单项
int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
if (updateCount <= 0) {
throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
}
}
/**
* 判断指定订单的所有订单项,是不是都售后成功
*
@@ -789,6 +843,116 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()));
}
@Override
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_COMMENT)
public Long createOrderItemCommentByMember(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO) {
// 1.1 先通过订单项 ID查询订单项是否存在
TradeOrderItemDO orderItem = tradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId);
if (orderItem == null) {
throw exception(ORDER_ITEM_NOT_FOUND);
}
// 1.2 校验订单相关状态
TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderItem.getOrderId(), userId);
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
}
if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) {
throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
}
// 2. 创建评价
Long commentId = createOrderItemComment0(orderItem, createReqVO);
// 3. 如果订单项都评论了,则更新订单评价状态
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)
.setFinishTime(LocalDateTime.now()));
// 增加订单日志。注意:只有在所有订单项都评价后,才会增加
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
}
return commentId;
}
@Override
public int createOrderItemCommentBySystem() {
// 1. 查询过期的待支付订单
LocalDateTime expireTime = minusTime(tradeOrderProperties.getCommentExpireTime());
List<TradeOrderDO> orders = tradeOrderMapper.selectListByStatusAndReceiveTimeLt(
TradeOrderStatusEnum.COMPLETED.getStatus(), expireTime, false);
if (CollUtil.isEmpty(orders)) {
return 0;
}
// 2. 遍历执行,逐个取消
int count = 0;
for (TradeOrderDO order : orders) {
try {
getSelf().createOrderItemCommentBySystemBySystem(order);
count++;
} catch (Throwable e) {
log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e);
}
}
return count;
}
/**
* 创建单个订单的评论
*
* @param order 订单
*/
@Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT)
public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) {
// 1. 查询未评论的订单项
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(order.getId(), Boolean.FALSE);
if (CollUtil.isEmpty(orderItems)) {
return;
}
// 2. 逐个评论
for (TradeOrderItemDO orderItem : orderItems) {
// 2.1 创建评价
AppTradeOrderItemCommentCreateReqVO commentCreateReqVO = new AppTradeOrderItemCommentCreateReqVO()
.setOrderItemId(orderItem.getId()).setAnonymous(false).setContent("")
.setBenefitScores(5).setDescriptionScores(5);
createOrderItemComment0(orderItem, commentCreateReqVO);
// 2.2 更新订单项评价状态
tradeOrderItemMapper.updateById(new TradeOrderItemDO().setId(orderItem.getId()).setCommentStatus(Boolean.TRUE));
}
// 3. 所有订单项都评论了,则更新订单评价状态
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE)
.setFinishTime(LocalDateTime.now()));
// 增加订单日志。注意:只有在所有订单项都评价后,才会增加
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
}
/**
* 创建订单项的评论的核心实现
*
* @param orderItem 订单项
* @param createReqVO 评论内容
* @return 评论编号
*/
private Long createOrderItemComment0(TradeOrderItemDO orderItem, AppTradeOrderItemCommentCreateReqVO createReqVO) {
// 1. 创建评价
ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItem);
Long commentId = productCommentApi.createComment(productCommentCreateReqDTO);
// 2. 更新订单项评价状态
tradeOrderItemMapper.updateById(new TradeOrderItemDO().setId(orderItem.getId()).setCommentStatus(Boolean.TRUE));
return commentId;
}
// =================== 营销相关的操作 ===================
@Async
protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) {
int bizType = MemberExperienceBizTypeEnum.ORDER.getType();
@@ -829,14 +993,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
/**
* 创建分销记录
*
* <p>
* 目前是支付成功后,就会创建分销记录。
*
* <p>
* 业内还有两种做法,可以根据自己的业务调整:
* 1. 确认收货后,才创建分销记录
* 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
* 1. 确认收货后,才创建分销记录
* 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
*
* @param userId 用户编号
* @param userId 用户编号
* @param orderId 订单编号
*/
@Async

View File

@@ -16,6 +16,7 @@ import java.util.Optional;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
// TODO @疯狂:这个可以搞个单测;
/**
* 赠送积分的 {@link TradePriceCalculator} 实现类
*
@@ -25,6 +26,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Order(TradePriceCalculator.ORDER_POINT_GIVE)
@Slf4j
public class TradePointGiveCalculator implements TradePriceCalculator {
@Resource
private MemberPointApi memberPointApi;
@@ -54,9 +56,9 @@ public class TradePointGiveCalculator implements TradePriceCalculator {
TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
// 商品可能赠送了积分,所以这里要加上
orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
TradePriceCalculatorHelper.recountPayPrice(orderItem);
}
// 3.3 更新订单赠送积分
TradePriceCalculatorHelper.recountAllGivePoint(result);
}
}

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
@@ -15,11 +14,13 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.RoundingMode;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
// TODO @疯狂:搞个单测,嘿嘿;
/**
* 使用积分的 {@link TradePriceCalculator} 实现类
*
@@ -29,6 +30,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Order(TradePriceCalculator.ORDER_POINT_USE)
@Slf4j
public class TradePointUsePriceCalculator implements TradePriceCalculator {
@Resource
private MemberPointApi memberPointApi;
@Resource
@@ -43,18 +45,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
}
// 1.2 校验积分抵扣是否开启
MemberPointConfigRespDTO config = memberPointApi.getConfig();
if (!checkDeductPointEnable(config)) {
if (!isDeductPointEnable(config)) {
return;
}
// 1.3 校验用户积分余额
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
if (user.getPoint() == null || user.getPoint() < 0) {
if (user.getPoint() == null || user.getPoint() <= 0) {
return;
}
// 2.1 计算积分优惠金额
int pointPrice = calculatePointPrice(config, user.getPoint(), result);
// 2.1 计算分摊的积分、抵扣金额
// 2.2 计算分摊的积分、抵扣金额
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSelected);
List<Integer> dividePointPrices = TradePriceCalculatorHelper.dividePrice(orderItems, pointPrice);
List<Integer> divideUsePoints = TradePriceCalculatorHelper.dividePrice(orderItems, result.getUsePoint());
@@ -74,16 +76,10 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllPrice(result);
}
private boolean checkDeductPointEnable(MemberPointConfigRespDTO config) {
if (config == null) {
return false;
}
if (!BooleanUtil.isTrue(config.getTradeDeductEnable())) {
return false;
}
// 有没有配置1 积分抵扣多少分
return config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0;
private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
return config != null &&
!BooleanUtil.isTrue(config.getTradeDeductEnable()) && // 积分功能是否启用
config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置1 积分抵扣多少分
}
private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
@@ -91,19 +87,24 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
if (config.getTradeDeductMaxPrice() != null && config.getTradeDeductMaxPrice() > 0) {
usePoint = Math.min(usePoint, config.getTradeDeductMaxPrice());
}
// TODO @疯狂:这里应该是,抵扣到只剩下 0.01
// 积分优惠金额(分)
int pointPrice = usePoint * config.getTradeDeductUnitPrice();
// 0元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额, 这时需要根据支付金额反推使用多少积分
if (result.getPrice().getPayPrice() < pointPrice) {
pointPrice = result.getPrice().getPayPrice();
// 反推需要扣除的积分
usePoint = NumberUtil.toBigDecimal(pointPrice)
.divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
.intValue();
if (result.getPrice().getPayPrice() <= pointPrice) {
// 禁止 0 元购
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
}
// // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
// if (result.getPrice().getPayPrice() < pointPrice) {
// pointPrice = result.getPrice().getPayPrice();
// // 反推需要扣除的积分
// usePoint = NumberUtil.toBigDecimal(pointPrice)
// .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
// .intValue();
// }
// 记录使用的积分
result.setUsePoint(usePoint);
return pointPrice;
}
}

View File

@@ -21,11 +21,13 @@ public interface TradePriceCalculator {
/**
* 快递运费的计算
*
* 放在各种营销活动、优惠劵后面 TODO
* 放在各种营销活动、优惠劵后面
*/
int ORDER_DELIVERY = 50;
/**
* 赠送积分,放最后
*
* 放在 {@link #ORDER_DELIVERY} 后面的原因,是运费也会产生费用,需要赠送对应积分
*/
int ORDER_POINT_GIVE = 999;

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
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;
@@ -202,6 +203,8 @@ public class TradePriceCalculatorHelper {
/**
* 按照支付金额,返回每个订单项的分摊金额数组
*
* 实际上 price 不仅仅可以传递的是金额,也可以是积分。因为它的实现逻辑,就是根据 payPrice 做分摊而已
*
* @param orderItems 订单项数组
* @param price 金额
* @return 分摊金额数组,和传入的 orderItems 一一对应
@@ -233,6 +236,36 @@ public class TradePriceCalculatorHelper {
return prices;
}
/**
* 计算订单调价价格分摊
*
* 和 {@link #dividePrice(List, Integer)} 逻辑一致,只是传入的是 TradeOrderItemDO 对象
*
* @param items 订单项
* @param price 订单支付金额
* @return 分摊金额数组,和传入的 orderItems 一一对应
*/
public static List<Integer> dividePrice2(List<TradeOrderItemDO> items, Integer price) {
Integer total = getSumValue(items, TradeOrderItemDO::getPrice, Integer::sum);
assert total != null;
// 遍历每一个,进行分摊
List<Integer> prices = new ArrayList<>(items.size());
int remainPrice = price;
for (int i = 0; i < items.size(); i++) {
TradeOrderItemDO orderItem = items.get(i);
int partPrice;
if (i < items.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
partPrice = (int) (price * (1.0D * orderItem.getPayPrice() / total));
remainPrice -= partPrice;
} else {
partPrice = remainPrice;
}
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
prices.add(partPrice);
}
return prices;
}
/**
* 添加【匹配】单个 OrderItem 的营销明细
*

View File

@@ -4,35 +4,38 @@
<select id="selectSummaryPageByUserId"
resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
SELECT bu.id, bu.bind_user_time AS brokerageTime, u.nickname, u.avatar,
SELECT bu.id, bu.bind_user_time AS brokerageTime,
(SELECT SUM(price) FROM trade_brokerage_record r
WHERE r.user_id = u.id AND biz_type = 1 AND r.status = 1 AND r.deleted = FALSE) AS brokeragePrice,
WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokeragePrice,
(SELECT COUNT(1) FROM trade_brokerage_record r
WHERE r.user_id = u.id AND biz_type = 1 AND r.status = 1 AND r.deleted = FALSE) AS brokerageOrderCount,
WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokerageOrderCount,
(SELECT COUNT(1) FROM trade_brokerage_user c
WHERE c.bind_user_id = u.id AND c.deleted = FALSE) AS brokerageUserCount
FROM member_user AS u
JOIN trade_brokerage_user AS bu ON bu.id = u.id
WHERE c.bind_user_id = bu.id AND c.deleted = FALSE) AS brokerageUserCount
FROM trade_brokerage_user AS bu
<where>
<if test="param.nickname != null and param.nickname != ''">
AND u.nickname LIKE concat('', #{param.nickname}, '')
bu.deleted = false
<if test="ids != null and ids.size() > 0">
and bu.id in
<foreach collection="ids" open="(" item="id" separator="," close=")">
#{id}
</foreach>
</if>
<if test="param.level == 1">
AND bu.bind_user_id = #{userId}
</if>
<if test="param.level == 2">
AND bu.bind_user_id IN (SELECT id FROM trade_brokerage_user c WHERE c.bind_user_id = #{userId})
<if test="bindUserIds != null and bindUserIds.size() > 0">
and bu.bind_user_id in
<foreach collection="bindUserIds" open="(" item="bindUserId" separator="," close=")">
#{bindUserId}
</foreach>
</if>
</where>
<choose>
<when test="param.sortingField.field == 'userCount'">
ORDER BY brokerageUserCount ${param.sortingField.order}
<when test="sortingField.field == 'userCount'">
ORDER BY brokerageUserCount ${sortingField.order}
</when>
<when test="param.sortingField.field == 'orderCount'">
ORDER BY brokerageOrderCount ${param.sortingField.order}
<when test="sortingField.field == 'orderCount'">
ORDER BY brokerageOrderCount ${sortingField.order}
</when>
<when test="param.sortingField.field == 'price'">
ORDER BY brokeragePrice ${param.sortingField.order}
<when test="sortingField.field == 'price'">
ORDER BY brokeragePrice ${sortingField.order}
</when>
<otherwise>
ORDER BY bu.bind_user_time DESC

View File

@@ -38,7 +38,7 @@ public class KdNiaoExpressClientIntegrationTest {
public void testGetExpressTrackList() {
ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO();
reqDTO.setExpressCode("STO");
reqDTO.setLogisticsNo("663220402764314");
reqDTO.setLogisticsNo("777168349863987");
List<ExpressTrackRespDTO> tracks = client.getExpressTrackList(reqDTO);
System.out.println(JsonUtils.toJsonPrettyString(tracks));
}

View File

@@ -87,7 +87,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
@BeforeEach
public void setUp() {
when(tradeOrderProperties.getAppId()).thenReturn(888L);
when(tradeOrderProperties.getExpireTime()).thenReturn(Duration.ofDays(1));
when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1));
}
@Test
@@ -306,7 +306,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
// mock 方法(支付单)
// 调用
tradeOrderUpdateService.receiveOrder(userId, id);
tradeOrderUpdateService.receiveOrderByMember(userId, id);
// 断言
TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L);
assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());