fix: 支持多SKU共箱整托装柜
This commit is contained in:
@@ -1068,19 +1068,48 @@ public class LoadingPickService {
|
||||
}
|
||||
Map<Long, LoadingPickItemEntity> itemsById = taskItems.stream().collect(Collectors.toMap(
|
||||
LoadingPickItemEntity::getId, item -> item));
|
||||
Set<Long> unresolvedIds = allItems.stream().filter(item -> !isResolved(item))
|
||||
.map(LoadingPickItemEntity::getId).collect(Collectors.toSet());
|
||||
Set<Long> requestIds = request.items().stream().map(BatchItemRequest::itemId)
|
||||
.collect(Collectors.toSet());
|
||||
if (requestIds.size() != request.items().size()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "整批明细不能包含重复itemId");
|
||||
}
|
||||
if (!requestIds.equals(unresolvedIds)) {
|
||||
Set<String> unresolvedUnits = allItems.stream().filter(item -> !isResolved(item))
|
||||
.map(LoadingPickService::batchUnitKey).collect(Collectors.toSet());
|
||||
Set<String> requestUnits = request.items().stream().map(requestItem -> {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (item == null || isResolved(item)) throw conflict("PDA明细不属于当前任务或已完成");
|
||||
return batchUnitKey(item);
|
||||
}).collect(Collectors.toSet());
|
||||
if (requestUnits.size() != request.items().size()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "同一整托只能提交一次");
|
||||
}
|
||||
if (!requestUnits.equals(unresolvedUnits)) {
|
||||
throw conflict("整批提交必须且只能包含当前任务全部待拣明细");
|
||||
}
|
||||
|
||||
Map<Long, Boolean> inboundHasWork = new HashMap<>();
|
||||
List<BatchItemRequest> expandedRequests = new ArrayList<>();
|
||||
for (BatchItemRequest requestItem : request.items()) {
|
||||
LoadingPickItemEntity representative = itemsById.get(requestItem.itemId());
|
||||
if (representative.getPalletId() == null) {
|
||||
expandedRequests.add(requestItem);
|
||||
continue;
|
||||
}
|
||||
List<LoadingPickItemEntity> members = allItems.stream().filter(item -> !isResolved(item)
|
||||
&& representative.getPalletId().equals(item.getPalletId())).toList();
|
||||
int aggregateVersion = members.stream().map(LoadingPickItemEntity::getVersion)
|
||||
.filter(Objects::nonNull).max(Integer::compareTo).orElse(0);
|
||||
if (!Objects.equals(requestItem.version(), aggregateVersion)) {
|
||||
throw conflict("PDA明细版本已变化,请刷新后重试");
|
||||
}
|
||||
for (LoadingPickItemEntity member : members) {
|
||||
expandedRequests.add(new BatchItemRequest(member.getId(), requestItem.action(),
|
||||
requestItem.pickedPackageCount(), requestItem.finalPickedPackageCount(),
|
||||
member.getVersion(), requestItem.reason(), requestItem.palletNo()));
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, Boolean> inboundHasWork = new HashMap<>();
|
||||
for (BatchItemRequest requestItem : expandedRequests) {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (item == null || item.getInboundId() == null || item.getLoadingPlanItemId() == null
|
||||
|| item.getInboundDetailId() == null) {
|
||||
@@ -1092,7 +1121,7 @@ public class LoadingPickService {
|
||||
}
|
||||
|
||||
List<BatchLine> result = new ArrayList<>();
|
||||
for (BatchItemRequest requestItem : request.items()) {
|
||||
for (BatchItemRequest requestItem : expandedRequests) {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (!Objects.equals(requestItem.version(), item.getVersion())) {
|
||||
throw conflict("PDA明细版本已变化,请刷新后重试");
|
||||
@@ -1152,6 +1181,10 @@ public class LoadingPickService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String batchUnitKey(LoadingPickItemEntity item) {
|
||||
return item.getPalletId() == null ? "ITEM:" + item.getId() : "PALLET:" + item.getPalletId();
|
||||
}
|
||||
|
||||
private void validateWholePallets(List<BatchLine> lines, List<LoadingPickItemEntity> allItems, long warehouseId) {
|
||||
Map<Long, List<BatchLine>> groups = lines.stream().filter(line -> "PICK_PALLET".equals(line.action()))
|
||||
.collect(Collectors.groupingBy(line -> line.item().getPalletId()));
|
||||
@@ -1281,6 +1314,7 @@ public class LoadingPickService {
|
||||
Map<Long, Integer> requestedByInbound = new HashMap<>();
|
||||
Map<Long, BatchLine> lineByItem = lines.stream().collect(Collectors.toMap(
|
||||
line -> line.item().getId(), line -> line));
|
||||
Set<String> countedPalletInbounds = new HashSet<>();
|
||||
for (LoadingPickItemEntity item : allItems) {
|
||||
BatchLine line = lineByItem.get(item.getId());
|
||||
int picked = line == null ? count(item.getPickedPackageCount())
|
||||
@@ -1288,6 +1322,10 @@ public class LoadingPickService {
|
||||
? count(item.getPickedPackageCount()) + line.request().pickedPackageCount()
|
||||
: "PICK_PALLET".equals(line.action()) ? count(item.getPlannedPackageCount())
|
||||
: "PARTIAL".equals(line.action()) ? line.request().finalPickedPackageCount() : 0;
|
||||
if (line != null && "PICK_PALLET".equals(line.action())) {
|
||||
String physicalUnit = item.getPalletId() + ":" + item.getInboundId();
|
||||
if (!countedPalletInbounds.add(physicalUnit)) continue;
|
||||
}
|
||||
requestedByInbound.merge(item.getInboundId(), picked, Integer::sum);
|
||||
}
|
||||
requestedByInbound.forEach((inboundId, requested) -> {
|
||||
|
||||
@@ -296,7 +296,7 @@ components:
|
||||
items: {$ref: '#/components/schemas/BatchItemRequest'}
|
||||
BatchItemRequest:
|
||||
type: object
|
||||
description: PICK_PALLET submits every pickItem of the scanned DP pallet with its own version in the same atomic batch. No carton quantities or skip reason are accepted; the server verifies the complete SEALED pallet. Scanning only changes the device draft.
|
||||
description: PICK_PALLET submits one representative pickItem for each scanned DP pallet. The server expands that physical pallet to every SKU line and verifies them atomically. No carton quantities or skip reason are accepted. Scanning only changes the device draft.
|
||||
required: [itemId, action, version]
|
||||
properties:
|
||||
itemId: {type: integer, format: int64, minimum: 1}
|
||||
|
||||
@@ -1368,8 +1368,7 @@ class LoadingPickServiceTest {
|
||||
var snapshot = service.inventorySnapshot(20L, 101L);
|
||||
assertThat(snapshot.units()).hasSize(2).allSatisfy(unit -> assertThat(unit.palletNo()).isEqualTo("DP-260909-0001"));
|
||||
var request = new BatchRequest(101L, 10L, 99L, 0, snapshot.snapshotToken(), List.of(
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"),
|
||||
new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
service.batch(20L, "batch-pallet", request, 9L, "operator");
|
||||
service.batch(20L, "batch-pallet", request, 9L, "operator");
|
||||
verify(repository, times(1)).pick(30L, 2, 0, 101L);
|
||||
@@ -1382,20 +1381,20 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"missing-row", "ordinary", "partial", "skip", "quantity", "wrong-code", "unsealed", "wrong-warehouse", "physical-short", "stale-item", "stale-task", "stale-snapshot"})
|
||||
@ValueSource(strings = {"missing-unit", "duplicate-pallet", "ordinary", "partial", "skip", "quantity", "wrong-code", "unsealed", "wrong-warehouse", "physical-short", "stale-item", "stale-task", "stale-snapshot"})
|
||||
void rejectsInvalidWholePalletBatchBeforeAnyWrite(String scenario) {
|
||||
var items = prepareWholePalletBatch();
|
||||
String snapshot = service.inventorySnapshot(20L, 101L).snapshotToken();
|
||||
var lines = new java.util.ArrayList<>(List.of(
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"),
|
||||
new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
switch (scenario) {
|
||||
case "missing-row" -> lines.remove(1);
|
||||
case "ordinary" -> lines.set(1, new BatchItemRequest(31L, "PICK", 2, null, 0, null, null));
|
||||
case "partial" -> lines.set(1, new BatchItemRequest(31L, "PARTIAL", null, 1, 0, null, null));
|
||||
case "skip" -> lines.set(1, new BatchItemRequest(31L, "SKIP", null, null, 0, "skip", null));
|
||||
case "quantity" -> lines.set(1, new BatchItemRequest(31L, "PICK_PALLET", 2, null, 0, null, "DP-260909-0001"));
|
||||
case "wrong-code" -> lines.set(1, new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0999"));
|
||||
case "missing-unit" -> lines.clear();
|
||||
case "duplicate-pallet" -> lines.add(new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"));
|
||||
case "ordinary" -> lines.set(0, new BatchItemRequest(30L, "PICK", 2, null, 0, null, null));
|
||||
case "partial" -> lines.set(0, new BatchItemRequest(30L, "PARTIAL", null, 1, 0, null, null));
|
||||
case "skip" -> lines.set(0, new BatchItemRequest(30L, "SKIP", null, null, 0, "skip", null));
|
||||
case "quantity" -> lines.set(0, new BatchItemRequest(30L, "PICK_PALLET", 2, null, 0, null, "DP-260909-0001"));
|
||||
case "wrong-code" -> lines.set(0, new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0999"));
|
||||
case "unsealed" -> dropshipPallets.sealedPalletLines(801L).get(0).setPalletStatus("OPEN");
|
||||
case "wrong-warehouse" -> dropshipPallets.sealedPalletLines(801L).get(0).setWarehouseId(999L);
|
||||
case "physical-short" -> dropshipPallets.sealedPalletLines(801L).get(0).setCartonCount(1);
|
||||
|
||||
Reference in New Issue
Block a user