拼团活动:完善 review 提到的问题

This commit is contained in:
puhui999 2023-10-09 11:53:02 +08:00
parent 7b727d5ce2
commit 53e67c0437
14 changed files with 106 additions and 48 deletions

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
@ -29,9 +30,9 @@ public interface CombinationRecordApi {
* 创建开团记录
*
* @param reqDTO 请求 DTO
* @return 开团记录编号
* @return key 开团记录编号 value 团长编号
*/
Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
KeyValue<Long, Long> createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
/**
* 查询拼团记录是否成功

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@ -29,7 +30,7 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
}
@Override
public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
return recordService.createCombinationRecord(reqDTO);
}

View File

@ -54,10 +54,11 @@ public class CombinationRecordController {
@PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() {
CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO();
summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录
summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null, null)); // 获取拼团用户参与数量
summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录
CombinationRecordStatusEnum.SUCCESS.getStatus(), null));
summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录
CombinationRecordStatusEnum.SUCCESS.getStatus(), null, CombinationRecordDO.HEAD_ID_GROUP));
summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(// 获取虚拟成团记录
null, Boolean.TRUE, CombinationRecordDO.HEAD_ID_GROUP));
return success(summaryVO);
}

View File

@ -46,8 +46,8 @@ public class AppCombinationRecordController {
@Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
// 1. 获得拼团记录数量
Long count = combinationRecordService.getCombinationRecordCount(null, null);
// 1. 获得拼团参与用户数量
Long count = combinationRecordService.getCombinationRecordCount(null, null, null);
if (count == 0) {
summary.setAvatars(Collections.emptyList());
summary.setUserCount(count);

View File

@ -28,7 +28,6 @@ import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@ -114,9 +113,6 @@ public interface CombinationActivityConvert {
ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
return convert(reqDTO).setVirtualGroup(false)
.setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中
.setStartTime(LocalDateTime.now()) // TODO @puhui999想了下这个 startTime 应该是团长的
// TODO @puhui999有团长的情况下expireTime 应该是团长的
.setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration()))
.setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录
// 用户信息
.setNickname(user.getNickname()).setAvatar(user.getAvatar())

View File

@ -100,13 +100,21 @@ public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO
.betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime()));
}
// TODO @puhui999这个最好把 headId 也作为一个参数因为有个要求 userCount它要 DISTINCT 整体可以参考 selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId
default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup) {
return selectCount(new LambdaQueryWrapperX<CombinationRecordDO>()
.eq(status != null || virtualGroup != null,
CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长
.eqIfPresent(CombinationRecordDO::getStatus, status)
.eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup));
/**
* 查询指定条件的记录数
* 如果参数都为 null 时则查询用户拼团记录DISTINCT 去重也就是说查询会员表中的用户有多少人参与过拼团活动每个人只统计一次
*
* @param status 状态可为 null
* @param virtualGroup 是否虚拟成团可为 null
* @param headId 团长编号可为 null
* @return 记录数
*/
default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup, Long headId) {
return selectCount(new QueryWrapper<CombinationRecordDO>()
.select(status == null && virtualGroup == null && headId == null, "COUNT(DISTINCT(user_id))")
.eq(status != null, "status", status)
.eq(virtualGroup != null, "virtual_group", virtualGroup)
.eq(headId != null, "head_id", headId));
}
}

View File

