Files
M-Project/HISTORY_TRACKING_IDEAS.md
Hyungi Ahn c680453227 feat: 관리함 통합 수정 모달 및 히스토리 추적 방안 구현
🔧 통합 수정 모달:
- 모든 진행 중 상태에서 '확인' 버튼으로 모달 열기
- 완료 대기 상태에서도 '수정' 버튼으로 동일 모달 사용
- 6열 와이드 모달로 모든 정보 한눈에 표시

📝 모달 구성:
- 왼쪽: 기본 정보 (프로젝트, 부적합명, 상세내용, 원인분류, 업로드 사진)
- 오른쪽: 관리 정보 (해결방안, 담당부서/자, 조치예상일) + 완료 신청 정보

🎯 버튼 시스템 개선:
- 일반 진행 중: 저장 | 확인 | 완료처리
- 완료 대기: 반려 | 수정 | 최종확인
- 모달에서 통합 수정 가능

✏️ 수정 기능:
- 부적합명, 상세내용 직접 수정
- 해결방안, 담당부서/자, 조치예상일 수정
- 모달에서 저장 시 실시간 반영

📋 히스토리 추적 방안 문서화:
- 단일 히스토리 테이블 vs 페이지별 테이블 비교
- 변경 이력 기록 서비스 클래스 설계
- 프론트엔드 히스토리 조회 모달 구현 방안
- 감사 추적, 데이터 복구, 보안 고려사항 포함

🔍 구현 우선순위:
- Phase 1: 기본 히스토리 테이블 + 관리함 이력
- Phase 2: 수신함 이력 + 히스토리 UI
- Phase 3: 데이터 복구 + 감사 보고서

💡 추가 아이디어:
- 변경 승인 워크플로우
- 자동 백업 시스템
- 변경 영향도 분석

Expected Result:
 모든 진행 중 상태에서 통합 수정 모달 사용
 완료 대기 상태 정보 포함 표시
 체계적인 히스토리 추적 방안 수립
 투명하고 추적 가능한 이슈 관리 기반 마련
2025-10-26 13:11:26 +09:00

11 KiB
Raw Permalink Blame History

📝 수정 히스토리 저장 방안

🎯 목적

  • 각 페이지에서 수정한 내용의 변경 이력 추적
  • 누가, 언제, 무엇을, 왜 변경했는지 기록
  • 데이터 무결성 및 감사 추적 (Audit Trail) 제공

🗄️ DB 구조 방안

방안 1: 단일 히스토리 테이블 (권장)

CREATE TABLE issue_history (
    id SERIAL PRIMARY KEY,
    issue_id INTEGER NOT NULL REFERENCES issues(id),
    changed_by_id INTEGER NOT NULL REFERENCES users(id),
    changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    change_type VARCHAR(50) NOT NULL, -- 'UPDATE', 'STATUS_CHANGE', 'COMPLETION_REQUEST' 등
    page_source VARCHAR(50) NOT NULL, -- 'INBOX', 'MANAGEMENT', 'DASHBOARD' 등
    field_name VARCHAR(100), -- 변경된 필드명
    old_value TEXT, -- 이전 값 (JSON 형태로 저장 가능)
    new_value TEXT, -- 새로운 값 (JSON 형태로 저장 가능)
    change_reason TEXT, -- 변경 사유 (선택사항)
    session_id VARCHAR(100), -- 동일 세션에서 여러 필드 변경 시 그룹핑
    ip_address INET, -- 변경자 IP 주소
    user_agent TEXT -- 브라우저 정보
);

-- 인덱스 생성
CREATE INDEX idx_issue_history_issue_id ON issue_history(issue_id);
CREATE INDEX idx_issue_history_changed_at ON issue_history(changed_at);
CREATE INDEX idx_issue_history_changed_by ON issue_history(changed_by_id);

방안 2: 페이지별 히스토리 테이블

