Compare commits
2 Commits
master
...
f296ef38d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f296ef38d7 | ||
|
|
d5ba6aeab6 |
@@ -30,6 +30,12 @@ public class PlayOrderInfoQueryVo extends BasePageEntity {
|
|||||||
@ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "订单的编号,支持模糊查询")
|
@ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "订单的编号,支持模糊查询")
|
||||||
private String orderNo;
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一关键字(订单号或店员昵称)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "关键词", example = "ORDER20240320001", notes = "支持订单号或店员昵称模糊查询")
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -628,11 +628,15 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
if (!OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus())) {
|
if (!OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus())) {
|
||||||
throw new CustomException("当前状态无法撤销");
|
throw new CustomException("当前状态无法撤销");
|
||||||
}
|
}
|
||||||
|
if (!OrderConstant.OrderType.NORMAL.getCode().equals(order.getOrderType())) {
|
||||||
|
throw new CustomException("仅支持撤销普通服务订单");
|
||||||
|
}
|
||||||
|
|
||||||
OrderRevocationContext.EarningsAdjustStrategy strategy = context.getEarningsStrategy() != null
|
OrderRevocationContext.EarningsAdjustStrategy strategy = context.getEarningsStrategy() != null
|
||||||
? context.getEarningsStrategy()
|
? context.getEarningsStrategy()
|
||||||
: OrderRevocationContext.EarningsAdjustStrategy.NONE;
|
: OrderRevocationContext.EarningsAdjustStrategy.NONE;
|
||||||
if (strategy != OrderRevocationContext.EarningsAdjustStrategy.NONE && earningsService.hasLockedLines(order.getId())) {
|
if (strategy == OrderRevocationContext.EarningsAdjustStrategy.REVERSE_CLERK
|
||||||
|
&& earningsService.hasLockedLines(order.getId())) {
|
||||||
throw new CustomException("收益已提现或处理中,无法撤销");
|
throw new CustomException("收益已提现或处理中,无法撤销");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,6 +861,10 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean ensureEarnings(PlayOrderInfoEntity order, OrderTriggerSource source) {
|
private boolean ensureEarnings(PlayOrderInfoEntity order, OrderTriggerSource source) {
|
||||||
|
if (OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode().equals(order.getOrderType())) {
|
||||||
|
log.debug("Skip earnings creation for blind box purchase order {}", order.getId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Long existing = earningsService.lambdaQuery()
|
Long existing = earningsService.lambdaQuery()
|
||||||
.eq(EarningsLineEntity::getTenantId, order.getTenantId())
|
.eq(EarningsLineEntity::getTenantId, order.getTenantId())
|
||||||
.eq(EarningsLineEntity::getOrderId, order.getId())
|
.eq(EarningsLineEntity::getOrderId, order.getId())
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
public IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo vo) {
|
public IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo vo) {
|
||||||
|
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), vo.getKeyword());
|
||||||
lambdaQueryWrapper.in(PlayOrderInfoEntity::getPlaceType, "0", "1", "2");
|
lambdaQueryWrapper.in(PlayOrderInfoEntity::getPlaceType, "0", "1", "2");
|
||||||
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
||||||
@@ -454,7 +454,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
public PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String orderId) {
|
public PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String orderId) {
|
||||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||||
entity.setId(orderId);
|
entity.setId(orderId);
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity, null);
|
||||||
// 拼接用户等级
|
// 拼接用户等级
|
||||||
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
||||||
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
||||||
@@ -505,7 +505,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
@Override
|
@Override
|
||||||
public IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo) {
|
public IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), null);
|
||||||
// 拼接用户等级
|
// 拼接用户等级
|
||||||
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
||||||
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
||||||
@@ -520,7 +520,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||||
entity.setId(orderId);
|
entity.setId(orderId);
|
||||||
entity.setPurchaserBy(customId);
|
entity.setPurchaserBy(customId);
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity, null);
|
||||||
PlayCustomOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayCustomOrderDetailsReturnVo.class,
|
PlayCustomOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayCustomOrderDetailsReturnVo.class,
|
||||||
lambdaQueryWrapper);
|
lambdaQueryWrapper);
|
||||||
// 如果订单状态为退款,查询订单退款原因
|
// 如果订单状态为退款,查询订单退款原因
|
||||||
@@ -546,7 +546,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
@Override
|
@Override
|
||||||
public IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo) {
|
public IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), null);
|
||||||
if (StringUtils.isBlank(vo.getOrderType())) {
|
if (StringUtils.isBlank(vo.getOrderType())) {
|
||||||
lambdaQueryWrapper.notIn(PlayOrderInfoEntity::getOrderType,
|
lambdaQueryWrapper.notIn(PlayOrderInfoEntity::getOrderType,
|
||||||
OrderConstant.OrderType.RECHARGE.getCode(),
|
OrderConstant.OrderType.RECHARGE.getCode(),
|
||||||
@@ -711,7 +711,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
*
|
*
|
||||||
* @return MPJLambdaWrapper<PlayOrderInfoEntity>
|
* @return MPJLambdaWrapper<PlayOrderInfoEntity>
|
||||||
*/
|
*/
|
||||||
public MPJLambdaWrapper<PlayOrderInfoEntity> getCommonOrderQueryVo(PlayOrderInfoEntity entity) {
|
public MPJLambdaWrapper<PlayOrderInfoEntity> getCommonOrderQueryVo(PlayOrderInfoEntity entity, String keyword) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
||||||
// 查询主表全部字段
|
// 查询主表全部字段
|
||||||
lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
|
lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
|
||||||
@@ -748,6 +748,11 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, entity.getOrderType());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, entity.getOrderType());
|
||||||
}
|
}
|
||||||
lambdaQueryWrapper.like(StringUtils.isNotEmpty(entity.getOrderNo()), PlayOrderInfoEntity::getOrderNo, entity.getOrderNo());
|
lambdaQueryWrapper.like(StringUtils.isNotEmpty(entity.getOrderNo()), PlayOrderInfoEntity::getOrderNo, entity.getOrderNo());
|
||||||
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
|
lambdaQueryWrapper.and(w -> w.like(PlayOrderInfoEntity::getOrderNo, keyword)
|
||||||
|
.or()
|
||||||
|
.like(PlayClerkUserInfoEntity::getNickname, keyword));
|
||||||
|
}
|
||||||
lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getCreatedTime);
|
lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getCreatedTime);
|
||||||
return lambdaQueryWrapper;
|
return lambdaQueryWrapper;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
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;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||||
@@ -29,6 +30,9 @@ 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;
|
||||||
|
if (OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode().equals(orderInfo.getOrderType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// amount from estimatedRevenue; fallback to orderMoney 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.getOrderMoney() != null ? orderInfo.getOrderMoney() : BigDecimal.ZERO);
|
: (orderInfo.getOrderMoney() != null ? orderInfo.getOrderMoney() : BigDecimal.ZERO);
|
||||||
|
|||||||
@@ -10,8 +10,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
import com.starry.admin.common.apitest.ApiTestDataSeeder;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderRefundInfoEntity;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||||
|
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
||||||
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
|
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||||
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
import com.starry.common.context.CustomSecurityContextHolder;
|
import com.starry.common.context.CustomSecurityContextHolder;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
@@ -38,12 +45,28 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPlayOrderInfoService orderInfoService;
|
private IPlayOrderInfoService orderInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEarningsService earningsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPlayOrderRefundInfoService orderRefundInfoService;
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
private final List<String> orderIdsToCleanup = new ArrayList<>();
|
private final List<String> orderIdsToCleanup = new ArrayList<>();
|
||||||
|
private final List<String> earningsLineIdsToCleanup = new ArrayList<>();
|
||||||
|
private final List<String> refundIdsToCleanup = new ArrayList<>();
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void tearDown() {
|
void tearDown() {
|
||||||
ensureTenantContext();
|
ensureTenantContext();
|
||||||
|
if (!earningsLineIdsToCleanup.isEmpty()) {
|
||||||
|
earningsService.removeByIds(earningsLineIdsToCleanup);
|
||||||
|
earningsLineIdsToCleanup.clear();
|
||||||
|
}
|
||||||
|
if (!refundIdsToCleanup.isEmpty()) {
|
||||||
|
orderRefundInfoService.removeByIds(refundIdsToCleanup);
|
||||||
|
refundIdsToCleanup.clear();
|
||||||
|
}
|
||||||
if (!orderIdsToCleanup.isEmpty()) {
|
if (!orderIdsToCleanup.isEmpty()) {
|
||||||
orderInfoService.removeByIds(orderIdsToCleanup);
|
orderInfoService.removeByIds(orderIdsToCleanup);
|
||||||
orderIdsToCleanup.clear();
|
orderIdsToCleanup.clear();
|
||||||
@@ -183,6 +206,236 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
assertFilterMatches(combinedPayload, matching.getId());
|
assertFilterMatches(combinedPayload, matching.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listByPage_keywordFiltersByOrderNoOrClerkName() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
String marker = ("KW" + IdUtils.getUuid().replace("-", "").substring(0, 4)).toUpperCase();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusHours(2);
|
||||||
|
|
||||||
|
PlayOrderInfoEntity orderByNo = persistOrder(marker, "ord", reference, order -> {
|
||||||
|
order.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
});
|
||||||
|
PlayOrderInfoEntity orderByClerk = persistOrder(marker, "clk", reference.minusMinutes(10), order -> {
|
||||||
|
order.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
});
|
||||||
|
|
||||||
|
ObjectNode orderNoPayload = baseQuery();
|
||||||
|
orderNoPayload.put("keyword", orderByNo.getOrderNo());
|
||||||
|
assertFilterMatches(orderNoPayload, orderByNo.getId());
|
||||||
|
|
||||||
|
ObjectNode clerkKeywordPayload = baseQuery();
|
||||||
|
clerkKeywordPayload.put("keyword", "小测官");
|
||||||
|
clerkKeywordPayload.set("purchaserTime", range(reference.minusMinutes(5), reference.plusMinutes(5)));
|
||||||
|
clerkKeywordPayload.put("placeType", "1");
|
||||||
|
RecordsResponse clerkResponse = executeList(clerkKeywordPayload);
|
||||||
|
JsonNode clerkRecords = clerkResponse.records;
|
||||||
|
assertThat(clerkRecords.size()).isGreaterThan(0);
|
||||||
|
List<String> ids = new ArrayList<>();
|
||||||
|
clerkRecords.forEach(node -> ids.add(node.path("id").asText()));
|
||||||
|
assertThat(ids).contains(orderByClerk.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listByPage_keywordRespectsAdditionalFilters() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
String marker = ("KWFLT" + IdUtils.getUuid().replace("-", "").substring(0, 4)).toUpperCase();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusHours(3);
|
||||||
|
|
||||||
|
PlayOrderInfoEntity assignedOrder = persistOrder(marker, "assigned", reference, order -> {
|
||||||
|
order.setOrderStatus("3");
|
||||||
|
order.setPlaceType("0");
|
||||||
|
});
|
||||||
|
|
||||||
|
persistOrder(marker, "random", reference.minusMinutes(20), order -> {
|
||||||
|
order.setOrderStatus("3");
|
||||||
|
order.setPlaceType("1");
|
||||||
|
});
|
||||||
|
|
||||||
|
ObjectNode keywordAndFilterPayload = baseQuery();
|
||||||
|
keywordAndFilterPayload.put("keyword", "小测官");
|
||||||
|
keywordAndFilterPayload.put("placeType", "0");
|
||||||
|
keywordAndFilterPayload.set("purchaserTime", range(reference.minusMinutes(2), reference.plusMinutes(2)));
|
||||||
|
|
||||||
|
RecordsResponse filteredResponse = executeList(keywordAndFilterPayload);
|
||||||
|
JsonNode records = filteredResponse.records;
|
||||||
|
assertThat(records.size()).isEqualTo(1);
|
||||||
|
assertThat(records.get(0).path("id").asText()).isEqualTo(assignedOrder.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_keepEarningsIgnoresLockedLines() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusHours(1);
|
||||||
|
PlayOrderInfoEntity order = persistOrder("RVK", "keep", reference, entity -> {
|
||||||
|
entity.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
entity.setFinalAmount(new BigDecimal("166.00"));
|
||||||
|
});
|
||||||
|
seedLockedEarningLine(order.getId(), new BigDecimal("80.00"), "withdrawn");
|
||||||
|
|
||||||
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
|
payload.put("orderId", order.getId());
|
||||||
|
payload.put("refundToCustomer", false);
|
||||||
|
payload.put("refundAmount", BigDecimal.ZERO);
|
||||||
|
payload.put("refundReason", "API撤销-保留收益");
|
||||||
|
payload.put("earningsStrategy", "NONE");
|
||||||
|
|
||||||
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||||
|
.header(USER_HEADER, DEFAULT_USER)
|
||||||
|
.content(payload.toString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_reverseClerkBlockedWhenLocked() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusHours(2);
|
||||||
|
PlayOrderInfoEntity order = persistOrder("RVK", "reverse", reference, entity -> {
|
||||||
|
entity.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
entity.setFinalAmount(new BigDecimal("210.00"));
|
||||||
|
});
|
||||||
|
seedLockedEarningLine(order.getId(), new BigDecimal("120.00"), "withdrawn");
|
||||||
|
|
||||||
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
|
payload.put("orderId", order.getId());
|
||||||
|
payload.put("refundToCustomer", false);
|
||||||
|
payload.put("refundAmount", new BigDecimal("20.00"));
|
||||||
|
payload.put("refundReason", "API撤销-冲销收益");
|
||||||
|
payload.put("earningsStrategy", "REVERSE_CLERK");
|
||||||
|
|
||||||
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||||
|
.header(USER_HEADER, DEFAULT_USER)
|
||||||
|
.content(payload.toString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(500))
|
||||||
|
.andExpect(jsonPath("$.message").value("收益已提现或处理中,无法撤销"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_counterStrategyCreatesNegativeLineAfterWithdrawal() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusMinutes(90);
|
||||||
|
PlayOrderInfoEntity order = persistOrder("RVK", "counter", reference, entity -> {
|
||||||
|
entity.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
entity.setFinalAmount(new BigDecimal("230.00"));
|
||||||
|
entity.setEstimatedRevenue(new BigDecimal("150.00"));
|
||||||
|
});
|
||||||
|
seedLockedEarningLine(order.getId(), new BigDecimal("140.00"), "withdrawn");
|
||||||
|
|
||||||
|
String counterClerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID + "-pei-hold";
|
||||||
|
|
||||||
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
|
payload.put("orderId", order.getId());
|
||||||
|
payload.put("refundToCustomer", false);
|
||||||
|
payload.put("refundAmount", new BigDecimal("50.00"));
|
||||||
|
payload.put("refundReason", "API撤销-转待扣");
|
||||||
|
payload.put("earningsStrategy", "COUNTER_TO_PEIPEI");
|
||||||
|
payload.put("counterClerkId", counterClerkId);
|
||||||
|
|
||||||
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||||
|
.header(USER_HEADER, DEFAULT_USER)
|
||||||
|
.content(payload.toString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||||
|
|
||||||
|
List<EarningsLineEntity> lines = earningsService.lambdaQuery()
|
||||||
|
.eq(EarningsLineEntity::getOrderId, order.getId())
|
||||||
|
.list();
|
||||||
|
assertThat(lines).hasSize(2);
|
||||||
|
|
||||||
|
EarningsLineEntity counterLine = lines.stream()
|
||||||
|
.filter(line -> line.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new AssertionError("未生成负收益行"));
|
||||||
|
assertThat(counterLine.getAmount()).isEqualByComparingTo(new BigDecimal("-50.00"));
|
||||||
|
assertThat(counterLine.getStatus()).isEqualTo("available");
|
||||||
|
assertThat(counterLine.getClerkId()).isEqualTo(counterClerkId);
|
||||||
|
earningsLineIdsToCleanup.add(counterLine.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_refundAndCounterCreatesRecords() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusMinutes(45);
|
||||||
|
PlayOrderInfoEntity order = persistOrder("RVK", "refundCounter", reference, entity -> {
|
||||||
|
entity.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
entity.setFinalAmount(new BigDecimal("320.00"));
|
||||||
|
entity.setEstimatedRevenue(new BigDecimal("180.00"));
|
||||||
|
entity.setPaymentSource(OrderConstant.PaymentSource.WX_PAY.getCode());
|
||||||
|
});
|
||||||
|
seedLockedEarningLine(order.getId(), new BigDecimal("110.00"), "withdrawn");
|
||||||
|
|
||||||
|
String counterClerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID + "-refund-counter";
|
||||||
|
|
||||||
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
|
payload.put("orderId", order.getId());
|
||||||
|
payload.put("refundToCustomer", true);
|
||||||
|
payload.put("refundAmount", new BigDecimal("60.00"));
|
||||||
|
payload.put("refundReason", "API撤销-退款并待扣");
|
||||||
|
payload.put("earningsStrategy", "COUNTER_TO_PEIPEI");
|
||||||
|
payload.put("counterClerkId", counterClerkId);
|
||||||
|
|
||||||
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||||
|
.header(USER_HEADER, DEFAULT_USER)
|
||||||
|
.content(payload.toString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||||
|
|
||||||
|
PlayOrderRefundInfoEntity refundInfo = orderRefundInfoService.selectPlayOrderRefundInfoByOrderId(order.getId());
|
||||||
|
assertThat(refundInfo).isNotNull();
|
||||||
|
assertThat(refundInfo.getRefundAmount()).isEqualByComparingTo(new BigDecimal("60.00"));
|
||||||
|
refundIdsToCleanup.add(refundInfo.getId());
|
||||||
|
|
||||||
|
List<EarningsLineEntity> lines = earningsService.lambdaQuery()
|
||||||
|
.eq(EarningsLineEntity::getOrderId, order.getId())
|
||||||
|
.list();
|
||||||
|
EarningsLineEntity counterLine = lines.stream()
|
||||||
|
.filter(line -> line.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new AssertionError("未生成负收益行"));
|
||||||
|
assertThat(counterLine.getAmount()).isEqualByComparingTo(new BigDecimal("-60.00"));
|
||||||
|
assertThat(counterLine.getClerkId()).isEqualTo(counterClerkId);
|
||||||
|
earningsLineIdsToCleanup.add(counterLine.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_blocksNonNormalOrders() throws Exception {
|
||||||
|
ensureTenantContext();
|
||||||
|
LocalDateTime reference = LocalDateTime.now().minusMinutes(10);
|
||||||
|
PlayOrderInfoEntity giftOrder = persistOrder("RVK", "gift", reference, entity -> {
|
||||||
|
entity.setOrderStatus(OrderStatus.COMPLETED.getCode());
|
||||||
|
entity.setOrderType(OrderConstant.OrderType.GIFT.getCode());
|
||||||
|
entity.setPlaceType(OrderConstant.PlaceType.REWARD.getCode());
|
||||||
|
});
|
||||||
|
|
||||||
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
|
payload.put("orderId", giftOrder.getId());
|
||||||
|
payload.put("refundToCustomer", false);
|
||||||
|
payload.put("refundReason", "gift revoke");
|
||||||
|
payload.put("earningsStrategy", "NONE");
|
||||||
|
|
||||||
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(TENANT_HEADER, DEFAULT_TENANT)
|
||||||
|
.header(USER_HEADER, DEFAULT_USER)
|
||||||
|
.content(payload.toString()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(500))
|
||||||
|
.andExpect(jsonPath("$.message").value("仅支持撤销普通服务订单"));
|
||||||
|
}
|
||||||
|
|
||||||
private PlayOrderInfoEntity persistOrder(
|
private PlayOrderInfoEntity persistOrder(
|
||||||
String marker,
|
String marker,
|
||||||
String token,
|
String token,
|
||||||
@@ -272,6 +525,29 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String seedLockedEarningLine(String orderId, BigDecimal amount, String status) {
|
||||||
|
EarningsLineEntity entity = new EarningsLineEntity();
|
||||||
|
String id = "earn-revoke-" + IdUtils.getUuid();
|
||||||
|
entity.setId(id);
|
||||||
|
entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
|
||||||
|
entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
||||||
|
entity.setOrderId(orderId);
|
||||||
|
entity.setAmount(amount);
|
||||||
|
entity.setStatus(status);
|
||||||
|
entity.setEarningType(EarningsType.ORDER);
|
||||||
|
entity.setUnlockTime(LocalDateTime.now().minusHours(2));
|
||||||
|
entity.setWithdrawalId("withdraw-" + IdUtils.getUuid());
|
||||||
|
Date nowDate = toDate(LocalDateTime.now());
|
||||||
|
entity.setCreatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||||
|
entity.setCreatedTime(nowDate);
|
||||||
|
entity.setUpdatedBy(ApiTestDataSeeder.DEFAULT_ADMIN_USER_ID);
|
||||||
|
entity.setUpdatedTime(nowDate);
|
||||||
|
entity.setDeleted(false);
|
||||||
|
earningsService.save(entity);
|
||||||
|
earningsLineIdsToCleanup.add(id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
private void assertFilterMatches(ObjectNode payload, String expectedOrderId) throws Exception {
|
private void assertFilterMatches(ObjectNode payload, String expectedOrderId) throws Exception {
|
||||||
RecordsResponse response = executeList(payload);
|
RecordsResponse response = executeList(payload);
|
||||||
JsonNode records = response.records;
|
JsonNode records = response.records;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.starry.admin.modules.order.service.impl;
|
package com.starry.admin.modules.order.service.impl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -190,7 +191,6 @@ class OrderLifecycleServiceImplTest {
|
|||||||
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||||
|
|
||||||
when(orderInfoMapper.selectById(orderId)).thenReturn(completed);
|
when(orderInfoMapper.selectById(orderId)).thenReturn(completed);
|
||||||
when(earningsService.hasLockedLines(orderId)).thenReturn(true);
|
|
||||||
|
|
||||||
OrderRevocationContext context = new OrderRevocationContext();
|
OrderRevocationContext context = new OrderRevocationContext();
|
||||||
context.setOrderId(orderId);
|
context.setOrderId(orderId);
|
||||||
@@ -204,6 +204,54 @@ class OrderLifecycleServiceImplTest {
|
|||||||
verify(applicationEventPublisher, never()).publishEvent(any());
|
verify(applicationEventPublisher, never()).publishEvent(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_counterStrategyAllowedAfterWithdrawal() {
|
||||||
|
String orderId = UUID.randomUUID().toString();
|
||||||
|
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||||
|
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||||
|
completed.setEstimatedRevenue(BigDecimal.valueOf(120));
|
||||||
|
completed.setFinalAmount(BigDecimal.valueOf(200));
|
||||||
|
PlayOrderInfoEntity revoked = buildOrder(orderId, OrderStatus.REVOKED.getCode());
|
||||||
|
revoked.setFinalAmount(BigDecimal.valueOf(200));
|
||||||
|
|
||||||
|
when(orderInfoMapper.selectById(orderId)).thenReturn(completed, revoked);
|
||||||
|
lenient().when(orderInfoMapper.update(isNull(), any())).thenReturn(1);
|
||||||
|
|
||||||
|
OrderRevocationContext context = new OrderRevocationContext();
|
||||||
|
context.setOrderId(orderId);
|
||||||
|
context.setOperatorId("admin-counter");
|
||||||
|
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||||
|
context.setRefundToCustomer(false);
|
||||||
|
context.setRefundAmount(BigDecimal.valueOf(50));
|
||||||
|
context.setRefundReason("撤销并转待扣");
|
||||||
|
context.setEarningsStrategy(EarningsAdjustStrategy.COUNTER_TO_PEIPEI);
|
||||||
|
context.setCounterClerkId("clerk-negative");
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> lifecycleService.revokeCompletedOrder(context));
|
||||||
|
|
||||||
|
verify(orderInfoMapper).update(isNull(), any());
|
||||||
|
verify(applicationEventPublisher).publishEvent(any(OrderRevocationEvent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revokeCompletedOrder_rejectsNonNormalOrders() {
|
||||||
|
String orderId = UUID.randomUUID().toString();
|
||||||
|
PlayOrderInfoEntity giftOrder = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||||
|
giftOrder.setOrderType(OrderConstant.OrderType.GIFT.getCode());
|
||||||
|
|
||||||
|
when(orderInfoMapper.selectById(orderId)).thenReturn(giftOrder);
|
||||||
|
|
||||||
|
OrderRevocationContext context = new OrderRevocationContext();
|
||||||
|
context.setOrderId(orderId);
|
||||||
|
context.setOperatorId("admin-block");
|
||||||
|
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||||
|
context.setEarningsStrategy(EarningsAdjustStrategy.NONE);
|
||||||
|
|
||||||
|
CustomException ex = assertThrows(CustomException.class, () -> lifecycleService.revokeCompletedOrder(context));
|
||||||
|
assertEquals("仅支持撤销普通服务订单", ex.getMessage());
|
||||||
|
verify(orderInfoMapper, never()).update(isNull(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void placeOrder_throwsWhenCommandNull() {
|
void placeOrder_throwsWhenCommandNull() {
|
||||||
assertThrows(CustomException.class, () -> lifecycleService.placeOrder(null));
|
assertThrows(CustomException.class, () -> lifecycleService.placeOrder(null));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
|
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
|
||||||
@@ -71,6 +72,17 @@ class EarningsServiceImplTest {
|
|||||||
verify(baseMapper, never()).insert(any());
|
verify(baseMapper, never()).insert(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createFromOrder_skipsBlindBoxPurchaseOrders() {
|
||||||
|
PlayOrderInfoEntity order = baselineOrder();
|
||||||
|
order.setEstimatedRevenue(BigDecimal.valueOf(66));
|
||||||
|
order.setOrderType(OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode());
|
||||||
|
|
||||||
|
earningsService.createFromOrder(order);
|
||||||
|
|
||||||
|
verify(baseMapper, never()).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createCounterLine_persistsNegativeAvailableLine() {
|
void createCounterLine_persistsNegativeAvailableLine() {
|
||||||
when(baseMapper.insert(any())).thenReturn(1);
|
when(baseMapper.insert(any())).thenReturn(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user