From bc8c1467ee3d777cb12ede42fe915cda97c95a5a Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 6 Oct 2026 00:52:48 +0800 Subject: [PATCH] fix(lastleg): return persisted grouped dispatch replay facts --- .../dispatch/service/DispatchService.java | 7 +++- .../dispatch/service/DispatchServiceTest.java | 32 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java index 63840c822..62a86b738 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java @@ -556,7 +556,12 @@ public class DispatchService { drafts.updateById(draft); } } - return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId())); + // Exact grouped replay must return the same persisted timestamp precision + // on the initial call as on later reads from DATETIME(3). + DispatchOrder responseOrder = preserveExactReplay + ? Objects.requireNonNull(orders.lockById(customerId, dispatch.getId()), "Saved dispatch is missing") + : dispatch; + return response(responseOrder, items.findByDispatchOrder(customerId, dispatch.getId())); } @Transactional diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java index 2bcef6e42..1cbb76918 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java @@ -584,6 +584,14 @@ class DispatchServiceTest { verify(orders, never()).insert(any(DispatchOrder.class)); } + @Test + void groupedInitialResponseUsesPersistedTimestampPrecisionForExactReplay() { + var fixture = twoGroupFixture(true); + var first = service.preArrangeDraft("7", fixture.request()); + assertEquals(123_000_000, first.get(0).dispatch().createdAt().getNano()); + assertEquals(first, service.preArrangeDraft("7", fixture.request())); + } + @Test void changedMembershipDuplicateSourceExpiredDraftAndCapacityFailBeforeCreatingAnyGroup() { var fixture = twoGroupFixture(); @@ -613,7 +621,8 @@ class DispatchServiceTest { var jdbc = new org.springframework.jdbc.core.JdbcTemplate(ds); jdbc.execute("CREATE TABLE dispatch_probe(id BIGINT PRIMARY KEY)"); org.mockito.Mockito.doAnswer(call -> { var row=(DispatchOrder)call.getArgument(0); - jdbc.update("INSERT INTO dispatch_probe VALUES(?)",row.getId());return 1; }).when(orders).insert(any(DispatchOrder.class)); + jdbc.update("INSERT INTO dispatch_probe VALUES(?)",row.getId()); + fixture.savedOrders().put(row.getId(), row);return 1; }).when(orders).insert(any(DispatchOrder.class)); var transaction = new org.springframework.transaction.support.TransactionTemplate( new org.springframework.jdbc.datasource.DataSourceTransactionManager(ds)); assertThrows(ResponseStatusException.class, () -> transaction.execute(status -> service.preArrangeDraft("7",fixture.request()))); @@ -622,6 +631,10 @@ class DispatchServiceTest { } private GroupFixture twoGroupFixture() { + return twoGroupFixture(false); + } + + private GroupFixture twoGroupFixture(boolean databaseTimestampPrecision) { var draft = new DispatchDraft(); draft.setId(1L);draft.setDraftToken("draft-multi");draft.setOperatorId("7"); draft.setStatus("ACTIVE");draft.setExpiresAt(LocalDateTime.now().plusMinutes(30)); lenient().when(drafts.lockActive("draft-multi","7")).thenReturn(draft); @@ -629,7 +642,17 @@ class DispatchServiceTest { var allCandidates = new java.util.ArrayList(); var savedOrders = new java.util.HashMap(); var savedItems = new java.util.HashMap>(); - lenient().doAnswer(call -> {var row=(DispatchOrder)call.getArgument(0);savedOrders.put(row.getId(),row);return 1;}) + lenient().doAnswer(call -> { + var row=(DispatchOrder)call.getArgument(0); + if (databaseTimestampPrecision) { + row.setCreatedAt(row.getCreatedAt().withNano(123_456_000)); + var persisted = new DispatchOrder(); + org.springframework.beans.BeanUtils.copyProperties(row, persisted); + persisted.setCreatedAt(row.getCreatedAt().withNano(123_000_000)); + savedOrders.put(row.getId(), persisted); + } else savedOrders.put(row.getId(),row); + return 1; + }) .when(orders).insert(any(DispatchOrder.class)); lenient().when(orders.lockById(eq("100"),any())).thenAnswer(call -> savedOrders.get(call.getArgument(1))); lenient().doAnswer(call -> {var row=(DispatchItem)call.getArgument(0);savedItems.computeIfAbsent(row.getDispatchOrderId(),k->new java.util.ArrayList<>()).add(row);return 1;}) @@ -651,11 +674,12 @@ class DispatchServiceTest { } lenient().when(draftContainers.findByToken("draft-multi")).thenReturn(containerList); return new GroupFixture(draft,containerList,allCandidates,new DispatchRequests.PreArrangeDraft("draft-multi",List.of( - new DispatchRequests.DraftGroup(21L,List.of(10L,11L),List.of()),new DispatchRequests.DraftGroup(22L,List.of(12L,13L),List.of())))); + new DispatchRequests.DraftGroup(21L,List.of(10L,11L),List.of()),new DispatchRequests.DraftGroup(22L,List.of(12L,13L),List.of()))),savedOrders); } private record GroupFixture(DispatchDraft draft,List containers, - List candidates,DispatchRequests.PreArrangeDraft request) { } + List candidates,DispatchRequests.PreArrangeDraft request, + java.util.Map savedOrders) { } private DispatchPalletCandidate candidate(long planPalletId, Long warehousePalletId, String destination) { return candidate(planPalletId, warehousePalletId, destination, 12);