useWorkOrderApi.ts 6.01 KB
import type {
  WorkOrder,
  WorkOrderListQuery,
  WorkOrderMutationResponse,
  WorkOrderPayload,
  WorkOrderTransitionAction
} from '~/types'
import { WORK_ORDER_STATUS_VALUES } from '~/types/work-order'
import { createSharedComposable } from '@vueuse/core'

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

function isStatus(value: unknown): value is WorkOrder['status'] {
  return typeof value === 'string' && WORK_ORDER_STATUS_VALUES.includes(value as WorkOrder['status'])
}

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 normalizeWorkOrders(payload: unknown): WorkOrder[] {
  if (!Array.isArray(payload)) {
    return []
  }

  return payload
    .filter(isRecord)
    .map((item) => {
      const progress = isRecord(item.progress) ? item.progress : {}
      const audit = isRecord(item.audit) ? item.audit : {}
      const events = Array.isArray(item.events) ? item.events : []

      return {
        id: normalizeNumber(item.id),
        orderNo: normalizeString(item.orderNo),
        deviceCode: normalizeString(item.deviceCode),
        batchNo: normalizeString(item.batchNo),
        line: normalizeString(item.line),
        ownerUsername: normalizeString(item.ownerUsername),
        ownerName: normalizeString(item.ownerName),
        plannedDate: normalizeString(item.plannedDate),
        status: isStatus(item.status) ? item.status : 'draft',
        progress: {
          completedSn: normalizeNumber(progress.completedSn),
          totalSn: normalizeNumber(progress.totalSn)
        },
        audit: {
          createdBy: normalizeString(audit.createdBy),
          createdAt: normalizeString(audit.createdAt),
          updatedBy: normalizeString(audit.updatedBy),
          updatedAt: normalizeString(audit.updatedAt),
          lastAction: normalizeString(audit.lastAction) as WorkOrder['audit']['lastAction'],
          lastActionAt: normalizeString(audit.lastActionAt),
          lastActionBy: normalizeString(audit.lastActionBy)
        },
        events: events
          .filter(isRecord)
          .map(event => ({
            id: normalizeString(event.id),
            action: normalizeString(event.action) as WorkOrder['events'][number]['action'],
            fromStatus: isStatus(event.fromStatus) ? event.fromStatus : null,
            toStatus: isStatus(event.toStatus) ? event.toStatus : 'draft',
            operator: normalizeString(event.operator),
            at: normalizeString(event.at),
            remark: normalizeString(event.remark, undefined)
          }))
      } satisfies WorkOrder
    })
    .filter(item => item.id > 0 && item.orderNo.length > 0)
}

function normalizeMutation(payload: unknown, fallbackMessage: string): WorkOrderMutationResponse {
  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
  }
}

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

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

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

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

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

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

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

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

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

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

  const getWorkOrders = async (query: WorkOrderListQuery) => {
    const result = await api.request<unknown>(`/api/work-orders${buildQuery(query)}`, {
      method: 'GET'
    })

    return normalizeWorkOrders(result)
  }

  const createWorkOrder = async (payload: WorkOrderPayload & { operator: string }) => {
    try {
      const result = await api.request<unknown>('/api/work-orders', {
        method: 'POST',
        body: payload
      })

      return normalizeMutation(result, '创建工单失败。')
    } catch {
      return {
        success: false,
        errorCode: 'REQUEST_FAILED',
        message: '创建工单失败。'
      } satisfies WorkOrderMutationResponse
    }
  }

  const updateWorkOrderDraft = async (id: number, payload: WorkOrderPayload & { operator: string }) => {
    try {
      const result = await api.request<unknown>(`/api/work-orders/${id}`, {
        method: 'PUT',
        body: payload
      })

      return normalizeMutation(result, '更新工单失败。')
    } catch {
      return {
        success: false,
        errorCode: 'REQUEST_FAILED',
        message: '更新工单失败。'
      } satisfies WorkOrderMutationResponse
    }
  }

  const transitionWorkOrder = async (
    id: number,
    action: Exclude<WorkOrderTransitionAction, 'create' | 'edit_draft'>,
    payload: { operator: string, remark?: string }
  ) => {
    try {
      const result = await api.request<unknown>(`/api/work-orders/${id}/actions/${action}`, {
        method: 'POST',
        body: payload
      })

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

  return {
    getWorkOrders,
    createWorkOrder,
    updateWorkOrderDraft,
    transitionWorkOrder
  }
}

export const useWorkOrderApi = createSharedComposable(_useWorkOrderApi)