贴标申请,仓储转运
This commit is contained in:
@@ -39,7 +39,7 @@ public final class RelabelResponses {
|
|||||||
long availablePackages, long version, List<InventoryPosition> positions,
|
long availablePackages, long version, List<InventoryPosition> positions,
|
||||||
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 relabelledQuantity, long relabelReservedQuantity, Long stockQuantity, String inventoryStatus, String occupiedRelabelNo, List<String> mergedIntoCartonMarks, boolean hasOutbound) {
|
||||||
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,
|
||||||
@@ -48,7 +48,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);
|
skuItems, relabelledPackages, null, relabelledPackages, 0, null, null, null, List.of(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public record HistoricalOrder(Long id, String orderNo, String sourceOrderNo, String outboundOrderNo,
|
public record HistoricalOrder(Long id, String orderNo, String sourceOrderNo, String outboundOrderNo,
|
||||||
|
|||||||
+20
-12
@@ -94,6 +94,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||||
@Resource private ReturnInventoryCartonItemMapper returnCartonItems;
|
@Resource private ReturnInventoryCartonItemMapper returnCartonItems;
|
||||||
@Resource private ReturnOrderMapper returnOrders;
|
@Resource private ReturnOrderMapper returnOrders;
|
||||||
|
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper returnPackingTraces;
|
||||||
@Resource private RelabelDocumentFileGateway documentFiles;
|
@Resource private RelabelDocumentFileGateway documentFiles;
|
||||||
@Resource private PdaRelabelTaskMapper pdaTasks;
|
@Resource private PdaRelabelTaskMapper pdaTasks;
|
||||||
@Resource private RelabelPhotoFileGateway photoFiles;
|
@Resource private RelabelPhotoFileGateway photoFiles;
|
||||||
@@ -269,7 +270,11 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
if (StringUtils.hasText(keyword)
|
if (StringUtils.hasText(keyword)
|
||||||
&& !containsIgnoreCase(carton.getCartonMark(), keyword)
|
&& !containsIgnoreCase(carton.getCartonMark(), keyword)
|
||||||
&& !containsIgnoreCase(carton.getSourceReturnNo(), keyword)) continue;
|
&& !containsIgnoreCase(carton.getSourceReturnNo(), keyword)) continue;
|
||||||
List<RelabelResponses.SkuItem> cartonSkus = returnCartonItems.listCarton(carton.getId()).stream()
|
var cartonLines = returnCartonItems.listCarton(carton.getId());
|
||||||
|
String productNames = cartonLines.stream().filter(item -> value(item.getQuantity()) > 0)
|
||||||
|
.map(item -> trim(item.getProductNameZh())).filter(StringUtils::hasText).distinct()
|
||||||
|
.collect(Collectors.joining("、"));
|
||||||
|
List<RelabelResponses.SkuItem> cartonSkus = cartonLines.stream()
|
||||||
.filter(item -> value(item.getQuantity()) > 0)
|
.filter(item -> value(item.getQuantity()) > 0)
|
||||||
.map(item -> {
|
.map(item -> {
|
||||||
long remaining = "AVAILABLE".equals(carton.getStatus())
|
long remaining = "AVAILABLE".equals(carton.getStatus())
|
||||||
@@ -277,7 +282,7 @@ 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);
|
||||||
}).toList();
|
}).toList();
|
||||||
if (cartonSkus.isEmpty()) continue;
|
if (cartonSkus.isEmpty() && !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();
|
||||||
@@ -285,9 +290,12 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
long reserved = returnCartons.relabelReservedQuantity(carton.getId());
|
long reserved = returnCartons.relabelReservedQuantity(carton.getId());
|
||||||
result.add(new RelabelResponses.InventoryCandidate(null, null, carton.getSourceReturnNo(),
|
result.add(new RelabelResponses.InventoryCandidate(null, null, carton.getSourceReturnNo(),
|
||||||
null, carton.getOverseasWarehouseId(), null, null, carton.getDestinationWarehouseCode(),
|
null, carton.getOverseasWarehouseId(), null, null, carton.getDestinationWarehouseCode(),
|
||||||
carton.getDestinationWarehouseName(), skuCodes, "退货入库库存", availablePackages, 0, List.of(),
|
carton.getDestinationWarehouseName(), skuCodes, productNames, availablePackages, 0, List.of(),
|
||||||
"PARCEL".equals(carton.getSourceUnitType()) ? "RETURN_PARCEL" : "RETURN_CARTON", carton.getId(), carton.getCartonMark(), cartonSkus, relabelledPackages,
|
"PARCEL".equals(carton.getSourceUnitType()) ? "RETURN_PARCEL" : "RETURN_CARTON", carton.getId(), carton.getCartonMark(), cartonSkus, relabelledPackages,
|
||||||
com.baiyue.lastleg.returnreceipt.service.ReturnRelabelProgress.status(value(carton.getTotalQuantity()), relabelledPackages, reserved), relabelledPackages, reserved));
|
com.baiyue.lastleg.returnreceipt.service.ReturnRelabelProgress.status(value(carton.getTotalQuantity()), relabelledPackages, reserved), relabelledPackages, reserved, java.util.Set.of("MERGED", "OUTBOUNDED").contains(carton.getStatus()) ? 0L : value(carton.getTotalQuantity()), carton.getStatus(), carton.getRelabelNo(),
|
||||||
|
returnPackingTraces.listCartonHistory(customerId, carton.getCartonMark()).stream()
|
||||||
|
.filter(history -> carton.getCartonMark().equals(history.sourceCartonMark()))
|
||||||
|
.map(history -> history.targetCartonMark()).distinct().toList(), returnCartons.hasOutbound(carton.getId())));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -679,10 +687,10 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
// Reviewing/processing a return relabel must not move the already
|
// Reviewing/processing a return relabel must not move the already
|
||||||
// completed inbound order back to assignment or relabel waiting.
|
// completed inbound order back to assignment or relabel waiting.
|
||||||
if ("RETURN_RELABEL".equals(order.getOrderType())) {
|
if ("RETURN_RELABEL".equals(order.getOrderType())) {
|
||||||
orderStatus = "INBOUND_COMPLETED";
|
orderStatus = orderBefore;
|
||||||
}
|
}
|
||||||
order.setCurrentStatus(orderStatus);
|
order.setCurrentStatus(orderStatus);
|
||||||
order.setSubOrderStatus("RELABEL:" + next);
|
if (!"RETURN_RELABEL".equals(order.getOrderType())) order.setSubOrderStatus("RELABEL:" + next);
|
||||||
order.setRelabelStatus(businessRelabelStatus(next));
|
order.setRelabelStatus(businessRelabelStatus(next));
|
||||||
order.setUpdatedAt(now);
|
order.setUpdatedAt(now);
|
||||||
orders.updateById(order);
|
orders.updateById(order);
|
||||||
@@ -794,7 +802,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
applications.updateById(application);
|
applications.updateById(application);
|
||||||
for (LastlegOrder order : tailOrders(application)) {
|
for (LastlegOrder order : tailOrders(application)) {
|
||||||
order.setRelabelStatus(PENDING_REVIEW);
|
order.setRelabelStatus(PENDING_REVIEW);
|
||||||
order.setSubOrderStatus("RELABEL:" + PENDING_REVIEW);
|
if (!"RETURN_RELABEL".equals(order.getOrderType())) order.setSubOrderStatus("RELABEL:" + PENDING_REVIEW);
|
||||||
order.setUpdatedAt(now);
|
order.setUpdatedAt(now);
|
||||||
orders.updateById(order);
|
orders.updateById(order);
|
||||||
}
|
}
|
||||||
@@ -813,7 +821,8 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
application.setUpdatedAt(LocalDateTime.now()); applications.updateById(application);
|
application.setUpdatedAt(LocalDateTime.now()); applications.updateById(application);
|
||||||
for (LastlegOrder order : tailOrders(application)) {
|
for (LastlegOrder order : tailOrders(application)) {
|
||||||
if ("RETURN_RELABEL".equals(order.getOrderType()) && !Objects.equals(order.getRelabelOrderNo(), application.getRelabelNo())) continue;
|
if ("RETURN_RELABEL".equals(order.getOrderType()) && !Objects.equals(order.getRelabelOrderNo(), application.getRelabelNo())) continue;
|
||||||
order.setRelabelStatus("PROCESSING"); order.setSubOrderStatus("RELABEL:PROCESSING");
|
order.setRelabelStatus("PROCESSING");
|
||||||
|
if (!"RETURN_RELABEL".equals(order.getOrderType())) order.setSubOrderStatus("RELABEL:PROCESSING");
|
||||||
order.setUpdatedAt(LocalDateTime.now()); orders.updateById(order);
|
order.setUpdatedAt(LocalDateTime.now()); orders.updateById(order);
|
||||||
}
|
}
|
||||||
insertHistory(customerId, application.getId(), before, "PROCESSING", operatorId, remark, LocalDateTime.now());
|
insertHistory(customerId, application.getId(), before, "PROCESSING", operatorId, remark, LocalDateTime.now());
|
||||||
@@ -930,9 +939,9 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
String orderBefore = order.getCurrentStatus();
|
String orderBefore = order.getCurrentStatus();
|
||||||
// 换标确认时货物已经完成入库,不得退回最初建单的“待分配”。
|
// 换标确认时货物已经完成入库,不得退回最初建单的“待分配”。
|
||||||
// 独立换标先进入待出库;仓储转运会在下方按目的仓进一步收敛为待排车或待确认目的仓。
|
// 独立换标先进入待出库;仓储转运会在下方按目的仓进一步收敛为待排车或待确认目的仓。
|
||||||
order.setCurrentStatus("RETURN_RELABEL".equals(order.getOrderType()) ? "INBOUND_COMPLETED"
|
order.setCurrentStatus("RETURN_RELABEL".equals(order.getOrderType()) ? orderBefore
|
||||||
: requiresOutbound ? "WAIT_OUTBOUND" : "COMPLETED");
|
: requiresOutbound ? "WAIT_OUTBOUND" : "COMPLETED");
|
||||||
order.setSubOrderStatus("RELABEL:" + application.getFulfillmentStatus());
|
if (!"RETURN_RELABEL".equals(order.getOrderType())) order.setSubOrderStatus("RELABEL:" + application.getFulfillmentStatus());
|
||||||
order.setRelabelStatus("COMPLETED");
|
order.setRelabelStatus("COMPLETED");
|
||||||
order.setUpdatedAt(now);
|
order.setUpdatedAt(now);
|
||||||
orders.updateById(order);
|
orders.updateById(order);
|
||||||
@@ -1235,7 +1244,6 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
if (existingReturn != null) {
|
if (existingReturn != null) {
|
||||||
existingReturn.setRelabelOrderNo(relabelNo); existingReturn.setRelabelStatus(PENDING_REVIEW);
|
existingReturn.setRelabelOrderNo(relabelNo); existingReturn.setRelabelStatus(PENDING_REVIEW);
|
||||||
existingReturn.setSubOrderStatus("RETURN_RELABEL:" + PENDING_REVIEW); existingReturn.setPackageCount(packageCount);
|
|
||||||
existingReturn.setUpdatedAt(now); orders.updateById(existingReturn); return existingReturn;
|
existingReturn.setUpdatedAt(now); orders.updateById(existingReturn); return existingReturn;
|
||||||
}
|
}
|
||||||
LastlegOrder order = new LastlegOrder();
|
LastlegOrder order = new LastlegOrder();
|
||||||
@@ -1265,7 +1273,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
|||||||
// A linked warehouse-transfer relabel can cover only part of the
|
// A linked warehouse-transfer relabel can cover only part of the
|
||||||
// transfer. Keep the authoritative whole-order package count so later
|
// transfer. Keep the authoritative whole-order package count so later
|
||||||
// dispatches can attribute the remaining cartons correctly.
|
// dispatches can attribute the remaining cartons correctly.
|
||||||
if (!"WAREHOUSE_TRANSFER".equals(order.getOrderType())) order.setPackageCount(packageCount);
|
if (!"WAREHOUSE_TRANSFER".equals(order.getOrderType()) && !"RETURN_RELABEL".equals(order.getOrderType())) order.setPackageCount(packageCount);
|
||||||
order.setUpdatedAt(now); orders.updateById(order);
|
order.setUpdatedAt(now); orders.updateById(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -10,6 +10,8 @@ import java.util.List;
|
|||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ReturnInventoryCartonMapper extends BaseMapper<ReturnInventoryCarton> {
|
public interface ReturnInventoryCartonMapper extends BaseMapper<ReturnInventoryCarton> {
|
||||||
|
@Select("SELECT EXISTS(SELECT 1 FROM ll_return_inventory_outbound_movement WHERE carton_id=#{cartonId} AND quantity>0)")
|
||||||
|
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}")
|
||||||
int clearRelabel(@Param("id") long id);
|
int clearRelabel(@Param("id") long id);
|
||||||
@Select("SELECT * FROM ll_return_inventory_carton WHERE source_receipt_id=#{receiptId} ORDER BY id FOR UPDATE")
|
@Select("SELECT * FROM ll_return_inventory_carton WHERE source_receipt_id=#{receiptId} ORDER BY id FOR UPDATE")
|
||||||
@@ -40,7 +42,7 @@ public interface ReturnInventoryCartonMapper extends BaseMapper<ReturnInventoryC
|
|||||||
"ORDER BY created_at DESC,id DESC LIMIT 500</script>")
|
"ORDER BY created_at DESC,id DESC LIMIT 500</script>")
|
||||||
List<ReturnInventoryCarton> findAvailable(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId);
|
List<ReturnInventoryCarton> findAvailable(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId);
|
||||||
@Select("SELECT * FROM ll_return_inventory_carton WHERE customer_id=#{customerId} AND overseas_warehouse_id=#{warehouseId} " +
|
@Select("SELECT * FROM ll_return_inventory_carton WHERE customer_id=#{customerId} AND overseas_warehouse_id=#{warehouseId} " +
|
||||||
"AND count_status='CONFIRMED' AND status IN ('AVAILABLE','RELABEL_PENDING','PACKING_RESERVED') ORDER BY created_at DESC,id DESC LIMIT 500")
|
"AND count_status='CONFIRMED' AND status IN ('AVAILABLE','RELABEL_PENDING','PACKING_RESERVED','MERGED','OUTBOUNDED','OUTBOUND_PENDING','DISCARD_PENDING') ORDER BY created_at DESC,id DESC LIMIT 500")
|
||||||
List<ReturnInventoryCarton> findRelabelCandidates(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId);
|
List<ReturnInventoryCarton> findRelabelCandidates(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId);
|
||||||
@Select("SELECT * FROM ll_return_inventory_carton WHERE carton_mark=#{cartonMark} LIMIT 1")
|
@Select("SELECT * FROM ll_return_inventory_carton WHERE carton_mark=#{cartonMark} LIMIT 1")
|
||||||
ReturnInventoryCarton findMark(@Param("cartonMark") String cartonMark);
|
ReturnInventoryCarton findMark(@Param("cartonMark") String cartonMark);
|
||||||
|
|||||||
+1
-2
@@ -444,9 +444,8 @@ public class ReturnReceiptService {
|
|||||||
// Do not create a second independent tail order or overwrite the inbound status.
|
// Do not create a second independent tail order or overwrite the inbound status.
|
||||||
tail.setRelabelOrderNo(relabelNo);
|
tail.setRelabelOrderNo(relabelNo);
|
||||||
tail.setRelabelStatus("PENDING_REVIEW");
|
tail.setRelabelStatus("PENDING_REVIEW");
|
||||||
tail.setSubOrderStatus("RETURN_RELABEL:PENDING_REVIEW");
|
|
||||||
tail.setCurrentStatus("RETURN_RELABEL".equals(tail.getOrderType()) ? "INBOUND_COMPLETED" : tail.getCurrentStatus());
|
|
||||||
if (!"RETURN_RELABEL".equals(tail.getOrderType())) {
|
if (!"RETURN_RELABEL".equals(tail.getOrderType())) {
|
||||||
|
tail.setSubOrderStatus("RETURN_RELABEL:PENDING_REVIEW");
|
||||||
tail.setPackageCount(Math.toIntExact(totalQuantity));
|
tail.setPackageCount(Math.toIntExact(totalQuantity));
|
||||||
tail.setProductQuantity(Math.toIntExact(totalQuantity));
|
tail.setProductQuantity(Math.toIntExact(totalQuantity));
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-1
@@ -66,6 +66,26 @@ import static org.mockito.Mockito.times;
|
|||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class RelabelApplicationServiceTest {
|
class RelabelApplicationServiceTest {
|
||||||
|
@Test void returnInventoryCandidateUsesRealProductNames() {
|
||||||
|
var cartonMapper = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper.class);
|
||||||
|
var lineMapper = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||||
|
ReflectionTestUtils.setField(service, "returnCartons", cartonMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "returnCartonItems", lineMapper);
|
||||||
|
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.setSourceUnitType("BOX"); carton.setTotalQuantity(4L);
|
||||||
|
var first = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem();
|
||||||
|
first.setSkuCode("A"); first.setProductNameZh(" 中文商品甲 "); first.setQuantity(2L);
|
||||||
|
var second = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCartonItem();
|
||||||
|
second.setSkuCode("B"); second.setProductNameZh("中文商品乙"); second.setQuantity(2L);
|
||||||
|
when(cartonMapper.findRelabelCandidates("C", "W")).thenReturn(List.of(carton));
|
||||||
|
when(lineMapper.listCarton(20L)).thenReturn(List.of(first, second));
|
||||||
|
var candidate = service.candidates("C", "W", null).get(0);
|
||||||
|
assertEquals("中文商品甲、中文商品乙", candidate.productNameZh());
|
||||||
|
assertEquals(4L, candidate.stockQuantity());
|
||||||
|
assertEquals("AVAILABLE", candidate.inventoryStatus());
|
||||||
|
assertEquals(List.of(), candidate.mergedIntoCartonMarks());
|
||||||
|
}
|
||||||
@Mock RelabelApplicationMapper applications;
|
@Mock RelabelApplicationMapper applications;
|
||||||
@Mock RelabelItemMapper items;
|
@Mock RelabelItemMapper items;
|
||||||
@Mock RelabelStatusHistoryMapper histories;
|
@Mock RelabelStatusHistoryMapper histories;
|
||||||
@@ -554,9 +574,11 @@ class RelabelApplicationServiceTest {
|
|||||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", RelabelApplicationService.READY_FOR_OUTBOUND);
|
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", RelabelApplicationService.READY_FOR_OUTBOUND);
|
||||||
application.setLastlegOrderId(21L);
|
application.setLastlegOrderId(21L);
|
||||||
application.setFulfillmentStatus("PRE_DISPATCH_CREATED");
|
application.setFulfillmentStatus("PRE_DISPATCH_CREATED");
|
||||||
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-1", "PROCESSING");
|
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-1", "INBOUND_COMPLETED");
|
||||||
order.setOrderType("RETURN_RELABEL");
|
order.setOrderType("RETURN_RELABEL");
|
||||||
order.setRelabelOrderNo("FBR-1");
|
order.setRelabelOrderNo("FBR-1");
|
||||||
|
order.setPackageCount(7); order.setProductQuantity(110);
|
||||||
|
order.setSubOrderStatus("RETURN_RELABEL:COMPLETED");
|
||||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||||
stubDetail(application);
|
stubDetail(application);
|
||||||
when(orders.selectById(21L)).thenReturn(order);
|
when(orders.selectById(21L)).thenReturn(order);
|
||||||
@@ -568,6 +590,31 @@ class RelabelApplicationServiceTest {
|
|||||||
assertEquals("OUTBOUNDED", application.getFulfillmentStatus());
|
assertEquals("OUTBOUNDED", application.getFulfillmentStatus());
|
||||||
assertEquals("INBOUND_COMPLETED", order.getCurrentStatus());
|
assertEquals("INBOUND_COMPLETED", order.getCurrentStatus());
|
||||||
assertEquals("COMPLETED", order.getRelabelStatus());
|
assertEquals("COMPLETED", order.getRelabelStatus());
|
||||||
|
assertEquals(7, order.getPackageCount());
|
||||||
|
assertEquals(110, order.getProductQuantity());
|
||||||
|
assertEquals("RETURN_RELABEL:COMPLETED", order.getSubOrderStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void partialReturnRelabelCreationPreservesParentReceiptQuantitiesAndStatus() {
|
||||||
|
LastlegOrder parent = order(21L, "10001", "RT-ORIGINAL", "INBOUND_COMPLETED");
|
||||||
|
parent.setOrderType("RETURN_RELABEL"); parent.setSourceOrderNo("RT-ORIGINAL");
|
||||||
|
parent.setPackageCount(7); parent.setReceivedPackageCount(7); parent.setProductQuantity(110);
|
||||||
|
parent.setSubOrderStatus("RETURN_RELABEL:COMPLETED");
|
||||||
|
when(orders.selectById(21L)).thenReturn(parent);
|
||||||
|
ReflectionTestUtils.invokeMethod(service, "linkExistingTailOrder", "10001", 21L, "RL-PARTIAL", 1, LocalDateTime.now());
|
||||||
|
assertEquals(7, parent.getPackageCount()); assertEquals(7, parent.getReceivedPackageCount());
|
||||||
|
assertEquals(110, parent.getProductQuantity());
|
||||||
|
assertEquals("RT-ORIGINAL", parent.getSourceOrderNo());
|
||||||
|
assertEquals("INBOUND_COMPLETED", parent.getCurrentStatus());
|
||||||
|
assertEquals("RETURN_RELABEL:COMPLETED", parent.getSubOrderStatus());
|
||||||
|
assertEquals("RL-PARTIAL", parent.getRelabelOrderNo());
|
||||||
|
when(orders.selectOne(any())).thenReturn(parent);
|
||||||
|
var request = org.mockito.Mockito.mock(RelabelRequests.Create.class);
|
||||||
|
when(request.customerOrderNo()).thenReturn("RT-ORIGINAL");
|
||||||
|
ReflectionTestUtils.invokeMethod(service, "insertTailOrder", "10001", request, "hash", "RL-SECOND", "W", 1, LocalDateTime.now());
|
||||||
|
assertEquals(7, parent.getPackageCount()); assertEquals(110, parent.getProductQuantity());
|
||||||
|
assertEquals("RETURN_RELABEL:COMPLETED", parent.getSubOrderStatus());
|
||||||
|
assertEquals("RL-SECOND", parent.getRelabelOrderNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1013,6 +1060,7 @@ class RelabelApplicationServiceTest {
|
|||||||
var lineMapper = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
var lineMapper = org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper.class);
|
||||||
ReflectionTestUtils.setField(service, "returnCartons", cartonMapper);
|
ReflectionTestUtils.setField(service, "returnCartons", cartonMapper);
|
||||||
ReflectionTestUtils.setField(service, "returnCartonItems", lineMapper);
|
ReflectionTestUtils.setField(service, "returnCartonItems", lineMapper);
|
||||||
|
ReflectionTestUtils.setField(service, "returnPackingTraces", org.mockito.Mockito.mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper.class));
|
||||||
var carton = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton();
|
var carton = new com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton();
|
||||||
carton.setId(901L); carton.setCustomerId("10001"); carton.setOverseasWarehouseId("WH-1");
|
carton.setId(901L); carton.setCustomerId("10001"); carton.setOverseasWarehouseId("WH-1");
|
||||||
carton.setCartonMark("RETURN-BOX-1"); carton.setStatus("AVAILABLE"); carton.setSourceUnitType("BOX");
|
carton.setCartonMark("RETURN-BOX-1"); carton.setStatus("AVAILABLE"); carton.setSourceUnitType("BOX");
|
||||||
|
|||||||
+1
@@ -28,6 +28,7 @@ class RelabelReturnCartonCandidateTest {
|
|||||||
ReflectionTestUtils.setField(service, "inventoryPositions", positions);
|
ReflectionTestUtils.setField(service, "inventoryPositions", positions);
|
||||||
ReflectionTestUtils.setField(service, "returnCartons", cartons);
|
ReflectionTestUtils.setField(service, "returnCartons", cartons);
|
||||||
ReflectionTestUtils.setField(service, "returnCartonItems", cartonItems);
|
ReflectionTestUtils.setField(service, "returnCartonItems", cartonItems);
|
||||||
|
ReflectionTestUtils.setField(service, "returnPackingTraces", mock(com.baiyue.lastleg.returnreceipt.mapper.ReturnPackingTraceMapper.class));
|
||||||
|
|
||||||
ReturnInventoryCarton carton = new ReturnInventoryCarton();
|
ReturnInventoryCarton carton = new ReturnInventoryCarton();
|
||||||
carton.setId(9001L); carton.setCustomerId("10001"); carton.setOverseasWarehouseId("WH-1");
|
carton.setId(9001L); carton.setCustomerId("10001"); carton.setOverseasWarehouseId("WH-1");
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
-- 退货近期增量汇总(2026-10-12),MySQL 8.0.16+,可重复执行。
|
||||||
|
-- 连接到需要升级的 MySQL 实例后,整份执行;目标仅 baiyue_lastleg。
|
||||||
|
-- USE 指定数据库,不指定服务器;本脚本不创建数据库,不写测试单据,不修改库存/清点事实。
|
||||||
|
-- 前提:已有尾程和退货合箱/出库基线表(不是新库初始化脚本)。
|
||||||
|
-- 汇总10月10日/12日直接退货增量,并补齐清点、事实数量及多次换标申请结构。
|
||||||
|
-- 排车车队/IAM权限为独立业务基线,不纳入本退货脚本。
|
||||||
|
-- PARCEL三类型换标、RETURN_PACK/RETURN_COMPLETION_TRANSFER历史以及清点续作均复用现有结构。
|
||||||
|
-- DDL隐式提交;执行客户端遇错须停止,不使用 --force。可重复执行不等于整个脚本事务回滚。
|
||||||
|
USE `baiyue_lastleg`;
|
||||||
|
SELECT DATABASE() AS target_database, @@hostname AS mysql_host, @@port AS mysql_port, VERSION() AS mysql_version;
|
||||||
|
|
||||||
|
-- 一、件数出库快照、部分扣件守恒、同箱分批出库索引。
|
||||||
|
-- 退货单 SKU 按件数出库快照。目标库:baiyue_lastleg。可重复执行。
|
||||||
|
USE baiyue_lastleg;
|
||||||
|
SET @return_sku_quantity_ddl = IF(
|
||||||
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||||
|
AND table_name='ll_outbound_pallet_item' AND column_name='return_sku_quantity'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE ll_outbound_pallet_item ADD COLUMN return_sku_quantity BIGINT NULL COMMENT ''退货单SKU本来源计划出库件数快照,NULL为历史整箱出库'' AFTER sku_code'
|
||||||
|
);
|
||||||
|
PREPARE return_sku_quantity_stmt FROM @return_sku_quantity_ddl;
|
||||||
|
EXECUTE return_sku_quantity_stmt;
|
||||||
|
DEALLOCATE PREPARE return_sku_quantity_stmt;
|
||||||
|
|
||||||
|
-- 部分出库仅扣件数;整箱扣一箱,保留守恒和非负约束。
|
||||||
|
SET @return_sku_check_ddl = IF(EXISTS(SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='ll_return_inventory_outbound_movement' AND constraint_name='chk_ll_return_outbound_movement_delta'), 'ALTER TABLE ll_return_inventory_outbound_movement DROP CHECK chk_ll_return_outbound_movement_delta, ADD CONSTRAINT chk_ll_return_outbound_movement_delta CHECK(quantity>0 AND total_after>=0 AND available_after>=0 AND total_before-total_after=quantity AND available_before-available_after=quantity AND cartons_after>=0 AND cartons_before-cartons_after IN (0,1) AND total_after>=available_after)', 'ALTER TABLE ll_return_inventory_outbound_movement ADD CONSTRAINT chk_ll_return_outbound_movement_delta CHECK(quantity>0 AND total_after>=0 AND available_after>=0 AND total_before-total_after=quantity AND available_before-available_after=quantity AND cartons_after>=0 AND cartons_before-cartons_after IN (0,1) AND total_after>=available_after)');
|
||||||
|
PREPARE return_sku_check_stmt FROM @return_sku_check_ddl;
|
||||||
|
EXECUTE return_sku_check_stmt;
|
||||||
|
DEALLOCATE PREPARE return_sku_check_stmt;
|
||||||
|
|
||||||
|
-- 二、标签状态(旧记录保留NULL,应用按既有兼容规则处理)。
|
||||||
|
SET @ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='label_status'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN label_status VARCHAR(32) NULL COMMENT ''WAIT_LABEL待贴标 CONFIRMED已扫码确认标签''');
|
||||||
|
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 三、清点结构;不将历史已签收箱自动回退为待清点。
|
||||||
|
-- 退货托盘签收箱内清点;MySQL 8;仅在目标尾程库执行,可重复运行。
|
||||||
|
USE baiyue_lastleg;
|
||||||
|
SET @rc_count_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='count_status'),'SELECT 1','ALTER TABLE ll_return_inventory_carton ADD COLUMN count_status VARCHAR(16) NOT NULL DEFAULT ''CONFIRMED'' COMMENT ''箱内清点状态 PENDING待清点 CONFIRMED已确认''');
|
||||||
|
PREPARE rc_count_stmt FROM @rc_count_ddl; EXECUTE rc_count_stmt; DEALLOCATE PREPARE rc_count_stmt;
|
||||||
|
SET @rc_count_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='counted_at'),'SELECT 1','ALTER TABLE ll_return_inventory_carton ADD COLUMN counted_at DATETIME(3) NULL COMMENT ''箱内清点确认时间''');
|
||||||
|
PREPARE rc_count_stmt FROM @rc_count_ddl; EXECUTE rc_count_stmt; DEALLOCATE PREPARE rc_count_stmt;
|
||||||
|
SET @rc_count_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='counted_by'),'SELECT 1','ALTER TABLE ll_return_inventory_carton ADD COLUMN counted_by VARCHAR(128) NULL COMMENT ''箱内清点确认操作人''');
|
||||||
|
PREPARE rc_count_stmt FROM @rc_count_ddl; EXECUTE rc_count_stmt; DEALLOCATE PREPARE rc_count_stmt;
|
||||||
|
SET @rc_count_ddl = IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND index_name='idx_ll_return_count'),'SELECT 1','CREATE INDEX idx_ll_return_count ON ll_return_inventory_carton(overseas_warehouse_id,count_status,status,source_receipt_id)');
|
||||||
|
PREPARE rc_count_stmt FROM @rc_count_ddl; EXECUTE rc_count_stmt; DEALLOCATE PREPARE rc_count_stmt;
|
||||||
|
|
||||||
|
-- MySQL 8,本地尾程库,可重复执行;不修改现有清点数量或自动释放未完成任务。
|
||||||
|
USE baiyue_lastleg;
|
||||||
|
-- 退货箱内清点作业会话:每位操作员只能认领一个批次,每个批次只能被一人认领。
|
||||||
|
CREATE TABLE IF NOT EXISTS ll_return_carton_count_session (
|
||||||
|
operator_id VARCHAR(128) NOT NULL COMMENT '平台PDA操作员标识',
|
||||||
|
warehouse_id VARCHAR(128) NOT NULL COMMENT '认领时绑定海外仓标识',
|
||||||
|
receipt_id BIGINT NULL COMMENT '正在清点的签收批次,完成后释放为NULL',
|
||||||
|
started_at DATETIME(3) NULL COMMENT '本次开始清点时间',
|
||||||
|
created_at DATETIME(3) NOT NULL COMMENT '会话创建时间',
|
||||||
|
updated_at DATETIME(3) NOT NULL COMMENT '会话更新时间',
|
||||||
|
PRIMARY KEY(operator_id),
|
||||||
|
UNIQUE KEY uk_ll_return_count_session_receipt(receipt_id)
|
||||||
|
) ENGINE=InnoDB COMMENT='退货箱内清点单人单任务持久会话';
|
||||||
|
|
||||||
|
-- 四、换标事实数量与一单多申请;保留幂等唯一键。
|
||||||
|
SET @ddl = (
|
||||||
|
SELECT IF(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'll_relabel_pda_unit_fact'
|
||||||
|
AND column_name = 'quantity'
|
||||||
|
),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE ll_relabel_pda_unit_fact ADD COLUMN quantity INT NOT NULL DEFAULT 1 COMMENT ''本次事实处理件数,箱级作业固定为1'' AFTER reason'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PREPARE ll_return_bulk_relabel_quantity_stmt FROM @ddl;
|
||||||
|
EXECUTE ll_return_bulk_relabel_quantity_stmt;
|
||||||
|
DEALLOCATE PREPARE ll_return_bulk_relabel_quantity_stmt;
|
||||||
|
|
||||||
|
|
||||||
|
SET @relabel_has_unique = (SELECT COUNT(*) FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'll_relabel_application'
|
||||||
|
AND index_name = 'uk_ll_relabel_application_order');
|
||||||
|
SET @relabel_has_index = (SELECT COUNT(*) FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'll_relabel_application'
|
||||||
|
AND index_name = 'idx_ll_relabel_application_order');
|
||||||
|
SET @relabel_index_sql = CASE
|
||||||
|
WHEN @relabel_has_unique > 0 AND @relabel_has_index = 0 THEN
|
||||||
|
'ALTER TABLE ll_relabel_application DROP INDEX uk_ll_relabel_application_order, ADD INDEX idx_ll_relabel_application_order (lastleg_order_id)'
|
||||||
|
WHEN @relabel_has_unique > 0 THEN
|
||||||
|
'ALTER TABLE ll_relabel_application DROP INDEX uk_ll_relabel_application_order'
|
||||||
|
WHEN @relabel_has_index = 0 THEN
|
||||||
|
'ALTER TABLE ll_relabel_application ADD INDEX idx_ll_relabel_application_order (lastleg_order_id)'
|
||||||
|
ELSE 'SELECT 1' END;
|
||||||
|
PREPARE relabel_index_migration FROM @relabel_index_sql;
|
||||||
|
EXECUTE relabel_index_migration;
|
||||||
|
DEALLOCATE PREPARE relabel_index_migration;
|
||||||
|
|
||||||
|
-- 同箱分批索引:同时已有旧/新索引或旧索引已删除时也可执行。
|
||||||
|
SET @return_index_ddl = IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_outbound_movement' AND index_name='uk_ll_return_outbound_carton_sku'), 'ALTER TABLE ll_return_inventory_outbound_movement DROP INDEX uk_ll_return_outbound_carton_sku', 'SELECT 1');
|
||||||
|
PREPARE return_index_stmt FROM @return_index_ddl; EXECUTE return_index_stmt; DEALLOCATE PREPARE return_index_stmt;
|
||||||
|
SET @return_index_ddl = IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_outbound_movement' AND index_name='idx_ll_return_outbound_carton_sku'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_outbound_movement ADD INDEX idx_ll_return_outbound_carton_sku(customer_id,carton_id,sku_code)');
|
||||||
|
PREPARE return_index_stmt FROM @return_index_ddl; EXECUTE return_index_stmt; DEALLOCATE PREPARE return_index_stmt;
|
||||||
|
|
||||||
|
-- 校验输出:核对新增字段、索引和守恒约束。
|
||||||
|
SELECT table_name,column_name,column_type,is_nullable,column_default FROM information_schema.columns
|
||||||
|
WHERE table_schema=DATABASE() AND (
|
||||||
|
(table_name='ll_outbound_pallet_item' AND column_name='return_sku_quantity') OR
|
||||||
|
(table_name='ll_return_inventory_carton' AND column_name IN('label_status','count_status','counted_at','counted_by')) OR
|
||||||
|
(table_name='ll_relabel_pda_unit_fact' AND column_name='quantity')) ORDER BY table_name,column_name;
|
||||||
|
SELECT table_name,index_name,non_unique,GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns
|
||||||
|
FROM information_schema.statistics WHERE table_schema=DATABASE() AND index_name IN
|
||||||
|
('idx_ll_return_outbound_carton_sku','uk_ll_return_outbound_pick_sku','idx_ll_relabel_application_order','uk_ll_return_count_session_receipt','idx_ll_return_count') GROUP BY table_name,index_name,non_unique;
|
||||||
|
SELECT constraint_name,check_clause FROM information_schema.check_constraints WHERE constraint_schema=DATABASE() AND constraint_name='chk_ll_return_outbound_movement_delta';
|
||||||
Reference in New Issue
Block a user