/** * 입고 관리 서버 액션 * * API Endpoints: * - GET /api/v1/receivings - 목록 조회 * - GET /api/v1/receivings/stats - 통계 조회 * - GET /api/v1/receivings/{id} - 상세 조회 * - POST /api/v1/receivings - 등록 * - PUT /api/v1/receivings/{id} - 수정 * - DELETE /api/v1/receivings/{id} - 삭제 * - POST /api/v1/receivings/{id}/process - 입고처리 */ 'use server'; // ===== 목데이터 모드 플래그 ===== const USE_MOCK_DATA = true; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { serverFetch } from '@/lib/api/fetch-wrapper'; import type { ReceivingItem, ReceivingDetail, ReceivingStats, ReceivingStatus, ReceivingProcessFormData, } from './types'; // ===== 목데이터 ===== const MOCK_RECEIVING_LIST: ReceivingItem[] = [ { id: '1', lotNo: 'LOT-2026-001', inspectionStatus: '적', inspectionDate: '2026-01-25', supplier: '(주)대한철강', itemCode: 'STEEL-001', itemName: 'SUS304 스테인리스 판재', specification: '1000x2000x3T', unit: 'EA', receivingQty: 100, receivingDate: '2026-01-26', createdBy: '김철수', status: 'completed', }, { id: '2', lotNo: 'LOT-2026-002', inspectionStatus: '적', inspectionDate: '2026-01-26', supplier: '삼성전자부품', itemCode: 'ELEC-002', itemName: 'MCU 컨트롤러 IC', specification: 'STM32F103C8T6', unit: 'EA', receivingQty: 500, receivingDate: '2026-01-27', createdBy: '이영희', status: 'completed', }, { id: '3', lotNo: 'LOT-2026-003', inspectionStatus: '-', inspectionDate: undefined, supplier: '한국플라스틱', itemCode: 'PLAS-003', itemName: 'ABS 사출 케이스', specification: '150x100x50', unit: 'SET', receivingQty: undefined, receivingDate: undefined, createdBy: '박민수', status: 'receiving_pending', }, { id: '4', lotNo: 'LOT-2026-004', inspectionStatus: '부적', inspectionDate: '2026-01-27', supplier: '(주)대한철강', itemCode: 'STEEL-002', itemName: '알루미늄 프로파일', specification: '40x40x2000L', unit: 'EA', receivingQty: 50, receivingDate: '2026-01-28', createdBy: '김철수', status: 'inspection_pending', }, { id: '5', lotNo: 'LOT-2026-005', inspectionStatus: '-', inspectionDate: undefined, supplier: '글로벌전자', itemCode: 'ELEC-005', itemName: 'DC 모터 24V', specification: '24V 100RPM', unit: 'EA', receivingQty: undefined, receivingDate: undefined, createdBy: '최지훈', status: 'receiving_pending', }, { id: '6', lotNo: 'LOT-2026-006', inspectionStatus: '적', inspectionDate: '2026-01-24', supplier: '동양화학', itemCode: 'CHEM-001', itemName: '에폭시 접착제', specification: '500ml', unit: 'EA', receivingQty: 200, receivingDate: '2026-01-25', createdBy: '이영희', status: 'completed', }, { id: '7', lotNo: 'LOT-2026-007', inspectionStatus: '적', inspectionDate: '2026-01-28', supplier: '삼성전자부품', itemCode: 'ELEC-007', itemName: '커패시터 100uF', specification: '100uF 50V', unit: 'EA', receivingQty: 1000, receivingDate: '2026-01-28', createdBy: '박민수', status: 'completed', }, { id: '8', lotNo: 'LOT-2026-008', inspectionStatus: '-', inspectionDate: undefined, supplier: '한국볼트', itemCode: 'BOLT-001', itemName: 'SUS 볼트 M8x30', specification: 'M8x30 SUS304', unit: 'EA', receivingQty: undefined, receivingDate: undefined, createdBy: '김철수', status: 'receiving_pending', }, ]; const MOCK_RECEIVING_STATS: ReceivingStats = { receivingPendingCount: 3, receivingCompletedCount: 4, inspectionPendingCount: 1, inspectionCompletedCount: 5, }; // 기획서 2026-01-28 기준 상세 목데이터 const MOCK_RECEIVING_DETAIL: Record = { '1': { id: '1', // 기본 정보 lotNo: 'LOT-2026-001', itemCode: 'STEEL-001', itemName: 'SUS304 스테인리스 판재', specification: '1000x2000x3T', unit: 'EA', supplier: '(주)대한철강', receivingQty: 100, receivingDate: '2026-01-26', createdBy: '김철수', status: 'completed', remark: '', // 수입검사 정보 inspectionDate: '2026-01-25', inspectionResult: '합격', certificateFile: undefined, // 하위 호환 orderNo: 'PO-2026-001', orderUnit: 'EA', }, '2': { id: '2', lotNo: 'LOT-2026-002', itemCode: 'ELEC-002', itemName: 'MCU 컨트롤러 IC', specification: 'STM32F103C8T6', unit: 'EA', supplier: '삼성전자부품', receivingQty: 500, receivingDate: '2026-01-27', createdBy: '이영희', status: 'completed', remark: '긴급 입고', inspectionDate: '2026-01-26', inspectionResult: '합격', orderNo: 'PO-2026-002', orderUnit: 'EA', }, '3': { id: '3', lotNo: 'LOT-2026-003', itemCode: 'PLAS-003', itemName: 'ABS 사출 케이스', specification: '150x100x50', unit: 'SET', supplier: '한국플라스틱', receivingQty: undefined, receivingDate: undefined, createdBy: '박민수', status: 'receiving_pending', remark: '', inspectionDate: undefined, inspectionResult: undefined, orderNo: 'PO-2026-003', orderUnit: 'SET', }, '4': { id: '4', lotNo: 'LOT-2026-004', itemCode: 'STEEL-002', itemName: '알루미늄 프로파일', specification: '40x40x2000L', unit: 'EA', supplier: '(주)대한철강', receivingQty: 50, receivingDate: '2026-01-28', createdBy: '김철수', status: 'inspection_pending', remark: '검사 진행 중', inspectionDate: '2026-01-27', inspectionResult: '불합격', orderNo: 'PO-2026-004', orderUnit: 'EA', }, '5': { id: '5', lotNo: 'LOT-2026-005', itemCode: 'ELEC-005', itemName: 'DC 모터 24V', specification: '24V 100RPM', unit: 'EA', supplier: '글로벌전자', receivingQty: undefined, receivingDate: undefined, createdBy: '최지훈', status: 'receiving_pending', remark: '', inspectionDate: undefined, inspectionResult: undefined, orderNo: 'PO-2026-005', orderUnit: 'EA', }, }; // ===== API 데이터 타입 ===== interface ReceivingApiData { id: number; receiving_number: string; order_no?: string; order_date?: string; item_id?: number; item_code: string; item_name: string; specification?: string; supplier: string; order_qty: string | number; order_unit: string; due_date?: string; receiving_qty?: string | number; receiving_date?: string; lot_no?: string; supplier_lot?: string; receiving_location?: string; receiving_manager?: string; status: ReceivingStatus; remark?: string; creator?: { id: number; name: string }; created_at?: string; updated_at?: string; } interface ReceivingApiPaginatedResponse { data: ReceivingApiData[]; current_page: number; last_page: number; per_page: number; total: number; } interface ReceivingApiStatsResponse { receiving_pending_count: number; shipping_count: number; inspection_pending_count: number; today_receiving_count: number; } // ===== API → Frontend 변환 (목록용) ===== function transformApiToListItem(data: ReceivingApiData): ReceivingItem { return { id: String(data.id), orderNo: data.order_no || data.receiving_number, itemCode: data.item_code, itemName: data.item_name, supplier: data.supplier, orderQty: parseFloat(String(data.order_qty)) || 0, orderUnit: data.order_unit || 'EA', receivingQty: data.receiving_qty ? parseFloat(String(data.receiving_qty)) : undefined, lotNo: data.lot_no, status: data.status, }; } // ===== API → Frontend 변환 (상세용) ===== function transformApiToDetail(data: ReceivingApiData): ReceivingDetail { return { id: String(data.id), orderNo: data.order_no || data.receiving_number, orderDate: data.order_date, supplier: data.supplier, itemCode: data.item_code, itemName: data.item_name, specification: data.specification, orderQty: parseFloat(String(data.order_qty)) || 0, orderUnit: data.order_unit || 'EA', dueDate: data.due_date, status: data.status, receivingDate: data.receiving_date, receivingQty: data.receiving_qty ? parseFloat(String(data.receiving_qty)) : undefined, receivingLot: data.lot_no, supplierLot: data.supplier_lot, receivingLocation: data.receiving_location, receivingManager: data.receiving_manager, }; } // ===== API → Frontend 변환 (통계용) ===== function transformApiToStats(data: ReceivingApiStatsResponse): ReceivingStats { return { receivingPendingCount: data.receiving_pending_count, shippingCount: data.shipping_count, inspectionPendingCount: data.inspection_pending_count, todayReceivingCount: data.today_receiving_count, }; } // ===== Frontend → API 변환 (등록/수정용) ===== function transformFrontendToApi( data: Partial ): Record { const result: Record = {}; if (data.orderNo !== undefined) result.order_no = data.orderNo; if (data.orderDate !== undefined) result.order_date = data.orderDate; if (data.itemCode !== undefined) result.item_code = data.itemCode; if (data.itemName !== undefined) result.item_name = data.itemName; if (data.specification !== undefined) result.specification = data.specification; if (data.supplier !== undefined) result.supplier = data.supplier; if (data.orderQty !== undefined) result.order_qty = data.orderQty; if (data.orderUnit !== undefined) result.order_unit = data.orderUnit; if (data.dueDate !== undefined) result.due_date = data.dueDate; if (data.status !== undefined) result.status = data.status; return result; } // ===== Frontend → API 변환 (입고처리용) ===== function transformProcessDataToApi( data: ReceivingProcessFormData ): Record { return { receiving_qty: data.receivingQty, lot_no: data.receivingLot, supplier_lot: data.supplierLot, receiving_location: data.receivingLocation, remark: data.remark, }; } // ===== 페이지네이션 타입 ===== interface PaginationMeta { currentPage: number; lastPage: number; perPage: number; total: number; } // ===== 입고 목록 조회 ===== export async function getReceivings(params?: { page?: number; perPage?: number; startDate?: string; endDate?: string; status?: string; search?: string; }): Promise<{ success: boolean; data: ReceivingItem[]; pagination: PaginationMeta; error?: string; __authError?: boolean; }> { // ===== 목데이터 모드 ===== if (USE_MOCK_DATA) { let filteredData = [...MOCK_RECEIVING_LIST]; // 상태 필터 if (params?.status && params.status !== 'all') { filteredData = filteredData.filter(item => item.status === params.status); } // 검색 필터 if (params?.search) { const search = params.search.toLowerCase(); filteredData = filteredData.filter( item => item.lotNo?.toLowerCase().includes(search) || item.itemCode.toLowerCase().includes(search) || item.itemName.toLowerCase().includes(search) || item.supplier.toLowerCase().includes(search) ); } const page = params?.page || 1; const perPage = params?.perPage || 20; const total = filteredData.length; const lastPage = Math.ceil(total / perPage); const startIndex = (page - 1) * perPage; const paginatedData = filteredData.slice(startIndex, startIndex + perPage); return { success: true, data: paginatedData, pagination: { currentPage: page, lastPage, perPage, total, }, }; } try { const searchParams = new URLSearchParams(); if (params?.page) searchParams.set('page', String(params.page)); if (params?.perPage) searchParams.set('per_page', String(params.perPage)); if (params?.startDate) searchParams.set('start_date', params.startDate); if (params?.endDate) searchParams.set('end_date', params.endDate); if (params?.status && params.status !== 'all') { searchParams.set('status', params.status); } if (params?.search) searchParams.set('search', params.search); const queryString = searchParams.toString(); const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings${queryString ? `?${queryString}` : ''}`; const { response, error } = await serverFetch(url, { method: 'GET', cache: 'no-store', }); if (error) { return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }, error: error.message, __authError: error.code === 'UNAUTHORIZED', }; } if (!response) { return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }, error: '입고 목록 조회에 실패했습니다.', }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }, error: result.message || '입고 목록 조회에 실패했습니다.', }; } const paginatedData: ReceivingApiPaginatedResponse = result.data || { data: [], current_page: 1, last_page: 1, per_page: 20, total: 0, }; const receivings = (paginatedData.data || []).map(transformApiToListItem); return { success: true, data: receivings, 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('[ReceivingActions] getReceivings error:', error); return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }, error: '서버 오류가 발생했습니다.', }; } } // ===== 입고 통계 조회 ===== export async function getReceivingStats(): Promise<{ success: boolean; data?: ReceivingStats; error?: string; __authError?: boolean; }> { // ===== 목데이터 모드 ===== if (USE_MOCK_DATA) { return { success: true, data: MOCK_RECEIVING_STATS }; } try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/stats`, { method: 'GET', cache: 'no-store' } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고 통계 조회에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success || !result.data) { return { success: false, error: result.message || '입고 통계 조회에 실패했습니다.' }; } return { success: true, data: transformApiToStats(result.data) }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] getReceivingStats error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 입고 상세 조회 ===== export async function getReceivingById(id: string): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean; }> { // ===== 목데이터 모드 ===== if (USE_MOCK_DATA) { const detail = MOCK_RECEIVING_DETAIL[id]; if (detail) { return { success: true, data: detail }; } return { success: false, error: '입고 정보를 찾을 수 없습니다.' }; } try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}`, { method: 'GET', cache: 'no-store' } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고 조회에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success || !result.data) { return { success: false, error: result.message || '입고 조회에 실패했습니다.' }; } return { success: true, data: transformApiToDetail(result.data) }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] getReceivingById error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 입고 등록 ===== export async function createReceiving( data: Partial ): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> { try { const apiData = transformFrontendToApi(data); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings`, { method: 'POST', body: JSON.stringify(apiData) } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고 등록에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '입고 등록에 실패했습니다.' }; } return { success: true, data: transformApiToDetail(result.data) }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] createReceiving error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 입고 수정 ===== export async function updateReceiving( id: string, data: Partial ): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> { try { const apiData = transformFrontendToApi(data); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}`, { method: 'PUT', body: JSON.stringify(apiData) } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고 수정에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '입고 수정에 실패했습니다.' }; } return { success: true, data: transformApiToDetail(result.data) }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] updateReceiving error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 입고 삭제 ===== export async function deleteReceiving( id: string ): Promise<{ success: boolean; error?: string; __authError?: boolean }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}`, { method: 'DELETE' } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고 삭제에 실패했습니다.' }; } 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('[ReceivingActions] deleteReceiving error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 입고처리 ===== export async function processReceiving( id: string, data: ReceivingProcessFormData ): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> { try { const apiData = transformProcessDataToApi(data); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/receivings/${id}/process`, { method: 'POST', body: JSON.stringify(apiData) } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '입고처리에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success) { return { success: false, error: result.message || '입고처리에 실패했습니다.' }; } return { success: true, data: transformApiToDetail(result.data) }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] processReceiving error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } } // ===== 수입검사 템플릿 타입 (ImportInspectionDocument와 동일) ===== export interface InspectionTemplateResponse { templateId: string; templateName: string; headerInfo: { productName: string; specification: string; materialNo: string; lotSize: number; supplier: string; lotNo: string; inspectionDate: string; inspector: string; reportDate: string; approvers: { writer?: string; reviewer?: string; approver?: string; }; }; inspectionItems: Array<{ id: string; no: number; name: string; subName?: string; parentId?: string; standard: { description?: string; value?: string | number; options?: Array<{ id: string; label: string; tolerance: string; isSelected: boolean; }>; }; inspectionMethod: string; inspectionCycle: string; measurementType: 'okng' | 'numeric' | 'both'; measurementCount: number; rowSpan?: number; isSubRow?: boolean; }>; notes?: string[]; } // ===== 수입검사 템플릿 조회 (품목명/규격 기반) ===== export async function getInspectionTemplate(params: { itemName: string; specification: string; lotNo?: string; supplier?: string; }): Promise<{ success: boolean; data?: InspectionTemplateResponse; error?: string; __authError?: boolean; }> { // ===== 목데이터 모드 - EGI 강판 템플릿 반환 ===== if (USE_MOCK_DATA) { // 품목명/규격에 따라 다른 템플릿 반환 (추후 24종 확장) const mockTemplate: InspectionTemplateResponse = { templateId: 'EGI-001', templateName: '전기 아연도금 강판', headerInfo: { productName: params.itemName || '전기 아연도금 강판 (KS D 3528, SECC) "EGI 평국판"', specification: params.specification || '1.55 * 1218 × 480', materialNo: 'PE02RB', lotSize: 200, supplier: params.supplier || '지오TNS (KG스틸)', lotNo: params.lotNo || '250715-02', inspectionDate: new Date().toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' }).replace('. ', '/').replace('.', ''), inspector: '노원호', reportDate: new Date().toISOString().split('T')[0], approvers: { writer: '노원호', reviewer: '', approver: '', }, }, inspectionItems: [ { id: 'appearance', no: 1, name: '겉모양', standard: { description: '사용상 해로운 결함이 없을 것' }, inspectionMethod: '육안검사', inspectionCycle: '', measurementType: 'okng', measurementCount: 3, }, { id: 'thickness', no: 2, name: '치수', subName: '두께', standard: { value: 1.55, options: [ { id: 't1', label: '0.8 이상 ~ 1.0 미만', tolerance: '± 0.07', isSelected: false }, { id: 't2', label: '1.0 이상 ~ 1.25 미만', tolerance: '± 0.08', isSelected: false }, { id: 't3', label: '1.25 이상 ~ 1.6 미만', tolerance: '± 0.10', isSelected: true }, { id: 't4', label: '1.6 이상 ~ 2.0 미만', tolerance: '± 0.12', isSelected: false }, ], }, inspectionMethod: 'n = 3\nc = 0', inspectionCycle: '체크검사', measurementType: 'numeric', measurementCount: 3, rowSpan: 3, }, { id: 'width', no: 2, name: '치수', subName: '너비', parentId: 'thickness', standard: { value: 1219, options: [{ id: 'w1', label: '1250 미만', tolerance: '+ 7\n- 0', isSelected: true }], }, inspectionMethod: '', inspectionCycle: '', measurementType: 'numeric', measurementCount: 3, isSubRow: true, }, { id: 'length', no: 2, name: '치수', subName: '길이', parentId: 'thickness', standard: { value: 480, options: [{ id: 'l1', label: '2000 이상 ~ 4000 미만', tolerance: '+ 15\n- 0', isSelected: true }], }, inspectionMethod: '', inspectionCycle: '', measurementType: 'numeric', measurementCount: 3, isSubRow: true, }, { id: 'tensileStrength', no: 3, name: '인장강도 (N/㎟)', standard: { description: '270 이상' }, inspectionMethod: '', inspectionCycle: '', measurementType: 'numeric', measurementCount: 1, }, { id: 'elongation', no: 4, name: '연신율 %', standard: { options: [ { id: 'e1', label: '두께 0.6 이상 ~ 1.0 미만', tolerance: '36 이상', isSelected: false }, { id: 'e2', label: '두께 1.0 이상 ~ 1.6 미만', tolerance: '37 이상', isSelected: true }, { id: 'e3', label: '두께 1.6 이상 ~ 2.3 미만', tolerance: '38 이상', isSelected: false }, ], }, inspectionMethod: '공급업체\n밀시트', inspectionCycle: '입고시', measurementType: 'numeric', measurementCount: 1, }, { id: 'zincCoating', no: 5, name: '아연의 최소 부착량 (g/㎡)', standard: { description: '편면 17 이상' }, inspectionMethod: '', inspectionCycle: '', measurementType: 'numeric', measurementCount: 2, }, ], notes: [ '※ 1.55mm의 경우 KS F 4510에 따른 MIN 1.5의 기준에 따름', '※ 두께의 경우 너비 1000 이상 ~ 1250 미만 기준에 따름', ], }; return { success: true, data: mockTemplate }; } // ===== 실제 API 호출 ===== try { const searchParams = new URLSearchParams(); searchParams.set('item_name', params.itemName); searchParams.set('specification', params.specification); if (params.lotNo) searchParams.set('lot_no', params.lotNo); if (params.supplier) searchParams.set('supplier', params.supplier); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/inspection-templates?${searchParams.toString()}`, { method: 'GET', cache: 'no-store' } ); if (error) { return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; } if (!response) { return { success: false, error: '검사 템플릿 조회에 실패했습니다.' }; } const result = await response.json(); if (!response.ok || !result.success || !result.data) { return { success: false, error: result.message || '검사 템플릿 조회에 실패했습니다.' }; } return { success: true, data: result.data }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ReceivingActions] getInspectionTemplate error:', error); return { success: false, error: '서버 오류가 발생했습니다.' }; } }