Merge origin/dev and resolve backend conflicts

This commit is contained in:
2026-09-08 20:50:28 +08:00
342 changed files with 13161 additions and 2296 deletions
@@ -66,6 +66,6 @@ class DeliveryLongIdJsonContractTest {
DELIVERY_ID,
null, null, "PS-1", null, null, null, null, "CREATED", null,
null, null, null, null, null, null, null, null, null, null, null,
0, 0, null, null, null, null, null, null, null, null);
0, 0, null, null, null, null, null, null, null, null, 0, java.util.List.of());
}
}
@@ -0,0 +1,40 @@
package com.baiyue.lastleg.delivery;
import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class DropshipOutboundCandidateMapperContractTest {
@Test
void candidateQueryIsSingleStatementAndFailsClosedOnEveryEligibilityFact() throws Exception {
String sql = select("listOutboundCandidates", String.class, String.class);
for (String token : new String[] {"p.customer_id=#{customerId}", "p.status='ACTIVE'",
"o.order_type='ONE_CLICK_DROPSHIP'", "COALESCE(o.detained,0)=0", "pos.status='OCCUPIED'",
"pos.available_packages>0", "s.package_count>0", "c.status='AVAILABLE'",
"ll_dispatch_pallet_lock", "ll_warehouse_transfer_inventory_allocation",
"ll_outbound_pallet_item", "pos.overseas_warehouse_id=#{warehouseId}", "LIMIT 500"}) {
assertTrue(sql.contains(token), token);
}
assertTrue(sql.contains("LEAST(pos.available_packages,s.package_count"));
}
@Test
void creationLockUsesTheSameEligibilityContractAndLocksOneSkuDimension() throws Exception {
String sql = select("lockOutboundCandidate", String.class, Long.class, String.class);
assertTrue(sql.contains("AND pos.id=#{positionId}"));
assertTrue(sql.contains("AND s.sku_code=#{skuCode}"));
assertTrue(sql.contains("LIMIT 1 FOR UPDATE"));
assertTrue(sql.contains("ll_dispatch_pallet_lock"));
assertTrue(sql.contains("ll_outbound_pallet_item"));
}
private String select(String method, Class<?>... parameterTypes) throws Exception {
Select annotation = DropshipOrderProfileMapper.class.getMethod(method, parameterTypes)
.getAnnotation(Select.class);
assertNotNull(annotation);
return String.join("", annotation.value());
}
}
@@ -11,6 +11,12 @@ import com.baiyue.lastleg.security.OverseasPermissions;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
@@ -25,8 +31,31 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class PlatformDispatchOutboundContractTest {
@Test
void outboundDetailAcceptsTheDocumentUrlWithoutPagingParameters() throws Exception {
DeliveryTransferController controller = new DeliveryTransferController();
ReflectionTestUtils.setField(controller, "service",
mock(com.baiyue.lastleg.delivery.service.DeliveryTransferService.class));
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setCustomArgumentResolvers(new AuthenticationPrincipalArgumentResolver())
.build();
AuthenticatedUser platform = new AuthenticatedUser(1000001L, 0L, 0L, 1L, "platform_admin",
"PLATFORM", "WEB", Set.of(OverseasPermissions.OUTBOUND_LIST));
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(platform, null, Set.of()));
try {
mockMvc.perform(get("/api/platform/v1/last-leg/outbound-orders/OB-WH-20260907-030529"))
.andExpect(status().isOk());
} finally {
SecurityContextHolder.clearContext();
}
}
@Test
void platformActionsHaveIndependentPermissionsAndNoCustomerParameter() throws Exception {
Method update = DispatchController.class.getMethod("update", AuthenticatedUser.class,
@@ -35,7 +64,7 @@ class PlatformDispatchOutboundContractTest {
Method transfer = DeliveryTransferController.class.getMethod("transfer", AuthenticatedUser.class,
String.class, DeliveryRequests.Transfer.class);
Method outboundDetail = DeliveryTransferController.class.getMethod("outboundDetail",
AuthenticatedUser.class, String.class, DeliveryRequests.PalletQuery.class);
AuthenticatedUser.class, String.class);
Method outboundCancel = DeliveryTransferController.class.getMethod("cancelOutbound",
AuthenticatedUser.class, String.class, DeliveryRequests.Cancel.class);
@@ -43,6 +72,7 @@ class PlatformDispatchOutboundContractTest {
assertPermission(cancel, OverseasPermissions.DISPATCH_CANCEL);
assertPermission(transfer, OverseasPermissions.DISPATCH_TRANSFER_DELIVERY);
assertPermission(outboundDetail, OverseasPermissions.OUTBOUND_LIST);
assertEquals(2, outboundDetail.getParameterCount());
assertPermission(outboundCancel, OverseasPermissions.OUTBOUND_CANCEL);
for (Method method : Set.of(update, cancel, transfer, outboundDetail, outboundCancel)) {
assertTrue(Arrays.stream(method.getParameters())
@@ -60,15 +90,20 @@ class PlatformDispatchOutboundContractTest {
void oneClickRequiresCustomerWhileOutboundListKeepsOptionalFilter() throws Exception {
Method create = DeliveryTransferController.class.getMethod("createOneClick", AuthenticatedUser.class,
String.class, DeliveryRequests.OneClickOutbound.class);
Method candidates = DeliveryTransferController.class.getMethod("oneClickCandidates", AuthenticatedUser.class,
String.class, String.class);
Method page = DeliveryTransferController.class.getMethod("outboundPage", AuthenticatedUser.class,
String.class, DeliveryRequests.Query.class);
assertPermission(create, OverseasPermissions.OUTBOUND_CREATE);
assertPermission(candidates, OverseasPermissions.OUTBOUND_CREATE);
assertPermission(page, OverseasPermissions.OUTBOUND_LIST);
RequestParam createCustomer = create.getParameters()[1].getAnnotation(RequestParam.class);
RequestParam pageCustomer = page.getParameters()[1].getAnnotation(RequestParam.class);
assertNotNull(createCustomer);
assertTrue(createCustomer.required());
assertTrue(candidates.getParameters()[1].getAnnotation(RequestParam.class).required());
assertFalse(candidates.getParameters()[2].getAnnotation(RequestParam.class).required());
assertNotNull(pageCustomer);
assertFalse(pageCustomer.required());
@@ -91,8 +126,7 @@ class PlatformDispatchOutboundContractTest {
() -> dispatchController.cancel(merchant, "DSP-101"));
ResponseStatusException outboundRejected = org.junit.jupiter.api.Assertions.assertThrows(
ResponseStatusException.class,
() -> deliveryController.outboundDetail(merchant, "OUT-101",
new DeliveryRequests.PalletQuery(1, 20, null)));
() -> deliveryController.outboundDetail(merchant, "OUT-101"));
assertEquals(403, dispatchRejected.getStatusCode().value());
assertEquals(403, outboundRejected.getStatusCode().value());
@@ -137,6 +171,10 @@ class PlatformDispatchOutboundContractTest {
for (String action : Set.of(dispatchAction, transfer, outboundDetail, outboundCancel)) {
assertFalse(action.contains("name: customerId"));
}
assertTrue(outboundDetail.contains("pallets[].palletNo"));
assertTrue(yaml.contains("/api/platform/v1/last-leg/outbound-orders/one-click-dropship/candidates:"));
assertTrue(yaml.contains("OneClickOutboundCandidate"));
assertTrue(yaml.contains("单次 SQL 返回,不逐行补查"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:edit"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:cancel"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:transfer-delivery"));
@@ -0,0 +1,26 @@
package com.baiyue.lastleg.delivery.model;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class DeliveryStatusPolicyTest {
@Test
void exposesOnlyServerAuthorizedActionsForEachState() {
assertThat(DeliveryStatusPolicy.allowedActions("WAIT_DELIVERY"))
.containsExactly("START_DELIVERY", "COMPLETE_DELIVERY", "REJECT_DELIVERY", "CANCEL_DELIVERY");
assertThat(DeliveryStatusPolicy.allowedActions("DELIVERING"))
.containsExactly("COMPLETE_DELIVERY", "REJECT_DELIVERY", "MARK_RETURN");
assertThat(DeliveryStatusPolicy.allowedActions("DELIVERED")).containsExactly("MARK_RETURN");
assertThat(DeliveryStatusPolicy.allowedActions("RETURN_PENDING")).isEmpty();
assertThat(DeliveryStatusPolicy.allowedActions("CANCELLED")).isEmpty();
}
@Test
void terminalStatesCannotReenterTheDeliveryFlow() {
assertThat(DeliveryStatusPolicy.canTransition("DELIVERED", "WAIT_DELIVERY")).isFalse();
assertThat(DeliveryStatusPolicy.canTransition("DELIVERED", "DELIVERING")).isFalse();
assertThat(DeliveryStatusPolicy.canTransition("REJECTED", "DELIVERING")).isFalse();
assertThat(DeliveryStatusPolicy.canTransition("CANCELLED", "DELIVERED")).isFalse();
}
}
@@ -19,6 +19,7 @@ import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanLineMapper;
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletAllocationMapper;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
@@ -34,6 +35,8 @@ import com.baiyue.lastleg.dropship.entity.DropshipOrderProfile;
import com.baiyue.lastleg.dropship.entity.DropshipCarton;
import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper;
import com.baiyue.lastleg.dropship.mapper.DropshipCartonMapper;
import com.baiyue.lastleg.dropship.mapper.DropshipSkuInventoryMapper;
import com.baiyue.lastleg.dropship.model.DropshipOutboundCandidate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -53,6 +56,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -68,6 +72,7 @@ class DeliveryTransferServiceTest {
@Mock DeliveryOperationMapper deliveryOperations;
@Mock InboundPlanPalletAllocationMapper planAllocations;
@Mock InboundPlanLineMapper planLines;
@Mock InboundPlanCartonMapper inboundCartons;
@Mock LastlegOrderMapper tailOrders;
@Mock LastlegOrderStatusHistoryMapper histories;
@Mock LastlegOrderDetailMapper tailOrderDetails;
@@ -77,6 +82,7 @@ class DeliveryTransferServiceTest {
@Mock InventoryBalanceMapper inventoryBalances;
@Mock DropshipOrderProfileMapper dropshipProfiles;
@Mock DropshipCartonMapper dropshipCartons;
@Mock DropshipSkuInventoryMapper dropshipSkuInventory;
private DeliveryTransferService service;
@@ -92,6 +98,7 @@ class DeliveryTransferServiceTest {
ReflectionTestUtils.setField(service, "deliveryOperations", deliveryOperations);
ReflectionTestUtils.setField(service, "planAllocations", planAllocations);
ReflectionTestUtils.setField(service, "planLines", planLines);
ReflectionTestUtils.setField(service, "inboundCartons", inboundCartons);
ReflectionTestUtils.setField(service, "orderNoGenerator", new LastlegOrderNoGenerator());
ReflectionTestUtils.setField(service, "tailOrders", tailOrders);
ReflectionTestUtils.setField(service, "histories", histories);
@@ -102,6 +109,7 @@ class DeliveryTransferServiceTest {
ReflectionTestUtils.setField(service, "inventoryBalances", inventoryBalances);
ReflectionTestUtils.setField(service, "dropshipProfiles", dropshipProfiles);
ReflectionTestUtils.setField(service, "dropshipCartons", dropshipCartons);
ReflectionTestUtils.setField(service, "dropshipSkuInventory", dropshipSkuInventory);
}
@Test
@@ -140,6 +148,40 @@ class DeliveryTransferServiceTest {
assertTrue(!result.replayed());
}
@Test
void transferBackfillsReceivedPlanPalletsBeforeCheckingDispatchItems() {
DispatchOrder dispatch = dispatch(101L, "DSP-101", "TENANT-A");
when(dispatchOrders.lockGlobalByDispatchNo("DSP-101")).thenReturn(dispatch);
when(dispatchItems.bindReceivedPallets("TENANT-A", 101L)).thenReturn(1);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(
item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 12)));
service.platformTransfer("DSP-101", "U-1", transferRequest("transfer-after-receiving", null));
var ordered = inOrder(dispatchItems);
ordered.verify(dispatchItems).bindReceivedPallets("TENANT-A", 101L);
ordered.verify(dispatchItems).findByDispatchOrder("TENANT-A", 101L);
}
@Test
void transferCollectsLxCodesWhenAnInboundCodeBelongsToMultipleTailOrders() {
DispatchOrder dispatch = dispatch(101L, "DSP-101", "TENANT-A");
when(dispatchOrders.lockGlobalByDispatchNo("DSP-101")).thenReturn(dispatch);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(
item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 12)));
LastlegOrder primary = new LastlegOrder();
primary.setCustomerId("TENANT-A"); primary.setInboundCode("IN-A"); primary.setLxCode("LX-1");
LastlegOrder derived = new LastlegOrder();
derived.setCustomerId("TENANT-A"); derived.setInboundCode("IN-A"); derived.setLxCode(null);
when(tailOrders.selectList(any())).thenReturn(List.of(primary, derived), List.of());
service.platformTransfer("DSP-101", "U-1", transferRequest("transfer-duplicate-inbound", null));
ArgumentCaptor<OutboundOrder> outbound = ArgumentCaptor.forClass(OutboundOrder.class);
verify(outboundOrders).insert(outbound.capture());
assertEquals("LX-1", outbound.getValue().getLxCodes());
}
@Test
void sameIdempotencyKeyReturnsExistingDocumentsWithoutWrites() {
DispatchOrder dispatch = dispatch(101L, "DSP-101", "TENANT-A");
@@ -235,20 +277,26 @@ class DeliveryTransferServiceTest {
}
@Test
void outboundDetailDoesNotExposeExcludedPalletOrOutboundFulfillmentRows() {
void outboundDetailIncludesPalletCodesForDisplay() {
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
OutboundPalletItem pallet = pallet(501L, "IP-ONT8-20260907-000001-P001", "ONT8", "Ontario", "IN-A", 12);
pallet.setSkuCode("SKU-1");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(pallet));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301",
new DeliveryRequests.PalletQuery(1, 20, null));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
assertEquals("OUT-301", detail.outbound().outboundNo());
assertEquals("DEL-401", detail.delivery().deliveryNo());
assertTrue(java.util.Arrays.stream(DeliveryResponses.OutboundDetail.class.getRecordComponents())
.noneMatch(component -> "pallets".equals(component.getName())));
verify(outboundPallets, never()).selectPage(any(Page.class), any());
assertEquals(1, detail.pallets().size());
assertEquals("IP-ONT8-20260907-000001-P001", detail.pallets().get(0).palletNo());
assertEquals("IN-A", detail.pallets().get(0).inboundCode());
assertEquals("ONT8", detail.pallets().get(0).destinationWarehouseCode());
assertEquals("SKU-1", detail.pallets().get(0).skuCode());
assertEquals(12, detail.pallets().get(0).packageCount());
verify(outboundPallets).findByOutbound("TENANT-A", 301L);
}
@Test
@@ -258,8 +306,7 @@ class DeliveryTransferServiceTest {
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301",
new DeliveryRequests.PalletQuery(1, 20, null));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
assertEquals("OUT-301", detail.outbound().outboundNo());
verify(deliveryOrders).lockByOutbound("TENANT-A", 301L);
@@ -358,12 +405,8 @@ class DeliveryTransferServiceTest {
@Test
void oneClickOutboundRejectsSummarySkuWithoutAvailableDropshipCartons() {
InventoryPosition position = new InventoryPosition();
position.setId(901L); position.setCustomerId("TENANT-A"); position.setOverseasWarehouseId("WH-1");
position.setPalletId(501L); position.setPalletNo("PLT-501"); position.setAvailablePackages(1L);
DropshipOrderProfile profile = new DropshipOrderProfile(); profile.setId(1001L);
when(inventoryPositions.selectById(901L)).thenReturn(position);
when(dropshipProfiles.findPosition("TENANT-A", 901L)).thenReturn(profile);
DropshipOutboundCandidate candidate = dropshipCandidate(901L, 1001L, "WH-1", "PLT-501", "MIXED", 1L);
when(dropshipProfiles.lockOutboundCandidate("TENANT-A", 901L, "MIXED")).thenReturn(candidate);
when(dropshipCartons.lockAvailable("TENANT-A", 1001L, "MIXED", 1L)).thenReturn(List.of());
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.createOneClick(
@@ -371,9 +414,43 @@ class DeliveryTransferServiceTest {
new DeliveryRequests.OneClickItem(901L, 1L, "MIXED")), null, "one-click-mixed")));
assertEquals(409, exception.getStatusCode().value());
assertTrue(exception.getReason().contains("一件代发可用箱数不足"));
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
}
@Test
void oneClickCandidatesUseOneAuthoritativeQueryAndExposeStableBusinessFields() {
DropshipOutboundCandidate candidate = dropshipCandidate(901L, 1001L, "WH-1", "DS-A002", "SKU-1", 3L);
candidate.setOverseasWarehouseName("Los Angeles"); candidate.setInboundCode("IN-1");
candidate.setProductName("Product 1");
when(dropshipProfiles.listOutboundCandidates("TENANT-A", "WH-1")).thenReturn(List.of(candidate));
List<DeliveryResponses.OneClickCandidate> result = service.oneClickCandidates("TENANT-A", " WH-1 ");
assertEquals(1, result.size());
assertEquals(901L, result.get(0).inventoryPositionId());
assertEquals("DS-A002", result.get(0).palletNo());
assertEquals("SKU-1", result.get(0).skuCode());
assertEquals(3L, result.get(0).availablePackages());
verify(dropshipProfiles).listOutboundCandidates("TENANT-A", "WH-1");
verify(inventoryPositions, never()).selectById(any());
}
@Test
void oneClickCreationRejectsOrdinaryInventoryBeforeAnyWrite() {
when(dropshipProfiles.lockOutboundCandidate("TENANT-A", 901L, "MIXED")).thenReturn(null);
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.createOneClick(
"TENANT-A", "U-1", new DeliveryRequests.OneClickOutbound("recipient", List.of(
new DeliveryRequests.OneClickItem(901L, 1L, "MIXED")), null, "ordinary-stock")));
assertEquals(409, exception.getStatusCode().value());
assertEquals("所选库存不是可用的一件代发库存,或已被其他作业占用", exception.getReason());
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
verify(outboundPallets, never()).insert(any(OutboundPalletItem.class));
verify(pdaTasks, never()).insert(any(OutboundPdaTask.class));
}
@Test
void outboundPdaListAndDetailUseOnlyBoundWarehouse() throws Exception {
assertNotNull(DeliveryTransferService.class.getMethod("pdaPick", String.class, String.class,
@@ -393,6 +470,53 @@ class DeliveryTransferServiceTest {
verify(pdaTasks).listByWarehouse("WH-1", null);
}
@Test
void outboundNumberResolvesDispatchWorkflowInBoundWarehouse() {
OutboundOrder outbound = outbound(301L, 401L, "DSP-401", "OUT-301", "transfer-key");
outbound.setOverseasWarehouseId("WH-1");
outbound.setOutboundType("DISPATCH_DELIVERY");
DispatchOrder dispatch = new DispatchOrder();
dispatch.setId(401L); dispatch.setDispatchNo("DSP-401"); dispatch.setOverseasWarehouseId("WH-1");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(dispatchOrders.findGlobalByDispatchNo("DSP-401")).thenReturn(dispatch);
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
assertEquals("DISPATCH", result.workflow());
assertEquals("DSP-401", result.dispatchNo());
assertEquals("OUT-301", result.outboundNo());
}
@Test
void outboundNumberResolvesOneClickTaskInBoundWarehouse() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
outbound.setOverseasWarehouseId("WH-1");
outbound.setOutboundType("ONE_CLICK_DROPSHIP");
OutboundPdaTask task = new OutboundPdaTask();
task.setCustomerId("TENANT-A"); task.setOutboundOrderId(301L); task.setTaskNo("PDA-701");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(pdaTasks.findByOutboundAndWarehouse("TENANT-A", 301L, "WH-1")).thenReturn(task);
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
assertEquals("OUTBOUND", result.workflow());
assertEquals("PDA-701", result.taskNo());
assertEquals("OUT-301", result.outboundNo());
}
@Test
void pdaTaskResolutionDoesNotLeakAnotherWarehouse() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
outbound.setOverseasWarehouseId("WH-2");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> service.resolvePdaTask("WH-1", "OUT-301"));
assertEquals(404, exception.getStatusCode().value());
verify(pdaTasks, never()).findByOutboundAndWarehouse(any(), any(), any());
}
@Test
void outboundPdaDetailReturnsEveryPickablePositionWithStableBusinessCodes() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
@@ -565,4 +689,12 @@ class DeliveryTransferServiceTest {
value.setInboundCode(inboundCode); value.setOrderRefs("LMO-1"); value.setPackageCount(packages);
return value;
}
private static DropshipOutboundCandidate dropshipCandidate(Long positionId, Long profileId, String warehouseId,
String palletNo, String sku, Long packages) {
DropshipOutboundCandidate value = new DropshipOutboundCandidate();
value.setInventoryPositionId(positionId); value.setProfileId(profileId); value.setWarehousePalletId(501L);
value.setOverseasWarehouseId(warehouseId); value.setPalletNo(palletNo); value.setSkuCode(sku);
value.setAvailablePackages(packages); return value;
}
}