PayrollManagementSystem/frontend/e2e/correction-pairs.mjs
liuwenxin 1e53792ad6 feat(T+): 完成数据贯通与自动接入闭环
打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。
新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。
补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
2026-11-05 18:47:13 +08:00

91 lines
5.9 KiB
JavaScript

/** 隔离渲染真实更正单组件,合成接口仅用于交互与布局验证。 */
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/correction-pairs-browser')
fs.mkdirSync(output, { recursive: true })
const id = 'virtual:correction-pairs'
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/system/tplus/CorrectionPairDialog.vue'
import {useUserStore} from '/src/store/user.ts'
const pinia=createPinia()
createApp({setup(){const open=ref(false);const user=useUserStore();user.userInfo={permissions:['tplus:facts']};return ()=>h('main',[
h('button',{onClick:()=>open.value=true},'打开更正处理'),
h('button',{onClick:()=>user.userInfo={permissions:[]}},'切换只读'),
h(Dialog,{modelValue:open.value,'onUpdate:modelValue':v=>open.value=v})])}}).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: 'correction-pair-proof', resolveId: value => value === id ? '\0' + id : undefined,
load: value => value === '\0' + id ? harness : undefined,
configureServer(instance) { instance.middlewares.use('/__correction-proof', async (_request, response) => {
response.setHeader('Content-Type', 'text/html; charset=utf-8')
response.end(await instance.transformIndexHtml('/__correction-proof', '<!doctype html><html lang="zh-CN"><meta charset="UTF-8"><div id="app"></div><script type="module" src="/@id/__x00__virtual:correction-pairs"></script></html>'))
}) },
}], server: { host: '127.0.0.1', port: 0 }, optimizeDeps: { include: ['vue', 'element-plus', 'pinia'] } })
let browser
const errors = [], saves = [], checks = []
try {
await server.listen()
browser = await chromium.launch({ headless: true, channel: 'msedge' })
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, reducedMotion: 'reduce' })
page.on('pageerror', error => errors.push(error.message))
await page.route(url => url.pathname.startsWith('/api/'), async route => {
const request = route.request(), url = new URL(request.url())
let data
if (url.pathname.endsWith('/correction-pairs')) { saves.push(request.postDataJSON()); data = { pair_key: 'synthetic' } }
else if (url.pathname.endsWith('/facts')) data = { list: [{ fact_key: 'original', data: { customer_id: '8' } }], total: 1 }
else if (url.pathname.endsWith('/source-records')) data = { list: url.searchParams.get('entity') === 'receipts'
? [{ source_id: '30', source_no: 'B-CASH', date: '2026-03-10' }]
: [{ source_id: '30', source_no: 'A-NEG', date: '2026-03-10' }, { source_id: '40', source_no: 'B-POS', date: '2026-03-10' }], total: 2 }
else throw new Error('未预期接口:' + url.pathname)
return route.fulfill({ json: { code: 200, message: 'success', data } })
})
await page.goto(`http://127.0.0.1:${server.httpServer.address().port}/__correction-proof`)
await page.getByRole('button', { name: '打开更正处理' }).click()
const dialog = page.getByRole('dialog', { name: '转客户更正单处理' })
await dialog.waitFor()
await dialog.getByRole('button', { name: '确认成对处理' }).click()
assert.equal(saves.length, 0)
checks.push('必填资料不完整时阻止提交')
for (const width of [1440, 768, 390]) {
await page.setViewportSize({ width, height: 900 })
await page.screenshot({ path: path.join(output, `pair-${width}.png`), fullPage: true })
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
}
checks.push('1440、768、390 宽度无页面横向溢出')
await page.setViewportSize({ width: 1440, height: 900 })
for (const [label, option] of [['负数更正单', 'A-NEG'], ['正数更正单', 'B-POS'], ['原业务平账组', 'original'], ['实际回款', 'B-CASH']]) {
await dialog.getByRole('combobox', { name: label, exact: true }).locator('xpath=ancestor::div[contains(@class,"el-select__wrapper")][1]').click()
await page.getByRole('option').filter({ hasText: option }).click()
await page.keyboard.press('Escape')
}
await dialog.getByRole('textbox', { name: '更正处理依据' }).fill('合成转客户例单已核对')
await dialog.getByRole('button', { name: '确认成对处理' }).click()
await dialog.waitFor({ state: 'hidden' })
assert.deepEqual(saves[0], { negative_shipment_id: '30', positive_shipment_id: '40', receipt_ids: ['30'], target_group_key: 'original', evidence: '合成转客户例单已核对' })
checks.push('成对选单、同 ID 不同实体、实际回款和原业务完整提交')
await page.getByRole('button', { name: '切换只读' }).click()
await page.getByRole('button', { name: '打开更正处理' }).click()
await dialog.waitFor()
assert.equal(await dialog.getByRole('button', { name: '确认成对处理' }).isDisabled(), true)
checks.push('无事实维护权限时禁用提交')
assert.deepEqual(errors, [])
fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify({ checks, errors }, null, 2))
console.log(JSON.stringify({ checks, errors }))
} finally { await browser?.close(); await server.close() }