| | |
| | | await stack.start() |
| | | stack.executeIsolatedSql(` |
| | | INSERT INTO t_battery_group(group_id,group_name,model) VALUES(9701,'甲组','型号'),(9702,'乙组','型号'); |
| | | INSERT INTO t_battery_group_field(field_id,field_name,field_type) VALUES(9701,'质量','number'),(9702,'另一质量','number'); |
| | | INSERT INTO t_battery_group_relation_field(group_id,battery_group_field_id,field_value) VALUES(9701,9701,'12.5'),(9702,9701,'28'),(9701,9702,'12500'),(9702,9702,'28000'); |
| | | INSERT INTO t_test_type(test_type_id,test_type_name) VALUES(9701,'容量'); |
| | | INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status,started_at_utc) |
| | | VALUES(9701,'测试甲',1,1,9701,'隔离验收','COMPLETED','ENDED','2026-09-01 00:00:00'),(9702,'测试乙',1,1,9701,'隔离验收','COMPLETED','ENDED','2026-09-02 00:00:00'); |
| | |
| | | expect(result.code, `${path}: ${result.msg}`).toBe(code) |
| | | return result.data |
| | | } |
| | | test('来源详情与同名模板独立编辑冻结,主动更新字段并保留历史打印', async ({ page }) => { |
| | | const errors: string[] = [] |
| | | page.on('pageerror', error => errors.push(error.message)) |
| | | await login(page) |
| | | const detailA = { experimentName: '原实验', batteryFields: ['质量', '取消字段'], specialFields: ['备注'], fieldValues: { 质量: { '1号': '控制台未知单位', '2号': '' }, 取消字段: { '1号': '取消原值' }, 备注: { '1号': '来源备注', '2号': '' } } } |
| | | await api(page, 'test/9701/console-detail', 'PUT', detailA) |
| | | await api(page, 'test/9702/console-detail', 'PUT', { experimentName: '另一实验', batteryFields: ['质量'], specialFields: [], fieldValues: { 质量: { '1号': '跨组人工质量' } } }) |
| | | const templateFields = [ |
| | | { name: '质量', displayName: '质量', category: 'battery', unit: 'g', fieldDefinitionId: 9701 }, |
| | | { name: '另一质量', displayName: '质量', category: 'battery', unit: 'mg', fieldDefinitionId: 9702 }, |
| | | { name: '备注', displayName: '模板备注', category: 'special', unit: '', fieldDefinitionId: null }, |
| | | ] |
| | | const templateId = await api(page, 'splice-report/templates', 'POST', { name: '同名不同身份纯结构', fields: templateFields }) |
| | | await page.goto('/report-splice/new') |
| | | await page.getByPlaceholder('请输入拼接报表名称').fill('来源模板边界') |
| | | for (const label of ['测试甲(甲组,1号)', '测试甲(甲组,2号)', '测试乙(乙组,1号)']) { |
| | | await page.locator('.panel-actions').getByRole('button', { name: '添加拼接', exact: true }).click() |
| | | await page.getByRole('dialog').locator('.el-select').last().click() |
| | | await page.getByRole('option', { name: label, exact: true }).click() |
| | | await page.getByRole('dialog').getByRole('button', { name: '添加拼接', exact: true }).click() |
| | | } |
| | | const sourceQuality = page.getByRole('textbox', { name: '测试甲 1号 电池字段 质量', exact: true }) |
| | | await expect(sourceQuality).toHaveValue('控制台未知单位') |
| | | await expect(page.getByRole('textbox', { name: '测试甲 2号 电池字段 质量', exact: true })).toHaveValue('') |
| | | await sourceQuality.fill('报表来源覆盖') |
| | | await page.getByRole('textbox', { name: '测试甲 1号 特殊字段 备注', exact: true }).fill('') |
| | | await page.getByRole('textbox', { name: '测试甲 1号 实验名称', exact: true }).fill('') |
| | | const applyTemplate = async () => { |
| | | await page.getByRole('button', { name: '选择字段模板', exact: true }).click() |
| | | await page.getByRole('dialog').getByText('同名不同身份纯结构', { exact: true }).click() |
| | | await page.getByRole('dialog').getByRole('button', { name: '应用模板', exact: true }).click() |
| | | } |
| | | await applyTemplate() |
| | | const rowG = page.locator('.report-field-table tr').filter({ has: page.locator('td').filter({ hasText: /^质量 \(g\)$/ }) }) |
| | | const rowMg = page.locator('.report-field-table tr').filter({ has: page.locator('td').filter({ hasText: /^质量 \(mg\)$/ }) }) |
| | | await expect(rowG.locator('input').first()).toHaveValue('12.5') |
| | | await expect(rowMg.locator('input').first()).toHaveValue('12500') |
| | | await expect(sourceQuality).toHaveValue('报表来源覆盖') |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 报表字段 模板备注', exact: true })).toHaveValue('') |
| | | await rowG.locator('input').first().fill('13.7') |
| | | await page.getByRole('textbox', { name: '测试甲 1号 报表字段 模板备注', exact: true }).fill('模板人工值') |
| | | await applyTemplate() |
| | | await expect(rowG.locator('input').first()).toHaveValue('13.7') |
| | | await expect(rowMg.locator('input').first()).toHaveValue('12500') |
| | | await expect(sourceQuality).toHaveValue('报表来源覆盖') |
| | | // 模板保存只取报表结构,来源实验、控制台字段及两类人工值都不能夹带。 |
| | | await page.getByRole('button', { name: '选择字段模板', exact: true }).click() |
| | | await page.getByRole('dialog').getByRole('button', { name: '保存为模板', exact: true }).click() |
| | | const templateDialog = page.getByRole('dialog', { name: '保存拼接字段模板', exact: true }) |
| | | await templateDialog.getByRole('textbox').fill('边界另存结构') |
| | | await templateDialog.getByRole('button', { name: '保存', exact: true }).click() |
| | | await expect(templateDialog).toBeHidden() |
| | | await page.getByRole('dialog', { name: '选择拼接字段模板', exact: true }).getByRole('button', { name: '取消', exact: true }).click() |
| | | const template = (await api(page, 'splice-report/templates')).find((item: any) => item.name === '边界另存结构') |
| | | expect(template.fields).toEqual(templateFields) |
| | | expect(JSON.stringify(template)).not.toContain('报表来源覆盖') |
| | | expect(JSON.stringify(template)).not.toContain('模板人工值') |
| | | expect(JSON.stringify(template)).not.toContain('取消字段') |
| | | await page.getByRole('button', { name: '保存报表', exact: true }).click() |
| | | await expect(page).toHaveURL(/report-splice$/) |
| | | const token = await page.evaluate(() => localStorage.getItem('battery_detection_auth_token')) |
| | | const list = await (await page.request.get(`${stack.backendUrl}/system/splice-report/list`, { headers: { Authorization: `Bearer ${token}` } })).json() |
| | | const id = list.rows.find((item: any) => item.name === '来源模板边界').id |
| | | const first = await api(page, `splice-report/${id}`) |
| | | expect(first.settings.fieldStructure).toHaveLength(3) |
| | | expect(first.settings.sourceDetailOverrides['9701|9701']).toEqual({ experimentName: '', fieldValues: { 'battery|质量': '报表来源覆盖', 'special|备注': '' } }) |
| | | expect(first.settings.detailFieldValues.质量['9701|9701']).toBe('13.7') |
| | | expect(first.segments[0].consoleDetail.fieldValues.质量).toBe('控制台未知单位') |
| | | expect((await api(page, 'test/9701')).consoleDetail).toMatchObject(detailA) |
| | | await page.goto(`/report-splice/${id}/edit`) |
| | | await page.reload() |
| | | await expect(sourceQuality).toHaveValue('报表来源覆盖') |
| | | await expect(rowG.locator('input').first()).toHaveValue('13.7') |
| | | await expect(rowMg.locator('input').first()).toHaveValue('12500') |
| | | await page.getByRole('button', { name: '保存修改', exact: true }).click() |
| | | await expect(page).toHaveURL(/report-splice$/) |
| | | const second = await api(page, `splice-report/${id}`) |
| | | expect(second.segments).toEqual(first.segments) |
| | | // 更新新增/取消字段不能依赖模板列表;消失但人工改过的质量、备注及空实验继续保留。 |
| | | await api(page, 'test/9701/console-detail', 'PUT', { experimentName: '新实验', batteryFields: ['新增来源字段'], specialFields: [], fieldValues: { 新增来源字段: { '1号': '新增内容', '2号': '' } } }) |
| | | const refreshed = await api(page, `splice-report/${id}/refresh-source`, 'POST', { baseVersionNo: 2, changeSummary: '来源字段增减' }) |
| | | expect(refreshed.settings.fieldStructure).toEqual(first.settings.fieldStructure) |
| | | expect(refreshed.settings.sourceDetailOverrides).toEqual(first.settings.sourceDetailOverrides) |
| | | await page.goto(`/report-splice/${id}/edit`) |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 电池字段 新增来源字段', exact: true })).toHaveValue('新增内容') |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 电池字段 取消字段', exact: true })).toHaveCount(0) |
| | | await expect(sourceQuality).toHaveValue('报表来源覆盖') |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 特殊字段 备注', exact: true })).toHaveValue('') |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 实验名称', exact: true })).toHaveValue('') |
| | | await page.goto(`/report-splice/${id}/edit?mode=view`) |
| | | const sourceSection = page.locator('.source-detail-section') |
| | | await expect(sourceSection).toContainText('新增内容') |
| | | await expect(sourceSection).toContainText('报表来源覆盖') |
| | | await expect(sourceSection).not.toContainText('取消字段') |
| | | await expect(sourceSection).not.toContainText('(g)') |
| | | await expect(page.locator('.report-field-section')).toContainText('13.7') |
| | | await expect(page.locator('.report-field-section')).toContainText('12500') |
| | | await page.screenshot({ path: 'tests/e2e/.artifacts/splice-source-template-boundary.png', fullPage: true }) |
| | | await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } }) |
| | | const pending = page.waitForEvent('popup') |
| | | await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click() |
| | | const popup = await pending |
| | | await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true') |
| | | expect(await popup.locator('.splice-snapshot').innerText()).toEqual(await page.locator('.splice-snapshot').innerText()) |
| | | await popup.pdf({ path: 'tests/e2e/.artifacts/splice-source-template-boundary.pdf', preferCSSPageSize: true, printBackground: true }) |
| | | await popup.close() |
| | | await page.goto(`/report-splice/${id}/edit?mode=view&version=1`) |
| | | await expect(page.locator('.source-detail-section')).toContainText('取消原值') |
| | | await expect(page.locator('.source-detail-section')).not.toContainText('新增内容') |
| | | expect(await api(page, `splice-report/${id}/versions/1`)).toEqual(first) |
| | | for (const sourceDetailOverrides of [{ '9701|9999': { experimentName: '错归属' } }, { '9701|9701': { fieldValues: { 'voltage': '不能冒充设备值' } } }]) |
| | | await api(page, `splice-report/${id}/versions`, 'POST', { name: first.name, baseVersionNo: 3, changeSummary: '拒绝无效详情', settings: { ...refreshed.settings, sourceDetailOverrides } }, 500) |
| | | expect((await api(page, `splice-report/${id}`)).versionNo).toBe(3) |
| | | // 隔离库复原旧07快照形状:普通读写不补来源行,明确更新才启用新详情,同时保留旧人工值。 |
| | | const legacy = await api(page, 'splice-report', 'POST', { name: '旧07快照', selections: [selection(9701), selection(9702)], settings: { |
| | | fieldStructure: [{ name: '旧人工字段', displayName: '旧人工字段', category: 'special', unit: '' }], |
| | | detailFieldValues: { 旧人工字段: { '9701|9701': '旧人工保留' } }, sourceExperimentNames: { '9701|9701': '旧实验保留' }, |
| | | } }) |
| | | stack.executeIsolatedSql(`UPDATE t_splice_report_version SET snapshot_json=JSON_REMOVE(snapshot_json,'$.settings.sourceDetailOverrides') WHERE report_id=${legacy.id}`) |
| | | const old = await api(page, `splice-report/${legacy.id}`) |
| | | expect(old.settings.sourceDetailOverrides).toBeUndefined() |
| | | await page.goto(`/report-splice/${legacy.id}/edit`) |
| | | await expect(page.locator('.source-detail-table')).toHaveCount(0) |
| | | await expect(page.getByRole('textbox', { name: '测试甲 1号 报表字段 旧人工字段', exact: true })).toHaveValue('旧人工保留') |
| | | await page.getByRole('button', { name: '保存修改', exact: true }).click() |
| | | await expect(page).toHaveURL(/report-splice$/) |
| | | const legacyEdited = await api(page, `splice-report/${legacy.id}`) |
| | | expect(legacyEdited.settings.sourceDetailOverrides).toBeUndefined() |
| | | expect(legacyEdited.segments).toEqual(old.segments) |
| | | const legacyUpdated = await api(page, `splice-report/${legacy.id}/refresh-source`, 'POST', { baseVersionNo: 2, changeSummary: '旧版明确更新' }) |
| | | expect(legacyUpdated.settings.detailFieldValues).toEqual(old.settings.detailFieldValues) |
| | | expect(legacyUpdated.settings.sourceDetailOverrides['9701|9701'].experimentName).toBe('旧实验保留') |
| | | await page.goto(`/report-splice/${legacy.id}/edit?mode=view`) |
| | | await expect(page.locator('.source-detail-section')).toContainText('旧实验保留') |
| | | await expect(page.locator('.source-detail-section')).toContainText('新增内容') |
| | | await expect(page.locator('.report-field-section')).toContainText('旧人工保留') |
| | | expect(await api(page, `splice-report/${legacy.id}/versions/1`)).toEqual(old) |
| | | expect(templateId).toBeGreaterThan(0) |
| | | expect(errors).toEqual([]) |
| | | }) |
| | | |
| | | test('逐电池身份、接续、来源详情、界面重排及保存打印闭环',async ({page}) => { |
| | | await login(page) |
| | | await api(page,'test/9701/console-detail','PUT',{experimentName:'实验甲',batteryFields:['人工字段无共享身份'],specialFields:['备注'],fieldValues:{备注:{'1号':'甲一','2号':''},人工字段无共享身份:{'1号':'原样单位未知','2号':'另一值'}}}) |
| | |
| | | } |
| | | await expect(page.locator('.splice-chart')).toHaveCount(2) |
| | | await expect(page.locator('.splice-snapshot h1')).toHaveCount(0) |
| | | const detailRow = page.locator('.detail-table tr').filter({has:page.locator('td').filter({hasText:/^备注$/})}) |
| | | const detailRow = page.locator('.source-detail-table tr').filter({has:page.locator('td').filter({hasText:/^备注(特殊字段)$/})}) |
| | | await expect(detailRow.locator('input').nth(0)).toHaveValue('甲一') |
| | | await expect(detailRow.locator('input').nth(1)).toHaveValue('') |
| | | await detailRow.locator('input').nth(2).fill('乙一报表') |
| | |
| | | const saved = await api(page,`splice-report/${list.rows.find((r:any) => r.name === '逐电池拼接界面').id}`) |
| | | expect(saved.selections).toEqual([selection(9701),selection(9703),selection(9702)]) |
| | | expect(saved.settings.hiddenSeries).toBe(true) |
| | | expect(saved.settings.detailFieldValues.备注['9702|9703']).toBe('乙一报表') |
| | | expect(saved.settings.sourceDetailOverrides['9702|9703'].fieldValues['special|备注']).toBe('乙一报表') |
| | | expect(saved.settings.statisticValues['9702|9703|1|finalVoltage1']).toBe('8') |
| | | expect(saved.segments[1].statistics['1'].finalVoltage1).toBe(0) |
| | | expect((await api(page,'test/9702')).consoleDetail.fieldValues.备注['1号']).toBe('乙一') |