# [Specification] 작업 타임테이블 + Excel 다운로드 > SM 운영, 정기점검, PM, SR 기반 작업 등 모든 수행 이력을 타임테이블로 관리하고 > Excel 파일로 다운로드하는 기능 명세. --- ## 1. 타임테이블 개요 ### 1.1. 목적 | 목적 | 설명 | |------|------| | 작업 이력 관리 | 정기점검·PM·SR·장애대응 모든 작업을 단일 화면에서 통합 관리 | | 감사 자료 제출 | 기관·감사원 요청 시 Excel로 즉시 제출 가능한 형태 유지 | | PM 보고 자료 | 월간/분기 활동 보고서 자동 생성 기반 데이터 | | 작업 표준화 | 수행 명령어·쉘 스크립트를 기록해 재사용 가능하도록 관리 | ### 1.2. 작업 유형 | work_type | 한글명 | 설명 | |-----------|--------|------| | `REGULAR_CHECK` | 정기점검 | 월간·분기·반기·연간 계획된 점검 | | `PM` | 예방 정비 | 하드웨어 교체, 패치 적용, 취약점 조치 | | `SR` | SR 기반 작업 | ITSM SR과 연계된 작업 | | `ADHOC` | 수시점검 | 비정기 확인, 고객 요청 확인 | | `DEPLOY` | 배포 작업 | 소스 배포, 설정 변경 | | `EMERGENCY` | 긴급 대응 | 장애 복구, 긴급 조치 | --- ## 2. 데이터 모델 ### 2.1. 주요 컬럼 상세 | 컬럼 | 타입 | 필수 | 설명 | |------|------|------|------| | `timetable_id` | BIGINT | PK | | | `work_type` | VARCHAR(30) | ✅ | 작업 유형 | | `title` | VARCHAR(200) | ✅ | 작업 제목 (예: 2025 Q1 정기점검 - 기획재정부) | | `inst_id` | VARCHAR(20) | | 대상 기관 | | `server_id` | VARCHAR(30) | | 대상 서버 (복수면 JSON) | | `sr_id` | VARCHAR(30) | | 연계 SR ID | | `script_id` | BIGINT | | 사용 쉘 스크립트 ID | | `scheduled_at` | TIMESTAMP | ✅ | 처리 예정 일시 | | `started_at` | TIMESTAMP | | 실제 시작 일시 | | `completed_at` | TIMESTAMP | | 완료 일시 | | `content` | TEXT | ✅ | 처리 내용 (무엇을, 어떻게, 왜) | | `command_or_shell` | TEXT | | 실행한 명령어 또는 쉘 이름 | | `result` | TEXT | | 처리 결과 (성공/실패 내용) | | `result_status` | VARCHAR(20) | | PENDING / SUCCESS / FAILED / PARTIAL | | `assignee` | VARCHAR(50) | ✅ | 담당 엔지니어 사번 | | `reviewer` | VARCHAR(50) | | PM/검토자 사번 | | `attachments` | JSONB | | 첨부파일 목록 | --- ## 3. API 엔드포인트 | 메서드 | URL | 권한 | 설명 | |--------|-----|------|------| | `GET` | `/api/timetable` | ADMIN, PM, ENGINEER | 타임테이블 목록 (필터링 지원) | | `GET` | `/api/timetable/{id}` | ADMIN, PM, ENGINEER | 상세 조회 | | `POST` | `/api/timetable` | ENGINEER, PM, ADMIN | 새 작업 등록 | | `PATCH` | `/api/timetable/{id}` | ENGINEER, PM, ADMIN | 수정 | | `DELETE` | `/api/timetable/{id}` | ADMIN | 삭제 | | `GET` | `/api/timetable/export/excel` | ADMIN, PM | Excel 다운로드 | | `GET` | `/api/timetable/stats` | ADMIN, PM | 기간별 통계 | ### 3.1. 목록 조회 쿼리 파라미터 ``` GET /api/timetable ?inst_code=MOF # 기관 필터 &work_type=REGULAR_CHECK # 작업 유형 필터 &result_status=SUCCESS # 결과 상태 필터 &assignee=EMP-0101 # 담당자 필터 &date_from=2025-01-01 # 시작일 &date_to=2025-03-31 # 종료일 &keyword=점검 # 제목/내용 검색 &skip=0&limit=50 ``` ### 3.2. Excel 다운로드 쿼리 파라미터 ``` GET /api/timetable/export/excel ?inst_code=MOF &date_from=2025-01-01 &date_to=2025-03-31 &work_type=REGULAR_CHECK ``` **응답**: `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` **파일명**: `GUARDiA_작업이력_MOF_20250101_20250331.xlsx` --- ## 4. Excel 파일 구조 > `openpyxl` 라이브러리 사용 (on-premise, 외부 API 호출 없음) ### 4.1. 시트 구성 | 시트 | 내용 | |------|------| | `작업이력` | 전체 타임테이블 데이터 | | `통계요약` | 작업 유형별·결과별 집계 | | `서버현황` | 대상 서버 목록 (민감정보 제외) | ### 4.2. 작업이력 시트 컬럼 | # | 컬럼명 | 데이터 | |---|--------|--------| | A | 번호 | 순번 | | B | 작업유형 | 정기점검 / PM / SR작업 / 수시점검 | | C | 제목 | work timetable title | | D | 기관명 | inst_name | | E | 대상서버 | server_id (hostname) | | F | 처리예정일시 | scheduled_at | | G | 시작일시 | started_at | | H | 완료일시 | completed_at | | I | 소요시간(분) | completed_at - started_at | | J | 처리내용 | content | | K | 명령어/쉘 | command_or_shell | | L | 처리결과 | result | | M | 결과상태 | SUCCESS / FAILED / PARTIAL | | N | 담당자 | assignee 이름 | | O | 검토자 | reviewer 이름 | | P | SR번호 | sr_id | | Q | 비고 | note | ### 4.3. 스타일 규칙 ```python # 헤더 스타일 header_fill = PatternFill("solid", fgColor="1E3A5F") # 네이비 header_font = Font(name="맑은 고딕", bold=True, color="FFFFFF", size=11) header_align = Alignment(horizontal="center", vertical="center") # 데이터 행 교대 색상 row_fill_even = PatternFill("solid", fgColor="F2F6FA") row_fill_odd = None # 흰색 # 결과 상태 강조 색상 status_colors = { "SUCCESS": "C6EFCE", # 연초록 "FAILED": "FFC7CE", # 연빨강 "PARTIAL": "FFEB9C", # 연노랑 "PENDING": "F4F4F4", # 연회색 } ``` --- ## 5. 프론트엔드 화면 구성 ### 5.1. 타임테이블 뷰 (`view-timetable`) ``` ┌─────────────────────────────────────────────────────────┐ │ 📅 작업 타임테이블 [+ 작업 등록] [📥 Excel] │ ├─────────────────────────────────────────────────────────┤ │ [기관▼] [작업유형▼] [결과▼] [날짜범위] [담당자] [검색] │ ├─────────────────────────────────────────────────────────┤ │ 날짜/시간 │ 유형 │ 제목 │ 기관 │ │ │ │ │ │ │ 2025-03-15 14:00 │ 정기점검 │ 1분기 정기점검 │ MOF │ │ │ │ - 기획재정부 │ ✅완료 │ │ │ │ │ │ │ 2025-03-20 09:00 │ SR작업 │ SR-20250320-001 │ MOIS │ │ │ │ WAS 재기동 │ ⚠일부 │ └───────────────────────────────────────────────────────--┘ ``` ### 5.2. 신규 등록 모달 ``` ┌─ 작업 등록 ──────────────────────────────────────────┐ │ 작업유형 [정기점검 ▼] 기관 [MOF ▼] │ │ 제목 [___________________________________] │ │ 대상서버 [MOF-WAS-01 ▼] SR연계 [선택 ▼] │ │ 처리예정 [2025-03-15] [14:00] │ │ │ │ 처리내용 │ │ [──────────────────────────────────────────] │ │ [ ] │ │ │ │ 명령어/쉘 스크립트 │ │ [쉘 선택 ▼] 또는 직접 입력 │ │ [──────────────────────────────────────────] │ │ │ │ 처리결과 [___________________________] │ │ 결과상태 [완료 ✅ / 부분완료 ⚠ / 실패 ❌] │ │ 담당자 [김엔지니어 ▼] 검토자 [이PM ▼] │ │ │ │ [취소] [저장] │ └───────────────────────────────────────────────────────┘ ``` --- ## 6. Excel 생성 코드 구조 (서버측) ```python # routers/timetable.py from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from fastapi.responses import StreamingResponse import io @router.get("/export/excel") async def export_excel( inst_code: str | None = Query(None), date_from: date | None = Query(None), date_to: date | None = Query(None), work_type: str | None = Query(None), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): # 1. 데이터 조회 rows = await _fetch_timetable(db, inst_code, date_from, date_to, work_type) # 2. Workbook 생성 wb = Workbook() ws = wb.active ws.title = "작업이력" # 3. 헤더 작성 headers = ["번호", "작업유형", "제목", "기관명", "대상서버", "처리예정", "시작", "완료", "소요(분)", "처리내용", "명령어/쉘", "처리결과", "결과상태", "담당자", "검토자", "SR번호", "비고"] _write_header(ws, headers) # 4. 데이터 작성 for i, row in enumerate(rows, start=2): _write_row(ws, i, row) # 5. 통계 시트 추가 _add_stats_sheet(wb, rows) # 6. StreamingResponse 반환 buf = io.BytesIO() wb.save(buf) buf.seek(0) filename = f"GUARDiA_작업이력_{inst_code or 'ALL'}_{date.today():%Y%m%d}.xlsx" return StreamingResponse( buf, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": f"attachment; filename*=UTF-8''{filename}"} ) ``` --- ## 7. 의존성 ``` openpyxl>=3.1.0 # Excel 생성 (on-premise, 외부 API 없음) ``` `requirements.txt`에 추가: ``` openpyxl==3.1.2 ```