优化
This commit is contained in:
@@ -50,6 +50,13 @@ class RelabelContractTest {
|
||||
"source_warehouse_code", "source_warehouse_name", "idx_ll_relabel_item_source_position" }) {
|
||||
assertTrue(deployment.contains(field), () -> "deployment SQL must include " + field);
|
||||
}
|
||||
for (String field : new String[] { "delivery_method", "print_shipping_label", "shipping_label_file_id",
|
||||
"shipping_label_file_name", "shipping_label_file_url", "fulfillment_status", "outbound_order_no",
|
||||
"customer_confirmation_key", "customer_confirmed_at" }) {
|
||||
assertTrue(baseline.contains(field), () -> "baseline SQL must include " + field);
|
||||
assertTrue(migration.contains(field), () -> "migration SQL must include " + field);
|
||||
assertTrue(deployment.contains(field), () -> "deployment SQL must include " + field);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,6 +70,19 @@ class RelabelContractTest {
|
||||
assertTrue(openApi.contains("platform:overseas-warehouse:relabel-order:list"));
|
||||
assertTrue(openApi.contains("platform:overseas-warehouse:relabel-order:create"));
|
||||
assertTrue(openApi.contains("platform:overseas-warehouse:relabel-order:status"));
|
||||
for (String path : new String[] {
|
||||
"/api/merchant/v1/last-leg/relabel-applications/inventory-candidates:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications/{relabelNo}:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications/{relabelNo}/shipping-label:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications/{relabelNo}/shipping-label/{fileId}/download:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications/{relabelNo}/completion-photos/{fileId}/download:",
|
||||
"/api/merchant/v1/last-leg/relabel-applications/{relabelNo}/confirm-completion:" }) {
|
||||
assertTrue(openApi.contains(path), () -> "merchant relabel path missing: " + path);
|
||||
}
|
||||
assertTrue(openApi.contains("RelabelMerchantPageResponse"));
|
||||
assertTrue(openApi.contains("RelabelCustomerConfirm"));
|
||||
assertTrue(openApi.contains("merchant:warehouse:relabel"));
|
||||
String relabelPaths = openApi.substring(openApi.indexOf("/api/platform/v1/last-leg/relabel-applications:"),
|
||||
openApi.indexOf("components:"));
|
||||
assertTrue(relabelPaths.contains("name: customerId, in: query, required: true"));
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ class PdaRelabelServiceTest {
|
||||
new PdaRelabelRequests.Review("APPROVE", 2, "review-1", "ok"));
|
||||
|
||||
assertEquals("COMPLETED", result.taskStatus());
|
||||
assertEquals("COMPLETED", result.applicationStatus());
|
||||
assertEquals(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, result.applicationStatus());
|
||||
verify(applicationService).changeStatus(eq("100"), eq("FBR-1"), eq("8"), any(RelabelRequests.ChangeStatus.class));
|
||||
}
|
||||
|
||||
|
||||
+88
-2
@@ -22,6 +22,12 @@ import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPdaTask;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaTaskMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -60,6 +66,9 @@ class RelabelApplicationServiceTest {
|
||||
@Mock InventoryCommandService inventoryCommands;
|
||||
@Mock InventoryBalanceMapper inventoryBalances;
|
||||
@Mock InventoryPositionMapper inventoryPositions;
|
||||
@Mock OutboundOrderMapper outboundOrders;
|
||||
@Mock OutboundPalletItemMapper outboundItems;
|
||||
@Mock OutboundPdaTaskMapper outboundTasks;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
private RelabelApplicationService service;
|
||||
@@ -77,6 +86,9 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
|
||||
ReflectionTestUtils.setField(service, "inventoryBalances", inventoryBalances);
|
||||
ReflectionTestUtils.setField(service, "inventoryPositions", inventoryPositions);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "outboundItems", outboundItems);
|
||||
ReflectionTestUtils.setField(service, "outboundTasks", outboundTasks);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", mapper());
|
||||
lenient().when(walletCommands.publish(any(), any(Long.class), any(), any(), any(), any(), any(Long.class))).thenReturn("wallet-event-1");
|
||||
lenient().when(inventoryPositions.findTenantPosition(any(), any())).thenAnswer(invocation -> {
|
||||
@@ -344,7 +356,7 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
@Test
|
||||
void completedReturnRelabelOrderEntersPendingAssignment() {
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", "PROCESSING");
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", RelabelApplicationService.READY_FOR_OUTBOUND);
|
||||
application.setLastlegOrderId(21L);
|
||||
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-1", "PROCESSING");
|
||||
order.setOrderType("RETURN_RELABEL");
|
||||
@@ -362,7 +374,7 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
@Test
|
||||
void completingPartialRelabelReleasesOnlyNotifiedPackages() {
|
||||
RelabelApplication application = application(11L, "FBR-PARTIAL", "10001", "key-partial", "hash", "PROCESSING");
|
||||
RelabelApplication application = application(11L, "FBR-PARTIAL", "10001", "key-partial", "hash", RelabelApplicationService.READY_FOR_OUTBOUND);
|
||||
application.setLastlegOrderId(21L);
|
||||
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-PARTIAL", "PROCESSING");
|
||||
when(applications.lockNo("10001", "FBR-PARTIAL")).thenReturn(application);
|
||||
@@ -380,6 +392,80 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals(6L, move.getValue().packages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void truckCustomerConfirmationIsIdempotentAndWaitsForPreDispatch() {
|
||||
RelabelApplication application = confirmationApplication("TRUCK", false);
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||
stubDetail(application);
|
||||
|
||||
service.confirmCompletion("10001", "FBR-1", "customer-1",
|
||||
new RelabelRequests.CustomerConfirm("confirm-1", "photos accepted"));
|
||||
|
||||
assertEquals(RelabelApplicationService.READY_FOR_OUTBOUND, application.getStatus());
|
||||
assertEquals("WAIT_PRE_DISPATCH", application.getFulfillmentStatus());
|
||||
assertNull(application.getOutboundOrderNo());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
|
||||
service.confirmCompletion("10001", "FBR-1", "customer-1",
|
||||
new RelabelRequests.CustomerConfirm("confirm-1", "photos accepted"));
|
||||
verify(applications, times(1)).updateById(application);
|
||||
}
|
||||
|
||||
@Test
|
||||
void expressCustomerConfirmationRequiresShippingLabelWhenWarehousePrintingIsSelected() {
|
||||
RelabelApplication application = confirmationApplication("EXPRESS", true);
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.confirmCompletion("10001", "FBR-1", "customer-1",
|
||||
new RelabelRequests.CustomerConfirm("confirm-express", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
verify(applications, never()).updateById(application);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pickupCustomerConfirmationCreatesOneExactCartonOutbound() {
|
||||
RelabelApplication application = confirmationApplication("PICKUP", false);
|
||||
RelabelItem source = item(31L, application.getId(), "SKU-A", 1, 1);
|
||||
source.setSourceInventoryPositionId(201L);
|
||||
source.setSourceInboundCode("CODE-1");
|
||||
source.setCartonMark("BOX-1");
|
||||
source.setDestinationFbaWarehouseCode("ONT8");
|
||||
InventoryPosition position = new InventoryPosition();
|
||||
position.setId(201L); position.setCustomerId("10001"); position.setPalletId(501L); position.setPalletNo("PALLET-1");
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||
when(applications.findNo("10001", "FBR-1")).thenReturn(application);
|
||||
when(items.selectList(any())).thenReturn(List.of(source));
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(orders.selectById(21L)).thenReturn(order(21L, "10001", "LMO-RELABEL-1", "PENDING_ASSIGNMENT"));
|
||||
when(inventoryPositions.selectById(201L)).thenReturn(position);
|
||||
|
||||
service.confirmCompletion("10001", "FBR-1", "customer-1",
|
||||
new RelabelRequests.CustomerConfirm("confirm-pickup", null));
|
||||
|
||||
assertEquals("OUTBOUND_CREATED", application.getFulfillmentStatus());
|
||||
assertNotNull(application.getOutboundOrderNo());
|
||||
ArgumentCaptor<OutboundOrder> outbound = ArgumentCaptor.forClass(OutboundOrder.class);
|
||||
verify(outboundOrders, times(1)).insert(outbound.capture());
|
||||
assertEquals("RELABEL_OUTBOUND", outbound.getValue().getOutboundType());
|
||||
ArgumentCaptor<OutboundPalletItem> outboundItem = ArgumentCaptor.forClass(OutboundPalletItem.class);
|
||||
verify(outboundItems, times(1)).insert(outboundItem.capture());
|
||||
assertEquals("BOX-1", outboundItem.getValue().getCartonMarks());
|
||||
assertEquals("PALLET-1", outboundItem.getValue().getPalletNo());
|
||||
verify(outboundTasks, times(1)).insert(any(OutboundPdaTask.class));
|
||||
}
|
||||
|
||||
private RelabelApplication confirmationApplication(String deliveryMethod, boolean printShippingLabel) {
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash",
|
||||
RelabelApplicationService.CUSTOMER_CONFIRM_PENDING);
|
||||
application.setLastlegOrderId(21L);
|
||||
application.setDeliveryMethod(deliveryMethod);
|
||||
application.setPrintShippingLabel(printShippingLabel);
|
||||
return application;
|
||||
}
|
||||
|
||||
private void stubDetail(RelabelApplication application) {
|
||||
when(applications.findNo(application.getCustomerId(), application.getRelabelNo())).thenReturn(application);
|
||||
when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 10, 6)));
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.baiyue.lastleg.relabel.service;
|
||||
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelItem;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RelabelPalletMutationServiceTest {
|
||||
@Mock private RelabelItemMapper relabelItems;
|
||||
@Mock private InventoryPositionMapper positions;
|
||||
@Mock private OutboundOrderMapper outboundOrders;
|
||||
@Mock private OutboundPalletItemMapper outboundItems;
|
||||
private RelabelPalletMutationService service;
|
||||
|
||||
@BeforeEach void setUp() {
|
||||
service = new RelabelPalletMutationService();
|
||||
ReflectionTestUtils.setField(service, "relabelItems", relabelItems);
|
||||
ReflectionTestUtils.setField(service, "positions", positions);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "outboundItems", outboundItems);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
}
|
||||
|
||||
@Test void partialCartonMoveSplitsOutboundSnapshotAndKeepsUnselectedCartonOnSource() {
|
||||
RelabelApplication application = new RelabelApplication(); application.setId(10L);
|
||||
application.setCustomerId("100"); application.setRelabelNo("RL-1"); application.setOutboundOrderNo("OUT-1");
|
||||
RelabelItem selected = item(20L, "CARTON-A", 101L);
|
||||
RelabelItem remaining = item(21L, "CARTON-B", 100L);
|
||||
InventoryPosition target = new InventoryPosition(); target.setId(101L); target.setPalletId(11L); target.setPalletNo("TARGET");
|
||||
OutboundOrder outbound = new OutboundOrder(); outbound.setId(30L); outbound.setCustomerId("100");
|
||||
outbound.setOutboundNo("OUT-1"); outbound.setOutboundType("RELABEL_OUTBOUND"); outbound.setStatus("WAIT_PICK");
|
||||
OutboundPalletItem source = new OutboundPalletItem(); source.setId(31L); source.setCustomerId("100");
|
||||
source.setOutboundOrderId(30L); source.setWarehousePalletId(10L); source.setPalletNo("SOURCE");
|
||||
source.setPackageCount(2); source.setPickedPackageCount(0); source.setStatus("WAIT_PICK");
|
||||
source.setCartonMarks("CARTON-A,CARTON-B");
|
||||
when(outboundOrders.lockByNo("100", "OUT-1")).thenReturn(outbound);
|
||||
when(outboundItems.findByOutbound("100", 30L)).thenReturn(List.of(source));
|
||||
when(relabelItems.selectList(any())).thenReturn(List.of(remaining));
|
||||
|
||||
service.moveSelectedItems(application, Map.of(100L, List.of(selected)), Map.of(100L, target));
|
||||
|
||||
assertEquals(101L, selected.getSourceInventoryPositionId());
|
||||
verify(relabelItems).updateById(selected);
|
||||
assertEquals("CARTON-B", source.getCartonMarks()); assertEquals(1, source.getPackageCount());
|
||||
ArgumentCaptor<OutboundPalletItem> split = ArgumentCaptor.forClass(OutboundPalletItem.class);
|
||||
verify(outboundItems).insert(split.capture());
|
||||
assertEquals("CARTON-A", split.getValue().getCartonMarks());
|
||||
assertEquals(11L, split.getValue().getWarehousePalletId());
|
||||
assertEquals(1, split.getValue().getPackageCount());
|
||||
assertEquals(2, outbound.getPalletCount());
|
||||
}
|
||||
|
||||
private static RelabelItem item(long id, String mark, long positionId) {
|
||||
RelabelItem value = new RelabelItem(); value.setId(id); value.setApplicationId(10L); value.setCustomerId("100");
|
||||
value.setCartonMark(mark); value.setNotifiedPackageCount(1); value.setSourceInventoryPositionId(positionId);
|
||||
value.setDestinationFbaWarehouseCode("ONT8"); return value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user