This commit is contained in:
zlwu committed 2026-08-31 15:18:18 +08:00
1 parent 04d462ad43
commit 7e9155fef1
7 files changed
+1754 -68

No files matched your search

@@ -0,0 +1,137 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import {
buildTaskListDisplayItems,
pickSharedRemarkAndAttachments,
companyGroupProgress,
uniqueAssigneeNames
} from './qc-company-task-group'
type TestQcTask = {
id: string
taskType: string
taskNo?: string
itemCode?: string
itemName?: string
taskStatus?: string
workOrderId?: string
companyId?: string
companyName?: string
remark?: string
thirdPartyAttachments?: string
updateTime?: string
assigneeUserNames?: string
attachmentFiles?: Array<{ fileUrl: string; fileName?: string; fileType?: string }>
}
function task(partial: Partial<TestQcTask> & Pick<TestQcTask, 'id' | 'taskType'>): TestQcTask {
return {
taskNo: 'T1',
itemCode: 'I',
itemName: 'Item',
taskStatus: 'dzx',
workOrderId: 'wo1',
...partial
}
}
describe('buildTaskListDisplayItems', () => {
it('keeps lab tasks as single cards and groups third-party by companyId', () => {
const tasks = [
task({ id: 'l1', taskType: 'sysz', itemName: '无菌' }),
task({
id: 't1',
taskType: 'dsfjc',
companyId: 'c1',
companyName: '金域',
itemName: '乙肝'
}),
task({
id: 't2',
taskType: 'dsfjc',
companyId: 'c1',
companyName: '金域',
itemName: '丙肝'
}),
task({
id: 't3',
taskType: 'dsfjc',
companyId: 'c2',
companyName: '迪安',
itemName: 'HIV'
})
]
const items = buildTaskListDisplayItems(tasks)
assert.equal(items.length, 3)
assert.equal(items[0].kind, 'lab')
assert.equal(items[0].kind === 'lab' && items[0].task.id, 'l1')
assert.equal(items[1].kind, 'company')
if (items[1].kind === 'company') {
assert.equal(items[1].companyId, 'c1')
assert.equal(items[1].tasks.length, 2)
}
assert.equal(items[2].kind, 'company')
if (items[2].kind === 'company') {
assert.equal(items[2].companyId, 'c2')
}
})
it('falls back to single lab-like card when third-party has empty companyId', () => {
const tasks = [
task({ id: 't0', taskType: 'dsfjc', companyId: '', companyName: '', itemName: '孤儿' })
]
const items = buildTaskListDisplayItems(tasks)
assert.equal(items.length, 1)
assert.equal(items[0].kind, 'orphan-third')
})
})
describe('companyGroupProgress', () => {
it('counts ywc vs total', () => {
const p = companyGroupProgress([
task({ id: 'a', taskType: 'dsfjc', taskStatus: 'ywc' }),
task({ id: 'b', taskType: 'dsfjc', taskStatus: 'dzx' })
])
assert.deepEqual(p, { completed: 1, total: 2 })
})
})
describe('pickSharedRemarkAndAttachments', () => {
it('picks latest non-empty remark and attachments by updateTime', () => {
const picked = pickSharedRemarkAndAttachments([
task({
id: 'a',
taskType: 'dsfjc',
remark: '旧备注',
thirdPartyAttachments: 'http://a.pdf',
updateTime: '2026-08-01 10:00:00',
attachmentFiles: [{ fileUrl: 'http://a.pdf', fileName: 'a.pdf' }]
}),
task({
id: 'b',
taskType: 'dsfjc',
remark: '新备注',
thirdPartyAttachments: '',
updateTime: '2026-08-28 12:00:00',
attachmentFiles: []
})
])
assert.equal(picked.remark, '新备注')
assert.equal(picked.thirdPartyAttachments, 'http://a.pdf')
assert.equal(picked.attachmentFiles?.length, 1)
})
})
describe('uniqueAssigneeNames', () => {
it('dedupes assignee names split by comma or顿号', () => {
const names = uniqueAssigneeNames([
task({ id: 'a', taskType: 'dsfjc', assigneeUserNames: '张三,李四' }),
task({ id: 'b', taskType: 'dsfjc', assigneeUserNames: '李四、王五' })
])
assert.equal(names, '张三、李四、王五')
})
it('returns em dash when no assignees', () => {
assert.equal(uniqueAssigneeNames([task({ id: 'a', taskType: 'dsfjc' })]), '—')
})
})
@@ -0,0 +1,106 @@
import type { QcTask } from '../api/qc-task'
export type QcTaskListLabItem = { kind: 'lab'; task: QcTask }
export type QcTaskListOrphanThirdItem = { kind: 'orphan-third'; task: QcTask }
export type QcTaskListCompanyItem = {
kind: 'company'
companyId: string
companyName: string
tasks: QcTask[]
}
export type QcTaskListDisplayItem =
| QcTaskListLabItem
| QcTaskListOrphanThirdItem
| QcTaskListCompanyItem
function trimId(v?: string) {
return (v || '').trim()
}
/** 自检卡在前(原序);有 companyId 的第三方按首次出现公司顺序分组;无 companyId 降级为 orphan-third */
export function buildTaskListDisplayItems(tasks: QcTask[]): QcTaskListDisplayItem[] {
const lab: QcTaskListDisplayItem[] = []
const orphans: QcTaskListDisplayItem[] = []
const companyOrder: string[] = []
const companyMap = new Map<string, QcTaskListCompanyItem>()
for (const t of tasks || []) {
if ((t.taskType || '').trim() === 'sysz') {
lab.push({ kind: 'lab', task: t })
continue
}
if ((t.taskType || '').trim() !== 'dsfjc') {
lab.push({ kind: 'lab', task: t })
continue
}
const cid = trimId(t.companyId)
if (!cid) {
orphans.push({ kind: 'orphan-third', task: t })
continue
}
let g = companyMap.get(cid)
if (!g) {
g = {
kind: 'company',
companyId: cid,
companyName: (t.companyName || '').trim() || '第三方公司',
tasks: []
}
companyMap.set(cid, g)
companyOrder.push(cid)
}
g.tasks.push(t)
}
return [...lab, ...companyOrder.map((id) => companyMap.get(id)!), ...orphans]
}
export function companyGroupProgress(tasks: QcTask[]) {
const list = tasks || []
const completed = list.filter((t) => (t.taskStatus || '').trim() === 'ywc').length
return { completed, total: list.length }
}
function timeMs(v?: string) {
if (!v) return 0
const n = Date.parse(String(v).replace(/-/g, '/'))
return Number.isFinite(n) ? n : 0
}
export function pickSharedRemarkAndAttachments(tasks: QcTask[]): {
remark: string
thirdPartyAttachments: string
attachmentFiles: NonNullable<QcTask['attachmentFiles']>
} {
const list = [...(tasks || [])].sort((a, b) => timeMs(b.updateTime) - timeMs(a.updateTime))
let remark = ''
let thirdPartyAttachments = ''
let attachmentFiles: NonNullable<QcTask['attachmentFiles']> = []
for (const t of list) {
if (!remark && (t.remark || '').trim()) remark = (t.remark || '').trim()
const urls = (t.thirdPartyAttachments || '').trim()
const files = t.attachmentFiles || []
if (!thirdPartyAttachments && (urls || files.length)) {
thirdPartyAttachments = urls
attachmentFiles = files.length ? [...files] : []
}
if (remark && (thirdPartyAttachments || attachmentFiles.length)) break
}
return { remark, thirdPartyAttachments, attachmentFiles }
}
export function uniqueAssigneeNames(tasks: QcTask[]): string {
const names: string[] = []
const seen = new Set<string>()
for (const t of tasks || []) {
const raw = (t.assigneeUserNames || '').trim()
if (!raw) continue
for (const part of raw.split(/[,,、]/)) {
const n = part.trim()
if (!n || seen.has(n)) continue
seen.add(n)
names.push(n)
}
}
return names.join('、') || '—'
}