优化
This commit is contained in:
@@ -18,6 +18,8 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -32,6 +34,7 @@ import java.util.stream.Collectors;
|
||||
/** PDA 任务结算:按柜次生成实装/余量履约分片,并结算测量与库位资源。 */
|
||||
@Service
|
||||
public class PdaPickSettlementService {
|
||||
private static final Logger log = LoggerFactory.getLogger(PdaPickSettlementService.class);
|
||||
private static final long PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID = 0L;
|
||||
private static final String SOURCE_SERVICE = "firstleg-service";
|
||||
|
||||
@@ -379,7 +382,7 @@ public class PdaPickSettlementService {
|
||||
|
||||
private static List<Long> releasedReservationIds(List<InboundPalletAllocationEntity> allocations) {
|
||||
return allocations.stream().filter(item -> remaining(item) == 0).map(PdaPickSettlementService::reservationId)
|
||||
.sorted().toList();
|
||||
.distinct().sorted().toList();
|
||||
}
|
||||
|
||||
private static List<WarehousePalletLocationClient.SettlementTransferItem> remainingTransferItems(
|
||||
@@ -422,18 +425,35 @@ public class PdaPickSettlementService {
|
||||
operatorId, operatorName, "PDA-SETTLE-" + taskId + '-' + parentInboundId,
|
||||
releasedReservationIds, remainingItems));
|
||||
} catch (FeignException exception) {
|
||||
log.warn("PDA warehouse settlement transport failure taskId={} sourceInboundId={} operationId={} "
|
||||
+ "httpStatus={} releasedReservationIds={} remainingReservations={}",
|
||||
taskId, parentInboundId, "PDA-SETTLE-" + taskId + '-' + parentInboundId,
|
||||
exception.status(), releasedReservationIds, settlementTransferFacts(remainingItems));
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "仓储服务不可用,无法结算PDA库位资源");
|
||||
}
|
||||
if (response == null || !response.successful() || response.data() == null
|
||||
|| response.data().released() == null || response.data().transferred() == null
|
||||
|| !sameReleasedReservations(releasedReservationIds, response.data().released())
|
||||
|| !sameTransferredResources(remainingItems, response.data().transferred())) {
|
||||
log.warn("PDA warehouse settlement rejected taskId={} sourceInboundId={} operationId={} "
|
||||
+ "responseCode={} requestId={} traceId={} expectedAction=RELEASE_OR_TRANSFER "
|
||||
+ "releasedReservationIds={} remainingReservations={}",
|
||||
taskId, parentInboundId, "PDA-SETTLE-" + taskId + '-' + parentInboundId,
|
||||
response == null ? null : response.code(), response == null ? null : response.requestId(),
|
||||
response == null ? null : response.traceId(), releasedReservationIds,
|
||||
settlementTransferFacts(remainingItems));
|
||||
ResultCode code = response == null ? ResultCode.SERVICE_UNAVAILABLE : ResultCode.of(response.code());
|
||||
throw new BizException(code, response == null || response.message() == null
|
||||
? "仓储服务原子结算PDA库位资源失败" : response.message());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> settlementTransferFacts(
|
||||
List<WarehousePalletLocationClient.SettlementTransferItem> remainingItems) {
|
||||
return remainingItems.stream().map(item -> item.reservationId() + ":" + item.remainingPackageCount())
|
||||
.sorted().toList();
|
||||
}
|
||||
|
||||
private long existingSettlementTarget(long sourceInboundId, String idempotencyKey) {
|
||||
try {
|
||||
ApiResponse<Long> response = warehouse.settlementTarget(internalToken, sourceInboundId, idempotencyKey);
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.BatchRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchItemRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.CompleteLoadingRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
||||
import com.baiyue.firstleg.client.WarehousePalletLocationClient;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
@@ -613,6 +614,62 @@ class LoadingPickServiceTest {
|
||||
verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void warehouseSettlementConflictLeavesCompletionClaimAndDownstreamFinalizationUnwritten() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
task.setVersion(1);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
var owner = new LoadingPickItemMapper.InboundOwner(101L, 50L);
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(owner));
|
||||
when(repository.inboundForUpdate(101L, 50L)).thenReturn(
|
||||
new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7));
|
||||
doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT,
|
||||
"释放库位资源时发生并发变更"))
|
||||
.when(settlementService).settle(20L, 9L, "operator");
|
||||
|
||||
assertThatThrownBy(() -> service.complete(20L,
|
||||
new CompleteLoadingRequest(1, "complete-real-a0409", "PDA确认装柜完成"), 9L, "operator"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("释放库位资源时发生并发变更");
|
||||
|
||||
verify(repository, never()).approve(anyLong(), anyLong(), anyString(), anyString());
|
||||
verify(repository, never()).insert(any(LoadingPickOperationEntity.class));
|
||||
verify(repository, never()).syncInboundProjections(anyLong(), anyLong(), anyString());
|
||||
verify(loadingListService, never()).finishFromPda(anyLong(), anyLong(), anyLong(), anyString());
|
||||
verify(loadingListService, never()).publishCompletedEvent(anyLong());
|
||||
verify(loadingDeliveryService, never()).completeAfterPdaSettlement(anyLong(), anyLong(), anyLong(),
|
||||
anyLong(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentCompleteRequestFinishesAfterUniqueResourceSettlementSucceeds() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
task.setVersion(1);
|
||||
LoadingPickItemEntity item = item();
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.items(20L)).thenReturn(List.of(item));
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.approve(20L, 9L, "operator", "PDA确认装柜完成")).thenReturn(1);
|
||||
var owner = new LoadingPickItemMapper.InboundOwner(101L, 50L);
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(owner));
|
||||
when(repository.inboundForUpdate(101L, 50L)).thenReturn(
|
||||
new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7));
|
||||
|
||||
Task result = service.complete(20L, new CompleteLoadingRequest(1,
|
||||
"complete-1000001-2092817774453780481-2092787765815078913--45b8eb0887ff317c",
|
||||
"PDA确认装柜完成"), 9L, "operator");
|
||||
|
||||
assertThat(result.id()).isEqualTo(20L);
|
||||
verify(settlementService).settle(20L, 9L, "operator");
|
||||
verify(repository).insert(org.mockito.ArgumentMatchers.argThat(
|
||||
(LoadingPickOperationEntity operation) -> "COMPLETE_LOADING".equals(operation.getActionType())));
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(loadingListService).publishCompletedEvent(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicallySubmitsCompleteBatchAndReplaysSameResult() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
|
||||
+6
-3
@@ -179,10 +179,13 @@ class PdaPickSettlementServiceTest {
|
||||
when(splits.lockParent(31L)).thenReturn(parent(20));
|
||||
when(splits.lockLatestApprovedItemsForInbound(10001L, 31L))
|
||||
.thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 20)));
|
||||
when(allocations.lockConfirmed(10001L, 31L))
|
||||
.thenReturn(List.of(allocation(1L, 1, 501L, 10), allocation(2L, 2, 502L, 10)));
|
||||
InboundPalletAllocationEntity first = allocation(1L, 1, 501L, 10);
|
||||
InboundPalletAllocationEntity second = allocation(2L, 2, 501L, 10);
|
||||
first.setPalletId(801L);
|
||||
second.setPalletId(801L);
|
||||
when(allocations.lockConfirmed(10001L, 31L)).thenReturn(List.of(first, second));
|
||||
when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success(
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(released(91L, 501L), released(92L, 502L)),
|
||||
new WarehousePalletLocationClient.SettlementResult(List.of(released(91L, 501L), released(92L, 501L)),
|
||||
List.of())));
|
||||
writes(0, 20);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user