""" 리비전 비교 전용 서비스 두 리비전 간의 자재 비교 및 차이점 분석 """ from sqlalchemy.orm import Session from sqlalchemy import text from typing import List, Dict, Any, Optional, Tuple from decimal import Decimal import hashlib from datetime import datetime from ..models import Material, File from ..utils.logger import get_logger from .database_service import DatabaseService logger = get_logger(__name__) class RevisionComparisonService: """리비전 비교 전용 서비스""" def __init__(self, db: Session): self.db = db self.db_service = DatabaseService(db) def compare_revisions( self, current_file_id: int, previous_file_id: int, category_filter: Optional[str] = None ) -> Dict[str, Any]: """ 두 리비전 간 자재 비교 Args: current_file_id: 현재 리비전 파일 ID previous_file_id: 이전 리비전 파일 ID category_filter: 특정 카테고리만 비교 (선택사항) Returns: 비교 결과 딕셔너리 """ # 이전/현재 자재 조회 previous_materials = self._get_materials_for_comparison(previous_file_id, category_filter) current_materials = self._get_materials_for_comparison(current_file_id, category_filter) # 비교 수행 comparison_result = { "comparison_date": datetime.now().isoformat(), "current_file_id": current_file_id, "previous_file_id": previous_file_id, "category_filter": category_filter, "summary": { "previous_count": len(previous_materials), "current_count": len(current_materials), "unchanged": 0, "modified": 0, "added": 0, "removed": 0 }, "changes": { "unchanged": [], "modified": [], "added": [], "removed": [] } } # 이전 자재 기준으로 비교 for key, prev_material in previous_materials.items(): if key in current_materials: curr_material = current_materials[key] # 자재 변경 여부 확인 if self._is_material_changed(prev_material, curr_material): comparison_result["changes"]["modified"].append({ "key": key, "previous": prev_material, "current": curr_material, "changes": self._get_material_changes(prev_material, curr_material) }) comparison_result["summary"]["modified"] += 1 else: comparison_result["changes"]["unchanged"].append({ "key": key, "material": curr_material }) comparison_result["summary"]["unchanged"] += 1 else: # 제거된 자재 comparison_result["changes"]["removed"].append({ "key": key, "material": prev_material }) comparison_result["summary"]["removed"] += 1 # 신규 자재 for key, curr_material in current_materials.items(): if key not in previous_materials: comparison_result["changes"]["added"].append({ "key": key, "material": curr_material }) comparison_result["summary"]["added"] += 1 return comparison_result def get_category_comparison( self, current_file_id: int, previous_file_id: int, category: str ) -> Dict[str, Any]: """특정 카테고리의 리비전 비교""" return self.compare_revisions(current_file_id, previous_file_id, category) # PIPE 관련 메서드는 별도 처리 예정 def _get_materials_for_comparison( self, file_id: int, category_filter: Optional[str] = None ) -> Dict[str, Dict]: """비교용 자재 데이터 조회""" query = """ SELECT m.id, m.original_description, m.classified_category, m.material_grade, m.schedule, m.size_spec, m.main_nom, m.red_nom, m.quantity, m.unit, m.length, m.drawing_name, m.line_no, m.purchase_confirmed, m.confirmed_quantity, m.purchase_status, m.material_hash, m.revision_status, m.brand, m.user_requirement, m.line_number, m.is_active, -- 비교 키 생성 (PIPE 제외) COALESCE(m.material_hash, CONCAT(m.original_description, '|', COALESCE(m.material_grade, ''), '|', COALESCE(m.size_spec, ''))) as comparison_key FROM materials m WHERE m.file_id = :file_id AND m.is_active = true """ params = {"file_id": file_id} if category_filter: query += " AND m.classified_category = :category" params["category"] = category_filter # PIPE 카테고리는 제외 query += " AND m.classified_category != 'PIPE'" query += " ORDER BY m.line_number" result = self.db_service.execute_query(query, params) materials = {} for row in result.fetchall(): row_dict = dict(row._mapping) comparison_key = row_dict['comparison_key'] # PIPE 제외한 일반 자재 처리 materials[comparison_key] = row_dict return materials def _is_material_changed(self, prev_material: Dict, curr_material: Dict) -> bool: """자재 변경 여부 확인""" # 주요 필드 비교 compare_fields = ['quantity', 'material_grade', 'schedule', 'size_spec', 'main_nom', 'red_nom', 'unit', 'length'] for field in compare_fields: prev_val = prev_material.get(field) curr_val = curr_material.get(field) # 수치 필드는 부동소수점 오차 고려 if field in ['quantity', 'length']: if prev_val is not None and curr_val is not None: if abs(float(prev_val) - float(curr_val)) > 0.001: return True elif prev_val != curr_val: return True else: if prev_val != curr_val: return True return False def _get_material_changes(self, prev_material: Dict, curr_material: Dict) -> Dict[str, Any]: """자재 변경 내용 상세 분석""" changes = {} compare_fields = ['quantity', 'material_grade', 'schedule', 'size_spec', 'main_nom', 'red_nom', 'unit', 'length'] for field in compare_fields: prev_val = prev_material.get(field) curr_val = curr_material.get(field) if field in ['quantity', 'length']: if prev_val is not None and curr_val is not None: if abs(float(prev_val) - float(curr_val)) > 0.001: changes[field] = { "previous": float(prev_val), "current": float(curr_val), "change": float(curr_val) - float(prev_val) } elif prev_val != curr_val: changes[field] = { "previous": prev_val, "current": curr_val } else: if prev_val != curr_val: changes[field] = { "previous": prev_val, "current": curr_val } return changes