支持私人地址按实际收货生成库存凭证

This commit is contained in:
2026-11-04 13:55:24 +08:00
parent 22e8939f1f
commit 40475388e1
2 changed files with 54 additions and 1 deletions
@@ -273,7 +273,8 @@ public class InboundStockReceiptService {
throw conflict("Stock-in requires picked packages and an assigned warehouse pallet"); throw conflict("Stock-in requires picked packages and an assigned warehouse pallet");
} }
String destinationCode = first(line.getDestinationWarehouseCode(), order.getFbaWarehouseCode()); String destinationCode = first(line.getDestinationWarehouseCode(), order.getFbaWarehouseCode());
if (destinationCode == null && trim(line.getDestinationWarehouseId()) == null) { if (destinationCode == null && trim(line.getDestinationWarehouseId()) == null
&& (!"OVERSEAS".equals(line.getDestinationType()) || trim(line.getDestinationWarehouseName()) == null)) {
throw conflict("Destination warehouse ID or code is required for stock-in"); throw conflict("Destination warehouse ID or code is required for stock-in");
} }
if (destinationCode == null) destinationCode = ""; if (destinationCode == null) destinationCode = "";
@@ -26,6 +26,8 @@ import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@@ -37,6 +39,7 @@ import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
@@ -202,6 +205,55 @@ class InboundStockReceiptServiceTest {
assertEquals(2001L, request.getValue().palletId()); assertEquals(2001L, request.getValue().palletId());
} }
@ParameterizedTest
@ValueSource(booleans = {false, true})
void privateAddressStocksActualCartonsWithoutAMasterDestinationWarehouse(boolean allowPartial) {
Fixture fixture = fixture(allowPartial ? 4 : 1, 1, 1, allowPartial ? 4 : 1);
fixture.line.setDestinationType("OVERSEAS");
fixture.line.setDestinationWarehouseId(null);
fixture.line.setDestinationWarehouseCode(null);
fixture.line.setDestinationWarehouseName("私人地址 A");
fixture.order.setDestinationType("OVERSEAS");
fixture.order.setFbaWarehouseCode(null);
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
when(workflowRepository.cartons(1L)).thenReturn(List.of(receivedCarton(1L, fixture.pallet, "SKU-1")));
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 1, 0, 0, 0, false, false));
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), allowPartial);
ArgumentCaptor<InventoryCommandRequests.StockIn> request = ArgumentCaptor.forClass(InventoryCommandRequests.StockIn.class);
verify(inventory).stockIn(eq("100"), eq("operator"), request.capture());
assertNull(request.getValue().destinationWarehouseId());
assertEquals("", request.getValue().destinationWarehouseCode());
assertEquals("私人地址 A", request.getValue().destinationWarehouseName());
assertEquals("900", request.getValue().overseasWarehouseId());
assertEquals(2001L, request.getValue().palletId());
assertEquals(1L, request.getValue().packages());
verify(receipts).insert(any(InboundStockReceipt.class));
}
@Test
void privateAddressCannotStockInWithoutANamedDestination() {
Fixture fixture = fixture(1, 1, 1, 1);
fixture.line.setDestinationType("OVERSEAS");
fixture.line.setDestinationWarehouseId(null);
fixture.line.setDestinationWarehouseCode(null);
fixture.line.setDestinationWarehouseName(" ");
fixture.order.setFbaWarehouseCode(null);
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
when(workflowRepository.cartons(1L)).thenReturn(List.of(receivedCarton(1L, fixture.pallet, "SKU-1")));
ResponseStatusException failure = assertThrows(ResponseStatusException.class,
() -> service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now()));
assertEquals("Destination warehouse ID or code is required for stock-in", failure.getReason());
verify(inventory, never()).stockIn(any(), any(), any());
verify(receipts, never()).insert(any(InboundStockReceipt.class));
}
/** 同票现场调整打托后,两种 SKU 的实托归属仍由箱码决定。 */ /** 同票现场调整打托后,两种 SKU 的实托归属仍由箱码决定。 */
@Test @Test
void overseasCompleteReceiptKeepsReversedSkuPalletAssignments() { void overseasCompleteReceiptKeepsReversedSkuPalletAssignments() {