diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs
index fea49054c..5b55ff983 100644
--- a/scripts/admin-test-env-acceptance.mjs
+++ b/scripts/admin-test-env-acceptance.mjs
@@ -69,7 +69,7 @@ const MODULE_DEPENDENCIES = new Map([
['phase3-waiting-for-loading', ['phase3-outbound-dispatch']],
['phase3-carrier-schedule-queries', ['phase3-outbound-dispatch']],
['phase3-order-splitting', ['phase3-loading-finalize']],
- ['manual-state-matrix', ['phase3-loading-finalize', 'phase3-customs', 'ocean-bookings']],
+ ['manual-state-matrix', ['phase3-loading-finalize', 'phase3-customs', 'ocean-bookings', 'phase3-container-documents']],
['phase4-carrier-pickup-ready', ['phase3-carrier-schedule-queries', 'phase3-container-documents']],
['phase4-tail-order-controls', ['phase4-carrier-pickup-ready']],
['phase4-overseas-pda', ['phase4-tail-order-controls']],
@@ -1572,7 +1572,7 @@ export class AcceptanceRun {
await this.runPhase4ProducerVariant('DROPSHIP-PRIMARY', 'FBA', 'ONE_CLICK_DROPSHIP', true)
}
if (Number(readiness().fbaContainerCount || 0) < 2) {
- await this.runPhase4ProducerVariant('FBA-STANDARD', 'FBA', 'STANDARD', false)
+ await this.runPhase4ProducerVariant('FBA-STANDARD', 'FBA', 'STANDARD', true)
}
if (Number(readiness().overseasContainerCount || 0) < 1) {
await this.runPhase4ProducerVariant('OVERSEAS-PRIMARY', 'OVERSEAS', 'ONE_CLICK_DROPSHIP', true)
@@ -1583,11 +1583,15 @@ export class AcceptanceRun {
const matrix = this.createdResources.phase4InboundProducerFixtures
const containers = Array.isArray(matrix?.containers) ? matrix.containers : []
const dropshipReady = containers.find((item) => item?.fulfillmentType === 'ONE_CLICK_DROPSHIP')
+ const standardFbaReady = containers.find((item) => item?.destinationType === 'FBA'
+ && item?.fulfillmentType !== 'ONE_CLICK_DROPSHIP'
+ && String(item?.resourceKey || '').includes('-P4-FBA-STANDARD:'))
const overseasReady = containers.find((item) => item?.destinationType === 'OVERSEAS')
- const awaitingVariants = containers.filter((item) => ['FBA-STANDARD', 'SECONDARY']
+ const awaitingVariants = containers.filter((item) => ['SECONDARY']
.some((label) => String(item?.resourceKey || '').includes(`-P4-${label}:`)))
const shippingMatrixReady = dropshipReady?.shippingReady === true
&& Boolean(dropshipReady?.carrierScheduleQueryId)
+ && standardFbaReady?.shippingReady === true && Boolean(standardFbaReady?.carrierScheduleQueryId)
&& overseasReady?.shippingReady === true && Boolean(overseasReady?.carrierScheduleQueryId)
&& awaitingVariants.length >= 1
&& awaitingVariants.every((item) => item.shippingReady === false
diff --git a/scripts/admin-test-env-modules/inbound-fees-contract.test.mjs b/scripts/admin-test-env-modules/inbound-fees-contract.test.mjs
index 7c4bc44a5..d73d32dc8 100644
--- a/scripts/admin-test-env-modules/inbound-fees-contract.test.mjs
+++ b/scripts/admin-test-env-modules/inbound-fees-contract.test.mjs
@@ -45,6 +45,8 @@ test('automatic fee adjustment confirms the ten percent tolerance boundary', asy
test('customer confirms chargeable quantity and manual unit price in one locked version', async () => {
const source = await readFile(moduleFile, 'utf8')
+ assert.match(source, /const orderId = id\(fixture\?\.orderId\)/)
+ assert.doesNotMatch(source, /const orderId = id\(context\.createdResources\.orders\?\.orderId\)/)
assert.match(source, /Client 读取已推送计费量、对客单价和费用合计/)
assert.match(source, /Client 一次确认计费量、对客单价和费用/)
assert.match(source, /customerConfirmationStatus === 'CONFIRMED'/)
diff --git a/scripts/admin-test-env-modules/inbound-fees.mjs b/scripts/admin-test-env-modules/inbound-fees.mjs
index 7b81403ee..be615f844 100644
--- a/scripts/admin-test-env-modules/inbound-fees.mjs
+++ b/scripts/admin-test-env-modules/inbound-fees.mjs
@@ -131,7 +131,7 @@ export async function run(context) {
&& money(published?.amount) === money(sent?.totalAmount),
`status=${sent?.feeStatus || 'missing'}, process=${sent?.nextProcessStatus || 'missing'}, publication=${published?.publicationStatus || 'missing'}`, OPTIONS)
- const orderId = id(context.createdResources.orders?.orderId)
+ const orderId = id(fixture?.orderId)
const merchantLoginResponse = await context.request('POST', '/api/auth/login', {
loginId: context.options.merchantUsername, password: context.options.merchantPassword,
}, '')
@@ -260,6 +260,7 @@ export async function run(context) {
receivableTotal >= money(published?.amount) + money(manualV2?.amount) && payableTotal >= 0,
`receivableTotal=${receivableTotal.toFixed(2)}, freight=${money(published?.amount).toFixed(2)}, manual=${money(manualV2?.amount).toFixed(2)}, payableTotal=${payableTotal.toFixed(2)}`, OPTIONS)
+ if (!customerConfirmed?.customerConfirmationStatus) { finish(context, start); return }
context.createdResources.inboundFees = { marker: context.marker, tenantId, inboundId,
freightFeeId: id(sent.feeId), freightVersion: sent.feeVersion, manualDetailId: id(manualV2.id),
manualVersion: manualV2.sourceFeeVersion, customerConfirmationStatus: customerConfirmed.customerConfirmationStatus }
diff --git a/scripts/admin-test-env-modules/inbound-measurement.mjs b/scripts/admin-test-env-modules/inbound-measurement.mjs
index 403c03a5e..1326096c1 100644
--- a/scripts/admin-test-env-modules/inbound-measurement.mjs
+++ b/scripts/admin-test-env-modules/inbound-measurement.mjs
@@ -1097,7 +1097,9 @@ export async function run(context) {
context.createdResources.inboundMeasurement = {
sealedCartonMarks, sealedSourcePalletNos,
- marker: context.marker, tenantId, inboundId, measurementId: asId(draft.measurementId),
+ marker: context.marker, tenantId, inboundId,
+ orderId: asId(summary?.orderId || context.createdResources?.orders?.orderId),
+ measurementId: asId(draft.measurementId),
boxCode, finalStatus: finalMeasurement?.measurementStatus || null,
processStatus: finalMeasurement?.processStatus || null,
palletCount: weightEvidence.count, actualGrossWeightKg: weightEvidence.grossWeightKg,
diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs
index 8443cfcd4..788d65410 100644
--- a/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs
+++ b/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs
@@ -57,17 +57,16 @@ test('fails closed when invoked outside the manual matrix wrapper', () => {
assert.match(source, /missing=manualStateMatrix\.pages/)
})
-test('covers clearance through its public API and keeps customs on the dedicated customs-v1 flow', () => {
- assert.match(source, /new FormData\(\)/)
- assert.match(source, /form\.append\('file'/)
- assert.match(source, /form\.append\('expectedVersion'/)
- assert.match(source, /form\.append\('operationReason'/)
- assert.match(source, /'Idempotency-Key'/)
- assert.match(source, /clearancePath\}\/document\/upload/)
- assert.match(source, /clearancePath\}\/complete/)
+test('reuses the departed-container clearance action verified by the dedicated module', () => {
+ assert.match(source, /createdResources\?\.phase3ContainerDocuments/)
+ assert.match(source, /documentIds\?\.DESTINATION_CLEARANCE/)
+ assert.match(source, /本轮离仓后已完成的目的港清关动作/)
+ assert.match(source, /GET', `\$\{CONTAINERS\}\/\$\{containerId\}\/clearance`/)
+ assert.match(source, /completed\?\.documentType === 'DESTINATION_CLEARANCE'/)
+ assert.match(source, /Boolean\(completed\?\.completedAt\)/)
assert.match(source, /matrix\.pages\.containerLoading\.actions/)
assert.doesNotMatch(source, /CUSTOMS_DECLARATION|loading-customs|customsPath/)
assert.match(source, /tasks\/\$\{taskId\}\/evidence/)
assert.doesNotMatch(source, /internal\/file|business-files/)
- assert.doesNotMatch(source, /\{\s*fileId\s*:/)
+ assert.doesNotMatch(source, /clearance\/document\/upload/)
})
diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs
index 6a2a139dc..1255cc298 100644
--- a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs
+++ b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs
@@ -19,37 +19,6 @@ function operationId(context, label) {
return `${context.marker}-manual-loading-${label}-${crypto.randomUUID()}`.slice(0, 128)
}
-async function post(context, path, body, label) {
- return context.request('POST', path, body, context.token,
- { 'Idempotency-Key': operationId(context, label) })
-}
-
-async function uploadDocument(context, path, expectedVersion, operationReason, label) {
- const form = new FormData()
- form.append('file', new Blob([`${context.marker}-${label}`], { type: 'text/plain' }), `${label}.txt`)
- form.append('expectedVersion', String(expectedVersion))
- if (operationReason) form.append('operationReason', operationReason)
- try {
- const response = await fetch(`${context.options.gateway}${path}`, {
- method: 'POST',
- headers: {
- Accept: 'application/json', Authorization: `Bearer ${context.token}`,
- 'Idempotency-Key': operationId(context, `${label}-upload`),
- },
- body: form,
- signal: AbortSignal.timeout(60_000),
- })
- const raw = await response.text()
- let body = null
- try { body = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
- return { method: 'POST', path, http: response.status, body, raw: body ? null : raw.slice(0, 500),
- requestId: body?.requestId || response.headers.get('x-request-id'),
- traceId: body?.traceId || response.headers.get('x-trace-id') }
- } catch (error) {
- return { method: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
- }
-}
-
async function ensureCompletionEvidence(context, chain, label) {
const taskId = id(chain.task?.id)
const warehouseId = id(chain.source?.warehouseId)
@@ -351,34 +320,26 @@ function fixture(value, status, detailPath, extra = {}) {
return { id: id(value), status, detailPath, ...extra }
}
-async function completeSimpleDocuments(context, chain, matrix) {
- const reason = `${context.marker} 人工页面动作验收`
- const clearancePath = `${CONTAINERS}/${chain.containerId}/clearance`
- const clearanceUpload = await uploadDocument(context, `${clearancePath}/document/upload`, 0,
- reason, 'container-clearance')
- const clearanceDocument = context.apiSuccess(MODULE, '货柜公开接口上传目的港清关资料',
- clearanceUpload, '', OPTIONS)
- context.assert(MODULE, '货柜清关上传返回真实文件事实', clearanceUpload,
- Boolean(clearanceDocument?.id) && Boolean(clearanceDocument?.fileId)
- && clearanceDocument?.documentType === 'DESTINATION_CLEARANCE'
- && /^[0-9a-f]{64}$/i.test(String(clearanceDocument?.fileHash || ''))
- && !clearanceDocument?.completedAt,
- `documentId=${id(clearanceDocument?.id) || 'missing'}, version=${clearanceDocument?.version ?? 'missing'}`, OPTIONS)
- if (!clearanceDocument?.id || !clearanceDocument?.fileId || !Number.isInteger(Number(clearanceDocument?.version))) return
- const clearanceCompleteResponse = await post(context, `${clearancePath}/complete`, {
- expectedVersion: Number(clearanceDocument?.version), operationReason: reason,
- }, 'container-clearance-complete')
- const clearanceCompleted = context.apiSuccess(MODULE, '货柜点击完成目的港清关',
- clearanceCompleteResponse, '', OPTIONS)
- context.assert(MODULE, '货柜清关完成事实可回读', clearanceCompleteResponse,
- id(clearanceCompleted?.id) === id(clearanceDocument?.id) && Boolean(clearanceCompleted?.completedAt)
- && Number(clearanceCompleted?.version) === Number(clearanceDocument?.version) + 1,
- `documentId=${id(clearanceCompleted?.id) || 'missing'}, completedAt=${clearanceCompleted?.completedAt || 'missing'}`, OPTIONS)
+async function registerClearanceAction(context, matrix) {
+ const documentsFixture = context.createdResources?.phase3ContainerDocuments
+ const containerId = id(documentsFixture?.containerBookingId)
+ const expectedDocumentId = id(documentsFixture?.documentIds?.DESTINATION_CLEARANCE)
+ context.assert(MODULE, '复用本轮离仓后已完成的目的港清关动作', null,
+ documentsFixture?.marker === context.marker && Boolean(containerId && expectedDocumentId),
+ `containerBookingId=${containerId || 'missing'}, documentId=${expectedDocumentId || 'missing'}`, OPTIONS)
+ if (!containerId || !expectedDocumentId || documentsFixture?.marker !== context.marker) return
- matrix.pages.containerLoading.actions = { clearance: fixture(chain.containerId, 'COMPLETED',
- `/container-loading/${chain.containerId}/smart-loading`, {
- documentId: id(clearanceCompleted?.id), documentVersion: Number(clearanceCompleted?.version),
- completedAt: clearanceCompleted?.completedAt || null,
+ const response = await context.request('GET', `${CONTAINERS}/${containerId}/clearance`)
+ const completed = context.apiSuccess(MODULE, '回读目的港清关动作验收事实', response, '', OPTIONS)
+ context.assert(MODULE, '目的港清关动作保持完成和版本事实', response,
+ id(completed?.id) === expectedDocumentId && completed?.documentType === 'DESTINATION_CLEARANCE'
+ && Boolean(completed?.completedAt) && Number.isInteger(Number(completed?.version)),
+ `documentId=${id(completed?.id) || 'missing'}, completedAt=${completed?.completedAt || 'missing'}, version=${completed?.version ?? 'missing'}`, OPTIONS)
+
+ matrix.pages.containerLoading.actions = { clearance: fixture(containerId, 'COMPLETED',
+ `/container-loading/${containerId}/smart-loading`, {
+ documentId: id(completed?.id), documentVersion: Number(completed?.version),
+ completedAt: completed?.completedAt || null,
}) }
}
@@ -464,6 +425,6 @@ export async function run(context, matrix = context.createdResources.manualState
id(listDetail?.summary?.planId) === listFixture.id && listDetail?.summary?.status === 'COMPLETED',
`planId=${listFixture.id}, actual=${listDetail?.summary?.status || 'missing'}`, OPTIONS)
- await completeSimpleDocuments(context, completed, matrix)
+ await registerClearanceAction(context, matrix)
}
import { iso6346ContainerNumber } from './iso6346-container-number.mjs'
diff --git a/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs b/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs
index 29201174a..94b20a224 100644
--- a/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs
+++ b/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs
@@ -26,6 +26,11 @@ test('focused matrix execution injects real container lifecycle prerequisites an
'phase3-evidence-gate',
'phase3-loading-finalize',
'phase3-customs',
+ 'phase3-port-schedules',
+ 'truck-appointments',
+ 'service-center-flow',
+ 'phase3-outbound-dispatch',
+ 'phase3-container-documents',
'manual-state-matrix',
]
assert.deepEqual(normalizeModuleOrder(['order-inbound-status-fixtures', 'manual-state-matrix']), expected)
@@ -47,6 +52,11 @@ test('focused splitting and manual groups resolve producers before consumers wit
'phase3-loading-finalize',
'phase3-order-splitting',
'phase3-customs',
+ 'phase3-port-schedules',
+ 'truck-appointments',
+ 'service-center-flow',
+ 'phase3-outbound-dispatch',
+ 'phase3-container-documents',
'manual-state-matrix',
]
assert.deepEqual(normalizeModuleOrder([
diff --git a/scripts/admin-test-env-modules/master-data-version-contract.test.mjs b/scripts/admin-test-env-modules/master-data-version-contract.test.mjs
index 6b2f0d5f6..637964831 100644
--- a/scripts/admin-test-env-modules/master-data-version-contract.test.mjs
+++ b/scripts/admin-test-env-modules/master-data-version-contract.test.mjs
@@ -10,3 +10,9 @@ test('non-versioned master data stays explicit without making the full gate perm
assert.match(source, /非版本化主数据不伪造旧version请求/)
assert.match(source, /phase4-overseas-master-data/)
})
+
+test('warehouse import fixture tracks the current version two template and internal code column', () => {
+ assert.match(source, /仓库编码\*', '内部编号', '仓库名称/)
+ assert.match(source, /sheet: '仓库', templateVersion: '2'/)
+ assert.match(source, /xlsxBytes\(fixture\.headers, fixture\.values, fixture\.sheet, fixture\.templateVersion\)/)
+})
diff --git a/scripts/admin-test-env-modules/master-data.mjs b/scripts/admin-test-env-modules/master-data.mjs
index bfe308b76..8a2760e92 100644
--- a/scripts/admin-test-env-modules/master-data.mjs
+++ b/scripts/admin-test-env-modules/master-data.mjs
@@ -31,8 +31,8 @@ function u16(value) { return Uint8Array.from([value & 255, (value >>> 8) & 255])
function u32(value) { return Uint8Array.from([value & 255, (value >>> 8) & 255, (value >>> 16) & 255, (value >>> 24) & 255]) }
function concat(...parts) { const result = new Uint8Array(parts.reduce((sum, item) => sum + item.length, 0)); let offset = 0; for (const part of parts) { result.set(part, offset); offset += part.length } return result }
function xmlEscape(value) { return String(value ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''') }
-function xlsxBytes(headers, values, sheetName) {
- const rows = [['模板版本', '1'], ['说明', 'Admin HTTP acceptance fixture'], headers, values]
+function xlsxBytes(headers, values, sheetName, templateVersion = '1') {
+ const rows = [['模板版本', templateVersion], ['说明', 'Admin HTTP acceptance fixture'], headers, values]
const cells = rows.map((row, rowIndex) => `${row.map((value, columnIndex) => `${xmlEscape(value)}`).join('')}
`).join('')
const files = {
'[Content_Types].xml': '',
@@ -189,7 +189,7 @@ export async function run(context) {
['往来单位', '/api/platform/v1/partners/import-template'],
]
const importFixtures = {
- 仓库: { headers: ['仓库编码*', '仓库名称', '仓库类型', '国家二字码', '省/州', '城市*', '区/县', '详细地址*', '邮政编码', '时区', '状态', '联系人', '联系电话', '联系邮箱', '备注'], values: [`IMW${suffix}`, `导入验收仓-${suffix}`, 'DOMESTIC', 'CN', '广东省', '深圳市', '南山区', `导入地址-${context.marker}`, '518000', 'Asia/Shanghai', 'ACTIVE', '导入联系人', '13800000011', `im-${suffix}@example.com`, context.marker], sheet: '仓库' },
+ 仓库: { headers: ['仓库编码*', '内部编号', '仓库名称', '仓库类型', '国家二字码', '省/州', '城市*', '区/县', '详细地址*', '邮政编码', '时区', '状态', '联系人', '联系电话', '联系邮箱', '备注'], values: [`IMW${suffix}`, '', `导入验收仓-${suffix}`, 'DOMESTIC', 'CN', '广东省', '深圳市', '南山区', `导入地址-${context.marker}`, '518000', 'Asia/Shanghai', 'ACTIVE', '导入联系人', '13800000011', `im-${suffix}@example.com`, context.marker], sheet: '仓库', templateVersion: '2' },
快递公司: { headers: ['快递公司编码*', '快递公司名称*', '客服电话', '官方网站', '轨迹查询地址模板', '状态*', '备注'], values: [`IME${suffix}`, `导入快递-${suffix}`, '4000000011', 'https://example.com', 'https://example.com/{trackingNo}', 'ACTIVE', context.marker], sheet: '快递公司' },
往来单位: { headers: ['单位编码*', '单位名称*', '单位类型*', '联系人*', '联系电话*', '联系邮箱', '船司标准编码', '国家二字码*', '省/州', '城市*', '区/县', '详细地址*', '邮政编码', '状态*', '备注'], values: [`IMP${suffix}`, `导入船公司-${suffix}`, 'CARRIER', '导入联系人', '13800000012', `ip-${suffix}@example.com`, 'MATSON', 'CN', '广东省', '深圳市', '南山区', `导入地址-${context.marker}`, '518000', 'ACTIVE', context.marker], sheet: '往来单位' },
}
@@ -199,7 +199,7 @@ export async function run(context) {
`bytes=${response.bytes?.length ?? 0}, contentType=${response.contentType || 'missing'}`)
if (xlsxOk(response)) {
const fixture = importFixtures[name]
- const fixtureBytes = xlsxBytes(fixture.headers, fixture.values, fixture.sheet)
+ const fixtureBytes = xlsxBytes(fixture.headers, fixture.values, fixture.sheet, fixture.templateVersion)
const importPath = `${path.replace('/import-template', '/import')}?tenantId=0`
const first = await uploadTemplate(context, importPath, fixtureBytes, `${context.marker}-${name}.xlsx`)
const firstData = first.body?.data
diff --git a/scripts/admin-test-env-modules/phase3-container-overview.mjs b/scripts/admin-test-env-modules/phase3-container-overview.mjs
index 3baabdd5d..503ddfe26 100644
--- a/scripts/admin-test-env-modules/phase3-container-overview.mjs
+++ b/scripts/admin-test-env-modules/phase3-container-overview.mjs
@@ -94,14 +94,17 @@ export async function run(context) {
for (const documentType of ['DELIVERY_ORDER']) {
const response = await context.request('GET', `${CONTAINERS}/${encodeURIComponent(containerId)}/documents/${documentType}`)
- const data = context.apiSuccess(MODULE, `查询 ${documentType} 单证`, response, `documentType=${documentType}`, OPTIONS)
- context.assert(MODULE, `${documentType} 单证归属货柜`, response,
- data == null || id(data.containerId || containerId) === containerId,
- `containerBookingId=${containerId}`, OPTIONS)
+ context.assert(MODULE, `国内未出仓时 ${documentType} 不生成国外工作台数据`, response,
+ response.http === 200 && String(response?.body?.code) === 'A0404'
+ && String(response?.body?.message || '').includes('国外工作台货柜不存在'),
+ `HTTP=${response.http}, code=${response?.body?.code ?? 'missing'}, documentType=${documentType}`, OPTIONS)
}
const clearance = await context.request('GET', `${CONTAINERS}/${encodeURIComponent(containerId)}/clearance`)
- context.apiSuccess(MODULE, '查询目的港清关资料', clearance, `containerBookingId=${containerId}`, OPTIONS)
+ context.assert(MODULE, '国内未出仓时目的港清关不生成国外工作台数据', clearance,
+ clearance.http === 200 && String(clearance?.body?.code) === 'A0404'
+ && String(clearance?.body?.message || '').includes('国外工作台货柜不存在'),
+ `HTTP=${clearance.http}, code=${clearance?.body?.code ?? 'missing'}, containerBookingId=${containerId}`, OPTIONS)
const customs = await context.request('GET', `/api/platform/v1/first-leg/loading-lists/${encodeURIComponent(planId)}/customs?warehouseId=${encodeURIComponent(warehouseId)}`)
if (fixture?.finalStatus === 'LOADED') {
context.apiSuccess(MODULE, '查询出口报关资料', customs, `loadingPlanId=${planId}, warehouseId=${warehouseId}`, OPTIONS)
diff --git a/scripts/admin-test-env-modules/phase3-container-regression-contract.test.mjs b/scripts/admin-test-env-modules/phase3-container-regression-contract.test.mjs
index 160033774..ff083903f 100644
--- a/scripts/admin-test-env-modules/phase3-container-regression-contract.test.mjs
+++ b/scripts/admin-test-env-modules/phase3-container-regression-contract.test.mjs
@@ -52,6 +52,12 @@ test('overview regression requires workflowStatus and distinguishes 100 percent
assert.doesNotMatch(matrix, /PDA overview `workflowStatus` 缺失[\s\S]*BLOCKED_CONTRACT/)
})
+test('pre-departure overview rejects destination workbench documents explicitly', () => {
+ assert.match(overviewAcceptance, /国内未出仓时 \$\{documentType\} 不生成国外工作台数据/)
+ assert.match(overviewAcceptance, /国内未出仓时目的港清关不生成国外工作台数据/)
+ assert.match(overviewAcceptance, /国外工作台货柜不存在/)
+})
+
test('active overview photos download through controlled paths while void history has no download entry', () => {
assert.match(overviewOpenApi, /\/api\/platform\/v1\/containers\/\{id\}\/pda\/photos\/\{fileId\}\/download/)
assert.match(overviewAcceptance, /\['evidenceId', 'fileId', 'status'\]\.every/)
diff --git a/scripts/admin-test-env-modules/phase3-order-splitting-contract.test.mjs b/scripts/admin-test-env-modules/phase3-order-splitting-contract.test.mjs
index 96a8a44ec..14dc2b700 100644
--- a/scripts/admin-test-env-modules/phase3-order-splitting-contract.test.mjs
+++ b/scripts/admin-test-env-modules/phase3-order-splitting-contract.test.mjs
@@ -64,6 +64,17 @@ test('PDA opens only after linking a released requirement and uses stable plan i
assert.doesNotMatch(source, /push-preload/)
})
+test('plan confirmation prepares the remainder initial fee first', () => {
+ const feeIndex = source.indexOf('ensureInitialCalculated(context')
+ const confirmIndex = source.indexOf("`/api/platform/v1/first-leg/loading-plans/${encodeURIComponent(planId)}/confirm`")
+ assert.match(source, /import \{ ensureInitialCalculated \} from '\.\/inbound-fees\.mjs'/)
+ assert.ok(feeIndex >= 0 && confirmIndex > feeIndex)
+ assert.match(source, /inboundId: id\(candidate\.inboundId\)/)
+ assert.match(source, /finalStatus: 'APPROVED'/)
+ assert.match(source, /processStatus: 'WAIT_SPLIT'/)
+ assert.match(source, /if \(!initialFee\) return null/)
+})
+
test('each remainder settles only through inventory snapshot, atomic batch and explicit complete', () => {
const claimIndex = source.indexOf("`${PDA}/tasks/${taskId}/claim`")
const snapshotIndex = source.indexOf('inventory-snapshot?warehouseId=')
diff --git a/scripts/admin-test-env-modules/phase3-order-splitting.mjs b/scripts/admin-test-env-modules/phase3-order-splitting.mjs
index b4249f523..b57902ad2 100644
--- a/scripts/admin-test-env-modules/phase3-order-splitting.mjs
+++ b/scripts/admin-test-env-modules/phase3-order-splitting.mjs
@@ -9,6 +9,7 @@ const PDA = '/api/platform/v1/first-leg/pda-loading'
import { oceanBookingDraft } from './ocean-booking-fixture.mjs'
import { iso6346ContainerNumber } from './iso6346-container-number.mjs'
+import { ensureInitialCalculated } from './inbound-fees.mjs'
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
@@ -242,6 +243,14 @@ async function loadOneFragment(context, state) {
`orderId=${sourceOrderId}, available=${candidate?.availablePackageCount ?? 'missing'}`, OPTIONS)
if (!candidate?.inboundId || !candidate?.inboundDetailId) return null
+ const initialFee = await ensureInitialCalculated(context, {
+ tenantId,
+ inboundId: id(candidate.inboundId),
+ finalStatus: 'APPROVED',
+ processStatus: 'WAIT_SPLIT',
+ }, OPTIONS)
+ if (!initialFee) return null
+
const createPlan = await request(context, 'GET', `${SMART}/plans/by-booking/${containerId}`)
const plan = context.apiSuccess(MODULE, `读取 ${sourceOrderNo} 模拟柜自动创建的装柜计划`, createPlan, '', OPTIONS)
const planId = id(plan?.id)
diff --git a/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs b/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs
index e7129fd93..83d107f6c 100644
--- a/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs
+++ b/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs
@@ -118,7 +118,7 @@ test('measurement and loading pass destination identity into the LOADED producer
test('only PDA-operated variants traverse real outbound and carrier nodes before overseas receiving', () => {
const entry = read('../admin-test-env-acceptance.mjs')
assert.match(entry, /runPhase4ProducerVariant\('OVERSEAS-PRIMARY', 'OVERSEAS', 'ONE_CLICK_DROPSHIP', true\)/)
- assert.match(entry, /runPhase4ProducerVariant\('FBA-STANDARD', 'FBA', 'STANDARD', false\)/)
+ assert.match(entry, /runPhase4ProducerVariant\('FBA-STANDARD', 'FBA', 'STANDARD', true\)/)
assert.match(entry, /runPhase4ProducerVariant\('SECONDARY', 'FBA', 'STANDARD', false\)/)
const start = entry.indexOf('if (pickupRequired && producerKey)')
const end = entry.indexOf('} finally {', start)
@@ -147,6 +147,7 @@ test('only PDA-operated variants traverse real outbound and carrier nodes before
assert.match(entry, /overseasReady\?\.shippingReady === true/)
assert.match(entry, /awaitingVariants\.length >= 1/)
assert.doesNotMatch(entry, /awaitingVariants\.length === 2/)
+ assert.match(entry, /standardFbaReady\?\.shippingReady === true/)
assert.match(entry, /awaitingVariants\.every\(\(item\) => item\.shippingReady === false/)
})
diff --git a/scripts/admin-test-env-modules/phase3-warehouse-rbac.mjs b/scripts/admin-test-env-modules/phase3-warehouse-rbac.mjs
index dd09e2b4c..e112f93b4 100644
--- a/scripts/admin-test-env-modules/phase3-warehouse-rbac.mjs
+++ b/scripts/admin-test-env-modules/phase3-warehouse-rbac.mjs
@@ -24,8 +24,10 @@ const PHASE5_OPERATOR_CODES = [
'platform:overseas-warehouse:delivery:list',
'platform:overseas-warehouse:exception-order:list',
'platform:overseas-warehouse:exception-order:create',
+ 'platform:overseas-warehouse:exception-order:process',
'platform:overseas-warehouse:delivery:return',
'platform:overseas-warehouse:delivery:status',
+ 'platform:overseas-warehouse:delivery:documents',
'platform:overseas-warehouse:customer-visit:list',
'platform:overseas-warehouse:customer-visit:edit',
]
@@ -88,7 +90,7 @@ export async function run(context) {
context.assert(MODULE, '操作员具备国内入仓权限、四期只读权限与五期履约权限', rolesResponse,
operatorCodes.has('platform:firstleg:inbound:loading:view')
&& operatorCodes.has('platform:firstleg:inbound:loading:edit')
- && operatorCodes.size === 29
+ && operatorCodes.size === 31
&& PHASE4_READ_CODES.every((code) => operatorCodes.has(code))
&& PHASE5_OPERATOR_CODES.every((code) => operatorCodes.has(code))
&& !operatorCodes.has('platform:firstleg:inbound:loading:complete')
diff --git a/scripts/admin-test-env-modules/phase4-canonical-warehouse-rbac-contract.test.mjs b/scripts/admin-test-env-modules/phase4-canonical-warehouse-rbac-contract.test.mjs
index d3997a36c..8bdd57e31 100644
--- a/scripts/admin-test-env-modules/phase4-canonical-warehouse-rbac-contract.test.mjs
+++ b/scripts/admin-test-env-modules/phase4-canonical-warehouse-rbac-contract.test.mjs
@@ -26,7 +26,7 @@ test('warehouse operator and tally use the exact phase-four and phase-five permi
assert.match(iamSql, new RegExp(`'WAREHOUSE_OPERATOR','${code}'`))
assert.match(iamSql, new RegExp(`'WAREHOUSE_TALLY','${code}'`))
}
- assert.match(acceptance, /operatorCodes\.size === 29/)
+ assert.match(acceptance, /operatorCodes\.size === 31/)
assert.match(acceptance, /tallyCodes\.size === 27/)
assert.match(acceptance, /PHASE4_WRITE_SUFFIXES/)
assert.match(iamSql, /assert_phase4_warehouse_role_minimum/)
@@ -47,8 +47,10 @@ test('warehouse roles include the exact phase-five fulfillment responsibilities'
'platform:overseas-warehouse:delivery:list',
'platform:overseas-warehouse:exception-order:list',
'platform:overseas-warehouse:exception-order:create',
+ 'platform:overseas-warehouse:exception-order:process',
'platform:overseas-warehouse:delivery:return',
'platform:overseas-warehouse:delivery:status',
+ 'platform:overseas-warehouse:delivery:documents',
'platform:overseas-warehouse:customer-visit:list',
'platform:overseas-warehouse:customer-visit:edit'
]) {