贴标申请,仓储转运
This commit is contained in:
+34
@@ -66,6 +66,40 @@ class DeliveryCompletedOutboxServiceTest {
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipsWarehouseTransferWithoutDomesticSourceOrder() {
|
||||
LastlegEventOutboxMapper mapper = mock(LastlegEventOutboxMapper.class);
|
||||
DeliveryCompletedOutboxService service = new DeliveryCompletedOutboxService();
|
||||
ReflectionTestUtils.setField(service, "outbox", mapper);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().registerModule(new JavaTimeModule()));
|
||||
DeliveryOrder delivery = new DeliveryOrder();
|
||||
delivery.setId(2L); delivery.setDeliveryNo("DL-WT"); delivery.setVersion(1L);
|
||||
LastlegOrder transfer = new LastlegOrder();
|
||||
transfer.setId(22L); transfer.setOrderType("WAREHOUSE_TRANSFER");
|
||||
transfer.setCustomerId("88"); transfer.setSourceInboundId("WAREHOUSE_TRANSFER:WT-1");
|
||||
|
||||
service.publish(delivery, List.of(transfer), Instant.parse("2026-09-15T08:00:00Z"));
|
||||
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipsOverseasLocalOrderWithoutDomesticSourceOrder() {
|
||||
LastlegEventOutboxMapper mapper = mock(LastlegEventOutboxMapper.class);
|
||||
DeliveryCompletedOutboxService service = new DeliveryCompletedOutboxService();
|
||||
ReflectionTestUtils.setField(service, "outbox", mapper);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().registerModule(new JavaTimeModule()));
|
||||
DeliveryOrder delivery = new DeliveryOrder();
|
||||
delivery.setId(3L); delivery.setDeliveryNo("DL-LOCAL"); delivery.setVersion(1L);
|
||||
LastlegOrder local = new LastlegOrder();
|
||||
local.setId(23L); local.setOrderType("RETURN_RELABEL"); local.setSourceRegion("CUSTOMER");
|
||||
local.setCustomerId("88"); local.setSourceInboundId("RETURN-1");
|
||||
|
||||
service.publish(delivery, List.of(local), Instant.parse("2026-09-15T08:00:00Z"));
|
||||
|
||||
verifyNoInteractions(mapper);
|
||||
}
|
||||
|
||||
private LastlegOrder order(long id, String inboundId, String orderId, String inboundCode, String fba) {
|
||||
LastlegOrder value = new LastlegOrder();
|
||||
value.setId(id); value.setOrderNo("LM-" + id); value.setCustomerId("88");
|
||||
|
||||
+20
@@ -1,6 +1,8 @@
|
||||
package com.baiyue.lastleg.palletops.controller;
|
||||
|
||||
import com.baiyue.lastleg.palletops.dto.PalletOperationResponses;
|
||||
import com.baiyue.lastleg.palletops.mapper.PdaPalletOperationMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -8,6 +10,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -35,4 +38,21 @@ class PdaPalletOperationControllerContractTest {
|
||||
assertFalse(sql.contains("<"));
|
||||
assertFalse(sql.contains(">"));
|
||||
}
|
||||
|
||||
@Test void relabelSourceAndResolvedPalletSerializeSnowflakeIdsAsStrings() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
var source = new PalletOperationResponses.RelabelSource(2100204001227157507L, 2L,
|
||||
2100203890682015746L, "PLT-1", "LOC-1", "IN-1", 1L, 1L, List.of("C-1"));
|
||||
var content = new PalletOperationResponses.PalletContent(2100203890682015746L, "PLT-1",
|
||||
"OCCUPIED", 1, 2092783373163335682L, "LOC-1", null, 1L, List.of());
|
||||
|
||||
assertEquals("2100204001227157507", mapper.readTree(mapper.writeValueAsString(source))
|
||||
.path("positionId").textValue());
|
||||
assertEquals("2100203890682015746", mapper.readTree(mapper.writeValueAsString(source))
|
||||
.path("palletId").textValue());
|
||||
assertEquals("2100203890682015746", mapper.readTree(mapper.writeValueAsString(content))
|
||||
.path("palletId").textValue());
|
||||
assertEquals("2092783373163335682", mapper.readTree(mapper.writeValueAsString(content))
|
||||
.path("locationId").textValue());
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -9,6 +9,7 @@ import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
@@ -37,4 +38,23 @@ class HttpPalletPhysicalGatewayTest {
|
||||
.hasMessageContaining("404");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveIgnoresNewWarehousePalletMetadata() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/PAL-1"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"200","message":"success","data":{"palletId":"30","palletCode":"PAL-1",
|
||||
"warehouseId":"88","locationId":"40","status":"IDLE","fbaWarehouseCode":"XLX1",
|
||||
"unitPrice":1.00,"version":2,"locationType":"VIRTUAL","locationStatus":"ACTIVE",
|
||||
"reservable":true,"reservationBlockReason":null},
|
||||
"timestamp":"2026-09-19T03:18:35.283336500Z"}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
var result = gateway.resolve("PAL-1");
|
||||
|
||||
assertThat(result.palletId()).isEqualTo(30L);
|
||||
assertThat(result.palletCode()).isEqualTo("PAL-1");
|
||||
assertThat(result.fbaWarehouseCode()).isEqualTo("XLX1");
|
||||
server.verify();
|
||||
}
|
||||
}
|
||||
|
||||
+65
-2
@@ -18,6 +18,11 @@ import com.baiyue.lastleg.relabel.entity.RelabelItem;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.baiyue.lastleg.relabel.service.RelabelPalletMutationService;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryBalanceMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -62,6 +67,9 @@ class PdaPalletOperationServiceTest {
|
||||
@Mock private RelabelApplicationMapper relabelApplications;
|
||||
@Mock private RelabelItemMapper relabelItems;
|
||||
@Mock private RelabelPalletMutationService relabelPalletMutations;
|
||||
@Mock private ReturnInventoryCartonMapper returnCartons;
|
||||
@Mock private ReturnInventoryCartonItemMapper returnCartonItems;
|
||||
@Mock private ReturnInventoryBalanceMapper returnBalances;
|
||||
private PdaPalletOperationService service;
|
||||
|
||||
@BeforeEach void setUp() {
|
||||
@@ -75,10 +83,44 @@ class PdaPalletOperationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "relabelApplications", relabelApplications);
|
||||
ReflectionTestUtils.setField(service, "relabelItems", relabelItems);
|
||||
ReflectionTestUtils.setField(service, "relabelPalletMutations", relabelPalletMutations);
|
||||
ReflectionTestUtils.setField(service, "returnCartons", returnCartons);
|
||||
ReflectionTestUtils.setField(service, "returnCartonItems", returnCartonItems);
|
||||
ReflectionTestUtils.setField(service, "returnBalances", returnBalances);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
service.configureTransactions(new NoOpTransactionManager());
|
||||
}
|
||||
|
||||
@Test void partialReturnRelabelKeepsUnlabelledRemainderOnOriginalPallet() {
|
||||
RelabelApplication app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100"); app.setRelabelNo("RL-1");
|
||||
RelabelItem item = new RelabelItem(); item.setId(701L); item.setSkuCode("SKU-1");
|
||||
item.setReturnCartonId(801L); item.setDestinationFbaWarehouseCode("XLX6");
|
||||
ReturnInventoryCarton original = new ReturnInventoryCarton(); original.setId(801L);
|
||||
original.setCustomerId("100"); original.setOverseasWarehouseId("88");
|
||||
original.setTotalQuantity(100L); original.setPalletCode("OLD");
|
||||
ReturnInventoryCartonItem originalLine = new ReturnInventoryCartonItem();
|
||||
originalLine.setId(901L); originalLine.setSkuCode("SKU-1"); originalLine.setQuantity(100L);
|
||||
when(relabelItems.selectList(any())).thenReturn(List.of(item));
|
||||
when(returnCartonItems.lockCarton(801L)).thenReturn(List.of(originalLine));
|
||||
when(returnCartonItems.updateById(any(ReturnInventoryCartonItem.class))).thenReturn(1);
|
||||
when(returnCartons.releasePartial(801L, "100", 600L, 70L)).thenReturn(1);
|
||||
when(returnCartons.insert(any(ReturnInventoryCarton.class))).thenReturn(1);
|
||||
when(returnCartonItems.insert(any(ReturnInventoryCartonItem.class))).thenReturn(1);
|
||||
when(returnBalances.adjustCartonQuantity("100", "88", "SKU-1", 1)).thenReturn(1);
|
||||
when(relabelItems.updateById(any(RelabelItem.class))).thenReturn(1);
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "splitReturnRelabelCarton", app, item, original,
|
||||
pallet(30L, "NEW-XLX6", 88L, 0), 30L);
|
||||
|
||||
assertEquals(70L, originalLine.getQuantity());
|
||||
var moved = ArgumentCaptor.forClass(ReturnInventoryCarton.class);
|
||||
verify(returnCartons).insert(moved.capture());
|
||||
assertEquals(30L, moved.getValue().getTotalQuantity());
|
||||
assertEquals("NEW-XLX6", moved.getValue().getPalletCode());
|
||||
assertEquals("XLX6", moved.getValue().getDestinationWarehouseCode());
|
||||
assertEquals(moved.getValue().getId(), item.getReturnCartonId());
|
||||
verify(returnBalances).adjustCartonQuantity("100", "88", "SKU-1", 1);
|
||||
}
|
||||
|
||||
@Test void resolveRejectsPalletOutsideTokenFrozenWarehouse() {
|
||||
when(physical.resolve("P-1")).thenReturn(pallet(10L, "P-1", 99L, 1));
|
||||
assertThrows(ResponseStatusException.class, () -> service.resolve(identity(),
|
||||
@@ -100,7 +142,7 @@ class PdaPalletOperationServiceTest {
|
||||
RelabelApplication application = new RelabelApplication(); application.setRelabelNo("RL-1");
|
||||
application.setStatus("READY_FOR_OUTBOUND"); application.setOverseasWarehouseId("88");
|
||||
when(relabelApplications.findAnyByRelabelNo("RL-1")).thenReturn(application);
|
||||
when(physical.ensureRelabelPallet(88L, "RL-1:new-pallet-1")).thenReturn(
|
||||
when(physical.ensureRelabelPallet(88L, "RL-1:new-pallet-1", null)).thenReturn(
|
||||
new PalletPhysicalGateway.PalletReference(30L, "PAL-NEW-1", 88L, 1030L, "IDLE", null, 0));
|
||||
|
||||
var result = service.createRelabelPallet(identity(),
|
||||
@@ -358,6 +400,27 @@ class PdaPalletOperationServiceTest {
|
||||
assertEquals(java.util.Set.of(), ReflectionTestUtils.invokeMethod(service, "boundRelabelMarks", app));
|
||||
}
|
||||
|
||||
@Test void relabelSourcesExposeAuthoritativePalletId() {
|
||||
var tasks = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper.class);
|
||||
ReflectionTestUtils.setField(service, "relabelWorkTasks", tasks);
|
||||
RelabelApplication app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100");
|
||||
app.setRelabelNo("RL-1"); app.setOverseasWarehouseId("88"); app.setStatus("READY_FOR_OUTBOUND");
|
||||
app.setFulfillmentStatus("WAIT_PRE_DISPATCH");
|
||||
when(relabelApplications.findAnyByRelabelNo("RL-1")).thenReturn(app);
|
||||
RelabelItem item = relabelItem(701L, "CARTON-A", 100L);
|
||||
when(relabelItems.selectList(any())).thenReturn(List.of(item));
|
||||
when(positions.selectById(100L)).thenReturn(position(100L, 20L, "DISPLAY-CODE", 1L, 3L));
|
||||
PalletInventoryRow source = row(100L, 20L, 3L, 0L); source.setPalletNo("CANONICAL-CODE");
|
||||
source.setHoldPackages(1L);
|
||||
when(mapper.relabelSources("100", "88", 200L)).thenReturn(List.of(source));
|
||||
|
||||
var result = service.relabelSources(identity(), new PalletOperationRequests.RelabelSources("RL-1"));
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(20L, result.get(0).palletId());
|
||||
assertEquals("CANONICAL-CODE", result.get(0).palletCode());
|
||||
}
|
||||
|
||||
@Test void createsPalletBeforeCustomerConfirmationOnlyForClaimingOperator() {
|
||||
var tasks = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper.class);
|
||||
ReflectionTestUtils.setField(service, "relabelWorkTasks", tasks);
|
||||
@@ -366,7 +429,7 @@ class PdaPalletOperationServiceTest {
|
||||
when(relabelApplications.findAnyByRelabelNo("RL-1")).thenReturn(app);
|
||||
var task = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelTask(); task.setStatus("PROCESSING"); task.setOperatorKey("7");
|
||||
when(tasks.find("100", 600L)).thenReturn(task);
|
||||
when(physical.ensureRelabelPallet(88L, "RL-1:before-confirm")).thenReturn(pallet(30L, "NEW", 88L, 0));
|
||||
when(physical.ensureRelabelPallet(88L, "RL-1:before-confirm", null)).thenReturn(pallet(30L, "NEW", 88L, 0));
|
||||
assertEquals("NEW", service.createRelabelPallet(identity(), new PalletOperationRequests.NewRelabelPallet("RL-1", "before-confirm")).palletCode());
|
||||
task.setOperatorKey("other");
|
||||
assertThrows(ResponseStatusException.class, () -> service.createRelabelPallet(identity(), new PalletOperationRequests.NewRelabelPallet("RL-1", "different")));
|
||||
|
||||
@@ -117,4 +117,20 @@ class RelabelContractTest {
|
||||
assertTrue(service.contains("requiresOutbound && (!StringUtils.hasText(deliveryMethod)"
|
||||
+ " || !DELIVERY_METHODS.contains(deliveryMethod))"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnRelabelReviewStatusIsProjectedWithoutLosingPendingOrRejectedState() throws Exception {
|
||||
String service = Files.readString(Path.of("src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java"),
|
||||
StandardCharsets.UTF_8);
|
||||
assertTrue(service.contains("Set.of(PENDING_REVIEW, PENDING_PROCESSING, \"PROCESSING\", \"REJECTED\", \"CANCELLED\")"));
|
||||
assertTrue(service.contains("return workflowStatus;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnCartonConstraintsAllowRelabelReservationWithPositiveInventory() throws Exception {
|
||||
String baseline = Files.readString(Path.of("src/main/resources/db/lastleg.sql"), StandardCharsets.UTF_8)
|
||||
.replaceAll("\\s+", "").toUpperCase();
|
||||
assertTrue(baseline.contains("STATUSIN('AVAILABLE','RELABEL_PENDING','OUTBOUNDED')ANDSKU_TYPES>0ANDTOTAL_QUANTITY>0"));
|
||||
assertTrue(baseline.contains("STATUSIN('AVAILABLE','RELABEL_PENDING','MERGED','OUTBOUNDED')"));
|
||||
}
|
||||
}
|
||||
|
||||
+67
@@ -264,6 +264,73 @@ class PdaRelabelServiceTest {
|
||||
verify(unitFacts, never()).insert(any(PdaRelabelUnitFact.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnSkuTotalCompletesOneSkuOnceAndAllocatesAcrossSourcePalletItems() {
|
||||
application.setStatus("PROCESSING"); application.setNotifiedPackageCount(100);
|
||||
item.setOperationType("RETURN_BULK_SKU_LABEL"); item.setSkuCode("sku-fba100");
|
||||
item.setNotifiedPackageCount(50); item.setCartonMark("PALLET-A");
|
||||
RelabelItem second = item(); second.setId(11L); second.setOperationType("RETURN_BULK_SKU_LABEL");
|
||||
second.setSkuCode("sku-fba100"); second.setNotifiedPackageCount(50); second.setCartonMark("PALLET-B");
|
||||
PdaRelabelTask task = task("PROCESSING", 0); task.setExpectedPackages(100);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task);
|
||||
when(items.selectList(any())).thenReturn(List.of(item, second));
|
||||
|
||||
var result = service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("sku-fba100", 100, 0, "sku-total-1"));
|
||||
|
||||
assertEquals(100, result.relabelledPackages());
|
||||
assertEquals(2, persistedFacts.size());
|
||||
assertEquals(List.of(50, 50), persistedFacts.stream().map(PdaRelabelUnitFact::getQuantity).toList());
|
||||
assertEquals(List.of(10L, 11L), persistedFacts.stream().map(PdaRelabelUnitFact::getItemId).toList());
|
||||
assertTrue(persistedFacts.stream().allMatch(fact -> fact.getOldLabel() == null));
|
||||
assertEquals(1, result.version());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiSkuPartialReturnRelabelRequiresBothSkuTotalsBeforeCompletion() {
|
||||
verifyMultiSkuReturnCompletion(30, 15);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiSkuFullReturnRelabelRequiresBothSkuTotalsBeforeCompletion() {
|
||||
verifyMultiSkuReturnCompletion(50, 40);
|
||||
}
|
||||
|
||||
private void verifyMultiSkuReturnCompletion(int firstQuantity, int secondQuantity) {
|
||||
application.setStatus("PROCESSING");
|
||||
application.setNotifiedPackageCount(firstQuantity + secondQuantity);
|
||||
item.setOperationType("RETURN_BULK_SKU_LABEL"); item.setSkuCode("SKU-A");
|
||||
item.setNotifiedPackageCount(firstQuantity); item.setCartonMark("RC-A");
|
||||
RelabelItem second = item(); second.setId(11L);
|
||||
second.setOperationType("RETURN_BULK_SKU_LABEL"); second.setSkuCode("SKU-B");
|
||||
second.setNotifiedPackageCount(secondQuantity); second.setCartonMark("RC-B");
|
||||
PdaRelabelTask task = task("PROCESSING", 0);
|
||||
task.setExpectedPackages(firstQuantity + secondQuantity);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task);
|
||||
when(items.selectList(any())).thenReturn(List.of(item, second));
|
||||
|
||||
var first = service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("SKU-A", firstQuantity, 0, "multi-a"));
|
||||
assertEquals(firstQuantity, first.relabelledPackages());
|
||||
assertThrows(ResponseStatusException.class, () -> service.complete("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Complete(List.of(501L), 1, "multi-early", null)));
|
||||
assertThrows(ResponseStatusException.class, () -> service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("SKU-A", 1, 1, "multi-over")));
|
||||
|
||||
var both = service.completeReturnSkuTotal("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.BulkSkuTotal("SKU-B", secondQuantity, 1, "multi-b"));
|
||||
assertEquals(firstQuantity + secondQuantity, both.relabelledPackages());
|
||||
assertEquals(List.of("SKU-A", "SKU-B"), persistedFacts.stream()
|
||||
.map(fact -> fact.getItemId().equals(item.getId()) ? "SKU-A" : "SKU-B").toList());
|
||||
assertEquals(List.of(firstQuantity, secondQuantity), persistedFacts.stream()
|
||||
.map(PdaRelabelUnitFact::getQuantity).toList());
|
||||
var completed = service.complete("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Complete(List.of(501L), 2, "multi-complete", "multi SKU complete"));
|
||||
assertEquals(PdaRelabelService.WAIT_REVIEW, completed.taskStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeRequiresEveryUnitAndMovesOnlyPdaTaskToWaitReview() {
|
||||
application.setStatus("PROCESSING");
|
||||
|
||||
+12
@@ -30,6 +30,8 @@ import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaTaskMapper;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferOrderMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrder;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -75,6 +77,7 @@ class RelabelApplicationServiceTest {
|
||||
@Mock OutboundPdaTaskMapper outboundTasks;
|
||||
@Mock WarehouseTransferOrderMapper warehouseTransfers;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
@Mock ReturnOrderMapper returnOrders;
|
||||
@Mock com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper pdaTasks;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
@@ -103,6 +106,7 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "outboundItems", outboundItems);
|
||||
ReflectionTestUtils.setField(service, "outboundTasks", outboundTasks);
|
||||
ReflectionTestUtils.setField(service, "warehouseTransfers", warehouseTransfers);
|
||||
ReflectionTestUtils.setField(service, "returnOrders", returnOrders);
|
||||
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 -> {
|
||||
@@ -411,6 +415,10 @@ class RelabelApplicationServiceTest {
|
||||
when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 1, 1)));
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(orders.selectById(21L)).thenReturn(order);
|
||||
ReturnOrder returnOrder = new ReturnOrder();
|
||||
returnOrder.setId(41L); returnOrder.setCustomerId("10001");
|
||||
returnOrder.setRelabelApplicationId(application.getId()); returnOrder.setRelabelStatus("PROCESSING");
|
||||
when(returnOrders.selectOne(any())).thenReturn(returnOrder);
|
||||
|
||||
service.changeStatus("10001", "FBR-REVIEW", "operator-2",
|
||||
new RelabelRequests.ChangeStatus(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, "submitted"));
|
||||
@@ -419,6 +427,8 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals("WAIT_CUSTOMER_CONFIRMATION", application.getFulfillmentStatus());
|
||||
assertEquals("COMPLETED", order.getRelabelStatus());
|
||||
assertEquals("RELABEL:CUSTOMER_CONFIRM_PENDING", order.getSubOrderStatus());
|
||||
assertEquals(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, returnOrder.getRelabelStatus());
|
||||
verify(returnOrders).updateById(returnOrder);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -558,6 +568,7 @@ class RelabelApplicationServiceTest {
|
||||
new RelabelRequests.CustomerConfirm("confirm-transfer", "accepted"));
|
||||
|
||||
assertEquals("READY_FOR_DISPATCH", transfer.getStatus());
|
||||
assertEquals("WAIT_PRE_DISPATCH", application.getFulfillmentStatus());
|
||||
assertNotNull(transfer.getRelabelCompletedAt());
|
||||
assertEquals("READY_FOR_DISPATCH", transferOrder.getCurrentStatus());
|
||||
assertEquals("COMPLETED", transferOrder.getRelabelStatus());
|
||||
@@ -610,6 +621,7 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
assertEquals("ONT8", transfer.getFbaWarehouseCode());
|
||||
assertEquals("READY_FOR_DISPATCH", transfer.getStatus());
|
||||
assertEquals("WAIT_PRE_DISPATCH", application.getFulfillmentStatus());
|
||||
assertEquals("ONT8", transferOrder.getFbaWarehouseCode());
|
||||
assertEquals("READY_FOR_DISPATCH", transferOrder.getCurrentStatus());
|
||||
}
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ class RelabelReturnCartonCandidateTest {
|
||||
assertEquals("RETURN_CARTON", result.get(0).sourceType());
|
||||
assertEquals(9001L, result.get(0).returnCartonId());
|
||||
assertEquals("RC-WH-0001", result.get(0).cartonMark());
|
||||
assertEquals(12, result.get(0).availablePackages());
|
||||
assertEquals("SKU-RETURN", result.get(0).skuItems().get(0).skuCode());
|
||||
assertEquals(12, result.get(0).skuItems().get(0).quantity());
|
||||
}
|
||||
|
||||
+106
-5
@@ -4,6 +4,7 @@ import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
|
||||
import com.baiyue.lastleg.returnreceipt.dto.PdaReturnReceiptRequests;
|
||||
import com.baiyue.lastleg.returnreceipt.dto.ReturnOrderRequests;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrder;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnOrderItem;
|
||||
@@ -14,6 +15,14 @@ import com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptBatchMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptItemMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelItem;
|
||||
import com.baiyue.lastleg.service.FbaWarehouseValidator;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -53,6 +62,11 @@ class ReturnReceiptServiceTest {
|
||||
@Mock ReturnInventoryService inventory;
|
||||
@Mock LastlegOrderNoGenerator numbers;
|
||||
@Mock LastlegOrderMapper lastlegOrders;
|
||||
@Mock ReturnInventoryCartonMapper cartons;
|
||||
@Mock ReturnInventoryCartonItemMapper cartonItems;
|
||||
@Mock RelabelApplicationMapper relabelApplications;
|
||||
@Mock RelabelItemMapper relabelItems;
|
||||
@Mock FbaWarehouseValidator fbaWarehouseValidator;
|
||||
|
||||
ReturnReceiptService service;
|
||||
PdaWarehouseIdentity identity;
|
||||
@@ -69,9 +83,76 @@ class ReturnReceiptServiceTest {
|
||||
ReflectionTestUtils.setField(service, "numbers", numbers);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "lastlegOrders", lastlegOrders);
|
||||
ReflectionTestUtils.setField(service, "cartons", cartons);
|
||||
ReflectionTestUtils.setField(service, "cartonItems", cartonItems);
|
||||
ReflectionTestUtils.setField(service, "relabelApplications", relabelApplications);
|
||||
ReflectionTestUtils.setField(service, "relabelItems", relabelItems);
|
||||
ReflectionTestUtils.setField(service, "fbaWarehouseValidator", fbaWarehouseValidator);
|
||||
identity = new PdaWarehouseIdentity("operator-1", "仓管员", 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiSkuPartialRelabelLocksOnlyRequestedQuantities() {
|
||||
verifyMultiSkuRelabel(30, 15);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiSkuFullRelabelLocksAllReceivedQuantities() {
|
||||
verifyMultiSkuRelabel(50, 40);
|
||||
}
|
||||
|
||||
private void verifyMultiSkuRelabel(long firstQuantity, long secondQuantity) {
|
||||
ReturnOrder order = order("RT-MULTI", "10", "COMPLETED", 90, 90);
|
||||
order.setLastlegOrderId(99L);
|
||||
LastlegOrder tail = new LastlegOrder(); tail.setId(99L);
|
||||
ReturnInventoryCarton first = returnCarton(101L, "RC-A", "P-1", 50);
|
||||
ReturnInventoryCarton second = returnCarton(102L, "RC-B", "P-2", 40);
|
||||
when(orders.findNo("customer-1", "RT-MULTI")).thenReturn(order);
|
||||
when(orderItems.lockOrder(1L)).thenReturn(List.of(item(11L, "SKU-A", 50, 50), item(12L, "SKU-B", 40, 40)));
|
||||
when(fbaWarehouseValidator.required("200", "XLX6", null)).thenReturn(
|
||||
new FbaWarehouseValidator.VerifiedFbaWarehouse(200L, "XLX6", "FBA XLX6", "US", "CA"));
|
||||
when(numbers.next(eq("RELABEL"), eq("10"), anyLong())).thenReturn("RL-MULTI");
|
||||
when(lastlegOrders.selectById(99L)).thenReturn(tail);
|
||||
when(cartons.findAvailable("customer-1", "10")).thenReturn(List.of(first, second));
|
||||
when(cartons.lockId(101L)).thenReturn(first);
|
||||
when(cartons.lockId(102L)).thenReturn(second);
|
||||
when(cartonItems.listCarton(101L)).thenReturn(List.of(returnCartonItem("SKU-A", 50)));
|
||||
when(cartonItems.listCarton(102L)).thenReturn(List.of(returnCartonItem("SKU-B", 40)));
|
||||
|
||||
var result = service.createRelabel("customer-1", "merchant-1", "RT-MULTI",
|
||||
new ReturnOrderRequests.RelabelCreate(List.of(
|
||||
new ReturnOrderRequests.RelabelItem("SKU-A", firstQuantity),
|
||||
new ReturnOrderRequests.RelabelItem("SKU-B", secondQuantity)),
|
||||
"SKU_TOTAL", "200", "XLX6", "multi-key", "multi SKU acceptance"));
|
||||
|
||||
assertEquals("RL-MULTI", result.relabelNo());
|
||||
var application = ArgumentCaptor.forClass(RelabelApplication.class);
|
||||
verify(relabelApplications).insert(application.capture());
|
||||
assertEquals(firstQuantity + secondQuantity, application.getValue().getNotifiedPackageCount().longValue());
|
||||
assertEquals(2, application.getValue().getItemCount());
|
||||
var lines = ArgumentCaptor.forClass(RelabelItem.class);
|
||||
verify(relabelItems, times(2)).insert(lines.capture());
|
||||
assertEquals(List.of("SKU-A", "SKU-B"), lines.getAllValues().stream().map(RelabelItem::getSkuCode).toList());
|
||||
assertEquals(List.of((int) firstQuantity, (int) secondQuantity),
|
||||
lines.getAllValues().stream().map(RelabelItem::getNotifiedPackageCount).toList());
|
||||
assertTrue(lines.getAllValues().stream().allMatch(line -> "RETURN_BULK_SKU_LABEL".equals(line.getOperationType())));
|
||||
assertEquals("RELABEL_PENDING", first.getStatus());
|
||||
assertEquals("RELABEL_PENDING", second.getStatus());
|
||||
}
|
||||
|
||||
private static ReturnInventoryCarton returnCarton(long id, String mark, String pallet, long quantity) {
|
||||
ReturnInventoryCarton carton = new ReturnInventoryCarton();
|
||||
carton.setId(id); carton.setCustomerId("customer-1"); carton.setOverseasWarehouseId("10");
|
||||
carton.setSourceReturnNo("RT-MULTI"); carton.setCartonMark(mark); carton.setPalletCode(pallet);
|
||||
carton.setTotalQuantity(quantity); carton.setStatus("AVAILABLE");
|
||||
return carton;
|
||||
}
|
||||
|
||||
private static ReturnInventoryCartonItem returnCartonItem(String sku, long quantity) {
|
||||
ReturnInventoryCartonItem line = new ReturnInventoryCartonItem();
|
||||
line.setSkuCode(sku); line.setQuantity(quantity); return line;
|
||||
}
|
||||
|
||||
@Test
|
||||
void startReturnsOnlyRemainingSkuLinesForTheBoundWarehouse() {
|
||||
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.PARTIALLY_RECEIVED, 8, 3);
|
||||
@@ -110,6 +191,26 @@ class ReturnReceiptServiceTest {
|
||||
assertEquals("RETU-1", result.order().lastlegOrderNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectingForecastMarksReturnAndLinkedTailAsRejected() {
|
||||
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.PENDING_REVIEW, 2, 0);
|
||||
order.setLastlegOrderId(99L);
|
||||
LastlegOrder tail = new LastlegOrder();
|
||||
tail.setId(99L);
|
||||
when(orders.findNo("customer-1", "RT-1")).thenReturn(order);
|
||||
when(lastlegOrders.selectById(99L)).thenReturn(tail);
|
||||
|
||||
service.review("customer-1", "RT-1", new ReturnOrderRequests.Review("REJECT", "资料不齐"));
|
||||
|
||||
assertEquals("REJECTED", order.getStatus());
|
||||
assertEquals("资料不齐", order.getRemark());
|
||||
assertEquals("REJECTED", tail.getCurrentStatus());
|
||||
assertEquals("RETURN_RELABEL:REJECTED", tail.getSubOrderStatus());
|
||||
assertEquals("REJECTED", tail.getRelabelStatus());
|
||||
verify(orders).updateById(order);
|
||||
verify(lastlegOrders).updateById(tail);
|
||||
}
|
||||
|
||||
@Test
|
||||
void skuLookupReturnsOnlyActiveCandidatesFromTheBoundWarehouse() {
|
||||
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.PENDING_RECEIPT, 5, 1);
|
||||
@@ -162,7 +263,7 @@ class ReturnReceiptServiceTest {
|
||||
ReturnOrder order = order("RT-1", "10", ReturnReceiptService.PARTIALLY_RECEIVED, 2, 1);
|
||||
ReturnOrderItem line = item(11L, "SKU-A", 2, 1);
|
||||
stubNewReceipt(order, List.of(line), "RC-AUTO");
|
||||
var request = confirm("SKU", null, null, "key-final",
|
||||
var request = confirm("SKU", null, "RC-AUTO", "key-final",
|
||||
List.of(new PdaReturnReceiptRequests.Item("SKU-A", 1)));
|
||||
|
||||
var first = service.confirm(identity, request);
|
||||
@@ -175,7 +276,7 @@ class ReturnReceiptServiceTest {
|
||||
assertTrue(first.fullyMatched());
|
||||
assertEquals(ReturnReceiptService.COMPLETED, first.orderStatus());
|
||||
assertEquals(first.receiptNo(), replay.receiptNo());
|
||||
assertEquals("RC-AUTO", replay.cartonMark());
|
||||
assertEquals("RC-AUTO", replay.palletCode());
|
||||
verify(inventory, times(1)).createReceiptCarton(any(), any(), any(), any());
|
||||
verify(batches, times(1)).insert(any(ReturnReceiptBatch.class));
|
||||
verify(receipts, times(1)).insert(any(ReturnReceipt.class));
|
||||
@@ -249,7 +350,7 @@ class ReturnReceiptServiceTest {
|
||||
var request = confirm("RETURN_NO", null, null, "concurrent-key", List.of(new PdaReturnReceiptRequests.Item("SKU-A", 1)));
|
||||
ReturnOrder completed = order("RT-1", "10", ReturnReceiptService.COMPLETED, 1, 1);
|
||||
ReturnReceipt committed = new ReturnReceipt();
|
||||
committed.setReceiptNo("RR-COMMITTED"); committed.setReceiptBatchNo("RB-COMMITTED"); committed.setCartonMark("RC-COMMITTED");
|
||||
committed.setReceiptNo("RR-COMMITTED"); committed.setReceiptBatchNo("RB-COMMITTED"); committed.setPalletCode("RC-COMMITTED");
|
||||
committed.setRequestHash(java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(new ObjectMapper().writeValueAsBytes(request))));
|
||||
when(orders.lockNo("RT-1")).thenReturn(completed);
|
||||
@@ -301,8 +402,8 @@ class ReturnReceiptServiceTest {
|
||||
return item;
|
||||
}
|
||||
|
||||
private static PdaReturnReceiptRequests.Confirm confirm(String startedBy, String trackingNo, String cartonMark,
|
||||
private static PdaReturnReceiptRequests.Confirm confirm(String startedBy, String trackingNo, String palletCode,
|
||||
String key, List<PdaReturnReceiptRequests.Item> items) {
|
||||
return new PdaReturnReceiptRequests.Confirm("RT-1", trackingNo, startedBy, cartonMark, items, key);
|
||||
return new PdaReturnReceiptRequests.Confirm("RT-1", trackingNo, startedBy, palletCode, items, key);
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -46,6 +46,18 @@ class FbaWarehouseValidatorTest {
|
||||
assertThat(result.region()).isEqualTo("CA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsMatchingIdAndCodeWhenCallerDoesNotCarryWarehouseName() {
|
||||
when(client.activeWarehouse(193000000000000001L, "internal-token"))
|
||||
.thenReturn(ApiResponse.success(detail("FBA", "ACTIVE", "XLX1", "XLX1")));
|
||||
|
||||
var result = validator.required("193000000000000001", "XLX1", null);
|
||||
|
||||
assertThat(result.id()).isEqualTo(193000000000000001L);
|
||||
assertThat(result.code()).isEqualTo("XLX1");
|
||||
assertThat(result.name()).isEqualTo("XLX1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAnActualOverseasReceivingWarehouseAsFbaDestination() {
|
||||
when(client.activeWarehouse(193000000000000001L, "internal-token"))
|
||||
|
||||
+38
@@ -352,6 +352,42 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals("WAREHOUSE_PICKUP", transfer.getValue().getDeliveryMethod());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingDestinationReleasesLinkedRelabelForPreDispatch() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_FBA_DESTINATION);
|
||||
transfer.setNeedRelabel(true);
|
||||
transfer.setRelabelNo("FBR-1");
|
||||
transfer.setFbaWarehouseCode(null);
|
||||
LastlegOrder order = tailOrder();
|
||||
RelabelApplication relabel = new RelabelApplication();
|
||||
relabel.setId(71L);
|
||||
relabel.setCustomerId("TENANT-A");
|
||||
relabel.setRelabelNo("FBR-1");
|
||||
relabel.setFulfillmentStatus("WAIT_FBA_DESTINATION");
|
||||
InboundPlan plan = new InboundPlan();
|
||||
plan.setId(81L);
|
||||
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(orders.selectById(transfer.getLastlegOrderId())).thenReturn(order);
|
||||
when(orderDetails.selectList(any())).thenReturn(List.of());
|
||||
when(inboundPlans.findBySourceOrder("TENANT-A", transfer.getLastlegOrderId())).thenReturn(plan);
|
||||
when(inboundPlanLines.selectList(any())).thenReturn(List.of());
|
||||
when(inboundPlanPallets.selectList(any())).thenReturn(List.of());
|
||||
when(relabelApplications.lockNo("TENANT-A", "FBR-1")).thenReturn(relabel);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
when(transferPallets.listOrder("TENANT-A", transfer.getId())).thenReturn(List.of());
|
||||
|
||||
service.bindDestination("OP-1", new WarehouseTransferRequests.BindDestination(
|
||||
List.of(new WarehouseTransferRequests.DestinationSelection("TENANT-A", "WTR-1")),
|
||||
"FBA", null, "ONT8", "ONT8", null, null, null, null, "BIND-1"));
|
||||
|
||||
assertEquals(WarehouseTransferService.READY_FOR_DISPATCH, transfer.getStatus());
|
||||
assertEquals("WAIT_PRE_DISPATCH", relabel.getFulfillmentStatus());
|
||||
verify(relabelApplications).updateById(relabel);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newTransitInboundCreatesSnapshotAndImmutableCartonMarksWithoutHistoricalInventory() {
|
||||
WarehouseTransferRequests.Create request = new WarehouseTransferRequests.Create(
|
||||
@@ -645,6 +681,7 @@ class WarehouseTransferServiceTest {
|
||||
ArgumentCaptor.forClass(com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests.StockIn.class);
|
||||
verify(inventory).stockIn(eq("TENANT-A"), eq("USER-1"), stockIn.capture());
|
||||
assertEquals(5, stockIn.getValue().packages());
|
||||
assertEquals("Customer A", stockIn.getValue().customerName());
|
||||
assertEquals("SKU-TRANSFER", stockIn.getValue().skuCode());
|
||||
assertEquals("WTR-1", stockIn.getValue().businessOrderNo());
|
||||
assertEquals("WAREHOUSE_TRANSFER_RECEIPT:WTR-1:501", stockIn.getValue().idempotencyKey());
|
||||
@@ -840,6 +877,7 @@ class WarehouseTransferServiceTest {
|
||||
private static InventoryBalance balance(long id, long inboundOrderId, String inboundCode) {
|
||||
InventoryBalance balance = new InventoryBalance();
|
||||
balance.setId(id); balance.setCustomerId("TENANT-A"); balance.setInboundOrderId(inboundOrderId);
|
||||
balance.setCustomerName("Customer A");
|
||||
balance.setInboundCode(inboundCode); balance.setSkuCode("SKU-TRANSFER");
|
||||
return balance;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user