fix(lastleg): align outbound pallet workflows
This commit is contained in:
+7
@@ -34,6 +34,13 @@ public class OutboundPdaController {
|
|||||||
return ApiResponse.success(service.pdaTasks(String.valueOf(identity.overseasWarehouseId()), status));
|
return ApiResponse.success(service.pdaTasks(String.valueOf(identity.overseasWarehouseId()), status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/resolve/{businessNo}")
|
||||||
|
public ApiResponse<DeliveryResponses.PdaTaskResolution> resolve(
|
||||||
|
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String businessNo) {
|
||||||
|
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||||
|
return ApiResponse.success(service.resolvePdaTask(String.valueOf(identity.overseasWarehouseId()), businessNo));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{taskNo}")
|
@GetMapping("/{taskNo}")
|
||||||
public ApiResponse<DeliveryResponses.PdaTaskDetail> detail(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<DeliveryResponses.PdaTaskDetail> detail(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@PathVariable String taskNo) {
|
@PathVariable String taskNo) {
|
||||||
|
|||||||
+5
-1
@@ -40,8 +40,12 @@ public final class DeliveryResponses {
|
|||||||
public record DeliveryDetail(DeliveryRow order, List<DeliverySku> skus,
|
public record DeliveryDetail(DeliveryRow order, List<DeliverySku> skus,
|
||||||
List<DeliveryFeeRow> fees, List<DeliveryOperationRow> operations) {}
|
List<DeliveryFeeRow> fees, List<DeliveryOperationRow> operations) {}
|
||||||
|
|
||||||
public record OutboundDetail(OutboundRow outbound, DeliveryRow delivery) {}
|
/** palletNo is the warehouse-authoritative physical pallet code after inbound assignment. */
|
||||||
|
public record OutboundPalletRow(String palletNo, String inboundCode, String destinationWarehouseCode,
|
||||||
|
String destinationWarehouseName, String skuCode, int packageCount) {}
|
||||||
|
public record OutboundDetail(OutboundRow outbound, List<OutboundPalletRow> pallets, DeliveryRow delivery) {}
|
||||||
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
|
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
|
||||||
|
public record PdaTaskResolution(String workflow, String taskNo, String outboundNo, String dispatchNo) {}
|
||||||
public record PdaPickPosition(Long inventoryPositionId, String palletNo, String cartonMark,
|
public record PdaPickPosition(Long inventoryPositionId, String palletNo, String cartonMark,
|
||||||
String skuCode, String inboundCode, long availablePackages,
|
String skuCode, String inboundCode, long availablePackages,
|
||||||
long pickedPackages) {}
|
long pickedPackages) {}
|
||||||
|
|||||||
+6
@@ -23,6 +23,12 @@ public interface OutboundPdaTaskMapper extends BaseMapper<OutboundPdaTask> {
|
|||||||
"AND o.customer_id=t.customer_id WHERE t.task_no=#{taskNo} AND o.overseas_warehouse_id=#{warehouseId} LIMIT 1")
|
"AND o.customer_id=t.customer_id WHERE t.task_no=#{taskNo} AND o.overseas_warehouse_id=#{warehouseId} LIMIT 1")
|
||||||
OutboundPdaTask findByNoAndWarehouse(@Param("taskNo") String taskNo, @Param("warehouseId") String warehouseId);
|
OutboundPdaTask findByNoAndWarehouse(@Param("taskNo") String taskNo, @Param("warehouseId") String warehouseId);
|
||||||
|
|
||||||
|
@Select("SELECT t.* FROM ll_outbound_pda_task t JOIN ll_outbound_order o ON o.id=t.outbound_order_id " +
|
||||||
|
"AND o.customer_id=t.customer_id WHERE t.customer_id=#{customerId} AND t.outbound_order_id=#{outboundOrderId} " +
|
||||||
|
"AND o.overseas_warehouse_id=#{warehouseId} LIMIT 1")
|
||||||
|
OutboundPdaTask findByOutboundAndWarehouse(@Param("customerId") String customerId,
|
||||||
|
@Param("outboundOrderId") Long outboundOrderId, @Param("warehouseId") String warehouseId);
|
||||||
|
|
||||||
@Select("SELECT t.* FROM ll_outbound_pda_task t JOIN ll_outbound_order o ON o.id=t.outbound_order_id " +
|
@Select("SELECT t.* FROM ll_outbound_pda_task t JOIN ll_outbound_order o ON o.id=t.outbound_order_id " +
|
||||||
"AND o.customer_id=t.customer_id WHERE o.overseas_warehouse_id=#{warehouseId} " +
|
"AND o.customer_id=t.customer_id WHERE o.overseas_warehouse_id=#{warehouseId} " +
|
||||||
"AND (#{status} IS NULL OR t.status=#{status}) ORDER BY t.pushed_at DESC LIMIT 200")
|
"AND (#{status} IS NULL OR t.status=#{status}) ORDER BY t.pushed_at DESC LIMIT 200")
|
||||||
|
|||||||
+42
-1
@@ -409,6 +409,36 @@ public class DeliveryTransferService {
|
|||||||
return new DeliveryResponses.PdaTaskDetail(taskResult(outbound, task), items);
|
return new DeliveryResponses.PdaTaskDetail(taskResult(outbound, task), items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeliveryResponses.PdaTaskResolution resolvePdaTask(String warehouseId, String businessNo) {
|
||||||
|
String boundWarehouseId = required(warehouseId, "PDA warehouse binding is required");
|
||||||
|
String code = required(businessNo, "Outbound task, outbound or dispatch number is required");
|
||||||
|
|
||||||
|
OutboundPdaTask task = pdaTasks.findByNoAndWarehouse(code, boundWarehouseId);
|
||||||
|
if (task != null) {
|
||||||
|
OutboundOrder outbound = requireOutboundInWarehouse(task, boundWarehouseId);
|
||||||
|
return outboundResolution(outbound, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutboundOrder outbound = outboundOrders.findGlobalByNo(code);
|
||||||
|
if (outbound != null && boundWarehouseId.equals(outbound.getOverseasWarehouseId())) {
|
||||||
|
if ("DISPATCH_DELIVERY".equals(outbound.getOutboundType()) && StringUtils.hasText(outbound.getDispatchNo())) {
|
||||||
|
DispatchOrder dispatch = dispatchOrders.findGlobalByDispatchNo(outbound.getDispatchNo());
|
||||||
|
if (dispatch != null && boundWarehouseId.equals(dispatch.getOverseasWarehouseId())) {
|
||||||
|
return new DeliveryResponses.PdaTaskResolution("DISPATCH", null, outbound.getOutboundNo(),
|
||||||
|
dispatch.getDispatchNo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task = pdaTasks.findByOutboundAndWarehouse(outbound.getCustomerId(), outbound.getId(), boundWarehouseId);
|
||||||
|
if (task != null) return outboundResolution(outbound, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchOrder dispatch = dispatchOrders.findGlobalByDispatchNo(code);
|
||||||
|
if (dispatch != null && boundWarehouseId.equals(dispatch.getOverseasWarehouseId())) {
|
||||||
|
return new DeliveryResponses.PdaTaskResolution("DISPATCH", null, null, dispatch.getDispatchNo());
|
||||||
|
}
|
||||||
|
throw notFound("Outbound or dispatch task not found in the bound warehouse");
|
||||||
|
}
|
||||||
|
|
||||||
private List<DeliveryResponses.PdaPickPosition> pdaPickPositions(String customerId, String warehouseId,
|
private List<DeliveryResponses.PdaPickPosition> pdaPickPositions(String customerId, String warehouseId,
|
||||||
OutboundPalletItem item) {
|
OutboundPalletItem item) {
|
||||||
if (item.getWarehousePalletId() == null) return List.of();
|
if (item.getWarehousePalletId() == null) return List.of();
|
||||||
@@ -448,6 +478,10 @@ public class DeliveryTransferService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DeliveryResponses.PdaTask taskResult(OutboundOrder outbound, OutboundPdaTask task) { return new DeliveryResponses.PdaTask(task.getId(), task.getTaskNo(), task.getStatus(), outbound.getOutboundNo(), task.getPushedAt(), task.getCompletedAt()); }
|
private DeliveryResponses.PdaTask taskResult(OutboundOrder outbound, OutboundPdaTask task) { return new DeliveryResponses.PdaTask(task.getId(), task.getTaskNo(), task.getStatus(), outbound.getOutboundNo(), task.getPushedAt(), task.getCompletedAt()); }
|
||||||
|
private DeliveryResponses.PdaTaskResolution outboundResolution(OutboundOrder outbound, OutboundPdaTask task) {
|
||||||
|
return new DeliveryResponses.PdaTaskResolution("OUTBOUND", task.getTaskNo(), outbound.getOutboundNo(),
|
||||||
|
outbound.getDispatchNo());
|
||||||
|
}
|
||||||
private OutboundOrder requireOutbound(OutboundPdaTask task) {
|
private OutboundOrder requireOutbound(OutboundPdaTask task) {
|
||||||
OutboundOrder outbound = outboundOrders.selectById(task.getOutboundOrderId());
|
OutboundOrder outbound = outboundOrders.selectById(task.getOutboundOrderId());
|
||||||
if (outbound == null || !task.getCustomerId().equals(outbound.getCustomerId())) {
|
if (outbound == null || !task.getCustomerId().equals(outbound.getCustomerId())) {
|
||||||
@@ -481,7 +515,9 @@ public class DeliveryTransferService {
|
|||||||
if (outbound == null) throw notFound("Outbound order not found");
|
if (outbound == null) throw notFound("Outbound order not found");
|
||||||
String customerId = outbound.getCustomerId();
|
String customerId = outbound.getCustomerId();
|
||||||
DeliveryOrder delivery = requireDelivery(customerId, outbound.getId());
|
DeliveryOrder delivery = requireDelivery(customerId, outbound.getId());
|
||||||
return new DeliveryResponses.OutboundDetail(outboundRow(outbound), deliveryRow(delivery));
|
List<DeliveryResponses.OutboundPalletRow> pallets = outboundPallets
|
||||||
|
.findByOutbound(customerId, outbound.getId()).stream().map(this::outboundPalletRow).toList();
|
||||||
|
return new DeliveryResponses.OutboundDetail(outboundRow(outbound), pallets, deliveryRow(delivery));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageResult<DeliveryResponses.DeliveryRow> deliveryPage(String customerId, DeliveryRequests.Query query) {
|
public PageResult<DeliveryResponses.DeliveryRow> deliveryPage(String customerId, DeliveryRequests.Query query) {
|
||||||
@@ -556,6 +592,11 @@ public class DeliveryTransferService {
|
|||||||
d.getDeliveryNo(), d.getStatus(), o.getPalletCount(), o.getPackageCount(), replayed);
|
d.getDeliveryNo(), d.getStatus(), o.getPalletCount(), o.getPackageCount(), replayed);
|
||||||
}
|
}
|
||||||
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt()); }
|
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt()); }
|
||||||
|
private DeliveryResponses.OutboundPalletRow outboundPalletRow(OutboundPalletItem item) {
|
||||||
|
return new DeliveryResponses.OutboundPalletRow(item.getPalletNo(), item.getInboundCode(),
|
||||||
|
item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(), item.getSkuCode(),
|
||||||
|
value(item.getPackageCount()));
|
||||||
|
}
|
||||||
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt()); }
|
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt()); }
|
||||||
private DeliveryResponses.DeliverySku sku(LastlegOrderDetail d, LastlegOrder order) { return new DeliveryResponses.DeliverySku(order == null ? null : order.getInboundCode(), order == null ? null : order.getCustomerName(), d.getDestinationWarehouseCode(), null, d.getShipmentNo(), d.getFbaTrackingNo(), d.getSkuCode(), firstText(d.getProductNameZh(), d.getProductNameEn()), value(d.getPackageCount()), d.getGrossWeightKg(), d.getVolumeCbm()); }
|
private DeliveryResponses.DeliverySku sku(LastlegOrderDetail d, LastlegOrder order) { return new DeliveryResponses.DeliverySku(order == null ? null : order.getInboundCode(), order == null ? null : order.getCustomerName(), d.getDestinationWarehouseCode(), null, d.getShipmentNo(), d.getFbaTrackingNo(), d.getSkuCode(), firstText(d.getProductNameZh(), d.getProductNameEn()), value(d.getPackageCount()), d.getGrossWeightKg(), d.getVolumeCbm()); }
|
||||||
private DeliveryResponses.DeliveryFeeRow feeRow(DeliveryFee f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getFeeDirection(), f.getFeeType(), f.getCurrency(), f.getAmount(), f.getRemark()); }
|
private DeliveryResponses.DeliveryFeeRow feeRow(DeliveryFee f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getFeeDirection(), f.getFeeType(), f.getCurrency(), f.getAmount(), f.getRemark()); }
|
||||||
|
|||||||
+5
-1
@@ -171,13 +171,17 @@ public class InboundPdaWorkflowService {
|
|||||||
if (Objects.equals(pallet.getWarehousePalletId(), resource.palletId())) return null;
|
if (Objects.equals(pallet.getWarehousePalletId(), resource.palletId())) return null;
|
||||||
throw conflict("Destination pallet is already bound to another physical pallet");
|
throw conflict("Destination pallet is already bound to another physical pallet");
|
||||||
}
|
}
|
||||||
|
if (resource.palletNo() == null || resource.palletNo().isBlank()) {
|
||||||
|
throw conflict("Resolved physical pallet has no pallet code");
|
||||||
|
}
|
||||||
String destinationKey = destinationKey(pallet);
|
String destinationKey = destinationKey(pallet);
|
||||||
String businessId = plan.getContainerNo() + ":" + destinationKey;
|
String businessId = plan.getContainerNo() + ":" + destinationKey;
|
||||||
String reservationKey = "INBOUND_CONTAINER_DESTINATION_PALLET:" + businessId + ":" + resource.palletId();
|
String reservationKey = "INBOUND_CONTAINER_DESTINATION_PALLET:" + businessId + ":" + resource.palletId();
|
||||||
long lockId = warehouse.reserve(customerId, resource.warehouseId(), resource.palletId(), resource.locationId(),
|
long lockId = warehouse.reserve(customerId, resource.warehouseId(), resource.palletId(), resource.locationId(),
|
||||||
"INBOUND_CONTAINER_DESTINATION_PALLET", businessId, reservationKey);
|
"INBOUND_CONTAINER_DESTINATION_PALLET", businessId, reservationKey);
|
||||||
try {
|
try {
|
||||||
pallet.setWarehousePalletId(resource.palletId()); pallet.setWarehouseLocationId(resource.locationId());
|
pallet.setWarehousePalletId(resource.palletId()); pallet.setPalletNo(resource.palletNo().trim());
|
||||||
|
pallet.setWarehouseLocationId(resource.locationId());
|
||||||
pallet.setWarehouseResourceLockId(lockId); pallet.setAssignmentStatus("ASSIGNED");
|
pallet.setWarehouseResourceLockId(lockId); pallet.setAssignmentStatus("ASSIGNED");
|
||||||
pallet.setLockedAt(LocalDateTime.now()); repository.update(pallet);
|
pallet.setLockedAt(LocalDateTime.now()); repository.update(pallet);
|
||||||
return lockId;
|
return lockId;
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ paths:
|
|||||||
description: 权限 platform:overseas-warehouse:outbound:list;只按全局唯一 outboundNo 定位并从记录解析客户范围,不接收 customerId。
|
description: 权限 platform:overseas-warehouse:outbound:list;只按全局唯一 outboundNo 定位并从记录解析客户范围,不接收 customerId。
|
||||||
security: [{ bearerAuth: [] }]
|
security: [{ bearerAuth: [] }]
|
||||||
parameters: [{ name: outboundNo, in: path, required: true, schema: { type: string } }]
|
parameters: [{ name: outboundNo, in: path, required: true, schema: { type: string } }]
|
||||||
responses: { '200': { description: 出库单及关联派送单详情 }, '404': { description: 出库单不存在 } }
|
responses: { '200': { description: 出库单、出库托盘及关联派送单详情;pallets[].palletNo 为 Warehouse 权威实物托盘码 }, '404': { description: 出库单不存在 } }
|
||||||
/api/platform/v1/last-leg/outbound-orders/{outboundNo}/cancel:
|
/api/platform/v1/last-leg/outbound-orders/{outboundNo}/cancel:
|
||||||
post:
|
post:
|
||||||
summary: 取消未执行出库单
|
summary: 取消未执行出库单
|
||||||
@@ -1537,7 +1537,7 @@ components:
|
|||||||
orderNo: { type: string }
|
orderNo: { type: string }
|
||||||
orderRefs: { type: string }
|
orderRefs: { type: string }
|
||||||
inboundCode: { type: string }
|
inboundCode: { type: string }
|
||||||
palletNo: { type: string }
|
palletNo: { type: string, description: 未入库时为计划托盘码;绑定实物托盘后为 Warehouse 权威实物托盘码 }
|
||||||
destinationWarehouseId: { type: string }
|
destinationWarehouseId: { type: string }
|
||||||
destinationWarehouseCode: { type: string }
|
destinationWarehouseCode: { type: string }
|
||||||
destinationWarehouseName: { type: string }
|
destinationWarehouseName: { type: string }
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ paths:
|
|||||||
description: 一件代发复用统一出库任务模型;不接收customerId或warehouseId,派送不进入PDA。
|
description: 一件代发复用统一出库任务模型;不接收customerId或warehouseId,派送不进入PDA。
|
||||||
parameters: [{ name: status, in: query, required: false, schema: { type: string } }]
|
parameters: [{ name: status, in: query, required: false, schema: { type: string } }]
|
||||||
responses: { '200': { description: 出库任务列表 } }
|
responses: { '200': { description: 出库任务列表 } }
|
||||||
|
/api/pda/v1/last-leg/outbound-tasks/resolve/{businessNo}:
|
||||||
|
get:
|
||||||
|
summary: 解析出库作业扫码业务号
|
||||||
|
description: 接受一件代发任务号、出库单号或排车单号;按Token绑定仓返回 OUTBOUND 的 taskNo 或 DISPATCH 的 dispatchNo,PDA据此进入正确作业页。
|
||||||
|
parameters: [{ name: businessNo, in: path, required: true, schema: { type: string } }]
|
||||||
|
responses:
|
||||||
|
'200': { description: 正确作业类型及稳定业务编号 }
|
||||||
|
'404': { description: 当前绑定仓无此出库或排车任务 }
|
||||||
/api/pda/v1/last-leg/outbound-tasks/{taskNo}:
|
/api/pda/v1/last-leg/outbound-tasks/{taskNo}:
|
||||||
get:
|
get:
|
||||||
summary: 查询一件代发出库任务详情
|
summary: 查询一件代发出库任务详情
|
||||||
@@ -569,7 +577,7 @@ components:
|
|||||||
required: [outboundPalletItemId, palletNo, skuCode, inboundCode, packageCount, pickedPackageCount, status, positions]
|
required: [outboundPalletItemId, palletNo, skuCode, inboundCode, packageCount, pickedPackageCount, status, positions]
|
||||||
properties:
|
properties:
|
||||||
outboundPalletItemId: { type: string, description: pick请求提交键,不作为用户展示编号 }
|
outboundPalletItemId: { type: string, description: pick请求提交键,不作为用户展示编号 }
|
||||||
palletNo: { type: string }
|
palletNo: { type: string, description: Warehouse 权威实物托盘码 }
|
||||||
skuCode: { type: string }
|
skuCode: { type: string }
|
||||||
inboundCode: { type: string }
|
inboundCode: { type: string }
|
||||||
packageCount: { type: integer }
|
packageCount: { type: integer }
|
||||||
@@ -582,7 +590,7 @@ components:
|
|||||||
required: [inventoryPositionId, palletNo, cartonMark, skuCode, inboundCode, availablePackages, pickedPackages]
|
required: [inventoryPositionId, palletNo, cartonMark, skuCode, inboundCode, availablePackages, pickedPackages]
|
||||||
properties:
|
properties:
|
||||||
inventoryPositionId: { type: string, description: pick请求提交键,不作为用户展示编号 }
|
inventoryPositionId: { type: string, description: pick请求提交键,不作为用户展示编号 }
|
||||||
palletNo: { type: string }
|
palletNo: { type: string, description: Warehouse 权威实物托盘码 }
|
||||||
cartonMark: { type: string, nullable: true }
|
cartonMark: { type: string, nullable: true }
|
||||||
skuCode: { type: string }
|
skuCode: { type: string }
|
||||||
inboundCode: { type: string }
|
inboundCode: { type: string }
|
||||||
|
|||||||
+1
@@ -166,6 +166,7 @@ class PlatformDispatchOutboundContractTest {
|
|||||||
for (String action : Set.of(dispatchAction, transfer, outboundDetail, outboundCancel)) {
|
for (String action : Set.of(dispatchAction, transfer, outboundDetail, outboundCancel)) {
|
||||||
assertFalse(action.contains("name: customerId"));
|
assertFalse(action.contains("name: customerId"));
|
||||||
}
|
}
|
||||||
|
assertTrue(outboundDetail.contains("pallets[].palletNo"));
|
||||||
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:edit"));
|
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:edit"));
|
||||||
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:cancel"));
|
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:cancel"));
|
||||||
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:transfer-delivery"));
|
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:transfer-delivery"));
|
||||||
|
|||||||
+58
-4
@@ -270,19 +270,26 @@ class DeliveryTransferServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void outboundDetailDoesNotExposeExcludedPalletOrOutboundFulfillmentRows() {
|
void outboundDetailIncludesPalletCodesForDisplay() {
|
||||||
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
|
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
|
||||||
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
|
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
|
||||||
|
OutboundPalletItem pallet = pallet(501L, "IP-ONT8-20260907-000001-P001", "ONT8", "Ontario", "IN-A", 12);
|
||||||
|
pallet.setSkuCode("SKU-1");
|
||||||
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
|
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
|
||||||
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
||||||
|
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(pallet));
|
||||||
|
|
||||||
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
|
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
|
||||||
|
|
||||||
assertEquals("OUT-301", detail.outbound().outboundNo());
|
assertEquals("OUT-301", detail.outbound().outboundNo());
|
||||||
assertEquals("DEL-401", detail.delivery().deliveryNo());
|
assertEquals("DEL-401", detail.delivery().deliveryNo());
|
||||||
assertTrue(java.util.Arrays.stream(DeliveryResponses.OutboundDetail.class.getRecordComponents())
|
assertEquals(1, detail.pallets().size());
|
||||||
.noneMatch(component -> "pallets".equals(component.getName())));
|
assertEquals("IP-ONT8-20260907-000001-P001", detail.pallets().get(0).palletNo());
|
||||||
verify(outboundPallets, never()).selectPage(any(Page.class), any());
|
assertEquals("IN-A", detail.pallets().get(0).inboundCode());
|
||||||
|
assertEquals("ONT8", detail.pallets().get(0).destinationWarehouseCode());
|
||||||
|
assertEquals("SKU-1", detail.pallets().get(0).skuCode());
|
||||||
|
assertEquals(12, detail.pallets().get(0).packageCount());
|
||||||
|
verify(outboundPallets).findByOutbound("TENANT-A", 301L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -426,6 +433,53 @@ class DeliveryTransferServiceTest {
|
|||||||
verify(pdaTasks).listByWarehouse("WH-1", null);
|
verify(pdaTasks).listByWarehouse("WH-1", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outboundNumberResolvesDispatchWorkflowInBoundWarehouse() {
|
||||||
|
OutboundOrder outbound = outbound(301L, 401L, "DSP-401", "OUT-301", "transfer-key");
|
||||||
|
outbound.setOverseasWarehouseId("WH-1");
|
||||||
|
outbound.setOutboundType("DISPATCH_DELIVERY");
|
||||||
|
DispatchOrder dispatch = new DispatchOrder();
|
||||||
|
dispatch.setId(401L); dispatch.setDispatchNo("DSP-401"); dispatch.setOverseasWarehouseId("WH-1");
|
||||||
|
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
|
||||||
|
when(dispatchOrders.findGlobalByDispatchNo("DSP-401")).thenReturn(dispatch);
|
||||||
|
|
||||||
|
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
|
||||||
|
|
||||||
|
assertEquals("DISPATCH", result.workflow());
|
||||||
|
assertEquals("DSP-401", result.dispatchNo());
|
||||||
|
assertEquals("OUT-301", result.outboundNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outboundNumberResolvesOneClickTaskInBoundWarehouse() {
|
||||||
|
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
|
||||||
|
outbound.setOverseasWarehouseId("WH-1");
|
||||||
|
outbound.setOutboundType("ONE_CLICK_DROPSHIP");
|
||||||
|
OutboundPdaTask task = new OutboundPdaTask();
|
||||||
|
task.setCustomerId("TENANT-A"); task.setOutboundOrderId(301L); task.setTaskNo("PDA-701");
|
||||||
|
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
|
||||||
|
when(pdaTasks.findByOutboundAndWarehouse("TENANT-A", 301L, "WH-1")).thenReturn(task);
|
||||||
|
|
||||||
|
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
|
||||||
|
|
||||||
|
assertEquals("OUTBOUND", result.workflow());
|
||||||
|
assertEquals("PDA-701", result.taskNo());
|
||||||
|
assertEquals("OUT-301", result.outboundNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pdaTaskResolutionDoesNotLeakAnotherWarehouse() {
|
||||||
|
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
|
||||||
|
outbound.setOverseasWarehouseId("WH-2");
|
||||||
|
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
|
||||||
|
|
||||||
|
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||||
|
() -> service.resolvePdaTask("WH-1", "OUT-301"));
|
||||||
|
|
||||||
|
assertEquals(404, exception.getStatusCode().value());
|
||||||
|
verify(pdaTasks, never()).findByOutboundAndWarehouse(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void outboundPdaDetailReturnsEveryPickablePositionWithStableBusinessCodes() {
|
void outboundPdaDetailReturnsEveryPickablePositionWithStableBusinessCodes() {
|
||||||
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
|
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
|
||||||
|
|||||||
+35
@@ -167,6 +167,41 @@ class InboundPdaWorkflowServiceTest {
|
|||||||
targetPallet.getWarehouseLocationId(), "assign-wrong-warehouse")));
|
targetPallet.getWarehouseLocationId(), "assign-wrong-warehouse")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvedAssignmentReplacesPlanPalletNumberWithPhysicalWarehouseCode() {
|
||||||
|
targetPallet.setPalletNo("IP-ACY2-20260907-597057-P001");
|
||||||
|
targetPallet.setWarehousePalletId(null);
|
||||||
|
targetPallet.setWarehouseLocationId(null);
|
||||||
|
targetPallet.setWarehouseResourceLockId(null);
|
||||||
|
targetPallet.setAssignmentStatus("PENDING_ASSIGNMENT");
|
||||||
|
WarehouseResourceGateway.WarehousePalletReference resource =
|
||||||
|
new WarehouseResourceGateway.WarehousePalletReference(188L, "A002", 88L, 288L,
|
||||||
|
"IDLE", "ACY2", 1);
|
||||||
|
when(warehouse.reserve("100", 88L, 188L, 288L, "INBOUND_CONTAINER_DESTINATION_PALLET",
|
||||||
|
"CONT-1:WH-A", "INBOUND_CONTAINER_DESTINATION_PALLET:CONT-1:WH-A:188")).thenReturn(999L);
|
||||||
|
|
||||||
|
service.assignResolvedPallet("100", plan, targetPallet, resource);
|
||||||
|
|
||||||
|
assertEquals("A002", targetPallet.getPalletNo());
|
||||||
|
assertEquals(188L, targetPallet.getWarehousePalletId());
|
||||||
|
assertEquals(288L, targetPallet.getWarehouseLocationId());
|
||||||
|
verify(repository).update(targetPallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvedAssignmentRejectsPhysicalPalletWithoutBusinessCodeBeforeReservation() {
|
||||||
|
targetPallet.setWarehouseResourceLockId(null);
|
||||||
|
WarehouseResourceGateway.WarehousePalletReference resource =
|
||||||
|
new WarehouseResourceGateway.WarehousePalletReference(188L, " ", 88L, 288L,
|
||||||
|
"IDLE", "ACY2", 1);
|
||||||
|
|
||||||
|
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||||
|
() -> service.assignResolvedPallet("100", plan, targetPallet, resource));
|
||||||
|
|
||||||
|
assertEquals(409, exception.getStatusCode().value());
|
||||||
|
verify(warehouse, never()).reserve(any(), anyLong(), anyLong(), anyLong(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void cartonScanBindsOnlyExpectedCartonAndRemoveMakesItScannableAgain() {
|
void cartonScanBindsOnlyExpectedCartonAndRemoveMakesItScannableAgain() {
|
||||||
InboundPlanCarton carton = carton("BOX-001", "UNASSIGNED", targetLine.getId(), "WH-A");
|
InboundPlanCarton carton = carton("BOX-001", "UNASSIGNED", targetLine.getId(), "WH-A");
|
||||||
|
|||||||
+2
@@ -87,10 +87,12 @@ class InboundStockReceiptServiceTest {
|
|||||||
assertEquals("Customer", request.getValue().customerName());
|
assertEquals("Customer", request.getValue().customerName());
|
||||||
assertEquals("900", request.getValue().overseasWarehouseId());
|
assertEquals("900", request.getValue().overseasWarehouseId());
|
||||||
assertEquals("Los Angeles", request.getValue().overseasWarehouseName());
|
assertEquals("Los Angeles", request.getValue().overseasWarehouseName());
|
||||||
|
assertEquals("PLT-21", request.getValue().palletNo());
|
||||||
ArgumentCaptor<InboundStockReceipt> receipt = ArgumentCaptor.forClass(InboundStockReceipt.class);
|
ArgumentCaptor<InboundStockReceipt> receipt = ArgumentCaptor.forClass(InboundStockReceipt.class);
|
||||||
verify(receipts).insert(receipt.capture());
|
verify(receipts).insert(receipt.capture());
|
||||||
assertEquals(5L, receipt.getValue().getReceivedPackages());
|
assertEquals(5L, receipt.getValue().getReceivedPackages());
|
||||||
assertEquals(10L, receipt.getValue().getInventoryPositionId());
|
assertEquals(10L, receipt.getValue().getInventoryPositionId());
|
||||||
|
assertEquals("PLT-21", receipt.getValue().getPalletNo());
|
||||||
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
|
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
|
||||||
assertEquals("INBOUND_PLAN:COMPLETED", fixture.order.getSubOrderStatus());
|
assertEquals("INBOUND_PLAN:COMPLETED", fixture.order.getSubOrderStatus());
|
||||||
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
|
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
|
||||||
|
|||||||
+3
-1
@@ -74,13 +74,15 @@ class PdaOperationsOpenApiContractTest {
|
|||||||
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/complete:",
|
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/complete:",
|
||||||
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:",
|
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:",
|
||||||
"/api/pda/v1/last-leg/outbound-tasks:",
|
"/api/pda/v1/last-leg/outbound-tasks:",
|
||||||
|
"/api/pda/v1/last-leg/outbound-tasks/resolve/{businessNo}:",
|
||||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}:",
|
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}:",
|
||||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pick:",
|
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pick:",
|
||||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:");
|
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:");
|
||||||
for (String path : paths) assertTrue(yaml.contains(path), path);
|
for (String path : paths) assertTrue(yaml.contains(path), path);
|
||||||
assertEquals(32, paths.size());
|
assertEquals(33, paths.size());
|
||||||
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
|
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
|
||||||
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
|
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
|
||||||
|
assertTrue(yaml.contains("Warehouse 权威实物托盘码"));
|
||||||
assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]"));
|
assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]"));
|
||||||
assertEquals(3, occurrences(yaml, "body.code=A0404"));
|
assertEquals(3, occurrences(yaml, "body.code=A0404"));
|
||||||
assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));
|
assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));
|
||||||
|
|||||||
Reference in New Issue
Block a user