feat: explain supplemental pallet counts

This commit is contained in:
2026-09-11 15:39:38 +08:00
parent cc7c2f0954
commit 774f0472e8
7 changed files with 89 additions and 34 deletions
+2
View File
@@ -18,6 +18,8 @@
人工计划自动生成托盘与资源重试:`inboundplan/controller/InboundPlanController.java` → `inboundplan/service/ContainerInboundPlanConversionCoordinator.java` 的 `convertManualAndAutoMaterialize`;保存单票后重读全柜预览,全员为 `PLANNED/RESOURCE_PENDING`、预览就绪且存在待绑定资源时自动调用柜级 `confirm`。一票柜立即触发,多票柜在最后一票齐全后触发;资源异常沿 `resourcePendingPlanCount` 和原柜级预览/确认入口重试,不新增独立正常人工步骤。回归入口为 `src/test/java/com/baiyue/lastleg/inboundplan/service/ContainerInboundPlanConversionCoordinatorTest.java`。 人工计划自动生成托盘与资源重试:`inboundplan/controller/InboundPlanController.java` → `inboundplan/service/ContainerInboundPlanConversionCoordinator.java` 的 `convertManualAndAutoMaterialize`;保存单票后重读全柜预览,全员为 `PLANNED/RESOURCE_PENDING`、预览就绪且存在待绑定资源时自动调用柜级 `confirm`。一票柜立即触发,多票柜在最后一票齐全后触发;资源异常沿 `resourcePendingPlanCount` 和原柜级预览/确认入口重试,不新增独立正常人工步骤。回归入口为 `src/test/java/com/baiyue/lastleg/inboundplan/service/ContainerInboundPlanConversionCoordinatorTest.java`。
柜级预览同时返回 `sourcePalletCount/supplementPalletCount/targetPalletCount`:头程冻结托盘码原样保留,海外仓最终托数较多时按同柜同 FBA 编码序列补足差额,不因头尾装托密度不同阻断;页面必须分别展示头程已有、海外补充和最终托数,不能把最终总数误称为“本次新增”。
头程来源托盘快照:`service/LoadingCompletedConsumer.java`、`mapper/LastlegSourcePalletMapper.java`;来源标签随装柜完成事件落入 `ll_order_source_pallet`,后续由 `inboundplan/service/InboundPlanService.java` 与 `ContainerInboundPlanConversionCoordinator.java` 传递给海外资源批量预留。排查标签跨区丢失时沿此链路核对,不能在尾程页面重新生成头程托盘码。 头程来源托盘快照:`service/LoadingCompletedConsumer.java`、`mapper/LastlegSourcePalletMapper.java`;来源标签随装柜完成事件落入 `ll_order_source_pallet`,后续由 `inboundplan/service/InboundPlanService.java` 与 `ContainerInboundPlanConversionCoordinator.java` 传递给海外资源批量预留。排查标签跨区丢失时沿此链路核对,不能在尾程页面重新生成头程托盘码。
| 功能 | 入口 | | 功能 | 入口 |
@@ -17,6 +17,8 @@ public record ContainerInboundPlanPreviewResponse(
int blockedOrderCount, int blockedOrderCount,
int pendingOrderCount, int pendingOrderCount,
int pendingPalletCount, int pendingPalletCount,
int sourcePalletCount,
int supplementPalletCount,
int existingPlannedPalletCount, int existingPlannedPalletCount,
int totalSuggestedPalletCount, int totalSuggestedPalletCount,
int totalPlannedPalletCount, int totalPlannedPalletCount,
@@ -31,6 +33,8 @@ public record ContainerInboundPlanPreviewResponse(
int orderCount, int orderCount,
int pendingOrderCount, int pendingOrderCount,
int pendingPalletCount, int pendingPalletCount,
int sourcePalletCount,
int supplementPalletCount,
int existingPlannedPalletCount, int existingPlannedPalletCount,
int suggestedPalletCount, int suggestedPalletCount,
int plannedPalletCount, int plannedPalletCount,
@@ -52,6 +56,9 @@ public record ContainerInboundPlanPreviewResponse(
int packageCount, int packageCount,
BigDecimal volumeCbm, BigDecimal volumeCbm,
Integer suggestedPalletCount, Integer suggestedPalletCount,
int sourcePalletCount,
int targetPalletCount,
int supplementPalletCount,
Integer plannedPalletCount, Integer plannedPalletCount,
String existingPlanNo, String existingPlanNo,
String planStatus, String planStatus,
@@ -27,7 +27,7 @@ public interface InboundPlanOrderMapper {
String overseasWarehouseName, String fbaWarehouseCode, String destinationWarehouseId, String overseasWarehouseName, String fbaWarehouseCode, String destinationWarehouseId,
String destinationWarehouseCode, String destinationWarehouseName, int destinationCount, String destinationWarehouseCode, String destinationWarehouseName, int destinationCount,
boolean detained, int version, String planNo, String planStatus, boolean detained, int version, String planNo, String planStatus,
Integer plannedPalletCount, Integer materializedPalletCount Integer plannedPalletCount, Integer materializedPalletCount, Integer sourcePalletCount
) { } ) { }
@Select("SELECT o.customer_id AS customerId,o.id AS orderId,o.order_no AS orderNo," + @Select("SELECT o.customer_id AS customerId,o.id AS orderId,o.order_no AS orderNo," +
@@ -45,7 +45,9 @@ public interface InboundPlanOrderMapper {
"o.detained AS detained,o.version AS version,p.plan_no AS planNo,p.status AS planStatus," + "o.detained AS detained,o.version AS version,p.plan_no AS planNo,p.status AS planStatus," +
"p.pallet_count AS plannedPalletCount," + "p.pallet_count AS plannedPalletCount," +
"(SELECT COUNT(*) FROM ll_inbound_plan_pallet pp WHERE pp.customer_id=p.customer_id " + "(SELECT COUNT(*) FROM ll_inbound_plan_pallet pp WHERE pp.customer_id=p.customer_id " +
"AND pp.plan_id=p.id AND pp.warehouse_pallet_id IS NOT NULL) AS materializedPalletCount " + "AND pp.plan_id=p.id AND pp.warehouse_pallet_id IS NOT NULL) AS materializedPalletCount," +
"(SELECT COUNT(*) FROM ll_order_source_pallet sp WHERE sp.customer_id=o.customer_id " +
"AND sp.order_id=o.id) AS sourcePalletCount " +
"FROM ll_order o LEFT JOIN ll_inbound_plan p ON p.customer_id=o.customer_id AND p.source_order_id=o.id " + "FROM ll_order o LEFT JOIN ll_inbound_plan p ON p.customer_id=o.customer_id AND p.source_order_id=o.id " +
"WHERE o.source_container_booking_id=#{sourceContainerBookingId} ORDER BY o.customer_id,o.id") "WHERE o.source_container_booking_id=#{sourceContainerBookingId} ORDER BY o.customer_id,o.id")
List<ContainerPreviewOrderRow> containerPreviewOrders( List<ContainerPreviewOrderRow> containerPreviewOrders(
@@ -207,6 +207,10 @@ public class InboundPlanService {
items.stream().filter(InboundPlanService::requiresContainerConversion) items.stream().filter(InboundPlanService::requiresContainerConversion)
.map(InboundPlanService::pendingPalletCount) .map(InboundPlanService::pendingPalletCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum(), .filter(Objects::nonNull).mapToInt(Integer::intValue).sum(),
items.stream().filter(InboundPlanService::requiresContainerConversion)
.mapToInt(ContainerInboundPlanPreviewResponse.Order::sourcePalletCount).sum(),
items.stream().filter(InboundPlanService::requiresContainerConversion)
.mapToInt(ContainerInboundPlanPreviewResponse.Order::supplementPalletCount).sum(),
items.stream().filter(item -> "PLANNED".equals(item.readiness())) items.stream().filter(item -> "PLANNED".equals(item.readiness()))
.map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount) .map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum(), .filter(Objects::nonNull).mapToInt(Integer::intValue).sum(),
@@ -220,6 +224,10 @@ public class InboundPlanService {
int pendingPallets = orders.stream().filter(InboundPlanService::requiresContainerConversion) int pendingPallets = orders.stream().filter(InboundPlanService::requiresContainerConversion)
.map(InboundPlanService::pendingPalletCount) .map(InboundPlanService::pendingPalletCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum(); .filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
int sourcePallets = orders.stream().filter(InboundPlanService::requiresContainerConversion)
.mapToInt(ContainerInboundPlanPreviewResponse.Order::sourcePalletCount).sum();
int supplementPallets = orders.stream().filter(InboundPlanService::requiresContainerConversion)
.mapToInt(ContainerInboundPlanPreviewResponse.Order::supplementPalletCount).sum();
int existingPallets = orders.stream().filter(item -> "PLANNED".equals(item.readiness())) int existingPallets = orders.stream().filter(item -> "PLANNED".equals(item.readiness()))
.map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount) .map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum(); .filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
@@ -228,7 +236,7 @@ public class InboundPlanService {
bookingId, containerNo, warehouseId, snapshotVersion, orders)); bookingId, containerNo, warehouseId, snapshotVersion, orders));
return new ContainerInboundPlanPreviewResponse(bookingId, containerNo, warehouseId, warehouseCode, return new ContainerInboundPlanPreviewResponse(bookingId, containerNo, warehouseId, warehouseCode,
warehouseName, snapshotVersion, snapshotHash, orders.size(), orders.size() - blocked, blocked, warehouseName, snapshotVersion, snapshotHash, orders.size(), orders.size() - blocked, blocked,
pendingOrders, pendingPallets, existingPallets, pendingOrders, pendingPallets, sourcePallets, supplementPallets, existingPallets,
orders.stream().map(ContainerInboundPlanPreviewResponse.Order::suggestedPalletCount) orders.stream().map(ContainerInboundPlanPreviewResponse.Order::suggestedPalletCount)
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum(), .filter(Objects::nonNull).mapToInt(Integer::intValue).sum(),
orders.stream().map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount) orders.stream().map(ContainerInboundPlanPreviewResponse.Order::plannedPalletCount)
@@ -258,6 +266,12 @@ public class InboundPlanService {
|| row.packageCount() > 0 && row.volumeCbm().signum() == 0) blockers.add("CARGO_FACT_INVALID"); || row.packageCount() > 0 && row.volumeCbm().signum() == 0) blockers.add("CARGO_FACT_INVALID");
} }
Integer suggested = suggestedPalletCount(row, blockers); Integer suggested = suggestedPalletCount(row, blockers);
int sourcePalletCount = "ONE_CLICK_DROPSHIP".equals(row.orderType())
? Objects.requireNonNullElse(suggested, 0)
: Objects.requireNonNullElse(row.sourcePalletCount(), 0);
int targetPalletCount = Objects.requireNonNullElse(row.plannedPalletCount(),
Objects.requireNonNullElse(suggested, 0));
int supplementPalletCount = Math.max(0, targetPalletCount - sourcePalletCount);
if (!planned && (suggested == null || suggested <= 0)) blockers.add("PALLET_COUNT_NOT_READY"); if (!planned && (suggested == null || suggested <= 0)) blockers.add("PALLET_COUNT_NOT_READY");
String destinationType = destinationType(row); String destinationType = destinationType(row);
String fbaWarehouseCode = isFbaDestination(row) ? trimToNull(row.fbaWarehouseCode()) : null; String fbaWarehouseCode = isFbaDestination(row) ? trimToNull(row.fbaWarehouseCode()) : null;
@@ -281,7 +295,8 @@ public class InboundPlanService {
String.valueOf(row.orderId()), trimToNull(row.orderNo()) == null ? row.sourceOrderNo() : row.orderNo(), String.valueOf(row.orderId()), trimToNull(row.orderNo()) == null ? row.sourceOrderNo() : row.orderNo(),
row.inboundCode(), row.orderType(), destinationType, row.destinationWarehouseId(), row.inboundCode(), row.orderType(), destinationType, row.destinationWarehouseId(),
destinationCode, row.destinationWarehouseName(), fbaWarehouseCode, row.packageCount(), row.volumeCbm(), destinationCode, row.destinationWarehouseName(), fbaWarehouseCode, row.packageCount(), row.volumeCbm(),
suggested, row.plannedPalletCount(), row.planNo(), row.planStatus(), row.version(), readiness, suggested, sourcePalletCount, targetPalletCount, supplementPalletCount,
row.plannedPalletCount(), row.planNo(), row.planStatus(), row.version(), readiness,
List.copyOf(blockers)); List.copyOf(blockers));
} }
@@ -321,9 +336,7 @@ public class InboundPlanService {
} }
private static Integer pendingPalletCount(ContainerInboundPlanPreviewResponse.Order order) { private static Integer pendingPalletCount(ContainerInboundPlanPreviewResponse.Order order) {
return "RESOURCE_PENDING".equals(order.readiness()) return order.targetPalletCount();
? Objects.requireNonNullElse(order.plannedPalletCount(), 0)
: Objects.requireNonNullElse(order.suggestedPalletCount(), 0);
} }
private static String destinationType(InboundPlanOrderMapper.ContainerPreviewOrderRow row) { private static String destinationType(InboundPlanOrderMapper.ContainerPreviewOrderRow row) {
@@ -169,7 +169,7 @@ paths:
/api/platform/v1/last-leg/orders: /api/platform/v1/last-leg/orders:
get: get:
summary: 分页查询尾程订单 summary: 分页查询尾程订单
description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。 description: 权限 platform:overseas-warehouse:last-mile-order:list;平台管理员省略 customerId 时查询全部客户。containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
security: [{ bearerAuth: [] }] security: [{ bearerAuth: [] }]
parameters: parameters:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } } - { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
@@ -190,7 +190,7 @@ paths:
/api/platform/v1/last-leg/orders/{id}: /api/platform/v1/last-leg/orders/{id}:
get: get:
summary: 查询尾程订单详情 summary: 查询尾程订单详情
description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。 description: 权限 platform:overseas-warehouse:last-mile-order:list;返回订单、货物、状态历史、费用明细和操作记录。订单中的 containerNode 来自稳定 sourceContainerBookingId 对应的柜级船节点门禁。
security: [{ bearerAuth: [] }] security: [{ bearerAuth: [] }]
parameters: parameters:
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } } - { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
@@ -2806,7 +2806,7 @@ components:
version: { type: integer } version: { type: integer }
ContainerInboundPlanPreview: ContainerInboundPlanPreview:
type: object type: object
required: [sourceContainerBookingId, snapshotVersion, snapshotHash, totalOrderCount, readyOrderCount, blockedOrderCount, pendingOrderCount, pendingPalletCount, existingPlannedPalletCount, totalSuggestedPalletCount, totalPlannedPalletCount, ready, groups] required: [sourceContainerBookingId, snapshotVersion, snapshotHash, totalOrderCount, readyOrderCount, blockedOrderCount, pendingOrderCount, pendingPalletCount, sourcePalletCount, supplementPalletCount, existingPlannedPalletCount, totalSuggestedPalletCount, totalPlannedPalletCount, ready, groups]
properties: properties:
sourceContainerBookingId: { type: string } sourceContainerBookingId: { type: string }
containerNo: { type: string, nullable: true } containerNo: { type: string, nullable: true }
@@ -2819,7 +2819,9 @@ components:
readyOrderCount: { type: integer } readyOrderCount: { type: integer }
blockedOrderCount: { type: integer } blockedOrderCount: { type: integer }
pendingOrderCount: { type: integer, description: 本次确认将新建计划的 READY 订单数 } pendingOrderCount: { type: integer, description: 本次确认将新建计划的 READY 订单数 }
pendingPalletCount: { type: integer, description: 本次确认将向资源服务批量预留的新托盘数 } pendingPalletCount: { type: integer, description: 本次确认最终向资源服务批量预留的托盘总数,包含头程已有编码及海外补充编码 }
sourcePalletCount: { type: integer, description: 本次处理订单随头程装柜完成事件冻结并传入的托盘码数 }
supplementPalletCount: { type: integer, description: 最终托数超过头程已有托盘码数时,在海外仓连续补充的托盘码数 }
existingPlannedPalletCount: { type: integer, description: 已有有效计划的托盘总数,不计入本次新增 } existingPlannedPalletCount: { type: integer, description: 已有有效计划的托盘总数,不计入本次新增 }
totalSuggestedPalletCount: { type: integer } totalSuggestedPalletCount: { type: integer }
totalPlannedPalletCount: { type: integer } totalPlannedPalletCount: { type: integer }
@@ -2827,7 +2829,7 @@ components:
groups: { type: array, items: { $ref: '#/components/schemas/ContainerInboundPlanPreviewGroup' } } groups: { type: array, items: { $ref: '#/components/schemas/ContainerInboundPlanPreviewGroup' } }
ContainerInboundPlanPreviewGroup: ContainerInboundPlanPreviewGroup:
type: object type: object
required: [destinationType, orderCount, pendingOrderCount, pendingPalletCount, existingPlannedPalletCount, suggestedPalletCount, plannedPalletCount, orders] required: [destinationType, orderCount, pendingOrderCount, pendingPalletCount, sourcePalletCount, supplementPalletCount, existingPlannedPalletCount, suggestedPalletCount, plannedPalletCount, orders]
properties: properties:
destinationType: { type: string, enum: [FBA, ONE_CLICK_DROPSHIP, PRIVATE_ADDRESS] } destinationType: { type: string, enum: [FBA, ONE_CLICK_DROPSHIP, PRIVATE_ADDRESS] }
destinationWarehouseId: { type: string, nullable: true } destinationWarehouseId: { type: string, nullable: true }
@@ -2836,13 +2838,15 @@ components:
orderCount: { type: integer } orderCount: { type: integer }
pendingOrderCount: { type: integer } pendingOrderCount: { type: integer }
pendingPalletCount: { type: integer } pendingPalletCount: { type: integer }
sourcePalletCount: { type: integer }
supplementPalletCount: { type: integer }
existingPlannedPalletCount: { type: integer } existingPlannedPalletCount: { type: integer }
suggestedPalletCount: { type: integer } suggestedPalletCount: { type: integer }
plannedPalletCount: { type: integer } plannedPalletCount: { type: integer }
orders: { type: array, items: { $ref: '#/components/schemas/ContainerInboundPlanPreviewOrder' } } orders: { type: array, items: { $ref: '#/components/schemas/ContainerInboundPlanPreviewOrder' } }
ContainerInboundPlanPreviewOrder: ContainerInboundPlanPreviewOrder:
type: object type: object
required: [customerId, orderId, businessOrderNo, orderType, destinationType, packageCount, volumeCbm, orderVersion, readiness, blockers] required: [customerId, orderId, businessOrderNo, orderType, destinationType, packageCount, volumeCbm, sourcePalletCount, targetPalletCount, supplementPalletCount, orderVersion, readiness, blockers]
properties: properties:
customerId: { type: string } customerId: { type: string }
customerName: { type: string, nullable: true } customerName: { type: string, nullable: true }
@@ -2858,11 +2862,14 @@ components:
packageCount: { type: integer } packageCount: { type: integer }
volumeCbm: { type: number } volumeCbm: { type: number }
suggestedPalletCount: { type: integer, nullable: true } suggestedPalletCount: { type: integer, nullable: true }
sourcePalletCount: { type: integer, description: 头程已经冻结并传入的托盘码数 }
targetPalletCount: { type: integer, description: 本票最终生成的托盘总数,不少于头程已有托盘码数 }
supplementPalletCount: { type: integer, description: 本票需要在海外仓连续补充的托盘码数 }
plannedPalletCount: { type: integer, nullable: true } plannedPalletCount: { type: integer, nullable: true }
existingPlanNo: { type: string, nullable: true } existingPlanNo: { type: string, nullable: true }
planStatus: { type: string, nullable: true } planStatus: { type: string, nullable: true }
orderVersion: { type: integer, minimum: 0 } orderVersion: { type: integer, minimum: 0 }
readiness: { type: string, enum: [READY, PLANNED, BLOCKED] } readiness: { type: string, enum: [READY, RESOURCE_PENDING, PLANNED, BLOCKED] }
blockers: { type: array, items: { type: string } } blockers: { type: array, items: { type: string } }
InboundPlanLine: InboundPlanLine:
type: object type: object
@@ -87,7 +87,7 @@ class ContainerInboundPlanConversionCoordinatorTest {
var ready = new ContainerInboundPlanPreviewResponse.Order( var ready = new ContainerInboundPlanPreviewResponse.Order(
"200", "Customer 200", "12", "LM-12", "RK-2", "FIRST_LEG", "FBA", "200", "Customer 200", "12", "LM-12", "RK-2", "FIRST_LEG", "FBA",
null, "ONT8", "Amazon ONT8", "ONT8", 10, BigDecimal.ONE, 1, null, "ONT8", "Amazon ONT8", "ONT8", 10, BigDecimal.ONE, 1,
null, null, null, 0, "READY", List.of()); 0, 1, 1, null, null, null, 0, "READY", List.of());
when(plans.convertManual(request, "manual-key", "7")).thenReturn(logical); when(plans.convertManual(request, "manual-key", "7")).thenReturn(logical);
when(plans.sourceContainerBookingId("100", 11L)).thenReturn(BOOKING_ID); when(plans.sourceContainerBookingId("100", 11L)).thenReturn(BOOKING_ID);
when(plans.previewContainer(BOOKING_ID)).thenReturn(manualPreview(List.of(resourcePendingOrder(), ready))); when(plans.previewContainer(BOOKING_ID)).thenReturn(manualPreview(List.of(resourcePendingOrder(), ready)));
@@ -272,11 +272,11 @@ class ContainerInboundPlanConversionCoordinatorTest {
var fba = previewOrder("100", "11", "RK-FBA", "ABE3", "ABE3"); var fba = previewOrder("100", "11", "RK-FBA", "ABE3", "ABE3");
var overseas = previewOrder("200", "12", "RK-OVERSEAS", "US-LAX-01", null); var overseas = previewOrder("200", "12", "RK-OVERSEAS", "US-LAX-01", null);
var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, 2, 2, 0, 2, 2, 0, 2, 0, true, "Los Angeles", 7L, SNAPSHOT_HASH, 2, 2, 0, 2, 2, 2, 0, 0, 2, 0, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("ONE_CLICK_DROPSHIP", null, "ABE3", "Amazon ABE3", List.of(new ContainerInboundPlanPreviewResponse.Group("ONE_CLICK_DROPSHIP", null, "ABE3", "Amazon ABE3",
1, 1, 1, 0, 1, 0, List.of(fba)), 1, 1, 1, 1, 0, 0, 1, 0, List.of(fba)),
new ContainerInboundPlanPreviewResponse.Group("ONE_CLICK_DROPSHIP", "31", "US-LAX-01", "Los Angeles", new ContainerInboundPlanPreviewResponse.Group("ONE_CLICK_DROPSHIP", "31", "US-LAX-01", "Los Angeles",
1, 1, 1, 0, 1, 0, List.of(overseas)))); 1, 1, 1, 1, 0, 0, 1, 0, List.of(overseas))));
when(plans.previewContainer(BOOKING_ID)).thenReturn(preview); when(plans.previewContainer(BOOKING_ID)).thenReturn(preview);
when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class))) when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class)))
.thenReturn("IP-FBA", "IP-OVERSEAS"); .thenReturn("IP-FBA", "IP-OVERSEAS");
@@ -305,11 +305,11 @@ class ContainerInboundPlanConversionCoordinatorTest {
var overseas = new ContainerInboundPlanPreviewResponse.Order( var overseas = new ContainerInboundPlanPreviewResponse.Order(
"200", "Customer 200", "12", "LM-12", "RK-OVERSEAS", "FIRST_LEG", "FIRST_LEG", "200", "Customer 200", "12", "LM-12", "RK-OVERSEAS", "FIRST_LEG", "FIRST_LEG",
"31", "US-LAX-01", "Los Angeles", null, 10, BigDecimal.ONE, 2, "31", "US-LAX-01", "Los Angeles", null, 10, BigDecimal.ONE, 2,
null, null, null, 0, "READY", List.of()); 0, 2, 2, null, null, null, 0, "READY", List.of());
var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 2, 2, 0, 1, 0, true, "Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 1, 2, 0, 2, 0, 1, 0, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("FIRST_LEG", "31", "US-LAX-01", "Los Angeles", List.of(new ContainerInboundPlanPreviewResponse.Group("FIRST_LEG", "31", "US-LAX-01", "Los Angeles",
1, 1, 2, 0, 2, 0, List.of(overseas)))); 1, 1, 2, 0, 2, 0, 2, 0, List.of(overseas))));
when(plans.previewContainer(BOOKING_ID)).thenReturn(preview); when(plans.previewContainer(BOOKING_ID)).thenReturn(preview);
when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class))).thenReturn("IP-OVERSEAS"); when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class))).thenReturn("IP-OVERSEAS");
when(plans.sourcePalletNos("200", 12L, "FIRST_LEG")).thenReturn(List.of()); when(plans.sourcePalletNos("200", 12L, "FIRST_LEG")).thenReturn(List.of());
@@ -337,11 +337,11 @@ class ContainerInboundPlanConversionCoordinatorTest {
var order = new ContainerInboundPlanPreviewResponse.Order( var order = new ContainerInboundPlanPreviewResponse.Order(
"100", "Customer 100", "11", "LM-11", "RK-FBA", "FIRST_LEG", "FBA", "100", "Customer 100", "11", "LM-11", "RK-FBA", "FIRST_LEG", "FBA",
null, "ACY2", "Amazon ACY2", "ACY2", 20, new BigDecimal("5.8000"), 3, null, "ACY2", "Amazon ACY2", "ACY2", 20, new BigDecimal("5.8000"), 3,
null, null, null, 0, "READY", List.of()); 2, 3, 1, null, null, null, 0, "READY", List.of());
var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 1, 3, 0, 3, 0, true, "Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 1, 3, 2, 1, 0, 3, 0, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2", List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2",
1, 1, 3, 0, 3, 0, List.of(order)))); 1, 1, 3, 2, 1, 0, 3, 0, List.of(order))));
when(plans.previewContainer(BOOKING_ID)).thenReturn(preview); when(plans.previewContainer(BOOKING_ID)).thenReturn(preview);
when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class))).thenReturn("IP-ACY2"); when(numbers.next(eq("INBOUND_PLAN"), any(String.class), any(Long.class))).thenReturn("IP-ACY2");
when(plans.sourcePalletNos("100", 11L, "FIRST_LEG")).thenReturn(List.of( when(plans.sourcePalletNos("100", 11L, "FIRST_LEG")).thenReturn(List.of(
@@ -369,11 +369,11 @@ class ContainerInboundPlanConversionCoordinatorTest {
var order = new ContainerInboundPlanPreviewResponse.Order( var order = new ContainerInboundPlanPreviewResponse.Order(
"100", "Customer 100", "11", "LM-11", "RK-FBA", "FIRST_LEG", "FBA", "100", "Customer 100", "11", "LM-11", "RK-FBA", "FIRST_LEG", "FBA",
null, "ACY2", "Amazon ACY2", "ACY2", 20, new BigDecimal("5.8000"), 3, null, "ACY2", "Amazon ACY2", "ACY2", 20, new BigDecimal("5.8000"), 3,
3, "IP-MANUAL", "PENDING_ASSIGNMENT", 1, "RESOURCE_PENDING", List.of()); 2, 3, 1, 3, "IP-MANUAL", "PENDING_ASSIGNMENT", 1, "RESOURCE_PENDING", List.of());
var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", var preview = new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 1, 3, 3, 3, 3, true, "Los Angeles", 7L, SNAPSHOT_HASH, 1, 1, 0, 1, 3, 2, 1, 3, 3, 3, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2", List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2",
1, 1, 3, 3, 3, 3, List.of(order)))); 1, 1, 3, 2, 1, 3, 3, 3, List.of(order))));
when(plans.previewContainer(BOOKING_ID)).thenReturn(preview); when(plans.previewContainer(BOOKING_ID)).thenReturn(preview);
when(plans.sourcePalletNos("100", 11L, "FIRST_LEG")).thenReturn(List.of( when(plans.sourcePalletNos("100", 11L, "FIRST_LEG")).thenReturn(List.of(
"MSCU6639870-101-001", "MSCU6639870-101-002")); "MSCU6639870-101-001", "MSCU6639870-101-002"));
@@ -404,7 +404,8 @@ class ContainerInboundPlanConversionCoordinatorTest {
"LM-" + orderId, inboundCode, "ONE_CLICK_DROPSHIP", "ONE_CLICK_DROPSHIP", "LM-" + orderId, inboundCode, "ONE_CLICK_DROPSHIP", "ONE_CLICK_DROPSHIP",
fbaWarehouseCode == null ? "31" : null, destinationCode, fbaWarehouseCode == null ? "31" : null, destinationCode,
fbaWarehouseCode == null ? "Los Angeles" : "Amazon " + destinationCode, fbaWarehouseCode == null ? "Los Angeles" : "Amazon " + destinationCode,
fbaWarehouseCode, 10, BigDecimal.ONE, 1, null, null, null, 0, "READY", List.of()); fbaWarehouseCode, 10, BigDecimal.ONE, 1, 1, 1, 0,
null, null, null, 0, "READY", List.of());
} }
private ManualInboundPlanRequest manualRequest() { private ManualInboundPlanRequest manualRequest() {
@@ -416,7 +417,7 @@ class ContainerInboundPlanConversionCoordinatorTest {
return new ContainerInboundPlanPreviewResponse.Order( return new ContainerInboundPlanPreviewResponse.Order(
"100", "Customer 100", "11", "LM-11", "RK-1", "FIRST_LEG", "FBA", "100", "Customer 100", "11", "LM-11", "RK-1", "FIRST_LEG", "FBA",
null, "ACY2", "Amazon ACY2", "ACY2", 10, BigDecimal.ONE, 1, null, "ACY2", "Amazon ACY2", "ACY2", 10, BigDecimal.ONE, 1,
1, "IP-MANUAL", "PENDING_ASSIGNMENT", 1, "RESOURCE_PENDING", List.of()); 1, 1, 0, 1, "IP-MANUAL", "PENDING_ASSIGNMENT", 1, "RESOURCE_PENDING", List.of());
} }
private ContainerInboundPlanPreviewResponse manualPreview( private ContainerInboundPlanPreviewResponse manualPreview(
@@ -425,9 +426,9 @@ class ContainerInboundPlanConversionCoordinatorTest {
|| "READY".equals(order.readiness())).count(); || "READY".equals(order.readiness())).count();
return new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", return new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", 7L, SNAPSHOT_HASH, orders.size(), orders.size(), 0, pending, pending, "Los Angeles", 7L, SNAPSHOT_HASH, orders.size(), orders.size(), 0, pending, pending,
0, orders.size(), 1, true, pending, 0, 0, orders.size(), 1, true,
List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2", List.of(new ContainerInboundPlanPreviewResponse.Group("FBA", null, "ACY2", "Amazon ACY2",
orders.size(), pending, pending, 0, orders.size(), 1, orders))); orders.size(), pending, pending, pending, 0, 0, orders.size(), 1, orders)));
} }
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) { private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
@@ -475,6 +476,6 @@ class ContainerInboundPlanConversionCoordinatorTest {
private ContainerInboundPlanPreviewResponse preview(long version, String hash) { private ContainerInboundPlanPreviewResponse preview(long version, String hash) {
return new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01", return new ContainerInboundPlanPreviewResponse(BOOKING_ID, "MSCU6639870", "31", "US-LAX-01",
"Los Angeles", version, hash, 1, 1, 0, 0, 0, 1, 1, 1, true, List.of()); "Los Angeles", version, hash, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, true, List.of());
} }
} }
@@ -57,6 +57,8 @@ class ContainerInboundPlanPreviewTest {
assertFalse(result.ready()); assertFalse(result.ready());
assertEquals(4, result.pendingOrderCount()); assertEquals(4, result.pendingOrderCount());
assertEquals(7, result.pendingPalletCount()); assertEquals(7, result.pendingPalletCount());
assertEquals(7, result.sourcePalletCount());
assertEquals(0, result.supplementPalletCount());
assertEquals(2, result.existingPlannedPalletCount()); assertEquals(2, result.existingPlannedPalletCount());
assertEquals(List.of("A-01", "B-02", "C-03", "DROPSHIP", ""), result.groups().stream() assertEquals(List.of("A-01", "B-02", "C-03", "DROPSHIP", ""), result.groups().stream()
.map(group -> group.destinationWarehouseCode() == null ? "" : group.destinationWarehouseCode()).toList()); .map(group -> group.destinationWarehouseCode() == null ? "" : group.destinationWarehouseCode()).toList());
@@ -74,7 +76,7 @@ class ContainerInboundPlanPreviewTest {
new InboundPlanOrderMapper.ContainerPreviewOrderRow("100", 1L, "LM-1", "FO-1", "测试客户", new InboundPlanOrderMapper.ContainerPreviewOrderRow("100", 1L, "LM-1", "FO-1", "测试客户",
"FIRST_LEG", "OVERSEAS", "IN-1", "PENDING_ASSIGNMENT", null, null, 0, 0, "FIRST_LEG", "OVERSEAS", "IN-1", "PENDING_ASSIGNMENT", null, null, 0, 0,
BigDecimal.ZERO, null, null, null, null, null, null, null, 0, false, 1, BigDecimal.ZERO, null, null, null, null, null, null, null, 0, false, 1,
null, null, null, null))); null, null, null, null, null)));
var result = service.previewContainer("BOOKING-NULL"); var result = service.previewContainer("BOOKING-NULL");
@@ -118,6 +120,27 @@ class ContainerInboundPlanPreviewTest {
assertEquals("RESOURCE_PENDING", result.groups().get(0).orders().get(0).readiness()); assertEquals("RESOURCE_PENDING", result.groups().get(0).orders().get(0).readiness());
} }
@Test
void exposesHeadLegExistingSupplementAndFinalPalletCountsWithoutBlocking() {
when(orderMapper.containerPreviewOrders("BOOKING-SUPPLEMENT")).thenReturn(List.of(
new InboundPlanOrderMapper.ContainerPreviewOrderRow("100", 1L, "LM-1", "FO-1",
"测试客户", "FIRST_LEG", "FBA", "IN-1", "PENDING_ASSIGNMENT", null,
"MSCU6639870", 2, 250, new BigDecimal("5.3000"), "31", "US-LAX-01",
"洛杉矶仓", "ACY2", null, null, "Amazon ACY2", 0, false, 1,
null, null, null, null, 2)));
var result = service.previewContainer("BOOKING-SUPPLEMENT");
var order = result.groups().get(0).orders().get(0);
assertEquals(2, order.sourcePalletCount());
assertEquals(3, order.targetPalletCount());
assertEquals(1, order.supplementPalletCount());
assertEquals(2, result.sourcePalletCount());
assertEquals(1, result.supplementPalletCount());
assertEquals(3, result.pendingPalletCount());
assertEquals("READY", order.readiness());
}
private InboundPlanOrderMapper.ContainerPreviewOrderRow row( private InboundPlanOrderMapper.ContainerPreviewOrderRow row(
long id, String orderNo, String orderType, String destinationType, String destinationCode, String destinationName, long id, String orderNo, String orderType, String destinationType, String destinationCode, String destinationName,
int recommendedPallets, String planNo, Integer plannedPallets, int version) { int recommendedPallets, String planNo, Integer plannedPallets, int version) {
@@ -137,6 +160,6 @@ class ContainerInboundPlanPreviewTest {
"FBA".equals(destinationType) || destinationCode == null ? null : "WH-" + id, "FBA".equals(destinationType) || destinationCode == null ? null : "WH-" + id,
"FBA".equals(destinationType) ? null : destinationCode, destinationName, "FBA".equals(destinationType) ? null : destinationCode, destinationName,
"FBA".equals(destinationType) || destinationCode == null ? 0 : 1, false, version, planNo, "FBA".equals(destinationType) || destinationCode == null ? 0 : 1, false, version, planNo,
planStatus, plannedPallets, materializedPallets); planStatus, plannedPallets, materializedPallets, recommendedPallets);
} }
} }