- 리비전 관리 라우터 및 서비스 추가 (revision_management.py, revision_comparison_service.py, revision_session_service.py) - 구매확정 기능 구현: materials 테이블에 purchase_confirmed 필드 추가 및 업데이트 로직 - 리비전 비교 로직 구현: 구매확정된 자재 기반으로 신규/변경 자재 자동 분류 - 데이터베이스 스키마 확장: revision_sessions, revision_material_changes, inventory_transfers 테이블 추가 - 구매신청 생성 시 자재 상세 정보 저장 및 purchase_confirmed 자동 업데이트 - 프론트엔드: 리비전 관리 컴포넌트 및 hooks 추가 - 파일 목록 조회 API 추가 (/files/list) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
655 lines
27 KiB
Python
655 lines
27 KiB
Python
#!/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} 컬럼이 이미 존재합니다")
|
||
|
||
# purchase_requests 테이블에 누락된 컬럼들 확인 및 추가
|
||
purchase_requests_columns = {
|
||
'file_id': 'INTEGER REFERENCES files(id)',
|
||
'category': 'VARCHAR(50)',
|
||
'material_count': 'INTEGER DEFAULT 0',
|
||
'excel_file_path': 'VARCHAR(500)'
|
||
}
|
||
|
||
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} 컬럼이 이미 존재합니다")
|
||
|
||
# purchase_request_items 테이블에 누락된 컬럼들 확인 및 추가
|
||
purchase_request_items_columns = {
|
||
'user_requirement': 'TEXT',
|
||
'description': 'TEXT',
|
||
'category': 'VARCHAR(50)',
|
||
'subcategory': 'VARCHAR(100)',
|
||
'material_grade': 'VARCHAR(50)',
|
||
'size_spec': 'VARCHAR(50)',
|
||
'drawing_name': 'VARCHAR(100)',
|
||
'notes': 'TEXT',
|
||
'is_ordered': 'BOOLEAN DEFAULT FALSE',
|
||
'is_received': 'BOOLEAN DEFAULT FALSE'
|
||
}
|
||
|
||
for column_name, column_type in purchase_request_items_columns.items():
|
||
cursor.execute("""
|
||
SELECT column_name
|
||
FROM information_schema.columns
|
||
WHERE table_name = 'purchase_request_items' AND column_name = %s;
|
||
""", (column_name,))
|
||
|
||
if not cursor.fetchone():
|
||
print(f"➕ purchase_request_items 테이블에 {column_name} 컬럼 추가 중...")
|
||
cursor.execute(f"""
|
||
ALTER TABLE purchase_request_items ADD COLUMN {column_name} {column_type};
|
||
""")
|
||
print(f"✅ purchase_request_items.{column_name} 컬럼 추가 완료")
|
||
else:
|
||
print(f"✅ purchase_request_items.{column_name} 컬럼이 이미 존재합니다")
|
||
|
||
print("✅ 모든 누락된 컬럼 추가 완료!")
|
||
|
||
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,
|
||
file_id INTEGER REFERENCES files(id),
|
||
job_no VARCHAR(50) NOT NULL,
|
||
category VARCHAR(50),
|
||
material_count INTEGER DEFAULT 0,
|
||
excel_file_path VARCHAR(500),
|
||
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 테이블 이미 존재")
|
||
|
||
# 5. revision_sessions 테이블
|
||
print("📋 5. revision_sessions 테이블 확인...")
|
||
cursor.execute("""
|
||
SELECT EXISTS (
|
||
SELECT FROM information_schema.tables
|
||
WHERE table_schema = 'public' AND table_name = 'revision_sessions'
|
||
);
|
||
""")
|
||
if not cursor.fetchone()[0]:
|
||
print("➕ revision_sessions 테이블 생성 중...")
|
||
cursor.execute("""
|
||
CREATE TABLE revision_sessions (
|
||
id SERIAL PRIMARY KEY,
|
||
job_no VARCHAR(50) NOT NULL,
|
||
current_file_id INTEGER REFERENCES files(id),
|
||
previous_file_id INTEGER REFERENCES files(id),
|
||
current_revision VARCHAR(20) NOT NULL,
|
||
previous_revision VARCHAR(20) NOT NULL,
|
||
|
||
status VARCHAR(20) DEFAULT 'processing',
|
||
total_materials INTEGER DEFAULT 0,
|
||
processed_materials INTEGER DEFAULT 0,
|
||
|
||
added_count INTEGER DEFAULT 0,
|
||
removed_count INTEGER DEFAULT 0,
|
||
changed_count INTEGER DEFAULT 0,
|
||
unchanged_count INTEGER DEFAULT 0,
|
||
|
||
purchase_cancel_count INTEGER DEFAULT 0,
|
||
inventory_transfer_count INTEGER DEFAULT 0,
|
||
additional_purchase_count INTEGER DEFAULT 0,
|
||
|
||
created_by VARCHAR(100),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
completed_at TIMESTAMP
|
||
);
|
||
CREATE INDEX idx_revision_sessions_job_no ON revision_sessions(job_no);
|
||
CREATE INDEX idx_revision_sessions_status ON revision_sessions(status);
|
||
""")
|
||
print("✅ revision_sessions 테이블 생성 완료")
|
||
else:
|
||
print("✅ revision_sessions 테이블 이미 존재")
|
||
|
||
# 6. revision_material_changes 테이블
|
||
print("📋 6. revision_material_changes 테이블 확인...")
|
||
cursor.execute("""
|
||
SELECT EXISTS (
|
||
SELECT FROM information_schema.tables
|
||
WHERE table_schema = 'public' AND table_name = 'revision_material_changes'
|
||
);
|
||
""")
|
||
if not cursor.fetchone()[0]:
|
||
print("➕ revision_material_changes 테이블 생성 중...")
|
||
cursor.execute("""
|
||
CREATE TABLE revision_material_changes (
|
||
id SERIAL PRIMARY KEY,
|
||
session_id INTEGER REFERENCES revision_sessions(id) ON DELETE CASCADE,
|
||
|
||
material_id INTEGER REFERENCES materials(id),
|
||
previous_material_id INTEGER,
|
||
material_description TEXT NOT NULL,
|
||
category VARCHAR(50) NOT NULL,
|
||
|
||
change_type VARCHAR(20) NOT NULL,
|
||
previous_quantity NUMERIC(10,3),
|
||
current_quantity NUMERIC(10,3),
|
||
quantity_difference NUMERIC(10,3),
|
||
|
||
purchase_status VARCHAR(20) NOT NULL,
|
||
purchase_confirmed_at TIMESTAMP,
|
||
|
||
revision_action VARCHAR(30),
|
||
action_status VARCHAR(20) DEFAULT 'pending',
|
||
|
||
processed_by VARCHAR(100),
|
||
processed_at TIMESTAMP,
|
||
processing_notes TEXT,
|
||
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
CREATE INDEX idx_revision_changes_session ON revision_material_changes(session_id);
|
||
CREATE INDEX idx_revision_changes_action ON revision_material_changes(revision_action);
|
||
CREATE INDEX idx_revision_changes_status ON revision_material_changes(action_status);
|
||
""")
|
||
print("✅ revision_material_changes 테이블 생성 완료")
|
||
else:
|
||
print("✅ revision_material_changes 테이블 이미 존재")
|
||
|
||
# 7. inventory_transfers 테이블
|
||
print("📋 7. inventory_transfers 테이블 확인...")
|
||
cursor.execute("""
|
||
SELECT EXISTS (
|
||
SELECT FROM information_schema.tables
|
||
WHERE table_schema = 'public' AND table_name = 'inventory_transfers'
|
||
);
|
||
""")
|
||
if not cursor.fetchone()[0]:
|
||
print("➕ inventory_transfers 테이블 생성 중...")
|
||
cursor.execute("""
|
||
CREATE TABLE inventory_transfers (
|
||
id SERIAL PRIMARY KEY,
|
||
revision_change_id INTEGER REFERENCES revision_material_changes(id),
|
||
|
||
material_description TEXT NOT NULL,
|
||
category VARCHAR(50) NOT NULL,
|
||
quantity NUMERIC(10,3) NOT NULL,
|
||
unit VARCHAR(10) NOT NULL,
|
||
|
||
inventory_location VARCHAR(100),
|
||
storage_notes TEXT,
|
||
|
||
transferred_by VARCHAR(100) NOT NULL,
|
||
transferred_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
status VARCHAR(20) DEFAULT 'transferred'
|
||
);
|
||
CREATE INDEX idx_inventory_transfers_material ON inventory_transfers(material_description);
|
||
CREATE INDEX idx_inventory_transfers_date ON inventory_transfers(transferred_at);
|
||
""")
|
||
print("✅ inventory_transfers 테이블 생성 완료")
|
||
else:
|
||
print("✅ inventory_transfers 테이블 이미 존재")
|
||
|
||
# 8. revision_action_logs 테이블
|
||
print("📋 8. revision_action_logs 테이블 확인...")
|
||
cursor.execute("""
|
||
SELECT EXISTS (
|
||
SELECT FROM information_schema.tables
|
||
WHERE table_schema = 'public' AND table_name = 'revision_action_logs'
|
||
);
|
||
""")
|
||
if not cursor.fetchone()[0]:
|
||
print("➕ revision_action_logs 테이블 생성 중...")
|
||
cursor.execute("""
|
||
CREATE TABLE revision_action_logs (
|
||
id SERIAL PRIMARY KEY,
|
||
session_id INTEGER REFERENCES revision_sessions(id),
|
||
revision_change_id INTEGER REFERENCES revision_material_changes(id),
|
||
|
||
action_type VARCHAR(30) NOT NULL,
|
||
action_description TEXT,
|
||
|
||
executed_by VARCHAR(100) NOT NULL,
|
||
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
result VARCHAR(20) NOT NULL,
|
||
result_message TEXT,
|
||
result_data JSONB
|
||
);
|
||
CREATE INDEX idx_revision_logs_session ON revision_action_logs(session_id);
|
||
CREATE INDEX idx_revision_logs_type ON revision_action_logs(action_type);
|
||
CREATE INDEX idx_revision_logs_date ON revision_action_logs(executed_at);
|
||
""")
|
||
print("✅ revision_action_logs 테이블 생성 완료")
|
||
else:
|
||
print("✅ revision_action_logs 테이블 이미 존재")
|
||
|
||
# 변경사항 커밋
|
||
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)
|