落库并展示质检工单级结论(可编辑/任务派生),库位树支持批量新增子节点与货架格子检索,协议/捐赠列表补充接收与门禁质检摘要,并同步生产物料与工单卡片展示优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
275 lines
8.8 KiB
TypeScript
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)
|
|
}
|
|
}
|