mock-dashboard-overview.ts 8.58 KB
import type {
  DashboardOverviewExportResponse,
  DashboardOverviewQuery,
  DashboardOverviewResponse,
  DashboardOverviewWorkOrderItem,
  DashboardWorkOrderStatus
} from '../../app/types'

interface MockDashboardWorkOrderRecord extends DashboardOverviewWorkOrderItem {
  inProcessSn: number
  frozenSn: number
  firstTestPassed: number
  firstTestTotal: number
  retestPassed: number
  retestTotal: number
  exceptionCount: number
}

const WORK_ORDER_STATUS_VALUES: DashboardWorkOrderStatus[] = [
  'pending_dispatch',
  'running',
  'pending_qc',
  'in_exception',
  'completed'
]

const MOCK_WORK_ORDER_DATA: MockDashboardWorkOrderRecord[] = [{
  orderNo: 'WO-202605-001',
  line: 'L1-装配线',
  status: 'running',
  plannedDate: '2026-05-17',
  completedSn: 82,
  totalSn: 120,
  inProcessSn: 31,
  frozenSn: 7,
  firstTestPassed: 70,
  firstTestTotal: 82,
  retestPassed: 8,
  retestTotal: 12,
  exceptionCount: 9
}, {
  orderNo: 'WO-202605-002',
  line: 'L2-测试线',
  status: 'pending_qc',
  plannedDate: '2026-05-17',
  completedSn: 56,
  totalSn: 56,
  inProcessSn: 0,
  frozenSn: 0,
  firstTestPassed: 49,
  firstTestTotal: 56,
  retestPassed: 4,
  retestTotal: 6,
  exceptionCount: 3
}, {
  orderNo: 'WO-202605-003',
  line: 'L3-总装线',
  status: 'in_exception',
  plannedDate: '2026-05-16',
  completedSn: 35,
  totalSn: 90,
  inProcessSn: 43,
  frozenSn: 12,
  firstTestPassed: 28,
  firstTestTotal: 35,
  retestPassed: 3,
  retestTotal: 5,
  exceptionCount: 14
}, {
  orderNo: 'WO-202605-004',
  line: 'L1-装配线',
  status: 'completed',
  plannedDate: '2026-05-16',
  completedSn: 130,
  totalSn: 130,
  inProcessSn: 0,
  frozenSn: 0,
  firstTestPassed: 120,
  firstTestTotal: 130,
  retestPassed: 7,
  retestTotal: 9,
  exceptionCount: 2
}, {
  orderNo: 'WO-202605-005',
  line: 'L2-测试线',
  status: 'pending_dispatch',
  plannedDate: '2026-05-15',
  completedSn: 0,
  totalSn: 75,
  inProcessSn: 75,
  frozenSn: 0,
  firstTestPassed: 0,
  firstTestTotal: 0,
  retestPassed: 0,
  retestTotal: 0,
  exceptionCount: 0
}, {
  orderNo: 'WO-202605-006',
  line: 'L3-总装线',
  status: 'running',
  plannedDate: '2026-05-15',
  completedSn: 46,
  totalSn: 88,
  inProcessSn: 39,
  frozenSn: 3,
  firstTestPassed: 40,
  firstTestTotal: 46,
  retestPassed: 3,
  retestTotal: 4,
  exceptionCount: 5
}, {
  orderNo: 'WO-202605-007',
  line: 'L1-装配线',
  status: 'pending_qc',
  plannedDate: '2026-05-14',
  completedSn: 64,
  totalSn: 64,
  inProcessSn: 0,
  frozenSn: 0,
  firstTestPassed: 57,
  firstTestTotal: 64,
  retestPassed: 5,
  retestTotal: 7,
  exceptionCount: 4
}, {
  orderNo: 'WO-202605-008',
  line: 'L2-测试线',
  status: 'completed',
  plannedDate: '2026-05-13',
  completedSn: 108,
  totalSn: 108,
  inProcessSn: 0,
  frozenSn: 0,
  firstTestPassed: 100,
  firstTestTotal: 108,
  retestPassed: 6,
  retestTotal: 7,
  exceptionCount: 2
}, {
  orderNo: 'WO-202605-009',
  line: 'L3-总装线',
  status: 'running',
  plannedDate: '2026-05-12',
  completedSn: 52,
  totalSn: 100,
  inProcessSn: 42,
  frozenSn: 6,
  firstTestPassed: 45,
  firstTestTotal: 52,
  retestPassed: 4,
  retestTotal: 6,
  exceptionCount: 7
}, {
  orderNo: 'WO-202605-010',
  line: 'L1-装配线',
  status: 'in_exception',
  plannedDate: '2026-05-11',
  completedSn: 28,
  totalSn: 72,
  inProcessSn: 33,
  frozenSn: 11,
  firstTestPassed: 21,
  firstTestTotal: 28,
  retestPassed: 2,
  retestTotal: 4,
  exceptionCount: 12
}, {
  orderNo: 'WO-202605-011',
  line: 'L2-测试线',
  status: 'running',
  plannedDate: '2026-05-10',
  completedSn: 61,
  totalSn: 92,
  inProcessSn: 26,
  frozenSn: 5,
  firstTestPassed: 54,
  firstTestTotal: 61,
  retestPassed: 4,
  retestTotal: 6,
  exceptionCount: 6
}, {
  orderNo: 'WO-202605-012',
  line: 'L3-总装线',
  status: 'pending_dispatch',
  plannedDate: '2026-05-09',
  completedSn: 0,
  totalSn: 80,
  inProcessSn: 80,
  frozenSn: 0,
  firstTestPassed: 0,
  firstTestTotal: 0,
  retestPassed: 0,
  retestTotal: 0,
  exceptionCount: 0
}]

