fix: 子表保存改为就地同步,避免整批换 id 打断外键。
协议样本、生产产出和盘点明细按行更新并保留原 id;补齐存储证明质检预填、离职人员显示名,以及样本采集通知模板字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
d66e55af65
commit
5e563a6669
139 files changed
+59986
-49048
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createEmptyStepOutputRow } from './production-step-output'
|
||||
import { createEmptyStepOutputRow, toStepOutputReplaceItem } from './production-step-output'
|
||||
|
||||
describe('createEmptyStepOutputRow', () => {
|
||||
it('defaults barcode to lab storage code', () => {
|
||||
@@ -20,4 +20,14 @@ describe('createEmptyStepOutputRow', () => {
|
||||
assert.equal(createEmptyStepOutputRow(null).outputBarcode, '')
|
||||
assert.equal(createEmptyStepOutputRow(undefined).outputBarcode, '')
|
||||
})
|
||||
|
||||
it('assigns a stable id and keeps it when building the save payload', () => {
|
||||
const row = createEmptyStepOutputRow('LAB-001')
|
||||
assert.match(row.id, /^[0-9A-F]{32}$/)
|
||||
const first = toStepOutputReplaceItem(row)
|
||||
const second = toStepOutputReplaceItem(row)
|
||||
assert.equal(first.id, row.id)
|
||||
assert.equal(second.id, row.id)
|
||||
assert.equal(first.output_barcode, 'LAB-001')
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,36 @@
|
||||
/** 与后端 generate_uuid 一致:32 位大写、无连字符。子表行从创建起就带稳定 ID。 */
|
||||
export function createStableRowId() {
|
||||
const uuid =
|
||||
typeof globalThis.crypto?.randomUUID === 'function'
|
||||
? globalThis.crypto.randomUUID()
|
||||
: `${Date.now().toString(16)}${Math.random().toString(16).slice(2)}`
|
||||
return uuid.replace(/-/g, '').toUpperCase()
|
||||
}
|
||||
|
||||
export function createStepOutputId() {
|
||||
return createStableRowId()
|
||||
}
|
||||
|
||||
/** 工艺步骤「产出物」新增行:条码默认实验室入库编码。 */
|
||||
export function createEmptyStepOutputRow(labStorageCode?: string | null) {
|
||||
return {
|
||||
id: createStepOutputId(),
|
||||
outputTypeId: '',
|
||||
outputName: '',
|
||||
outputBarcode: String(labStorageCode || '').trim(),
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存步骤产出物:带上原行 ID,避免后端整表换 ID。 */
|
||||
export function toStepOutputReplaceItem(row: Record<string, any>) {
|
||||
const id = String(row.id || '').trim() || createStepOutputId()
|
||||
row.id = id
|
||||
return {
|
||||
id,
|
||||
output_type_id: row.outputTypeId,
|
||||
output_name: row.outputName,
|
||||
output_barcode: row.outputBarcode,
|
||||
remark: row.remark || ''
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
|
||||
export type StorageCertQcTypeKey = 'perinatal_hsc' | 'uc_msc'
|
||||
|
||||
export type StorageCertQcControl = 'text' | 'textarea' | 'select_yny'
|
||||
export type StorageCertQcControl = 'text' | 'textarea' | 'select_yny' | 'select_result'
|
||||
|
||||
export interface StorageCertQcField {
|
||||
key: string
|
||||
@@ -53,19 +53,13 @@ export const STORAGE_CERT_QC_GROUPS: Record<StorageCertQcTypeKey, StorageCertQcG
|
||||
{
|
||||
title: '采集与运输',
|
||||
variant: 'alt',
|
||||
fields: [
|
||||
{ key: 'collection_time', label: '造血干细胞采集时间' },
|
||||
{ key: 'transport_temp_c', label: '运输温度 /℃' }
|
||||
]
|
||||
fields: [{ key: 'collection_time', label: '造血干细胞采集时间' }]
|
||||
},
|
||||
{
|
||||
title: '造血干细胞检测报告',
|
||||
variant: 'primary',
|
||||
fields: [
|
||||
{ key: 'receive_time', label: '造血干细胞接收时间' },
|
||||
{ key: 'process_time', label: '处理时间' },
|
||||
{ key: 'cryo_time', label: '冻存时间' },
|
||||
{ key: 'weight_g', label: '造血干细胞重量/g' },
|
||||
{ key: 'cryo_volume_ml', label: '造血干细胞冻存体积/mL' },
|
||||
{ key: 'tnc_1e8', label: '有核细胞数 TNC / *10E8' },
|
||||
{ key: 'cd34_count_1e6', label: 'CD34+ 细胞计数 / *10E6' },
|
||||
@@ -91,17 +85,16 @@ export const STORAGE_CERT_QC_GROUPS: Record<StorageCertQcTypeKey, StorageCertQcG
|
||||
{ key: 'hla_typing', label: 'HLA 分型', control: 'textarea', span: 3 },
|
||||
{ key: 'storage_date', label: '储存日期' },
|
||||
{ key: 'storage_years', label: '储存年限' },
|
||||
{ key: 'result_comment', label: '检测结果评论' },
|
||||
{ key: 'result_comment', label: '检测结果评论', control: 'select_result' },
|
||||
{ key: 'report_issue_date', label: '报告发出日期' }
|
||||
]
|
||||
}
|
||||
],
|
||||
uc_msc: [
|
||||
{
|
||||
title: '采集与运输 / 邮寄',
|
||||
title: '采集与运输',
|
||||
variant: 'alt',
|
||||
fields: [
|
||||
{ key: 'mail_flag', label: '是否邮寄' },
|
||||
{ key: 'collection_time', label: '脐带采集时间' },
|
||||
{ key: 'transport_temp_c', label: '运输温度 /℃' }
|
||||
]
|
||||
@@ -111,8 +104,6 @@ export const STORAGE_CERT_QC_GROUPS: Record<StorageCertQcTypeKey, StorageCertQcG
|
||||
variant: 'primary',
|
||||
fields: [
|
||||
{ key: 'receive_time', label: '脐带接收时间' },
|
||||
{ key: 'process_time', label: '处理时间' },
|
||||
{ key: 'cryo_time', label: '冻存时间' },
|
||||
{ key: 'storage_tube_count', label: '储存总量(管)' },
|
||||
{ key: 'available_cell_type', label: '可提供细胞类型' },
|
||||
{ key: 'available_cell_count_1e7', label: '可提供细胞数量 / *10E7' },
|
||||
@@ -134,7 +125,7 @@ export const STORAGE_CERT_QC_GROUPS: Record<StorageCertQcTypeKey, StorageCertQcG
|
||||
title: '储存证收尾信息',
|
||||
variant: 'meta',
|
||||
fields: [
|
||||
{ key: 'result_judgment', label: '检测结果评判' },
|
||||
{ key: 'result_judgment', label: '检测结果评判', control: 'select_result' },
|
||||
{ key: 'report_issue_date', label: '报告发出日期' },
|
||||
{ key: 'storage_date_p2', label: '储存日期 (P2冻存日期)' },
|
||||
{ key: 'freeze_method', label: '冷冻方式' },
|
||||
@@ -147,6 +138,60 @@ export const STORAGE_CERT_QC_GROUPS: Record<StorageCertQcTypeKey, StorageCertQcG
|
||||
|
||||
export const YNY_OPTIONS = [
|
||||
{ label: '阴', value: '阴' },
|
||||
{ label: '阳', value: '阳' },
|
||||
{ label: '/', value: '/' }
|
||||
{ label: '阳', value: '阳' }
|
||||
]
|
||||
|
||||
/** 储存证阴阳性:与质检参数值一致,用阴性 / 阳性。 */
|
||||
export const NP_OPTIONS = [
|
||||
{ label: '阴性', value: '阴性' },
|
||||
{ label: '阳性', value: '阳性' }
|
||||
]
|
||||
|
||||
export function isPerinatalQualField(
|
||||
typeKey: string | null | undefined,
|
||||
control?: StorageCertQcControl
|
||||
) {
|
||||
return (
|
||||
(typeKey === 'perinatal_hsc' || typeKey === 'uc_msc') && control === 'select_yny'
|
||||
)
|
||||
}
|
||||
|
||||
export function qualOptionsFor(
|
||||
typeKey: string | null | undefined,
|
||||
control?: StorageCertQcControl
|
||||
) {
|
||||
if (control === 'select_result') return RESULT_OPTIONS
|
||||
if (isPerinatalQualField(typeKey, control)) return NP_OPTIONS
|
||||
return YNY_OPTIONS
|
||||
}
|
||||
|
||||
export function normalizeQualValue(
|
||||
typeKey: string | null | undefined,
|
||||
control: StorageCertQcControl | undefined,
|
||||
raw: string
|
||||
) {
|
||||
if (!isPerinatalQualField(typeKey, control)) return raw
|
||||
const text = raw.trim()
|
||||
if (text === '阴' || text === '阴性') return '阴性'
|
||||
if (text === '阳' || text === '阳性') return '阳性'
|
||||
return raw
|
||||
}
|
||||
|
||||
export const RESULT_OPTIONS = [
|
||||
{ label: '合格', value: '合格' },
|
||||
{ label: '不合格', value: '不合格' }
|
||||
]
|
||||
|
||||
/** 已存档的非空值优先,只把预填补进空格。 */
|
||||
export function mergeStorageCertPrefill(
|
||||
payload: Record<string, unknown> | null | undefined,
|
||||
prefill: Record<string, unknown> | null | undefined
|
||||
): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = { ...(payload || {}) }
|
||||
for (const [key, value] of Object.entries(prefill || {})) {
|
||||
if (value == null || String(value).trim() === '') continue
|
||||
const current = out[key]
|
||||
if (current == null || String(current).trim() === '') out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user