test: stabilize full dev acceptance orchestration

This commit is contained in:
2026-09-09 20:36:53 +08:00
parent 71604c9c39
commit eac6875877
16 changed files with 104 additions and 85 deletions
+7 -3
View File
@@ -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
@@ -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'/)
@@ -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 }
@@ -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,
@@ -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/)
})
@@ -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'
@@ -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([
@@ -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\)/)
})
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;') }
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 r="${rowIndex + 1}">${row.map((value, columnIndex) => `<c r="${String.fromCharCode(65 + columnIndex)}${rowIndex + 1}" t="inlineStr"><is><t>${xmlEscape(value)}</t></is></c>`).join('')}</row>`).join('')
const files = {
'[Content_Types].xml': '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>',
@@ -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
@@ -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)
@@ -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/)
@@ -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=')
@@ -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)
@@ -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/)
})
@@ -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')
@@ -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'
]) {