重构收益与优惠券逻辑:统一使用 orderAmount,新增优惠券状态校验与过滤
- IPlayOrderInfoService/PlayOrderInfoServiceImpl/ClerkRevenueCalculator 将参数 finalAmount 更名为 orderAmount,避免语义混淆 - 预计收益计算兼容 null 与 0,防止 NPE 并明确边界 - 结算/回填:EarningsServiceImpl、EarningsBackfillServiceImpl 改为使用 orderMoney 兜底;0 金额允许,负数跳过 - 新增枚举:CouponOnlineState、CouponValidityPeriodType 用于券上下架与有效期判定 - IPlayCouponInfoService/Impl 增加 getCouponDetailRestrictionReason,支持已使用/过期/下架等状态校验 - WxCouponController 列表与下单查询增加状态/有效期/库存/白名单过滤逻辑 - OrderLifecycleServiceImpl 下单时校验优惠券状态,预计收益入参从 finalAmount 调整为 orderMoney - 完善单元测试:订单生命周期、优惠券过滤、收益生成与回填等覆盖
This commit is contained in:
@@ -52,13 +52,13 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
* @param croupIds 优惠券ID列表
|
* @param croupIds 优惠券ID列表
|
||||||
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
||||||
* @param firstOrder 是否是首单【0:不是,1:是】
|
* @param firstOrder 是否是首单【0:不是,1:是】
|
||||||
* @param finalAmount 订单支付金额
|
* @param orderAmount 订单金额
|
||||||
* @return com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo
|
* @return com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/7/18 16:39
|
* @since 2024/7/18 16:39
|
||||||
**/
|
**/
|
||||||
ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
||||||
String firstOrder, BigDecimal finalAmount);
|
String firstOrder, BigDecimal orderAmount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据店员等级和订单金额,获取店员预计收入
|
* 根据店员等级和订单金额,获取店员预计收入
|
||||||
@@ -66,12 +66,12 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
* @param clerkId 店员ID
|
* @param clerkId 店员ID
|
||||||
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
||||||
* @param firstOrder 是否是首单【0:不是,1:是】
|
* @param firstOrder 是否是首单【0:不是,1:是】
|
||||||
* @param finalAmount 订单支付金额
|
* @param orderAmount 订单金额
|
||||||
* @return math.BigDecimal 店员预计收入
|
* @return math.BigDecimal 店员预计收入
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/3 11:12
|
* @since 2024/6/3 11:12
|
||||||
**/
|
**/
|
||||||
BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal finalAmount);
|
BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal orderAmount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据店员等级,获取店员提成比例
|
* 根据店员等级,获取店员提成比例
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
throw new CustomException("优惠券不存在");
|
throw new CustomException("优惠券不存在");
|
||||||
}
|
}
|
||||||
PlayCouponInfoEntity couponInfo = playCouponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
|
PlayCouponInfoEntity couponInfo = playCouponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
|
||||||
|
playCouponInfoService.getCouponDetailRestrictionReason(
|
||||||
|
couponInfo,
|
||||||
|
couponDetails.getUseState(),
|
||||||
|
couponDetails.getObtainingTime())
|
||||||
|
.ifPresent(reason -> {
|
||||||
|
throw new CustomException("优惠券不可用 - " + reason);
|
||||||
|
});
|
||||||
String reason = playCouponInfoService.getCouponReasonForUnavailableUse(
|
String reason = playCouponInfoService.getCouponReasonForUnavailableUse(
|
||||||
couponInfo,
|
couponInfo,
|
||||||
pricingInput.getPlaceType().getCode(),
|
pricingInput.getPlaceType().getCode(),
|
||||||
@@ -617,7 +624,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
context.getPaymentInfo().getCouponIds(),
|
context.getPaymentInfo().getCouponIds(),
|
||||||
context.getPlaceType().getCode(),
|
context.getPlaceType().getCode(),
|
||||||
entity.getFirstOrder(),
|
entity.getFirstOrder(),
|
||||||
context.getPaymentInfo().getFinalAmount());
|
context.getPaymentInfo().getOrderMoney());
|
||||||
entity.setEstimatedRevenue(estimatedRevenue.getRevenueAmount());
|
entity.setEstimatedRevenue(estimatedRevenue.getRevenueAmount());
|
||||||
entity.setEstimatedRevenueRatio(estimatedRevenue.getRevenueRatio());
|
entity.setEstimatedRevenueRatio(estimatedRevenue.getRevenueRatio());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
||||||
String firstOrder, BigDecimal finalAmount) {
|
String firstOrder, BigDecimal orderAmount) {
|
||||||
return clerkRevenueCalculator.calculateEstimatedRevenue(clerkId, croupIds, placeType, firstOrder, finalAmount);
|
return clerkRevenueCalculator.calculateEstimatedRevenue(clerkId, croupIds, placeType, firstOrder, orderAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,35 +126,36 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
* @param clerkId 店员ID
|
* @param clerkId 店员ID
|
||||||
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
* @param placeType 下单类型(-1:其他类型;0:指定单;1:随机单;2:打赏单)
|
||||||
* @param firstOrder 是否是首单【0:不是,1:是】
|
* @param firstOrder 是否是首单【0:不是,1:是】
|
||||||
* @param finalAmount 订单支付金额
|
* @param orderAmount 订单金额
|
||||||
* @return math.BigDecimal 店员预计收入
|
* @return math.BigDecimal 店员预计收入
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/3 11:12
|
* @since 2024/6/3 11:12
|
||||||
**/
|
**/
|
||||||
@Override
|
@Override
|
||||||
public BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal finalAmount) {
|
public BigDecimal getEstimatedRevenue(String clerkId, String placeType, String firstOrder, BigDecimal orderAmount) {
|
||||||
PlayClerkLevelInfoEntity entity = playClerkUserInfoService.queryLevelCommission(clerkId);
|
PlayClerkLevelInfoEntity entity = playClerkUserInfoService.queryLevelCommission(clerkId);
|
||||||
boolean isFirst = OrderConstant.YesNoFlag.YES.getCode().equals(firstOrder);
|
boolean isFirst = OrderConstant.YesNoFlag.YES.getCode().equals(firstOrder);
|
||||||
|
BigDecimal safeOrderAmount = orderAmount == null ? BigDecimal.ZERO : orderAmount;
|
||||||
try {
|
try {
|
||||||
OrderConstant.PlaceType place = OrderConstant.PlaceType.fromCode(placeType);
|
OrderConstant.PlaceType place = OrderConstant.PlaceType.fromCode(placeType);
|
||||||
switch (place) {
|
switch (place) {
|
||||||
case SPECIFIED:
|
case SPECIFIED:
|
||||||
return calculateRevenue(finalAmount,
|
return calculateRevenue(safeOrderAmount,
|
||||||
isFirst ? entity.getFirstRegularRatio() : entity.getNotFirstRegularRatio());
|
isFirst ? entity.getFirstRegularRatio() : entity.getNotFirstRegularRatio());
|
||||||
case RANDOM:
|
case RANDOM:
|
||||||
return calculateRevenue(finalAmount,
|
return calculateRevenue(safeOrderAmount,
|
||||||
isFirst ? entity.getFirstRandomRadio() : entity.getNotFirstRandomRadio());
|
isFirst ? entity.getFirstRandomRadio() : entity.getNotFirstRandomRadio());
|
||||||
case REWARD:
|
case REWARD:
|
||||||
return calculateRevenue(finalAmount,
|
return calculateRevenue(safeOrderAmount,
|
||||||
isFirst ? entity.getFirstRewardRatio() : entity.getNotFirstRewardRatio());
|
isFirst ? entity.getFirstRewardRatio() : entity.getNotFirstRewardRatio());
|
||||||
case OTHER:
|
case OTHER:
|
||||||
default:
|
default:
|
||||||
log.error("下单类型异常,placeType={}", placeType);
|
log.error("下单类型异常,placeType={}", placeType);
|
||||||
return finalAmount;
|
return safeOrderAmount;
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
log.error("下单类型错误,placeType={}", placeType, ex);
|
log.error("下单类型错误,placeType={}", placeType, ex);
|
||||||
return finalAmount;
|
return safeOrderAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +446,9 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
returnVo.setRefundById(orderRefundInfoEntity.getRefundById());
|
returnVo.setRefundById(orderRefundInfoEntity.getRefundById());
|
||||||
returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason());
|
returnVo.setRefundReason(orderRefundInfoEntity.getRefundReason());
|
||||||
}
|
}
|
||||||
returnVo.setEstimatedRevenue(returnVo.getFinalAmount());
|
if (returnVo.getEstimatedRevenue() == null) {
|
||||||
|
returnVo.setEstimatedRevenue(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
return returnVo;
|
return returnVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +588,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
orderInfo.getCouponIds(),
|
orderInfo.getCouponIds(),
|
||||||
orderInfo.getPlaceType(),
|
orderInfo.getPlaceType(),
|
||||||
firstOrderFlag,
|
firstOrderFlag,
|
||||||
orderInfo.getFinalAmount());
|
orderInfo.getOrderMoney());
|
||||||
BigDecimal revenueAmount = estimatedRevenueVo.getRevenueAmount();
|
BigDecimal revenueAmount = estimatedRevenueVo.getRevenueAmount();
|
||||||
entity.setEstimatedRevenue(revenueAmount);
|
entity.setEstimatedRevenue(revenueAmount);
|
||||||
entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
|
entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ public class ClerkRevenueCalculator {
|
|||||||
List<String> couponIds,
|
List<String> couponIds,
|
||||||
String placeType,
|
String placeType,
|
||||||
String firstOrder,
|
String firstOrder,
|
||||||
BigDecimal finalAmount) {
|
BigDecimal orderAmount) {
|
||||||
PlayClerkLevelInfoEntity levelInfo = playClerkUserInfoService.queryLevelCommission(clerkId);
|
PlayClerkLevelInfoEntity levelInfo = playClerkUserInfoService.queryLevelCommission(clerkId);
|
||||||
|
BigDecimal baseAmount = orderAmount == null ? BigDecimal.ZERO : orderAmount;
|
||||||
ClerkEstimatedRevenueVo estimatedRevenueVo = new ClerkEstimatedRevenueVo();
|
ClerkEstimatedRevenueVo estimatedRevenueVo = new ClerkEstimatedRevenueVo();
|
||||||
|
|
||||||
boolean fallbackToOther = false;
|
boolean fallbackToOther = false;
|
||||||
@@ -48,20 +49,20 @@ public class ClerkRevenueCalculator {
|
|||||||
|
|
||||||
switch (placeTypeEnum) {
|
switch (placeTypeEnum) {
|
||||||
case SPECIFIED: // 指定单
|
case SPECIFIED: // 指定单
|
||||||
fillRegularOrderRevenue(firstOrder, finalAmount, levelInfo, estimatedRevenueVo);
|
fillRegularOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case RANDOM: // 随机单
|
case RANDOM: // 随机单
|
||||||
fillRandomOrderRevenue(firstOrder, finalAmount, levelInfo, estimatedRevenueVo);
|
fillRandomOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case REWARD: // 打赏单
|
case REWARD: // 打赏单
|
||||||
fillRewardOrderRevenue(firstOrder, finalAmount, levelInfo, estimatedRevenueVo);
|
fillRewardOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case OTHER:
|
case OTHER:
|
||||||
default:
|
default:
|
||||||
if (!fallbackToOther) {
|
if (!fallbackToOther) {
|
||||||
log.warn("按其他下单类型计算预计收益,placeType={},clerkId={}", placeType, clerkId);
|
log.warn("按其他下单类型计算预计收益,placeType={},clerkId={}", placeType, clerkId);
|
||||||
}
|
}
|
||||||
estimatedRevenueVo.setRevenueAmount(finalAmount);
|
estimatedRevenueVo.setRevenueAmount(baseAmount);
|
||||||
estimatedRevenueVo.setRevenueRatio(100);
|
estimatedRevenueVo.setRevenueRatio(100);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -70,36 +71,36 @@ public class ClerkRevenueCalculator {
|
|||||||
return estimatedRevenueVo;
|
return estimatedRevenueVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRegularOrderRevenue(String firstOrder, BigDecimal finalAmount,
|
private void fillRegularOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRegularRatio());
|
vo.setRevenueRatio(levelInfo.getFirstRegularRatio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getFirstRegularRatio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRegularRatio()));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRegularRatio());
|
vo.setRevenueRatio(levelInfo.getNotFirstRegularRatio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getNotFirstRegularRatio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRegularRatio()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRandomOrderRevenue(String firstOrder, BigDecimal finalAmount,
|
private void fillRandomOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRandomRadio());
|
vo.setRevenueRatio(levelInfo.getFirstRandomRadio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getFirstRandomRadio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRandomRadio()));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRandomRadio());
|
vo.setRevenueRatio(levelInfo.getNotFirstRandomRadio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getNotFirstRandomRadio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRandomRadio()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRewardOrderRevenue(String firstOrder, BigDecimal finalAmount,
|
private void fillRewardOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRewardRatio());
|
vo.setRevenueRatio(levelInfo.getFirstRewardRatio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getFirstRewardRatio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRewardRatio()));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRewardRatio());
|
vo.setRevenueRatio(levelInfo.getNotFirstRewardRatio());
|
||||||
vo.setRevenueAmount(scaleAmount(finalAmount, levelInfo.getNotFirstRewardRatio()));
|
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRewardRatio()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.starry.admin.modules.shop.module.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券上下架状态
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CouponOnlineState {
|
||||||
|
OFFLINE("0"),
|
||||||
|
ONLINE("1");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
CouponOnlineState(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<CouponOnlineState> fromCode(String code) {
|
||||||
|
return Arrays.stream(values()).filter(item -> item.code.equals(code)).findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.starry.admin.modules.shop.module.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券有效期类型
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum CouponValidityPeriodType {
|
||||||
|
PERMANENT("0"),
|
||||||
|
FIXED_RANGE("1"),
|
||||||
|
RELATIVE_DAYS("2");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
CouponValidityPeriodType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<CouponValidityPeriodType> fromCode(String code) {
|
||||||
|
return Arrays.stream(values()).filter(item -> item.code.equals(code)).findFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
|||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优惠券信息Service接口
|
* 优惠券信息Service接口
|
||||||
@@ -35,6 +37,22 @@ public interface IPlayCouponInfoService extends IService<PlayCouponInfoEntity> {
|
|||||||
String getCouponReasonForUnavailableUse(PlayCouponInfoEntity couponInfo, String placeType, String commodityId,
|
String getCouponReasonForUnavailableUse(PlayCouponInfoEntity couponInfo, String placeType, String commodityId,
|
||||||
Integer commodityQuantity, BigDecimal price);
|
Integer commodityQuantity, BigDecimal price);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取优惠券在状态层面的不可用原因(如已使用、已过期等)。
|
||||||
|
*
|
||||||
|
* @param couponInfo
|
||||||
|
* 优惠券信息
|
||||||
|
* @param useState
|
||||||
|
* 优惠券详情使用状态
|
||||||
|
* @param obtainingTime
|
||||||
|
* 优惠券领取时间
|
||||||
|
* @return 不可用原因
|
||||||
|
*/
|
||||||
|
Optional<String> getCouponDetailRestrictionReason(
|
||||||
|
PlayCouponInfoEntity couponInfo,
|
||||||
|
String useState,
|
||||||
|
LocalDateTime obtainingTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取优惠券不可领取的原因
|
* 获取优惠券不可领取的原因
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -9,15 +9,20 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
|||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
import com.starry.admin.modules.shop.mapper.PlayCouponInfoMapper;
|
import com.starry.admin.modules.shop.mapper.PlayCouponInfoMapper;
|
||||||
|
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
||||||
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
||||||
import com.starry.admin.modules.shop.module.enums.CouponClaimConditionType;
|
import com.starry.admin.modules.shop.module.enums.CouponClaimConditionType;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponOnlineState;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponValidityPeriodType;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoQueryVo;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponInfoReturnVo;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -71,6 +76,60 @@ public class PlayCouponInfoServiceImpl extends ServiceImpl<PlayCouponInfoMapper,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getCouponDetailRestrictionReason(
|
||||||
|
PlayCouponInfoEntity couponInfo,
|
||||||
|
String useState,
|
||||||
|
LocalDateTime obtainingTime) {
|
||||||
|
if (couponInfo == null) {
|
||||||
|
return Optional.of("优惠券不存在");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(useState)) {
|
||||||
|
return Optional.of("优惠券状态异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
CouponUseState state;
|
||||||
|
try {
|
||||||
|
state = CouponUseState.fromCode(useState);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
return Optional.of("优惠券状态异常");
|
||||||
|
}
|
||||||
|
if (state != CouponUseState.UNUSED) {
|
||||||
|
return Optional.of("优惠券已使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<CouponOnlineState> onlineStateOpt = CouponOnlineState.fromCode(couponInfo.getCouponOnLineState());
|
||||||
|
if (onlineStateOpt.isEmpty()) {
|
||||||
|
return Optional.of("优惠券状态异常");
|
||||||
|
}
|
||||||
|
if (onlineStateOpt.get() != CouponOnlineState.ONLINE) {
|
||||||
|
return Optional.of("优惠券已下架");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
CouponValidityPeriodType validityType = CouponValidityPeriodType
|
||||||
|
.fromCode(couponInfo.getValidityPeriodType())
|
||||||
|
.orElse(CouponValidityPeriodType.PERMANENT);
|
||||||
|
if (validityType == CouponValidityPeriodType.FIXED_RANGE) {
|
||||||
|
if (couponInfo.getProductiveTime() != null && now.isBefore(couponInfo.getProductiveTime())) {
|
||||||
|
return Optional.of("优惠券未生效");
|
||||||
|
}
|
||||||
|
if (couponInfo.getExpirationTime() != null && now.isAfter(couponInfo.getExpirationTime())) {
|
||||||
|
return Optional.of("优惠券已过期");
|
||||||
|
}
|
||||||
|
} else if (validityType == CouponValidityPeriodType.RELATIVE_DAYS) {
|
||||||
|
Integer effectiveDay = couponInfo.getEffectiveDay();
|
||||||
|
if (obtainingTime == null || effectiveDay == null || effectiveDay <= 0) {
|
||||||
|
return Optional.of("优惠券有效期未配置");
|
||||||
|
}
|
||||||
|
LocalDateTime expiration = obtainingTime.plusDays(effectiveDay);
|
||||||
|
if (now.isAfter(expiration)) {
|
||||||
|
return Optional.of("优惠券已过期");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取优惠券不可领取的原因
|
* 获取优惠券不可领取的原因
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
|||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
||||||
import com.starry.admin.modules.shop.module.enums.CouponClaimConditionType;
|
import com.starry.admin.modules.shop.module.enums.CouponClaimConditionType;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponOnlineState;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponValidityPeriodType;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
||||||
@@ -26,10 +28,12 @@ import io.swagger.annotations.ApiOperation;
|
|||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
import io.swagger.annotations.ApiResponse;
|
import io.swagger.annotations.ApiResponse;
|
||||||
import io.swagger.annotations.ApiResponses;
|
import io.swagger.annotations.ApiResponses;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -99,11 +103,37 @@ public class WxCouponController {
|
|||||||
List<PlayCouponDetailsReturnVo> obtainedCoupons = couponDetailsService
|
List<PlayCouponDetailsReturnVo> obtainedCoupons = couponDetailsService
|
||||||
.selectByCustomId(currentCustomId);
|
.selectByCustomId(currentCustomId);
|
||||||
List<PlayCouponInfoEntity> couponInfoEntities = couponInfoService.queryAll();
|
List<PlayCouponInfoEntity> couponInfoEntities = couponInfoService.queryAll();
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
List<WxCouponReceiveReturnVo> returnVos = new ArrayList<>(couponInfoEntities.size());
|
List<WxCouponReceiveReturnVo> returnVos = new ArrayList<>(couponInfoEntities.size());
|
||||||
for (PlayCouponInfoEntity couponInfoEntity : couponInfoEntities) {
|
for (PlayCouponInfoEntity couponInfoEntity : couponInfoEntities) {
|
||||||
if ("0".equals(couponInfoEntity.getCouponOnLineState())) {
|
boolean online = CouponOnlineState.fromCode(couponInfoEntity.getCouponOnLineState())
|
||||||
|
.map(state -> state == CouponOnlineState.ONLINE)
|
||||||
|
.orElse(false);
|
||||||
|
if (!online) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (couponInfoEntity.getRemainingQuantity() != null && couponInfoEntity.getRemainingQuantity() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CouponValidityPeriodType validityType = CouponValidityPeriodType
|
||||||
|
.fromCode(couponInfoEntity.getValidityPeriodType())
|
||||||
|
.orElse(CouponValidityPeriodType.PERMANENT);
|
||||||
|
if (validityType == CouponValidityPeriodType.FIXED_RANGE) {
|
||||||
|
if (couponInfoEntity.getProductiveTime() != null
|
||||||
|
&& now.isBefore(couponInfoEntity.getProductiveTime())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (couponInfoEntity.getExpirationTime() != null
|
||||||
|
&& now.isAfter(couponInfoEntity.getExpirationTime())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (validityType == CouponValidityPeriodType.RELATIVE_DAYS) {
|
||||||
|
Integer effectiveDay = couponInfoEntity.getEffectiveDay();
|
||||||
|
if (effectiveDay == null || effectiveDay <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// RELATIVE_DAYS 按领取时间计算,这里仅进行配置有效性校验,实际过期由详情校验处理
|
||||||
|
}
|
||||||
// 领取白名单:非白名单用户不可见
|
// 领取白名单:非白名单用户不可见
|
||||||
if (CouponClaimConditionType.WHITELIST.code().equals(couponInfoEntity.getClaimConditionType())) {
|
if (CouponClaimConditionType.WHITELIST.code().equals(couponInfoEntity.getClaimConditionType())) {
|
||||||
List<String> wl = couponInfoEntity.getCustomWhitelist();
|
List<String> wl = couponInfoEntity.getCustomWhitelist();
|
||||||
@@ -151,6 +181,14 @@ public class WxCouponController {
|
|||||||
try {
|
try {
|
||||||
PlayCouponInfoEntity couponInfo = couponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
|
PlayCouponInfoEntity couponInfo = couponInfoService.selectPlayCouponInfoById(couponDetails.getCouponId());
|
||||||
WxCouponOrderReturnVo wxCouponReturnVo = ConvertUtil.entityToVo(couponDetails, WxCouponOrderReturnVo.class);
|
WxCouponOrderReturnVo wxCouponReturnVo = ConvertUtil.entityToVo(couponDetails, WxCouponOrderReturnVo.class);
|
||||||
|
Optional<String> statusReason = couponInfoService.getCouponDetailRestrictionReason(
|
||||||
|
couponInfo,
|
||||||
|
couponDetails.getUseState(),
|
||||||
|
couponDetails.getObtainingTime());
|
||||||
|
if (statusReason.isPresent()) {
|
||||||
|
// 已使用、过期、下架等状态的优惠券不展示
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String couponReasonForUnavailableUse = couponInfoService.getCouponReasonForUnavailableUse(couponInfo,
|
String couponReasonForUnavailableUse = couponInfoService.getCouponReasonForUnavailableUse(couponInfo,
|
||||||
vo.getPlaceType(), vo.getCommodityId(), vo.getCommodityQuantity(),
|
vo.getPlaceType(), vo.getCommodityId(), vo.getCommodityQuantity(),
|
||||||
commodityInfo.getCommodityPrice());
|
commodityInfo.getCommodityPrice());
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class EarningsBackfillServiceImpl implements IEarningsBackfillService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BigDecimal amount = order.getEstimatedRevenue() != null ? order.getEstimatedRevenue()
|
BigDecimal amount = order.getEstimatedRevenue() != null ? order.getEstimatedRevenue()
|
||||||
: (order.getFinalAmount() != null ? order.getFinalAmount() : BigDecimal.ZERO);
|
: (order.getOrderMoney() != null ? order.getOrderMoney() : BigDecimal.ZERO);
|
||||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
if (amount.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
addWarning(warnings, "订单 " + order.getId() + " 收益金额为0,跳过");
|
addWarning(warnings, "订单 " + order.getId() + " 收益金额为负数,跳过");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (order.getOrderEndTime() == null) {
|
if (order.getOrderEndTime() == null) {
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ public class EarningsServiceImpl extends ServiceImpl<EarningsLineMapper, Earning
|
|||||||
@Override
|
@Override
|
||||||
public void createFromOrder(PlayOrderInfoEntity orderInfo) {
|
public void createFromOrder(PlayOrderInfoEntity orderInfo) {
|
||||||
if (orderInfo == null || orderInfo.getAcceptBy() == null) return;
|
if (orderInfo == null || orderInfo.getAcceptBy() == null) return;
|
||||||
// amount from estimatedRevenue; fallback to finalAmount if null
|
// amount from estimatedRevenue; fallback to orderMoney if null
|
||||||
BigDecimal amount = orderInfo.getEstimatedRevenue() != null ? orderInfo.getEstimatedRevenue()
|
BigDecimal amount = orderInfo.getEstimatedRevenue() != null ? orderInfo.getEstimatedRevenue()
|
||||||
: (orderInfo.getFinalAmount() != null ? orderInfo.getFinalAmount() : BigDecimal.ZERO);
|
: (orderInfo.getOrderMoney() != null ? orderInfo.getOrderMoney() : BigDecimal.ZERO);
|
||||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) return;
|
if (amount.compareTo(BigDecimal.ZERO) < 0) return;
|
||||||
|
|
||||||
int freezeHours = freezePolicyService
|
int freezeHours = freezePolicyService
|
||||||
.resolveFreezeHours(orderInfo.getTenantId(), orderInfo.getAcceptBy());
|
.resolveFreezeHours(orderInfo.getTenantId(), orderInfo.getAcceptBy());
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -229,7 +230,7 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
|
|||||||
request.getPaymentInfo().getCouponIds(),
|
request.getPaymentInfo().getCouponIds(),
|
||||||
request.getPlaceType().getCode(),
|
request.getPlaceType().getCode(),
|
||||||
YesNoFlag.YES.getCode(),
|
YesNoFlag.YES.getCode(),
|
||||||
request.getPaymentInfo().getFinalAmount());
|
request.getPaymentInfo().getOrderMoney());
|
||||||
|
|
||||||
assertEquals(YesNoFlag.YES.getCode(), created.getFirstOrder());
|
assertEquals(YesNoFlag.YES.getCode(), created.getFirstOrder());
|
||||||
assertEquals(revenueVo.getRevenueAmount(), created.getEstimatedRevenue());
|
assertEquals(revenueVo.getRevenueAmount(), created.getEstimatedRevenue());
|
||||||
@@ -904,6 +905,42 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
|
|||||||
assertMoneyEquals("25.00", result.getAmountBreakdown().getNetAmount());
|
assertMoneyEquals("25.00", result.getAmountBreakdown().getNetAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void placeOrder_rejectsCouponWhenRestrictionReasonPresent() {
|
||||||
|
List<String> coupons = Collections.singletonList("coupon-reused");
|
||||||
|
OrderCreationContext context = baseContext(
|
||||||
|
PlaceType.SPECIFIED,
|
||||||
|
RewardType.NOT_APPLICABLE,
|
||||||
|
payment(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, coupons),
|
||||||
|
null,
|
||||||
|
"clerk-1");
|
||||||
|
|
||||||
|
stubDefaultPersistence();
|
||||||
|
|
||||||
|
PlayCouponDetailsEntity detail = new PlayCouponDetailsEntity();
|
||||||
|
detail.setCouponId("coupon-master");
|
||||||
|
detail.setUseState(CouponUseState.USED.getCode());
|
||||||
|
detail.setObtainingTime(LocalDateTime.now().minusDays(1));
|
||||||
|
when(playCouponDetailsService.getById("coupon-reused")).thenReturn(detail);
|
||||||
|
|
||||||
|
PlayCouponInfoEntity info = new PlayCouponInfoEntity();
|
||||||
|
info.setCouponOnLineState("1");
|
||||||
|
info.setValidityPeriodType("0");
|
||||||
|
when(playCouponInfoService.selectPlayCouponInfoById("coupon-master")).thenReturn(info);
|
||||||
|
when(playCouponInfoService.getCouponDetailRestrictionReason(eq(info), eq(detail.getUseState()), any(LocalDateTime.class)))
|
||||||
|
.thenReturn(Optional.of("优惠券已使用"));
|
||||||
|
|
||||||
|
CustomException exception = assertThrows(CustomException.class, () -> lifecycleService.placeOrder(command(
|
||||||
|
context,
|
||||||
|
pricing(BigDecimal.valueOf(30), 1, coupons, "commodity-reused", PlaceType.SPECIFIED),
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null)));
|
||||||
|
|
||||||
|
assertEquals("优惠券不可用 - 优惠券已使用", exception.getMessage());
|
||||||
|
verify(playCouponInfoService).getCouponDetailRestrictionReason(eq(info), eq(detail.getUseState()), any(LocalDateTime.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void placeOrder_withDeductionFetchesCustomerBalance() {
|
void placeOrder_withDeductionFetchesCustomerBalance() {
|
||||||
OrderCreationContext context = baseContext(
|
OrderCreationContext context = baseContext(
|
||||||
@@ -1157,6 +1194,8 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
|
|||||||
lenient().when(orderInfoMapper.insert(any())).thenReturn(1);
|
lenient().when(orderInfoMapper.insert(any())).thenReturn(1);
|
||||||
lenient().doNothing().when(customUserInfoService).saveOrderInfo(any());
|
lenient().doNothing().when(customUserInfoService).saveOrderInfo(any());
|
||||||
lenient().doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), anyString());
|
lenient().doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), anyString());
|
||||||
|
lenient().when(playCouponInfoService.getCouponDetailRestrictionReason(any(), any(), any()))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
ClerkEstimatedRevenueVo revenueVo = new ClerkEstimatedRevenueVo();
|
ClerkEstimatedRevenueVo revenueVo = new ClerkEstimatedRevenueVo();
|
||||||
revenueVo.setRevenueAmount(BigDecimal.ZERO);
|
revenueVo.setRevenueAmount(BigDecimal.ZERO);
|
||||||
revenueVo.setRevenueRatio(0);
|
revenueVo.setRevenueRatio(0);
|
||||||
@@ -1236,6 +1275,8 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
|
|||||||
when(playCouponInfoService.selectPlayCouponInfoById(masterId)).thenReturn(info);
|
when(playCouponInfoService.selectPlayCouponInfoById(masterId)).thenReturn(info);
|
||||||
when(playCouponInfoService.getCouponReasonForUnavailableUse(
|
when(playCouponInfoService.getCouponReasonForUnavailableUse(
|
||||||
eq(info), anyString(), anyString(), anyInt(), any())).thenReturn(reason);
|
eq(info), anyString(), anyString(), anyInt(), any())).thenReturn(reason);
|
||||||
|
when(playCouponInfoService.getCouponDetailRestrictionReason(eq(info), any(), any()))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayCustomUserInfoEntity customer(String id, BigDecimal balance) {
|
private PlayCustomUserInfoEntity customer(String id, BigDecimal balance) {
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
package com.starry.admin.modules.shop.service;
|
package com.starry.admin.modules.shop.service;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
||||||
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponOnlineState;
|
||||||
|
import com.starry.admin.modules.shop.module.enums.CouponValidityPeriodType;
|
||||||
|
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
||||||
|
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
||||||
import com.starry.admin.modules.shop.service.impl.PlayCouponInfoServiceImpl;
|
import com.starry.admin.modules.shop.service.impl.PlayCouponInfoServiceImpl;
|
||||||
import com.starry.admin.modules.weichat.controller.WxCouponController;
|
import com.starry.admin.modules.weichat.controller.WxCouponController;
|
||||||
|
import com.starry.admin.modules.weichat.entity.WxCouponOrderQueryVo;
|
||||||
|
import com.starry.admin.modules.weichat.entity.WxCouponOrderReturnVo;
|
||||||
import com.starry.admin.modules.weichat.entity.WxCouponReceiveReturnVo;
|
import com.starry.admin.modules.weichat.entity.WxCouponReceiveReturnVo;
|
||||||
import com.starry.common.result.R;
|
import com.starry.common.result.R;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -97,4 +108,105 @@ public class CouponWhitelistTest {
|
|||||||
assertEquals(1, list.size(), "非白名单券应被过滤不可见");
|
assertEquals(1, list.size(), "非白名单券应被过滤不可见");
|
||||||
assertEquals("c1", list.get(0).getId());
|
assertEquals("c1", list.get(0).getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("列表过滤-状态:下架或过期优惠券不可见")
|
||||||
|
void testQueryAllFiltersInvalidStatus() {
|
||||||
|
PlayCustomUserInfoEntity current = new PlayCustomUserInfoEntity();
|
||||||
|
current.setId("uid-2");
|
||||||
|
ThreadLocalRequestDetail.setRequestDetail(current);
|
||||||
|
|
||||||
|
PlayCouponInfoEntity offline = new PlayCouponInfoEntity();
|
||||||
|
offline.setId("offline");
|
||||||
|
offline.setCouponOnLineState(CouponOnlineState.OFFLINE.getCode());
|
||||||
|
|
||||||
|
PlayCouponInfoEntity expired = new PlayCouponInfoEntity();
|
||||||
|
expired.setId("expired");
|
||||||
|
expired.setCouponOnLineState(CouponOnlineState.ONLINE.getCode());
|
||||||
|
expired.setValidityPeriodType(CouponValidityPeriodType.FIXED_RANGE.getCode());
|
||||||
|
expired.setProductiveTime(LocalDateTime.now().minusDays(5));
|
||||||
|
expired.setExpirationTime(LocalDateTime.now().minusDays(1));
|
||||||
|
expired.setRemainingQuantity(5);
|
||||||
|
|
||||||
|
PlayCouponInfoEntity active = new PlayCouponInfoEntity();
|
||||||
|
active.setId("active");
|
||||||
|
active.setCouponOnLineState(CouponOnlineState.ONLINE.getCode());
|
||||||
|
active.setValidityPeriodType(CouponValidityPeriodType.PERMANENT.getCode());
|
||||||
|
active.setRemainingQuantity(10);
|
||||||
|
|
||||||
|
when(couponDetailsService.selectByCustomId("uid-2")).thenReturn(Collections.emptyList());
|
||||||
|
when(couponInfoService.queryAll()).thenReturn(Arrays.asList(offline, expired, active));
|
||||||
|
|
||||||
|
R resp = wxCouponController.queryAll();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<WxCouponReceiveReturnVo> list = (List<WxCouponReceiveReturnVo>) resp.getData();
|
||||||
|
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size(), "仅应展示在线有效的优惠券");
|
||||||
|
assertEquals("active", list.get(0).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("下单查询:存在限制原因的优惠券被过滤")
|
||||||
|
void testQueryByOrderFiltersRestrictedCoupons() {
|
||||||
|
PlayCustomUserInfoEntity current = new PlayCustomUserInfoEntity();
|
||||||
|
current.setId("uid-3");
|
||||||
|
ThreadLocalRequestDetail.setRequestDetail(current);
|
||||||
|
|
||||||
|
PlayCouponDetailsReturnVo restricted = new PlayCouponDetailsReturnVo();
|
||||||
|
restricted.setId("detail-1");
|
||||||
|
restricted.setCouponId("coupon-restrict");
|
||||||
|
restricted.setUseState(CouponUseState.USED.getCode());
|
||||||
|
restricted.setObtainingTime(LocalDateTime.now().minusDays(3));
|
||||||
|
|
||||||
|
PlayCouponDetailsReturnVo usable = new PlayCouponDetailsReturnVo();
|
||||||
|
usable.setId("detail-2");
|
||||||
|
usable.setCouponId("coupon-usable");
|
||||||
|
usable.setUseState(CouponUseState.UNUSED.getCode());
|
||||||
|
usable.setObtainingTime(LocalDateTime.now().minusDays(1));
|
||||||
|
|
||||||
|
when(couponDetailsService.selectByCustomId("uid-3"))
|
||||||
|
.thenReturn(Arrays.asList(restricted, usable));
|
||||||
|
|
||||||
|
PlayCouponInfoEntity restrictedInfo = new PlayCouponInfoEntity();
|
||||||
|
restrictedInfo.setId("coupon-restrict");
|
||||||
|
restrictedInfo.setCouponOnLineState(CouponOnlineState.ONLINE.getCode());
|
||||||
|
restrictedInfo.setValidityPeriodType(CouponValidityPeriodType.PERMANENT.getCode());
|
||||||
|
|
||||||
|
PlayCouponInfoEntity usableInfo = new PlayCouponInfoEntity();
|
||||||
|
usableInfo.setId("coupon-usable");
|
||||||
|
usableInfo.setCouponOnLineState(CouponOnlineState.ONLINE.getCode());
|
||||||
|
usableInfo.setValidityPeriodType(CouponValidityPeriodType.PERMANENT.getCode());
|
||||||
|
|
||||||
|
when(couponInfoService.selectPlayCouponInfoById("coupon-restrict")).thenReturn(restrictedInfo);
|
||||||
|
when(couponInfoService.selectPlayCouponInfoById("coupon-usable")).thenReturn(usableInfo);
|
||||||
|
when(couponInfoService.getCouponDetailRestrictionReason(eq(restrictedInfo), eq(restricted.getUseState()), any(LocalDateTime.class)))
|
||||||
|
.thenReturn(Optional.of("优惠券已使用"));
|
||||||
|
when(couponInfoService.getCouponDetailRestrictionReason(eq(usableInfo), eq(usable.getUseState()), any(LocalDateTime.class)))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
when(couponInfoService.getCouponReasonForUnavailableUse(eq(usableInfo), anyString(), anyString(), anyInt(), any()))
|
||||||
|
.thenReturn("");
|
||||||
|
|
||||||
|
PlayCommodityInfoVo commodityInfo = new PlayCommodityInfoVo();
|
||||||
|
commodityInfo.setCommodityId("cmd-1");
|
||||||
|
commodityInfo.setCommodityPrice(BigDecimal.valueOf(50));
|
||||||
|
when(commodityInfoService.queryCommodityInfo("cmd-1", "level-1")).thenReturn(commodityInfo);
|
||||||
|
|
||||||
|
WxCouponOrderQueryVo vo = new WxCouponOrderQueryVo();
|
||||||
|
vo.setCommodityId("cmd-1");
|
||||||
|
vo.setLevelId("level-1");
|
||||||
|
vo.setClerkId("");
|
||||||
|
vo.setPlaceType("0");
|
||||||
|
vo.setCommodityQuantity(1);
|
||||||
|
|
||||||
|
R resp = wxCouponController.queryByOrder(vo);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<WxCouponOrderReturnVo> list = (List<WxCouponOrderReturnVo>) resp.getData();
|
||||||
|
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size(), "受限优惠券应被过滤");
|
||||||
|
assertEquals("detail-2", list.get(0).getId());
|
||||||
|
assertEquals("1", list.get(0).getAvailable());
|
||||||
|
verify(couponInfoService).getCouponDetailRestrictionReason(eq(restrictedInfo), eq(restricted.getUseState()), any(LocalDateTime.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.starry.admin.modules.withdraw.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class EarningsBackfillServiceImplTest {
|
||||||
|
|
||||||
|
private final EarningsBackfillServiceImpl service = new EarningsBackfillServiceImpl();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private BigDecimal invokeResolveAmount(PlayOrderInfoEntity order, List<String> warnings) throws Exception {
|
||||||
|
Method method = EarningsBackfillServiceImpl.class.getDeclaredMethod("resolveAmount", PlayOrderInfoEntity.class,
|
||||||
|
List.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
return (BigDecimal) method.invoke(service, order, warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveAmount_acceptsZeroAmountWithoutWarnings() throws Exception {
|
||||||
|
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||||
|
order.setId("order-zero");
|
||||||
|
order.setAcceptBy("clerk-001");
|
||||||
|
order.setOrderEndTime(LocalDateTime.now());
|
||||||
|
order.setOrderMoney(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
BigDecimal amount = invokeResolveAmount(order, warnings);
|
||||||
|
|
||||||
|
assertEquals(BigDecimal.ZERO, amount);
|
||||||
|
assertTrue(warnings.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveAmount_rejectsNegativeAmount() throws Exception {
|
||||||
|
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||||
|
order.setId("order-negative");
|
||||||
|
order.setAcceptBy("clerk-001");
|
||||||
|
order.setOrderEndTime(LocalDateTime.now());
|
||||||
|
order.setEstimatedRevenue(BigDecimal.valueOf(-5));
|
||||||
|
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
BigDecimal amount = invokeResolveAmount(order, warnings);
|
||||||
|
|
||||||
|
assertNull(amount);
|
||||||
|
assertEquals(1, warnings.size());
|
||||||
|
assertTrue(warnings.get(0).contains("order-negative"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.starry.admin.modules.withdraw.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
|
||||||
|
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class EarningsServiceImplTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private EarningsServiceImpl earningsService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EarningsLineMapper baseMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IFreezePolicyService freezePolicyService;
|
||||||
|
|
||||||
|
private PlayOrderInfoEntity baselineOrder() {
|
||||||
|
PlayOrderInfoEntity order = new PlayOrderInfoEntity();
|
||||||
|
order.setId("order-001");
|
||||||
|
order.setTenantId("tenant-001");
|
||||||
|
order.setAcceptBy("clerk-001");
|
||||||
|
order.setOrderEndTime(LocalDateTime.now());
|
||||||
|
order.setOrderSettlementState("0");
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createFromOrder_usesOrderAmountWhenEstimatedMissing_evenIfZero() {
|
||||||
|
PlayOrderInfoEntity order = baselineOrder();
|
||||||
|
order.setEstimatedRevenue(null);
|
||||||
|
order.setOrderMoney(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
when(freezePolicyService.resolveFreezeHours("tenant-001", "clerk-001")).thenReturn(0);
|
||||||
|
when(baseMapper.insert(any())).thenReturn(1);
|
||||||
|
|
||||||
|
earningsService.createFromOrder(order);
|
||||||
|
|
||||||
|
ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
|
||||||
|
verify(baseMapper).insert(captor.capture());
|
||||||
|
assertEquals(BigDecimal.ZERO, captor.getValue().getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createFromOrder_skipsWhenAmountNegative() {
|
||||||
|
PlayOrderInfoEntity order = baselineOrder();
|
||||||
|
order.setEstimatedRevenue(BigDecimal.valueOf(-10));
|
||||||
|
order.setOrderMoney(BigDecimal.valueOf(20));
|
||||||
|
|
||||||
|
earningsService.createFromOrder(order);
|
||||||
|
|
||||||
|
verify(baseMapper, never()).insert(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user