🎯 UI/UX 개선 및 안정성 강화
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
✅ 주요 개선사항: - Rev.0일 때 Revisions 카운트 0으로 정확히 표시 - 업로드 후 파일 목록 자동 새로고침 - 대시보드 계정 메뉴 zIndex 문제 해결 - 구매관리 페이지 500 오류 해결 및 대시보드 리다이렉트 - 구매신청 관리 페이지 버튼 텍스트 개선 🔧 기술적 수정: - purchase_requests API SQL 쿼리 테이블 구조에 맞게 수정 - UserMenu 드롭다운 zIndex 1050으로 상향 조정 - 프론트엔드 완전 재빌드로 최신 변경사항 반영 - 완전한 자동 마이그레이션 시스템 구축 (43개 테이블 스키마 동기화) 🚀 다음 단계: 리비전 기능 재도입 준비 완료
This commit is contained in:
@@ -11,6 +11,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libpq-dev \
|
libpq-dev \
|
||||||
libmagic1 \
|
libmagic1 \
|
||||||
libmagic-dev \
|
libmagic-dev \
|
||||||
|
netcat-openbsd \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# requirements.txt 복사 및 의존성 설치
|
# requirements.txt 복사 및 의존성 설치
|
||||||
@@ -27,4 +28,4 @@ EXPOSE 8000
|
|||||||
ENV PYTHONPATH=/app
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
# 서버 실행
|
# 서버 실행
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["bash", "start.sh"]
|
||||||
@@ -187,17 +187,14 @@ async def get_purchase_requests(
|
|||||||
pr.request_no,
|
pr.request_no,
|
||||||
pr.file_id,
|
pr.file_id,
|
||||||
pr.job_no,
|
pr.job_no,
|
||||||
pr.category,
|
pr.total_items,
|
||||||
pr.material_count,
|
pr.request_date,
|
||||||
pr.excel_file_path,
|
|
||||||
pr.requested_at,
|
|
||||||
pr.status,
|
pr.status,
|
||||||
u.name as requested_by,
|
pr.requested_by_username as requested_by,
|
||||||
f.original_filename,
|
f.original_filename,
|
||||||
j.job_name,
|
j.job_name,
|
||||||
COUNT(pri.item_id) as item_count
|
COUNT(pri.item_id) as item_count
|
||||||
FROM purchase_requests pr
|
FROM purchase_requests pr
|
||||||
LEFT JOIN users u ON pr.requested_by = u.user_id
|
|
||||||
LEFT JOIN files f ON pr.file_id = f.id
|
LEFT JOIN files f ON pr.file_id = f.id
|
||||||
LEFT JOIN jobs j ON pr.job_no = j.job_no
|
LEFT JOIN jobs j ON pr.job_no = j.job_no
|
||||||
LEFT JOIN purchase_request_items pri ON pr.request_id = pri.request_id
|
LEFT JOIN purchase_request_items pri ON pr.request_id = pri.request_id
|
||||||
@@ -207,9 +204,9 @@ async def get_purchase_requests(
|
|||||||
AND (:status IS NULL OR pr.status = :status)
|
AND (:status IS NULL OR pr.status = :status)
|
||||||
GROUP BY
|
GROUP BY
|
||||||
pr.request_id, pr.request_no, pr.file_id, pr.job_no,
|
pr.request_id, pr.request_no, pr.file_id, pr.job_no,
|
||||||
pr.category, pr.material_count, pr.excel_file_path,
|
pr.total_items, pr.request_date, pr.status,
|
||||||
pr.requested_at, pr.status, u.name, f.original_filename, j.job_name
|
pr.requested_by_username, f.original_filename, j.job_name
|
||||||
ORDER BY pr.requested_at DESC
|
ORDER BY pr.request_date DESC
|
||||||
""")
|
""")
|
||||||
|
|
||||||
results = db.execute(query, {
|
results = db.execute(query, {
|
||||||
@@ -226,11 +223,11 @@ async def get_purchase_requests(
|
|||||||
"file_id": row.file_id,
|
"file_id": row.file_id,
|
||||||
"job_no": row.job_no,
|
"job_no": row.job_no,
|
||||||
"job_name": row.job_name,
|
"job_name": row.job_name,
|
||||||
"category": row.category,
|
"category": "ALL", # 기본값
|
||||||
"material_count": row.material_count,
|
"material_count": row.total_items or 0,
|
||||||
"item_count": row.item_count,
|
"item_count": row.item_count,
|
||||||
"excel_file_path": row.excel_file_path,
|
"excel_file_path": None, # 현재 테이블에 없음
|
||||||
"requested_at": row.requested_at.isoformat() if row.requested_at else None,
|
"requested_at": row.request_date.isoformat() if row.request_date else None,
|
||||||
"status": row.status,
|
"status": row.status,
|
||||||
"requested_by": row.requested_by,
|
"requested_by": row.requested_by,
|
||||||
"source_file": row.original_filename
|
"source_file": row.original_filename
|
||||||
|
|||||||
437
backend/scripts/create_missing_tables.py
Normal file
437
backend/scripts/create_missing_tables.py
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
#!/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)
|
||||||
25
backend/start.sh
Executable file
25
backend/start.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🚀 TK-MP Backend 시작 중..."
|
||||||
|
|
||||||
|
# 데이터베이스 연결 대기
|
||||||
|
echo "⏳ 데이터베이스 연결 대기 중..."
|
||||||
|
while ! nc -z tk-mp-postgres 5432; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "✅ 데이터베이스 연결 확인"
|
||||||
|
|
||||||
|
# 자동 마이그레이션 실행 (처음 설치 시에만)
|
||||||
|
echo "🔧 자동 마이그레이션 실행 중..."
|
||||||
|
python scripts/create_missing_tables.py
|
||||||
|
|
||||||
|
migration_result=$?
|
||||||
|
if [ $migration_result -eq 0 ]; then
|
||||||
|
echo "✅ 마이그레이션 완료"
|
||||||
|
else
|
||||||
|
echo "⚠️ 마이그레이션에 문제가 있었지만 서버를 시작합니다..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# FastAPI 서버 시작
|
||||||
|
echo "🌟 FastAPI 서버 시작..."
|
||||||
|
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
620
current_schema.txt
Normal file
620
current_schema.txt
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
table_name | column_name | data_type | is_nullable | column_default
|
||||||
|
-------------------------------+----------------------------+-----------------------------+-------------+--------------------------------------------------------------
|
||||||
|
bolt_details | id | integer | NO | nextval('bolt_details_id_seq'::regclass)
|
||||||
|
bolt_details | material_id | integer | YES |
|
||||||
|
bolt_details | file_id | integer | YES |
|
||||||
|
bolt_details | bolt_type | character varying | YES |
|
||||||
|
bolt_details | thread_type | character varying | YES |
|
||||||
|
bolt_details | diameter | character varying | YES |
|
||||||
|
bolt_details | length | character varying | YES |
|
||||||
|
bolt_details | material_standard | character varying | YES |
|
||||||
|
bolt_details | material_grade | character varying | YES |
|
||||||
|
bolt_details | coating_type | character varying | YES |
|
||||||
|
bolt_details | pressure_rating | character varying | YES |
|
||||||
|
bolt_details | includes_nut | boolean | YES |
|
||||||
|
bolt_details | includes_washer | boolean | YES |
|
||||||
|
bolt_details | nut_type | character varying | YES |
|
||||||
|
bolt_details | washer_type | character varying | YES |
|
||||||
|
bolt_details | classification_confidence | double precision | YES |
|
||||||
|
bolt_details | additional_info | jsonb | YES |
|
||||||
|
bolt_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
bolt_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
confirmed_purchase_items | id | integer | NO | nextval('confirmed_purchase_items_id_seq'::regclass)
|
||||||
|
confirmed_purchase_items | confirmation_id | integer | YES |
|
||||||
|
confirmed_purchase_items | item_code | character varying | NO |
|
||||||
|
confirmed_purchase_items | category | character varying | NO |
|
||||||
|
confirmed_purchase_items | specification | text | YES |
|
||||||
|
confirmed_purchase_items | size | character varying | YES |
|
||||||
|
confirmed_purchase_items | material | character varying | YES |
|
||||||
|
confirmed_purchase_items | bom_quantity | numeric | NO | 0
|
||||||
|
confirmed_purchase_items | calculated_qty | numeric | NO | 0
|
||||||
|
confirmed_purchase_items | unit | character varying | NO | 'EA'::character varying
|
||||||
|
confirmed_purchase_items | safety_factor | numeric | NO | 1.0
|
||||||
|
confirmed_purchase_items | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
files | id | integer | NO | nextval('files_id_seq'::regclass)
|
||||||
|
files | project_id | integer | YES |
|
||||||
|
files | filename | character varying | NO |
|
||||||
|
files | original_filename | character varying | NO |
|
||||||
|
files | file_path | character varying | NO |
|
||||||
|
files | revision | character varying | YES | 'Rev.0'::character varying
|
||||||
|
files | upload_date | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
files | uploaded_by | character varying | YES |
|
||||||
|
files | file_type | character varying | YES |
|
||||||
|
files | file_size | integer | YES |
|
||||||
|
files | is_active | boolean | YES | true
|
||||||
|
files | purchase_confirmed | boolean | YES | false
|
||||||
|
files | confirmed_at | timestamp without time zone | YES |
|
||||||
|
files | confirmed_by | character varying | YES |
|
||||||
|
files | job_no | character varying | YES |
|
||||||
|
files | bom_name | character varying | YES |
|
||||||
|
files | description | text | YES |
|
||||||
|
files | parsed_count | integer | YES | 0
|
||||||
|
fitting_details | id | integer | NO | nextval('fitting_details_id_seq'::regclass)
|
||||||
|
fitting_details | material_id | integer | YES |
|
||||||
|
fitting_details | file_id | integer | YES |
|
||||||
|
fitting_details | fitting_type | character varying | YES |
|
||||||
|
fitting_details | fitting_subtype | character varying | YES |
|
||||||
|
fitting_details | connection_method | character varying | YES |
|
||||||
|
fitting_details | connection_code | character varying | YES |
|
||||||
|
fitting_details | pressure_rating | character varying | YES |
|
||||||
|
fitting_details | max_pressure | character varying | YES |
|
||||||
|
fitting_details | manufacturing_method | character varying | YES |
|
||||||
|
fitting_details | material_standard | character varying | YES |
|
||||||
|
fitting_details | material_grade | character varying | YES |
|
||||||
|
fitting_details | material_type | character varying | YES |
|
||||||
|
fitting_details | main_size | character varying | YES |
|
||||||
|
fitting_details | reduced_size | character varying | YES |
|
||||||
|
fitting_details | length_mm | numeric | YES |
|
||||||
|
fitting_details | schedule | character varying | YES |
|
||||||
|
fitting_details | classification_confidence | double precision | YES |
|
||||||
|
fitting_details | additional_info | jsonb | YES |
|
||||||
|
fitting_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
fitting_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
flange_details | id | integer | NO | nextval('flange_details_id_seq'::regclass)
|
||||||
|
flange_details | material_id | integer | YES |
|
||||||
|
flange_details | file_id | integer | YES |
|
||||||
|
flange_details | flange_type | character varying | YES |
|
||||||
|
flange_details | facing_type | character varying | YES |
|
||||||
|
flange_details | pressure_rating | character varying | YES |
|
||||||
|
flange_details | material_standard | character varying | YES |
|
||||||
|
flange_details | material_grade | character varying | YES |
|
||||||
|
flange_details | size_inches | character varying | YES |
|
||||||
|
flange_details | bolt_hole_count | integer | YES |
|
||||||
|
flange_details | bolt_hole_size | character varying | YES |
|
||||||
|
flange_details | classification_confidence | double precision | YES |
|
||||||
|
flange_details | additional_info | jsonb | YES |
|
||||||
|
flange_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
flange_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
gasket_details | id | integer | NO | nextval('gasket_details_id_seq'::regclass)
|
||||||
|
gasket_details | material_id | integer | YES |
|
||||||
|
gasket_details | file_id | integer | YES |
|
||||||
|
gasket_details | gasket_type | character varying | YES |
|
||||||
|
gasket_details | gasket_subtype | character varying | YES |
|
||||||
|
gasket_details | material_type | character varying | YES |
|
||||||
|
gasket_details | filler_material | character varying | YES |
|
||||||
|
gasket_details | size_inches | character varying | YES |
|
||||||
|
gasket_details | pressure_rating | character varying | YES |
|
||||||
|
gasket_details | thickness | character varying | YES |
|
||||||
|
gasket_details | temperature_range | character varying | YES |
|
||||||
|
gasket_details | fire_safe | boolean | YES |
|
||||||
|
gasket_details | classification_confidence | double precision | YES |
|
||||||
|
gasket_details | additional_info | jsonb | YES |
|
||||||
|
gasket_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
gasket_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
instrument_details | id | integer | NO | nextval('instrument_details_id_seq'::regclass)
|
||||||
|
instrument_details | material_id | integer | YES |
|
||||||
|
instrument_details | file_id | integer | YES |
|
||||||
|
instrument_details | instrument_type | character varying | YES |
|
||||||
|
instrument_details | instrument_subtype | character varying | YES |
|
||||||
|
instrument_details | measurement_type | character varying | YES |
|
||||||
|
instrument_details | measurement_range | character varying | YES |
|
||||||
|
instrument_details | accuracy | character varying | YES |
|
||||||
|
instrument_details | connection_type | character varying | YES |
|
||||||
|
instrument_details | connection_size | character varying | YES |
|
||||||
|
instrument_details | body_material | character varying | YES |
|
||||||
|
instrument_details | wetted_parts_material | character varying | YES |
|
||||||
|
instrument_details | electrical_rating | character varying | YES |
|
||||||
|
instrument_details | output_signal | character varying | YES |
|
||||||
|
instrument_details | classification_confidence | double precision | YES |
|
||||||
|
instrument_details | additional_info | jsonb | YES |
|
||||||
|
instrument_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
instrument_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
jobs | job_no | character varying | NO |
|
||||||
|
jobs | job_name | character varying | NO |
|
||||||
|
jobs | client_name | character varying | NO |
|
||||||
|
jobs | end_user | character varying | YES |
|
||||||
|
jobs | epc_company | character varying | YES |
|
||||||
|
jobs | project_site | character varying | YES |
|
||||||
|
jobs | contract_date | date | YES |
|
||||||
|
jobs | delivery_date | date | YES |
|
||||||
|
jobs | delivery_terms | character varying | YES |
|
||||||
|
jobs | status | character varying | YES | '진행중'::character varying
|
||||||
|
jobs | delivery_completed_date | date | YES |
|
||||||
|
jobs | project_closed_date | date | YES |
|
||||||
|
jobs | description | text | YES |
|
||||||
|
jobs | created_by | character varying | YES |
|
||||||
|
jobs | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
jobs | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
jobs | is_active | boolean | YES | true
|
||||||
|
jobs | updated_by | character varying | YES |
|
||||||
|
jobs | assigned_to | character varying | YES |
|
||||||
|
jobs | project_type | character varying | NO | '냉동기'::character varying
|
||||||
|
login_logs | log_id | integer | NO | nextval('login_logs_log_id_seq'::regclass)
|
||||||
|
login_logs | user_id | integer | YES |
|
||||||
|
login_logs | login_time | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
login_logs | ip_address | character varying | YES |
|
||||||
|
login_logs | user_agent | text | YES |
|
||||||
|
login_logs | login_status | character varying | YES |
|
||||||
|
login_logs | failure_reason | character varying | YES |
|
||||||
|
login_logs | session_duration | integer | YES |
|
||||||
|
login_logs | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_categories | id | integer | NO | nextval('material_categories_id_seq'::regclass)
|
||||||
|
material_categories | standard_id | integer | YES |
|
||||||
|
material_categories | category_code | character varying | NO |
|
||||||
|
material_categories | category_name | character varying | NO |
|
||||||
|
material_categories | description | text | YES |
|
||||||
|
material_categories | is_active | boolean | YES | true
|
||||||
|
material_categories | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_categories | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_comparison_details | id | integer | NO | nextval('material_comparison_details_id_seq'::regclass)
|
||||||
|
material_comparison_details | comparison_id | integer | NO |
|
||||||
|
material_comparison_details | material_hash | character varying | NO |
|
||||||
|
material_comparison_details | change_type | character varying | NO |
|
||||||
|
material_comparison_details | description | text | NO |
|
||||||
|
material_comparison_details | size_spec | character varying | YES |
|
||||||
|
material_comparison_details | material_grade | character varying | YES |
|
||||||
|
material_comparison_details | previous_quantity | numeric | YES | 0
|
||||||
|
material_comparison_details | current_quantity | numeric | YES | 0
|
||||||
|
material_comparison_details | quantity_diff | numeric | YES | 0
|
||||||
|
material_comparison_details | additional_purchase_needed | numeric | YES | 0
|
||||||
|
material_comparison_details | classified_category | character varying | YES |
|
||||||
|
material_comparison_details | classification_confidence | numeric | YES |
|
||||||
|
material_comparison_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_grades | id | integer | NO | nextval('material_grades_id_seq'::regclass)
|
||||||
|
material_grades | specification_id | integer | YES |
|
||||||
|
material_grades | grade_code | character varying | NO |
|
||||||
|
material_grades | grade_name | character varying | YES |
|
||||||
|
material_grades | composition | character varying | YES |
|
||||||
|
material_grades | applications | character varying | YES |
|
||||||
|
material_grades | temp_max | character varying | YES |
|
||||||
|
material_grades | temp_range | character varying | YES |
|
||||||
|
material_grades | yield_strength | character varying | YES |
|
||||||
|
material_grades | tensile_strength | character varying | YES |
|
||||||
|
material_grades | corrosion_resistance | character varying | YES |
|
||||||
|
material_grades | stabilizer | character varying | YES |
|
||||||
|
material_grades | base_grade | character varying | YES |
|
||||||
|
material_grades | is_active | boolean | YES | true
|
||||||
|
material_grades | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_grades | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_patterns | id | integer | NO | nextval('material_patterns_id_seq'::regclass)
|
||||||
|
material_patterns | specification_id | integer | YES |
|
||||||
|
material_patterns | pattern | text | NO |
|
||||||
|
material_patterns | description | character varying | YES |
|
||||||
|
material_patterns | priority | integer | YES | 1
|
||||||
|
material_patterns | is_active | boolean | YES | true
|
||||||
|
material_patterns | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_patterns | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_purchase_mapping | id | integer | NO | nextval('material_purchase_mapping_id_seq'::regclass)
|
||||||
|
material_purchase_mapping | material_id | integer | NO |
|
||||||
|
material_purchase_mapping | purchase_item_id | integer | NO |
|
||||||
|
material_purchase_mapping | quantity_ratio | numeric | YES | 1.0
|
||||||
|
material_purchase_mapping | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_purchase_tracking | id | integer | NO | nextval('material_purchase_tracking_id_seq'::regclass)
|
||||||
|
material_purchase_tracking | material_hash | character varying | NO |
|
||||||
|
material_purchase_tracking | original_description | text | NO |
|
||||||
|
material_purchase_tracking | size_spec | character varying | YES |
|
||||||
|
material_purchase_tracking | material_grade | character varying | YES |
|
||||||
|
material_purchase_tracking | bom_quantity | numeric | NO |
|
||||||
|
material_purchase_tracking | confirmed_quantity | numeric | YES |
|
||||||
|
material_purchase_tracking | purchase_quantity | numeric | YES |
|
||||||
|
material_purchase_tracking | status | character varying | YES | 'pending'::character varying
|
||||||
|
material_purchase_tracking | confirmed_by | character varying | YES |
|
||||||
|
material_purchase_tracking | confirmed_at | timestamp without time zone | YES |
|
||||||
|
material_purchase_tracking | ordered_by | character varying | YES |
|
||||||
|
material_purchase_tracking | ordered_at | timestamp without time zone | YES |
|
||||||
|
material_purchase_tracking | approved_by | character varying | YES |
|
||||||
|
material_purchase_tracking | approved_at | timestamp without time zone | YES |
|
||||||
|
material_purchase_tracking | job_no | character varying | YES |
|
||||||
|
material_purchase_tracking | revision | character varying | YES |
|
||||||
|
material_purchase_tracking | file_id | integer | YES |
|
||||||
|
material_purchase_tracking | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_purchase_tracking | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_revisions_comparison | id | integer | NO | nextval('material_revisions_comparison_id_seq'::regclass)
|
||||||
|
material_revisions_comparison | job_no | character varying | NO |
|
||||||
|
material_revisions_comparison | current_revision | character varying | NO |
|
||||||
|
material_revisions_comparison | previous_revision | character varying | NO |
|
||||||
|
material_revisions_comparison | current_file_id | integer | NO |
|
||||||
|
material_revisions_comparison | previous_file_id | integer | NO |
|
||||||
|
material_revisions_comparison | total_current_items | integer | YES | 0
|
||||||
|
material_revisions_comparison | total_previous_items | integer | YES | 0
|
||||||
|
material_revisions_comparison | new_items_count | integer | YES | 0
|
||||||
|
material_revisions_comparison | modified_items_count | integer | YES | 0
|
||||||
|
material_revisions_comparison | removed_items_count | integer | YES | 0
|
||||||
|
material_revisions_comparison | unchanged_items_count | integer | YES | 0
|
||||||
|
material_revisions_comparison | comparison_details | jsonb | YES |
|
||||||
|
material_revisions_comparison | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_revisions_comparison | created_by | character varying | YES |
|
||||||
|
material_specifications | id | integer | NO | nextval('material_specifications_id_seq'::regclass)
|
||||||
|
material_specifications | category_id | integer | YES |
|
||||||
|
material_specifications | spec_code | character varying | NO |
|
||||||
|
material_specifications | spec_name | character varying | NO |
|
||||||
|
material_specifications | description | text | YES |
|
||||||
|
material_specifications | material_type | character varying | YES |
|
||||||
|
material_specifications | manufacturing | character varying | YES |
|
||||||
|
material_specifications | pressure_rating | character varying | YES |
|
||||||
|
material_specifications | is_active | boolean | YES | true
|
||||||
|
material_specifications | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_specifications | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_standards | id | integer | NO | nextval('material_standards_id_seq'::regclass)
|
||||||
|
material_standards | standard_code | character varying | NO |
|
||||||
|
material_standards | standard_name | character varying | NO |
|
||||||
|
material_standards | description | text | YES |
|
||||||
|
material_standards | country | character varying | YES |
|
||||||
|
material_standards | is_active | boolean | YES | true
|
||||||
|
material_standards | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_standards | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_tubing_mapping | id | integer | NO | nextval('material_tubing_mapping_id_seq'::regclass)
|
||||||
|
material_tubing_mapping | material_id | integer | YES |
|
||||||
|
material_tubing_mapping | tubing_product_id | integer | YES |
|
||||||
|
material_tubing_mapping | confidence_score | numeric | YES |
|
||||||
|
material_tubing_mapping | mapping_method | character varying | YES |
|
||||||
|
material_tubing_mapping | mapped_by | character varying | YES |
|
||||||
|
material_tubing_mapping | mapped_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
material_tubing_mapping | required_length_m | numeric | YES |
|
||||||
|
material_tubing_mapping | calculated_quantity | numeric | YES |
|
||||||
|
material_tubing_mapping | is_verified | boolean | YES | false
|
||||||
|
material_tubing_mapping | verified_by | character varying | YES |
|
||||||
|
material_tubing_mapping | verified_at | timestamp without time zone | YES |
|
||||||
|
material_tubing_mapping | notes | text | YES |
|
||||||
|
material_tubing_mapping | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
materials | id | integer | NO | nextval('materials_id_seq'::regclass)
|
||||||
|
materials | file_id | integer | YES |
|
||||||
|
materials | line_number | integer | YES |
|
||||||
|
materials | original_description | text | NO |
|
||||||
|
materials | classified_category | character varying | YES |
|
||||||
|
materials | classified_subcategory | character varying | YES |
|
||||||
|
materials | material_grade | character varying | YES |
|
||||||
|
materials | schedule | character varying | YES |
|
||||||
|
materials | size_spec | character varying | YES |
|
||||||
|
materials | quantity | numeric | NO |
|
||||||
|
materials | unit | character varying | NO |
|
||||||
|
materials | drawing_name | character varying | YES |
|
||||||
|
materials | area_code | character varying | YES |
|
||||||
|
materials | line_no | character varying | YES |
|
||||||
|
materials | classification_confidence | numeric | YES |
|
||||||
|
materials | classification_details | jsonb | YES |
|
||||||
|
materials | is_verified | boolean | YES | false
|
||||||
|
materials | verified_by | character varying | YES |
|
||||||
|
materials | verified_at | timestamp without time zone | YES |
|
||||||
|
materials | drawing_reference | character varying | YES |
|
||||||
|
materials | notes | text | YES |
|
||||||
|
materials | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
materials | main_nom | character varying | YES |
|
||||||
|
materials | red_nom | character varying | YES |
|
||||||
|
materials | full_material_grade | text | YES |
|
||||||
|
materials | row_number | integer | YES |
|
||||||
|
materials | length | numeric | YES |
|
||||||
|
materials | purchase_confirmed | boolean | YES | false
|
||||||
|
materials | confirmed_quantity | numeric | YES |
|
||||||
|
materials | purchase_status | character varying | YES |
|
||||||
|
materials | purchase_confirmed_by | character varying | YES |
|
||||||
|
materials | purchase_confirmed_at | timestamp without time zone | YES |
|
||||||
|
materials | revision_status | character varying | YES |
|
||||||
|
materials | material_hash | character varying | YES |
|
||||||
|
materials | normalized_description | text | YES |
|
||||||
|
materials | brand | character varying | YES |
|
||||||
|
materials | user_requirement | text | YES |
|
||||||
|
materials | is_active | boolean | YES | true
|
||||||
|
materials | total_length | numeric | YES |
|
||||||
|
permissions | permission_id | integer | NO | nextval('permissions_permission_id_seq'::regclass)
|
||||||
|
permissions | permission_name | character varying | NO |
|
||||||
|
permissions | description | text | YES |
|
||||||
|
permissions | module | character varying | YES |
|
||||||
|
permissions | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
pipe_details | id | integer | NO | nextval('pipe_details_id_seq'::regclass)
|
||||||
|
pipe_details | material_id | integer | YES |
|
||||||
|
pipe_details | file_id | integer | YES |
|
||||||
|
pipe_details | outer_diameter | character varying | YES |
|
||||||
|
pipe_details | schedule | character varying | YES |
|
||||||
|
pipe_details | material_spec | character varying | YES |
|
||||||
|
pipe_details | manufacturing_method | character varying | YES |
|
||||||
|
pipe_details | end_preparation | character varying | YES |
|
||||||
|
pipe_details | length_mm | numeric | YES |
|
||||||
|
pipe_details | classification_confidence | double precision | YES |
|
||||||
|
pipe_details | additional_info | jsonb | YES |
|
||||||
|
pipe_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
pipe_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
pipe_end_preparations | id | integer | NO | nextval('pipe_end_preparations_id_seq'::regclass)
|
||||||
|
pipe_end_preparations | material_id | integer | NO |
|
||||||
|
pipe_end_preparations | file_id | integer | NO |
|
||||||
|
pipe_end_preparations | end_preparation_type | character varying | YES | 'PBE'::character varying
|
||||||
|
pipe_end_preparations | end_preparation_code | character varying | YES |
|
||||||
|
pipe_end_preparations | machining_required | boolean | YES | false
|
||||||
|
pipe_end_preparations | cutting_note | text | YES |
|
||||||
|
pipe_end_preparations | original_description | text | NO |
|
||||||
|
pipe_end_preparations | clean_description | text | NO |
|
||||||
|
pipe_end_preparations | confidence | double precision | YES | 0.0
|
||||||
|
pipe_end_preparations | matched_pattern | character varying | YES |
|
||||||
|
pipe_end_preparations | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
pipe_end_preparations | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
projects | id | integer | NO | nextval('projects_id_seq'::regclass)
|
||||||
|
projects | official_project_code | character varying | YES |
|
||||||
|
projects | project_name | character varying | NO |
|
||||||
|
projects | client_name | character varying | YES |
|
||||||
|
projects | design_project_code | character varying | YES |
|
||||||
|
projects | design_project_name | character varying | YES |
|
||||||
|
projects | is_code_matched | boolean | YES | false
|
||||||
|
projects | matched_by | character varying | YES |
|
||||||
|
projects | matched_at | timestamp without time zone | YES |
|
||||||
|
projects | status | character varying | YES | 'active'::character varying
|
||||||
|
projects | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
projects | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
projects | description | text | YES |
|
||||||
|
projects | notes | text | YES |
|
||||||
|
purchase_confirmations | id | integer | NO | nextval('purchase_confirmations_id_seq'::regclass)
|
||||||
|
purchase_confirmations | job_no | character varying | NO |
|
||||||
|
purchase_confirmations | file_id | integer | YES |
|
||||||
|
purchase_confirmations | bom_name | character varying | NO |
|
||||||
|
purchase_confirmations | revision | character varying | NO | 'Rev.0'::character varying
|
||||||
|
purchase_confirmations | confirmed_at | timestamp without time zone | NO |
|
||||||
|
purchase_confirmations | confirmed_by | character varying | NO |
|
||||||
|
purchase_confirmations | is_active | boolean | NO | true
|
||||||
|
purchase_confirmations | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_confirmations | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_items | id | integer | NO | nextval('purchase_items_id_seq'::regclass)
|
||||||
|
purchase_items | item_code | character varying | NO |
|
||||||
|
purchase_items | category | character varying | NO |
|
||||||
|
purchase_items | specification | text | NO |
|
||||||
|
purchase_items | material_spec | character varying | YES |
|
||||||
|
purchase_items | size_spec | character varying | YES |
|
||||||
|
purchase_items | unit | character varying | NO |
|
||||||
|
purchase_items | bom_quantity | numeric | NO |
|
||||||
|
purchase_items | safety_factor | numeric | YES | 1.10
|
||||||
|
purchase_items | minimum_order_qty | numeric | YES | 0
|
||||||
|
purchase_items | order_unit_qty | numeric | YES | 1
|
||||||
|
purchase_items | calculated_qty | numeric | YES |
|
||||||
|
purchase_items | cutting_loss | numeric | YES | 0
|
||||||
|
purchase_items | standard_length | numeric | YES |
|
||||||
|
purchase_items | pipes_count | integer | YES |
|
||||||
|
purchase_items | waste_length | numeric | YES |
|
||||||
|
purchase_items | detailed_spec | jsonb | YES |
|
||||||
|
purchase_items | preferred_supplier | character varying | YES |
|
||||||
|
purchase_items | last_unit_price | numeric | YES |
|
||||||
|
purchase_items | currency | character varying | YES | 'KRW'::character varying
|
||||||
|
purchase_items | lead_time_days | integer | YES | 30
|
||||||
|
purchase_items | job_no | character varying | NO |
|
||||||
|
purchase_items | revision | character varying | YES | 'Rev.0'::character varying
|
||||||
|
purchase_items | file_id | integer | YES |
|
||||||
|
purchase_items | is_active | boolean | YES | true
|
||||||
|
purchase_items | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_items | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_items | created_by | character varying | YES |
|
||||||
|
purchase_items | updated_by | character varying | YES |
|
||||||
|
purchase_items | approved_by | character varying | YES |
|
||||||
|
purchase_items | approved_at | timestamp without time zone | YES |
|
||||||
|
purchase_request_items | item_id | integer | NO | nextval('purchase_request_items_item_id_seq'::regclass)
|
||||||
|
purchase_request_items | request_id | integer | YES |
|
||||||
|
purchase_request_items | material_id | integer | YES |
|
||||||
|
purchase_request_items | description | text | NO |
|
||||||
|
purchase_request_items | category | character varying | YES |
|
||||||
|
purchase_request_items | subcategory | character varying | YES |
|
||||||
|
purchase_request_items | material_grade | character varying | YES |
|
||||||
|
purchase_request_items | size_spec | character varying | YES |
|
||||||
|
purchase_request_items | quantity | numeric | NO |
|
||||||
|
purchase_request_items | unit | character varying | NO |
|
||||||
|
purchase_request_items | drawing_name | character varying | YES |
|
||||||
|
purchase_request_items | notes | text | YES |
|
||||||
|
purchase_request_items | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_requests | request_id | integer | NO | nextval('purchase_requests_request_id_seq'::regclass)
|
||||||
|
purchase_requests | request_no | character varying | NO |
|
||||||
|
purchase_requests | job_no | character varying | NO |
|
||||||
|
purchase_requests | project_name | character varying | YES |
|
||||||
|
purchase_requests | requested_by | integer | YES |
|
||||||
|
purchase_requests | requested_by_username | character varying | YES |
|
||||||
|
purchase_requests | request_date | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_requests | status | character varying | YES | 'pending'::character varying
|
||||||
|
purchase_requests | total_items | integer | YES | 0
|
||||||
|
purchase_requests | notes | text | YES |
|
||||||
|
purchase_requests | approved_by | integer | YES |
|
||||||
|
purchase_requests | approved_by_username | character varying | YES |
|
||||||
|
purchase_requests | approved_at | timestamp without time zone | YES |
|
||||||
|
purchase_requests | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_requests | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
purchase_requests | file_id | integer | YES |
|
||||||
|
requirement_types | id | integer | NO | nextval('requirement_types_id_seq'::regclass)
|
||||||
|
requirement_types | type_code | character varying | NO |
|
||||||
|
requirement_types | type_name | character varying | NO |
|
||||||
|
requirement_types | category | character varying | NO |
|
||||||
|
requirement_types | description | text | YES |
|
||||||
|
requirement_types | is_active | boolean | YES | true
|
||||||
|
requirement_types | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
role_permissions | role_permission_id | integer | NO | nextval('role_permissions_role_permission_id_seq'::regclass)
|
||||||
|
role_permissions | role | character varying | NO |
|
||||||
|
role_permissions | permission_id | integer | YES |
|
||||||
|
role_permissions | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_details | id | integer | NO | nextval('special_material_details_id_seq'::regclass)
|
||||||
|
special_material_details | material_id | integer | YES |
|
||||||
|
special_material_details | file_id | integer | YES |
|
||||||
|
special_material_details | special_type | character varying | YES |
|
||||||
|
special_material_details | special_subtype | character varying | YES |
|
||||||
|
special_material_details | material_standard | character varying | YES |
|
||||||
|
special_material_details | material_grade | character varying | YES |
|
||||||
|
special_material_details | specifications | text | YES |
|
||||||
|
special_material_details | dimensions | character varying | YES |
|
||||||
|
special_material_details | weight_kg | numeric | YES |
|
||||||
|
special_material_details | classification_confidence | numeric | YES |
|
||||||
|
special_material_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_grades | id | integer | NO | nextval('special_material_grades_id_seq'::regclass)
|
||||||
|
special_material_grades | material_id | integer | YES |
|
||||||
|
special_material_grades | grade_code | character varying | NO |
|
||||||
|
special_material_grades | composition | character varying | YES |
|
||||||
|
special_material_grades | applications | character varying | YES |
|
||||||
|
special_material_grades | temp_max | character varying | YES |
|
||||||
|
special_material_grades | strength | character varying | YES |
|
||||||
|
special_material_grades | purity | character varying | YES |
|
||||||
|
special_material_grades | corrosion | character varying | YES |
|
||||||
|
special_material_grades | is_active | boolean | YES | true
|
||||||
|
special_material_grades | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_grades | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_patterns | id | integer | NO | nextval('special_material_patterns_id_seq'::regclass)
|
||||||
|
special_material_patterns | material_id | integer | YES |
|
||||||
|
special_material_patterns | pattern | text | NO |
|
||||||
|
special_material_patterns | description | character varying | YES |
|
||||||
|
special_material_patterns | priority | integer | YES | 1
|
||||||
|
special_material_patterns | is_active | boolean | YES | true
|
||||||
|
special_material_patterns | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_material_patterns | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_materials | id | integer | NO | nextval('special_materials_id_seq'::regclass)
|
||||||
|
special_materials | material_type | character varying | NO |
|
||||||
|
special_materials | material_name | character varying | NO |
|
||||||
|
special_materials | description | text | YES |
|
||||||
|
special_materials | composition | character varying | YES |
|
||||||
|
special_materials | applications | text | YES |
|
||||||
|
special_materials | temp_max | character varying | YES |
|
||||||
|
special_materials | manufacturing | character varying | YES |
|
||||||
|
special_materials | is_active | boolean | YES | true
|
||||||
|
special_materials | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
special_materials | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
support_details | id | integer | NO | nextval('support_details_id_seq'::regclass)
|
||||||
|
support_details | material_id | integer | YES |
|
||||||
|
support_details | file_id | integer | YES |
|
||||||
|
support_details | support_type | character varying | YES |
|
||||||
|
support_details | support_subtype | character varying | YES |
|
||||||
|
support_details | load_rating | character varying | YES |
|
||||||
|
support_details | load_capacity | character varying | YES |
|
||||||
|
support_details | material_standard | character varying | YES |
|
||||||
|
support_details | material_grade | character varying | YES |
|
||||||
|
support_details | pipe_size | character varying | YES |
|
||||||
|
support_details | length_mm | numeric | YES |
|
||||||
|
support_details | width_mm | numeric | YES |
|
||||||
|
support_details | height_mm | numeric | YES |
|
||||||
|
support_details | classification_confidence | numeric | YES |
|
||||||
|
support_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
support_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_categories | id | integer | NO | nextval('tubing_categories_id_seq'::regclass)
|
||||||
|
tubing_categories | category_code | character varying | NO |
|
||||||
|
tubing_categories | category_name | character varying | NO |
|
||||||
|
tubing_categories | description | text | YES |
|
||||||
|
tubing_categories | is_active | boolean | YES | true
|
||||||
|
tubing_categories | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_categories | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_manufacturers | id | integer | NO | nextval('tubing_manufacturers_id_seq'::regclass)
|
||||||
|
tubing_manufacturers | manufacturer_code | character varying | NO |
|
||||||
|
tubing_manufacturers | manufacturer_name | character varying | NO |
|
||||||
|
tubing_manufacturers | country | character varying | YES |
|
||||||
|
tubing_manufacturers | website | character varying | YES |
|
||||||
|
tubing_manufacturers | contact_info | jsonb | YES |
|
||||||
|
tubing_manufacturers | quality_certs | jsonb | YES |
|
||||||
|
tubing_manufacturers | is_active | boolean | YES | true
|
||||||
|
tubing_manufacturers | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_manufacturers | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_products | id | integer | NO | nextval('tubing_products_id_seq'::regclass)
|
||||||
|
tubing_products | specification_id | integer | YES |
|
||||||
|
tubing_products | manufacturer_id | integer | YES |
|
||||||
|
tubing_products | manufacturer_part_number | character varying | NO |
|
||||||
|
tubing_products | manufacturer_product_name | character varying | YES |
|
||||||
|
tubing_products | list_price | numeric | YES |
|
||||||
|
tubing_products | currency | character varying | YES | 'KRW'::character varying
|
||||||
|
tubing_products | lead_time_days | integer | YES |
|
||||||
|
tubing_products | minimum_order_qty | numeric | YES |
|
||||||
|
tubing_products | standard_packaging_qty | numeric | YES |
|
||||||
|
tubing_products | availability_status | character varying | YES |
|
||||||
|
tubing_products | last_price_update | date | YES |
|
||||||
|
tubing_products | datasheet_url | character varying | YES |
|
||||||
|
tubing_products | catalog_page | character varying | YES |
|
||||||
|
tubing_products | notes | text | YES |
|
||||||
|
tubing_products | is_active | boolean | YES | true
|
||||||
|
tubing_products | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_products | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_specifications | id | integer | NO | nextval('tubing_specifications_id_seq'::regclass)
|
||||||
|
tubing_specifications | category_id | integer | YES |
|
||||||
|
tubing_specifications | spec_code | character varying | NO |
|
||||||
|
tubing_specifications | spec_name | character varying | NO |
|
||||||
|
tubing_specifications | outer_diameter_mm | numeric | YES |
|
||||||
|
tubing_specifications | wall_thickness_mm | numeric | YES |
|
||||||
|
tubing_specifications | inner_diameter_mm | numeric | YES |
|
||||||
|
tubing_specifications | material_grade | character varying | YES |
|
||||||
|
tubing_specifications | material_standard | character varying | YES |
|
||||||
|
tubing_specifications | max_pressure_bar | numeric | YES |
|
||||||
|
tubing_specifications | max_temperature_c | numeric | YES |
|
||||||
|
tubing_specifications | min_temperature_c | numeric | YES |
|
||||||
|
tubing_specifications | standard_length_m | numeric | YES |
|
||||||
|
tubing_specifications | bend_radius_min_mm | numeric | YES |
|
||||||
|
tubing_specifications | surface_finish | character varying | YES |
|
||||||
|
tubing_specifications | hardness | character varying | YES |
|
||||||
|
tubing_specifications | notes | text | YES |
|
||||||
|
tubing_specifications | is_active | boolean | YES | true
|
||||||
|
tubing_specifications | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
tubing_specifications | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
user_activity_logs | id | integer | NO | nextval('user_activity_logs_id_seq'::regclass)
|
||||||
|
user_activity_logs | user_id | integer | YES |
|
||||||
|
user_activity_logs | username | character varying | NO |
|
||||||
|
user_activity_logs | activity_type | character varying | NO |
|
||||||
|
user_activity_logs | activity_description | text | YES |
|
||||||
|
user_activity_logs | target_id | integer | YES |
|
||||||
|
user_activity_logs | target_type | character varying | YES |
|
||||||
|
user_activity_logs | ip_address | character varying | YES |
|
||||||
|
user_activity_logs | user_agent | text | YES |
|
||||||
|
user_activity_logs | metadata | jsonb | YES |
|
||||||
|
user_activity_logs | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
user_requirements | id | integer | NO | nextval('user_requirements_id_seq'::regclass)
|
||||||
|
user_requirements | file_id | integer | NO |
|
||||||
|
user_requirements | material_id | integer | YES |
|
||||||
|
user_requirements | requirement_type | character varying | NO |
|
||||||
|
user_requirements | requirement_title | character varying | NO |
|
||||||
|
user_requirements | requirement_description | text | YES |
|
||||||
|
user_requirements | requirement_spec | text | YES |
|
||||||
|
user_requirements | status | character varying | YES | 'PENDING'::character varying
|
||||||
|
user_requirements | priority | character varying | YES | 'NORMAL'::character varying
|
||||||
|
user_requirements | assigned_to | character varying | YES |
|
||||||
|
user_requirements | due_date | date | YES |
|
||||||
|
user_requirements | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
user_requirements | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
user_sessions | session_id | integer | NO | nextval('user_sessions_session_id_seq'::regclass)
|
||||||
|
user_sessions | user_id | integer | YES |
|
||||||
|
user_sessions | refresh_token | character varying | NO |
|
||||||
|
user_sessions | expires_at | timestamp without time zone | NO |
|
||||||
|
user_sessions | ip_address | character varying | YES |
|
||||||
|
user_sessions | user_agent | text | YES |
|
||||||
|
user_sessions | is_active | boolean | YES | true
|
||||||
|
user_sessions | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
user_sessions | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
users | user_id | integer | NO | nextval('users_user_id_seq'::regclass)
|
||||||
|
users | username | character varying | NO |
|
||||||
|
users | password | character varying | NO |
|
||||||
|
users | name | character varying | NO |
|
||||||
|
users | email | character varying | YES |
|
||||||
|
users | role | character varying | YES | 'user'::character varying
|
||||||
|
users | access_level | character varying | YES | 'worker'::character varying
|
||||||
|
users | is_active | boolean | YES | true
|
||||||
|
users | failed_login_attempts | integer | YES | 0
|
||||||
|
users | locked_until | timestamp without time zone | YES |
|
||||||
|
users | department | character varying | YES |
|
||||||
|
users | position | character varying | YES |
|
||||||
|
users | phone | character varying | YES |
|
||||||
|
users | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
users | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
users | last_login_at | timestamp without time zone | YES |
|
||||||
|
users | status | character varying | YES | 'active'::character varying
|
||||||
|
valve_details | id | integer | NO | nextval('valve_details_id_seq'::regclass)
|
||||||
|
valve_details | material_id | integer | YES |
|
||||||
|
valve_details | file_id | integer | YES |
|
||||||
|
valve_details | valve_type | character varying | YES |
|
||||||
|
valve_details | valve_subtype | character varying | YES |
|
||||||
|
valve_details | actuator_type | character varying | YES |
|
||||||
|
valve_details | connection_method | character varying | YES |
|
||||||
|
valve_details | pressure_rating | character varying | YES |
|
||||||
|
valve_details | pressure_class | character varying | YES |
|
||||||
|
valve_details | body_material | character varying | YES |
|
||||||
|
valve_details | trim_material | character varying | YES |
|
||||||
|
valve_details | size_inches | character varying | YES |
|
||||||
|
valve_details | fire_safe | boolean | YES |
|
||||||
|
valve_details | low_temp_service | boolean | YES |
|
||||||
|
valve_details | special_features | jsonb | YES |
|
||||||
|
valve_details | classification_confidence | double precision | YES |
|
||||||
|
valve_details | additional_info | jsonb | YES |
|
||||||
|
valve_details | created_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
valve_details | updated_at | timestamp without time zone | YES | CURRENT_TIMESTAMP
|
||||||
|
(616 rows)
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
animation: dropdownSlide 0.3s ease-out;
|
animation: dropdownSlide 0.3s ease-out;
|
||||||
z-index: 1000;
|
z-index: 1050;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes dropdownSlide {
|
@keyframes dropdownSlide {
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ const BOMFilesTab = ({
|
|||||||
<span style={{ fontWeight: '500' }}>Latest:</span> {latestFile.revision || 'Rev.0'}
|
<span style={{ fontWeight: '500' }}>Latest:</span> {latestFile.revision || 'Rev.0'}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontWeight: '500' }}>Revisions:</span> {files.length}
|
<span style={{ fontWeight: '500' }}>Revisions:</span> {Math.max(0, files.length - 1)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontWeight: '500' }}>Updated:</span> {formatDate(latestFile.upload_date)}
|
<span style={{ fontWeight: '500' }}>Updated:</span> {formatDate(latestFile.upload_date)}
|
||||||
|
|||||||
@@ -152,16 +152,14 @@ const BOMUploadTab = ({
|
|||||||
|
|
||||||
setSuccess(`${selectedFiles.length}개 파일이 성공적으로 업로드되었습니다!`);
|
setSuccess(`${selectedFiles.length}개 파일이 성공적으로 업로드되었습니다!`);
|
||||||
|
|
||||||
|
// 업로드 성공 즉시 콜백 호출 (파일 목록 새로고침)
|
||||||
|
if (onUploadSuccess) {
|
||||||
|
onUploadSuccess(uploadedFile);
|
||||||
|
}
|
||||||
|
|
||||||
// 파일 초기화
|
// 파일 초기화
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setBomName('');
|
setBomName('');
|
||||||
|
|
||||||
// 2초 후 Files 탭으로 이동
|
|
||||||
setTimeout(() => {
|
|
||||||
if (onUploadSuccess) {
|
|
||||||
onUploadSuccess(uploadedFile);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('업로드 실패:', err);
|
console.error('업로드 실패:', err);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const UserMenu = ({ user, onNavigate, onLogout }) => {
|
|||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||||
zIndex: 1000,
|
zIndex: 1050,
|
||||||
minWidth: '200px'
|
minWidth: '200px'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ padding: '8px 0' }}>
|
<div style={{ padding: '8px 0' }}>
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ const PurchaseRequestPage = ({ onNavigate, fileId, jobNo, selectedProject }) =>
|
|||||||
setRequests(response.data.requests || []);
|
setRequests(response.data.requests || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load requests:', error);
|
console.error('Failed to load requests:', error);
|
||||||
|
// API 오류 시 대시보드로 리다이렉트
|
||||||
|
if (error.response?.status === 500 || error.response?.status === 404) {
|
||||||
|
alert('구매신청 페이지에 문제가 발생했습니다. 대시보드로 이동합니다.');
|
||||||
|
onNavigate('dashboard');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -133,8 +138,8 @@ const PurchaseRequestPage = ({ onNavigate, fileId, jobNo, selectedProject }) =>
|
|||||||
return (
|
return (
|
||||||
<div className="purchase-request-page">
|
<div className="purchase-request-page">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<button onClick={() => onNavigate('bom', { selectedProject })} className="back-btn">
|
<button onClick={() => onNavigate('dashboard')} className="back-btn">
|
||||||
← BOM 관리로 돌아가기
|
← 대시보드로 돌아가기
|
||||||
</button>
|
</button>
|
||||||
<h1>구매신청 관리</h1>
|
<h1>구매신청 관리</h1>
|
||||||
<p className="subtitle">구매신청한 자재들을 그룹별로 관리합니다</p>
|
<p className="subtitle">구매신청한 자재들을 그룹별로 관리합니다</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user