diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/OutboundPdaController.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/OutboundPdaController.java index ad1d097db..67bdf0e02 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/OutboundPdaController.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/OutboundPdaController.java @@ -34,6 +34,13 @@ public class OutboundPdaController { return ApiResponse.success(service.pdaTasks(String.valueOf(identity.overseasWarehouseId()), status)); } + @GetMapping("/resolve/{businessNo}") + public ApiResponse resolve( + @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String businessNo) { + PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user); + return ApiResponse.success(service.resolvePdaTask(String.valueOf(identity.overseasWarehouseId()), businessNo)); + } + @GetMapping("/{taskNo}") public ApiResponse detail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String taskNo) { diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java index 0ec6b6c33..eabea2350 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java @@ -40,8 +40,12 @@ public final class DeliveryResponses { public record DeliveryDetail(DeliveryRow order, List skus, List fees, List 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 pallets, DeliveryRow delivery) {} 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, String skuCode, String inboundCode, long availablePackages, long pickedPackages) {} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaTaskMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaTaskMapper.java index 624436ef4..063871975 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaTaskMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaTaskMapper.java @@ -23,6 +23,12 @@ public interface OutboundPdaTaskMapper extends BaseMapper { "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); + @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 " + "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") diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java index 976899c5b..22545a256 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java @@ -409,6 +409,36 @@ public class DeliveryTransferService { 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 pdaPickPositions(String customerId, String warehouseId, OutboundPalletItem item) { 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.PdaTaskResolution outboundResolution(OutboundOrder outbound, OutboundPdaTask task) { + return new DeliveryResponses.PdaTaskResolution("OUTBOUND", task.getTaskNo(), outbound.getOutboundNo(), + outbound.getDispatchNo()); + } private OutboundOrder requireOutbound(OutboundPdaTask task) { OutboundOrder outbound = outboundOrders.selectById(task.getOutboundOrderId()); if (outbound == null || !task.getCustomerId().equals(outbound.getCustomerId())) { @@ -481,7 +515,9 @@ public class DeliveryTransferService { if (outbound == null) throw notFound("Outbound order not found"); String customerId = outbound.getCustomerId(); DeliveryOrder delivery = requireDelivery(customerId, outbound.getId()); - return new DeliveryResponses.OutboundDetail(outboundRow(outbound), deliveryRow(delivery)); + List pallets = outboundPallets + .findByOutbound(customerId, outbound.getId()).stream().map(this::outboundPalletRow).toList(); + return new DeliveryResponses.OutboundDetail(outboundRow(outbound), pallets, deliveryRow(delivery)); } public PageResult deliveryPage(String customerId, DeliveryRequests.Query query) { @@ -556,6 +592,11 @@ public class DeliveryTransferService { 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.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.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()); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java index 73962eb29..872c68bda 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java @@ -171,13 +171,17 @@ public class InboundPdaWorkflowService { if (Objects.equals(pallet.getWarehousePalletId(), resource.palletId())) return null; 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 businessId = plan.getContainerNo() + ":" + destinationKey; String reservationKey = "INBOUND_CONTAINER_DESTINATION_PALLET:" + businessId + ":" + resource.palletId(); long lockId = warehouse.reserve(customerId, resource.warehouseId(), resource.palletId(), resource.locationId(), "INBOUND_CONTAINER_DESTINATION_PALLET", businessId, reservationKey); 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.setLockedAt(LocalDateTime.now()); repository.update(pallet); return lockId; diff --git a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml index c25a1f72e..83f5a212a 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -564,7 +564,7 @@ paths: description: 权限 platform:overseas-warehouse:outbound:list;只按全局唯一 outboundNo 定位并从记录解析客户范围,不接收 customerId。 security: [{ bearerAuth: [] }] 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: post: summary: 取消未执行出库单 @@ -1537,7 +1537,7 @@ components: orderNo: { type: string } orderRefs: { type: string } inboundCode: { type: string } - palletNo: { type: string } + palletNo: { type: string, description: 未入库时为计划托盘码;绑定实物托盘后为 Warehouse 权威实物托盘码 } destinationWarehouseId: { type: string } destinationWarehouseCode: { type: string } destinationWarehouseName: { type: string } diff --git a/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml index 1fc1daacd..444352eeb 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml @@ -46,6 +46,14 @@ paths: description: 一件代发复用统一出库任务模型;不接收customerId或warehouseId,派送不进入PDA。 parameters: [{ name: status, in: query, required: false, schema: { type: string } }] 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}: get: summary: 查询一件代发出库任务详情 @@ -569,7 +577,7 @@ components: required: [outboundPalletItemId, palletNo, skuCode, inboundCode, packageCount, pickedPackageCount, status, positions] properties: outboundPalletItemId: { type: string, description: pick请求提交键,不作为用户展示编号 } - palletNo: { type: string } + palletNo: { type: string, description: Warehouse 权威实物托盘码 } skuCode: { type: string } inboundCode: { type: string } packageCount: { type: integer } @@ -582,7 +590,7 @@ components: required: [inventoryPositionId, palletNo, cartonMark, skuCode, inboundCode, availablePackages, pickedPackages] properties: inventoryPositionId: { type: string, description: pick请求提交键,不作为用户展示编号 } - palletNo: { type: string } + palletNo: { type: string, description: Warehouse 权威实物托盘码 } cartonMark: { type: string, nullable: true } skuCode: { type: string } inboundCode: { type: string } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/PlatformDispatchOutboundContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/PlatformDispatchOutboundContractTest.java index 798549ed2..5096b6359 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/PlatformDispatchOutboundContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/PlatformDispatchOutboundContractTest.java @@ -166,6 +166,7 @@ class PlatformDispatchOutboundContractTest { for (String action : Set.of(dispatchAction, transfer, outboundDetail, outboundCancel)) { 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:cancel")); assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:transfer-delivery")); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java index 9b914dce4..0ea310a8f 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java @@ -270,19 +270,26 @@ class DeliveryTransferServiceTest { } @Test - void outboundDetailDoesNotExposeExcludedPalletOrOutboundFulfillmentRows() { + void outboundDetailIncludesPalletCodesForDisplay() { OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101"); 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(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery); + when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(pallet)); DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301"); assertEquals("OUT-301", detail.outbound().outboundNo()); assertEquals("DEL-401", detail.delivery().deliveryNo()); - assertTrue(java.util.Arrays.stream(DeliveryResponses.OutboundDetail.class.getRecordComponents()) - .noneMatch(component -> "pallets".equals(component.getName()))); - verify(outboundPallets, never()).selectPage(any(Page.class), any()); + assertEquals(1, detail.pallets().size()); + assertEquals("IP-ONT8-20260907-000001-P001", detail.pallets().get(0).palletNo()); + 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 @@ -426,6 +433,53 @@ class DeliveryTransferServiceTest { 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 void outboundPdaDetailReturnsEveryPickablePositionWithStableBusinessCodes() { OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java index 483e7e6cc..8d90ec21f 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java @@ -167,6 +167,41 @@ class InboundPdaWorkflowServiceTest { 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 void cartonScanBindsOnlyExpectedCartonAndRemoveMakesItScannableAgain() { InboundPlanCarton carton = carton("BOX-001", "UNASSIGNED", targetLine.getId(), "WH-A"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java index b14845ecc..9ebb45b68 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java @@ -87,10 +87,12 @@ class InboundStockReceiptServiceTest { assertEquals("Customer", request.getValue().customerName()); assertEquals("900", request.getValue().overseasWarehouseId()); assertEquals("Los Angeles", request.getValue().overseasWarehouseName()); + assertEquals("PLT-21", request.getValue().palletNo()); ArgumentCaptor receipt = ArgumentCaptor.forClass(InboundStockReceipt.class); verify(receipts).insert(receipt.capture()); assertEquals(5L, receipt.getValue().getReceivedPackages()); assertEquals(10L, receipt.getValue().getInventoryPositionId()); + assertEquals("PLT-21", receipt.getValue().getPalletNo()); assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus()); assertEquals("INBOUND_PLAN:COMPLETED", fixture.order.getSubOrderStatus()); ArgumentCaptor history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/PdaOperationsOpenApiContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/PdaOperationsOpenApiContractTest.java index a4ea6b108..660588fdb 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/PdaOperationsOpenApiContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/PdaOperationsOpenApiContractTest.java @@ -74,13 +74,15 @@ class PdaOperationsOpenApiContractTest { "/api/pda/v1/last-leg/dispatches/{dispatchNo}/complete:", "/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:", "/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}/pick:", "/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:"); 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: [APPROVE, REWORK]")); + assertTrue(yaml.contains("Warehouse 权威实物托盘码")); assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]")); assertEquals(3, occurrences(yaml, "body.code=A0404")); assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));