install 스크립트 개선: JDK 21 자동 설치, 경로 문제 수정, 문서 업데이트
- install.bat/install.sh: JDK 미설치 시 자동 설치 (winget/brew/apt/yum) - install.bat: ERRORLEVEL 지연 확장 통일, gradlew 경로 수정, CRLF/이스케이프 수정 - install.sh: set -euo pipefail 제거, PROJECT_ROOT 기준 경로로 변경 - Lombok @Builder.Default 경고 3건 수정 (WbxUserRole, WbxAuditLog, RolePermission) - 개발자가이드/설치가이드 PDF: JDK 자동 설치 기능 반영 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
이 Commit은 다음에 포함되어 있습니다:
바이너리 파일은 표시되지 않습니다.
바이너리 파일은 표시되지 않습니다.
838
docs/generate_system_design.py
일반 파일
838
docs/generate_system_design.py
일반 파일
@@ -0,0 +1,838 @@
|
|||||||
|
"""
|
||||||
|
한화오션 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()
|
||||||
@@ -7,6 +7,13 @@ setlocal EnableDelayedExpansion
|
|||||||
:: 사용법: scripts\install.bat
|
:: 사용법: scripts\install.bat
|
||||||
:: ============================================================
|
:: ============================================================
|
||||||
|
|
||||||
|
:: ---------- 프로젝트 루트 설정 ----------
|
||||||
|
set "PROJECT_ROOT=%~dp0.."
|
||||||
|
pushd "!PROJECT_ROOT!" || (
|
||||||
|
echo [FAIL] 프로젝트 루트를 찾을 수 없습니다: %~dp0..
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo WBX Spring Core — 설치 점검
|
echo WBX Spring Core — 설치 점검
|
||||||
@@ -17,27 +24,55 @@ set ERRORS=0
|
|||||||
|
|
||||||
:: ---------- 1. JDK 21 ----------
|
:: ---------- 1. JDK 21 ----------
|
||||||
echo 1. JDK 확인
|
echo 1. JDK 확인
|
||||||
|
set "JDK_OK=0"
|
||||||
where java >nul 2>&1
|
where java >nul 2>&1
|
||||||
if %ERRORLEVEL% equ 0 (
|
if !ERRORLEVEL! equ 0 (
|
||||||
for /f "tokens=3" %%v in ('java -version 2^>^&1 ^| findstr /i "version"') do (
|
for /f "tokens=3" %%v in ('java -version 2^>^&1 ^| findstr /i "version"') do (
|
||||||
set "JAVA_FULL=%%~v"
|
set "JAVA_FULL=%%~v"
|
||||||
)
|
)
|
||||||
for /f "tokens=1 delims=." %%m in ("!JAVA_FULL!") do set "JAVA_MAJOR=%%m"
|
for /f "tokens=1 delims=." %%m in ("!JAVA_FULL!") do set "JAVA_MAJOR=%%m"
|
||||||
if !JAVA_MAJOR! GEQ 21 (
|
if !JAVA_MAJOR! GEQ 21 (
|
||||||
echo [OK] JDK !JAVA_FULL!
|
echo [OK] JDK !JAVA_FULL!
|
||||||
|
set "JDK_OK=1"
|
||||||
) else (
|
) else (
|
||||||
echo [FAIL] JDK !JAVA_FULL! — 21 이상 필요
|
echo [INFO] JDK !JAVA_FULL! — 21 이상 필요, 자동 설치 시도...
|
||||||
set /a ERRORS+=1
|
|
||||||
)
|
)
|
||||||
) else (
|
) else (
|
||||||
echo [FAIL] java 명령어 없음 — JDK 21 설치 필요
|
echo [INFO] java 명령어 없음 — 자동 설치 시도...
|
||||||
set /a ERRORS+=1
|
)
|
||||||
|
|
||||||
|
if !JDK_OK! equ 0 (
|
||||||
|
where winget >nul 2>&1
|
||||||
|
if !ERRORLEVEL! equ 0 (
|
||||||
|
echo [INFO] winget으로 Eclipse Temurin JDK 21 설치 중...
|
||||||
|
winget install --id EclipseAdoptium.Temurin.21.JDK --accept-source-agreements --accept-package-agreements --silent
|
||||||
|
if !ERRORLEVEL! equ 0 (
|
||||||
|
:: 설치된 JDK를 PATH에 추가
|
||||||
|
for /d %%j in ("C:\Program Files\Eclipse Adoptium\jdk-21*") do (
|
||||||
|
set "JAVA_HOME=%%j"
|
||||||
|
)
|
||||||
|
if defined JAVA_HOME (
|
||||||
|
set "PATH=!JAVA_HOME!\bin;!PATH!"
|
||||||
|
echo [OK] JDK 21 설치 완료 — !JAVA_HOME!
|
||||||
|
echo [INFO] 시스템 PATH 반영을 위해 설치 후 새 터미널을 여세요.
|
||||||
|
) else (
|
||||||
|
echo [FAIL] JDK 설치 경로를 찾을 수 없습니다
|
||||||
|
set /a ERRORS+=1
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo [FAIL] JDK 설치 실패 — 수동으로 JDK 21을 설치하세요
|
||||||
|
set /a ERRORS+=1
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo [FAIL] winget 없음 — https://adoptium.net 에서 JDK 21을 수동 설치하세요
|
||||||
|
set /a ERRORS+=1
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
:: ---------- 2. Git ----------
|
:: ---------- 2. Git ----------
|
||||||
echo 2. Git 확인
|
echo 2. Git 확인
|
||||||
where git >nul 2>&1
|
where git >nul 2>&1
|
||||||
if %ERRORLEVEL% equ 0 (
|
if !ERRORLEVEL! equ 0 (
|
||||||
for /f "delims=" %%g in ('git --version') do echo [OK] %%g
|
for /f "delims=" %%g in ('git --version') do echo [OK] %%g
|
||||||
) else (
|
) else (
|
||||||
echo [FAIL] git 없음
|
echo [FAIL] git 없음
|
||||||
@@ -47,7 +82,7 @@ if %ERRORLEVEL% equ 0 (
|
|||||||
:: ---------- 3. Docker (선택) ----------
|
:: ---------- 3. Docker (선택) ----------
|
||||||
echo 3. Docker 확인 (선택)
|
echo 3. Docker 확인 (선택)
|
||||||
where docker >nul 2>&1
|
where docker >nul 2>&1
|
||||||
if %ERRORLEVEL% equ 0 (
|
if !ERRORLEVEL! equ 0 (
|
||||||
for /f "delims=" %%d in ('docker --version') do echo [OK] %%d
|
for /f "delims=" %%d in ('docker --version') do echo [OK] %%d
|
||||||
) else (
|
) else (
|
||||||
echo [WARN] Docker 미설치 — DB/Redis를 직접 설치해야 합니다
|
echo [WARN] Docker 미설치 — DB/Redis를 직접 설치해야 합니다
|
||||||
@@ -58,7 +93,7 @@ echo 4. Gradle 빌드
|
|||||||
if !ERRORS! GTR 0 (
|
if !ERRORS! GTR 0 (
|
||||||
echo [FAIL] 사전 요구사항 미충족 — 빌드 건너뜀
|
echo [FAIL] 사전 요구사항 미충족 — 빌드 건너뜀
|
||||||
) else (
|
) else (
|
||||||
call gradlew.bat build -x test --console=plain -q
|
call "!PROJECT_ROOT!\gradlew.bat" build -x test --console=plain -q
|
||||||
if !ERRORLEVEL! equ 0 (
|
if !ERRORLEVEL! equ 0 (
|
||||||
echo [OK] BUILD SUCCESSFUL
|
echo [OK] BUILD SUCCESSFUL
|
||||||
) else (
|
) else (
|
||||||
@@ -69,7 +104,7 @@ if !ERRORS! GTR 0 (
|
|||||||
|
|
||||||
:: ---------- 5. .env 템플릿 ----------
|
:: ---------- 5. .env 템플릿 ----------
|
||||||
echo 5. 환경변수 파일
|
echo 5. 환경변수 파일
|
||||||
if not exist .env (
|
if not exist "!PROJECT_ROOT!\.env" (
|
||||||
(
|
(
|
||||||
echo # ===== WBX Spring Core — 환경변수 =====
|
echo # ===== WBX Spring Core — 환경변수 =====
|
||||||
echo # 이 파일을 환경에 맞게 수정하세요.
|
echo # 이 파일을 환경에 맞게 수정하세요.
|
||||||
@@ -80,7 +115,7 @@ if not exist .env (
|
|||||||
echo # --- 서버 ---
|
echo # --- 서버 ---
|
||||||
echo SERVER_CONTEXT_PATH=/
|
echo SERVER_CONTEXT_PATH=/
|
||||||
echo.
|
echo.
|
||||||
echo # --- JWT ^(필수 변경!^) ---
|
echo # --- JWT ^(필수 변경^^^!^) ---
|
||||||
echo JWT_SECRET=your-production-secret-key-minimum-256-bits-long
|
echo JWT_SECRET=your-production-secret-key-minimum-256-bits-long
|
||||||
echo.
|
echo.
|
||||||
echo # --- DB ---
|
echo # --- DB ---
|
||||||
@@ -119,7 +154,7 @@ if not exist .env (
|
|||||||
echo # AWS_S3_BUCKET=
|
echo # AWS_S3_BUCKET=
|
||||||
echo # AWS_ACCESS_KEY=
|
echo # AWS_ACCESS_KEY=
|
||||||
echo # AWS_SECRET_KEY=
|
echo # AWS_SECRET_KEY=
|
||||||
) > .env
|
) > "!PROJECT_ROOT!\.env"
|
||||||
echo [OK] .env 생성 완료 (값을 수정하세요)
|
echo [OK] .env 생성 완료 (값을 수정하세요)
|
||||||
) else (
|
) else (
|
||||||
echo [WARN] .env 이미 존재 — 건너뜀
|
echo [WARN] .env 이미 존재 — 건너뜀
|
||||||
@@ -127,9 +162,9 @@ if not exist .env (
|
|||||||
|
|
||||||
:: ---------- 6. 디렉토리 ----------
|
:: ---------- 6. 디렉토리 ----------
|
||||||
echo 6. 디렉토리 생성
|
echo 6. 디렉토리 생성
|
||||||
if not exist logs mkdir logs
|
if not exist "!PROJECT_ROOT!\logs" mkdir "!PROJECT_ROOT!\logs"
|
||||||
if not exist uploads mkdir uploads
|
if not exist "!PROJECT_ROOT!\uploads" mkdir "!PROJECT_ROOT!\uploads"
|
||||||
if not exist backup mkdir backup
|
if not exist "!PROJECT_ROOT!\backup" mkdir "!PROJECT_ROOT!\backup"
|
||||||
echo [OK] logs\ uploads\ backup\
|
echo [OK] logs\ uploads\ backup\
|
||||||
|
|
||||||
:: ---------- 결과 ----------
|
:: ---------- 결과 ----------
|
||||||
@@ -140,7 +175,7 @@ if !ERRORS! equ 0 (
|
|||||||
echo.
|
echo.
|
||||||
echo 다음 단계:
|
echo 다음 단계:
|
||||||
echo 1. .env 파일을 환경에 맞게 수정
|
echo 1. .env 파일을 환경에 맞게 수정
|
||||||
echo 2. DB 생성 (또는 docker compose -f docker-compose-dev.yml up -d)
|
echo 2. DB 생성 ^(또는 docker compose -f docker-compose-dev.yml up -d^)
|
||||||
echo 3. gradlew.bat bootRun
|
echo 3. gradlew.bat bootRun
|
||||||
echo 4. http://localhost:8080/health 확인
|
echo 4. http://localhost:8080/health 확인
|
||||||
) else (
|
) else (
|
||||||
@@ -149,4 +184,5 @@ if !ERRORS! equ 0 (
|
|||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
popd
|
||||||
endlocal
|
endlocal
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
# WBX Spring Core — Linux/macOS 설치 스크립트
|
# WBX Spring Core — Linux/macOS 설치 스크립트
|
||||||
# 사용법: chmod +x scripts/install.sh && ./scripts/install.sh
|
# 사용법: chmod +x scripts/install.sh && ./scripts/install.sh
|
||||||
# ============================================================
|
# ============================================================
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ---------- 색상 ----------
|
# ---------- 색상 ----------
|
||||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||||
@@ -14,6 +13,11 @@ info() { echo -e " ${CYAN}[INFO]${NC} $1"; }
|
|||||||
|
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
|
|
||||||
|
# ---------- 프로젝트 루트 설정 ----------
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
cd "$PROJECT_ROOT" || { echo "[FAIL] 프로젝트 루트를 찾을 수 없습니다: $SCRIPT_DIR/.."; exit 1; }
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " WBX Spring Core — 설치 점검"
|
echo " WBX Spring Core — 설치 점검"
|
||||||
@@ -22,17 +26,100 @@ echo ""
|
|||||||
|
|
||||||
# ---------- 1. JDK 21 ----------
|
# ---------- 1. JDK 21 ----------
|
||||||
echo "1. JDK 확인"
|
echo "1. JDK 확인"
|
||||||
|
JDK_OK=0
|
||||||
if command -v java &>/dev/null; then
|
if command -v java &>/dev/null; then
|
||||||
JAVA_VER=$(java -version 2>&1 | head -1 | awk -F '"' '{print $2}' | cut -d. -f1)
|
JAVA_VER=$(java -version 2>&1 | head -1 | awk -F '"' '{print $2}' | cut -d. -f1)
|
||||||
if [ "$JAVA_VER" -ge 21 ] 2>/dev/null; then
|
if [ "$JAVA_VER" -ge 21 ] 2>/dev/null; then
|
||||||
ok "JDK $JAVA_VER"
|
ok "JDK $JAVA_VER"
|
||||||
|
JDK_OK=1
|
||||||
else
|
else
|
||||||
fail "JDK $JAVA_VER (21 이상 필요)"
|
info "JDK $JAVA_VER — 21 이상 필요, 자동 설치 시도..."
|
||||||
ERRORS=$((ERRORS + 1))
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
fail "java 명령어 없음 — JDK 21 설치 필요"
|
info "java 명령어 없음 — 자동 설치 시도..."
|
||||||
ERRORS=$((ERRORS + 1))
|
fi
|
||||||
|
|
||||||
|
if [ $JDK_OK -eq 0 ]; then
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
# macOS — Homebrew
|
||||||
|
if command -v brew &>/dev/null; then
|
||||||
|
info "Homebrew로 Temurin JDK 21 설치 중..."
|
||||||
|
if brew install --cask temurin@21; then
|
||||||
|
export JAVA_HOME=$(/usr/libexec/java_home -v 21 2>/dev/null || true)
|
||||||
|
if [ -n "$JAVA_HOME" ]; then
|
||||||
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
|
ok "JDK 21 설치 완료 — $JAVA_HOME"
|
||||||
|
else
|
||||||
|
fail "JDK 설치 경로를 찾을 수 없습니다"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "JDK 설치 실패 — 수동으로 JDK 21을 설치하세요"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "Homebrew 없음 — https://adoptium.net 에서 JDK 21을 수동 설치하세요"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Linux — apt / yum / dnf
|
||||||
|
INSTALL_OK=0
|
||||||
|
if command -v apt-get &>/dev/null; then
|
||||||
|
info "apt로 Temurin JDK 21 설치 중..."
|
||||||
|
if sudo apt-get update -qq && sudo apt-get install -y temurin-21-jdk 2>/dev/null; then
|
||||||
|
INSTALL_OK=1
|
||||||
|
else
|
||||||
|
# Adoptium 저장소 추가 후 재시도
|
||||||
|
info "Adoptium 저장소 추가 중..."
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo tee /etc/apt/keyrings/adoptium.asc >/dev/null
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
|
||||||
|
if sudo apt-get update -qq && sudo apt-get install -y temurin-21-jdk; then
|
||||||
|
INSTALL_OK=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif command -v yum &>/dev/null; then
|
||||||
|
info "yum으로 Temurin JDK 21 설치 중..."
|
||||||
|
if sudo yum install -y temurin-21-jdk 2>/dev/null; then
|
||||||
|
INSTALL_OK=1
|
||||||
|
else
|
||||||
|
info "Adoptium 저장소 추가 중..."
|
||||||
|
cat <<-REPO | sudo tee /etc/yum.repos.d/adoptium.repo >/dev/null
|
||||||
|
[Adoptium]
|
||||||
|
name=Adoptium
|
||||||
|
baseurl=https://packages.adoptium.net/artifactory/rpm/rhel/\$releasever/\$basearch
|
||||||
|
enabled=1
|
||||||
|
gpgcheck=1
|
||||||
|
gpgkey=https://packages.adoptium.net/artifactory/api/gpg/key/public
|
||||||
|
REPO
|
||||||
|
if sudo yum install -y temurin-21-jdk; then
|
||||||
|
INSTALL_OK=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "패키지 관리자를 찾을 수 없음 — https://adoptium.net 에서 JDK 21을 수동 설치하세요"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 설치 결과 확인
|
||||||
|
if [ $INSTALL_OK -eq 1 ]; then
|
||||||
|
if command -v java &>/dev/null; then
|
||||||
|
JAVA_VER=$(java -version 2>&1 | head -1 | awk -F '"' '{print $2}' | cut -d. -f1)
|
||||||
|
if [ "$JAVA_VER" -ge 21 ] 2>/dev/null; then
|
||||||
|
ok "JDK 21 설치 완료"
|
||||||
|
else
|
||||||
|
fail "JDK 설치 후에도 버전이 21 미만입니다"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "JDK 설치 후 java 명령어를 찾을 수 없습니다"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
elif [ $INSTALL_OK -eq 0 ] && command -v apt-get &>/dev/null || command -v yum &>/dev/null; then
|
||||||
|
fail "JDK 설치 실패 — 수동으로 JDK 21을 설치하세요"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- 2. Git ----------
|
# ---------- 2. Git ----------
|
||||||
@@ -57,8 +144,8 @@ echo "4. Gradle 빌드"
|
|||||||
if [ $ERRORS -gt 0 ]; then
|
if [ $ERRORS -gt 0 ]; then
|
||||||
fail "사전 요구사항 미충족 — 빌드 건너뜀"
|
fail "사전 요구사항 미충족 — 빌드 건너뜀"
|
||||||
else
|
else
|
||||||
chmod +x gradlew
|
chmod +x "$PROJECT_ROOT/gradlew"
|
||||||
if ./gradlew build -x test --console=plain -q; then
|
if "$PROJECT_ROOT/gradlew" build -x test --console=plain -q; then
|
||||||
ok "BUILD SUCCESSFUL"
|
ok "BUILD SUCCESSFUL"
|
||||||
else
|
else
|
||||||
fail "빌드 실패"
|
fail "빌드 실패"
|
||||||
@@ -68,8 +155,8 @@ fi
|
|||||||
|
|
||||||
# ---------- 5. .env 템플릿 ----------
|
# ---------- 5. .env 템플릿 ----------
|
||||||
echo "5. 환경변수 파일"
|
echo "5. 환경변수 파일"
|
||||||
if [ ! -f .env ]; then
|
if [ ! -f "$PROJECT_ROOT/.env" ]; then
|
||||||
cat > .env << 'ENVEOF'
|
cat > "$PROJECT_ROOT/.env" << 'ENVEOF'
|
||||||
# ===== WBX Spring Core — 환경변수 =====
|
# ===== WBX Spring Core — 환경변수 =====
|
||||||
# 이 파일을 환경에 맞게 수정하세요.
|
# 이 파일을 환경에 맞게 수정하세요.
|
||||||
|
|
||||||
@@ -119,7 +206,7 @@ LOG_PATH=/opt/wbx-app/logs/app.log
|
|||||||
# AWS_ACCESS_KEY=
|
# AWS_ACCESS_KEY=
|
||||||
# AWS_SECRET_KEY=
|
# AWS_SECRET_KEY=
|
||||||
ENVEOF
|
ENVEOF
|
||||||
chmod 600 .env
|
chmod 600 "$PROJECT_ROOT/.env"
|
||||||
ok ".env 생성 완료 (값을 수정하세요)"
|
ok ".env 생성 완료 (값을 수정하세요)"
|
||||||
else
|
else
|
||||||
warn ".env 이미 존재 — 건너뜀"
|
warn ".env 이미 존재 — 건너뜀"
|
||||||
@@ -127,7 +214,7 @@ fi
|
|||||||
|
|
||||||
# ---------- 6. 디렉토리 ----------
|
# ---------- 6. 디렉토리 ----------
|
||||||
echo "6. 디렉토리 생성"
|
echo "6. 디렉토리 생성"
|
||||||
mkdir -p logs uploads backup
|
mkdir -p "$PROJECT_ROOT/logs" "$PROJECT_ROOT/uploads" "$PROJECT_ROOT/backup"
|
||||||
ok "logs/ uploads/ backup/"
|
ok "logs/ uploads/ backup/"
|
||||||
|
|
||||||
# ---------- 결과 ----------
|
# ---------- 결과 ----------
|
||||||
|
|||||||
5
scripts/run-install.bat
일반 파일
5
scripts/run-install.bat
일반 파일
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
set "JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-21.0.10.7-hotspot"
|
||||||
|
set "PATH=%JAVA_HOME%\bin;%PATH%"
|
||||||
|
cd /d "%~dp0.."
|
||||||
|
call scripts\install.bat
|
||||||
@@ -33,5 +33,6 @@ public class WbxAuditLog {
|
|||||||
private String ipAddress;
|
private String ipAddress;
|
||||||
|
|
||||||
@Column(updatable = false)
|
@Column(updatable = false)
|
||||||
|
@Builder.Default
|
||||||
private LocalDateTime createdAt = LocalDateTime.now();
|
private LocalDateTime createdAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,5 +25,6 @@ public class RolePermission {
|
|||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(name = "dept_scope", length = 10)
|
@Column(name = "dept_scope", length = 10)
|
||||||
|
@Builder.Default
|
||||||
private DeptScope deptScope = DeptScope.OWN;
|
private DeptScope deptScope = DeptScope.OWN;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,5 +29,6 @@ public class WbxUserRole {
|
|||||||
private Long scopeId;
|
private Long scopeId;
|
||||||
|
|
||||||
@Column(name = "granted_at")
|
@Column(name = "granted_at")
|
||||||
|
@Builder.Default
|
||||||
private LocalDateTime grantedAt = LocalDateTime.now();
|
private LocalDateTime grantedAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|||||||
새 Issue에서 참조
사용자 차단