572 lines
22 KiB
TypeScript
572 lines
22 KiB
TypeScript
import { Buffer } from 'node:buffer';
|
|
|
|
import type { Page, Route } from '@playwright/test';
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
const eventId = '01M00000000000000000000160';
|
|
const voucherId = '01M00000000000000000000161';
|
|
const companyId = '01M00000000000000000000162';
|
|
const projectId = '01M00000000000000000000163';
|
|
const counterpartyId = '01M00000000000000000000164';
|
|
const debitAccountId = '01M00000000000000000000165';
|
|
const creditAccountId = '01M00000000000000000000166';
|
|
|
|
interface AccountingFixtureOptions {
|
|
initialTab?: 'pending' | 'draft';
|
|
initialVoucher?: ReturnType<typeof voucher>;
|
|
initialEvidence?: Array<Record<string, unknown>>;
|
|
generateRequests?: Array<Record<string, unknown>>;
|
|
updateRequests?: Array<Record<string, unknown>>;
|
|
recordRequests?: Array<Record<string, unknown>>;
|
|
verifyRequests?: Array<Record<string, unknown>>;
|
|
reverseRequests?: Array<Record<string, unknown>>;
|
|
reopenRequests?: Array<Record<string, unknown>>;
|
|
}
|
|
|
|
function accountingEvent() {
|
|
return {
|
|
publicId: eventId,
|
|
businessNo: 'EVT-E2E-001',
|
|
eventType: 'PAYABLE',
|
|
sourceType: 'PAYABLE',
|
|
sourcePublicId: '01M00000000000000000000167',
|
|
sourceVersion: '1',
|
|
eventSequence: 1,
|
|
sourceEventKey: 'PAYABLE:E2E:1',
|
|
company: { publicId: companyId, code: 'GS-E2E-001', name: '凯迪测试公司' },
|
|
project: { publicId: projectId, code: 'XM-E2E-001', name: '记账验收项目' },
|
|
counterparty: { publicId: counterpartyId, code: 'GY-E2E-001', name: '验收供应商' },
|
|
businessDate: '2026-08-15',
|
|
period: '2026-08',
|
|
amount: '1000.00',
|
|
currency: 'CNY',
|
|
status: 'PENDING',
|
|
voucherPublicId: null,
|
|
version: 0,
|
|
createdAt: '2026-08-15T08:00:00Z',
|
|
allowedActions: ['GENERATE_DRAFT'],
|
|
};
|
|
}
|
|
|
|
function voucher(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
publicId: voucherId,
|
|
businessNo: 'VCH-202608-E2E001',
|
|
eventPublicId: eventId,
|
|
eventBusinessNo: 'EVT-E2E-001',
|
|
company: { publicId: companyId, code: 'GS-E2E-001', name: '凯迪测试公司' },
|
|
project: { publicId: projectId, code: 'XM-E2E-001', name: '记账验收项目' },
|
|
period: '2026-08',
|
|
businessDate: '2026-08-15',
|
|
summary: '应付确认 EVT-E2E-001',
|
|
debitTotal: '1000.00',
|
|
creditTotal: '1000.00',
|
|
balanceStatus: 'BALANCED',
|
|
status: 'DRAFT',
|
|
submittedByName: null,
|
|
reviewedByName: null,
|
|
exportedAt: null,
|
|
exportBatchNo: null,
|
|
exportSha256: null,
|
|
exportFileId: null,
|
|
ruleVersion: 'ACCOUNTING-RULE-V1',
|
|
externalVoucherNo: null,
|
|
resultAt: null,
|
|
resultCycleNo: 0,
|
|
evidenceFileIds: [],
|
|
resultRecordedByName: null,
|
|
resultRecordedAt: null,
|
|
resultRecordRemark: null,
|
|
resultVerifiedByName: null,
|
|
resultVerifiedAt: null,
|
|
resultVerifyOpinion: null,
|
|
voidReason: null,
|
|
version: 1,
|
|
createdAt: '2026-08-15T08:01:00Z',
|
|
updatedAt: '2026-08-15T08:01:00Z',
|
|
allowedActions: ['EDIT', 'SUBMIT', 'VOID'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function entries() {
|
|
const auxiliaryJson = JSON.stringify({ projectId, counterpartyId });
|
|
return [
|
|
{
|
|
publicId: '01M00000000000000000000168',
|
|
lineNo: 1,
|
|
direction: 'DEBIT',
|
|
account: { publicId: debitAccountId, code: '6601', name: '项目成本' },
|
|
amount: '1000.00',
|
|
summary: '应付确认 EVT-E2E-001',
|
|
auxiliaryJson,
|
|
},
|
|
{
|
|
publicId: '01M00000000000000000000169',
|
|
lineNo: 2,
|
|
direction: 'CREDIT',
|
|
account: { publicId: creditAccountId, code: '2202', name: '应付账款' },
|
|
amount: '1000.00',
|
|
summary: '应付确认 EVT-E2E-001',
|
|
auxiliaryJson,
|
|
},
|
|
];
|
|
}
|
|
|
|
function voucherDetail(
|
|
currentVoucher = voucher(),
|
|
evidenceFiles: Array<Record<string, unknown>> = [],
|
|
resultActions: Array<Record<string, unknown>> = [],
|
|
) {
|
|
return { voucher: currentVoucher, event: accountingEvent(), entries: entries(), evidenceFiles, resultActions };
|
|
}
|
|
|
|
function userSession(reviewer = false) {
|
|
return {
|
|
authenticated: true,
|
|
user: {
|
|
publicId: reviewer ? '01M00000000000000000000183' : '01M00000000000000000000170',
|
|
username: reviewer ? 'accounting-reviewer-e2e' : 'accounting-recorder-e2e',
|
|
displayName: reviewer ? '记账复核员' : '记账登记员',
|
|
departmentName: '财务部',
|
|
mustChangePassword: false,
|
|
},
|
|
roles: [{ code: 'FINANCE_MANAGER', name: '财务管理人员', workbenchRoute: '/workbench/finance' }],
|
|
activeRole: 'FINANCE_MANAGER',
|
|
permissions: [
|
|
'accounting:event:view',
|
|
'accounting:event:generate',
|
|
'accounting:voucher:view',
|
|
'accounting:voucher:edit',
|
|
'accounting:voucher:reverse-result',
|
|
'accounting:voucher:reopen-result',
|
|
],
|
|
};
|
|
}
|
|
|
|
async function installAccountingFixture(page: Page, options: AccountingFixtureOptions = {}) {
|
|
let reviewerSession = false;
|
|
let currentVoucher = options.initialVoucher || voucher();
|
|
let currentEvidence: Array<Record<string, unknown>> = options.initialEvidence || [];
|
|
const resultActions: Array<Record<string, unknown>> = [];
|
|
|
|
await page.route('**/api/v1/**', async (route: Route) => {
|
|
const request = route.request();
|
|
const pathname = new URL(request.url()).pathname;
|
|
const session = userSession(reviewerSession);
|
|
|
|
if (pathname === '/api/v1/auth/session') {
|
|
await route.fulfill({ json: { data: session, requestId: '01M00000000000000000000171' } });
|
|
return;
|
|
}
|
|
if (pathname === '/api/v1/auth/profile') {
|
|
await route.fulfill({ json: { data: session.user, requestId: '01M00000000000000000000172' } });
|
|
return;
|
|
}
|
|
if (pathname === '/api/v1/auth/roles') {
|
|
await route.fulfill({ json: { data: session.roles, requestId: '01M00000000000000000000173' } });
|
|
return;
|
|
}
|
|
if (pathname === '/api/v1/auth/csrf') {
|
|
await route.fulfill({
|
|
headers: { 'set-cookie': 'XSRF-TOKEN=accounting-e2e-csrf; Path=/' },
|
|
json: { data: { token: 'accounting-e2e-csrf' }, requestId: '01M00000000000000000000174' },
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET' && pathname === '/api/v1/accounting/accounts') {
|
|
await route.fulfill({
|
|
json: {
|
|
data: [
|
|
{
|
|
publicId: debitAccountId,
|
|
code: '6601',
|
|
name: '项目成本',
|
|
accountType: 'EXPENSE',
|
|
auxiliaryRequired: true,
|
|
},
|
|
{
|
|
publicId: creditAccountId,
|
|
code: '2202',
|
|
name: '应付账款',
|
|
accountType: 'LIABILITY',
|
|
auxiliaryRequired: true,
|
|
},
|
|
],
|
|
requestId: '01M00000000000000000000175',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET' && pathname === '/api/v1/masterdata/references') {
|
|
await route.fulfill({
|
|
json: {
|
|
data: {
|
|
companies: [accountingEvent().company],
|
|
projects: [accountingEvent().project],
|
|
counterparties: [accountingEvent().counterparty],
|
|
costCategories: [],
|
|
},
|
|
requestId: '01M00000000000000000000176',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET' && pathname === '/api/v1/accounting/events') {
|
|
await route.fulfill({
|
|
json: {
|
|
data: [accountingEvent()],
|
|
meta: { page: 1, size: 20, totalElements: 1, totalPages: 1 },
|
|
requestId: '01M00000000000000000000177',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET' && pathname === '/api/v1/accounting/vouchers') {
|
|
await route.fulfill({
|
|
json: {
|
|
data: [currentVoucher],
|
|
meta: { page: 1, size: 20, totalElements: 1, totalPages: 1 },
|
|
requestId: '01M00000000000000000000178',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET' && pathname === `/api/v1/accounting/vouchers/${voucherId}`) {
|
|
await route.fulfill({
|
|
json: {
|
|
data: voucherDetail(currentVoucher, currentEvidence, resultActions),
|
|
requestId: '01M00000000000000000000179',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === `/api/v1/accounting/events/${eventId}/generate-draft`) {
|
|
options.generateRequests?.push(request.postDataJSON() as Record<string, unknown>);
|
|
await route.fulfill({
|
|
json: { data: voucherDetail(currentVoucher), requestId: '01M00000000000000000000180' },
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'PATCH' && pathname === `/api/v1/accounting/vouchers/${voucherId}`) {
|
|
const payload = request.postDataJSON() as Record<string, unknown>;
|
|
options.updateRequests?.push(payload);
|
|
currentVoucher = voucher({ summary: payload.summary, version: 2, updatedAt: '2026-08-15T08:02:00Z' });
|
|
await route.fulfill({
|
|
json: { data: voucherDetail(currentVoucher), requestId: '01M00000000000000000000181' },
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === '/api/v1/files') {
|
|
currentEvidence = [
|
|
{
|
|
publicId: '01M00000000000000000000184',
|
|
originalName: 'accounting-result.pdf',
|
|
mediaType: 'application/pdf',
|
|
sizeBytes: 20,
|
|
sha256: 'c'.repeat(64),
|
|
scanStatus: 'AVAILABLE',
|
|
},
|
|
];
|
|
await route.fulfill({
|
|
json: { data: currentEvidence[0], requestId: '01M00000000000000000000185' },
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === `/api/v1/accounting/vouchers/${voucherId}/record-result`) {
|
|
const payload = request.postDataJSON() as Record<string, unknown>;
|
|
options.recordRequests?.push(payload);
|
|
currentVoucher = voucher({
|
|
status: 'RESULT_RECORDED',
|
|
version: 4,
|
|
allowedActions: ['VERIFY_RESULT'],
|
|
externalVoucherNo: payload.externalVoucherNo,
|
|
resultAt: payload.resultAt,
|
|
evidenceFileIds: payload.evidenceFileIds,
|
|
resultRecordedByName: '记账登记员',
|
|
resultRecordedAt: '2026-08-15T08:10:00Z',
|
|
resultRecordRemark: payload.remark,
|
|
});
|
|
reviewerSession = true;
|
|
await route.fulfill({
|
|
json: {
|
|
data: voucherDetail(currentVoucher, currentEvidence, resultActions),
|
|
requestId: '01M00000000000000000000186',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === `/api/v1/accounting/vouchers/${voucherId}/verify-result`) {
|
|
const payload = request.postDataJSON() as Record<string, unknown>;
|
|
options.verifyRequests?.push(payload);
|
|
currentVoucher = voucher({
|
|
...currentVoucher,
|
|
status: 'COMPLETED',
|
|
version: 5,
|
|
allowedActions: [],
|
|
resultVerifiedByName: '记账复核员',
|
|
resultVerifiedAt: '2026-08-15T08:15:00Z',
|
|
resultVerifyOpinion: payload.opinion,
|
|
});
|
|
await route.fulfill({
|
|
json: {
|
|
data: voucherDetail(currentVoucher, currentEvidence, resultActions),
|
|
requestId: '01M00000000000000000000187',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === `/api/v1/accounting/vouchers/${voucherId}/reverse-result`) {
|
|
const payload = request.postDataJSON() as Record<string, unknown>;
|
|
options.reverseRequests?.push(payload);
|
|
resultActions.unshift({
|
|
publicId: '01M00000000000000000000188',
|
|
sequenceNo: 1,
|
|
resultCycleNo: 0,
|
|
actionType: 'REVERSE',
|
|
reason: payload.reason,
|
|
externalVoucherNo: currentVoucher.externalVoucherNo,
|
|
resultAt: currentVoucher.resultAt,
|
|
resultRecordedByName: currentVoucher.resultRecordedByName,
|
|
resultRecordedAt: currentVoucher.resultRecordedAt,
|
|
resultRecordRemark: currentVoucher.resultRecordRemark,
|
|
resultVerifiedByName: currentVoucher.resultVerifiedByName,
|
|
resultVerifiedAt: currentVoucher.resultVerifiedAt,
|
|
resultVerifyOpinion: currentVoucher.resultVerifyOpinion,
|
|
evidenceFiles: currentEvidence,
|
|
operatedByName: '记账复核员',
|
|
operatedAt: '2026-08-15T08:20:00Z',
|
|
versionBefore: 5,
|
|
versionAfter: 6,
|
|
});
|
|
currentVoucher = voucher({
|
|
...currentVoucher,
|
|
status: 'RESULT_REVERSED',
|
|
version: 6,
|
|
allowedActions: ['REOPEN_RESULT'],
|
|
});
|
|
reviewerSession = false;
|
|
await route.fulfill({
|
|
json: {
|
|
data: voucherDetail(currentVoucher, currentEvidence, resultActions),
|
|
requestId: '01M00000000000000000000189',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'POST' && pathname === `/api/v1/accounting/vouchers/${voucherId}/reopen-result`) {
|
|
const payload = request.postDataJSON() as Record<string, unknown>;
|
|
options.reopenRequests?.push(payload);
|
|
resultActions.unshift({
|
|
...resultActions[0],
|
|
publicId: '01M00000000000000000000190',
|
|
sequenceNo: 2,
|
|
actionType: 'REOPEN',
|
|
reason: payload.reason,
|
|
operatedByName: '记账登记员',
|
|
operatedAt: '2026-08-15T08:25:00Z',
|
|
versionBefore: 6,
|
|
versionAfter: 7,
|
|
});
|
|
currentVoucher = voucher({
|
|
...currentVoucher,
|
|
status: 'EXPORTED',
|
|
version: 7,
|
|
resultCycleNo: 1,
|
|
allowedActions: ['RECORD_RESULT'],
|
|
externalVoucherNo: null,
|
|
resultAt: null,
|
|
evidenceFileIds: [],
|
|
resultRecordedByName: null,
|
|
resultRecordedAt: null,
|
|
resultRecordRemark: null,
|
|
resultVerifiedByName: null,
|
|
resultVerifiedAt: null,
|
|
resultVerifyOpinion: null,
|
|
});
|
|
currentEvidence = [];
|
|
await route.fulfill({
|
|
json: {
|
|
data: voucherDetail(currentVoucher, currentEvidence, resultActions),
|
|
requestId: '01M00000000000000000000191',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
if (request.method() === 'GET') {
|
|
await route.fulfill({
|
|
json: {
|
|
data: [],
|
|
meta: { page: 1, size: 20, totalElements: 0, totalPages: 0 },
|
|
requestId: '01M00000000000000000000182',
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
await route.fulfill({ status: 204 });
|
|
});
|
|
}
|
|
|
|
test('PAGE-16 generates one voucher draft with the fixed rule version', async ({ page }) => {
|
|
const generateRequests: Array<Record<string, unknown>> = [];
|
|
await installAccountingFixture(page, { generateRequests });
|
|
|
|
await page.goto('/finance/accounting?tab=pending');
|
|
await expect(page.getByRole('heading', { name: '记账准备', exact: true })).toBeVisible();
|
|
await expect(page.getByText('EVT-E2E-001', { exact: true }).first()).toBeVisible();
|
|
await page.getByRole('button', { name: '生成草稿', exact: true }).click();
|
|
|
|
await expect.poll(() => generateRequests.length).toBe(1);
|
|
expect(generateRequests[0]).toEqual({ version: 0, ruleVersion: 'ACCOUNTING-RULE-V1' });
|
|
await expect(page.getByText('凭证草稿已生成', { exact: true })).toBeVisible();
|
|
await expect(page.getByText('借贷平衡', { exact: true })).toBeVisible();
|
|
});
|
|
|
|
test('PAGE-16 edits an auxiliary voucher directly from the list without raw JSON input', async ({ page }) => {
|
|
const updateRequests: Array<Record<string, unknown>> = [];
|
|
await installAccountingFixture(page, { initialTab: 'draft', updateRequests });
|
|
|
|
await page.goto('/finance/accounting?tab=draft');
|
|
await expect(page.getByText('VCH-202608-E2E001', { exact: true }).first()).toBeVisible();
|
|
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
|
await page.getByText('编辑草稿', { exact: true }).click();
|
|
|
|
const dialog = page.locator('.t-dialog').filter({ hasText: '编辑凭证草稿' });
|
|
await expect(dialog.getByText('项目:XM-E2E-001 记账验收项目', { exact: true }).first()).toBeVisible();
|
|
await expect(dialog.getByText('往来单位:GY-E2E-001 验收供应商', { exact: true }).first()).toBeVisible();
|
|
await expect(dialog.getByText('辅助核算 JSON', { exact: true })).toHaveCount(0);
|
|
await dialog.locator('textarea').fill('核对来源维度后保存');
|
|
await dialog.getByRole('button', { name: '保存草稿', exact: true }).click();
|
|
|
|
await expect.poll(() => updateRequests.length).toBe(1);
|
|
const payload = updateRequests[0];
|
|
const payloadEntries = payload.entries as Array<Record<string, unknown>>;
|
|
expect(payloadEntries).toHaveLength(2);
|
|
for (const entry of payloadEntries) {
|
|
expect(JSON.parse(String(entry.auxiliaryJson))).toEqual({ projectId, counterpartyId });
|
|
}
|
|
expect(payload.changeReason).toBe('核对来源维度后保存');
|
|
await expect(dialog).toBeHidden();
|
|
await expect(page.getByText('凭证草稿已保存', { exact: true })).toBeVisible();
|
|
});
|
|
|
|
test('PAGE-16 records evidence and preserves separate record and verification notes', async ({ page }) => {
|
|
const recordRequests: Array<Record<string, unknown>> = [];
|
|
const verifyRequests: Array<Record<string, unknown>> = [];
|
|
await installAccountingFixture(page, {
|
|
initialVoucher: voucher({ status: 'EXPORTED', version: 3, allowedActions: ['RECORD_RESULT'] }),
|
|
recordRequests,
|
|
verifyRequests,
|
|
});
|
|
|
|
await page.goto('/finance/accounting?tab=result');
|
|
await expect(page.getByText('VCH-202608-E2E001', { exact: true }).first()).toBeVisible();
|
|
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
|
await page.getByText('登记记账结果', { exact: true }).click();
|
|
|
|
const recordDialog = 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(recordDialog.locator('.t-date-picker input')).toHaveValue(resultDate);
|
|
await recordDialog.getByPlaceholder('请输入', { exact: true }).fill('TPLUS-E2E-001');
|
|
await recordDialog.locator('textarea').fill('人工导入成功');
|
|
await recordDialog.locator('input[type="file"]').setInputFiles({
|
|
name: 'accounting-result.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: Buffer.from('accounting-result-evidence'),
|
|
});
|
|
await recordDialog.getByRole('button', { name: '上传已选文件', exact: true }).click();
|
|
await expect(recordDialog.getByText('可用', { exact: true })).toBeVisible();
|
|
await recordDialog.getByRole('button', { name: '确认登记记账结果', exact: true }).click();
|
|
|
|
await expect.poll(() => recordRequests.length).toBe(1);
|
|
expect(recordRequests[0]).toEqual({
|
|
version: 3,
|
|
externalVoucherNo: 'TPLUS-E2E-001',
|
|
resultAt: resultDate,
|
|
evidenceFileIds: ['01M00000000000000000000184'],
|
|
remark: '人工导入成功',
|
|
});
|
|
await expect(recordDialog).toBeHidden();
|
|
|
|
await page.reload();
|
|
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
|
await page.getByText('复核记账结果', { exact: true }).click();
|
|
const verifyDialog = page.locator('.t-dialog').filter({ hasText: '复核记账结果' });
|
|
await verifyDialog.locator('textarea').fill('凭证号和导入结果一致');
|
|
await verifyDialog.getByRole('button', { name: '确认复核记账结果', exact: true }).click();
|
|
|
|
await expect.poll(() => verifyRequests.length).toBe(1);
|
|
expect(verifyRequests[0]).toEqual({ version: 4, opinion: '凭证号和导入结果一致' });
|
|
await expect(verifyDialog).toBeHidden();
|
|
|
|
await page.getByText('VCH-202608-E2E001', { exact: true }).first().click();
|
|
await expect(page.getByText('人工导入成功', { exact: true })).toBeVisible();
|
|
await expect(page.getByText('凭证号和导入结果一致', { exact: true })).toBeVisible();
|
|
});
|
|
|
|
test('PAGE-16 reverses and reopens a completed result from server allowed actions', async ({ page }) => {
|
|
const reverseRequests: Array<Record<string, unknown>> = [];
|
|
const reopenRequests: Array<Record<string, unknown>> = [];
|
|
const evidence = {
|
|
publicId: '01M00000000000000000000184',
|
|
originalName: 'accounting-result.pdf',
|
|
mediaType: 'application/pdf',
|
|
sizeBytes: 20,
|
|
sha256: 'c'.repeat(64),
|
|
scanStatus: 'AVAILABLE',
|
|
};
|
|
await installAccountingFixture(page, {
|
|
initialVoucher: voucher({
|
|
status: 'COMPLETED',
|
|
version: 5,
|
|
allowedActions: ['REVERSE_RESULT'],
|
|
externalVoucherNo: 'TPLUS-E2E-REVERSED',
|
|
resultAt: '2026-08-15',
|
|
evidenceFileIds: [evidence.publicId],
|
|
resultRecordedByName: '记账登记员',
|
|
resultRecordedAt: '2026-08-15T08:10:00Z',
|
|
resultRecordRemark: '原记账结果',
|
|
resultVerifiedByName: '记账复核员',
|
|
resultVerifiedAt: '2026-08-15T08:15:00Z',
|
|
resultVerifyOpinion: '原结果复核通过',
|
|
}),
|
|
initialEvidence: [evidence],
|
|
reverseRequests,
|
|
reopenRequests,
|
|
});
|
|
|
|
await page.goto('/finance/accounting?tab=completed');
|
|
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
|
await page.getByText('冲销记账结果', { exact: true }).click();
|
|
const reverseDialog = page.locator('.t-dialog').filter({ hasText: '冲销记账结果' });
|
|
await reverseDialog.getByRole('button', { name: '确认冲销记账结果', exact: true }).click();
|
|
await expect(reverseDialog.getByText('处理意见或原因至少填写 2 个字符。', { exact: true })).toBeVisible();
|
|
await reverseDialog.locator('textarea').fill('外部账套凭证有误');
|
|
await reverseDialog.getByRole('button', { name: '确认冲销记账结果', exact: true }).click();
|
|
|
|
await expect.poll(() => reverseRequests.length).toBe(1);
|
|
expect(reverseRequests[0]).toEqual({ version: 5, reason: '外部账套凭证有误' });
|
|
await expect(reverseDialog).toBeHidden();
|
|
|
|
await page.getByRole('button', { name: '更多操作', exact: true }).click();
|
|
await page.getByText('重开记账结果', { exact: true }).click();
|
|
const reopenDialog = page.locator('.t-dialog').filter({ hasText: '重开记账结果' });
|
|
await reopenDialog.locator('textarea').fill('更正后重新登记');
|
|
await reopenDialog.getByRole('button', { name: '确认重开记账结果', exact: true }).click();
|
|
|
|
await expect.poll(() => reopenRequests.length).toBe(1);
|
|
expect(reopenRequests[0]).toEqual({ version: 6, reason: '更正后重新登记' });
|
|
await expect(reopenDialog).toBeHidden();
|
|
|
|
await page.getByText('VCH-202608-E2E001', { exact: true }).first().click();
|
|
await expect(page.getByRole('region', { name: '记账结果冲销与重开记录' })).toBeVisible();
|
|
await expect(page.getByText('TPLUS-E2E-REVERSED', { exact: true })).toHaveCount(2);
|
|
await expect(page.getByText('外部账套凭证有误', { exact: true })).toBeVisible();
|
|
await expect(page.getByText('更正后重新登记', { exact: true })).toBeVisible();
|
|
await expect(page.getByText('1 份证据', { exact: true })).toHaveCount(2);
|
|
await expect(page.getByRole('region', { name: '记账结果证据' })).toHaveCount(0);
|
|
});
|