feat: 完成撤销收益扣回與限額改動
This commit is contained in:
@@ -15,6 +15,7 @@ import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
||||
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import com.starry.admin.utils.SecurityUtils;
|
||||
import com.starry.common.annotation.Log;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
@@ -28,6 +29,7 @@ import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -59,6 +61,9 @@ public class PlayOrderInfoController {
|
||||
@Resource
|
||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
private IEarningsService earningsService;
|
||||
|
||||
/**
|
||||
* 分页查询订单列表
|
||||
*/
|
||||
@@ -115,11 +120,8 @@ public class PlayOrderInfoController {
|
||||
context.setRefundToCustomer(vo.isRefundToCustomer());
|
||||
context.setRefundAmount(vo.getRefundAmount());
|
||||
context.setRefundReason(vo.getRefundReason());
|
||||
OrderRevocationContext.EarningsAdjustStrategy strategy = vo.getEarningsStrategy() != null
|
||||
? vo.getEarningsStrategy()
|
||||
: OrderRevocationContext.EarningsAdjustStrategy.NONE;
|
||||
context.setEarningsStrategy(strategy);
|
||||
context.setCounterClerkId(vo.getCounterClerkId());
|
||||
context.setDeductClerkEarnings(vo.isDeductClerkEarnings());
|
||||
context.setEarningsAdjustAmount(vo.getDeductAmount());
|
||||
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||
context.setOperatorId(SecurityUtils.getUserId());
|
||||
context.withTriggerSource(OrderTriggerSource.ADMIN_API);
|
||||
@@ -127,6 +129,29 @@ public class PlayOrderInfoController {
|
||||
return R.ok("撤销成功");
|
||||
}
|
||||
|
||||
@ApiOperation(value = "撤销限额", notes = "查询指定订单可退金额与可扣回收益")
|
||||
@GetMapping("/{id}/revocationLimits")
|
||||
public R getRevocationLimits(@PathVariable("id") String id) {
|
||||
PlayOrderInfoEntity order = orderInfoService.selectOrderInfoById(id);
|
||||
BigDecimal maxRefundAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
|
||||
BigDecimal maxDeductAmount = BigDecimal.ZERO;
|
||||
if (order.getAcceptBy() != null) {
|
||||
maxDeductAmount = Optional.ofNullable(earningsService.getRemainingEarningsForOrder(order.getId(), order.getAcceptBy()))
|
||||
.orElse(BigDecimal.ZERO);
|
||||
}
|
||||
if (maxDeductAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
maxDeductAmount = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
PlayOrderRevocationLimitsVo limitsVo = new PlayOrderRevocationLimitsVo();
|
||||
limitsVo.setOrderId(order.getId());
|
||||
limitsVo.setMaxRefundAmount(maxRefundAmount);
|
||||
limitsVo.setMaxDeductAmount(maxDeductAmount);
|
||||
limitsVo.setDefaultDeductAmount(maxDeductAmount);
|
||||
limitsVo.setDeductible(order.getAcceptBy() != null);
|
||||
return R.ok(limitsVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理后台强制取消进行中订单
|
||||
*/
|
||||
|
||||
@@ -26,30 +26,29 @@ public class OrderRevocationEarningsListener {
|
||||
return;
|
||||
}
|
||||
OrderRevocationContext context = event.getContext();
|
||||
switch (context.getEarningsStrategy()) {
|
||||
case NONE:
|
||||
return;
|
||||
case REVERSE_CLERK:
|
||||
earningsService.reverseByOrder(event.getOrderSnapshot().getId(), context.getOperatorId());
|
||||
return;
|
||||
case COUNTER_TO_PEIPEI:
|
||||
createCounterLine(event);
|
||||
return;
|
||||
default:
|
||||
throw new CustomException("未知的收益处理策略");
|
||||
if (!context.isDeductClerkEarnings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
createCounterLine(event);
|
||||
}
|
||||
|
||||
private void createCounterLine(OrderRevocationEvent event) {
|
||||
OrderRevocationContext context = event.getContext();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
PlayOrderInfoEntity order = event.getOrderSnapshot();
|
||||
String targetClerkId = context.getCounterClerkId();
|
||||
if (targetClerkId == null) {
|
||||
String targetClerkId = order.getAcceptBy();
|
||||
if (targetClerkId == null || targetClerkId.trim().isEmpty()) {
|
||||
throw new CustomException("需要指定收益冲销目标账号");
|
||||
}
|
||||
BigDecimal amount = context.getRefundAmount();
|
||||
BigDecimal amount = context.getEarningsAdjustAmount();
|
||||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
amount = Optional.ofNullable(order.getEstimatedRevenue()).orElse(BigDecimal.ZERO);
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
earningsService.createCounterLine(order.getId(), order.getTenantId(), targetClerkId, amount, context.getOperatorId());
|
||||
}
|
||||
|
||||
@@ -26,31 +26,15 @@ public class OrderRevocationContext {
|
||||
|
||||
private boolean refundToCustomer;
|
||||
|
||||
private EarningsAdjustStrategy earningsStrategy = EarningsAdjustStrategy.NONE;
|
||||
|
||||
@Nullable
|
||||
private String counterClerkId;
|
||||
private boolean deductClerkEarnings;
|
||||
|
||||
private OrderTriggerSource triggerSource = OrderTriggerSource.UNKNOWN;
|
||||
|
||||
@Nullable
|
||||
private BigDecimal earningsAdjustAmount;
|
||||
|
||||
public OrderRevocationContext withTriggerSource(OrderTriggerSource triggerSource) {
|
||||
this.triggerSource = triggerSource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public enum EarningsAdjustStrategy {
|
||||
NONE("NO_ADJUST"),
|
||||
REVERSE_CLERK("REV_CLERK"),
|
||||
COUNTER_TO_PEIPEI("CTR_PEIPEI");
|
||||
|
||||
private final String logCode;
|
||||
|
||||
EarningsAdjustStrategy(String logCode) {
|
||||
this.logCode = logCode;
|
||||
}
|
||||
|
||||
public String getLogCode() {
|
||||
return logCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.starry.admin.modules.order.module.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel(value = "撤销限额信息", description = "展示撤销时可退金额、可扣回收益等信息")
|
||||
public class PlayOrderRevocationLimitsVo {
|
||||
|
||||
@ApiModelProperty("订单ID")
|
||||
private String orderId;
|
||||
|
||||
@ApiModelProperty("最大可退金额")
|
||||
private BigDecimal maxRefundAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("最大可扣回收益")
|
||||
private BigDecimal maxDeductAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("建议扣回金额")
|
||||
private BigDecimal defaultDeductAmount = BigDecimal.ZERO;
|
||||
|
||||
@ApiModelProperty("是否存在可扣回店员")
|
||||
private boolean deductible;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.starry.admin.modules.order.module.vo;
|
||||
|
||||
import com.starry.admin.modules.order.module.dto.OrderRevocationContext.EarningsAdjustStrategy;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.math.BigDecimal;
|
||||
@@ -24,9 +23,9 @@ public class PlayOrderRevocationVo {
|
||||
@ApiModelProperty(value = "撤销原因")
|
||||
private String refundReason;
|
||||
|
||||
@ApiModelProperty(value = "收益处理策略:NONE/REVERSE_CLERK/COUNTER_TO_PEIPEI")
|
||||
private EarningsAdjustStrategy earningsStrategy = EarningsAdjustStrategy.NONE;
|
||||
@ApiModelProperty(value = "是否扣回店员收益")
|
||||
private boolean deductClerkEarnings;
|
||||
|
||||
@ApiModelProperty(value = "收益冲销目标账号ID,策略为 COUNTER_TO_PEIPEI 时必填")
|
||||
private String counterClerkId;
|
||||
@ApiModelProperty(value = "扣回金额,未填写则默认按本单收益全额扣回")
|
||||
private BigDecimal deductAmount;
|
||||
}
|
||||
|
||||
@@ -632,18 +632,32 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
||||
throw new CustomException("仅支持撤销普通服务订单");
|
||||
}
|
||||
|
||||
OrderRevocationContext.EarningsAdjustStrategy strategy = context.getEarningsStrategy() != null
|
||||
? context.getEarningsStrategy()
|
||||
: OrderRevocationContext.EarningsAdjustStrategy.NONE;
|
||||
if (strategy == OrderRevocationContext.EarningsAdjustStrategy.REVERSE_CLERK
|
||||
&& earningsService.hasLockedLines(order.getId())) {
|
||||
throw new CustomException("收益已提现或处理中,无法撤销");
|
||||
}
|
||||
|
||||
String operatorType = StrUtil.isNotBlank(context.getOperatorType()) ? context.getOperatorType() : OperatorType.ADMIN.getCode();
|
||||
context.setOperatorType(operatorType);
|
||||
String operatorId = StrUtil.isNotBlank(context.getOperatorId()) ? context.getOperatorId() : SecurityUtils.getUserId();
|
||||
context.setOperatorId(operatorId);
|
||||
if (context.isDeductClerkEarnings()) {
|
||||
String targetClerkId = order.getAcceptBy();
|
||||
if (StrUtil.isBlank(targetClerkId)) {
|
||||
throw new CustomException("未找到可冲销的店员收益账号");
|
||||
}
|
||||
BigDecimal availableEarnings = Optional.ofNullable(
|
||||
earningsService.getRemainingEarningsForOrder(order.getId(), targetClerkId))
|
||||
.orElse(BigDecimal.ZERO);
|
||||
if (availableEarnings.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new CustomException("本单店员收益已全部扣回");
|
||||
}
|
||||
BigDecimal requested = context.getEarningsAdjustAmount();
|
||||
if (requested == null || requested.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
requested = availableEarnings;
|
||||
}
|
||||
if (requested.compareTo(availableEarnings) > 0) {
|
||||
throw new CustomException("扣回金额不能超过本单收益" + availableEarnings);
|
||||
}
|
||||
context.setEarningsAdjustAmount(requested);
|
||||
} else {
|
||||
context.setEarningsAdjustAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
BigDecimal finalAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
|
||||
BigDecimal refundAmount = context.getRefundAmount();
|
||||
@@ -708,9 +722,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
||||
String operationType = String.format(
|
||||
"%s_%s",
|
||||
LifecycleOperation.REVOKE_COMPLETED.name(),
|
||||
strategy != null
|
||||
? strategy.getLogCode()
|
||||
: OrderRevocationContext.EarningsAdjustStrategy.NONE.getLogCode());
|
||||
context.isDeductClerkEarnings() ? "DEDUCT" : "KEEP");
|
||||
recordOrderLog(latest, actor, context.getOperatorId(), LifecycleOperation.REVOKE_COMPLETED,
|
||||
context.getRefundReason(), operationType);
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import com.fasterxml.jackson.annotation.JsonValue;
|
||||
*/
|
||||
public enum EarningsType {
|
||||
ORDER("ORDER"),
|
||||
COMMISSION("COMMISSION");
|
||||
COMMISSION("COMMISSION"),
|
||||
ADJUSTMENT("ADJUSTMENT");
|
||||
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
|
||||
@@ -18,9 +18,7 @@ public interface IEarningsService extends IService<EarningsLineEntity> {
|
||||
|
||||
List<EarningsLineEntity> findWithdrawable(String clerkId, BigDecimal amount, LocalDateTime now);
|
||||
|
||||
void reverseByOrder(String orderId, String operatorId);
|
||||
|
||||
void createCounterLine(String orderId, String tenantId, String targetClerkId, BigDecimal amount, String operatorId);
|
||||
|
||||
boolean hasLockedLines(String orderId);
|
||||
BigDecimal getRemainingEarningsForOrder(String orderId, String clerkId);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.starry.admin.modules.withdraw.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
@@ -15,7 +14,6 @@ import com.starry.common.utils.IdUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -98,17 +96,6 @@ public class EarningsServiceImpl extends ServiceImpl<EarningsLineMapper, Earning
|
||||
return picked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reverseByOrder(String orderId, String operatorId) {
|
||||
if (StrUtil.isBlank(orderId)) {
|
||||
return;
|
||||
}
|
||||
this.update(Wrappers.lambdaUpdate(EarningsLineEntity.class)
|
||||
.eq(EarningsLineEntity::getOrderId, orderId)
|
||||
.in(EarningsLineEntity::getStatus, Arrays.asList("available", "frozen"))
|
||||
.set(EarningsLineEntity::getStatus, "reversed"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCounterLine(String orderId, String tenantId, String targetClerkId, BigDecimal amount, String operatorId) {
|
||||
if (StrUtil.hasBlank(orderId, tenantId, targetClerkId)) {
|
||||
@@ -119,27 +106,63 @@ public class EarningsServiceImpl extends ServiceImpl<EarningsLineMapper, Earning
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime resolvedUnlock = now;
|
||||
String resolvedStatus = "available";
|
||||
|
||||
List<EarningsLineEntity> references = this.baseMapper.selectList(new LambdaQueryWrapper<EarningsLineEntity>()
|
||||
.eq(EarningsLineEntity::getOrderId, orderId)
|
||||
.eq(EarningsLineEntity::getClerkId, targetClerkId)
|
||||
.eq(EarningsLineEntity::getDeleted, false)
|
||||
.orderByAsc(EarningsLineEntity::getUnlockTime));
|
||||
EarningsLineEntity reference = references.stream()
|
||||
.filter(line -> line.getAmount() != null && line.getAmount().compareTo(BigDecimal.ZERO) > 0)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (reference == null) {
|
||||
throw new IllegalStateException("未找到可冲销的收益记录");
|
||||
}
|
||||
LocalDateTime refUnlock = reference.getUnlockTime();
|
||||
String refStatus = reference.getStatus();
|
||||
boolean shouldPreserveFreeze = "frozen".equalsIgnoreCase(refStatus)
|
||||
&& refUnlock != null
|
||||
&& refUnlock.isAfter(now);
|
||||
if (shouldPreserveFreeze) {
|
||||
resolvedUnlock = refUnlock;
|
||||
resolvedStatus = "frozen";
|
||||
} else {
|
||||
resolvedUnlock = now;
|
||||
resolvedStatus = "available";
|
||||
}
|
||||
|
||||
EarningsLineEntity line = new EarningsLineEntity();
|
||||
line.setId(IdUtils.getUuid());
|
||||
line.setOrderId(orderId);
|
||||
line.setTenantId(tenantId);
|
||||
line.setClerkId(targetClerkId);
|
||||
line.setAmount(normalized.negate());
|
||||
line.setEarningType(EarningsType.ORDER);
|
||||
line.setStatus("available");
|
||||
line.setUnlockTime(LocalDateTime.now());
|
||||
line.setEarningType(EarningsType.ADJUSTMENT);
|
||||
line.setStatus(resolvedStatus);
|
||||
line.setUnlockTime(resolvedUnlock);
|
||||
this.save(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLockedLines(String orderId) {
|
||||
if (StrUtil.isBlank(orderId)) {
|
||||
return false;
|
||||
public BigDecimal getRemainingEarningsForOrder(String orderId, String clerkId) {
|
||||
if (StrUtil.hasBlank(orderId, clerkId)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
Long count = this.lambdaQuery()
|
||||
List<EarningsLineEntity> lines = this.lambdaQuery()
|
||||
.eq(EarningsLineEntity::getOrderId, orderId)
|
||||
.in(EarningsLineEntity::getStatus, Arrays.asList("withdrawing", "withdrawn"))
|
||||
.count();
|
||||
return count != null && count > 0;
|
||||
.eq(EarningsLineEntity::getClerkId, clerkId)
|
||||
.eq(EarningsLineEntity::getDeleted, false)
|
||||
.list();
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (EarningsLineEntity line : lines) {
|
||||
BigDecimal amount = line.getAmount() == null ? BigDecimal.ZERO : line.getAmount();
|
||||
total = total.add(amount);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user