打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
91 lines
5.9 KiB
JavaScript
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() }
|