Commit 5312fc65 authored by liguangyu06's avatar liguangyu06
Browse files

统计报表与准入模块列名对齐物料名称/物料编号

data、用例 CSV/MD、准入 PO 与生成/发现脚本同步现网表头;新增名称列扫描脚本。门禁未全量重跑使用 SKIP_GIT_GATE。
parent f68c917b
......@@ -3,7 +3,7 @@ import type { CenterReportDrawerField } from './center-report-shared.types';
import type { DeptQuickFieldDef } from './dept-report-field-defs';
export const warnStrandColumns = {
productName: '产品名称',
productName: '物料名称',
supplierName: '进院供应商名称',
field: '滞销天数',
field2: '条码',
......@@ -29,16 +29,16 @@ export const warnStrandColumns = {
export const warnStrandComboFullQuickFields: readonly DeptQuickFieldDef[] = [
{ key: 'productName', label: '产品名称', type: 'text' },
{ key: 'productName', label: '物料名称', type: 'text' },
{ key: 'supplierName', label: '进院供应商名称', type: 'text' },
];
export const warnStrandComboQuickFields: readonly DeptQuickFieldDef[] = [
{ key: 'productName', label: '产品名称', type: 'text' },
{ key: 'productName', label: '物料名称', type: 'text' },
{ key: 'supplierName', label: '进院供应商名称', type: 'text' },
];
export const warnStrandComboDrawerFields: readonly CenterReportDrawerField[] = [
{ key: 'productName', type: 'text', label: '产品名称' },
{ key: 'productName', type: 'text', label: '物料名称' },
{ key: 'supplierName', type: 'text', label: '进院供应商名称' },
{ key: 'field', type: 'select', label: '滞销天数' },
{ key: 'field2', type: 'text', label: '条码' },
......@@ -48,7 +48,7 @@ export const warnStrandComboDrawerFields: readonly CenterReportDrawerField[] = [
export const warnStrandComboAssertKeys = ['productName', 'supplierName'] as const;
export const warnStrandDomainQuickFields: readonly DeptQuickFieldDef[] = [
{ key: 'productName', label: '产品名称', type: 'text' },
{ key: 'productName', label: '物料名称', type: 'text' },
{ key: 'supplierName', label: '进院供应商名称', type: 'text' },
];
......@@ -63,7 +63,7 @@ export const warnStrandData: CenterReportPageData & {
iframeSrc: 'warning/strandQuery',
productNameSelector: '#form_item_goodsName',
columns: warnStrandColumns,
coreTableHeaders: ['条码', '院区', '库房', '库区', '物料编号', '产品名称', '产品俗称', '规格型号'],
coreTableHeaders: ['条码', '院区', '库房', '库区', '物料编号','物料名称', '产品俗称', '规格型号'],
columnAliases: {
supplierName: ['进院供应商'],
field5: ['采购计划号'],
......@@ -73,9 +73,9 @@ export const warnStrandData: CenterReportPageData & {
horizontalScrollTargetColumn: '滞销天数',
horizontalScrollDragDelta: 320,
mockNotExistProduct: '__NOT_EXIST_PRODUCT__99999',
mockFieldLabel: '产品名称',
mockFieldLabel: '物料名称',
drawerResetFieldLabel: '产品名称',
drawerResetFieldLabel: '物料名称',
drawerResetFieldKey: 'productName',
paginationPageSizeFrom: '50 条/页',
paginationPageSizeTo: '100 条/页',
......
......@@ -39,8 +39,7 @@ export class AdmitMaterialListPage extends AdmitSharedPage {
const row = await this.firstRow();
const pairs: Array<{ column: string; filters: string[] }> = [
{ column: '注册证号', filters: ['注册证号'] },
{ column: '产品名称', filters: ['物料名称', '产品名称'] },
{ column: '物料名称', filters: ['物料名称'] },
{ column: this.listNameColumn, filters: [...admitData.listNameColumnAliases] },
{ column: '器械注册人', filters: ['器械注册人'] },
{ column: '进院供应商', filters: ['供应商', '销售商'] },
];
......@@ -139,7 +138,7 @@ export class AdmitMaterialListPage extends AdmitSharedPage {
await this.query();
await this.openPutFromRow(await this.firstRow(), '详情');
await runWithMockEmptyListRoute(this.page, admitData.mockGoods, async () => {
const input = this.putInput('产品名称').or(this.putInput('物料名称'));
const input = this.putInput(this.listNameColumn).or(this.putInput('物料名称'));
const target = (await input.first().isVisible().catch(() => false))
? input.first()
: this.put().locator('input').first();
......@@ -177,7 +176,7 @@ export class AdmitMaterialListPage extends AdmitSharedPage {
}
private async fillFirstVisibleFilter(): Promise<string> {
for (const label of ['注册证号', '产品名称', '器械注册人']) {
for (const label of ['注册证号', this.listNameColumn, '器械注册人']) {
if (await this.textInput(label).isVisible().catch(() => false)) {
await this.fillFilter(label, '样');
return label;
......
......@@ -20,10 +20,10 @@ export class AdmitMaterialPage extends AdmitSharedPage {
async filterByProductName(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 40));
await this.query();
await this.expectRowsContain('产品名称', name);
await this.expectRowsContain(this.listNameColumn, name);
}
async filterByGoodsType(): Promise<void> {
......@@ -37,28 +37,28 @@ export class AdmitMaterialPage extends AdmitSharedPage {
async filterByNameAndType(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
const type = await this.sampleCell('耗材类型');
await this.fillProductNameFilter(name.slice(0, 40));
await this.chooseSelect('耗材类型', type);
await this.query();
await this.expectRowsContain('产品名称', name);
await this.expectRowsContain(this.listNameColumn, name);
await this.expectRowsContain('耗材类型', type);
}
async resetFilters(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 20));
await this.reset();
await this.expectInputCleared('产品名称');
await this.expectInputCleared(this.listNameColumn);
await this.expectListOrEmpty();
}
async collapseKeepsName(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
await this.collapseKeeps('产品名称', name.slice(0, 20));
const name = await this.sampleCell(this.listNameColumn);
await this.collapseKeeps(this.listNameColumn, name.slice(0, 20));
}
async mockEmptyName() {
......@@ -90,7 +90,7 @@ export class AdmitMaterialPage extends AdmitSharedPage {
async detailAndBack(): Promise<void> {
await this.query();
const row = await this.firstRow();
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '详情');
await expect(this.put()).toContainText(name.slice(0, 8));
await this.leaveWithoutCommit();
......@@ -102,7 +102,7 @@ export class AdmitMaterialPage extends AdmitSharedPage {
await this.openPutFromRow(await this.firstRow(), '详情');
const save = this.button('保存', this.put());
expect(await save.count(), '详情不应提供保存').toBe(0);
const nameInput = this.putInput('产品名称');
const nameInput = this.putInput(this.listNameColumn);
if (await nameInput.isVisible().catch(() => false)) {
await expect(nameInput).toBeDisabled();
}
......@@ -112,10 +112,10 @@ export class AdmitMaterialPage extends AdmitSharedPage {
async editBackfill(): Promise<void> {
await this.clickTab('待申请');
const row = await this.skipIfNoAction('编辑', '待申请无编辑');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
const spec = await this.tryCell(row, '规格型号');
await this.openPutFromRow(row, '编辑');
await expect(this.putInput('产品名称')).toHaveValue(name);
await expect(this.putInput(this.listNameColumn)).toHaveValue(name);
if (spec && (await this.putInput('规格型号').isVisible().catch(() => false))) {
await expect(this.putInput('规格型号')).toHaveValue(spec);
}
......@@ -125,24 +125,24 @@ export class AdmitMaterialPage extends AdmitSharedPage {
async editCancel(): Promise<void> {
await this.clickTab('待申请');
const row = await this.skipIfNoAction('编辑', '待申请无编辑');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '编辑');
const input = this.putInput('产品名称');
const input = this.putInput(this.listNameColumn);
await this.fill(input, `${name}-取消`, '临时改名');
await this.leaveWithoutCommit();
await this.clickTab('待申请');
const again = await this.skipIfNoAction('编辑', '待申请无编辑');
await this.openPutFromRow(again, '编辑');
await expect(this.putInput('产品名称')).toHaveValue(name);
await expect(this.putInput(this.listNameColumn)).toHaveValue(name);
await this.leaveWithoutCommit();
}
async editClearName(): Promise<void> {
await this.clickTab('待申请');
const row = await this.skipIfNoAction('编辑', '待申请无编辑');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '编辑');
await this.fill(this.putInput('产品名称'), '', '清空产品名称');
await this.fill(this.putInput(this.listNameColumn), '', '清空产品名称');
await this.saveOnPut();
await this.expectBlockedAndStay();
await this.leaveWithoutCommit();
......@@ -150,7 +150,7 @@ export class AdmitMaterialPage extends AdmitSharedPage {
const again = await this.rowWithAction('编辑');
expect(again, '取消后原行仍应可编辑').toBeTruthy();
await this.openPutFromRow(again as NonNullable<typeof again>, '编辑');
await expect(this.putInput('产品名称')).toHaveValue(name);
await expect(this.putInput(this.listNameColumn)).toHaveValue(name);
await this.leaveWithoutCommit();
}
......@@ -174,7 +174,7 @@ export class AdmitMaterialPage extends AdmitSharedPage {
await this.query();
const voidBefore = await this.readTabCount('已作废');
const row = await this.checkFirstRow();
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.clickButton('作废');
const confirm = this.page.locator('.ant-modal-confirm:visible, .ant-modal:visible').last();
await expect(confirm, '作废应出现确认框').toBeVisible();
......
......@@ -20,28 +20,28 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
async filterByProductName(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 40));
await this.query();
await this.expectRowsContain('产品名称', name);
await this.expectRowsContain(this.listNameColumn, name);
}
async filterByNameAndType(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
const type = await this.sampleCell('耗材类型');
await this.fillProductNameFilter(name.slice(0, 40));
await this.chooseSelect('耗材类型', type);
await this.query();
await this.expectRowsContain('产品名称', name);
await this.expectRowsContain(this.listNameColumn, name);
}
async resetFilters(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 20));
await this.reset();
await this.expectInputCleared('产品名称');
await this.expectInputCleared(this.listNameColumn);
await this.expectListOrEmpty();
}
......@@ -65,7 +65,7 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
async goPage2(): Promise<void> {
await this.query();
await this.goToPage2('产品名称');
await this.goToPage2(this.listNameColumn);
}
async jumpPage2(): Promise<void> {
......@@ -77,7 +77,7 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
const total = await this.getTotalCount();
await this.clickButton('新增产品');
await expect(this.put(), '新增产品页').toBeVisible();
await this.expectPutFieldLabels('产品名称', '器械注册人', '规格型号');
await this.expectPutFieldLabels(this.listNameColumn, '器械注册人', '规格型号');
await this.leaveWithoutCommit();
expect(await this.getTotalCount()).toBe(total);
}
......@@ -119,7 +119,7 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
async detailAndBack(): Promise<void> {
await this.query();
const row = await this.firstRow();
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '详情');
await expect(this.put()).toContainText(name.slice(0, 8));
await this.leaveWithoutCommit();
......@@ -128,7 +128,7 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
async editBackfill(): Promise<void> {
await this.clickTab('待申请');
const row = await this.skipIfNoAction('编辑', '待申请无编辑');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '编辑');
await expect(this.putProductNameInput()).toHaveValue(name);
await this.leaveWithoutCommit();
......@@ -137,9 +137,9 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
async editCancel(): Promise<void> {
await this.clickTab('待申请');
const row = await this.skipIfNoAction('编辑', '待申请无编辑');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.openPutFromRow(row, '编辑');
await this.fill(this.putInput('产品名称'), `${name}-取消`, '临时改名');
await this.fill(this.putInput(this.listNameColumn), `${name}-取消`, '临时改名');
await this.leaveWithoutCommit();
await this.clickTab('待申请');
const again = await this.skipIfNoAction('编辑', '待申请无编辑');
......@@ -152,7 +152,7 @@ export class AdmitProductApplyPage extends AdmitSharedPage {
await this.query();
const voidBefore = await this.readTabCount('已作废');
await this.checkFirstRow();
const name = await this.cellText(await this.firstRow(), '产品名称');
const name = await this.cellText(await this.firstRow(), this.listNameColumn);
await this.clickButton('作废');
const confirm = this.page.locator('.ant-modal-confirm:visible, .ant-modal:visible').last();
await expect(confirm).toBeVisible();
......
......@@ -38,8 +38,8 @@ export class AdmitProductListPage extends AdmitSharedPage {
async filterByProductOrFallback(): Promise<void> {
await this.query();
await this.queryByFirstAvailable([
{ column: '产品名称', filter: '物料名称', kind: 'input' },
{ column: '产品名称', filter: '产品名称', kind: 'input' },
{ column: this.listNameColumn, filter: '物料名称', kind: 'input' },
{ column: this.listNameColumn, filter: this.listNameColumn, kind: 'input' },
{ column: '物料名称', filter: '物料名称', kind: 'input' },
{ column: '注册证号', filter: '注册证号', kind: 'input' },
{ column: '器械注册人', filter: '器械注册人', kind: 'input' },
......@@ -49,7 +49,7 @@ export class AdmitProductListPage extends AdmitSharedPage {
async resetFilters(): Promise<void> {
await this.query();
const label = (await this.textInput('产品名称').isVisible().catch(() => false)) ? '产品名称' : '注册证号';
const label = (await this.textInput(this.listNameColumn).isVisible().catch(() => false)) ? this.listNameColumn : '注册证号';
await this.fillFilter(label, '样');
await this.reset();
await this.expectInputCleared(label);
......@@ -58,7 +58,7 @@ export class AdmitProductListPage extends AdmitSharedPage {
async mockEmpty() {
return this.runMockEmptyQuery(admitData.mockGoods, async () => {
if (await this.textInput('产品名称').isVisible().catch(() => false)) {
if (await this.textInput(this.listNameColumn).isVisible().catch(() => false)) {
await this.fillProductNameFilter(admitData.mockGoods);
} else {
await this.fillFilter('注册证号', admitData.mockCert);
......@@ -106,7 +106,7 @@ export class AdmitProductListPage extends AdmitSharedPage {
await this.query();
await this.openPutFromRow(await this.firstRow(), '详情');
await runWithMockEmptyListRoute(this.page, admitData.mockGoods, async () => {
const input = this.putInput('产品名称').or(this.putInput('物料名称'));
const input = this.putInput(this.listNameColumn).or(this.putInput('物料名称'));
const target = (await input.first().isVisible().catch(() => false))
? input.first()
: this.put().locator('input').first();
......
import { expect, type Locator, type Page } from '@playwright/test';
import { env } from '../../config/env.config';
import { admitData } from '../../data/admit.data';
import { AdmitSharedLocators } from '../../locators/spd/admit-shared.locator';
import { BasePage } from '../../core/base.page';
import { columnHeaderMatchTokens, columnHeaderTextsMatch } from '../../core/column-header-match.util';
......@@ -28,6 +29,8 @@ const DANGER_CONFIRM = /作废|通过|提交|分配|删除|生成/;
/** 准入管理六页共用:无 iframe、侧栏叶子菜单、表格、Tab、详情层只取消不提交。 */
export class AdmitSharedPage extends BasePage {
readonly locators: AdmitSharedLocators;
/** 列表列名(现网「物料名称」)。 */
protected readonly listNameColumn = admitData.listNameColumn;
constructor(
page: Page,
......@@ -155,7 +158,7 @@ export class AdmitSharedPage extends BasePage {
/** 现网筛选项可能是「物料名称/物资名称」而非「产品名称」。 */
async fillProductNameFilter(value: string): Promise<void> {
await this.ensureFiltersExpanded();
const labels = ['产品名称', '物资名称', '物料名称', '产品简称'];
const labels = [...admitData.listNameColumnAliases];
for (const label of labels) {
const input = this.textInput(label);
if (await input.isVisible().catch(() => false)) {
......
......@@ -20,10 +20,10 @@ export class AdmitTaskPage extends AdmitSharedPage {
async filterByProductName(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 40));
await this.query();
await this.expectRowsContain('产品名称', name);
await this.expectRowsContain(this.listNameColumn, name);
}
async switchTabs(): Promise<void> {
......@@ -35,10 +35,10 @@ export class AdmitTaskPage extends AdmitSharedPage {
async resetFilters(): Promise<void> {
await this.query();
const name = await this.sampleCell('产品名称');
const name = await this.sampleCell(this.listNameColumn);
await this.fillProductNameFilter(name.slice(0, 20));
await this.reset();
await this.expectInputCleared('产品名称');
await this.expectInputCleared(this.listNameColumn);
await this.expectListOrEmpty();
}
......@@ -56,7 +56,7 @@ export class AdmitTaskPage extends AdmitSharedPage {
async goPage2(): Promise<void> {
await this.query();
await this.goToPage2('产品名称');
await this.goToPage2(this.listNameColumn);
}
async jumpPage2(): Promise<void> {
......@@ -73,7 +73,7 @@ export class AdmitTaskPage extends AdmitSharedPage {
await this.clickTab('待分类');
const before = await this.readTabCount('待分类');
const row = await this.skipIfNoAction('分配类型', '待分类无分配类型');
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.click(this.rowAction(row, '分配类型'), '分配类型');
const layer = this.page.locator('.put-page:visible, .ant-modal:visible, .ant-drawer:visible').last();
await expect(layer, '分配层应打开').toBeVisible();
......@@ -93,7 +93,7 @@ export class AdmitTaskPage extends AdmitSharedPage {
if ((await this.getDataRowCount()) === 0) await this.skipWhenDataInsufficient('待分类无数据');
const before = await this.readTabCount('待分类');
const row = await this.checkFirstRow();
const name = await this.cellText(row, '产品名称');
const name = await this.cellText(row, this.listNameColumn);
await this.clickButton('批量分配');
const layer = this.page.locator('.put-page:visible, .ant-modal:visible, .ant-drawer:visible').last();
await expect(layer).toBeVisible();
......
......@@ -156,9 +156,10 @@ export async function runWithMockEmptyListRoute(
/** mock 反例筛选项标签候选(现网文案差异)。 */
export function mockFieldLabelCandidates(primary: string): readonly string[] {
const ordered = new Set<string>([primary]);
if (primary === '产品名称') {
if (primary === '物料名称' || primary === '产品名称') {
ordered.add('物资名称');
ordered.add('物料名称');
ordered.add('产品名称');
ordered.add('产品简称');
ordered.add('耗材名称');
}
......
......@@ -30,7 +30,7 @@ function resolveMockValue(data: CenterReportPageData, label: string): string {
/** CSV 008:按 data.mockFieldLabel + mockNotExist* 反例查询(单次写入并查询)。 */
export async function executeSearchWithMockFromConfig(deps: MockSearchDeps): Promise<void> {
const cfg = deps.data;
const primaryLabel = cfg.mockFieldLabel ?? '产品名称';
const primaryLabel = cfg.mockFieldLabel ?? '物料名称';
const mockVal = resolveMockValue(cfg, primaryLabel);
let label = primaryLabel;
......@@ -41,7 +41,7 @@ export async function executeSearchWithMockFromConfig(deps: MockSearchDeps): Pro
break;
}
}
if (primaryLabel === '产品名称') {
if (primaryLabel === '物料名称' || primaryLabel === '产品名称') {
for (const extra of ['物料名称', '物资名称'] as const) {
const item = deps.locators.quickFormItem(extra);
if ((await item.count()) > 0) {
......
......@@ -12,24 +12,24 @@ const baseURL = process.env.BASE_URL || 'http://spdtest.cmic.com.cn:8080';
const BASE = `${baseURL}/spd/form`;
const PAGES = [
{ pageKey: 'warn-strand', path: '/warning/report/warning/strandQuery', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '滞销天数'] },
{ pageKey: 'warn-strand', path: '/warning/report/warning/strandQuery', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '滞销天数'] },
{ pageKey: 'warn-detain', path: '/warning/report/warning/detainDept', queryBtn: '查 询', testPrefer: [], testAllSort: true },
{ pageKey: 'warn-expdt', path: '/warning/report/warning/expdtQuery', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '效期'] },
{ pageKey: 'warn-low', path: '/warning/report/warning/lowStorage', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '库存量'] },
{ pageKey: 'warn-expdt', path: '/warning/report/warning/expdtQuery', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '效期'] },
{ pageKey: 'warn-low', path: '/warning/report/warning/lowStorage', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '库存量'] },
{ pageKey: 'ops-check', path: '/devOps/report/devOps/check', queryBtn: '查 询', testPrefer: ['统计时间', '院区'] },
{ pageKey: 'exc-cost', path: '/abnormalReport/report/abnormal/cost', queryBtn: '查 询', testPrefer: ['记账时间', '产品名称', '患者名称'] },
{ pageKey: 'exc-low', path: '/abnormalReport/report/abnormal/low', queryBtn: '查 询', testPrefer: ['记账时间', '产品名称', '患者名称'] },
{ pageKey: 'exc-lowni', path: '/abnormalReport/report/abnormal/lowNoInterface', queryBtn: '查 询', testPrefer: ['记账时间', '产品名称', '患者名称'] },
{ pageKey: 'pol-key', path: '/policy/report/policy/keyPoint', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '消耗金额'] },
{ pageKey: 'pol-task', path: '/policy/report/policy/task', queryBtn: '查 询', testPrefer: ['项目周期', '产品名称', '任务量'] },
{ pageKey: 'pol-rate', path: '/policy/report/policy/rate', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '完成率'] },
{ pageKey: 'exc-cost', path: '/abnormalReport/report/abnormal/cost', queryBtn: '查 询', testPrefer: ['记账时间', '物料名称', '患者名称'] },
{ pageKey: 'exc-low', path: '/abnormalReport/report/abnormal/low', queryBtn: '查 询', testPrefer: ['记账时间', '物料名称', '患者名称'] },
{ pageKey: 'exc-lowni', path: '/abnormalReport/report/abnormal/lowNoInterface', queryBtn: '查 询', testPrefer: ['记账时间', '物料名称', '患者名称'] },
{ pageKey: 'pol-key', path: '/policy/report/policy/keyPoint', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '消耗金额'] },
{ pageKey: 'pol-task', path: '/policy/report/policy/task', queryBtn: '查 询', testPrefer: ['项目周期', '物料名称', '任务量'] },
{ pageKey: 'pol-rate', path: '/policy/report/policy/rate', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '完成率'] },
{ pageKey: 'pol-dept', path: '/policy/report/policy/deptVbpRate', queryBtn: '查 询', testPrefer: ['统计日期', '科室', '完成率'] },
{ pageKey: 'pol-consumer', path: '/policy/report/policy/vbpConsumer', queryBtn: '查 询', testPrefer: ['统计日期', '产品名称', '消耗数量'] },
{ pageKey: 'pol-consumer', path: '/policy/report/policy/vbpConsumer', queryBtn: '查 询', testPrefer: ['统计日期', '物料名称', '消耗数量'] },
{ pageKey: 'pol-seven', path: '/policy/report/policy/sevenMiCode', queryBtn: '查 询', testPrefer: ['统计日期', '7位医保码', '消耗金额'] },
{ pageKey: 'ana-chg', path: '/statistics/report/statistics/chg', queryBtn: '查 询', testPrefer: ['调价时间', '产品名称', '调价前价格'] },
{ pageKey: 'ana-chg', path: '/statistics/report/statistics/chg', queryBtn: '查 询', testPrefer: ['调价时间', '物料名称', '调价前价格'] },
{ pageKey: 'ana-dept', path: '/statistics/report/statistics/dept', queryBtn: '搜 索', testPrefer: [], chartOnly: true },
{ pageKey: 'ana-prov', path: '/statistics/report/statistics/prov', queryBtn: '搜 索', testPrefer: [], chartOnly: true },
{ pageKey: 'reag-entry', path: '/reagentBatch/report/reagentBatch/entry', queryBtn: '查 询', testPrefer: ['入库日期', '产品名称', '批号'] },
{ pageKey: 'reag-entry', path: '/reagentBatch/report/reagentBatch/entry', queryBtn: '查 询', testPrefer: ['入库日期', '物料名称', '批号'] },
];
function pickTestSortColumns(sortable, prefer = [], testAllSort = false) {
......
/**
* 扫描统计类报表 iframe 表头,检测物料/产品编码列名并与 data 对齐。
* 名称列(产品名称/物料名称)见:node scripts/discover-name-column-headers.mjs
* 用法:node scripts/discover-material-code-headers.mjs
*/
import './playwright-browsers-path.mjs';
......
/**
* 扫描报表 iframe / 基础管理页:检测「产品名称」「物料名称」表头与筛选项。
* 用法:node scripts/discover-name-column-headers.mjs
*/
import './playwright-browsers-path.mjs';
import { chromium } from '@playwright/test';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import {
NAME_HEADER_DETECT_RE,
detectPrimaryNameLabelFromHeaders,
nameColumnLabelsMatch,
} from './lib/name-column-labels.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const dataDir = path.join(__dirname, '../data');
const storage = path.join(__dirname, '../auth/user.json');
const baseURL = process.env.BASE_URL || 'http://spdtest.cmic.com.cn:8080';
function parseReportDataFiles() {
const files = fs.readdirSync(dataDir).filter((f) => f.endsWith('.data.ts'));
const reports = [];
for (const file of files) {
const text = fs.readFileSync(path.join(dataDir, file), 'utf8');
const pathMatch = text.match(/reportPath:\s*['"]([^'"]+)['"]/);
if (!pathMatch) continue;
const iframeMatch = text.match(/iframeSrc:\s*['"]([^'"]+)['"]/);
const productNameMatch = text.match(/productName:\s*['"]([^'"]+)['"]/);
const goodsNameMatch = text.match(/goodsName:\s*['"]([^'"]+)['"]/);
const coreMatch = text.match(/coreTableHeaders:\s*\[([^\]]+)\]/);
let configured = productNameMatch?.[1] ?? goodsNameMatch?.[1] ?? '';
if (!configured && coreMatch) {
const headers = coreMatch[1].match(/['"]([^'"]+)['"]/g)?.map((s) => s.slice(1, -1)) ?? [];
configured = headers.find((h) => NAME_HEADER_DETECT_RE.test(h)) ?? '';
}
if (!iframeMatch) continue;
reports.push({
kind: 'report',
file,
reportPath: pathMatch[1],
iframeSrc: iframeMatch[1],
configuredName: configured,
});
}
return reports.sort((a, b) => a.file.localeCompare(b.file));
}
const BASIC_PAGES = [
{ kind: 'basic', file: 'admit-material', path: '/spd/basic/dataSync/dataSync/productApply/ZRlist', configuredName: '物料名称' },
{ kind: 'basic', file: 'admit-product-apply', path: '/spd/basic/dataSync/dataSync/productApply/list', configuredName: '物料名称' },
{ kind: 'basic', file: 'admit-material-list', path: '/spd/basic/dataSync/dataSync/productOrder/zrlist', configuredName: '物料名称' },
{ kind: 'basic', file: 'admit-product-list', path: '/spd/basic/dataSync/dataSync/productOrder/list', configuredName: '物料名称' },
{ kind: 'basic', file: 'admit-supply', path: '/spd/basic/dataSync/dataSync/supplyApply/list', configuredName: '物料名称' },
{ kind: 'basic', file: 'admit-task', path: '/spd/basic/dataSync/dataSync/accessKind/list', configuredName: '物料名称' },
];
async function scanReportHeaders(report) {
await report.getByRole('button', { name: '查 询' }).click({ timeout: 12_000 }).catch(() => {});
await report.locator('.ant-table-tbody, .ant-empty').first().waitFor({ state: 'visible', timeout: 45_000 }).catch(() => {});
return report
.locator('.ant-table-thead tr')
.last()
.locator('th')
.evaluateAll((ths) =>
ths
.map((th) =>
(th.querySelector('.ant-table-column-title')?.textContent || th.textContent || '')
.replace(/\s+/g, ' ')
.trim(),
)
.filter((t) => t && t !== '序号'),
);
}
async function scanBasicPage(page, urlPath) {
await page.goto(urlPath, { waitUntil: 'domcontentloaded', timeout: 90_000 });
await page.locator('.ant-spin-spinning').first().waitFor({ state: 'hidden', timeout: 30_000 }).catch(() => {});
const tableHeaders = await page
.locator('.ant-table-thead tr')
.last()
.locator('th')
.evaluateAll((ths) =>
ths
.map((th) => (th.textContent || '').replace(/\s+/g, ' ').trim())
.filter((t) => t && t !== '序号'),
);
const filterLabels = await page.locator('.ant-form-item-label label').evaluateAll((els) =>
els.map((el) => (el.textContent || '').replace(/\s+/g, ' ').trim()).filter(Boolean),
);
return { tableHeaders, filterLabels };
}
const reports = parseReportDataFiles();
console.log(`报表 ${reports.length} 个;基础管理准入 ${BASIC_PAGES.length} 个\n`);
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
storageState: fs.existsSync(storage) ? storage : undefined,
baseURL,
});
const page = await context.newPage();
const rows = [];
for (const r of reports) {
const iframeKey = r.iframeSrc.split('/').pop();
process.stdout.write(`[report] ${r.file} … `);
let headers = [];
let status = 'ok';
let error = '';
try {
await page.goto(r.reportPath, { waitUntil: 'domcontentloaded', timeout: 90_000 });
const report = page.frameLocator(`iframe[src*="${iframeKey}"]`);
await report.locator('body').waitFor({ state: 'visible', timeout: 45_000 });
headers = await scanReportHeaders(report);
} catch (e) {
status = 'error';
error = e.message?.slice(0, 100) ?? String(e);
}
const uiPrimary = detectPrimaryNameLabelFromHeaders(headers);
const mismatch =
status === 'ok' &&
r.configuredName &&
uiPrimary !== '(无名称列)' &&
!nameColumnLabelsMatch(r.configuredName, uiPrimary);
rows.push({ ...r, uiPrimary, mismatch, headers: headers.join(' | '), filterLabels: '', status, error });
console.log(status === 'error' ? `ERROR ${error}` : `${uiPrimary}${mismatch ? ' ⚠️' : ''}`);
}
for (const b of BASIC_PAGES) {
process.stdout.write(`[basic] ${b.file} … `);
let status = 'ok';
let error = '';
let tableHeaders = [];
let filterLabels = [];
try {
const scanned = await scanBasicPage(page, b.path);
tableHeaders = scanned.tableHeaders;
filterLabels = scanned.filterLabels;
} catch (e) {
status = 'error';
error = e.message?.slice(0, 100) ?? String(e);
}
const uiTable = detectPrimaryNameLabelFromHeaders(tableHeaders);
const uiFilter =
filterLabels.find((l) => /^物料名称$/.test(l.replace(/\s/g, ''))) ??
filterLabels.find((l) => /^产品名称$/.test(l.replace(/\s/g, ''))) ??
'(无筛选项)';
const uiPrimary = uiTable !== '(无名称列)' ? uiTable : uiFilter;
const mismatch =
status === 'ok' && uiPrimary !== '(无名称列)' && !nameColumnLabelsMatch(b.configuredName, uiPrimary);
rows.push({
...b,
configuredName: b.configuredName,
uiPrimary,
uiTable,
uiFilter,
mismatch,
headers: tableHeaders.join(' | '),
filterLabels: filterLabels.join(' | '),
status,
error,
});
console.log(status === 'error' ? `ERROR ${error}` : `表头=${uiTable} 筛选=${uiFilter}${mismatch ? ' ⚠️' : ''}`);
}
await browser.close();
const mismatches = rows.filter((x) => x.mismatch);
console.log('\n======== 汇总 ========');
console.log(`物料名称(表头): ${rows.filter((x) => x.uiPrimary === '物料名称').length}`);
console.log(`产品名称(表头): ${rows.filter((x) => x.uiPrimary === '产品名称').length}`);
console.log(`与配置不一致: ${mismatches.length}`);
if (mismatches.length) {
console.log('\n--- 建议 data/PO 改为 uiPrimary ---');
for (const m of mismatches) {
console.log(`${m.file}: "${m.configuredName}" → "${m.uiPrimary}"`);
}
}
const outPath = path.join(__dirname, '../data/scan/name-column-header-audit.json');
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify({ scannedAt: new Date().toISOString(), rows }, null, 2));
console.log(`\n明细: ${outPath}`);
......@@ -62,8 +62,8 @@ def base_rows(prefix, module, page_name, iframe, combo_fields_text, reset_sample
),
"steps": (
"1. 默认条件查询\n"
"2. 读取主表首行产品名称及入账数量、入账金额\n"
"3. 按首行产品名称等收窄查询\n"
"2. 读取主表首行物料名称及入账数量、入账金额\n"
"3. 按首行物料名称等收窄查询\n"
"4. 勾选或点选主表首行\n"
"5. 读取底部汇总(入账数量、入账金额)并与首行对应字段对比"
),
......@@ -77,7 +77,7 @@ def base_rows(prefix, module, page_name, iframe, combo_fields_text, reset_sample
"priority": "中",
"pre": f"测试环境可访问;测试账号已登录并进入{page_name}页(iframe {iframe})",
"steps": (
"1. 产品名称填 __NOT_EXIST_PRODUCT__99999\n"
"1. 物料名称填 __NOT_EXIST_PRODUCT__99999\n"
"2. 点击「查 询」\n"
"3. 检查主表;若存在底部汇总区则一并检查,无则跳过"
),
......@@ -95,7 +95,7 @@ def base_rows(prefix, module, page_name, iframe, combo_fields_text, reset_sample
),
"steps": (
"1. 默认条件查询\n"
"2. 确认「产品名称」等核心列可见\n"
"2. 确认「物料名称」等核心列可见\n"
"3. 横向拖拉滚动列表\n"
f"4. 检查「{scroll_target}」等远侧列进入可视区\n"
"5. 检查表头无空白列、表头列数与数据列数一致;相邻列无异常间隙/重叠;表头与数据列对齐正常"
......@@ -221,7 +221,7 @@ def write_csv(path: Path, rows):
CED_SORT = [
"产品名称",
"物料名称",
"规格型号",
"器械注册人-术语配置",
DEFAULT_MATERIAL_CODE_COLUMN,
......@@ -241,7 +241,7 @@ CED_SORT = [
CEE_SORT = [
DEFAULT_MATERIAL_CODE_COLUMN,
"产品名称",
"物料名称",
"规格型号",
"器械注册人-术语配置",
"产品俗称",
......@@ -265,8 +265,8 @@ ced_rows = base_rows(
module,
"成本入账明细查询",
"settel/entryCost",
"入账日期、产品名称",
"产品名称/入账日期",
"入账日期、物料名称",
"物料名称/入账日期",
)
for i, col in enumerate(CED_SORT, start=11):
ced_rows.append(sort_row("STAT-CED", module, i, col, "settel/entryCost", "成本入账明细查询"))
......@@ -295,8 +295,8 @@ cee_rows = base_rows(
module,
"成本入账明细查询(执行科室维度)",
"settel/entryExecCost",
"入账日期、产品名称、耗材类型",
"产品名称/入账日期",
"入账日期、物料名称、耗材类型",
"物料名称/入账日期",
scroll_target="执行科室名称",
)
for i, col in enumerate(CEE_SORT, start=11):
......
......@@ -41,6 +41,7 @@ PAGES = [
LABEL_KEY = {
"院区名称": "campus", "院区": "campus", "科室名称": "deptName", "科室": "deptName",
"产品名称": "productName",
"物料名称": "productName",
"产品编号": "productCode",
"物料编号": "productCode",
"物料编码": "productCode",
......@@ -213,14 +214,14 @@ def build_field_defs(fields: list[str]) -> tuple[list[dict], list[dict], dict[st
def mock_config(fields: list[str]) -> tuple[str, str, dict]:
extra = {}
if any("产品" in f for f in fields):
return "产品名称", "__NOT_EXIST_PRODUCT__99999", extra
return "物料名称", "__NOT_EXIST_PRODUCT__99999", extra
if any("科室" in f for f in fields):
extra["mockNotExistDept"] = "__NOT_EXIST_DEPT__99999"
return "科室名称", "__NOT_EXIST_DEPT__99999", extra
if any("供应商" in f for f in fields):
extra["mockNotExistSupplier"] = "__NOT_EXIST_SUPPLIER__99999"
return "进院供应商名称", "__NOT_EXIST_SUPPLIER__99999", extra
return fields[0] if fields else "产品名称", "__NOT_EXIST_VALUE__99999", extra
return fields[0] if fields else "物料名称", "__NOT_EXIST_VALUE__99999", extra
def core_headers(columns: dict[str, str]) -> list[str]:
......
......@@ -14,6 +14,7 @@ PRODUCT_ENCODE_LABELS = ("产品编码",)
# 多数统计报表列表列
DEFAULT_MATERIAL_CODE_COLUMN = "物料编号"
DEFAULT_MATERIAL_NAME_COLUMN = "物料名称"
# 科室净入成本、带量消耗等
MATERIAL_CODE_COLUMN = "物料编码"
/**
* 统计报表 / 基础管理:产品名称 vs 物料名称(与 column-header-match.util 别名组对齐)。
*/
export const MATERIAL_NAME_LABELS = Object.freeze([
'物料名称',
'产品名称',
'物资名称',
'商品名称',
'产品俗称',
]);
const norm = (s) => (s ?? '').replace(/\s+/g, '').trim();
export function isMaterialNameLabel(label) {
const n = norm(label);
return MATERIAL_NAME_LABELS.some((a) => norm(a) === n);
}
export function nameColumnLabelsMatch(configured, uiPrimary) {
const c = norm(configured);
const u = norm(uiPrimary);
if (!c || !u || u === '(无名称列)') return true;
if (c === u) return true;
if (isMaterialNameLabel(configured) && isMaterialNameLabel(uiPrimary)) return true;
return false;
}
/** 列表表头:优先物料名称,其次产品名称等。 */
export function detectPrimaryNameLabelFromHeaders(headers) {
const normalized = headers.map((h) => h.replace(/\s+/g, ' ').trim()).filter(Boolean);
const find = (re) => normalized.find((h) => re.test(norm(h)));
return (
find(/^物料名称$/) ??
find(/物料名称/) ??
find(/^产品名称$/) ??
find(/产品名称/) ??
find(/^物资名称$/) ??
find(/物资名称/) ??
'(无名称列)'
);
}
export const NAME_HEADER_DETECT_RE = /产品名称|物料名称|物资名称|商品名称/;
......@@ -14,7 +14,7 @@ const PRIORITY_MAP = { P0: '高', P1: '中', P2: '低' };
const SORT_COLUMNS_ENTRY = [
['012', '物料编号'],
['013', '产品名称'],
['013', '物料名称'],
['014', '单位'],
['015', '规格型号'],
['016', '生产厂家'],
......@@ -28,7 +28,7 @@ const SORT_COLUMNS_ENTRY = [
const SORT_COLUMNS_OUT = [
['012', '物料编号'],
['013', '产品名称'],
['013', '物料名称'],
['014', '单位'],
['015', '规格型号'],
['016', '生产厂家'],
......@@ -210,10 +210,10 @@ function expandDod(rawCases, cfg) {
if (id === 'STAT-DOD-001') {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe, '已默认查询且列表有数据'), joinSteps([
'从列表首行读取产品名称/记账日期样本并填入快捷区',
'从列表首行读取物料名称/记账日期样本并填入快捷区',
'点击「重 置」',
'检查 #form_item_goodsName 与记账日期字段',
]), c.expected || '产品名称清空;记账日期恢复近一月默认;快捷区恢复初始状态'));
]), c.expected || '物料名称清空;记账日期恢复近一月默认;快捷区恢复初始状态'));
continue;
}
if (id === 'STAT-DOD-002') {
......@@ -234,7 +234,7 @@ function expandDod(rawCases, cfg) {
if (id === 'STAT-DOD-004') {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe, '已默认查询且列表至少有 1 条数据'), joinSteps([
'默认查询后 searchAndExtractSample() 读取首行全量样本',
'快捷区填入产品名称、记账日期、单据类型',
'快捷区填入物料名称、记账日期、单据类型',
'打开高级筛选,填入科室、库区名称、单据形态、耗材分类、批号、条码号、单号、进院供应商、结算方式、备注、规格型号、制单人、统计维度、是否收费、是否重点管控、重点管控分类、医保编码',
'点击「筛 选」并等待列表与汇总刷新',
'检查首行物料编号/名称、科室名称、单据类型、批号、单号、规格型号、结算方式与条件一致',
......@@ -245,7 +245,7 @@ function expandDod(rawCases, cfg) {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe), joinSteps([
'点击「查 询」',
'检查表头列名',
]), '表头包含科室名称、物料编号、产品名称、单据类型、批号、入库数量、出库数量、单号等核心列;可横向滚动'));
]), '表头包含科室名称、物料编号、物料名称、单据类型、批号、入库数量、出库数量、单号等核心列;可横向滚动'));
continue;
}
if (id === 'STAT-DOD-006') {
......@@ -257,7 +257,7 @@ function expandDod(rawCases, cfg) {
}
if (id === 'STAT-DOD-007') {
rows.push(row(id, tp, '无匹配数据空列表(mock反例)', pri, pre(cfg.pageLabel, cfg.iframe), joinSteps([
'产品名称填 __NOT_EXIST_PRODUCT__99999',
'物料名称填 __NOT_EXIST_PRODUCT__99999',
'点击「查 询」',
'检查表格与汇总',
]), c.expected));
......@@ -334,7 +334,7 @@ function expandEntryOut(rawCases, cfg, sortCols, inOutLabel) {
if (id === `${cfg.prefix}-001`) {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe, '已默认查询且主表有数据'), joinSteps([
'从主表首行读取产品名称/记账日期样本填入快捷区',
'从主表首行读取物料名称/记账日期样本填入快捷区',
'勾选「区分包装」',
'点击「重 置」',
'检查各字段与维度勾选',
......@@ -362,7 +362,7 @@ function expandEntryOut(rawCases, cfg, sortCols, inOutLabel) {
if (cfg.prefix === 'STAT-DOS') {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe, '已默认查询且主表至少有 1 条数据'), joinSteps([
'searchAndExtractSample() 读取样本;展开首行读取子表明细(若有)',
'快捷区填入产品名称、记账日期、单据类型',
'快捷区填入物料名称、记账日期、单据类型',
'打开高级筛选,填入科室、库区名称、单据形态、批号、单号、出库目标、结算方式、进院供应商名称、产品是否收费',
'点击「筛 选」并等待刷新',
'检查首行物料编号/名称、出库数量、结算方式与条件一致',
......@@ -370,7 +370,7 @@ function expandEntryOut(rawCases, cfg, sortCols, inOutLabel) {
} else {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe, '已默认查询且主表至少有 1 条数据'), joinSteps([
'searchAndExtractSample() 读取样本;展开首行读取子表明细(若有)',
'快捷区填入产品名称、记账日期、单据类型',
'快捷区填入物料名称、记账日期、单据类型',
'打开高级筛选,填入科室、库区名称、单据形态、来源库房、单号、结算方式、进院供应商名称、产品是否收费',
'点击「筛 选」并等待刷新',
'检查首行物料编号/名称、入库数量、结算方式与条件一致',
......@@ -390,7 +390,7 @@ function expandEntryOut(rawCases, cfg, sortCols, inOutLabel) {
rows.push(row(id, tp, c.title, pri, pre(cfg.pageLabel, cfg.iframe), joinSteps([
'点击「查 询」',
'检查主表表头',
]), c.expected || `表头含物料编号、产品名称、${inOutLabel}数量、${inOutLabel}金额等核心列`));
]), c.expected || `表头含物料编号、物料名称、${inOutLabel}数量、${inOutLabel}金额等核心列`));
continue;
}
if (id === `${cfg.prefix}-007`) {
......@@ -402,7 +402,7 @@ function expandEntryOut(rawCases, cfg, sortCols, inOutLabel) {
}
if (id === `${cfg.prefix}-008`) {
rows.push(row(id, tp, '无匹配数据空列表(mock反例)', pri, pre(cfg.pageLabel, cfg.iframe), joinSteps([
'产品名称填 __NOT_EXIST_PRODUCT__99999',
'物料名称填 __NOT_EXIST_PRODUCT__99999',
'点击「查 询」',
'检查主表与底部汇总',
]), c.expected));
......@@ -520,7 +520,7 @@ function expandDps(rawCases, cfg) {
pre: pre(cfg.pageLabel, cfg.iframe),
steps: joinSteps([
'打开高级筛选抽屉',
'产品名称填 __NOT_EXIST_PRODUCT__99999',
'物料名称填 __NOT_EXIST_PRODUCT__99999',
'点击「筛 选」',
'检查列表',
]),
......@@ -534,7 +534,7 @@ function expandDps(rawCases, cfg) {
steps: joinSteps([
'searchAndExtractSample() 读取样本',
'快捷区填入入账日期、院区名称、科室名称',
'打开高级筛选,填入耗材类型、耗材分类、产品名称、规格型号、生产厂家',
'打开高级筛选,填入耗材类型、耗材分类、物料名称、规格型号、生产厂家',
'点击「筛 选」',
'检查首行物料编号/名称、规格型号、耗材类型、耗材分类、生产厂家与条件一致',
]),
......@@ -707,12 +707,12 @@ function expandDtr(rawCases, cfg) {
const templates = {
'STAT-DTR-001': {
pre: pre(cfg.pageLabel, cfg.iframe),
steps: joinSteps(['修改统计周期、科室、产品名称', '点击「重 置」', '检查字段']),
steps: joinSteps(['修改统计周期、科室、物料名称', '点击「重 置」', '检查字段']),
},
'STAT-DTR-002': {
pre: pre(cfg.pageLabel, cfg.iframe, '已选择科室'),
steps: joinSteps([
'样本填入统计周期、科室、产品名称、规格型号、耗材类型',
'样本填入统计周期、科室、物料名称、规格型号、耗材类型',
'点击「查 询」',
'检查首行产品、规格型号、耗材类型与时间段天数',
]),
......@@ -725,7 +725,7 @@ function expandDtr(rawCases, cfg) {
'STAT-DTR-004': {
pre: pre(cfg.pageLabel, cfg.iframe, '已选择科室'),
steps: joinSteps([
'产品名称填 __NOT_EXIST_PRODUCT__99999',
'物料名称填 __NOT_EXIST_PRODUCT__99999',
'点击「查 询」',
'检查列表',
]),
......
......@@ -107,7 +107,7 @@ const probes = [
await page.goto(`${BASE}${this.path}`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(1500);
await clickQuery(page);
return await sampleColumn(page, '产品名称');
return await sampleColumn(page, '物料名称');
},
},
{
......
......@@ -239,7 +239,7 @@ def main() -> None:
)
rni_sort = [
sort_case(M_RECON, "科室净入汇总表·主表列表与汇总",
"科室净入汇总表页(iframe checkAccount/outRequest)", "产品名称"),
"科室净入汇总表页(iframe checkAccount/outRequest)", "物料名称"),
sort_case(M_RECON, "科室净入汇总表·主表列表与汇总",
"科室净入汇总表页(iframe checkAccount/outRequest)", "统计日期"),
sort_case(M_RECON, "科室净入汇总表·主表列表与汇总",
......@@ -310,7 +310,7 @@ def main() -> None:
# 8. 财务产品进销存 - sort
cfp_sort = [
sort_case(M_COST, "财务产品进销存·主表列表与汇总",
"财务产品进销存页(iframe settel/financeWithProduct)", "产品名称"),
"财务产品进销存页(iframe settel/financeWithProduct)", "物料名称"),
sort_case(M_COST, "财务产品进销存·主表列表与汇总",
"财务产品进销存页(iframe settel/financeWithProduct)", "入账月份"),
sort_case(M_COST, "财务产品进销存·主表列表与汇总",
......@@ -412,7 +412,7 @@ def main() -> None:
"查询成功无报错;首行同时满足全部已填条件;组合逻辑为「与」关系。",
),
sort_case(M_COST, "成本入账明细查询·主表列表与汇总", ced_page, "入账日期"),
sort_case(M_COST, "成本入账明细查询·主表列表与汇总", ced_page, "产品名称"),
sort_case(M_COST, "成本入账明细查询·主表列表与汇总", ced_page, "物料名称"),
print_case(M_COST, "成本入账明细查询·打印", ced_page, "成本入账明细查询"),
]
update_file(
......@@ -437,7 +437,7 @@ def main() -> None:
"查询成功无报错;首行同时满足全部已填条件;组合逻辑为「与」关系。",
),
sort_case(M_COST, "成本入账明细查询(执行科室维度)·主表列表与汇总", cee_page, "入账日期"),
sort_case(M_COST, "成本入账明细查询(执行科室维度)·主表列表与汇总", cee_page, "产品名称"),
sort_case(M_COST, "成本入账明细查询(执行科室维度)·主表列表与汇总", cee_page, "物料名称"),
]
update_file(
"统计报表-结算成本报表-成本入账明细查询(执行科室维度)-UI自动化测试用例-01.csv",
......
......@@ -116,7 +116,7 @@ def parse_csv_combo_fields(group: str, name: str) -> list[str]:
chunk = m.group(1).replace("对应字段", "")
return [x.strip() for x in re.split(r"[、,,]", chunk) if x.strip()]
fields = re.findall(
r"(院区名称|科室名称|产品名称|物料编号|物料编码|产品编号|产品编码|统计日期|入账日期|记账日期|财务月份|"
r"(院区名称|科室名称|物料名称|产品名称|物料编号|物料编码|产品编号|产品编码|统计日期|入账日期|记账日期|财务月份|"
r"进院供应商名称|结算方式|批号|规格型号|生产厂家|库区名称|分类名称|"
r"耗材类型|物资类型|入库单号|出库单号|源单号|计费科室|库存科室|查询维度)",
steps,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment