Files
zkxw/web-admin/src/utils/production-mine-task-groups.ts
T
zlwuandCursor a3af039e74 feat: 质检工单结论、库位批量/筛选与协议样本接收质检摘要
落库并展示质检工单级结论(可编辑/任务派生),库位树支持批量新增子节点与货架格子检索,协议/捐赠列表补充接收与门禁质检摘要,并同步生产物料与工单卡片展示优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:46:24 +08:00

275 lines
8.8 KiB
TypeScript

import type { ProductionWorkOrderItem } from '@/api/production-work-order'
import { fetchProductionWorkOrderDetail } from '@/api/production-work-order'
import { fetchMyProductionTasks } from '@/api/production-task'
import type { ProductionWorkOrderTabKey } from '@/utils/production-work-order-tabs'
import {
resolveTabKeyFromWorkOrder,
getProductionWorkOrderTab,
toWorkOrderListQuery,
isAllProductionWorkOrderTab
} from '@/utils/production-work-order-tabs'
import { toAllTabListQuery, workOrderMatchesCategory } from '@/utils/production-work-order-category'
import { matchesWorkOrderStatusFilter } from '@/utils/status-tag-registry'
import { computeWorkOrderPlannedStartTimeRange } from '@/utils/production-labels'
export type ProductionTaskMineFilters = {
keyword?: string
taskStatus?: string
productionCategory?: string
tabKey: ProductionWorkOrderTabKey
excludeVoidProtocol?: boolean
plannedStartTimeStart?: string
plannedStartTimeEnd?: string
}
export type MineWorkOrderSearchFilters = {
keyword?: string
workOrderStatus?: string | string[]
productionCategory?: string
tabKey: ProductionWorkOrderTabKey
excludeVoidProtocol?: boolean
}
export type ProductionTaskRow = Record<string, any>
export type MineWorkOrderEntry = {
workOrderId: string
workOrder: ProductionWorkOrderItem
tabKey: ProductionWorkOrderTabKey
tasks: ProductionTaskRow[]
}
const MINE_TASK_FETCH_PAGE_SIZE = 100
function keywordMatchesWorkOrder(
row: ProductionWorkOrderItem,
keyword: string,
tabKey: ProductionWorkOrderTabKey
): boolean {
const kw = keyword.trim().toLowerCase()
if (!kw) return true
const tab = getProductionWorkOrderTab(tabKey)!
const fields =
tabKey === 'temporary'
? [row.remark]
: 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.sampleCode, row.labStorageCode, row.motherName, row.newbornName]
return fields.some((f) => (f || '').toLowerCase().includes(kw))
}
export function resolveWorkOrderTabKey(task: ProductionTaskRow): ProductionWorkOrderTabKey {
return resolveTabKeyFromWorkOrder({
workOrderType: task.workOrderType || task.orderSource,
receptionSource: task.receptionSource,
bizType: task.bizType
})
}
export function buildWorkOrderFromTasks(tasks: ProductionTaskRow[]): ProductionWorkOrderItem {
const t = tasks[0]
const tabKey = resolveWorkOrderTabKey(t)
const tab = getProductionWorkOrderTab(tabKey)!
return {
id: t.workOrderId,
workOrderNo: t.workOrderNo || '—',
workOrderType: tab.workOrderType,
workOrderStatus: t.workOrderStatus || '',
protocolNo: t.protocolNo || '',
sampleCode: t.sampleCode || '',
receptionSource: t.receptionSource,
receptionSourceLabel: t.receptionSourceLabel,
protocolStatus: t.protocolStatus,
bizType: tab.group === 'outbound' ? t.bizType : undefined,
bizTypeLabel: tab.group === 'outbound' ? t.bizTypeLabel : undefined,
applicationNo: t.applicationNo,
applicationName: t.applicationName,
channelName: t.channelName,
projectTitle: t.projectTitle,
customerName: t.customerName,
patientName: t.patientName,
sortNo: t.sortNo,
totalCourseCount: t.totalCourseCount,
infusionTime: t.infusionTime,
sampleTypeName: t.sampleTypeName,
handlerName: t.handlerName,
receptionTime: t.receptionTime,
labStorageCode: t.labStorageCode,
motherName: t.motherName,
newbornName: t.newbornName,
remark: t.workOrderRemark || t.remark,
createTime: t.workOrderCreateTime || t.createTime || ''
}
}
export function groupMineTasksByWorkOrder(tasks: ProductionTaskRow[]): MineWorkOrderEntry[] {
const map = new Map<string, ProductionTaskRow[]>()
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<string, ProductionTaskRow[]> {
const map: Record<string, ProductionTaskRow[]> = {}
for (const entry of entries) {
map[entry.workOrder.id] = entry.tasks
}
return map
}
async function fetchMineTasksByScope(
scope: 'current' | 'history',
filters: ProductionTaskMineFilters
): Promise<ProductionTaskRow[]> {
const tab = getProductionWorkOrderTab(filters.tabKey)!
const listQuery = isAllProductionWorkOrderTab(filters.tabKey)
? toAllTabListQuery(filters.productionCategory || '')
: toWorkOrderListQuery(tab)
const pageSize = MINE_TASK_FETCH_PAGE_SIZE
let page = 1
const all: ProductionTaskRow[] = []
let total = 0
while (true) {
const res = await fetchMyProductionTasks({
scope,
page,
pageSize,
keyword: filters.keyword || undefined,
taskStatus: filters.taskStatus || undefined,
workOrderType: listQuery.workOrderType,
receptionSource: listQuery.receptionSource,
bizType: listQuery.bizType,
includeStepSummary: true,
excludeVoidProtocol: filters.excludeVoidProtocol,
plannedStartTimeStart: filters.plannedStartTimeStart,
plannedStartTimeEnd: filters.plannedStartTimeEnd
})
const list = res.list || []
all.push(...list)
total = res.total || 0
if (!list.length || all.length >= total) break
page += 1
}
return all
}
/** 合并当前负责人任务与曾转发任务(方案 C:单列表) */
export async function fetchAllMineProductionTasks(
filters: ProductionTaskMineFilters
): Promise<ProductionTaskRow[]> {
const [current, history] = await Promise.all([
fetchMineTasksByScope('current', filters),
fetchMineTasksByScope('history', filters)
])
const map = new Map<string, ProductionTaskRow>()
for (const row of [...current, ...history]) {
if (row.id) map.set(row.id, row)
}
return [...map.values()]
}
export async function enrichMineEntriesWithWorkOrders(
entries: MineWorkOrderEntry[]
): Promise<MineWorkOrderEntry[]> {
const cache = new Map<string, ProductionWorkOrderItem>()
const uniqueIds = [...new Set(entries.map((entry) => entry.workOrderId))]
await Promise.all(
uniqueIds.map(async (id) => {
try {
const detail = await fetchProductionWorkOrderDetail(id)
cache.set(id, detail)
} catch {
// 保留任务聚合的工单摘要
}
})
)
return entries.map((entry) => {
const detail = cache.get(entry.workOrderId)
if (!detail) return entry
return {
...entry,
workOrder: {
...entry.workOrder,
...detail,
paymentSummary: detail.paymentSummary ?? entry.workOrder.paymentSummary,
feeSummary: detail.feeSummary ?? entry.workOrder.feeSummary
},
tabKey: resolveTabKeyFromWorkOrder(detail)
}
})
}
export function filterMineEntriesByWorkOrderSearch(
entries: MineWorkOrderEntry[],
filters: MineWorkOrderSearchFilters
): MineWorkOrderEntry[] {
const tabKey = filters.tabKey
return entries.filter((entry) => {
const row = entry.workOrder
if (!isAllProductionWorkOrderTab(tabKey) && resolveTabKeyFromWorkOrder(row) !== tabKey) {
return false
}
if (
isAllProductionWorkOrderTab(tabKey) &&
!workOrderMatchesCategory(row, filters.productionCategory || '')
) {
return false
}
if (!isAllProductionWorkOrderTab(tabKey) && entry.tabKey !== tabKey) return false
if (!matchesWorkOrderStatusFilter(row.workOrderStatus, filters.workOrderStatus)) return false
const keywordTabKey = isAllProductionWorkOrderTab(tabKey)
? resolveTabKeyFromWorkOrder(row)
: tabKey
if (!keywordMatchesWorkOrder(row, filters.keyword || '', keywordTabKey)) return false
return true
})
}
export function paginateMineEntries<T>(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 buildMineTableRows(pageEntries: MineWorkOrderEntry[]): {
rows: ProductionWorkOrderItem[]
presetTasksByWorkOrderId: Record<string, ProductionTaskRow[]>
} {
return {
rows: pageEntries.map((e) => ({
...e.workOrder,
plannedStartTimeRange: computeWorkOrderPlannedStartTimeRange(e.tasks)
})),
presetTasksByWorkOrderId: buildPresetTasksMap(pageEntries)
}
}