function count(value) { const number = Number(value) return Number.isFinite(number) ? number : NaN } function currentMonth() { return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', }).format(new Date()).slice(0, 7) } function id(value) { return value == null ? '' : String(value) } function listOf(value) { if (Array.isArray(value)) return value if (Array.isArray(value?.records)) return value.records return null } export async function run(context) { const module = 'sailing-calendar' const base = '/api/platform/v1/sailings' const month = currentMonth() const calendarResponse = await context.request('GET', `${base}/calendar?month=${month}`) const calendar = context.apiSuccess(module, '查询船期月历', calendarResponse) const records = Array.isArray(calendar?.records) ? calendar.records : [] const summary = calendar?.summary const filterOptions = calendar?.filterOptions context.assert(module, '月历记录、汇总与筛选选项结构完整', calendarResponse, records.every((row) => id(row.id) && id(row.sailingNo) && (row.etaAt == null || Number.isFinite(Date.parse(row.etaAt)))) && summary && Number.isFinite(count(summary.sailingCount)) && filterOptions && Array.isArray(filterOptions.warehouses) && Array.isArray(filterOptions.channels), `month=${month},records=${records.length},sailingCount=${summary?.sailingCount ?? 'missing'}`) context.assert(module, '月历容量汇总均为非负有限数', calendarResponse, ['actualReceivedCbm', 'catchableForecastCbm', 'riskUnmatchedCbm'].every((field) => Number.isFinite(count(summary?.[field])) && count(summary?.[field]) >= 0), JSON.stringify(summary || {}).slice(0, 200)) const channelsResponse = await context.request('GET', '/api/platform/v1/sailing-channels') const channels = context.apiSuccess(module, '查询船期渠道资源', channelsResponse) const channelRows = listOf(channels) ?? [] context.assert(module, '渠道列表字段完整', channelsResponse, channelRows.every((row) => id(row.id) && id(row.channelCode) && Number.isFinite(count(row.defaultCutoffDays))), `count=${channelRows.length}`) const templatesResponse = await context.request('GET', '/api/platform/v1/sailing-templates?page=1&size=200') const templates = context.apiSuccess(module, '查询船期模板', templatesResponse) const templateRows = listOf(templates) ?? [] context.assert(module, '模板列表字段完整', templatesResponse, templateRows.every((row) => id(row.id) && id(row.channelId) && id(row.warehouseId) && Array.isArray(row.weekdays)), `count=${templateRows.length}`) const exceptionsResponse = await context.request('GET', '/api/platform/v1/sailing-assignments/exceptions?page=1&size=20') const exceptions = context.apiSuccess(module, '查询船期异常分配', exceptionsResponse) const exceptionRows = listOf(exceptions) ?? [] context.assert(module, '异常分配列表字段完整', exceptionsResponse, exceptionRows.every((row) => id(row.assignmentId) && Number.isFinite(count(row.volumeCbm))), `count=${exceptionRows.length},total=${exceptions?.total ?? 'array'}`) if (records[0]) { const detailResponse = await context.request('GET', `${base}/${encodeURIComponent(records[0].id)}`) const detail = context.apiSuccess(module, '查询当前船期详情', detailResponse) context.assert(module, '船期详情包含版本与货量摘要', detailResponse, id(detail?.sailing?.id) === id(records[0].id) && Number.isFinite(count(detail?.sailing?.version)) && detail?.sailing?.volumeSummary != null && Array.isArray(detail?.histories), `id=${detail?.sailing?.id},version=${detail?.sailing?.version}`) } else { context.addCoverageGap(module, 'sailing-calendar', '船期详情与变更历史', '当前月份没有船期记录;需先由订舱/模板生成本轮船期后重跑', 'not-implemented', { blocking: false }) } const missingResponse = await context.request('GET', `${base}/999999999999999`) context.assert(module, '不存在的船期详情被拒绝', missingResponse, (missingResponse.http === 200 && ['A0403', 'A0404'].includes(String(missingResponse.body?.code))) || missingResponse.http === 404, `HTTP=${missingResponse.http},code=${missingResponse.body?.code ?? 'missing'}`) }