const MODULE = 'phase4-overseas-master-data' const DOMESTIC_BASE = '/api/platform/v1/warehouse-admin' const OVERSEAS_BASE = '/api/platform/v1/last-leg/master-data' const BLOCKER = { scenario: 'phase4-overseas-master-data-lifecycle', deploymentBlocker: true } const success = (response) => response?.http === 200 && String(response?.body?.code) === '200' const data = (response) => success(response) ? response.body.data : null const recordVersion = (value) => Number.isInteger(Number(value)) && Number(value) >= 0 const asId = (value) => value == null ? '' : String(value) function requireData(context, name, response) { const value = context.apiSuccess(MODULE, name, response, '', BLOCKER) if (value == null) throw new Error(`${name}失败,生命周期无法继续`) return value } function assertBinaryCsv(context, name, response, expectedHeader, expectedTokens) { const text = response?.bytes instanceof Uint8Array ? new TextDecoder().decode(response.bytes).replace(/^\uFEFF/, '') : '' const header = text.split(/\r?\n/, 1)[0] context.assert(MODULE, name, response, response?.http === 200 && String(response.contentType || '').toLowerCase().startsWith('text/csv') && response.bytes instanceof Uint8Array && response.bytes.length > 0, `HTTP=${response?.http ?? 0}, contentType=${response?.contentType || 'missing'}, bytes=${response?.bytes?.length || 0}`, BLOCKER) context.assert(MODULE, `${name}表头与marker内容`, response, header === expectedHeader && expectedTokens.every((token) => text.includes(String(token))), `header=${header || 'missing'}, expectedTokens=${expectedTokens.join('|')}`, BLOCKER) } const optionIds = (values) => Array.isArray(values) ? values.map((item) => asId(item?.warehouseId)).filter(Boolean) : [] function assertForbidden(context, name, response) { context.assert(MODULE, name, response, response?.http === 403 || (response?.http === 200 && ['403', 'A0403'].includes(String(response?.body?.code))), `HTTP=${response?.http ?? 0}, code=${response?.body?.code ?? 'missing'}`, BLOCKER) } async function safeCleanup(context, name, action) { try { await action() } catch (error) { context.assert(MODULE, `${name} finally停用`, null, false, error instanceof Error ? error.message : String(error), BLOCKER) } } async function continueLifecycle(context, name, action) { try { await action() } catch (error) { context.assert(MODULE, `${name}生命周期执行`, null, false, error instanceof Error ? error.message : String(error), BLOCKER) } } function warehousePayload(marker, edited = false) { const suffix = marker.replace(/[^A-Za-z0-9]/g, '').slice(-20).toUpperCase() return { tenantId: 0, warehouseCode: `P4-${suffix}`.slice(0, 50), warehouseName: edited ? `${marker} 合作仓-已编辑` : `${marker} 合作仓`, warehouseType: 'OVERSEAS_PARTNER', countryCode: 'US', timezone: 'UTC-8', contactName: 'Phase4 QA', contactPhone: '+1-213-555-0199', contactEmail: null, status: 'ACTIVE', remark: edited ? `${marker} lifecycle edited` : `${marker} lifecycle`, address: { addressType: 'WAREHOUSE', countryCode: 'US', stateProvince: 'California', city: 'Los Angeles', district: 'Commerce', postalCode: '90040', addressLine1: edited ? '402 Phase Four Test Avenue' : '401 Phase Four Test Avenue', addressLine2: null, latitude: null, longitude: null, }, capabilities: [{ capabilityCode: 'STORAGE', capabilityName: 'Storage', enabled: true, cutoffTime: null, description: `${marker} acceptance capability`, }], } } async function createAndVerifyPartnerWarehouse(context, resource) { const initial = warehousePayload(context.marker) const createResponse = await context.request('POST', DOMESTIC_BASE, initial) const createdId = asId(requireData(context, '新增OVERSEAS_PARTNER合作仓', createResponse)) if (!createdId) throw new Error('合作仓创建响应未返回ID') resource.partnerWarehouse = { id: createdId, code: initial.warehouseCode, finalStatus: 'ACTIVE' } const detailPath = `${DOMESTIC_BASE}/${encodeURIComponent(createdId)}?tenantId=0` const detailResponse = await context.request('GET', detailPath) const detail = requireData(context, '查询合作仓详情', detailResponse) context.assert(MODULE, '合作仓真实类型与marker', detailResponse, detail?.warehouse?.warehouseType === 'OVERSEAS_PARTNER' && detail.warehouse.warehouseCode === initial.warehouseCode, `id=${createdId}, type=${detail?.warehouse?.warehouseType || 'missing'}, code=${detail?.warehouse?.warehouseCode || 'missing'}`, BLOCKER) const edited = warehousePayload(context.marker, true) const editResponse = await context.request('PUT', detailPath, edited) context.apiSuccess(MODULE, '编辑合作仓(当前契约无version)', editResponse, 'warehouse SaveRequest 无乐观锁字段', BLOCKER) const afterResponse = await context.request('GET', detailPath) const after = requireData(context, '编辑后查询合作仓详情', afterResponse) context.assert(MODULE, '合作仓编辑持久化', afterResponse, after?.warehouse?.warehouseName === edited.warehouseName && after?.warehouse?.remark === edited.remark && after?.warehouse?.warehouseType === 'OVERSEAS_PARTNER', `name=${after?.warehouse?.warehouseName || 'missing'}, type=${after?.warehouse?.warehouseType || 'missing'}`, BLOCKER) } async function captureWarehouseOptionsBeforeCreate(context, resource) { const response = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`) const options = requireData(context, '创建合作仓前查询报价候选缓存基线', response) context.assert(MODULE, '合作仓候选基线为规范数组', response, Array.isArray(options) && options.every((item) => asId(item?.warehouseId) && item.warehouseCode && item.warehouseName && /^[A-Z]{2}$/.test(item.countryCode)), `count=${Array.isArray(options) ? options.length : 'invalid'}`, BLOCKER) resource.partnerWarehouseOptionsBefore = optionIds(options) } function quoteCreatePayload(context, warehouseId) { return { name: `${context.marker} 海外报价`, warehouseId, providerName: `${context.marker} Provider`, currency: 'USD', effectiveStart: '2098-01-01', effectiveEnd: '2098-12-31', versionCode: `${context.marker}-V1`.slice(0, 64), warehouseFees: [{ item: 'Storage QA', billingUnit: 'PALLET', unitPrice: 1.25, minimumCharge: 2.5, remark: context.marker }], transitPrices: [{ fbaWarehouseCode: 'ONT8', deliveryMethod: 'TRUCK', estimatedMiles: 12.5, price: 18.75, minimumCharge: 20, remark: context.marker }], remark: `${context.marker} lifecycle`, } } function quoteUpdatePayload(created, context, version) { return { recordVersion: version, name: `${context.marker} 海外报价-已编辑`, providerName: created.providerName, currency: created.currency, effectiveStart: created.effectiveStart, effectiveEnd: created.effectiveEnd, warehouseFees: [{ item: 'Storage QA Edited', billingUnit: 'PALLET', unitPrice: 1.5, minimumCharge: 3, remark: `${context.marker} edited` }], transitPrices: created.transitPrices, remark: `${context.marker} lifecycle edited`, } } async function quoteLifecycle(context, resource) { const optionsResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`) const options = requireData(context, '查询海外报价合作仓候选', optionsResponse) context.assert(MODULE, '报价候选只来自海外正式接口', optionsResponse, Array.isArray(options) && options.every((item) => asId(item.warehouseId) && item.warehouseCode && item.warehouseName && item.countryCode), `count=${Array.isArray(options) ? options.length : 'invalid'}`, BLOCKER) const createdId = resource.partnerWarehouse?.id if (resource.partnerWarehouseOptionsBefore?.length === 0) { context.assert(MODULE, '空候选不负缓存30分钟', optionsResponse, optionIds(options).includes(createdId), `createdId=${createdId}, ids=${optionIds(options).join(',') || 'empty'}`, BLOCKER) } const refreshResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options?refresh=true`) const refreshed = requireData(context, '强制刷新海外报价合作仓候选', refreshResponse) const option = Array.isArray(refreshed) ? refreshed.find((item) => asId(item.warehouseId) === createdId) : null context.assert(MODULE, '刷新后新建合作仓立即可见', refreshResponse, Boolean(option), `createdId=${createdId}, ids=${optionIds(refreshed).join(',') || 'empty'}`, BLOCKER) if (!option) throw new Error(`warehouse-options refresh=true 未返回本轮合作仓 ${createdId}`) const cachedResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`) const cached = requireData(context, '刷新后读取30分钟正缓存', cachedResponse) context.assert(MODULE, '30分钟正缓存与刷新快照一致', cachedResponse, JSON.stringify(cached) === JSON.stringify(refreshed), `refreshed=${optionIds(refreshed).join(',')}, cached=${optionIds(cached).join(',')}`, BLOCKER) const payload = quoteCreatePayload(context, asId(option.warehouseId)) const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/overseas-quotes`, payload) let detail = requireData(context, '新增海外报价', createResponse) const id = asId(detail?.summary?.id) if (!id || !recordVersion(detail?.summary?.recordVersion)) throw new Error('报价创建响应缺少字符串ID或recordVersion') resource.quote = { id, warehouseId: payload.warehouseId, recordVersion: Number(detail.summary.recordVersion), finalStatus: detail.summary.status } const path = `${OVERSEAS_BASE}/overseas-quotes/${encodeURIComponent(id)}` const detailResponse = await context.requestOverseas('GET', path) detail = requireData(context, '查询海外报价详情', detailResponse) context.assert(MODULE, '报价详情长ID、费用和版本', detailResponse, asId(detail?.summary?.id) === id && recordVersion(detail?.summary?.recordVersion) && Array.isArray(detail?.warehouseFees) && detail.warehouseFees.length > 0, `id=${asId(detail?.summary?.id) || 'missing'}, version=${detail?.summary?.recordVersion ?? 'missing'}`, BLOCKER) const listResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes?page=1&size=20&keyword=${encodeURIComponent(context.marker)}`) const page = requireData(context, '分页查询marker海外报价', listResponse) context.assert(MODULE, '报价列表包含本轮长ID与recordVersion', listResponse, Array.isArray(page?.records) && page.records.some((item) => asId(item.id) === id && recordVersion(item.recordVersion)), `id=${id}, total=${page?.total ?? 'missing'}`, BLOCKER) const staleVersion = Number(detail.summary.recordVersion) const updatePayload = quoteUpdatePayload({ ...payload, transitPrices: payload.transitPrices }, context, staleVersion) const updateResponse = await context.requestOverseas('PUT', path, updatePayload) detail = requireData(context, '编辑海外报价', updateResponse) resource.quote.recordVersion = Number(detail.summary.recordVersion) context.assert(MODULE, '报价编辑版本递增', updateResponse, detail.summary.name === updatePayload.name && resource.quote.recordVersion === staleVersion + 1, `before=${staleVersion}, after=${resource.quote.recordVersion}`, BLOCKER) const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, name: `${context.marker} stale` }) context.expectConflict(MODULE, '报价旧recordVersion编辑冲突', conflictResponse, BLOCKER.scenario) const afterConflictResponse = await context.requestOverseas('GET', path) detail = requireData(context, '报价冲突后重新查询', afterConflictResponse) context.assert(MODULE, '报价冲突未覆盖新值', afterConflictResponse, detail.summary.name === updatePayload.name && Number(detail.summary.recordVersion) === resource.quote.recordVersion, `name=${detail.summary.name}, version=${detail.summary.recordVersion}`, BLOCKER) const activateResponse = await context.requestOverseas('PATCH', `${path}/status`, { status: 'ACTIVE', recordVersion: resource.quote.recordVersion, }) detail = requireData(context, '启用海外报价', activateResponse) resource.quote.recordVersion = Number(detail.summary.recordVersion) resource.quote.finalStatus = detail.summary.status context.assert(MODULE, '报价状态切换为ACTIVE', activateResponse, detail.summary.status === 'ACTIVE', `status=${detail.summary.status}`, BLOCKER) const csv = await context.requestOverseasBinary('GET', `${OVERSEAS_BASE}/overseas-quotes/export?keyword=${encodeURIComponent(context.marker)}`) assertBinaryCsv(context, '导出海外报价CSV', csv, '报价ID,报价名称,合作仓编码,合作仓名称,报价方,币种,生效开始,生效结束,版本号,状态,更新时间', [id, context.marker]) return true } async function chooseSafeExchangeMonth(context) { const digits = context.marker.replace(/\D/g, '') const seed = Number(digits.slice(-4) || '0') for (let offset = 0; offset < 24; offset += 1) { const serial = ((seed + offset) % 120) const month = `${2090 + Math.floor(serial / 12)}-${String((serial % 12) + 1).padStart(2, '0')}` const path = `${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=${month}&monthTo=${month}` const response = await context.requestOverseas('GET', path) const page = requireData(context, `探测安全汇率月份${month}`, response) if (Array.isArray(page?.records) && page.records.length === 0 && Number(page.total) === 0) return month } throw new Error('2090-2099安全月份池均已占用,拒绝覆盖既有汇率') } async function exchangeLifecycle(context, resource) { const month = await chooseSafeExchangeMonth(context) const createPayload = { month, usdToCnyAverage: 7.123456, remark: `${context.marker} safe marker month` } const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/exchange-rates`, createPayload) let rate = requireData(context, '新增安全marker月汇率', createResponse) resource.exchangeRate = { month, recordVersion: Number(rate.recordVersion), finalStatus: rate.status } const path = `${OVERSEAS_BASE}/exchange-rates/${encodeURIComponent(month)}` const detailResponse = await context.requestOverseas('GET', path) rate = requireData(context, '查询marker月汇率详情', detailResponse) context.assert(MODULE, '汇率详情月份、decimal与版本', detailResponse, rate.month === month && Number(rate.usdToCnyAverage) > 0 && recordVersion(rate.recordVersion), `month=${rate.month}, rate=${rate.usdToCnyAverage}, version=${rate.recordVersion}`, BLOCKER) const staleVersion = Number(rate.recordVersion) const updatePayload = { recordVersion: staleVersion, usdToCnyAverage: 7.234567, remark: `${context.marker} overwritten once` } const updateResponse = await context.requestOverseas('PUT', path, updatePayload) rate = requireData(context, '覆盖维护marker月汇率', updateResponse) resource.exchangeRate.recordVersion = Number(rate.recordVersion) context.assert(MODULE, '汇率覆盖版本递增', updateResponse, Number(rate.usdToCnyAverage) === updatePayload.usdToCnyAverage && resource.exchangeRate.recordVersion === staleVersion + 1, `before=${staleVersion}, after=${resource.exchangeRate.recordVersion}`, BLOCKER) const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, usdToCnyAverage: 7.345678 }) context.expectConflict(MODULE, '汇率旧recordVersion覆盖冲突', conflictResponse, BLOCKER.scenario) const afterConflictResponse = await context.requestOverseas('GET', path) rate = requireData(context, '汇率冲突后重新查询', afterConflictResponse) context.assert(MODULE, '汇率冲突未覆盖新值', afterConflictResponse, Number(rate.usdToCnyAverage) === updatePayload.usdToCnyAverage && Number(rate.recordVersion) === resource.exchangeRate.recordVersion, `rate=${rate.usdToCnyAverage}, version=${rate.recordVersion}`, BLOCKER) const statusResponse = await context.requestOverseas('PATCH', `${path}/status`, { status: 'INACTIVE', recordVersion: resource.exchangeRate.recordVersion, remark: `${context.marker} lifecycle complete`, }) rate = requireData(context, '停用marker月汇率', statusResponse) resource.exchangeRate.recordVersion = Number(rate.recordVersion) resource.exchangeRate.finalStatus = rate.status context.assert(MODULE, '汇率状态切换为INACTIVE', statusResponse, rate.status === 'INACTIVE', `status=${rate.status}`, BLOCKER) const listResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=${month}&monthTo=${month}&status=INACTIVE`) const page = requireData(context, '分页查询marker月汇率', listResponse) context.assert(MODULE, '汇率列表包含本轮月份与recordVersion', listResponse, Array.isArray(page?.records) && page.records.some((item) => item.month === month && recordVersion(item.recordVersion)), `month=${month}, total=${page?.total ?? 'missing'}`, BLOCKER) const csv = await context.requestOverseasBinary('GET', `${OVERSEAS_BASE}/exchange-rates/export?monthFrom=${month}&monthTo=${month}`) assertBinaryCsv(context, '导出marker月汇率CSV', csv, '月份,基准币种,报价币种,月平均汇率,状态,更新人,更新时间', [month, 'INACTIVE']) } function vehiclePayload(context, edited = false) { const suffix = context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-18).toUpperCase() return { plateNumber: `QA-${suffix}`.slice(0, 64), model: edited ? 'Phase4 QA Truck Edited' : 'Phase4 QA Truck', fuelConsumptionPer100Km: edited ? 18.75 : 18.5, mileageKm: edited ? 101 : 100, driverOrDispatchCompany: `${context.marker} Carrier`, phone: '+1-213-555-0188', region: 'OVERSEAS', ownership: 'OWNED', ...(edited ? {} : { status: 'IN_SERVICE' }), remark: edited ? `${context.marker} lifecycle edited` : `${context.marker} lifecycle`, } } async function vehicleLifecycle(context, resource) { const createPayload = vehiclePayload(context) const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/vehicles`, createPayload) let vehicle = requireData(context, '新增marker车辆', createResponse) const id = asId(vehicle.id) if (!id || !recordVersion(vehicle.recordVersion)) throw new Error('车辆创建响应缺少字符串ID或recordVersion') resource.vehicle = { id, plateNumber: createPayload.plateNumber, recordVersion: Number(vehicle.recordVersion), finalStatus: vehicle.status } const path = `${OVERSEAS_BASE}/vehicles/${encodeURIComponent(id)}` const detailResponse = await context.requestOverseas('GET', path) vehicle = requireData(context, '查询marker车辆详情', detailResponse) context.assert(MODULE, '车辆详情长ID、五态字段与版本', detailResponse, asId(vehicle.id) === id && ['IN_SERVICE', 'DISPATCHED', 'MAINTENANCE', 'INACTIVE', 'RETURNED'].includes(vehicle.status) && recordVersion(vehicle.recordVersion), `id=${asId(vehicle.id)}, status=${vehicle.status}, version=${vehicle.recordVersion}`, BLOCKER) const staleVersion = Number(vehicle.recordVersion) const updatePayload = { recordVersion: staleVersion, ...vehiclePayload(context, true) } const updateResponse = await context.requestOverseas('PUT', path, updatePayload) vehicle = requireData(context, '编辑marker车辆', updateResponse) const updatedVersion = Number(vehicle.recordVersion) const updatedMileage = Number(vehicle.mileageKm) resource.vehicle.recordVersion = updatedVersion context.assert(MODULE, '车辆编辑版本递增', updateResponse, vehicle.model === updatePayload.model && updatedMileage === Number(updatePayload.mileageKm) && updatedVersion === Number(staleVersion) + 1, `before=${staleVersion}, after=${updatedVersion}, mileage=${updatedMileage}`, BLOCKER) const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, model: 'stale overwrite' }) context.expectConflict(MODULE, '车辆旧recordVersion编辑冲突', conflictResponse, BLOCKER.scenario) const afterConflictResponse = await context.requestOverseas('GET', path) vehicle = requireData(context, '车辆冲突后重新查询', afterConflictResponse) context.assert(MODULE, '车辆冲突未覆盖新值', afterConflictResponse, vehicle.model === updatePayload.model && Number(vehicle.recordVersion) === resource.vehicle.recordVersion, `model=${vehicle.model}, version=${vehicle.recordVersion}`, BLOCKER) const statusResponse = await context.requestOverseas('PATCH', `${path}/status`, { status: 'MAINTENANCE', recordVersion: resource.vehicle.recordVersion, }) vehicle = requireData(context, '车辆切换安全维护状态', statusResponse) resource.vehicle.recordVersion = Number(vehicle.recordVersion) resource.vehicle.finalStatus = vehicle.status context.assert(MODULE, '车辆状态切换为MAINTENANCE', statusResponse, vehicle.status === 'MAINTENANCE', `status=${vehicle.status}`, BLOCKER) const listResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/vehicles?page=1&size=20&keyword=${encodeURIComponent(createPayload.plateNumber)}`) const page = requireData(context, '分页查询marker车辆', listResponse) context.assert(MODULE, '车辆列表包含本轮长ID与recordVersion', listResponse, Array.isArray(page?.records) && page.records.some((item) => asId(item.id) === id && recordVersion(item.recordVersion)), `id=${id}, total=${page?.total ?? 'missing'}`, BLOCKER) const csv = await context.requestOverseasBinary('GET', `${OVERSEAS_BASE}/vehicles/export?keyword=${encodeURIComponent(createPayload.plateNumber)}`) assertBinaryCsv(context, '导出marker车辆CSV', csv, '车辆ID,车牌号,车型,油耗(L/100km),当前公里码(km),司机或外派单位,联系电话,区域,车辆归属,状态,更新时间', [id, createPayload.plateNumber, 'MAINTENANCE']) } async function verifyCanonicalPermissionDenial(context, resource) { const suffix = context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-18).toUpperCase() const roleResponse = await context.request('POST', '/api/platform/v1/iam/roles', { roleCode: `P4_MD_DENY_${suffix}`.slice(0, 64), roleName: `四期主数据拒绝-${suffix}`, description: context.marker, }) const role = requireData(context, '创建无主数据权限角色', roleResponse) const roleId = asId(role?.id) if (!roleId) throw new Error('无主数据权限角色未返回ID') resource.permissionProbe = { roleId, membershipId: null, roleStatus: 'ACTIVE', userStatus: null } const phone = await context.findAvailablePhone(MODULE) const username = `p4_md_deny_${suffix}`.slice(0, 64).toLowerCase() const password = `P4md${suffix.slice(-8)}A1!` const userResponse = await context.request('POST', '/api/platform/v1/iam/users', { username, displayName: `四期主数据拒绝-${suffix}`, phone, email: `${username}@example.com`, tenantId: '0', roleIds: [roleId], password, status: 'ACTIVE', }) const user = requireData(context, '创建无主数据权限用户', userResponse) const membershipId = asId(user?.membershipId) if (!membershipId) throw new Error('无主数据权限用户未返回membershipId') resource.permissionProbe.membershipId = membershipId resource.permissionProbe.userStatus = 'ACTIVE' const loginResponse = await context.request('POST', '/api/auth/login', { username, password }, '') const login = requireData(context, '无主数据权限用户登录', loginResponse) if (!login?.overseasAccessToken) throw new Error('无主数据权限用户未签发海外Token') const denied = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes?page=1&size=1`, undefined, {}, login.overseasAccessToken) assertForbidden(context, '无quote:list权限访问海外报价被拒绝', denied) } async function cleanupPartnerWarehouse(context, resource) { if (!resource.partnerWarehouse?.id) return const response = await context.request('PATCH', `${DOMESTIC_BASE}/${encodeURIComponent(resource.partnerWarehouse.id)}/status?tenantId=0&status=INACTIVE`) context.apiSuccess(MODULE, '合作仓finally停用', response, `id=${resource.partnerWarehouse.id}`, BLOCKER) if (success(response)) resource.partnerWarehouse.finalStatus = 'INACTIVE' } async function cleanupVersioned(context, resource, key, path, extract) { const current = resource[key] if (!current) return const detailResponse = await context.requestOverseas('GET', path) const detail = data(detailResponse) const value = extract(detail) if (!success(detailResponse) || !value || !recordVersion(value.recordVersion)) { context.assert(MODULE, `${key} finally查询当前版本`, detailResponse, false, `HTTP=${detailResponse?.http ?? 0}, code=${detailResponse?.body?.code || 'missing'}`, BLOCKER) return } current.recordVersion = Number(value.recordVersion) current.finalStatus = value.status if (value.status === 'INACTIVE') return const response = await context.requestOverseas('PATCH', `${path}/status`, { status: 'INACTIVE', recordVersion: current.recordVersion, ...(key === 'exchangeRate' ? { remark: `${context.marker} finally cleanup` } : {}), }) const stopped = data(response) const normalized = extract(stopped) context.apiSuccess(MODULE, `${key} finally停用`, response, `path=${path}`, BLOCKER) if (normalized) { current.recordVersion = Number(normalized.recordVersion) current.finalStatus = normalized.status } } async function cleanupPermissionProbe(context, resource) { const probe = resource.permissionProbe if (!probe) return if (probe.membershipId && probe.userStatus !== 'DISABLED') { const response = await context.request('PUT', `/api/platform/v1/iam/users/${probe.membershipId}/status`, { status: 'DISABLED' }) context.apiSuccess(MODULE, '无权限探针用户finally停用', response, `membershipId=${probe.membershipId}`, BLOCKER) if (success(response)) probe.userStatus = 'DISABLED' } if (probe.roleId && probe.roleStatus !== 'DISABLED') { const response = await context.request('PUT', `/api/platform/v1/iam/roles/${probe.roleId}/status`, { status: 'DISABLED' }) context.apiSuccess(MODULE, '无权限探针角色finally停用', response, `roleId=${probe.roleId}`, BLOCKER) if (success(response)) probe.roleStatus = 'DISABLED' } } export async function run(context) { const resource = { marker: context.marker } context.createdResources.phase4OverseasMasterData = resource try { await continueLifecycle(context, '海外报价', async () => { await captureWarehouseOptionsBeforeCreate(context, resource) await createAndVerifyPartnerWarehouse(context, resource) await quoteLifecycle(context, resource) }) await continueLifecycle(context, '月度汇率', () => exchangeLifecycle(context, resource)) await continueLifecycle(context, '车辆主数据', () => vehicleLifecycle(context, resource)) await continueLifecycle(context, 'canonical权限拒绝', () => verifyCanonicalPermissionDenial(context, resource)) } finally { await safeCleanup(context, '海外报价', async () => { if (!resource.quote?.id) return const path = `${OVERSEAS_BASE}/overseas-quotes/${encodeURIComponent(resource.quote.id)}` await cleanupVersioned(context, resource, 'quote', path, (value) => value?.summary) }) await safeCleanup(context, '月度汇率', async () => { if (!resource.exchangeRate?.month) return const path = `${OVERSEAS_BASE}/exchange-rates/${encodeURIComponent(resource.exchangeRate.month)}` await cleanupVersioned(context, resource, 'exchangeRate', path, (value) => value) }) await safeCleanup(context, '车辆', async () => { if (!resource.vehicle?.id) return const path = `${OVERSEAS_BASE}/vehicles/${encodeURIComponent(resource.vehicle.id)}` await cleanupVersioned(context, resource, 'vehicle', path, (value) => value) }) await safeCleanup(context, '合作仓', () => cleanupPartnerWarehouse(context, resource)) await safeCleanup(context, 'canonical权限探针', () => cleanupPermissionProbe(context, resource)) } }