@ -49,9 +49,9 @@ public interface CombinationRecordService {
* 创建拼团记录
*
* @param reqDTO 创建信息
* @return 开团记录编号
* @return key 开团记录编号 value 团长编号
*/
Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
/**
* 获得拼团记录
@ -90,9 +90,10 @@ public interface CombinationRecordService {
*
* @param status 状态-允许为空
* @param virtualGroup 是否虚拟成团-允许为空
* @param headId 团长编号允许空目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 可以设置
* @return 记录数
*/
Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup);
Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId);
/**
* 获取最近的 count 条拼团记录

View File

@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationP
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -28,6 +27,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -62,8 +62,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Resource
@Lazy
private ProductSkuApi productSkuApi;
@Resource
private TradeOrderApi tradeOrderApi;
// TODO @芋艿在详细预览下
@Override
@ -164,32 +162,36 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
// 1. 校验拼团活动
public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
// 1.校验拼团活动
KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
// 2.1 组合数据创建拼团记录
// 2.组合数据创建拼团记录
MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
// 2.2 如果是团长需要设置 headId CombinationRecordDO#HEAD_ID_GROUP
// 2.1.如果是团长需要设置 headId CombinationRecordDO#HEAD_ID_GROUP
if (record.getHeadId() == null) {
record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
record.setStartTime(LocalDateTime.now())
.setExpireTime(keyValue.getKey().getStartTime().plusHours(keyValue.getKey().getLimitDuration()))
.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
} else {
// 2.2.有团长的情况下需要设置开始时间和过期时间为团长的
CombinationRecordDO headRecord = recordMapper.selectByHeadId(record.getHeadId(),
CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); // 查询进行中的父拼团
record.setStartTime(headRecord.getStartTime()).setExpireTime(headRecord.getExpireTime());
}
recordMapper.insert(record);
if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
return record.getId();
return new KeyValue<>(record.getId(), record.getHeadId());
}
// TODO @puhui是不是这里的更新放到 order 模块那支付完成后
// 4更新拼团相关信息到订单
tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId());
// 4更新拼团记录
// 3更新拼团记录
updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
return record.getId();
return new KeyValue<>(record.getId(), record.getHeadId());
}
/**
@ -241,8 +243,8 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
}
@Override
public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) {
return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup);
public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId) {
return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup, headId);
}
@Override

View File

@ -33,7 +33,8 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态");
ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货");
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");

View File

@ -110,4 +110,16 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
}
default TradeOrderDO selectByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
.and(q -> q.eq(TradeOrderDO::getUserId, userId)
.eq(TradeOrderDO::getStatus, status))
.and(q -> q.eq(TradeOrderDO::getCombinationActivityId, activityId)
.or()
.eq(TradeOrderDO::getSeckillActivityId, activityId)
.or()
.eq(TradeOrderDO::getBargainActivityId, activityId))
);
}
}

View File

@ -40,6 +40,16 @@ public interface TradeOrderQueryService {
*/
TradeOrderDO getOrder(Long userId, Long id);
/**
* 获得指定用户指定活动指定状态的交易订单
*
* @param userId 用户编号
* @param activityId 活动编号
* @param status 订单状态
* @return 交易订单
*/
TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status);
/**
* 获得订单列表
*
@ -95,7 +105,7 @@ public interface TradeOrderQueryService {
/**
* 会员在指定秒杀活动下用户购买的商品数量
*
* @param userId 用户编号
* @param userId 用户编号
* @param activityId 活动编号
* @return 秒杀商品数量
*/

View File

@ -72,6 +72,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
return order;
}
@Override
public TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
return tradeOrderMapper.selectByUserIdAndActivityIdAndStatus(userId, activityId, status);
}
@Override
public List<TradeOrderDO> getOrderList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {

View File

@ -742,9 +742,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
// 校验交易订单
TradeOrderDO order = validateOrderExists(reqVO.getId());
// 发货后不允许修改
// TODO @puhui999只有待发货可以执行 update
if (TradeOrderStatusEnum.isDelivered(order.getStatus())) {
// 只有待发货状态才可以修改订单收货地址
if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {
throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
}

View File

@ -1,16 +1,23 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
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.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID;
/**
* 拼团订单 handler 接口实现类
*
@ -19,6 +26,11 @@ import java.util.List;
@Component
public class TradeCombinationHandler implements TradeOrderHandler {
@Resource
private TradeOrderQueryService orderQueryService;
@Resource
private TradeOrderUpdateService orderUpdateService;
@Resource
private CombinationRecordApi combinationRecordApi;
@ -34,21 +46,30 @@ public class TradeCombinationHandler implements TradeOrderHandler {
TradeOrderItemDO item = orderItems.get(0);
combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
order.getCombinationHeadId(), item.getSkuId(), item.getCount());
// TODO @puhui999这里还要限制下是不是已经 createOrder就是还没支付的时候重复下单了需要校验下不然的话一个拼团可以下多个单子了
// 校验该用户是否存在未支付的拼团活动订单就是还没支付的时候重复下单了需要校验下不然的话一个拼团可以下多个单子了
TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus(
order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus());
if (activityOrder != null) {
throw exception(ORDER_CREATE_FAIL_EXIST_UNPAID);
}
}
@Override
public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
// 如果不是拼团订单则结束
// 1.如果不是拼团订单则结束
if (TradeOrderTypeEnum.isCombination(order.getType())) {
return;
}
Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
// 获取商品信息
// 2.获取商品信息
TradeOrderItemDO item = orderItems.get(0);
// 创建拼团记录
combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));
// 2.1.创建拼团记录
KeyValue<Long, Long> recordIdAndHeadId = combinationRecordApi.createCombinationRecord(
TradeOrderConvert.INSTANCE.convert(order, item));
// 3.更新拼团相关信息到订单
orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(),
recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue());
}
@Override