装柜回归测试

This commit is contained in:
2026-08-09 20:35:11 +08:00
parent b20cd9bd77
commit 0ce264a257
67 changed files with 3329 additions and 208 deletions
@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
public record UnifiedOrderQuery(
@Size(max = 64) String keyword,
@Size(max = 64) String orderNo,
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT",
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED",
"LOADING", "LOADED", "DELIVERED"})
@Size(max = 32) String mainStatus,
Long customerId,
@@ -78,37 +78,6 @@ public class OrderMasterDataGuard {
throw downstreamFailure(exception);
}
}
public PartnerClient.Partner validateCarrier(long id) {
try {
ApiResponse<PartnerClient.Partner> response;
try {
response = partners.active(token, publicTenantId, id);
} catch (FeignException.NotFound exception) {
throw new BizException(ResultCode.NOT_FOUND, "承运商不存在或不可用");
}
requireSuccessful(response, "承运商");
if (response.data() == null) throw unavailable("承运商");
var value = response.data();
if (!Long.valueOf(id).equals(value.id())) throw unavailable("承运商");
if (!Long.valueOf(publicTenantId).equals(value.tenantId()))
throw new BizException(ResultCode.FORBIDDEN, "承运商不属于平台公共主数据");
if (!"ACTIVE".equals(value.status()))
throw new BizException(ResultCode.CONFLICT, "承运商已停用");
boolean hasCarrierType = value.types() != null && value.types().contains("CARRIER");
boolean legacyCarrierType = (value.types() == null || value.types().isEmpty())
&& "CARRIER".equals(value.partnerType());
if (!hasCarrierType && !legacyCarrierType)
throw new BizException(ResultCode.VALIDATION_FAILED, "所选往来单位不具备承运商类型");
if (!StringUtils.hasText(value.partnerName())) throw unavailable("承运商");
return value;
} catch (FeignException exception) {
throw downstreamFailure(exception);
} catch (BizException exception) {
throw exception;
} catch (RuntimeException exception) {
throw downstreamFailure(exception);
}
}
private PartnerClient.Partner loadPartner(long id) {
ApiResponse<PartnerClient.Partner> response;
try {
@@ -31,7 +31,8 @@ import java.util.stream.Collectors;
@Service
public class UnifiedOrderQueryService {
private static final Set<String> MAIN_STATUSES = Set.of(
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
private static final String LEGACY_WAIT_SPLIT = "WAIT_SPLIT";
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
private static final List<OrderStatus> ACTIVE_ORDER_STATUSES = List.copyOf(EnumSet.of(
OrderStatus.SUBMITTED, OrderStatus.APPROVED, OrderStatus.ACCEPTED,
@@ -92,7 +93,7 @@ public class UnifiedOrderQueryService {
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);
}
FirstlegUnifiedOrderClient.Query request = new FirstlegUnifiedOrderClient.Query(
tenantId, query.keyword(), query.orderNo(), query.mainStatus(), query.customerId(),
tenantId, query.keyword(), query.orderNo(), canonicalMainStatus(query.mainStatus()), query.customerId(),
query.receivingWarehouseId(), query.destinationWarehouseId(), query.inboundCode(),
query.internalOrderStatus(), query.expectedArrivalDate(), query.feeReviewStatus(), query.channelName(),
query.containerNo(), query.cabinetNo(), query.sailingDate(), query.loaded(), query.fullContainer(),
@@ -199,8 +200,7 @@ public class UnifiedOrderQueryService {
if (status == null) return null;
return switch (status) {
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED" -> "WAIT_MEASURE";
case "WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "INBOUNDED" -> "INBOUNDED";
case "WAIT_SPLIT" -> "WAIT_SPLIT";
case "WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING", "INBOUNDED", LEGACY_WAIT_SPLIT -> "INBOUNDED";
case "LOADING", "LOADED", "DELIVERED" -> status;
default -> null;
};
@@ -211,7 +211,9 @@ public class UnifiedOrderQueryService {
}
private void validate(UnifiedOrderQuery query) {
if (StringUtils.hasText(query.mainStatus()) && !MAIN_STATUSES.contains(query.mainStatus())) {
if (StringUtils.hasText(query.mainStatus())
&& !MAIN_STATUSES.contains(query.mainStatus())
&& !LEGACY_WAIT_SPLIT.equals(query.mainStatus())) {
throw new BizException(ResultCode.BAD_REQUEST, "统一订单主状态不合法");
}
if (query.createdFrom() != null && query.createdTo() != null
@@ -241,6 +243,10 @@ public class UnifiedOrderQueryService {
|| StringUtils.hasText(query.measurementReviewStatus());
}
private static String canonicalMainStatus(String status) {
return LEGACY_WAIT_SPLIT.equals(status) ? "INBOUNDED" : status;
}
private <T> ApiResponse<T> call(java.util.concurrent.Callable<ApiResponse<T>> invocation) {
try { return invocation.call(); }
catch (FeignException exception) { throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "头程服务暂时不可用"); }
@@ -311,7 +311,7 @@ components:
required: false
schema:
type: string
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, WAIT_SPLIT, LOADING, LOADED, DELIVERED]
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED]
UnifiedInboundCode:
{name: inboundCode, in: query, required: false, description: 客户提交后生成的入仓码,待审核订单同样可查询, schema: {type: string, maxLength: 40}}
UnifiedInternalOrderStatus:
@@ -25,7 +25,7 @@ class UnifiedOrderApiContractTest {
}
@Test
void documentsPhaseTwoWaitSplitStatus() throws Exception {
void documentsInboundedAsThePhaseTwoTerminalStatus() throws Exception {
String yaml = new ClassPathResource("openapi/order-v1.yaml")
.getContentAsString(StandardCharsets.UTF_8);
Map<String, Object> document = stringMap(new Yaml().load(yaml));
@@ -39,8 +39,7 @@ class UnifiedOrderApiContractTest {
Map<String, Object> mainStatus = stringMap(parameters.get("UnifiedMainStatus"));
Map<String, Object> schema = stringMap(mainStatus.get("schema"));
assertThat(stringList(schema.get("enum"))).containsExactly(
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT",
"LOADING", "LOADED", "DELIVERED");
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
}
@Test
@@ -45,31 +45,6 @@ class OrderMasterDataGuardTest {
assertThat(result.receiving().warehouse().warehouseName()).isEqualTo("深圳仓");
}
@Test
void carrierValidationReturnsTrustedPublicCarrierSnapshot() {
var carrier = new PartnerClient.Partner(9L, PUBLIC_TENANT_ID, "服务端船司名", "ACTIVE", "CARRIER", List.of("SUPPLIER", "CARRIER"));
when(partners.active("token", PUBLIC_TENANT_ID, 9L)).thenReturn(ApiResponse.success(carrier));
assertThat(guard.validateCarrier(9L).partnerName()).isEqualTo("服务端船司名");
}
@Test
void carrierValidationSupportsLegacySingleTypeSnapshot() {
var carrier = new PartnerClient.Partner(9L, PUBLIC_TENANT_ID, "旧数据船司", "ACTIVE", "CARRIER", null);
when(partners.active("token", PUBLIC_TENANT_ID, 9L)).thenReturn(ApiResponse.success(carrier));
assertThat(guard.validateCarrier(9L).partnerName()).isEqualTo("旧数据船司");
}
@Test
void partnerWithoutCarrierTypeCannotBeAssignedToOrder() {
var supplier = new PartnerClient.Partner(9L, PUBLIC_TENANT_ID, "普通供应商", "ACTIVE", "SUPPLIER", List.of("SUPPLIER"));
when(partners.active("token", PUBLIC_TENANT_ID, 9L)).thenReturn(ApiResponse.success(supplier));
assertThatThrownBy(() -> guard.validateCarrier(9L))
.isInstanceOf(BizException.class)
.hasMessageContaining("不具备承运商类型")
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
}
@Test
void inactiveOrCrossTenantMasterDataIsRejected() {
when(partners.active("token",PUBLIC_TENANT_ID,7)).thenReturn(ApiResponse.success(null));
assertThatThrownBy(() -> guard.validate(10002,request(7L,8L)))
@@ -255,7 +230,7 @@ class OrderMasterDataGuardTest {
var cargo=new OrderRequests.Cargo("货物",null,"CARTON",1,BigDecimal.ONE,BigDecimal.ONE,
null,null,null,null,null,false,null,null,null,null,null,null,null,null,null,null,null,null,null,
null,null,null,null,null,null);
return new OrderRequests.SaveRequest(partner,"不可信客户名","SEA",null,"CN",destinationWarehouseId,"私人地址",null,null,party,party,List.of(cargo),"PICKUP",warehouse,"不可信收货仓","CH","渠道",9L,"船司","BUY_DECLARATION",null,"LCL",false,destinationType,null,null,null);
return new OrderRequests.SaveRequest(partner,"不可信客户名","SEA",null,"CN",destinationWarehouseId,"私人地址",null,null,party,party,List.of(cargo),"PICKUP",warehouse,"不可信收货仓","CH","渠道",null,null,"BUY_DECLARATION",null,"LCL",false,destinationType,null,null,null);
}
private WarehouseClient.Detail warehouse(long id,String type,boolean receiving) {
var value=new WarehouseClient.Warehouse(id,PUBLIC_TENANT_ID,"WH-"+id,"仓库"+id,type,"CN","ACTIVE");
@@ -141,9 +141,9 @@ class UnifiedOrderQueryServiceTest {
}
@Test
void waitSplitFilterForwardsExactStatusAndPreservesSummaryForMerchantScope() {
void historicalWaitSplitFilterForwardsInboundedAndReturnsInbounded() {
UnifiedOrderQuery query = query("WAIT_SPLIT");
var summary = summary("WAIT_SPLIT", null);
var summary = summary("INBOUNDED", null);
when(firstleg.page(eq("internal-token"), any()))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(summary), 1, 20, 1, 1)));
when(orders.findByIds(eq(10001L), eq(List.of(77L)))).thenReturn(List.of(order(OrderStatus.PROCESSING)));
@@ -151,18 +151,18 @@ class UnifiedOrderQueryServiceTest {
var result = service.page(10001L, query);
assertThat(result.records()).singleElement()
.extracting(row -> row.mainStatus()).isEqualTo("WAIT_SPLIT");
.extracting(row -> row.mainStatus()).isEqualTo("INBOUNDED");
ArgumentCaptor<FirstlegUnifiedOrderClient.Query> captor =
ArgumentCaptor.forClass(FirstlegUnifiedOrderClient.Query.class);
verify(firstleg).page(eq("internal-token"), captor.capture());
assertThat(captor.getValue().tenantId()).isEqualTo(10001L);
assertThat(captor.getValue().mainStatus()).isEqualTo("WAIT_SPLIT");
assertThat(captor.getValue().mainStatus()).isEqualTo("INBOUNDED");
}
@Test
void fulfillmentTabsForwardStatusSearchAndPaginationToFirstleg() {
List<String> statuses = List.of(
"WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
"WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
when(firstleg.page(eq("internal-token"), any()))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(), 3, 20, 0, 0)));