/** * 작업지시 관리 서버 액션 * * API Endpoints: * - GET /api/v1/work-orders - 목록 조회 * - GET /api/v1/work-orders/stats - 통계 조회 * - GET /api/v1/work-orders/{id} - 상세 조회 * - POST /api/v1/work-orders - 등록 * - PUT /api/v1/work-orders/{id} - 수정 * - DELETE /api/v1/work-orders/{id} - 삭제 * - PATCH /api/v1/work-orders/{id}/status - 상태 변경 * - PATCH /api/v1/work-orders/{id}/assign - 담당자 배정 * - PATCH /api/v1/work-orders/{id}/bending/toggle - 벤딩 필드 토글 * - POST /api/v1/work-orders/{id}/issues - 이슈 등록 * - PATCH /api/v1/work-orders/{id}/issues/{issueId}/resolve - 이슈 해결 * - PATCH /api/v1/work-orders/{id}/items/{itemId}/status - 품목 상태 변경 */ 'use server'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { serverFetch } from '@/lib/api/fetch-wrapper'; import { buildApiUrl } from '@/lib/api/query-params'; import type { WorkOrder, WorkOrderStats, WorkOrderStatus, WorkOrderApiPaginatedResponse, WorkOrderStatsApi, } from './types'; import { transformApiToFrontend, transformFrontendToApi, transformStatsApiToFrontend, } from './types'; // ===== 페이지네이션 타입 ===== interface PaginationMeta { currentPage: number; lastPage: number; perPage: number; total: number; } // ===== 작업지시 목록 조회 ===== export async function getWorkOrders(params?: { page?: number; perPage?: number; status?: WorkOrderStatus | 'all'; processId?: number | 'all' | 'none'; // 공정 ID (FK → processes.id), 'none' = 미지정 processType?: 'screen' | 'slat' | 'bending'; // 공정 타입 필터 priority?: string; // 우선순위 필터 (urgent/priority/normal) search?: string; startDate?: string; endDate?: string; }): Promise<{ success: boolean; data: WorkOrder[]; pagination: PaginationMeta; error?: string; }> { const emptyResponse = { success: false, data: [] as WorkOrder[], pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }, }; try { const url = buildApiUrl('/api/v1/work-orders', { page: params?.page, per_page: params?.perPage, status: params?.status && params.status !== 'all' ? params.status : undefined, // 'none': 공정 미지정 필터 (process_id IS NULL), 'all': 제외 process_id: params?.processId && params.processId !== 'all' ? String(params.processId) : undefined, process_type: params?.processType, priority: params?.priority && params.priority !== 'all' ? params.priority : undefined, search: params?.search, start_date: params?.startDate, end_date: params?.endDate, }); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { ...emptyResponse, error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.warn('[WorkOrderActions] GET work-orders error:', response.status); return { ...emptyResponse, error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success) { return { ...emptyResponse, error: result.message || '작업지시 목록 조회에 실패했습니다.', }; } const paginatedData: WorkOrderApiPaginatedResponse = result.data || { data: [], current_page: 1, last_page: 1, per_page: 20, total: 0, }; const workOrders = (paginatedData.data || []).map(transformApiToFrontend); return { success: true, data: workOrders, pagination: { currentPage: paginatedData.current_page, lastPage: paginatedData.last_page, perPage: paginatedData.per_page, total: paginatedData.total, }, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getWorkOrders error:', error); return { ...emptyResponse, error: '서버 오류가 발생했습니다.' }; } } // ===== 작업지시 통계 조회 ===== export async function getWorkOrderStats(): Promise<{ success: boolean; data?: WorkOrderStats; error?: string; }> { try { const url = buildApiUrl('/api/v1/work-orders/stats'); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.warn('[WorkOrderActions] GET stats error:', response.status); return { success: false, error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, error: result.message || '통계 조회에 실패했습니다.', }; } const statsApi: WorkOrderStatsApi = result.data; return { success: true, data: transformStatsApiToFrontend(statsApi), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getWorkOrderStats error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 작업지시 상세 조회 ===== export async function getWorkOrderById(id: string): Promise<{ success: boolean; data?: WorkOrder; error?: string; }> { try { const url = buildApiUrl(`/api/v1/work-orders/${id}`); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.error('[WorkOrderActions] GET work-order error:', response.status); return { success: false, error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success || !result.data) { return { success: false, error: result.message || '작업지시 조회에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getWorkOrderById error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 작업지시 등록 ===== export async function createWorkOrder( data: Partial & { salesOrderId?: number; assigneeId?: number; // 단일 담당자 (하위 호환) assigneeIds?: number[]; // 다중 담당자 teamId?: number; items?: Array<{ // 수동 등록 시 품목 목록 item_id?: number; item_name: string; specification?: string; quantity?: number; unit?: string; }>; } ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { // 다중 담당자 우선, 없으면 단일 담당자 배열로 변환 const assigneeIds = data.assigneeIds && data.assigneeIds.length > 0 ? data.assigneeIds : data.assigneeId ? [data.assigneeId] : undefined; const apiData = { ...transformFrontendToApi(data), sales_order_id: data.salesOrderId, assignee_ids: assigneeIds, // 배열로 전송 team_id: data.teamId, ...(data.items && data.items.length > 0 ? { items: data.items } : {}), }; const { response, error } = await serverFetch( buildApiUrl('/api/v1/work-orders'), { method: 'POST', body: JSON.stringify(apiData), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '작업지시 등록에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] createWorkOrder error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 품목 검색 (수동 등록용) ===== export interface ManualItemOption { id: number; code: string; name: string; specification: string; unit: string; itemCategory: string; } export async function searchItemsForWorkOrder( query?: string, itemCategory?: string ): Promise<{ success: boolean; data: ManualItemOption[] }> { try { const { response, error } = await serverFetch( buildApiUrl('/api/v1/items', { search: query, item_category: itemCategory, per_page: 50, }), { method: 'GET' } ); if (error || !response) { return { success: false, data: [] }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, data: [] }; } const items: ManualItemOption[] = (result.data?.data || []).map((item: Record) => ({ id: item.id as number, code: (item.item_code || item.code) as string, name: (item.item_name || item.name) as string, specification: (item.specification || '') as string, unit: (item.unit || 'EA') as string, itemCategory: (item.item_category || '') as string, })); return { success: true, data: items }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] searchItemsForWorkOrder error:', error); return { success: false, data: [] }; } } // ===== 작업지시 수정 ===== export async function updateWorkOrder( id: string, data: Partial & { assigneeIds?: number[] } ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { const apiData = transformFrontendToApi(data); const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}`), { method: 'PUT', body: JSON.stringify(apiData), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '작업지시 수정에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] updateWorkOrder error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 작업지시 삭제 ===== export async function deleteWorkOrder(id: string): Promise<{ success: boolean; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}`), { method: 'DELETE' } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '작업지시 삭제에 실패했습니다.', }; } return { success: true }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] deleteWorkOrder error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 작업지시 상태 변경 ===== export async function updateWorkOrderStatus( id: string, status: WorkOrderStatus ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}/status`), { method: 'PATCH', body: JSON.stringify({ status }), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '상태 변경에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] updateWorkOrderStatus error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 담당자 배정 ===== export async function assignWorkOrder( id: string, assigneeIds: number | number[], // 단일 또는 다중 담당자 teamId?: number ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { // 배열로 통일 const ids = Array.isArray(assigneeIds) ? assigneeIds : [assigneeIds]; const body: { assignee_ids: number[]; team_id?: number } = { assignee_ids: ids }; if (teamId) body.team_id = teamId; const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}/assign`), { method: 'PATCH', body: JSON.stringify(body), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '담당자 배정에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] assignWorkOrder error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 벤딩 필드 토글 ===== export async function toggleBendingField( id: string, field: string ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}/bending/toggle`), { method: 'PATCH', body: JSON.stringify({ field }), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '벤딩 필드 토글에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] toggleBendingField error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 이슈 등록 ===== export async function addWorkOrderIssue( id: string, data: { title: string; description?: string; priority?: 'low' | 'medium' | 'high'; } ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${id}/issues`), { method: 'POST', body: JSON.stringify(data), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '이슈 등록에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] addWorkOrderIssue error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 이슈 해결 ===== export async function resolveWorkOrderIssue( workOrderId: string, issueId: string ): Promise<{ success: boolean; data?: WorkOrder; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/issues/${issueId}/resolve`), { method: 'PATCH' } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '이슈 해결 처리에 실패했습니다.', }; } return { success: true, data: transformApiToFrontend(result.data), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] resolveWorkOrderIssue error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 품목 상태 변경 ===== export type WorkOrderItemStatus = 'waiting' | 'in_progress' | 'completed'; export async function updateWorkOrderItemStatus( workOrderId: string, itemId: number, status: WorkOrderItemStatus ): Promise<{ success: boolean; itemId: number; status: WorkOrderItemStatus; workOrderStatus?: string; workOrderStatusChanged?: boolean; error?: string; }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/items/${itemId}/status`), { method: 'PATCH', body: JSON.stringify({ status }), } ); if (error || !response) { return { success: false, itemId, status, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, itemId, status, error: result.message || '품목 상태 변경에 실패했습니다.', }; } return { success: true, itemId, status: result.data?.item?.status || status, workOrderStatus: result.data?.work_order_status, workOrderStatusChanged: result.data?.work_order_status_changed || false, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] updateWorkOrderItemStatus error:', error); return { success: false, itemId, status, error: '서버 오류가 발생했습니다.' }; } } // ===== 중간검사 성적서 데이터 조회 ===== export interface InspectionReportItem { id: number; item_name: string; specification: string; quantity: number; sort_order: number; status: string; options: Record | null; inspection_data: Record | null; } export interface InspectionReportNodeGroup { node_id: number | null; node_name: string; floor: string; code: string; width: number; height: number; total_quantity: number; options: Record; items: InspectionReportItem[]; } export interface InspectionReportData { work_order: { id: number; order_no: string; status: string; planned_date: string | null; due_date: string | null; }; order: { id: number; order_no: string; client_name: string | null; site_name: string | null; order_date: string | null; } | null; node_groups?: InspectionReportNodeGroup[]; items: InspectionReportItem[]; summary: { total_items: number; inspected_items: number; passed_items: number; failed_items: number; }; } export async function getInspectionReport( workOrderId: string ): Promise<{ success: boolean; data?: InspectionReportData; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-report`), { method: 'GET' } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '검사 성적서 데이터를 불러올 수 없습니다.', }; } return { success: true, data: result.data }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getInspectionReport error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 중간검사 데이터 저장 (deprecated: WorkerScreen/actions.ts의 saveItemInspection 사용) ===== /** @deprecated WorkerScreen/actions.ts의 saveItemInspection을 사용하세요 */ export async function saveInspectionData( workOrderId: string, processType: string, data: unknown ): Promise<{ success: boolean; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection`), { method: 'POST', body: JSON.stringify({ process_type: processType, inspection_data: data, }), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '검사 데이터 저장에 실패했습니다.', }; } return { success: true }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] saveInspectionData error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 검사 문서 템플릿 조회 (document_template 기반) ===== import type { InspectionTemplateData } from '@/components/production/WorkerScreen/types'; export async function getInspectionTemplate( workOrderId: string ): Promise<{ success: boolean; data?: InspectionTemplateData; error?: string }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-template`), { method: 'GET' } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '검사 템플릿을 불러올 수 없습니다.' }; } return { success: true, data: result.data }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getInspectionTemplate error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 검사 문서 저장 (Document + DocumentData) ===== export async function saveInspectionDocument( workOrderId: string, data: { step_id?: number; title?: string; data: Record[]; approvers?: { role_name: string; user_id?: number }[]; } ): Promise<{ success: boolean; data?: { document_id: number; document_no: string; status: string }; error?: string; }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-document`), { method: 'POST', body: JSON.stringify(data), } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '검사 문서 저장에 실패했습니다.' }; } return { success: true, data: result.data }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] saveInspectionDocument error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 검사 문서 resolve (기존 문서/템플릿 조회) ===== export async function resolveInspectionDocument( workOrderId: string, params?: { step_id?: number } ): Promise<{ success: boolean; data?: { mode: 'existing' | 'new'; document?: Record; template?: Record }; error?: string; }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/inspection-resolve`, { step_id: params?.step_id }), { method: 'GET' } ); if (error || !response) { return { success: false, error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '검사 문서 조회에 실패했습니다.' }; } return { success: true, data: result.data }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] resolveInspectionDocument error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 수주 목록 조회 (작업지시 생성용) ===== export interface SalesOrderForWorkOrder { id: number; orderNo: string; client: string; projectName: string; dueDate: string; status: string; itemCount: number; splitCount: number; } export async function getSalesOrdersForWorkOrder(params?: { q?: string; status?: string; }): Promise<{ success: boolean; data: SalesOrderForWorkOrder[]; error?: string; }> { try { // 작업지시 생성 가능한 상태만 조회 (예: 회계확인 완료) const url = buildApiUrl('/api/v1/orders', { for_work_order: '1', q: params?.q, status: params?.status, }); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { success: false, data: [], error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.warn('[WorkOrderActions] GET orders error:', response.status); return { success: false, data: [], error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], error: result.message || '수주 목록 조회에 실패했습니다.', }; } // API 응답 변환 const salesOrders: SalesOrderForWorkOrder[] = (result.data?.data || result.data || []).map( (item: { id: number; order_no: string; client?: { name: string }; project_name?: string; due_date?: string; status: string; items_count?: number; split_count?: number; }) => ({ id: item.id, orderNo: item.order_no, client: item.client?.name || '-', projectName: item.project_name || '-', dueDate: item.due_date || '-', status: item.status, itemCount: item.items_count || 0, splitCount: item.split_count || 0, }) ); return { success: true, data: salesOrders, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getSalesOrdersForWorkOrder error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } } // ===== 부서 + 사용자 조회 (담당자 선택용) ===== export interface DepartmentUser { id: number; name: string; email: string; } export interface DepartmentWithUsers { id: number; name: string; code: string | null; users: DepartmentUser[]; children: DepartmentWithUsers[]; } export async function getDepartmentsWithUsers(): Promise<{ success: boolean; data: DepartmentWithUsers[]; error?: string; }> { try { const url = buildApiUrl('/api/v1/departments/tree', { with_users: 1 }); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { success: false, data: [], error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.warn('[WorkOrderActions] GET departments error:', response.status); return { success: false, data: [], error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], error: result.message || '부서 목록 조회에 실패했습니다.', }; } // API 응답을 프론트엔드 형식으로 변환 const transformDepartment = (dept: { id: number; name: string; code: string | null; users?: { id: number; name: string; email: string }[]; children?: unknown[]; }): DepartmentWithUsers => ({ id: dept.id, name: dept.name, code: dept.code, users: (dept.users || []).map((u) => ({ id: u.id, name: u.name, email: u.email, })), children: (dept.children || []).map((child) => transformDepartment(child as typeof dept) ), }); const departments = (result.data || []).map(transformDepartment); return { success: true, data: departments, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getDepartmentsWithUsers error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } } // ===== 공정 목록 조회 (작업지시 생성용) ===== export interface ProcessOption { id: number; processCode: string; processName: string; } export async function getProcessOptions(): Promise<{ success: boolean; data: ProcessOption[]; error?: string; }> { try { const url = buildApiUrl('/api/v1/processes/options'); const { response, error } = await serverFetch(url, { method: 'GET' }); if (error || !response) { return { success: false, data: [], error: error?.message || 'API 요청 실패' }; } if (!response.ok) { console.warn('[WorkOrderActions] GET process options error:', response.status); return { success: false, data: [], error: `API 오류: ${response.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], error: result.message || '공정 목록 조회에 실패했습니다.', }; } // API 응답 변환 const processes: ProcessOption[] = (result.data || []).map( (item: { id: number; process_code: string; process_name: string; }) => ({ id: item.id, processCode: item.process_code, processName: item.process_name, }) ); return { success: true, data: processes, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getProcessOptions error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } } // ===== 자재 투입 LOT 번호 조회 ===== export interface MaterialInputLot { lot_no: string; item_code: string; item_name: string; total_qty: number; input_count: number; first_input_at: string; } export async function getMaterialInputLots(workOrderId: string): Promise<{ success: boolean; data: MaterialInputLot[]; error?: string; }> { try { const { response, error } = await serverFetch( buildApiUrl(`/api/v1/work-orders/${workOrderId}/material-input-lots`), { method: 'GET' } ); if (error || !response) { return { success: false, data: [], error: error?.message || 'API 요청 실패' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, data: [], error: result.message || '투입 LOT 조회에 실패했습니다.' }; } return { success: true, data: result.data || [] }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[WorkOrderActions] getMaterialInputLots error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } }