/** 来源业务核算准备组件回归;所有接口均为隔离模拟。 */ import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import vue from '@vitejs/plugin-vue' import { createServer } from 'vite' const require = createRequire(import.meta.url) const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'C:/Users/Administrator/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright') const root = fileURLToPath(new URL('../', import.meta.url)) const output = path.resolve(root, '../.ai/tplus-payroll-browser') fs.mkdirSync(output, { recursive: true }) const id = 'virtual:tplus-payroll-proof' const harness = `import {createApp,h,ref} from 'vue';import {createPinia} from 'pinia';import ElementPlus from 'element-plus';import 'element-plus/dist/index.css';import '/src/assets/styles/index.css';import Dialog from '/src/views/business/businesses/PayrollDialog.vue';import {useUserStore} from '/src/store/user.ts';const pinia=createPinia();createApp({setup(){useUserStore().userInfo={permissions:['payroll:view','payroll:prepare','payroll:calculate']};const open=ref(false);setTimeout(()=>open.value=true,0);return()=>h(Dialog,{modelValue:open.value,'onUpdate:modelValue':v=>open.value=v,businessId:12,sourceManaged:true})}}).use(pinia).use(ElementPlus).mount('#app')` const server = await createServer({ configFile: false, root, resolve: { alias: { '@': path.join(root, 'src') } }, define: { 'import.meta.env.VITE_API_BASE_URL': JSON.stringify('/api') }, cacheDir: path.join(output, 'vite-cache'), plugins: [vue(), { name: 'tplus-payroll-proof', resolveId: value => value === id ? '\0' + id : undefined, load: value => value === '\0' + id ? harness : undefined, configureServer(instance) { instance.middlewares.use('/__payroll-proof', async (_request, response) => { response.setHeader('Content-Type', 'text/html; charset=utf-8') response.end(await instance.transformIndexHtml('/__payroll-proof', '
')) }) }, }], server: { host: '127.0.0.1', port: 0 }, optimizeDeps: { include: ['vue', 'element-plus', 'pinia'] } }) let browser, snapshot = null, submitted = null, large = false, failLargePage = false const largePeople = Array.from({length: 205}, (_, index) => ({id: index + 1, personnel_name: `分页业务员${index + 1}`})) const largeShipments = Array.from({length: 205}, (_, index) => ({id: index + 101, shipment_no: `SH-${index + 101}`, product_name: `分页产品${index + 1}`, ship_date: '2026-03-10', amount: '100.00'})) const errors = [] try { await server.listen() browser = await chromium.launch({ headless: true, channel: 'msedge' }) const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, reducedMotion: 'reduce' }) page.on('pageerror', error => errors.push(error.message)) await page.route(url => url.pathname.startsWith('/api/'), async route => { const req = route.request(), pathname = new URL(req.url()).pathname let data if (pathname.endsWith('/input') && req.method() === 'POST') { submitted = req.postDataJSON() snapshot = { id: 1, version: 1, source: 'test_source', fingerprint: 'test', data: { group: { opening_balance: submitted.opening_balance }, evidence: submitted.evidence, facts: { handovers: submitted.handovers ?? snapshot?.data?.facts?.handovers ?? [] }, shipments: submitted.shipment_ownerships.map(row => ({ line_id: String(row.shipment_item_id), personnel_id: String(row.personnel_id), manager_id: null, unit_cost: row.unit_cost, freight_amount: row.freight_amount })) } } data = { version: 1 } } else if (pathname.endsWith('/input')) data = snapshot else if (pathname.includes('/meta/payroll-input/') && large) { const pageNumber = Number(new URL(req.url()).searchParams.get('pageNum') || 1) if (pageNumber === 2 && failLargePage) return route.fulfill({status: 500, json: {code: 500, message: '加载失败'}}) const start = (pageNumber - 1) * 200 data = { business_key: 'customer:8', personnel: largePeople.slice(start, start + 200), personnel_total: largePeople.length, shipments: largeShipments.slice(start, start + 200), shipments_total: largeShipments.length } } else if (pathname.includes('/meta/payroll-input/')) data = { business_key: 'customer:8', personnel: [{id: 1, personnel_name: '测试业务员'}, {id: 2, personnel_name: '接手业务员'}], shipments: [ {id: 101, shipment_no: 'SH-001', product_name: '测试管材', ship_date: '2026-03-10', amount: '100.00', source_personnel_name: '来源员工', matched_personnel_id: 1, guidance_status: 'FINALIZED_UNIT', guidance_unit_cost: '25'}, ...(snapshot ? [{id: 102, shipment_no: 'SH-002', product_name: '追加发货', ship_date: '2026-03-11', amount: '50.00', source_personnel_name: '未匹配员工', matched_personnel_id: null}] : []), ] } else if (pathname.includes('/months/')) data = { month: '2026-03', allocation_locked: false, maker_name: null } else throw new Error('未预期接口:' + pathname) await route.fulfill({ json: { code: 200, message: 'success', data } }) }) await page.goto(`http://127.0.0.1:${server.httpServer.address().port}/__payroll-proof`) const dialog = page.getByRole('dialog', { name: '正式绩效核算' }) await dialog.getByText('测试管材', { exact: false }).first().waitFor() await dialog.getByText('来源员工', { exact: true }).waitFor() await dialog.locator('tr').filter({hasText: '测试管材'}).getByText('测试业务员', {exact: true}).waitFor() await dialog.locator('.el-date-editor input').fill('2026-03') await dialog.locator('.el-date-editor input').press('Enter') await page.waitForResponse(response => response.url().includes('/months/2026-03')) await dialog.locator('.el-form-item').filter({hasText: '期初应收'}).locator('input').fill('0') await dialog.locator('.el-form-item').filter({hasText: '核对依据'}).locator('input').fill('已核实来源') await dialog.getByRole('combobox', {name: '发货归属人员'}).first().click() await page.getByRole('option', {name: '测试业务员'}).click() assert.equal(await dialog.getByRole('textbox', {name: '含税单位指导成本'}).first().inputValue(), '25') assert.equal(await dialog.getByRole('textbox', {name: '含税单位指导成本'}).first().isDisabled(), true) await dialog.getByText('使用已封存的月度指导单价', {exact: true}).waitFor() await dialog.getByRole('textbox', {name: '发货明细运费'}).first().fill('0') await dialog.getByRole('button', {name: '添加交接'}).click() await dialog.locator('.el-form-item').filter({hasText: '交接类型'}).locator('.el-select').click() await page.getByRole('option', {name: '在职交接'}).click() await dialog.getByRole('combobox', {name: '移交人员'}).click() await page.getByRole('option', {name: '测试业务员'}).click() await dialog.getByRole('combobox', {name: '接手人员'}).click() await page.getByRole('option', {name: '接手业务员'}).click() await dialog.locator('.el-form-item').filter({hasText: '生效日期'}).locator('input').fill('2026-03-20') await dialog.locator('.el-form-item').filter({hasText: '生效日期'}).locator('input').press('Enter') await dialog.locator('label.el-checkbox').filter({hasText: '已核实期初'}).click() await dialog.getByRole('button', {name: '保存核算资料'}).click() assert.equal(submitted, null) await dialog.locator('label.el-checkbox').filter({hasText: '已逐次核实发货归属'}).click() await dialog.getByRole('button', {name: '保存核算资料'}).click() await dialog.getByText('追加发货', {exact: false}).first().waitFor() assert.equal(submitted.shipment_ownerships[0].freight_amount, '0') assert.equal(submitted.shipment_ownerships[0].personnel_id, 1) assert.deepEqual(submitted.handovers, [{business_key: 'customer:8', mode: 'ACTIVE', from_personnel_id: '1', to_personnel_id: '2', effective_date: '2026-03-20', shipping_ownership_confirmed: true}]) await dialog.locator('.el-form-item').filter({hasText: '移交人员'}).getByText('测试业务员').waitFor() const freight = dialog.getByRole('textbox', {name: '发货明细运费'}) assert.equal(await freight.nth(0).isDisabled(), true) assert.equal(await freight.nth(1).isEnabled(), true) await dialog.getByText('未匹配员工(待匹配)', {exact: true}).waitFor() await dialog.getByRole('combobox', {name: '发货归属人员'}).nth(1).click() await page.getByRole('option', {name: '测试业务员'}).click() await dialog.getByRole('textbox', {name: '含税单位指导成本'}).nth(1).fill('25') await freight.nth(1).fill('0') submitted = null const secondSave = page.waitForResponse(response => response.url().endsWith('/input') && response.request().method() === 'POST') await dialog.getByRole('button', {name: '保存核算资料'}).click() await secondSave assert.equal(Object.hasOwn(submitted, 'handovers'), false) assert.equal(snapshot.data.facts.handovers.length, 1) for (const width of [1440, 768, 390]) { await page.setViewportSize({ width, height: 900 }) await page.screenshot({ path: path.join(output, `payroll-${width}.png`), fullPage: true }) assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false) } snapshot.data.shipments[0].personnel_id = '2' await page.reload() await dialog.locator('tr').filter({hasText: '测试管材'}).getByText('接手业务员', {exact: true}).waitFor() assert.equal(await dialog.getByRole('combobox', {name: '发货归属人员'}).first().isDisabled(), true) large = true failLargePage = true snapshot = { id: 1, version: 2, source: 'test_source', fingerprint: 'test', data: { group: { opening_balance: '0' }, evidence: '分页依据', shipments: largeShipments.map((row, index) => ({line_id: String(row.id), personnel_id: String(index + 1), manager_id: null, unit_cost: '25', freight_amount: '0'})) } } submitted = null await page.reload() await dialog.getByText('资料加载失败,请重试。').waitFor() assert.equal(submitted, null) failLargePage = false await dialog.getByRole('button', {name: '重新加载'}).click() await dialog.getByText('分页产品1', {exact: false}).first().waitFor() await dialog.locator('.el-pagination .number').getByText('5', {exact: true}).click() await dialog.getByText('分页产品205', {exact: false}).waitFor() await dialog.getByText('分页业务员205', {exact: true}).waitFor() await dialog.locator('.el-date-editor input').fill('2026-03') await dialog.locator('.el-date-editor input').press('Enter') await page.waitForResponse(response => response.url().includes('/months/2026-03')) await dialog.locator('label.el-checkbox').filter({hasText: '已核实期初'}).click() await dialog.getByRole('button', {name: '保存核算资料'}).click() await page.waitForFunction(() => document.body.innerText.includes('核算资料已保存为新版本')) assert.equal(submitted.shipment_ownerships.length, 205) assert.equal(submitted.shipment_ownerships[204].personnel_id, 205) assert.deepEqual(errors, []) process.stdout.write('来源核算分页、加载失败、完整提交及响应式检查通过。\n') } finally { await browser?.close() await server.close() }