useOperationApi.ts 6.45 KB
import type {
  OperationTask,
  OperationTaskAction,
  OperationTaskActionPayload,
  OperationEvidenceUploadResponse,
  OperationTaskListQuery,
  OperationTaskMutationResponse,
  OperationTaskResult,
  OperationTaskStatus
} from '~/types'
import { OPERATION_TASK_ACTION_VALUES, OPERATION_TASK_STATUS_VALUES } from '~/types/operation'
import { createSharedComposable } from '@vueuse/core'

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null
}

function normalizeString(value: unknown, fallback = '') {
  return typeof value === 'string' ? value : fallback
}

function normalizeNumber(value: unknown, fallback = 0) {
  const num = Number(value)
  return Number.isFinite(num) ? num : fallback
}

function isStatus(value: unknown): value is OperationTaskStatus {
  return typeof value === 'string' && OPERATION_TASK_STATUS_VALUES.includes(value as OperationTaskStatus)
}

function isAction(value: unknown): value is OperationTaskAction {
  return typeof value === 'string' && OPERATION_TASK_ACTION_VALUES.includes(value as OperationTaskAction)
}

function isResult(value: unknown): value is OperationTaskResult {
  return value === 'pending' || value === 'pass' || value === 'fail'
}

function normalizeTasks(payload: unknown): OperationTask[] {
  if (!Array.isArray(payload)) {
    return []
  }

  return payload
    .filter(isRecord)
    .map((item) => {
      const auditEvents = Array.isArray(item.auditEvents) ? item.auditEvents : []

      return {
        id: normalizeNumber(item.id),
        workOrderNo: normalizeString(item.workOrderNo),
        sn: normalizeString(item.sn),
        stepName: normalizeString(item.stepName),
        workstation: normalizeString(item.workstation),
        device: normalizeString(item.device),
        operator: normalizeString(item.operator),
        startedAt: normalizeString(item.startedAt, undefined),
        endedAt: normalizeString(item.endedAt, undefined),
        result: isResult(item.result) ? item.result : 'pending',
        status: isStatus(item.status) ? item.status : 'pending',
        nextAction: normalizeString(item.nextAction),
        auditEvents: auditEvents
          .filter(isRecord)
          .map(event => ({
            id: normalizeString(event.id),
            action: isAction(event.action) ? event.action : 'start',
            fromStatus: isStatus(event.fromStatus) ? event.fromStatus : null,
            toStatus: isStatus(event.toStatus) ? event.toStatus : 'pending',
            operator: normalizeString(event.operator),
            at: normalizeString(event.at),
            remark: normalizeString(event.remark, undefined),
            evidencePath: normalizeString(event.evidencePath, undefined),
            nextAction: normalizeString(event.nextAction)
          }))
      } satisfies OperationTask
    })
    .filter(item => item.id > 0 && item.sn.length > 0)
}

function normalizeMutation(payload: unknown, fallbackMessage: string): OperationTaskMutationResponse {
  if (!isRecord(payload)) {
    return {
      success: false,
      errorCode: 'REQUEST_FAILED',
      message: fallbackMessage
    }
  }

  return {
    success: Boolean(payload.success),
    errorCode: typeof payload.errorCode === 'string' ? payload.errorCode : null,
    message: typeof payload.message === 'string' && payload.message.length > 0
      ? payload.message
      : fallbackMessage,
    state: isStatus(payload.state) ? payload.state : undefined,
    nextAction: typeof payload.nextAction === 'string' ? payload.nextAction : undefined
  }
}

function normalizeUpload(payload: unknown, fallbackMessage: string): OperationEvidenceUploadResponse {
  if (!isRecord(payload)) {
    return {
      success: false,
      errorCode: 'REQUEST_FAILED',
      message: fallbackMessage
    }
  }

  return {
    success: Boolean(payload.success),
    errorCode: typeof payload.errorCode === 'string' ? payload.errorCode : null,
    message: typeof payload.message === 'string' && payload.message.length > 0
      ? payload.message
      : fallbackMessage,
    relativePath: typeof payload.relativePath === 'string' && payload.relativePath.length > 0
      ? payload.relativePath
      : undefined
  }
}

function buildQuery(query: OperationTaskListQuery) {
  const params = new URLSearchParams()

  if (query.workstation) {
    params.set('workstation', query.workstation)
  }

  if (query.stepName) {
    params.set('stepName', query.stepName)
  }

  if (query.operator) {
    params.set('operator', query.operator)
  }

  if (query.status) {
    params.set('status', query.status)
  }

  if (query.sn) {
    params.set('sn', query.sn)
  }

  const search = params.toString()
  return search.length > 0 ? `?${search}` : ''
}

const _useOperationApi = () => {
  const api = useApiGateway()

  const getOperationTasks = async (query: OperationTaskListQuery) => {
    const result = await api.request<unknown>(`/api/operations${buildQuery(query)}`, {
      method: 'GET'
    })

    return normalizeTasks(result)
  }

  const runOperationAction = async (
    id: number,
    action: OperationTaskAction | string,
    payload: OperationTaskActionPayload
  ) => {
    if (!isAction(action)) {
      return {
        success: false,
        errorCode: 'VALIDATION_ERROR',
        message: '工序任务动作不支持。'
      } satisfies OperationTaskMutationResponse
    }

    const actionPath = `/api/operations/${id}/actions/${action}`

    try {
      const result = await api.request<unknown>(actionPath, {
        method: 'POST',
        body: payload,
        remotePath: actionPath
      })

      return normalizeMutation(result, '工序任务状态更新失败。')
    } catch {
      return {
        success: false,
        errorCode: 'REQUEST_FAILED',
        message: '工序任务状态更新失败。'
      } satisfies OperationTaskMutationResponse
    }
  }

  const uploadOperationEvidence = async (file: File): Promise<OperationEvidenceUploadResponse> => {
    const formData = new FormData()
    formData.append('file', file)

    try {
      const result = await api.request<unknown>('/api/files/operation-evidence', {
        method: 'POST',
        body: formData
      })

      return normalizeUpload(result, '附件上传失败。')
    } catch {
      return {
        success: false,
        errorCode: 'REQUEST_FAILED',
        message: '附件上传失败。'
      }
    }
  }

  return {
    getOperationTasks,
    runOperationAction,
    uploadOperationEvidence
  }
}

export const useOperationApi = createSharedComposable(_useOperationApi)