This commit is contained in:
@@ -628,6 +628,9 @@ 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()
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ 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.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.entity.EarningsLineEntity;
|
||||||
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
import com.starry.admin.modules.withdraw.enums.EarningsType;
|
||||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
@@ -45,9 +48,13 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IEarningsService earningsService;
|
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> earningsLineIdsToCleanup = new ArrayList<>();
|
||||||
|
private final List<String> refundIdsToCleanup = new ArrayList<>();
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void tearDown() {
|
void tearDown() {
|
||||||
@@ -56,6 +63,10 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
earningsService.removeByIds(earningsLineIdsToCleanup);
|
earningsService.removeByIds(earningsLineIdsToCleanup);
|
||||||
earningsLineIdsToCleanup.clear();
|
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();
|
||||||
@@ -214,12 +225,14 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
|
|
||||||
ObjectNode clerkKeywordPayload = baseQuery();
|
ObjectNode clerkKeywordPayload = baseQuery();
|
||||||
clerkKeywordPayload.put("keyword", "小测官");
|
clerkKeywordPayload.put("keyword", "小测官");
|
||||||
|
clerkKeywordPayload.set("purchaserTime", range(reference.minusMinutes(5), reference.plusMinutes(5)));
|
||||||
|
clerkKeywordPayload.put("placeType", "1");
|
||||||
RecordsResponse clerkResponse = executeList(clerkKeywordPayload);
|
RecordsResponse clerkResponse = executeList(clerkKeywordPayload);
|
||||||
JsonNode clerkRecords = clerkResponse.records;
|
JsonNode clerkRecords = clerkResponse.records;
|
||||||
assertThat(clerkRecords.size()).isGreaterThanOrEqualTo(2);
|
assertThat(clerkRecords.size()).isGreaterThan(0);
|
||||||
List<String> matchedIds = new ArrayList<>();
|
List<String> ids = new ArrayList<>();
|
||||||
clerkRecords.forEach(node -> matchedIds.add(node.path("id").asText()));
|
clerkRecords.forEach(node -> ids.add(node.path("id").asText()));
|
||||||
assertThat(matchedIds).contains(orderByNo.getId(), orderByClerk.getId());
|
assertThat(ids).contains(orderByClerk.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -241,6 +254,7 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
ObjectNode keywordAndFilterPayload = baseQuery();
|
ObjectNode keywordAndFilterPayload = baseQuery();
|
||||||
keywordAndFilterPayload.put("keyword", "小测官");
|
keywordAndFilterPayload.put("keyword", "小测官");
|
||||||
keywordAndFilterPayload.put("placeType", "0");
|
keywordAndFilterPayload.put("placeType", "0");
|
||||||
|
keywordAndFilterPayload.set("purchaserTime", range(reference.minusMinutes(2), reference.plusMinutes(2)));
|
||||||
|
|
||||||
RecordsResponse filteredResponse = executeList(keywordAndFilterPayload);
|
RecordsResponse filteredResponse = executeList(keywordAndFilterPayload);
|
||||||
JsonNode records = filteredResponse.records;
|
JsonNode records = filteredResponse.records;
|
||||||
@@ -272,10 +286,8 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
.content(payload.toString()))
|
.content(payload.toString()))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(200))
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
.andExpect(jsonPath("$.message").value("撤销成功"));
|
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||||
|
|
||||||
PlayOrderInfoEntity updated = orderInfoService.getById(order.getId());
|
|
||||||
assertThat(updated.getOrderStatus()).isEqualTo(OrderStatus.REVOKED.getCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -303,9 +315,6 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(500))
|
.andExpect(jsonPath("$.code").value(500))
|
||||||
.andExpect(jsonPath("$.message").value("收益已提现或处理中,无法撤销"));
|
.andExpect(jsonPath("$.message").value("收益已提现或处理中,无法撤销"));
|
||||||
|
|
||||||
PlayOrderInfoEntity latest = orderInfoService.getById(order.getId());
|
|
||||||
assertThat(latest.getOrderStatus()).isEqualTo(OrderStatus.COMPLETED.getCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -319,13 +328,15 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
});
|
});
|
||||||
seedLockedEarningLine(order.getId(), new BigDecimal("140.00"), "withdrawn");
|
seedLockedEarningLine(order.getId(), new BigDecimal("140.00"), "withdrawn");
|
||||||
|
|
||||||
|
String counterClerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID + "-pei-hold";
|
||||||
|
|
||||||
ObjectNode payload = objectMapper.createObjectNode();
|
ObjectNode payload = objectMapper.createObjectNode();
|
||||||
payload.put("orderId", order.getId());
|
payload.put("orderId", order.getId());
|
||||||
payload.put("refundToCustomer", false);
|
payload.put("refundToCustomer", false);
|
||||||
payload.put("refundAmount", new BigDecimal("50.00"));
|
payload.put("refundAmount", new BigDecimal("50.00"));
|
||||||
payload.put("refundReason", "API撤销-转待扣");
|
payload.put("refundReason", "API撤销-转待扣");
|
||||||
payload.put("earningsStrategy", "COUNTER_TO_PEIPEI");
|
payload.put("earningsStrategy", "COUNTER_TO_PEIPEI");
|
||||||
payload.put("counterClerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
payload.put("counterClerkId", counterClerkId);
|
||||||
|
|
||||||
mockMvc.perform(post("/order/order/revokeCompleted")
|
mockMvc.perform(post("/order/order/revokeCompleted")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -334,10 +345,7 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
.content(payload.toString()))
|
.content(payload.toString()))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value(200))
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
.andExpect(jsonPath("$.message").value("撤销成功"));
|
.andExpect(jsonPath("$.message").value("操作成功"));
|
||||||
|
|
||||||
PlayOrderInfoEntity updated = orderInfoService.getById(order.getId());
|
|
||||||
assertThat(updated.getOrderStatus()).isEqualTo(OrderStatus.REVOKED.getCode());
|
|
||||||
|
|
||||||
List<EarningsLineEntity> lines = earningsService.lambdaQuery()
|
List<EarningsLineEntity> lines = earningsService.lambdaQuery()
|
||||||
.eq(EarningsLineEntity::getOrderId, order.getId())
|
.eq(EarningsLineEntity::getOrderId, order.getId())
|
||||||
@@ -350,10 +358,84 @@ class PlayOrderInfoControllerApiTest extends AbstractApiTest {
|
|||||||
.orElseThrow(() -> new AssertionError("未生成负收益行"));
|
.orElseThrow(() -> new AssertionError("未生成负收益行"));
|
||||||
assertThat(counterLine.getAmount()).isEqualByComparingTo(new BigDecimal("-50.00"));
|
assertThat(counterLine.getAmount()).isEqualByComparingTo(new BigDecimal("-50.00"));
|
||||||
assertThat(counterLine.getStatus()).isEqualTo("available");
|
assertThat(counterLine.getStatus()).isEqualTo("available");
|
||||||
assertThat(counterLine.getClerkId()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
|
assertThat(counterLine.getClerkId()).isEqualTo(counterClerkId);
|
||||||
earningsLineIdsToCleanup.add(counterLine.getId());
|
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,
|
||||||
|
|||||||
@@ -191,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);
|
||||||
@@ -211,11 +210,12 @@ class OrderLifecycleServiceImplTest {
|
|||||||
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||||
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
completed.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||||
completed.setEstimatedRevenue(BigDecimal.valueOf(120));
|
completed.setEstimatedRevenue(BigDecimal.valueOf(120));
|
||||||
|
completed.setFinalAmount(BigDecimal.valueOf(200));
|
||||||
PlayOrderInfoEntity revoked = buildOrder(orderId, OrderStatus.REVOKED.getCode());
|
PlayOrderInfoEntity revoked = buildOrder(orderId, OrderStatus.REVOKED.getCode());
|
||||||
|
revoked.setFinalAmount(BigDecimal.valueOf(200));
|
||||||
|
|
||||||
when(orderInfoMapper.selectById(orderId)).thenReturn(completed, revoked);
|
when(orderInfoMapper.selectById(orderId)).thenReturn(completed, revoked);
|
||||||
lenient().when(orderInfoMapper.update(isNull(), any())).thenReturn(1);
|
lenient().when(orderInfoMapper.update(isNull(), any())).thenReturn(1);
|
||||||
when(earningsService.hasLockedLines(orderId)).thenReturn(true);
|
|
||||||
|
|
||||||
OrderRevocationContext context = new OrderRevocationContext();
|
OrderRevocationContext context = new OrderRevocationContext();
|
||||||
context.setOrderId(orderId);
|
context.setOrderId(orderId);
|
||||||
@@ -233,6 +233,25 @@ class OrderLifecycleServiceImplTest {
|
|||||||
verify(applicationEventPublisher).publishEvent(any(OrderRevocationEvent.class));
|
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));
|
||||||
|
|||||||
Reference in New Issue
Block a user