refactor(WEB): CEO 대시보드 대규모 개선 및 문서/권한/스토어 리팩토링
- CEO 대시보드: 섹션별 API 연동 강화 (매출/매입/생산 실데이터 표시) - DashboardSettingsDialog 드래그 정렬 및 설정 UX 개선 - dashboard transformers 모듈 분리 (파일 분할) - DocumentTable/DocumentWrapper 공통 문서 컴포넌트 추출 - LineItemsTable organisms 컴포넌트 추가 - PurchaseOrderDocument/InspectionRequestDocument 문서 컴포넌트 리팩토링 - PermissionContext → permissionStore(Zustand) 전환 - useUIStore, stores/utils/userStorage 추가 - favoritesStore/useTableColumnStore 사용자별 저장 지원 - DepositDetail/WithdrawalDetail 삭제 (통합) - PurchaseDetail/SalesDetail 간소화 - amount.ts/formatters.ts 유틸 확장 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
192
src/lib/api/dashboard/transformers/receivable.ts
Normal file
192
src/lib/api/dashboard/transformers/receivable.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 미수금 (Receivable) + 채권추심 (DebtCollection) 변환
|
||||
*/
|
||||
|
||||
import type { ReceivablesApiResponse, BadDebtApiResponse } from '../types';
|
||||
import type {
|
||||
ReceivableData,
|
||||
DebtCollectionData,
|
||||
CheckPoint,
|
||||
CheckPointType,
|
||||
} from '@/components/business/CEODashboard/types';
|
||||
import { formatAmount, normalizePath } from './common';
|
||||
|
||||
// ============================================
|
||||
// 미수금 (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: normalizePath('/accounting/receivables-status'),
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 채권추심 (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;
|
||||
}
|
||||
|
||||
// TODO: 백엔드 per-card sub_label/count 제공 시 더미값 제거
|
||||
// 채권추심 카드별 더미 서브라벨 (회사명 + 건수)
|
||||
const DEBT_COLLECTION_FALLBACK_SUB_LABELS: Record<string, { company: string; count: number }> = {
|
||||
dc1: { company: '(주)부산화학 외', count: 5 },
|
||||
dc2: { company: '(주)삼성테크 외', count: 3 },
|
||||
dc3: { company: '(주)대한전자 외', count: 2 },
|
||||
dc4: { company: '(주)한국정밀 외', count: 3 },
|
||||
};
|
||||
|
||||
/**
|
||||
* 채권추심 subLabel 생성 헬퍼
|
||||
* dc1(누적)은 API client_count 사용, 나머지는 더미값
|
||||
*/
|
||||
function buildDebtSubLabel(cardId: string, clientCount?: number): string | undefined {
|
||||
const fallback = DEBT_COLLECTION_FALLBACK_SUB_LABELS[cardId];
|
||||
if (!fallback) return undefined;
|
||||
|
||||
const count = cardId === 'dc1' && clientCount !== undefined ? clientCount : fallback.count;
|
||||
if (count <= 0) return undefined;
|
||||
|
||||
const remaining = count - 1;
|
||||
if (remaining > 0) {
|
||||
return `${fallback.company} ${remaining}건`;
|
||||
}
|
||||
return fallback.company.replace(/ 외$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* BadDebt API 응답 → Frontend 타입 변환
|
||||
*/
|
||||
export function transformDebtCollectionResponse(api: BadDebtApiResponse): DebtCollectionData {
|
||||
return {
|
||||
cards: [
|
||||
{
|
||||
id: 'dc1',
|
||||
label: '누적 악성채권',
|
||||
amount: api.total_amount,
|
||||
subLabel: buildDebtSubLabel('dc1', api.client_count),
|
||||
},
|
||||
{
|
||||
id: 'dc2',
|
||||
label: '추심중',
|
||||
amount: api.collecting_amount,
|
||||
subLabel: buildDebtSubLabel('dc2'),
|
||||
},
|
||||
{
|
||||
id: 'dc3',
|
||||
label: '법적조치',
|
||||
amount: api.legal_action_amount,
|
||||
subLabel: buildDebtSubLabel('dc3'),
|
||||
},
|
||||
{
|
||||
id: 'dc4',
|
||||
label: '회수완료',
|
||||
amount: api.recovered_amount,
|
||||
subLabel: buildDebtSubLabel('dc4'),
|
||||
},
|
||||
],
|
||||
checkPoints: generateDebtCollectionCheckPoints(api),
|
||||
detailButtonPath: normalizePath('/accounting/bad-debt-collection'),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user