PayrollManagementSystem/frontend/e2e/tplus-payroll-prepare.mjs

154 lines
11 KiB
JavaScript
Raw Normal View History

/** 来源业务核算准备组件回归;所有接口均为隔离模拟。 */
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', '<!doctype html><html lang="zh-CN"><meta charset="UTF-8"><div id="app"></div><script type="module" src="/@id/__x00__virtual:tplus-payroll-proof"></script></html>'))
}) },
}], 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()
}