import type { Page, Route } from '@playwright/test'; import { expect, test } from '@playwright/test'; const packageId = '01M00000000000000000000301'; const createdPackageId = '01M00000000000000000000302'; const companyId = '01M00000000000000000000303'; const projectId = '01M00000000000000000000304'; const itemId = '01M00000000000000000000305'; interface ArchiveFixtureOptions { permissions?: string[]; packageActions?: string[]; listQueries?: URL[]; createRequests?: Array>; checkRequests?: Array>; } function archivePackage(allowedActions: string[] = []) { return { publicId: packageId, businessNo: 'ARC-E2E-001', company: { publicId: companyId, businessNo: 'COMP-E2E-001', name: '凯迪档案测试公司' }, project: { publicId: projectId, businessNo: 'P-E2E-001', name: '档案验收项目' }, packageVersion: 1, rootPackagePublicId: packageId, supersedesPackagePublicId: null, revisionReason: null, ruleVersion: 'ARCHIVE-RULE-V1', completeness: '80.00', missingCount: 1, retentionYears: 10, retentionExpiresOn: '2026-10-31', retentionStatus: 'DUE_SOON', physicalLocation: 'A-01-03', frozen: false, status: 'PREPARING', manifestSha256: 'a'.repeat(64), submittedByName: null, reviewedByName: null, submittedAt: null, archivedAt: null, updatedAt: '2026-08-15T08:00:00Z', version: 3, allowedActions, items: [ { publicId: itemId, itemType: 'CONTRACT', itemName: '合同文件', required: true, status: 'MISSING', missingReason: '尚未上传合同文件', allowedActions: ['UPLOAD'], }, ], objectSnapshots: [], actions: [ { sequenceNo: 1, actionCode: 'GENERATE', fromStatus: null, toStatus: 'PREPARING', opinion: '生成项目档案包', actorName: '财务归档员', occurredAt: '2026-08-15T08:00:00Z', manifestSha256: 'a'.repeat(64), }, ], }; } function session(permissions: string[]) { return { authenticated: true, user: { publicId: '01M00000000000000000000306', username: 'archive-e2e', displayName: '财务归档员', departmentName: '财务部', mustChangePassword: false, }, roles: [{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' }], activeRole: 'FINANCE_MANAGER', permissions, }; } async function installArchiveFixture(page: Page, options: ArchiveFixtureOptions = {}) { const userSession = session(options.permissions || ['archive:package:view']); let currentPackage = archivePackage(options.packageActions || []); await page.route('**/api/v1/**', async (route: Route) => { const request = route.request(); const pathname = new URL(request.url()).pathname; if (pathname === '/api/v1/auth/session') { await route.fulfill({ json: { data: userSession, requestId: '01M00000000000000000000307' } }); return; } if (pathname === '/api/v1/auth/profile') { await route.fulfill({ json: { data: userSession.user, requestId: '01M00000000000000000000308' } }); return; } if (pathname === '/api/v1/auth/roles') { await route.fulfill({ json: { data: userSession.roles, requestId: '01M00000000000000000000309' } }); return; } if (pathname === '/api/v1/auth/csrf') { await route.fulfill({ headers: { 'set-cookie': 'XSRF-TOKEN=archive-e2e-csrf; Path=/' }, json: { data: { token: 'archive-e2e-csrf' }, requestId: '01M00000000000000000000310' }, }); return; } if (request.method() === 'GET' && pathname === '/api/v1/archives/packages') { options.listQueries?.push(new URL(request.url())); await route.fulfill({ json: { data: [currentPackage], meta: { page: 1, size: 20, totalElements: 1, totalPages: 1 }, requestId: '01M00000000000000000000311', }, }); return; } if (request.method() === 'GET' && pathname === `/api/v1/archives/packages/${currentPackage.publicId}`) { await route.fulfill({ json: { data: currentPackage, requestId: '01M00000000000000000000312' } }); return; } if (request.method() === 'POST' && pathname === '/api/v1/archives/packages') { options.createRequests?.push(request.postDataJSON() as Record); currentPackage = { ...archivePackage(['CHECK']), publicId: createdPackageId, businessNo: 'ARC-E2E-002' }; await route.fulfill({ json: { data: currentPackage, requestId: '01M00000000000000000000313' } }); return; } if (request.method() === 'POST' && pathname === `/api/v1/archives/packages/${currentPackage.publicId}/check`) { options.checkRequests?.push(request.postDataJSON() as Record); currentPackage = { ...currentPackage, status: 'CHECKED', version: currentPackage.version + 1, allowedActions: ['SUBMIT'], }; await route.fulfill({ json: { data: currentPackage, requestId: '01M00000000000000000000314' } }); return; } if (request.method() === 'GET') { await route.fulfill({ json: { data: [], meta: { page: 1, size: 20, totalElements: 0, totalPages: 0 }, requestId: '01M00000000000000000000315', }, }); return; } await route.fulfill({ status: 204 }); }); } test('PAGE-17 keeps list filters in the URL and reset removes optional conditions', async ({ page }) => { const listQueries: URL[] = []; await installArchiveFixture(page, { listQueries }); await page.goto( `/archives/projects?view=archived&projectId=${projectId}&status=ARCHIVED&frozen=true&sort=archivedAt%2Cdesc`, ); await expect.poll(() => listQueries.length).toBeGreaterThan(0); const initialQuery = listQueries.at(-1)?.searchParams; expect(initialQuery?.get('view')).toBe('archived'); expect(initialQuery?.get('projectId')).toBe(projectId); expect(initialQuery?.get('status')).toBe('ARCHIVED'); expect(initialQuery?.get('frozen')).toBe('true'); expect(initialQuery?.get('sort')).toBe('archivedAt,desc'); await expect .poll(() => { const url = new URL(page.url()); return { pathname: url.pathname, view: url.searchParams.get('view'), projectId: url.searchParams.get('projectId'), status: url.searchParams.get('status'), frozen: url.searchParams.get('frozen'), sort: url.searchParams.get('sort'), }; }) .toEqual({ pathname: '/archives/projects', view: 'archived', projectId, status: 'ARCHIVED', frozen: 'true', sort: 'archivedAt,desc', }); await expect(page.getByText('档案验收项目', { exact: true })).toBeVisible(); await expect(page.getByText('即将到期', { exact: true })).toBeVisible(); await page.getByRole('button', { name: '重置', exact: true }).click(); await expect.poll(() => listQueries.length).toBeGreaterThan(1); const resetQuery = listQueries.at(-1)?.searchParams; expect(resetQuery?.get('view')).toBe('archived'); expect(resetQuery?.has('projectId')).toBe(false); expect(resetQuery?.has('status')).toBe(false); expect(resetQuery?.has('frozen')).toBe(false); expect(resetQuery?.get('sort')).toBe('updatedAt,desc'); await expect .poll(() => { const url = new URL(page.url()); return { pathname: url.pathname, view: url.searchParams.get('view'), projectId: url.searchParams.get('projectId'), status: url.searchParams.get('status'), frozen: url.searchParams.get('frozen'), sort: url.searchParams.get('sort'), }; }) .toEqual({ pathname: '/archives/projects', view: 'archived', projectId: null, status: null, frozen: null, sort: 'updatedAt,desc', }); }); test('PAGE-17 hides archive generation without create permission and opens scoped detail', async ({ page }) => { await installArchiveFixture(page); await page.goto('/archives/projects'); await expect(page.getByRole('heading', { name: '项目档案包', exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: '生成档案包', exact: true })).toHaveCount(0); await page.getByText('ARC-E2E-001', { exact: true }).click(); const drawer = page.locator('.t-drawer').filter({ hasText: 'ARC-E2E-001' }); await expect(drawer.getByText('保管到期', { exact: true })).toBeVisible(); await expect( drawer.getByText('档案将于 2026-10-31 到期。系统仅提示,不会自动删除或销毁。', { exact: true }), ).toBeVisible(); await expect(drawer.getByRole('button', { name: '检查', exact: true })).toHaveCount(0); }); test('PAGE-17 validates and sends the archive package creation payload', async ({ page }) => { const createRequests: Array> = []; await installArchiveFixture(page, { permissions: ['archive:package:view', 'archive:package:create'], createRequests, }); await page.goto('/archives/projects'); await page.getByRole('button', { name: '生成档案包', exact: true }).click(); const dialog = page.locator('.t-dialog').filter({ hasText: '生成项目档案包' }); await dialog.getByRole('button', { name: '生成', exact: true }).click(); await expect(dialog.getByText('请填写项目公开编号', { exact: true })).toBeVisible(); expect(createRequests).toHaveLength(0); await dialog.getByPlaceholder('请输入项目公开编号', { exact: true }).fill(projectId); await dialog.getByPlaceholder('可选,例如 A-01-03', { exact: true }).fill('B-02-08'); await dialog.getByRole('button', { name: '生成', exact: true }).click(); await expect.poll(() => createRequests.length).toBe(1); expect(createRequests[0]).toEqual({ projectId, retentionYears: 10, physicalLocation: 'B-02-08' }); await expect(page.getByText('档案包已生成', { exact: true })).toBeVisible(); await expect(dialog).toBeHidden(); await expect(page.getByText('ARC-E2E-002', { exact: true })).toBeVisible(); }); test('PAGE-17 executes only server-provided package actions and refreshes detail', async ({ page }) => { const checkRequests: Array> = []; await installArchiveFixture(page, { permissions: ['archive:package:view', 'archive:package:submit'], packageActions: ['CHECK'], checkRequests, }); await page.goto('/archives/projects'); await page.getByText('ARC-E2E-001', { exact: true }).click(); const drawer = page.locator('.t-drawer').filter({ hasText: 'ARC-E2E-001' }); await drawer.getByRole('button', { name: '检查', exact: true }).click(); await expect(drawer.getByRole('button', { name: '提交审核', exact: true })).toHaveCount(0); const dialog = page.locator('.t-dialog').filter({ hasText: '检查' }); await dialog.getByRole('button', { name: '确认检查', exact: true }).click(); await expect.poll(() => checkRequests.length).toBe(1); expect(checkRequests[0]).toEqual({ version: 3 }); await expect(page.getByText('检查已完成', { exact: true })).toBeVisible(); await expect(dialog).toBeHidden(); await expect(drawer.getByRole('button', { name: '提交审核', exact: true })).toBeVisible(); await expect(drawer.getByRole('button', { name: '检查', exact: true })).toHaveCount(0); });