feat: complete dropship palletization workflow
This commit is contained in:
@@ -88,7 +88,10 @@ class InternalOrderCargoControllerTest {
|
||||
@Test
|
||||
void listsCargoInRepositoryOrderForStableCartonAllocation() {
|
||||
OrderCargoEntity first = cargo(31L, 1, 3, "60", "40", "30");
|
||||
first.setUnitsPerPackage(24);
|
||||
OrderCargoEntity second = cargo(32L, 2, 2, "50", "30", "20");
|
||||
OrderEntity order = new OrderEntity(); order.setId(21L); order.setFulfillmentType("STANDARD");
|
||||
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of(first, second));
|
||||
|
||||
var response = controller.listForCartonLabels("internal-token", 11L, 21L);
|
||||
@@ -97,10 +100,68 @@ class InternalOrderCargoControllerTest {
|
||||
assertThat(response.data()).extracting(InternalOrderCargoController.CargoLabelBinding::cargoId)
|
||||
.containsExactly(31L, 32L);
|
||||
assertThat(response.data().get(0).packageCount()).isEqualTo(3);
|
||||
assertThat(response.data().get(0).unitsPerCarton()).isEqualTo(24);
|
||||
assertThat(response.data().get(1).unitsPerCarton()).isNull();
|
||||
assertThat(response.data().get(0).lengthCm()).isEqualByComparingTo("60");
|
||||
verify(orders).findOwned(11L, 21L);
|
||||
verify(orders).findCargoInheritedByOrder(11L, 21L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneClickDropshipReturnsAuthoritativePositiveUnitsPerCarton() {
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(21L); order.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
OrderCargoEntity cargo = cargo(31L, 1, 3, "60", "40", "30");
|
||||
cargo.setUnitsPerPackage(12);
|
||||
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of(cargo));
|
||||
|
||||
var response = controller.listForCartonLabels("internal-token", 11L, 21L);
|
||||
|
||||
assertThat(response.data()).singleElement()
|
||||
.extracting(InternalOrderCargoController.CargoLabelBinding::unitsPerCarton)
|
||||
.isEqualTo(12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneClickDropshipFailsClosedWhenUnitsPerCartonIsMissing() {
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(21L); order.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||
when(orders.findCargoInheritedByOrder(11L, 21L))
|
||||
.thenReturn(List.of(cargo(31L, 1, 3, "60", "40", "30")));
|
||||
|
||||
assertThatThrownBy(() -> controller.listForCartonLabels("internal-token", 11L, 21L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("每箱件数缺失或无效");
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneClickDropshipFailsClosedWhenUnitsPerCartonIsNotPositive() {
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(21L); order.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
OrderCargoEntity cargo = cargo(31L, 1, 3, "60", "40", "30");
|
||||
cargo.setUnitsPerPackage(0);
|
||||
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of(cargo));
|
||||
|
||||
assertThatThrownBy(() -> controller.listForCartonLabels("internal-token", 11L, 21L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("每箱件数缺失或无效");
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonLabelListDoesNotExposeCargoWhenOrderIsOutsideCustomerScope() {
|
||||
when(orders.findOwned(12L, 21L)).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> controller.listForCartonLabels("internal-token", 12L, 21L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("订单不存在或不属于当前客户");
|
||||
|
||||
verify(orders).findOwned(12L, 21L);
|
||||
verifyNoMoreInteractions(orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void childOrderCanInheritRootCargoBindingWithoutCrossCustomerLeak() {
|
||||
OrderCargoEntity cargo = cargo(31L, 1, 3, "60", "40", "30");
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.baiyue.order.controller;
|
||||
|
||||
import com.baiyue.order.client.FirstlegUnifiedOrderClient;
|
||||
import com.baiyue.order.client.FirstlegColoadGroupClient;
|
||||
import com.baiyue.order.dto.UnifiedOrderQuery;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
@@ -26,7 +28,7 @@ class UnifiedOrderApiContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void documentsSevenProductMainStatuses() throws Exception {
|
||||
void documentsEightProductMainStatuses() throws Exception {
|
||||
String yaml = new ClassPathResource("openapi/order-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
Map<String, Object> document = stringMap(new Yaml().load(yaml));
|
||||
@@ -40,22 +42,32 @@ 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", "LOADING", "LOADED", "DELIVERED", "WAIT_SPLIT");
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_PALLETIZE", "INBOUNDED", "WAIT_SPLIT",
|
||||
"LOADING", "LOADED", "DELIVERED");
|
||||
assertThat(mainStatus.get("description").toString())
|
||||
.contains("WAIT_SPLIT", "INBOUNDED");
|
||||
.contains("WAIT_PALLETIZE", "WAIT_SPLIT", "INBOUNDED");
|
||||
|
||||
Map<String, Object> schemas = stringMap(components.get("schemas"));
|
||||
Map<String, Object> responseStatus = stringMap(schemas.get("UnifiedMainStatus"));
|
||||
assertThat(stringList(responseStatus.get("enum"))).containsExactly(
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
|
||||
assertThat(responseStatus.get("description").toString()).contains("WAIT_SPLIT", "INBOUNDED");
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_PALLETIZE", "INBOUNDED", "WAIT_SPLIT",
|
||||
"LOADING", "LOADED", "DELIVERED");
|
||||
assertThat(responseStatus.get("description").toString())
|
||||
.contains("WAIT_PALLETIZE", "WAIT_SPLIT", "INBOUNDED");
|
||||
Schema queryAnnotation = UnifiedOrderQuery.class.getDeclaredField("mainStatus")
|
||||
.getAnnotation(Schema.class);
|
||||
assertThat(queryAnnotation).isNotNull();
|
||||
assertThat(queryAnnotation.allowableValues()).containsExactly(
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_PALLETIZE", "INBOUNDED", "WAIT_SPLIT",
|
||||
"LOADING", "LOADED", "DELIVERED");
|
||||
|
||||
Map<String, Object> row = stringMap(schemas.get("UnifiedOrderRow"));
|
||||
assertThat(stringMap(row.get("properties"))).containsKeys(
|
||||
"orderId", "mainStatus", "channelCode", "inboundId", "inboundVersion", "measurement", "preload",
|
||||
"loading", "delivery", "transport", "customerRemark", "latestInternalNote",
|
||||
"holdFlag", "currentHoldReason",
|
||||
"expectedArrivalAt", "requestedPickupAt", "feeReviewStatus", "companyName", "businessType", "pickupMethod",
|
||||
"expectedArrivalAt", "requestedPickupAt", "feeReviewStatus", "companyName", "businessType",
|
||||
"fulfillmentType", "lxCode", "pickupMethod",
|
||||
"taxMode", "containerMode", "customsInspection", "destinationType", "destinationCode",
|
||||
"totalPalletCount", "chargeableWeightKg", "chargeableVolumeCbm", "volumetricWeightKg", "totalNetWeightKg",
|
||||
"measurementCompletedAt", "inboundCompletedAt", "expressCompany", "expressTrackingNo",
|
||||
@@ -86,7 +98,7 @@ class UnifiedOrderApiContractTest {
|
||||
assertThat(page.getAnnotation(PostMapping.class).value()).containsExactly("/query");
|
||||
assertThat(List.of(FirstlegUnifiedOrderClient.Inbound.class.getRecordComponents()).stream()
|
||||
.map(component -> component.getName()))
|
||||
.contains("channelCode", "channelName", "holdFlag", "currentHoldReason")
|
||||
.contains("channelCode", "channelName", "holdFlag", "currentHoldReason", "fulfillmentType", "lxCode")
|
||||
.doesNotContain("holdCargo", "holdReason");
|
||||
}
|
||||
|
||||
|
||||
@@ -549,6 +549,70 @@ class UnifiedOrderQueryServiceTest {
|
||||
assertThat(captor.getValue().mainStatus()).isEqualTo("WAIT_SPLIT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitPalletizeFilterReturnsDropshipProjectionAndForwardsToFirstleg() {
|
||||
UnifiedOrderQuery query = query("WAIT_PALLETIZE");
|
||||
var summary = summary("WAIT_PALLETIZE", "PALLETIZE_DROPSHIP");
|
||||
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)));
|
||||
|
||||
var result = service.page(10001L, query);
|
||||
|
||||
assertThat(result.records()).singleElement().satisfies(row -> {
|
||||
assertThat(row.mainStatus()).isEqualTo("WAIT_PALLETIZE");
|
||||
assertThat(row.nextAction()).isEqualTo("PALLETIZE_DROPSHIP");
|
||||
assertThat(row.fulfillmentType()).isEqualTo("ONE_CLICK_DROPSHIP");
|
||||
assertThat(row.lxCode()).isEqualTo("LX-20260908-01");
|
||||
});
|
||||
ArgumentCaptor<FirstlegUnifiedOrderClient.Query> captor =
|
||||
ArgumentCaptor.forClass(FirstlegUnifiedOrderClient.Query.class);
|
||||
verify(firstleg).page(eq("internal-token"), captor.capture());
|
||||
assertThat(captor.getValue().mainStatus()).isEqualTo("WAIT_PALLETIZE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderOnlyProjectionUsesExistingFulfillmentFieldsWithoutCreatingInboundFacts() {
|
||||
UnifiedOrderQuery query = query("PENDING_REVIEW");
|
||||
OrderEntity order = order(OrderStatus.SUBMITTED);
|
||||
order.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
order.setLxCode("LX-ORDER-01");
|
||||
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
||||
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
||||
|
||||
assertThat(service.page(10001L, query).records()).singleElement().satisfies(row -> {
|
||||
assertThat(row.mainStatus()).isEqualTo("PENDING_REVIEW");
|
||||
assertThat(row.fulfillmentType()).isEqualTo("ONE_CLICK_DROPSHIP");
|
||||
assertThat(row.lxCode()).isEqualTo("LX-ORDER-01");
|
||||
assertThat(row.inboundId()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailMapsWaitPalletizeSummaryAndTimeline() {
|
||||
OrderEntity order = order(OrderStatus.PROCESSING);
|
||||
when(orders.findOwned(10001L, 77L)).thenReturn(order);
|
||||
when(orders.findCargo(10001L, 77L)).thenReturn(List.of());
|
||||
when(orders.findParties(10001L, 77L)).thenReturn(List.of());
|
||||
when(orders.findNotes(10001L, 77L)).thenReturn(List.of());
|
||||
when(orders.findHistory(10001L, 77L)).thenReturn(List.of());
|
||||
var timeline = new FirstlegUnifiedOrderClient.Timeline(
|
||||
"FIRSTLEG", "STATUS", "WAIT_MEASURE", "WAIT_PALLETIZE", "MEASUREMENT_APPROVED",
|
||||
null, 9L, "平台操作员", LocalDateTime.of(2026, 9, 8, 10, 0));
|
||||
var summary = summary("WAIT_PALLETIZE", "PALLETIZE_DROPSHIP");
|
||||
when(firstleg.detail(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new FirstlegUnifiedOrderClient.Detail(summary, List.of(timeline))));
|
||||
|
||||
var result = service.detail(10001L, 77L, false);
|
||||
|
||||
assertThat(result.summary().mainStatus()).isEqualTo("WAIT_PALLETIZE");
|
||||
assertThat(result.summary().fulfillmentType()).isEqualTo("ONE_CLICK_DROPSHIP");
|
||||
assertThat(result.summary().lxCode()).isEqualTo("LX-20260908-01");
|
||||
assertThat(result.timeline()).singleElement()
|
||||
.extracting(UnifiedOrderResponse.TimelineEvent::mainStatus)
|
||||
.isEqualTo("WAIT_PALLETIZE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void listKeepsLoadedFulfillmentAheadOfRawApprovedOrderStatus() {
|
||||
UnifiedOrderQuery query = query(null);
|
||||
@@ -728,7 +792,7 @@ class UnifiedOrderQueryServiceTest {
|
||||
@Test
|
||||
void fulfillmentTabsForwardStatusSearchAndPaginationToFirstleg() {
|
||||
List<String> statuses = List.of(
|
||||
"WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
|
||||
"WAIT_MEASURE", "WAIT_PALLETIZE", "INBOUNDED", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
|
||||
when(firstleg.page(eq("internal-token"), any()))
|
||||
.thenReturn(ApiResponse.success(new PageResult<>(List.of(), 3, 20, 0, 0)));
|
||||
|
||||
@@ -840,6 +904,8 @@ class UnifiedOrderQueryServiceTest {
|
||||
when(inbound.locationCodes()).thenReturn("A0-01, B2-03");
|
||||
when(inbound.companyName()).thenReturn("测试客户有限公司");
|
||||
when(inbound.businessType()).thenReturn("SEA_LCL");
|
||||
when(inbound.fulfillmentType()).thenReturn("ONE_CLICK_DROPSHIP");
|
||||
when(inbound.lxCode()).thenReturn("LX-20260908-01");
|
||||
when(inbound.palletCount()).thenReturn(2);
|
||||
when(inbound.actualGrossWeightKg()).thenReturn(new BigDecimal("1787.500"));
|
||||
when(inbound.actualNetWeightKg()).thenReturn(new BigDecimal("1747.500"));
|
||||
|
||||
Reference in New Issue
Block a user