From de29e639e4047547197e184ac25f7b96304a9c0d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 16 Sep 2026 11:50:30 +0800 Subject: [PATCH] fix: check released bookings only for active loading allocations --- .../mapper/LoadingCompletionGateMapper.java | 6 ++- .../LoadingCompletionGateSqlContractTest.java | 3 ++ .../LoadingCompletionGateServiceTest.java | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingCompletionGateServiceTest.java diff --git a/firstleg/src/main/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateMapper.java index 62e2ea610..fa400f188 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateMapper.java @@ -4,7 +4,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; -/** SQL-backed hard gate for every loading plan linked to an inbound. */ +/** SQL-backed hard gate for loading plans with an active allocation to the inbound. */ @Mapper public interface LoadingCompletionGateMapper { @Select(""" @@ -16,6 +16,10 @@ public interface LoadingCompletionGateMapper { FROM fl_preload_loading_plan_link l JOIN fl_loading_plan p ON p.id=l.loading_plan_id WHERE l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId} + AND EXISTS ( + SELECT 1 FROM fl_loading_plan_item i + WHERE i.loading_plan_id=l.loading_plan_id AND i.account_id=l.tenant_id + AND i.inbound_id=l.inbound_id AND i.deleted=0 AND i.package_count>0) """) GateSnapshot snapshot(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateSqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateSqlContractTest.java index 66e311dab..507a4fcf5 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateSqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/mapper/LoadingCompletionGateSqlContractTest.java @@ -19,6 +19,9 @@ class LoadingCompletionGateSqlContractTest { .contains("b.status='RELEASED'") .doesNotContain("fl_truck_appointment", "truckReady", "t.status='COMPLETED'") .contains("l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId}") + .contains("SELECT 1 FROM fl_loading_plan_item i") + .contains("i.loading_plan_id=l.loading_plan_id AND i.account_id=l.tenant_id") + .contains("i.inbound_id=l.inbound_id AND i.deleted=0 AND i.package_count>0") .doesNotContain("fl_customs_case", "fl_customs_receipt", "customsReady") .doesNotContain("<", ">"); } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingCompletionGateServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingCompletionGateServiceTest.java new file mode 100644 index 000000000..e66877ee6 --- /dev/null +++ b/firstleg/src/test/java/com/baiyue/firstleg/loading/service/LoadingCompletionGateServiceTest.java @@ -0,0 +1,38 @@ +package com.baiyue.firstleg.loading.service; + +import com.baiyue.firstleg.loading.mapper.LoadingCompletionGateMapper; +import com.baiyue.firstleg.loading.mapper.LoadingCompletionGateMapper.GateSnapshot; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LoadingCompletionGateServiceTest { + @Mock LoadingCompletionGateMapper mapper; + @InjectMocks LoadingCompletionGateService service; + + @Test + void acceptsAllActivelyAllocatedContainersWithReleasedBookings() { + when(mapper.snapshot(10L, 20L)).thenReturn(new GateSnapshot(2, 2)); + service.requireReleasedBooking(10L, 20L); + } + + @Test + void blocksAnyActivelyAllocatedContainerWithoutReleasedBooking() { + when(mapper.snapshot(10L, 20L)).thenReturn(new GateSnapshot(2, 1)); + assertThatThrownBy(() -> service.requireReleasedBooking(10L, 20L)) + .hasMessageContaining("订舱尚未完成"); + } + + @Test + void noActiveAllocationDoesNotVacuouslyPass() { + when(mapper.snapshot(10L, 20L)).thenReturn(new GateSnapshot(0, 0)); + assertThatThrownBy(() -> service.requireReleasedBooking(10L, 20L)) + .hasMessageContaining("未找到有效柜次关联"); + } +}