'use server'; import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action'; import type { PaginatedApiResponse } from '@/lib/api/types'; import { fetchBankAccountDetailOptions } from '@/lib/api/shared-lookups'; import type { ExpectedExpenseRecord, TransactionType, PaymentStatus, ApprovalStatus } from './types'; const API_URL = process.env.NEXT_PUBLIC_API_URL; // ===== 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; } type ExpensePaginatedResponse = PaginatedApiResponse; interface SummaryData { total_amount: number; total_count: number; by_payment_status: Record; by_transaction_type: Record; by_month: Record; } interface FrontendPagination { currentPage: number; lastPage: number; perPage: number; total: number } const DEFAULT_PAGINATION: FrontendPagination = { currentPage: 1, lastPage: 1, perPage: 50, total: 0 }; // ===== 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: FrontendPagination; error?: string }> { 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 result = await executeServerAction({ url: `${API_URL}/api/v1/expected-expenses${queryString ? `?${queryString}` : ''}`, transform: (data: ExpensePaginatedResponse) => ({ items: (data?.data || []).map(transformApiToFrontend), pagination: { currentPage: data?.current_page || 1, lastPage: data?.last_page || 1, perPage: data?.per_page || 50, total: data?.total || 0 }, }), errorMessage: '미지급비용 조회에 실패했습니다.', }); return { success: result.success, data: result.data?.items || [], pagination: result.data?.pagination || DEFAULT_PAGINATION, error: result.error }; } // ===== 미지급비용 상세 조회 ===== export async function getExpectedExpenseById(id: string): Promise> { return executeServerAction({ url: `${API_URL}/api/v1/expected-expenses/${id}`, transform: (data: ExpectedExpenseApiData) => transformApiToFrontend(data), errorMessage: '미지급비용 조회에 실패했습니다.', }); } // ===== 미지급비용 등록 ===== export async function createExpectedExpense(data: Partial): Promise> { return executeServerAction({ url: `${API_URL}/api/v1/expected-expenses`, method: 'POST', body: transformFrontendToApi(data), transform: (data: ExpectedExpenseApiData) => transformApiToFrontend(data), errorMessage: '미지급비용 등록에 실패했습니다.', }); } // ===== 미지급비용 수정 ===== export async function updateExpectedExpense(id: string, data: Partial): Promise> { return executeServerAction({ url: `${API_URL}/api/v1/expected-expenses/${id}`, method: 'PUT', body: transformFrontendToApi(data), transform: (data: ExpectedExpenseApiData) => transformApiToFrontend(data), errorMessage: '미지급비용 수정에 실패했습니다.', }); } // ===== 미지급비용 삭제 ===== export async function deleteExpectedExpense(id: string): Promise { return executeServerAction({ url: `${API_URL}/api/v1/expected-expenses/${id}`, method: 'DELETE', errorMessage: '미지급비용 삭제에 실패했습니다.', }); } // ===== 미지급비용 일괄 삭제 ===== export async function deleteExpectedExpenses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; }> { const result = await executeServerAction({ url: `${API_URL}/api/v1/expected-expenses`, method: 'DELETE', body: { ids: ids.map(id => parseInt(id, 10)) }, transform: (data: { deleted_count?: number }) => ({ deletedCount: data?.deleted_count }), errorMessage: '미지급비용 일괄 삭제에 실패했습니다.', }); return { success: result.success, deletedCount: result.data?.deletedCount, error: result.error }; } // ===== 예상 지급일 일괄 변경 ===== export async function updateExpectedPaymentDate(ids: string[], expectedPaymentDate: string): Promise<{ success: boolean; updatedCount?: number; error?: string; }> { const result = await executeServerAction({ url: `${API_URL}/api/v1/expected-expenses/update-payment-date`, method: 'PUT', body: { ids: ids.map(id => parseInt(id, 10)), expected_payment_date: expectedPaymentDate }, transform: (data: { updated_count?: number }) => ({ updatedCount: data?.updated_count }), errorMessage: '예상 지급일 변경에 실패했습니다.', }); return { success: result.success, updatedCount: result.data?.updatedCount, error: result.error }; } // ===== 미지급비용 요약 조회 ===== export async function getExpectedExpenseSummary(params?: { startDate?: string; endDate?: string; paymentStatus?: string; }): Promise> { 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(); return executeServerAction({ url: `${API_URL}/api/v1/expected-expenses/summary${queryString ? `?${queryString}` : ''}`, errorMessage: '요약 조회에 실패했습니다.', }); } // ===== 거래처 목록 조회 ===== export async function getClients(): Promise<{ success: boolean; data: { id: string; name: string }[]; error?: string; }> { const result = await executeServerAction({ url: `${API_URL}/api/v1/clients?per_page=100`, transform: (data: { data?: { id: number; name: string }[] } | { id: number; name: string }[]) => { type ClientApi = { id: number; name: string }; const clients: ClientApi[] = Array.isArray(data) ? data : (data as { data?: ClientApi[] })?.data || []; return clients.map(c => ({ id: String(c.id), name: c.name })); }, errorMessage: '거래처 조회에 실패했습니다.', }); return { success: result.success, data: result.data || [], error: result.error }; } // ===== 은행 계좌 목록 조회 ===== export async function getBankAccounts(): Promise<{ success: boolean; data: { id: string; bankName: string; accountName: string; accountNumber: string }[]; error?: string; }> { const result = await fetchBankAccountDetailOptions(); return { success: result.success, data: result.data || [], error: result.error }; }