Files
TK-BOM-Project/backend/scripts/create_missing_tables.py
Hyungi Ahn c258303bb7
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
🎯 UI/UX 개선 및 안정성 강화
 주요 개선사항:
- Rev.0일 때 Revisions 카운트 0으로 정확히 표시
- 업로드 후 파일 목록 자동 새로고침
- 대시보드 계정 메뉴 zIndex 문제 해결
- 구매관리 페이지 500 오류 해결 및 대시보드 리다이렉트
- 구매신청 관리 페이지 버튼 텍스트 개선

🔧 기술적 수정:
- purchase_requests API SQL 쿼리 테이블 구조에 맞게 수정
- UserMenu 드롭다운 zIndex 1050으로 상향 조정
- 프론트엔드 완전 재빌드로 최신 변경사항 반영
- 완전한 자동 마이그레이션 시스템 구축 (43개 테이블 스키마 동기화)

🚀 다음 단계: 리비전 기능 재도입 준비 완료
2025-10-21 15:44:43 +09:00

438 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
누락된 테이블 생성 스크립트
- support_details
- special_material_details
- purchase_requests
- purchase_request_items
"""
import sys
import os
import psycopg2
from psycopg2.extras import RealDictCursor
import bcrypt
# 프로젝트 루트를 Python path에 추가
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def get_db_connection():
"""데이터베이스 연결"""
try:
# Docker 환경에서는 서비스명으로 연결
conn = psycopg2.connect(
host="tk-mp-postgres",
port="5432",
database="tk_mp_bom",
user="tkmp_user",
password="tkmp_password_2025"
)
return conn
except Exception as e:
print(f"❌ DB 연결 실패: {e}")
return None
def create_admin_user(cursor):
"""기본 admin 계정 생성"""
try:
# admin 계정이 이미 있는지 확인
cursor.execute("SELECT COUNT(*) FROM users WHERE username = 'admin';")
if cursor.fetchone()[0] > 0:
print("✅ admin 계정이 이미 존재합니다.")
return
# bcrypt로 비밀번호 해시 생성
password = "admin123"
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
# admin 계정 생성
cursor.execute("""
INSERT INTO users (
username, password, name, email, role, access_level,
is_active, status, department, position
) VALUES (
'admin', %s, 'System Administrator', 'admin@example.com',
'admin', 'admin', true, 'active', 'IT', 'Administrator'
);
""", (hashed_password,))
print("✅ admin 계정 생성 완료 (username: admin, password: admin123)")
except Exception as e:
print(f"⚠️ admin 계정 생성 실패: {e}")
def add_missing_columns(cursor):
"""누락된 컬럼들 추가"""
try:
print("🔧 누락된 컬럼 확인 및 추가 중...")
# users 테이블에 status 컬럼 확인 및 추가
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'status';
""")
if not cursor.fetchone():
print(" users 테이블에 status 컬럼 추가 중...")
cursor.execute("""
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';
""")
print("✅ users.status 컬럼 추가 완료")
else:
print("✅ users.status 컬럼이 이미 존재합니다")
# files 테이블에 누락된 컬럼들 확인 및 추가
files_columns = {
'job_no': 'VARCHAR(50)',
'bom_name': 'VARCHAR(255)',
'description': 'TEXT',
'parsed_count': 'INTEGER DEFAULT 0',
'classification_completed': 'BOOLEAN DEFAULT FALSE'
}
for column_name, column_type in files_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'files' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" files 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE files ADD COLUMN {column_name} {column_type};
""")
print(f"✅ files.{column_name} 컬럼 추가 완료")
else:
print(f"✅ files.{column_name} 컬럼이 이미 존재합니다")
# materials 테이블에 누락된 컬럼들 확인 및 추가
materials_columns = {
'main_nom': 'VARCHAR(50)',
'red_nom': 'VARCHAR(50)',
'full_material_grade': 'TEXT',
'row_number': 'INTEGER',
'length': 'NUMERIC(10,3)',
'purchase_confirmed': 'BOOLEAN DEFAULT FALSE',
'confirmed_quantity': 'NUMERIC(10,3)',
'purchase_status': 'VARCHAR(20)',
'purchase_confirmed_by': 'VARCHAR(100)',
'purchase_confirmed_at': 'TIMESTAMP',
'revision_status': 'VARCHAR(20)',
'material_hash': 'VARCHAR(64)',
'normalized_description': 'TEXT',
'drawing_reference': 'VARCHAR(100)',
'notes': 'TEXT',
'created_at': 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
'brand': 'VARCHAR(100)',
'user_requirement': 'TEXT',
'is_active': 'BOOLEAN DEFAULT TRUE',
'total_length': 'NUMERIC(10,3)'
}
for column_name, column_type in materials_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'materials' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" materials 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE materials ADD COLUMN {column_name} {column_type};
""")
print(f"✅ materials.{column_name} 컬럼 추가 완료")
else:
print(f"✅ materials.{column_name} 컬럼이 이미 존재합니다")
# purchase_requests 테이블에 누락된 컬럼들 확인 및 추가
purchase_requests_columns = {
'file_id': 'INTEGER REFERENCES files(id)'
}
for column_name, column_type in purchase_requests_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'purchase_requests' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" purchase_requests 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE purchase_requests ADD COLUMN {column_name} {column_type};
""")
print(f"✅ purchase_requests.{column_name} 컬럼 추가 완료")
else:
print(f"✅ purchase_requests.{column_name} 컬럼이 이미 존재합니다")
# material_purchase_tracking 테이블에 누락된 컬럼들 확인 및 추가
mpt_columns = {
'description': 'TEXT',
'purchase_status': 'VARCHAR(20) DEFAULT \'pending\''
}
for column_name, column_type in mpt_columns.items():
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'material_purchase_tracking' AND column_name = %s;
""", (column_name,))
if not cursor.fetchone():
print(f" material_purchase_tracking 테이블에 {column_name} 컬럼 추가 중...")
cursor.execute(f"""
ALTER TABLE material_purchase_tracking ADD COLUMN {column_name} {column_type};
""")
print(f"✅ material_purchase_tracking.{column_name} 컬럼 추가 완료")
else:
print(f"✅ material_purchase_tracking.{column_name} 컬럼이 이미 존재합니다")
except Exception as e:
print(f"⚠️ 컬럼 추가 실패: {e}")
def create_missing_tables():
"""누락된 테이블들 생성 (처음 설치 시에만)"""
conn = get_db_connection()
if not conn:
return False
try:
cursor = conn.cursor()
# 이미 설치되어 있는지 확인 (핵심 테이블들이 모두 존재하는지 체크)
print("🔍 기존 설치 상태 확인 중...")
cursor.execute("""
SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('support_details', 'special_material_details', 'purchase_requests', 'purchase_request_items');
""")
existing_tables = cursor.fetchone()[0]
if existing_tables == 4:
print("✅ 모든 테이블이 이미 존재합니다.")
# 컬럼 체크는 항상 수행
print("🔧 누락된 컬럼 확인 중...")
add_missing_columns(cursor)
# admin 계정 확인
cursor.execute("SELECT COUNT(*) FROM users WHERE username = 'admin';")
admin_exists = cursor.fetchone()[0]
if admin_exists == 0:
print("👤 admin 계정 생성 중...")
create_admin_user(cursor)
conn.commit()
print("✅ admin 계정 생성 완료")
else:
print("✅ admin 계정이 이미 존재합니다.")
conn.commit()
return True
print(f"🔍 누락된 테이블 확인 및 생성 중... ({existing_tables}/4개 존재)")
# 1. support_details 테이블
print("📋 1. support_details 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'support_details'
);
""")
if not cursor.fetchone()[0]:
print(" support_details 테이블 생성 중...")
cursor.execute("""
CREATE TABLE support_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
support_type VARCHAR(50),
support_subtype VARCHAR(100),
load_rating VARCHAR(50),
load_capacity VARCHAR(50),
material_standard VARCHAR(100),
material_grade VARCHAR(50),
pipe_size VARCHAR(20),
length_mm NUMERIC(10,2),
width_mm NUMERIC(10,2),
height_mm NUMERIC(10,2),
classification_confidence NUMERIC(3,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_support_details_material_id ON support_details(material_id);
CREATE INDEX idx_support_details_file_id ON support_details(file_id);
""")
print("✅ support_details 테이블 생성 완료")
else:
print("✅ support_details 테이블 이미 존재")
# 2. special_material_details 테이블
print("📋 2. special_material_details 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'special_material_details'
);
""")
if not cursor.fetchone()[0]:
print(" special_material_details 테이블 생성 중...")
cursor.execute("""
CREATE TABLE special_material_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
special_type VARCHAR(50),
special_subtype VARCHAR(100),
material_standard VARCHAR(100),
material_grade VARCHAR(50),
specifications TEXT,
dimensions VARCHAR(100),
weight_kg NUMERIC(10,3),
classification_confidence NUMERIC(3,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_special_material_details_material_id ON special_material_details(material_id);
CREATE INDEX idx_special_material_details_file_id ON special_material_details(file_id);
""")
print("✅ special_material_details 테이블 생성 완료")
else:
print("✅ special_material_details 테이블 이미 존재")
# 3. purchase_requests 테이블
print("📋 3. purchase_requests 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'purchase_requests'
);
""")
if not cursor.fetchone()[0]:
print(" purchase_requests 테이블 생성 중...")
cursor.execute("""
CREATE TABLE purchase_requests (
request_id SERIAL PRIMARY KEY,
request_no VARCHAR(50) UNIQUE NOT NULL,
job_no VARCHAR(50) NOT NULL,
project_name VARCHAR(200),
requested_by INTEGER REFERENCES users(user_id),
requested_by_username VARCHAR(100),
request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'pending',
total_items INTEGER DEFAULT 0,
notes TEXT,
approved_by INTEGER REFERENCES users(user_id),
approved_by_username VARCHAR(100),
approved_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_purchase_requests_job_no ON purchase_requests(job_no);
CREATE INDEX idx_purchase_requests_status ON purchase_requests(status);
CREATE INDEX idx_purchase_requests_requested_by ON purchase_requests(requested_by);
""")
print("✅ purchase_requests 테이블 생성 완료")
else:
print("✅ purchase_requests 테이블 이미 존재")
# 4. purchase_request_items 테이블
print("📋 4. purchase_request_items 테이블 확인...")
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'purchase_request_items'
);
""")
if not cursor.fetchone()[0]:
print(" purchase_request_items 테이블 생성 중...")
cursor.execute("""
CREATE TABLE purchase_request_items (
item_id SERIAL PRIMARY KEY,
request_id INTEGER REFERENCES purchase_requests(request_id) ON DELETE CASCADE,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
description TEXT NOT NULL,
category VARCHAR(50),
subcategory VARCHAR(100),
material_grade VARCHAR(50),
size_spec VARCHAR(50),
quantity NUMERIC(10,3) NOT NULL,
unit VARCHAR(10) NOT NULL,
drawing_name VARCHAR(100),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_purchase_request_items_request_id ON purchase_request_items(request_id);
CREATE INDEX idx_purchase_request_items_material_id ON purchase_request_items(material_id);
CREATE INDEX idx_purchase_request_items_category ON purchase_request_items(category);
""")
print("✅ purchase_request_items 테이블 생성 완료")
else:
print("✅ purchase_request_items 테이블 이미 존재")
# 변경사항 커밋
conn.commit()
print("\n🎉 누락된 테이블 생성 완료!")
# 최종 테이블 목록 확인
print("\n📋 현재 테이블 목록:")
cursor.execute("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
""")
tables = cursor.fetchall()
for table in tables:
print(f" - {table[0]}")
print(f"\n{len(tables)}개 테이블 존재")
# users 테이블에 status 컬럼 추가 (필요한 경우)
add_missing_columns(cursor)
# admin 계정 생성
create_admin_user(cursor)
conn.commit()
return True
except Exception as e:
print(f"❌ 테이블 생성 실패: {e}")
conn.rollback()
return False
finally:
if conn:
conn.close()
if __name__ == "__main__":
print("🚀 누락된 테이블 생성 시작...")
success = create_missing_tables()
if success:
print("✅ 모든 작업 완료!")
sys.exit(0)
else:
print("❌ 작업 실패!")
sys.exit(1)