diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index 772b25e1e..819ed1c5e 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -461,6 +461,9 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE phase4_receiving_drop_raw_photos_stmt FROM @ddl; EXECUTE phase4_receiving_drop_raw_photos_stmt; DEALLOCATE PREPARE phase4_receiving_drop_raw_photos_stmt; DROP PROCEDURE IF EXISTS assert_receiving_global_locks_migratable; +UPDATE ll_receiving_container_session +SET active_operator_key=NULL,active_container_key=NULL +WHERE status='COMPLETED' AND (active_operator_key IS NOT NULL OR active_container_key IS NOT NULL); DELIMITER $$ CREATE PROCEDURE assert_receiving_global_locks_migratable() BEGIN diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/ReceivingContainerSession.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/ReceivingContainerSession.java index 2667cb427..388bcf420 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/ReceivingContainerSession.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/ReceivingContainerSession.java @@ -1,6 +1,8 @@ package com.baiyue.lastleg.inboundplan.pda.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.Version; @@ -19,7 +21,9 @@ public class ReceivingContainerSession { private String overseasWarehouseName; private String operatorKey; private String operatorName; + @TableField(updateStrategy = FieldStrategy.ALWAYS) private String activeOperatorKey; + @TableField(updateStrategy = FieldStrategy.ALWAYS) private String activeContainerKey; private String status; private String signPhotos; 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 80710f77c..fd8ac7df0 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 @@ -201,10 +201,15 @@ public class ReceivingContainerService { ReceivingContainerRequests.CartonResolve request) { ReceivingContainerSession session = requireSigned(operatorKey); String customerId = session.getCustomerId(); + List currentRefs = refs(customerId, session.getContainerNo()); + if (currentRefs.stream().noneMatch(value -> + "OVERSEAS".equals(destinationType(value.line(), value.order())))) { + throw conflict("FBA destination must use bulk count"); + } InboundPlanCarton carton = cartons.selectOne(new LambdaQueryWrapper() .eq(InboundPlanCarton::getCustomerId, customerId).eq(InboundPlanCarton::getCartonMark, request.cartonMark())); if (carton == null) throw notFound("Carton mark was not found"); - InboundRef ref = refs(customerId, session.getContainerNo()).stream() + InboundRef ref = currentRefs.stream() .filter(value -> Objects.equals(value.line().getOrderId(), carton.getOrderId())).findFirst() .orElseThrow(() -> conflict("Carton mark belongs to another container")); if (!"OVERSEAS".equals(destinationType(ref.line(), ref.order()))) { @@ -285,10 +290,15 @@ public class ReceivingContainerService { if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class); ReceivingContainerSession session = requireSigned(operatorKey); String customerId = session.getCustomerId(); + List currentRefs = refs(customerId, session.getContainerNo()); + if (currentRefs.stream().noneMatch(value -> + "OVERSEAS".equals(destinationType(value.line(), value.order())))) { + throw conflict("FBA destination must use bulk count"); + } InboundPlanCarton carton = cartons.selectOne(new LambdaQueryWrapper() .eq(InboundPlanCarton::getCustomerId, customerId).eq(InboundPlanCarton::getCartonMark, request.cartonMark())); if (carton == null) throw notFound("Carton mark was not found"); - InboundRef ref = refs(customerId, session.getContainerNo()).stream() + InboundRef ref = currentRefs.stream() .filter(value -> Objects.equals(value.line().getOrderId(), carton.getOrderId())).findFirst() .orElseThrow(() -> conflict("Carton mark belongs to another container")); if (!"OVERSEAS".equals(destinationType(ref.line(), ref.order()))) { diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java index 566acf469..f90963626 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/ReceivingContainerContractTest.java @@ -1,7 +1,10 @@ package com.baiyue.lastleg.inboundplan.pda; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baiyue.lastleg.inboundplan.pda.controller.ReceivingContainerController; import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerRequests; +import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingContainerSession; import org.junit.jupiter.api.Test; import org.springframework.security.access.prepost.PreAuthorize; import jakarta.validation.constraints.Size; @@ -46,6 +49,15 @@ class ReceivingContainerContractTest { assertTrue("hasAuthority('lastleg:pda:operate')".equals(permission.value())); } + @Test + void completedSessionAlwaysPersistsClearedGlobalLockKeys() throws NoSuchFieldException { + for (String fieldName : List.of("activeOperatorKey", "activeContainerKey")) { + TableField field = ReceivingContainerSession.class.getDeclaredField(fieldName) + .getAnnotation(TableField.class); + assertTrue(field != null && field.updateStrategy() == FieldStrategy.ALWAYS, fieldName); + } + } + @Test void schemaContainsCustomerScopedPlanChildrenAndGlobalPlatformPickerLocks() throws IOException { String schema = resource("db/lastleg.sql").toLowerCase(); @@ -81,6 +93,7 @@ class ReceivingContainerContractTest { assertTrue(unified.contains("customer_id is null or a.customer_id=''")); assertTrue(unified.contains("signal sqlstate '45000'")); assertTrue(unified.contains("assert_receiving_global_locks_migratable")); + assertTrue(unified.contains("where status='completed' and (active_operator_key is not null or active_container_key is not null)")); assertTrue(unified.contains("concat('operator:',operator_key)")); assertTrue(unified.contains("concat('container:',upper(trim(container_no)))")); assertTrue(unified.contains("assert_receiving_operation_global_key_migratable")); 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 c4e470282..0241f0153 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 @@ -172,11 +172,10 @@ class ReceivingContainerServiceBehaviorTest { org.mockito.Mockito.reset(plans, lines, orders, sessions, operations, cartons, warehouse); stubCurrent("FBA", "CONT-A"); - InboundPlanCarton carton = carton(1L, "BOX-1"); - when(cartons.selectOne(any())).thenReturn(carton); assertThatThrownBy(() -> service.receiveCarton("7", "receiver", new ReceivingContainerRequests.CartonReceive("BOX-1", "PAL-1", "carton-key"))) .isInstanceOf(ResponseStatusException.class).hasMessageContaining("409"); + verifyNoInteractions(cartons); verifyNoInteractions(warehouse); } diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs index 7b752fbd2..450762dac 100644 --- a/scripts/admin-test-env-acceptance.mjs +++ b/scripts/admin-test-env-acceptance.mjs @@ -22,7 +22,7 @@ const MODULE_DEPENDENCIES = new Map([ ['phase3-loading-finalize', ['phase3-loading', 'phase3-evidence-gate']], ['phase3-customs', ['phase3-loading-finalize']], ['phase3-port-schedules', ['ocean-bookings']], - ['phase3-outbound-dispatch', ['phase3-customs']], + ['phase3-outbound-dispatch', ['phase3-customs', 'phase3-port-schedules', 'service-center-flow']], ['phase3-outbound-aggregate', ['phase3-outbound-dispatch']], ['phase3-dispatch-gate', ['phase3-outbound-dispatch']], ['phase3-waiting-for-loading', ['phase3-outbound-dispatch']], @@ -870,6 +870,7 @@ export class AcceptanceRun { tenantId: fixture.tenantId, orderId: fixture.orderId || this.createdResources.orders?.orderId, inboundId: fixture.inboundId, + boxCode: fixture.boxCode, customerRemark: fixtureOrder.customerRemark, internalRemark: fixtureOrder.internalRemark, cargoRemark: fixtureOrder.cargoRemark, @@ -888,6 +889,7 @@ export class AcceptanceRun { tenantId: incrementalSource.tenantId, orderId: incrementalSource.orderId, inboundId: incrementalSource.inboundId, + boxCode: incrementalSource.boxCode, } : { marker: originalMarker, failed: true } this.createdResources.orchestrationFixtures = { ...(this.createdResources.orchestrationFixtures || {}), diff --git a/scripts/admin-test-env-modules/phase3-loading-finalize.mjs b/scripts/admin-test-env-modules/phase3-loading-finalize.mjs index 0c7b0f567..e64e70553 100644 --- a/scripts/admin-test-env-modules/phase3-loading-finalize.mjs +++ b/scripts/admin-test-env-modules/phase3-loading-finalize.mjs @@ -365,6 +365,7 @@ export async function run(context) { containerBookingId, sourceOrderId: orderId, sourceInboundId: inboundId, + cartonMark: fixture.cartonMark || null, destinationType: fixture.destinationType || null, destinationWarehouseId: id(fixture.destinationWarehouseId), destinationWarehouseCode: fixture.destinationWarehouseCode || null, diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index 80331208c..4280e8609 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -1180,6 +1180,7 @@ export async function run(context) { context.createdResources.phase3Loading = { tenantId, orderId: id(source?.orderId), inboundId, preloadPlanId: planId, + cartonMark: source?.boxCode || source?.cartonMark || null, destinationType: initial?.summary?.destinationType || source?.destinationType || null, destinationWarehouseId: id(initial?.summary?.destinationWarehouseId || source?.destinationWarehouseId), destinationWarehouseCode: initial?.summary?.destinationCode || source?.destinationWarehouseCode || null, diff --git a/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs b/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs index da8d4a5c5..1fd31f443 100644 --- a/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs @@ -29,7 +29,8 @@ test('focused carrier node execution injects the complete outbound-confirm produ const modules = normalizeModuleOrder(['phase4-carrier-nodes']) const required = ['merchant-qualification-fixture', 'orders', 'inbound-measurement', 'phase3-container-loading', 'phase3-loading', 'phase3-pda-convergence', 'phase3-evidence-gate', - 'phase3-loading-finalize', 'phase3-customs', 'phase3-outbound-dispatch', + 'phase3-loading-finalize', 'phase3-customs', 'ocean-bookings', 'phase3-port-schedules', + 'truck-appointments', 'service-center-flow', 'phase3-outbound-dispatch', 'phase3-carrier-schedule-queries', 'phase4-carrier-nodes'] for (const module of required) assert.ok(modules.includes(module), module) for (let index = 1; index < required.length; index += 1) { diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs index 94eaa6d78..18c01aa4b 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs @@ -15,8 +15,8 @@ function conflictMessage(response, pattern) { return conflict(response) && patte function notFound(response) { return (response?.http === 404 || (response?.http === 200 && ['404', 'A0404'].includes(code(response)))) && traced(response) } function forbidden(response) { return [401, 403].includes(response?.http) || (response?.http === 200 && ['A0401', 'A0403'].includes(code(response))) } function trace(response) { return `HTTP=${response?.http ?? 'missing'}, code=${response?.body?.code ?? 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` } -function key(context, action, suffix = '') { return `${context.marker}-${action}-${suffix}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 120) } -function markerSuffix(context) { return String(context.marker).replace(/[^A-Za-z0-9]/g, '').slice(-16).toUpperCase() } +function key(context, action, suffix = '') { return `${context.marker}-${context.pdaContainerSuffix || ''}-${action}-${suffix}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 120) } +function markerSuffix(context) { return `${context.marker}${context.pdaContainerSuffix || ''}`.replace(/[^A-Za-z0-9]/g, '').slice(-16).toUpperCase() } function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) { context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true }) @@ -183,8 +183,8 @@ async function receiveFba(context, token, inbound, pallet) { return true } -async function receiveOverseas(context, token, inbound, pallet) { - const cartonMark = context.createdResources?.inboundMeasurement?.boxCode +async function receiveOverseas(context, token, inbound, pallet, producerCartonMark = null) { + const cartonMark = producerCartonMark || context.createdResources?.inboundMeasurement?.boxCode if (!nonBlank(cartonMark)) { blockerGap(context, `OVERSEAS入仓码${inbound.inboundCode}逐箱`, '缺少本轮冻结箱唛,逐箱实收不能使用伪造箱号') return false @@ -207,12 +207,12 @@ async function receiveOverseas(context, token, inbound, pallet) { return true } -async function runReceiving(context, token, secondaryToken, merchantToken) { - if (context.overseasToken) { +async function runReceiving(context, token, secondaryToken, merchantToken, preferredProducer = null, runAuthorizationProbes = true) { + if (runAuthorizationProbes && context.overseasToken) { const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, context.overseasToken) context.addStep(MODULE, '平台管理员海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS) } - if (merchantToken) { + if (runAuthorizationProbes && merchantToken) { const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, merchantToken) context.addStep(MODULE, '客户海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS) } @@ -229,8 +229,16 @@ async function runReceiving(context, token, secondaryToken, merchantToken) { blockerGap(context, 'PDA柜号候选', `当前客户没有由三期装柜完成事件生成的待收入仓计划;${trace(candidates.response)}`, 'producer-chain', candidates.response) return } - const preferredContainer = context.createdResources?.phase3Loading?.containerNo - const candidate = candidates.data.find((item) => item.containerNo === preferredContainer) || candidates.data[0] + const preferredContainer = id(preferredProducer?.containerNo) + || context.createdResources?.phase3Loading?.containerNo + const candidate = preferredContainer + ? candidates.data.find((item) => item.containerNo === preferredContainer) + : candidates.data[0] + if (!candidate) { + blockerGap(context, `本轮柜号${preferredContainer || 'missing'}候选`, + '三期装柜完成生产者已落地,但海外候选尚未出现该柜;禁止回退处理历史或其他柜', 'producer-chain', candidates.response) + return + } const containerNo = id(candidate?.containerNo) context.assert(MODULE, '柜号候选来自本轮生产链且包含进度', candidates.response, nonBlank(containerNo) && Number(candidate?.totalPackages) > 0 && Number(candidate?.receivedPackages) >= 0, @@ -300,7 +308,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken) { inboundCode: overseasInbound.inboundCode, skuCode: 'MODE-REJECT', palletCode: pallet.palletCode, packageCount: 1, idempotencyKey: key(context, 'overseas-bulk-forbidden'), }, (response) => conflictMessage(response, /OVERSEAS.*carton|bulk.*forbidden/i)) - await receiveOverseas(context, token, overseasInbound, pallet) + await receiveOverseas(context, token, overseasInbound, pallet, preferredProducer?.cartonMark) } else blockerGap(context, 'OVERSEAS逐箱链', '当前柜没有带真实冻结箱唛与托盘的 OVERSEAS 入仓码') const refreshed = await pda(context, token, 'GET', '回查柜内逐入仓码实收进度', '/api/pda/v1/last-leg/receiving-containers/current') if (!success(refreshed.response) || Number(refreshed.data?.pendingPackages) !== 0) { @@ -321,7 +329,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken) { export async function run(context) { context.createdResources.phase4OverseasPda = { marker: context.marker, containerNo: null, customerId: null, - palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], + palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], completedContainers: [], overseasWarehouseId: null, overseasWarehouseName: null, cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence', } @@ -345,8 +353,20 @@ export async function run(context) { blockerGap(context, '海外PDA双操作员账号', '未能创建并登录两个WAREHOUSE_PICKER平台账号,无法验证全局一柜锁') return } - await runReceiving(context, primary.token, secondary.token, merchantToken) + const matrix = context.createdResources?.phase4InboundProducerFixtures + const producers = [matrix?.byDestinationType?.FBA?.[0], matrix?.byDestinationType?.OVERSEAS?.[0]] + .filter((item) => nonBlank(item?.containerNo)) + for (const [index, producer] of (producers.length ? producers : [null]).entries()) { + context.pdaContainerSuffix = producer?.containerNo || `CONTAINER-${index + 1}` + context.createdResources.phase4OverseasPda.finalStatus = null + await runReceiving(context, primary.token, secondary.token, + index === 0 ? merchantToken : null, producer, index === 0) + if (context.createdResources.phase4OverseasPda.finalStatus === 'COMPLETED') { + context.createdResources.phase4OverseasPda.completedContainers.push(producer?.containerNo || null) + } + } } finally { + delete context.pdaContainerSuffix await disablePicker(context, secondary, 2) await disablePicker(context, primary, 1) } diff --git a/scripts/admin-test-env-modules/phase4-overseas-warehouse-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-warehouse-contract.test.mjs index 10d20e210..261b0e84b 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-warehouse-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-warehouse-contract.test.mjs @@ -42,7 +42,7 @@ function contextFixture({ tenants = [{ id: '10001', status: 'ACTIVE' }], warehou if (requestPath.includes('/last-leg/orders/30001?')) return response({ order: { id: '30001' }, cargo: [], statusHistory: [] }) if (requestPath.startsWith('/api/platform/v1/last-leg/relabel-applications?')) return response(page([{ id: '40001', relabelNo: 'RL-1', status: 'PENDING_PROCESSING' }])) if (requestPath.includes('/relabel-applications/RL-1?')) return response({ application: { relabelNo: 'RL-1' }, items: [], history: [] }) - if (requestPath.startsWith('/api/platform/v1/last-leg/inbound-plans?')) return response(page([{ id: '50001', sourceOrderId: '30001', planNo: 'IP-1', customerId: '10001', status: 'PENDING_ASSIGNMENT' }])) + if (requestPath.startsWith('/api/platform/v1/last-leg/inbound-plans?')) return response(page([{ lineId: '50002', planId: '50001', orderId: '30001', planNo: 'IP-1', customerId: '10001', status: 'PENDING_ASSIGNMENT' }])) if (requestPath.includes('/inbound-plans/IP-1?')) return response({ planNo: 'IP-1', sourceOrderId: '30001', lines: [], pallets: [], allocations: [] }) if (requestPath.includes('/inventory/summary?')) return response({ inboundOrderCount: 1, palletCount: 1, totalPackages: 10, availablePackages: 10, holdPackages: 0, inTransitPackages: 0, receivingPackages: 0, procurementPackages: 0 }) if (requestPath.includes('/inventory/balances?')) return response(page([{ id: '60001', customerId: '10001', skuCode: 'SKU-1', totalPackages: 10, availablePackages: 10, updatedAt: '2026-08-21T10:00:00' }])) @@ -145,7 +145,9 @@ test('合同覆盖库存CSV并明确排除暂缓、离仓、PDA与内部路径', for (const forbidden of ['/cartons', '/products', '/outbound', '/dispatch', '/delivery', '/pda', '/internal']) assert.equal(source.includes(forbidden), false, forbidden) assert.match(source, /requestId=/) assert.match(source, /traceId=/) - assert.match(source, /longId\(plan\.sourceOrderId\)/) + for (const field of ['lineId', 'planId', 'orderId']) { + assert.match(source, new RegExp(`longId\\(plan\\.${field}\\)`)) + } assert.match(source, /id\(pallet\.customerId\) === '0'/) assert.match(source, /创建本轮换标单/) assert.match(source, /换标单同幂等键不同请求返回A0409/) diff --git a/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs b/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs index 3e977f3aa..7dc90a1b3 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs @@ -107,8 +107,9 @@ export async function run(context) { const plan = records(plans.data)[0] if (plan) { context.assert(MODULE, '入仓计划关键字段正确', plans.response, - longId(plan.id) && longId(plan.sourceOrderId) && nonBlank(plan.planNo) && id(plan.customerId) === customerId && nonBlank(plan.status), - `id=${id(plan.id) || 'missing'}, sourceOrderId=${id(plan.sourceOrderId) || 'missing'}, planNo=${plan.planNo || 'missing'}, status=${plan.status || 'missing'}; ${trace(plans.response)}`, OPTIONS) + longId(plan.lineId) && longId(plan.planId) && longId(plan.orderId) + && nonBlank(plan.planNo) && id(plan.customerId) === customerId && nonBlank(plan.status), + `lineId=${id(plan.lineId) || 'missing'}, planId=${id(plan.planId) || 'missing'}, orderId=${id(plan.orderId) || 'missing'}, planNo=${plan.planNo || 'missing'}, status=${plan.status || 'missing'}; ${trace(plans.response)}`, OPTIONS) const detail = await overseasGet(context, '查询入仓计划详情', query(`/api/platform/v1/last-leg/inbound-plans/${encodeURIComponent(plan.planNo)}`, { customerId })) context.assert(MODULE, '入仓计划详情对应列表记录', detail.response, detail.data?.planNo === plan.planNo && longId(detail.data?.sourceOrderId)