// Round 65 W4: closed-loop APS order/MRP frontend contract guard. // Source-level by design: validates backward-compatible typing and no-fake-success UI copy // without mutating a live APS world or requiring authentication. import { expect, test } from '@playwright/test'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; const webRoot = path.resolve(process.cwd()); test('订单面板消费 closedLoop 证据并明确六阶段门禁', async () => { const source = await readFile(path.join(webRoot, 'src/orders/OrderPanel.tsx'), 'utf8'); expect(source).toContain('setClosedLoop(res.result?.closedLoop ?? null)'); for (const label of ['商务订单', 'BOM/供需净算', '制造需求准入', '排产草案', 'P2 发布', 'P3 MES']) { expect(source).toContain(label); } expect(source).toContain('它们仍是制造需求,不是已释放生产订单'); expect(source).toContain('不会自动转采购'); expect(source).toContain('待独立确认'); expect(source).toContain('closed-loop-manufacturing-demands'); expect(source).toContain('closed-loop-blockers'); }); test('MRP 分解类型保持旧清单并可选扩展 closedLoop 契约', async () => { const source = await readFile(path.join(webRoot, 'src/api/types.ts'), 'utf8'); expect(source).toContain('make: MakeSuggestion[];'); expect(source).toContain('purchase: PurchaseSuggestion[];'); expect(source).toContain('outsource: OutsourceSuggestion[];'); expect(source).toContain('closedLoop?: MrpClosedLoopDecomposition;'); for (const field of [ 'problemId: string;', 'sourceHash: string;', 'businessDate: string;', 'summary: ClosedLoopSummary;', 'projection: ClosedLoopProjection;', 'manufacturingDemands: ClosedLoopManufacturingDemand[];', 'supplyEvents: ClosedLoopSupplyEvent[];', 'blockers: ClosedLoopBlocker[];', 'schedulingProblemV2: SchedulingProblemV2Projection;', ]) { expect(source).toContain(field); } });