feat(certification): GS인증 신청서 PDF 자동 생성
certification/generate_pdf.py: reportlab 기반 한글 PDF 생성기
- 맑은고딕(malgun.ttf) 자동 탐색 + 등록
- 7개 섹션 구성:
1. 신청자 정보 (회사명, 대표자, 사업자번호 등)
2. 소프트웨어 정보 (제품명, 버전, 등급, 분류)
3. 제품 개요 (설명문, 핵심기능 5개, 지원환경)
4. 시험 요청 항목 (ISO/IEC 25010 8개 품질특성)
5. 제출 서류 목록 (11개 필수/선택)
6. 예상 수수료 (650만원)
7. 서약 및 서명란
산출물: GS인증_신청서_GUARDiA_ITSM_v2.0.pdf (118.8 KB)
- A4 용지, 2페이지
- 네이비 테마 디자인
- 페이지 번호 + 회사명 푸터
- 바로 출력/제출 가능
실행: cd certification && python3 generate_pdf.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7373d74204
commit
0854a0fea2
BIN
certification/GS인증_신청서_GUARDiA_ITSM_v2.0.pdf
Normal file
BIN
certification/GS인증_신청서_GUARDiA_ITSM_v2.0.pdf
Normal file
Binary file not shown.
592
certification/generate_pdf.py
Normal file
592
certification/generate_pdf.py
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
"""
|
||||||
|
GUARDiA ITSM GS인증 신청서 PDF 생성기
|
||||||
|
실행: python generate_pdf.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# reportlab 임포트
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.lib.pagesizes import A4
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
|
||||||
|
from reportlab.platypus import (
|
||||||
|
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||||
|
HRFlowable, PageBreak, KeepTogether
|
||||||
|
)
|
||||||
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
|
# ── 한글 폰트 등록 ────────────────────────────────────────────
|
||||||
|
def register_korean_fonts():
|
||||||
|
"""Windows/Linux에서 한글 폰트 자동 탐색 후 등록."""
|
||||||
|
font_candidates = [
|
||||||
|
# Windows
|
||||||
|
"C:/Windows/Fonts/malgun.ttf",
|
||||||
|
"C:/Windows/Fonts/malgunbd.ttf",
|
||||||
|
"C:/Windows/Fonts/gulim.ttc",
|
||||||
|
"C:/Windows/Fonts/batang.ttc",
|
||||||
|
# Linux
|
||||||
|
"/usr/share/fonts/truetype/nanum/NanumGothic.ttf",
|
||||||
|
"/usr/share/fonts/truetype/unfonts-core/UnDotum.ttf",
|
||||||
|
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
||||||
|
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
|
||||||
|
]
|
||||||
|
|
||||||
|
font_bold_candidates = [
|
||||||
|
"C:/Windows/Fonts/malgunbd.ttf",
|
||||||
|
"/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 일반 폰트
|
||||||
|
for path in font_candidates:
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
pdfmetrics.registerFont(TTFont("KorFont", path))
|
||||||
|
print(f" 폰트 등록: {path}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 볼드 폰트
|
||||||
|
for path in font_bold_candidates:
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
pdfmetrics.registerFont(TTFont("KorFontBold", path))
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 폰트 패밀리 등록
|
||||||
|
try:
|
||||||
|
from reportlab.pdfbase.pdfmetrics import registerFontFamily
|
||||||
|
registerFontFamily("KorFont", normal="KorFont", bold="KorFontBold")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ── 색상 정의 ──────────────────────────────────────────────────
|
||||||
|
NAVY = colors.HexColor("#1e3a5f")
|
||||||
|
BLUE = colors.HexColor("#0051A2")
|
||||||
|
ACCENT = colors.HexColor("#00A3E0")
|
||||||
|
LIGHT_BG = colors.HexColor("#EBF3FB")
|
||||||
|
GRAY = colors.HexColor("#64748B")
|
||||||
|
LIGHT_GRAY = colors.HexColor("#F3F4F6")
|
||||||
|
WHITE = colors.white
|
||||||
|
BLACK = colors.black
|
||||||
|
GREEN = colors.HexColor("#065F46")
|
||||||
|
GREEN_BG = colors.HexColor("#D1FAE5")
|
||||||
|
|
||||||
|
# ── 페이지 설정 ────────────────────────────────────────────────
|
||||||
|
W, H = A4
|
||||||
|
MARGIN = 20 * mm
|
||||||
|
|
||||||
|
def make_styles():
|
||||||
|
"""스타일 정의."""
|
||||||
|
register_korean_fonts()
|
||||||
|
|
||||||
|
base_font = "KorFont" if "KorFont" in pdfmetrics.getRegisteredFontNames() else "Helvetica"
|
||||||
|
bold_font = "KorFontBold" if "KorFontBold" in pdfmetrics.getRegisteredFontNames() else "Helvetica-Bold"
|
||||||
|
|
||||||
|
styles = {
|
||||||
|
"cover_title": ParagraphStyle("cover_title", fontName=bold_font, fontSize=26,
|
||||||
|
textColor=WHITE, alignment=TA_CENTER, leading=32, spaceAfter=6),
|
||||||
|
"cover_sub": ParagraphStyle("cover_sub", fontName=base_font, fontSize=13,
|
||||||
|
textColor=colors.HexColor("#BDE3FF"), alignment=TA_CENTER, leading=18),
|
||||||
|
"cover_info": ParagraphStyle("cover_info", fontName=base_font, fontSize=11,
|
||||||
|
textColor=WHITE, alignment=TA_CENTER, leading=16),
|
||||||
|
|
||||||
|
"section": ParagraphStyle("section", fontName=bold_font, fontSize=13,
|
||||||
|
textColor=WHITE, leading=18, leftIndent=6),
|
||||||
|
"h2": ParagraphStyle("h2", fontName=bold_font, fontSize=11,
|
||||||
|
textColor=NAVY, leading=16, spaceBefore=8, spaceAfter=4),
|
||||||
|
"body": ParagraphStyle("body", fontName=base_font, fontSize=9.5,
|
||||||
|
textColor=BLACK, leading=15, spaceAfter=3),
|
||||||
|
"body_center": ParagraphStyle("body_center", fontName=base_font, fontSize=9.5,
|
||||||
|
textColor=BLACK, leading=15, alignment=TA_CENTER),
|
||||||
|
"small": ParagraphStyle("small", fontName=base_font, fontSize=8.5,
|
||||||
|
textColor=GRAY, leading=13),
|
||||||
|
"bold": ParagraphStyle("bold", fontName=bold_font, fontSize=9.5,
|
||||||
|
textColor=BLACK, leading=15),
|
||||||
|
"footer": ParagraphStyle("footer", fontName=base_font, fontSize=8,
|
||||||
|
textColor=GRAY, alignment=TA_CENTER, leading=12),
|
||||||
|
"note": ParagraphStyle("note", fontName=base_font, fontSize=8.5,
|
||||||
|
textColor=GRAY, leading=13, leftIndent=10),
|
||||||
|
}
|
||||||
|
return styles, base_font, bold_font
|
||||||
|
|
||||||
|
|
||||||
|
def build_pdf(output_path: str):
|
||||||
|
"""PDF 빌드 메인 함수."""
|
||||||
|
styles, base_font, bold_font = make_styles()
|
||||||
|
|
||||||
|
doc = SimpleDocTemplate(
|
||||||
|
output_path,
|
||||||
|
pagesize=A4,
|
||||||
|
leftMargin=MARGIN, rightMargin=MARGIN,
|
||||||
|
topMargin=MARGIN, bottomMargin=MARGIN,
|
||||||
|
title="GUARDiA ITSM GS인증 신청서",
|
||||||
|
author="(주)지오정보기술",
|
||||||
|
subject="GS인증 (Good Software) 1등급 신청",
|
||||||
|
)
|
||||||
|
|
||||||
|
story = []
|
||||||
|
W_CONTENT = W - 2 * MARGIN
|
||||||
|
|
||||||
|
# ── 표지 (Page 1) ─────────────────────────────────────────
|
||||||
|
# 파란 헤더 배너
|
||||||
|
header_table = Table(
|
||||||
|
[[Paragraph("GS인증 신청서", styles["cover_title"]),
|
||||||
|
Paragraph("Good Software Certification Application", styles["cover_sub"])]],
|
||||||
|
colWidths=[W_CONTENT],
|
||||||
|
)
|
||||||
|
header_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (-1, -1), NAVY),
|
||||||
|
("ROUNDEDCORNERS", (0, 0), (-1, -1), [8]),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 20),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 20),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 20),
|
||||||
|
("SPAN", (0,0), (0,-1)),
|
||||||
|
]))
|
||||||
|
story.append(header_table)
|
||||||
|
story.append(Spacer(1, 10*mm))
|
||||||
|
|
||||||
|
# 제품 로고 영역
|
||||||
|
logo_data = [
|
||||||
|
[Paragraph("GUARDiA ITSM", ParagraphStyle("logo", fontName=bold_font,
|
||||||
|
fontSize=36, textColor=BLUE, alignment=TA_CENTER))],
|
||||||
|
[Paragraph("AI 기반 레거시 인프라 자율 운영 플랫폼", ParagraphStyle("logo_sub",
|
||||||
|
fontName=base_font, fontSize=14, textColor=GRAY, alignment=TA_CENTER))],
|
||||||
|
[Spacer(1, 4*mm)],
|
||||||
|
[Paragraph("Version 2.0.0 | 2026년 9월 신청", ParagraphStyle("ver",
|
||||||
|
fontName=base_font, fontSize=11, textColor=GRAY, alignment=TA_CENTER))],
|
||||||
|
]
|
||||||
|
logo_table = Table(logo_data, colWidths=[W_CONTENT])
|
||||||
|
logo_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
|
||||||
|
("ROUNDEDCORNERS", (0,0), (-1,-1), [8]),
|
||||||
|
("TOPPADDING", (0,0), (-1,-1), 16),
|
||||||
|
("BOTTOMPADDING", (0,0), (-1,-1), 16),
|
||||||
|
]))
|
||||||
|
story.append(logo_table)
|
||||||
|
story.append(Spacer(1, 8*mm))
|
||||||
|
|
||||||
|
# 제출 정보 박스
|
||||||
|
submit_data = [
|
||||||
|
["제 출 처", "한국정보통신기술협회 (TTA) SW시험인증연구소"],
|
||||||
|
["신 청 등 급", "GS 1등급 (Good Software 1st Grade)"],
|
||||||
|
["신 청 일 자", "2026년 9월"],
|
||||||
|
["문 의 처", "031-724-0114 | sw@tta.or.kr"],
|
||||||
|
]
|
||||||
|
submit_table = Table(
|
||||||
|
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
|
||||||
|
for k, v in submit_data],
|
||||||
|
colWidths=[40*mm, W_CONTENT - 40*mm],
|
||||||
|
)
|
||||||
|
submit_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (0, -1), NAVY),
|
||||||
|
("BACKGROUND", (1, 0), (1, -1), WHITE),
|
||||||
|
("TEXTCOLOR", (0, 0), (0, -1), WHITE),
|
||||||
|
("FONTNAME", (0, 0), (0, -1), bold_font),
|
||||||
|
("FONTNAME", (1, 0), (1, -1), base_font),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(submit_table)
|
||||||
|
|
||||||
|
# 표지 하단
|
||||||
|
story.append(Spacer(1, 12*mm))
|
||||||
|
story.append(HRFlowable(width=W_CONTENT, color=BLUE, thickness=2))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
story.append(Paragraph(
|
||||||
|
"(주)지오정보기술 | Copyright © 2026 All Rights Reserved.",
|
||||||
|
styles["footer"]
|
||||||
|
))
|
||||||
|
story.append(PageBreak())
|
||||||
|
|
||||||
|
# ── 1. 신청자 정보 ─────────────────────────────────────────
|
||||||
|
def section_header(title, subtitle=""):
|
||||||
|
data = [[Paragraph(f" {title}", styles["section"])]]
|
||||||
|
t = Table(data, colWidths=[W_CONTENT])
|
||||||
|
t.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0,0), (-1,-1), NAVY),
|
||||||
|
("ROUNDEDCORNERS", (0,0), (-1,-1), [6]),
|
||||||
|
("TOPPADDING", (0,0), (-1,-1), 8),
|
||||||
|
("BOTTOMPADDING", (0,0), (-1,-1), 8),
|
||||||
|
]))
|
||||||
|
return t
|
||||||
|
|
||||||
|
def info_table(data_rows, col_widths=None):
|
||||||
|
"""키-값 테이블 생성."""
|
||||||
|
if col_widths is None:
|
||||||
|
col_widths = [45*mm, W_CONTENT - 45*mm]
|
||||||
|
rows = []
|
||||||
|
for k, v in data_rows:
|
||||||
|
rows.append([
|
||||||
|
Paragraph(k, styles["bold"]),
|
||||||
|
Paragraph(v, styles["body"]),
|
||||||
|
])
|
||||||
|
t = Table(rows, colWidths=col_widths)
|
||||||
|
t.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
|
||||||
|
("FONTNAME", (0, 0), (0, -1), bold_font),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
return t
|
||||||
|
|
||||||
|
story.append(section_header("1. 신청자 정보"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
story.append(info_table([
|
||||||
|
("회 사 명", "(주)지오정보기술"),
|
||||||
|
("대 표 자", "(대표이사명)"),
|
||||||
|
("사업자등록번호", "000-00-00000"),
|
||||||
|
("주 소", "서울특별시 (상세주소)"),
|
||||||
|
("전 화", "02-000-0000"),
|
||||||
|
("팩 스", "02-000-0001"),
|
||||||
|
("이 메 일", "gs@zioinfo.co.kr"),
|
||||||
|
("담 당 자", "(담당자명) / 개발팀장"),
|
||||||
|
("담당자 연락처", "010-0000-0000"),
|
||||||
|
]))
|
||||||
|
|
||||||
|
story.append(Spacer(1, 8*mm))
|
||||||
|
|
||||||
|
# ── 2. 소프트웨어 정보 ────────────────────────────────────
|
||||||
|
story.append(section_header("2. 소프트웨어 정보"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
story.append(info_table([
|
||||||
|
("제 품 명", "GUARDiA ITSM"),
|
||||||
|
("버 전", "2.0.0"),
|
||||||
|
("빌 드 일", "2026-08-31"),
|
||||||
|
("신청 등급", "GS 1등급"),
|
||||||
|
("제품 분류", "시스템 관리 소프트웨어"),
|
||||||
|
("세부 분류", "IT서비스관리(ITSM) / 인프라 자동화 플랫폼"),
|
||||||
|
("운영 환경", "서버 소프트웨어"),
|
||||||
|
("저작권 등록번호", "C-2026-XXXXXX (등록 후 기입)"),
|
||||||
|
("소프트웨어사업자", "제XXX호 (신고 후 기입)"),
|
||||||
|
]))
|
||||||
|
|
||||||
|
story.append(Spacer(1, 8*mm))
|
||||||
|
|
||||||
|
# ── 3. 제품 개요 ──────────────────────────────────────────
|
||||||
|
story.append(section_header("3. 제품 개요"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
|
||||||
|
# 제품 설명 박스
|
||||||
|
desc_text = (
|
||||||
|
"GUARDiA ITSM은 1,000개 이상 공공기관의 레거시 IT 인프라를 AI로 자율 운영하는 "
|
||||||
|
"온프레미스 통합 관리 플랫폼입니다. 메신저 한 줄 명령으로 에이전트 설치 없이 "
|
||||||
|
"SSH/SFTP를 통해 WAS 배포·운영을 완전 자동화하며, SR 관리, 인시던트 대응, "
|
||||||
|
"변경관리, CMDB, PMS 등 ITSM 전 기능을 단일 플랫폼에서 제공합니다."
|
||||||
|
)
|
||||||
|
desc_table = Table([[Paragraph(desc_text, styles["body"])]], colWidths=[W_CONTENT])
|
||||||
|
desc_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
|
||||||
|
("LEFTPADDING", (0,0), (-1,-1), 12),
|
||||||
|
("RIGHTPADDING", (0,0), (-1,-1), 12),
|
||||||
|
("TOPPADDING", (0,0), (-1,-1), 10),
|
||||||
|
("BOTTOMPADDING", (0,0), (-1,-1), 10),
|
||||||
|
("ROUNDEDCORNERS",(0,0), (-1,-1), [6]),
|
||||||
|
]))
|
||||||
|
story.append(desc_table)
|
||||||
|
story.append(Spacer(1, 5*mm))
|
||||||
|
|
||||||
|
# 핵심 기능
|
||||||
|
story.append(Paragraph("핵심 기능", styles["h2"]))
|
||||||
|
features = [
|
||||||
|
("1", "AI ChatOps", "메신저 25개 명령어로 인프라 실시간 제어"),
|
||||||
|
("2", "에이전트리스 배포", "SSH/SFTP 기반 — 대상 서버 소프트웨어 설치 불필요"),
|
||||||
|
("3", "통합 ITSM", "SR·인시던트·변경관리·SLA·CMDB 완비"),
|
||||||
|
("4", "PMS 프로젝트 관리", "WBS·산출물·일간/주간/월간 자동 보고서"),
|
||||||
|
("5", "보안 완전성", "JWT+MFA+AES-256-GCM+PAM+취약점스캔"),
|
||||||
|
]
|
||||||
|
feat_data = [[Paragraph(no, styles["body_center"]),
|
||||||
|
Paragraph(name, styles["bold"]),
|
||||||
|
Paragraph(desc, styles["body"])]
|
||||||
|
for no, name, desc in features]
|
||||||
|
feat_table = Table(feat_data, colWidths=[10*mm, 50*mm, W_CONTENT-60*mm])
|
||||||
|
feat_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (0, -1), NAVY),
|
||||||
|
("TEXTCOLOR", (0, 0), (0, -1), WHITE),
|
||||||
|
("FONTNAME", (0, 0), (0, -1), bold_font),
|
||||||
|
("BACKGROUND", (1, 0), (1, -1), LIGHT_BG),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 5),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(feat_table)
|
||||||
|
story.append(Spacer(1, 5*mm))
|
||||||
|
|
||||||
|
# 지원 환경
|
||||||
|
story.append(Paragraph("지원 환경", styles["h2"]))
|
||||||
|
env_data = [
|
||||||
|
["서버 OS", "Ubuntu 20.04+, CentOS 7+, RHEL 8+, Windows Server 2019+"],
|
||||||
|
["브라우저", "Chrome 90+, Firefox 88+, Edge 90+"],
|
||||||
|
["최소 사양", "CPU 2코어, RAM 8GB, 디스크 20GB"],
|
||||||
|
]
|
||||||
|
env_table = Table(
|
||||||
|
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
|
||||||
|
for k, v in env_data],
|
||||||
|
colWidths=[30*mm, W_CONTENT-30*mm],
|
||||||
|
)
|
||||||
|
env_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0,0), (0,-1), LIGHT_BG),
|
||||||
|
("FONTSIZE", (0,0), (-1,-1), 9.5),
|
||||||
|
("ALIGN", (0,0), (0,-1), "CENTER"),
|
||||||
|
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0,0), (-1,-1), 5),
|
||||||
|
("BOTTOMPADDING",(0,0), (-1,-1), 5),
|
||||||
|
("LEFTPADDING", (0,0), (-1,-1), 8),
|
||||||
|
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(env_table)
|
||||||
|
|
||||||
|
story.append(PageBreak())
|
||||||
|
|
||||||
|
# ── 4. 시험 요청 항목 ──────────────────────────────────────
|
||||||
|
story.append(section_header("4. 시험 요청 항목 (ISO/IEC 25010)"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
|
||||||
|
qualities = [
|
||||||
|
("기능 적합성", "Functional Suitability", "✓", "기능 완전성, 정확성, 적절성"),
|
||||||
|
("성능 효율성", "Performance Efficiency", "✓", "동시 100명 기준 응답시간 3초 이내"),
|
||||||
|
("호 환 성", "Compatibility", "✓", "4개 OS, 주요 브라우저 지원"),
|
||||||
|
("사 용 성", "Usability", "✓", "웹접근성(KWCAG 2.1 AA) 포함"),
|
||||||
|
("신 뢰 성", "Reliability", "✓", "백업/복구, 에러 처리 포함"),
|
||||||
|
("보 안 성", "Security", "✓", "시큐어코딩 점검 내장"),
|
||||||
|
("유지보수성", "Maintainability", "✓", "버전 정보, 오류코드 체계 포함"),
|
||||||
|
("이 식 성", "Portability", "✓", "설치/제거 스크립트 완비"),
|
||||||
|
]
|
||||||
|
|
||||||
|
qual_header = [[
|
||||||
|
Paragraph("품질 특성", styles["section"]),
|
||||||
|
Paragraph("ISO 명칭", styles["section"]),
|
||||||
|
Paragraph("요청", styles["section"]),
|
||||||
|
Paragraph("비고", styles["section"]),
|
||||||
|
]]
|
||||||
|
qual_data = [
|
||||||
|
[Paragraph(k, styles["bold"]),
|
||||||
|
Paragraph(en, styles["small"]),
|
||||||
|
Paragraph(req, ParagraphStyle("check", fontName=bold_font, fontSize=12,
|
||||||
|
textColor=GREEN, alignment=TA_CENTER)),
|
||||||
|
Paragraph(note, styles["body"])]
|
||||||
|
for k, en, req, note in qualities
|
||||||
|
]
|
||||||
|
|
||||||
|
qual_table = Table(
|
||||||
|
qual_header + qual_data,
|
||||||
|
colWidths=[30*mm, 45*mm, 15*mm, W_CONTENT-90*mm],
|
||||||
|
)
|
||||||
|
qual_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (-1, 0), NAVY),
|
||||||
|
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
|
||||||
|
("BACKGROUND", (0, 1), (-1, -1), WHITE),
|
||||||
|
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, LIGHT_BG]),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (-1, 0), "CENTER"),
|
||||||
|
("ALIGN", (2, 1), (2, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(qual_table)
|
||||||
|
story.append(Spacer(1, 8*mm))
|
||||||
|
|
||||||
|
# ── 5. 제출 서류 목록 ─────────────────────────────────────
|
||||||
|
story.append(section_header("5. 제출 서류 목록"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
|
||||||
|
docs = [
|
||||||
|
("필수", "GS인증 신청서", "본 문서"),
|
||||||
|
("필수", "사업자등록증 사본", "법인"),
|
||||||
|
("필수", "소프트웨어사업자 신고확인서", "과학기술정보통신부"),
|
||||||
|
("필수", "제품 기능 명세서", "certification/04_기술문서/ 참조"),
|
||||||
|
("필수", "사용자 매뉴얼 (PDF)", "관리자용 + 일반사용자용"),
|
||||||
|
("필수", "설치·제거 매뉴얼", "setup/uninstall.sh 포함"),
|
||||||
|
("필수", "소프트웨어 패키지", "설치 USB 또는 다운로드 링크"),
|
||||||
|
("필수", "저작권 등록증", "한국저작권위원회"),
|
||||||
|
("필수", "심사 수수료 납부 영수증", "약 650만원"),
|
||||||
|
("선택", "기술특허 출원서", "에이전트리스 자동화 방법"),
|
||||||
|
("선택", "GS이전버전 인증서", "없음 (신규 신청)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
doc_header = [[
|
||||||
|
Paragraph("구분", styles["section"]),
|
||||||
|
Paragraph("서류명", styles["section"]),
|
||||||
|
Paragraph("비고", styles["section"]),
|
||||||
|
]]
|
||||||
|
doc_data = []
|
||||||
|
for kind, name, note in docs:
|
||||||
|
bg = GREEN_BG if kind == "필수" else LIGHT_BG
|
||||||
|
doc_data.append([
|
||||||
|
Paragraph(kind, ParagraphStyle("dk", fontName=bold_font, fontSize=9,
|
||||||
|
textColor=GREEN if kind=="필수" else GRAY, alignment=TA_CENTER)),
|
||||||
|
Paragraph(name, styles["bold"]),
|
||||||
|
Paragraph(note, styles["body"]),
|
||||||
|
])
|
||||||
|
|
||||||
|
doc_table = Table(
|
||||||
|
doc_header + doc_data,
|
||||||
|
colWidths=[18*mm, 65*mm, W_CONTENT-83*mm],
|
||||||
|
)
|
||||||
|
doc_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (-1, 0), NAVY),
|
||||||
|
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
|
||||||
|
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, LIGHT_BG]),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (-1, 0), "CENTER"),
|
||||||
|
("ALIGN", (0, 1), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 5),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(doc_table)
|
||||||
|
story.append(Spacer(1, 8*mm))
|
||||||
|
|
||||||
|
# ── 6. 수수료 ─────────────────────────────────────────────
|
||||||
|
story.append(section_header("6. 예상 수수료"))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
fee_data = [
|
||||||
|
["심사 수수료", "5,000,000원"],
|
||||||
|
["시험 수수료", "1,500,000원"],
|
||||||
|
["합 계", "6,500,000원 (VAT 별도)"],
|
||||||
|
]
|
||||||
|
fee_table = Table(
|
||||||
|
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
|
||||||
|
for k, v in fee_data],
|
||||||
|
colWidths=[40*mm, W_CONTENT-40*mm],
|
||||||
|
)
|
||||||
|
fee_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
|
||||||
|
("BACKGROUND", (0, 2), (-1, 2), LIGHT_BG),
|
||||||
|
("FONTNAME", (0, 2), (-1, 2), bold_font),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 9.5),
|
||||||
|
("ALIGN", (0, 0), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(fee_table)
|
||||||
|
story.append(Spacer(1, 5*mm))
|
||||||
|
story.append(Paragraph(
|
||||||
|
"※ 실제 금액은 TTA 접수 시 확정됩니다. 위 금액은 2026년 기준 추정치입니다.",
|
||||||
|
styles["note"]
|
||||||
|
))
|
||||||
|
|
||||||
|
story.append(Spacer(1, 10*mm))
|
||||||
|
|
||||||
|
# ── 7. 서약 및 서명 ───────────────────────────────────────
|
||||||
|
story.append(section_header("7. 서약 및 서명"))
|
||||||
|
story.append(Spacer(1, 6*mm))
|
||||||
|
|
||||||
|
pledge_text = (
|
||||||
|
"본 신청서에 기재한 내용이 사실과 다름이 없음을 확인하며, "
|
||||||
|
"한국정보통신기술협회(TTA)의 GS인증 심사 규정을 준수할 것을 서약합니다. "
|
||||||
|
"심사 중 제출 자료의 허위 기재 사실이 밝혀질 경우 인증이 취소될 수 있음을 인지합니다."
|
||||||
|
)
|
||||||
|
pledge_table = Table([[Paragraph(pledge_text, styles["body"])]], colWidths=[W_CONTENT])
|
||||||
|
pledge_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
|
||||||
|
("LEFTPADDING", (0,0), (-1,-1), 12),
|
||||||
|
("RIGHTPADDING", (0,0), (-1,-1), 12),
|
||||||
|
("TOPPADDING", (0,0), (-1,-1), 10),
|
||||||
|
("BOTTOMPADDING", (0,0), (-1,-1), 10),
|
||||||
|
]))
|
||||||
|
story.append(pledge_table)
|
||||||
|
story.append(Spacer(1, 10*mm))
|
||||||
|
|
||||||
|
# 날짜 및 서명
|
||||||
|
sign_data = [
|
||||||
|
["신 청 일", "2026년 월 일", ""],
|
||||||
|
["", "", ""],
|
||||||
|
["회 사 명", "(주)지오정보기술", ""],
|
||||||
|
["대 표 자", "", " (인)"],
|
||||||
|
]
|
||||||
|
sign_table = Table(
|
||||||
|
[[Paragraph(k, styles["bold"]),
|
||||||
|
Paragraph(v, styles["body"]),
|
||||||
|
Paragraph(s, styles["body"])]
|
||||||
|
for k, v, s in sign_data],
|
||||||
|
colWidths=[30*mm, 90*mm, 30*mm],
|
||||||
|
)
|
||||||
|
sign_table.setStyle(TableStyle([
|
||||||
|
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
|
||||||
|
("FONTSIZE", (0, 0), (-1, -1), 10),
|
||||||
|
("ALIGN", (0, 0), (0, -1), "CENTER"),
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||||
|
("TOPPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("BOTTOMPADDING",(0, 0), (-1, -1), 8),
|
||||||
|
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
||||||
|
("LINEBELOW", (1, 3), (1, 3), 1, BLACK),
|
||||||
|
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
|
||||||
|
]))
|
||||||
|
story.append(sign_table)
|
||||||
|
|
||||||
|
# ── 최종 푸터 ─────────────────────────────────────────────
|
||||||
|
story.append(Spacer(1, 12*mm))
|
||||||
|
story.append(HRFlowable(width=W_CONTENT, color=BLUE, thickness=1.5))
|
||||||
|
story.append(Spacer(1, 4*mm))
|
||||||
|
|
||||||
|
footer_data = [
|
||||||
|
Paragraph("한국정보통신기술협회 (TTA) | 경기도 성남시 분당구 양현로 322", styles["footer"]),
|
||||||
|
Paragraph("Tel: 031-724-0114 | E-mail: sw@tta.or.kr | www.tta.or.kr", styles["footer"]),
|
||||||
|
Spacer(1, 3*mm),
|
||||||
|
Paragraph(
|
||||||
|
f"본 문서는 GUARDiA ITSM GS인증 신청을 위해 (주)지오정보기술이 작성한 공식 신청서입니다. "
|
||||||
|
f"생성일: {datetime.now().strftime('%Y-%m-%d')}",
|
||||||
|
styles["footer"]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for item in footer_data:
|
||||||
|
story.append(item)
|
||||||
|
|
||||||
|
# ── PDF 빌드 ──────────────────────────────────────────────
|
||||||
|
def on_first_page(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def on_later_pages(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
# 페이지 번호
|
||||||
|
canvas.setFont("Helvetica", 8)
|
||||||
|
canvas.setFillColor(GRAY)
|
||||||
|
canvas.drawRightString(W - MARGIN, 10*mm, f"- {doc.page} -")
|
||||||
|
canvas.drawString(MARGIN, 10*mm, "GUARDiA ITSM GS인증 신청서 | (주)지오정보기술")
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_later_pages)
|
||||||
|
size_kb = Path(output_path).stat().st_size / 1024
|
||||||
|
print(f"\n[OK] PDF 생성 완료: {output_path}")
|
||||||
|
print(f" 파일 크기: {size_kb:.1f} KB")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cert_dir = Path(__file__).parent
|
||||||
|
output = cert_dir / "GS인증_신청서_GUARDiA_ITSM_v2.0.pdf"
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
print("GUARDiA ITSM GS인증 신청서 PDF 생성 중...")
|
||||||
|
build_pdf(str(output))
|
||||||
Loading…
Reference in New Issue
Block a user