/** * 입고 관리 서버 액션 * * 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'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { serverFetch } from '@/lib/api/fetch-wrapper'; import type { ReceivingItem, ReceivingDetail, ReceivingStats, ReceivingStatus, ReceivingProcessFormData, } from './types'; // ===== 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; }> { 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; }> { 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; }> { 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: '서버 오류가 발생했습니다.' }; } }