import { test, expect } from '../fixtures/auth'; import type { Page, Response } from '@playwright/test'; /** * A2 UI 回归 第二批:Material * Material:通过 routing-op-details 反查找到必有引用的样本 → 实测删除拦截 * * Supplier UI 回归已迁移至 srm-fix-regression.spec.ts:使用 Supp=10001875 + SRM 引用样本 * 验证删除 409 / S01006 拦截。BUG-S0-SRMPURCHASE-SCHEMA-MISMATCH(commit 50359ac3)已修复, * 原"SRM 不可用"Block 标记已废弃。 */ async function token(page: Page) { return page.evaluate(() => { for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i)!; if (/access-token$/i.test(k) && !/x-access-token$/i.test(k)) { const raw = localStorage.getItem(k)!; try { const v = JSON.parse(raw); return typeof v === 'string' ? v : v?.value ?? raw; } catch { return raw; } } } return null; }); } async function pickMaterialReferencedByRouting(page: Page): Promise<{ id: number; itemNum: string } | null> { const tk = await token(page); const headers = { Authorization: `Bearer ${tk!}` }; const base = new URL(page.url()).origin; const r = await page.request.get( `${base}/api/s0/manufacturing/routing-op-details?page=1&pageSize=200&tenantId=1&factoryId=1`, { headers }, ); if (!r.ok()) return null; const codes: string[] = [...new Set(((await r.json()).list ?? []).map((x: any) => x.materialCode).filter(Boolean))]; for (const c of codes) { const lookup = await page.request.get( `${base}/api/s0/sales/materials?ItemNum=${encodeURIComponent(c)}&page=1&pageSize=5&tenantId=1&factoryId=1`, { headers }, ); const j = await lookup.json().catch(() => null); const hits = j?.list ?? []; if (hits.length > 0) return { id: Number(hits[0].id), itemNum: hits[0].itemNum }; } return null; } test('Material 删除被工艺引用 → 409 + 红色 toast + 行未消失', async ({ authedPage }) => { await authedPage.goto('/#/dashboard/home', { waitUntil: 'domcontentloaded' }); const sample = await pickMaterialReferencedByRouting(authedPage); test.skip(!sample, '未找到被 routing-op-details 引用的物料样本'); test.info().annotations.push({ type: 'sample', description: JSON.stringify(sample) }); await authedPage.goto('/#/aidop/s0/sales/material', { waitUntil: 'domcontentloaded' }); await authedPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {}); await expect(authedPage.locator('table tbody tr').first()).toBeVisible({ timeout: 15_000 }); // 页面筛选栏有两个输入:placeholder="编码/名称" 的 keyword + 物料编码单独输入 const kw = authedPage.locator('input[placeholder*="编码/名称"]').first(); await kw.fill(sample!.itemNum); await authedPage.getByRole('button', { name: /查询/ }).first().click(); await authedPage.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {}); const row = authedPage.locator('tr', { hasText: sample!.itemNum }).first(); await expect(row, '应在列表中找到目标物料').toBeVisible({ timeout: 10_000 }); let deleteResp: Response | null = null; authedPage.on('response', (r) => { if (/\/api\/s0\/sales\/materials\/\d+/.test(r.url()) && r.request().method() === 'DELETE') deleteResp = r; }); await row.getByRole('button', { name: /删除/ }).click(); const confirm = authedPage.locator('.el-message-box').getByRole('button', { name: /确\s*定/ }); await expect(confirm).toBeVisible({ timeout: 5_000 }); await confirm.click(); await expect.poll(() => deleteResp?.status() ?? 0, { timeout: 10_000 }).toBe(409); const body = await deleteResp!.json().catch(() => null); test.info().annotations.push({ type: 'api', description: `DELETE → ${deleteResp!.status()} body=${JSON.stringify(body).slice(0, 200)}`, }); expect(body?.code).toBe('S01006'); expect(String(body?.message ?? '')).toMatch(/引用该物料|RoutingOpDetail|工艺路线|物料替代/); const errToast = authedPage.locator('.el-message--error, .el-message.is-error').first(); await expect(errToast).toBeVisible({ timeout: 5_000 }); const toastText = (await errToast.innerText()).replace(/\s+/g, ''); test.info().annotations.push({ type: 'toast', description: toastText }); expect(toastText).toMatch(/引用该物料|工艺路线|物料替代/); const okToast = authedPage.locator('.el-message--success').first(); expect(await okToast.isVisible().catch(() => false), '不应出现 success toast').toBeFalsy(); await authedPage.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {}); await expect(authedPage.locator('tr', { hasText: sample!.itemNum }).first()).toBeVisible({ timeout: 3_000 }); }); // Supplier UI 回归不再放在本文件;见 srm-fix-regression.spec.ts。