This commit is contained in:
2026-09-11 11:51:26 +08:00
parent d60689ce02
commit d2cff49b9e
45 changed files with 569 additions and 66 deletions
@@ -1,6 +1,8 @@
package com.baiyue.firstleg.repository;
import org.junit.jupiter.api.Test;
import org.apache.ibatis.annotations.Select;
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
@@ -10,6 +12,17 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class DropshipPalletizationSqlContractTest {
@Test
void pendingPdaTasksAreScopedByTheSelectedWarehouse() throws Exception {
String sql = DropshipPalletizationMapper.class
.getDeclaredMethod("pending", long.class, long.class, String.class)
.getAnnotation(Select.class).value()[0];
assertThat(sql)
.contains("t.warehouse_id=#{warehouseId}")
.contains("t.assignee_id IS NULL OR t.assignee_id=#{operatorId}");
}
@Test
void moduleAndUnifiedSchemasExposeCountingPalletAndWholePalletAssignmentFacts() throws Exception {
String inbound = new ClassPathResource("db/phase2-inbound-schema.sql")
@@ -37,12 +37,13 @@ class LoadingPickPdaTaskSqlContractTest {
@Test
void pendingTasksAreLimitedToUnassignedOrCurrentOperatorWithoutCustomerInput() throws Exception {
String sql = LoadingPickTaskMapper.class
.getDeclaredMethod("selectPendingForOperator", long.class, String.class)
.getDeclaredMethod("selectPendingForOperator", long.class, long.class, String.class)
.getAnnotation(Select.class).value()[0];
assertThat(sql)
.contains("task.task_status IN ('LOADING','COMPLETING','COMPLETION_FAILED')")
.contains("booking.record_status = 'ACTIVE'")
.contains("booking.warehouse_id = #{warehouseId}")
.contains("booking.source_type AS sourceType")
.contains("booking.record_status AS recordStatus")
.contains("booking.merged_into_container_booking_id AS mergedIntoContainerBookingId")
@@ -22,18 +22,27 @@ class DropshipPalletizationServiceTest {
DropshipPalletizationServiceTest(){ ReflectionTestUtils.setField(service,"mapper",mapper); ReflectionTestUtils.setField(service,"documentNumbers",numbers); }
@Test void generatesAndBindsOnlyWhenPendingCartonsExist(){
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(task());
@Test void generatesBindsSealsAndCompletesOnlyAfterEveryCartonIsCounted(){
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(readyToComplete());
when(mapper.unboundCount(10L)).thenReturn(2); when(numbers.next("DP")).thenReturn("DP-260909-0001");
when(mapper.bindPending(eq(10L),anyLong(),eq("create-1"),eq(7L),eq("张三"))).thenReturn(2);
when(mapper.adjustBound(10L,7L,2)).thenReturn(1);
when(mapper.seal(eq(10L),anyLong(),eq(1),eq(100L),eq("create-1"),eq(7L),eq("张三"))).thenReturn(1);
when(mapper.completeTask(10L)).thenReturn(1); when(mapper.completeInbound(10L,1L,30L,7L)).thenReturn(1);
service.createAndBind(10L,new com.baiyue.firstleg.dto.DropshipPalletizationDtos.CreateAndBindRequest(100L,"create-1"),7L,"张三");
verify(mapper).insertPallet(anyLong(),eq(10L),eq(1L),eq(30L),eq(100L),eq("DP-260909-0001"),eq("create-1"),eq(7L),eq("张三"));
verify(numbers,times(1)).next("DP");
}
@Test void noPendingCartonsNeverAllocatesNumberOrEmptyPallet(){
@Test void incompleteCountingNeverAllocatesAPalletNumber(){
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(task());
assertThatThrownBy(()->service.createAndBind(10L,new com.baiyue.firstleg.dto.DropshipPalletizationDtos.CreateAndBindRequest(100L,"early"),7L,"张三"))
.hasMessageContaining("完成全部箱码清点");
verifyNoInteractions(numbers);
}
@Test void noPendingCartonsNeverAllocatesNumberOrEmptyPallet(){
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(readyToComplete());
assertThatThrownBy(()->service.createAndBind(10L,new com.baiyue.firstleg.dto.DropshipPalletizationDtos.CreateAndBindRequest(100L,"empty"),7L,"张三")).hasMessageContaining("没有待绑托箱");
verifyNoInteractions(numbers);
verify(mapper,never()).insertPallet(anyLong(),anyLong(),anyLong(),anyLong(),anyLong(),anyString(),anyString(),anyLong(),anyString());
@@ -48,7 +57,7 @@ class DropshipPalletizationServiceTest {
}
@Test void rejectsOtherOperatorAndCrossTaskReplay(){
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(task());
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(readyToComplete());
var request=new com.baiyue.firstleg.dto.DropshipPalletizationDtos.CreateAndBindRequest(100L,"replay");
assertThatThrownBy(()->service.createAndBind(10L,request,8L,"李四")).hasMessageContaining("当前操作员");
PalletRow other=pallet(); other.setTaskId(11L); when(mapper.palletByOpenOperation("replay")).thenReturn(other);
@@ -59,7 +68,7 @@ class DropshipPalletizationServiceTest {
@Test void rejectsUnknownWarehouseAndChangedBindCount(){
var request=new com.baiyue.firstleg.dto.DropshipPalletizationDtos.CreateAndBindRequest(100L,"new");
assertThatThrownBy(()->service.createAndBind(10L,request,7L,"张三")).hasMessageContaining("当前仓库");
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(task());
when(mapper.lockTask(10L,100L)).thenReturn(10L); when(mapper.task(10L,100L)).thenReturn(readyToComplete());
when(mapper.unboundCount(10L)).thenReturn(2); when(numbers.next("DP")).thenReturn("DP-260909-0002");
assertThatThrownBy(()->service.createAndBind(10L,request,7L,"张三")).hasMessageContaining("待绑托箱已变化");
verify(mapper,never()).adjustBound(anyLong(),anyLong(),anyInt());
@@ -87,23 +96,16 @@ class DropshipPalletizationServiceTest {
verify(mapper,never()).insertCarton(anyLong(),anyLong(),any(),anyLong(),anyLong(),anyLong(),anyLong(),any(),any(),any(),anyInt(),anyInt(),any(),any(),any(),anyLong(),any());
}
@Test void countsOneCartonDirectlyIntoScannedPallet(){
TaskRow before=task(),after=task(); after.setCountedCartons(1); after.setAllocatedCartons(1);
LabelRow label=label(); PalletRow pallet=pallet(); CartonRow carton=carton();
when(mapper.task(10L,100L)).thenReturn(before,after);
when(mapper.label(10L,"BOX-1")).thenReturn(label); when(mapper.palletByNo("DP-260908-0001")).thenReturn(pallet);
when(mapper.addCounted(10L,7L,1,0)).thenReturn(1); when(mapper.carton(eq(10L),anyLong())).thenReturn(carton);
when(mapper.pallet(10L,20L,100L)).thenReturn(pallet); when(mapper.cartons(20L)).thenReturn(java.util.List.of(carton));
when(mapper.metrics(20L)).thenReturn(new DropshipPalletizationMapper.PalletMetrics(java.math.BigDecimal.ONE,java.math.BigDecimal.TEN));
@Test void countingCannotExposeOrBindAPalletNumberEarly(){
when(mapper.task(10L,100L)).thenReturn(task()); when(mapper.label(10L,"BOX-1")).thenReturn(label());
var request=new CountCartonRequest(100L,"BOX-1",12,DiscrepancyAction.MATCH,null,"DP-260908-0001","op-1");
var result=service.countCarton(10L,request,7L,"张三");
assertThat(result.carton().countStatus()).isEqualTo("BOUND");
assertThat(result.pallet().status()).isEqualTo("OPEN");
verify(mapper).insertCarton(anyLong(),eq(10L),eq(20L),eq(1L),eq(30L),eq(40L),eq(50L),eq("SKU-1"),
eq("货物"),eq("BOX-1"),eq(12),eq(12),eq("MATCH"),isNull(),eq("op-1"),eq(7L),eq("张三"));
assertThatThrownBy(()->service.countCarton(10L,request,7L,"张三"))
.hasMessageContaining("不能提前绑定托盘码");
verify(mapper,never()).insertCarton(anyLong(),anyLong(),any(),anyLong(),anyLong(),anyLong(),anyLong(),any(),any(),any(),anyInt(),anyInt(),any(),any(),any(),anyLong(),any());
}
private static TaskRow task(){ TaskRow t=new TaskRow();t.setId(10L);t.setTenantId(1L);t.setInboundId(30L);t.setWarehouseId(100L);t.setInboundCode("IN-1");t.setTaskStatus("CLAIMED");t.setAssigneeId(7L);t.setProcessStatus("WAIT_PALLETIZE");t.setTotalCartons(2);t.setCountedCartons(0);t.setAllocatedCartons(0);t.setDiscrepancyCartons(0);t.setVersion(1);return t; }
private static TaskRow readyToComplete(){ TaskRow t=task();t.setCountedCartons(2);return t; }
private static LabelRow label(){LabelRow l=new LabelRow();l.setCartonLabelId(50L);l.setInboundDetailId(40L);l.setSkuCode("SKU-1");l.setCargoName("货物");l.setUnitsPerCarton(12);l.setInboundCode("IN-1");l.setBoxCode("BOX-1");return l;}
private static PalletRow pallet(){PalletRow p=new PalletRow();p.setId(20L);p.setTaskId(10L);p.setTenantId(1L);p.setInboundId(30L);p.setWarehouseId(100L);p.setPalletNo("DP-260908-0001");p.setPalletStatus("OPEN");p.setVersion(1);return p;}
private static CartonRow carton(){CartonRow c=new CartonRow();c.setId(60L);c.setTaskId(10L);c.setPalletId(20L);c.setPalletNo("DP-260908-0001");c.setInboundId(30L);c.setInboundDetailId(40L);c.setSkuCode("SKU-1");c.setCargoName("货物");c.setBoxCode("BOX-1");c.setExpectedSkuQuantity(12);c.setActualSkuQuantity(12);c.setDiscrepancyStatus("MATCH");return c;}
@@ -4,6 +4,8 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.PrintPdfClient;
import com.baiyue.firstleg.dto.InboundCartonLabels;
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.SkuItem;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,6 +37,25 @@ class InboundCartonLabelPrintServiceTest {
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
}
@Test
void dropshipPalletLabelIsAvailableOnlyAfterSeal() {
Pallet open = dropshipPallet("OPEN");
assertThatThrownBy(() -> service.generateDropshipPallet(open, "request-open"))
.isInstanceOf(BizException.class)
.hasMessageContaining("已完成");
verifyNoInteractions(print);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("request-sealed"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {7, 8, 9}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateDropshipPallet(dropshipPallet("SEALED"), "request-sealed");
assertThat(response.getBody()).containsExactly(7, 8, 9);
}
@Test
void forwardsStableLabelsAndOnlySafePdfHeaders() {
when(labels.list(10001L, 70001L, List.of(1))).thenReturn(batch());
@@ -159,4 +180,11 @@ class InboundCartonLabelPrintServiceTest {
"PCHK2608040174", "MCC1", "CTNS:19-1", "洛杉矶普船海卡");
return new InboundCartonLabels.Batch(70001L, 80001L, "PCHK2608040174", 19, List.of(label));
}
private Pallet dropshipPallet(String status) {
SkuItem item = new SkuItem(70001L, "PCHK2608040174", 2L,
"SKU-1", "音箱", 1, 19, List.of("PCHK2608040174#19-1"));
return new Pallet(90001L, "DP-260911-0001", status, 1, 1, 0,
BigDecimal.ONE, BigDecimal.ONE, 1, List.of(item), List.of(), null);
}
}
@@ -337,9 +337,9 @@ class LoadingPickServiceTest {
row.setAssignmentVersion(0);
row.setItemCount(1);
row.setPendingItemCount(0);
when(repository.pendingTasks(9L, null)).thenReturn(List.of(row));
when(repository.pendingTasks(9L, 200001L, null)).thenReturn(List.of(row));
var result = service.pendingTasks(9L, null).get(0);
var result = service.pendingTasks(9L, 200001L, null).get(0);
assertThat(result.sourceType()).isEqualTo("SIMULATION");
assertThat(result.recordStatus()).isEqualTo("ACTIVE");
@@ -0,0 +1,39 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class PdaWarehouseCatalogServiceTest {
@Test
void returnsDomesticWarehouseMasterDataWithoutHardcodedOptions() {
WarehouseMasterDataClient client = mock(WarehouseMasterDataClient.class);
PdaWarehouseCatalogService service = new PdaWarehouseCatalogService();
ReflectionTestUtils.setField(service, "warehouses", client);
ReflectionTestUtils.setField(service, "internalToken", "token");
var option = new WarehouseMasterDataClient.WarehouseOption(200001L, "CN-DONGGUAN-01", "东莞仓");
when(client.domestic("token")).thenReturn(ApiResponse.success(List.of(option)));
assertThat(service.activeDomesticWarehouses()).containsExactly(option);
}
@Test
void failsClosedWhenWarehouseDirectoryIsUnavailable() {
WarehouseMasterDataClient client = mock(WarehouseMasterDataClient.class);
PdaWarehouseCatalogService service = new PdaWarehouseCatalogService();
ReflectionTestUtils.setField(service, "warehouses", client);
ReflectionTestUtils.setField(service, "internalToken", "token");
when(client.domestic("token")).thenThrow(new IllegalStateException("down"));
assertThatThrownBy(service::activeDomesticWarehouses).isInstanceOf(BizException.class);
}
}