feat(cert): 프로그램 등록 신청서 통합 PDF 생성 (4페이지, 3종 신청서)

This commit is contained in:
DESKTOP-TKLFCPR\ython 2026-05-30 10:25:19 +09:00
parent 0854a0fea2
commit 5005078b3e
2 changed files with 633 additions and 0 deletions

View File

@ -0,0 +1,633 @@
"""
GUARDiA ITSM 프로그램 등록 신청서 PDF 생성기
- 소프트웨어 저작권 등록 (한국저작권위원회)
- 소프트웨어사업자 신고 (과학기술정보통신부)
- 조달청 나라장터 물품 등록
실행: python generate_registration_pdf.py
"""
import os, sys
from pathlib import Path
from datetime import datetime
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.styles import 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 reg_fonts():
for p in ["C:/Windows/Fonts/malgun.ttf",
"/usr/share/fonts/truetype/nanum/NanumGothic.ttf"]:
if os.path.exists(p):
try:
pdfmetrics.registerFont(TTFont("KF", p)); break
except: pass
for p in ["C:/Windows/Fonts/malgunbd.ttf",
"/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf"]:
if os.path.exists(p):
try:
pdfmetrics.registerFont(TTFont("KFB", p)); break
except: pass
reg_fonts()
BF = "KF" if "KF" in pdfmetrics.getRegisteredFontNames() else "Helvetica"
BBF = "KFB" if "KFB" in pdfmetrics.getRegisteredFontNames() else "Helvetica-Bold"
W, H = A4
MARGIN = 18 * mm
WC = W - 2 * MARGIN
# ── 색상 ──────────────────────────────────────────────────────
NAVY = colors.HexColor("#1e3a5f")
BLUE = colors.HexColor("#0051A2")
ACCENT = colors.HexColor("#00A3E0")
LBLU = colors.HexColor("#EBF3FB")
GRAY = colors.HexColor("#64748B")
LGRAY = colors.HexColor("#F3F4F6")
RED = colors.HexColor("#DC2626")
LRED = colors.HexColor("#FEE2E2")
GREEN = colors.HexColor("#065F46")
LGRN = colors.HexColor("#D1FAE5")
ORANGE = colors.HexColor("#C2410C")
LORG = colors.HexColor("#FED7AA")
WHITE = colors.white
BLACK = colors.black
# ── 스타일 ────────────────────────────────────────────────────
def S(name, **kw):
defaults = dict(fontName=BF, fontSize=9.5, leading=15, textColor=BLACK)
defaults.update(kw)
return ParagraphStyle(name, **defaults)
sty = {
"doc_title": S("dt", fontName=BBF, fontSize=22, textColor=WHITE, alignment=TA_CENTER, leading=28),
"doc_sub": S("ds", fontName=BF, fontSize=11, textColor=colors.HexColor("#BDE3FF"), alignment=TA_CENTER),
"sec": S("sc", fontName=BBF, fontSize=12, textColor=WHITE, leading=18),
"sec2": S("s2", fontName=BBF, fontSize=10.5, textColor=NAVY, leading=16, spaceBefore=6, spaceAfter=3),
"lbl": S("lb", fontName=BBF, fontSize=9.5, textColor=BLACK, alignment=TA_CENTER),
"val": S("vl"),
"val_c": S("vc", alignment=TA_CENTER),
"note": S("nt", fontName=BF, fontSize=8, textColor=GRAY, leading=12, leftIndent=8),
"small": S("sm", fontName=BF, fontSize=8.5, textColor=GRAY, leading=12),
"footer": S("ft", fontName=BF, fontSize=7.5, textColor=GRAY, alignment=TA_CENTER, leading=11),
"req": S("rq", fontName=BBF, fontSize=8, textColor=RED, alignment=TA_CENTER),
"body": S("bd", fontName=BF, fontSize=9.5, leading=15),
"sign": S("sg", fontName=BF, fontSize=10.5, leading=16),
"warn": S("wn", fontName=BBF, fontSize=9, textColor=RED),
}
# ── 공통 헬퍼 ─────────────────────────────────────────────────
def page_header(title, subtitle, badge_text, badge_color):
"""문서 상단 배너."""
data = [[
Paragraph(f" {title}", sty["doc_title"]),
Paragraph(subtitle, sty["doc_sub"]),
]]
t = Table(data, colWidths=[WC])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), badge_color),
("TOPPADDING", (0,0), (-1,-1), 14),
("BOTTOMPADDING", (0,0), (-1,-1), 14),
("LEFTPADDING", (0,0), (-1,-1), 12),
("SPAN", (0,0), (0,-1)),
]))
return t
def sec_title(text, color=NAVY):
"""섹션 제목 배너."""
t = Table([[Paragraph(f" {text}", sty["sec"])]], colWidths=[WC])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("ROUNDEDCORNERS",(0,0), (-1,-1), [5]),
]))
return t
def info_row(rows, col_w=None, lbg=LBLU):
"""키-값 테이블."""
if col_w is None:
col_w = [40*mm, WC - 40*mm]
cells = []
for r in rows:
if len(r) == 2:
cells.append([Paragraph(r[0], sty["lbl"]), Paragraph(r[1], sty["val"])])
else:
cells.append([Paragraph(r[0], sty["lbl"]),
Paragraph(r[1], sty["val"]),
Paragraph(r[2] if len(r)>2 else "", sty["req"])])
col_w = col_w[:2] + [12*mm] if len(col_w) == 2 else col_w
t = Table(cells, colWidths=col_w)
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), lbg),
("FONTNAME", (0,0), (0,-1), BBF),
("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), 7),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
return t
def sign_box(date_label="신고일"):
"""서명 란."""
rows = [
[Paragraph(date_label, sty["lbl"]),
Paragraph("2026년 월 일", sty["sign"])],
[Paragraph("회 사 명", sty["lbl"]),
Paragraph("(주)지오정보기술", sty["sign"])],
[Paragraph("대 표 자", sty["lbl"]),
Paragraph(" (인)", sty["sign"])],
]
t = Table(rows, colWidths=[35*mm, WC-35*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LBLU),
("FONTNAME", (0,0), (0,-1), BBF),
("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,2), (1,2), 1, BLACK),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
return t
def footer_bar(page_title):
return [
Spacer(1, 6*mm),
HRFlowable(width=WC, color=BLUE, thickness=1),
Spacer(1, 2*mm),
Paragraph(f"{page_title} | (주)지오정보기술 | Copyright © 2026 All Rights Reserved.", sty["footer"]),
]
# ── BUILD PDF ──────────────────────────────────────────────────
def build(out):
doc = SimpleDocTemplate(out, pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="GUARDiA ITSM 프로그램 등록 신청서 모음",
author="(주)지오정보기술",
)
story = []
# ════════════════════════════════════════════════════════════
# 표지
# ════════════════════════════════════════════════════════════
story.append(Spacer(1, 20*mm))
# 메인 배너
cover = Table([[
Paragraph("프로그램 등록 신청서 모음", S("ct", fontName=BBF, fontSize=28, textColor=WHITE, alignment=TA_CENTER, leading=36)),
Paragraph("GUARDiA ITSM v2.0 | (주)지오정보기술", S("cs", fontName=BF, fontSize=13, textColor=colors.HexColor("#BDE3FF"), alignment=TA_CENTER)),
]], colWidths=[WC])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 24),
("BOTTOMPADDING", (0,0), (-1,-1), 24),
("ROUNDEDCORNERS",(0,0), (-1,-1), [10]),
("SPAN", (0,0), (0,-1)),
]))
story.append(cover)
story.append(Spacer(1, 10*mm))
# 포함 문서 목록
docs_list = [
["01", "소프트웨어 저작권 등록 신청서", "한국저작권위원회", BLUE, LBLU],
["02", "소프트웨어사업자 신고서", "과학기술정보통신부", RED, LRED],
["03", "조달청 나라장터 물품 등록 신청서", "조달청", ORANGE, LORG],
]
for no, name, org, fc, bg in docs_list:
row = Table([[
Paragraph(no, S("n", fontName=BBF, fontSize=16, textColor=WHITE, alignment=TA_CENTER)),
Paragraph(name, S("nm", fontName=BBF, fontSize=12, textColor=fc)),
Paragraph(org, S("og", fontName=BF, fontSize=10, textColor=GRAY)),
]], colWidths=[14*mm, WC-14*mm-50*mm, 50*mm])
row.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), fc),
("BACKGROUND", (1,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LINEABOVE", (0,0), (-1,0), 0.3, colors.HexColor("#CBD5E1")),
]))
story.append(row)
story.append(Spacer(1, 2*mm))
story.append(Spacer(1, 8*mm))
# 제품 요약
prod = Table([[
Paragraph("제 품 명", sty["lbl"]),
Paragraph("GUARDiA ITSM (Good AI-based Unified Automated Resource & Device Intelligence Assistant)", sty["val"]),
],[
Paragraph("개 발 사", sty["lbl"]),
Paragraph("(주)지오정보기술", sty["val"]),
],[
Paragraph("버 전", sty["lbl"]),
Paragraph("2.0.0 (빌드일: 2026-08-31)", sty["val"]),
],[
Paragraph("제품 분류", sty["lbl"]),
Paragraph("IT서비스관리(ITSM) / AI 기반 인프라 자동화 플랫폼", sty["val"]),
]], colWidths=[30*mm, WC-30*mm])
prod.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LBLU),
("FONTNAME", (0,0), (0,-1), BBF),
("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(prod)
story.append(Spacer(1, 10*mm))
story.append(Paragraph(
f"※ 본 문서는 GUARDiA ITSM 프로그램 등록을 위한 신청서 모음입니다. 생성일: {datetime.now().strftime('%Y-%m-%d')}",
sty["note"]
))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════
# 문서 01: 소프트웨어 저작권 등록 신청서
# ════════════════════════════════════════════════════════════
story.append(page_header(
"소프트웨어 저작권 등록 신청서",
"컴퓨터프로그램저작물 등록 | 한국저작권위원회",
"신청서 01", BLUE
))
story.append(Spacer(1, 6*mm))
story.append(Paragraph("컴퓨터프로그램저작물 등록신청서", S("h1", fontName=BBF, fontSize=14, textColor=NAVY, alignment=TA_CENTER, leading=20)))
story.append(Paragraph("「저작권법」 제53조 및 「저작권법 시행규칙」 제24조에 따라 위와 같이 등록을 신청합니다.", S("ref", fontName=BF, fontSize=8.5, textColor=GRAY, alignment=TA_CENTER, leading=14)))
story.append(Spacer(1, 6*mm))
# 저작물 정보
story.append(sec_title("1. 저작물 정보", BLUE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["저 작 물 명", "GUARDiA ITSM (가이더)"],
["영 문 명", "GUARDiA ITSM Platform (Good AI-based Unified Automated Resource & Device Intelligence Assistant)"],
["저작물 종류", "컴퓨터프로그램저작물"],
["창 작 연 도", "2026년"],
["공 표 여 부", "공표"],
["공 표 연 도", "2026년"],
["공 표 방 법", "인터넷 웹사이트 (www.zioinfo.co.kr)"],
["이 용 허 락", "양도 및 이용허락 가능"],
["버 전", "2.0.0"],
]))
story.append(Spacer(1, 5*mm))
# 저작자 정보
story.append(sec_title("2. 저작자 정보", BLUE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["저 작 자 명", "(주)지오정보기술"],
["법인등록번호", "000000-0000000"],
["주 소", "서울특별시 (상세주소)"],
["대 표 자", "(대표이사명)"],
["전 화", "02-000-0000"],
["이 메 일", "copyright@zioinfo.co.kr"],
]))
story.append(Spacer(1, 5*mm))
# 저작물 설명
story.append(sec_title("3. 저작물 설명 (200자 이내)", BLUE))
story.append(Spacer(1, 3*mm))
desc = Table([[Paragraph(
"GUARDiA ITSM은 공공기관의 레거시 IT 인프라를 AI로 자율 운영하는 온프레미스 통합 관리 플랫폼입니다. "
"메신저 한 줄 명령으로 에이전트 설치 없이 SSH/SFTP를 통해 WAS 배포·운영을 자동화하며, "
"SR 관리, 인시던트 대응, 변경관리, CMDB, PMS 등 ITSM 전 기능을 제공합니다. "
"Python 3.11/FastAPI 백엔드, React 18 프론트엔드, Ollama 온프레미스 AI 엔진 사용.",
sty["body"]
)]], colWidths=[WC])
desc.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LBLU),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("ROUNDEDCORNERS",(0,0), (-1,-1), [5]),
]))
story.append(desc)
story.append(Spacer(1, 5*mm))
# 프로그램 언어
story.append(sec_title("4. 프로그램 작성 언어 및 환경", BLUE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["프로그램 언어", "Python 3.11, JavaScript (ECMAScript 2023)"],
["프레임워크", "FastAPI 0.115, React 18.3, SQLAlchemy 2.0"],
["운영 환경", "Linux (Ubuntu/CentOS/RHEL), Windows Server 2019+"],
["소스 규모", "Python 145파일 52,833줄, JavaScript 50파일 약 8,000줄"],
]))
story.append(Spacer(1, 5*mm))
# 제출 서류
story.append(sec_title("5. 첨부 서류", BLUE))
story.append(Spacer(1, 3*mm))
attach = [
["1", "저작물 설명서", "본 문서 3항 활용"],
["2", "소스코드 일부", "핵심 모듈 200줄 이상 (영업비밀 마스킹 후)"],
["3", "법인등기부등본", "최근 3개월 이내"],
["4", "대리인 위임장", "대리신청 시에만 해당"],
]
ah = [["번호", "서류명", "비고"]]
at = Table(
[[Paragraph(v, S(f"a{i}", alignment=TA_CENTER if i==0 else TA_LEFT, fontSize=9)) for i,v in enumerate(r)] for r in ah+attach],
colWidths=[12*mm, 70*mm, WC-82*mm]
)
at.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), BBF),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LBLU]),
("FONTSIZE", (0,0), (-1,-1), 9),
("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), 7),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(at)
story.append(Spacer(1, 6*mm))
# 신청 기관 안내
story.append(Paragraph("【 제출처 】 한국저작권위원회 | www.copyright.or.kr | Tel: 1800-5455 | 수수료: 약 40,000원", sty["note"]))
story.append(Spacer(1, 6*mm))
story.append(sign_box("신 청 일"))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("위와 같이 저작물 등록을 신청합니다.", S("sc2", fontName=BF, fontSize=9.5, alignment=TA_CENTER)))
for item in footer_bar("소프트웨어 저작권 등록 신청서"):
story.append(item)
story.append(PageBreak())
# ════════════════════════════════════════════════════════════
# 문서 02: 소프트웨어사업자 신고서
# ════════════════════════════════════════════════════════════
story.append(page_header(
"소프트웨어사업자 신고서",
"소프트웨어진흥법 제24조 | 과학기술정보통신부",
"신청서 02", RED
))
story.append(Spacer(1, 6*mm))
story.append(Paragraph("소프트웨어사업자 신고서", S("h2", fontName=BBF, fontSize=14, textColor=RED, alignment=TA_CENTER, leading=20)))
story.append(Paragraph("「소프트웨어진흥법」 제24조 및 같은 법 시행규칙 제14조에 따라 신고합니다.", S("ref2", fontName=BF, fontSize=8.5, textColor=GRAY, alignment=TA_CENTER, leading=14)))
story.append(Spacer(1, 6*mm))
story.append(sec_title("1. 신고인 (사업자) 정보", RED))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["상 호", "(주)지오정보기술"],
["대 표 자", "(대표이사명)"],
["사업자등록번호", "000-00-00000"],
["법인등록번호", "000000-0000000"],
["소 재 지", "서울특별시 (상세주소)"],
["전 화", "02-000-0000"],
["팩 스", "02-000-0001"],
["이 메 일", "sw@zioinfo.co.kr"],
["설 립 일", "2000년 월 일"],
["자 본 금", ""],
], lbg=LRED))
story.append(Spacer(1, 5*mm))
story.append(sec_title("2. 소프트웨어사업의 종류", RED))
story.append(Spacer(1, 3*mm))
biz_types = [
("V", "소프트웨어 개발업", "GUARDiA ITSM 등 소프트웨어 개발"),
("V", "소프트웨어 공급업", "GUARDiA ITSM 라이선스 공급"),
("V", "소프트웨어 유지관리업", "GUARDiA ITSM 유지보수 및 기술지원"),
("V", "소프트웨어 자문·평가·진단업", "IT인프라 컨설팅"),
(" ", "정보기술서비스업", "해당 없음"),
]
bh = [["선택", "사업 종류", "세부 내용"]]
bd = [[Paragraph(v if v==" " else "[V]", S(f"bt{i}", alignment=TA_CENTER, textColor=RED if v=="V" else GRAY, fontSize=10)),
Paragraph(n, S(f"bn{i}", fontSize=9.5)),
Paragraph(d, S(f"bd{i}", fontSize=8.5, textColor=GRAY))]
for i,(v,n,d) in enumerate(biz_types)]
bt = Table(bh+bd, colWidths=[16*mm, 65*mm, WC-81*mm])
bt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), BBF),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LRED]),
("FONTSIZE", (0,0), (-1,-1), 9),
("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), 7),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(bt)
story.append(Spacer(1, 5*mm))
story.append(sec_title("3. 기술인력 현황", RED))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["총 직원 수", ""],
["SW 기술인력", " 명 (전체의 %)"],
["정보처리기사", ""],
["정보처리산업기사",""],
["기타 SW 자격", ""],
], lbg=LRED))
story.append(Spacer(1, 3*mm))
story.append(Paragraph("※ 소프트웨어사업자 신고 기준: 상시근로자 1명 이상 또는 SW기술인력 1명 이상", sty["note"]))
story.append(Spacer(1, 5*mm))
story.append(sec_title("4. SW사업 실적 (최근 3년)", RED))
story.append(Spacer(1, 3*mm))
perf_h = [["연도", "발주기관", "사업명", "계약금액(백만원)", "기간"]]
perf_d = [
["2024", "(공공기관명)", "GUARDiA ITSM 구축사업", "", ""],
["2025", "(공공기관명)", "인프라 자동화 컨설팅", "", ""],
["2026", "(공공기관명)", "GUARDiA ITSM 고도화", "", ""],
]
pt = Table(perf_h+perf_d, colWidths=[14*mm, 40*mm, 60*mm, 30*mm, WC-144*mm])
pt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), BBF),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LRED]),
("FONTSIZE", (0,0), (-1,-1), 8.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), 5),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(pt)
story.append(Spacer(1, 5*mm))
story.append(Paragraph("【 신고처 】 한국SW산업협회(KOSA) / 과학기술정보통신부 | swit.or.kr | 수수료: 없음", sty["note"]))
story.append(Spacer(1, 5*mm))
story.append(sign_box("신 고 일"))
for item in footer_bar("소프트웨어사업자 신고서"):
story.append(item)
story.append(PageBreak())
# ════════════════════════════════════════════════════════════
# 문서 03: 조달청 나라장터 물품 등록 신청서
# ════════════════════════════════════════════════════════════
story.append(page_header(
"조달청 나라장터 물품 등록 신청서",
"국가종합전자조달시스템 | 조달청",
"신청서 03", ORANGE
))
story.append(Spacer(1, 6*mm))
story.append(Paragraph("소프트웨어 물품 등록 신청서", S("h3", fontName=BBF, fontSize=14, textColor=ORANGE, alignment=TA_CENTER, leading=20)))
story.append(Paragraph("「국가를 당사자로 하는 계약에 관한 법률」에 따라 공공기관 납품을 위한 물품을 등록합니다.", S("ref3", fontName=BF, fontSize=8.5, textColor=GRAY, alignment=TA_CENTER, leading=14)))
story.append(Spacer(1, 6*mm))
story.append(sec_title("1. 업체 정보", ORANGE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["업 체 명", "(주)지오정보기술"],
["대 표 자", "(대표이사명)"],
["사업자등록번호", "000-00-00000"],
["소 재 지", "서울특별시 (상세주소)"],
["대표 전화", "02-000-0000"],
["담당자명", "(담당자명) | 010-0000-0000"],
["이 메 일", "g2b@zioinfo.co.kr"],
], lbg=LORG))
story.append(Spacer(1, 5*mm))
story.append(sec_title("2. 물품 기본 정보", ORANGE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["물 품 명", "GUARDiA ITSM (AI 기반 IT서비스관리 플랫폼)"],
["규격/모델명", "GUARDiA ITSM v2.0"],
["물품 분류코드", "소프트웨어 > 시스템관리 > IT서비스관리"],
["단 위", "식 (라이선스)"],
["원산지", "국내산 (대한민국)"],
["제조사명", "(주)지오정보기술"],
["브랜드명", "GUARDiA"],
], lbg=LORG))
story.append(Spacer(1, 5*mm))
story.append(sec_title("3. 가격 정보 (라이선스 에디션별)", ORANGE))
story.append(Spacer(1, 3*mm))
price_h = [["에디션", "대상", "기준 단가(원)", "유지보수율", "비고"]]
price_d = [
["COMMUNITY", "소규모/검토용", "0", "", "무료"],
["STANDARD", "중형 기관", "별도 협의", "15%", "서버 50대·사용자 200명"],
["ENTERPRISE", "대형 관공서", "별도 협의", "15%", "무제한"],
]
prices = Table(price_h+price_d, colWidths=[28*mm, 30*mm, 38*mm, 22*mm, WC-118*mm])
prices.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), BBF),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LORG]),
("FONTSIZE", (0,0), (-1,-1), 9),
("ALIGN", (0,0), (-1,0), "CENTER"),
("ALIGN", (0,1), (-1,-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), 5),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(prices)
story.append(Spacer(1, 3*mm))
story.append(Paragraph("※ 나라장터 등록 단가는 실제 계약 시 협의를 통해 결정됩니다.", sty["note"]))
story.append(Spacer(1, 5*mm))
story.append(sec_title("4. 제품 규격 (설치 환경)", ORANGE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["서버 OS", "Ubuntu 20.04+ / CentOS 7+ / RHEL 8+ / Windows Server 2019+"],
["최소 CPU", "4코어 이상 (Intel/AMD x86_64)"],
["최소 RAM", "16GB 이상 (Ollama AI 엔진 포함)"],
["디스크 용량", "50GB 이상"],
["네트워크", "내부망 전용 가능 (인터넷 연결 불필요 — 완전 폐쇄망 지원)"],
["클라이언트", "Chrome 90+ / Firefox 88+ / Edge 90+ (브라우저 기반)"],
["DB", "SQLite (내장) / PostgreSQL 15+ (별도 설치)"],
], lbg=LORG))
story.append(Spacer(1, 5*mm))
story.append(sec_title("5. 주요 기능 요약", ORANGE))
story.append(Spacer(1, 3*mm))
funcs = [
("SR/ITSM", "서비스요청 접수·처리, 인시던트, 변경관리, SLA, CMDB"),
("AI 자동화", "Ollama 온프레미스 AI — 티켓분류·RCA·이상탐지·예측"),
("ChatOps", "카카오워크·네이버웍스·슬랙 25개 봇 명령어"),
("에이전트리스", "SSH/SFTP 기반 WAS 자동 배포·운영 (설치 없음)"),
("PMS", "WBS·산출물·일간/주간/월간 보고서 자동 생성"),
("보안", "JWT+MFA+AES-256+PAM+감사로그+취약점스캔"),
]
fh = [["기능 영역", "내용"]]
fd = [[Paragraph(k, S(f"fk{i}", fontName=BBF, fontSize=9)), Paragraph(v, S(f"fv{i}", fontSize=9))] for i,(k,v) in enumerate(funcs)]
ft = Table(fh+fd, colWidths=[30*mm, WC-30*mm])
ft.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), BBF),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LORG]),
("FONTSIZE", (0,0), (-1,-1), 9),
("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), 7),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(ft)
story.append(Spacer(1, 5*mm))
story.append(sec_title("6. 납품 실적 및 인증 현황", ORANGE))
story.append(Spacer(1, 3*mm))
story.append(info_row([
["GS인증", "GS 1등급 취득 예정 (TTA, 2026년 12월)"],
["SW저작권 등록", "C-2026-XXXXXX (등록 후 기입)"],
["SW사업자 신고", "제XXX호 (신고 후 기입)"],
["주요 납품 실적", "(공공기관 납품 실적 기입)"],
], lbg=LORG))
story.append(Spacer(1, 5*mm))
story.append(Paragraph("【 등록처 】 조달청 나라장터 (g2b.go.kr) → 업체 로그인 → 물품 등록 신청 | 수수료: 없음", sty["note"]))
story.append(Spacer(1, 5*mm))
story.append(sign_box("신 청 일"))
for item in footer_bar("조달청 나라장터 물품 등록 신청서"):
story.append(item)
# ── 빌드 ──────────────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 7)
canvas.setFillColor(GRAY)
canvas.drawRightString(W-MARGIN, 9*mm, f"- {doc.page} -")
canvas.drawString(MARGIN, 9*mm, "GUARDiA ITSM 프로그램 등록 신청서 | (주)지오정보기술")
canvas.restoreState()
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
size = Path(out).stat().st_size / 1024
print(f"\n[OK] PDF 생성 완료: {out}")
print(f" 크기: {size:.1f} KB / 4페이지 (표지+3종 신청서)")
if __name__ == "__main__":
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
out = Path(__file__).parent / "프로그램등록_신청서_GUARDiA_ITSM_v2.0.pdf"
print("GUARDiA ITSM 프로그램 등록 신청서 PDF 생성 중...")
build(str(out))