fix(dispatch): preserve loading groups in atomic draft pre-arrangement
This commit is contained in:
+4
@@ -16,6 +16,10 @@ class SmartDispatchOpenApiContractTest {
|
||||
}
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/candidates:"));
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/pre-arrange:"));
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/drafts/pre-arrange:"));
|
||||
assertTrue(yaml.contains("required: [draftToken, groups]"));
|
||||
assertTrue(yaml.contains("required: [draftContainerId, planPalletIds, returnReceiptIds]"));
|
||||
assertTrue(yaml.contains("SmartDispatchDraftGroupResult:"));
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/draft-locks/lock:"));
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/draft-locks/unlock:"));
|
||||
assertTrue(yaml.contains("/api/platform/v1/last-leg/dispatches/draft-locks/release:"));
|
||||
|
||||
+14
@@ -75,6 +75,20 @@ class SmartDispatchControllerTest {
|
||||
assertThat(preArrange.value()).contains(OverseasPermissions.INBOUND_ORDER_SMART_DISPATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupedDraftUsesAuthenticatedOperatorAndDedicatedWriteAuthority() throws Exception {
|
||||
var request = new DispatchRequests.PreArrangeDraft("draft-1", List.of(
|
||||
new DispatchRequests.DraftGroup(1L, List.of(10L), List.of()),
|
||||
new DispatchRequests.DraftGroup(2L, List.of(11L), List.of())));
|
||||
controller.preArrangeDraft(platform(), request);
|
||||
verify(service).preArrangeDraft("7", request);
|
||||
assertThat(SmartDispatchController.class.getMethod("preArrangeDraft", AuthenticatedUser.class,
|
||||
DispatchRequests.PreArrangeDraft.class).getAnnotation(PreAuthorize.class).value())
|
||||
.contains(OverseasPermissions.INBOUND_ORDER_SMART_DISPATCH);
|
||||
org.junit.jupiter.api.Assertions.assertThrows(org.springframework.web.server.ResponseStatusException.class,
|
||||
() -> controller.preArrangeDraft(new AuthenticatedUser(7L, 100L, 0L, 8L, "customer", "MERCHANT", "WEB", Set.of()), request));
|
||||
}
|
||||
|
||||
private static AuthenticatedUser platform() {
|
||||
return new AuthenticatedUser(7L, 0L, 0L, 8L, "operator", "PLATFORM", "WEB",
|
||||
Set.of(OverseasPermissions.INBOUND_ORDER_LIST, OverseasPermissions.INBOUND_ORDER_SMART_DISPATCH));
|
||||
|
||||
+106
@@ -551,6 +551,112 @@ class DispatchServiceTest {
|
||||
verify(locks, never()).insert(any(DispatchPalletLock.class)); verify(pdaTasks, never()).insert(any(DispatchPdaTask.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoLoadingGroupsCreateTwoIndependentDispatchesAndExactReplayDoesNotWriteAgain() throws Exception {
|
||||
var fixture = twoGroupFixture();
|
||||
assertNotNull(DispatchService.class.getMethod("preArrangeDraft", String.class,
|
||||
DispatchRequests.PreArrangeDraft.class).getAnnotation(Transactional.class));
|
||||
var result = service.preArrangeDraft("7", fixture.request());
|
||||
assertEquals(2, result.size());
|
||||
assertTrue(!result.get(0).dispatch().id().equals(result.get(1).dispatch().id()));
|
||||
for (var group : result) {
|
||||
assertEquals(2, group.dispatch().palletCount()); assertEquals(2, group.dispatch().packageCount());
|
||||
assertEquals("ABE4,ABE8", group.dispatch().route());
|
||||
assertEquals(2, group.dispatch().items().size());
|
||||
}
|
||||
assertEquals(java.util.Set.of(10L, 11L), result.get(0).dispatch().items().stream()
|
||||
.map(DispatchResponse.Item::planPalletId).collect(java.util.stream.Collectors.toSet()));
|
||||
assertEquals(java.util.Set.of(12L, 13L), result.get(1).dispatch().items().stream()
|
||||
.map(DispatchResponse.Item::planPalletId).collect(java.util.stream.Collectors.toSet()));
|
||||
org.mockito.Mockito.clearInvocations(orders, items, locks, pdaTasks, drafts);
|
||||
assertEquals(result, service.preArrangeDraft("7", fixture.request()));
|
||||
verify(orders, never()).insert(any(DispatchOrder.class)); verify(orders, never()).updateById(any(DispatchOrder.class));
|
||||
verify(items, never()).insert(any(DispatchItem.class)); verify(locks, never()).updateById(any(DispatchPalletLock.class));
|
||||
verify(pdaTasks, never()).insert(any(DispatchPdaTask.class)); verify(drafts, never()).updateById(any(DispatchDraft.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatRequestCannotCollapseTwoOwnedDraftContainers() {
|
||||
var fixture = twoGroupFixture();
|
||||
when(candidates.lockSelected("100", List.of(10L,11L,12L,13L))).thenReturn(fixture.candidates());
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrange("100", "7",
|
||||
new DispatchRequests.PreArrange(List.of(10L,11L,12L,13L),null,null,null,"draft-multi")));
|
||||
verify(orders, never()).insert(any(DispatchOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changedMembershipDuplicateSourceExpiredDraftAndCapacityFailBeforeCreatingAnyGroup() {
|
||||
var fixture = twoGroupFixture();
|
||||
var swapped = new DispatchRequests.PreArrangeDraft("draft-multi", List.of(
|
||||
new DispatchRequests.DraftGroup(21L,List.of(10L,12L),List.of()),
|
||||
new DispatchRequests.DraftGroup(22L,List.of(11L,13L),List.of())));
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrangeDraft("7", swapped));
|
||||
var duplicated = new DispatchRequests.PreArrangeDraft("draft-multi", List.of(
|
||||
new DispatchRequests.DraftGroup(21L,List.of(10L,11L),List.of()),
|
||||
new DispatchRequests.DraftGroup(22L,List.of(10L,13L),List.of())));
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrangeDraft("7", duplicated));
|
||||
fixture.containers().get(1).setCapacity(1);
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrangeDraft("7", fixture.request()));
|
||||
fixture.containers().get(1).setCapacity(30);
|
||||
fixture.draft().setExpiresAt(LocalDateTime.now().minusSeconds(1));
|
||||
assertThrows(ResponseStatusException.class, () -> service.preArrangeDraft("7", fixture.request()));
|
||||
verify(orders, never()).insert(any(DispatchOrder.class)); verify(items, never()).insert(any(DispatchItem.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondGroupFailureRollsBackFirstGroupInsertInTheSameTransaction() {
|
||||
var fixture = twoGroupFixture();
|
||||
when(candidates.lockSelected("100",List.of(12L,13L))).thenReturn(List.of(
|
||||
candidate(12L,912L,null,1),candidate(13L,913L,"ABE8",1)));
|
||||
var ds = new org.h2.jdbcx.JdbcDataSource();
|
||||
ds.setURL("jdbc:h2:mem:dispatch-group-"+java.util.UUID.randomUUID()+";DB_CLOSE_DELAY=-1");
|
||||
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));
|
||||
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())));
|
||||
verify(orders).insert(any(DispatchOrder.class));
|
||||
assertEquals(0,jdbc.queryForObject("SELECT COUNT(*) FROM dispatch_probe",Integer.class));
|
||||
}
|
||||
|
||||
private GroupFixture twoGroupFixture() {
|
||||
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);
|
||||
var containerList = new java.util.ArrayList<DispatchDraftContainer>();
|
||||
var allCandidates = new java.util.ArrayList<DispatchPalletCandidate>();
|
||||
var savedOrders = new java.util.HashMap<Long,DispatchOrder>();
|
||||
var savedItems = new java.util.HashMap<Long,java.util.ArrayList<DispatchItem>>();
|
||||
lenient().doAnswer(call -> {var row=(DispatchOrder)call.getArgument(0);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;})
|
||||
.when(items).insert(any(DispatchItem.class));
|
||||
lenient().when(items.findByDispatchOrder(eq("100"),any())).thenAnswer(call -> savedItems.getOrDefault(call.getArgument(1),new java.util.ArrayList<>()));
|
||||
for(int i=0;i<2;i++) {
|
||||
long containerId=21L+i, first=10L+2*i;
|
||||
var c=new DispatchDraftContainer();c.setId(containerId);c.setDraftId(1L);c.setDraftToken("draft-multi");c.setCapacity(30);c.setSequenceNo(i+1);containerList.add(c);
|
||||
var rows=List.of(candidate(first,900L+first,"ABE4",1),candidate(first+1,901L+first,"ABE8",1));allCandidates.addAll(rows);
|
||||
var bound=new java.util.ArrayList<DispatchPalletLock>();
|
||||
for(var row:rows) {var lock=new DispatchPalletLock();lock.setId(row.planPalletId());lock.setCustomerId("100");
|
||||
lock.setDraftToken("draft-multi");lock.setDraftContainerId(containerId);lock.setOperatorId("7");
|
||||
lock.setStatus("ACTIVE");lock.setPlanPalletId(row.planPalletId());lock.setSourcePalletKey(row.sourcePalletKey());bound.add(lock);
|
||||
lenient().when(locks.lockActive("100",row.sourcePalletKey())).thenReturn(lock);}
|
||||
lenient().when(locks.lockActiveByDraftContainer("draft-multi",containerId)).thenReturn(bound);
|
||||
lenient().when(locks.findDispatchOrderIdByDraftContainer("draft-multi",containerId)).thenAnswer(call -> bound.get(0).getDispatchOrderId());
|
||||
lenient().when(candidates.findCustomerScopesByPlanPalletIds(List.of(first,first+1))).thenReturn(List.of("100"));
|
||||
lenient().when(candidates.lockSelected("100",List.of(first,first+1))).thenReturn(rows);
|
||||
}
|
||||
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()))));
|
||||
}
|
||||
|
||||
private record GroupFixture(DispatchDraft draft,List<DispatchDraftContainer> containers,
|
||||
List<DispatchPalletCandidate> candidates,DispatchRequests.PreArrangeDraft request) { }
|
||||
|
||||
private DispatchPalletCandidate candidate(long planPalletId, Long warehousePalletId, String destination) {
|
||||
return candidate(planPalletId, warehousePalletId, destination, 12);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user