贴标申请,仓储转运
This commit is contained in:
+3
-1
@@ -258,11 +258,13 @@ public interface DispatchCandidateMapper {
|
|||||||
@Param("relabelNo") String relabelNo);
|
@Param("relabelNo") String relabelNo);
|
||||||
|
|
||||||
@Select("<script>SELECT a.customer_id AS customerId,a.lastleg_order_id AS orderId,a.overseas_warehouse_id AS overseasWarehouseId," +
|
@Select("<script>SELECT a.customer_id AS customerId,a.lastleg_order_id AS orderId,a.overseas_warehouse_id AS overseasWarehouseId," +
|
||||||
"MIN(ri.source_warehouse_name) AS overseasWarehouseName,a.relabel_no AS orderNo,a.relabel_no AS orderRefs," +
|
"MIN(ri.source_warehouse_name) AS overseasWarehouseName,'RETURN_RELABEL' AS orderType,a.relabel_no AS orderNo,a.relabel_no AS orderRefs," +
|
||||||
"GROUP_CONCAT(DISTINCT c.source_return_no ORDER BY c.source_return_no SEPARATOR ',') AS inboundCode," +
|
"GROUP_CONCAT(DISTINCT c.source_return_no ORDER BY c.source_return_no SEPARATOR ',') AS inboundCode," +
|
||||||
"c.pallet_code AS palletNo,COUNT(DISTINCT c.id) AS packageCount,'RELABEL' AS sourceStage,1 AS returnCartonSource," +
|
"c.pallet_code AS palletNo,COUNT(DISTINCT c.id) AS packageCount,'RELABEL' AS sourceStage,1 AS returnCartonSource," +
|
||||||
"GROUP_CONCAT(DISTINCT c.carton_mark ORDER BY c.carton_mark SEPARATOR ',') AS relabelCartonMarks," +
|
"GROUP_CONCAT(DISTINCT c.carton_mark ORDER BY c.carton_mark SEPARATOR ',') AS relabelCartonMarks," +
|
||||||
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS fbaWarehouseCode," +
|
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS fbaWarehouseCode," +
|
||||||
|
"COALESCE((SELECT CASE WHEN COUNT(*)=COUNT(x.weight) THEN SUM(x.weight) END FROM (SELECT DISTINCT c2.id,c2.weight FROM ll_return_inventory_carton c2 JOIN ll_relabel_item ri2 ON ri2.return_carton_id=c2.id AND ri2.customer_id=c2.customer_id JOIN ll_relabel_application a2 ON a2.id=ri2.application_id AND a2.customer_id=ri2.customer_id WHERE c2.customer_id=a.customer_id AND c2.pallet_code=c.pallet_code AND c2.status='RELABEL_PENDING' AND a2.id=a.id) x),(SELECT rr.weight FROM ll_return_receipt rr WHERE rr.customer_id=a.customer_id AND rr.unit_type='PALLET' AND rr.unit_code=c.pallet_code ORDER BY rr.signed_at DESC LIMIT 1)) AS grossWeightKg," +
|
||||||
|
"COALESCE((SELECT CASE WHEN COUNT(*)=COUNT(x.volume) THEN SUM(x.volume) END FROM (SELECT DISTINCT c2.id,(c2.length*c2.width*c2.height)/1000000 AS volume FROM ll_return_inventory_carton c2 JOIN ll_relabel_item ri2 ON ri2.return_carton_id=c2.id AND ri2.customer_id=c2.customer_id JOIN ll_relabel_application a2 ON a2.id=ri2.application_id AND a2.customer_id=ri2.customer_id WHERE c2.customer_id=a.customer_id AND c2.pallet_code=c.pallet_code AND c2.status='RELABEL_PENDING' AND a2.id=a.id) x),(SELECT rr.length*rr.width*rr.height/1000000 FROM ll_return_receipt rr WHERE rr.customer_id=a.customer_id AND rr.unit_type='PALLET' AND rr.unit_code=c.pallet_code ORDER BY rr.signed_at DESC LIMIT 1)) AS volumeCbm," +
|
||||||
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS destinationWarehouseCode " +
|
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS destinationWarehouseCode " +
|
||||||
"FROM ll_relabel_application a JOIN ll_relabel_item ri ON ri.application_id=a.id AND ri.customer_id=a.customer_id " +
|
"FROM ll_relabel_application a JOIN ll_relabel_item ri ON ri.application_id=a.id AND ri.customer_id=a.customer_id " +
|
||||||
"JOIN ll_return_inventory_carton c ON c.id=ri.return_carton_id AND c.customer_id=a.customer_id " +
|
"JOIN ll_return_inventory_carton c ON c.id=ri.return_carton_id AND c.customer_id=a.customer_id " +
|
||||||
|
|||||||
+8
-1
@@ -25,7 +25,14 @@ public final class PalletOperationResponses {
|
|||||||
String palletCode, String status, int palletVersion,
|
String palletCode, String status, int palletVersion,
|
||||||
@JsonSerialize(using = ToStringSerializer.class) Long locationId,
|
@JsonSerialize(using = ToStringSerializer.class) Long locationId,
|
||||||
String locationCode, String destinationWarehouseCode,
|
String locationCode, String destinationWarehouseCode,
|
||||||
long totalPackages, List<InventoryLine> inventory) { }
|
long totalPackages, List<InventoryLine> inventory,
|
||||||
|
java.math.BigDecimal grossWeightKg, java.math.BigDecimal volumeCbm) {
|
||||||
|
public PalletContent(long palletId, String palletCode, String status, int palletVersion, Long locationId,
|
||||||
|
String locationCode, String destinationWarehouseCode, long totalPackages, List<InventoryLine> inventory) {
|
||||||
|
this(palletId, palletCode, status, palletVersion, locationId, locationCode, destinationWarehouseCode,
|
||||||
|
totalPackages, inventory, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
public record PrintablePallet(@JsonSerialize(using = ToStringSerializer.class) long palletId,
|
public record PrintablePallet(@JsonSerialize(using = ToStringSerializer.class) long palletId,
|
||||||
String palletCode, int palletVersion,
|
String palletCode, int palletVersion,
|
||||||
String relabelNo, String printableHtml) { }
|
String relabelNo, String printableHtml) { }
|
||||||
|
|||||||
+19
-4
@@ -316,7 +316,8 @@ public class PdaPalletOperationService {
|
|||||||
PalletOperationRequests.RelabelDisassemble request) {
|
PalletOperationRequests.RelabelDisassemble request) {
|
||||||
requireRelabelDestination(identity.overseasWarehouseId(), selected, target);
|
requireRelabelDestination(identity.overseasWarehouseId(), selected, target);
|
||||||
Set<String> alreadyBound = boundRelabelMarks(application);
|
Set<String> alreadyBound = boundRelabelMarks(application);
|
||||||
if (selected.stream().map(RelabelItem::getCartonMark).anyMatch(alreadyBound::contains))
|
boolean rebind = request.actualSource() == null && selected.stream().allMatch(item -> alreadyBound.contains(item.getCartonMark()));
|
||||||
|
if (!rebind && selected.stream().map(RelabelItem::getCartonMark).anyMatch(alreadyBound::contains))
|
||||||
throw conflict("退货换标箱已在本轮完成组托,请刷新后重试");
|
throw conflict("退货换标箱已在本轮完成组托,请刷新后重试");
|
||||||
long quantity = 0;
|
long quantity = 0;
|
||||||
List<ReturnInventoryCarton> cartons = new ArrayList<>();
|
List<ReturnInventoryCarton> cartons = new ArrayList<>();
|
||||||
@@ -330,8 +331,8 @@ public class PdaPalletOperationService {
|
|||||||
quantity += value(item.getNotifiedPackageCount());
|
quantity += value(item.getNotifiedPackageCount());
|
||||||
cartons.add(carton);
|
cartons.add(carton);
|
||||||
}
|
}
|
||||||
if (request.actualSource() == null || request.actualSource().positionId() != cartons.get(0).getId()
|
if (!rebind && (request.actualSource() == null || request.actualSource().positionId() != cartons.get(0).getId()
|
||||||
|| request.actualSource().packages() != quantity)
|
|| request.actualSource().packages() != quantity))
|
||||||
throw conflict("退货换标拆出数量必须等于本批已完成贴标数量");
|
throw conflict("退货换标拆出数量必须等于本批已完成贴标数量");
|
||||||
String key = required(request.idempotencyKey(), "幂等键不能为空");
|
String key = required(request.idempotencyKey(), "幂等键不能为空");
|
||||||
String requestHash = hash(request);
|
String requestHash = hash(request);
|
||||||
@@ -836,6 +837,8 @@ public class PdaPalletOperationService {
|
|||||||
Set<String> destinations = new HashSet<>();
|
Set<String> destinations = new HashSet<>();
|
||||||
selected.forEach(item -> destinations.add(text(item.getDestinationFbaWarehouseCode())));
|
selected.forEach(item -> destinations.add(text(item.getDestinationFbaWarehouseCode())));
|
||||||
if (destinations.size() != 1 || destinations.contains("")) throw conflict("请按已维护的新目的仓分批组托");
|
if (destinations.size() != 1 || destinations.contains("")) throw conflict("请按已维护的新目的仓分批组托");
|
||||||
|
returnCartons.palletCartons(String.valueOf(warehouseId), target.palletCode())
|
||||||
|
.forEach(carton -> destinations.add(text(carton.getDestinationWarehouseCode())));
|
||||||
for (PalletInventoryRow row : mapper.content(String.valueOf(warehouseId), target.palletId())) {
|
for (PalletInventoryRow row : mapper.content(String.valueOf(warehouseId), target.palletId())) {
|
||||||
var bound = relabelItems.selectList(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<RelabelItem>()
|
var bound = relabelItems.selectList(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<RelabelItem>()
|
||||||
.eq("customer_id", row.getCustomerId()).eq("source_inventory_position_id", row.getPositionId()));
|
.eq("customer_id", row.getCustomerId()).eq("source_inventory_position_id", row.getPositionId()));
|
||||||
@@ -898,10 +901,22 @@ public class PdaPalletOperationService {
|
|||||||
value(row.getReceivingPackages()), value(row.getVersion()));
|
value(row.getReceivingPackages()), value(row.getVersion()));
|
||||||
}).toList();
|
}).toList();
|
||||||
long total = rows.stream().mapToLong(PdaPalletOperationService::rowPackages).sum();
|
long total = rows.stream().mapToLong(PdaPalletOperationService::rowPackages).sum();
|
||||||
|
var boxes = returnCartons.palletCartons(String.valueOf(warehouseId), pallet.palletCode());
|
||||||
|
boxes.forEach(box -> destinations.add(text(box.getDestinationWarehouseCode())));
|
||||||
|
total += boxes.size();
|
||||||
|
// Derive totals from current physical bindings, so retries and rebinds cannot double count.
|
||||||
|
java.math.BigDecimal weight = rows.isEmpty() ? java.math.BigDecimal.ZERO : null;
|
||||||
|
java.math.BigDecimal volume = rows.isEmpty() ? java.math.BigDecimal.ZERO : null;
|
||||||
|
for (var box : boxes) {
|
||||||
|
if (weight != null) weight = box.getWeight() == null || box.getWeight().signum() <= 0 ? null : weight.add(box.getWeight());
|
||||||
|
if (volume != null) volume = box.getLength() == null || box.getWidth() == null || box.getHeight() == null
|
||||||
|
|| box.getLength().signum() <= 0 || box.getWidth().signum() <= 0 || box.getHeight().signum() <= 0 ? null
|
||||||
|
: volume.add(box.getLength().multiply(box.getWidth()).multiply(box.getHeight()).movePointLeft(6));
|
||||||
|
}
|
||||||
String locationCode = locationCode(warehouseId, pallet.locationId());
|
String locationCode = locationCode(warehouseId, pallet.locationId());
|
||||||
return new PalletOperationResponses.PalletContent(pallet.palletId(), pallet.palletCode(), pallet.status(),
|
return new PalletOperationResponses.PalletContent(pallet.palletId(), pallet.palletCode(), pallet.status(),
|
||||||
pallet.version(), pallet.locationId(), locationCode, destinations.size() == 1 ? destinations.iterator().next() : null,
|
pallet.version(), pallet.locationId(), locationCode, destinations.size() == 1 ? destinations.iterator().next() : null,
|
||||||
total, inventory);
|
total, inventory, weight, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PdaPalletOperation begin(PdaWarehouseIdentity identity, String type, String key, String requestHash) {
|
private PdaPalletOperation begin(PdaWarehouseIdentity identity, String type, String key, String requestHash) {
|
||||||
|
|||||||
+12
-1
@@ -42,7 +42,18 @@ public final class PdaRelabelResponses {
|
|||||||
List<SkuItem> skuItems,
|
List<SkuItem> skuItems,
|
||||||
int expectedPackages, int relabelledPackages, int skippedPackages,
|
int expectedPackages, int relabelledPackages, int skippedPackages,
|
||||||
int exceptionPackages, int cartonProcessedPackages, int skuProcessedPackages,
|
int exceptionPackages, int cartonProcessedPackages, int skuProcessedPackages,
|
||||||
String destinationFbaWarehouseCode, Long returnCartonId, long returnQuantity) {
|
String destinationFbaWarehouseCode, Long returnCartonId, long returnQuantity,
|
||||||
|
String sourcePalletCode, String sourcePalletBasis) {
|
||||||
|
public Item(Long itemId, String skuCode, String productNameZh, String shipmentNo, String trackingCode,
|
||||||
|
String newSkuCode, String labelRequirement, String relabelFileId, String relabelFileName, String relabelFileUrl,
|
||||||
|
String cartonMark, String fbaCartonMark, String operationType, List<SkuItem> skuItems, int expectedPackages,
|
||||||
|
int relabelledPackages, int skippedPackages, int exceptionPackages, int cartonProcessedPackages,
|
||||||
|
int skuProcessedPackages, String destinationFbaWarehouseCode, Long returnCartonId, long returnQuantity) {
|
||||||
|
this(itemId, skuCode, productNameZh, shipmentNo, trackingCode, newSkuCode, labelRequirement,
|
||||||
|
relabelFileId, relabelFileName, relabelFileUrl, cartonMark, fbaCartonMark, operationType, skuItems,
|
||||||
|
expectedPackages, relabelledPackages, skippedPackages, exceptionPackages, cartonProcessedPackages,
|
||||||
|
skuProcessedPackages, destinationFbaWarehouseCode, returnCartonId, returnQuantity, null, null);
|
||||||
|
}
|
||||||
public Item(Long itemId, String skuCode, String productNameZh, String shipmentNo, String trackingCode,
|
public Item(Long itemId, String skuCode, String productNameZh, String shipmentNo, String trackingCode,
|
||||||
String newSkuCode, String labelRequirement, String relabelFileId, String relabelFileName, String relabelFileUrl,
|
String newSkuCode, String labelRequirement, String relabelFileId, String relabelFileName, String relabelFileUrl,
|
||||||
String cartonMark, String fbaCartonMark, String operationType, List<SkuItem> skuItems, int expectedPackages,
|
String cartonMark, String fbaCartonMark, String operationType, List<SkuItem> skuItems, int expectedPackages,
|
||||||
|
|||||||
+12
-1
@@ -92,6 +92,7 @@ public class PdaRelabelService {
|
|||||||
@Resource private PdaRelabelTaskMapper tasks;
|
@Resource private PdaRelabelTaskMapper tasks;
|
||||||
@Resource private PdaRelabelUnitFactMapper unitFacts;
|
@Resource private PdaRelabelUnitFactMapper unitFacts;
|
||||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||||
|
@Resource private com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper sourcePositions;
|
||||||
@Resource private PdaRelabelOperationMapper operations;
|
@Resource private PdaRelabelOperationMapper operations;
|
||||||
@Resource private PdaRelabelHistoryMapper histories;
|
@Resource private PdaRelabelHistoryMapper histories;
|
||||||
@Resource private RelabelApplicationService applicationService;
|
@Resource private RelabelApplicationService applicationService;
|
||||||
@@ -786,7 +787,7 @@ public class PdaRelabelService {
|
|||||||
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "CARTON") ? 1 : 0),
|
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "CARTON") ? 1 : 0),
|
||||||
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "SKU") ? 1 : 0),
|
count(itemFacts, "RELABELLED") + (itemFacts.isEmpty() && mixedStageDone(task, item.getId(), "SKU") ? 1 : 0),
|
||||||
item.getDestinationFbaWarehouseCode(), item.getReturnCartonId(),
|
item.getDestinationFbaWarehouseCode(), item.getReturnCartonId(),
|
||||||
returnQuantity(item));
|
returnQuantity(item), sourcePalletCode(item), item.getReturnCartonId() != null ? "RETURN_CARTON" : "INVENTORY_REFERENCE");
|
||||||
}).toList();
|
}).toList();
|
||||||
List<PdaRelabelResponses.UnitFact> factResponses = facts.stream().map(fact ->
|
List<PdaRelabelResponses.UnitFact> factResponses = facts.stream().map(fact ->
|
||||||
new PdaRelabelResponses.UnitFact(fact.getId(), fact.getItemId(), fact.getOldLabel(), fact.getNewLabel(),
|
new PdaRelabelResponses.UnitFact(fact.getId(), fact.getItemId(), fact.getOldLabel(), fact.getNewLabel(),
|
||||||
@@ -881,6 +882,16 @@ public class PdaRelabelService {
|
|||||||
List.class, PdaRelabelResponses.SkuItem.class));
|
List.class, PdaRelabelResponses.SkuItem.class));
|
||||||
} catch (JsonProcessingException exception) { throw new IllegalStateException("Cannot read relabel SKU details", exception); }
|
} catch (JsonProcessingException exception) { throw new IllegalStateException("Cannot read relabel SKU details", exception); }
|
||||||
}
|
}
|
||||||
|
private String sourcePalletCode(RelabelItem item) {
|
||||||
|
if (item.getReturnCartonId() != null) {
|
||||||
|
var carton = returnCartons == null ? null : returnCartons.selectById(item.getReturnCartonId());
|
||||||
|
return carton != null && Objects.equals(item.getCustomerId(), carton.getCustomerId()) ? carton.getPalletCode() : null;
|
||||||
|
}
|
||||||
|
var position = sourcePositions == null || item.getSourceInventoryPositionId() == null ? null
|
||||||
|
: sourcePositions.findTenantPosition(item.getCustomerId(), item.getSourceInventoryPositionId());
|
||||||
|
return position != null ? position.getPalletNo() : null;
|
||||||
|
}
|
||||||
|
|
||||||
private long returnQuantity(RelabelItem item) {
|
private long returnQuantity(RelabelItem item) {
|
||||||
var carton = item.getReturnCartonId() == null ? null : returnCartons.selectById(item.getReturnCartonId());
|
var carton = item.getReturnCartonId() == null ? null : returnCartons.selectById(item.getReturnCartonId());
|
||||||
return carton == null || carton.getTotalQuantity() == null ? 0L : carton.getTotalQuantity();
|
return carton == null || carton.getTotalQuantity() == null ? 0L : carton.getTotalQuantity();
|
||||||
|
|||||||
+9
-2
@@ -25,11 +25,18 @@ public final class PdaReturnReceiptRequests {
|
|||||||
@Size(max = 16) String unitType,
|
@Size(max = 16) String unitType,
|
||||||
@Size(max = 128) String unitCode,
|
@Size(max = 128) String unitCode,
|
||||||
BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight,
|
BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight,
|
||||||
Long totalBoxCount) {
|
Long totalBoxCount, Boolean originalPackaging) {
|
||||||
public Confirm(String returnNo, String trackingNo, String startedBy, String palletCode,
|
public Confirm(String returnNo, String trackingNo, String startedBy, String palletCode,
|
||||||
List<@Valid Item> items, String idempotencyKey) {
|
List<@Valid Item> items, String idempotencyKey) {
|
||||||
this(returnNo, trackingNo, startedBy, palletCode, items, idempotencyKey,
|
this(returnNo, trackingNo, startedBy, palletCode, items, idempotencyKey,
|
||||||
null, null, null, null, null, null, null);
|
null, null, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
public Confirm(String returnNo, String trackingNo, String startedBy, String palletCode,
|
||||||
|
List<@Valid Item> items, String idempotencyKey, String unitType, String unitCode,
|
||||||
|
BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight,
|
||||||
|
Long totalBoxCount) {
|
||||||
|
this(returnNo, trackingNo, startedBy, palletCode, items, idempotencyKey, unitType, unitCode,
|
||||||
|
length, width, height, weight, totalBoxCount, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public record Item(@NotBlank @Size(max = 128) String skuCode,
|
public record Item(@NotBlank @Size(max = 128) String skuCode,
|
||||||
|
|||||||
+8
-2
@@ -3,14 +3,20 @@ package com.baiyue.lastleg.returnreceipt.dto;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.*;
|
import jakarta.validation.constraints.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
public final class ReturnCartonCountDtos {
|
public final class ReturnCartonCountDtos {
|
||||||
private ReturnCartonCountDtos() { }
|
private ReturnCartonCountDtos() { }
|
||||||
public record Query(@Min(1) long page, @Min(1) @Max(50) long size, @Size(max=128) String keyword) { }
|
public record Query(@Min(1) long page, @Min(1) @Max(50) long size, @Size(max=128) String keyword) { }
|
||||||
public record Sku(String skuCode, String productNameZh, long quantity) { }
|
public record Sku(String skuCode, String productNameZh, long quantity) { }
|
||||||
public record Box(String cartonMark, List<Sku> items) { }
|
public record Box(String cartonMark, List<Sku> items, BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight) {
|
||||||
|
public Box(String cartonMark, List<Sku> items) { this(cartonMark, items, null, null, null, null); }
|
||||||
|
}
|
||||||
public record Task(String receiptNo, String returnNo, String palletCode, String status, List<Sku> expectedItems, List<Box> boxes) { }
|
public record Task(String receiptNo, String returnNo, String palletCode, String status, List<Sku> expectedItems, List<Box> boxes) { }
|
||||||
public record CountItem(@NotBlank @Size(max=128) String skuCode, @Min(1) long quantity) { }
|
public record CountItem(@NotBlank @Size(max=128) String skuCode, @Min(1) long quantity) { }
|
||||||
public record CountBox(@NotBlank @Size(max=128) String cartonMark, @NotEmpty @Size(max=100) List<@NotNull @Valid CountItem> items) { }
|
public record CountBox(@NotBlank @Size(max=128) String cartonMark, @NotEmpty @Size(max=100) List<@NotNull @Valid CountItem> items,
|
||||||
|
@Positive BigDecimal length, @Positive BigDecimal width, @Positive BigDecimal height, @Positive BigDecimal weight) {
|
||||||
|
public CountBox(String cartonMark, List<@NotNull @Valid CountItem> items) { this(cartonMark, items, null, null, null, null); }
|
||||||
|
}
|
||||||
public record Confirm(@NotBlank @Size(max=128) String idempotencyKey, @NotEmpty @Size(max=500) List<@NotNull @Valid CountBox> boxes) { }
|
public record Confirm(@NotBlank @Size(max=128) String idempotencyKey, @NotEmpty @Size(max=500) List<@NotNull @Valid CountBox> boxes) { }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -41,6 +41,7 @@ public class ReturnInventoryCarton {
|
|||||||
private BigDecimal width;
|
private BigDecimal width;
|
||||||
private BigDecimal height;
|
private BigDecimal height;
|
||||||
private BigDecimal weight;
|
private BigDecimal weight;
|
||||||
|
private Boolean originalPackaging;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
private LocalDateTime mergedAt;
|
private LocalDateTime mergedAt;
|
||||||
|
|||||||
+4
@@ -23,6 +23,10 @@ public class ReturnReceipt {
|
|||||||
private BigDecimal width;
|
private BigDecimal width;
|
||||||
private BigDecimal height;
|
private BigDecimal height;
|
||||||
private BigDecimal weight;
|
private BigDecimal weight;
|
||||||
|
/** Whether the signed physical unit reused its original box/packaging. */
|
||||||
|
private Boolean originalPackaging;
|
||||||
|
/** PALLET_MEASURED for a measured carrier, UNIT_SUM for child-unit aggregation. */
|
||||||
|
private String measurementSource;
|
||||||
private String palletCode;
|
private String palletCode;
|
||||||
private String cartonMark;
|
private String cartonMark;
|
||||||
private String trackingNo;
|
private String trackingNo;
|
||||||
|
|||||||
+4
@@ -10,6 +10,10 @@ import java.util.List;
|
|||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ReturnInventoryCartonMapper extends BaseMapper<ReturnInventoryCarton> {
|
public interface ReturnInventoryCartonMapper extends BaseMapper<ReturnInventoryCarton> {
|
||||||
|
@Select("SELECT * FROM ll_return_inventory_carton WHERE overseas_warehouse_id=#{warehouseId} " +
|
||||||
|
"AND pallet_code=#{palletCode} AND total_quantity>0 " +
|
||||||
|
"AND status IN ('AVAILABLE','PACKING_RESERVED','RELABEL_PENDING','OUTBOUND_PENDING','DISCARD_PENDING') ORDER BY id")
|
||||||
|
List<ReturnInventoryCarton> palletCartons(@Param("warehouseId") String warehouseId, @Param("palletCode") String palletCode);
|
||||||
@Select("SELECT EXISTS(SELECT 1 FROM ll_return_inventory_outbound_movement WHERE carton_id=#{cartonId} AND quantity>0)")
|
@Select("SELECT EXISTS(SELECT 1 FROM ll_return_inventory_outbound_movement WHERE carton_id=#{cartonId} AND quantity>0)")
|
||||||
boolean hasOutbound(@Param("cartonId") long cartonId);
|
boolean hasOutbound(@Param("cartonId") long cartonId);
|
||||||
@Update("UPDATE ll_return_inventory_carton SET relabel_application_id=NULL,relabel_no=NULL,relabel_status=NULL WHERE id=#{id}")
|
@Update("UPDATE ll_return_inventory_carton SET relabel_application_id=NULL,relabel_no=NULL,relabel_status=NULL WHERE id=#{id}")
|
||||||
|
|||||||
+4
-2
@@ -129,6 +129,7 @@ public class ReturnCartonCountService {
|
|||||||
Set<String> marks = new HashSet<>();
|
Set<String> marks = new HashSet<>();
|
||||||
for (CountBox box : request.boxes()) {
|
for (CountBox box : request.boxes()) {
|
||||||
if (!byMark.containsKey(box.cartonMark()) || !marks.add(box.cartonMark())) throw error(HttpStatus.BAD_REQUEST, "箱号不属于本次签收或重复填写");
|
if (!byMark.containsKey(box.cartonMark()) || !marks.add(box.cartonMark())) throw error(HttpStatus.BAD_REQUEST, "箱号不属于本次签收或重复填写");
|
||||||
|
if (box.length() == null || box.width() == null || box.height() == null || box.weight() == null || box.length().signum() <= 0 || box.width().signum() <= 0 || box.height().signum() <= 0 || box.weight().signum() <= 0) throw error(HttpStatus.BAD_REQUEST, "每个箱子必须填写有效的长、宽、高和重量");
|
||||||
Set<String> skus = new HashSet<>();
|
Set<String> skus = new HashSet<>();
|
||||||
for (CountItem line : box.items()) {
|
for (CountItem line : box.items()) {
|
||||||
if (!expected.containsKey(line.skuCode()) || !skus.add(line.skuCode()) || line.quantity() <= 0) throw error(HttpStatus.BAD_REQUEST, "SKU 不属于本次签收、数量无效或同箱重复");
|
if (!expected.containsKey(line.skuCode()) || !skus.add(line.skuCode()) || line.quantity() <= 0) throw error(HttpStatus.BAD_REQUEST, "SKU 不属于本次签收、数量无效或同箱重复");
|
||||||
@@ -144,7 +145,7 @@ public class ReturnCartonCountService {
|
|||||||
item.setCustomerId(box.getCustomerId()); item.setSkuCode(line.skuCode()); item.setProductNameZh(names.get(line.skuCode())); item.setQuantity(line.quantity());
|
item.setCustomerId(box.getCustomerId()); item.setSkuCode(line.skuCode()); item.setProductNameZh(names.get(line.skuCode())); item.setQuantity(line.quantity());
|
||||||
item.setPackingReservedQuantity(0L); items.insert(item);
|
item.setPackingReservedQuantity(0L); items.insert(item);
|
||||||
}
|
}
|
||||||
box.setSkuTypes(input.items().size()); box.setTotalQuantity(input.items().stream().mapToLong(CountItem::quantity).sum());
|
box.setSkuTypes(input.items().size()); box.setTotalQuantity(input.items().stream().mapToLong(CountItem::quantity).sum()); box.setLength(input.length()); box.setWidth(input.width()); box.setHeight(input.height()); box.setWeight(input.weight());
|
||||||
box.setCountStatus("CONFIRMED"); box.setCountedAt(now); box.setCountedBy(identity.operatorKey()); box.setUpdatedAt(now); cartons.updateById(box);
|
box.setCountStatus("CONFIRMED"); box.setCountedAt(now); box.setCountedBy(identity.operatorKey()); box.setUpdatedAt(now); cartons.updateById(box);
|
||||||
}
|
}
|
||||||
for (String sku : expected.keySet()) {
|
for (String sku : expected.keySet()) {
|
||||||
@@ -167,7 +168,8 @@ public class ReturnCartonCountService {
|
|||||||
private Task task(ReturnReceipt receipt, List<ReturnInventoryCarton> boxes) {
|
private Task task(ReturnReceipt receipt, List<ReturnInventoryCarton> boxes) {
|
||||||
List<Sku> expected = receiptItems.listReceipt(receipt.getId()).stream().map(line -> new Sku(line.getSkuCode(), null, line.getReceivedQuantity())).toList();
|
List<Sku> expected = receiptItems.listReceipt(receipt.getId()).stream().map(line -> new Sku(line.getSkuCode(), null, line.getReceivedQuantity())).toList();
|
||||||
List<Box> contents = boxes.stream().map(box -> new Box(box.getCartonMark(), items.listCarton(box.getId()).stream()
|
List<Box> contents = boxes.stream().map(box -> new Box(box.getCartonMark(), items.listCarton(box.getId()).stream()
|
||||||
.map(line -> new Sku(line.getSkuCode(), line.getProductNameZh(), "PENDING".equals(box.getCountStatus()) ? 0 : line.getQuantity())).toList())).toList();
|
.map(line -> new Sku(line.getSkuCode(), line.getProductNameZh(), "PENDING".equals(box.getCountStatus()) ? 0 : line.getQuantity())).toList(),
|
||||||
|
box.getLength(), box.getWidth(), box.getHeight(), box.getWeight())).toList();
|
||||||
return new Task(receipt.getReceiptNo(), boxes.isEmpty() ? null : boxes.get(0).getSourceReturnNo(), receipt.getPalletCode(),
|
return new Task(receipt.getReceiptNo(), boxes.isEmpty() ? null : boxes.get(0).getSourceReturnNo(), receipt.getPalletCode(),
|
||||||
boxes.stream().allMatch(box -> "CONFIRMED".equals(box.getCountStatus())) ? "CONFIRMED" : sessions.findReceipt(receipt.getId()) != null ? "COUNTING" : "PENDING", expected, contents);
|
boxes.stream().allMatch(box -> "CONFIRMED".equals(box.getCountStatus())) ? "CONFIRMED" : sessions.findReceipt(receipt.getId()) != null ? "COUNTING" : "PENDING", expected, contents);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -509,6 +509,7 @@ public class ReturnInventoryService {
|
|||||||
// Pallet measurements describe the carrier, never each child carton.
|
// Pallet measurements describe the carrier, never each child carton.
|
||||||
if ("BOX".equals(receipt.getUnitType()) || "PARCEL".equals(receipt.getUnitType())) {
|
if ("BOX".equals(receipt.getUnitType()) || "PARCEL".equals(receipt.getUnitType())) {
|
||||||
carton.setLength(receipt.getLength()); carton.setWidth(receipt.getWidth()); carton.setHeight(receipt.getHeight()); carton.setWeight(receipt.getWeight());
|
carton.setLength(receipt.getLength()); carton.setWidth(receipt.getWidth()); carton.setHeight(receipt.getHeight()); carton.setWeight(receipt.getWeight());
|
||||||
|
carton.setOriginalPackaging(receipt.getOriginalPackaging());
|
||||||
}
|
}
|
||||||
carton.setStatus(AVAILABLE); carton.setSkuTypes(lines.size()); carton.setTotalQuantity(total); carton.setCreatedAt(now); carton.setUpdatedAt(now);
|
carton.setStatus(AVAILABLE); carton.setSkuTypes(lines.size()); carton.setTotalQuantity(total); carton.setCreatedAt(now); carton.setUpdatedAt(now);
|
||||||
cartons.insert(carton);
|
cartons.insert(carton);
|
||||||
@@ -1086,6 +1087,16 @@ public class ReturnInventoryService {
|
|||||||
}
|
}
|
||||||
if (existingTarget != null) {
|
if (existingTarget != null) {
|
||||||
target.setTotalQuantity(cartonItems.totalQuantity(targetId)); target.setSkuTypes(cartonItems.skuTypes(targetId));
|
target.setTotalQuantity(cartonItems.totalQuantity(targetId)); target.setSkuTypes(cartonItems.skuTypes(targetId));
|
||||||
|
if (request.weight() != null) target.setWeight(request.weight());
|
||||||
|
else {
|
||||||
|
BigDecimal combinedWeight = target.getWeight();
|
||||||
|
for (var source : sources) combinedWeight = combinedWeight == null || source.getWeight() == null
|
||||||
|
? null : combinedWeight.add(source.getWeight());
|
||||||
|
target.setWeight(combinedWeight);
|
||||||
|
}
|
||||||
|
if (request.length() != null) target.setLength(request.length());
|
||||||
|
if (request.width() != null) target.setWidth(request.width());
|
||||||
|
if (request.height() != null) target.setHeight(request.height());
|
||||||
target.setUpdatedAt(now); target.setLocationCode(null); target.setPutawayAt(null); cartons.updateById(target); cartons.clearPutaway(targetId);
|
target.setUpdatedAt(now); target.setLocationCode(null); target.setPutawayAt(null); cartons.updateById(target); cartons.clearPutaway(targetId);
|
||||||
}
|
}
|
||||||
for (TraceTransfer transfer : transfers) packingTrace.insert(IdWorker.getId(), customerId, operation.getId(),
|
for (TraceTransfer transfer : transfers) packingTrace.insert(IdWorker.getId(), customerId, operation.getId(),
|
||||||
|
|||||||
+10
@@ -618,6 +618,14 @@ public class ReturnReceiptService {
|
|||||||
|| request.height().signum() <= 0 || request.weight().signum() <= 0)) {
|
|| request.height().signum() <= 0 || request.weight().signum() <= 0)) {
|
||||||
throw badRequest("托盘签收必须填写有效的长、宽、高和重量");
|
throw badRequest("托盘签收必须填写有效的长、宽、高和重量");
|
||||||
}
|
}
|
||||||
|
if ("BOX".equals(unitType) || "PARCEL".equals(unitType)) {
|
||||||
|
if (request.originalPackaging() == null) throw badRequest("箱子或包裹必须选择是否使用原箱原包装");
|
||||||
|
if (request.length() == null || request.width() == null || request.height() == null || request.weight() == null
|
||||||
|
|| request.length().signum() <= 0 || request.width().signum() <= 0
|
||||||
|
|| request.height().signum() <= 0 || request.weight().signum() <= 0) {
|
||||||
|
throw badRequest("箱子或包裹必须填写有效的长、宽、高和重量");
|
||||||
|
}
|
||||||
|
}
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
long receiptId = IdWorker.getId();
|
long receiptId = IdWorker.getId();
|
||||||
ReturnReceipt receipt = new ReturnReceipt();
|
ReturnReceipt receipt = new ReturnReceipt();
|
||||||
@@ -629,6 +637,8 @@ public class ReturnReceiptService {
|
|||||||
receipt.setTotalBoxCount(totalBoxCount);
|
receipt.setTotalBoxCount(totalBoxCount);
|
||||||
receipt.setPalletCode("PALLET".equals(unitType) ? unitCode : ("BOX".equals(unitType) ? text(request.palletCode()) : null));
|
receipt.setPalletCode("PALLET".equals(unitType) ? unitCode : ("BOX".equals(unitType) ? text(request.palletCode()) : null));
|
||||||
receipt.setLength(request.length()); receipt.setWidth(request.width()); receipt.setHeight(request.height()); receipt.setWeight(request.weight());
|
receipt.setLength(request.length()); receipt.setWidth(request.width()); receipt.setHeight(request.height()); receipt.setWeight(request.weight());
|
||||||
|
receipt.setOriginalPackaging(request.originalPackaging());
|
||||||
|
receipt.setMeasurementSource("PALLET".equals(unitType) ? "PALLET_MEASURED" : "UNIT_MEASURED");
|
||||||
receipt.setReceivedQuantity(receiptQuantity); receipt.setOperatorId(identity.operatorKey());
|
receipt.setReceivedQuantity(receiptQuantity); receipt.setOperatorId(identity.operatorKey());
|
||||||
receipt.setOperatorName(identity.operatorName()); receipt.setIdempotencyKey(key); receipt.setRequestHash(requestHash);
|
receipt.setOperatorName(identity.operatorName()); receipt.setIdempotencyKey(key); receipt.setRequestHash(requestHash);
|
||||||
receipt.setSignedAt(now); receipt.setCreatedAt(now);
|
receipt.setSignedAt(now); receipt.setCreatedAt(now);
|
||||||
|
|||||||
@@ -3018,6 +3018,8 @@ EXECUTE ll_return_receipt_alter_stmt;
|
|||||||
DEALLOCATE PREPARE ll_return_receipt_alter_stmt;
|
DEALLOCATE PREPARE ll_return_receipt_alter_stmt;
|
||||||
SET @ll_return_receipt_add_pallet = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='pallet_code'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN pallet_code VARCHAR(128) NULL COMMENT ''本次签收绑定的托盘码'' AFTER receipt_batch_no'));
|
SET @ll_return_receipt_add_pallet = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='pallet_code'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN pallet_code VARCHAR(128) NULL COMMENT ''本次签收绑定的托盘码'' AFTER receipt_batch_no'));
|
||||||
PREPARE ll_return_receipt_add_pallet_stmt FROM @ll_return_receipt_add_pallet; EXECUTE ll_return_receipt_add_pallet_stmt; DEALLOCATE PREPARE ll_return_receipt_add_pallet_stmt;
|
PREPARE ll_return_receipt_add_pallet_stmt FROM @ll_return_receipt_add_pallet; EXECUTE ll_return_receipt_add_pallet_stmt; DEALLOCATE PREPARE ll_return_receipt_add_pallet_stmt;
|
||||||
|
SET @ll_return_receipt_add_measurement_facts = (SELECT IF(COUNT(*)=0, 'ALTER TABLE ll_return_receipt ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'', ADD COLUMN measurement_source VARCHAR(32) NULL COMMENT ''尺寸重量来源'' AFTER weight', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='original_packaging');
|
||||||
|
PREPARE ll_return_receipt_add_measurement_facts_stmt FROM @ll_return_receipt_add_measurement_facts; EXECUTE ll_return_receipt_add_measurement_facts_stmt; DEALLOCATE PREPARE ll_return_receipt_add_measurement_facts_stmt;
|
||||||
CREATE TABLE IF NOT EXISTS ll_return_receipt_item (
|
CREATE TABLE IF NOT EXISTS ll_return_receipt_item (
|
||||||
id BIGINT NOT NULL COMMENT '签收SKU明细ID', receipt_id BIGINT NOT NULL COMMENT 'PDA退货签收单ID', return_order_item_id BIGINT NOT NULL COMMENT '退货预报明细ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', received_quantity BIGINT NOT NULL COMMENT '本次SKU实收数量',
|
id BIGINT NOT NULL COMMENT '签收SKU明细ID', receipt_id BIGINT NOT NULL COMMENT 'PDA退货签收单ID', return_order_item_id BIGINT NOT NULL COMMENT '退货预报明细ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', received_quantity BIGINT NOT NULL COMMENT '本次SKU实收数量',
|
||||||
PRIMARY KEY(id), KEY idx_ll_return_receipt_item_receipt(receipt_id), KEY idx_ll_return_receipt_item_order_item(return_order_item_id), CONSTRAINT chk_ll_return_receipt_item_quantity CHECK(received_quantity>0)
|
PRIMARY KEY(id), KEY idx_ll_return_receipt_item_receipt(receipt_id), KEY idx_ll_return_receipt_item_order_item(return_order_item_id), CONSTRAINT chk_ll_return_receipt_item_quantity CHECK(received_quantity>0)
|
||||||
@@ -3037,6 +3039,8 @@ CREATE TABLE IF NOT EXISTS ll_return_inventory_carton (
|
|||||||
) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛';
|
) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛';
|
||||||
SET @ll_return_carton_add_pallet = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='pallet_code'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN pallet_code VARCHAR(128) NULL COMMENT ''签收时绑定的托盘码'' AFTER source_batch_no'));
|
SET @ll_return_carton_add_pallet = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='pallet_code'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN pallet_code VARCHAR(128) NULL COMMENT ''签收时绑定的托盘码'' AFTER source_batch_no'));
|
||||||
PREPARE ll_return_carton_add_pallet_stmt FROM @ll_return_carton_add_pallet; EXECUTE ll_return_carton_add_pallet_stmt; DEALLOCATE PREPARE ll_return_carton_add_pallet_stmt;
|
PREPARE ll_return_carton_add_pallet_stmt FROM @ll_return_carton_add_pallet; EXECUTE ll_return_carton_add_pallet_stmt; DEALLOCATE PREPARE ll_return_carton_add_pallet_stmt;
|
||||||
|
SET @ll_return_carton_add_original_packaging = (SELECT IF(COUNT(*)=0, 'ALTER TABLE ll_return_inventory_carton ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'' AFTER weight', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='original_packaging');
|
||||||
|
PREPARE ll_return_carton_add_original_packaging_stmt FROM @ll_return_carton_add_original_packaging; EXECUTE ll_return_carton_add_original_packaging_stmt; DEALLOCATE PREPARE ll_return_carton_add_original_packaging_stmt;
|
||||||
-- 已合箱的来源箱保留审计记录,但其箱内库存必须归零;动态 SQL 兼容 Spring 的数据库初始化器。
|
-- 已合箱的来源箱保留审计记录,但其箱内库存必须归零;动态 SQL 兼容 Spring 的数据库初始化器。
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND constraint_name='chk_ll_return_inventory_carton_quantity'), 'ALTER TABLE ll_return_inventory_carton DROP CHECK chk_ll_return_inventory_carton_quantity', 'SELECT 1'));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND constraint_name='chk_ll_return_inventory_carton_quantity'), 'ALTER TABLE ll_return_inventory_carton DROP CHECK chk_ll_return_inventory_carton_quantity', 'SELECT 1'));
|
||||||
PREPARE return_inventory_carton_check_drop_stmt FROM @ddl; EXECUTE return_inventory_carton_check_drop_stmt; DEALLOCATE PREPARE return_inventory_carton_check_drop_stmt;
|
PREPARE return_inventory_carton_check_drop_stmt FROM @ddl; EXECUTE return_inventory_carton_check_drop_stmt; DEALLOCATE PREPARE return_inventory_carton_check_drop_stmt;
|
||||||
|
|||||||
+78
@@ -92,6 +92,84 @@ class PdaPalletOperationServiceTest {
|
|||||||
service.configureTransactions(new NoOpTransactionManager());
|
service.configureTransactions(new NoOpTransactionManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test void returnPalletTotalsFollowCurrentBindingsWithoutCountingRetriesTwice() {
|
||||||
|
var first = new ReturnInventoryCarton(); first.setWeight(new java.math.BigDecimal("5"));
|
||||||
|
first.setLength(new java.math.BigDecimal("30")); first.setWidth(new java.math.BigDecimal("20"));
|
||||||
|
first.setHeight(new java.math.BigDecimal("20")); first.setDestinationWarehouseCode("SCK8");
|
||||||
|
var second = new ReturnInventoryCarton(); second.setWeight(new java.math.BigDecimal("11"));
|
||||||
|
second.setLength(new java.math.BigDecimal("40")); second.setWidth(new java.math.BigDecimal("30"));
|
||||||
|
second.setHeight(new java.math.BigDecimal("30")); second.setDestinationWarehouseCode("SCK8");
|
||||||
|
var pallet = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, null, "OCCUPIED", "SCK8", 2);
|
||||||
|
when(returnCartons.palletCartons("88", "TARGET")).thenReturn(List.of(first), List.of(first, second), List.of(first, second), List.of(first));
|
||||||
|
var before = (com.baiyue.lastleg.palletops.dto.PalletOperationResponses.PalletContent) ReflectionTestUtils.invokeMethod(service, "content", 88L, pallet);
|
||||||
|
assertEquals(0, before.grossWeightKg().compareTo(new java.math.BigDecimal("5")));
|
||||||
|
var added = (com.baiyue.lastleg.palletops.dto.PalletOperationResponses.PalletContent) ReflectionTestUtils.invokeMethod(service, "content", 88L, pallet);
|
||||||
|
assertEquals(2, added.totalPackages());
|
||||||
|
assertEquals(0, added.grossWeightKg().compareTo(new java.math.BigDecimal("16")));
|
||||||
|
assertEquals(0, added.volumeCbm().compareTo(new java.math.BigDecimal("0.048")));
|
||||||
|
assertEquals(added, ReflectionTestUtils.invokeMethod(service, "content", 88L, pallet));
|
||||||
|
assertEquals(before, ReflectionTestUtils.invokeMethod(service, "content", 88L, pallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void returnRebindingRequiresCurrentRoundBindingAndAddsToExistingPallet() {
|
||||||
|
var workTasks = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper.class);
|
||||||
|
var workHistory = mock(com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelHistoryMapper.class);
|
||||||
|
ReflectionTestUtils.setField(service, "relabelWorkTasks", workTasks);
|
||||||
|
ReflectionTestUtils.setField(service, "relabelWorkHistory", workHistory);
|
||||||
|
var app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100");
|
||||||
|
var task = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelTask(); task.setId(700L);
|
||||||
|
when(workTasks.find("100", 600L)).thenReturn(task);
|
||||||
|
var item = new RelabelItem(); item.setReturnCartonId(801L); item.setCartonMark("BOX-1");
|
||||||
|
item.setDestinationFbaWarehouseCode("SCK8"); item.setNotifiedPackageCount(1);
|
||||||
|
var box = new ReturnInventoryCarton(); box.setId(801L); box.setCustomerId("100");
|
||||||
|
box.setPalletCode("SOURCE"); box.setRelabelApplicationId(600L); box.setStatus("RELABEL_PENDING");
|
||||||
|
box.setTotalQuantity(10L); box.setWeight(new java.math.BigDecimal("11"));
|
||||||
|
box.setLength(new java.math.BigDecimal("40")); box.setWidth(new java.math.BigDecimal("30"));
|
||||||
|
box.setHeight(new java.math.BigDecimal("30")); box.setDestinationWarehouseCode("SCK8");
|
||||||
|
var existing = new ReturnInventoryCarton(); existing.setWeight(new java.math.BigDecimal("5"));
|
||||||
|
existing.setLength(new java.math.BigDecimal("30")); existing.setWidth(new java.math.BigDecimal("20"));
|
||||||
|
existing.setHeight(new java.math.BigDecimal("20")); existing.setDestinationWarehouseCode("SCK8");
|
||||||
|
var source = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L, "OCCUPIED", "SCK8", 1);
|
||||||
|
var target = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, 1010L, "OCCUPIED", "SCK8", 2);
|
||||||
|
when(returnCartons.lockId(801L)).thenReturn(box);
|
||||||
|
when(returnCartons.palletCartons("88", "TARGET")).thenAnswer(call ->
|
||||||
|
"TARGET".equals(box.getPalletCode()) ? List.of(existing, box) : List.of(existing));
|
||||||
|
var request = new PalletOperationRequests.RelabelDisassemble("rebind-current", "RL-1",
|
||||||
|
new PalletOperationRequests.PalletExpectation("SOURCE", 1),
|
||||||
|
new PalletOperationRequests.PalletExpectation("TARGET", 2), List.of("BOX-1"), "rebind");
|
||||||
|
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service,
|
||||||
|
"bindReturnRelabelPallet", identity(), app, List.of(item), source, target, request));
|
||||||
|
verifyNoInteractions(physical);
|
||||||
|
var binding = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory();
|
||||||
|
binding.setDetailJson("{\"round\":\"INITIAL\",\"cartonMarks\":[\"BOX-1\"]}");
|
||||||
|
when(workHistory.selectList(any())).thenReturn(List.of(binding));
|
||||||
|
task.setReviewedAt(LocalDateTime.of(2026, 10, 12, 12, 0));
|
||||||
|
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service,
|
||||||
|
"bindReturnRelabelPallet", identity(), app, List.of(item), source, target, request));
|
||||||
|
task.setReviewedAt(null);
|
||||||
|
when(physical.resolve("SOURCE")).thenReturn(source);
|
||||||
|
when(physical.resolve("TARGET")).thenReturn(target);
|
||||||
|
var result = (com.baiyue.lastleg.palletops.dto.PalletOperationResponses.Operation)
|
||||||
|
ReflectionTestUtils.invokeMethod(service, "bindReturnRelabelPallet", identity(), app,
|
||||||
|
List.of(item), source, target, request);
|
||||||
|
assertEquals("TARGET", box.getPalletCode());
|
||||||
|
assertEquals(0, result.pallets().get(0).totalPackages());
|
||||||
|
assertEquals(2, result.pallets().get(1).totalPackages());
|
||||||
|
assertEquals(0, result.pallets().get(1).grossWeightKg().compareTo(new java.math.BigDecimal("16")));
|
||||||
|
assertEquals(0, result.pallets().get(1).volumeCbm().compareTo(new java.math.BigDecimal("0.048")));
|
||||||
|
verify(returnCartons).updateById(box);
|
||||||
|
verify(workHistory).insert(any(com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void missingCartonMeasurementDoesNotBecomeZeroWeight() {
|
||||||
|
var box = new ReturnInventoryCarton(); box.setWeight(new java.math.BigDecimal("5"));
|
||||||
|
when(returnCartons.palletCartons("88", "TARGET")).thenReturn(List.of(box));
|
||||||
|
var pallet = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, null, "OCCUPIED", "SCK8", 2);
|
||||||
|
var result = (com.baiyue.lastleg.palletops.dto.PalletOperationResponses.PalletContent) ReflectionTestUtils.invokeMethod(service, "content", 88L, pallet);
|
||||||
|
assertNull(result.volumeCbm());
|
||||||
|
assertEquals(0, result.grossWeightKg().compareTo(new java.math.BigDecimal("5")));
|
||||||
|
}
|
||||||
|
|
||||||
@Test void partialReturnRelabelKeepsUnlabelledRemainderOnOriginalPallet() {
|
@Test void partialReturnRelabelKeepsUnlabelledRemainderOnOriginalPallet() {
|
||||||
RelabelApplication app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100"); app.setRelabelNo("RL-1");
|
RelabelApplication app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100"); app.setRelabelNo("RL-1");
|
||||||
RelabelItem item = new RelabelItem(); item.setId(701L); item.setSkuCode("SKU-1");
|
RelabelItem item = new RelabelItem(); item.setId(701L); item.setSkuCode("SKU-1");
|
||||||
|
|||||||
+25
@@ -82,6 +82,31 @@ class PdaRelabelServiceTest {
|
|||||||
lenient().when(tasks.updateWithVersion(any(), anyInt())).thenReturn(1);
|
lenient().when(tasks.updateWithVersion(any(), anyInt())).thenReturn(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detailDistinguishesInventoryReferenceFromRecordedReturnPallet() {
|
||||||
|
when(applications.findAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||||
|
var positions = org.mockito.Mockito.mock(com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper.class);
|
||||||
|
ReflectionTestUtils.setField(service, "sourcePositions", positions);
|
||||||
|
item.setSourceInventoryPositionId(30L);
|
||||||
|
var position = new com.baiyue.lastleg.inventory.entity.InventoryPosition();
|
||||||
|
position.setPalletNo("PLT-REFERENCE");
|
||||||
|
when(positions.findTenantPosition("100", 30L)).thenReturn(position);
|
||||||
|
var ordinary = service.detail("7", 900L, "FBR-1").items().get(0);
|
||||||
|
assertEquals("PLT-REFERENCE", ordinary.sourcePalletCode());
|
||||||
|
assertEquals("INVENTORY_REFERENCE", ordinary.sourcePalletBasis());
|
||||||
|
var cartons = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||||
|
ReflectionTestUtils.setField(service, "returnCartons", cartons);
|
||||||
|
item.setReturnCartonId(40L);
|
||||||
|
var carton = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton();
|
||||||
|
carton.setCustomerId("100"); carton.setPalletCode("PLT-RETURN"); carton.setTotalQuantity(20L);
|
||||||
|
when(cartons.selectById(40L)).thenReturn(carton);
|
||||||
|
var returned = service.detail("7", 900L, "FBR-1").items().get(0);
|
||||||
|
assertEquals("PLT-RETURN", returned.sourcePalletCode());
|
||||||
|
assertEquals("RETURN_CARTON", returned.sourcePalletBasis());
|
||||||
|
carton.setCustomerId("OTHER");
|
||||||
|
assertEquals(null, service.detail("7", 900L, "FBR-1").items().get(0).sourcePalletCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void candidatesUseCustomerNameOnlyFromMatchingCustomerOrder() {
|
void candidatesUseCustomerNameOnlyFromMatchingCustomerOrder() {
|
||||||
var orders = org.mockito.Mockito.mock(com.baiyue.lastleg.mapper.LastlegOrderMapper.class);
|
var orders = org.mockito.Mockito.mock(com.baiyue.lastleg.mapper.LastlegOrderMapper.class);
|
||||||
|
|||||||
+2
-2
@@ -454,7 +454,7 @@ class ReturnReceiptServiceTest {
|
|||||||
stubNewReceipt(order, List.of(line), "SP-SOURCE");
|
stubNewReceipt(order, List.of(line), "SP-SOURCE");
|
||||||
var request = new PdaReturnReceiptRequests.Confirm("RT-1", null, "SKU", null,
|
var request = new PdaReturnReceiptRequests.Confirm("RT-1", null, "SKU", null,
|
||||||
List.of(new PdaReturnReceiptRequests.Item("SKU-A", 1)), "parcel-key",
|
List.of(new PdaReturnReceiptRequests.Item("SKU-A", 1)), "parcel-key",
|
||||||
"PARCEL", null, null, null, null, null, null);
|
"PARCEL", null, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, null, true);
|
||||||
var result = service.confirm(identity, request);
|
var result = service.confirm(identity, request);
|
||||||
var capture = ArgumentCaptor.forClass(ReturnReceipt.class);
|
var capture = ArgumentCaptor.forClass(ReturnReceipt.class);
|
||||||
verify(receipts).insert(capture.capture());
|
verify(receipts).insert(capture.capture());
|
||||||
@@ -611,6 +611,6 @@ class ReturnReceiptServiceTest {
|
|||||||
// These focused tests exercise receipt/idempotency behavior at the
|
// These focused tests exercise receipt/idempotency behavior at the
|
||||||
// carton level. Pallet receipts have a separate multi-carton contract.
|
// carton level. Pallet receipts have a separate multi-carton contract.
|
||||||
return new PdaReturnReceiptRequests.Confirm("RT-1", trackingNo, startedBy, palletCode, boxed, key,
|
return new PdaReturnReceiptRequests.Confirm("RT-1", trackingNo, startedBy, palletCode, boxed, key,
|
||||||
"BOX", palletCode, null, null, null, null, 1L);
|
"BOX", palletCode, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, 1L, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user