功能:新增托盘业务报表页面
This commit is contained in:
@@ -253,6 +253,83 @@ const businessPageContracts = {
|
||||
}
|
||||
}
|
||||
|
||||
const reportPageContracts = {
|
||||
palletInventory: {
|
||||
relativePath: 'src/views/report/palletInventory/index.vue',
|
||||
apiName: 'listPalletInventory',
|
||||
permission: 'report:palletinventory:list',
|
||||
queryKeys: [
|
||||
'pageNum', 'pageSize', 'beginTime', 'endTime', 'palletCode', 'flowState',
|
||||
'warehouseId', 'siteId', 'lineId', 'strainId', 'batchNo'
|
||||
],
|
||||
columnKeys: [
|
||||
'palletCode', 'flowState', 'warehouseName', 'siteName', 'lineName',
|
||||
'strainName', 'batchNo', 'initialPackages', 'remainingPackages'
|
||||
]
|
||||
},
|
||||
filling: {
|
||||
relativePath: 'src/views/report/filling/index.vue',
|
||||
apiName: 'listFillingReport',
|
||||
permission: 'report:filling:list',
|
||||
queryKeys: ['pageNum', 'pageSize', 'beginTime', 'endTime', 'warehouseId', 'palletCode', 'strainId', 'batchNo'],
|
||||
columnKeys: [
|
||||
'operatedAt', 'warehouseName', 'palletCode', 'strainCodeSnapshot',
|
||||
'strainNameSnapshot', 'batchNo', 'packageSpec', 'initialPackages'
|
||||
]
|
||||
},
|
||||
shipment: {
|
||||
relativePath: 'src/views/report/shipment/index.vue',
|
||||
apiName: 'listShipmentReport',
|
||||
permission: 'report:shipment:list',
|
||||
queryKeys: [
|
||||
'pageNum', 'pageSize', 'beginTime', 'endTime', 'shipmentNo',
|
||||
'sourceWarehouseId', 'targetSiteId', 'palletCode', 'status'
|
||||
],
|
||||
columnKeys: [
|
||||
'shipmentNo', 'confirmedAt', 'sourceWarehouseName', 'customerName',
|
||||
'targetSiteName', 'palletCode', 'packageQty'
|
||||
]
|
||||
},
|
||||
consumption: {
|
||||
relativePath: 'src/views/report/consumption/index.vue',
|
||||
apiName: 'listConsumptionReport',
|
||||
permission: 'report:consumption:list',
|
||||
queryKeys: [
|
||||
'pageNum', 'pageSize', 'beginTime', 'endTime', 'customerId',
|
||||
'siteId', 'lineId', 'palletCode', 'strainId', 'batchNo'
|
||||
],
|
||||
columnKeys: [
|
||||
'occurredAt', 'customerName', 'siteName', 'lineName', 'palletCode',
|
||||
'strainName', 'batchNo', 'usedPackages', 'remainingPackagesAfter'
|
||||
]
|
||||
},
|
||||
return: {
|
||||
relativePath: 'src/views/report/return/index.vue',
|
||||
apiName: 'listReturnReport',
|
||||
permission: 'report:return:list',
|
||||
queryKeys: [
|
||||
'pageNum', 'pageSize', 'beginTime', 'endTime', 'returnNo',
|
||||
'sourceSiteId', 'targetWarehouseId', 'palletCode', 'status'
|
||||
],
|
||||
columnKeys: [
|
||||
'returnNo', 'status', 'requestedAt', 'confirmedAt', 'sourceSiteName',
|
||||
'targetWarehouseName', 'palletCode', 'remainingPackagesSnapshot'
|
||||
]
|
||||
},
|
||||
palletTrace: {
|
||||
relativePath: 'src/views/report/palletTrace/index.vue',
|
||||
apiName: 'listPalletTrace',
|
||||
permission: 'report:pallettrace:list',
|
||||
queryKeys: ['pageNum', 'pageSize', 'beginTime', 'endTime', 'palletCode', 'eventType'],
|
||||
columnKeys: [
|
||||
'occurredAt', 'eventType', 'sourceType', 'sourceId', 'palletCode',
|
||||
'beforeState', 'afterState', 'beforeWarehouseName', 'beforeSiteName',
|
||||
'beforeLineName', 'afterWarehouseName', 'afterSiteName', 'afterLineName',
|
||||
'beforeRemainingPackages', 'afterRemainingPackages', 'operatorName'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const delimiterPairs = { '(': ')', '[': ']', '{': '}' }
|
||||
|
||||
function sanitizeSource(source, { allowTemplateInterpolation = false } = {}) {
|
||||
@@ -680,6 +757,66 @@ test('六个报表 API 只提供 GET /list,不提供导出接口', async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test('六个只读报表页面逐页满足 API、权限、筛选、列和异步分页契约', async (t) => {
|
||||
for (const [resource, contract] of Object.entries(reportPageContracts)) {
|
||||
await t.test(contract.relativePath, async () => {
|
||||
const source = await readFile(path.join(frontendRoot, contract.relativePath), 'utf8')
|
||||
const importedApiModules = [...source.matchAll(/from\s*['"]@\/api\/(report|master|business)\/([A-Za-z]+)['"]/g)]
|
||||
.map((match) => `${match[1]}/${match[2]}`)
|
||||
|
||||
assert.deepEqual(importedApiModules, [`report/${resource}`], `${resource} 页面只能导入自己的报表 API`)
|
||||
assert.deepEqual(extractImportedNames(source, `@/api/report/${resource}`), [contract.apiName])
|
||||
assert.doesNotMatch(source, /@\/api\/(?:master|business)\//)
|
||||
assert.match(source, new RegExp(`${contract.apiName}\\(listContext\\.query\\)`))
|
||||
|
||||
const actualPermissions = [...new Set(
|
||||
[...source.matchAll(/['"](report:[a-z]+:[a-z]+)['"]/g)].map((match) => match[1])
|
||||
)]
|
||||
assert.deepEqual(actualPermissions, [contract.permission], `${resource} 页面只能出现自己的 list 权限`)
|
||||
assertButtonsUsePermission(source, 'handleQuery', contract.permission)
|
||||
assertButtonsUsePermission(source, 'resetQuery', contract.permission)
|
||||
assert.doesNotMatch(source, /report:[a-z]+:(?:add|edit|remove|export|print)/)
|
||||
assert.doesNotMatch(source, /\bhandle(?:Add|Edit|Delete|Remove|Export|Print|Download)\b|proxy\.download\s*\(/)
|
||||
|
||||
for (const queryKey of contract.queryKeys) {
|
||||
assert.match(source, new RegExp(`\\b${queryKey}\\b`), `${resource} 缺少筛选键 ${queryKey}`)
|
||||
}
|
||||
for (const columnKey of contract.columnKeys) {
|
||||
assert.match(source, new RegExp(`\\b${columnKey}\\b`), `${resource} 缺少核心列 ${columnKey}`)
|
||||
}
|
||||
|
||||
assert.match(source, /<el-date-picker\b[\s\S]*?type="daterange"[\s\S]*?value-format="YYYY-MM-DD"/)
|
||||
assert.match(source, /<el-table\b[^>]*v-loading="loading"[^>]*:data="reportList"/)
|
||||
assert.match(source, /<template\s+#empty>[\s\S]*?<el-empty\b/)
|
||||
assert.match(source, /<el-alert\b[^>]*v-if="loadError"/)
|
||||
assert.match(source, /<pagination\b[\s\S]*?:total="total"[\s\S]*?:page="queryParams\.pageNum"[\s\S]*?:limit="queryParams\.pageSize"/)
|
||||
assert.match(source, /@update:page="handleCurrentChange"/)
|
||||
assert.match(source, /@update:limit="handleSizeChange"/)
|
||||
assert.match(source, /@pagination="getList"/)
|
||||
|
||||
assert.match(source, /let listGeneration = 0;?/)
|
||||
assert.match(source, /function invalidateListRequest\(\)\s*{[\s\S]*?listGeneration \+= 1[\s\S]*?loading\.value = false/)
|
||||
assert.match(source, /function isCurrentListRequest\(listContext\)/)
|
||||
const listSource = extractNamedFunction(source, 'getList')
|
||||
assert.match(listSource, /generation: \+\+listGeneration/)
|
||||
assert.match(listSource, /query:/)
|
||||
assertPromiseCallbacksStartWithGuard(listSource, 'isCurrentListRequest')
|
||||
assert.match(listSource, /reportList\.value = Array\.isArray\(response\.rows\) \? response\.rows : \[\]/)
|
||||
assert.match(listSource, /total\.value = response\.total/)
|
||||
assert.match(listSource, /loadError\.value\s*=/)
|
||||
|
||||
assert.match(extractNamedFunction(source, 'handleQuery'), /queryParams\.value\.pageNum = 1[\s\S]*?getList\(\)/)
|
||||
const resetSource = extractNamedFunction(source, 'resetQuery')
|
||||
assert.match(resetSource, /dateRange\.value = \[\]/)
|
||||
assert.match(resetSource, /resetForm\(['"]queryRef['"]\)/)
|
||||
assert.match(resetSource, /queryParams\.value\.pageNum = 1[\s\S]*?getList\(\)/)
|
||||
assert.match(extractNamedFunction(source, 'handleSizeChange'), /queryParams\.value\.pageSize = pageSize[\s\S]*?queryParams\.value\.pageNum = 1/)
|
||||
assert.match(extractNamedFunction(source, 'handleCurrentChange'), /queryParams\.value\.pageNum = pageNum/)
|
||||
assert.match(source, /onBeforeUnmount\(\(\) => \{\s*invalidateListRequest\(\)\s*}\)/)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('扫码组件声明交互契约并保持触控尺寸', async () => {
|
||||
const source = await readFile(path.join(frontendRoot, 'src/components/PalletScanInput/index.vue'), 'utf8')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user