fix: keep fleet booking timestamps stable across persistent replay

This commit is contained in:
2026-10-23 04:25:47 +08:00
parent 5c5514436e
commit f946285bcc
2 changed files with 63 additions and 2 deletions
@@ -0,0 +1,51 @@
package com.baiyue.lastleg.fleet.trip;
import com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory;
import com.baiyue.lastleg.fleet.quote.FleetQuoteCandidateService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/** 模拟重新从DATETIME(3)读取的实体,不让同一个内存对象掩盖首次与重放精度差异。 */
class FleetBookingServiceReplayTest {
@Test void incomingReplayKeepsPersistedMillisecondTimestampAndCreatesOnlyOneIncoming() throws Exception {
var json=new ObjectMapper().findAndRegisterModules();
var trips=mock(FleetTripMapper.class);var bookings=mock(FleetBookingMapper.class);
var history=mock(FleetTripService.class);var incoming=mock(FleetBookingIncomingGateway.class);
var trip=new FleetTripEntity();trip.setId(2L);trip.setTripNo("CT-ONE");trip.setStatus("PLANNED");trip.setWarehouseId(42L);
trip.setWarehouseName("TEST");trip.setRecordVersion(5L);trip.setPlannedDepartureAt(LocalDateTime.parse("2026-10-23T12:00:00"));
when(trips.lockByNo("CT-ONE")).thenReturn(trip);
var row=new FleetBookingEntity();row.setId(1L);row.setTripId(2L);row.setTripNo("CT-ONE");row.setBookingNo("FB-ONE");
row.setCustomerId("101");row.setStatus("CONFIRMED");row.setRecordVersion(7L);row.setDestinationKey("FBA:ONT8");
row.setUpdatedAt(LocalDateTime.parse("2026-10-23T09:00:00"));
row.setCargoJson(json.writeValueAsString(new FleetBookingModels.Cargo("FBA:ONT8","TEST",1,1,BigDecimal.ONE,BigDecimal.ONE,
"PALLET",BigDecimal.TEN,"USD",null,null,null)));
var persisted=new AtomicReference<>(row);
when(bookings.lock(2L,"FB-ONE")).thenAnswer(call->json.convertValue(persisted.get(),FleetBookingEntity.class));
when(bookings.updateById(any(FleetBookingEntity.class))).thenAnswer(call->{
var stored=json.convertValue(call.getArgument(0),FleetBookingEntity.class);
stored.setUpdatedAt(stored.getUpdatedAt().truncatedTo(ChronoUnit.MILLIS));persisted.set(stored);return 1;
});
doAnswer(call->{trip.setRecordVersion(trip.getRecordVersion()+1);return null;})
.when(history).recordExecution(eq(trip),eq(5L),eq("BOOKING_INCOMING"),anyString(),eq("operator"));
when(incoming.create(eq("operator"),any())).thenReturn(new FleetBookingIncomingGateway.Evidence(
"ZY-ONE","RK-ONE","42","FBA:ONT8","WAIT_PDA_RECEIVING",null,List.of(),0));
var service=new FleetBookingService(trips,bookings,new FleetBookingReservations(bookings,json),history,
mock(FleetTripExecutionService.class),mock(FleetCustomerDirectory.class),json,incoming,
mock(FleetBookingQuoteContext.class),mock(FleetQuoteCandidateService.class),
Clock.fixed(Instant.parse("2026-10-23T10:00:00.123456789Z"),ZoneOffset.UTC));
var command=new FleetBookingModels.StartIncoming(5L,7L,Instant.parse("2026-10-23T11:00:00Z"),"TEST",null,null,null,null,"test-incoming-key");
var first=service.startIncoming("CT-ONE","FB-ONE","operator",command);
var replay=service.startIncoming("CT-ONE","FB-ONE","operator",command);
assertFalse(first.replayed());assertTrue(replay.replayed());
assertEquals(Instant.parse("2026-10-23T10:00:00.123Z"),first.booking().updatedAt());
assertEquals(first.booking(),replay.booking());assertEquals(first.tripRecordVersion(),replay.tripRecordVersion());
verify(incoming,times(1)).create(eq("operator"),any());verify(bookings,times(1)).updateById(any(FleetBookingEntity.class));
}
}