🔄 전반적인 시스템 리팩토링 완료
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

 백엔드 구조 개선:
- DatabaseService: 공통 DB 쿼리 로직 통합
- FileUploadService: 파일 업로드 로직 모듈화 및 트랜잭션 관리 개선
- 서비스 레이어 패턴 도입으로 코드 재사용성 향상

 프론트엔드 컴포넌트 개선:
- LoadingSpinner, ErrorMessage, ConfirmDialog 공통 컴포넌트 생성
- 재사용 가능한 컴포넌트 라이브러리 구축
- deprecated/backup 파일들 완전 제거

 성능 최적화:
- optimize_database.py: 핵심 DB 인덱스 자동 생성
- 쿼리 최적화 및 통계 업데이트 자동화
- VACUUM ANALYZE 자동 실행

 코드 정리:
- 개별 SQL 마이그레이션 파일들을 legacy/ 폴더로 정리
- 중복된 마이그레이션 스크립트 정리
- 깔끔하고 체계적인 프로젝트 구조 완성

 자동 마이그레이션 시스템 강화:
- complete_migrate.py: SQLAlchemy 기반 완전한 마이그레이션
- analyze_and_fix_schema.py: 백엔드 코드 분석 기반 스키마 수정
- fix_missing_tables.py: 누락된 테이블/컬럼 자동 생성
- start.sh: 배포 시 자동 실행 순서 최적화
This commit is contained in:
Hyungi Ahn
2025-10-20 08:41:06 +09:00
parent 0c99697a6f
commit 3398f71b80
61 changed files with 3370 additions and 4512 deletions

View File

