fix(firstleg): make PDA allocation settlement idempotent
This commit is contained in:
+26
-2
@@ -86,7 +86,11 @@ public class InboundPalletAllocationRepository {
|
||||
if (allocated <= picked) {
|
||||
continue;
|
||||
}
|
||||
if (allocation.getId() == null || mapper.markTransferred(allocation.getId(), tenantId, inboundId) != 1) {
|
||||
if (allocation.getId() == null) {
|
||||
throw new IllegalStateException("库位资源转移状态已被并发修改");
|
||||
}
|
||||
int changed = mapper.markTransferred(allocation.getId(), tenantId, inboundId);
|
||||
if (changed != 1 && !isAllocationIdState(allocation.getId(), tenantId, inboundId, "TRANSFERRED")) {
|
||||
throw new IllegalStateException("库位资源转移状态已被并发修改");
|
||||
}
|
||||
}
|
||||
@@ -94,12 +98,32 @@ public class InboundPalletAllocationRepository {
|
||||
|
||||
public void markReleased(long tenantId, long inboundId, List<Long> reservationIds) {
|
||||
for (Long reservationId : reservationIds) {
|
||||
if (reservationId == null || mapper.markReleased(tenantId, inboundId, reservationId) != 1) {
|
||||
if (reservationId == null) {
|
||||
throw new IllegalStateException("库位释放状态已被并发修改");
|
||||
}
|
||||
int changed = mapper.markReleased(tenantId, inboundId, reservationId);
|
||||
if (changed != 1 && !isReservationState(reservationId, tenantId, inboundId, "RELEASED")) {
|
||||
throw new IllegalStateException("库位释放状态已被并发修改");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAllocationIdState(long id, long tenantId, long inboundId, String status) {
|
||||
InboundPalletAllocationEntity current = mapper.selectOne(new LambdaQueryWrapper<InboundPalletAllocationEntity>()
|
||||
.eq(InboundPalletAllocationEntity::getTenantId, tenantId)
|
||||
.eq(InboundPalletAllocationEntity::getInboundId, inboundId)
|
||||
.eq(InboundPalletAllocationEntity::getId, id));
|
||||
return current != null && status.equals(current.getAllocationStatus());
|
||||
}
|
||||
|
||||
private boolean isReservationState(long reservationId, long tenantId, long inboundId, String status) {
|
||||
InboundPalletAllocationEntity current = mapper.selectOne(new LambdaQueryWrapper<InboundPalletAllocationEntity>()
|
||||
.eq(InboundPalletAllocationEntity::getTenantId, tenantId)
|
||||
.eq(InboundPalletAllocationEntity::getInboundId, inboundId)
|
||||
.eq(InboundPalletAllocationEntity::getReservationId, reservationId));
|
||||
return current != null && status.equals(current.getAllocationStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 依据已经锁定且已扣减本次拣货数量的父单分配,预先固化子单快照。
|
||||
* 仓库 transfer 是本地事务最后一个外部动作,成功后不再执行本地写入,避免跨服务重试时目标子单变化。
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baiyue.firstleg.entity.InboundPalletAllocationEntity;
|
||||
import com.baiyue.firstleg.mapper.InboundPalletAllocationMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InboundPalletAllocationRepositoryTest {
|
||||
@Mock
|
||||
private InboundPalletAllocationMapper mapper;
|
||||
@InjectMocks
|
||||
private InboundPalletAllocationRepository repository;
|
||||
|
||||
@Test
|
||||
void acceptsReleasedSnapshotWhenRetryUpdateAffectsNoRows() {
|
||||
InboundPalletAllocationEntity current = allocation(1L, 91L, "RELEASED", 10, 10);
|
||||
when(mapper.markReleased(10001L, 31L, 91L)).thenReturn(0);
|
||||
when(mapper.selectOne(any())).thenReturn(current);
|
||||
|
||||
assertThatCode(() -> repository.markReleased(10001L, 31L, List.of(91L)))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsTransferredSnapshotWhenRetryUpdateAffectsNoRows() {
|
||||
InboundPalletAllocationEntity source = allocation(1L, 91L, "CONFIRMED", 10, 2);
|
||||
InboundPalletAllocationEntity current = allocation(1L, 91L, "TRANSFERRED", 10, 2);
|
||||
when(mapper.markTransferred(1L, 10001L, 31L)).thenReturn(0);
|
||||
when(mapper.selectOne(any())).thenReturn(current);
|
||||
|
||||
assertThatCode(() -> repository.markTransferred(10001L, 31L, List.of(source), List.of()))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
private static InboundPalletAllocationEntity allocation(long id, long reservationId, String status,
|
||||
int allocated, int picked) {
|
||||
InboundPalletAllocationEntity entity = new InboundPalletAllocationEntity();
|
||||
entity.setId(id);
|
||||
entity.setReservationId(reservationId);
|
||||
entity.setAllocationStatus(status);
|
||||
entity.setAllocatedPackageCount(allocated);
|
||||
entity.setPickedPackageCount(picked);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user