diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java b/lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java index 9386f9c91..c02100a12 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java @@ -93,6 +93,31 @@ public class LastlegOrderController { return ApiResponse.success(orderService.createFee(platformScope(user, customerId), id, request, operatorName(user))); } + @PutMapping("/api/platform/v1/last-leg/orders/{id}/detained") + @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')") + @Operation(summary = "Set tail-leg order hold status", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT) + public ApiResponse setDetained( + @AuthenticationPrincipal AuthenticatedUser user, + @RequestParam(required = false) String customerId, + @PathVariable long id, + @Valid @RequestBody LastlegOrderRequests.SetDetained request) { + orderService.setDetained(platformScope(user, customerId), id, request, operatorName(user)); + return ApiResponse.success(null); + } + + @PutMapping("/api/platform/v1/last-leg/orders/{id}/pallet-merge-required") + @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')") + @Operation(summary = "Set the PDA pallet-merge reminder for one tail-leg order", + description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT) + public ApiResponse setPalletMergeRequired( + @AuthenticationPrincipal AuthenticatedUser user, + @RequestParam(required = false) String customerId, + @PathVariable long id, + @Valid @RequestBody LastlegOrderRequests.SetPalletMergeRequired request) { + orderService.setPalletMergeRequired(platformScope(user, customerId), id, request, operatorName(user)); + return ApiResponse.success(null); + } + @GetMapping(value = "/api/platform/v1/last-leg/orders/export", produces = "text/csv") @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EXPORT + "')") @Operation(summary = "Export filtered tail-leg orders as UTF-8 CSV", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EXPORT) 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 e54443cdc..6249a645f 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 @@ -53,6 +53,7 @@ import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import java.math.BigDecimal; import java.util.*; @@ -77,6 +78,7 @@ public class DeliveryTransferService { @Resource private LastlegOrderMapper tailOrders; @Resource private LastlegOrderStatusHistoryMapper histories; @Resource private LastlegOrderDetailMapper tailOrderDetails; + @Resource private LastlegOrderNoGenerator orderNoGenerator; @Transactional public DeliveryResponses.TransferResult transfer(String customerId, String dispatchNo, String operatorId, @@ -105,7 +107,7 @@ public class DeliveryTransferService { LocalDateTime now = LocalDateTime.now(); OutboundOrder outbound = new OutboundOrder(); outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId); - outbound.setOutboundNo("OUT" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId()); + outbound.setOutboundNo(orderNoGenerator.next("OUTBOUND", dispatch.getOverseasWarehouseId(), outbound.getId())); outbound.setDispatchOrderId(dispatch.getId()); outbound.setDispatchNo(dispatch.getDispatchNo()); outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); outbound.setOutboundType("DISPATCH_DELIVERY"); outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum()); @@ -141,7 +143,7 @@ public class DeliveryTransferService { DeliveryOrder delivery = new DeliveryOrder(); delivery.setId(IdWorker.getId()); delivery.setCustomerId(customerId); - delivery.setDeliveryNo("DEL" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + delivery.getId()); + delivery.setDeliveryNo(orderNoGenerator.next("DELIVERY", dispatch.getOverseasWarehouseId(), delivery.getId())); delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo()); delivery.setDispatchOrderId(dispatch.getId()); delivery.setDispatchNo(dispatch.getDispatchNo()); delivery.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); delivery.setOverseasWarehouseName(dispatch.getOverseasWarehouseName()); @@ -197,14 +199,16 @@ public class DeliveryTransferService { OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim()); if (existing != null) return taskResult(existing, pdaTasks.lockByOutbound(customerId, existing.getId())); LocalDateTime now = LocalDateTime.now(); + InventoryPosition firstPosition = inventoryPositions.selectById(request.items().get(0).inventoryPositionId()); + if (firstPosition == null || !customerId.equals(firstPosition.getCustomerId())) throw notFound("Inventory position not found"); + String selectedWarehouseId = firstPosition.getOverseasWarehouseId(); OutboundOrder outbound = new OutboundOrder(); outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId); - outbound.setOutboundNo("FBOO" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId()); + outbound.setOutboundNo(orderNoGenerator.next("ONE_CLICK_DROPSHIP", selectedWarehouseId, outbound.getId())); outbound.setOutboundType("ONE_CLICK_DROPSHIP"); outbound.setStatus("WAIT_PICK"); outbound.setRecipientInfo(request.recipient().trim()); outbound.setPackageCount(request.items().stream().mapToInt(i -> i.packages().intValue()).sum()); outbound.setPalletCount(request.items().size()); outbound.setIdempotencyKey(request.idempotencyKey().trim()); outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now); outboundOrders.insert(outbound); - String selectedWarehouseId = null; for (DeliveryRequests.OneClickItem input : request.items()) { InventoryPosition position = inventoryPositions.selectById(input.inventoryPositionId()); if (position == null || !customerId.equals(position.getCustomerId())) throw notFound("Inventory position not found"); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/discard/service/CargoDiscardService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/discard/service/CargoDiscardService.java index 2f366c605..60d0f580a 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/discard/service/CargoDiscardService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/discard/service/CargoDiscardService.java @@ -44,6 +44,7 @@ import java.util.Set; @Service public class CargoDiscardService { + @Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator; public static final String APPROVAL_PERMISSION = "lastleg:cargo-discard:approve"; public static final String PENDING_REVIEW = "PENDING_REVIEW"; public static final String APPROVED = "APPROVED"; @@ -105,7 +106,7 @@ public class CargoDiscardService { LocalDateTime now = LocalDateTime.now(); CargoDiscardApplication application = new CargoDiscardApplication(); application.setId(IdWorker.getId()); application.setCustomerId(customerId); - application.setDiscardNo("FH" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + application.getId()); + application.setDiscardNo(orderNoGenerator.next("DISCARD", warehouseId, application.getId())); application.setOverseasWarehouseId(warehouseId); application.setOverseasWarehouseName(warehouseName); application.setInboundCodes(orderCache.values().stream().map(LastlegOrder::getInboundCode) .distinct().sorted().collect(java.util.stream.Collectors.joining(","))); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java index 54a7aa03e..9fcd08079 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java @@ -31,6 +31,7 @@ import org.springframework.web.server.ResponseStatusException; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -48,6 +49,7 @@ public class DispatchService { @Resource private DispatchDraftMapper drafts; @Resource private DispatchDraftContainerMapper draftContainers; @Resource private DispatchCandidateMapper candidates; + @Resource private LastlegOrderNoGenerator orderNoGenerator; public String resolveCustomerScopeForPlans(List planIds) { return requireSingleCustomerScope(candidates.findCustomerScopesByPlanIds(normalizeIds(planIds, "Plan IDs")), @@ -239,7 +241,7 @@ public class DispatchService { DispatchOrder dispatch = new DispatchOrder(); dispatch.setId(IdWorker.getId()); dispatch.setCustomerId(customerId); - dispatch.setDispatchNo("PC" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + dispatch.getId()); + dispatch.setDispatchNo(orderNoGenerator.next("DISPATCH", selected.get(0).overseasWarehouseId(), dispatch.getId())); dispatch.setOverseasWarehouseId(selected.get(0).overseasWarehouseId()); dispatch.setOverseasWarehouseName(selected.get(0).overseasWarehouseName()); dispatch.setRoute(StringUtils.hasText(request.route()) ? request.route().trim() : route(selected)); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderRequests.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderRequests.java index 01b53a403..122ae8520 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderRequests.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderRequests.java @@ -68,4 +68,15 @@ public final class LastlegOrderRequests { @Size(max = 500) String supplierDescription ) { } + + public record SetDetained( + @NotNull Boolean detained, + @NotBlank @Size(max = 500) String remark + ) { + } + + public record SetPalletMergeRequired( + @NotNull Boolean palletMergeRequired + ) { + } } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java index c2c1ca972..61122525e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java @@ -31,6 +31,7 @@ import java.util.Objects; @Service public class ExceptionOrderService { + @Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator; @Resource private ExceptionOrderMapper exceptionOrders; @Resource private LastlegOrderMapper lastMileOrders; @Resource private LastlegInboundOrderMapper inboundOrders; @@ -158,7 +159,7 @@ public class ExceptionOrderService { LocalDateTime now = LocalDateTime.now(); ExceptionOrder value = new ExceptionOrder(); value.setId(IdWorker.getId()); value.setCustomerId(relation.customerId()); - value.setExceptionNo("EXC" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + value.getId()); + value.setExceptionNo(orderNoGenerator.next("EXCEPTION", relation.overseasWarehouseId(), value.getId())); value.setExceptionType(type); value.setStatus("PENDING"); value.setReason(trim(reason)); value.setLastMileOrderId(relation.lastMileOrderId()); value.setLastMileOrderNo(relation.lastMileOrderNo()); value.setInboundOrderId(relation.inboundOrderId()); value.setInboundCode(relation.inboundCode()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/ReceivingContainerResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/ReceivingContainerResponses.java index 6afe67cba..f5f6056b2 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/ReceivingContainerResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/ReceivingContainerResponses.java @@ -26,14 +26,28 @@ public final class ReceivingContainerResponses { String destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String fbaWarehouseCode, int plannedPalletCount, int totalPackages, - int receivedPackages, int pendingPackages) { } + int receivedPackages, int pendingPackages, + boolean palletMergeRequired, String palletMergeHint) { } public record CartonResolved(String cartonMark, String inboundCode, String destinationType, String overseasWarehouseId, String overseasWarehouseName, String destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String fbaWarehouseCode, int plannedPalletCount, int totalPackages, - int receivedPackages, int pendingPackages) { } + int receivedPackages, int pendingPackages, + boolean palletMergeRequired, String palletMergeHint) { + public CartonResolved(String cartonMark, String inboundCode, String destinationType, + String overseasWarehouseId, String overseasWarehouseName, + String destinationWarehouseId, String destinationWarehouseCode, + String destinationWarehouseName, String fbaWarehouseCode, + int plannedPalletCount, int totalPackages, int receivedPackages, + int pendingPackages) { + this(cartonMark, inboundCode, destinationType, overseasWarehouseId, overseasWarehouseName, + destinationWarehouseId, destinationWarehouseCode, destinationWarehouseName, + fbaWarehouseCode, plannedPalletCount, totalPackages, receivedPackages, + pendingPackages, false, null); + } + } public record PalletResolved(String palletCode, long warehouseId, String status, String fbaWarehouseCode, String inboundCode, @@ -41,7 +55,15 @@ public final class ReceivingContainerResponses { public record Receipt(String result, String containerNo, String inboundCode, String destinationType, String palletCode, int acceptedPackages, - int receivedPackages, int pendingPackages, String sessionStatus) { } + int receivedPackages, int pendingPackages, String sessionStatus, + boolean palletMergeRequired, String palletMergeHint) { + public Receipt(String result, String containerNo, String inboundCode, + String destinationType, String palletCode, int acceptedPackages, + int receivedPackages, int pendingPackages, String sessionStatus) { + this(result, containerNo, inboundCode, destinationType, palletCode, acceptedPackages, + receivedPackages, pendingPackages, sessionStatus, false, null); + } + } public record Photo(String fileId, String fileName, String contentType, long sizeBytes) { } } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java index 61f29be57..c0ea09d00 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java @@ -224,15 +224,17 @@ public class InboundStockReceiptService { String operationKey = "PDA_STOCK_IN:" + task.getId() + ":" + allocation.getId() + ":" + sku.skuCode(); String overseasWarehouseId = required(order.getOverseasWarehouseId(), "Actual overseas warehouse is required for stock-in"); - InventoryCommandResponse result = inventory.stockIn(customerId, task.getOperatorId(), - new InventoryCommandRequests.StockIn(null, order.getCustomerName(), overseasWarehouseId, + InventoryCommandRequests.StockIn stockIn = new InventoryCommandRequests.StockIn(null, order.getCustomerName(), overseasWarehouseId, null, order.getOverseasWarehouseName(), line.getDestinationWarehouseId(), destinationCode, line.getDestinationWarehouseName(), order.getId(), plan.getPlanNo(), line.getInboundCode(), sku.skuCode(), sku.productNameZh(), sku.productNameEn(), pallet.getWarehousePalletId(), pallet.getPalletNo(), plan.getPlanNo(), pallet.getWarehouseLocationId(), null, packages, order.getOrderNo(), order.getSourceEventId(), operationKey, - "PDA inbound completion for " + line.getInboundCode())); + "PDA inbound completion for " + line.getInboundCode()); + InventoryCommandResponse result = Boolean.TRUE.equals(order.getDetained()) + ? inventory.stockInHeld(customerId, task.getOperatorId(), stockIn) + : inventory.stockIn(customerId, task.getOperatorId(), stockIn); InboundStockReceipt receipt = new InboundStockReceipt(); receipt.setId(IdWorker.getId()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java index ad9ca9d54..169f08ffa 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java @@ -61,6 +61,7 @@ import java.util.stream.Collectors; public class ReceivingContainerService { private static final String PHOTO_DOWNLOAD_PATH = "/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files/{fileId}/download"; + private static final String PALLET_MERGE_HINT = "该入仓单已标记合托,请按合托要求拣货"; @Resource private InboundPlanMapper plans; @Resource private InboundPlanLineMapper lines; @@ -245,7 +246,8 @@ public class ReceivingContainerService { inbound.inboundCode(), inbound.destinationType(), inbound.overseasWarehouseId(), inbound.overseasWarehouseName(), inbound.destinationWarehouseId(), inbound.destinationWarehouseCode(), inbound.destinationWarehouseName(), inbound.fbaWarehouseCode(), - inbound.plannedPalletCount(), inbound.totalPackages(), inbound.receivedPackages(), inbound.pendingPackages()); + inbound.plannedPalletCount(), inbound.totalPackages(), inbound.receivedPackages(), inbound.pendingPackages(), + inbound.palletMergeRequired(), inbound.palletMergeHint()); } public ReceivingContainerResponses.PalletResolved resolvePallet(String operatorKey, @@ -290,7 +292,8 @@ public class ReceivingContainerService { int total = fbaPlanRefs.stream().mapToInt(refValue -> value(refValue.line().getRequiredPackageCount())).sum(); ReceivingContainerResponses.Receipt result = new ReceivingContainerResponses.Receipt("RECEIVED", session.getContainerNo(), ref.line().getInboundCode(), "FBA", resource.palletNo(), accepted, - received, Math.max(0, total - received), session.getStatus()); + received, Math.max(0, total - received), session.getStatus(), palletMergeRequired(ref.order()), + palletMergeHint(ref.order())); save(session, "BULK_RECEIVE", request.idempotencyKey(), request, result); return result; } catch (RuntimeException failure) { @@ -328,7 +331,8 @@ public class ReceivingContainerService { ReceivingContainerResponses.Inbound inbound = inbound(ref); ReceivingContainerResponses.Receipt result = new ReceivingContainerResponses.Receipt("RECEIVED", session.getContainerNo(), inbound.inboundCode(), inbound.destinationType(), resource.palletNo(), 1, - inbound.receivedPackages(), inbound.pendingPackages(), session.getStatus()); + inbound.receivedPackages(), inbound.pendingPackages(), session.getStatus(), + inbound.palletMergeRequired(), inbound.palletMergeHint()); save(session, "CARTON_RECEIVE", request.idempotencyKey(), request, result); return result; } catch (RuntimeException failure) { @@ -539,7 +543,17 @@ public class ReceivingContainerService { : String.valueOf(ref.plan().getOverseasWarehouseId()), ref.order().getOverseasWarehouseName(), ref.line().getDestinationWarehouseId(), ref.line().getDestinationWarehouseCode(), ref.line().getDestinationWarehouseName(), ref.line().getFbaWarehouseCode(), - value(ref.line().getPlannedPalletCount()), total, received, Math.max(0, total - received)); + value(ref.line().getPlannedPalletCount()), total, received, Math.max(0, total - received), + palletMergeRequired(ref.order()), palletMergeHint(ref.order())); + } + + private boolean palletMergeRequired(LastlegOrder order) { + return order != null && ("REQUIRED".equals(order.getPalletMergeFlag()) + || "MERGED".equals(order.getPalletMergeFlag())); + } + + private String palletMergeHint(LastlegOrder order) { + return palletMergeRequired(order) ? PALLET_MERGE_HINT : null; } private Totals totals(List containerPlans) { diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java index 90e8c7df0..3201c9d1f 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java @@ -36,6 +36,7 @@ import java.util.stream.Collectors; @Service public class InboundPlanService { + @Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator; @Resource private InboundPlanMapper planMapper; @Resource private InboundPlanLineMapper lineMapper; @Resource private InboundPlanPalletMapper palletMapper; @@ -106,7 +107,7 @@ public class InboundPlanService { LocalDateTime now = LocalDateTime.now(); InboundPlan plan = new InboundPlan(); plan.setId(IdWorker.getId()); - plan.setPlanNo("LIP" + plan.getId()); + plan.setPlanNo(orderNoGenerator.next("INBOUND_PLAN", orders.get(0).destinationWarehouseCode(), plan.getId())); plan.setCustomerId(customerId); plan.setSourceOrderId(selectedOrderId); plan.setContainerNo(containerNo); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java index e292046d9..343491524 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java @@ -31,4 +31,10 @@ public interface InventoryCommandMapper { List lockInboundTransitBalances(@Param("customerId") String customerId, @Param("inboundOrderId") Long inboundOrderId, @Param("inboundCode") String inboundCode); + + @Select("SELECT p.* FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id " + + "WHERE p.customer_id=#{customerId} AND b.inbound_order_id=#{inboundOrderId} " + + "AND p.hold_packages>0 ORDER BY p.id FOR UPDATE") + List lockInboundHeldPositions(@Param("customerId") String customerId, + @Param("inboundOrderId") Long inboundOrderId); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java index 9481a2c11..6d2bef118 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java @@ -60,13 +60,25 @@ public class InventoryCommandService { @Transactional public InventoryCommandResponse stockIn(String customerId, String operatorId, InventoryCommandRequests.StockIn request) { + return stockIn(customerId, operatorId, request, false); + } + + @Transactional + public InventoryCommandResponse stockInHeld(String customerId, String operatorId, + InventoryCommandRequests.StockIn request) { + return stockIn(customerId, operatorId, request, true); + } + + private InventoryCommandResponse stockIn(String customerId, String operatorId, + InventoryCommandRequests.StockIn request, boolean held) { return execute(customerId, operatorId, "STOCK_IN", request.idempotencyKey(), request, operation -> { List transitBalances = commands.lockInboundTransitBalances(customerId, request.inboundOrderId(), request.inboundCode()); InventoryBalance balance = balance(customerId, request); InventoryPosition position = position(customerId, balance, request); - long before = value(position.getAvailablePackages()); - position.setAvailablePackages(before + request.packages()); + long before = held ? value(position.getHoldPackages()) : value(position.getAvailablePackages()); + if (held) position.setHoldPackages(before + request.packages()); + else position.setAvailablePackages(before + request.packages()); position.setStatus("OCCUPIED"); position.setVersion(value(position.getVersion()) + 1); position.setUpdatedAt(LocalDateTime.now()); @@ -74,11 +86,12 @@ public class InventoryCommandService { long transferred = transferInTransit(customerId, operatorId, operation.getId(), balance, transitBalances, request); - balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages()); + if (held) balance.setHoldPackages(value(balance.getHoldPackages()) + request.packages()); + else balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages()); touch(balance); balances.updateById(balance); ledger(customerId, operatorId, operation.getId(), balance, position, sku(request.skuCode()), "STOCK_IN", - transferred == request.packages() ? "IN_TRANSIT" : "OUTSIDE", "AVAILABLE", + transferred == request.packages() ? "IN_TRANSIT" : "OUTSIDE", held ? "HOLD" : "AVAILABLE", before, request.packages(), before + request.packages(), request.businessOrderNo(), request.inboundOrderNo(), null, request.sourceEventId(), request.idempotencyKey(), request.remark()); @@ -86,6 +99,17 @@ public class InventoryCommandService { }); } + @Transactional + public void releaseInboundOrderHold(String customerId, String operatorId, long inboundOrderId, + String businessOrderNo, String remark) { + for (InventoryPosition position : commands.lockInboundHeldPositions(customerId, inboundOrderId)) { + long packages = value(position.getHoldPackages()); + if (packages == 0) continue; + release(customerId, operatorId, new InventoryCommandRequests.Move(position.getId(), packages, + businessOrderNo, null, null, "ORDER_HOLD_RELEASE:" + inboundOrderId + ":" + position.getId(), remark)); + } + } + private long transferInTransit(String customerId, String operatorId, Long operationId, InventoryBalance target, List sources, InventoryCommandRequests.StockIn request) { diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java index b50c883ba..9dfa094fd 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java @@ -70,7 +70,7 @@ public class LocalLooseboardService { LocalLooseboardOrder order = new LocalLooseboardOrder(); order.setId(IdWorker.getId()); order.setCustomerId(customerId); - order.setOrderNo("LB" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + order.getId()); + order.setOrderNo(lastlegOrderNoGenerator.next("LOOSEBOARD", request.destination(), order.getId())); apply(order, request.transportMode(), request.palletRequired(), request.transportType(), request.origin(), request.originAddress(), request.destination(), request.destinationAddress(), request.containerType(), request.packageCount(), request.totalWeightKg(), request.totalVolumeCbm(), request.budgetAmount(), @@ -257,7 +257,7 @@ public class LocalLooseboardService { private LastlegOrder createLastlegOrder(LocalLooseboardOrder source, String requestHash, LocalDateTime now) { LastlegOrder order = new LastlegOrder(); order.setId(IdWorker.getId()); - order.setOrderNo(lastlegOrderNoGenerator.next()); + order.setOrderNo(lastlegOrderNoGenerator.next("LOOSEBOARD", source.getDestination(), order.getId())); order.setCustomerId(source.getCustomerId()); order.setOrderType("LOCAL_LOOSEBOARD"); order.setInboundCode(source.getOrderNo()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java index 9973bdca2..b07c332a9 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java @@ -42,4 +42,10 @@ public interface LastlegOrderMapper extends BaseMapper { int claimReceivingWarehouse(@Param("customerId") String customerId, @Param("orderId") long orderId, @Param("warehouseId") String warehouseId, @Param("warehouseName") String warehouseName); + + @Update("UPDATE ll_order SET pallet_merge_flag=#{flag},updated_at=#{updatedAt} " + + "WHERE customer_id=#{customerId} AND id=#{id}") + int updatePalletMergeFlag(@Param("customerId") String customerId, @Param("id") long id, + @Param("flag") String flag, + @Param("updatedAt") java.time.LocalDateTime updatedAt); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/procurement/service/OverseasProcurementService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/procurement/service/OverseasProcurementService.java index 6a08ae5aa..44eafd56e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/procurement/service/OverseasProcurementService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/procurement/service/OverseasProcurementService.java @@ -37,6 +37,7 @@ import java.util.Set; @Service public class OverseasProcurementService { + @Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator; public static final String REVIEW_PERMISSION = OverseasPermissions.PURCHASE_REVIEW; public static final String MANAGE_PERMISSION = OverseasPermissions.PURCHASE_EDIT; public static final String PENDING_REVIEW = "PENDING_REVIEW"; @@ -62,7 +63,7 @@ public class OverseasProcurementService { order.setId(IdWorker.getId()); order.setCustomerId(customerId); order.setCustomerName(trim(request.customerName())); - order.setOrderNo("HC" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + order.getId()); + order.setOrderNo(orderNoGenerator.next("PROCUREMENT", request.receivingAddress(), order.getId())); order.setPurchaseCategory(request.purchaseCategory().trim()); order.setProductName(request.productName().trim()); order.setProductSpecification(trim(request.productSpecification())); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java index c9bce8008..99cc8d7cc 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java @@ -67,10 +67,10 @@ public class RelabelApplicationService { } LocalDateTime now = LocalDateTime.now(); long applicationId = IdWorker.getId(); - String relabelNo = "FBR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + applicationId; + String relabelNo = orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), applicationId); LastlegOrder order = new LastlegOrder(); order.setId(IdWorker.getId()); - order.setOrderNo(orderNoGenerator.next()); + order.setOrderNo(orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), order.getId())); order.setCustomerId(customerId); order.setOrderType("RELABEL_APPLICATION"); order.setInboundCode(relabelNo); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderNoGenerator.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderNoGenerator.java index 86b89766f..bb76b6e76 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderNoGenerator.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderNoGenerator.java @@ -3,15 +3,59 @@ package com.baiyue.lastleg.service; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.springframework.stereotype.Component; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; @Component public class LastlegOrderNoGenerator { public String next() { - return "LMO" + IdWorker.getIdStr(); + long id = IdWorker.getId(); + return next("LMO", null, id); + } + + public String next(String businessType, String warehouse, long uniqueId) { + String type = normalizeType(businessType); + return type + "-" + warehouseCode(warehouse) + "-" + + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "-" + + String.format(Locale.ROOT, "%06d", Math.floorMod(uniqueId, 1_000_000)); + } + + private String normalizeType(String value) { + String code = value == null ? "LMO" : value.trim().toUpperCase(Locale.ROOT); + return switch (code) { + case "FIRST_LEG" -> "FL"; + case "ONE_CLICK_DROPSHIP" -> "OD"; + case "RELABEL_APPLICATION", "RELABEL" -> "RL"; + case "WAREHOUSE_TRANSFER", "TRANSFER" -> "WT"; + case "LOCAL_LOOSEBOARD", "LOOSEBOARD" -> "LB"; + case "DISPATCH", "PICKUP_CAR" -> "DS"; + case "OUTBOUND" -> "OB"; + case "DELIVERY" -> "DL"; + case "TRUCK" -> "TR"; + case "EXCEPTION" -> "EX"; + case "DISCARD" -> "CD"; + case "PROCUREMENT" -> "PR"; + case "INBOUND_PLAN" -> "IP"; + default -> { + String normalized = code.replaceAll("[^A-Z0-9]", ""); + yield normalized.isEmpty() ? "LMO" : normalized.substring(0, Math.min(4, normalized.length())); + } + }; + } + + public String warehouseCode(String value) { + String code = value == null ? "" : value.trim().toUpperCase(Locale.ROOT); + if (code.isEmpty()) return "WH"; + if (code.matches(".*(LAX|ONT|NYC|JFK|LGB|EWR|SFO|SEA|DFW|ORD|ATL|MIA|PHX|LAS|YYZ|YVR|AMS|LHR|FRA|CDG|HKG|NRT|SYD).*")) { + var match = java.util.regex.Pattern.compile("(LAX|ONT|NYC|JFK|LGB|EWR|SFO|SEA|DFW|ORD|ATL|MIA|PHX|LAS|YYZ|YVR|AMS|LHR|FRA|CDG|HKG|NRT|SYD)").matcher(code); + if (match.find()) return match.group(1); + } + if (code.matches("[A-Z0-9]{2,8}")) return code; + return "WH"; } /** The physical container number is the tail-leg business order grouping boundary. */ @@ -20,14 +64,11 @@ public class LastlegOrderNoGenerator { throw new IllegalArgumentException("containerNo is required"); } String normalized = containerNo.trim(); - if (normalized.matches("[0-9]{1,29}")) { - return "LMO" + normalized; - } try { byte[] digest = MessageDigest.getInstance("SHA-256") .digest(normalized.getBytes(StandardCharsets.UTF_8)); - String digits = new BigInteger(1, digest).toString(); - return "LMO" + digits.substring(0, Math.min(29, digits.length())); + long suffix = java.nio.ByteBuffer.wrap(digest, 0, 8).getLong(); + return next("FIRST_LEG", null, suffix); } catch (NoSuchAlgorithmException exception) { throw new IllegalStateException("SHA-256 is unavailable", exception); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java index a9ddd69c9..b8a887139 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java @@ -14,6 +14,7 @@ import com.baiyue.lastleg.entity.LastlegOrderFee; import com.baiyue.lastleg.entity.LastlegOrderOperationLog; import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper; +import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; @@ -38,6 +39,8 @@ public class LastlegOrderService { public static final String PENDING_ASSIGNMENT = "PENDING_ASSIGNMENT"; public static final String DESTINATION_ASSIGNED = "DESTINATION_ASSIGNED"; public static final String INBOUND_PLANNED = "INBOUND_PLANNED"; + public static final String PALLET_MERGE_REQUIRED = "REQUIRED"; + public static final String PALLET_MERGED = "MERGED"; @Resource private LastlegOrderMapper orderMapper; @Resource private LastlegOrderDetailMapper detailMapper; @@ -45,6 +48,7 @@ public class LastlegOrderService { @Resource private LastlegOrderFeeMapper feeMapper; @Resource private LastlegOrderOperationLogMapper operationLogMapper; @Resource private InboundPlanCartonMapper cartonMapper; + @Resource private InventoryCommandService inventory; public PageResult page(String customerId, LastlegOrderRequests.Query query) { Page page = orderMapper.selectPage( @@ -110,6 +114,70 @@ public class LastlegOrderService { return fee; } + @Transactional + public void setDetained(String customerId, long orderId, LastlegOrderRequests.SetDetained request, + String operatorName) { + LastlegOrder current = findOwned(customerId, orderId); + LastlegOrder order = orderMapper.lockById(current.getCustomerId(), orderId); + if (order == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tail-leg order not found"); + boolean detained = request.detained(); + if (Boolean.TRUE.equals(order.getDetained()) == detained) { + throw new ResponseStatusException(HttpStatus.CONFLICT, detained ? "Order is already held" : "Order is not held"); + } + if (detained && "INBOUND_COMPLETED".equals(order.getCurrentStatus())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Completed inbound orders cannot be newly held"); + } + LocalDateTime now = LocalDateTime.now(); + order.setDetained(detained); + order.setUpdatedAt(now); + orderMapper.updateById(order); + if (!detained && "INBOUND_COMPLETED".equals(order.getCurrentStatus())) { + inventory.releaseInboundOrderHold(order.getCustomerId(), operatorName, orderId, order.getOrderNo(), request.remark().trim()); + } + LastlegOrderOperationLog operation = new LastlegOrderOperationLog(); + operation.setId(IdWorker.getId()); + operation.setCustomerId(order.getCustomerId()); + operation.setOrderId(orderId); + operation.setOperationNode(detained ? "暂扣" : "解除暂扣"); + operation.setOperatorName(operatorName); + operation.setResult(request.remark().trim()); + operation.setCreatedAt(now); + operationLogMapper.insert(operation); + } + + @Transactional + public void setPalletMergeRequired(String customerId, long orderId, + LastlegOrderRequests.SetPalletMergeRequired request, + String operatorName) { + LastlegOrder current = findOwned(customerId, orderId); + LastlegOrder order = orderMapper.lockById(current.getCustomerId(), orderId); + if (order == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tail-leg order not found"); + if ("INBOUND_COMPLETED".equals(order.getCurrentStatus())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, + "Completed inbound orders cannot change the pallet merge mark"); + } + if (PALLET_MERGED.equals(order.getPalletMergeFlag())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, + "A completed pallet merge cannot be unmarked"); + } + String target = request.palletMergeRequired() ? PALLET_MERGE_REQUIRED : null; + if (Objects.equals(target, order.getPalletMergeFlag())) return; + LocalDateTime now = LocalDateTime.now(); + if (orderMapper.updatePalletMergeFlag(order.getCustomerId(), orderId, target, now) != 1) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Pallet merge mark was not updated"); + } + LastlegOrderOperationLog operation = new LastlegOrderOperationLog(); + operation.setId(IdWorker.getId()); + operation.setCustomerId(order.getCustomerId()); + operation.setOrderId(orderId); + operation.setOperationNode(request.palletMergeRequired() ? "标记合托" : "取消合托标记"); + operation.setOperatorName(operatorName); + operation.setResult(request.palletMergeRequired() + ? "PDA扫描本单箱唛时提示需要合托" : "已取消PDA合托提示"); + operation.setCreatedAt(now); + operationLogMapper.insert(operation); + } + @Transactional public int transferDestination(String customerId, LastlegOrderRequests.TransferDestination request) { List ids = request.orderIds().stream().filter(Objects::nonNull).distinct().toList(); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java index f904547a6..0a0deca35 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java @@ -83,8 +83,8 @@ public class LoadingCompletedConsumer { shippingGates.initialize(payload.containerBookingId(), envelope.tenantId(), "", payload.containerNo(), 0, envelope.handoverVersion()); - String orderNo = orderNoGenerator.forContainerNo(payload.containerNo()); JsonNode rawOrders = envelope.payload().path("orders"); + String orderNo = orderNoGenerator.forContainerNo(payload.containerNo()); for (int index = 0; index < payload.orders().size(); index++) { var source = payload.orders().get(index); JsonNode rawSource = rawOrders.isArray() && index < rawOrders.size() ? rawOrders.get(index) : objectMapper.createObjectNode(); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java index 8995b02bd..3abe2459e 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java @@ -117,7 +117,7 @@ public class WarehouseTransferService { LocalDateTime now = LocalDateTime.now(); long transferId = IdWorker.getId(); - String transferNo = "WTR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + transferId; + String transferNo = orderNoGenerator.next("TRANSFER", request.operatingWarehouseId(), transferId); LastlegOrder order = createTailOrder(customerId, inboundOrderId, transferNo, request, requestHash, now); WarehouseTransferOrder transfer = new WarehouseTransferOrder(); @@ -464,7 +464,7 @@ public class WarehouseTransferService { private LastlegOrder createTailOrder(String customerId, Long inboundOrderId, String transferNo, WarehouseTransferRequests.Create request, String requestHash, LocalDateTime now) { LastlegOrder order = new LastlegOrder(); - order.setId(IdWorker.getId()); order.setOrderNo(orderNoGenerator.next()); order.setCustomerId(customerId); + order.setId(IdWorker.getId()); order.setOrderNo(orderNoGenerator.next("TRANSFER", request.operatingWarehouseId(), order.getId())); order.setCustomerId(customerId); order.setOrderType(request.needRelabel() ? RETURN_RELABEL_ORDER_TYPE : ORDER_TYPE); order.setInboundCode(request.inboundCode().trim()); order.setSourceRegion("CUSTOMER"); order.setSourceInboundId("WAREHOUSE_TRANSFER:" + transferNo); order.setRootInboundId(String.valueOf(inboundOrderId)); @@ -505,7 +505,7 @@ public class WarehouseTransferService { private void createRelabel(String customerId, String operatorId, WarehouseTransferOrder transfer, LastlegOrder order, WarehouseTransferRequests.Create request, LocalDateTime now) { long applicationId = IdWorker.getId(); - String relabelNo = "FBR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + applicationId; + String relabelNo = orderNoGenerator.next("RELABEL", request.operatingWarehouseId(), applicationId); RelabelApplication application = new RelabelApplication(); application.setId(applicationId); application.setCustomerId(customerId); application.setRelabelNo(relabelNo); application.setLastlegOrderId(order.getId()); application.setCustomerOrderNo(transfer.getTransferNo()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/trucking/service/UsTruckOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/trucking/service/UsTruckOrderService.java index fe586c4da..f6aae16a0 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/trucking/service/UsTruckOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/trucking/service/UsTruckOrderService.java @@ -39,6 +39,7 @@ import java.util.function.Consumer; @Service public class UsTruckOrderService { + @Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator; public static final String MANAGE_PERMISSION = OverseasPermissions.TRUCK_STATUS; public static final String PENDING_REVIEW = "PENDING_REVIEW"; /** Kept for source compatibility; new orders use PENDING_REVIEW. */ @@ -79,7 +80,7 @@ public class UsTruckOrderService { UsTruckOrder order = new UsTruckOrder(); order.setId(IdWorker.getId()); order.setCustomerId(customerId); - order.setOrderNo("TK" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + order.getId()); + order.setOrderNo(orderNoGenerator.next("TRUCK", request.origin(), order.getId())); order.setTransportType(StringUtils.hasText(request.transportType()) ? norm(request.transportType()) : null); order.setOrigin(trim(request.origin())); order.setOriginAddress(trim(request.originAddress())); order.setDestination(trim(request.destination())); order.setDestinationAddress(trim(request.destinationAddress())); 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 3d99adc59..884f6ac5e 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -179,6 +179,25 @@ paths: responses: '200': { description: 新增后的费用明细, content: { application/json: { schema: { $ref: '#/components/schemas/LastMileFeeApiResponse' } } } } '404': { description: 尾程订单不存在或不属于操作客户 } + /api/platform/v1/last-leg/orders/{id}/pallet-merge-required: + put: + summary: 设置尾程订单合托标记 + description: 权限 platform:overseas-warehouse:last-mile-order:edit;标记后,PDA扫描本单箱唛并解析入仓单时返回合托提示;实际完成合托后不可取消。 + security: [{ bearerAuth: [] }] + parameters: + - { name: id, in: path, required: true, schema: { type: integer, format: int64 } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [palletMergeRequired] + properties: { palletMergeRequired: { type: boolean } } + responses: + '200': { description: 合托标记已更新 } + '404': { description: 尾程订单不存在或不属于操作客户 } + '409': { description: 入仓已完成或实际合托已完成,不能修改标记 } /api/platform/v1/last-leg/dispatches: get: summary: 分页查询排车单 @@ -729,7 +748,7 @@ components: timestamp: { type: string, format: date-time } ReceivingInbound: type: object - required: [inboundCode, destinationType, plannedPalletCount, totalPackages, receivedPackages, pendingPackages] + required: [inboundCode, destinationType, plannedPalletCount, totalPackages, receivedPackages, pendingPackages, palletMergeRequired] properties: inboundCode: { type: string } destinationType: { type: string, enum: [FBA, OVERSEAS] } @@ -743,6 +762,8 @@ components: totalPackages: { type: integer } receivedPackages: { type: integer } pendingPackages: { type: integer } + palletMergeRequired: { type: boolean, description: 尾程订单是否要求PDA合托提醒 } + palletMergeHint: { type: string, nullable: true, description: 标记合托时供PDA直接展示的中文提示 } ReceivingWarehouseProfile: type: object required: [warehouseId, warehouseCode, warehouseName] @@ -810,7 +831,7 @@ components: compatible: { type: boolean } ReceivingReceipt: type: object - required: [result, containerNo, inboundCode, destinationType, palletCode, acceptedPackages, receivedPackages, pendingPackages, sessionStatus] + required: [result, containerNo, inboundCode, destinationType, palletCode, acceptedPackages, receivedPackages, pendingPackages, sessionStatus, palletMergeRequired] properties: result: { type: string } containerNo: { type: string } @@ -821,6 +842,8 @@ components: receivedPackages: { type: integer } pendingPackages: { type: integer } sessionStatus: { type: string, enum: [SIGNED, RECEIVING] } + palletMergeRequired: { type: boolean } + palletMergeHint: { type: string, nullable: true } ReceivingCandidateListApiResponse: allOf: - { $ref: '#/components/schemas/ReceivingApiResponseBase' } 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 91e56a48d..17703c2da 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 @@ -22,6 +22,7 @@ import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -73,6 +74,7 @@ class DeliveryTransferServiceTest { ReflectionTestUtils.setField(service, "deliveryOperations", deliveryOperations); ReflectionTestUtils.setField(service, "planAllocations", planAllocations); ReflectionTestUtils.setField(service, "planLines", planLines); + ReflectionTestUtils.setField(service, "orderNoGenerator", new LastlegOrderNoGenerator()); ReflectionTestUtils.setField(service, "tailOrders", tailOrders); ReflectionTestUtils.setField(service, "histories", histories); ReflectionTestUtils.setField(service, "tailOrderDetails", tailOrderDetails); @@ -102,10 +104,10 @@ class DeliveryTransferServiceTest { assertEquals(2, outbound.getValue().getPalletCount()); assertEquals(20, outbound.getValue().getPackageCount()); assertEquals("transfer-101", outbound.getValue().getIdempotencyKey()); - assertTrue(outbound.getValue().getOutboundNo().startsWith("OUT")); + assertTrue(outbound.getValue().getOutboundNo().matches("OB-WH-\\d{8}-\\d{6}")); assertEquals(outbound.getValue().getId(), delivery.getValue().getOutboundOrderId()); assertEquals("WAIT_DELIVERY", delivery.getValue().getStatus()); - assertTrue(delivery.getValue().getDeliveryNo().startsWith("DEL")); + assertTrue(delivery.getValue().getDeliveryNo().matches("DL-WH-\\d{8}-\\d{6}")); assertEquals(List.of("ONT8", "LAX9"), pallets.getAllValues().stream() .map(OutboundPalletItem::getDestinationWarehouseCode).toList()); assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream() diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/discard/service/CargoDiscardServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/discard/service/CargoDiscardServiceTest.java index ce9707e49..3fe43bd57 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/discard/service/CargoDiscardServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/discard/service/CargoDiscardServiceTest.java @@ -14,6 +14,7 @@ import com.baiyue.lastleg.dropship.entity.DropshipCarton; import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory; import com.baiyue.lastleg.entity.LastlegOrder; import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -63,6 +64,7 @@ class CargoDiscardServiceTest { ReflectionTestUtils.setField(service, "histories", histories); ReflectionTestUtils.setField(service, "inventory", inventory); ReflectionTestUtils.setField(service, "adjustments", adjustments); + ReflectionTestUtils.setField(service, "orderNoGenerator", new LastlegOrderNoGenerator()); ReflectionTestUtils.setField(service, "orders", orders); ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper()); ReflectionTestUtils.setField(service, "feePerPackage", new BigDecimal("5.00")); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java index 95e14f0e3..46a243918 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchServiceTest.java @@ -56,6 +56,7 @@ class DispatchServiceTest { ReflectionTestUtils.setField(service, "candidates", candidates); ReflectionTestUtils.setField(service, "drafts", drafts); ReflectionTestUtils.setField(service, "draftContainers", draftContainers); + ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator()); lenient().when(items.findByDispatchOrder(any(), any())).thenReturn(List.of()); } 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 b72218afc..647a6b2ac 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 @@ -155,6 +155,21 @@ class InboundStockReceiptServiceTest { assertEquals(5L, requests.getAllValues().stream().mapToLong(InventoryCommandRequests.StockIn::packages).sum()); } + @Test + void stocksInToHeldInventoryWhenInboundOrderIsDetained() { + Fixture fixture = fixture(5, 5, 5, 10); + fixture.order.setDetained(true); + when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order)); + when(inventory.stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class))) + .thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 5, 0, 0, 0, false, false)); + + service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line), + List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now()); + + verify(inventory).stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)); + verify(inventory, never()).stockIn(any(), any(), any()); + } + @Test void existingTaskReceiptMakesStockInReplayANoOp() { Fixture fixture = fixture(10, 5, 5, 10); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java index 8be1cc079..c1b436fbb 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java @@ -422,6 +422,20 @@ class ReceivingContainerServiceBehaviorTest { verifyNoInteractions(workflow, operations); } + @Test + void cartonResolutionReturnsInboundAndPalletMergeReminderFromTailLegOrder() { + stubCurrent("FBA", "CONT-A"); + LastlegOrder marked = order("FBA"); + marked.setPalletMergeFlag("REQUIRED"); + when(orders.selectById(1L)).thenReturn(marked); + + var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("IN-1#2-1")); + + assertThat(resolved.inboundCode()).isEqualTo("IN-1"); + assertThat(resolved.palletMergeRequired()).isTrue(); + assertThat(resolved.palletMergeHint()).contains("合托"); + } + @Test void fbaCartonResolutionRejectsCrossContainerAndInvalidPrefixesAsConflicts() { stubCurrent("FBA", "CONT-A"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java index 5b0395ca4..1d19eeb84 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java @@ -55,6 +55,7 @@ class InboundPlanServiceTest { ReflectionTestUtils.setField(service, "allocationMapper", allocationMapper); ReflectionTestUtils.setField(service, "orderMapper", orderMapper); ReflectionTestUtils.setField(service, "inventoryBalanceMapper", inventoryBalanceMapper); + ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator()); org.mockito.Mockito.lenient().when(lineMapper.selectList(any())).thenReturn(List.of()); org.mockito.Mockito.lenient().when(palletMapper.selectList(any())).thenReturn(List.of()); org.mockito.Mockito.lenient().when(allocationMapper.selectList(any())).thenReturn(List.of()); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java index 330c6b2fd..f769cad6e 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.server.ResponseStatusException; @@ -43,7 +44,7 @@ class LocalLooseboardServiceTest { @Mock LocalLooseboardOperationMapper operations; @Mock LastlegOrderMapper lastlegOrders; @Mock LastlegOrderStatusHistoryMapper lastlegHistories; - @Mock LastlegOrderNoGenerator lastlegOrderNoGenerator; + @Spy LastlegOrderNoGenerator lastlegOrderNoGenerator = new LastlegOrderNoGenerator(); private LocalLooseboardService service; private final Map operationRows = new HashMap<>(); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/OverseasProcurementServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/OverseasProcurementServiceTest.java index a73d2f04a..08ec9fc11 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/OverseasProcurementServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/procurement/service/OverseasProcurementServiceTest.java @@ -45,6 +45,7 @@ class OverseasProcurementServiceTest { ReflectionTestUtils.setField(service, "histories", histories); ReflectionTestUtils.setField(service, "warehouseDirectory", warehouseDirectory); ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper()); + ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator()); } @Test diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java index 97a8dad24..a6b93f678 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.server.ResponseStatusException; @@ -44,7 +45,7 @@ class RelabelApplicationServiceTest { @Mock RelabelStatusHistoryMapper histories; @Mock LastlegOrderMapper orders; @Mock LastlegOrderStatusHistoryMapper orderHistories; - @Mock LastlegOrderNoGenerator orderNoGenerator; + @Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator(); private RelabelApplicationService service; @@ -58,7 +59,6 @@ class RelabelApplicationServiceTest { ReflectionTestUtils.setField(service, "orderHistories", orderHistories); ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator); ReflectionTestUtils.setField(service, "objectMapper", mapper()); - lenient().when(orderNoGenerator.next()).thenReturn("LMO-RELABEL-1"); } @Test @@ -91,10 +91,10 @@ class RelabelApplicationServiceTest { assertEquals("PENDING_ASSIGNMENT", tailOrder.getValue().getCurrentStatus()); assertEquals("RELABEL:PENDING_PROCESSING", tailOrder.getValue().getSubOrderStatus()); assertEquals("CUSTOMER", tailOrder.getValue().getSourceRegion()); - assertEquals("LMO-RELABEL-1", tailOrder.getValue().getOrderNo()); + assertTrue(tailOrder.getValue().getOrderNo().matches("RL-WH-\\d{8}-\\d{6}")); assertEquals(tailOrder.getValue().getInboundCode(), tailOrder.getValue().getSourceInboundId()); assertEquals(tailOrder.getValue().getInboundCode(), application.getValue().getRelabelNo()); - assertTrue(tailOrder.getValue().getInboundCode().startsWith("FBR")); + assertTrue(tailOrder.getValue().getInboundCode().startsWith("RL-")); assertEquals(10, tailOrder.getValue().getPackageCount()); assertEquals(6, application.getValue().getNotifiedPackageCount()); assertEquals("SKU-A", relabelItem.getValue().getSkuCode()); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderNoGeneratorTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderNoGeneratorTest.java index 410b21505..efdb4bd62 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderNoGeneratorTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderNoGeneratorTest.java @@ -7,13 +7,19 @@ import static org.junit.jupiter.api.Assertions.*; class LastlegOrderNoGeneratorTest { @Test void actualContainerNumberIsStableAndShared() { LastlegOrderNoGenerator generator = new LastlegOrderNoGenerator(); - assertEquals("LMO2087379674555461633", generator.forContainerNo("2087379674555461633")); + assertTrue(generator.forContainerNo("2087379674555461633").matches("FL-WH-\\d{8}-\\d{6}")); assertEquals(generator.forContainerNo("MSCU1"), new LastlegOrderNoGenerator().forContainerNo("MSCU1")); assertNotEquals(generator.forContainerNo("MSCU1"), generator.forContainerNo("CAB1")); assertTrue(generator.forContainerNo("MSCU1234567890").length() <= 32); assertThrows(IllegalArgumentException.class, () -> generator.forContainerNo(" ")); } + @Test void formatsBusinessWarehouseAndDate() { + String value = new LastlegOrderNoGenerator().next("OUTBOUND", "warehouse-LAX-01", 123L); + assertTrue(value.matches("OB-LAX-\\d{8}-000123"), value); + assertTrue(value.length() <= 24); + } + @Test void remainsUniqueAcrossInstancesAndConcurrentCalls() throws Exception { LastlegOrderNoGenerator beforeRestart=new LastlegOrderNoGenerator(); String first=beforeRestart.next(); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java index 6746ae723..6284fcb56 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java @@ -6,6 +6,7 @@ import com.baiyue.lastleg.entity.LastlegOrderDetail; import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper; +import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; @@ -27,6 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,6 +42,7 @@ class LastlegOrderServiceTest { @Mock LastlegOrderFeeMapper fees; @Mock LastlegOrderOperationLogMapper operations; @Mock InboundPlanCartonMapper cartons; + @Mock InventoryCommandService inventory; LastlegOrderService service; @BeforeEach @@ -50,6 +54,7 @@ class LastlegOrderServiceTest { ReflectionTestUtils.setField(service, "feeMapper", fees); ReflectionTestUtils.setField(service, "operationLogMapper", operations); ReflectionTestUtils.setField(service, "cartonMapper", cartons); + ReflectionTestUtils.setField(service, "inventory", inventory); } @Test @@ -115,6 +120,37 @@ class LastlegOrderServiceTest { verify(operations).insert(any(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class)); } + @Test + void palletMergeMarkIsPersistedAndAuditedForPdaReminder() { + LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1"); + when(orders.selectOne(any())).thenReturn(order); + when(orders.lockById("100", 11L)).thenReturn(order); + when(orders.updatePalletMergeFlag(any(), anyLong(), any(), any())).thenReturn(1); + + service.setPalletMergeRequired("100", 11L, + new LastlegOrderRequests.SetPalletMergeRequired(true), "platform_admin"); + + verify(orders).updatePalletMergeFlag(org.mockito.ArgumentMatchers.eq("100"), + org.mockito.ArgumentMatchers.eq(11L), org.mockito.ArgumentMatchers.eq("REQUIRED"), any()); + ArgumentCaptor operation = + ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class); + verify(operations).insert(operation.capture()); + assertEquals("标记合托", operation.getValue().getOperationNode()); + assertTrue(operation.getValue().getResult().contains("PDA")); + } + + @Test + void completedPhysicalMergeCannotBeUnmarked() { + LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1"); + order.setPalletMergeFlag("MERGED"); + when(orders.selectOne(any())).thenReturn(order); + when(orders.lockById("100", 11L)).thenReturn(order); + + assertThrows(ResponseStatusException.class, () -> service.setPalletMergeRequired("100", 11L, + new LastlegOrderRequests.SetPalletMergeRequired(false), "platform_admin")); + verify(orders, never()).updatePalletMergeFlag(any(), anyLong(), any(), any()); + } + @Test void transferRejectsAnOrderAlreadyBoundToInboundPlan() { LastlegOrder order = order(11L, "100", "DESTINATION_ASSIGNED", "IB-1"); @@ -157,6 +193,38 @@ class LastlegOrderServiceTest { assertEquals(25, page.getValue().getSize()); } + @Test + void holdingBeforeInboundPersistsTheOrderMarkerAndOperationRemark() { + LastlegOrder order = order(11L, "100", "PENDING_INBOUND", null); + when(orders.selectOne(any())).thenReturn(order); + when(orders.lockById("100", 11L)).thenReturn(order); + + service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(true, "等待质检"), "operator"); + + assertTrue(order.getDetained()); + verify(orders).updateById(order); + ArgumentCaptor operation = + ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class); + verify(operations).insert(operation.capture()); + assertEquals("暂扣", operation.getValue().getOperationNode()); + assertEquals("等待质检", operation.getValue().getResult()); + verify(inventory, never()).releaseInboundOrderHold(any(), any(), anyLong(), any(), any()); + } + + @Test + void releasingAfterInboundMovesAllOrderHeldInventoryToAvailable() { + LastlegOrder order = order(11L, "100", "INBOUND_COMPLETED", "IP-1"); + order.setOrderNo("LM-1"); + order.setDetained(true); + when(orders.selectOne(any())).thenReturn(order); + when(orders.lockById("100", 11L)).thenReturn(order); + + service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(false, "质检通过"), "operator"); + + verify(inventory).releaseInboundOrderHold("100", "operator", 11L, "LM-1", "质检通过"); + assertEquals(false, order.getDetained()); + } + private static LastlegOrder order(long id, String customerId, String status, String inboundOrderNo) { LastlegOrder order = new LastlegOrder(); order.setId(id); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java index df28e085b..32f23b8bd 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.server.ResponseStatusException; @@ -70,7 +71,7 @@ class WarehouseTransferServiceTest { @Mock RelabelApplicationMapper relabelApplications; @Mock RelabelItemMapper relabelItems; @Mock RelabelStatusHistoryMapper relabelHistories; - @Mock LastlegOrderNoGenerator orderNoGenerator; + @Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator(); @Mock InventoryCommandService inventory; @Mock InventoryCommandMapper inventoryCommands; private WarehouseTransferService service; @@ -116,7 +117,7 @@ class WarehouseTransferServiceTest { assertEquals(WarehouseTransferService.RETURN_RELABEL_ORDER_TYPE, tailOrder.getValue().getOrderType()); assertEquals("PENDING_ASSIGNMENT", tailOrder.getValue().getCurrentStatus()); assertEquals("WAREHOUSE_TRANSFER:PENDING_REVIEW;RELABEL:PENDING_PROCESSING", tailOrder.getValue().getSubOrderStatus()); - assertEquals("LMO-TRANSFER-1", tailOrder.getValue().getOrderNo()); + assertTrue(tailOrder.getValue().getOrderNo().matches("WT-WH-\\d{8}-\\d{6}")); assertNull(tailOrder.getValue().getTransferWarehouseId()); verify(sourceOrders).insert(any(WarehouseTransferSourceOrder.class)); verify(relabelApplications).insert(any(RelabelApplication.class));