yeg
2026-09-08 0c7ab59fe6f7c91280665533cdaa57530a70dc6b
tests/e2e/splice-battery-segments.spec.ts
@@ -9,6 +9,8 @@
  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');
@@ -39,6 +41,147 @@
  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号':'另一值'}}})
@@ -74,7 +217,7 @@
  }
  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('乙一报表')
@@ -98,7 +241,7 @@
  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('乙一')