fix(firstleg): preserve confirmed loading history in departure gates

This commit is contained in:
2026-10-02 13:36:39 +08:00
parent 0b34f99be5
commit 2d73ce9f37
2 changed files with 82 additions and 6 deletions
@@ -11,6 +11,9 @@ import com.baiyue.firstleg.outbound.dispatch.service.OutboundDispatchService;
import com.baiyue.firstleg.portschedule.service.CarrierScheduleQueryService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -31,6 +34,72 @@ class OutboundDispatchServiceTest {
when(repository.timeline(any(), any(), any())).thenReturn(List.of());
}
@ParameterizedTest
@ValueSource(strings = {"DELIVERED", "COMPLETED"})
void confirmedDepartureKeepsLoadedGateWhenInboundAdvances(String inboundStatus) {
stubContainer(List.of(bindingWithStatus(11L, inboundStatus)));
OutboundDispatchEntity confirmed = state(31L, 11L, "WAITING_FOR_LOADING", 1);
confirmed.setConfirmedAt(LocalDateTime.of(2026, 10, 1, 12, 0));
when(repository.dispatch(7L, 11L, 22L)).thenReturn(confirmed);
Detail detail = serviceWithoutCarrier().detail(22L);
assertThat(detail.aggregate().gate().passed()).isTrue();
assertThat(detail.aggregate().inbounds().get(0).inboundStatus()).isEqualTo(inboundStatus);
assertThat(detail.aggregate().gate().inbounds().get(0).gate().items())
.filteredOn(item -> item.code().equals("INBOUND_LOADED"))
.singleElement().satisfies(item -> {
assertThat(item.passed()).isTrue();
assertThat(item.actual()).isEqualTo(inboundStatus);
assertThat(item.requirement()).contains("已确认离仓");
});
verify(repository, never()).confirm(anyLong(), anyInt(), anyLong(), anyString(), any());
}
@ParameterizedTest
@ValueSource(strings = {"DELIVERED", "COMPLETED"})
void laterInboundStateWithoutConfirmedDepartureCannotPassOrConfirm(String inboundStatus) {
var bindings = List.of(bindingWithStatus(11L, inboundStatus));
stubContainer(bindings);
when(repository.lockBindings(22L)).thenReturn(bindings);
when(repository.claim(eq(9L), eq("CONFIRM_DISPATCH"), eq("later-unconfirmed"), anyString())).thenReturn(true);
var service = serviceWithoutCarrier();
assertThat(service.checkGate(22L).passed()).isFalse();
assertThatThrownBy(() -> service.confirm(22L, 9L, "admin", "later-unconfirmed", new ActionRequest(0, null)))
.isInstanceOf(BizException.class).hasMessageContaining("门禁");
verify(repository, never()).confirm(anyLong(), anyInt(), anyLong(), anyString(), any());
}
@Test
void reversedDepartureAndMissingConfirmationTimeDoNotProveLoadedHistory() {
stubContainer(List.of(bindingWithStatus(11L, "DELIVERED")));
var state = state(31L, 11L, "READY_FOR_DISPATCH", 2);
state.setConfirmedAt(LocalDateTime.of(2026, 10, 1, 12, 0));
when(repository.dispatch(7L, 11L, 22L)).thenReturn(state);
assertThat(serviceWithoutCarrier().checkGate(22L).passed()).isFalse();
state.setStatus("WAITING_FOR_LOADING");
state.setConfirmedAt(null);
assertThat(serviceWithoutCarrier().checkGate(22L).passed()).isFalse();
}
@Test
void confirmedDepartureDoesNotBypassLoadingOrOtherAuthoritativeGates() {
var confirmed = state(31L, 11L, "WAITING_FOR_LOADING", 1);
confirmed.setConfirmedAt(LocalDateTime.of(2026, 10, 1, 12, 0));
stubContainer(List.of(bindingWithStatus(11L, "LOADING")));
when(repository.dispatch(7L, 11L, 22L)).thenReturn(confirmed);
assertThat(serviceWithoutCarrier().checkGate(22L).passed()).isFalse();
stubContainer(List.of(bindingWithStatus(11L, "DELIVERED")));
when(repository.evidence(22L, 44L)).thenReturn(new OutboundDispatchReadMapper.Evidence(4, 1));
var gate = serviceWithoutCarrier().checkGate(22L);
assertThat(gate.passed()).isFalse();
assertThat(gate.inbounds().get(0).gate().items()).filteredOn(item -> item.code().equals("LOADING_EVIDENCE_COMPLETE"))
.extracting(GateItem::passed).containsExactly(false);
}
private static OutboundDispatchReadMapper.Binding bindingWithStatus(long inboundId, String status) {
return new OutboundDispatchReadMapper.Binding(10L, "O-10", inboundId, "I-" + inboundId, 7L, 8L, "客户A",
status, false, 22L, "CAB-22", "C-22", 44L, "COMPLETED");
}
@Test
void containerEvidenceFailureFailsEveryBindingConsistently() {
OutboundDispatchService service = serviceWithoutCarrier();