-- 수신함 히스토리
CREATE TABLE inbox_history (
    id SERIAL PRIMARY KEY,
    issue_id INTEGER NOT NULL REFERENCES issues(id),
    action_type VARCHAR(50), -- 'REVIEW', 'DISPOSE', 'STATUS_CHANGE'
    old_data JSONB,
    new_data JSONB,
    changed_by_id INTEGER NOT NULL REFERENCES users(id),
    changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 관리함 히스토리
CREATE TABLE management_history (
    id SERIAL PRIMARY KEY,
    issue_id INTEGER NOT NULL REFERENCES issues(id),
    action_type VARCHAR(50), -- 'UPDATE', 'COMPLETION_REQUEST', 'APPROVAL'
    old_data JSONB,
    new_data JSONB,
    changed_by_id INTEGER NOT NULL REFERENCES users(id),
    changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

🔧 구현 방안

1 백엔드 구현 (FastAPI)

히스토리 서비스 클래스

# services/history_service.py
from typing import Dict, Any, Optional
import json
from datetime import datetime
from sqlalchemy.orm import Session

class HistoryService:
    @staticmethod
    def log_change(
        db: Session,
        issue_id: int,
        changed_by_id: int,
        change_type: str,
        page_source: str,
        old_data: Dict[str, Any],
        new_data: Dict[str, Any],
        change_reason: Optional[str] = None,
        session_id: Optional[str] = None
    ):
        """변경 이력 기록"""
        
        # 변경된 필드들 찾기
        changed_fields = []
        for field, new_value in new_data.items():
            old_value = old_data.get(field)
            if old_value != new_value:
                history_entry = IssueHistory(
                    issue_id=issue_id,
                    changed_by_id=changed_by_id,
                    change_type=change_type,
                    page_source=page_source,
                    field_name=field,
                    old_value=json.dumps(old_value) if old_value else None,
                    new_value=json.dumps(new_value) if new_value else None,
                    change_reason=change_reason,
                    session_id=session_id
                )
                db.add(history_entry)
                changed_fields.append(field)
        
        db.commit()
        return changed_fields

    @staticmethod
    def get_issue_history(db: Session, issue_id: int, limit: int = 50):
        """이슈의 변경 이력 조회"""
        return db.query(IssueHistory)\
                 .filter(IssueHistory.issue_id == issue_id)\
                 .order_by(IssueHistory.changed_at.desc())\
                 .limit(limit)\
                 .all()

API 엔드포인트에서 히스토리 기록

# routers/management.py
from services.history_service import HistoryService

@router.put("/{issue_id}")
async def update_issue(
    issue_id: int,
    update_request: ManagementUpdateRequest,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    # 기존 이슈 데이터 백업
    issue = db.query(Issue).filter(Issue.id == issue_id).first()
    old_data = {
        "final_description": issue.final_description,
        "solution": issue.solution,
        "responsible_department": issue.responsible_department,
        "responsible_person": issue.responsible_person,
        "expected_completion_date": issue.expected_completion_date.isoformat() if issue.expected_completion_date else None
    }
    
    # 업데이트 수행
    update_data = update_request.dict(exclude_unset=True)
    for field, value in update_data.items():
        setattr(issue, field, value)
    
    db.commit()
    db.refresh(issue)
    
    # 새로운 데이터
    new_data = {
        "final_description": issue.final_description,
        "solution": issue.solution,
        "responsible_department": issue.responsible_department,
        "responsible_person": issue.responsible_person,
        "expected_completion_date": issue.expected_completion_date.isoformat() if issue.expected_completion_date else None
    }
    
    # 히스토리 기록
    session_id = str(uuid.uuid4())
    HistoryService.log_change(
        db=db,
        issue_id=issue_id,
        changed_by_id=current_user.id,
        change_type="UPDATE",
        page_source="MANAGEMENT",
        old_data=old_data,
        new_data=new_data,
        session_id=session_id
    )
    
    return {"message": "업데이트 완료", "changed_fields": list(update_data.keys())}

2 프론트엔드 구현

히스토리 조회 모달

// 히스토리 조회 함수
async function showIssueHistory(issueId) {
    try {
        const response = await fetch(`/api/issues/${issueId}/history`, {
            headers: {
                'Authorization': `Bearer ${localStorage.getItem('access_token')}`
            }
        });
        
        if (response.ok) {
            const history = await response.json();
            openHistoryModal(issueId, history);
        }
    } catch (error) {
        console.error('히스토리 조회 실패:', error);
    }
}

// 히스토리 모달 생성
function openHistoryModal(issueId, history) {
    const modalContent = `
        <div class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" id="historyModal">
            <div class="bg-white rounded-xl shadow-xl max-w-4xl w-full mx-4 max-h-[80vh] overflow-y-auto">
                <div class="p-6">
                    <div class="flex items-center justify-between mb-6">
                        <h3 class="text-xl font-semibold">
                            <i class="fas fa-history text-blue-500 mr-2"></i>
                            변경 이력 - No.${issueId}
                        </h3>
                        <button onclick="closeHistoryModal()" class="text-gray-400 hover:text-gray-600">
                            <i class="fas fa-times text-xl"></i>
                        </button>
                    </div>
                    
                    <div class="space-y-4">
                        ${history.map(entry => `
                            <div class="border-l-4 border-blue-400 bg-blue-50 p-4 rounded-r-lg">
                                <div class="flex justify-between items-start mb-2">
                                    <div class="flex items-center space-x-2">
                                        <span class="font-semibold text-blue-800">${entry.field_name}</span>
                                        <span class="px-2 py-1 bg-blue-200 text-blue-800 text-xs rounded-full">${entry.page_source}</span>
                                    </div>
                                    <span class="text-sm text-gray-500">${new Date(entry.changed_at).toLocaleString('ko-KR')}</span>
                                </div>
                                <div class="grid grid-cols-2 gap-4 text-sm">
                                    <div>
                                        <span class="font-medium text-red-600">이전:</span>
                                        <p class="text-gray-700 bg-red-50 p-2 rounded mt-1">${entry.old_value || '없음'}</p>
                                    </div>
                                    <div>
                                        <span class="font-medium text-green-600">변경:</span>
                                        <p class="text-gray-700 bg-green-50 p-2 rounded mt-1">${entry.new_value || '없음'}</p>
                                    </div>
                                </div>
                                <div class="mt-2 text-sm text-gray-600">
                                    <i class="fas fa-user mr-1"></i>변경자: ${entry.changed_by_name}
                                </div>
                            </div>
                        `).join('')}
                    </div>
                </div>
            </div>
        </div>
    `;
    
    document.body.insertAdjacentHTML('beforeend', modalContent);
}

📊 히스토리 활용 방안

1 관리자 대시보드

  • 최근 변경 사항 요약
  • 사용자별 활동 통계
  • 페이지별 수정 빈도

2 감사 보고서

  • 특정 기간 동안의 모든 변경 사항
  • 중요 필드 변경 알림
  • 규정 준수 확인

3 데이터 복구

  • 잘못된 변경 사항 롤백
  • 특정 시점으로 데이터 복원
  • 변경 사항 비교 및 분석

🔒 보안 고려사항

1 접근 권한

  • 히스토리 조회 권한 분리
  • 민감한 정보 마스킹
  • 관리자만 전체 히스토리 접근

2 데이터 보호

  • 히스토리 데이터 암호화
  • 개인정보 자동 삭제 정책
  • 백업 및 아카이빙

🚀 구현 우선순위

Phase 1 (필수)

  1. 기본 히스토리 테이블 생성
  2. 관리함 수정 이력 기록
  3. 간단한 히스토리 조회 API

Phase 2 (확장)

  1. 수신함 처리 이력 기록
  2. 히스토리 조회 UI 구현
  3. 변경 사유 입력 기능

Phase 3 (고급)

  1. 데이터 복구 기능
  2. 감사 보고서 생성
  3. 실시간 변경 알림

💡 추가 아이디어

1 변경 승인 워크플로우

  • 중요한 변경사항은 승인 후 적용
  • 변경 요청 → 검토 → 승인/반려

2 자동 백업

  • 중요 변경 전 자동 스냅샷
  • 일정 주기별 전체 데이터 백업

3 변경 영향도 분석

  • 연관된 다른 이슈에 미치는 영향
  • 변경으로 인한 통계 변화

이러한 히스토리 시스템을 통해 투명하고 추적 가능한 이슈 관리가 가능해집니다! 🎯