This commit is contained in:
zlwu committed 2026-08-18 14:40:15 +08:00
1 parent a7fbaa8fd9
commit d2792174b5
55 files changed
+1863 -1419

No files matched your search

@@ -0,0 +1,23 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { createEmptyStepOutputRow } from './production-step-output'
describe('createEmptyStepOutputRow', () => {
it('defaults barcode to lab storage code', () => {
const row = createEmptyStepOutputRow('LAB-001')
assert.equal(row.outputBarcode, 'LAB-001')
assert.equal(row.outputTypeId, '')
assert.equal(row.outputName, '')
assert.equal(row.remark, '')
})
it('trims lab storage code', () => {
assert.equal(createEmptyStepOutputRow(' LAB-002 ').outputBarcode, 'LAB-002')
})
it('uses empty barcode when lab storage code is missing', () => {
assert.equal(createEmptyStepOutputRow('').outputBarcode, '')
assert.equal(createEmptyStepOutputRow(null).outputBarcode, '')
assert.equal(createEmptyStepOutputRow(undefined).outputBarcode, '')
})
})
@@ -0,0 +1,9 @@
/** 工艺步骤「产出物」新增行:条码默认实验室入库编码。 */
export function createEmptyStepOutputRow(labStorageCode?: string | null) {
return {
outputTypeId: '',
outputName: '',
outputBarcode: String(labStorageCode || '').trim(),
remark: ''
}
}
@@ -0,0 +1,40 @@
/** 编码 DESC → 时间 DESC;负值表示 a 排在 b 前(与后端一致) */
export function compareLabStorageThenCreateTimeDesc(
codeA: string | null | undefined,
timeA: string | null | undefined,
codeB: string | null | undefined,
timeB: string | null | undefined
): number {
const ca = String(codeA || '').trim()
const cb = String(codeB || '').trim()
if (ca !== cb) {
return ca > cb ? -1 : 1
}
const ta = String(timeA || '').trim()
const tb = String(timeB || '').trim()
if (ta !== tb) {
return ta > tb ? -1 : 1
}
return 0
}
export function resolveGroupLabStorageCode(
rows: { labStorageCode?: string | null }[]
): string {
for (const row of rows) {
const code = String(row.labStorageCode || '').trim()
if (code) return code
}
return ''
}
export function resolveGroupMaxCreateTime(
rows: { createTime?: string | null }[]
): string {
let max = ''
for (const row of rows) {
const t = String(row.createTime || '')
if (t > max) max = t
}
return max
}
+35 -2
View File
@@ -162,16 +162,49 @@ export function resolveBatchSections<T extends QcBatchTaskLike>(
return []
}
export function createEmptyInspectionBatch(defaultAssigneeUserIds?: string[]): QcInspectionBatchConfigureItem {
/** 细胞存储 / 科研捐赠:批次名默认用实验室入库编码 */
const INBOUND_LAB_CODE_BATCH_SOURCES = new Set(['cell_storage', 'research_donation'])
export type ResolveDefaultQcBatchNameOpts = {
receptionSource?: string | null
labStorageCode?: string | null
/** 已占用名称;仅非入库类型做 YYYYMMDD检测 去重时使用 */
usedNames?: Iterable<string>
}
/**
* 配置检测弹窗新增批次的默认名称。
* - 细胞存储 / 科研捐赠:有实验室入库编码则用该编码(多批次相同);无则空串
* - 其它类型:YYYYMMDD检测,冲突则 -2、-3…
*/
export function resolveDefaultQcBatchName(opts: ResolveDefaultQcBatchNameOpts = {}): string {
const source = String(opts.receptionSource || '').trim()
if (INBOUND_LAB_CODE_BATCH_SOURCES.has(source)) {
return String(opts.labStorageCode || '').trim()
}
const today = new Date()
const y = today.getFullYear()
const m = String(today.getMonth() + 1).padStart(2, '0')
const d = String(today.getDate()).padStart(2, '0')
const base = `${y}${m}${d}检测`
const used = new Set(
[...(opts.usedNames || [])].map((n) => String(n || '').trim()).filter(Boolean)
)
if (!used.has(base)) return base
let n = 2
while (used.has(`${base}-${n}`)) n += 1
return `${base}-${n}`
}
export function createEmptyInspectionBatch(
defaultAssigneeUserIds?: string[],
nameOpts?: ResolveDefaultQcBatchNameOpts
): QcInspectionBatchConfigureItem {
const assigneeUserIds = (defaultAssigneeUserIds || [])
.map((id) => String(id || '').trim())
.filter(Boolean)
return {
batchName: `${y}${m}${d}检测`,
batchName: resolveDefaultQcBatchName(nameOpts),
assigneeUserIds,
hasLab: false,
hasThirdParty: false,
@@ -18,6 +18,7 @@ import {
workOrderMatchesCategory
} from '@/utils/qc-work-order-category'
import { matchesWorkOrderStatusFilter } from '@/utils/status-tag-registry'
import { compareLabStorageThenCreateTimeDesc } from '@/utils/qc-inbound-lab-storage-sort'
export type QcTaskMineFilters = {
keyword?: string
@@ -173,6 +174,19 @@ export function paginateMineEntries<T>(items: T[], page: number, pageSize: numbe
}
}
export function sortMineEntriesForInboundLabStorage(
entries: MineWorkOrderEntry[]
): MineWorkOrderEntry[] {
return [...entries].sort((a, b) =>
compareLabStorageThenCreateTimeDesc(
a.workOrder.labStorageCode,
a.workOrder.createTime,
b.workOrder.labStorageCode,
b.workOrder.createTime
)
)
}
export function buildMineTableRows(pageEntries: MineWorkOrderEntry[]): {
rows: QcWorkOrder[]
presetTasksByWorkOrderId: Record<string, QcTask[]>
+7 -1
View File
@@ -23,6 +23,12 @@ function sortWorkOrdersInGroup(rows: QcWorkOrder[]) {
})
}
function sortWorkOrdersInInboundGroup(rows: QcWorkOrder[]) {
return [...rows].sort((a, b) =>
String(b.createTime || '').localeCompare(String(a.createTime || ''))
)
}
/** 入库 Tab:按样本编码分组(前端分组,分页仍按工单条数) */
export function groupInboundQcWorkOrders(rows: QcWorkOrder[]): QcWorkOrderSampleGroupModel[] {
const map = new Map<string, QcWorkOrder[]>()
@@ -33,7 +39,7 @@ export function groupInboundQcWorkOrders(rows: QcWorkOrder[]): QcWorkOrderSample
else map.set(key, [row])
}
return Array.from(map.entries()).map(([groupKey, workOrders]) => {
const sorted = sortWorkOrdersInGroup(workOrders)
const sorted = sortWorkOrdersInInboundGroup(workOrders)
return {
groupKey,
sampleCode: sorted[0]?.sampleCode || '',
@@ -1,4 +1,9 @@
import type { QcProtocolSiblingSample, QcWorkOrder } from '@/api/qc-work-order'
import {
compareLabStorageThenCreateTimeDesc,
resolveGroupLabStorageCode,
resolveGroupMaxCreateTime
} from '@/utils/qc-inbound-lab-storage-sort'
import {
groupInboundQcWorkOrders,
resolveInboundProtocolGroupKey,
@@ -33,15 +38,6 @@ function sampleTypeNameForGroup(group: QcWorkOrderSampleGroupModel) {
return (gate?.sampleTypeName || group.workOrders[0]?.sampleTypeName || '').trim()
}
function protocolSortTime(group: QcWorkOrderSampleGroupModel): string {
let max = ''
for (const wo of group.workOrders) {
const t = String(wo.createTime || '')
if (t > max) max = t
}
return max
}
function resolveProtocolSiblingSamplesForGroup(
group: QcWorkOrderSampleGroupModel
): QcProtocolSiblingSample[] {
@@ -140,17 +136,14 @@ export function buildInboundSampleListItems(rows: QcWorkOrder[]): QcInboundSampl
}
}
const sortedGroups = [...groups].sort((a, b) => {
const headA = a.workOrders[0]
const headB = b.workOrders[0]
if (!headA || !headB) return 0
const protocolA = resolveInboundProtocolGroupKey(headA)
const protocolB = resolveInboundProtocolGroupKey(headB)
if (protocolA !== protocolB) {
return protocolSortTime(b).localeCompare(protocolSortTime(a))
}
return (a.sampleCode || '').localeCompare(b.sampleCode || '')
})
const sortedGroups = [...groups].sort((a, b) =>
compareLabStorageThenCreateTimeDesc(
resolveGroupLabStorageCode(a.workOrders),
resolveGroupMaxCreateTime(a.workOrders),
resolveGroupLabStorageCode(b.workOrders),
resolveGroupMaxCreateTime(b.workOrders)
)
)
return sortedGroups.map((group) => ({
group,