/** * 악성채권 추심관리 서버 액션 * * API Endpoints: * - GET /api/v1/bad-debts - 목록 조회 * - GET /api/v1/bad-debts/{id} - 상세 조회 * - POST /api/v1/bad-debts - 등록 * - PUT /api/v1/bad-debts/{id} - 수정 * - DELETE /api/v1/bad-debts/{id} - 삭제 * - PATCH /api/v1/bad-debts/{id}/toggle - 활성화 토글 * - GET /api/v1/bad-debts/summary - 통계 조회 */ 'use server'; import { revalidatePath } from 'next/cache'; import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action'; import { buildApiUrl } from '@/lib/api/query-params'; import type { PaginatedApiResponse } from '@/lib/api/types'; import type { BadDebtRecord, BadDebtItem, CollectionStatus } from './types'; // ===== API 응답 타입 ===== interface BadDebtItemApiData { id: number; debt_amount: number; status: 'collecting' | 'legal_action' | 'recovered' | 'bad_debt'; overdue_days: number; is_active: boolean; occurred_at: string | null; assigned_user?: { id: number; name: string } | null; } interface BadDebtApiData { id: number; client_id: number; client_code: string; client_name: string; business_no: string | null; contact_person: string | null; phone: string | null; mobile: string | null; email: string | null; address: string | null; client_type: string | null; total_debt_amount: number; max_overdue_days: number; bad_debt_count: number; status: 'collecting' | 'legal_action' | 'recovered' | 'bad_debt'; is_active: boolean; assigned_user?: { id: number; name: string } | null; bad_debts: BadDebtItemApiData[]; } interface BadDebtSummaryApiData { total_amount: number; collecting_amount: number; legal_action_amount: number; recovered_amount: number; bad_debt_amount: number; total_count: number; collecting_count: number; legal_action_count: number; recovered_count: number; bad_debt_count: number; } // ===== 헬퍼 함수 ===== function mapApiStatusToFrontend(apiStatus: string): CollectionStatus { switch (apiStatus) { case 'collecting': return 'collecting'; case 'legal_action': return 'legalAction'; case 'recovered': case 'bad_debt': case 'collection_end': return 'collectionEnd'; default: return 'collecting'; } } function mapFrontendStatusToApi(status: CollectionStatus): string { switch (status) { case 'collecting': return 'collecting'; case 'legalAction': return 'legal_action'; case 'collectionEnd': return 'collection_end'; default: return 'collecting'; } } function mapClientTypeToVendorType(clientType?: string | null): 'sales' | 'purchase' | 'both' { switch (clientType) { case 'customer': case 'sales': return 'sales'; case 'supplier': case 'purchase': return 'purchase'; default: return 'both'; } } function transformApiToFrontend(apiData: BadDebtApiData): BadDebtRecord { const manager = apiData.assigned_user; const firstBadDebt = apiData.bad_debts?.[0]; return { id: String(apiData.id), vendorId: String(apiData.client_id), vendorCode: apiData.client_code || '', vendorName: apiData.client_name || '거래처 없음', businessNumber: apiData.business_no || '', representativeName: '', vendorType: mapClientTypeToVendorType(apiData.client_type), businessType: '', businessCategory: '', zipCode: '', address1: apiData.address || '', address2: '', phone: apiData.phone || '', mobile: apiData.mobile || '', fax: '', email: apiData.email || '', contactName: apiData.contact_person || '', contactPhone: '', systemManager: '', debtAmount: apiData.total_debt_amount || 0, badDebtCount: apiData.bad_debt_count || 0, status: mapApiStatusToFrontend(apiData.status), overdueDays: apiData.max_overdue_days || 0, overdueToggle: apiData.is_active, occurrenceDate: firstBadDebt?.occurred_at || '', endDate: null, assignedManagerId: manager ? String(manager.id) : null, assignedManager: manager ? { id: String(manager.id), departmentName: '', name: manager.name, position: '', phone: '', } : null, settingToggle: apiData.is_active, badDebts: (apiData.bad_debts || []).map(bd => ({ id: String(bd.id), debtAmount: bd.debt_amount || 0, status: mapApiStatusToFrontend(bd.status), overdueDays: bd.overdue_days || 0, isActive: bd.is_active, occurredAt: bd.occurred_at, assignedManager: bd.assigned_user ? { id: String(bd.assigned_user.id), name: bd.assigned_user.name } : null, })), files: [], memos: [], createdAt: '', updatedAt: '', }; } function transformFrontendToApi(data: Partial): Record { return { client_id: data.vendorId ? parseInt(data.vendorId) : null, debt_amount: data.debtAmount || 0, status: data.status ? mapFrontendStatusToApi(data.status) : 'collecting', overdue_days: data.overdueDays || 0, occurred_at: data.occurrenceDate || null, closed_at: data.endDate || null, assigned_manager_id: data.assignedManagerId ? parseInt(data.assignedManagerId) : null, is_active: data.settingToggle ?? true, note: null, }; } // ===== 악성채권 목록 조회 ===== export async function getBadDebts(params?: { page?: number; size?: number; status?: string; client_id?: string; }): Promise { const result = await executeServerAction({ url: buildApiUrl('/api/v1/bad-debts', { page: params?.page, size: params?.size, status: params?.status && params.status !== 'all' ? mapFrontendStatusToApi(params.status as CollectionStatus) : undefined, client_id: params?.client_id && params.client_id !== 'all' ? params.client_id : undefined, }), transform: (data: PaginatedApiResponse) => data.data.map(transformApiToFrontend), errorMessage: '악성채권 목록 조회에 실패했습니다.', }); return result.data || []; } // ===== 악성채권 상세 조회 ===== export async function getBadDebtById(id: string): Promise { const result = await executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${id}`), transform: (data: BadDebtApiData) => transformApiToFrontend(data), errorMessage: '악성채권 조회에 실패했습니다.', }); return result.data || null; } // ===== 악성채권 통계 조회 ===== export async function getBadDebtSummary(): Promise { const result = await executeServerAction({ url: buildApiUrl('/api/v1/bad-debts/summary'), errorMessage: '악성채권 통계 조회에 실패했습니다.', }); return result.data || null; } // ===== 악성채권 등록 ===== export async function createBadDebt( data: Partial ): Promise> { const result = await executeServerAction({ url: buildApiUrl('/api/v1/bad-debts'), method: 'POST', body: transformFrontendToApi(data), transform: (data: BadDebtApiData) => transformApiToFrontend(data), errorMessage: '악성채권 등록에 실패했습니다.', }); if (result.success) revalidatePath('/accounting/bad-debt-collection'); return result; } // ===== 악성채권 수정 ===== export async function updateBadDebt( id: string, data: Partial ): Promise> { const result = await executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${id}`), method: 'PUT', body: transformFrontendToApi(data), transform: (data: BadDebtApiData) => transformApiToFrontend(data), errorMessage: '악성채권 수정에 실패했습니다.', }); if (result.success) revalidatePath('/accounting/bad-debt-collection'); return result; } // ===== 악성채권 삭제 ===== export async function deleteBadDebt(id: string): Promise { const result = await executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${id}`), method: 'DELETE', errorMessage: '악성채권 삭제에 실패했습니다.', }); if (result.success) revalidatePath('/accounting/bad-debt-collection'); return result; } // ===== 악성채권 활성화 토글 ===== export async function toggleBadDebt(id: string): Promise> { const result = await executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${id}/toggle`), method: 'PATCH', transform: (data: BadDebtApiData) => transformApiToFrontend(data), errorMessage: '상태 변경에 실패했습니다.', }); if (result.success) revalidatePath('/accounting/bad-debt-collection'); return result; } // ===== 악성채권 메모 추가 ===== export async function addBadDebtMemo( badDebtId: string, content: string ): Promise> { return executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${badDebtId}/memos`), method: 'POST', body: { content }, transform: (memo: { id: number; content: string; created_at: string; created_by_user?: { name: string } | null }) => ({ id: String(memo.id), content: memo.content, createdAt: memo.created_at, createdBy: memo.created_by_user?.name || '사용자', }), errorMessage: '메모 추가에 실패했습니다.', }); } // ===== 악성채권 메모 삭제 ===== export async function deleteBadDebtMemo( badDebtId: string, memoId: string ): Promise { return executeServerAction({ url: buildApiUrl(`/api/v1/bad-debts/${badDebtId}/memos/${memoId}`), method: 'DELETE', errorMessage: '메모 삭제에 실패했습니다.', }); }