feat(order): add batch review preview
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.order.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BatchReviewOpenApiContractTest {
|
||||
@Test
|
||||
void documentsSingleRequestPreviewFieldsAndVersionedPartialApproval() throws Exception {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/openapi/order-v1.yaml"));
|
||||
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/orders/first-leg/batch-review-preview:")
|
||||
.contains("/api/platform/v1/orders/first-leg/batch-approve:")
|
||||
.contains("inboundCode:")
|
||||
.contains("customerName:")
|
||||
.contains("chineseGoodsNames:")
|
||||
.contains("channelCode:")
|
||||
.contains("channelName:")
|
||||
.contains("destinationWarehouseCode:")
|
||||
.contains("destinationWarehouseName:")
|
||||
.contains("orderVersion:")
|
||||
.contains("canApprove:")
|
||||
.contains("blockReason:")
|
||||
.contains("允许部分成功");
|
||||
}
|
||||
}
|
||||
+25
-4
@@ -4,10 +4,12 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.BatchOrderReviewPreviewResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
import com.baiyue.order.service.InboundCodeAssociationService;
|
||||
import com.baiyue.order.service.BatchOrderReviewPreviewService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -23,11 +25,13 @@ import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(PlatformFirstLegOrderController.class)
|
||||
class PlatformOrderPageControllerTest {
|
||||
@MockBean private OrderApplicationService service;
|
||||
@MockBean private InboundCodeAssociationService inboundCodeAssociations;
|
||||
@MockBean private BatchOrderReviewPreviewService batchReviewPreview;
|
||||
@Resource private PlatformFirstLegOrderController controller;
|
||||
private final OrderRequests.QueryRequest query = new OrderRequests.QueryRequest(
|
||||
null, null, null, null, null, null, 1, 20);
|
||||
@@ -77,10 +81,13 @@ class PlatformOrderPageControllerTest {
|
||||
@Test
|
||||
void batchApproveReturnsPartialFailureAndContinuesRemainingOrders() {
|
||||
doThrow(new BizException(ResultCode.CONFLICT, "订单已审核"))
|
||||
.when(service).transit(10001L, 1L, "operator", 102L, OrderStatus.APPROVED, null);
|
||||
.when(service).transit(10001L, 1L, "operator", 102L, OrderStatus.APPROVED, null, 4);
|
||||
|
||||
var response = controller.batchApprove(user("PLATFORM", 0L), 10001L,
|
||||
new OrderRequests.BatchReviewRequest(List.of(101L, 102L, 103L)));
|
||||
new OrderRequests.BatchReviewRequest(List.of(
|
||||
new OrderRequests.BatchReviewItem(101L, 3),
|
||||
new OrderRequests.BatchReviewItem(102L, 4),
|
||||
new OrderRequests.BatchReviewItem(103L, 5))));
|
||||
|
||||
BatchOrderReviewResponse result = response.data();
|
||||
assertThat(result.total()).isEqualTo(3);
|
||||
@@ -90,8 +97,8 @@ class PlatformOrderPageControllerTest {
|
||||
assertThat(failure.code()).isEqualTo(ResultCode.CONFLICT.code());
|
||||
assertThat(failure.message()).isEqualTo("订单已审核");
|
||||
});
|
||||
verify(service).transit(10001L, 1L, "operator", 101L, OrderStatus.APPROVED, null);
|
||||
verify(service).transit(10001L, 1L, "operator", 103L, OrderStatus.APPROVED, null);
|
||||
verify(service).transit(10001L, 1L, "operator", 101L, OrderStatus.APPROVED, null, 3);
|
||||
verify(service).transit(10001L, 1L, "operator", 103L, OrderStatus.APPROVED, null, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,6 +110,20 @@ class PlatformOrderPageControllerTest {
|
||||
verify(service).transit(10001L, 1L, "operator", 102L, OrderStatus.REJECTED, "资料不完整");
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchReviewPreviewUsesOneControllerCallAndExactCustomerScope() {
|
||||
var request = new OrderRequests.BatchReviewRequest(List.of(
|
||||
new OrderRequests.BatchReviewItem(101L, 3),
|
||||
new OrderRequests.BatchReviewItem(102L, 4)));
|
||||
var preview = new BatchOrderReviewPreviewResponse(2, 1, List.of());
|
||||
when(batchReviewPreview.preview(10001L, request.items())).thenReturn(preview);
|
||||
|
||||
var response = controller.batchReviewPreview(user("PLATFORM", 0L), 10001L, request);
|
||||
|
||||
assertThat(response.data()).isSameAs(preview);
|
||||
verify(batchReviewPreview).preview(10001L, request.items());
|
||||
}
|
||||
|
||||
private AuthenticatedUser user(String appCode, Long tenantId) {
|
||||
return new AuthenticatedUser(1L, tenantId, null, null, "operator", appCode, "WEB", Set.of());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,20 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class OrderRequestsValidationTest {
|
||||
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
|
||||
@Test
|
||||
void batchReviewRequiresDistinctOrderIdsAndNonNegativeVersions() {
|
||||
var duplicate = new OrderRequests.BatchReviewRequest(List.of(
|
||||
new OrderRequests.BatchReviewItem(101L, 1),
|
||||
new OrderRequests.BatchReviewItem(101L, 2)));
|
||||
var invalidVersion = new OrderRequests.BatchReviewRequest(List.of(
|
||||
new OrderRequests.BatchReviewItem(102L, -1)));
|
||||
|
||||
assertThat(validator.validate(duplicate))
|
||||
.anyMatch(violation -> violation.getMessage().contains("订单 ID 不能重复"));
|
||||
assertThat(validator.validate(invalidVersion))
|
||||
.anyMatch(violation -> violation.getPropertyPath().toString().contains("orderVersion"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void carrierSnapshotIsOptionalWhenCustomerCreatesAnOrderDraft() {
|
||||
assertThat(validator.validate(request(null, null, "TAX_INCLUDED"))).isEmpty();
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.baiyue.order.service;
|
||||
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.entity.OrderCargoEntity;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.repository.OrderRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class BatchOrderReviewPreviewServiceTest {
|
||||
private final OrderRepository orders = mock(OrderRepository.class);
|
||||
private final BatchOrderReviewPreviewService service = new BatchOrderReviewPreviewService();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
org.springframework.test.util.ReflectionTestUtils.setField(service, "orders", orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsStableRowsWithOrderedDistinctChineseGoodsUsingTwoBatchQueries() {
|
||||
OrderEntity eligible = order(101L, 3, OrderStatus.SUBMITTED, "RK-101", "客户甲");
|
||||
OrderEntity stale = order(102L, 8, OrderStatus.SUBMITTED, "RK-102", "客户乙");
|
||||
when(orders.findByIds(11L, List.of(101L, 102L))).thenReturn(List.of(stale, eligible));
|
||||
when(orders.findCargoByOrderIds(11L, List.of(101L, 102L))).thenReturn(List.of(
|
||||
cargo(101L, " 蓝牙音箱 "), cargo(101L, "蓝牙音箱"), cargo(101L, "充电器"),
|
||||
cargo(102L, "数据线")));
|
||||
|
||||
var result = service.preview(11L, List.of(
|
||||
new OrderRequests.BatchReviewItem(101L, 3),
|
||||
new OrderRequests.BatchReviewItem(102L, 7)));
|
||||
|
||||
assertThat(result.total()).isEqualTo(2);
|
||||
assertThat(result.approvableCount()).isEqualTo(1);
|
||||
assertThat(result.items()).extracting(item -> item.orderId()).containsExactly(101L, 102L);
|
||||
assertThat(result.items().get(0).chineseGoodsNames()).containsExactly("蓝牙音箱", "充电器");
|
||||
assertThat(result.items().get(0).destinationWarehouseCode()).isEqualTo("ONT8");
|
||||
assertThat(result.items().get(0).canApprove()).isTrue();
|
||||
assertThat(result.items().get(1).orderVersion()).isEqualTo(8);
|
||||
assertThat(result.items().get(1).blockReason()).contains("版本已变化");
|
||||
verify(orders).findByIds(11L, List.of(101L, 102L));
|
||||
verify(orders).findCargoByOrderIds(11L, List.of(101L, 102L));
|
||||
verifyNoMoreInteractions(orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsUnknownAndWrongStatusRowsWithoutLeakingAnotherCustomer() {
|
||||
OrderEntity approved = order(101L, 3, OrderStatus.APPROVED, "RK-101", "客户甲");
|
||||
when(orders.findByIds(11L, List.of(999L, 101L))).thenReturn(List.of(approved));
|
||||
when(orders.findCargoByOrderIds(11L, List.of(999L, 101L))).thenReturn(List.of());
|
||||
|
||||
var result = service.preview(11L, List.of(
|
||||
new OrderRequests.BatchReviewItem(999L, 1),
|
||||
new OrderRequests.BatchReviewItem(101L, 3)));
|
||||
|
||||
assertThat(result.items().get(0).inboundCode()).isNull();
|
||||
assertThat(result.items().get(0).blockReason()).contains("不存在或不属于当前客户");
|
||||
assertThat(result.items().get(1).blockReason()).contains("状态不可审核");
|
||||
assertThat(result.approvableCount()).isZero();
|
||||
}
|
||||
|
||||
private static OrderEntity order(long id, int version, OrderStatus status,
|
||||
String inboundCode, String customerName) {
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(id);
|
||||
order.setTenantId(11L);
|
||||
order.setVersion(version);
|
||||
order.setStatus(status);
|
||||
order.setInboundCode(inboundCode);
|
||||
order.setCustomerId(11L);
|
||||
order.setCustomerName(customerName);
|
||||
order.setChannelCode("FBA-SEA");
|
||||
order.setChannelName("美森海运");
|
||||
order.setAmazonWarehouseCode("ONT8");
|
||||
order.setDestinationWarehouseName("ONT8 亚马逊仓");
|
||||
return order;
|
||||
}
|
||||
|
||||
private static OrderCargoEntity cargo(long orderId, String name) {
|
||||
OrderCargoEntity cargo = new OrderCargoEntity();
|
||||
cargo.setOrderId(orderId);
|
||||
cargo.setCargoName(name);
|
||||
return cargo;
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,24 @@ class OrderApplicationServiceStateTest {
|
||||
&& event.getPayloadJson().contains("\"totalPackageCount\":4")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchApprovalRejectsStalePreviewVersionBeforeChangingOrder() {
|
||||
OrderRepository repository = mock(OrderRepository.class);
|
||||
OrderEntity order = persistedOrder(OrderStatus.SUBMITTED);
|
||||
order.setVersion(6);
|
||||
when(repository.findOwned(10001L, 1L)).thenReturn(order);
|
||||
|
||||
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class),
|
||||
mock(OrderMasterDataGuard.class)).transit(
|
||||
10001L, 2L, "u", 1L, OrderStatus.APPROVED, null, 5))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessage("订单版本已变化,请刷新后重试");
|
||||
|
||||
assertThat(order.getStatus()).isEqualTo(OrderStatus.SUBMITTED);
|
||||
verify(repository, never()).saveOrder(anyLong(), any());
|
||||
verify(repository, never()).insertOutbox(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void submittingDraftGeneratesAndPersistsInboundCode() {
|
||||
OrderRepository repository = mock(OrderRepository.class);
|
||||
|
||||
Reference in New Issue
Block a user