'use server'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { serverFetch } from '@/lib/api/fetch-wrapper'; import type { ExpectedExpenseRecord, TransactionType, PaymentStatus, ApprovalStatus } from './types'; // ===== API 응답 타입 ===== interface ExpectedExpenseApiData { id: number; tenant_id: number; expected_payment_date: string; settlement_date: string | null; transaction_type: string; amount: number | string; client_id: number | null; client_name: string | null; bank_account_id: number | null; account_code: string | null; payment_status: string; approval_status: string; description: string | null; created_at: string; updated_at: string; client?: { id: number; name: string; } | null; bank_account?: { id: number; bank_name: string; account_name: string; } | null; } interface PaginationMeta { current_page: number; last_page: number; per_page: number; total: number; } interface SummaryData { total_amount: number; total_count: number; by_payment_status: Record; by_transaction_type: Record; by_month: Record; } // ===== API → Frontend 변환 ===== function transformApiToFrontend(apiData: ExpectedExpenseApiData): ExpectedExpenseRecord { return { id: String(apiData.id), expectedPaymentDate: apiData.expected_payment_date, settlementDate: apiData.settlement_date || '', transactionType: (apiData.transaction_type || 'other') as TransactionType, amount: typeof apiData.amount === 'string' ? parseFloat(apiData.amount) : apiData.amount, vendorId: apiData.client_id ? String(apiData.client_id) : '', vendorName: apiData.client_name || apiData.client?.name || '', bankAccount: apiData.bank_account ? `${apiData.bank_account.bank_name} ${apiData.bank_account.account_name}` : '', accountSubject: apiData.account_code || '', paymentStatus: (apiData.payment_status || 'pending') as PaymentStatus, approvalStatus: (apiData.approval_status || 'none') as ApprovalStatus, note: apiData.description || '', createdAt: apiData.created_at, updatedAt: apiData.updated_at, }; } // ===== Frontend → API 변환 ===== function transformFrontendToApi(data: Partial): Record { const result: Record = {}; if (data.expectedPaymentDate !== undefined) result.expected_payment_date = data.expectedPaymentDate; if (data.settlementDate !== undefined) result.settlement_date = data.settlementDate || null; if (data.transactionType !== undefined) result.transaction_type = data.transactionType; if (data.amount !== undefined) result.amount = data.amount; if (data.vendorId !== undefined) result.client_id = data.vendorId ? parseInt(data.vendorId, 10) : null; if (data.vendorName !== undefined) result.client_name = data.vendorName || null; if (data.accountSubject !== undefined) result.account_code = data.accountSubject || null; if (data.paymentStatus !== undefined) result.payment_status = data.paymentStatus; if (data.approvalStatus !== undefined) result.approval_status = data.approvalStatus; if (data.note !== undefined) result.description = data.note || null; return result; } // ===== 미지급비용 목록 조회 ===== export async function getExpectedExpenses(params?: { page?: number; perPage?: number; startDate?: string; endDate?: string; transactionType?: string; paymentStatus?: string; approvalStatus?: string; clientId?: string; search?: string; sortBy?: string; sortDir?: 'asc' | 'desc'; }): Promise<{ success: boolean; data: ExpectedExpenseRecord[]; pagination: { currentPage: number; lastPage: number; perPage: number; total: number; }; error?: string; }> { 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?.transactionType && params.transactionType !== 'all') { searchParams.set('transaction_type', params.transactionType); } if (params?.paymentStatus && params.paymentStatus !== 'all') { searchParams.set('payment_status', params.paymentStatus); } if (params?.approvalStatus && params.approvalStatus !== 'all') { searchParams.set('approval_status', params.approvalStatus); } if (params?.clientId) searchParams.set('client_id', params.clientId); if (params?.search) searchParams.set('search', params.search); if (params?.sortBy) searchParams.set('sort_by', params.sortBy); if (params?.sortDir) searchParams.set('sort_dir', params.sortDir); const queryString = searchParams.toString(); const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses${queryString ? `?${queryString}` : ''}`; const { response, error } = await serverFetch(url, { method: 'GET' }); if (error) { return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 50, total: 0 }, error: error.message, }; } if (!response?.ok) { console.warn('[ExpectedExpenseActions] GET expected-expenses error:', response?.status); return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 50, total: 0 }, error: `API 오류: ${response?.status}`, }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 50, total: 0 }, error: result.message || '미지급비용 조회에 실패했습니다.', }; } const paginationData = result.data; const expenses = (paginationData?.data || []).map(transformApiToFrontend); const meta: PaginationMeta = { current_page: paginationData?.current_page || 1, last_page: paginationData?.last_page || 1, per_page: paginationData?.per_page || 50, total: paginationData?.total || expenses.length, }; return { success: true, data: expenses, pagination: { currentPage: meta.current_page, lastPage: meta.last_page, perPage: meta.per_page, total: meta.total, }, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] getExpectedExpenses error:', error); return { success: false, data: [], pagination: { currentPage: 1, lastPage: 1, perPage: 50, total: 0 }, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 상세 조회 ===== export async function getExpectedExpenseById(id: string): Promise<{ success: boolean; data?: ExpectedExpenseRecord; error?: string; }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses/${id}`, { method: 'GET' } ); if (error) { return { success: false, error: error.message }; } if (!response?.ok) { console.error('[ExpectedExpenseActions] GET expected-expense 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('[ExpectedExpenseActions] getExpectedExpenseById error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 등록 ===== export async function createExpectedExpense( data: Partial ): Promise<{ success: boolean; data?: ExpectedExpenseRecord; error?: string }> { try { const apiData = transformFrontendToApi(data); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses`, { method: 'POST', body: JSON.stringify(apiData), } ); if (error) { return { success: false, error: error.message }; } 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('[ExpectedExpenseActions] createExpectedExpense error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 수정 ===== export async function updateExpectedExpense( id: string, data: Partial ): Promise<{ success: boolean; data?: ExpectedExpenseRecord; error?: string }> { try { const apiData = transformFrontendToApi(data); const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses/${id}`, { method: 'PUT', body: JSON.stringify(apiData), } ); if (error) { return { success: false, error: error.message }; } 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('[ExpectedExpenseActions] updateExpectedExpense error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 삭제 ===== export async function deleteExpectedExpense(id: string): Promise<{ success: boolean; error?: string }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses/${id}`, { method: 'DELETE' } ); if (error) { return { success: false, error: error.message }; } 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('[ExpectedExpenseActions] deleteExpectedExpense error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 일괄 삭제 ===== export async function deleteExpectedExpenses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses`, { method: 'DELETE', body: JSON.stringify({ ids: ids.map(id => parseInt(id, 10)), }), } ); if (error) { return { success: false, error: error.message }; } const result = await response?.json(); if (!response?.ok || !result.success) { return { success: false, error: result?.message || '미지급비용 일괄 삭제에 실패했습니다.', }; } return { success: true, deletedCount: result.data?.deleted_count, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] deleteExpectedExpenses error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 예상 지급일 일괄 변경 ===== export async function updateExpectedPaymentDate( ids: string[], expectedPaymentDate: string ): Promise<{ success: boolean; updatedCount?: number; error?: string; }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses/update-payment-date`, { method: 'PUT', body: JSON.stringify({ ids: ids.map(id => parseInt(id, 10)), expected_payment_date: expectedPaymentDate, }), } ); if (error) { return { success: false, error: error.message }; } const result = await response?.json(); if (!response?.ok || !result.success) { return { success: false, error: result?.message || '예상 지급일 변경에 실패했습니다.', }; } return { success: true, updatedCount: result.data?.updated_count, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] updateExpectedPaymentDate error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 미지급비용 요약 조회 ===== export async function getExpectedExpenseSummary(params?: { startDate?: string; endDate?: string; paymentStatus?: string; }): Promise<{ success: boolean; data?: SummaryData; error?: string; }> { try { const searchParams = new URLSearchParams(); if (params?.startDate) searchParams.set('start_date', params.startDate); if (params?.endDate) searchParams.set('end_date', params.endDate); if (params?.paymentStatus && params.paymentStatus !== 'all') { searchParams.set('payment_status', params.paymentStatus); } const queryString = searchParams.toString(); const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/expected-expenses/summary${queryString ? `?${queryString}` : ''}`; const { response, error } = await serverFetch(url, { method: 'GET' }); if (error) { return { success: false, error: error.message }; } if (!response?.ok) { console.warn('[ExpectedExpenseActions] GET summary error:', response?.status); return { success: false, error: `API 오류: ${response?.status}`, }; } const result = await response.json(); if (!result.success) { return { success: false, error: result.message || '요약 조회에 실패했습니다.', }; } return { success: true, data: result.data, }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] getExpectedExpenseSummary error:', error); return { success: false, error: '서버 오류가 발생했습니다.', }; } } // ===== 거래처 목록 조회 ===== export async function getClients(): Promise<{ success: boolean; data: { id: string; name: string }[]; error?: string; }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/clients?per_page=100`, { method: 'GET' } ); if (error) { return { success: false, data: [], error: error.message }; } if (!response?.ok) { return { success: false, data: [], error: `API 오류: ${response?.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], error: result.message }; } const clients = result.data?.data || result.data || []; return { success: true, data: clients.map((c: { id: number; name: string }) => ({ id: String(c.id), name: c.name, })), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] getClients error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } } // ===== 은행 계좌 목록 조회 ===== export async function getBankAccounts(): Promise<{ success: boolean; data: { id: string; bankName: string; accountName: string; accountNumber: string }[]; error?: string; }> { try { const { response, error } = await serverFetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts?per_page=100`, { method: 'GET' } ); if (error) { return { success: false, data: [], error: error.message }; } if (!response?.ok) { return { success: false, data: [], error: `API 오류: ${response?.status}` }; } const result = await response.json(); if (!result.success) { return { success: false, data: [], error: result.message }; } const accounts = result.data?.data || result.data || []; return { success: true, data: accounts.map((a: { id: number; bank_name: string; account_name: string; account_number: string }) => ({ id: String(a.id), bankName: a.bank_name, accountName: a.account_name, accountNumber: a.account_number, })), }; } catch (error) { if (isNextRedirectError(error)) throw error; console.error('[ExpectedExpenseActions] getBankAccounts error:', error); return { success: false, data: [], error: '서버 오류가 발생했습니다.' }; } }