fix(firstleg): make truck status informational for departure

This commit is contained in:
2026-08-26 12:36:14 +08:00
parent 8eeb52ba77
commit 1584c7a0a3
4 changed files with 41 additions and 5 deletions
@@ -44,6 +44,34 @@ class OutboundDispatchServiceTest {
.extracting(GateItem::passed).containsExactly(false);
}
@Test
void confirmAllowsMissingTruckAppointmentWhenEveryAuthoritativeGatePasses() {
OutboundDispatchService service = serviceWithoutCarrier();
stubContainer();
when(repository.truck(22L, 44L)).thenReturn(null);
stubConfirmMutation("no-truck");
Detail result = service.confirm(22L, 9L, "admin", "no-truck", new ActionRequest(0, null));
assertThat(result.aggregate().truckAppointmentId()).isNull();
assertThat(result.aggregate().gate().passed()).isTrue();
verify(repository, times(2)).confirm(anyLong(), eq(0), eq(9L), eq("admin"), isNull());
}
@Test
void confirmAllowsPendingTruckAppointmentWhenEveryAuthoritativeGatePasses() {
OutboundDispatchService service = serviceWithoutCarrier();
stubContainer();
when(repository.truck(22L, 44L)).thenReturn(new OutboundDispatchReadMapper.IdStatus(77L, "PENDING"));
stubConfirmMutation("pending-truck");
Detail result = service.confirm(22L, 9L, "admin", "pending-truck", new ActionRequest(0, null));
assertThat(result.aggregate().truckAppointmentStatus()).isEqualTo("PENDING");
assertThat(result.aggregate().gate().passed()).isTrue();
verify(repository, times(2)).confirm(anyLong(), eq(0), eq(9L), eq("admin"), isNull());
}
@Test
void detailReturnsOneVersionAndAllInboundBindings() {
OutboundDispatchService service = serviceWithoutCarrier();
@@ -185,6 +213,15 @@ class OutboundDispatchServiceTest {
when(repository.evidence(7L, 12L)).thenReturn(new OutboundDispatchReadMapper.Evidence(5, 1));
}
private void stubConfirmMutation(String key) {
when(repository.lockBindings(22L)).thenReturn(bindings());
when(repository.dispatchLocked(7L, 11L, 22L)).thenReturn(state(31L, 11L, "READY_FOR_DISPATCH", 0));
when(repository.dispatchLocked(7L, 12L, 22L)).thenReturn(state(32L, 12L, "READY_FOR_DISPATCH", 0));
when(repository.claim(eq(9L), eq("CONFIRM_DISPATCH"), eq(key), anyString())).thenReturn(true);
when(repository.confirm(anyLong(), eq(0), eq(9L), eq("admin"), isNull())).thenReturn(1);
when(repository.completeOperation(31L, 9L, "CONFIRM_DISPATCH", key)).thenReturn(1);
}
private static List<OutboundDispatchReadMapper.Binding> bindings() {
return List.of(binding(11L, "I-11"), binding(12L, "I-12"));
}
@@ -48,6 +48,8 @@ class OutboundDispatchSqlContractTest {
.contains("outbound-dispatches/{containerBookingId}/reverse:")
.contains("every real inbound binding", "No partial success is possible")
.contains("Once an actual DEPARTED_WAREHOUSE node exists")
.contains("absence never blocks manual outbound confirmation")
.contains("PENDING or any non-terminal state never affects gate.passed")
.contains("expectedVersion")
.contains("Idempotency-Key")
.doesNotContain("confirm-dispatch", "reverse-dispatch", "check-gate", "inboundId, containerBookingId");