import fs from 'fs'; import path from 'path'; import type { Priority, TaskStatus } from '@prisma/client'; export interface HrProject { idx?: number; name: string; pm?: string; priority: string; startDate?: string; endDate?: string; status?: string; category: string; summary?: string; content?: string; briefIntro?: string; progress?: number; progressLog?: string; progressStatus?: string; issues?: string; statusText?: string; isIssue?: boolean; keywords?: string[]; refLinks?: { name: string; url: string }[]; referenceUrl?: string; subPhases?: { name: string; status?: string; text?: string }[]; timelineItems?: { startDate?: string; endDate?: string; desc?: string }[]; showOnDashboard?: boolean; } export interface MappedTask { title: string; description: string | null; status: TaskStatus; priority: Priority; quarter: string; category: string; section: string; taskType: string; progress: number; issueNote: string | null; startDate: Date | null; dueDate: Date | null; keywords: string | null; showDate: boolean; showDescription: boolean; showStatus: boolean; showIssue: boolean; showProgress: boolean; milestones: { title: string; description: string | null; progress: number; links: string | null; }[]; detailContent: string | null; } const SECTION_MAP: Record = { 인사관리: '인사관리', 성장지원: '학습성장', 운영지원: '운영지원', 전산관리: '전산관리', }; const STATUS_MAP: Record = { 진행: 'IN_PROGRESS', 진행중: 'IN_PROGRESS', 대기: 'TODO', 완료: 'DONE', }; const PHASE_PROGRESS: Record = { 완료: 100, 진행중: 50, 미착수: 0, }; export function defaultHrDataPath(): string { return path.resolve(__dirname, '../../../HR_Dashboard/data.json'); } export function loadHrProjects(filePath = defaultHrDataPath()): HrProject[] { const raw = fs.readFileSync(filePath, 'utf-8'); const data = JSON.parse(raw) as { PROJECTS?: HrProject[] }; return (data.PROJECTS ?? []).filter((p) => p.showOnDashboard !== false); } function parseDate(value?: string): Date | null { if (!value?.trim()) return null; const d = new Date(value); return Number.isNaN(d.getTime()) ? null : d; } function mapSection(category: string): string { return SECTION_MAP[category] ?? category; } function mapStatus(status?: string, isRoutine = false): TaskStatus { if (!status?.trim()) return isRoutine ? 'IN_PROGRESS' : 'TODO'; return STATUS_MAP[status] ?? 'IN_PROGRESS'; } function mapPriority(priority: string): Priority { if (priority === '상시') return 'MEDIUM'; if (priority === '높음') return 'HIGH'; if (priority === '낮음') return 'LOW'; return 'MEDIUM'; } function mapProgress(value?: number): number { if (value == null || Number.isNaN(value)) return 0; if (value <= 1) return Math.round(value * 100); return Math.min(100, Math.round(value)); } function pickDescription(p: HrProject): string | null { const candidates = [p.content, p.summary, p.briefIntro].map((v) => v?.trim()).filter(Boolean) as string[]; const best = candidates.find((v) => v.length > 2 && v !== '12'); return best ?? null; } function pickIssueNote(p: HrProject): string | null { const parts: string[] = []; if (p.isIssue && p.statusText?.trim()) parts.push(p.statusText.trim()); if (p.issues?.trim()) parts.push(p.issues.trim()); if (p.progressLog?.trim() && p.progressLog !== '이슈사항') parts.push(p.progressLog.trim()); return parts.length ? parts.join('\n') : null; } function buildLinks(p: HrProject): string | null { const links: { label: string; url: string }[] = []; for (const ref of p.refLinks ?? []) { if (ref.url?.trim()) links.push({ label: ref.name || '참고자료', url: ref.url.trim() }); } if (p.referenceUrl?.trim()) links.push({ label: '참고링크', url: p.referenceUrl.trim() }); return links.length ? JSON.stringify(links) : null; } function buildMilestones(p: HrProject): MappedTask['milestones'] { const milestones: MappedTask['milestones'] = []; for (const [i, phase] of (p.subPhases ?? []).entries()) { milestones.push({ title: phase.name, description: phase.text?.trim() || null, progress: PHASE_PROGRESS[phase.status ?? ''] ?? 0, links: null, }); } for (const item of p.timelineItems ?? []) { if (!item.desc?.trim()) continue; milestones.push({ title: item.desc.trim(), description: [item.startDate, item.endDate].filter(Boolean).join(' ~ ') || null, progress: 0, links: null, }); } if (milestones.length === 0 && buildLinks(p)) { milestones.push({ title: '참고자료', description: null, progress: 0, links: buildLinks(p), }); } return milestones; } function buildDetailContent(p: HrProject): string | null { const content = p.progressStatus?.trim() || p.progressLog?.trim(); if (!content || content === '이슈사항' || content === '12') return null; return content; } export function mapHrProjectToTask(p: HrProject, quarter = '2026-Q2'): MappedTask { const isRoutine = p.priority === '상시'; const taskType = isRoutine ? '기반업무' : '실행과제'; const visible = !isRoutine; return { title: p.name.trim(), description: pickDescription(p), status: mapStatus(p.status, isRoutine), priority: mapPriority(p.priority), quarter, category: mapSection(p.category), section: mapSection(p.category), taskType, progress: mapProgress(p.progress), issueNote: pickIssueNote(p), startDate: parseDate(p.startDate), dueDate: parseDate(p.endDate), keywords: p.keywords?.length ? p.keywords.join(', ') : null, showDate: visible, showDescription: visible, showStatus: visible, showIssue: visible, showProgress: visible, milestones: buildMilestones(p), detailContent: buildDetailContent(p), }; } export function mapAllHrProjects(filePath?: string, quarter = '2026-Q2'): MappedTask[] { return loadHrProjects(filePath).map((p) => mapHrProjectToTask(p, quarter)); }