提交
This commit is contained in:
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
|
||||
}
|
||||
@@ -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[]>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user