import type { QcWorkOrder } from '@/api/qc-work-order' import { fetchMyQcTaskList, type QcTask } from '@/api/qc-task' import { groupInboundQcWorkOrders, matchesTemporaryQcSourceFilter } from '@/utils/qc-work-order-groups' import type { QcWorkOrderTabKey } from '@/utils/qc-work-order-tabs' import { resolveTabKeyFromWorkOrder, tabUsesInfusionTimeFilter, getQcWorkOrderTab, toWorkOrderListQuery, isAllQcWorkOrderTab } from '@/utils/qc-work-order-tabs' import { matchesAllTabTriggerSource, toAllTabListQuery, 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 taskType?: string taskStatus?: string tabKey: QcWorkOrderTabKey } export type MineWorkOrderSearchFilters = { keyword?: string workOrderStatus?: string | string[] triggerSource?: string qcCategory?: string createTimeStart?: string createTimeEnd?: string infusionTimeStart?: string infusionTimeEnd?: string tabKey: QcWorkOrderTabKey sampleTypeName?: string } export type MineWorkOrderEntry = { workOrderId: string workOrder: QcWorkOrder tabKey: QcWorkOrderTabKey tasks: QcTask[] } const MINE_TASK_FETCH_PAGE_SIZE = 100 function dateInRange(value: string | undefined, start?: string, end?: string): boolean { const v = (value || '').trim().slice(0, 10) if (!v) return !start && !end if (start && v < start) return false if (end && v > end) return false return true } function keywordMatchesWorkOrder( row: QcWorkOrder, keyword: string, tabKey: QcWorkOrderTabKey ): boolean { const kw = keyword.trim().toLowerCase() if (!kw) return true const tab = getQcWorkOrderTab(tabKey)! const fields = tabKey === 'temporary' ? [row.workOrderNo, row.productionWorkOrderNo] : tabKey === 'outbound_basic_experiment' ? [ row.workOrderNo, row.applicationNo, row.projectTitle, row.customerName, row.applicationName ] : tab.group === 'outbound' ? [row.workOrderNo, row.applicationNo, row.applicationName, row.patientName] : [ row.workOrderNo, row.protocolNo, row.sampleCode, row.labStorageCode, row.motherName, row.newbornName ] return fields.some((f) => (f || '').toLowerCase().includes(kw)) } export function filterMineEntriesByWorkOrderSearch( entries: MineWorkOrderEntry[], filters: MineWorkOrderSearchFilters ): MineWorkOrderEntry[] { const tabKey = filters.tabKey const tab = getQcWorkOrderTab(tabKey)! return entries.filter((entry) => { const row = entry.workOrder if (!isAllQcWorkOrderTab(tabKey) && resolveTabKeyFromWorkOrder(row) !== tabKey) { return false } if (isAllQcWorkOrderTab(tabKey) && !workOrderMatchesCategory(row, filters.qcCategory || '')) { return false } if (!matchesWorkOrderStatusFilter(row.workOrderStatus, filters.workOrderStatus)) return false if (filters.triggerSource) { if (isAllQcWorkOrderTab(tabKey)) { if (!matchesAllTabTriggerSource(row, filters.triggerSource)) return false } else if (tabKey === 'temporary') { if (!matchesTemporaryQcSourceFilter(row, filters.triggerSource)) return false } else if (tab.listLayout !== 'standalone' && row.triggerSource !== filters.triggerSource) { return false } } const keywordTabKey = isAllQcWorkOrderTab(tabKey) ? resolveTabKeyFromWorkOrder(row) : tabKey if (!keywordMatchesWorkOrder(row, filters.keyword || '', keywordTabKey)) return false const sampleTypeName = (filters.sampleTypeName || '').trim() if (sampleTypeName && (row.sampleTypeName || '').trim() !== sampleTypeName) return false if (isAllQcWorkOrderTab(tabKey) || !tabUsesInfusionTimeFilter(tab)) { if (!dateInRange(row.createTime, filters.createTimeStart, filters.createTimeEnd)) { return false } } else if (!dateInRange(row.infusionTime, filters.infusionTimeStart, filters.infusionTimeEnd)) { return false } return true }) } export async function fetchAllMineTasks(filters: QcTaskMineFilters): Promise { const tab = getQcWorkOrderTab(filters.tabKey)! const listQuery = isAllQcWorkOrderTab(filters.tabKey) ? toAllTabListQuery('') : toWorkOrderListQuery(tab) const pageSize = MINE_TASK_FETCH_PAGE_SIZE let page = 1 const all: QcTask[] = [] let total = 0 while (true) { const res = await fetchMyQcTaskList({ page, pageSize, keyword: filters.keyword || undefined, taskType: filters.taskType || undefined, taskStatus: filters.taskStatus || undefined, qcBizType: listQuery.workOrderType, receptionSource: listQuery.receptionSource, bizType: listQuery.bizType }) const list = res.list || [] all.push(...list) total = res.total || 0 if (!list.length || all.length >= total) break page += 1 } return all } export async function enrichMineEntriesWithWorkOrders( entries: MineWorkOrderEntry[] ): Promise { // 工单摘要已由 /qc-tasks/mine 接口批量 enrich,无需再逐条请求详情 return entries } export function paginateMineEntries(items: T[], page: number, pageSize: number) { const safePage = Math.max(1, page) const safeSize = Math.max(1, pageSize) const start = (safePage - 1) * safeSize return { total: items.length, items: items.slice(start, start + safeSize) } } 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 } { return { rows: pageEntries.map((e) => e.workOrder), presetTasksByWorkOrderId: buildPresetTasksMap(pageEntries) } } export function resolveWorkOrderTabKey(task: QcTask): QcWorkOrderTabKey { const woType = task.qcBizType || task.orderSource || 'inbound' if (woType === 'outbound') { return resolveTabKeyFromWorkOrder({ workOrderType: 'outbound', bizType: task.receptionSource }) } if (woType === 'temporary') { return 'temporary' } return resolveTabKeyFromWorkOrder({ workOrderType: 'inbound', receptionSource: task.receptionSource }) } export function buildWorkOrderFromTasks(tasks: QcTask[]): QcWorkOrder { const t = tasks[0] const tabKey = resolveWorkOrderTabKey(t) const woType = getQcWorkOrderTab(tabKey)?.workOrderType || 'inbound' return { id: t.workOrderId, workOrderNo: t.workOrderNo || '—', workOrderType: woType, workOrderStatus: t.workOrderStatus || '', receptionId: t.receptionId || '', protocolSampleId: t.protocolSampleId || '', collectionProtocolId: t.collectionProtocolId || '', protocolNo: t.protocolNo || '', sampleCode: t.sampleCode || '', labStorageCode: t.labStorageCode, receptionSource: t.receptionSource, receptionSourceLabel: t.receptionSourceLabel, qcBizType: t.qcBizType || woType, qcBizTypeLabel: t.qcBizTypeLabel, cellProductId: t.cellProductId, cellProductCode: t.cellProductCode, cellProductName: t.cellProductName, cellProductTypeName: t.cellProductTypeName, cellProductPassage: t.cellProductPassage, cellProductSpecName: t.cellProductSpecName, sampleTypeName: t.sampleTypeName, isSpecial: tasks.some((x) => x.isSpecial) || !!t.isSpecial, specialRemark: String( tasks.find((x) => x.isSpecial)?.specialRemark || t.specialRemark || '' ).trim(), receptionTime: t.receptionTime, handlerName: t.handlerName, hasLab: t.hasLab ?? tasks.some((x) => x.taskType === 'sysz'), hasThirdParty: t.hasThirdParty ?? tasks.some((x) => x.taskType === 'dsfjc'), createTime: t.workOrderCreateTime || t.createTime || '', bizType: woType === 'outbound' ? t.bizType || t.receptionSource : undefined, bizTypeLabel: woType === 'outbound' ? t.bizTypeLabel || t.receptionSourceLabel : undefined, applicationNo: t.applicationNo, applicationName: t.applicationName, projectTitle: t.projectTitle, customerName: t.customerName, channelName: t.channelName, patientName: t.patientName, motherName: t.motherName, newbornName: t.newbornName, sortNo: t.sortNo, totalCourseCount: t.totalCourseCount, infusionTime: t.infusionTime, products: t.products, cellType: t.cellType, specText: t.specText, triggerSource: t.triggerSource, triggerSourceLabel: t.triggerSourceLabel, productionWorkOrderId: t.productionWorkOrderId, productionWorkOrderNo: t.productionWorkOrderNo, protocolStatus: t.protocolStatus, protocolStatusLabel: t.protocolStatusLabel, paymentSummary: t.paymentSummary, feeSummary: t.feeSummary, protocolSiblingSamples: t.protocolSiblingSamples, remark: t.workOrderRemark || t.remark, unfinishedBadgeCount: t.unfinishedBadgeCount } } /** 按工单聚合任务,保持 API 返回顺序 */ export function groupMineTasksByWorkOrder(tasks: QcTask[]): MineWorkOrderEntry[] { const map = new Map() const order: string[] = [] for (const row of tasks) { const key = row.workOrderId || row.workOrderNo || row.id if (!map.has(key)) { map.set(key, []) order.push(key) } map.get(key)!.push(row) } return order.map((workOrderId) => { const groupTasks = map.get(workOrderId)! return { workOrderId, workOrder: buildWorkOrderFromTasks(groupTasks), tabKey: resolveWorkOrderTabKey(groupTasks[0]), tasks: groupTasks } }) } export function buildPresetTasksMap(entries: MineWorkOrderEntry[]): Record { const map: Record = {} for (const entry of entries) { map[entry.workOrder.id] = entry.tasks } return map } export type MineCardListModel = { inboundGroups: ReturnType standaloneEntries: MineWorkOrderEntry[] presetTasksByWorkOrderId: Record hasItems: boolean } /** 入库走 SampleGroup,出库/临时走 SingleCard standalone */ export function buildMineCardListModel( tasks: QcTask[], tabKey: QcWorkOrderTabKey ): MineCardListModel { const entries = groupMineTasksByWorkOrder(tasks).filter((e) => isAllQcWorkOrderTab(tabKey) ? true : e.tabKey === tabKey ) const presetTasksByWorkOrderId = buildPresetTasksMap(entries) const tab = getQcWorkOrderTab(tabKey)! const useInboundGroups = tab.listLayout === 'inbound-grouped' const inboundGroups = useInboundGroups ? groupInboundQcWorkOrders(entries.map((e) => e.workOrder)) : [] const standaloneEntries = useInboundGroups ? [] : entries return { inboundGroups, standaloneEntries, presetTasksByWorkOrderId, hasItems: entries.length > 0 } }