function isValidDate(value?: string) {
  if (!value) {
    return false
  }

  const date = new Date(`${value}T00:00:00`)
  return !Number.isNaN(date.getTime())
}

function inRange(dateText: string, start?: string, end?: string) {
  if (!isValidDate(dateText)) {
    return false
  }

  const current = new Date(`${dateText}T00:00:00`).getTime()
  const startTime = isValidDate(start) ? new Date(`${start}T00:00:00`).getTime() : null
  const endTime = isValidDate(end) ? new Date(`${end}T23:59:59`).getTime() : null

  if (startTime !== null && current < startTime) {
    return false
  }

  if (endTime !== null && current > endTime) {
    return false
  }

  return true
}

function safePercent(numerator: number, denominator: number) {
  if (denominator <= 0) {
    return 0
  }

  return Number(((numerator / denominator) * 100).toFixed(2))
}

function normalizeQuery(query: DashboardOverviewQuery): DashboardOverviewQuery {
  return {
    start: typeof query.start === 'string' ? query.start.trim() || undefined : undefined,
    end: typeof query.end === 'string' ? query.end.trim() || undefined : undefined,
    line: typeof query.line === 'string' ? query.line.trim() || undefined : undefined,
    workOrderStatus: WORK_ORDER_STATUS_VALUES.includes(query.workOrderStatus as DashboardWorkOrderStatus)
      ? query.workOrderStatus
      : undefined
  }
}

function filterRecords(query: DashboardOverviewQuery) {
  return MOCK_WORK_ORDER_DATA.filter((item) => {
    if (!inRange(item.plannedDate, query.start, query.end)) {
      return false
    }

    if (query.line && item.line !== query.line) {
      return false
    }

    if (query.workOrderStatus && item.status !== query.workOrderStatus) {
      return false
    }

    return true
  })
}

export function getDashboardOverview(query: DashboardOverviewQuery): DashboardOverviewResponse {
  const normalizedQuery = normalizeQuery(query)
  const list = filterRecords(normalizedQuery)

  const totalWorkOrders = list.length
  const runningCount = list.filter(item => item.status === 'running').length
  const pendingQcCount = list.filter(item => item.status === 'pending_qc').length
  const exceptionCount = list.filter(item => item.status === 'in_exception').length

  const completedSn = list.reduce((sum, item) => sum + item.completedSn, 0)
  const totalSn = list.reduce((sum, item) => sum + item.totalSn, 0)
  const frozenSn = list.reduce((sum, item) => sum + item.frozenSn, 0)
  const inProcessSn = list.reduce((sum, item) => sum + item.inProcessSn, 0)

  const firstTestPassed = list.reduce((sum, item) => sum + item.firstTestPassed, 0)
  const firstTestTotal = list.reduce((sum, item) => sum + item.firstTestTotal, 0)
  const retestPassed = list.reduce((sum, item) => sum + item.retestPassed, 0)
  const retestTotal = list.reduce((sum, item) => sum + item.retestTotal, 0)
  const totalExceptions = list.reduce((sum, item) => sum + item.exceptionCount, 0)

  const latestWorkOrders = list
    .slice()
    .sort((a, b) => b.plannedDate.localeCompare(a.plannedDate))
    .slice(0, 8)
    .map(item => ({
      orderNo: item.orderNo,
      line: item.line,
      status: item.status,
      plannedDate: item.plannedDate,
      completedSn: item.completedSn,
      totalSn: item.totalSn
    }))

  return {
    query: normalizedQuery,
    filters: {
      lines: Array.from(new Set(MOCK_WORK_ORDER_DATA.map(item => item.line))),
      workOrderStatuses: WORK_ORDER_STATUS_VALUES
    },
    workOrders: {
      total: totalWorkOrders,
      running: runningCount,
      pendingQc: pendingQcCount,
      inException: exceptionCount
    },
    sn: {
      completed: completedSn,
      inProcess: inProcessSn,
      frozen: frozenSn
    },
    quality: {
      firstPassRate: safePercent(firstTestPassed, firstTestTotal),
      retestPassRate: safePercent(retestPassed, retestTotal),
      exceptionRate: safePercent(totalExceptions, totalSn)
    },
    latestWorkOrders
  }
}

export function exportDashboardOverview(query: DashboardOverviewQuery): DashboardOverviewExportResponse {
  const overview = getDashboardOverview(query)

  const rows = overview.latestWorkOrders.map((item) => {
    return [
      item.orderNo,
      item.line,
      item.status,
      item.plannedDate,
      String(item.completedSn),
      String(item.totalSn)
    ]
  })

  const csvLines = [
    ['orderNo', 'line', 'status', 'plannedDate', 'completedSn', 'totalSn'].join(','),
    ...rows.map(row => row.join(','))
  ]

  const content = csvLines.join('\n')
  const dateSuffix = new Date().toISOString().slice(0, 10)

  return {
    success: true,
    errorCode: null,
    message: '汇总导出已生成。',
    fileName: `dashboard-overview-${dateSuffix}.csv`,
    contentType: 'text/csv;charset=utf-8',
    content
  }
}