Merge remote-tracking branch 'origin/test' into dev

# Conflicts:
#	lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java
#	lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGatewayTest.java
#	warehouse-overseas-service/src/main/java/com/baiyue/warehouse/service/WarehousePalletBatchService.java
This commit is contained in:
2026-09-09 18:20:40 +08:00
58 changed files with 1787 additions and 932 deletions
@@ -23,6 +23,8 @@ class FbaPricingContractTest {
+ " 'firstleg:fba-pricing:confirm')");
assertPricingAuthority("send", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
+ " 'firstleg:inbound:send-fee')");
assertPricingAuthority("adjustPublished", "hasAnyAuthority('firstleg:fba-pricing:adjust',"
+ " 'firstleg:inbound:confirm-send-fee')");
}
@Test
@@ -75,6 +75,9 @@ class LoadingListControllerContractTest {
.contains("PDA任务尚未领取且无任何物理作业事实")
.contains("/loading-plans/{planId}/confirm")
.contains("/loading-plans/{planId}/lifecycle")
.contains("/loading-plans/{planId}/pallet-labels/prepare")
.contains("/loading-plans/{planId}/pallet-labels/print")
.contains("柜号-FBA仓编码-三位序号")
.contains("不得以错误文案或 A0404 猜测阶段")
.contains("confirmationStatus")
.contains("canConfirm")
@@ -123,6 +126,8 @@ class LoadingListControllerContractTest {
"/api/platform/v1/first-leg/loading-plans/{planId}/lifecycle",
"/api/platform/v1/first-leg/loading-plans/{planId}/transport",
"/api/platform/v1/first-leg/loading-plans/{planId}/confirm",
"/api/platform/v1/first-leg/loading-plans/{planId}/pallet-labels/prepare",
"/api/platform/v1/first-leg/loading-plans/{planId}/pallet-labels/print",
"/api/platform/v1/first-leg/loading-lists",
"/api/platform/v1/first-leg/loading-lists/{planId}");
Map<String, Object> components = stringMap(document.get("components"));
@@ -132,7 +137,7 @@ class LoadingListControllerContractTest {
"ConfirmedSummary", "LoadingItem", "OperationHistory", "PreloadDetail",
"ConfirmedDetail", "TransportUpdateRequest", "PreloadReadiness",
"ConfirmPreloadRequest", "PreloadConfirmation", "PreloadConfirmationEnvelope",
"LifecycleResolution", "LifecycleResolutionEnvelope");
"LifecycleResolution", "LifecycleResolutionEnvelope", "PalletLabelPrintRequest");
assertThat(properties(schemas, "LifecycleResolution")).containsOnlyKeys(
"planId", "containerBookingId", "warehouseId", "planNo", "planStatus",
"detailResource", "requiredPermission");
@@ -9,6 +9,7 @@ import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.Row;
import com.baiyue.firstleg.mapper.LoadingCompletedEventMapper.PalletRow;
import com.baiyue.firstleg.mapper.LoadingCartonManifestMapper;
import com.baiyue.firstleg.mapper.LoadingCartonManifestMapper.LabelRow;
import com.baiyue.firstleg.mapper.LoadingPalletLabelMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,14 +31,19 @@ class LoadingCompletedOutboxServiceTest {
private final LoadingCompletedEventMapper snapshots = mock(LoadingCompletedEventMapper.class);
private final InboundOutboxMapper outbox = mock(InboundOutboxMapper.class);
private final LoadingCartonManifestMapper manifests = mock(LoadingCartonManifestMapper.class);
private final LoadingPalletLabelMapper palletLabels = mock(LoadingPalletLabelMapper.class);
private final LoadingPalletLabelService palletLabelService = mock(LoadingPalletLabelService.class);
private final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
private final LoadingCompletedOutboxService service = new LoadingCompletedOutboxService();
@BeforeEach void setup() {
ReflectionTestUtils.setField(service,"snapshotMapper",snapshots);
ReflectionTestUtils.setField(service,"cartonManifestMapper",manifests);
ReflectionTestUtils.setField(service,"palletLabelMapper",palletLabels);
ReflectionTestUtils.setField(service,"palletLabelService",palletLabelService);
ReflectionTestUtils.setField(service,"outboxMapper",outbox);
ReflectionTestUtils.setField(service,"objectMapper",mapper);
when(palletLabels.labels(anyLong())).thenReturn(List.of());
when(manifests.labelPool(anyLong(), anyLong())).thenAnswer(invocation -> List.of(
label(101, "BOX-101"), label(102, "BOX-102"),
label(202, "BOX-202"), label(302, "BOX-302")));
@@ -0,0 +1,46 @@
package com.baiyue.firstleg.service;
import com.baiyue.firstleg.mapper.LoadingPalletLabelMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class LoadingPalletLabelServiceTest {
@Test
void generatesStableIndexesPerContainerAndFbaAndSkipsDropship() {
LoadingPalletLabelMapper mapper = mock(LoadingPalletLabelMapper.class);
LoadingPalletLabelService service = new LoadingPalletLabelService();
ReflectionTestUtils.setField(service, "mapper", mapper);
List<LoadingPalletLabelMapper.LabelRow> saved = new ArrayList<>();
when(mapper.lockSources(10L)).thenReturn(List.of(source(3, "LAX9", 1, "STANDARD"),
source(1, "ONT8", 2, "STANDARD"), source(2, "ONT8", 1, "STANDARD"),
source(4, "ONT8", 4, "ONE_CLICK_DROPSHIP")));
when(mapper.labels(10L)).thenAnswer(ignored -> List.copyOf(saved));
when(mapper.insert(any())).thenAnswer(invocation -> { saved.add(invocation.getArgument(0)); return 1; });
var manifest = service.prepare(10L, 20L);
assertThat(manifest.totalCount()).isEqualTo(4);
assertThat(manifest.groups()).extracting(group -> group.destinationWarehouseCode() + ":" + group.totalCount())
.containsExactly("LAX9:1", "ONT8:3");
assertThat(saved).extracting(LoadingPalletLabelMapper.LabelRow::getPalletCode)
.containsExactly("MSCU6639870-LAX9-001", "MSCU6639870-ONT8-001",
"MSCU6639870-ONT8-002", "MSCU6639870-ONT8-003");
}
private LoadingPalletLabelMapper.SourceRow source(long itemId, String destination, int pallets, String fulfillment) {
var row = new LoadingPalletLabelMapper.SourceRow(); row.setPlanId(10L); row.setPlanNo("LP-1");
row.setPlanStatus("LOADING"); row.setContainerNo("MSCU6639870"); row.setWarehouseId(20L);
row.setItemId(itemId); row.setAccountId(100L); row.setInboundId(itemId + 1000);
row.setInboundCode("RK-" + itemId); row.setDestinationWarehouseCode(destination);
row.setDestinationWarehouseName(destination); row.setPalletCount(pallets);
row.setFulfillmentType(fulfillment); row.setDestinationType("FBA"); return row;
}
}
@@ -7,6 +7,7 @@ import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
@@ -16,7 +17,7 @@ class TerminalDrayageMailDispatcherTest {
TerminalDrayageMailDispatcher worker = spy(new TerminalDrayageMailDispatcher(mapper));
void setup() {
ReflectionTestUtils.setField(worker,"from","operations@example.com");
ReflectionTestUtils.setField(worker,"username","operations@example.com");
doReturn(sender).when(worker).sender();
when(mapper.pendingMails()).thenReturn(List.of(new TerminalDrayageMapper.MailTask(
1L,11L,2L,"Fleet","fleet@example.com","REQUEST_PICKUP","Pickup","Body","PENDING",0,null,null,null)));
@@ -27,8 +28,10 @@ class TerminalDrayageMailDispatcherTest {
@Test void onlyClaimsOnceAndMarksSentAfterSmtpAccepted() {
setup(); worker.dispatch();
var ordered = inOrder(mapper,sender);
var sent = org.mockito.ArgumentCaptor.forClass(SimpleMailMessage.class);
ordered.verify(mapper).claimMail(1L);
ordered.verify(sender).send(any(SimpleMailMessage.class));
ordered.verify(sender).send(sent.capture());
assertThat(sent.getValue().getFrom()).isEqualTo("operations@example.com");
ordered.verify(mapper).completeMail(1L);
when(mapper.claimMail(1L)).thenReturn(0);
worker.dispatch();
@@ -27,6 +27,8 @@ class TerminalDrayageMailServiceTest {
}
@Test void replacementQueuesCancellationThenRequestWithSeparateFrozenRecipients() {
when(mapper.lastPickupMail(11L, 1L)).thenReturn(new TerminalDrayageMapper.MailTask(
90L,11L,1L,"Old Fleet","old@example.com","REQUEST_PICKUP","subject","body","SENT",1,null,null,null));
service.replaceFleet(11L, "MSCU1234567", 1L, 2L, 7L);
var order = inOrder(mapper);
order.verify(mapper).insertMail(anyLong(), eq(11L), eq(1L), eq("Old Fleet"), eq("old@example.com"),
@@ -35,6 +37,16 @@ class TerminalDrayageMailServiceTest {
eq("REQUEST_PICKUP"), eq("Port pickup request - MSCU1234567"), contains("Please arrange"), eq(7L));
}
@Test void replacementWithoutPreviousPickupMailQueuesOnlyNewRequest() {
service.replaceFleet(11L, "MSCU1234567", 1L, 2L, 7L);
verify(mapper, never()).insertMail(anyLong(), eq(11L), eq(1L), anyString(), anyString(),
eq("CANCEL_PICKUP"), anyString(), anyString(), eq(7L));
verify(mapper).insertMail(anyLong(), eq(11L), eq(2L), eq("New Fleet"), eq("new@example.com"),
eq("REQUEST_PICKUP"), eq("Port pickup request - MSCU1234567"), contains("Please arrange"), eq(7L));
verify(suppliers, never()).truckingContact("test-token", 1L, false);
}
@Test void cancellationUsesOriginalRecipientEvenIfSupplierEmailChanged() {
when(mapper.lastPickupMail(11L, 1L)).thenReturn(new TerminalDrayageMapper.MailTask(
90L,11L,1L,"Frozen Fleet","original@example.com","REQUEST_PICKUP","subject","body","SENT",1,null,null,null));
@@ -374,18 +374,23 @@ class TerminalDrayageServiceTest {
}
@Test
void manualPickupEmailUsesLockedVersionAndRejectsStaleClick() {
void manualPickupEmailRejectsStaleClickAndAllowsRepeatWithLatestVersion() {
Context context = context("ARRIVED_DESTINATION_PORT",null);
when(mapper.lockContext(11L)).thenReturn(context);
when(mapper.context(11L)).thenReturn(context);
TerminalDrayageEntity row = row(3);
when(mapper.lock(11L)).thenReturn(row);
when(mapper.find(11L)).thenReturn(row);
when(mapper.updateAggregate(any(),anyInt(),anyLong(),anyString())).thenAnswer(invocation -> {row.setVersion(4);return 1;});
when(mapper.updateAggregate(any(),anyInt(),anyLong(),anyString())).thenAnswer(invocation -> {
row.setVersion(invocation.getArgument(1, Integer.class) + 1);
return 1;
});
var request = new com.baiyue.firstleg.terminaldrayage.dto.TerminalDrayageDtos.EmailRequest(3);
service.sendPickupEmail(11L,7L,"admin","mail-1",request);
assertThatThrownBy(() -> service.sendPickupEmail(11L,7L,"admin","mail-2",request)).isInstanceOf(BizException.class);
verify(mails,times(1)).request(11L,"MSCU1234567",91L,7L);
service.sendPickupEmail(11L,7L,"admin","mail-3",
new com.baiyue.firstleg.terminaldrayage.dto.TerminalDrayageDtos.EmailRequest(4));
verify(mails,times(2)).request(11L,"MSCU1234567",91L,7L);
}
private Context context(String node, LocalDateTime unloadedAt) {