From 38d262bfcc2b30fd8567b22809d363f47df8bbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 22 Mar 2026 20:54:05 +0900 Subject: [PATCH] =?UTF-8?q?style:=20[barobill]=20PPT=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=ED=85=8C=EB=A7=88=EB=A1=9C=20=EC=A0=84=ED=99=98=20?= =?UTF-8?q?(=EC=9D=B8=EC=87=84=20=EC=B9=9C=ED=99=94=EC=A0=81=20=ED=9D=B0?= =?UTF-8?q?=EC=83=89=20=EB=B0=B0=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presentations/barobill-migration-report.cjs | 476 ++++++++++--------- presentations/barobill-migration-report.pptx | Bin 305862 -> 318257 bytes 2 files changed, 239 insertions(+), 237 deletions(-) diff --git a/presentations/barobill-migration-report.cjs b/presentations/barobill-migration-report.cjs index 5f6f4a4..185f0b1 100644 --- a/presentations/barobill-migration-report.cjs +++ b/presentations/barobill-migration-report.cjs @@ -4,8 +4,8 @@ module.paths.unshift(path.join(require('os').homedir(), '.claude/skills/pptx-ski const PptxGenJS = require('pptxgenjs'); -// BI 로고를 base64로 변환 -const biLogoPath = '/home/aweso/sam/docs/assets/bi/sam_bi_white.png'; +// BI 로고 (밝은 배경용 검정 로고) +const biLogoPath = '/home/aweso/sam/docs/assets/bi/sam_bi_black.png'; const biLogoBase64 = fs.existsSync(biLogoPath) ? 'image/png;base64,' + fs.readFileSync(biLogoPath).toString('base64') : null; @@ -14,95 +14,121 @@ async function main() { const pres = new PptxGenJS(); pres.defineLayout({ name: 'CUSTOM_16x9', width: 10, height: 5.625 }); pres.layout = 'CUSTOM_16x9'; + pres.author = '(주)코드브릿지엑스'; + pres.title = '바로빌 서비스 이관 완료 보고'; - // ─── 색상 팔레트 ─── + // ─── 라이트 테마 색상 팔레트 (인쇄 친화) ─── const C = { - dark: '1a1a2e', - darkBlue: '16213e', - accent: '0f3460', - blue: '2196F3', - green: '4CAF50', - greenBg: 'E8F5E9', - greenDark: '2E7D32', - orange: 'FF9800', - orangeBg: 'FFF3E0', - red: 'E53935', - redBg: 'FFEBEE', - purple: '7C4DFF', - purpleBg: 'EDE7F6', + // 배경 white: 'FFFFFF', - gray: '666666', - grayLight: 'F5F5F5', - grayBorder: 'E0E0E0', - text: '333333', - textLight: '999999', + bg: 'F8FAFC', // 슬라이드 배경 (아주 밝은 회색) + cardBg: 'FFFFFF', + // 텍스트 + title: '0F172A', // 거의 검정 + text: '334155', // 슬레이트 700 + textLight: '94A3B8', // 슬레이트 400 + textMuted: '64748B', // 슬레이트 500 + // 포인트 컬러 + primary: '2563EB', // 블루 600 + primaryLight: 'DBEAFE', // 블루 100 + primaryDark: '1E40AF', // 블루 800 + green: '16A34A', // 그린 600 + greenLight: 'DCFCE7', // 그린 100 + greenDark: '15803D', // 그린 700 + orange: 'EA580C', // 오렌지 600 + orangeLight: 'FFEDD5', // 오렌지 100 + red: 'DC2626', // 레드 600 + redLight: 'FEE2E2', // 레드 100 + purple: '7C3AED', // 바이올렛 600 + purpleLight: 'EDE9FE', // 바이올렛 100 + teal: '0D9488', // 틸 600 + tealLight: 'CCFBF1', // 틸 100 + // 구조 + border: 'E2E8F0', // 슬레이트 200 + divider: 'CBD5E1', // 슬레이트 300 + headerBg: 'F1F5F9', // 슬레이트 100 + dark: '1E293B', // 슬레이트 800 (강조 배경) }; // ─── 공통 함수 ─── function addFooter(slide, pageNum, totalPages) { - slide.addShape(pres.ShapeType.rect, { x: 0, y: 5.25, w: 10, h: 0.375, fill: { color: C.dark } }); - slide.addText('SAM 바로빌 서비스 이관 보고 | (주)코드브릿지엑스', { - x: 0.5, y: 5.25, w: 7, h: 0.375, fontSize: 7, color: '888888', fontFace: 'Arial' + slide.addShape(pres.ShapeType.rect, { x: 0, y: 5.25, w: 10, h: 0.375, fill: { color: C.headerBg } }); + slide.addShape(pres.ShapeType.rect, { x: 0, y: 5.24, w: 10, h: 0.01, fill: { color: C.border } }); + slide.addText('SAM 바로빌 서비스 이관 보고 | (주)코드브릿지엑스', { + x: 0.5, y: 5.25, w: 7, h: 0.375, fontSize: 7, color: C.textLight, fontFace: 'Arial' }); slide.addText(`${pageNum} / ${totalPages}`, { - x: 8.5, y: 5.25, w: 1, h: 0.375, fontSize: 7, color: '888888', align: 'right', fontFace: 'Arial' + x: 8.5, y: 5.25, w: 1, h: 0.375, fontSize: 7, color: C.textLight, align: 'right', fontFace: 'Arial' }); } function addPageTitle(slide, title, subtitle) { - slide.addShape(pres.ShapeType.rect, { x: 0.5, y: 0.35, w: 0.06, h: 0.35, fill: { color: C.blue } }); - slide.addText(title, { x: 0.7, y: 0.28, w: 6, h: 0.5, fontSize: 20, bold: true, color: C.dark, fontFace: 'Arial' }); + slide.addShape(pres.ShapeType.rect, { x: 0.5, y: 0.35, w: 0.06, h: 0.4, fill: { color: C.primary } }); + slide.addText(title, { x: 0.7, y: 0.28, w: 6, h: 0.55, fontSize: 22, bold: true, color: C.title, fontFace: 'Arial' }); if (subtitle) { - slide.addText(subtitle, { x: 0.7, y: 0.72, w: 6, h: 0.3, fontSize: 10, color: C.gray, fontFace: 'Arial' }); + slide.addText(subtitle, { x: 0.7, y: 0.78, w: 6, h: 0.25, fontSize: 10, color: C.textMuted, fontFace: 'Arial' }); } } - function addBadge(slide, x, y, text, bgColor, textColor) { - slide.addShape(pres.ShapeType.roundRect, { x, y, w: text.length * 0.12 + 0.4, h: 0.28, rectRadius: 0.04, fill: { color: bgColor } }); - slide.addText(text, { x, y, w: text.length * 0.12 + 0.4, h: 0.28, fontSize: 8, bold: true, color: textColor, align: 'center', fontFace: 'Arial' }); - } - const totalPages = 7; // ═══════════════════════════════════════════════════════════════ // 슬라이드 1: 표지 // ═══════════════════════════════════════════════════════════════ const slide1 = pres.addSlide(); - slide1.background = { fill: C.dark }; + slide1.background = { fill: C.white }; - // 상단 장식 라인 - slide1.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.blue } }); + // 상단 컬러 바 + slide1.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 10, h: 0.08, fill: { color: C.primary } }); + + // 좌측 세로 장식 + slide1.addShape(pres.ShapeType.rect, { x: 0, y: 0, w: 0.08, h: 5.625, fill: { color: C.primary } }); // BI 로고 if (biLogoBase64) { - slide1.addImage({ data: biLogoBase64, x: 0.7, y: 0.5, w: 1.2, h: 1.2 }); + slide1.addImage({ data: biLogoBase64, x: 0.6, y: 0.4, w: 0.8, h: 0.8 }); } - // 제목 영역 - slide1.addText('바로빌 서비스 이관 완료 보고', { - x: 0.7, y: 1.8, w: 8.6, h: 0.8, - fontSize: 32, bold: true, color: C.white, fontFace: 'Arial' + // 제목 + slide1.addText('바로빌 서비스 이관', { + x: 0.6, y: 1.8, w: 6, h: 0.7, + fontSize: 36, bold: true, color: C.title, fontFace: 'Arial' }); - slide1.addText('MNG → API 전체 기능 이관 현황', { - x: 0.7, y: 2.6, w: 8.6, h: 0.5, - fontSize: 16, color: C.blue, fontFace: 'Arial' + slide1.addText('완료 보고', { + x: 0.6, y: 2.4, w: 6, h: 0.6, + fontSize: 36, bold: true, color: C.primary, fontFace: 'Arial' }); - // 구분선 - slide1.addShape(pres.ShapeType.rect, { x: 0.7, y: 3.3, w: 2, h: 0.03, fill: { color: C.blue } }); + // 부제목 + slide1.addShape(pres.ShapeType.rect, { x: 0.6, y: 3.2, w: 2.5, h: 0.03, fill: { color: C.primary } }); + slide1.addText('MNG → API 전체 기능 이관 현황', { + x: 0.6, y: 3.4, w: 5, h: 0.35, + fontSize: 14, color: C.textMuted, fontFace: 'Arial' + }); // 메타 정보 slide1.addText([ - { text: '보고일: 2026-03-22', options: { fontSize: 11, color: '888888' } }, - { text: '\n담당: R&D실', options: { fontSize: 11, color: '888888' } }, - { text: '\n프로젝트: SAM (Smart Automation Management)', options: { fontSize: 11, color: '888888' } }, - ], { x: 0.7, y: 3.6, w: 5, h: 1.2, fontFace: 'Arial', lineSpacingMultiple: 1.5 }); + { text: '보고일 2026-03-22', options: { fontSize: 10, color: C.textLight } }, + { text: '\n담당 R&D실', options: { fontSize: 10, color: C.textLight } }, + ], { x: 0.6, y: 4.0, w: 4, h: 0.8, fontFace: 'Arial', lineSpacingMultiple: 1.6 }); - // 우측 하이라이트 카드 - slide1.addShape(pres.ShapeType.roundRect, { x: 6.5, y: 3.5, w: 3, h: 1.4, rectRadius: 0.15, fill: { color: '222244' }, line: { color: C.blue, width: 1 } }); - slide1.addText('이관 완료', { x: 6.5, y: 3.6, w: 3, h: 0.3, fontSize: 10, color: C.blue, align: 'center', fontFace: 'Arial' }); - slide1.addText('12 / 12', { x: 6.5, y: 3.9, w: 3, h: 0.5, fontSize: 36, bold: true, color: C.green, align: 'center', fontFace: 'Arial' }); - slide1.addText('100% 완료', { x: 6.5, y: 4.4, w: 3, h: 0.3, fontSize: 12, bold: true, color: C.green, align: 'center', fontFace: 'Arial' }); + // 우측 완료 카드 + slide1.addShape(pres.ShapeType.roundRect, { x: 6.5, y: 1.5, w: 3, h: 2.8, rectRadius: 0.15, fill: { color: C.greenLight }, line: { color: C.green, width: 1.5 } }); + slide1.addText('이관 완료', { x: 6.5, y: 1.7, w: 3, h: 0.3, fontSize: 11, color: C.green, align: 'center', fontFace: 'Arial' }); + slide1.addText('12 / 12', { x: 6.5, y: 2.1, w: 3, h: 0.7, fontSize: 44, bold: true, color: C.greenDark, align: 'center', fontFace: 'Arial' }); + slide1.addText('100%', { x: 6.5, y: 2.8, w: 3, h: 0.4, fontSize: 20, bold: true, color: C.green, align: 'center', fontFace: 'Arial' }); + + // 핵심 수치 카드들 + const metrics = [ + { label: 'API 엔드포인트', value: '87+', color: C.primary, bg: C.primaryLight }, + { label: 'SOAP 메서드', value: '57', color: C.teal, bg: C.tealLight }, + ]; + metrics.forEach((m, i) => { + const mx = 6.5 + i * 1.55; + slide1.addShape(pres.ShapeType.roundRect, { x: mx, y: 3.55, w: 1.4, h: 0.65, rectRadius: 0.08, fill: { color: m.bg } }); + slide1.addText(m.value, { x: mx, y: 3.52, w: 1.4, h: 0.4, fontSize: 18, bold: true, color: m.color, align: 'center', fontFace: 'Arial' }); + slide1.addText(m.label, { x: mx, y: 3.88, w: 1.4, h: 0.25, fontSize: 7, color: m.color, align: 'center', fontFace: 'Arial' }); + }); addFooter(slide1, 1, totalPages); @@ -110,14 +136,15 @@ async function main() { // 슬라이드 2: 이관 배경 및 목적 // ═══════════════════════════════════════════════════════════════ const slide2 = pres.addSlide(); - slide2.background = { fill: C.white }; + slide2.background = { fill: C.bg }; addPageTitle(slide2, '이관 배경 및 목적', 'MNG 백오피스 → API 서비스 전환'); - // 아키텍처 다이어그램 - // Before 카드 - slide2.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.3, w: 4.2, h: 3.5, rectRadius: 0.12, fill: { color: C.redBg }, line: { color: 'FFCDD2', width: 1 } }); - addBadge(slide2, 1.6, 1.45, 'AS-IS', C.red, C.white); - slide2.addText('MNG 단독 운영', { x: 0.7, y: 1.9, w: 3.8, h: 0.35, fontSize: 14, bold: true, color: C.red, align: 'center', fontFace: 'Arial' }); + // AS-IS + slide2.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.3, w: 4.2, h: 3.6, rectRadius: 0.12, fill: { color: C.white }, line: { color: C.border, width: 1 } }); + // 헤더 + slide2.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.3, w: 4.2, h: 0.5, rectRadius: 0.12, fill: { color: C.redLight } }); + slide2.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.68, w: 4.2, h: 0.13, fill: { color: C.redLight } }); + slide2.addText('AS-IS | MNG 단독 운영', { x: 0.7, y: 1.35, w: 3.8, h: 0.4, fontSize: 12, bold: true, color: C.red, fontFace: 'Arial' }); const asIsItems = [ 'SOAP 연동이 MNG에만 존재', @@ -126,19 +153,21 @@ async function main() { '멀티테넌트 SaaS 제공 불가', ]; asIsItems.forEach((item, i) => { - slide2.addShape(pres.ShapeType.roundRect, { x: 0.7, y: 2.5 + i * 0.55, w: 3.8, h: 0.42, rectRadius: 0.06, fill: { color: C.white } }); - slide2.addShape(pres.ShapeType.ellipse, { x: 0.85, y: 2.58 + i * 0.55, w: 0.22, h: 0.22, fill: { color: C.red } }); - slide2.addText('x', { x: 0.85, y: 2.56 + i * 0.55, w: 0.22, h: 0.22, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide2.addText(item, { x: 1.2, y: 2.5 + i * 0.55, w: 3.2, h: 0.42, fontSize: 10, color: C.text, valign: 'middle', fontFace: 'Arial' }); + const iy = 2.05 + i * 0.6; + slide2.addShape(pres.ShapeType.roundRect, { x: 0.7, y: iy, w: 3.8, h: 0.45, rectRadius: 0.06, fill: { color: C.headerBg } }); + slide2.addShape(pres.ShapeType.ellipse, { x: 0.85, y: iy + 0.1, w: 0.25, h: 0.25, fill: { color: C.red } }); + slide2.addText('X', { x: 0.85, y: iy + 0.1, w: 0.25, h: 0.25, fontSize: 10, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide2.addText(item, { x: 1.25, y: iy, w: 3.1, h: 0.45, fontSize: 10, color: C.text, valign: 'middle', fontFace: 'Arial' }); }); // 화살표 - slide2.addText('→', { x: 4.6, y: 2.7, w: 0.8, h: 0.5, fontSize: 28, bold: true, color: C.blue, align: 'center', fontFace: 'Arial' }); + slide2.addText('▶', { x: 4.6, y: 2.8, w: 0.8, h: 0.5, fontSize: 24, color: C.primary, align: 'center', fontFace: 'Arial' }); - // After 카드 - slide2.addShape(pres.ShapeType.roundRect, { x: 5.3, y: 1.3, w: 4.2, h: 3.5, rectRadius: 0.12, fill: { color: C.greenBg }, line: { color: 'C8E6C9', width: 1 } }); - addBadge(slide2, 6.4, 1.45, 'TO-BE', C.green, C.white); - slide2.addText('API + React 서비스', { x: 5.5, y: 1.9, w: 3.8, h: 0.35, fontSize: 14, bold: true, color: C.greenDark, align: 'center', fontFace: 'Arial' }); + // TO-BE + slide2.addShape(pres.ShapeType.roundRect, { x: 5.3, y: 1.3, w: 4.2, h: 3.6, rectRadius: 0.12, fill: { color: C.white }, line: { color: C.border, width: 1 } }); + slide2.addShape(pres.ShapeType.roundRect, { x: 5.3, y: 1.3, w: 4.2, h: 0.5, rectRadius: 0.12, fill: { color: C.greenLight } }); + slide2.addShape(pres.ShapeType.rect, { x: 5.3, y: 1.68, w: 4.2, h: 0.13, fill: { color: C.greenLight } }); + slide2.addText('TO-BE | API + React 서비스', { x: 5.5, y: 1.35, w: 3.8, h: 0.4, fontSize: 12, bold: true, color: C.greenDark, fontFace: 'Arial' }); const toBeItems = [ 'API에 독립 SOAP 서비스 구축', @@ -147,10 +176,11 @@ async function main() { '멀티테넌트 SaaS 과금 체계', ]; toBeItems.forEach((item, i) => { - slide2.addShape(pres.ShapeType.roundRect, { x: 5.5, y: 2.5 + i * 0.55, w: 3.8, h: 0.42, rectRadius: 0.06, fill: { color: C.white } }); - slide2.addShape(pres.ShapeType.ellipse, { x: 5.65, y: 2.58 + i * 0.55, w: 0.22, h: 0.22, fill: { color: C.green } }); - slide2.addText('✓', { x: 5.65, y: 2.56 + i * 0.55, w: 0.22, h: 0.22, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide2.addText(item, { x: 6.0, y: 2.5 + i * 0.55, w: 3.2, h: 0.42, fontSize: 10, color: C.text, valign: 'middle', fontFace: 'Arial' }); + const iy = 2.05 + i * 0.6; + slide2.addShape(pres.ShapeType.roundRect, { x: 5.5, y: iy, w: 3.8, h: 0.45, rectRadius: 0.06, fill: { color: C.headerBg } }); + slide2.addShape(pres.ShapeType.ellipse, { x: 5.65, y: iy + 0.1, w: 0.25, h: 0.25, fill: { color: C.green } }); + slide2.addText('✓', { x: 5.65, y: iy + 0.08, w: 0.25, h: 0.25, fontSize: 11, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide2.addText(item, { x: 6.05, y: iy, w: 3.1, h: 0.45, fontSize: 10, color: C.text, valign: 'middle', fontFace: 'Arial' }); }); addFooter(slide2, 2, totalPages); @@ -159,54 +189,64 @@ async function main() { // 슬라이드 3: 이관 현황 총괄 // ═══════════════════════════════════════════════════════════════ const slide3 = pres.addSlide(); - slide3.background = { fill: C.white }; + slide3.background = { fill: C.bg }; addPageTitle(slide3, '이관 현황 총괄', '12개 영역 전체 API 이관 완료'); // 프로그레스 바 - slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.15, w: 9, h: 0.35, rectRadius: 0.06, fill: { color: 'E0E0E0' } }); + slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.15, w: 9, h: 0.35, rectRadius: 0.06, fill: { color: C.border } }); slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 1.15, w: 9, h: 0.35, rectRadius: 0.06, fill: { color: C.green } }); slide3.addText('100% 완료 (12/12 영역)', { x: 0.5, y: 1.15, w: 9, h: 0.35, fontSize: 11, bold: true, color: C.white, align: 'center', fontFace: 'Arial' }); - // 영역 테이블 + // 테이블 const areas = [ - ['SOAP 연동 서비스', '57개 메서드', C.green], - ['바로빌 설정/회원', '설정 + 회원 CRUD', C.green], - ['계좌 조회 (EAccount)', '13 EP + Sync', C.green], - ['카드 조회 (ECard)', '16 EP + Sync', C.green], - ['홈택스 매입/매출', '13 EP + Sync', C.green], - ['전자세금계산서 발행', '16 EP (SOAP)', C.green], - ['자동 동기화 스케줄러', 'Job (매일 06:00)', C.green], - ['카카오톡 발송', '12 EP', C.blue], - ['SMS 발송', '4 EP', C.blue], - ['구독/과금 관리', '9 EP', C.purple], - ['사용량 관리', '4 EP', C.purple], + ['SOAP 연동 서비스', '57개 메서드', 'green', '기존'], + ['바로빌 설정/회원 관리', '설정 + 회원 CRUD', 'green', '기존'], + ['계좌 조회 (EAccount)', '13 EP + Sync', 'green', '기존'], + ['카드 조회 (ECard)', '16 EP + Sync', 'green', '기존'], + ['홈택스 매입/매출', '13 EP + Sync', 'green', '기존'], + ['전자세금계산서 발행', '16 EP (SOAP)', 'green', '기존'], + ['자동 동기화 스케줄러', 'Job (매일 06:00)', 'green', '기존'], + ['카카오톡 발송', '12 EP', 'blue', '신규'], + ['SMS 발송', '4 EP', 'blue', '신규'], + ['구독/과금 관리', '9 EP', 'purple', '신규'], + ['사용량 관리', '4 EP', 'purple', '신규'], ]; - // 테이블 헤더 const tblY = 1.7; - slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: tblY, w: 9, h: 0.35, rectRadius: 0.04, fill: { color: C.dark } }); - slide3.addText('#', { x: 0.55, y: tblY, w: 0.4, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide3.addText('영역', { x: 1.0, y: tblY, w: 4, h: 0.35, fontSize: 8, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); - slide3.addText('API 규모', { x: 5.2, y: tblY, w: 2, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide3.addText('상태', { x: 7.5, y: tblY, w: 1.8, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + // 헤더 + slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: tblY, w: 9, h: 0.35, rectRadius: 0.03, fill: { color: C.dark } }); + slide3.addText('#', { x: 0.6, y: tblY, w: 0.4, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText('영역', { x: 1.1, y: tblY, w: 3.5, h: 0.35, fontSize: 8, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); + slide3.addText('API 규모', { x: 4.8, y: tblY, w: 2, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText('구분', { x: 7.0, y: tblY, w: 0.8, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText('상태', { x: 8.0, y: tblY, w: 1.3, h: 0.35, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - areas.forEach((area, i) => { - const rowY = tblY + 0.38 + i * 0.3; - const rowBg = i % 2 === 0 ? C.grayLight : C.white; - slide3.addShape(pres.ShapeType.rect, { x: 0.5, y: rowY, w: 9, h: 0.3, fill: { color: rowBg } }); - slide3.addText(`${i + 1}`, { x: 0.55, y: rowY, w: 0.4, h: 0.3, fontSize: 8, color: C.gray, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide3.addText(area[0], { x: 1.0, y: rowY, w: 4, h: 0.3, fontSize: 9, color: C.text, valign: 'middle', fontFace: 'Arial' }); - slide3.addText(area[1], { x: 5.2, y: rowY, w: 2, h: 0.3, fontSize: 8, color: C.gray, align: 'center', valign: 'middle', fontFace: 'Arial' }); - // 상태 배지 - slide3.addShape(pres.ShapeType.roundRect, { x: 7.8, y: rowY + 0.04, w: 1.2, h: 0.22, rectRadius: 0.04, fill: { color: area[2] } }); - slide3.addText('완료', { x: 7.8, y: rowY + 0.04, w: 1.2, h: 0.22, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + const colorMap = { green: C.green, blue: C.primary, purple: C.purple }; + const bgMap = { green: C.greenLight, blue: C.primaryLight, purple: C.purpleLight }; + + areas.forEach((a, i) => { + const ry = tblY + 0.38 + i * 0.29; + const rowBg = i % 2 === 0 ? C.headerBg : C.white; + slide3.addShape(pres.ShapeType.rect, { x: 0.5, y: ry, w: 9, h: 0.29, fill: { color: rowBg } }); + slide3.addText(`${i + 1}`, { x: 0.6, y: ry, w: 0.4, h: 0.29, fontSize: 8, color: C.textLight, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText(a[0], { x: 1.1, y: ry, w: 3.5, h: 0.29, fontSize: 9, color: C.text, valign: 'middle', fontFace: 'Arial' }); + slide3.addText(a[1], { x: 4.8, y: ry, w: 2, h: 0.29, fontSize: 8, color: C.textMuted, align: 'center', valign: 'middle', fontFace: 'Arial' }); + // 구분 배지 + slide3.addShape(pres.ShapeType.roundRect, { x: 7.05, y: ry + 0.04, w: 0.7, h: 0.21, rectRadius: 0.03, fill: { color: bgMap[a[2]] } }); + slide3.addText(a[3], { x: 7.05, y: ry + 0.04, w: 0.7, h: 0.21, fontSize: 7, bold: true, color: colorMap[a[2]], align: 'center', valign: 'middle', fontFace: 'Arial' }); + // 완료 배지 + slide3.addShape(pres.ShapeType.roundRect, { x: 8.2, y: ry + 0.04, w: 0.9, h: 0.21, rectRadius: 0.03, fill: { color: C.green } }); + slide3.addText('완료', { x: 8.2, y: ry + 0.04, w: 0.9, h: 0.21, fontSize: 7, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); }); - // 신규 이관 표시 - slide3.addShape(pres.ShapeType.ellipse, { x: 0.6, y: 4.85, w: 0.15, h: 0.15, fill: { color: C.blue } }); - slide3.addText('이번 세션 신규 이관 (카카오톡/SMS)', { x: 0.85, y: 4.82, w: 3, h: 0.2, fontSize: 7, color: C.blue, fontFace: 'Arial' }); - slide3.addShape(pres.ShapeType.ellipse, { x: 4.0, y: 4.85, w: 0.15, h: 0.15, fill: { color: C.purple } }); - slide3.addText('이번 세션 신규 이관 (과금/사용량)', { x: 4.25, y: 4.82, w: 3, h: 0.2, fontSize: 7, color: C.purple, fontFace: 'Arial' }); + // 범례 + slide3.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.85, w: 0.5, h: 0.2, rectRadius: 0.03, fill: { color: C.primaryLight } }); + slide3.addText('신규', { x: 0.5, y: 4.85, w: 0.5, h: 0.2, fontSize: 7, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText('이번 세션 카카오톡/SMS 이관', { x: 1.1, y: 4.83, w: 2.5, h: 0.22, fontSize: 8, color: C.textMuted, fontFace: 'Arial' }); + + slide3.addShape(pres.ShapeType.roundRect, { x: 3.8, y: 4.85, w: 0.5, h: 0.2, rectRadius: 0.03, fill: { color: C.purpleLight } }); + slide3.addText('신규', { x: 3.8, y: 4.85, w: 0.5, h: 0.2, fontSize: 7, bold: true, color: C.purple, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide3.addText('이번 세션 과금/사용량 이관', { x: 4.4, y: 4.83, w: 2.5, h: 0.22, fontSize: 8, color: C.textMuted, fontFace: 'Arial' }); addFooter(slide3, 3, totalPages); @@ -214,27 +254,18 @@ async function main() { // 슬라이드 4: API 엔드포인트 상세 // ═══════════════════════════════════════════════════════════════ const slide4 = pres.addSlide(); - slide4.background = { fill: C.white }; + slide4.background = { fill: C.bg }; addPageTitle(slide4, 'API 엔드포인트 상세', '총 87+ REST API 엔드포인트 구축'); const epGroups = [ - { title: '데이터 조회/관리', color: C.green, bg: C.greenBg, border: 'C8E6C9', items: [ - ['카드 거래', '16 EP'], - ['은행 거래', '13 EP'], - ['홈택스', '13 EP'], - ['세금계산서', '16 EP'], + { title: '데이터 조회/관리', color: C.green, bg: C.greenLight, items: [ + ['카드 거래', '16 EP'], ['은행 거래', '13 EP'], ['홈택스', '13 EP'], ['세금계산서', '16 EP'], ]}, - { title: '통신/발송', color: C.blue, bg: 'E3F2FD', border: 'BBDEFB', items: [ - ['카카오톡', '12 EP'], - ['SMS', '4 EP'], - ['동기화', '3 EP'], - ['회원관리', '3 EP'], + { title: '통신/발송', color: C.primary, bg: C.primaryLight, items: [ + ['카카오톡', '12 EP'], ['SMS', '4 EP'], ['동기화', '3 EP'], ['회원관리', '3 EP'], ]}, - { title: '관리/과금', color: C.purple, bg: C.purpleBg, border: 'D1C4E9', items: [ - ['과금 관리', '9 EP'], - ['사용량', '4 EP'], - ['설정', '3 EP'], - ['스케줄러', 'Daily Job'], + { title: '관리/과금', color: C.purple, bg: C.purpleLight, items: [ + ['과금 관리', '9 EP'], ['사용량', '4 EP'], ['설정', '3 EP'], ['스케줄러', 'Daily Job'], ]}, ]; @@ -244,19 +275,16 @@ async function main() { const gy = 1.2; // 카드 배경 - slide4.addShape(pres.ShapeType.roundRect, { x: gx, y: gy, w: cardW, h: 3.8, rectRadius: 0.12, fill: { color: group.bg }, line: { color: group.border, width: 1 } }); - - // 카드 헤더 + slide4.addShape(pres.ShapeType.roundRect, { x: gx, y: gy, w: cardW, h: 3.8, rectRadius: 0.12, fill: { color: C.white }, line: { color: C.border, width: 1 } }); + // 헤더 slide4.addShape(pres.ShapeType.roundRect, { x: gx, y: gy, w: cardW, h: 0.5, rectRadius: 0.12, fill: { color: group.color } }); - // 하단 모서리 채우기 slide4.addShape(pres.ShapeType.rect, { x: gx, y: gy + 0.38, w: cardW, h: 0.13, fill: { color: group.color } }); slide4.addText(group.title, { x: gx, y: gy + 0.05, w: cardW, h: 0.4, fontSize: 13, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - // 카드 항목 group.items.forEach((item, ii) => { const iy = gy + 0.7 + ii * 0.75; - slide4.addShape(pres.ShapeType.roundRect, { x: gx + 0.15, y: iy, w: cardW - 0.3, h: 0.6, rectRadius: 0.08, fill: { color: C.white }, shadow: { type: 'outer', blur: 3, offset: 1, color: '00000010' } }); - slide4.addText(item[0], { x: gx + 0.3, y: iy + 0.05, w: cardW - 0.6, h: 0.22, fontSize: 10, bold: true, color: C.text, fontFace: 'Arial' }); + slide4.addShape(pres.ShapeType.roundRect, { x: gx + 0.15, y: iy, w: cardW - 0.3, h: 0.6, rectRadius: 0.08, fill: { color: C.headerBg }, line: { color: C.border, width: 0.5 } }); + slide4.addText(item[0], { x: gx + 0.3, y: iy + 0.05, w: cardW - 0.6, h: 0.22, fontSize: 10, color: C.text, fontFace: 'Arial' }); slide4.addText(item[1], { x: gx + 0.3, y: iy + 0.3, w: cardW - 0.6, h: 0.22, fontSize: 16, bold: true, color: group.color, fontFace: 'Arial' }); }); }); @@ -264,22 +292,21 @@ async function main() { addFooter(slide4, 4, totalPages); // ═══════════════════════════════════════════════════════════════ - // 슬라이드 5: SOAP 메서드 구현율 + // 슬라이드 5: SOAP 구현율 // ═══════════════════════════════════════════════════════════════ const slide5 = pres.addSlide(); - slide5.background = { fill: C.white }; + slide5.background = { fill: C.bg }; addPageTitle(slide5, 'SOAP 메서드 구현율', 'MNG 대비 API 100% 이상 구현'); const soapData = [ - { name: 'CORPSTATE\n회원관리', mng: 3, api: 3, color: '26A69A' }, - { name: 'BANKACCOUNT\n계좌조회', mng: 13, api: 14, color: '42A5F5' }, - { name: 'CARD\n카드조회', mng: 11, api: 11, color: '5C6BC0' }, - { name: 'TI\n세금계산서', mng: 3, api: 3, color: 'AB47BC' }, - { name: 'KAKAOTALK\n카카오톡', mng: 15, api: 15, color: 'FFA726' }, - { name: 'SMS\n문자전송', mng: 4, api: 4, color: 'EF5350' }, + { name: 'CORPSTATE\n회원관리', mng: 3, api: 3, color: C.teal }, + { name: 'BANK\nACCOUNT', mng: 13, api: 14, color: C.primary }, + { name: 'CARD\n카드조회', mng: 11, api: 11, color: '6366F1' }, + { name: 'TI\n세금계산서', mng: 3, api: 3, color: C.purple }, + { name: 'KAKAOTALK\n카카오톡', mng: 15, api: 15, color: C.orange }, + { name: 'SMS\n문자전송', mng: 4, api: 4, color: C.red }, ]; - // 차트 영역 const chartX = 0.5; const chartY = 1.3; const barW = 1.3; @@ -287,176 +314,151 @@ async function main() { const maxVal = 16; const chartH = 2.5; + // 차트 배경 + slide5.addShape(pres.ShapeType.roundRect, { x: 0.3, y: 1.1, w: 9.4, h: 3.1, rectRadius: 0.1, fill: { color: C.white }, line: { color: C.border, width: 0.5 } }); + soapData.forEach((d, i) => { - const bx = chartX + i * (barW + barGap); + const bx = chartX + 0.15 + i * (barW + barGap); // MNG 바 const mngH = (d.mng / maxVal) * chartH; slide5.addShape(pres.ShapeType.roundRect, { - x: bx, y: chartY + chartH - mngH, w: barW * 0.45, h: mngH, - rectRadius: 0.04, fill: { color: 'BDBDBD' } + x: bx, y: chartY + chartH - mngH, w: barW * 0.42, h: mngH, + rectRadius: 0.04, fill: { color: C.border } }); slide5.addText(`${d.mng}`, { - x: bx, y: chartY + chartH - mngH - 0.22, w: barW * 0.45, h: 0.22, - fontSize: 9, bold: true, color: C.gray, align: 'center', fontFace: 'Arial' + x: bx, y: chartY + chartH - mngH - 0.2, w: barW * 0.42, h: 0.2, + fontSize: 8, bold: true, color: C.textMuted, align: 'center', fontFace: 'Arial' }); // API 바 const apiH = (d.api / maxVal) * chartH; slide5.addShape(pres.ShapeType.roundRect, { - x: bx + barW * 0.5, y: chartY + chartH - apiH, w: barW * 0.45, h: apiH, + x: bx + barW * 0.48, y: chartY + chartH - apiH, w: barW * 0.42, h: apiH, rectRadius: 0.04, fill: { color: d.color } }); slide5.addText(`${d.api}`, { - x: bx + barW * 0.5, y: chartY + chartH - apiH - 0.22, w: barW * 0.45, h: 0.22, - fontSize: 9, bold: true, color: d.color, align: 'center', fontFace: 'Arial' + x: bx + barW * 0.48, y: chartY + chartH - apiH - 0.2, w: barW * 0.42, h: 0.2, + fontSize: 8, bold: true, color: d.color, align: 'center', fontFace: 'Arial' }); // 라벨 slide5.addText(d.name, { - x: bx, y: chartY + chartH + 0.08, w: barW, h: 0.45, - fontSize: 8, color: C.text, align: 'center', valign: 'top', fontFace: 'Arial' + x: bx - 0.05, y: chartY + chartH + 0.08, w: barW + 0.1, h: 0.4, + fontSize: 7, color: C.text, align: 'center', valign: 'top', fontFace: 'Arial' }); - - // 구현율 - const rate = Math.round((d.api / d.mng) * 100); - slide5.addShape(pres.ShapeType.roundRect, { x: bx + 0.2, y: chartY + chartH + 0.52, w: barW - 0.4, h: 0.22, rectRadius: 0.04, fill: { color: C.greenBg } }); - slide5.addText(`${rate}%`, { x: bx + 0.2, y: chartY + chartH + 0.52, w: barW - 0.4, h: 0.22, fontSize: 8, bold: true, color: C.greenDark, align: 'center', fontFace: 'Arial' }); }); // 범례 - slide5.addShape(pres.ShapeType.rect, { x: 7.5, y: 1.3, w: 0.3, h: 0.18, fill: { color: 'BDBDBD' } }); - slide5.addText('MNG', { x: 7.85, y: 1.28, w: 0.8, h: 0.22, fontSize: 9, color: C.gray, fontFace: 'Arial' }); - slide5.addShape(pres.ShapeType.rect, { x: 7.5, y: 1.55, w: 0.3, h: 0.18, fill: { color: C.blue } }); - slide5.addText('API', { x: 7.85, y: 1.53, w: 0.8, h: 0.22, fontSize: 9, color: C.blue, fontFace: 'Arial' }); + slide5.addShape(pres.ShapeType.rect, { x: 8.0, y: 1.25, w: 0.25, h: 0.15, fill: { color: C.border } }); + slide5.addText('MNG', { x: 8.3, y: 1.22, w: 0.6, h: 0.2, fontSize: 8, color: C.textMuted, fontFace: 'Arial' }); + slide5.addShape(pres.ShapeType.rect, { x: 8.0, y: 1.48, w: 0.25, h: 0.15, fill: { color: C.primary } }); + slide5.addText('API', { x: 8.3, y: 1.45, w: 0.6, h: 0.2, fontSize: 8, color: C.primary, fontFace: 'Arial' }); - // 합계 박스 + // 합계 바 slide5.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.55, w: 9, h: 0.5, rectRadius: 0.08, fill: { color: C.dark } }); slide5.addText([ - { text: 'SOAP 메서드 합계 ', options: { fontSize: 11, color: '888888' } }, - { text: 'MNG 49개', options: { fontSize: 11, color: 'BDBDBD', bold: true } }, - { text: ' → ', options: { fontSize: 11, color: '888888' } }, - { text: 'API 50개', options: { fontSize: 11, color: C.green, bold: true } }, - { text: ' (100%+)', options: { fontSize: 11, color: C.green } }, + { text: 'SOAP 메서드 합계 ', options: { fontSize: 10, color: C.textLight } }, + { text: 'MNG 49개', options: { fontSize: 10, color: C.divider, bold: true } }, + { text: ' → ', options: { fontSize: 10, color: C.textLight } }, + { text: 'API 50개', options: { fontSize: 10, color: C.green, bold: true } }, + { text: ' (100%+)', options: { fontSize: 10, color: C.green } }, ], { x: 0.5, y: 4.55, w: 9, h: 0.5, align: 'center', valign: 'middle', fontFace: 'Arial' }); addFooter(slide5, 5, totalPages); // ═══════════════════════════════════════════════════════════════ - // 슬라이드 6: 코드 매핑 요약 + // 슬라이드 6: 코드 매핑 // ═══════════════════════════════════════════════════════════════ const slide6 = pres.addSlide(); - slide6.background = { fill: C.white }; + slide6.background = { fill: C.bg }; addPageTitle(slide6, '코드 매핑 요약', 'MNG → API 파일 대응 관계'); const mappings = [ - { cat: 'Service', mng: 6, api: 8, detail: 'SOAP, Sync, Billing, Usage, TaxInvoice' }, - { cat: 'Controller', mng: 14, api: 11, detail: '카드/은행/홈택스/세금계산서/카카오톡/SMS/과금/사용량' }, - { cat: 'Model', mng: 18, api: 17, detail: '거래, 분개, 구독, 과금, 정책 등' }, - { cat: 'Migration', mng: '-', api: 30, detail: '바로빌 관련 테이블 생성/수정' }, - { cat: 'Route', mng: 20, api: '87+', detail: 'finance.php 내 바로빌 라우트 그룹' }, - { cat: 'Job', mng: 1, api: 1, detail: 'SyncBarobillDataJob (매일 자동 동기화)' }, + { cat: 'Service', mng: '6개', api: '8개', detail: 'SOAP, Sync, Billing, Usage, TaxInvoice 등' }, + { cat: 'Controller', mng: '14개', api: '11개', detail: '카드/은행/홈택스/세금계산서/카카오톡/SMS/과금/사용량' }, + { cat: 'Model', mng: '18개', api: '17개', detail: '거래, 분개, 구독, 과금, 정책 등' }, + { cat: 'Migration', mng: '-', api: '30개', detail: '바로빌 관련 테이블 생성/수정' }, + { cat: 'Route', mng: '20개', api: '87+', detail: 'finance.php 내 바로빌 라우트 그룹' }, + { cat: 'Job', mng: '1개', api: '1개', detail: 'SyncBarobillDataJob (매일 자동 동기화)' }, ]; - // 테이블 헤더 + // 테이블 const mY = 1.2; - slide6.addShape(pres.ShapeType.roundRect, { x: 0.5, y: mY, w: 9, h: 0.4, rectRadius: 0.04, fill: { color: C.accent } }); - slide6.addText('유형', { x: 0.6, y: mY, w: 1.5, h: 0.4, fontSize: 9, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); - slide6.addText('MNG', { x: 2.2, y: mY, w: 1, h: 0.4, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide6.addText('API', { x: 3.3, y: mY, w: 1, h: 0.4, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide6.addText('포함 내용', { x: 4.5, y: mY, w: 4.8, h: 0.4, fontSize: 9, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); + slide6.addShape(pres.ShapeType.roundRect, { x: 0.5, y: mY, w: 9, h: 0.4, rectRadius: 0.04, fill: { color: C.dark } }); + slide6.addText('유형', { x: 0.6, y: mY, w: 1.2, h: 0.4, fontSize: 9, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); + slide6.addText('MNG', { x: 1.9, y: mY, w: 0.9, h: 0.4, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide6.addText('API', { x: 2.9, y: mY, w: 0.9, h: 0.4, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide6.addText('포함 내용', { x: 4.0, y: mY, w: 5.3, h: 0.4, fontSize: 9, bold: true, color: C.white, valign: 'middle', fontFace: 'Arial' }); mappings.forEach((m, i) => { - const ry = mY + 0.44 + i * 0.55; - const bg = i % 2 === 0 ? C.grayLight : C.white; - slide6.addShape(pres.ShapeType.roundRect, { x: 0.5, y: ry, w: 9, h: 0.5, rectRadius: 0.04, fill: { color: bg } }); - + const ry = mY + 0.44 + i * 0.5; + const bg = i % 2 === 0 ? C.white : C.headerBg; + slide6.addShape(pres.ShapeType.rect, { x: 0.5, y: ry, w: 9, h: 0.5, fill: { color: bg } }); // 카테고리 배지 - slide6.addShape(pres.ShapeType.roundRect, { x: 0.65, y: ry + 0.1, w: 1.3, h: 0.3, rectRadius: 0.04, fill: { color: C.accent } }); - slide6.addText(m.cat, { x: 0.65, y: ry + 0.1, w: 1.3, h: 0.3, fontSize: 9, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - - slide6.addText(`${m.mng}`, { x: 2.2, y: ry, w: 1, h: 0.5, fontSize: 12, color: C.gray, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide6.addText(`${m.api}`, { x: 3.3, y: ry, w: 1, h: 0.5, fontSize: 12, bold: true, color: C.blue, align: 'center', valign: 'middle', fontFace: 'Arial' }); - slide6.addText(m.detail, { x: 4.5, y: ry, w: 4.8, h: 0.5, fontSize: 9, color: C.text, valign: 'middle', fontFace: 'Arial' }); + slide6.addShape(pres.ShapeType.roundRect, { x: 0.65, y: ry + 0.1, w: 1, h: 0.3, rectRadius: 0.04, fill: { color: C.primary } }); + slide6.addText(m.cat, { x: 0.65, y: ry + 0.1, w: 1, h: 0.3, fontSize: 8, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide6.addText(m.mng, { x: 1.9, y: ry, w: 0.9, h: 0.5, fontSize: 11, color: C.textMuted, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide6.addText(m.api, { x: 2.9, y: ry, w: 0.9, h: 0.5, fontSize: 11, bold: true, color: C.primary, align: 'center', valign: 'middle', fontFace: 'Arial' }); + slide6.addText(m.detail, { x: 4.0, y: ry, w: 5.3, h: 0.5, fontSize: 9, color: C.text, valign: 'middle', fontFace: 'Arial' }); }); // 이관 원칙 박스 - slide6.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.2, w: 9, h: 0.8, rectRadius: 0.08, fill: { color: 'FFF8E1' }, line: { color: 'FFE082', width: 1 } }); + slide6.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.15, w: 9, h: 0.85, rectRadius: 0.08, fill: { color: C.orangeLight }, line: { color: C.orange, width: 0.5 } }); slide6.addText([ - { text: '이관 원칙: ', options: { fontSize: 10, bold: true, color: C.orange } }, + { text: '이관 원칙\n', options: { fontSize: 10, bold: true, color: C.orange } }, { text: 'MNG 코드를 수정/삭제하지 않고, API에 독립적으로 새로 구현. 같은 DB(samdb)를 공유하되, 각각 독립 SOAP 호출. 멀티테넌트(tenant_id) 격리 필수.', options: { fontSize: 9, color: C.text } }, - ], { x: 0.7, y: 4.25, w: 8.6, h: 0.7, valign: 'middle', fontFace: 'Arial' }); + ], { x: 0.7, y: 4.2, w: 8.6, h: 0.75, valign: 'middle', fontFace: 'Arial' }); addFooter(slide6, 6, totalPages); // ═══════════════════════════════════════════════════════════════ - // 슬라이드 7: 향후 계획 (Next Steps) + // 슬라이드 7: 향후 계획 // ═══════════════════════════════════════════════════════════════ const slide7 = pres.addSlide(); - slide7.background = { fill: C.white }; + slide7.background = { fill: C.bg }; addPageTitle(slide7, '향후 계획', '출시까지의 로드맵'); - // 타임라인 const phases = [ - { title: 'Phase 1', sub: 'SOAP 이관\n(API 개발)', status: '완료', color: C.green, statusBg: C.greenBg, statusColor: C.greenDark }, - { title: 'Phase 2', sub: 'UI 구현\n(React 개발)', status: '다음', color: C.blue, statusBg: 'E3F2FD', statusColor: C.blue }, - { title: 'Phase 3', sub: '베타테스트\n(내부→외부)', status: '예정', color: C.orange, statusBg: C.orangeBg, statusColor: C.orange }, - { title: 'Phase 4', sub: '정식 출시\n(온보딩 가동)', status: '예정', color: C.purple, statusBg: C.purpleBg, statusColor: C.purple }, + { title: 'Phase 1', sub: 'SOAP 이관\n(API 개발)', status: '완료', color: C.green, statusBg: C.greenLight }, + { title: 'Phase 2', sub: 'UI 구현\n(React 개발)', status: '다음', color: C.primary, statusBg: C.primaryLight }, + { title: 'Phase 3', sub: '베타테스트\n(내부→외부)', status: '예정', color: C.orange, statusBg: C.orangeLight }, + { title: 'Phase 4', sub: '정식 출시\n(온보딩 가동)', status: '예정', color: C.purple, statusBg: C.purpleLight }, ]; phases.forEach((p, i) => { const px = 0.5 + i * 2.35; - const py = 1.3; - + const py = 1.2; // 카드 - slide7.addShape(pres.ShapeType.roundRect, { x: px, y: py, w: 2.15, h: 1.6, rectRadius: 0.1, fill: { color: C.white }, line: { color: p.color, width: 1.5 } }); - + slide7.addShape(pres.ShapeType.roundRect, { x: px, y: py, w: 2.15, h: 1.7, rectRadius: 0.1, fill: { color: C.white }, line: { color: p.color, width: 1.5 } }); // 넘버 서클 slide7.addShape(pres.ShapeType.ellipse, { x: px + 0.75, y: py + 0.15, w: 0.6, h: 0.6, fill: { color: p.color } }); slide7.addText(`${i + 1}`, { x: px + 0.75, y: py + 0.15, w: 0.6, h: 0.6, fontSize: 18, bold: true, color: C.white, align: 'center', valign: 'middle', fontFace: 'Arial' }); - - // Phase 제목 slide7.addText(p.title, { x: px, y: py + 0.8, w: 2.15, h: 0.25, fontSize: 11, bold: true, color: p.color, align: 'center', fontFace: 'Arial' }); - slide7.addText(p.sub, { x: px, y: py + 1.0, w: 2.15, h: 0.35, fontSize: 8, color: C.gray, align: 'center', fontFace: 'Arial' }); - + slide7.addText(p.sub, { x: px, y: py + 1.05, w: 2.15, h: 0.35, fontSize: 8, color: C.textMuted, align: 'center', fontFace: 'Arial' }); // 상태 배지 - slide7.addShape(pres.ShapeType.roundRect, { x: px + 0.55, y: py + 1.38, w: 1, h: 0.2, rectRadius: 0.04, fill: { color: p.statusBg } }); - slide7.addText(p.status, { x: px + 0.55, y: py + 1.38, w: 1, h: 0.2, fontSize: 8, bold: true, color: p.statusColor, align: 'center', valign: 'middle', fontFace: 'Arial' }); - - // 연결선 + slide7.addShape(pres.ShapeType.roundRect, { x: px + 0.55, y: py + 1.42, w: 1, h: 0.22, rectRadius: 0.04, fill: { color: p.statusBg } }); + slide7.addText(p.status, { x: px + 0.55, y: py + 1.42, w: 1, h: 0.22, fontSize: 8, bold: true, color: p.color, align: 'center', valign: 'middle', fontFace: 'Arial' }); + // 화살표 if (i < 3) { - slide7.addText('→', { x: px + 2.05, y: py + 0.55, w: 0.4, h: 0.4, fontSize: 16, color: C.grayBorder, align: 'center', fontFace: 'Arial' }); + slide7.addText('▶', { x: px + 2.1, y: py + 0.55, w: 0.3, h: 0.4, fontSize: 12, color: C.divider, align: 'center', fontFace: 'Arial' }); } }); - // 남은 과제 카드 - slide7.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 3.2, w: 4.3, h: 1.7, rectRadius: 0.1, fill: { color: C.white }, line: { color: C.blue, width: 1 } }); - slide7.addText('Phase 2 주요 작업 (React UI)', { x: 0.7, y: 3.3, w: 3.9, h: 0.3, fontSize: 11, bold: true, color: C.blue, fontFace: 'Arial' }); - - const phase2Tasks = [ - '계좌/카드/홈택스 조회 화면', - '세금계산서 발행 화면', - '바로빌 연동 설정 화면 보강', - '카카오톡/SMS 발송 화면', - ]; - phase2Tasks.forEach((task, i) => { - slide7.addShape(pres.ShapeType.ellipse, { x: 0.8, y: 3.7 + i * 0.28, w: 0.12, h: 0.12, fill: { color: C.blue } }); - slide7.addText(task, { x: 1.05, y: 3.65 + i * 0.28, w: 3.5, h: 0.22, fontSize: 9, color: C.text, fontFace: 'Arial' }); + // Phase 2 작업 + slide7.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 3.2, w: 4.3, h: 1.7, rectRadius: 0.1, fill: { color: C.white }, line: { color: C.primary, width: 1 } }); + slide7.addText('Phase 2 주요 작업 (React UI)', { x: 0.7, y: 3.3, w: 3.9, h: 0.3, fontSize: 11, bold: true, color: C.primary, fontFace: 'Arial' }); + ['계좌/카드/홈택스 조회 화면', '세금계산서 발행 화면', '바로빌 연동 설정 화면 보강', '카카오톡/SMS 발송 화면'].forEach((t, i) => { + slide7.addShape(pres.ShapeType.ellipse, { x: 0.85, y: 3.72 + i * 0.28, w: 0.12, h: 0.12, fill: { color: C.primary } }); + slide7.addText(t, { x: 1.1, y: 3.66 + i * 0.28, w: 3.5, h: 0.22, fontSize: 9, color: C.text, fontFace: 'Arial' }); }); - // 확인 필요 사항 카드 + // 확인 사항 slide7.addShape(pres.ShapeType.roundRect, { x: 5.2, y: 3.2, w: 4.3, h: 1.7, rectRadius: 0.1, fill: { color: C.white }, line: { color: C.orange, width: 1 } }); slide7.addText('출시 전 확인 필요 사항', { x: 5.4, y: 3.3, w: 3.9, h: 0.3, fontSize: 11, bold: true, color: C.orange, fontFace: 'Arial' }); - - const confirmTasks = [ - '멀티테넌트 CERTKEY 구조 확인 (바로빌 측)', - '테스트→운영 모드 전환 프로세스', - '공동인증서 등록 URL 제공 플로우', - '충전잔액 모니터링 + 과금 정책 확정', - ]; - confirmTasks.forEach((task, i) => { - slide7.addShape(pres.ShapeType.ellipse, { x: 5.5, y: 3.7 + i * 0.28, w: 0.12, h: 0.12, fill: { color: C.orange } }); - slide7.addText(task, { x: 5.75, y: 3.65 + i * 0.28, w: 3.5, h: 0.22, fontSize: 9, color: C.text, fontFace: 'Arial' }); + ['멀티테넌트 CERTKEY 구조 확인 (바로빌)', '테스트→운영 모드 전환 프로세스', '공동인증서 등록 URL 제공 플로우', '충전잔액 모니터링 + 과금 정책 확정'].forEach((t, i) => { + slide7.addShape(pres.ShapeType.ellipse, { x: 5.55, y: 3.72 + i * 0.28, w: 0.12, h: 0.12, fill: { color: C.orange } }); + slide7.addText(t, { x: 5.8, y: 3.66 + i * 0.28, w: 3.5, h: 0.22, fontSize: 9, color: C.text, fontFace: 'Arial' }); }); addFooter(slide7, 7, totalPages); diff --git a/presentations/barobill-migration-report.pptx b/presentations/barobill-migration-report.pptx index be0bc6b6bdc79fe6c0e29302ce62d261e9cc11a3..fb767bc37bae3f7fd2feb041f8ab9afe440f6625 100644 GIT binary patch delta 24837 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 P!1F3C7L|WP!`uG@0BgC|