/** * CEO Dashboard API 응답 → Frontend 타입 변환 함수 * * 참조: docs/plans/AI_리포트_키워드_색상체계_가이드_v1.4.md */ import type { DailyReportApiResponse, ReceivablesApiResponse, BadDebtApiResponse, ExpectedExpenseApiResponse, CardTransactionApiResponse, } from './types'; import type { DailyReportData, ReceivableData, DebtCollectionData, MonthlyExpenseData, CardManagementData, CheckPoint, CheckPointType, } from '@/components/business/CEODashboard/types'; // ============================================ // 헬퍼 함수 // ============================================ /** * 금액 포맷팅 * @example formatAmount(3050000000) → "30.5억원" */ function formatAmount(amount: number): string { const absAmount = Math.abs(amount); if (absAmount >= 100000000) { return `${(amount / 100000000).toFixed(1)}억원`; } else if (absAmount >= 10000) { return `${Math.round(amount / 10000).toLocaleString()}만원`; } return `${amount.toLocaleString()}원`; } /** * 날짜 포맷팅 (API → 한국어 형식) * @example formatDate("2026-01-20", "월요일") → "2026년 1월 20일 월요일" */ function formatDate(dateStr: string, dayOfWeek: string): string { const date = new Date(dateStr); return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일 ${dayOfWeek}`; } /** * 퍼센트 변화율 계산 */ function calculateChangeRate(current: number, previous: number): number { if (previous === 0) return current > 0 ? 100 : 0; return ((current - previous) / previous) * 100; } // ============================================ // 1. DailyReport 변환 // ============================================ /** * 일일 일보 CheckPoints 생성 * 참조: AI 리포트 색상 체계 가이드 - 섹션 2 */ function generateDailyReportCheckPoints(api: DailyReportApiResponse): CheckPoint[] { const checkPoints: CheckPoint[] = []; // 출금 정보 const withdrawal = api.krw_totals.expense; if (withdrawal > 0) { checkPoints.push({ id: 'dr-withdrawal', type: 'info' as CheckPointType, message: `어제 ${formatAmount(withdrawal)} 출금했습니다.`, highlights: [ { text: formatAmount(withdrawal), color: 'red' as const }, ], }); } // 입금 정보 const deposit = api.krw_totals.income; if (deposit > 0) { checkPoints.push({ id: 'dr-deposit', type: 'success' as CheckPointType, message: `어제 ${formatAmount(deposit)}이 입금되었습니다.`, highlights: [ { text: formatAmount(deposit), color: 'green' as const }, { text: '입금', color: 'green' as const }, ], }); } // 현금성 자산 현황 const cashAsset = api.cash_asset_total; checkPoints.push({ id: 'dr-cash-asset', type: 'info' as CheckPointType, message: `총 현금성 자산이 ${formatAmount(cashAsset)}입니다.`, highlights: [ { text: formatAmount(cashAsset), color: 'blue' as const }, ], }); return checkPoints; } /** * DailyReport API 응답 → Frontend 타입 변환 */ export function transformDailyReportResponse(api: DailyReportApiResponse): DailyReportData { return { date: formatDate(api.date, api.day_of_week), cards: [ { id: 'dr1', label: '현금성 자산 합계', amount: api.cash_asset_total, }, { id: 'dr2', label: '외국환(USD) 합계', amount: api.foreign_currency_total, currency: 'USD', }, { id: 'dr3', label: '입금 합계', amount: api.krw_totals.income, }, { id: 'dr4', label: '출금 합계', amount: api.krw_totals.expense, }, ], checkPoints: generateDailyReportCheckPoints(api), }; } // ============================================ // 2. Receivable 변환 // ============================================ /** * 미수금 현황 CheckPoints 생성 */ function generateReceivableCheckPoints(api: ReceivablesApiResponse): CheckPoint[] { const checkPoints: CheckPoint[] = []; // 연체 거래처 경고 if (api.overdue_vendor_count > 0) { checkPoints.push({ id: 'rv-overdue', type: 'warning' as CheckPointType, message: `연체 거래처 ${api.overdue_vendor_count}곳. 회수 조치가 필요합니다.`, highlights: [ { text: `연체 거래처 ${api.overdue_vendor_count}곳`, color: 'red' as const }, ], }); } // 미수금 현황 if (api.total_receivables > 0) { checkPoints.push({ id: 'rv-total', type: 'info' as CheckPointType, message: `총 미수금 ${formatAmount(api.total_receivables)}입니다.`, highlights: [ { text: formatAmount(api.total_receivables), color: 'blue' as const }, ], }); } return checkPoints; } /** * Receivables API 응답 → Frontend 타입 변환 */ export function transformReceivableResponse(api: ReceivablesApiResponse): ReceivableData { // 누적 미수금 = 이월 + 매출 - 입금 const cumulativeReceivable = api.total_carry_forward + api.total_sales - api.total_deposits; return { cards: [ { id: 'rv1', label: '누적 미수금', amount: cumulativeReceivable, subItems: [ { label: '이월', value: api.total_carry_forward }, { label: '매출', value: api.total_sales }, { label: '입금', value: api.total_deposits }, ], }, { id: 'rv2', label: '당월 미수금', amount: api.total_receivables, subItems: [ { label: '매출', value: api.total_sales }, { label: '입금', value: api.total_deposits }, ], }, { id: 'rv3', label: '거래처 현황', amount: api.vendor_count, unit: '곳', subLabel: `연체 ${api.overdue_vendor_count}곳`, }, ], checkPoints: generateReceivableCheckPoints(api), detailButtonLabel: '미수금 상세', detailButtonPath: '/accounting/receivables-status', }; } // ============================================ // 3. DebtCollection 변환 // ============================================ /** * 채권추심 CheckPoints 생성 */ function generateDebtCollectionCheckPoints(api: BadDebtApiResponse): CheckPoint[] { const checkPoints: CheckPoint[] = []; // 법적조치 진행 중 if (api.legal_action_amount > 0) { checkPoints.push({ id: 'dc-legal', type: 'warning' as CheckPointType, message: `법적조치 진행 중 ${formatAmount(api.legal_action_amount)}입니다.`, highlights: [ { text: formatAmount(api.legal_action_amount), color: 'red' as const }, ], }); } // 회수 완료 if (api.recovered_amount > 0) { checkPoints.push({ id: 'dc-recovered', type: 'success' as CheckPointType, message: `총 ${formatAmount(api.recovered_amount)}을 회수 완료했습니다.`, highlights: [ { text: formatAmount(api.recovered_amount), color: 'green' as const }, { text: '회수 완료', color: 'green' as const }, ], }); } return checkPoints; } /** * BadDebt API 응답 → Frontend 타입 변환 */ export function transformDebtCollectionResponse(api: BadDebtApiResponse): DebtCollectionData { return { cards: [ { id: 'dc1', label: '누적 악성채권', amount: api.total_amount, }, { id: 'dc2', label: '추심중', amount: api.collecting_amount, }, { id: 'dc3', label: '법적조치', amount: api.legal_action_amount, }, { id: 'dc4', label: '회수완료', amount: api.recovered_amount, }, ], checkPoints: generateDebtCollectionCheckPoints(api), detailButtonPath: '/accounting/bad-debt-collection', }; } // ============================================ // 4. MonthlyExpense 변환 // ============================================ /** * 당월 예상 지출 CheckPoints 생성 */ function generateMonthlyExpenseCheckPoints(api: ExpectedExpenseApiResponse): CheckPoint[] { const checkPoints: CheckPoint[] = []; // 총 예상 지출 checkPoints.push({ id: 'me-total', type: 'info' as CheckPointType, message: `이번 달 예상 지출은 ${formatAmount(api.total_amount)}입니다.`, highlights: [ { text: formatAmount(api.total_amount), color: 'blue' as const }, ], }); return checkPoints; } /** * ExpectedExpense API 응답 → Frontend 타입 변환 * 주의: 실제 API는 상세 분류(매입/카드/어음 등)를 제공하지 않음 * by_transaction_type에서 추출하거나 기본값 사용 */ export function transformMonthlyExpenseResponse(api: ExpectedExpenseApiResponse): MonthlyExpenseData { // transaction_type별 금액 추출 const purchaseTotal = api.by_transaction_type['purchase']?.total ?? 0; const cardTotal = api.by_transaction_type['card']?.total ?? 0; const billTotal = api.by_transaction_type['bill']?.total ?? 0; return { cards: [ { id: 'me1', label: '매입', amount: purchaseTotal, }, { id: 'me2', label: '카드', amount: cardTotal, }, { id: 'me3', label: '발행어음', amount: billTotal, }, { id: 'me4', label: '총 예상 지출 합계', amount: api.total_amount, }, ], checkPoints: generateMonthlyExpenseCheckPoints(api), }; } // ============================================ // 5. CardManagement 변환 // ============================================ /** * 카드/가지급금 CheckPoints 생성 */ function generateCardManagementCheckPoints(api: CardTransactionApiResponse): CheckPoint[] { const checkPoints: CheckPoint[] = []; // 전월 대비 변화 const changeRate = calculateChangeRate(api.current_month_total, api.previous_month_total); if (Math.abs(changeRate) > 10) { const type: CheckPointType = changeRate > 0 ? 'warning' : 'info'; checkPoints.push({ id: 'cm-change', type, message: `당월 카드 사용액이 전월 대비 ${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}% 변동했습니다.`, highlights: [ { text: `${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}%`, color: changeRate > 0 ? 'red' as const : 'green' as const }, ], }); } // 당월 사용액 checkPoints.push({ id: 'cm-current', type: 'info' as CheckPointType, message: `당월 카드 사용 총 ${formatAmount(api.current_month_total)}입니다.`, highlights: [ { text: formatAmount(api.current_month_total), color: 'blue' as const }, ], }); return checkPoints; } /** * CardTransaction API 응답 → Frontend 타입 변환 * 주의: 가지급금, 법인세 예상 가중 등은 별도 API 필요 (현재 목업 유지) */ export function transformCardManagementResponse( api: CardTransactionApiResponse, fallbackData?: CardManagementData ): CardManagementData { const changeRate = calculateChangeRate(api.current_month_total, api.previous_month_total); return { // 가지급금 관련 경고는 API 데이터가 없으므로 fallback 사용 warningBanner: fallbackData?.warningBanner, cards: [ { id: 'cm1', label: '카드', amount: api.current_month_total, previousLabel: `전월 대비 ${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}%`, }, // 아래 항목들은 API에서 제공하지 않으므로 fallback 사용 fallbackData?.cards[1] ?? { id: 'cm2', label: '가지급금', amount: 0, }, fallbackData?.cards[2] ?? { id: 'cm3', label: '법인세 예상 가중', amount: 0, }, fallbackData?.cards[3] ?? { id: 'cm4', label: '대표자 종합세 예상 가중', amount: 0, }, ], checkPoints: generateCardManagementCheckPoints(api), }; }