feat: React 18 프론트엔드 추가 및 프로젝트 구조 정리
- wtm-frontend → wtm-frontend-vue 이름 변경 - wtm-frontend-react 추가 (React 18 + PrimeReact + Zustand) - 동일한 모듈 구조 및 API 연동 (Vue 버전과 기능 동일) - Vue:5173 / React:5174 포트 분리 - 개발자 가이드에 React 프론트엔드 안내 추가 - .gitignore: Claude/OMC, 문서 생성 스크립트, package-lock 제외 - 불필요 파일 git 추적 제거 (.omc, generate_*.py, regenerate_*.py) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
이 Commit은 다음에 포함되어 있습니다:
@@ -1,838 +0,0 @@
|
||||
"""
|
||||
한화오션 EPU WTM 시스템 설계서 PPT 생성
|
||||
요구사항정의 → To-Be → 시스템구성도 → 기능정의 → 시스템설계
|
||||
"""
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt, Emu
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
import os
|
||||
|
||||
OUTPUT = os.path.join(os.path.dirname(__file__), "한화오션_EPU_WTM_시스템설계서.pptx")
|
||||
|
||||
# 색상
|
||||
PRIMARY = RGBColor(30, 60, 120)
|
||||
ACCENT = RGBColor(0, 120, 200)
|
||||
DARK = RGBColor(40, 40, 40)
|
||||
BODY = RGBColor(80, 80, 80)
|
||||
WHITE = RGBColor(255, 255, 255)
|
||||
LIGHT_BG = RGBColor(240, 245, 255)
|
||||
GREEN_BG = RGBColor(220, 240, 220)
|
||||
ORANGE_BG = RGBColor(255, 240, 220)
|
||||
RED_ACCENT = RGBColor(200, 60, 60)
|
||||
|
||||
|
||||
def add_box(slide, left, top, width, height, text, fill_color=LIGHT_BG, font_color=PRIMARY, font_size=10, bold=True):
|
||||
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
|
||||
shape.fill.solid()
|
||||
shape.fill.fore_color.rgb = fill_color
|
||||
shape.line.color.rgb = PRIMARY
|
||||
shape.line.width = Pt(1)
|
||||
tf = shape.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = text
|
||||
p.font.size = Pt(font_size)
|
||||
p.font.color.rgb = font_color
|
||||
p.font.bold = bold
|
||||
p.alignment = PP_ALIGN.CENTER
|
||||
tf.paragraphs[0].space_before = Pt(2)
|
||||
return shape
|
||||
|
||||
|
||||
def add_textbox(slide, left, top, width, height, text, font_size=10, color=BODY, bold=False, align=PP_ALIGN.LEFT):
|
||||
txBox = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = txBox.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = text
|
||||
p.font.size = Pt(font_size)
|
||||
p.font.color.rgb = color
|
||||
p.font.bold = bold
|
||||
p.alignment = align
|
||||
return txBox
|
||||
|
||||
|
||||
def add_multiline_box(slide, left, top, width, height, title, items, fill_color=LIGHT_BG):
|
||||
shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
|
||||
shape.fill.solid()
|
||||
shape.fill.fore_color.rgb = fill_color
|
||||
shape.line.color.rgb = PRIMARY
|
||||
shape.line.width = Pt(1)
|
||||
tf = shape.text_frame
|
||||
tf.word_wrap = True
|
||||
p = tf.paragraphs[0]
|
||||
p.text = title
|
||||
p.font.size = Pt(9)
|
||||
p.font.color.rgb = PRIMARY
|
||||
p.font.bold = True
|
||||
for item in items:
|
||||
p2 = tf.add_paragraph()
|
||||
p2.text = f"• {item}"
|
||||
p2.font.size = Pt(7)
|
||||
p2.font.color.rgb = BODY
|
||||
p2.space_before = Pt(1)
|
||||
return shape
|
||||
|
||||
|
||||
def add_arrow(slide, start_left, start_top, end_left, end_top):
|
||||
connector = slide.shapes.add_connector(
|
||||
1, start_left, start_top, end_left, end_top) # 1 = straight
|
||||
connector.line.color.rgb = PRIMARY
|
||||
connector.line.width = Pt(1.5)
|
||||
return connector
|
||||
|
||||
|
||||
def add_table(slide, left, top, width, rows_data, col_widths=None):
|
||||
rows = len(rows_data)
|
||||
cols = len(rows_data[0])
|
||||
table_shape = slide.shapes.add_table(rows, cols, left, top, width, Inches(0.3 * rows))
|
||||
table = table_shape.table
|
||||
|
||||
if col_widths:
|
||||
for i, w in enumerate(col_widths):
|
||||
table.columns[i].width = w
|
||||
|
||||
for ri, row in enumerate(rows_data):
|
||||
for ci, val in enumerate(row):
|
||||
cell = table.cell(ri, ci)
|
||||
cell.text = str(val)
|
||||
p = cell.text_frame.paragraphs[0]
|
||||
p.font.size = Pt(8)
|
||||
if ri == 0:
|
||||
p.font.bold = True
|
||||
p.font.color.rgb = WHITE
|
||||
cell.fill.solid()
|
||||
cell.fill.fore_color.rgb = PRIMARY
|
||||
else:
|
||||
p.font.color.rgb = BODY
|
||||
if ri % 2 == 0:
|
||||
cell.fill.solid()
|
||||
cell.fill.fore_color.rgb = LIGHT_BG
|
||||
p.alignment = PP_ALIGN.CENTER if ci > 0 else PP_ALIGN.LEFT
|
||||
|
||||
|
||||
def title_slide(prs, title, subtitle):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
|
||||
# 배경
|
||||
bg = slide.background.fill
|
||||
bg.solid()
|
||||
bg.fore_color.rgb = PRIMARY
|
||||
# 타이틀
|
||||
add_textbox(slide, Inches(1), Inches(2), Inches(8), Inches(1),
|
||||
title, font_size=32, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
|
||||
add_textbox(slide, Inches(1), Inches(3.2), Inches(8), Inches(0.8),
|
||||
subtitle, font_size=16, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
|
||||
add_textbox(slide, Inches(1), Inches(5.5), Inches(8), Inches(0.5),
|
||||
"아큐라시스템 | 2026년 3월", font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)
|
||||
return slide
|
||||
|
||||
|
||||
def section_slide(prs, num, title, subtitle=""):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
bg = slide.background.fill
|
||||
bg.solid()
|
||||
bg.fore_color.rgb = RGBColor(25, 50, 100)
|
||||
# 큰 번호
|
||||
add_textbox(slide, Inches(0.8), Inches(1.5), Inches(2), Inches(1.2),
|
||||
f"{num:02d}", font_size=72, color=ACCENT, bold=True)
|
||||
# 구분선
|
||||
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
|
||||
Inches(0.8), Inches(3.0), Inches(8), Pt(3))
|
||||
shape.fill.solid()
|
||||
shape.fill.fore_color.rgb = ACCENT
|
||||
shape.line.fill.background()
|
||||
# 타이틀
|
||||
add_textbox(slide, Inches(0.8), Inches(3.3), Inches(9), Inches(0.8),
|
||||
title, font_size=28, color=WHITE, bold=True)
|
||||
# 서브타이틀
|
||||
if subtitle:
|
||||
add_textbox(slide, Inches(0.8), Inches(4.3), Inches(8), Inches(0.5),
|
||||
subtitle, font_size=12, color=RGBColor(150, 180, 220))
|
||||
return slide
|
||||
|
||||
|
||||
def content_slide(prs, title):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
# 상단 바
|
||||
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, Inches(10), Inches(0.6))
|
||||
shape.fill.solid()
|
||||
shape.fill.fore_color.rgb = PRIMARY
|
||||
shape.line.fill.background()
|
||||
add_textbox(slide, Inches(0.3), Inches(0.05), Inches(9), Inches(0.5),
|
||||
title, font_size=16, color=WHITE, bold=True)
|
||||
return slide
|
||||
|
||||
|
||||
def build():
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(10)
|
||||
prs.slide_height = Inches(7.5)
|
||||
|
||||
# ============================================================
|
||||
# 표지
|
||||
# ============================================================
|
||||
title_slide(prs,
|
||||
"한화오션 EPU\nWork Time Manager (WTM)",
|
||||
"시스템 설계서 — 요구사항정의 · To-Be · 시스템구성 · 기능정의 · 설계")
|
||||
|
||||
# ============================================================
|
||||
# 목차
|
||||
# ============================================================
|
||||
slide = content_slide(prs, "목차")
|
||||
toc = [
|
||||
"01. 프로젝트 개요",
|
||||
"02. 요구사항 정의서",
|
||||
"03. As-Is / To-Be 분석",
|
||||
"04. 시스템 구성도",
|
||||
"05. 기능 정의서",
|
||||
"06. 데이터베이스 설계",
|
||||
"07. API 설계",
|
||||
"08. 화면 구성",
|
||||
"09. 인터페이스 설계 (SAP/P6/Cognite)",
|
||||
"10. 보안 설계",
|
||||
"11. 단계별 추진 일정",
|
||||
]
|
||||
y = 1.0
|
||||
for item in toc:
|
||||
add_textbox(slide, Inches(1.5), Inches(y), Inches(7), Inches(0.35),
|
||||
item, font_size=14, color=DARK)
|
||||
y += 0.4
|
||||
|
||||
# ============================================================
|
||||
# 01. 프로젝트 개요
|
||||
# ============================================================
|
||||
section_slide(prs, 1, "프로젝트 개요", "WTM 시스템 범위, 기술 스택, 추진 구조")
|
||||
|
||||
slide = content_slide(prs, "01. 프로젝트 개요")
|
||||
overview_data = [
|
||||
["항목", "내용"],
|
||||
["프로젝트명", "WTM (Work Time Manager)"],
|
||||
["고객", "한화오션 EPU (Engineering Procurement Unit)"],
|
||||
["목적", "EPC 프로젝트 인력 시수 관리, WBS 연동, 결재, 리포트"],
|
||||
["기능 요구사항", "86개 (PH1 Y=62 / PH2 이관=24)"],
|
||||
["비기능 요구사항", "17개 (NF.1~17)"],
|
||||
["화면 수", "49개 (PH1-1차 26 / PH1-2차 11 / PH2 12)"],
|
||||
["API 수", "79개 (PH1-1: 67 / PH1-2: 6 / PH2: 6)"],
|
||||
["기술 스택", "Spring Boot 3.5.0 + Java 21 (wbx-spring 프레임워크)"],
|
||||
["DB", "Azure SQL (프로필 전환: Oracle/MSSQL/MySQL/PG)"],
|
||||
["인증", "Azure Entra ID SSO + MFA/TOTP"],
|
||||
]
|
||||
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
|
||||
overview_data, col_widths=[Inches(2.5), Inches(6.5)])
|
||||
|
||||
# ============================================================
|
||||
# 02. 요구사항 정의서
|
||||
# ============================================================
|
||||
section_slide(prs, 2, "요구사항 정의서", "기능 86개 (Y=62/N=24) + 비기능 17개 + 확인필요사항 7건")
|
||||
|
||||
# 기능 요구사항 요약
|
||||
slide = content_slide(prs, "02-1. 기능 요구사항 (86개) — 영역별 분류")
|
||||
req_data = [
|
||||
["영역", "PH1 (Y)", "PH2 이관 (N)", "합계"],
|
||||
["User Registration (No.1~7)", "5", "2", "7"],
|
||||
["Login / 인증 (No.8~16)", "8", "1", "9"],
|
||||
["User Home / 권한 (No.17~24)", "6", "2", "8"],
|
||||
["Project / WBS (No.25~41)", "14", "3", "17"],
|
||||
["Resource Assignment (No.42~54)", "6", "7", "13"],
|
||||
["Time Sheet (No.55~69)", "13", "2", "15"],
|
||||
["Approval (No.70~75)", "6", "0", "6"],
|
||||
["Reporting (No.76~86)", "2", "9", "11"],
|
||||
["합계", "62", "24", "86"],
|
||||
]
|
||||
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
|
||||
req_data, col_widths=[Inches(4), Inches(2), Inches(2), Inches(1)])
|
||||
|
||||
# 비기능 요구사항
|
||||
slide = content_slide(prs, "02-2. 비기능 요구사항 (17개)")
|
||||
nf_data = [
|
||||
["No.", "카테고리", "요구사항", "비고"],
|
||||
["NF.1~2", "클라우드", "Azure Hybrid Security Zone 내 구성", "MS Azure"],
|
||||
["NF.3", "보안(서버)", "HIWARE, V3, Secuver TOS", "한화 표준"],
|
||||
["NF.4", "보안(DB)", "Cubeone(암호화), Dbsafer(접근제어)", "한화 표준"],
|
||||
["NF.5", "보안(Cloud)", "Defender + Analytics", "Native"],
|
||||
["NF.6~7", "정보보호", "보안성 심의, 컴플라이언스", "그룹 표준"],
|
||||
["NF.8~10", "모니터링", "onTune(SMS), Maxguage(DB), Jennifer(WAS)", "한화 표준"],
|
||||
["NF.11", "인증(내부)", "Azure Entra ID SSO", ""],
|
||||
["NF.12", "인증(외부)", "2-Way 인증 (MFA)", "TOTP"],
|
||||
["NF.13", "I/F(HR)", "SAP BTP 경유 SuccessFactors 연동", ""],
|
||||
["NF.14", "I/F(P6)", "파일 기반 (물리적 I/F 없음)", ""],
|
||||
["NF.15", "I/F(Cognite)", "Extractor 서버 구성", "PH2"],
|
||||
["NF.16~17", "아키텍처", "이중화, 백업 전략", ""],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
|
||||
nf_data, col_widths=[Inches(1), Inches(1.5), Inches(4.5), Inches(1.5)])
|
||||
|
||||
# 확인필요사항
|
||||
slide = content_slide(prs, "02-3. 확인필요사항 (7건)")
|
||||
confirm_data = [
|
||||
["#", "영역", "내용", "현재 상태"],
|
||||
["1", "User", "파트너사/외주 인력 관리 정책", "파트너 마스터 미연계"],
|
||||
["2", "권한", "사용자 관리규정 및 권한정책", "No.18 확정 (기능 기반 역할)"],
|
||||
["3", "WBS", "WBS별 Resource Plan 방식", "TT에서 투입시수 세팅"],
|
||||
["4", "배정", "인력 배치 시 스케줄 확인 절차", "Discipline별 인력 선정 후 제출"],
|
||||
["5", "EPC", "C단계 시수 입력 방안", "C단계 대상 제외 (EPU만)"],
|
||||
["6", "결재", "업무별 승인 절차", "User→DL→PM 3단계 확정"],
|
||||
["7", "배정", "Project Assignment 프로세스", "SA 생성→PM 배정→확정"],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
|
||||
confirm_data, col_widths=[Inches(0.5), Inches(1), Inches(3.5), Inches(3.5)])
|
||||
|
||||
# ============================================================
|
||||
# 03. As-Is / To-Be
|
||||
# ============================================================
|
||||
section_slide(prs, 3, "As-Is / To-Be 분석", "Excel 수작업 → WTM 시스템 전환 효과")
|
||||
|
||||
slide = content_slide(prs, "03. As-Is → To-Be 전환")
|
||||
|
||||
# As-Is 박스들
|
||||
add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.4),
|
||||
"As-Is (현재)", font_size=14, color=RED_ACCENT, bold=True)
|
||||
|
||||
as_is = [
|
||||
("Excel 기반 시수 관리", "수작업 입력, 오류 빈번"),
|
||||
("이메일 기반 결재", "추적 불가, 지연"),
|
||||
("P6 WBS 수동 매핑", "버전 관리 어려움"),
|
||||
("리포트 수동 집계", "실시간 분석 불가"),
|
||||
("인력 배정 구두 전달", "이력 관리 없음"),
|
||||
]
|
||||
y = 1.4
|
||||
for title, desc in as_is:
|
||||
add_multiline_box(slide, Inches(0.5), Inches(y), Inches(4), Inches(0.55),
|
||||
title, [desc], fill_color=RGBColor(255, 235, 235))
|
||||
y += 0.6
|
||||
|
||||
# 화살표 영역
|
||||
add_textbox(slide, Inches(4.5), Inches(2.5), Inches(1), Inches(0.5),
|
||||
"→", font_size=36, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
|
||||
# To-Be 박스들
|
||||
add_textbox(slide, Inches(5.5), Inches(0.9), Inches(4), Inches(0.4),
|
||||
"To-Be (WTM 시스템)", font_size=14, color=ACCENT, bold=True)
|
||||
|
||||
to_be = [
|
||||
("통합 시수 입력 (3종)", "Non-Project/Other/EPC 탭 전환, 규칙 엔진"),
|
||||
("온라인 결재 워크플로우", "User→DL→PM 3단계, 일괄 승인, 실시간 알림"),
|
||||
("P6 WBS 자동 파싱", "Canonical WBS 매핑, 버전 관리, 비교 UI"),
|
||||
("실시간 리포트", "프로젝트별/WBS별 시수 분석, Excel Export"),
|
||||
("체계적 인력 배정", "프로젝트-Discipline-인력 매핑, 이력 관리"),
|
||||
]
|
||||
y = 1.4
|
||||
for title, desc in to_be:
|
||||
add_multiline_box(slide, Inches(5.5), Inches(y), Inches(4), Inches(0.55),
|
||||
title, [desc], fill_color=RGBColor(230, 245, 255))
|
||||
y += 0.6
|
||||
|
||||
# 하단 기대효과
|
||||
add_textbox(slide, Inches(0.5), Inches(5.0), Inches(9), Inches(0.3),
|
||||
"기대효과", font_size=12, color=PRIMARY, bold=True)
|
||||
effects = "• 시수 입력 오류 90% 감소 • 결재 처리 시간 70% 단축 • 실시간 프로젝트 시수 현황 파악 • WBS 변경 추적 자동화"
|
||||
add_textbox(slide, Inches(0.5), Inches(5.3), Inches(9), Inches(0.5),
|
||||
effects, font_size=10, color=BODY)
|
||||
|
||||
# ============================================================
|
||||
# 04. 시스템 구성도
|
||||
# ============================================================
|
||||
section_slide(prs, 4, "시스템 구성도", "전체 아키텍처, 기술 스택, 네트워크 구성")
|
||||
|
||||
# 전체 아키텍처
|
||||
slide = content_slide(prs, "04-1. 전체 시스템 아키텍처")
|
||||
|
||||
# 클라이언트 영역
|
||||
add_textbox(slide, Inches(0.3), Inches(0.9), Inches(2), Inches(0.3),
|
||||
"클라이언트", font_size=10, color=BODY, bold=True)
|
||||
clients = ["WBX No-Code", "Web Browser", "Mobile"]
|
||||
cx = 0.3
|
||||
for c in clients:
|
||||
add_box(slide, Inches(cx), Inches(1.2), Inches(1.4), Inches(0.5),
|
||||
c, fill_color=RGBColor(200, 220, 255), font_size=8)
|
||||
cx += 1.6
|
||||
|
||||
# Nginx
|
||||
add_box(slide, Inches(5.5), Inches(1.2), Inches(1.8), Inches(0.5),
|
||||
"Nginx (SSL/LB)", fill_color=RGBColor(220, 220, 220), font_color=DARK, font_size=9)
|
||||
|
||||
# Spring Boot WTM
|
||||
add_multiline_box(slide, Inches(0.3), Inches(2.2), Inches(4.5), Inches(2.8),
|
||||
"Spring Boot WTM-API (wbx-spring 프레임워크)", [
|
||||
"JWT 인증 / Azure Entra ID SSO / MFA",
|
||||
"RBAC 권한 (dept_scope)",
|
||||
"통합 결재 엔진 (Handler Registry)",
|
||||
"SSE 실시간 알림",
|
||||
"시수 입력 3종 + 규칙 엔진",
|
||||
"WBS/TEAL 관리 + P6 파서",
|
||||
"리포트 (QueryDSL)",
|
||||
"SAP BTP HR 연동",
|
||||
], fill_color=RGBColor(230, 240, 255))
|
||||
|
||||
# WBX FastAPI
|
||||
add_multiline_box(slide, Inches(5.2), Inches(2.2), Inches(2.3), Inches(2.0),
|
||||
"WBX FastAPI (선택)", [
|
||||
"문서관리",
|
||||
"이메일",
|
||||
"게시판/일정",
|
||||
"/api/gw/*"
|
||||
], fill_color=RGBColor(245, 240, 255))
|
||||
|
||||
# DB
|
||||
add_multiline_box(slide, Inches(7.8), Inches(2.2), Inches(2), Inches(1.5),
|
||||
"Database", [
|
||||
"wtm_db (Azure SQL)",
|
||||
"wbx_gw (MySQL)",
|
||||
"프로필 전환 지원"
|
||||
], fill_color=RGBColor(255, 245, 230))
|
||||
|
||||
# Redis
|
||||
add_box(slide, Inches(7.8), Inches(4.0), Inches(1), Inches(0.5),
|
||||
"Redis", fill_color=GREEN_BG, font_size=9)
|
||||
|
||||
# Blob Storage
|
||||
add_box(slide, Inches(8.9), Inches(4.0), Inches(1), Inches(0.5),
|
||||
"Storage", fill_color=ORANGE_BG, font_size=9)
|
||||
|
||||
# SSO
|
||||
add_box(slide, Inches(5.2), Inches(4.5), Inches(2.3), Inches(0.5),
|
||||
"Azure Entra ID (SSO+MFA)", fill_color=RGBColor(255, 235, 235), font_size=8)
|
||||
|
||||
# SAP BTP
|
||||
add_box(slide, Inches(0.3), Inches(5.3), Inches(2), Inches(0.5),
|
||||
"SAP BTP (HR)", fill_color=RGBColor(255, 240, 220), font_size=9)
|
||||
|
||||
# P6
|
||||
add_box(slide, Inches(2.5), Inches(5.3), Inches(1.5), Inches(0.5),
|
||||
"P6 (WBS)", fill_color=RGBColor(255, 240, 220), font_size=9)
|
||||
|
||||
# Cognite
|
||||
add_box(slide, Inches(4.2), Inches(5.3), Inches(1.5), Inches(0.5),
|
||||
"Cognite", fill_color=RGBColor(255, 240, 220), font_size=9)
|
||||
|
||||
# 기술 스택 슬라이드
|
||||
slide = content_slide(prs, "04-2. 기술 스택")
|
||||
tech_data = [
|
||||
["레이어", "기술", "버전/비고"],
|
||||
["Language", "Java", "21 (LTS)"],
|
||||
["Framework", "Spring Boot + wbx-spring", "3.5.0"],
|
||||
["Security", "Spring Security 6 + OAuth2", "Entra ID SSO + MFA"],
|
||||
["ORM", "Spring Data JPA + QueryDSL", "Hibernate 6"],
|
||||
["DB", "Azure SQL / Oracle / MySQL / PG", "프로필 전환"],
|
||||
["Migration", "Flyway", "DBMS별 DDL 분리"],
|
||||
["Cache", "Redis", "세션 + 캐시"],
|
||||
["WEB/WAS", "Nginx + Embedded Tomcat", "이중화"],
|
||||
["CI/CD", "GitHub Actions + Azure DevOps", "승인 게이트"],
|
||||
["형상관리", "GitHub + Azure Repos", "SemVer"],
|
||||
["모니터링", "Jennifer + Actuator + Prometheus", "한화 표준"],
|
||||
["Cloud", "Microsoft Azure", "Hybrid Security Zone"],
|
||||
]
|
||||
add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
|
||||
tech_data, col_widths=[Inches(2), Inches(3.5), Inches(3.5)])
|
||||
|
||||
# ============================================================
|
||||
# 05. 기능 정의서
|
||||
# ============================================================
|
||||
section_slide(prs, 5, "기능 정의서", "시수 입력 3종, WBS/TEAL, 결재 워크플로우, 리포트")
|
||||
|
||||
# 시수 입력
|
||||
slide = content_slide(prs, "05-1. 시수 입력 (3종 통합 UI)")
|
||||
|
||||
# 탭 UI 도식
|
||||
tabs = ["Non-Project", "Other Project", "EPC Project"]
|
||||
tx = 0.5
|
||||
for i, tab in enumerate(tabs):
|
||||
color = ACCENT if i == 2 else RGBColor(180, 180, 180)
|
||||
add_box(slide, Inches(tx), Inches(1.0), Inches(2), Inches(0.4),
|
||||
tab, fill_color=RGBColor(230, 240, 255) if i == 2 else RGBColor(245, 245, 245),
|
||||
font_color=color, font_size=9)
|
||||
tx += 2.2
|
||||
|
||||
# EPC 입력 폼 도식
|
||||
fields = [
|
||||
"프로젝트: [EPU-2025-001 ▼]",
|
||||
"WBS: [E.01.03 Piping Detail ▼]",
|
||||
"TEAL: [Detail Engineering ▼]",
|
||||
]
|
||||
fy = 1.6
|
||||
for field in fields:
|
||||
add_textbox(slide, Inches(0.8), Inches(fy), Inches(6), Inches(0.3),
|
||||
field, font_size=10, color=DARK)
|
||||
fy += 0.35
|
||||
|
||||
# 주간 그리드
|
||||
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "합계"]
|
||||
vals = ["8.0", "8.0", "8.0", "8.0", "4.0", "-", "36.0h"]
|
||||
grid_data = [days, vals]
|
||||
add_table(slide, Inches(0.5), Inches(2.9), Inches(6.5),
|
||||
grid_data, col_widths=[Inches(0.9)] * 7)
|
||||
|
||||
# 규칙 엔진 박스
|
||||
add_multiline_box(slide, Inches(7.3), Inches(1.0), Inches(2.5), Inches(2.5),
|
||||
"규칙 엔진", [
|
||||
"일 최소 8시간",
|
||||
"주 최대 52시간",
|
||||
"Activity 최소 1개",
|
||||
"Location별 규칙",
|
||||
"미래 날짜 입력 불가",
|
||||
"초과 시 경고/차단",
|
||||
], fill_color=ORANGE_BG)
|
||||
|
||||
# 하단 설명
|
||||
add_textbox(slide, Inches(0.5), Inches(3.6), Inches(6), Inches(0.3),
|
||||
"[임시 저장] [결재 요청] 주간 합계: 36.0 / 52h",
|
||||
font_size=9, color=BODY)
|
||||
|
||||
ts_desc = [
|
||||
("Non-Project (No.55~59)", "Overhead Type 선택 → TEAL Activity → 시간 입력. WBS/프로젝트 입력 불가"),
|
||||
("Other Project (No.60~62)", "프로젝트 선택 → Canonical WBS L2~4 → Activity → 시간. P6 연동 불필요"),
|
||||
("EPC Project (No.64~69)", "프로젝트 → Canonical WBS L2~5 → TEAL → 시간. Revision 관리(PH1-2)"),
|
||||
]
|
||||
fy = 4.2
|
||||
for title, desc in ts_desc:
|
||||
add_textbox(slide, Inches(0.5), Inches(fy), Inches(9), Inches(0.2),
|
||||
title, font_size=9, color=PRIMARY, bold=True)
|
||||
add_textbox(slide, Inches(0.5), Inches(fy + 0.2), Inches(9), Inches(0.25),
|
||||
desc, font_size=8, color=BODY)
|
||||
fy += 0.5
|
||||
|
||||
# WBS/TEAL
|
||||
slide = content_slide(prs, "05-2. WBS · TEAL 관리")
|
||||
|
||||
# Canonical WBS 구조
|
||||
add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.3),
|
||||
"Canonical WBS 구조 (No.34)", font_size=12, color=PRIMARY, bold=True)
|
||||
wbs_levels = [
|
||||
["Level", "구분", "설명", "예시"],
|
||||
["L1", "Project", "프로젝트", "EPU-2025-001"],
|
||||
["L2", "Phase", "Engineering/Procurement/Construction/...", "Engineering"],
|
||||
["L3", "Asset/Area", "자산 또는 영역", "Hull Structure"],
|
||||
["L4", "Work/Discipline", "작업 또는 Discipline", "Piping"],
|
||||
["L5", "Deliverable", "산출물 (Eng/SCM only)", "Detail Drawing"],
|
||||
]
|
||||
add_table(slide, Inches(0.5), Inches(1.3), Inches(9),
|
||||
wbs_levels, col_widths=[Inches(0.8), Inches(1.5), Inches(4), Inches(2.5)])
|
||||
|
||||
# WBS 흐름
|
||||
add_textbox(slide, Inches(0.5), Inches(3.5), Inches(4), Inches(0.3),
|
||||
"WBS 업로드 흐름", font_size=12, color=PRIMARY, bold=True)
|
||||
flow_steps = ["P6 Export\n(PM 업로드)", "WBS 파싱\n(Level 1~5)", "Canonical\nWBS 매핑", "PCM 승인\n버전 등록", "TEAL\nActivity 연결"]
|
||||
fx = 0.3
|
||||
for i, step in enumerate(flow_steps):
|
||||
add_box(slide, Inches(fx), Inches(3.9), Inches(1.6), Inches(0.8),
|
||||
step, fill_color=RGBColor(230, 240, 255) if i % 2 == 0 else GREEN_BG,
|
||||
font_size=8, font_color=DARK)
|
||||
if i < len(flow_steps) - 1:
|
||||
add_textbox(slide, Inches(fx + 1.6), Inches(4.05), Inches(0.3), Inches(0.3),
|
||||
"→", font_size=16, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
fx += 1.9
|
||||
|
||||
# 결재 워크플로우
|
||||
slide = content_slide(prs, "05-3. 결재 워크플로우")
|
||||
|
||||
# 결재 흐름도
|
||||
steps = [
|
||||
("User\n시수 입력", RGBColor(200, 220, 255)),
|
||||
("제출\n(결재 요청)", RGBColor(255, 240, 200)),
|
||||
("DL\n1차 결재", RGBColor(220, 240, 220)),
|
||||
("PM\n최종 결재", RGBColor(220, 240, 220)),
|
||||
("승인 완료\n(APPROVED)", RGBColor(200, 255, 200)),
|
||||
]
|
||||
sx = 0.3
|
||||
for i, (label, color) in enumerate(steps):
|
||||
add_box(slide, Inches(sx), Inches(1.3), Inches(1.6), Inches(0.9),
|
||||
label, fill_color=color, font_color=DARK, font_size=10)
|
||||
if i < len(steps) - 1:
|
||||
add_textbox(slide, Inches(sx + 1.6), Inches(1.5), Inches(0.3), Inches(0.3),
|
||||
"→", font_size=18, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
sx += 1.9
|
||||
|
||||
# 반려 흐름
|
||||
add_box(slide, Inches(3.5), Inches(2.5), Inches(2.5), Inches(0.5),
|
||||
"반려 → User 수정 후 재제출", fill_color=RGBColor(255, 230, 230),
|
||||
font_color=RED_ACCENT, font_size=9)
|
||||
|
||||
# 결재 기능 표
|
||||
approval_data = [
|
||||
["기능", "설명", "요구사항"],
|
||||
["시수 결재 요청", "User가 주간 시수 작성 후 제출", "No.70"],
|
||||
["승인/반려 + 코멘트", "DL/PM이 승인 또는 반려 (사유 필수)", "No.72"],
|
||||
["일괄 승인", "여러 시수를 한번에 승인", "No.73"],
|
||||
["결재 이력 조회", "과거 결재 내역 전체 조회", "No.74"],
|
||||
["초과 하이라이트", "기준 초과 시 결재자에게 강조 표시", "No.75 (PH1-2)"],
|
||||
["미완료 알림", "미입력/미제출 시수 자동 리마인더", "No.70"],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(3.3), Inches(9.4),
|
||||
approval_data, col_widths=[Inches(2), Inches(5), Inches(1.5)])
|
||||
|
||||
# 리포트
|
||||
slide = content_slide(prs, "05-4. 리포트")
|
||||
report_data = [
|
||||
["리포트", "설명", "Phase", "필터"],
|
||||
["프로젝트별 시수 분석", "프로젝트별 실제 투입 Manhour", "PH1-1", "기간, 프로젝트, Discipline"],
|
||||
["WBS Level별 시수 분석", "Canonical WBS Level별 Manhour", "PH1-1", "기간, 프로젝트, WBS Level"],
|
||||
["Phase별 시수 비율", "Engineering/Procurement 등 비율", "PH1-2", "기간, 프로젝트"],
|
||||
["Non-Project 비율", "Overhead Manhour 투입 비율", "PH1-2", "기간, 부서"],
|
||||
["WBS 버전 이력", "WBS 변경 이력 조회", "PH1-2", "프로젝트, 버전"],
|
||||
["Discipline 생산성", "Discipline별 생산성 분석", "PH2", "기간, Discipline"],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
|
||||
report_data, col_widths=[Inches(2.2), Inches(3.5), Inches(1), Inches(2)])
|
||||
|
||||
# ============================================================
|
||||
# 06. 데이터베이스 설계
|
||||
# ============================================================
|
||||
section_slide(prs, 6, "데이터베이스 설계", "ERD, 주요 테이블 14개, Flyway 마이그레이션")
|
||||
|
||||
slide = content_slide(prs, "06-1. ERD 개요 (wtm_db)")
|
||||
|
||||
# ERD 도식
|
||||
entities = [
|
||||
("users", Inches(0.3), Inches(1.0), ["id, employee_number", "email, full_name", "department, discipline", "is_active, mfa_enabled"]),
|
||||
("projects", Inches(3.3), Inches(1.0), ["id, project_code", "name, project_type", "status, pm_user_id"]),
|
||||
("canonical_wbs", Inches(6.3), Inches(1.0), ["id, project_id", "wbs_code, level", "name, discipline"]),
|
||||
("timesheets", Inches(0.3), Inches(3.2), ["id, user_id", "week_start_date", "status, total_hours"]),
|
||||
("timesheet_entries", Inches(3.3), Inches(3.2), ["id, timesheet_id", "entry_type, entry_date", "hours, epc_project_id", "canonical_wbs_id"]),
|
||||
("tt_approvals", Inches(6.3), Inches(3.2), ["id, timesheet_id", "requester_id, status", "submitted_at"]),
|
||||
("tt_approval_lines", Inches(6.3), Inches(5.0), ["id, approval_id", "approver_id, order", "role_code, status"]),
|
||||
("wbs_versions", Inches(3.3), Inches(5.0), ["id, project_id", "version_number", "effective_date"]),
|
||||
]
|
||||
|
||||
for name, x, y, fields in entities:
|
||||
h = Inches(0.2 + len(fields) * 0.2)
|
||||
add_multiline_box(slide, x, y, Inches(2.7), h, name, fields, fill_color=LIGHT_BG)
|
||||
|
||||
# DB 테이블 목록
|
||||
slide = content_slide(prs, "06-2. 주요 테이블 목록")
|
||||
db_data = [
|
||||
["테이블", "용도", "주요 컬럼"],
|
||||
["users", "사용자 (HR 필드 포함)", "employee_number, business_unit~part (5레벨)"],
|
||||
["projects", "프로젝트 (EPC/Other/Non)", "project_code, type, status, pm_user_id"],
|
||||
["wbs_versions", "P6 WBS 스냅샷", "version_number, effective_date, source_filename"],
|
||||
["wbs_nodes", "WBS 트리 (L1~L5)", "wbs_code, level, parent_id, planned_hours"],
|
||||
["canonical_wbs", "정규 WBS", "wbs_code, mapped_p6_code, discipline"],
|
||||
["teal_entries", "TEAL Activity", "canonical_wbs_id, activity_code, activity_name"],
|
||||
["timesheets", "주간 시수 헤더", "user_id, week_start_date, status, total_hours"],
|
||||
["timesheet_entries", "일별 시수 상세", "entry_type, entry_date, hours, remark"],
|
||||
["tt_approvals", "결재 요청", "timesheet_id, requester_id, status"],
|
||||
["tt_approval_lines", "결재 라인 (DL/PM)", "approver_id, approval_order, role_code"],
|
||||
["work_rules", "근무 규칙", "location_code, min_daily, max_weekly"],
|
||||
["overhead_types", "Overhead 유형", "code, name (SA 관리)"],
|
||||
["project_assignments", "인력 배정", "project_id, user_id, discipline"],
|
||||
]
|
||||
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
|
||||
db_data, col_widths=[Inches(2), Inches(2.5), Inches(4.5)])
|
||||
|
||||
# ============================================================
|
||||
# 07. API 설계
|
||||
# ============================================================
|
||||
section_slide(prs, 7, "API 설계", "REST API 79개 — /api/wtm/ prefix")
|
||||
|
||||
slide = content_slide(prs, "07. REST API 스펙 (79개, /api/wtm/)")
|
||||
api_data = [
|
||||
["모듈", "PH1-1", "PH1-2", "PH2", "합계", "주요 Endpoint"],
|
||||
["Auth", "8", "0", "0", "8", "/auth/login, /auth/sso, /auth/mfa/*"],
|
||||
["Users", "7", "1", "0", "8", "/users, /users/upload/internal"],
|
||||
["Projects", "7", "0", "0", "7", "/projects, /projects/my, /projects/{id}/members"],
|
||||
["WBS", "6", "1", "0", "7", "/projects/{id}/wbs/upload, /canonical-wbs"],
|
||||
["TEAL", "4", "0", "0", "4", "/projects/{id}/teal/active"],
|
||||
["Timesheets", "8", "0", "0", "8", "/timesheets/week, /entries/batch, /upload"],
|
||||
["Approvals", "7", "1", "0", "8", "/approvals/unified/action/{type}/{id}/*"],
|
||||
["Reports", "4", "2", "3", "9", "/reports/project-hours, /wbs-hours"],
|
||||
["Resource", "8", "0", "0", "8", "/projects/{id}/assignments"],
|
||||
["Config", "4", "0", "0", "4", "/overhead-types, /work-rules"],
|
||||
["Integration", "1", "1", "1", "3", "/integration/hr/sync, /cognite/export"],
|
||||
["Notification", "3", "0", "0", "3", "/notifications/stream (SSE)"],
|
||||
["합계", "67", "6", "6", "79", ""],
|
||||
]
|
||||
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
|
||||
api_data, col_widths=[Inches(1.2), Inches(0.7), Inches(0.7), Inches(0.7), Inches(0.7), Inches(5)])
|
||||
|
||||
# ============================================================
|
||||
# 08. 화면 구성
|
||||
# ============================================================
|
||||
section_slide(prs, 8, "화면 구성", "전체 49개 화면 — PH1-1차 26 / PH1-2차 11 / PH2 12")
|
||||
|
||||
slide = content_slide(prs, "08. 전체 화면 목록 (49개)")
|
||||
screen_data = [
|
||||
["그룹", "PH1-1차 (26개)", "PH1-2차 (11개)", "PH2 (12개)"],
|
||||
["로그인/인증", "로그인, SSO, PW찾기, PW만료 (4)", "", ""],
|
||||
["홈/권한", "역할별 홈 5종 (5)", "권한라우팅, SA로그 (2)", ""],
|
||||
["사용자/인력", "내부/외주 업로드, 관리 (3)", "", "외주 개별입력 (1)"],
|
||||
["프로젝트/WBS", "등록, P6, WBS, TEAL 등 (6)", "WBS비교, 시수조회 (3)", ""],
|
||||
["시수 입력", "Non/Other/EPC 입력, Excel (4)", "EPC Revision (1)", "벤치마킹, Rate (2)"],
|
||||
["결재", "결재요청, 목록, 일괄승인 (3)", "초과알림, MFA (2)", ""],
|
||||
["리포트", "프로젝트별, WBS별 (2)", "Phase비율, NP비율 (2)", "RCP 3종, Discipline 등 (5)"],
|
||||
["기타", "", "HR배치 (1)", "외주포털, Favorite 등 (4)"],
|
||||
]
|
||||
add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
|
||||
screen_data, col_widths=[Inches(1.5), Inches(3), Inches(2.5), Inches(2.5)])
|
||||
|
||||
# ============================================================
|
||||
# 09. 인터페이스 설계
|
||||
# ============================================================
|
||||
section_slide(prs, 9, "인터페이스 설계", "SAP BTP (HR), P6 (WBS 파일), Cognite (Export)")
|
||||
|
||||
slide = content_slide(prs, "09. 외부 시스템 인터페이스")
|
||||
|
||||
# SAP BTP
|
||||
add_multiline_box(slide, Inches(0.3), Inches(1.0), Inches(2.5), Inches(2),
|
||||
"SAP SuccessFactors", [
|
||||
"HR Master Data",
|
||||
"Employee Central",
|
||||
"OData API"
|
||||
], fill_color=RGBColor(255, 245, 220))
|
||||
|
||||
add_textbox(slide, Inches(2.8), Inches(1.7), Inches(0.8), Inches(0.3),
|
||||
"→ BTP →", font_size=10, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
|
||||
add_multiline_box(slide, Inches(3.6), Inches(1.0), Inches(2.5), Inches(2),
|
||||
"SAP BTP (CPI)", [
|
||||
"OData → REST 변환",
|
||||
"필드 매핑",
|
||||
"일 1회 배치 (PH1-2)",
|
||||
"실시간 이벤트 (PH2)"
|
||||
], fill_color=RGBColor(240, 235, 255))
|
||||
|
||||
add_textbox(slide, Inches(6.1), Inches(1.7), Inches(0.5), Inches(0.3),
|
||||
"→", font_size=14, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
|
||||
add_multiline_box(slide, Inches(6.6), Inches(1.0), Inches(3), Inches(2),
|
||||
"WTM Spring Boot", [
|
||||
"POST /api/wtm/integration/hr/sync",
|
||||
"HrSyncService",
|
||||
"사용자 자동 동기화",
|
||||
"PH1-1: Excel 수동 업로드"
|
||||
], fill_color=RGBColor(230, 240, 255))
|
||||
|
||||
# P6
|
||||
add_box(slide, Inches(0.3), Inches(3.5), Inches(2.5), Inches(0.7),
|
||||
"P6 (WBS Export)\n파일 기반 (No I/F)", fill_color=RGBColor(255, 245, 220),
|
||||
font_color=DARK, font_size=9)
|
||||
|
||||
add_textbox(slide, Inches(2.8), Inches(3.65), Inches(1), Inches(0.3),
|
||||
"→ 파일 →", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
|
||||
add_box(slide, Inches(3.8), Inches(3.5), Inches(3), Inches(0.7),
|
||||
"WTM: P6 WBS 파서\nPM 업로드 → PCM 승인", fill_color=RGBColor(230, 240, 255),
|
||||
font_color=DARK, font_size=9)
|
||||
|
||||
# Cognite
|
||||
add_box(slide, Inches(0.3), Inches(4.5), Inches(2.5), Inches(0.7),
|
||||
"Cognite (PH2)\nExtractor 서버", fill_color=RGBColor(255, 245, 220),
|
||||
font_color=DARK, font_size=9)
|
||||
|
||||
add_textbox(slide, Inches(2.8), Inches(4.65), Inches(1), Inches(0.3),
|
||||
"← Export", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
|
||||
|
||||
add_box(slide, Inches(3.8), Inches(4.5), Inches(5.5), Inches(0.7),
|
||||
"Export: Employee + Project + WBS + Time Fact + Mapping Version Metadata",
|
||||
fill_color=RGBColor(230, 240, 255), font_color=DARK, font_size=8)
|
||||
|
||||
# ============================================================
|
||||
# 10. 보안 설계
|
||||
# ============================================================
|
||||
section_slide(prs, 10, "보안 설계", "한화오션 표준 보안 SW 12종, Azure Entra ID SSO + MFA")
|
||||
|
||||
slide = content_slide(prs, "10. 한화오션 표준 보안 SW")
|
||||
sec_data = [
|
||||
["분류", "SW", "용도", "구성"],
|
||||
["서버 보안", "HIWARE", "서버 접근 제어", "Agent 설치"],
|
||||
["서버 보안", "V3 (AhnLab)", "서버 백신", "Agent 설치"],
|
||||
["서버 보안", "Secuver TOS", "파일 무결성", "Agent 설치"],
|
||||
["DB 보안", "Cubeone", "DB 암호화 (컬럼)", "연동"],
|
||||
["DB 보안", "Dbsafer", "DB 접근 제어/감사", "Proxy"],
|
||||
["클라우드", "Azure Defender", "위협 탐지", "Native"],
|
||||
["모니터링", "onTune", "SMS (서버)", "Agent"],
|
||||
["모니터링", "Maxguage", "DB 모니터링", "Proxy"],
|
||||
["모니터링", "Jennifer", "WAS APM", "-javaagent"],
|
||||
["인증", "Azure Entra ID", "SSO + Conditional Access", "OAuth2 OIDC"],
|
||||
["인증", "TOTP (MFA)", "외부사용자 2-Way 인증", "Google/MS Auth"],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
|
||||
sec_data, col_widths=[Inches(1.3), Inches(2), Inches(3), Inches(2.5)])
|
||||
|
||||
# ============================================================
|
||||
# 11. 단계별 추진 일정
|
||||
# ============================================================
|
||||
section_slide(prs, 11, "단계별 추진 일정", "PH1-1차(9주) + PH1-2차(4주) = 15 M/M")
|
||||
|
||||
slide = content_slide(prs, "11-1. 전체 일정 (Gantt)")
|
||||
|
||||
# Gantt-like 도식
|
||||
months = ["4월", "5월", "6월", "7월~"]
|
||||
mx = 2.5
|
||||
for m in months:
|
||||
add_box(slide, Inches(mx), Inches(1.0), Inches(1.8), Inches(0.35),
|
||||
m, fill_color=PRIMARY, font_color=WHITE, font_size=10)
|
||||
mx += 1.8
|
||||
|
||||
phases = [
|
||||
("분석·설계", 2.5, 2.5, RGBColor(100, 160, 230)),
|
||||
("BE 개발", 3.0, 5.5, RGBColor(70, 140, 200)),
|
||||
("FE 개발", 3.5, 5.0, RGBColor(90, 170, 220)),
|
||||
("QA·UAT", 6.5, 2.0, RGBColor(230, 160, 60)),
|
||||
("PH1-2차", 6.1, 3.6, RGBColor(140, 200, 140)),
|
||||
]
|
||||
gy = 1.6
|
||||
for name, start, width, color in phases:
|
||||
add_box(slide, Inches(start), Inches(gy), Inches(width), Inches(0.35),
|
||||
name, fill_color=color, font_color=WHITE, font_size=9)
|
||||
gy += 0.45
|
||||
|
||||
# 마일스톤
|
||||
milestones = [
|
||||
("4/10", "설계 확정"),
|
||||
("5/7", "BE API 완료"),
|
||||
("5/20", "FE 완료"),
|
||||
("5/31", "★ PH1-1 오픈"),
|
||||
("6/30", "★ PH1-2 오픈"),
|
||||
]
|
||||
add_textbox(slide, Inches(0.5), Inches(4.0), Inches(9), Inches(0.3),
|
||||
"주요 마일스톤", font_size=12, color=PRIMARY, bold=True)
|
||||
my = 4.4
|
||||
for date, desc in milestones:
|
||||
add_textbox(slide, Inches(0.5), Inches(my), Inches(1.5), Inches(0.25),
|
||||
date, font_size=10, color=ACCENT, bold=True)
|
||||
add_textbox(slide, Inches(2.0), Inches(my), Inches(7), Inches(0.25),
|
||||
desc, font_size=10, color=DARK)
|
||||
my += 0.3
|
||||
|
||||
# 인력 투입
|
||||
slide = content_slide(prs, "11-2. 인력 투입 계획")
|
||||
manpower = [
|
||||
["역할", "PH1-1차 (9주)", "PH1-2차 (4주)", "담당"],
|
||||
["BE 시니어 / 기술리드", "2.0 M/M", "1.0 M/M", "아키텍처, SSO, 결재, 코드리뷰"],
|
||||
["풀스택 엔지니어 ①", "2.0 M/M", "1.0 M/M", "WBS, TEAL, 프로젝트"],
|
||||
["풀스택 엔지니어 ②", "2.0 M/M", "1.0 M/M", "시수 입력 3종, 규칙 엔진"],
|
||||
["풀스택 엔지니어 ③", "2.0 M/M", "1.0 M/M", "리포트, 로그인, 사용자"],
|
||||
["DevOps (파트타임)", "1.0 M/M", "0.5 M/M", "Azure 인프라, CI/CD"],
|
||||
["QA 겸임", "1.0 M/M", "0.5 M/M", "통합테스트, UAT 지원"],
|
||||
["합계", "~10.0 M/M", "~5.0 M/M", "총 PH1: ~15 M/M"],
|
||||
]
|
||||
add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
|
||||
manpower, col_widths=[Inches(2.2), Inches(1.5), Inches(1.5), Inches(4)])
|
||||
|
||||
# ============================================================
|
||||
# 마지막
|
||||
# ============================================================
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
bg = slide.background.fill
|
||||
bg.solid()
|
||||
bg.fore_color.rgb = PRIMARY
|
||||
add_textbox(slide, Inches(1), Inches(2.5), Inches(8), Inches(1),
|
||||
"감사합니다", font_size=36, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
|
||||
add_textbox(slide, Inches(1), Inches(3.8), Inches(8), Inches(0.5),
|
||||
"한화오션 EPU Time Tracking 시스템을\n함께 성공적으로 완수하겠습니다.",
|
||||
font_size=14, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
|
||||
add_textbox(slide, Inches(1), Inches(5), Inches(8), Inches(0.5),
|
||||
"아큐라시스템 | accura@accurasoft.co.kr",
|
||||
font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)
|
||||
|
||||
prs.save(OUTPUT)
|
||||
size_kb = os.path.getsize(OUTPUT) // 1024
|
||||
try:
|
||||
print(f"Generated: {OUTPUT} ({size_kb} KB)")
|
||||
except UnicodeEncodeError:
|
||||
print(f"Generated: PPT ({size_kb} KB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -1,372 +0,0 @@
|
||||
"""
|
||||
WBX Spring Framework PDF 가이드 재생성 스크립트.
|
||||
Docker/Redis → Embedded Redis 변경 사항 반영.
|
||||
|
||||
사용법: python regenerate_pdfs.py
|
||||
"""
|
||||
import os
|
||||
import copy
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
from fpdf import FPDF
|
||||
|
||||
DOCS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
FONT_PATH = r"C:\Windows\Fonts\malgun.ttf"
|
||||
FONT_BOLD_PATH = r"C:\Windows\Fonts\malgunbd.ttf"
|
||||
|
||||
|
||||
class KoreanPDF(FPDF):
|
||||
"""Korean-capable PDF with consistent styling."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_font("malgun", "", FONT_PATH)
|
||||
self.add_font("malgun", "B", FONT_BOLD_PATH)
|
||||
self.set_auto_page_break(auto=True, margin=20)
|
||||
|
||||
def header_line(self, title):
|
||||
self.set_font("malgun", "B", 9)
|
||||
self.set_text_color(100, 100, 100)
|
||||
self.cell(0, 6, title, ln=True)
|
||||
self.set_draw_color(30, 60, 120)
|
||||
self.set_line_width(0.5)
|
||||
self.line(10, self.get_y(), 200, self.get_y())
|
||||
self.ln(4)
|
||||
|
||||
def section_title(self, text):
|
||||
self.set_font("malgun", "B", 14)
|
||||
self.set_text_color(30, 60, 120)
|
||||
self.cell(0, 10, text, ln=True)
|
||||
self.ln(2)
|
||||
|
||||
def sub_title(self, text):
|
||||
self.set_font("malgun", "B", 11)
|
||||
self.set_text_color(40, 40, 40)
|
||||
self.cell(0, 8, text, ln=True)
|
||||
self.ln(1)
|
||||
|
||||
def body(self, text):
|
||||
self.set_font("malgun", "", 10)
|
||||
self.set_text_color(60, 60, 60)
|
||||
self.multi_cell(0, 6, text)
|
||||
self.ln(2)
|
||||
|
||||
def code_block(self, text):
|
||||
self.set_fill_color(245, 245, 245)
|
||||
self.set_font("malgun", "", 9)
|
||||
self.set_text_color(40, 40, 40)
|
||||
x = self.get_x()
|
||||
self.set_x(x + 5)
|
||||
self.multi_cell(180, 5.5, text, fill=True)
|
||||
self.ln(3)
|
||||
|
||||
def bullet(self, text):
|
||||
self.set_font("malgun", "", 10)
|
||||
self.set_text_color(60, 60, 60)
|
||||
x = self.get_x()
|
||||
self.set_x(x + 5)
|
||||
self.cell(5, 6, "\u2022")
|
||||
self.multi_cell(170, 6, text)
|
||||
|
||||
def note_box(self, text):
|
||||
self.set_fill_color(255, 248, 220)
|
||||
self.set_draw_color(200, 180, 100)
|
||||
self.set_font("malgun", "B", 9)
|
||||
self.set_text_color(120, 90, 0)
|
||||
y = self.get_y()
|
||||
self.rect(12, y, 186, 20, style="DF")
|
||||
self.set_xy(15, y + 3)
|
||||
self.multi_cell(180, 5, text)
|
||||
self.set_y(y + 23)
|
||||
|
||||
def page_number(self):
|
||||
self.set_y(-15)
|
||||
self.set_font("malgun", "", 8)
|
||||
self.set_text_color(150, 150, 150)
|
||||
self.cell(0, 10, f"- {self.page_no()} -", align="C")
|
||||
|
||||
|
||||
def replace_pages(src_path, replacements, out_path):
|
||||
"""Replace specific pages in a PDF with new fpdf2-generated pages.
|
||||
|
||||
replacements: dict of {page_index: fpdf_generation_function}
|
||||
"""
|
||||
reader = PdfReader(src_path)
|
||||
writer = PdfWriter()
|
||||
|
||||
# Generate replacement pages
|
||||
replacement_pages = {}
|
||||
for page_idx, gen_func in replacements.items():
|
||||
pdf = gen_func()
|
||||
tmp = os.path.join(DOCS_DIR, f"_tmp_page_{page_idx}.pdf")
|
||||
pdf.output(tmp)
|
||||
tmp_reader = PdfReader(tmp)
|
||||
replacement_pages[page_idx] = tmp_reader.pages[0]
|
||||
|
||||
# Build output
|
||||
for i, page in enumerate(reader.pages):
|
||||
if i in replacement_pages:
|
||||
writer.add_page(replacement_pages[i])
|
||||
else:
|
||||
writer.add_page(page)
|
||||
|
||||
with open(out_path, "wb") as f:
|
||||
writer.write(f)
|
||||
|
||||
# Cleanup temp files
|
||||
for page_idx in replacements:
|
||||
tmp = os.path.join(DOCS_DIR, f"_tmp_page_{page_idx}.pdf")
|
||||
if os.path.exists(tmp):
|
||||
os.remove(tmp)
|
||||
|
||||
print(f" [OK] {os.path.basename(out_path)}")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 1. 설치가이드_OnPremise.pdf — Page 3 (requirements), Page 9 (Redis)
|
||||
# ============================================================
|
||||
|
||||
def onprem_page3():
|
||||
"""Page 3: 사전 요구사항 - Redis를 선택으로 변경."""
|
||||
pdf = KoreanPDF()
|
||||
pdf.add_page()
|
||||
pdf.header_line("WBX Spring Framework | On-Premise 설치 가이드")
|
||||
pdf.section_title("1. 사전 요구사항 \u00b7 서버 사양")
|
||||
|
||||
pdf.sub_title("1-1. 최소 서버 사양")
|
||||
pdf.body("항목 최소 사양 권장 사양\n"
|
||||
"OS RHEL 8+ / Ubuntu 22.04+ RHEL 9 / Rocky Linux 9\n"
|
||||
"CPU 4 vCPU 8 vCPU\n"
|
||||
"RAM 8 GB 16 GB\n"
|
||||
"Disk 50 GB SSD 100 GB SSD (RAID)\n"
|
||||
"Network 1 Gbps 10 Gbps")
|
||||
|
||||
pdf.sub_title("1-2. 필수 소프트웨어")
|
||||
pdf.body("소프트웨어 버전 용도\n"
|
||||
"JDK 21 (Temurin LTS) Spring Boot 런타임\n"
|
||||
"DB Oracle 19c+ 등 애플리케이션 데이터\n"
|
||||
"Nginx 1.24+ 리버스 프록시, SSL")
|
||||
|
||||
pdf.sub_title("1-2b. 선택 소프트웨어")
|
||||
pdf.body("소프트웨어 버전 용도\n"
|
||||
"Redis 7.x 캐시 (미설치 시 Embedded Redis 자동 구동)")
|
||||
pdf.note_box("NOTE: Redis를 별도 설치하지 않아도 WBX Spring에 내장된 Embedded Redis가\n"
|
||||
"자동으로 시작됩니다. 운영 환경에서는 성능을 위해 외부 Redis 설치를 권장합니다.")
|
||||
|
||||
pdf.sub_title("1-3. 네트워크 포트")
|
||||
pdf.body("포트 서비스 접근 범위 비고\n"
|
||||
"443 HTTPS 외부 Nginx SSL\n"
|
||||
"80 HTTP 외부->443 리다이렉트\n"
|
||||
"8080 Spring Boot 내부 Nginx에서만 접근\n"
|
||||
"8081 wtm-api 내부 WTM 프로젝트\n"
|
||||
"3306 MySQL 내부 DB 선택에 따라\n"
|
||||
"5432 PostgreSQL 내부 \n"
|
||||
"1521 Oracle 내부 \n"
|
||||
"1433 MSSQL 내부 \n"
|
||||
"6379 Redis 내부 선택 (Embedded Redis 자동 대체)")
|
||||
|
||||
pdf.page_number()
|
||||
return pdf
|
||||
|
||||
|
||||
def onprem_page9():
|
||||
"""Page 9: Redis 설치 섹션 재작성."""
|
||||
pdf = KoreanPDF()
|
||||
pdf.add_page()
|
||||
pdf.header_line("WBX Spring Framework | On-Premise 설치 가이드")
|
||||
pdf.section_title("5. Redis 설치 (선택)")
|
||||
|
||||
pdf.note_box("WBX Spring Framework에는 Embedded Redis가 내장되어 있어 별도 설치 없이도\n"
|
||||
"앱이 정상 구동됩니다. 운영 환경에서 높은 성능이 필요한 경우에만 설치하세요.")
|
||||
|
||||
pdf.sub_title("5-1. Embedded Redis (기본 — 설치 불필요)")
|
||||
pdf.body("앱 시작 시 다음 순서로 자동 동작합니다:")
|
||||
pdf.bullet("외부 Redis 감지 시 -> 외부 Redis 사용 (운영 환경)")
|
||||
pdf.bullet("외부 Redis 없음 -> Embedded Redis 자동 시작 (개발/소규모 운영)")
|
||||
pdf.bullet("Embedded Redis 실패 -> 인메모리 캐시 전환 (최후 안전장치)")
|
||||
pdf.ln(3)
|
||||
|
||||
pdf.sub_title("5-2. 외부 Redis 설치 (선택 — 대규모 운영 환경 권장)")
|
||||
pdf.code_block(
|
||||
"sudo dnf install -y redis\n"
|
||||
"sudo systemctl enable --now redis\n\n"
|
||||
"# 보안: bind + password\n"
|
||||
"sudo sed -i 's/^bind .*/bind 127.0.0.1/' /etc/redis/redis.conf\n"
|
||||
"echo 'requirepass RedisP@ss123' | sudo tee -a /etc/redis/redis.conf\n"
|
||||
"sudo systemctl restart redis\n\n"
|
||||
"# 확인\n"
|
||||
"redis-cli -a RedisP@ss123 ping # PONG"
|
||||
)
|
||||
|
||||
pdf.sub_title("5-3. .env 설정 (외부 Redis 사용 시)")
|
||||
pdf.code_block(
|
||||
"SPRING_DATA_REDIS_HOST=127.0.0.1\n"
|
||||
"# 또는 원격 Redis 서버 주소\n"
|
||||
"# SPRING_DATA_REDIS_HOST=redis.company.com"
|
||||
)
|
||||
|
||||
pdf.page_number()
|
||||
return pdf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. 개발자가이드.pdf — Page 3 (사전 준비), Page 4 (Docker Compose)
|
||||
# ============================================================
|
||||
|
||||
def dev_page3():
|
||||
"""Page 3: 개발환경 설정 - Docker/Redis 부분 수정."""
|
||||
pdf = KoreanPDF()
|
||||
pdf.add_page()
|
||||
pdf.header_line("WBX Spring Framework | 개발자 가이드")
|
||||
pdf.section_title("Chapter 0. 개발환경 설정 - IDE \u00b7 프로젝트 생성")
|
||||
|
||||
pdf.sub_title("0-1. 사전 준비")
|
||||
pdf.bullet("JDK 21 설치 (Eclipse Temurin 권장)")
|
||||
pdf.bullet("Git 설치 (git config user.name / user.email)")
|
||||
pdf.bullet("Docker Desktop 설치 (선택 - 로컬 DB 컨테이너 사용 시에만)")
|
||||
pdf.bullet("IDE 설치 (아래 택 1)")
|
||||
pdf.ln(2)
|
||||
|
||||
pdf.note_box("NOTE: Redis는 Embedded Redis가 앱과 함께 자동 시작되므로 별도 설치가 필요\n"
|
||||
"없습니다. Docker Desktop도 DB를 직접 설치하면 불필요합니다.")
|
||||
|
||||
pdf.sub_title("온보딩 플로우")
|
||||
pdf.body("1) JDK 설치 -> 2) IDE 설정 -> 3) DB 준비 -> 4) 프로젝트 생성 -> 5) bootRun -> 6) Swagger 확인")
|
||||
|
||||
pdf.sub_title("0-2. IDE 선택 가이드")
|
||||
pdf.body("IDE 권장 대상 핵심 장점\n"
|
||||
"IntelliJ IDEA 메인 개발 Spring Boot 최적, 리팩터링, DB 브라우저\n"
|
||||
"VS Code 경량/FE 병행 Extension Pack, DevContainer\n"
|
||||
"Eclipse/STS 무료/레거시 Spring Tool Suite 플러그인")
|
||||
|
||||
pdf.sub_title("0-3. IntelliJ IDEA 필수 설정")
|
||||
pdf.bullet("플러그인: Spring Boot, Lombok, JPA Buddy, GitToolBox, .env, SonarLint")
|
||||
pdf.bullet("Annotation Processor 활성화 (Settings > Build > Compiler)")
|
||||
pdf.bullet("Hot Reload: Build project automatically + Allow auto-make")
|
||||
pdf.ln(1)
|
||||
pdf.code_block(
|
||||
"Run Configuration:\n"
|
||||
" Main class: kr.co.accura.wbx.spring.WbxSpringCoreApplication\n"
|
||||
" Env: JWT_SECRET=dev-secret-key"
|
||||
)
|
||||
|
||||
pdf.sub_title("0-4. VS Code 필수 설정")
|
||||
pdf.bullet("Extension: Java Pack, Spring Boot Pack, Lombok, Gradle, REST Client, GitLens")
|
||||
pdf.bullet(".vscode/settings.json - Lombok, Gradle Wrapper, 포맷터 설정")
|
||||
pdf.bullet(".vscode/launch.json - Spring Boot 디버깅 프로필 (local, test)")
|
||||
|
||||
pdf.page_number()
|
||||
return pdf
|
||||
|
||||
|
||||
def dev_page4():
|
||||
"""Page 4: Docker Compose 및 프로젝트 생성 - Redis 부분 수정."""
|
||||
pdf = KoreanPDF()
|
||||
pdf.add_page()
|
||||
pdf.header_line("WBX Spring Framework | 개발자 가이드")
|
||||
|
||||
pdf.code_block(
|
||||
'// .vscode/launch.json\n'
|
||||
'{\n'
|
||||
' "type": "java",\n'
|
||||
' "name": "App (Local)",\n'
|
||||
' "mainClass": "kr.co.accura.wbx.spring.WbxSpringCoreApplication",\n'
|
||||
' "env": {"JWT_SECRET": "dev-secret-key"}\n'
|
||||
'}'
|
||||
)
|
||||
|
||||
pdf.sub_title("0-5. Eclipse / STS 설정")
|
||||
pdf.bullet("Spring Tools 4 플러그인 설치 (Eclipse Marketplace)")
|
||||
pdf.bullet("Lombok 설치: java -jar lombok.jar -> Eclipse 경로 선택")
|
||||
pdf.bullet("Gradle Import: File > Import > Existing Gradle Project")
|
||||
pdf.bullet("Annotation Processor: Project Properties > Java Compiler 에서 활성화")
|
||||
pdf.ln(2)
|
||||
|
||||
pdf.sub_title("0-6. 프로젝트 생성 (3가지 방법)")
|
||||
pdf.body("방법 A: Spring Initializr (start.spring.io)")
|
||||
pdf.code_block("Project: Gradle-Kotlin | Java: 21 | Boot: 3.5.0\n"
|
||||
"Group: kr.co.accura | Artifact: {앱}-api\n"
|
||||
"-> Generate -> 압축 해제 -> wbx-spring-starter 의존성 추가")
|
||||
pdf.body("방법 B: Git Template Repository")
|
||||
pdf.code_block("git clone https://git.wbx.kr/accura/wbx-spring-template.git my-app\n"
|
||||
"cd my-app && ./init.sh --name my-app --group kr.co.accura")
|
||||
|
||||
pdf.sub_title("0-7. 설치 스크립트 (권장)")
|
||||
pdf.body("프로젝트에 포함된 설치 스크립트가 JDK 자동 설치, Git 사전 검사, 빌드, .env 템플릿 생성을 자동 처리합니다.")
|
||||
pdf.code_block("# Windows\nscripts\\install.bat\n\n# Linux/macOS\nchmod +x scripts/install.sh && ./scripts/install.sh")
|
||||
pdf.body("TIP: JDK 미설치 시 스크립트가 자동으로 설치합니다.")
|
||||
|
||||
pdf.sub_title("0-8. 로컬 개발 인프라")
|
||||
pdf.body("Redis는 Embedded Redis가 앱 시작 시 자동 구동되므로 별도 설치가 필요 없습니다.\n"
|
||||
"DB만 Docker Compose 또는 직접 설치하면 됩니다.")
|
||||
pdf.code_block("# Docker로 DB만 시작 (Redis 불필요)\n"
|
||||
"docker compose -f docker-compose-dev.yml up -d mysql\n\n"
|
||||
"# 또는 DB를 직접 설치한 경우 바로 앱 실행\n"
|
||||
"gradlew.bat bootRun")
|
||||
pdf.note_box("Embedded Redis 동작: 앱 시작 시 외부 Redis 감지 -> 없으면 자동 시작 -> 실패 시 인메모리 캐시")
|
||||
|
||||
pdf.page_number()
|
||||
return pdf
|
||||
|
||||
|
||||
def dev_page5():
|
||||
"""Page 5: DevContainer + 코드 품질 도구 - Redis 부분 수정."""
|
||||
pdf = KoreanPDF()
|
||||
pdf.add_page()
|
||||
pdf.header_line("WBX Spring Framework | 개발자 가이드")
|
||||
|
||||
pdf.sub_title("0-9. DevContainer (VS Code)")
|
||||
pdf.body("devcontainer.json 으로 JDK 21 + DB가 포함된 완전한 개발 환경을 컨테이너로 제공합니다.\n"
|
||||
"Redis는 Embedded Redis가 자동 구동되므로 DevContainer에 별도 포함하지 않아도 됩니다.\n"
|
||||
"팀원 전체 동일 환경 보장.")
|
||||
|
||||
pdf.sub_title("0-10. 코드 품질 도구")
|
||||
pdf.bullet("Spotless - 코드 포맷 자동화 (./gradlew spotlessApply)")
|
||||
pdf.bullet("CheckStyle - 코드 규칙 검사")
|
||||
pdf.bullet("JaCoCo - 테스트 커버리지 (최소 70%)")
|
||||
pdf.bullet("SonarLint - IDE 실시간 코드 분석")
|
||||
pdf.bullet(".editorconfig - IDE 공통 코드 스타일 (UTF-8, LF, 4 spaces)")
|
||||
|
||||
pdf.page_number()
|
||||
return pdf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
print("WBX Spring Framework PDF 가이드 재생성")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 설치가이드_OnPremise.pdf
|
||||
print("\n[1] 설치가이드_OnPremise.pdf")
|
||||
src = os.path.join(DOCS_DIR, "WBX_Spring_Framework_설치가이드_OnPremise.pdf")
|
||||
out = os.path.join(DOCS_DIR, "WBX_Spring_Framework_설치가이드_OnPremise.pdf")
|
||||
replace_pages(src, {
|
||||
2: onprem_page3, # Page 3: 사전 요구사항 (Redis 선택으로)
|
||||
8: onprem_page9, # Page 9: Redis 설치 (Embedded Redis 안내)
|
||||
}, out)
|
||||
|
||||
# 2. 설치가이드_Cloud.pdf — 변경 없음 (클라우드 관리형 Redis 사용)
|
||||
print("\n[2] 설치가이드_Cloud.pdf")
|
||||
print(" [SKIP] 클라우드 환경은 관리형 Redis(Azure Cache/ElastiCache) 사용 - 변경 불필요")
|
||||
|
||||
# 3. 개발자가이드.pdf
|
||||
print("\n[3] 개발자가이드.pdf")
|
||||
src = os.path.join(DOCS_DIR, "WBX_Spring_Framework_개발자가이드.pdf")
|
||||
out = os.path.join(DOCS_DIR, "WBX_Spring_Framework_개발자가이드.pdf")
|
||||
replace_pages(src, {
|
||||
2: dev_page3, # Page 3: 사전 준비 (Docker/Redis 선택)
|
||||
3: dev_page4, # Page 4: Docker Compose (Redis 불필요)
|
||||
4: dev_page5, # Page 5: DevContainer (Redis 제외)
|
||||
}, out)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("완료! 변경된 PDF:")
|
||||
print(" - WBX_Spring_Framework_설치가이드_OnPremise.pdf (Page 3, 9)")
|
||||
print(" - WBX_Spring_Framework_개발자가이드.pdf (Page 3, 4, 5)")
|
||||
print(" - WBX_Spring_Framework_설치가이드_Cloud.pdf (변경 없음)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -120,7 +120,46 @@
|
||||
GRANT ALL ON mos.* TO 'jsh'@'%';
|
||||
|
||||
|
||||
[2-3] Redis (설치 불필요)
|
||||
[2-3] Node.js (프론트엔드 개발 시 필요)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
용도: WTM 프론트엔드 개발 서버 실행 (Vue 3 또는 React 18)
|
||||
|
||||
* Windows
|
||||
winget install --id OpenJS.NodeJS.LTS
|
||||
|
||||
* macOS
|
||||
brew install node@22
|
||||
|
||||
* Linux
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
|
||||
sudo apt install -y nodejs
|
||||
|
||||
설치 확인:
|
||||
node -v → v22.x.x
|
||||
npm -v → 10.x.x
|
||||
|
||||
프론트엔드 선택:
|
||||
+----------------------+------------------+----------+---------------------+
|
||||
| 프로젝트 | 프레임워크 | 포트 | UI 라이브러리 |
|
||||
+----------------------+------------------+----------+---------------------+
|
||||
| wtm-frontend-vue | Vue 3 + Vite | 5173 | PrimeVue 4 |
|
||||
| wtm-frontend-react | React 18 + Vite | 5174 | PrimeReact 10 |
|
||||
+----------------------+------------------+----------+---------------------+
|
||||
|
||||
두 프론트엔드는 동일한 백엔드 API(wtm-api :8081)에 연결됩니다.
|
||||
프로젝트 상황에 맞는 프레임워크를 선택하세요.
|
||||
|
||||
빠른 시작:
|
||||
# Vue 3 버전
|
||||
cd wtm-frontend-vue
|
||||
npm install && npm run dev → http://localhost:5173
|
||||
|
||||
# React 18 버전
|
||||
cd wtm-frontend-react
|
||||
npm install && npm run dev → http://localhost:5174
|
||||
|
||||
|
||||
[2-4] Redis (설치 불필요)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
용도: 캐시, 세션 저장소
|
||||
|
||||
@@ -178,6 +217,17 @@
|
||||
http://localhost:8081/swagger-ui → WTM API 문서
|
||||
http://localhost:8081/admin/login → WTM 관리 콘솔
|
||||
|
||||
[WTM 프론트엔드 실행 — Vue 3 또는 React 18 중 택 1]
|
||||
# Vue 3 버전
|
||||
cd wtm-frontend-vue
|
||||
npm install && npm run dev
|
||||
http://localhost:5173 → WTM 프론트엔드 (Vue 3)
|
||||
|
||||
# React 18 버전
|
||||
cd wtm-frontend-react
|
||||
npm install && npm run dev
|
||||
http://localhost:5174 → WTM 프론트엔드 (React 18)
|
||||
|
||||
|
||||
[방법 B] DB 직접 설치 (Docker 없이)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -228,7 +278,7 @@
|
||||
[환경별 수정]
|
||||
SPRING_PROFILES_ACTIVE 프로필 조합 (아래 참고)
|
||||
DB_HOST / DB_PORT DB 서버 주소
|
||||
CORS_ORIGINS 프론트엔드 URL
|
||||
CORS_ORIGINS 프론트엔드 URL (Vue: 5173, React: 5174)
|
||||
|
||||
[프로필 조합 예시]
|
||||
로컬 개발 (MySQL) : local,mysql
|
||||
@@ -250,7 +300,8 @@
|
||||
| 3306 | MySQL | 기본 DB (mysql 프로필) |
|
||||
| 5432 | PostgreSQL | 대안 DB (postgresql 프로필) |
|
||||
| 6379 | Redis | Embedded Redis 자동 구동 (별도 설치 불필요)|
|
||||
| 5173 | wtm-frontend | Vue 3 개발 서버 |
|
||||
| 5173 | wtm-frontend-vue | Vue 3 개발 서버 |
|
||||
| 5174 | wtm-frontend-react| React 18 개발 서버 |
|
||||
| 8001 | WBX FastAPI | 선택, 그룹웨어 동시 운영 시 |
|
||||
+--------+------------------+------------------------------------------+
|
||||
|
||||
@@ -285,6 +336,19 @@
|
||||
A: netstat -ano | findstr :8080 으로 프로세스 확인 후 종료
|
||||
또는 application.yml에서 server.port 변경
|
||||
|
||||
Q: 프론트엔드(Vue/React) npm install 실패
|
||||
A: Node.js 22 LTS가 설치되어 있는지 확인: node -v
|
||||
node_modules 삭제 후 재설치: rm -rf node_modules && npm install
|
||||
|
||||
Q: 프론트엔드에서 API 호출 시 CORS 에러
|
||||
A: wtm-api의 application.yml에서 CORS 설정 확인:
|
||||
cors.allowed-origins에 http://localhost:5173, http://localhost:5174 포함 필요
|
||||
또는 .env에서 CORS_ORIGINS 설정
|
||||
|
||||
Q: Vue(5173)와 React(5174) 동시 실행 가능한가?
|
||||
A: 네, 포트가 다르므로 동시 실행 가능합니다. 둘 다 동일한 wtm-api(:8081)에
|
||||
연결되며 API 프록시가 각각 설정되어 있습니다.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
8. 참고 문서
|
||||
|
||||
새 Issue에서 참조
사용자 차단