fix: expose overseas receiving business facts

This commit is contained in:
2026-09-11 10:32:30 +08:00
parent b2f85501ac
commit a4b7247c57
8 changed files with 68 additions and 6 deletions
@@ -221,6 +221,7 @@ public class DispatchPdaWorkflowService {
outboundOrders.updateById(outbound); outboundOrders.updateById(outbound);
order.setShippedPackageCount((int) shipped); order.setShippedPackageCount((int) shipped);
order.setStatus("DEPARTED"); order.setStatus("DEPARTED");
order.setDepartAt(now);
order.setUpdatedAt(now); order.setUpdatedAt(now);
orders.updateById(order); orders.updateById(order);
releasePalletLocks(customerId, order.getId(), now); releasePalletLocks(customerId, order.getId(), now);
@@ -10,6 +10,9 @@ public record InboundPlanResponse(
String planNo, String planNo,
String customerId, String customerId,
Long sourceOrderId, Long sourceOrderId,
String sourceOrderNo,
String customerName,
String orderType,
String containerNo, String containerNo,
String overseasWarehouseCode, String overseasWarehouseCode,
String overseasWarehouseName, String overseasWarehouseName,
@@ -29,7 +32,9 @@ public record InboundPlanResponse(
boolean mergeRequired, boolean mergeRequired,
boolean urgent, boolean urgent,
String creationMode, String creationMode,
String remark String remark,
LocalDateTime createdAt,
LocalDateTime updatedAt
) { ) {
public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId, public record Line(Long id, Long orderId, String inboundCode, String fbaWarehouseCode, String destinationWarehouseId,
String destinationWarehouseCode, String destinationWarehouseName, String destinationWarehouseCode, String destinationWarehouseName,
@@ -16,6 +16,7 @@ public interface InboundPlanOrderMapper {
String orderType) { } String orderType) { }
record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode, record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode,
String overseasWarehouseName) { } String overseasWarehouseName) { }
record DetailOrderSnapshot(String orderNo, String customerName, String orderType) { }
record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId, record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId,
String overseasWarehouseId) { } String overseasWarehouseId) { }
record ContainerPreviewOrderRow( record ContainerPreviewOrderRow(
@@ -99,6 +100,11 @@ public interface InboundPlanOrderMapper {
ReceivingWarehouseSnapshot findReceivingWarehouse(@Param("customerId") String customerId, ReceivingWarehouseSnapshot findReceivingWarehouse(@Param("customerId") String customerId,
@Param("orderId") Long orderId); @Param("orderId") Long orderId);
@Select("SELECT order_no AS orderNo,customer_name AS customerName,order_type AS orderType " +
"FROM ll_order WHERE customer_id=#{customerId} AND id=#{orderId} LIMIT 1")
DetailOrderSnapshot findDetailOrder(@Param("customerId") String customerId,
@Param("orderId") Long orderId);
@Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," + @Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," +
"pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " + "pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " +
"WHERE customer_id=#{customerId} AND id=#{orderId}") "WHERE customer_id=#{customerId} AND id=#{orderId}")
@@ -1000,6 +1000,11 @@ public class InboundPlanService {
} }
WarehouseResourceGateway.WarehouseReference authoritativeWarehouse = resolvedWarehouse == null WarehouseResourceGateway.WarehouseReference authoritativeWarehouse = resolvedWarehouse == null
? requiredReceivingWarehouse(receivingWarehouse.overseasWarehouseId()) : resolvedWarehouse; ? requiredReceivingWarehouse(receivingWarehouse.overseasWarehouseId()) : resolvedWarehouse;
InboundPlanOrderMapper.DetailOrderSnapshot sourceOrder = plan.getSourceOrderId() == null ? null
: orderMapper.findDetailOrder(plan.getCustomerId(), plan.getSourceOrderId());
if (plan.getSourceOrderId() != null && sourceOrder == null) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "入库计划关联订单不存在");
}
List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>() List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>()
.eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId)); .eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId));
List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>() List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
@@ -1009,7 +1014,10 @@ public class InboundPlanService {
.eq(InboundPlanPalletAllocation::getPlanId, plan.getId()) .eq(InboundPlanPalletAllocation::getPlanId, plan.getId())
.orderByAsc(InboundPlanPalletAllocation::getId)); .orderByAsc(InboundPlanPalletAllocation::getId));
return new InboundPlanResponse(plan.getId(), plan.getPlanNo(), plan.getCustomerId(), plan.getSourceOrderId(), return new InboundPlanResponse(plan.getId(), plan.getPlanNo(), plan.getCustomerId(), plan.getSourceOrderId(),
plan.getContainerNo(), authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(), sourceOrder == null ? null : sourceOrder.orderNo(),
sourceOrder == null ? null : sourceOrder.customerName(),
sourceOrder == null ? null : sourceOrder.orderType(), plan.getContainerNo(),
authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(),
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(), plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(),
lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(), lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(),
allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(), allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(),
@@ -1027,7 +1035,8 @@ public class InboundPlanService {
item.getPickedPackageCount(), item.getActualVolumeCbm(), item.getStatus(), item.getVersion())).toList(), item.getPickedPackageCount(), item.getActualVolumeCbm(), item.getStatus(), item.getVersion())).toList(),
plan.getVersion() == null ? 0 : plan.getVersion(), plan.getPlannedArrivalDate(), plan.getPalletType(), plan.getVersion() == null ? 0 : plan.getVersion(), plan.getPlannedArrivalDate(), plan.getPalletType(),
Boolean.TRUE.equals(plan.getAssignedFlag()), Boolean.TRUE.equals(plan.getMergeRequired()), Boolean.TRUE.equals(plan.getAssignedFlag()), Boolean.TRUE.equals(plan.getMergeRequired()),
Boolean.TRUE.equals(plan.getUrgentFlag()), plan.getCreationMode(), plan.getPlanRemark()); Boolean.TRUE.equals(plan.getUrgentFlag()), plan.getCreationMode(), plan.getPlanRemark(),
plan.getCreatedAt(), plan.getUpdatedAt());
} }
private WarehouseResourceGateway.WarehouseReference requiredReceivingWarehouse(String warehouseId) { private WarehouseResourceGateway.WarehouseReference requiredReceivingWarehouse(String warehouseId) {
@@ -2773,12 +2773,15 @@ components:
version: { type: integer } version: { type: integer }
InboundPlanDetail: InboundPlanDetail:
type: object type: object
required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version] required: [id, planNo, customerId, overseasWarehouseCode, overseasWarehouseName, status, lineCount, palletCount, totalVolumeCbm, expectedPackageCount, pickedPackageCount, lines, pallets, allocations, version, createdAt, updatedAt]
properties: properties:
id: { type: string, pattern: '^[0-9]+$' } id: { type: string, pattern: '^[0-9]+$' }
planNo: { type: string } planNo: { type: string }
customerId: { type: string } customerId: { type: string }
sourceOrderId: { type: string, pattern: '^[0-9]+$', nullable: true } sourceOrderId: { type: string, pattern: '^[0-9]+$', nullable: true }
sourceOrderNo: { type: string, nullable: true, description: 关联尾程订单业务单号;手工入库计划为空 }
customerName: { type: string, nullable: true, description: 货主名称;手工入库计划为空 }
orderType: { type: string, nullable: true, description: 关联尾程订单类型;手工入库计划为空 }
containerNo: { type: string, nullable: true } containerNo: { type: string, nullable: true }
overseasWarehouseCode: { type: string, description: 接柜仓编码,取自尾程订单预分配接收仓事实 } overseasWarehouseCode: { type: string, description: 接柜仓编码,取自尾程订单预分配接收仓事实 }
overseasWarehouseName: { type: string, description: 接柜仓名称,取自尾程订单预分配接收仓事实 } overseasWarehouseName: { type: string, description: 接柜仓名称,取自尾程订单预分配接收仓事实 }
@@ -2795,6 +2798,8 @@ components:
urgent: { type: boolean } urgent: { type: boolean }
creationMode: { type: string, enum: [AUTO, MANUAL] } creationMode: { type: string, enum: [AUTO, MANUAL] }
remark: { type: string, nullable: true } remark: { type: string, nullable: true }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
lines: { type: array, items: { $ref: '#/components/schemas/InboundPlanLine' } } lines: { type: array, items: { $ref: '#/components/schemas/InboundPlanLine' } }
pallets: { type: array, items: { $ref: '#/components/schemas/InboundPlanPallet' } } pallets: { type: array, items: { $ref: '#/components/schemas/InboundPlanPallet' } }
allocations: { type: array, items: { $ref: '#/components/schemas/InboundPlanAllocation' } } allocations: { type: array, items: { $ref: '#/components/schemas/InboundPlanAllocation' } }
@@ -33,6 +33,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -226,6 +227,33 @@ class DispatchPdaWorkflowServiceTest {
verify(outboundOrders, never()).updateById((OutboundOrder) any()); verify(outboundOrders, never()).updateById((OutboundOrder) any());
} }
@Test
void successfulDeparturePersistsBusinessDepartureTime() {
DispatchOrder order = order("100", "DSP-1", "WH-1", "READY_TO_DEPART");
order.setVehiclePlate("CA-100");
DispatchPdaTask task = task(11L, "100", "WH-1", "COMPLETED");
DispatchItem item = item(21L, "100", 6);
DispatchPdaPick pick = new DispatchPdaPick(); pick.setPackages(6L);
OutboundOrder outbound = new OutboundOrder();
outbound.setOutboundNo("OB-1"); outbound.setStatus("WAIT_OUTBOUND");
when(orders.findGlobalByDispatchNo("DSP-1")).thenReturn(order);
when(orders.lockById("100", 1L)).thenReturn(order);
when(outboundOrders.lockByDispatch("100", 1L)).thenReturn(outbound);
when(tasks.lockByDispatch("100", 1L)).thenReturn(task);
when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item));
when(picks.totalItem("100", 11L, 21L)).thenReturn(6L);
when(picks.listTask("100", 11L)).thenReturn(List.of(pick));
when(palletLocks.selectList(any())).thenReturn(List.of());
DispatchPdaResponses.Departed result = service.depart(
"WH-1", "DSP-1", "7", new DispatchPdaRequests.Depart("depart-1", null));
assertEquals("DEPARTED", result.status());
assertNotNull(order.getDepartAt());
assertEquals(order.getDepartAt(), order.getUpdatedAt());
verify(orders).updateById(order);
}
@Test @Test
void exactDepartRetryReturnsPersistedResultAfterStatusChanged() throws Exception { void exactDepartRetryReturnsPersistedResultAfterStatusChanged() throws Exception {
DispatchOrder order = order("100", "DSP-1", "WH-1", "DEPARTED"); DispatchOrder order = order("100", "DSP-1", "WH-1", "DEPARTED");
@@ -431,10 +431,11 @@ class ContainerInboundPlanConversionCoordinatorTest {
} }
private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) { private InboundPlanResponse plan(String planNo, List<InboundPlanResponse.Pallet> pallets) {
return new InboundPlanResponse(21L, planNo, "100", 11L, "MSCU6639870", "US-LAX-01", return new InboundPlanResponse(21L, planNo, "100", 11L, "FL-WH-1", "验收客户", "ONE_CLICK_DROPSHIP",
"MSCU6639870", "US-LAX-01",
"Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.ONE, 10, 0, "Los Angeles", "PENDING_ASSIGNMENT", 1, 1, BigDecimal.ONE, 10, 0,
List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false, List.of(), pallets, List.of(), 1, LocalDate.of(2026, 9, 10), "WOOD", false,
false, false, "MANUAL", "自动生成"); false, false, "MANUAL", "自动生成", null, null);
} }
private ConvertContainerInboundPlanRequest request() { private ConvertContainerInboundPlanRequest request() {
@@ -84,6 +84,9 @@ class InboundPlanServiceTest {
org.mockito.Mockito.lenient().when(orderMapper.findReceivingWarehouse(any(), any())).thenReturn( org.mockito.Mockito.lenient().when(orderMapper.findReceivingWarehouse(any(), any())).thenReturn(
new InboundPlanOrderMapper.ReceivingWarehouseSnapshot( new InboundPlanOrderMapper.ReceivingWarehouseSnapshot(
"193000000000200003", "US-LAX-01", "洛杉矶仓")); "193000000000200003", "US-LAX-01", "洛杉矶仓"));
org.mockito.Mockito.lenient().when(orderMapper.findDetailOrder(any(), anyLong())).thenReturn(
new InboundPlanOrderMapper.DetailOrderSnapshot(
"FL-WH-TEST", "测试客户", "ONE_CLICK_DROPSHIP"));
org.mockito.Mockito.lenient().when(warehouseGateway.requireActiveSharedWarehouse(193000000000200003L)).thenReturn( org.mockito.Mockito.lenient().when(warehouseGateway.requireActiveSharedWarehouse(193000000000200003L)).thenReturn(
new WarehouseResourceGateway.WarehouseReference( new WarehouseResourceGateway.WarehouseReference(
193000000000200003L, "US-LAX-01", "洛杉矶仓", "ACTIVE")); 193000000000200003L, "US-LAX-01", "洛杉矶仓", "ACTIVE"));
@@ -324,7 +327,11 @@ class InboundPlanServiceTest {
assertEquals("US-NY-01", result.overseasWarehouseCode()); assertEquals("US-NY-01", result.overseasWarehouseCode());
assertEquals("纽约仓", result.overseasWarehouseName()); assertEquals("纽约仓", result.overseasWarehouseName());
assertEquals("FL-WH-TEST", result.sourceOrderNo());
assertEquals("测试客户", result.customerName());
assertEquals("ONE_CLICK_DROPSHIP", result.orderType());
verify(orderMapper).findReceivingWarehouse("100", 1L); verify(orderMapper).findReceivingWarehouse("100", 1L);
verify(orderMapper).findDetailOrder("100", 1L);
} }
@Test @Test