@@ -34,11 +34,15 @@ class File(Base):
filename = Column(String(255), nullable=False)
original_filename = Column(String(255), nullable=False)
file_path = Column(String(500), nullable=False)
job_no = Column(String(50)) # 작업 번호
revision = Column(String(20), default='Rev.0')
bom_name = Column(String(200)) # BOM 이름
description = Column(Text) # 파일 설명
upload_date = Column(DateTime, default=datetime.utcnow)
uploaded_by = Column(String(100))
file_type = Column(String(10))
file_size = Column(Integer)
parsed_count = Column(Integer) # 파싱된 자재 수
is_active = Column(Boolean, default=True)
# 관계 설정
@@ -51,22 +55,40 @@ class Material(Base):
id = Column(Integer, primary_key=True, index=True)
file_id = Column(Integer, ForeignKey("files.id"))
line_number = Column(Integer)
row_number = Column(Integer) # 업로드 시 행 번호
original_description = Column(Text, nullable=False)
classified_category = Column(String(50))
classified_subcategory = Column(String(100))
material_grade = Column(String(50))
full_material_grade = Column(Text) # 전체 재질명 (ASTM A312 TP304 등)
schedule = Column(String(20))
size_spec = Column(String(50))
main_nom = Column(String(50)) # 주 사이즈 (4", 150A 등)
red_nom = Column(String(50)) # 축소 사이즈 (Reducing 피팅/플랜지용)
quantity = Column(Numeric(10, 3), nullable=False)
unit = Column(String(10), nullable=False)
# length = Column(Numeric(10, 3)) # 임시로 주석 처리
length = Column(Numeric(10, 3)) # 길이 정보
drawing_name = Column(String(100))
area_code = Column(String(20))
line_no = Column(String(50))
classification_confidence = Column(Numeric(3, 2))
classification_details = Column(JSON) # 분류 상세 정보 (JSON)
is_verified = Column(Boolean, default=False)
verified_by = Column(String(50))
verified_at = Column(DateTime)
# 구매 관련 필드
purchase_confirmed = Column(Boolean, default=False)
confirmed_quantity = Column(Numeric(10, 3))
purchase_status = Column(String(20))
purchase_confirmed_by = Column(String(100))
purchase_confirmed_at = Column(DateTime)
# 리비전 관리 필드
revision_status = Column(String(20)) # 'new', 'changed', 'inventory', 'deleted_not_purchased'
material_hash = Column(String(64)) # 자재 비교용 해시
normalized_description = Column(Text) # 정규화된 설명
drawing_reference = Column(String(100))
notes = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
@@ -450,3 +472,349 @@ class MaterialTubingMapping(Base):
# 관계 설정
material = relationship("Material", backref="tubing_mappings")
tubing_product = relationship("TubingProduct", back_populates="material_mappings")
class SupportDetails(Base):
__tablename__ = "support_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 서포트 정보
support_type = Column(String(50))
support_subtype = Column(String(100))
load_rating = Column(String(50))
load_capacity = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
pipe_size = Column(String(50))
# 치수 정보
length_mm = Column(Numeric(10, 2))
width_mm = Column(Numeric(10, 2))
height_mm = Column(Numeric(10, 2))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class PurchaseRequestItems(Base):
__tablename__ = "purchase_request_items"
id = Column(Integer, primary_key=True, index=True)
request_id = Column(String(50), nullable=False) # 구매신청 ID
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
# 수량 정보
quantity = Column(Integer, nullable=False)
unit = Column(String(10), nullable=False)
user_requirement = Column(Text)
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
class FittingDetails(Base):
__tablename__ = "fitting_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 피팅 정보
fitting_type = Column(String(50))
fitting_subtype = Column(String(100))
connection_type = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
nominal_size = Column(String(50))
wall_thickness = Column(String(50))
# 치수 정보
length_mm = Column(Numeric(10, 2))
width_mm = Column(Numeric(10, 2))
height_mm = Column(Numeric(10, 2))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class FlangeDetails(Base):
__tablename__ = "flange_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 플랜지 정보
flange_type = Column(String(50))
flange_subtype = Column(String(100))
pressure_rating = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
nominal_size = Column(String(50))
# 치수 정보
outer_diameter = Column(Numeric(10, 2))
thickness = Column(Numeric(10, 2))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class ValveDetails(Base):
__tablename__ = "valve_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 밸브 정보
valve_type = Column(String(50))
valve_subtype = Column(String(100))
connection_type = Column(String(50))
actuation_type = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
nominal_size = Column(String(50))
pressure_rating = Column(String(50))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class GasketDetails(Base):
__tablename__ = "gasket_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 가스켓 정보
gasket_type = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
nominal_size = Column(String(50))
pressure_rating = Column(String(50))
filler_material = Column(String(50))
thickness = Column(Numeric(10, 2))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class BoltDetails(Base):
__tablename__ = "bolt_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 볼트 정보
bolt_type = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
thread_size = Column(String(50))
length = Column(Numeric(10, 2))
pressure_rating = Column(String(50))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class InstrumentDetails(Base):
__tablename__ = "instrument_details"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
# 계기 정보
instrument_type = Column(String(50))
instrument_subtype = Column(String(100))
connection_type = Column(String(50))
material_standard = Column(String(50))
material_grade = Column(String(50))
nominal_size = Column(String(50))
# 분류 신뢰도
classification_confidence = Column(Numeric(3, 2))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class PurchaseRequests(Base):
__tablename__ = "purchase_requests"
request_id = Column(String(50), primary_key=True, index=True)
request_no = Column(String(100), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
job_no = Column(String(50), nullable=False)
category = Column(String(50))
material_count = Column(Integer)
excel_file_path = Column(String(500))
requested_by = Column(Integer, ForeignKey("users.user_id"))
# 시간 정보
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 관계 설정
file = relationship("File")
requested_by_user = relationship("User", foreign_keys=[requested_by])
class Jobs(Base):
__tablename__ = "jobs"
id = Column(Integer, primary_key=True, index=True)
job_no = Column(String(50), unique=True, nullable=False)
job_name = Column(String(200))
status = Column(String(20), default='active')
created_at = Column(DateTime, default=datetime.utcnow)
class PipeEndPreparations(Base):
__tablename__ = "pipe_end_preparations"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
end_prep_type = Column(String(50))
end_prep_standard = Column(String(50))
classification_confidence = Column(Numeric(3, 2))
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class MaterialPurchaseTracking(Base):
__tablename__ = "material_purchase_tracking"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"), nullable=False)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
purchase_status = Column(String(20))
requested_quantity = Column(Integer)
confirmed_quantity = Column(Integer)
purchase_date = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
material = relationship("Material")
file = relationship("File")
class ExcelExports(Base):
__tablename__ = "excel_exports"
id = Column(Integer, primary_key=True, index=True)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False)
export_type = Column(String(50))
file_path = Column(String(500))
exported_by = Column(Integer, ForeignKey("users.user_id"))
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
file = relationship("File")
exported_by_user = relationship("User", foreign_keys=[exported_by])
class UserActivityLogs(Base):
__tablename__ = "user_activity_logs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.user_id"))
activity_type = Column(String(50))
activity_description = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
user = relationship("User")
class ExcelExportHistory(Base):
__tablename__ = "excel_export_history"
export_id = Column(String(50), primary_key=True, index=True)
file_id = Column(Integer, ForeignKey("files.id"))
job_no = Column(String(50))
exported_by = Column(Integer, ForeignKey("users.user_id"))
export_date = Column(DateTime, default=datetime.utcnow)
# 관계 설정
file = relationship("File")
exported_by_user = relationship("User", foreign_keys=[exported_by])
class ExportedMaterials(Base):
__tablename__ = "exported_materials"
id = Column(Integer, primary_key=True, index=True)
export_id = Column(String(50), ForeignKey("excel_export_history.export_id"))
material_id = Column(Integer, ForeignKey("materials.id"))
quantity = Column(Integer)
status = Column(String(20))
# 관계 설정
export_history = relationship("ExcelExportHistory")
material = relationship("Material")
class PurchaseStatusHistory(Base):
__tablename__ = "purchase_status_history"
id = Column(Integer, primary_key=True, index=True)
material_id = Column(Integer, ForeignKey("materials.id"))
old_status = Column(String(20))
new_status = Column(String(20))
changed_by = Column(Integer, ForeignKey("users.user_id"))
changed_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
material = relationship("Material")
changed_by_user = relationship("User", foreign_keys=[changed_by])