This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
|
||||
import type { Page, Route } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const paymentId = '01M00000000000000000000201';
|
||||
const companyId = '01M00000000000000000000202';
|
||||
const projectId = '01M00000000000000000000203';
|
||||
const contractId = '01M00000000000000000000204';
|
||||
const supplierId = '01M00000000000000000000205';
|
||||
const bankAccountId = '01M00000000000000000000206';
|
||||
const fileId = '01M00000000000000000000207';
|
||||
|
||||
function envelope(data: unknown, totalElements?: number) {
|
||||
return {
|
||||
data,
|
||||
...(typeof totalElements === 'number'
|
||||
? { meta: { page: 1, size: 20, totalElements, totalPages: totalElements ? 1 : 0 } }
|
||||
: {}),
|
||||
requestId: '01M00000000000000000000299',
|
||||
};
|
||||
}
|
||||
|
||||
function session() {
|
||||
return {
|
||||
authenticated: true,
|
||||
user: {
|
||||
publicId: '01M00000000000000000000208',
|
||||
username: 'payment-e2e',
|
||||
displayName: '付款结果登记员',
|
||||
departmentName: '财务部',
|
||||
mustChangePassword: false,
|
||||
},
|
||||
roles: [{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' }],
|
||||
activeRole: 'FINANCE_MANAGER',
|
||||
permissions: ['payment:request:view', 'payment:request:record-result'],
|
||||
};
|
||||
}
|
||||
|
||||
function payment(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
publicId: paymentId,
|
||||
businessNo: 'PAY-E2E-001',
|
||||
company: { publicId: companyId, code: 'GS-E2E', name: '凯迪测试公司' },
|
||||
project: { publicId: projectId, code: 'XM-E2E', name: '付款结果项目' },
|
||||
contract: { publicId: contractId, code: 'HT-E2E', name: '测试合同' },
|
||||
payable: null,
|
||||
supplier: { publicId: supplierId, code: 'GYS-E2E', name: '测试供应商' },
|
||||
bankAccount: {
|
||||
publicId: bankAccountId,
|
||||
supplierId,
|
||||
accountName: '测试账户',
|
||||
bankName: '测试银行',
|
||||
maskedAccountNo: '****0001',
|
||||
versionNo: 1,
|
||||
},
|
||||
sourceDocumentId: null,
|
||||
originalPaymentId: null,
|
||||
repaymentSequence: 0,
|
||||
requestedAmount: '1000.00',
|
||||
approvedAmount: '1000.00',
|
||||
frozenAmount: '1000.00',
|
||||
paidAmount: '0.00',
|
||||
currency: 'CNY',
|
||||
requestedDate: '2026-08-15',
|
||||
purpose: '付款结果登记验收',
|
||||
status: 'OFFLINE_PENDING',
|
||||
submittedByName: null,
|
||||
approvedByName: '财务审批员',
|
||||
resultRecordedByName: null,
|
||||
resultVerifiedByName: null,
|
||||
version: 2,
|
||||
createdAt: '2026-08-15T08:00:00Z',
|
||||
updatedAt: '2026-08-15T08:00:00Z',
|
||||
allowedActions: ['RECORD_SUCCESS'],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
interface FixtureOptions {
|
||||
paymentQueries: URLSearchParams[];
|
||||
recordRequests: Array<Record<string, unknown>>;
|
||||
recordHeaders: Array<Record<string, string>>;
|
||||
}
|
||||
|
||||
async function installFixture(page: Page, options: FixtureOptions) {
|
||||
const userSession = session();
|
||||
let currentPayment = payment();
|
||||
|
||||
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=payment-e2e-csrf; Path=/' },
|
||||
json: envelope({ token: 'payment-e2e-csrf' }),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/payments/references') {
|
||||
return route.fulfill({
|
||||
json: envelope({
|
||||
companies: [],
|
||||
projects: [],
|
||||
suppliers: [],
|
||||
contracts: [],
|
||||
payables: [],
|
||||
bankAccounts: [],
|
||||
attachmentRules: [],
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'GET' && pathname === '/api/v1/payments') {
|
||||
options.paymentQueries.push(new URLSearchParams(url.searchParams));
|
||||
return route.fulfill({ json: envelope([currentPayment], 1) });
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === '/api/v1/files') {
|
||||
return route.fulfill({
|
||||
json: envelope({
|
||||
publicId: fileId,
|
||||
originalName: 'payment-receipt.pdf',
|
||||
mediaType: 'application/pdf',
|
||||
sizeBytes: 24,
|
||||
sha256: 'd'.repeat(64),
|
||||
scanStatus: 'AVAILABLE',
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (request.method() === 'POST' && pathname === `/api/v1/payments/${paymentId}/confirm-offline-result`) {
|
||||
options.recordRequests.push(request.postDataJSON() as Record<string, unknown>);
|
||||
options.recordHeaders.push(request.headers());
|
||||
currentPayment = payment({
|
||||
status: 'RESULT_RECORDED',
|
||||
version: 3,
|
||||
resultRecordedByName: '付款结果登记员',
|
||||
allowedActions: [],
|
||||
updatedAt: '2026-08-16T08:05:00Z',
|
||||
});
|
||||
return route.fulfill({ json: envelope(currentPayment) });
|
||||
}
|
||||
if (request.method() === 'GET') return route.fulfill({ json: envelope([], 0) });
|
||||
return route.fulfill({ status: 204 });
|
||||
});
|
||||
}
|
||||
|
||||
test('PAGE-15 blocks a result without evidence and records a successful offline payment', async ({ page }) => {
|
||||
const paymentQueries: URLSearchParams[] = [];
|
||||
const recordRequests: Array<Record<string, unknown>> = [];
|
||||
const recordHeaders: Array<Record<string, string>> = [];
|
||||
await installFixture(page, { paymentQueries, recordRequests, recordHeaders });
|
||||
|
||||
await page.goto('/finance/payments?tab=offline');
|
||||
await expect(page.getByRole('heading', { name: '进度款与付款', exact: true })).toBeVisible();
|
||||
await expect(page.getByText('PAY-E2E-001', { exact: true }).first()).toBeVisible();
|
||||
await expect.poll(() => paymentQueries.length).toBeGreaterThan(0);
|
||||
|
||||
const query = paymentQueries.at(-1)!;
|
||||
expect(query.get('tab')).toBe('offline');
|
||||
expect(query.get('sort')).toBe('updatedAt,desc');
|
||||
expect(query.get('page')).toBe('1');
|
||||
expect(query.get('size')).toBe('20');
|
||||
const initialListRequests = paymentQueries.length;
|
||||
|
||||
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
||||
await page.getByText('登记付款成功', { exact: true }).click();
|
||||
const dialog = page.locator('.t-dialog').filter({ hasText: '登记付款成功' });
|
||||
const resultDate = 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(resultDate);
|
||||
|
||||
await dialog.getByRole('button', { name: '确认', exact: true }).click();
|
||||
await expect(dialog.getByText('请上传回单或结果附件。', { exact: true })).toBeVisible();
|
||||
expect(recordRequests).toHaveLength(0);
|
||||
|
||||
await dialog.getByPlaceholder('请输入金额', { exact: true }).fill('1000');
|
||||
await dialog.locator('textarea').fill('线下转账已完成');
|
||||
await dialog.locator('input[type="file"]').setInputFiles({
|
||||
name: 'payment-receipt.pdf',
|
||||
mimeType: 'application/pdf',
|
||||
buffer: Buffer.from('payment-result-evidence'),
|
||||
});
|
||||
await dialog.getByRole('button', { name: '上传已选文件', exact: true }).click();
|
||||
await expect(dialog.getByText('可用', { exact: true })).toBeVisible();
|
||||
await dialog.getByRole('button', { name: '确认', exact: true }).click();
|
||||
|
||||
await expect.poll(() => recordRequests.length).toBe(1);
|
||||
expect(recordRequests[0]).toEqual({
|
||||
version: 2,
|
||||
resultAt: resultDate,
|
||||
amount: '1000.00',
|
||||
receiptFileIds: [fileId],
|
||||
remark: '线下转账已完成',
|
||||
});
|
||||
expect(recordHeaders[0]['x-xsrf-token']).toBe('payment-e2e-csrf');
|
||||
expect(recordHeaders[0]['idempotency-key']).toBeTruthy();
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect.poll(() => paymentQueries.length).toBeGreaterThan(initialListRequests);
|
||||
await expect(page.getByText('结果待复核', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('登记付款成功已完成', { exact: true })).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user