贴标申请,仓储转运
This commit is contained in:
@@ -44,7 +44,8 @@ public final class RelabelResponses {
|
|||||||
String sourceType, Long returnCartonId, String cartonMark,
|
String sourceType, Long returnCartonId, String cartonMark,
|
||||||
List<SkuItem> skuItems, long relabelledPackages, String relabelStatus,
|
List<SkuItem> skuItems, long relabelledPackages, String relabelStatus,
|
||||||
long relabelledQuantity, long relabelReservedQuantity, Long stockQuantity, String inventoryStatus, String occupiedRelabelNo, List<String> mergedIntoCartonMarks, boolean hasOutbound,
|
long relabelledQuantity, long relabelReservedQuantity, Long stockQuantity, String inventoryStatus, String occupiedRelabelNo, List<String> mergedIntoCartonMarks, boolean hasOutbound,
|
||||||
List<com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses.SkuConversion> skuConversions) {
|
List<com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses.SkuConversion> skuConversions,
|
||||||
|
List<SkuItem> cartonSkuItems, long cartonAvailablePackages) {
|
||||||
public InventoryCandidate(Long inventoryBalanceId, Long inboundOrderId, String inboundOrderNo, String inboundCode,
|
public InventoryCandidate(Long inventoryBalanceId, Long inboundOrderId, String inboundOrderNo, String inboundCode,
|
||||||
String overseasWarehouseId, String overseasWarehouseCode, String overseasWarehouseName,
|
String overseasWarehouseId, String overseasWarehouseCode, String overseasWarehouseName,
|
||||||
String destinationFbaWarehouseCode, String destinationFbaWarehouseName, String skuCode, String productNameZh,
|
String destinationFbaWarehouseCode, String destinationFbaWarehouseName, String skuCode, String productNameZh,
|
||||||
@@ -53,7 +54,7 @@ public final class RelabelResponses {
|
|||||||
this(inventoryBalanceId, inboundOrderId, inboundOrderNo, inboundCode, overseasWarehouseId,
|
this(inventoryBalanceId, inboundOrderId, inboundOrderNo, inboundCode, overseasWarehouseId,
|
||||||
overseasWarehouseCode, overseasWarehouseName, destinationFbaWarehouseCode, destinationFbaWarehouseName,
|
overseasWarehouseCode, overseasWarehouseName, destinationFbaWarehouseCode, destinationFbaWarehouseName,
|
||||||
skuCode, productNameZh, availablePackages, version, positions, sourceType, returnCartonId, cartonMark,
|
skuCode, productNameZh, availablePackages, version, positions, sourceType, returnCartonId, cartonMark,
|
||||||
skuItems, relabelledPackages, null, relabelledPackages, 0, null, null, null, List.of(), false, List.of());
|
skuItems, relabelledPackages, null, relabelledPackages, 0, null, null, null, List.of(), false, List.of(), List.of(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public record HistoricalOrder(Long id, String orderNo, String sourceOrderNo, String outboundOrderNo,
|
public record HistoricalOrder(Long id, String orderNo, String sourceOrderNo, String outboundOrderNo,
|
||||||
|
|||||||
+10
-3
@@ -284,7 +284,13 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
return new RelabelResponses.SkuItem(item.getSkuCode(), null,
|
return new RelabelResponses.SkuItem(item.getSkuCode(), null,
|
||||||
Math.toIntExact(Math.max(remaining, 0)), null);
|
Math.toIntExact(Math.max(remaining, 0)), null);
|
||||||
}).filter(item -> !"AVAILABLE".equals(carton.getStatus()) || item.quantity() > 0).toList();
|
}).filter(item -> !"AVAILABLE".equals(carton.getStatus()) || item.quantity() > 0).toList();
|
||||||
if (cartonSkus.isEmpty() && !java.util.Set.of("MERGED", "OUTBOUNDED").contains(carton.getStatus())) continue;
|
List<RelabelResponses.SkuItem> physicalSkus = cartonLines.stream()
|
||||||
|
.filter(line -> value(line.getQuantity()) > 0)
|
||||||
|
.map(line -> new RelabelResponses.SkuItem(line.getSkuCode(), null, Math.toIntExact(value(line.getQuantity())), null)).toList();
|
||||||
|
long cartonAvailable = "AVAILABLE".equals(carton.getStatus()) && carton.getRelabelApplicationId() == null
|
||||||
|
&& cartonLines.stream().noneMatch(line -> value(line.getPackingReservedQuantity()) > 0)
|
||||||
|
&& !physicalSkus.isEmpty() ? 1 : 0;
|
||||||
|
if (cartonSkus.isEmpty() && cartonAvailable == 0 && !java.util.Set.of("MERGED", "OUTBOUNDED").contains(carton.getStatus())) continue;
|
||||||
String skuCodes = cartonSkus.stream().map(RelabelResponses.SkuItem::skuCode)
|
String skuCodes = cartonSkus.stream().map(RelabelResponses.SkuItem::skuCode)
|
||||||
.collect(Collectors.joining(","));
|
.collect(Collectors.joining(","));
|
||||||
long availablePackages = cartonSkus.stream().mapToLong(RelabelResponses.SkuItem::quantity).sum();
|
long availablePackages = cartonSkus.stream().mapToLong(RelabelResponses.SkuItem::quantity).sum();
|
||||||
@@ -301,7 +307,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
.filter(history -> carton.getCartonMark().equals(history.sourceCartonMark()))
|
.filter(history -> carton.getCartonMark().equals(history.sourceCartonMark()))
|
||||||
.map(history -> history.targetCartonMark()).distinct().toList(), returnCartons.hasOutbound(carton.getId()),
|
.map(history -> history.targetCartonMark()).distinct().toList(), returnCartons.hasOutbound(carton.getId()),
|
||||||
skuConversionRows.listCarton(customerId, carton.getId()).stream()
|
skuConversionRows.listCarton(customerId, carton.getId()).stream()
|
||||||
.map(com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses.SkuConversion::from).toList()));
|
.map(com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses.SkuConversion::from).toList(), physicalSkus, cartonAvailable));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -414,7 +420,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
final long sourceReturnCartonId = carton.getId();
|
final long sourceReturnCartonId = carton.getId();
|
||||||
Map<String, Long> availableSku = returnCartonItems.lockCarton(sourceReturnCartonId).stream()
|
Map<String, Long> availableSku = returnCartonItems.lockCarton(sourceReturnCartonId).stream()
|
||||||
.collect(Collectors.toMap(line -> line.getSkuCode().toUpperCase(java.util.Locale.ROOT),
|
.collect(Collectors.toMap(line -> line.getSkuCode().toUpperCase(java.util.Locale.ROOT),
|
||||||
line -> remainingRelabelQuantity(sourceReturnCartonId, line), Long::sum));
|
line -> "CARTON_LABEL".equals(operationType(item)) ? value(line.getQuantity())
|
||||||
|
: remainingRelabelQuantity(sourceReturnCartonId, line), Long::sum));
|
||||||
if (availableSku.values().stream().noneMatch(quantity -> quantity > 0))
|
if (availableSku.values().stream().noneMatch(quantity -> quantity > 0))
|
||||||
throw conflict("来源箱 SKU 已全部换标,没有剩余可换标数量");
|
throw conflict("来源箱 SKU 已全部换标,没有剩余可换标数量");
|
||||||
Map<String, Long> requestedSku = selectedReturnSkus.computeIfAbsent(carton.getId(), ignored -> new java.util.HashMap<>());
|
Map<String, Long> requestedSku = selectedReturnSkus.computeIfAbsent(carton.getId(), ignored -> new java.util.HashMap<>());
|
||||||
|
|||||||
@@ -3110,6 +3110,15 @@ components:
|
|||||||
RelabelInventoryCandidate:
|
RelabelInventoryCandidate:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
cartonAvailablePackages: { type: integer, format: int64, minimum: 0, maximum: 1, description: 仅换箱唛可选择的当前实物箱数;已换 SKU 不影响可选箱数,占用和合箱预留仍不可选 }
|
||||||
|
cartonSkuItems:
|
||||||
|
type: array
|
||||||
|
description: 当前实物箱内全部 SKU 和件数,包含换标后的新 SKU
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
skuCode: { type: string }
|
||||||
|
quantity: { type: integer, minimum: 1 }
|
||||||
skuConversions: { type: array, items: { $ref: '#/components/schemas/ReturnSkuConversion' }, description: 当前实物关联的旧新SKU转换、实际件数和客户确认状态 }
|
skuConversions: { type: array, items: { $ref: '#/components/schemas/ReturnSkuConversion' }, description: 当前实物关联的旧新SKU转换、实际件数和客户确认状态 }
|
||||||
relabelStatus: { type: string, nullable: true, enum: [UNLABELLED, PROCESSING, PARTIALLY_COMPLETED, COMPLETED], description: 退货实物按实际完成件数和未完成申请占用计算 }
|
relabelStatus: { type: string, nullable: true, enum: [UNLABELLED, PROCESSING, PARTIALLY_COMPLETED, COMPLETED], description: 退货实物按实际完成件数和未完成申请占用计算 }
|
||||||
relabelledPackages: { type: integer, format: int64, description: 退货实际已完成商品换标件数,不从可用库存差值推导 }
|
relabelledPackages: { type: integer, format: int64, description: 退货实际已完成商品换标件数,不从可用库存差值推导 }
|
||||||
|
|||||||
+27
-2
@@ -109,11 +109,35 @@ class RelabelApplicationServiceTest {
|
|||||||
assertEquals(4L, candidate.stockQuantity());
|
assertEquals(4L, candidate.stockQuantity());
|
||||||
assertEquals("AVAILABLE", candidate.inventoryStatus());
|
assertEquals("AVAILABLE", candidate.inventoryStatus());
|
||||||
assertEquals(List.of(), candidate.mergedIntoCartonMarks());
|
assertEquals(List.of(), candidate.mergedIntoCartonMarks());
|
||||||
|
assertEquals(1, candidate.cartonAvailablePackages());
|
||||||
carton.setStatus("RELABEL_PENDING");
|
carton.setStatus("RELABEL_PENDING");
|
||||||
var occupied = service.candidates("C", "W", null).get(0);
|
var occupied = service.candidates("C", "W", null).get(0);
|
||||||
assertEquals(0, occupied.availablePackages());
|
assertEquals(0, occupied.availablePackages());
|
||||||
|
assertEquals(0, occupied.cartonAvailablePackages());
|
||||||
assertEquals(2, occupied.skuItems().size(), "处理中库存保留占用信息,但不可再次选择");
|
assertEquals(2, occupied.skuItems().size(), "处理中库存保留占用信息,但不可再次选择");
|
||||||
}
|
}
|
||||||
|
@Test void convertedSkuRemainsAvailableForCartonLabelOnly() {
|
||||||
|
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||||
|
var lines = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||||
|
ReflectionTestUtils.setField(service, "returnCartons", cartons);
|
||||||
|
ReflectionTestUtils.setField(service, "returnCartonItems", lines);
|
||||||
|
ReflectionTestUtils.setField(service, "returnPackingTraces", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper.class));
|
||||||
|
var carton = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton();
|
||||||
|
carton.setId(20L); carton.setStatus("AVAILABLE"); carton.setCartonMark("NEW-BOX"); carton.setTotalQuantity(15L);
|
||||||
|
var first = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem();
|
||||||
|
first.setSkuCode("NEW-A"); first.setQuantity(10L); first.setRelabelledQuantity(10L);
|
||||||
|
var second = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem();
|
||||||
|
second.setSkuCode("NEW-B"); second.setQuantity(5L); second.setRelabelledQuantity(5L);
|
||||||
|
when(cartons.findRelabelCandidates("C", "W")).thenReturn(List.of(carton));
|
||||||
|
when(lines.listCarton(20L)).thenReturn(List.of(first, second));
|
||||||
|
var candidate = service.candidates("C", "W", null).get(0);
|
||||||
|
assertEquals(0, candidate.availablePackages());
|
||||||
|
assertTrue(candidate.skuItems().isEmpty());
|
||||||
|
assertEquals("NEW-BOX", candidate.cartonMark());
|
||||||
|
assertEquals(1, candidate.cartonAvailablePackages());
|
||||||
|
assertEquals(List.of(new RelabelResponses.SkuItem("NEW-A", null, 10, null),
|
||||||
|
new RelabelResponses.SkuItem("NEW-B", null, 5, null)), candidate.cartonSkuItems());
|
||||||
|
}
|
||||||
@org.junit.jupiter.params.ParameterizedTest
|
@org.junit.jupiter.params.ParameterizedTest
|
||||||
@org.junit.jupiter.params.provider.ValueSource(longs = {0, 2, 5, 8})
|
@org.junit.jupiter.params.provider.ValueSource(longs = {0, 2, 5, 8})
|
||||||
void returnCandidatesOnlyOfferUnlabelledPieces(long completed) {
|
void returnCandidatesOnlyOfferUnlabelledPieces(long completed) {
|
||||||
@@ -1066,7 +1090,7 @@ class RelabelApplicationServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@org.junit.jupiter.params.ParameterizedTest
|
@org.junit.jupiter.params.ParameterizedTest
|
||||||
@org.junit.jupiter.params.provider.CsvSource({"CARTON_LABEL,0", "SKU_LABEL,0", "SKU_LABEL,2", "CARTON_AND_SKU,0"})
|
@org.junit.jupiter.params.provider.CsvSource({"CARTON_LABEL,0", "CARTON_LABEL,5", "SKU_LABEL,0", "SKU_LABEL,2", "CARTON_AND_SKU,0"})
|
||||||
void intactParcelSupportsAllLabelOperationsWithoutMergingIntoBox(String type, long completed) {
|
void intactParcelSupportsAllLabelOperationsWithoutMergingIntoBox(String type, long completed) {
|
||||||
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||||
var lines = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
var lines = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||||
@@ -1077,7 +1101,8 @@ class RelabelApplicationServiceTest {
|
|||||||
parcel.setCountStatus("CONFIRMED"); parcel.setTotalQuantity(5L);
|
parcel.setCountStatus("CONFIRMED"); parcel.setTotalQuantity(5L);
|
||||||
var line = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem(); line.setSkuCode("SKU-A"); line.setQuantity(5L);
|
var line = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem(); line.setSkuCode("SKU-A"); line.setQuantity(5L);
|
||||||
when(cartons.lockId(901L)).thenReturn(parcel); when(lines.lockCarton(901L)).thenReturn(List.of(line));
|
when(cartons.lockId(901L)).thenReturn(parcel); when(lines.lockCarton(901L)).thenReturn(List.of(line));
|
||||||
when(cartons.completedSkuQuantity(901L, "SKU-A")).thenReturn(completed);
|
if ("CARTON_LABEL".equals(type)) line.setRelabelledQuantity(completed);
|
||||||
|
else when(cartons.completedSkuQuantity(901L, "SKU-A")).thenReturn(completed);
|
||||||
when(cartons.markRelabelPending(anyLong(), anyLong(), anyString(), any())).thenReturn(1);
|
when(cartons.markRelabelPending(anyLong(), anyLong(), anyString(), any())).thenReturn(1);
|
||||||
var app = application(11L, "RL-PARCEL", "10001", "parcel-key", null, "PENDING_REVIEW"); app.setLastlegOrderId(21L);
|
var app = application(11L, "RL-PARCEL", "10001", "parcel-key", null, "PENDING_REVIEW"); app.setLastlegOrderId(21L);
|
||||||
when(applications.findNo(eq("10001"), any())).thenReturn(app);
|
when(applications.findNo(eq("10001"), any())).thenReturn(app);
|
||||||
|
|||||||
Reference in New Issue
Block a user