fix(firstleg): allow audited carrier node time corrections

This commit is contained in:
2026-08-26 15:41:03 +08:00
parent 4ae236fe11
commit 2ebea9e789
5 changed files with 85 additions and 3 deletions
@@ -24,6 +24,9 @@ class CarrierScheduleQueryOpenApiContractTest {
.contains("enum: [READY, MANUAL_REQUIRED, STOPPED]")
.contains("latestActualNode:")
.contains("latestActualAt:")
.contains("current latestActualNode may append an audited correction fact")
.contains("replace latestActualAt")
.contains("cannot be earlier")
.contains("internal execution fields are intentionally absent")
.doesNotContain("enum: [READY, MANUAL_REQUIRED, ARRIVED")
.doesNotContain("/{queryId}/attempts:")
@@ -292,6 +292,52 @@ class CarrierScheduleQueryServiceTest {
verify(repository).completeOperation(eq(1L), eq(7L), eq("manual-empty-1"), anyString());
}
@Test
void manualActualMayCorrectCurrentNodeTimeThroughNewImmutableFact() {
CarrierScheduleQueryEntity before = task(0, 3);
before.setStatus("READY"); before.setLatestStandardNode("PICKED_UP");
before.setLatestNodeObservedAt(NOW.minusDays(1));
CarrierScheduleQueryEntity after = task(0, 4);
after.setStatus("READY"); after.setLatestStandardNode("PICKED_UP");
after.setLatestNodeObservedAt(NOW.minusDays(2));
when(repository.lock(1L)).thenReturn(before); when(repository.find(1L)).thenReturn(after);
when(repository.insertIfAbsent(any(CarrierScheduleNodeFactEntity.class))).thenReturn(true);
when(repository.updateState(any(), any(Integer.class), anyLong(), anyString())).thenReturn(1);
var result = service.addManualNode(1L, 7L, "admin", "manual-picked-up-correction",
new ManualNodeRequest(3, "PICKED_UP", NOW, null, NOW.minusDays(2), null,
"Los Angeles", "修正提柜时间"));
assertThat(result.query().query().latestActualNode()).isEqualTo("PICKED_UP");
assertThat(result.query().query().latestActualAt()).isEqualTo(NOW.minusDays(2));
ArgumentCaptor<CarrierScheduleNodeFactEntity> fact =
ArgumentCaptor.forClass(CarrierScheduleNodeFactEntity.class);
verify(repository).insertIfAbsent(fact.capture());
assertThat(fact.getValue().getActualAt()).isEqualTo(NOW.minusDays(2));
verify(repository).updateState(before, 3, 7L, "admin");
verify(repository).completeOperation(eq(1L), eq(7L), eq("manual-picked-up-correction"), anyString());
}
@Test
void manualActualRejectsEarlierStandardNodeWithoutAppendingFact() {
CarrierScheduleQueryEntity task = task(0, 3);
task.setStatus("READY"); task.setLatestStandardNode("PICKED_UP");
task.setLatestNodeObservedAt(NOW.minusDays(1));
when(repository.lock(1L)).thenReturn(task);
assertThatThrownBy(() -> service.addManualNode(1L, 7L, "admin", "manual-pickup-ready-late",
new ManualNodeRequest(3, "PICKUP_READY", NOW, null, NOW.plusDays(2), null,
"Los Angeles", "倒序补录可提柜")))
.isInstanceOf(BizException.class)
.hasMessageContaining("不得早于当前最新实际节点");
verify(repository, never()).insertIfAbsent(any(CarrierScheduleNodeFactEntity.class));
verify(repository, never()).updateState(any(), any(Integer.class), anyLong(), anyString());
verify(repository, never()).insert(any(com.baiyue.firstleg.portschedule.entity.CarrierScheduleQueryHistoryEntity.class));
verify(repository, never()).completeOperation(anyLong(), anyLong(), anyString(), anyString());
verify(nodeOutbox, never()).publish(any(), any(), anyString());
}
@Test
void manualEstimatedFactAdvancesVersionWithoutAdvancingActualProgress() {
CarrierScheduleQueryEntity before = task(0, 3);