This commit is contained in:
2026-08-07 15:53:55 +08:00
parent a4a03a8ae8
commit 3f597677fe
23 changed files with 1157 additions and 2 deletions
@@ -0,0 +1,28 @@
package com.baiyue.firstleg.repository;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
class InboundCartonLabelSqlContractTest {
@Test
void baseSchemaAndExistingDatabaseMigrationKeepStableCartonKeys() throws Exception {
String schema = resource("db/phase2-inbound-schema.sql");
String migration = resource("db/phase2-inbound-carton-label.sql");
assertThat(schema).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label")
.contains("UNIQUE KEY uk_carton_label_box_code (tenant_id, box_code)")
.contains("UNIQUE KEY uk_carton_label_index (tenant_id, inbound_id, carton_index)")
.contains("inbound_detail_id BIGINT UNSIGNED NOT NULL");
assertThat(migration).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label")
.contains("固定二维码内容:入仓码#总箱数-箱下标")
.contains("idx_carton_label_detail (tenant_id, inbound_id, inbound_detail_id)");
}
private String resource(String path) throws Exception {
return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,155 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.OrderCargoClient;
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class InboundCartonLabelServiceTest {
@Mock private InboundCartonLabelRepository repository;
@Mock private OrderCargoClient orderCargoClient;
private InboundCartonLabelService service;
@BeforeEach
void setUp() {
service = new InboundCartonLabelService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "orderCargoClient", orderCargoClient);
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
}
@Test
void firstAllocationUsesCargoOrderAndFreezesEveryPhysicalCarton() {
InboundOrderEntity inbound = inbound();
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound);
when(repository.findByInbound(10001L, 70001L)).thenReturn(new ArrayList<>());
var second = cargo(80002L, 2, 2, "50", "30", "20");
var first = cargo(80001L, 1, 3, "60", "40", "30");
when(orderCargoClient.listForCartonLabels("internal-token", 10001L, 60001L))
.thenReturn(ApiResponse.success(List.of(second, first)));
List<InboundCartonLabelEntity> inserted = new ArrayList<>();
org.mockito.Mockito.doAnswer(invocation -> {
inserted.add(invocation.getArgument(0));
return null;
}).when(repository).insert(any());
var batch = service.getOrCreate(10001L, 70001L);
assertThat(batch.totalCount()).isEqualTo(5);
assertThat(batch.labels()).extracting(label -> label.boxCode())
.containsExactly("PCHK2608040174#5-1", "PCHK2608040174#5-2", "PCHK2608040174#5-3",
"PCHK2608040174#5-4", "PCHK2608040174#5-5");
assertThat(batch.labels()).extracting(label -> label.inboundDetailId())
.containsExactly(80001L, 80001L, 80001L, 80002L, 80002L);
assertThat(batch.labels().get(0).boxSpecification()).isEqualTo("60x40x30cm");
assertThat(batch.labels().get(0).cartonText()).isEqualTo("CTNS:5-1");
assertThat(inserted).hasSize(5);
}
@Test
void repeatedAndPartialPrintsReadFrozenMappingWithoutReallocation() {
InboundOrderEntity inbound = inbound();
List<InboundCartonLabelEntity> existing = labels(inbound, 5);
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound);
when(repository.findByInbound(10001L, 70001L)).thenReturn(existing);
var batch = service.list(10001L, 70001L, List.of(5, 2, 2));
assertThat(batch.totalCount()).isEqualTo(5);
assertThat(batch.labels()).extracting(label -> label.cartonIndex()).containsExactly(2, 5);
verify(orderCargoClient, never()).listForCartonLabels(any(String.class),
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong());
verify(repository, never()).insert(any());
}
@Test
void resolvesBoxCodeInsideCustomerBoundary() {
InboundCartonLabelEntity label = labels(inbound(), 1).get(0);
when(repository.findByBoxCode(10001L, "PCHK2608040174#1-1")).thenReturn(label);
var resolved = service.resolveByBoxCode(10001L, " PCHK2608040174#1-1 ");
assertThat(resolved.inboundId()).isEqualTo(70001L);
assertThat(resolved.orderId()).isEqualTo(60001L);
assertThat(resolved.inboundDetailId()).isEqualTo(80001L);
assertThat(resolved.cartonIndex()).isEqualTo(1);
verify(repository).findByBoxCode(10001L, "PCHK2608040174#1-1");
}
@Test
void refusesFirstAllocationAfterMeasurementSubmission() {
InboundOrderEntity inbound = inbound();
inbound.setProcessStatus("MEASURE_PENDING");
when(repository.lockInbound(10001L, 70001L)).thenReturn(inbound);
when(repository.findByInbound(10001L, 70001L)).thenReturn(List.of());
assertThatThrownBy(() -> service.getOrCreate(10001L, 70001L))
.isInstanceOf(BizException.class)
.hasMessageContaining("不允许首次生成箱号");
verify(orderCargoClient, never()).listForCartonLabels(any(String.class),
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong());
}
private InboundOrderEntity inbound() {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(70001L);
inbound.setTenantId(10001L);
inbound.setOrderId(60001L);
inbound.setInboundCode("PCHK2608040174");
inbound.setProcessStatus("WAIT_MEASURE");
inbound.setWarehouseName("MCC1");
inbound.setChannelName("洛杉矶普船海卡");
return inbound;
}
private OrderCargoClient.CargoLabelBinding cargo(long id, int itemNo, int count,
String length, String width, String height) {
return new OrderCargoClient.CargoLabelBinding(id, 60001L, itemNo, "货物" + itemNo, count,
new BigDecimal(length), new BigDecimal(width), new BigDecimal(height));
}
private List<InboundCartonLabelEntity> labels(InboundOrderEntity inbound, int total) {
List<InboundCartonLabelEntity> values = new ArrayList<>();
for (int index = 1; index <= total; index++) {
InboundCartonLabelEntity label = new InboundCartonLabelEntity();
label.setId((long) index);
label.setTenantId(inbound.getTenantId());
label.setInboundId(inbound.getId());
label.setOrderId(inbound.getOrderId());
label.setInboundDetailId(80001L);
label.setCargoItemNo(1);
label.setCargoName("货物1");
label.setDetailPackageCount(total);
label.setInboundCode(inbound.getInboundCode());
label.setBoxCode(inbound.getInboundCode() + "#" + total + "-" + index);
label.setTotalCartonCount(total);
label.setCartonIndex(index);
label.setBoxSpecification("60x40x30cm");
label.setLengthCm(new BigDecimal("60"));
label.setWidthCm(new BigDecimal("40"));
label.setHeightCm(new BigDecimal("30"));
values.add(label);
}
return values;
}
}