This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
import type { Page, Route } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const contractId = '01M00000000000000000000301';
|
||||
const payableId = '01M00000000000000000000302';
|
||||
const projectId = '01M00000000000000000000303';
|
||||
const sourceDocumentId = '01M00000000000000000000304';
|
||||
const costCategoryId = '01M00000000000000000000305';
|
||||
const companyId = '01M00000000000000000000306';
|
||||
|
||||
function envelope(data: unknown, totalElements?: number) {
|
||||
return {
|
||||
data,
|
||||
...(typeof totalElements === 'number'
|
||||
? { meta: { page: 1, size: 20, totalElements, totalPages: totalElements ? 1 : 0 } }
|
||||
: {}),
|
||||
requestId: '01M00000000000000000000399',
|
||||
};
|
||||
}
|
||||
|
||||
function session() {
|
||||
return {
|
||||
authenticated: true,
|
||||
user: {
|
||||
publicId: '01M00000000000000000000307',
|
||||
username: 'payable-e2e',
|
||||
displayName: '应付登记员',
|
||||
departmentName: '财务部',
|
||||
mustChangePassword: false,
|
||||
},
|
||||
roles: [{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' }],
|
||||
activeRole: 'FINANCE_MANAGER',
|
||||
permissions: ['contractcost:ledger:view', 'contractcost:payable:create', 'contractcost:payable:confirm'],
|
||||
};
|
||||
}
|
||||
|
||||
function contract(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
publicId: contractId,
|
||||
businessNo: 'HT-E2E-013',
|
||||
name: '合同成本验收合同',
|
||||
company: { publicId: companyId, code: 'GS-E2E', name: '凯迪测试公司' },
|
||||
project: { publicId: projectId, code: 'XM-E2E', name: '合同成本验收项目' },
|
||||
counterparty: { publicId: '01M00000000000000000000308', code: 'GYS-E2E', name: '测试供应商' },
|
||||
originalAmount: '1000.00',
|
||||
approvedChangeAmount: '0.00',
|
||||
settlementAmount: '0.00',
|
||||
invoicedAmount: '0.00',
|
||||
pendingPayableAmount: '0.00',
|
||||
payableAmount: '0.00',
|
||||
paidAmount: '0.00',
|
||||
availableAmount: '1000.00',
|
||||
currency: 'CNY',
|
||||
status: 'ACTIVE',
|
||||
version: 7,
|
||||
updatedAt: '2026-08-16T08:00:00Z',
|
||||
allowedActions: ['CREATE_PAYABLE'],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function payable(status: 'PENDING' | 'CONFIRMED' = 'PENDING') {
|
||||
return {
|
||||
publicId: payableId,
|
||||
businessNo: 'PAY-E2E-013',
|
||||
businessDate: '2026-08-16',
|
||||
costCategory: { publicId: costCategoryId, code: 'CC-E2E', name: '测试成本类别' },
|
||||
amount: '128.50',
|
||||
invoicedAmount: '0.00',
|
||||
paidAmount: '0.00',
|
||||
unpaidAmount: '128.50',
|
||||
status,
|
||||
version: status === 'PENDING' ? 0 : 1,
|
||||
updatedAt: '2026-08-16T08:05:00Z',
|
||||
allowedActions: status === 'PENDING' ? ['CONFIRM'] : [],
|
||||
};
|
||||
}
|
||||
|
||||
function summary(currentContract: ReturnType<typeof contract>) {
|
||||
return {
|
||||
contractCount: 1,
|
||||
originalAmount: currentContract.originalAmount,
|
||||
approvedChangeAmount: currentContract.approvedChangeAmount,
|
||||
settlementAmount: currentContract.settlementAmount,
|
||||
invoicedAmount: currentContract.invoicedAmount,
|
||||
pendingPayableAmount: currentContract.pendingPayableAmount,
|
||||
payableAmount: currentContract.payableAmount,
|
||||
paidAmount: currentContract.paidAmount,
|
||||
availableAmount: currentContract.availableAmount,
|
||||
};
|
||||
}
|
||||
|
||||
interface FixtureOptions {
|
||||
createRequests: Array<Record<string, unknown>>;
|
||||
confirmRequests: Array<Record<string, unknown>>;
|
||||
commandHeaders: Array<Record<string, string>>;
|
||||
sourceQueries: URLSearchParams[];
|
||||
}
|
||||
|
||||
async function installFixture(page: Page, options: FixtureOptions) {
|
||||
const userSession = session();
|
||||
let currentContract = contract();
|
||||
let currentPayables: Array<ReturnType<typeof payable>> = [];
|
||||
|
||||
const detail = () => ({
|
||||
contract: currentContract,
|
||||
changes: [],
|
||||
settlements: [],
|
||||
payables: currentPayables,
|
||||
invoices: [],
|
||||
payments: [],
|
||||
});
|
||||
|
||||
await page.route('**/api/v1/**', async (route: Route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
const pathname = url.pathname;
|
||||
|
||||
if (pathname === '/api/v1/auth/session') return route.fulfill({ json: envelope(userSession) });
|
||||
if (pathname === '/api/v1/auth/profile') return route.fulfill({ json: envelope(userSession.user) });
|
||||
if (pathname === '/api/v1/auth/roles') return route.fulfill({ json: envelope(userSession.roles) });
|
||||
if (pathname === '/api/v1/auth/csrf') {
|
||||
return route.fulfill({
|
||||
headers: { 'set-cookie': 'XSRF-TOKEN=payable-e2e-csrf; Path=/' },
|
||||
json: envelope({ token: 'payable-e2e-csrf' }),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/contract-costs/references') {
|
||||
return route.fulfill({
|
||||
json: envelope({
|
||||
companies: [currentContract.company],
|
||||
projects: [currentContract.project],
|
||||
counterparties: [currentContract.counterparty],
|
||||
costCategories: [{ publicId: costCategoryId, code: 'CC-E2E', name: '测试成本类别' }],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/contract-costs/contracts') {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
...envelope({ items: [currentContract], summary: summary(currentContract) }, 1),
|
||||
},
|
||||
});
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === `/api/v1/contract-costs/contracts/${contractId}`) {
|
||||
return route.fulfill({ json: envelope(detail()) });
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/source/forms') {
|
||||
options.sourceQueries.push(new URLSearchParams(url.searchParams));
|
||||
return route.fulfill({
|
||||
json: envelope(
|
||||
[
|
||||
{
|
||||
publicId: sourceDocumentId,
|
||||
businessNo: 'OA04-E2E-013',
|
||||
sourceNo: 'OA-04-SRC-013',
|
||||
formType: 'OA-04',
|
||||
formName: '应付资金确认单',
|
||||
templateVersion: 1,
|
||||
sourceSystem: 'OA',
|
||||
sourceVersion: '1',
|
||||
company: currentContract.company,
|
||||
project: currentContract.project,
|
||||
applicantName: '应付申请员',
|
||||
businessDate: '2026-08-16',
|
||||
status: 'APPROVED',
|
||||
version: 1,
|
||||
createdByName: '应付申请员',
|
||||
createdAt: '2026-08-16T07:00:00Z',
|
||||
updatedAt: '2026-08-16T07:30:00Z',
|
||||
allowedActions: [],
|
||||
},
|
||||
],
|
||||
1,
|
||||
),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/contract-costs/contracts/${contractId}/payables`) {
|
||||
options.createRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
options.commandHeaders.push(request.headers());
|
||||
currentContract = contract({
|
||||
pendingPayableAmount: '128.50',
|
||||
availableAmount: '871.50',
|
||||
version: 8,
|
||||
updatedAt: '2026-08-16T08:05:00Z',
|
||||
});
|
||||
currentPayables = [payable()];
|
||||
return route.fulfill({ json: envelope(detail()) });
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/contract-costs/payables/${payableId}/confirm`) {
|
||||
options.confirmRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
options.commandHeaders.push(request.headers());
|
||||
currentContract = contract({
|
||||
payableAmount: '128.50',
|
||||
availableAmount: '871.50',
|
||||
version: 9,
|
||||
updatedAt: '2026-08-16T08:10:00Z',
|
||||
});
|
||||
currentPayables = [payable('CONFIRMED')];
|
||||
return route.fulfill({ json: envelope(detail()) });
|
||||
}
|
||||
if (request.method() === 'GET') return route.fulfill({ json: envelope([], 0) });
|
||||
return route.fulfill({ status: 204 });
|
||||
});
|
||||
}
|
||||
|
||||
test('PAGE-13 registers an approved OA-04 payable and confirms it with separate versions', async ({ page }) => {
|
||||
const createRequests: Array<Record<string, unknown>> = [];
|
||||
const confirmRequests: Array<Record<string, unknown>> = [];
|
||||
const commandHeaders: Array<Record<string, string>> = [];
|
||||
const sourceQueries: URLSearchParams[] = [];
|
||||
await installFixture(page, { createRequests, confirmRequests, commandHeaders, sourceQueries });
|
||||
|
||||
await page.goto('/finance/contracts-costs');
|
||||
await expect(page.getByRole('heading', { name: '合同与成本台账', exact: true })).toBeVisible();
|
||||
await expect(page.getByText('HT-E2E-013', { exact: true }).first()).toBeVisible();
|
||||
await page.getByRole('button', { name: '查看', exact: true }).click();
|
||||
const drawer = page.locator('.t-drawer').filter({ hasText: 'HT-E2E-013' });
|
||||
await drawer.getByRole('button', { name: '登记应付', exact: true }).click();
|
||||
|
||||
const dialog = page.locator('.t-dialog').filter({ hasText: '登记应付' });
|
||||
const businessDate = await page.evaluate(() => {
|
||||
const now = new Date();
|
||||
return new Date(now.getTime() - now.getTimezoneOffset() * 60_000).toISOString().slice(0, 10);
|
||||
});
|
||||
await expect(dialog.locator('.t-date-picker input')).toHaveValue(businessDate);
|
||||
await dialog.getByPlaceholder('请输入应付业务编号', { exact: true }).fill('PAY-E2E-013');
|
||||
await dialog.getByPlaceholder('请选择成本类别', { exact: true }).click();
|
||||
await page.getByText('CC-E2E 测试成本类别', { exact: true }).click();
|
||||
await dialog.getByPlaceholder('请输入金额', { exact: true }).fill('128.50');
|
||||
|
||||
await dialog.getByRole('button', { name: '提交', exact: true }).click();
|
||||
await expect(dialog.getByText('请选择已批准的 OA-04 来源审批单', { exact: true })).toBeVisible();
|
||||
expect(createRequests).toHaveLength(0);
|
||||
|
||||
await dialog.getByPlaceholder('请选择已批准的应付资金确认单', { exact: true }).click();
|
||||
await page.getByText('OA04-E2E-013 · OA-04-SRC-013 · 应付资金确认单', { exact: true }).click();
|
||||
await dialog.getByRole('button', { name: '提交', exact: true }).click();
|
||||
|
||||
await expect.poll(() => createRequests.length).toBe(1);
|
||||
expect(createRequests[0]).toEqual({
|
||||
businessNo: 'PAY-E2E-013',
|
||||
costCategoryId,
|
||||
sourceDocumentId,
|
||||
businessDate,
|
||||
amount: '128.50',
|
||||
contractVersion: 7,
|
||||
});
|
||||
expect(commandHeaders[0]['x-xsrf-token']).toBe('payable-e2e-csrf');
|
||||
expect(commandHeaders[0]['idempotency-key']).toBeTruthy();
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect(drawer.getByText('PAY-E2E-013', { exact: true })).toBeVisible();
|
||||
await expect(drawer.getByText('待处理', { exact: true })).toBeVisible();
|
||||
|
||||
expect(sourceQueries).toHaveLength(1);
|
||||
expect(sourceQueries[0].get('formType')).toBe('OA-04');
|
||||
expect(sourceQueries[0].get('projectId')).toBe(projectId);
|
||||
expect(sourceQueries[0].get('status')).toBe('APPROVED');
|
||||
expect(sourceQueries[0].get('page')).toBe('1');
|
||||
expect(sourceQueries[0].get('size')).toBe('100');
|
||||
|
||||
await drawer.getByRole('button', { name: '确认', exact: true }).click();
|
||||
const popconfirm = page.locator('.t-popconfirm').filter({ hasText: '确认这笔应付已完成财务核对?' });
|
||||
await popconfirm.locator('.t-popconfirm__confirm').click();
|
||||
await expect.poll(() => confirmRequests.length).toBe(1);
|
||||
expect(confirmRequests[0]).toEqual({ version: 0 });
|
||||
expect(commandHeaders[1]['x-xsrf-token']).toBe('payable-e2e-csrf');
|
||||
expect(commandHeaders[1]['idempotency-key']).toBeTruthy();
|
||||
await expect(drawer.getByText('已确认', { exact: true })).toBeVisible();
|
||||
await expect(drawer.getByRole('button', { name: '确认', exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('应付已确认', { exact: true })).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user