This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
import type { Page, Route } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const departmentId = '01M00000000000000000000220';
|
||||
const legacyMappingId = '01M00000000000000000000250';
|
||||
|
||||
interface DepartmentFixtureOptions {
|
||||
createRequests: Array<Record<string, unknown>>;
|
||||
submitRequests: Array<Record<string, unknown>>;
|
||||
reviewRequests: Array<Record<string, unknown>>;
|
||||
disableRequests: Array<Record<string, unknown>>;
|
||||
versionRequests: string[];
|
||||
legacyCreateRequests: Array<Record<string, unknown>>;
|
||||
legacyDisableRequests: Array<Record<string, unknown>>;
|
||||
legacyListRequests: string[];
|
||||
}
|
||||
|
||||
function session() {
|
||||
return {
|
||||
authenticated: true,
|
||||
user: {
|
||||
publicId: '01M00000000000000000000221',
|
||||
username: 'masterdata-e2e',
|
||||
displayName: '主数据经办人',
|
||||
departmentName: '财务部',
|
||||
mustChangePassword: false,
|
||||
},
|
||||
roles: [{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' }],
|
||||
activeRole: 'FINANCE_MANAGER',
|
||||
permissions: [
|
||||
'masterdata:company:view',
|
||||
'masterdata:dictionary:view',
|
||||
'masterdata:dictionary:create',
|
||||
'masterdata:dictionary:edit',
|
||||
'masterdata:dictionary:submit',
|
||||
'masterdata:dictionary:review',
|
||||
'masterdata:dictionary:disable',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function department(status: string, version: number, allowedActions: string[]) {
|
||||
return {
|
||||
publicId: departmentId,
|
||||
resource: 'departments',
|
||||
code: 'DEPT-E2E-001',
|
||||
name: '财务管理部',
|
||||
parent: null,
|
||||
accountType: null,
|
||||
auxiliaryRequired: null,
|
||||
rate: null,
|
||||
status,
|
||||
version,
|
||||
createdAt: '2026-08-15T08:00:00Z',
|
||||
updatedAt: '2026-08-15T08:00:00Z',
|
||||
allowedActions,
|
||||
};
|
||||
}
|
||||
|
||||
function departmentSnapshot(value: ReturnType<typeof department>) {
|
||||
const { allowedActions: _allowedActions, ...snapshot } = value;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function legacyMapping(status: 'ACTIVE' | 'DISABLED', version: number, allowedActions: string[]) {
|
||||
return {
|
||||
publicId: legacyMappingId,
|
||||
resource: 'departments',
|
||||
sourceSystem: 'OLD_ERP',
|
||||
legacyCode: 'DEPT-LEGACY-001',
|
||||
targetPublicId: departmentId,
|
||||
status,
|
||||
version,
|
||||
createdAt: '2026-08-15T09:00:00Z',
|
||||
updatedAt: '2026-08-15T09:00:00Z',
|
||||
allowedActions,
|
||||
};
|
||||
}
|
||||
|
||||
async function installDepartmentFixture(page: Page, options: DepartmentFixtureOptions) {
|
||||
const userSession = session();
|
||||
let currentDepartment: ReturnType<typeof department> | null = null;
|
||||
const versionHistory: Array<Record<string, unknown>> = [];
|
||||
let currentLegacyMapping: ReturnType<typeof legacyMapping> | null = null;
|
||||
|
||||
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: '01M00000000000000000000222' } });
|
||||
return;
|
||||
}
|
||||
if (pathname === '/api/v1/auth/profile') {
|
||||
await route.fulfill({ json: { data: userSession.user, requestId: '01M00000000000000000000223' } });
|
||||
return;
|
||||
}
|
||||
if (pathname === '/api/v1/auth/roles') {
|
||||
await route.fulfill({ json: { data: userSession.roles, requestId: '01M00000000000000000000224' } });
|
||||
return;
|
||||
}
|
||||
if (pathname === '/api/v1/auth/csrf') {
|
||||
await route.fulfill({
|
||||
headers: { 'set-cookie': 'XSRF-TOKEN=masterdata-e2e-csrf; Path=/' },
|
||||
json: { data: { token: 'masterdata-e2e-csrf' }, requestId: '01M00000000000000000000225' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/masterdata/references') {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: { companies: [], projects: [], counterparties: [], costCategories: [] },
|
||||
requestId: '01M00000000000000000000226',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/masterdata/companies') {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: [],
|
||||
meta: { page: 1, size: 20, totalElements: 0, totalPages: 0 },
|
||||
requestId: '01M00000000000000000000227',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/masterdata/departments') {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: currentDepartment ? [currentDepartment] : [],
|
||||
meta: { page: 1, size: 20, totalElements: currentDepartment ? 1 : 0, totalPages: currentDepartment ? 1 : 0 },
|
||||
requestId: '01M00000000000000000000228',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === `/api/v1/masterdata/departments/${departmentId}`) {
|
||||
await route.fulfill({
|
||||
json: { data: currentDepartment, requestId: '01M00000000000000000000229' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === `/api/v1/masterdata/departments/${departmentId}/versions`) {
|
||||
options.versionRequests.push(new URL(request.url()).search);
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: versionHistory,
|
||||
meta: { page: 1, size: 20, totalElements: versionHistory.length, totalPages: 1 },
|
||||
requestId: '01M00000000000000000000235',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
request.method() === 'GET' &&
|
||||
pathname === `/api/v1/masterdata/departments/${departmentId}/legacy-identifiers`
|
||||
) {
|
||||
options.legacyListRequests.push(new URL(request.url()).search);
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: currentLegacyMapping ? [currentLegacyMapping] : [],
|
||||
meta: {
|
||||
page: 1,
|
||||
size: 20,
|
||||
totalElements: currentLegacyMapping ? 1 : 0,
|
||||
totalPages: currentLegacyMapping ? 1 : 0,
|
||||
allowedActions: ['CREATE'],
|
||||
},
|
||||
requestId: '01M00000000000000000000251',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
request.method() === 'POST' &&
|
||||
pathname === `/api/v1/masterdata/departments/${departmentId}/legacy-identifiers`
|
||||
) {
|
||||
options.legacyCreateRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
currentLegacyMapping = legacyMapping('ACTIVE', 0, ['DISABLE']);
|
||||
await route.fulfill({
|
||||
json: { data: currentLegacyMapping, requestId: '01M00000000000000000000252' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
request.method() === 'POST' &&
|
||||
pathname === `/api/v1/masterdata/departments/${departmentId}/legacy-identifiers/${legacyMappingId}/disable`
|
||||
) {
|
||||
options.legacyDisableRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
currentLegacyMapping = legacyMapping('DISABLED', 1, []);
|
||||
await route.fulfill({
|
||||
json: { data: currentLegacyMapping, requestId: '01M00000000000000000000253' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === '/api/v1/masterdata/departments') {
|
||||
options.createRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
currentDepartment = department('DRAFT', 0, ['EDIT', 'SUBMIT']);
|
||||
versionHistory.unshift({
|
||||
auditId: '01M00000000000000000000240',
|
||||
version: 0,
|
||||
status: 'DRAFT',
|
||||
actionCode: 'MASTERDATA_DICTIONARY_CREATE',
|
||||
actorName: 'masterdata-e2e',
|
||||
changedAt: '2026-08-15T08:00:00Z',
|
||||
before: null,
|
||||
after: departmentSnapshot(currentDepartment),
|
||||
});
|
||||
await route.fulfill({
|
||||
json: { data: currentDepartment, requestId: '01M00000000000000000000230' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/masterdata/departments/${departmentId}/submit`) {
|
||||
options.submitRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
const before = currentDepartment ? departmentSnapshot(currentDepartment) : null;
|
||||
currentDepartment = department('REVIEWING', 1, ['REVIEW']);
|
||||
versionHistory.unshift({
|
||||
auditId: '01M00000000000000000000241',
|
||||
version: 1,
|
||||
status: 'REVIEWING',
|
||||
actionCode: 'MASTERDATA_DICTIONARY_SUBMIT',
|
||||
actorName: 'masterdata-e2e',
|
||||
changedAt: '2026-08-15T08:10:00Z',
|
||||
before,
|
||||
after: departmentSnapshot(currentDepartment),
|
||||
});
|
||||
await route.fulfill({
|
||||
json: { data: currentDepartment, requestId: '01M00000000000000000000231' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/masterdata/departments/${departmentId}/review`) {
|
||||
options.reviewRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
const before = currentDepartment ? departmentSnapshot(currentDepartment) : null;
|
||||
currentDepartment = department('ACTIVE', 2, ['DISABLE']);
|
||||
versionHistory.unshift({
|
||||
auditId: '01M00000000000000000000242',
|
||||
version: 2,
|
||||
status: 'ACTIVE',
|
||||
actionCode: 'MASTERDATA_DICTIONARY_REVIEW',
|
||||
actorName: 'masterdata-reviewer',
|
||||
changedAt: '2026-08-15T08:20:00Z',
|
||||
before,
|
||||
after: departmentSnapshot(currentDepartment),
|
||||
});
|
||||
await route.fulfill({
|
||||
json: { data: currentDepartment, requestId: '01M00000000000000000000232' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/masterdata/departments/${departmentId}/disable`) {
|
||||
options.disableRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
const before = currentDepartment ? departmentSnapshot(currentDepartment) : null;
|
||||
currentDepartment = department('DISABLED', 3, []);
|
||||
versionHistory.unshift({
|
||||
auditId: '01M00000000000000000000243',
|
||||
version: 3,
|
||||
status: 'DISABLED',
|
||||
actionCode: 'MASTERDATA_DICTIONARY_DISABLE',
|
||||
actorName: 'masterdata-reviewer',
|
||||
changedAt: '2026-08-15T08:30:00Z',
|
||||
before,
|
||||
after: departmentSnapshot(currentDepartment),
|
||||
});
|
||||
await route.fulfill({
|
||||
json: { data: currentDepartment, requestId: '01M00000000000000000000233' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() === 'GET') {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
data: [],
|
||||
meta: { page: 1, size: 20, totalElements: 0, totalPages: 0 },
|
||||
requestId: '01M00000000000000000000234',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
await route.fulfill({ status: 404 });
|
||||
});
|
||||
}
|
||||
|
||||
test('PAGE-12 creates and advances a department using server allowed actions', async ({ page }) => {
|
||||
const createRequests: Array<Record<string, unknown>> = [];
|
||||
const submitRequests: Array<Record<string, unknown>> = [];
|
||||
const reviewRequests: Array<Record<string, unknown>> = [];
|
||||
const disableRequests: Array<Record<string, unknown>> = [];
|
||||
const versionRequests: string[] = [];
|
||||
const legacyCreateRequests: Array<Record<string, unknown>> = [];
|
||||
const legacyDisableRequests: Array<Record<string, unknown>> = [];
|
||||
const legacyListRequests: string[] = [];
|
||||
await installDepartmentFixture(page, {
|
||||
createRequests,
|
||||
submitRequests,
|
||||
reviewRequests,
|
||||
disableRequests,
|
||||
versionRequests,
|
||||
legacyCreateRequests,
|
||||
legacyDisableRequests,
|
||||
legacyListRequests,
|
||||
});
|
||||
|
||||
await page.goto('/finance/master-data');
|
||||
await page.locator('.resource-tabs').getByText('部门', { exact: true }).click();
|
||||
await page.locator('.page-header').getByRole('button', { name: '新建部门', exact: true }).click();
|
||||
|
||||
const createDialog = page.locator('.t-dialog').filter({ hasText: '新建部门' });
|
||||
await createDialog.locator('.t-form-item__code input').fill('DEPT-E2E-001');
|
||||
await createDialog.locator('.t-form-item__name input').fill('财务管理部');
|
||||
await createDialog.getByRole('button', { name: '创建部门', exact: true }).click();
|
||||
|
||||
await expect.poll(() => createRequests.length).toBe(1);
|
||||
expect(createRequests[0]).toEqual({ code: 'DEPT-E2E-001', name: '财务管理部' });
|
||||
await expect(page.getByText('草稿', { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByText('提交', { exact: true }).click();
|
||||
const submitDialog = page.locator('.t-dialog').filter({ hasText: '提交复核' });
|
||||
await submitDialog.getByRole('button', { name: '确认提交', exact: true }).click();
|
||||
await expect.poll(() => submitRequests.length).toBe(1);
|
||||
expect(submitRequests[0]).toEqual({ version: 0 });
|
||||
await expect(page.getByText('复核中', { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByText('复核', { exact: true }).click();
|
||||
const reviewDialog = page.locator('.t-dialog').filter({ hasText: '复核主数据' });
|
||||
await reviewDialog.locator('textarea').fill('部门资料核对无误');
|
||||
await reviewDialog.getByRole('button', { name: '确认复核', exact: true }).click();
|
||||
await expect.poll(() => reviewRequests.length).toBe(1);
|
||||
expect(reviewRequests[0]).toEqual({ version: 1, decision: 'APPROVE', opinion: '部门资料核对无误' });
|
||||
await expect(page.getByText('已生效', { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByText('停用', { exact: true }).click();
|
||||
const disableDialog = page.locator('.t-dialog').filter({ hasText: '停用主数据' });
|
||||
await disableDialog.getByRole('button', { name: '确认停用', exact: true }).click();
|
||||
await expect.poll(() => disableRequests.length).toBe(1);
|
||||
expect(disableRequests[0]).toEqual({ version: 2 });
|
||||
await expect(page.locator('.table-panel').getByText('已停用', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('停用', { exact: true })).toHaveCount(0);
|
||||
|
||||
await page.getByText('版本记录', { exact: true }).click();
|
||||
const historyDialog = page.locator('.t-dialog').filter({ hasText: '部门版本记录' });
|
||||
await expect(historyDialog).toBeVisible();
|
||||
await expect.poll(() => versionRequests.length).toBe(1);
|
||||
expect(versionRequests[0]).toContain('page=1');
|
||||
expect(versionRequests[0]).toContain('size=20');
|
||||
await expect(historyDialog.locator('.history-section tbody tr')).toHaveCount(4);
|
||||
const statusDifference = historyDialog.locator('.comparison-section tbody tr').filter({ hasText: '状态' });
|
||||
await expect(statusDifference).toContainText('草稿');
|
||||
await expect(statusDifference).toContainText('已停用');
|
||||
await expect(historyDialog.locator('.comparison-section tbody tr').filter({ hasText: '版本' })).toContainText('v3');
|
||||
|
||||
await historyDialog.getByRole('button', { name: '关闭', exact: true }).click();
|
||||
await page.getByText('查看', { exact: true }).click();
|
||||
const detailDialog = page.locator('.t-dialog').filter({ hasText: '部门详情' });
|
||||
await detailDialog.getByRole('button', { name: '旧编号', exact: true }).click();
|
||||
|
||||
const legacyDialog = page.locator('.t-dialog').filter({ hasText: '部门旧编号映射' });
|
||||
await expect(legacyDialog).toBeVisible();
|
||||
await expect.poll(() => legacyListRequests.length).toBe(1);
|
||||
await legacyDialog.locator('.t-form-item__sourceSystem input').fill('OLD_ERP');
|
||||
await legacyDialog.locator('.t-form-item__legacyCode input').fill('DEPT-LEGACY-001');
|
||||
await legacyDialog.getByRole('button', { name: '新增映射', exact: true }).click();
|
||||
await expect.poll(() => legacyCreateRequests.length).toBe(1);
|
||||
expect(legacyCreateRequests[0]).toEqual({ sourceSystem: 'OLD_ERP', legacyCode: 'DEPT-LEGACY-001' });
|
||||
await expect(legacyDialog.getByText('DEPT-LEGACY-001', { exact: true })).toBeVisible();
|
||||
await legacyDialog.getByText('停用', { exact: true }).click();
|
||||
const legacyDisableDialog = page.locator('.t-dialog').filter({ hasText: '停用旧编号映射' });
|
||||
await legacyDisableDialog.getByRole('button', { name: '停用', exact: true }).click();
|
||||
await expect.poll(() => legacyDisableRequests.length).toBe(1);
|
||||
expect(legacyDisableRequests[0]).toEqual({ version: 0 });
|
||||
await expect(legacyDialog.getByText('已停用', { exact: true })).toBeVisible();
|
||||
await expect(legacyDialog.getByText('停用', { exact: true })).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user