Files
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:19:15 +08:00

2047 lines
129 KiB
JavaScript

#!/usr/bin/env node
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'node:assert/strict'
import { access, readFile, readdir } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptPath = fileURLToPath(import.meta.url)
const appRoot = path.resolve(path.dirname(scriptPath), '..')
const inventoryPath = path.resolve(appRoot, '../../modern-api/generated/ui-inventory.json')
const forbiddenProductionSourceFiles = [
'src/views/ComponentsView.vue',
'src/views/PatternsView.vue',
'src/views/DeliveryView.vue',
'src/views/ParityView.vue',
'src/views/InventoryView.vue',
'src/views/LegacyRedirectView.vue',
'src/components/erp/CodePreview.vue',
'src/data/elementPlusInventory.js',
'src/data/erpComponentDocs.js',
'src/data/inventorySnapshot.ts',
'src/data/previewVerification.ts',
'src/data/parityVerification.ts',
'src/types/lab-data.d.ts'
]
async function source(file) {
return readFile(path.join(appRoot, file), 'utf8')
}
async function fileExists(file) {
try {
await access(path.join(appRoot, file))
return true
} catch {
return false
}
}
async function sourceFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true })
const files = await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
return sourceFiles(fullPath)
}
if (/\.(ts|vue)$/.test(entry.name)) {
return [fullPath]
}
return []
})
)
return files.flat()
}
const appVue = await source('src/App.vue')
const routerTs = await source('src/router/index.ts')
const businessPageVue = await source('src/views/BusinessPageView.vue')
const shellVue = await source('src/components/erp/ErpAppShell.vue')
const dashboardVue = await source('src/views/DashboardView.vue')
const moduleVue = await source('src/views/ModuleWorkspaceView.vue')
const businessCenterVue = await source('src/views/BusinessCenterView.vue')
const productAdminVue = await source('src/views/ProductAdminView.vue')
const orderAdminVue = await source('src/views/OrderAdminView.vue')
const partyAdminVue = await source('src/views/PartyAdminView.vue')
const accountingAdminVue = await source('src/views/AccountingAdminView.vue')
const inventoryAdminVue = await source('src/views/InventoryAdminView.vue')
const manufacturingAdminVue = await source('src/views/ManufacturingAdminView.vue')
const humanResAdminVue = await source('src/views/HumanResAdminView.vue')
const salesAdminVue = await source('src/views/SalesAdminView.vue')
const procurementAdminVue = await source('src/views/ProcurementAdminView.vue')
const scrumAdminVue = await source('src/views/ScrumAdminView.vue')
const operationsAdminVue = await source('src/views/OperationsAdminView.vue')
const contentAdminVue = await source('src/views/ContentAdminView.vue')
const marketingAdminVue = await source('src/views/MarketingAdminView.vue')
const commerceAdminVue = await source('src/views/CommerceAdminView.vue')
const posAdminVue = await source('src/views/PosAdminView.vue')
const marketplaceAdminVue = await source('src/views/MarketplaceAdminView.vue')
const analyticsAdminVue = await source('src/views/AnalyticsAdminView.vue')
const extensionAdminVue = await source('src/views/ExtensionAdminView.vue')
const systemVue = await source('src/views/SystemToolsView.vue')
const securityAdminVue = await source('src/views/SecurityAdminView.vue')
const systemOperationsVue = await source('src/views/SystemOperationsView.vue')
const loginVue = await source('src/views/LoginView.vue')
const modernCss = await source('src/styles/modern.css')
const rendererVue = await source('src/components/erp/ErpPageRenderer.vue')
const modernNavigationTs = await source('src/utils/modernNavigation.ts')
const displayTs = await source('src/utils/display.ts')
const searchFormVue = await source('src/components/erp/ErpSearchForm.vue')
const entityFormVue = await source('src/components/erp/ErpEntityForm.vue')
const apiTs = await source('src/services/api.ts')
const apiTypes = await source('src/types/api.ts')
const previewScript = await source('scripts/verify-preview.mjs')
const parityScript = await source('scripts/verify-parity.mjs')
const captureScreenshotsScript = await source('scripts/capture-admin-screenshots.mjs')
const partyWorkspaceVue = await source('src/components/erp/ErpPartyWorkspace.vue')
const portalAdminWorkspaceVue = await source('src/components/erp/ErpPortalAdminWorkspace.vue')
const gatewayAdminWorkspaceVue = await source('src/components/erp/ErpGatewayAdminWorkspace.vue')
const extensionOperationsWorkspaceVue = await source('src/components/erp/ErpExtensionOperationsWorkspace.vue')
const ebayOperationsWorkspaceVue = await source('src/components/erp/ErpEbayOperationsWorkspace.vue')
const businessIntelligenceWorkspaceVue = await source('src/components/erp/ErpBusinessIntelligenceWorkspace.vue')
const birtReportingWorkspaceVue = await source('src/components/erp/ErpBirtReportingWorkspace.vue')
const salesAutomationWorkspaceVue = await source('src/components/erp/ErpSalesAutomationWorkspace.vue')
const procurementWorkspaceVue = await source('src/components/erp/ErpProcurementWorkspace.vue')
const scrumWorkspaceVue = await source('src/components/erp/ErpScrumWorkspace.vue')
const humanResWorkspaceVue = await source('src/components/erp/ErpHumanResWorkspace.vue')
const workManagementWorkspaceVue = await source('src/components/erp/ErpWorkManagementWorkspace.vue')
const manufacturingWorkspaceVue = await source('src/components/erp/ErpManufacturingWorkspace.vue')
const marketingWorkspaceVue = await source('src/components/erp/ErpMarketingWorkspace.vue')
const reportWorkspaceVue = await source('src/components/erp/ErpReportWorkspace.vue')
const systemWorkspaceVue = await source('src/components/erp/ErpSystemWorkspace.vue')
const contentWorkspaceVue = await source('src/components/erp/ErpContentWorkspace.vue')
const assetMaintenanceWorkspaceVue = await source('src/components/erp/ErpAssetMaintenanceWorkspace.vue')
const financeOperationsWorkspaceVue = await source('src/components/erp/ErpFinanceOperationsWorkspace.vue')
const financeWorkspaceVue = await source('src/components/erp/ErpFinanceWorkspace.vue')
const mediaWorkspaceVue = await source('src/components/erp/ErpMediaWorkspace.vue')
const inventoryWorkspaceVue = await source('src/components/erp/ErpInventoryWorkspace.vue')
const orderWorkspaceVue = await source('src/components/erp/ErpOrderWorkspace.vue')
const fulfillmentWorkspaceVue = await source('src/components/erp/ErpFulfillmentWorkspace.vue')
const posWorkspaceVue = await source('src/components/erp/ErpPosWorkspace.vue')
const returnWorkspaceVue = await source('src/components/erp/ErpReturnWorkspace.vue')
const catalogWorkspaceVue = await source('src/components/erp/ErpCatalogWorkspace.vue')
const commerceSurfaceVue = await source('src/components/erp/ErpCommerceSurface.vue')
const adapterBlockVue = await source('src/components/erp/ErpAdapterBlock.vue')
const domainAdminVue = await source('src/components/erp/ErpDomainAdminView.vue')
const dataTableVue = await source('src/components/erp/ErpDataTable.vue')
const uploadVue = await source('src/components/erp/ErpUpload.vue')
const drawerVue = await source('src/components/erp/ErpDrawer.vue')
const moduleCatalog = await source('src/data/moduleCatalog.ts')
const packageJson = await source('package.json')
const inventory = JSON.parse(await readFile(inventoryPath, 'utf8'))
const generatedPageIds = new Set(Object.keys(inventory.pageDefinitions || {}))
const lingeringSourceFiles = []
for (const file of forbiddenProductionSourceFiles) {
if (await fileExists(file)) lingeringSourceFiles.push(file)
}
assert.deepEqual(
lingeringSourceFiles,
[],
'production administrator source must not ship component galleries, delivery/parity workbenches, inventory snapshots, or legacy bridge views'
)
assert.doesNotMatch(
modernCss,
/\.delivery-|\.parity-|\.modern-component-|\.modern-code|\.modern-usage-|\.modern-playbook-|\.modern-wrapper-demo/,
'production administrator stylesheet must not carry component gallery, delivery, parity, or code-preview styles'
)
const hardcodedPageIds = new Map()
for (const file of await sourceFiles(path.join(appRoot, 'src'))) {
const relativeFile = path.relative(appRoot, file)
const content = await readFile(file, 'utf8')
for (const match of content.matchAll(/(?:#\/pages\/|pageId:\s*['"`])([A-Za-z0-9_-]+__[A-Za-z0-9_.-]+)/g)) {
const pageId = match[1]
const files = hardcodedPageIds.get(pageId) || new Set()
files.add(relativeFile)
hardcodedPageIds.set(pageId, files)
}
}
function moduleConfigSource(moduleId, nextModuleId) {
const start = moduleCatalog.indexOf(`id: '${moduleId}'`)
assert.notEqual(start, -1, `module catalog should include ${moduleId}`)
const end = nextModuleId ? moduleCatalog.indexOf(`id: '${nextModuleId}'`, start + 1) : -1
return moduleCatalog.slice(start, end === -1 ? undefined : end)
}
function escapeRegExp(text) {
return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
// Hash deep links keep their leading '#', but the vue-router table stores paths
// without it (createWebHashHistory adds the '#'). Normalise a legacy '#/x' anchor
// to the router path '/x' so route->view coverage can be asserted against the
// central route table in src/router/index.ts.
function routerPath(route) {
return String(route).replace(/^#/, '')
}
// Assert the central route table maps a given path to a given view component,
// regardless of import style (lazy `() => import('../views/X.vue')` or a direct
// reference). Preserves the old `activePath === '#/x' ... return 'view'` intent.
function assertRouteMapsToView(route, viewComponent, message) {
const path = routerPath(route)
const pattern = new RegExp(
`path:\\s*'${escapeRegExp(path)}'[\\s\\S]*?${escapeRegExp(viewComponent)}\\.vue`
)
assert.match(routerTs, pattern, message)
}
function assertEntitySource(content, entityName, message) {
const directEntityLoad = new RegExp(`getEntityRows\\('${entityName}'`)
const wrapperEntitySource = new RegExp(`entityName: '${entityName}'`)
assert.ok(directEntityLoad.test(content) || wrapperEntitySource.test(content), message)
}
const visibleUiLeakageTerms = [
['component gallery copy', /组件展厅|组件规范|排版规则|全量清单/],
['preview copy', /技术预览|>\s*预览\s*<|title="预览"|预览模式|页面预览/],
['migration copy', /迁移|自动迁移|旧入口|旧 URL|旧页面|历史地址|旧屏幕来源|旧 widget/i],
['parity copy', /业务等价|功能等价|旧新报表比对|逐页比对|真实输出比对/],
['acceptance copy', /验收|待验收|验收清单|业务回归|待业务回归/],
['inventory checklist copy', /页面清单|覆盖率|覆盖台账|待补齐|待输出比对/],
['engineering metadata copy', /页面定义|页面结构|动作契约|控制规则|模板适配|适配器已覆盖|前端已重写/]
]
function assertNoVisibleUiLeakage(surfaces) {
for (const [fileName, content] of surfaces) {
for (const [label, pattern] of visibleUiLeakageTerms) {
assert.doesNotMatch(
content,
pattern,
`${fileName} must not expose ${label} in visible administrator product UI`
)
}
}
}
const postLoginCoreModuleAnchors = [
{
route: '#/orders',
activeView: 'order-admin',
component: 'OrderAdminView',
sourceName: 'OrderAdminView.vue',
source: orderAdminVue,
labels: ['订单运营台', '订单业务数据'],
entities: ['OrderHeader', 'OrderItem', 'Shipment']
},
{
route: '#/catalog/products',
activeView: 'product-admin',
component: 'ProductAdminView',
sourceName: 'ProductAdminView.vue',
source: productAdminVue,
labels: ['商品管理台', '商品业务数据'],
entities: ['Product', 'ProductCategory', 'ProductPrice']
},
{
route: '#/parties',
activeView: 'party-admin',
component: 'PartyAdminView',
sourceName: 'PartyAdminView.vue',
source: partyAdminVue,
labels: ['客户与组织管理台', '客户业务数据'],
entities: ['Party', 'Person', 'PartyRole']
},
{
route: '#/sales',
activeView: 'sales-admin',
component: 'SalesAdminView',
sourceName: 'SalesAdminView.vue',
source: salesAdminVue,
labels: ['销售管理台', '销售业务数据'],
entities: ['SalesOpportunity', 'SalesForecast', 'CommunicationEvent']
},
{
route: '#/procurement',
activeView: 'procurement-admin',
component: 'ProcurementAdminView',
sourceName: 'ProcurementAdminView.vue',
source: procurementAdminVue,
labels: ['采购管理台', '采购业务数据'],
entities: ['Requirement', 'SupplierProduct', 'Vendor']
},
{
route: '#/accounting',
activeView: 'accounting-admin',
component: 'AccountingAdminView',
sourceName: 'AccountingAdminView.vue',
source: accountingAdminVue,
labels: ['财务管理台', '财务业务数据'],
entities: ['Invoice', 'Payment', 'AcctgTrans']
},
{
route: '#/facility',
activeView: 'inventory-admin',
component: 'InventoryAdminView',
sourceName: 'InventoryAdminView.vue',
source: inventoryAdminVue,
labels: ['库存管理台', '库存业务数据'],
entities: ['InventoryItem', 'Facility', 'Shipment']
},
{
route: '#/manufacturing',
activeView: 'manufacturing-admin',
component: 'ManufacturingAdminView',
sourceName: 'ManufacturingAdminView.vue',
source: manufacturingAdminVue,
labels: ['生产管理台', '生产业务数据'],
entities: ['WorkEffort', 'Requirement', 'CostComponent']
},
{
route: '#/humanres',
activeView: 'humanres-admin',
component: 'HumanResAdminView',
sourceName: 'HumanResAdminView.vue',
source: humanResAdminVue,
labels: ['人事管理台', '人事业务数据'],
entities: ['Person', 'Employment', 'EmplPosition']
}
]
const businessPageAnchors = [
'order__showcart',
'party__NewCustomer',
'accounting__ManualTransaction',
'order__FindRequirements',
'facility__ReceiveInventoryAgainstPurchaseOrder',
'scrum__SprintTask'
]
function assertPostLoginProductAnchors() {
assert.match(appVue, /<ErpAppShell[\s\S]*v-else/, 'authenticated product UI must render inside the ERP application shell after the login gate')
assert.match(appVue, /if \(sessionData\.authenticated\) \{\s*await loadAuthenticatedData\(\)/, 'authenticated session refresh must load administrator navigation and inventory only after login')
assert.match(appVue, /loadAuthenticatedData[\s\S]*getNavigation\(\)[\s\S]*getInventory\(\)/, 'post-login product load must hydrate backend navigation and generated business page definitions')
assertRouteMapsToView('#/business', 'BusinessCenterView', 'post-login product must include a business center route')
assertRouteMapsToView('#/pages/:pageId(.*)', 'BusinessPageView', 'post-login product must render generated OFBiz business pages through BusinessPageView')
// BusinessPageView now reads pageId/query from the route and inventory from inject() instead of :page-id/:inventory props.
assert.match(businessPageVue, /route\.params\.pageId/, 'business pages must derive their page id from the route')
assert.match(businessPageVue, /inject\(InventoryKey/, 'business pages must read inventory from injected app state instead of a :inventory prop')
assert.match(shellVue + dashboardVue + businessCenterVue, /\/business/, 'post-login product chrome must expose the global business center anchor')
for (const anchor of postLoginCoreModuleAnchors) {
assert.match(routerTs, new RegExp(`${anchor.component}`), `${anchor.sourceName} must be mounted in the authenticated administrator app via the route table`)
assertRouteMapsToView(
anchor.route,
anchor.component,
`${anchor.route} must resolve to ${anchor.activeView} after login`
)
assert.match(
shellVue + dashboardVue + moduleCatalog,
new RegExp(escapeRegExp(routerPath(anchor.route))),
`${anchor.route} must be reachable from post-login shell, dashboard, or module catalog anchors`
)
for (const label of anchor.labels) {
assert.match(anchor.source, new RegExp(escapeRegExp(label)), `${anchor.sourceName} must expose product UI anchor: ${label}`)
}
for (const entityName of anchor.entities) {
assertEntitySource(anchor.source, entityName, `${anchor.sourceName} must load real ${entityName} rows for the product anchor`)
}
}
// The sidebar el-menu renders every module landing + quickPage as a `/pages/<pageId>`
// route (moduleItemIndex(module, `/pages/${page.pageId}`)), and the topbar quick
// actions deep-link the same way, so a generated page is reachable when its pageId
// is present in the moduleCatalog-driven nav surfaces.
for (const pageId of businessPageAnchors) {
assert.ok(generatedPageIds.has(pageId), `post-login business page anchor must exist in generated inventory: ${pageId}`)
assert.match(
shellVue + dashboardVue + businessCenterVue + moduleCatalog,
new RegExp(`(?:\\/pages\\/|pageId:\\s*['"\`])${escapeRegExp(pageId)}`),
`post-login product UI must link to generated business page anchor: /pages/${pageId}`
)
}
}
const procurementModuleSource = moduleConfigSource('procurement', 'accounting')
const missingHardcodedPageIds = [...hardcodedPageIds]
.filter(([pageId]) => !generatedPageIds.has(pageId))
.map(([pageId, files]) => ({ pageId, files: [...files].sort() }))
.sort((a, b) => a.pageId.localeCompare(b.pageId))
assert.deepEqual(missingHardcodedPageIds, [], 'hardcoded business page IDs must exist in generated OFBiz inventory')
assertPostLoginProductAnchors()
assertNoVisibleUiLeakage([
['LoginView.vue', loginVue],
['App.vue', appVue],
['ErpAppShell.vue', shellVue],
['DashboardView.vue', dashboardVue],
['ModuleWorkspaceView.vue', moduleVue],
['BusinessCenterView.vue', businessCenterVue],
['BusinessPageView.vue', businessPageVue],
['SystemToolsView.vue', systemVue],
['SecurityAdminView.vue', securityAdminVue],
['SystemOperationsView.vue', systemOperationsVue],
['ProductAdminView.vue', productAdminVue],
['OrderAdminView.vue', orderAdminVue],
['PartyAdminView.vue', partyAdminVue],
['AccountingAdminView.vue', accountingAdminVue],
['InventoryAdminView.vue', inventoryAdminVue],
['ManufacturingAdminView.vue', manufacturingAdminVue],
['HumanResAdminView.vue', humanResAdminVue],
['SalesAdminView.vue', salesAdminVue],
['ProcurementAdminView.vue', procurementAdminVue],
['ScrumAdminView.vue', scrumAdminVue],
['OperationsAdminView.vue', operationsAdminVue],
['ContentAdminView.vue', contentAdminVue],
['MarketingAdminView.vue', marketingAdminVue],
['CommerceAdminView.vue', commerceAdminVue],
['PosAdminView.vue', posAdminVue],
['MarketplaceAdminView.vue', marketplaceAdminVue],
['AnalyticsAdminView.vue', analyticsAdminVue],
['ExtensionAdminView.vue', extensionAdminVue],
['ErpDomainAdminView.vue', domainAdminVue],
['ErpAdapterBlock.vue', adapterBlockVue],
['ErpDataTable.vue', dataTableVue],
['ErpSearchForm.vue', searchFormVue],
['ErpEntityForm.vue', entityFormVue],
['ErpPageRenderer.vue', rendererVue]
])
for (const text of [
'登录后进入完整 ERP 后台',
'apiLogin',
'targetHash',
'订单、商品、客户、财务、库存'
]) {
assert.match(loginVue, new RegExp(text), `login door must present a complete OFBiz admin product: ${text}`)
}
assert.match(apiTs, /\/api\/v1\/login/, 'login service must authenticate inside the modern API webapp session')
// The deep link the user requested before authentication is captured from the
// active route (returnRoute = `#${route.fullPath}`), surfaced to the login gate as
// loginTargetHash, and replayed through the router on successful API session login.
assert.match(appVue, /returnRoute\.value\s*=\s*`#\$\{route\.fullPath\}`/, 'app should capture the requested administrator deep link from the active route before login')
assert.match(appVue, /loginTargetHash\s*=\s*computed\(\(\)\s*=>\s*returnRoute\.value\)/, 'login gate should resolve the captured deep link as its target hash')
assert.match(appVue, /handleLoginSuccess[\s\S]*payload\.targetHash[\s\S]*router\.replace/, 'login should preserve and navigate to the requested administrator deep link after API session login')
assert.match(appVue, /:target-hash="loginTargetHash"/, 'login gate should pass the resolved administrator deep link to the login view')
assert.match(captureScreenshotsScript, /\/modern\/app\//, 'browser verification should target the deployed modern administrator app')
assert.doesNotMatch(
loginVue,
/inventory\.counts|个业务入口|业务化后台界面/,
'login door must not present the administrator product as a page-count or inventory-backed entry catalog'
)
assert.doesNotMatch(
appVue,
/getSession\(\),\s*\n\s*getNavigation\(\),\s*\n\s*getInventory\(\)/,
'unauthenticated login should not fetch the page inventory before OFBiz session authentication'
)
assert.doesNotMatch(
loginVue + modernCss,
/admin-login__legacy|使用 OFBiz 原登录页|\/webtools\/control\/login/,
'login door must not expose the old login page as product chrome'
)
for (const text of [
'ERP 管理员工作台',
'管理员首页',
'经营数据',
'今日运营',
'待办队列',
'最近记录',
'系统健康与权限',
'快速检索',
'录入订单',
'新建客户',
'系统维护',
'快捷动作',
'当班状态',
'可处理范围'
]) {
assert.match(dashboardVue, new RegExp(text), `authenticated landing page must be an ERP admin site: ${text}`)
}
for (const text of [
'priorityWorkRows',
'workbenchStatusRows',
'continueWorkRows',
'recentDocumentRows',
'businessActionGroups',
'businessSearchRows',
'kpiRows',
'pendingWorkRows',
'erp-kpi',
'erp-row',
'erp-quick'
]) {
assert.match(dashboardVue, new RegExp(text), `administrator landing page should include operator work surface: ${text}`)
}
assert.doesNotMatch(
shellVue,
/`\$\{module\.navLabel\}入口`|>\s*入口\s*</,
'administrator shell should expose work queues instead of generic entry/catalog language'
)
assert.doesNotMatch(
shellVue,
/authorizedApplications\s*=\s*computed|后端授权/,
'administrator shell must not render raw OFBiz application lists as the administrator navigation surface'
)
// The professional sidebar derives its business-domain tree from the module catalog
// (primary/secondary modules grouped into 核心业务 / 渠道与扩展) rather than from a
// raw backend-authorization list, and routes each module through its dedicated
// modern administrator landing page via moduleLandingPath().
assert.match(shellVue, /navGroups\s*=\s*computed\([\s\S]*primaryBusinessModules[\s\S]*secondaryBusinessModules/, 'administrator shell should derive authorized business domains from the module catalog')
assert.match(shellVue, /primaryBusinessModules\s*=\s*computed\(\(\)\s*=>\s*primaryModules\.map/, 'administrator shell should build its core business navigation from the primary module catalog')
assert.match(
shellVue,
/moduleItemIndex\(module,\s*toPath\(moduleLandingPath\(module\)\)\)/,
'administrator shell should route catalog business domains through dedicated modern administrator landing pages'
)
assert.doesNotMatch(
loginVue + dashboardVue,
/Element Plus|个应用|界面方案|原应用清单|ORD-\d+|INV-\d+|PAY-\d+|SHP-\d+|DemoCustomer|value:\s*(?:18|7|12|8|6|5),/,
'administrator workbench must not use fixed demo documents or hardcoded operational counts'
)
assert.match(
dashboardVue,
/recentBusinessEntries|todayOperatingRows|operatingLanes|recentDocumentRows|businessActionGroups|systemHealthRows|businessSearchRows|pendingWorkRows|priorityWorkRows|workbenchStatusRows|businessDataStatusRows|continueWorkRows/,
'administrator workbench should derive daily operations from real OFBiz business data, modules, and navigation'
)
assert.doesNotMatch(
dashboardVue,
/props\.inventory|UiInventory|ParityPage|parityManifest|routeManifest|pageSource|pageList|highRiskPages|businessParityStatus|parityStatus|risk ===|page\.risk/,
'administrator workbench must not derive product UI from migration inventory or parity risk fields'
)
assert.doesNotMatch(
dashboardVue,
/常用操作|数据来源|空数据源|OFBiz Entity|实体可读/,
'administrator workbench should use operator language instead of technical source diagnostics'
)
assert.doesNotMatch(
dashboardVue,
/后端授权|后端连接|待登录授权|已授权业务|条处理路径|OFBiz Security|权限来源|会话与权限|entryCount:\s*module\.quickPages\.length|actions:\s*module\.quickPages\.length/,
'administrator workbench must not expose backend-authorization wording or quick-page counts as business operation language'
)
for (const [fileName, content] of [
['ProductAdminView.vue', productAdminVue],
['OrderAdminView.vue', orderAdminVue],
['PartyAdminView.vue', partyAdminVue],
['AccountingAdminView.vue', accountingAdminVue],
['InventoryAdminView.vue', inventoryAdminVue],
['ManufacturingAdminView.vue', manufacturingAdminVue],
['HumanResAdminView.vue', humanResAdminVue],
['SalesAdminView.vue', salesAdminVue],
['ProcurementAdminView.vue', procurementAdminVue],
['ScrumAdminView.vue', scrumAdminVue],
['OperationsAdminView.vue', operationsAdminVue],
['ContentAdminView.vue', contentAdminVue],
['MarketingAdminView.vue', marketingAdminVue],
['CommerceAdminView.vue', commerceAdminVue],
['PosAdminView.vue', posAdminVue],
['MarketplaceAdminView.vue', marketplaceAdminVue],
['AnalyticsAdminView.vue', analyticsAdminVue],
['ExtensionAdminView.vue', extensionAdminVue],
['SecurityAdminView.vue', securityAdminVue],
['SystemOperationsView.vue', systemOperationsVue]
]) {
assert.doesNotMatch(
content,
/数据源|个来源|不可用来源/,
`${fileName} must present business data and recoverable business areas instead of data-source diagnostics`
)
}
assert.match(apiTs, /export async function getModuleSummary/, 'API client should expose a business data summary loader for module workspaces')
assert.match(apiTypes, /export type ModuleSummary/, 'API types should document the module summary contract')
assert.match(moduleVue, /getModuleSummary|moduleSummary|moduleEntityMetrics|pendingQueues|processingLanes|业务处理中枢|处理事项|异常交接/, 'module workspaces should use real OFBiz business-domain signals')
assert.doesNotMatch(
moduleVue,
/props\.inventory|parityManifest|ParityPage|modulePages|filteredPages|workflowRows|scenarioColumns|businessParityStatus|parityStatus|frontendRewriteStatus|uiRewriteStatus|scenarioStatus|pending-business-e2e|ready-for-business-e2e/,
'module workspaces must not expose migration or parity status semantics'
)
assert.doesNotMatch(
moduleVue,
/risk ===|page\.risk|highRiskPages|highRisk|高风险|待生成/,
'module workspaces should not derive operator queues from migration risk fields'
)
const workspaceLabelExpectations = [
['ErpOrderWorkspace.vue', orderWorkspaceVue, ['订单录入', '订单运营']],
['ErpFinanceOperationsWorkspace.vue', financeOperationsWorkspaceVue, ['财务运营']],
['ErpFinanceWorkspace.vue', financeWorkspaceVue, ['财务处理']],
['ErpEbayOperationsWorkspace.vue', ebayOperationsWorkspaceVue, ['店铺运营']],
['ErpBusinessIntelligenceWorkspace.vue', businessIntelligenceWorkspaceVue, ['报表分析']],
['ErpBirtReportingWorkspace.vue', birtReportingWorkspaceVue, ['报表发布']],
['ErpSalesAutomationWorkspace.vue', salesAutomationWorkspaceVue, ['销售自动化']],
['ErpInventoryWorkspace.vue', inventoryWorkspaceVue, ['库存管理']],
['ErpFulfillmentWorkspace.vue', fulfillmentWorkspaceVue, ['履约发运']],
['ErpPosWorkspace.vue', posWorkspaceVue, ['收银工作台']],
['ErpReturnWorkspace.vue', returnWorkspaceVue, ['退货处理']],
['ErpCatalogWorkspace.vue', catalogWorkspaceVue, ['商品管理']],
['ErpCommerceSurface.vue', commerceSurfaceVue, ['电商前台']],
['ErpManufacturingWorkspace.vue', manufacturingWorkspaceVue, ['生产管理']],
['ErpWorkManagementWorkspace.vue', workManagementWorkspaceVue, ['项目任务']],
['ErpHumanResWorkspace.vue', humanResWorkspaceVue, ['人事管理']],
['ErpPartyWorkspace.vue', partyWorkspaceVue, ['客户与组织']],
['ErpMarketingWorkspace.vue', marketingWorkspaceVue, ['营销管理']],
['ErpMediaWorkspace.vue', mediaWorkspaceVue, ['媒体上传']],
['ErpReportWorkspace.vue', reportWorkspaceVue, ['财务报表']],
['ErpSystemWorkspace.vue', systemWorkspaceVue, ['系统管理']],
['ErpContentWorkspace.vue', contentWorkspaceVue, ['内容管理']],
['ErpAssetMaintenanceWorkspace.vue', assetMaintenanceWorkspaceVue, ['资产维护']],
['ErpExtensionOperationsWorkspace.vue', extensionOperationsWorkspaceVue, ['扩展应用']],
['ErpPluginOperationsWorkspace.vue', await source('src/components/erp/ErpPluginOperationsWorkspace.vue'), ['插件应用']],
['ErpProcurementWorkspace.vue', procurementWorkspaceVue, ['采购管理']],
['ErpScrumWorkspace.vue', scrumWorkspaceVue, ['敏捷交付']],
['ErpPortalAdminWorkspace.vue', portalAdminWorkspaceVue, ['门户管理']],
['ErpGatewayAdminWorkspace.vue', gatewayAdminWorkspaceVue, ['网关管理']]
]
const oldWorkspaceLabelPattern = /ORDER ENTRY|ORDER OPERATIONS|FINANCE OPERATIONS|FINANCE WORKSPACE|PARTY WORKSPACE|PORTAL ADMIN|GATEWAY ADMIN|BIRT REPORTING|REPORT WORKSPACE|EBAY OPERATIONS|BUSINESS INTELLIGENCE|PROCUREMENT WORKSPACE|SCRUM WORKSPACE|SALES AUTOMATION|ASSET MAINTENANCE|HUMANRES WORKSPACE|INVENTORY WORKSPACE|CATALOG WORKSPACE|CONTENT WORKSPACE|SYSTEM ADMIN WORKSPACE|EXTENSION OPERATIONS|PLUGIN OPERATIONS|FULFILLMENT WORKSPACE|RETURN WORKSPACE|UPLOAD WORKSPACE|POS WORKSPACE|MFG WORKSPACE|MARKETING WORKSPACE|WORK MANAGEMENT|COMMERCE SURFACE/
for (const [fileName, content, labels] of workspaceLabelExpectations) {
for (const label of labels) {
assert.match(
content,
new RegExp(`erp-workspace-kicker[\\s\\S]{0,180}${escapeRegExp(label)}`),
`${fileName} must use Chinese ERP operator-facing workspace label: ${label}`
)
}
assert.doesNotMatch(
content,
new RegExp(`erp-workspace-kicker[\\s\\S]{0,180}(?:${oldWorkspaceLabelPattern.source})`),
`${fileName} must not expose English engineering workspace labels in the administrator product`
)
}
for (const text of ['订单', '商品', '客户', '销售', '采购', '财务', '库存', '生产', '人事', '运营']) {
assert.match(shellVue + dashboardVue + moduleVue + moduleCatalog, new RegExp(text), `primary shell must expose ERP domains: ${text}`)
}
assert.match(routerTs, /InventoryAdminView/, 'administrator site should include a dedicated inventory administration view')
assertRouteMapsToView('#/facility', 'InventoryAdminView', 'inventory administration should have a dedicated facility route')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/facility/, 'inventory administration should be reachable from primary ERP navigation')
for (const text of ['库存管理台', '库存执行台', '收货上架', '可用量关注', '调拨跟进', '盘点差异', '库存异常', '库位交接', '库存项', '设施库位', '收货发运', '库存明细', '库存调拨', '盘点调整', '库存风险', '库存业务数据']) {
assert.match(inventoryAdminVue, new RegExp(text), `inventory administration page should expose ERP inventory operation: ${text}`)
}
for (const text of ['inventoryExecutionRows', 'receivingWorkRows', 'availabilityRows', 'transferWorkRows', 'countVarianceRows', 'inventoryExceptionRows', 'inventory-admin-execution-grid', 'inventory-admin-work-row', 'inventory-admin-risk-row']) {
assert.match(inventoryAdminVue + modernCss, new RegExp(text), `inventory administration page should expose warehouse operator work surfaces: ${text}`)
}
for (const entityName of ['InventoryItem', 'Facility', 'FacilityLocation', 'Shipment', 'InventoryItemDetail', 'InventoryTransfer', 'PhysicalInventory']) {
assert.match(inventoryAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `inventory administration page should load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
inventoryAdminVue,
/Demo[A-Za-z0-9_]*|INV-\d+|FAC-\d+|SHP-\d+|LOC-\d+|TRF-\d+|SampleWarehouse|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'inventory administration page should not expose fake inventory rows or engineering language'
)
assert.match(routerTs, /ManufacturingAdminView/, 'administrator site should include a dedicated manufacturing administration view')
assertRouteMapsToView('#/manufacturing', 'ManufacturingAdminView', 'manufacturing administration should have a dedicated route')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/manufacturing/, 'manufacturing administration should be reachable from primary ERP navigation')
for (const text of ['生产管理台', '生产调度台', '排产跟进', '物料缺口', '工序准备', '发料领用', '成本关注', '生产异常', '生产运行', '物料需求', 'BOM 工艺', '工序任务', '成本构成', '生产风险', '生产业务数据']) {
assert.match(manufacturingAdminVue, new RegExp(text), `manufacturing administration page should expose ERP manufacturing operation: ${text}`)
}
for (const text of ['manufacturingScheduleRows', 'materialShortageRows', 'routingPreparationRows', 'issuanceWorkRows', 'manufacturingExceptionRows', 'manufacturing-admin-execution-grid', 'manufacturing-admin-work-row', 'manufacturing-admin-risk-row']) {
assert.match(manufacturingAdminVue + modernCss, new RegExp(text), `manufacturing administration page should expose production operator work surfaces: ${text}`)
}
for (const entityName of ['WorkEffort', 'Requirement', 'ProductAssoc', 'WorkEffortGoodStandard', 'CostComponent', 'ItemIssuance']) {
assert.match(manufacturingAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `manufacturing administration page should load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
manufacturingAdminVue,
/Demo[A-Za-z0-9_]*|MFG-\d+|RUN-\d+|BOM-\d+|MAT-\d+|WORK-\d+|SampleFactory|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'manufacturing administration page should not expose fake manufacturing rows or engineering language'
)
assert.match(routerTs, /HumanResAdminView/, 'administrator site should include a dedicated human resources administration view')
assertRouteMapsToView('#/humanres', 'HumanResAdminView', 'human resources administration should have a dedicated route')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/humanres/, 'human resources administration should be reachable from primary ERP navigation')
for (const text of ['人事管理台', '人事执行台', '入转离跟进', '岗位空缺', '招聘处理', '绩效复核', '技能资质', '人事异常', '任职交接', '员工档案', '雇佣关系', '岗位编制', '招聘申请', '人事风险', '人事业务数据']) {
assert.match(humanResAdminVue, new RegExp(text), `human resources administration page should expose ERP human resources operation: ${text}`)
}
for (const text of ['humanResExecutionRows', 'employmentLifecycleRows', 'positionVacancyRows', 'recruitingWorkRows', 'performanceReviewRows', 'skillCredentialRows', 'humanResExceptionRows', 'humanres-admin-execution-grid', 'humanres-admin-work-row', 'humanres-admin-risk-row']) {
assert.match(humanResAdminVue + modernCss, new RegExp(text), `human resources administration page should expose HR operator work surfaces: ${text}`)
}
for (const entityName of ['Person', 'Employment', 'EmplPosition', 'EmplPositionFulfillment', 'EmploymentApp', 'JobRequisition', 'PerfReview', 'PartySkill']) {
assert.match(humanResAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `human resources administration page should load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
humanResAdminVue,
/Demo[A-Za-z0-9_]*|EMP-\d+|HR-\d+|POS-\d+|APP-\d+|SampleEmployee|SamplePosition|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'human resources administration page should not expose fake human resources rows or engineering language'
)
assert.match(moduleCatalog, /primaryModules\s*=\s*\[[\s\S]*'sales'[\s\S]*\]/, 'sales automation should be a first-class primary ERP module in the administrator shell')
assert.match(moduleCatalog, /id: 'sales'[\s\S]*navLabel: '销售'[\s\S]*SalesForceAutomation__FindSalesOpportunity[\s\S]*SalesForceAutomation__ConvertLead/, 'sales module should expose opportunity, lead, forecast, and conversion entry points')
assert.doesNotMatch(moduleCatalog, /id: 'marketing'[\s\S]*?prefixes:\s*\[[^\]]*SalesForceAutomation__/, 'marketing module must not own SalesForceAutomation routes')
assert.match(apiTs, /sales:\s*\[[\s\S]*SalesOpportunity[\s\S]*SalesForecast[\s\S]*PartyRole[\s\S]*CommunicationEvent/, 'sales module summary must load real sales automation entities')
assert.match(moduleCatalog, /primaryModules\s*=\s*\[[\s\S]*'procurement'[\s\S]*\]/, 'procurement should be a first-class primary ERP module in the administrator shell')
assert.match(moduleCatalog, /id: 'procurement'[\s\S]*navLabel: '采购'[\s\S]*order__FindRequirements[\s\S]*order__ApproveRequirements[\s\S]*ap__FindVendors[\s\S]*catalog__EditSupplierProduct[\s\S]*facility__ReceiveInventoryAgainstPurchaseOrder/, 'procurement module should expose requirement, approval, vendor, supplier product, and receiving entry points')
assert.doesNotMatch(procurementModuleSource, /prefixes:\s*\[[^\]]*(?:order__|ap__|catalog__|facility__)/, 'procurement module must not steal broad order, AP, catalog, or facility page ownership')
assert.match(apiTs, /procurement:\s*\[[\s\S]*Requirement[\s\S]*SupplierProduct[\s\S]*Vendor[\s\S]*Quote[\s\S]*CustRequest/, 'procurement module summary must load real procurement and supplier entities')
const operationsModuleSource = moduleConfigSource('operations', 'content')
const scrumModuleSource = moduleConfigSource('scrum', 'operations')
const commerceModuleSource = moduleConfigSource('commerce', 'marketplace')
const marketplaceModuleSource = moduleConfigSource('marketplace', 'pos')
const analyticsModuleSource = moduleConfigSource('analytics', 'system-admin')
const systemAdminModuleSource = moduleConfigSource('system-admin', 'extensions')
assert.match(moduleCatalog, /primaryModules\s*=\s*\[[\s\S]*'scrum'[\s\S]*\]/, 'Scrum should be a first-class primary ERP module in the administrator shell')
assert.match(scrumModuleSource, /id: 'scrum'[\s\S]*navLabel: '敏捷'[\s\S]*scrum__Sprints[\s\S]*scrum__AddProdBacklog[\s\S]*scrum__SprintTask[\s\S]*scrum__FindResource[\s\S]*scrum__FindTimeSheet[\s\S]*scrum__ProductStatistics/, 'Scrum module should expose sprint, backlog, task board, resource, timesheet, and statistics entry points')
assert.doesNotMatch(operationsModuleSource, /scrum__|\/scrum\/control\//, 'generic operations module must not own Scrum route prefixes or controller paths')
assert.match(apiTs, /scrum:\s*\[[\s\S]*ProductBacklog[\s\S]*ProjectSprint[\s\S]*ProjectSprintBacklogAndTask[\s\S]*Timesheet[\s\S]*TimeEntry/, 'Scrum module summary must load real agile delivery entities')
assert.match(routerTs, /ScrumAdminView/, 'administrator site should include a dedicated Scrum administration view')
assertRouteMapsToView('#/scrum', 'ScrumAdminView', 'Scrum administration should have a dedicated top-level route')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/scrum/, 'Scrum administration should be reachable from primary ERP navigation')
for (const text of ['敏捷交付管理台', '敏捷执行台', 'Backlog 承接', 'Sprint 推进', '任务交接', '工时复核', '资源协调', '交付异常', '产品 Backlog', 'Sprint 排程', '任务板', '团队资源', '工时确认', '交付风险', '敏捷业务数据']) {
assert.match(scrumAdminVue, new RegExp(text), `Scrum administration page should expose ERP agile operation: ${text}`)
}
for (const text of ['scrumExecutionRows', 'backlogHandoffRows', 'sprintDeliveryRows', 'sprintTaskHandoffRows', 'timeReviewRows', 'resourceCoordinationRows', 'scrumExceptionRows', 'scrum-admin-execution', 'scrum-admin-work-row', 'scrum-admin-risk-row']) {
assert.match(scrumAdminVue + modernCss, new RegExp(text), `Scrum administration page should expose executable delivery work surfaces: ${text}`)
}
for (const entityName of ['ProductBacklog', 'ProjectSprint', 'ProjectSprintBacklogAndTask', 'Timesheet', 'TimeEntry', 'WorkEffortPartyAssignment', 'CustRequest']) {
assert.match(scrumAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `Scrum administration page should load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
scrumAdminVue,
/Demo[A-Za-z0-9_]*|SCRUM-\d+|SPRINT-\d+|TASK-\d+|BACKLOG-\d+|SampleSprint|SampleBacklog|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'Scrum administration page should not expose fake agile delivery rows or engineering language'
)
assert.match(routerTs, /OperationsAdminView/, 'administrator site should include a dedicated operations administration view')
assertRouteMapsToView('#/operations', 'OperationsAdminView', 'operations administration should have a dedicated top-level route')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/operations/, 'operations administration should be reachable from primary ERP navigation')
for (const text of ['运营任务管理台', '运营执行台', '任务承接', '排程依赖', '人员分配', '工时复核', '请求跟进', '沟通交接', '运营异常', '任务排程', '工作分配', '工时表', '请求协同', '沟通记录', '运营风险', '运营业务数据']) {
assert.match(operationsAdminVue, new RegExp(text), `operations administration page should expose ERP operations work: ${text}`)
}
for (const text of ['operationsExecutionRows', 'taskHandoffRows', 'dependencyRows', 'assignmentDispatchRows', 'operationsTimeReviewRows', 'requestFollowupRows', 'communicationHandoffRows', 'operationsExceptionRows', 'operations-admin-execution', 'operations-admin-work-row', 'operations-admin-risk-row']) {
assert.match(operationsAdminVue + modernCss, new RegExp(text), `operations administration page should expose executable operations work surfaces: ${text}`)
}
for (const entityName of ['WorkEffort', 'WorkEffortAssoc', 'WorkEffortPartyAssignment', 'Timesheet', 'TimeEntry', 'CustRequest', 'CommunicationEvent']) {
assert.match(operationsAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `operations administration page should load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
operationsAdminVue,
/Demo[A-Za-z0-9_]*|OPS-\d+|WORK-\d+|TASK-\d+|SampleTask|SampleOperation|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'operations administration page should not expose fake operations rows or engineering language'
)
const secondaryAdminExpectations = [
{
viewName: 'ContentAdminView',
content: contentAdminVue,
route: '#/content',
activeView: 'content-admin',
labels: ['内容管理台', '内容资源', '站点管理', 'CMS 树', '论坛消息', '博客文章', '发布风险', '内容业务数据'],
entities: ['Content', 'DataResource', 'WebSite', 'WebPage', 'ElectronicText', 'CommunicationEvent']
},
{
viewName: 'MarketingAdminView',
content: marketingAdminVue,
route: '#/marketing',
activeView: 'marketing-admin',
labels: ['营销管理台', '营销活动', '联系名单', '追踪码', '细分群组', '活动统计', '营销风险', '营销业务数据'],
entities: ['MarketingCampaign', 'ContactList', 'TrackingCode', 'SegmentGroup', 'CommunicationEvent', 'PartyRole']
},
{
viewName: 'CommerceAdminView',
content: commerceAdminVue,
route: '#/commerce',
activeView: 'commerce-admin',
labels: ['电商管理台', '购物车', '会员订单', '商品浏览', '退货请求', '客户资料', '电商风险', '电商业务数据'],
entities: ['ShoppingList', 'OrderHeader', 'Product', 'ReturnHeader', 'Party', 'ProductStore']
},
{
viewName: 'PosAdminView',
content: posAdminVue,
route: '#/pos',
activeView: 'pos-admin',
labels: ['POS 管理台', '门店购物车', '收银订单', '支付记录', '经理授权', '门店库存', '收银风险', 'POS 业务数据'],
entities: ['ShoppingList', 'OrderHeader', 'Payment', 'UserLogin', 'Facility', 'InventoryItem']
},
{
viewName: 'MarketplaceAdminView',
content: marketplaceAdminVue,
route: '#/marketplace',
activeView: 'marketplace-admin',
labels: ['店铺运营管理台', '店铺配置', '物流方式', '库存同步', '活动刊登', '店铺商品', '店铺风险', '店铺业务数据'],
entities: ['EbayConfig', 'EbayShippingMethod', 'EbayProductStoreInventory', 'EbayProductListing', 'ProductStore', 'Product']
},
{
viewName: 'AnalyticsAdminView',
content: analyticsAdminVue,
route: '#/analytics',
activeView: 'analytics-admin',
labels: ['报表分析管理台', '数据维度', '事实数据', '报表资源', '报表发布', '输出队列', '报表风险', '报表业务数据'],
entities: ['DateDimension', 'ProductDimension', 'SalesOrderItemFact', 'SalesInvoiceItemFact', 'DataResource', 'Enumeration']
},
{
viewName: 'ExtensionAdminView',
content: extensionAdminVue,
route: '#/extensions',
activeView: 'extension-admin',
labels: ['扩展应用管理台', '扩展记录', '特征配置', '价目表导入', '报表资源', '支付网关', '短信通道', '扩展业务数据'],
entities: ['Example', 'ExampleFeature', 'ExampleFeatureAppl', 'ExcelImportHistory', 'DataResource', 'PaymentGatewayConfig', 'PaymentGatewayFirstData', 'Msg91GatewayConfig']
}
]
for (const adminPage of secondaryAdminExpectations) {
assert.match(routerTs, new RegExp(`${adminPage.viewName}`), `${adminPage.viewName} should be mounted as a dedicated administrator page via the route table`)
assertRouteMapsToView(adminPage.route, adminPage.viewName, `${adminPage.route} should resolve to ${adminPage.activeView}`)
assert.match(shellVue + dashboardVue + moduleCatalog, new RegExp(escapeRegExp(adminPage.route)), `${adminPage.viewName} should be reachable from administrator navigation`)
assert.match(adminPage.content, /ErpDomainAdminView/, `${adminPage.viewName} should use the shared domain admin renderer that loads entity sources`)
for (const text of adminPage.labels) {
assert.match(adminPage.content, new RegExp(text), `${adminPage.viewName} should expose ERP operation label: ${text}`)
}
for (const entityName of adminPage.entities) {
assert.match(adminPage.content, new RegExp(`entityName: '${entityName}'`), `${adminPage.viewName} should declare real ${entityName} rows for the entity API renderer`)
}
assert.doesNotMatch(
adminPage.content,
/Demo[A-Za-z0-9_]*|Sample[A-Za-z0-9_]*|MOCK-\d+|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
`${adminPage.viewName} should not expose fake rows or engineering language`
)
}
assert.match(moduleCatalog, /secondaryModules\s*=\s*\[[\s\S]*'marketplace'[\s\S]*\]/, 'marketplace should be a first-class administrator application module')
assert.match(marketplaceModuleSource, /id: 'marketplace'[\s\S]*navLabel: '店铺'[\s\S]*ebay__FindEbayConfigurations[\s\S]*ebay__EbayShippingMethods[\s\S]*ebaystore__ebayStoreInventory[\s\S]*ebaystore__ActiveListing/, 'marketplace module should expose eBay configuration, shipping, inventory, and active listing entry points')
assert.doesNotMatch(commerceModuleSource, /ebay__|ebaystore__|\/ebay\/control\/|\/ebaystore\/control\//, 'commerce module must not own eBay marketplace route prefixes or controller paths')
assert.match(apiTs, /marketplace:\s*\[[\s\S]*EbayConfig[\s\S]*ProductStore[\s\S]*EbayProductListing[\s\S]*EbayProductStoreInventory[\s\S]*EbayShippingMethod/, 'marketplace module summary must load real marketplace entities')
assert.match(moduleCatalog, /secondaryModules\s*=\s*\[[\s\S]*'analytics'[\s\S]*\]/, 'analytics should be a first-class administrator module instead of living under system tools')
assert.match(analyticsModuleSource, /id: 'analytics'[\s\S]*navLabel: '报表'[\s\S]*bi__main[\s\S]*bi__ReportBuilderSelectStarSchema[\s\S]*bi__ReportBuilderSelectStarSchemaFields[\s\S]*birt__ListFlexibleReport[\s\S]*birt__CreateFlexibleReport[\s\S]*birt__Mail/, 'analytics module should expose BI warehouse and BIRT report publishing entry points')
assert.match(analyticsModuleSource, /prefixes:\s*\[[\s\S]*'bi__'[\s\S]*'birt__'[\s\S]*\]/, 'analytics module should own BI and BIRT route prefixes')
assert.match(analyticsModuleSource, /legacyIncludes:\s*\[[\s\S]*'\/bi\/control\/'[\s\S]*'\/birt\/control\/'[\s\S]*\]/, 'analytics module should own BI and BIRT controller paths')
assert.doesNotMatch(systemAdminModuleSource, /bi__|birt__|\/bi\/control\/|\/birt\/control\/|报表中心|birt__main/, 'system admin module must not own BI/BIRT routes or report center quick links')
assert.match(apiTs, /analytics:\s*\[[\s\S]*DateDimension[\s\S]*ProductDimension[\s\S]*SalesOrderItemFact[\s\S]*SalesInvoiceItemFact[\s\S]*DataResource[\s\S]*Enumeration/, 'analytics module summary must load real BI warehouse and BIRT report entities')
assert.match(rendererVue, /ErpProcurementWorkspace/, 'procurement pages should use a first-class procurement workspace')
assert.match(rendererVue, /isProcurementPage/, 'renderer should detect procurement routes before order, AP, catalog, and inventory fallbacks')
assert.match(rendererVue, /procurementWorkspaceBlock/, 'renderer should collapse procurement blocks into one procurement workspace per page')
assert.match(rendererVue, /data-modern-procurement-page/, 'renderer should mark page-level procurement workspaces')
for (const pageId of [
'order__FindRequirements',
'order__ApproveRequirements',
'ap__FindVendors',
'catalog__EditSupplierProduct',
'facility__ReceiveInventoryAgainstPurchaseOrder'
]) {
assert.match(captureScreenshotsScript, new RegExp(`#\\/pages\\/${pageId}`), `screenshots must cover procurement page ${pageId}`)
}
for (const text of [
'采购管理',
'采购需求',
'需求审批',
'供应商',
'供应商品',
'供应商报价',
'采购请求',
'到货接收',
'Requirement',
'SupplierProduct',
'Vendor',
'Quote',
'CustRequest'
]) {
assert.match(procurementWorkspaceVue, new RegExp(text), `procurement workspace must expose real procurement operations: ${text}`)
}
for (const entityName of ['Requirement', 'SupplierProduct', 'Vendor', 'Quote', 'CustRequest', 'Shipment', 'InventoryItem']) {
assert.match(procurementWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `procurement workspace must load real ${entityName} rows from the entity API`)
}
assert.match(procurementWorkspaceVue, /采购业务数据/, 'procurement workspace must describe entity-backed procurement data')
assert.doesNotMatch(
procurementWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|REQ-\d+|PO-\d+|VEN-\d+|BigSupplier|Wholesale replenishment/,
'procurement workspace must not synthesize requirements, vendors, supplier products, quotes, requests, receiving, or inventory rows from generated metadata'
)
// System governance tools live in their own secondary sidebar group (系统治理) built from systemLinks.
assert.match(shellVue, /systemLinks\s*=\s*\[[\s\S]*'\/system'[\s\S]*'\/system\/security'[\s\S]*'\/system\/operations'/, 'shell should keep support/governance tools in a secondary support menu')
assert.match(shellVue, /系统治理/, 'shell should group system governance tools under a dedicated secondary sidebar section')
assert.match(shellVue, /quickActions[\s\S]*path: '\/pages\/order__showcart'[\s\S]*path: '\/pages\/party__NewCustomer'[\s\S]*path: '\/pages\/catalog__EditProduct'[\s\S]*path: '\/pages\/accounting__ManualTransaction'/, 'topbar quick actions should deep-link to high-frequency administrator work pages')
assert.doesNotMatch(shellVue, /order__orderentry/, 'topbar quick actions must not point at missing generated pages')
for (const [moduleId, landingPath] of [
['order', '#/orders'],
['catalog', '#/catalog/products'],
['party', '#/parties'],
['sales', '#/sales'],
['procurement', '#/procurement'],
['accounting', '#/accounting'],
['facility', '#/facility'],
['manufacturing', '#/manufacturing'],
['humanres', '#/humanres'],
['scrum', '#/scrum'],
['operations', '#/operations'],
['content', '#/content'],
['marketing', '#/marketing'],
['commerce', '#/commerce'],
['marketplace', '#/marketplace'],
['pos', '#/pos'],
['analytics', '#/analytics'],
['extensions', '#/extensions']
]) {
assert.match(moduleCatalog, new RegExp(`id: '${moduleId}'[\\s\\S]*landingPath: '${landingPath.replace('/', '\\/')}'`), `${moduleId} module should declare a dedicated administrator landing path`)
}
for (const [fileName, content, moduleId, landingPath] of [
['OrderAdminView.vue', orderAdminVue, 'order', '#/orders'],
['ProductAdminView.vue', productAdminVue, 'catalog', '#/catalog/products'],
['PartyAdminView.vue', partyAdminVue, 'party', '#/parties'],
['SalesAdminView.vue', salesAdminVue, 'sales', '#/sales'],
['ProcurementAdminView.vue', procurementAdminVue, 'procurement', '#/procurement'],
['AccountingAdminView.vue', accountingAdminVue, 'accounting', '#/accounting'],
['InventoryAdminView.vue', inventoryAdminVue, 'facility', '#/facility'],
['ManufacturingAdminView.vue', manufacturingAdminVue, 'manufacturing', '#/manufacturing'],
['HumanResAdminView.vue', humanResAdminVue, 'humanres', '#/humanres'],
['ScrumAdminView.vue', scrumAdminVue, 'scrum', '#/scrum'],
['OperationsAdminView.vue', operationsAdminVue, 'operations', '#/operations'],
['ContentAdminView.vue', contentAdminVue, 'content', '#/content'],
['MarketingAdminView.vue', marketingAdminVue, 'marketing', '#/marketing'],
['CommerceAdminView.vue', commerceAdminVue, 'commerce', '#/commerce'],
['MarketplaceAdminView.vue', marketplaceAdminVue, 'marketplace', '#/marketplace'],
['PosAdminView.vue', posAdminVue, 'pos', '#/pos'],
['AnalyticsAdminView.vue', analyticsAdminVue, 'analytics', '#/analytics'],
['ExtensionAdminView.vue', extensionAdminVue, 'extensions', '#/extensions']
]) {
assert.doesNotMatch(
content,
new RegExp(`#\\/module\\/${moduleId}`),
`${fileName} must not send administrator actions back to the generic module shell for ${moduleId}`
)
assert.match(
content,
new RegExp(escapeRegExp(landingPath)),
`${fileName} should keep administrator actions on the dedicated ${landingPath} landing page`
)
}
assert.match(shellVue, /moduleLandingPath/, 'shell should resolve module navigation through dedicated administrator landing paths')
assert.match(shellVue, /quickQueues[\s\S]*primaryBusinessModules[\s\S]*path: toPath\(moduleLandingPath\(module\)\)/, 'topbar work entries should route to dedicated ERP module landing pages')
assert.doesNotMatch(shellVue, /href="`#\/module\/\$\{item\.id\}`"|href="`#\/module\/\$\{module\.id\}`"/, 'primary navigation must not default every module to the generic module shell')
assert.doesNotMatch(shellVue + systemVue + moduleCatalog, /#\/module\/extensions/, 'extension application entry points should use the dedicated extension administrator page instead of the generic module shell')
assert.doesNotMatch(
shellVue,
/href="#\/orders"[\s\S]*v-for="item in primaryBusinessModules"/,
'sidebar core business navigation should be generated from the module catalog, not duplicated by hardcoded links and module links'
)
assert.match(dashboardVue, /moduleLandingPath\(module\)/, 'dashboard module shortcuts should open dedicated ERP administrator pages')
assert.match(shellVue, /@click="navigate\(item\.path\)"/, 'topbar work alerts should be actionable ERP entries')
assert.doesNotMatch(shellVue, /count:\s*(?:18|7|12)|待审核订单|待过账财务|待发运包裹/, 'topbar must not use fixed demo work-alert counts')
assert.doesNotMatch(
shellVue,
/count:\s*module\.quickPages\.length|后端授权|authorizedApplications/,
'administrator shell must not present quick-page counts or backend app lists as business queues'
)
for (const text of ['统一运营管理平台', '运营工作台', '核心业务', '渠道与扩展', 'quickQueues', 'navGroups']) {
assert.match(shellVue, new RegExp(text), `administrator shell should behave like an ERP operator chrome: ${text}`)
}
for (const text of ['业务处理中枢', '处理流', '待处理队列', '异常交接', '交接记录', '执行动作', '处理事项']) {
assert.match(moduleVue, new RegExp(text), `generic module workspace should present an ERP operations cockpit: ${text}`)
}
assert.doesNotMatch(
moduleVue,
/打开常用入口|常用操作|最近记录|真实 OFBiz 模块数据|模块数据源|模块数据|数据源|没有匹配的常用操作/,
'generic module workspace must not read like a route directory or engineering data-source shell'
)
assert.doesNotMatch(appVue + shellVue, /adminPreview|preserveAdminPreview|isAdminPreviewMode/, 'product code must not bypass the OFBiz login gate with preview parameters')
assert.doesNotMatch(
shellVue,
/const operationsTools = \[[\s\S]*?交付状态[\s\S]*?\]/,
'delivery and parity tools must not live in the high-frequency operations navigation'
)
assert.doesNotMatch(
shellVue,
/运维入口|旧入口跳转|历史地址|组件展厅/,
'sidebar should not foreground migration or component tooling as a main business entry'
)
assert.doesNotMatch(
shellVue + dashboardVue + systemVue,
/OFBiz 应用|<h2>业务应用<\/h2>|businessApps|navigationRows|admin-app-list|admin-app-row|modern-aside-section--apps|应用权限|system-app-list|navigation\.slice/,
'administrator shell and landing page must present ERP business domains instead of an OFBiz application catalog'
)
assert.doesNotMatch(shellVue, /\/webtools\/control\/main/, 'modern administrator chrome must not expose a primary button back to the old WebTools main page')
// The business-domain module directory is now the grouped sidebar navigation tree
// (each module is an el-sub-menu listing its quick pages), not a duplicate grid on
// the dashboard. Assert it on the shell where it actually lives.
assert.match(
shellVue,
/el-sub-menu[\s\S]*module\.navLabel[\s\S]*module\.quickPages/,
'administrator chrome should expose a business-domain module directory (grouped sidebar tree)'
)
assert.match(
dashboardVue,
/quickAdminActionRows[\s\S]*快捷动作[\s\S]*erp-quick/,
'administrator landing page should expose business-domain operation rows'
)
assert.doesNotMatch(
dashboardVue,
/组件展厅|旧入口名称|按组件或清单组织|迁移/,
'authenticated landing page must not read like a preview, component gallery, or migration dashboard'
)
assert.match(systemVue, /系统维护|安全与会话|运行任务|运行日志|缓存维护|导入导出|用户与权限|权限与业务域/, 'system page should own administrator operations')
assert.match(businessCenterVue, /处理动作|当前班次|业务受理台|统一业务中心/, 'business center should use business-facing product language')
assert.match(businessCenterVue, /currentHashQuery|syncQueryFromRoute|当前办理口径/, 'business center should consume global business search query')
assert.match(businessCenterVue, /getGlobalBusinessSearch|businessSearch|businessRecords|当前班次/, 'business center should use real OFBiz entity search results')
assert.match(apiTs, /export async function getGlobalBusinessSearch/, 'API client should expose global business search')
assert.match(apiTypes, /export type GlobalBusinessSearch/, 'API types should document global business search')
assert.match(shellVue, /\/business\?query=\$\{encodeURIComponent/, 'global command search should deep-link into the business center')
assert.doesNotMatch(businessCenterVue, />预览<\/el-button>|title="页面路由"|eyebrow="页面清单"/, 'business center must not read as a preview catalog')
assert.doesNotMatch(
businessCenterVue,
/业务入口|交接入口|高频入口|常用处理入口/,
'business entry center should present processing paths, not an entry directory'
)
for (const text of ['统一业务中心', '业务受理台', '待办事项', '当前班次', '当前办理口径', '今日承接', '优先处理', '异常队列', '续办记录', '待办队列', '快捷处理', '处理建议', '进入处理']) {
assert.match(businessCenterVue, new RegExp(text), `business entry center should expose ERP-facing field: ${text}`)
}
assert.doesNotMatch(
businessCenterVue + moduleVue,
/待登录授权|待授权|需授权|实体可读|可读实体|数据连接|数据状态|业务深链|处理路径/,
'business center and module workspaces must not use catalog, authorization-diagnostic, or route-path wording as primary product language'
)
for (const text of ['businessWorkLanes', 'businessShiftRows', 'priorityDispatchRows', 'exceptionQueueRows', 'continueRecordRows', 'queueCards', 'handoffRows', 'business-command-grid', 'business-shift-board', 'business-priority-board', 'business-exception-board', 'business-continue-board', '业务受理台', '办理口径', '待办总量']) {
assert.match(businessCenterVue, new RegExp(text), `business center should behave like an ERP operations console instead of a page directory: ${text}`)
}
assert.doesNotMatch(
businessCenterVue,
/props\.inventory\.routeManifest|routeManifest|label="页面标识"|label="适配"|label="验收清单"|label="业务等价"|label="控制规则"|row\.adapterCovered|row\.businessParityStatus|row\.controller|缺失入口|已接入工作区|需专属处理台|历史地址|待补齐事项|missingRoutes|missingActions|generated-renderable|generated-adapter-renderable|generated-needs-custom-vue|risk ===|row\.risk/,
'business entry center must not use route inventory or migration columns in the primary operator table'
)
assert.doesNotMatch(
appVue + businessCenterVue + systemVue,
/LegacyRedirectView|#\/legacy|\/legacy\?path=|goSourcePath|sourcePath|来源入口解析|历史地址映射/,
'administrator product must not expose a visible legacy/source-address bridge; old URLs should be redirected by the OFBiz filter into business pages'
)
assert.doesNotMatch(rendererVue, /不是页面预览/, 'business pages must not describe themselves as previews')
assert.doesNotMatch(
businessPageVue,
/page\.acceptance|checklist|pending-business-e2e|rewritten-preview-passed|frontend-passed|ready-for-business-e2e|验收|等价/,
'business page chrome must not expose migration acceptance state to ERP operators'
)
for (const text of ['处理台', '资料明细', '处理动作', '操作记录', '当前单据', '处理进度']) {
assert.match(
businessPageVue,
new RegExp(text),
`generated business pages should read as an ERP operator workspace: ${text}`
)
}
assert.doesNotMatch(
businessPageVue,
/eyebrow="业务页面"|label="业务数据"|label="流转记录"|>\s*业务能力\s*<|>\s*业务区块\s*</,
'generated business pages must use operator-facing workbench language instead of metadata, capability, or flow-ledger chrome'
)
assert.doesNotMatch(
businessPageVue,
/权限映射|NONE|可用工具|查询 \/ 列表 \/ 查找 \/ 上传/,
'generated business pages must not expose permission-map fallbacks or tool-catalog wording in the operator chrome'
)
assert.doesNotMatch(
rendererVue,
/page\.acceptance|业务状态[\s\S]*待处理配置|需专属业务流程|需复核/,
'business page renderer must derive visible status from business structure, not migration acceptance state'
)
assert.doesNotMatch(
rendererVue,
/ElMessage|条件已准备/,
'page-level business action feedback must be a persistent operation receipt instead of transient toast feedback'
)
for (const text of ['处理回执', '处理编号', '提交内容', '下一步处理', 'recentActionReceipts']) {
assert.match(rendererVue, new RegExp(text), `page-level action feedback must read as an ERP operation receipt: ${text}`)
}
assert.doesNotMatch(
searchFormVue,
/ElMessage|条件已就绪/,
'generated ERP forms must let the page-level Action API result own submit feedback'
)
assert.doesNotMatch(
entityFormVue,
/ElMessage|表单数据已收集/,
'generated ERP entity forms must let the page-level Action API result own submit feedback'
)
assert.match(
rendererVue,
/const actionPayload = actionPayloadFor\(payload\)[\s\S]*runAction\(actionId, actionPayload\)/,
'generated ERP forms with service or event targets must submit through the Action API bridge'
)
assert.doesNotMatch(dataTableVue, /previewRows|sampleValue|等待后端数据|待加载/, 'ERP data tables must not synthesize placeholder business rows')
assert.match(dataTableVue, /emptyDescription[\s\S]*业务数据/, 'ERP data tables must render unavailable or empty states instead of fake rows')
assert.match(dataTableVue, /visibleColumns\.value\.slice\(0,\s*12\)/, 'ERP data tables must cap visible columns at 12 before rendering')
assert.match(dataTableVue, /isUnavailableData[\s\S]*remoteState\.value === 'unavailable'[\s\S]*remoteState\.value === 'error'/, 'ERP data tables must only show unavailable alerts for unavailable or error states')
assert.match(adapterBlockVue, /dataSourceForBlock|业务数据源/, 'generic adapter blocks should bridge to real data sources or honest unavailable states')
assert.doesNotMatch(
adapterBlockVue,
/页面条件|可执行入口|业务数据源需要页面定义|待配置|流程入口/,
'generic adapter blocks must translate generated metadata into ERP workbench language before rendering it'
)
assert.doesNotMatch(
domainAdminVue,
/个来源|不可用来源/,
'domain administrator pages must present loaded and unavailable business areas instead of source-count diagnostics'
)
for (const text of ['domainWorkQueueRows', 'recentBusinessRows', 'handoffRows', 'businessActionRows', '业务队列', '业务记录', '快捷处理', '异常交接', '处理流', '进入处理']) {
assert.match(domainAdminVue, new RegExp(text), `shared domain administrator renderer must behave like an ERP operation console: ${text}`)
}
assert.doesNotMatch(
domainAdminVue,
/组件展厅|页面清单|业务等价|待验收|迁移|技术预览|预览/,
'shared domain administrator renderer must not expose component, preview, migration, or parity language'
)
assert.doesNotMatch(
domainAdminVue,
/接口|后端|对象|实体对象|业务对象|业务实体|待处理对象|可处理对象/,
'shared domain administrator renderer must describe visible records and tables instead of technical object/entity wording'
)
assert.doesNotMatch(
adapterBlockVue,
/fieldRows\.value\.map|fieldRows\.value\.slice|actionRows\.value\.map|capabilityRows\.value|capabilityRowsFor\(|processRowsFor\(|pageContextRows\(|\bindex \+ 1\b|阶段 \$\{/,
'generic adapter blocks must not synthesize business rows from generated metadata'
)
assert.doesNotMatch(apiTs, /API_TIMEOUT_MS\s*=\s*900/, 'production ERP API client must not use preview-grade 900ms timeouts')
assert.match(apiTs, /READ_API_TIMEOUT_MS\s*=\s*(?:[89]\d{3}|[1-9]\d{4,})/, 'read API calls must allow enough time for real OFBiz entity, page, and lookup responses')
assert.match(apiTs, /ACTION_API_TIMEOUT_MS\s*=\s*(?:3\d{4}|[4-9]\d{4,})/, 'Action API calls must allow enough time for real OFBiz service and event execution')
assert.match(apiTs, /LOOKUP_API_TIMEOUT_MS\s*=\s*(?:1[2-9]\d{3}|[2-9]\d{4,})/, 'lookup and dynamic option calls must allow enough time for real OFBiz searches')
assert.match(apiTs, /runAction[\s\S]*ACTION_API_TIMEOUT_MS/, 'service and event actions must use the action timeout instead of read timeout behavior')
assert.match(parityScript, /openModernUiRuntime\(chrome\)/, 'parity smoke must open one reusable browser render runtime')
assert.match(parityScript, /verifyModernUiRuntime\(page, modernRuntime\)/, 'parity smoke must verify modern business pages through a browser render pass')
assert.match(parityScript, /renderMode: 'chrome-cdp'/, 'parity smoke must record Chrome/CDP rendering evidence for modern UI pages')
assert.doesNotMatch(
parityScript,
/response\.ok && text\.includes\('<div id="app">'\)/,
'parity smoke must not treat the SPA index shell as proof that a business page rendered'
)
assert.doesNotMatch(
previewScript + parityScript,
/src\/data\/(?:previewVerification|parityVerification)\.ts|outTs|writeFile\(outTs/,
'verification reports must stay under plugins/modern-ui/verification and must not be written into production src/data'
)
assert.doesNotMatch(
previewScript,
/#\/legacy\?path=|legacy-redirect|来源入口解析|旧入口桥接/,
'preview verification must exercise the administrator product and server-side URL cutover, not a visible legacy bridge page'
)
assert.match(rendererVue, /ErpPartyWorkspace/, 'party pages should use a first-class customer and organization workspace')
assert.match(rendererVue, /isPartyOperationsBlock/, 'renderer should route party adapters into the Party workspace')
assert.match(rendererVue, /isPartyPage/, 'renderer should detect Party routes as page-level customer and organization workspaces')
assert.match(rendererVue, /partyWorkspaceBlock/, 'renderer should collapse Party blocks into one customer and organization workspace per page')
assert.match(rendererVue, /data-modern-party-page/, 'renderer should mark page-level Party administrator workspaces')
for (const text of ['客户沟通', '关系/联系人', '请求转化', '发送前检查']) {
assert.match(partyWorkspaceVue, new RegExp(text), `party workspace must expose real customer operations: ${text}`)
}
assert.match(rendererVue, /ErpPortalAdminWorkspace/, 'portal pages should use a first-class portal administrator workspace')
assert.match(rendererVue, /isPortalAdminPage/, 'renderer should detect myportal and PortalPage routes before generic fallbacks')
assert.match(rendererVue, /portalAdminWorkspaceBlock/, 'renderer should collapse portal blocks into one administrator workspace per page')
assert.match(rendererVue, /data-modern-portal-admin-page/, 'renderer should mark page-level portal administrator workspaces')
for (const text of ['门户管理', '门户页面', '栏目布局', 'Portlet 编排', '权限分配', 'PortalPage', 'PortalPageColumn', 'PortalPagePortlet', 'PortalPortlet', 'SecurityGroup', 'createPortalPage', 'updatePortalPage']) {
assert.match(portalAdminWorkspaceVue, new RegExp(text), `portal admin workspace must expose real portal operations: ${text}`)
}
for (const entityName of ['PortalPage', 'PortalPageColumn', 'PortalPagePortlet', 'PortalPortlet', 'SecurityGroup']) {
assert.match(portalAdminWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `portal admin workspace must load real ${entityName} rows from the entity API`)
}
assert.match(rendererVue, /ErpGatewayAdminWorkspace/, 'gateway pages should use a first-class payment and message gateway administrator workspace')
assert.match(rendererVue, /isGatewayAdminPage/, 'renderer should detect FirstData and message gateway routes before generic fallbacks')
assert.match(rendererVue, /gatewayAdminWorkspaceBlock/, 'renderer should collapse gateway blocks into one administrator workspace per page')
assert.match(rendererVue, /data-modern-gateway-admin-page/, 'renderer should mark page-level gateway administrator workspaces')
for (const text of ['网关管理', '支付网关', '短信通道', '连接检查', 'PaymentGatewayConfig', 'PaymentGatewayFirstData', 'TelecomGatewayConfig', 'Msg91GatewayConfig', 'updatePaymentGatewayFirstData']) {
assert.match(gatewayAdminWorkspaceVue, new RegExp(text), `gateway admin workspace must expose real gateway operations: ${text}`)
}
for (const entityName of ['PaymentGatewayConfig', 'PaymentGatewayFirstData', 'TelecomGatewayConfig', 'Msg91GatewayConfig']) {
assert.match(gatewayAdminWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `gateway admin workspace must load real ${entityName} rows from the entity API`)
}
assert.match(rendererVue, /ErpExtensionOperationsWorkspace/, 'extension and sample plugin pages should use a first-class extension operations workspace')
assert.match(rendererVue, /isExtensionOperationsPage/, 'renderer should detect example, exampleext, pricat, and pricatdemo routes')
assert.match(rendererVue, /extensionOperationsWorkspaceBlock/, 'renderer should collapse extension blocks into one operator workspace per page')
assert.match(rendererVue, /data-modern-extension-operations-page/, 'renderer should mark page-level extension operations workspaces')
for (const text of ['扩展应用', '扩展记录', '表单动作', '报表输出', '价目表导入', '导入日志', 'Example', 'ExampleFeature', 'ExcelImportHistory', 'DataResource']) {
assert.match(extensionOperationsWorkspaceVue, new RegExp(text), `extension operations workspace must expose real extension operations: ${text}`)
}
for (const entityName of ['Example', 'ExampleFeature', 'ExampleFeatureAppl', 'ExcelImportHistory', 'DataResource']) {
assert.match(extensionOperationsWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `extension operations workspace must load real ${entityName} rows from the entity API`)
}
assert.match(rendererVue, /ErpEbayOperationsWorkspace/, 'eBay pages should use a first-class marketplace operations workspace')
assert.match(rendererVue, /isEbayOperationsPage/, 'renderer should detect ebay and ebaystore routes')
assert.match(rendererVue, /ebayOperationsWorkspaceBlock/, 'renderer should collapse eBay blocks into one marketplace workspace per page')
assert.match(rendererVue, /data-modern-ebay-operations-page/, 'renderer should mark page-level eBay operations workspaces')
for (const text of ['店铺运营', '店铺配置', '刊登管理', '库存同步', '订单导入', '反馈/售后', '自动规则', 'EbayConfig', 'EbayProductStoreInventory', 'EbayProductListing', 'EbayProductStorePref', 'EbayShippingMethod', 'ProductStore']) {
assert.match(ebayOperationsWorkspaceVue, new RegExp(text), `eBay operations workspace must expose real marketplace operations: ${text}`)
}
assert.match(ebayOperationsWorkspaceVue, /function ebayRowsRequest/, 'eBay operations workspace must keep marketplace row loading behind a dedicated request helper')
assert.match(ebayOperationsWorkspaceVue, /getEntityRows\(entityName,\s*params\)/, 'eBay operations workspace request helper must still call the real entity row API')
assert.match(ebayOperationsWorkspaceVue, /normalizeFallbackEbayEntityRows\(result,\s*entityName,\s*params\)/, 'eBay operations workspace must normalize optional eBay empty data without hiding real requests')
for (const entityName of ['EbayConfig', 'EbayProductStoreInventory', 'EbayProductListing', 'EbayProductStorePref', 'EbayShippingMethod']) {
assert.match(ebayOperationsWorkspaceVue, new RegExp(`ebayRowsRequest\\('${entityName}'`), `eBay operations workspace must load real ${entityName} rows through the marketplace request helper`)
}
for (const entityName of ['ProductStore', 'OrderHeader']) {
assert.match(ebayOperationsWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `eBay operations workspace must load shared ${entityName} rows from the entity API`)
}
assert.match(rendererVue, /ErpBusinessIntelligenceWorkspace/, 'BI pages should use a first-class analytics and report builder workspace')
assert.match(rendererVue, /isBusinessIntelligencePage/, 'renderer should detect Business Intelligence routes before catalog/report fallbacks')
assert.match(rendererVue, /businessIntelligenceWorkspaceBlock/, 'renderer should collapse BI blocks into one analytics workspace per page')
assert.match(rendererVue, /data-modern-business-intelligence-page/, 'renderer should mark page-level BI operations workspaces')
for (const text of ['报表分析', '数据仓库', '星型模型', '维度管理', '事实表', '报表构建', '字段选择', '结果输出', 'DateDimension', 'ProductDimension', 'SalesOrderItemFact', 'SalesInvoiceItemFact', 'CurrencyDimension']) {
assert.match(businessIntelligenceWorkspaceVue, new RegExp(text), `BI workspace must expose real analytics operations: ${text}`)
}
for (const entityName of ['DateDimension', 'CurrencyDimension', 'ProductDimension', 'SalesOrderItemFact', 'SalesInvoiceItemFact']) {
assert.match(businessIntelligenceWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `BI workspace must load real ${entityName} rows from the entity API`)
}
assert.match(rendererVue, /ErpBirtReportingWorkspace/, 'BIRT pages should use a first-class report publishing workspace')
assert.match(rendererVue, /isBirtReportingPage/, 'renderer should detect BIRT routes before plugin/report fallbacks')
assert.match(rendererVue, /birtReportingWorkspaceBlock/, 'renderer should collapse BIRT blocks into one report publishing workspace per page')
assert.match(rendererVue, /data-modern-birt-reporting-page/, 'renderer should mark page-level BIRT reporting workspaces')
for (const text of ['报表发布', '报表库', '主模板', '设计资源', '参数表单', '输出队列', '邮件发送', 'FLEXIBLE_REPORT', 'REPORT_MASTER', 'rptDesign', 'sendBirtMail', 'DataResource', 'Content']) {
assert.match(birtReportingWorkspaceVue, new RegExp(text), `BIRT workspace must expose real reporting operations: ${text}`)
}
for (const entityName of ['Content', 'DataResource', 'ContentAssoc', 'ContentAttribute', 'CustomMethod', 'Enumeration']) {
assert.match(birtReportingWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `BIRT workspace must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
birtReportingWorkspaceVue,
/便携文档输出|页面输出|电子表格输出|application\/pdf|application\/vnd\.ms-excel/,
'BIRT workspace must not invent report output rows when OFBiz Enumeration data is empty'
)
assert.doesNotMatch(
birtReportingWorkspaceVue,
/预览参数/,
'BIRT reporting workspace should use production report operation language, not preview wording'
)
for (const [componentName, workspaceBlock, marker] of [
['ErpCatalogWorkspace', 'catalogWorkspaceBlock', 'data-modern-catalog-page'],
['ErpCommerceSurface', 'commerceWorkspaceBlock', 'data-modern-commerce-page'],
['ErpPosWorkspace', 'posWorkspaceBlock', 'data-modern-pos-page'],
['ErpManufacturingWorkspace', 'manufacturingWorkspaceBlock', 'data-modern-manufacturing-page'],
['ErpMarketingWorkspace', 'marketingWorkspaceBlock', 'data-modern-marketing-page'],
['ErpReportWorkspace', 'reportWorkspaceBlock', 'data-modern-report-page'],
['ErpPartyWorkspace', 'partyWorkspaceBlock', 'data-modern-party-page'],
['ErpSystemWorkspace', 'systemWorkspaceBlock', 'data-modern-system-admin-page'],
['ErpContentWorkspace', 'contentWorkspaceBlock', 'data-modern-content-page'],
['ErpAssetMaintenanceWorkspace', 'assetMaintenanceWorkspaceBlock', 'data-modern-asset-maintenance-page'],
['ErpFinanceWorkspace', 'financeWorkspaceBlock', 'data-modern-finance-page'],
['ErpFinanceOperationsWorkspace', 'financeOperationsWorkspaceBlock', 'data-modern-finance-operations-page'],
['ErpPortalAdminWorkspace', 'portalAdminWorkspaceBlock', 'data-modern-portal-admin-page'],
['ErpGatewayAdminWorkspace', 'gatewayAdminWorkspaceBlock', 'data-modern-gateway-admin-page'],
['ErpExtensionOperationsWorkspace', 'extensionOperationsWorkspaceBlock', 'data-modern-extension-operations-page'],
['ErpEbayOperationsWorkspace', 'ebayOperationsWorkspaceBlock', 'data-modern-ebay-operations-page'],
['ErpBusinessIntelligenceWorkspace', 'businessIntelligenceWorkspaceBlock', 'data-modern-business-intelligence-page'],
['ErpBirtReportingWorkspace', 'birtReportingWorkspaceBlock', 'data-modern-birt-reporting-page'],
['ErpSalesAutomationWorkspace', 'salesAutomationWorkspaceBlock', 'data-modern-sales-automation-page'],
['ErpProcurementWorkspace', 'procurementWorkspaceBlock', 'data-modern-procurement-page'],
['ErpScrumWorkspace', 'scrumWorkspaceBlock', 'data-modern-scrum-page']
]) {
assert.match(rendererVue, new RegExp(componentName), `renderer should import and mount ${componentName}`)
assert.match(rendererVue, new RegExp(workspaceBlock), `renderer should compute a page-level ${workspaceBlock}`)
assert.match(rendererVue, new RegExp(marker), `renderer should expose ${marker} for browser verification`)
}
for (const detectorName of [
'isCatalogPage',
'isCommercePage',
'isPosPage',
'isManufacturingPage',
'isMarketingPage',
'isReportPage',
'isSystemAdminPage',
'isContentPage',
'isAssetMaintenancePage',
'isSalesAutomationPage',
'isProcurementPage',
'isPortalAdminPage',
'isGatewayAdminPage',
'isExtensionOperationsPage',
'isEbayOperationsPage',
'isBusinessIntelligencePage',
'isPayableReceivablePage',
'isFinancePage',
'isScrumPage'
]) {
assert.match(rendererVue, new RegExp(detectorName), `renderer should detect ${detectorName} routes and generated adapters`)
}
assert.match(rendererVue, /pageId\.startsWith\('ap__'\)/, 'accounts payable pages must be detected as finance operations pages')
assert.match(rendererVue, /pageId\.startsWith\('ar__'\)/, 'accounts receivable pages must be detected as finance operations pages')
assert.match(rendererVue, /haystack\.includes\('\/ap\/control\/'\)/, 'AP controller routes must route to the finance operations workspace')
assert.match(rendererVue, /haystack\.includes\('\/ar\/control\/'\)/, 'AR controller routes must route to the finance operations workspace')
assert.match(rendererVue, /isPayableReceivablePage\(\)[\s\S]*block\.type === 'finance-workspace'/, 'AP/AR generated finance blocks must open the finance operations workspace')
for (const pageId of [
'ap__FindApInvoices',
'ap__FindApPayments',
'ar__FindArInvoices',
'ar__FindPayments'
]) {
assert.match(captureScreenshotsScript, new RegExp(`#\\/pages\\/${pageId}`), `screenshots must cover ${pageId}`)
}
assert.match(
rendererVue,
/function firstWorkspaceBlock\(types: string\[\], preferredNeedles: string\[\] = \[\]\)/,
'renderer should select the page-specific workspace block instead of blindly using the first generated adapter'
)
assert.match(
rendererVue,
/showcart[\s\S]*show cart[\s\S]*\/cart\//,
'commerce cart pages should select the cart surface even when layout sidebars generate catalog surfaces first'
)
assert.match(
rendererVue,
/isInternalLegacyControlTarget\(value\)[\s\S]*#\/pages\/\$\{pageIdFromTarget\(value\)\}/,
'generated page links must convert internal OFBiz /control/ targets back into modern #/pages routes before allowing absolute paths'
)
for (const [legacyContext, pagePrefix] of [
['ordermgr', 'order'],
['partymgr', 'party'],
['sfa', 'SalesForceAutomation']
]) {
assert.match(
modernNavigationTs,
new RegExp(`${legacyContext}: '${pagePrefix}'`),
`generated page links must map /${legacyContext}/control routes to the ${pagePrefix} PageDefinition prefix`
)
}
assert.match(
rendererVue,
/keyword[\s\S]*search[\s\S]*category[\s\S]*categories[\s\S]*layered/,
'commerce pages should select search and category surfaces by page semantics'
)
for (const [fileName, content] of [
['ErpPartyWorkspace.vue', partyWorkspaceVue],
['ErpManufacturingWorkspace.vue', manufacturingWorkspaceVue],
['ErpCatalogWorkspace.vue', catalogWorkspaceVue],
['ErpCommerceSurface.vue', commerceSurfaceVue],
['ErpReturnWorkspace.vue', returnWorkspaceVue],
['ErpAdapterBlock.vue', adapterBlockVue],
['ErpDataTable.vue', dataTableVue],
['ErpMarketingWorkspace.vue', marketingWorkspaceVue],
['ErpReportWorkspace.vue', reportWorkspaceVue],
['ErpSystemWorkspace.vue', systemWorkspaceVue],
['ErpContentWorkspace.vue', contentWorkspaceVue],
['ErpAssetMaintenanceWorkspace.vue', assetMaintenanceWorkspaceVue],
['ErpFinanceOperationsWorkspace.vue', financeOperationsWorkspaceVue],
['ErpFinanceWorkspace.vue', financeWorkspaceVue],
['ErpExtensionOperationsWorkspace.vue', extensionOperationsWorkspaceVue],
['ErpEbayOperationsWorkspace.vue', ebayOperationsWorkspaceVue],
['ErpBusinessIntelligenceWorkspace.vue', businessIntelligenceWorkspaceVue],
['ErpBirtReportingWorkspace.vue', birtReportingWorkspaceVue],
['ErpSalesAutomationWorkspace.vue', salesAutomationWorkspaceVue],
['ErpProcurementWorkspace.vue', procurementWorkspaceVue],
['ErpScrumWorkspace.vue', scrumWorkspaceVue],
['ErpMediaWorkspace.vue', mediaWorkspaceVue]
]) {
assert.doesNotMatch(
content,
/旧 widget|PageDefinition|业务 E2E|逐页比对|旧新报表比对|真实输出比对|仍需业务 E2E|按旧 widget|动作契约|不是组件预览|旧 OFBiz/,
`${fileName} must read as an ERP operator workspace, not migration or component-preview material`
)
}
for (const [fileName, content] of [
['ErpOrderWorkspace.vue', orderWorkspaceVue],
['ErpFinanceOperationsWorkspace.vue', financeOperationsWorkspaceVue],
['ErpFinanceWorkspace.vue', financeWorkspaceVue],
['ErpEbayOperationsWorkspace.vue', ebayOperationsWorkspaceVue],
['ErpBusinessIntelligenceWorkspace.vue', businessIntelligenceWorkspaceVue],
['ErpBirtReportingWorkspace.vue', birtReportingWorkspaceVue],
['ErpSalesAutomationWorkspace.vue', salesAutomationWorkspaceVue],
['ErpInventoryWorkspace.vue', inventoryWorkspaceVue],
['ErpFulfillmentWorkspace.vue', fulfillmentWorkspaceVue],
['ErpPosWorkspace.vue', posWorkspaceVue],
['ErpReturnWorkspace.vue', returnWorkspaceVue],
['ErpCatalogWorkspace.vue', catalogWorkspaceVue],
['ErpCommerceSurface.vue', commerceSurfaceVue],
['ErpManufacturingWorkspace.vue', manufacturingWorkspaceVue],
['ErpWorkManagementWorkspace.vue', workManagementWorkspaceVue],
['ErpHumanResWorkspace.vue', humanResWorkspaceVue],
['ErpPartyWorkspace.vue', partyWorkspaceVue],
['ErpMarketingWorkspace.vue', marketingWorkspaceVue],
['ErpMediaWorkspace.vue', mediaWorkspaceVue],
['ErpReportWorkspace.vue', reportWorkspaceVue],
['ErpSystemWorkspace.vue', systemWorkspaceVue],
['ErpContentWorkspace.vue', contentWorkspaceVue],
['ErpAssetMaintenanceWorkspace.vue', assetMaintenanceWorkspaceVue],
['ErpExtensionOperationsWorkspace.vue', extensionOperationsWorkspaceVue],
['ErpEbayOperationsWorkspace.vue', ebayOperationsWorkspaceVue],
['ErpBusinessIntelligenceWorkspace.vue', businessIntelligenceWorkspaceVue],
['ErpBirtReportingWorkspace.vue', birtReportingWorkspaceVue],
['ErpSalesAutomationWorkspace.vue', salesAutomationWorkspaceVue],
['ErpProcurementWorkspace.vue', procurementWorkspaceVue],
['ErpScrumWorkspace.vue', scrumWorkspaceVue],
['OrderAdminView.vue', orderAdminVue],
['PartyAdminView.vue', partyAdminVue],
['ProductAdminView.vue', productAdminVue],
['AccountingAdminView.vue', accountingAdminVue],
['InventoryAdminView.vue', inventoryAdminVue],
['ManufacturingAdminView.vue', manufacturingAdminVue],
['ErpUpload.vue', uploadVue],
['ErpAdapterBlock.vue', adapterBlockVue],
['ErpDataTable.vue', dataTableVue],
['ErpDrawer.vue', drawerVue]
]) {
assert.doesNotMatch(
content,
/Demo[A-Za-z0-9_]*|\/demo\b|ORD-\d+|INV-\d+|PAY-\d+|SHP-\d+|SO-\d+|TX-\d+|FAC-\d+|GZ-\d+|WG-\d+|KIT-\d+|SRV-\d+|DISC-\d+|SUB-\d+|MAT-\d+|BOM-\d+|PRUN-\d+|PROMO-|EMP-\d+|EMPL-\d+|POS-\d+|COM-\d+|PARTY-\d+|REQ-\d+|CMP-|TRK-|LIST-|CNT-(?:IMG|DOC)-\d+|RetailStore|BigSupplier|Wholesale replenishment|Return authorization|Retail replacement|SalesLead|OpsLead|FinanceLead|example\.com|product-main\.jpg|agreement\.pdf|应收账款|主营收入|应交税费|8850|8230|620/,
`${fileName} must not render fixed demo business records in the administrator product`
)
}
for (const [fileName, content] of [
['DashboardView.vue', dashboardVue],
['OrderAdminView.vue', orderAdminVue],
['PartyAdminView.vue', partyAdminVue],
['ProductAdminView.vue', productAdminVue],
['AccountingAdminView.vue', accountingAdminVue],
['InventoryAdminView.vue', inventoryAdminVue],
['ManufacturingAdminView.vue', manufacturingAdminVue],
['SystemToolsView.vue', systemVue],
['SystemOperationsView.vue', systemOperationsVue],
['ErpFulfillmentWorkspace.vue', fulfillmentWorkspaceVue],
['ErpWorkManagementWorkspace.vue', workManagementWorkspaceVue],
['ErpAdapterBlock.vue', adapterBlockVue]
]) {
assert.doesNotMatch(
content,
/count:\s*(?:18|7|12|8|6|5)|<strong>(?:18|7|12|8|6|5|38|16|186|158\.00|8,850|17\.5)<\/strong>|value:\s*(?:18|7|12|8|6|5),/,
`${fileName} must derive operator counts from inventory, navigation, page fields, or actions`
)
}
for (const [fileName, content] of [
['ErpReturnWorkspace.vue', returnWorkspaceVue],
['ErpCatalogWorkspace.vue', catalogWorkspaceVue],
['ErpCommerceSurface.vue', commerceSurfaceVue],
['ErpManufacturingWorkspace.vue', manufacturingWorkspaceVue],
['ErpAdapterBlock.vue', adapterBlockVue],
['ErpDataTable.vue', dataTableVue],
['ErpWorkManagementWorkspace.vue', workManagementWorkspaceVue],
['ErpHumanResWorkspace.vue', humanResWorkspaceVue],
['ErpPartyWorkspace.vue', partyWorkspaceVue],
['ErpMarketingWorkspace.vue', marketingWorkspaceVue],
['ErpMediaWorkspace.vue', mediaWorkspaceVue],
['ErpReportWorkspace.vue', reportWorkspaceVue],
['ErpSystemWorkspace.vue', systemWorkspaceVue],
['ErpContentWorkspace.vue', contentWorkspaceVue],
['ErpAssetMaintenanceWorkspace.vue', assetMaintenanceWorkspaceVue],
['ErpExtensionOperationsWorkspace.vue', extensionOperationsWorkspaceVue],
['ErpEbayOperationsWorkspace.vue', ebayOperationsWorkspaceVue],
['ErpBusinessIntelligenceWorkspace.vue', businessIntelligenceWorkspaceVue],
['ErpBirtReportingWorkspace.vue', birtReportingWorkspaceVue],
['ErpSalesAutomationWorkspace.vue', salesAutomationWorkspaceVue],
['ErpProcurementWorkspace.vue', procurementWorkspaceVue],
['ErpScrumWorkspace.vue', scrumWorkspaceVue]
]) {
assert.match(
content,
/fieldRowsFor|actionRowsFor|capabilityRowsFor|pageContextRows|getEntityRows/,
`${fileName} should derive rendered rows from OFBiz metadata or real entity APIs`
)
}
assert.doesNotMatch(
businessPageVue,
/旧 URL 参数|旧 GET|旧入口兼容|业务回归|功能等价|页面规则|控制规则|待业务回归/,
'generated business pages must keep compatibility and parity wording out of the operator-facing chrome'
)
assert.doesNotMatch(
displayTs,
/旧页面入口|旧屏幕来源|模板适配|模板规则|报表输出'[\s\S]{0,40}'report-preview'|路由工作台|领域工作台|页面定义|屏幕定义|表单定义|菜单定义|控制器 XML|页面规则文件/,
'shared display labels must translate generated OFBiz metadata into administrator-facing ERP language'
)
for (const text of [
'应付发票查询',
'应付付款查询',
'应收发票查询',
'应收付款查询',
'客户查询',
'购物车',
'门户页面查询',
'报表查询',
'销售机会查询',
'销售线索查询',
'销售预测查询',
'采购需求查询',
'供应商查询',
'Sprint 排程',
'任务板'
]) {
assert.match(displayTs, new RegExp(text), `generated OFBiz page titles should be localized for ERP operators: ${text}`)
}
assert.doesNotMatch(
displayTs,
/return labels\[lower\] \|\| normalized \|\| raw/,
'page titles should pass through business-language fallback before exposing raw OFBiz names'
)
assert.doesNotMatch(
rendererVue + adapterBlockVue,
/Element Plus rendering|legacy source traceability|media review|approval workflow|规则推断|现代控件渲染|来源可追踪/,
'business page renderer must not expose implementation, migration, or rendering-source labels in operator chrome'
)
assert.match(rendererVue, /ErpHumanResWorkspace/, 'human resource pages should use a first-class HR operations workspace')
assert.match(rendererVue, /humanResWorkspaceBlock/, 'renderer should collapse HumanRes blocks into one HR workspace per page')
assert.match(rendererVue, /renderedBlocks/, 'renderer should avoid duplicate HumanRes workspace rendering through the normal block loop')
for (const text of ['人事管理', '员工查询', '员工档案', '雇佣关系', '岗位编制', '人事队列']) {
assert.match(humanResWorkspaceVue, new RegExp(text), `HumanRes workspace must expose HR operations: ${text}`)
}
assert.match(rendererVue, /ErpWorkManagementWorkspace/, 'work effort, project, and scrum pages should use a first-class work management workspace')
assert.match(rendererVue, /isWorkManagementPage/, 'renderer should detect WorkEffort and ProjectMgr routes')
assert.match(rendererVue, /workManagementWorkspaceBlock/, 'renderer should collapse WorkEffort and ProjectMgr blocks into one work management workspace per page')
for (const text of ['项目任务', '任务队列', '项目', '工时', 'Backlog', '排程流转', '执行链路']) {
assert.match(workManagementWorkspaceVue, new RegExp(text), `Work management workspace must expose project/task operations: ${text}`)
}
assert.match(rendererVue, /ErpScrumWorkspace/, 'Scrum pages should use a first-class scrum operations workspace')
assert.match(rendererVue, /isScrumPage/, 'renderer should detect Scrum routes before generic work management fallbacks')
assert.match(rendererVue, /scrumWorkspaceBlock/, 'renderer should collapse Scrum blocks into one scrum workspace per page')
assert.match(rendererVue, /data-modern-scrum-page/, 'renderer should mark page-level Scrum workspaces')
assert.match(rendererVue, /if \(isScrumPage\(\)\) return false/, 'generic work management routing must not absorb Scrum pages')
for (const pageId of [
'scrum__Sprints',
'scrum__AddProdBacklog',
'scrum__SprintTask',
'scrum__FindResource',
'scrum__FindTimeSheet',
'scrum__ProductStatistics'
]) {
assert.match(captureScreenshotsScript, new RegExp(`#\\/pages\\/${pageId}`), `screenshots must cover ${pageId}`)
}
for (const text of [
'敏捷交付',
'产品 Backlog',
'Sprint',
'任务板',
'团队资源',
'工时/计费',
'会议与内容',
'ProductBacklog',
'ProjectSprint',
'ProjectSprintBacklogAndTask',
'ProjectSprintBacklogTaskAndTimeEntryTimeSheet'
]) {
assert.match(scrumWorkspaceVue, new RegExp(text), `Scrum workspace must expose real agile delivery operations: ${text}`)
}
for (const entityName of [
'Product',
'CustRequest',
'CustRequestItem',
'CustRequestWorkEffort',
'WorkEffort',
'WorkEffortAssoc',
'WorkEffortPartyAssignment',
'Timesheet',
'TimeEntry',
'Party',
'PartyRole',
'CommunicationEvent',
'Content',
'ProductBacklog',
'ProjectSprint',
'ProjectSprintBacklogAndTask',
'ProjectSprintBacklogTaskAndTimeEntryTimeSheet'
]) {
assert.match(scrumWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `Scrum workspace must load real ${entityName} rows from the entity API`)
}
assert.match(scrumWorkspaceVue, /Scrum 业务数据/, 'Scrum workspace must describe entity-backed Scrum data')
assert.doesNotMatch(
scrumWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|SPRINT-\d+|TASK-\d+|BACKLOG-\d+|USER-\d+|example\.com/,
'Scrum workspace must not synthesize backlog, sprint, task, resource, timesheet, meeting, or billing rows from generated metadata'
)
assert.match(rendererVue, /ErpInventoryWorkspace/, 'facility inventory pages should use a first-class inventory operations workspace')
assert.match(rendererVue, /isInventoryOperationsPage/, 'renderer should distinguish inventory facility routes from fulfillment routes')
assert.match(rendererVue, /inventoryWorkspaceBlock/, 'renderer should collapse inventory facility blocks into one inventory workspace per page')
assert.match(rendererVue, /if \(pageId\.startsWith\('facility__'\)\) return false/, 'facility pages must not be routed through the return workspace')
assert.match(rendererVue, /packorder[\s\S]*shipment[\s\S]*ship/, 'only pack, shipment, and ship facility routes should use the fulfillment workspace')
for (const text of ['库存管理', '库存项', '设施库位', '收货上架', '盘点调整', '库存流转', '库存控制', '履约联动']) {
assert.match(inventoryWorkspaceVue, new RegExp(text), `Inventory workspace must expose inventory operations: ${text}`)
}
assert.match(inventoryWorkspaceVue, /getEntityRows\('InventoryItem'/, 'inventory workspace must load real InventoryItem rows from the entity API')
assert.match(inventoryWorkspaceVue, /getEntityRows\('Facility'/, 'inventory workspace must load real Facility rows from the entity API')
assert.match(inventoryWorkspaceVue, /getEntityRows\('Shipment'/, 'inventory workspace must load real Shipment rows from the entity API')
assert.match(inventoryWorkspaceVue, /库存业务数据/, 'inventory workspace must describe entity-backed inventory data')
assert.doesNotMatch(
inventoryWorkspaceVue,
/<strong>42<\/strong>|fieldRows\.value\.map|fieldRows\.value\.slice|pageContextRows\(props\.page, props\.block\)|capabilityRowsFor\(props\.block|页面设施|业务设施|index \+ 1/,
'inventory workspace must not synthesize inventory, facility, receiving, or adjustment rows from generated metadata'
)
assert.match(catalogWorkspaceVue, /getEntityRows\('Product'/, 'catalog workspace must load real Product rows from the entity API')
assert.match(catalogWorkspaceVue, /getEntityRows\('ProductPrice'/, 'catalog workspace must load real ProductPrice rows from the entity API')
assert.match(catalogWorkspaceVue, /getEntityRows\('ProductPromo'/, 'catalog workspace must load real ProductPromo rows from the entity API')
assert.match(catalogWorkspaceVue, /getEntityRows\('ProductCategory'/, 'catalog workspace must load real ProductCategory rows from the entity API')
assert.match(catalogWorkspaceVue, /商品业务数据/, 'catalog workspace must describe entity-backed product data')
assert.doesNotMatch(
catalogWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value\.map|字段分组|按业务生效日期|\(index \+ 1\)/,
'catalog workspace must not synthesize products, prices, promos, or category trees from generated metadata'
)
assert.match(partyWorkspaceVue, /getEntityRows\('Party'/, 'party workspace must load real Party rows from the entity API')
assert.match(partyWorkspaceVue, /getEntityRows\('CommunicationEvent'/, 'party workspace must load real CommunicationEvent rows from the entity API')
assert.match(partyWorkspaceVue, /getEntityRows\('CustRequest'/, 'party workspace must load real CustRequest rows from the entity API')
assert.match(partyWorkspaceVue, /客户业务数据/, 'party workspace must describe entity-backed party data')
assert.doesNotMatch(
partyWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|High|Medium|Low/,
'party workspace must not synthesize communications, relationships, or requests from generated metadata'
)
assert.match(fulfillmentWorkspaceVue, /getEntityRows\('Shipment'/, 'fulfillment workspace must load real Shipment rows from the entity API')
assert.match(fulfillmentWorkspaceVue, /getEntityRows\('InventoryItem'/, 'fulfillment workspace must load real InventoryItem rows from the entity API')
assert.match(fulfillmentWorkspaceVue, /履约业务数据/, 'fulfillment workspace must describe entity-backed fulfillment data')
assert.doesNotMatch(
fulfillmentWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|包裹字段|\bindex \+ 1\b/,
'fulfillment workspace must not synthesize shipments or inventory movements from generated metadata'
)
assert.match(returnWorkspaceVue, /getEntityRows\('ReturnHeader'/, 'return workspace must load real ReturnHeader rows from the entity API')
assert.match(returnWorkspaceVue, /getEntityRows\('ReturnItem'/, 'return workspace must load real ReturnItem rows from the entity API')
assert.match(returnWorkspaceVue, /退货业务数据/, 'return workspace must describe entity-backed return data')
assert.doesNotMatch(
returnWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|processRowsFor\(|\bindex \+ 1\b/,
'return workspace must not synthesize returns, refunds, or quality checks from generated metadata'
)
assert.match(posWorkspaceVue, /getEntityRows\('Product'/, 'POS workspace must load real Product rows from the entity API')
assert.match(posWorkspaceVue, /getEntityRows\('Payment'/, 'POS workspace must load real Payment rows from the entity API')
assert.match(posWorkspaceVue, /收银业务数据/, 'POS workspace must describe entity-backed POS data')
assert.doesNotMatch(
posWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|\bindex \+ 1\b|\* 10|tenderedAmount = ref\(200\)/,
'POS workspace must not synthesize cart lines, payments, or tender totals from generated metadata'
)
assert.match(manufacturingWorkspaceVue, /getEntityRows\('Requirement'/, 'manufacturing workspace must load real Requirement rows from the entity API')
assert.match(manufacturingWorkspaceVue, /getEntityRows\('ProductAssoc'/, 'manufacturing workspace must load real ProductAssoc rows from the entity API')
assert.match(manufacturingWorkspaceVue, /getEntityRows\('WorkEffort'/, 'manufacturing workspace must load real WorkEffort rows from the entity API')
assert.match(manufacturingWorkspaceVue, /生产业务数据/, 'manufacturing workspace must describe entity-backed production data')
assert.doesNotMatch(
manufacturingWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|processRowsFor\(|\bindex \+ 1\b/,
'manufacturing workspace must not synthesize MRP, BOM, or production run rows from generated metadata'
)
assert.match(marketingWorkspaceVue, /getEntityRows\('MarketingCampaign'/, 'marketing workspace must load real MarketingCampaign rows from the entity API')
assert.match(marketingWorkspaceVue, /getEntityRows\('TrackingCode'/, 'marketing workspace must load real TrackingCode rows from the entity API')
assert.match(marketingWorkspaceVue, /getEntityRows\('ContactList'/, 'marketing workspace must load real ContactList rows from the entity API')
assert.match(marketingWorkspaceVue, /营销业务数据/, 'marketing workspace must describe entity-backed marketing data')
assert.doesNotMatch(
marketingWorkspaceVue,
/发送预览|fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b/,
'marketing workspace must not synthesize campaigns, tracking, or contact lists from generated metadata'
)
assert.match(rendererVue, /ErpSalesAutomationWorkspace/, 'SFA pages should use a first-class sales automation workspace')
assert.match(rendererVue, /isSalesAutomationPage/, 'renderer should detect SalesForceAutomation and SFA routes before marketing fallbacks')
assert.match(rendererVue, /salesAutomationWorkspaceBlock/, 'renderer should collapse SFA blocks into one sales automation workspace per page')
assert.match(rendererVue, /data-modern-sales-automation-page/, 'renderer should mark page-level sales automation workspaces')
for (const pageId of [
'SalesForceAutomation__FindSalesOpportunity',
'SalesForceAutomation__FindLeads',
'SalesForceAutomation__FindSalesForecast',
'SalesForceAutomation__ConvertLead'
]) {
assert.match(captureScreenshotsScript, new RegExp(`#\\/pages\\/${pageId}`), `screenshots must cover ${pageId}`)
}
for (const text of [
'销售自动化',
'销售线索',
'机会管道',
'账户/联系人',
'销售预测',
'跟进事件',
'线索转化',
'SalesOpportunity',
'SalesOpportunityStage',
'SalesOpportunityRole',
'SalesForecast',
'SalesForecastDetail',
'PartyRole'
]) {
assert.match(salesAutomationWorkspaceVue, new RegExp(text), `sales automation workspace must expose real SFA operations: ${text}`)
}
for (const entityName of [
'SalesOpportunity',
'SalesOpportunityStage',
'SalesOpportunityRole',
'SalesForecast',
'SalesForecastDetail',
'Party',
'PartyRole',
'Person',
'PartyGroup',
'CommunicationEvent'
]) {
assert.match(salesAutomationWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `sales automation workspace must load real ${entityName} rows from the entity API`)
}
assert.match(salesAutomationWorkspaceVue, /销售业务数据/, 'sales automation workspace must describe entity-backed sales data')
assert.doesNotMatch(
salesAutomationWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|LEAD-\d+|OPP-\d+|FORECAST-\d+|SalesLead|OpsLead|FinanceLead|example\.com/,
'sales automation workspace must not synthesize leads, opportunities, accounts, contacts, forecasts, or events from generated metadata'
)
assert.match(mediaWorkspaceVue, /getEntityRows\('Content'/, 'media workspace must load real Content rows from the entity API')
assert.match(mediaWorkspaceVue, /getEntityRows\('DataResource'/, 'media workspace must load real DataResource rows from the entity API')
assert.match(mediaWorkspaceVue, /媒体业务数据/, 'media workspace must describe entity-backed media data')
assert.doesNotMatch(
mediaWorkspaceVue + rendererVue,
/上传、预览|资产预览|asset preview|>\s*预览\s*</,
'media upload workspaces must use production media operation language, not preview wording'
)
assert.doesNotMatch(
mediaWorkspaceVue,
/fieldRows\.value\.map|字段 \$\{index \+ 1\}|\bindex \+ 1\b/,
'media workspace must not synthesize assets from generated metadata'
)
assert.match(reportWorkspaceVue, /getEntityRows\('AcctgTransEntry'/, 'report workspace must load real AcctgTransEntry rows from the entity API')
assert.match(reportWorkspaceVue, /getEntityRows\('Invoice'/, 'report workspace must load real Invoice rows from the entity API')
assert.match(reportWorkspaceVue, /getEntityRows\('Payment'/, 'report workspace must load real Payment rows from the entity API')
assert.match(reportWorkspaceVue, /报表业务数据/, 'report workspace must describe entity-backed report data')
assert.doesNotMatch(
reportWorkspaceVue,
/fieldRows\.value\.map|字段 \$\{index \+ 1\}|\bindex \+ 1\b|index % 2/,
'report workspace must not synthesize accounting lines from generated form fields'
)
for (const text of ['系统管理', '资料维护', '运行任务', '安全与权限', '运行日志', '标签资源', '导入导出', '资源与数据文件']) {
assert.match(systemWorkspaceVue, new RegExp(text), `system workspace must expose administrator operations: ${text}`)
}
for (const entityName of [
'UserLogin',
'SecurityGroup',
'UserLoginSecurityGroup',
'EntityAuditLog',
'SystemProperty',
'VisualTheme',
'JobSandbox',
'DataResource',
'FileExtension',
'ExcelImportHistory'
]) {
assert.match(systemWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `system workspace must load real ${entityName} rows from the entity API`)
}
assert.match(
systemWorkspaceVue,
/实时日志需授权查看|受权限保护的日志读取服务/,
'system workspace must show an honest unavailable state for runtime logs instead of fake log rows'
)
assert.match(
systemWorkspaceVue,
/实时运行信息需授权查看|受权限保护的运行服务/,
'system workspace must show an honest unavailable state for service runtime details'
)
assert.doesNotMatch(
systemWorkspaceVue,
/Backend|接口尚未开放|后端运行接口|暂未接入|标签 API|实体 API|Action API/,
'system workspace must not expose backend/API implementation wording in the administrator product'
)
assert.doesNotMatch(
systemVue + moduleCatalog,
/快速进入安全、服务|管理 WebTools|OFBiz 扩展|接口|后端|后台|实体对象|业务对象|业务实体|待处理对象|可处理对象/,
'system tools and module catalog copy must use administrator-facing task, record, and connection language'
)
assert.doesNotMatch(
systemWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|SRV-\d+|LOG-\d+/,
'system workspace must not synthesize system records from generated metadata'
)
for (const text of ['内容管理', '内容库', '站点/CMS', '博客/论坛', '媒体资源', '发布队列', 'SEO/路径']) {
assert.match(contentWorkspaceVue, new RegExp(text), `content workspace must expose content operations: ${text}`)
}
for (const entityName of [
'Content',
'DataResource',
'ContentAssoc',
'ContentRole',
'ElectronicText',
'CommunicationEvent',
'WebSite',
'WebSiteContent',
'WebSitePathAlias',
'ContentKeyword'
]) {
assert.match(contentWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `content workspace must load real ${entityName} rows from the entity API`)
}
assert.match(contentWorkspaceVue, /内容业务数据/, 'content workspace must describe entity-backed content data')
assert.match(contentWorkspaceVue, /发布任务和静态页生成需要发布权限/, 'content workspace must show an honest unavailable state for publish runtime details')
assert.doesNotMatch(
contentWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|CNT-\d+|BLOG-\d+|CMS-\d+/,
'content workspace must not synthesize content, CMS, blog, forum, media, publish, or SEO rows from generated metadata'
)
for (const text of ['资产维护', '资产维护', 'IT 资产', '工单排程', '备件领用', '维护记录']) {
assert.match(assetMaintenanceWorkspaceVue, new RegExp(text), `asset maintenance workspace must expose maintenance operations: ${text}`)
}
for (const entityName of [
'FixedAssetMaint',
'FixedAsset',
'WorkEffort',
'WorkEffortAndPartyAssign',
'WorkEffortAndTimeEntry',
'WorkEffortNote',
'ItemIssuance',
'ProductMaint'
]) {
assert.match(assetMaintenanceWorkspaceVue, new RegExp(`getEntityRows\\('${entityName}'`), `asset maintenance workspace must load real ${entityName} rows from the entity API`)
}
assert.match(assetMaintenanceWorkspaceVue, /资产维护业务数据/, 'asset maintenance workspace must describe entity-backed asset maintenance data')
assert.doesNotMatch(
assetMaintenanceWorkspaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|ASSET-\d+|HW-\d+|SW-\d+|WO-\d+/,
'asset maintenance workspace must not synthesize maintenance, hardware, software, work effort, or issuance rows from generated metadata'
)
assert.match(commerceSurfaceVue, /getEntityRows\('Product'/, 'commerce surface must load real Product rows from the entity API')
assert.match(commerceSurfaceVue, /getEntityRows\('ProductCategory'/, 'commerce surface must load real ProductCategory rows from the entity API')
assert.match(commerceSurfaceVue, /getEntityRows\('ProductPromo'/, 'commerce surface must load real ProductPromo rows from the entity API')
assert.match(commerceSurfaceVue, /电商业务数据/, 'commerce surface must describe entity-backed storefront data')
assert.doesNotMatch(
commerceSurfaceVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|fieldRows\.value\.slice|\bindex \+ 1\b|428 : 36/,
'commerce surface must not synthesize carts, product cards, trees, or contact/community counts from generated metadata'
)
for (const text of ['业务队列', '交接记录', '处理流', '执行动作', '处理事项', '复核交接', '异常交接']) {
assert.match(moduleVue, new RegExp(text), `module workspaces must behave like business consoles: ${text}`)
}
assert.match(appVue, /showLoginGate/, 'app should keep login/session gate before the administrator site')
assert.match(appVue, /showLoginGate\s*=\s*computed\(\(\)\s*=>\s*!booting\.value\s*&&\s*!isAuthenticated\.value\)/, 'administrator site should only open after a real OFBiz session')
assert.match(routerTs, /ProductAdminView/, 'administrator site should mount a first-class product administration page')
assertRouteMapsToView('#/catalog/products', 'ProductAdminView', 'product administration should be a dedicated administrator route')
assert.match(routerTs, /OrderAdminView/, 'administrator site should mount a first-class order operations page')
assertRouteMapsToView('#/orders', 'OrderAdminView', 'order operations should be a dedicated administrator route')
assert.match(routerTs, /PartyAdminView/, 'administrator site should mount a first-class party administration page')
assertRouteMapsToView('#/parties', 'PartyAdminView', 'party administration should be a dedicated administrator route')
assert.match(routerTs, /AccountingAdminView/, 'administrator site should mount a first-class accounting administration page')
assertRouteMapsToView('#/accounting', 'AccountingAdminView', 'accounting administration should be a dedicated administrator route')
assert.match(routerTs, /SalesAdminView/, 'administrator site should mount a first-class sales administration page')
assertRouteMapsToView('#/sales', 'SalesAdminView', 'sales administration should be a dedicated administrator route')
assert.match(routerTs, /ProcurementAdminView/, 'administrator site should mount a first-class procurement administration page')
assertRouteMapsToView('#/procurement', 'ProcurementAdminView', 'procurement administration should be a dedicated administrator route')
assert.match(routerTs, /SecurityAdminView/, 'administrator site should mount a first-class security administration page')
assertRouteMapsToView('#/system/security', 'SecurityAdminView', 'security administration should be a dedicated administrator route')
assert.match(routerTs, /SystemOperationsView/, 'administrator site should mount a first-class system operations page')
assertRouteMapsToView('#/system/operations', 'SystemOperationsView', 'system operations should be a dedicated administrator route')
assert.match(appVue, /:target-hash="loginTargetHash"/, 'login should preserve the requested administrator deep link after API session login')
// DashboardView now injects the session (provide(SessionKey, session) in App.vue) instead of receiving it as a prop.
assert.match(appVue, /provide\(SessionKey, session\)/, 'administrator dashboard should receive the current OFBiz session context through provided app state')
// The root route is the ERP workbench; unknown authenticated routes fall through the catch-all to NotFoundView.
assertRouteMapsToView('/', 'DashboardView', 'the ERP administrator workbench should be the default authenticated landing route')
assert.match(routerTs, /path:\s*'\/:pathMatch\(\.\*\)\*'[\s\S]*NotFoundView/, 'unknown authenticated routes should fall through the catch-all instead of a dead view')
assert.match(routerTs, /BusinessPageView/, 'generated OFBiz routes should render as business pages, not previews')
assert.doesNotMatch(appVue + routerTs, /PagePreviewView|page-preview|previewPageId/, 'administrator product should not keep preview-page route naming')
assert.doesNotMatch(routerTs, /'\/components'|'\/patterns'|PatternsView|ComponentsView/, 'component and pattern pages must not be top-level authenticated routes')
assert.doesNotMatch(
appVue + routerTs,
/LegacyRedirectView|DeliveryView|ParityView|ComponentsView|PatternsView|InventoryView/,
'production administrator site must not mount legacy bridge, engineering delivery, parity, component, pattern, or inventory workbenches'
)
assert.doesNotMatch(routerTs, /'\/legacy'|'\/delivery'|'\/parity'|'\/components'|'\/patterns'|'\/inventory'/, 'support tools and legacy bridge pages must not be top-level administrator routes')
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/catalog\/products/, 'product administration should be reachable from the shell, dashboard, or catalog module')
for (const text of ['商品管理台', '商品资料', '目录分类', '价格规则', '促销活动', '库存状态', '商品业务数据']) {
assert.match(productAdminVue, new RegExp(text), `product administration page must expose real administrator operation: ${text}`)
}
for (const entityName of ['Product', 'ProductCategory', 'ProductPrice', 'ProductPromo', 'InventoryItem']) {
assertEntitySource(productAdminVue, entityName, `product administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
productAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|SKU-\d+|PROD-\d+|RetailStore|BigSupplier|Wholesale replenishment|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'product administration page must not synthesize product rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/orders/, 'order operations should be reachable from the shell, dashboard, or order module')
for (const text of ['订单运营台', '订单执行台', '待审核订单', '待履约订单', '付款关注', '退货风险', '今日处理流', '交接记录', '订单队列', '订单明细', '履约与发运', '退货授权', '订单治理', '订单业务数据']) {
assert.match(orderAdminVue, new RegExp(text), `order operations page must expose real administrator operation: ${text}`)
}
for (const text of ['orderExecutionRows', 'orderFlowRows', 'orderHandoffRows', 'orderRiskRows', 'order-admin-execution', 'order-admin-flow-row', 'order-admin-handoff-row', 'order-admin-risk-row']) {
assert.match(orderAdminVue + modernCss, new RegExp(text), `order operations page must behave like an executable order console: ${text}`)
}
for (const entityName of ['OrderHeader', 'OrderItem', 'OrderStatus', 'Shipment', 'ReturnHeader']) {
assert.match(orderAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `order operations page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
orderAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|ORD-\d+|SHP-\d+|RET-\d+|SampleCustomer|Wholesale replenishment|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'order operations page must not synthesize order rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/sales/, 'sales administration should be reachable from the shell, dashboard, or sales module')
for (const text of ['销售管理台', '销售执行台', '线索承接', '机会推进', '预测复核', '跟进交接', '客户角色', '销售异常', '销售线索', '机会管道', '销售预测', '跟进事件', '销售治理', '销售风险', '销售业务数据']) {
assert.match(salesAdminVue, new RegExp(text), `sales administration page must expose real administrator operation: ${text}`)
}
for (const text of ['salesExecutionRows', 'leadHandoffRows', 'opportunityPipelineRows', 'forecastReviewRows', 'communicationFollowupRows', 'salesExceptionRows', 'sales-admin-execution-grid', 'sales-admin-work-row', 'sales-admin-risk-row']) {
assert.match(salesAdminVue + modernCss, new RegExp(text), `sales administration page must expose sales operator work surfaces: ${text}`)
}
for (const entityName of ['SalesOpportunity', 'SalesOpportunityStage', 'SalesOpportunityRole', 'SalesForecast', 'SalesForecastDetail', 'PartyRole', 'CommunicationEvent']) {
assert.match(salesAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `sales administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
salesAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|LEAD-\d+|SFA-\d+|OPP-\d+|FORECAST-\d+|SampleLead|SampleOpportunity|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'sales administration page must not synthesize sales rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/procurement/, 'procurement administration should be reachable from the shell, dashboard, or procurement module')
for (const text of ['采购管理台', '采购需求', '需求审批', '供应商', '供应商品', '供应商报价', '到货接收', '采购治理', '采购风险', '采购业务数据']) {
assert.match(procurementAdminVue, new RegExp(text), `procurement administration page must expose real administrator operation: ${text}`)
}
for (const entityName of ['Requirement', 'SupplierProduct', 'Vendor', 'Quote', 'CustRequest', 'Shipment', 'InventoryItem']) {
assert.match(procurementAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `procurement administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
procurementAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|REQ-\d+|PO-\d+|SUP-\d+|VEN-\d+|SampleVendor|SampleSupplier|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'procurement administration page must not synthesize procurement rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/parties/, 'party administration should be reachable from the shell, dashboard, or party module')
for (const text of ['客户与组织管理台', '客户主体', '个人档案', '组织档案', '角色关系', '联系方式', '沟通记录', '客户治理', '客户业务数据']) {
assert.match(partyAdminVue, new RegExp(text), `party administration page must expose real administrator operation: ${text}`)
}
for (const entityName of ['Party', 'Person', 'PartyGroup', 'PartyRole', 'ContactMech', 'CommunicationEvent']) {
assertEntitySource(partyAdminVue, entityName, `party administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
partyAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|PARTY-\d+|CUST-\d+|ORG-\d+|Person-\d+|SampleCustomer|example\.com|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'party administration page must not synthesize party rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/accounting/, 'accounting administration should be reachable from the shell, dashboard, or accounting module')
for (const text of ['财务管理台', '财务结账台', '应收跟进', '应付安排', '收付款匹配', '核销队列', '现金流关注', '财务异常', '本期结账', '发票队列', '付款收款', '会计凭证', '凭证明细', '总账科目', '核销处理', '账龄风险', '财务业务数据']) {
assert.match(accountingAdminVue, new RegExp(text), `accounting administration page must expose real administrator operation: ${text}`)
}
for (const text of ['financeCloseRows', 'receivableRows', 'payableRows', 'reconciliationRows', 'cashAttentionRows', 'financeExceptionRows', 'accounting-admin-close-grid', 'accounting-admin-work-row', 'accounting-admin-exception-row']) {
assert.match(accountingAdminVue + modernCss, new RegExp(text), `accounting administration page must expose finance operator work surfaces: ${text}`)
}
for (const entityName of ['Invoice', 'Payment', 'AcctgTrans', 'AcctgTransEntry', 'GlAccount']) {
assert.match(accountingAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `accounting administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
accountingAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|INV-\d+|PAY-\d+|GL-\d+|ACCT-\d+|SampleCustomer|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'accounting administration page must not synthesize finance rows or expose engineering preview language'
)
assert.match(shellVue + dashboardVue + moduleCatalog, /#\/procurement/, 'procurement administration should be reachable from the shell, dashboard, or procurement module')
for (const text of ['采购管理台', '采购执行台', '需求审批', '询价比价', '供应商风险', '到货接收', '补货交接', '采购异常', '供应商协同', '采购需求', '供应商', '供应商品', '供应商报价', '采购风险', '采购业务数据']) {
assert.match(procurementAdminVue, new RegExp(text), `procurement administration page must expose real administrator operation: ${text}`)
}
for (const text of ['procurementExecutionRows', 'approvalWorkRows', 'quoteComparisonRows', 'supplierRiskRows', 'receivingWorkRows', 'procurementHandoffRows', 'procurement-admin-execution-grid', 'procurement-admin-work-row', 'procurement-admin-risk-row']) {
assert.match(procurementAdminVue + modernCss, new RegExp(text), `procurement administration page must expose procurement operator work surfaces: ${text}`)
}
for (const entityName of ['Requirement', 'SupplierProduct', 'Vendor', 'Quote', 'CustRequest', 'Shipment', 'InventoryItem']) {
assert.match(procurementAdminVue, new RegExp(`getEntityRows\\('${entityName}'`), `procurement administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
procurementAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|REQ-\d+|PO-\d+|SUP-\d+|VEN-\d+|SampleVendor|SampleSupplier|组件展厅|页面清单|业务等价|待验收|迁移|技术预览/,
'procurement administration page must not synthesize procurement rows or expose engineering preview language'
)
assert.doesNotMatch(systemVue, /props\.inventory|UiInventory|routeManifest|counts\?\.routes|counts\?\.actions|counts\?\.services/, 'system maintenance must derive operator signals from navigation/session tools, not UI migration inventory counts')
assert.doesNotMatch(systemVue, /业务回归记录|界面组件规范|排版规则|覆盖台账|现代页面|动作契约|#\/system\/delivery|#\/system\/parity|#\/system\/components|#\/system\/patterns|#\/system\/inventory|待补齐事项|pendingE2ePages|customParityPages|missingRoutes|missingActions|历史地址/, 'system maintenance page must not foreground validation, component documentation, inventory counts, compatibility tools, or engineering workbenches')
assert.match(systemVue, /#\/system\/security/, 'system maintenance should link to the dedicated security administration page')
assert.match(systemVue + shellVue, /#\/system\/operations/, 'system maintenance and shell should link to the dedicated system operations page')
assert.doesNotMatch(systemVue, /用户与权限[\s\S]*path:\s*'#\/module\/party'/, 'user and permission administration must not be routed through the party module')
for (const text of ['运行日志', '缓存维护', '定时任务', '导入导出', '用户与权限']) {
assert.match(systemVue, new RegExp(text), `system maintenance should expose real administrator operation: ${text}`)
}
assert.doesNotMatch(
systemVue + moduleCatalog,
/快速进入安全、服务|管理 WebTools|OFBiz 扩展|OFBiz 管理|接口|后端|后台|实体对象|业务对象|业务实体|待处理对象|可处理对象/,
'system maintenance and module directory labels must avoid technical wording in visible product copy'
)
for (const text of ['系统运行管理', '计划任务', '任务队列', '缓存维护', '导入导出', '运行资源', '运行审计', '运行业务数据']) {
assert.match(systemOperationsVue, new RegExp(text), `system operations page must expose real administrator operation: ${text}`)
}
for (const entityName of ['JobSandbox', 'SystemProperty', 'DataResource', 'ExcelImportHistory']) {
assert.match(systemOperationsVue, new RegExp(`getEntityRows\\('${entityName}'`), `system operations page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
systemOperationsVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|JOB-\d+|CACHE-\d+|IMPORT-\d+|待验收|业务等价|组件展厅|页面清单|迁移|技术预览/,
'system operations page must not synthesize operations or expose engineering preview language'
)
for (const text of ['用户与权限管理', '账号与登录', '安全组', '授权关系', '权限审计', '权限业务数据']) {
assert.match(securityAdminVue, new RegExp(text), `security administration page must expose real administrator operation: ${text}`)
}
for (const entityName of ['UserLogin', 'SecurityGroup', 'UserLoginSecurityGroup']) {
assertEntitySource(securityAdminVue, entityName, `security administration page must load real ${entityName} rows from the entity API`)
}
assert.doesNotMatch(
securityAdminVue,
/fieldRows\.value\.map|actionRows\.value\.map|capabilityRows\.value|\bindex \+ 1\b|Demo[A-Za-z0-9_]*|USER-\d+|GROUP-\d+|SEC-\d+/,
'security administration page must not synthesize users, groups, or permission links from generated metadata'
)
for (const [fileName, content] of [
['ErpAppShell.vue', shellVue],
['DashboardView.vue', dashboardVue],
['OrderAdminView.vue', orderAdminVue],
['PartyAdminView.vue', partyAdminVue],
['ProductAdminView.vue', productAdminVue],
['AccountingAdminView.vue', accountingAdminVue],
['BusinessCenterView.vue', businessCenterVue],
['SystemToolsView.vue', systemVue],
['SystemOperationsView.vue', systemOperationsVue],
['SecurityAdminView.vue', securityAdminVue],
['ExtensionAdminView.vue', extensionAdminVue],
['ModuleWorkspaceView.vue', moduleVue]
]) {
assert.doesNotMatch(
content,
/组件展厅|交付|验收|迁移|业务等价|覆盖率|覆盖台账|待补齐|旧入口|旧 URL|旧页面|技术预览|页面清单|自动迁移|历史地址/,
`${fileName} must not expose preview, migration, parity, compatibility, or component-gallery language in the administrator product`
)
}
assert.match(packageJson, /verify:admin-product/, 'package scripts should expose product-level admin verification')
assert.doesNotMatch(
businessPageVue,
/前端已重写|适配器已覆盖|页面结构">已生成|界面规则|待输出比对|待验收/,
'generated business pages should not present migration or acceptance wording as product chrome'
)
console.log(JSON.stringify({
status: 'passed',
checked: [
'LoginView.vue',
'DashboardView.vue',
'ErpAppShell.vue',
'ModuleWorkspaceView.vue',
'BusinessCenterView.vue',
'SystemToolsView.vue',
'ErpPageRenderer.vue',
'BusinessPageView.vue',
'ErpPartyWorkspace.vue',
'ErpPortalAdminWorkspace.vue',
'ErpGatewayAdminWorkspace.vue',
'ErpHumanResWorkspace.vue',
'ErpWorkManagementWorkspace.vue',
'ErpInventoryWorkspace.vue',
'ErpFulfillmentWorkspace.vue',
'ErpReturnWorkspace.vue',
'ErpCatalogWorkspace.vue',
'ErpCommerceSurface.vue',
'ErpManufacturingWorkspace.vue',
'ErpSystemWorkspace.vue',
'ErpContentWorkspace.vue',
'ErpAdapterBlock.vue',
'ErpDataTable.vue',
'ErpUpload.vue',
'ExtensionAdminView.vue',
'App.vue',
'package.json',
'generated page IDs'
],
hardcodedPageIds: hardcodedPageIds.size
}, null, 2))