优化
This commit is contained in:
@@ -3,6 +3,8 @@ package com.baiyue.firstleg.client;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
@@ -38,6 +40,13 @@ public interface WarehousePalletLocationClient {
|
||||
ApiResponse<SettlementResult> settle(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody SettlementRequest request);
|
||||
|
||||
@GetMapping("/occupied-resources")
|
||||
ApiResponse<List<OccupiedResource>> occupiedResources(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestParam("tenantId") long tenantId,
|
||||
@RequestParam("sourceService") String sourceService,
|
||||
@RequestParam("businessId") long businessId);
|
||||
|
||||
record ReserveRequest(long tenantId, long warehouseId, String sourceService, long businessId,
|
||||
String inboundCode, String idempotencyKey, long operatorId,
|
||||
BigDecimal totalVolumeCbm, BigDecimal palletCapacityCbm, String palletType,
|
||||
@@ -75,4 +84,7 @@ public interface WarehousePalletLocationClient {
|
||||
String operatorName, String requestId, List<Long> releasedReservationIds,
|
||||
List<SettlementTransferItem> remainingItems) {}
|
||||
record SettlementResult(List<SelectiveReleaseResult> released, List<TransferResult> transferred) {}
|
||||
record OccupiedResource(long reservationId, long occupancyId, int allocationNo, long warehouseId,
|
||||
long palletId, long locationId, String locationCode, boolean sharedOverflow,
|
||||
int allocatedPackageCount) {}
|
||||
}
|
||||
|
||||
+10
@@ -10,6 +10,7 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.AssignRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Operation;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventorySnapshot;
|
||||
import java.util.List;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
@@ -48,6 +49,15 @@ public class PlatformLoadingPdaController {
|
||||
return ApiResponse.success(service.task(containerNo, warehouseId));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}/inventory-snapshot")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<InventorySnapshot> inventorySnapshot(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.inventorySnapshot(taskId, warehouseId));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/claim")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> claim(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
@@ -60,6 +60,7 @@ public final class LoadingPickDtos {
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@NotBlank @Size(max = 64) String containerNo,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 64) String inventorySnapshotToken,
|
||||
@NotEmpty @Size(max = 500) List<@Valid BatchItemRequest> items
|
||||
) {
|
||||
}
|
||||
@@ -118,4 +119,18 @@ public final class LoadingPickDtos {
|
||||
String workflowStatus, Integer version, Assignment assignment,
|
||||
Review review, List<PickItem> items) {
|
||||
}
|
||||
|
||||
public record InventoryResource(Long reservationId, Long occupancyId, Long palletId,
|
||||
Long locationId, String locationCode, Boolean sharedOverflow,
|
||||
Integer availablePackageCount) {
|
||||
}
|
||||
|
||||
public record InboundInventory(Long inboundId, Integer availablePackageCount,
|
||||
List<InventoryResource> resources) {
|
||||
}
|
||||
|
||||
public record InventorySnapshot(Long taskId, Integer taskVersion, String containerNo,
|
||||
Long warehouseId, LocalDateTime capturedAt, String snapshotToken,
|
||||
List<InboundInventory> inbounds) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.client.WarehousePalletLocationClient;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickItem;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
@@ -15,6 +17,9 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Operation;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.BatchItemRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventoryResource;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InboundInventory;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventorySnapshot;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
@@ -26,6 +31,8 @@ import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.baiyue.firstleg.repository.SmartLoadingRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import feign.FeignException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -43,10 +50,13 @@ import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 装柜中 PDA 拣货任务及明细同步。 */
|
||||
@Service
|
||||
public class LoadingPickService {
|
||||
private static final long PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID = 0L;
|
||||
private static final String SOURCE_SERVICE = "firstleg-service";
|
||||
@Resource
|
||||
private LoadingPickRepository repository;
|
||||
@Resource
|
||||
@@ -55,6 +65,10 @@ public class LoadingPickService {
|
||||
private LoadingListService loadingListService;
|
||||
@Resource
|
||||
private PdaPickSettlementService settlementService;
|
||||
@Resource
|
||||
private WarehousePalletLocationClient warehouse;
|
||||
@Value("${baiyue.security.internal-service-token:test-internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
@Transactional
|
||||
public void begin(long planId, String containerNo, long operatorId, String operatorName,
|
||||
@@ -105,6 +119,12 @@ public class LoadingPickService {
|
||||
return view(task, warehouseId);
|
||||
}
|
||||
|
||||
public InventorySnapshot inventorySnapshot(long taskId, long warehouseId) {
|
||||
LoadingPickTaskEntity task = requiredTask(taskId);
|
||||
List<LoadingPickItemEntity> items = warehouseScopedItems(task, warehouseId);
|
||||
return buildInventorySnapshot(task, warehouseId, items);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task claim(long taskId, ClaimRequest request, long operatorId, String operatorName) {
|
||||
String operationId = operation(request.operationId());
|
||||
@@ -275,6 +295,7 @@ public class LoadingPickService {
|
||||
}
|
||||
|
||||
List<BatchLine> lines = validateBatchLines(task, request);
|
||||
validateInventorySnapshot(task, request, lines);
|
||||
for (BatchLine line : lines) {
|
||||
applyBatchLine(taskId, line, operationId, operatorId, operatorName);
|
||||
}
|
||||
@@ -554,6 +575,93 @@ public class LoadingPickService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<LoadingPickItemEntity> warehouseScopedItems(LoadingPickTaskEntity task, long warehouseId) {
|
||||
List<LoadingPickItemEntity> allItems = repository.items(task.getId());
|
||||
List<LoadingPickItemEntity> scopedItems = repository.items(task.getId(), warehouseId);
|
||||
if (allItems.isEmpty() || scopedItems.size() != allItems.size()) {
|
||||
throw conflict("PDA任务柜次、仓库或客户归属不一致");
|
||||
}
|
||||
return scopedItems;
|
||||
}
|
||||
|
||||
private InventorySnapshot buildInventorySnapshot(LoadingPickTaskEntity task, long warehouseId,
|
||||
List<LoadingPickItemEntity> items) {
|
||||
List<InboundInventory> inbounds = items.stream().map(LoadingPickItemEntity::getInboundId)
|
||||
.filter(Objects::nonNull).distinct().sorted().map(inboundId -> {
|
||||
List<WarehousePalletLocationClient.OccupiedResource> current = occupiedResources(inboundId);
|
||||
List<InventoryResource> resources = current.stream()
|
||||
.filter(resource -> resource.warehouseId() == warehouseId)
|
||||
.sorted(Comparator.comparingLong(WarehousePalletLocationClient.OccupiedResource::reservationId)
|
||||
.thenComparingLong(WarehousePalletLocationClient.OccupiedResource::occupancyId))
|
||||
.map(resource -> new InventoryResource(resource.reservationId(), resource.occupancyId(),
|
||||
resource.palletId(), resource.locationId(), resource.locationCode(),
|
||||
resource.sharedOverflow(), resource.allocatedPackageCount()))
|
||||
.toList();
|
||||
int available = resources.stream().mapToInt(InventoryResource::availablePackageCount).sum();
|
||||
return new InboundInventory(inboundId, available, resources);
|
||||
}).toList();
|
||||
String token = inventoryToken(task, warehouseId, inbounds);
|
||||
return new InventorySnapshot(task.getId(), task.getVersion(), task.getContainerNo(), warehouseId,
|
||||
LocalDateTime.now(), token, inbounds);
|
||||
}
|
||||
|
||||
private List<WarehousePalletLocationClient.OccupiedResource> occupiedResources(long inboundId) {
|
||||
ApiResponse<List<WarehousePalletLocationClient.OccupiedResource>> response;
|
||||
try {
|
||||
response = warehouse.occupiedResources(internalToken, PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID,
|
||||
SOURCE_SERVICE, inboundId);
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "仓储实时库存查询失败,请刷新后重试");
|
||||
}
|
||||
if (response == null || !response.successful() || response.data() == null) {
|
||||
ResultCode code = response == null ? ResultCode.SERVICE_UNAVAILABLE : ResultCode.of(response.code());
|
||||
throw new BizException(code, response == null || response.message() == null
|
||||
? "仓储实时库存查询失败,请刷新后重试" : response.message());
|
||||
}
|
||||
return response.data();
|
||||
}
|
||||
|
||||
private static String inventoryToken(LoadingPickTaskEntity task, long warehouseId,
|
||||
List<InboundInventory> inbounds) {
|
||||
StringBuilder canonical = new StringBuilder("INVENTORY|").append(task.getId()).append('|')
|
||||
.append(task.getVersion()).append('|').append(warehouseId);
|
||||
for (InboundInventory inbound : inbounds) {
|
||||
canonical.append('|').append(inbound.inboundId()).append('|').append(inbound.availablePackageCount());
|
||||
for (InventoryResource resource : inbound.resources()) {
|
||||
canonical.append('|').append(resource.reservationId()).append('|').append(resource.occupancyId())
|
||||
.append('|').append(resource.locationId()).append('|').append(resource.availablePackageCount());
|
||||
}
|
||||
}
|
||||
return hash(canonical.toString());
|
||||
}
|
||||
|
||||
private void validateInventorySnapshot(LoadingPickTaskEntity task, BatchRequest request,
|
||||
List<BatchLine> lines) {
|
||||
List<LoadingPickItemEntity> allItems = warehouseScopedItems(task, request.warehouseId());
|
||||
InventorySnapshot current = buildInventorySnapshot(task, request.warehouseId(), allItems);
|
||||
if (!current.snapshotToken().equals(request.inventorySnapshotToken().trim())) {
|
||||
throw conflict("库存已变化,请刷新后重试");
|
||||
}
|
||||
Map<Long, Integer> availableByInbound = current.inbounds().stream().collect(Collectors.toMap(
|
||||
InboundInventory::inboundId, InboundInventory::availablePackageCount));
|
||||
Map<Long, Integer> requestedByInbound = new HashMap<>();
|
||||
Map<Long, BatchLine> lineByItem = lines.stream().collect(Collectors.toMap(
|
||||
line -> line.item().getId(), line -> line));
|
||||
for (LoadingPickItemEntity item : allItems) {
|
||||
BatchLine line = lineByItem.get(item.getId());
|
||||
int picked = line == null ? count(item.getPickedPackageCount())
|
||||
: "PICK".equals(line.action())
|
||||
? count(item.getPickedPackageCount()) + line.request().pickedPackageCount()
|
||||
: "PARTIAL".equals(line.action()) ? line.request().finalPickedPackageCount() : 0;
|
||||
requestedByInbound.merge(item.getInboundId(), picked, Integer::sum);
|
||||
}
|
||||
requestedByInbound.forEach((inboundId, requested) -> {
|
||||
if (requested > availableByInbound.getOrDefault(inboundId, 0)) {
|
||||
throw conflict("库存已变化,请刷新后重试");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void applyBatchLine(long taskId, BatchLine line, String batchOperationId,
|
||||
long operatorId, String operatorName) {
|
||||
LoadingPickItemEntity item = line.item();
|
||||
@@ -690,12 +798,13 @@ public class LoadingPickService {
|
||||
|
||||
private static String batchRequestHash(long taskId, BatchRequest request) {
|
||||
if (request == null || request.warehouseId() == null || !StringUtils.hasText(request.containerNo())
|
||||
|| request.expectedVersion() == null || request.items() == null || request.items().isEmpty()) {
|
||||
|| request.expectedVersion() == null || !StringUtils.hasText(request.inventorySnapshotToken())
|
||||
|| request.items() == null || request.items().isEmpty()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "整批提交参数不完整");
|
||||
}
|
||||
StringBuilder canonical = new StringBuilder("BATCH|").append(taskId).append('|')
|
||||
.append(request.warehouseId()).append('|').append(request.containerNo().trim()).append('|')
|
||||
.append(request.expectedVersion());
|
||||
.append(request.expectedVersion()).append('|').append(request.inventorySnapshotToken().trim());
|
||||
request.items().stream().sorted(Comparator.comparing(BatchItemRequest::itemId,
|
||||
Comparator.nullsFirst(Long::compareTo)))
|
||||
.forEach(item -> {
|
||||
|
||||
@@ -20,6 +20,16 @@ paths:
|
||||
content: {application/json: {schema: {$ref: '#/components/schemas/ClaimRequest'}}}
|
||||
responses:
|
||||
'200': {description: Claimed task or A0409 when another operator owns it}
|
||||
/api/platform/v1/first-leg/pda-loading/tasks/{taskId}/inventory-snapshot:
|
||||
get:
|
||||
summary: Read the current Warehouse inventory snapshot before atomic submission
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/TaskId'}
|
||||
- {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
responses:
|
||||
'200':
|
||||
description: Current occupied resources grouped by inbound order with a stable snapshot token
|
||||
content: {application/json: {schema: {$ref: '#/components/schemas/InventorySnapshot'}}}
|
||||
/api/platform/v1/first-leg/pda-loading/tasks/{taskId}/assign:
|
||||
post:
|
||||
summary: Assign or reassign a task with optimistic locking
|
||||
@@ -91,11 +101,12 @@ components:
|
||||
operationId: {type: string, maxLength: 128}
|
||||
BatchRequest:
|
||||
type: object
|
||||
required: [warehouseId, containerNo, expectedVersion, items]
|
||||
required: [warehouseId, containerNo, expectedVersion, inventorySnapshotToken, items]
|
||||
properties:
|
||||
warehouseId: {type: integer, format: int64, minimum: 1}
|
||||
containerNo: {type: string, maxLength: 64}
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
inventorySnapshotToken: {type: string, minLength: 64, maxLength: 64}
|
||||
items:
|
||||
type: array
|
||||
minItems: 1
|
||||
@@ -122,3 +133,36 @@ components:
|
||||
expectedTaskVersion: {type: integer, minimum: 0}
|
||||
operationId: {type: string, maxLength: 128}
|
||||
remark: {type: string, maxLength: 500}
|
||||
InventorySnapshot:
|
||||
type: object
|
||||
required: [taskId, taskVersion, containerNo, warehouseId, capturedAt, snapshotToken, inbounds]
|
||||
properties:
|
||||
taskId: {type: integer, format: int64}
|
||||
taskVersion: {type: integer, minimum: 0}
|
||||
containerNo: {type: string}
|
||||
warehouseId: {type: integer, format: int64}
|
||||
capturedAt: {type: string, format: date-time}
|
||||
snapshotToken: {type: string, minLength: 64, maxLength: 64}
|
||||
inbounds:
|
||||
type: array
|
||||
items: {$ref: '#/components/schemas/InboundInventory'}
|
||||
InboundInventory:
|
||||
type: object
|
||||
required: [inboundId, availablePackageCount, resources]
|
||||
properties:
|
||||
inboundId: {type: integer, format: int64}
|
||||
availablePackageCount: {type: integer, minimum: 0}
|
||||
resources:
|
||||
type: array
|
||||
items: {$ref: '#/components/schemas/InventoryResource'}
|
||||
InventoryResource:
|
||||
type: object
|
||||
required: [reservationId, occupancyId, palletId, locationId, sharedOverflow, availablePackageCount]
|
||||
properties:
|
||||
reservationId: {type: integer, format: int64}
|
||||
occupancyId: {type: integer, format: int64}
|
||||
palletId: {type: integer, format: int64}
|
||||
locationId: {type: integer, format: int64}
|
||||
locationCode: {type: string, nullable: true}
|
||||
sharedOverflow: {type: boolean}
|
||||
availablePackageCount: {type: integer, minimum: 1}
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ class PdaConvergenceOpenApiContractTest {
|
||||
assertThat(yaml)
|
||||
.contains("/api/platform/v1/first-leg/pda-loading/tasks:")
|
||||
.contains("/tasks/{taskId}/claim:")
|
||||
.contains("/tasks/{taskId}/inventory-snapshot:")
|
||||
.contains("/tasks/{taskId}/assign:")
|
||||
.contains("/tasks/{taskId}/items/{itemId}/pick:")
|
||||
.contains("/tasks/{taskId}/items/{itemId}/partial:")
|
||||
@@ -24,6 +25,8 @@ class PdaConvergenceOpenApiContractTest {
|
||||
.contains("/tasks/{taskId}/operations:")
|
||||
.contains("expectedAssignmentVersion")
|
||||
.contains("BatchRequest")
|
||||
.contains("inventorySnapshotToken")
|
||||
.contains("InventorySnapshot")
|
||||
.contains("expectedTaskVersion");
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ class PlatformLoadingPdaControllerContractTest {
|
||||
@Test
|
||||
void exposesAssignmentWorkAndReviewWithDedicatedAuthorities() throws Exception {
|
||||
authority("task", "platform:firstleg:pda:view", AuthenticatedUser.class, String.class, long.class);
|
||||
authority("inventorySnapshot", "platform:firstleg:pda:view", AuthenticatedUser.class, long.class, long.class);
|
||||
authority("claim", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, ClaimRequest.class);
|
||||
authority("assign", "platform:firstleg:pda:assign", AuthenticatedUser.class, long.class, AssignRequest.class);
|
||||
authority("pick", "platform:firstleg:pda:work", AuthenticatedUser.class, long.class, long.class, PickRequest.class);
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
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.client.WarehousePalletLocationClient;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
||||
@@ -44,6 +46,8 @@ class LoadingPickServiceTest {
|
||||
private LoadingListService loadingListService;
|
||||
@MockBean
|
||||
private PdaPickSettlementService settlementService;
|
||||
@MockBean
|
||||
private WarehousePalletLocationClient warehouse;
|
||||
@Resource
|
||||
private LoadingPickService service;
|
||||
|
||||
@@ -219,7 +223,11 @@ class LoadingPickServiceTest {
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 0, List.of(
|
||||
when(warehouse.occupiedResources(any(), any(Long.class), any(), any(Long.class))).thenReturn(
|
||||
ApiResponse.success(List.of(new WarehousePalletLocationClient.OccupiedResource(
|
||||
71L, 72L, 0, 101L, 73L, 74L, "A-01", false, 2))));
|
||||
String snapshotToken = service.inventorySnapshot(20L, 101L).snapshotToken();
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 0, snapshotToken, List.of(
|
||||
new BatchItemRequest(30L, "PICK", 2, null, "MANUAL", List.of(), 0, null),
|
||||
new BatchItemRequest(31L, "SKIP", null, null, null, null, 0, "damaged carton")));
|
||||
|
||||
@@ -249,7 +257,7 @@ class LoadingPickServiceTest {
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.items(20L)).thenReturn(items);
|
||||
when(repository.items(20L, 101L)).thenReturn(items);
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 0, List.of(
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 0, "stale-token", List.of(
|
||||
new BatchItemRequest(30L, "PICK", 2, null, "MANUAL", List.of(), 0, null),
|
||||
new BatchItemRequest(31L, "PARTIAL", null, 4, "AUTO", null, 0, null)));
|
||||
|
||||
@@ -262,12 +270,36 @@ class LoadingPickServiceTest {
|
||||
verify(settlementService, never()).settle(20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsChangedInventorySnapshotBeforeWritingAnyBatchLine() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
LoadingPickItemEntity picked = item();
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.items(20L)).thenReturn(List.of(picked));
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(picked));
|
||||
when(warehouse.occupiedResources(any(), any(Long.class), any(), any(Long.class))).thenReturn(
|
||||
ApiResponse.success(List.of(new WarehousePalletLocationClient.OccupiedResource(
|
||||
71L, 72L, 0, 101L, 73L, 74L, "A-01", false, 2))));
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 0, "0".repeat(64), List.of(
|
||||
new BatchItemRequest(30L, "PICK", 2, null, "MANUAL", List.of(), 0, null)));
|
||||
|
||||
assertThatThrownBy(() -> service.batch(20L, "batch-inventory-stale", request, 9L, "operator"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("库存已变化");
|
||||
|
||||
verify(repository, never()).pick(any(Long.class), any(Integer.class), any(), any(),
|
||||
any(Integer.class), any(Long.class));
|
||||
verify(repository, never()).touchTask(any(Long.class), any(Integer.class));
|
||||
verify(settlementService, never()).settle(20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBatchTaskVersionConflictBeforeWriting() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
task.setVersion(3);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 2, List.of(
|
||||
BatchRequest request = new BatchRequest(101L, "CSLU1234567", 2, "stale-token", List.of(
|
||||
new BatchItemRequest(30L, "PICK", 2, null, "AUTO", null, 0, null)));
|
||||
|
||||
assertThatThrownBy(() -> service.batch(20L, "batch-stale", request, 9L, "operator"))
|
||||
|
||||
Reference in New Issue
Block a user