前端:增加批次管理与旧数据纳入

This commit is contained in:
焦龙言
2026-09-23 16:54:22 +08:00
parent a7dc1a2a1e
commit 8221a5162d
15 changed files with 745 additions and 17 deletions
@@ -2414,3 +2414,74 @@ test('业务用户分配组件使用固定映射并允许空数组完整替换',
assert.match(source, /<el-alert\b[^>]*v-if="loadError"/)
assert.match(source, /width="min\(720px, 92vw\)"/)
})
test('测试批次管理与旧数据纳入遵守 root、权限和预览提交契约', async () => {
const apiPath = 'src/api/system/testBatch.js'
const apiSource = await readFile(path.join(frontendRoot, apiPath), 'utf8')
const expectedTestBatchRequests = [
{ url: "'/system/test-batch/options'", method: 'get' },
{ url: "'/system/test-batch/list'", method: 'get' },
{ url: "'/system/test-batch/' + batchId", method: 'get' },
{ url: "'/system/test-batch'", method: 'post' },
{ url: "'/system/test-batch/' + batchId", method: 'put' },
{ url: "'/system/test-batch/' + batchId + '/users'", method: 'put' },
{ url: "'/system/test-batch/' + batchId + '/close'", method: 'put' },
{ url: "'/system/test-batch/' + batchId + '/reopen'", method: 'put' },
{ url: "'/system/test-batch/' + batchId + '/adoption/preview'", method: 'post' },
{ url: "'/system/test-batch/' + batchId + '/adoption/execute'", method: 'post' },
{ url: "'/system/test-batch/' + batchId + '/purge/preview'", method: 'post' },
{ url: "'/system/test-batch/' + batchId + '/purge'", method: 'post' }
]
assert.deepEqual(extractRequests(apiSource), expectedTestBatchRequests)
const adoptionRoots = {
strain: { path: 'src/views/master/strain/index.vue', id: 'strainId' },
customer: { path: 'src/views/master/customer/index.vue', id: 'customerId' },
site: { path: 'src/views/master/site/index.vue', id: 'siteId' },
line: { path: 'src/views/master/line/index.vue', id: 'lineId' },
warehouse: { path: 'src/views/master/warehouse/index.vue', id: 'warehouseId' },
pallet: { path: 'src/views/master/pallet/index.vue', id: 'palletId' },
inbound: { path: 'src/views/business/inbound/index.vue', id: 'inboundId' },
filling: { path: 'src/views/business/filling/index.vue', id: 'fillingId' },
shipment: { path: 'src/views/business/shipment/index.vue', id: 'shipmentId' },
usage: { path: 'src/views/business/usage/index.vue', id: 'usageId' },
return: { path: 'src/views/business/return/index.vue', id: 'returnId' }
}
assert.equal(Object.keys(adoptionRoots).length, 11, '纳入来源必须固定为 11 类')
for (const [rootType, contract] of Object.entries(adoptionRoots)) {
const source = await readFile(path.join(frontendRoot, contract.path), 'utf8')
assert.match(source, /import TestBatchAdoptionDialog from ['"]@\/components\/TestBatchAdoptionDialog['"]/)
assert.match(source, /useUserStore\(\)/, `${rootType} 必须读取 admin 可见性`)
assert.match(source, /useTestBatchStore\(\)/, `${rootType} 必须读取测试模式`)
assert.match(source, /userStore\.admin === true\s*&&\s*!testBatchStore\.isTestMode/, `${rootType} 纳入入口必须只在管理员正式模式显示`)
assert.match(source, new RegExp(`<test-batch-adoption-dialog\\b[\\s\\S]*?:root-type="['\"]${rootType}['\"][\\s\\S]*?:resource-ids="selectedIds"`))
assert.match(source, new RegExp(`selectedIds\\.value\\s*=\\s*selection\\.map\\(\\(item\\)\\s*=>\\s*item\\.${contract.id}\\)`), `${rootType} 必须传递正整数根 ID`)
}
for (const rootType of ['strain', 'customer', 'site', 'line', 'warehouse', 'pallet']) {
const source = await readFile(path.join(frontendRoot, adoptionRoots[rootType].path), 'utf8')
assert.match(source, /userStore\.admin === true\s*\|\|\s*!testBatchStore\.isTestMode/, `${rootType} 测试模式主数据写操作必须要求管理员`)
}
const adoptionDialogPath = 'src/components/TestBatchAdoptionDialog/index.vue'
const adoptionDialog = await readFile(path.join(frontendRoot, adoptionDialogPath), 'utf8')
assert.match(adoptionDialog, /item\.status === ['"]ACTIVE['"]/)
assert.match(adoptionDialog, /Number\.isInteger\(id\)\s*&&\s*id\s*>\s*0/)
assert.match(adoptionDialog, /previewTestBatchAdoption\(.*\{\s*roots:\s*roots\.value\s*}\s*\)/s)
assert.match(adoptionDialog, /executeTestBatchAdoption\([^,]+,\s*\{\s*roots:\s*roots\.value,\s*batchName:\s*batchName\.value,\s*previewToken:\s*previewToken\.value\s*}\s*\)/s)
assert.match(adoptionDialog, /batchName\.value\s*!==\s*preview\.value\.batchName/)
assert.match(adoptionDialog, /function invalidatePreview\(\)[\s\S]*?previewToken\.value\s*=\s*['"][\s\S]*?batchName\.value\s*=\s*['"]/)
const managementPath = 'src/views/system/testBatch/index.vue'
const management = await readFile(path.join(frontendRoot, managementPath), 'utf8')
assert.match(management, /const actionsFor\s*=\s*row\s*=>\s*\(\{[\s\S]*?edit:\s*row\.status\s*!==\s*['"]PURGED['"][\s\S]*?assign:\s*row\.status\s*===\s*['"]ACTIVE['"][\s\S]*?close:\s*row\.status\s*===\s*['"]ACTIVE['"][\s\S]*?reopen:\s*row\.status\s*===\s*['"]CLOSED['"][\s\S]*?purgePreview:\s*row\.status\s*===\s*['"]CLOSED['"][\s\S]*?audit:\s*row\.status\s*===\s*['"]PURGED['"][\s\S]*?}\)/)
assert.match(management, /onlinePurgeAllowed\.value\s*===\s*false/)
assert.match(management, /blockingReason/)
assert.match(management, /testBatchStore\.leaveTestMode\(\)/)
const reportPaths = Object.values(reportPageContracts).map((contract) => contract.relativePath)
for (const reportPath of reportPaths) {
const source = await readFile(path.join(frontendRoot, reportPath), 'utf8')
assert.doesNotMatch(source, /TestBatchAdoptionDialog/, `${reportPath} 不得提供纳入入口`)
}
})