Files
TK-BOM-Project/database/init/99_complete_schema.sql
Hyungi Ahn 50570e4624
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: 사용자 요구사항 기능 완전 구현 및 전체 카테고리 추가
- 사용자 요구사항 저장/로드/엑셀 내보내기 기능 완전 구현
- 백엔드 API 수정: Request Body 방식으로 변경
- 데이터베이스 스키마: material_id 컬럼 추가
- 프론트엔드 상태 관리 개선: 저장 후 자동 리로드
- 입력 필드 연결 문제 해결: 누락된 onChange 핸들러 추가
- NewMaterialsPage에 '전체' 카테고리 버튼 추가 (기본 선택)
- Docker 환경 개선: 프론트엔드 볼륨 마운트 및 포트 수정
- UI 개선: 벌레 이모지 제거, 디버그 코드 정리
2025-09-30 08:55:20 +09:00

1570 lines
60 KiB
PL/PgSQL

-- ================================
-- TK-MP-Project 통합 데이터베이스 스키마
-- 다른 환경 배포용 완전한 초기화 스크립트
-- 생성일: 2025.09.09
-- ================================
-- ================================
-- 1. 기본 스키마 (01_schema.sql 기반)
-- ================================
-- 프로젝트 기본 정보
CREATE TABLE IF NOT EXISTS projects (
id SERIAL PRIMARY KEY,
-- 회사 공식 정보
official_project_code VARCHAR(50) UNIQUE, -- PP5-5701-DRYING (정식 코드)
project_name VARCHAR(200) NOT NULL,
client_name VARCHAR(100),
-- 설계팀 사용 정보
design_project_code VARCHAR(50), -- 설계팀이 실제 사용한 코드
design_project_name VARCHAR(200), -- 설계팀이 사용한 프로젝트명
-- 매칭 정보
is_code_matched BOOLEAN DEFAULT false, -- 정식 코드 매칭 완료 여부
matched_by VARCHAR(100), -- 매칭 작업자
matched_at TIMESTAMP, -- 매칭 완료 시점
-- 기본 정보
status VARCHAR(20) DEFAULT 'active', -- active/completed/on_hold
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
description TEXT,
notes TEXT
);
-- 업로드된 자재 목록 파일들
CREATE TABLE IF NOT EXISTS files (
id SERIAL PRIMARY KEY,
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
revision VARCHAR(20) DEFAULT 'Rev.0', -- Rev.0, Rev.1, Rev.2
upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
uploaded_by VARCHAR(100),
file_type VARCHAR(10), -- excel/csv/txt
file_size INTEGER,
is_active BOOLEAN DEFAULT true,
-- 추가 필드 (19_add_user_tracking_fields.sql)
updated_by VARCHAR(100),
bom_name VARCHAR(200), -- BOM 이름 필드 추가
-- 분류 관련 필드 (05_add_classification_columns.sql)
classification_stats JSONB,
classification_completed BOOLEAN DEFAULT FALSE,
classification_timestamp TIMESTAMP,
-- Job 연결 필드 (02_modify_files_table.sql)
job_no VARCHAR(50),
-- 파싱 통계 필드
parsed_count INTEGER DEFAULT 0
);
-- 개별 자재 상세 정보
CREATE TABLE IF NOT EXISTS materials (
id SERIAL PRIMARY KEY,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
line_number INTEGER, -- Excel 행 번호
row_number INTEGER, -- 실제 행 번호
original_description TEXT NOT NULL, -- 원본 품명
-- 분류 정보
classified_category VARCHAR(50), -- 파이프/피팅/볼트/밸브/계기
classified_subcategory VARCHAR(100), -- 엘보우/플랜지/벤드 등
-- 재질 및 스펙
material_grade VARCHAR(50), -- A333-6, A105, SS316
schedule VARCHAR(20), -- SCH40, SCH80, STD
size_spec VARCHAR(50), -- 6인치, 150A, M16
main_nom VARCHAR(50), -- 주 호칭 사이즈
red_nom VARCHAR(50), -- 축소 호칭 사이즈
-- 수량 정보
quantity DECIMAL(10,3) NOT NULL,
unit VARCHAR(10) NOT NULL, -- EA/mm/SET/kg
-- 도면 정보
drawing_name VARCHAR(100), -- P&ID-001-TANK
area_code VARCHAR(20), -- #01, #02, #03
line_no VARCHAR(50), -- LINE-001-A
-- 분류 신뢰도 및 검증
classification_confidence DECIMAL(3,2), -- 0.00-1.00 분류 신뢰도
classification_details JSONB, -- 분류 상세 정보 (JSON)
is_verified BOOLEAN DEFAULT false, -- 사용자 검증 여부
verified_by VARCHAR(100),
verified_at TIMESTAMP,
-- 구매 관련 필드
purchase_confirmed BOOLEAN DEFAULT false,
confirmed_quantity DECIMAL(10,3),
purchase_status VARCHAR(20),
purchase_confirmed_by VARCHAR(100),
purchase_confirmed_at TIMESTAMP,
-- 길이 정보 (05_add_length_to_materials.sql)
length NUMERIC(10, 3),
-- 사용자 추적 필드 (19_add_user_tracking_fields.sql)
classified_by VARCHAR(100),
classified_at TIMESTAMP,
updated_by VARCHAR(100),
-- 분류 상세 필드 (05_add_classification_columns.sql)
subcategory VARCHAR(100),
standard VARCHAR(200),
grade VARCHAR(200),
-- 해시 필드 (10_add_material_comparison_system.sql)
material_hash VARCHAR(64),
normalized_description TEXT,
-- 기타
drawing_reference VARCHAR(100),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 2. 자재 상세 정보 테이블들 (create_material_detail_tables.sql)
-- ================================
-- PIPE 상세 테이블
CREATE TABLE IF NOT EXISTS pipe_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 파이프 기본 정보
outer_diameter VARCHAR(50),
schedule VARCHAR(50),
material_spec VARCHAR(100),
manufacturing_method VARCHAR(50),
end_preparation VARCHAR(50),
-- 길이 정보
length_mm DECIMAL(10,3),
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- FITTING 상세 테이블
CREATE TABLE IF NOT EXISTS fitting_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 피팅 타입 정보
fitting_type VARCHAR(50),
fitting_subtype VARCHAR(50),
-- 연결 방식
connection_method VARCHAR(50),
connection_code VARCHAR(50),
-- 압력 등급
pressure_rating VARCHAR(50),
max_pressure VARCHAR(50),
-- 제작 방법
manufacturing_method VARCHAR(50),
-- 재질 정보
material_standard VARCHAR(100),
material_grade VARCHAR(100),
material_type VARCHAR(50),
-- 사이즈 정보
main_size VARCHAR(50),
reduced_size VARCHAR(50),
-- 길이 정보 (니플용)
length_mm DECIMAL(10,3),
schedule VARCHAR(50),
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- VALVE 상세 테이블
CREATE TABLE IF NOT EXISTS valve_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 밸브 타입 정보
valve_type VARCHAR(50),
valve_subtype VARCHAR(50),
actuator_type VARCHAR(50),
-- 연결 방식
connection_method VARCHAR(50),
-- 압력 등급
pressure_rating VARCHAR(50),
pressure_class VARCHAR(50),
-- 재질 정보
body_material VARCHAR(100),
trim_material VARCHAR(100),
-- 사이즈 정보
size_inches VARCHAR(50),
-- 특수 사양
fire_safe BOOLEAN,
low_temp_service BOOLEAN,
special_features JSONB,
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- FLANGE 상세 테이블
CREATE TABLE IF NOT EXISTS flange_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 플랜지 타입
flange_type VARCHAR(50),
facing_type VARCHAR(50),
-- 압력 등급
pressure_rating VARCHAR(50),
-- 재질 정보
material_standard VARCHAR(100),
material_grade VARCHAR(100),
-- 사이즈 정보
size_inches VARCHAR(50),
bolt_hole_count INTEGER,
bolt_hole_size VARCHAR(50),
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- BOLT 상세 테이블
CREATE TABLE IF NOT EXISTS bolt_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 볼트 타입
bolt_type VARCHAR(50),
thread_type VARCHAR(50),
-- 사양 정보
diameter VARCHAR(50),
length VARCHAR(50),
-- 재질 정보
material_standard VARCHAR(100),
material_grade VARCHAR(100),
coating_type VARCHAR(100),
-- 압력 등급 (06_add_pressure_rating_to_bolt_details.sql)
pressure_rating VARCHAR(50),
-- 너트/와셔 정보
includes_nut BOOLEAN,
includes_washer BOOLEAN,
nut_type VARCHAR(50),
washer_type VARCHAR(50),
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- GASKET 상세 테이블
CREATE TABLE IF NOT EXISTS gasket_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 가스켓 타입
gasket_type VARCHAR(50),
gasket_subtype VARCHAR(50),
-- 재질 정보
material_type VARCHAR(100),
filler_material VARCHAR(100),
-- 사이즈 및 등급
size_inches VARCHAR(50),
pressure_rating VARCHAR(50),
thickness VARCHAR(50),
-- 특수 사양
temperature_range VARCHAR(100),
fire_safe BOOLEAN,
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- INSTRUMENT 상세 테이블
CREATE TABLE IF NOT EXISTS instrument_details (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER REFERENCES files(id) ON DELETE CASCADE,
-- 계장품 타입
instrument_type VARCHAR(50),
instrument_subtype VARCHAR(50),
-- 측정 사양
measurement_type VARCHAR(50),
measurement_range VARCHAR(100),
accuracy VARCHAR(50),
-- 연결 정보
connection_type VARCHAR(50),
connection_size VARCHAR(50),
-- 재질 정보
body_material VARCHAR(100),
wetted_parts_material VARCHAR(100),
-- 전기 사양
electrical_rating VARCHAR(100),
output_signal VARCHAR(50),
-- 신뢰도
classification_confidence FLOAT,
-- 추가 정보
additional_info JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 3. 파이프 끝단 가공 테이블 (20_add_pipe_end_preparation_table.sql)
-- ================================
CREATE TABLE IF NOT EXISTS pipe_end_preparations (
id SERIAL PRIMARY KEY,
material_id INTEGER NOT NULL REFERENCES materials(id) ON DELETE CASCADE,
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
-- 끝단 가공 정보
end_preparation_type VARCHAR(50) DEFAULT 'PBE', -- PBE(양쪽무개선), BBE(양쪽개선), POE(한쪽개선), PE(무개선)
end_preparation_code VARCHAR(20), -- 원본 코드 (BBE, POE, PBE 등)
machining_required BOOLEAN DEFAULT FALSE, -- 가공 필요 여부
cutting_note TEXT, -- 가공 메모
-- 원본 정보 보존
original_description TEXT NOT NULL, -- 끝단 가공 포함된 원본 설명
clean_description TEXT NOT NULL, -- 끝단 가공 제외한 구매용 설명
-- 메타데이터
confidence FLOAT DEFAULT 0.0, -- 분류 신뢰도
matched_pattern VARCHAR(100), -- 매칭된 패턴
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 4. 인증 시스템 테이블들 (18_create_auth_tables.sql)
-- ================================
-- 사용자 테이블
CREATE TABLE IF NOT EXISTS users (
user_id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
email VARCHAR(100),
-- 권한 관리
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('admin', 'system', 'leader', 'support', 'user')),
access_level VARCHAR(20) DEFAULT 'worker' CHECK (access_level IN ('admin', 'system', 'group_leader', 'support_team', 'worker')),
-- 계정 상태 관리
is_active BOOLEAN DEFAULT true,
failed_login_attempts INT DEFAULT 0,
locked_until TIMESTAMP NULL,
-- 추가 정보
department VARCHAR(50),
position VARCHAR(50),
phone VARCHAR(20),
-- 타임스탬프
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP NULL
);
-- 로그인 이력 테이블
CREATE TABLE IF NOT EXISTS login_logs (
log_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id) ON DELETE CASCADE,
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
login_status VARCHAR(20) CHECK (login_status IN ('success', 'failed')),
failure_reason VARCHAR(100),
session_duration INT, -- 세션 지속 시간 (초)
-- 인덱스를 위한 컬럼
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 사용자 세션 테이블 (JWT Refresh Token 관리)
CREATE TABLE IF NOT EXISTS user_sessions (
session_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id) ON DELETE CASCADE,
refresh_token VARCHAR(500) NOT NULL,
expires_at TIMESTAMP NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 권한 테이블
CREATE TABLE IF NOT EXISTS permissions (
permission_id SERIAL PRIMARY KEY,
permission_name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
module VARCHAR(30), -- 모듈별 권한 관리 (bom, project, purchase 등)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 역할-권한 매핑 테이블
CREATE TABLE IF NOT EXISTS role_permissions (
role_permission_id SERIAL PRIMARY KEY,
role VARCHAR(20) NOT NULL,
permission_id INT REFERENCES permissions(permission_id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(role, permission_id)
);
-- ================================
-- 5. 구매 관리 테이블들 (08_create_purchase_tables.sql)
-- ================================
-- 구매 품목 마스터 테이블
CREATE TABLE IF NOT EXISTS purchase_items (
id SERIAL PRIMARY KEY,
-- 품목 식별
item_code VARCHAR(100) UNIQUE NOT NULL, -- PI-PIPE-A106-4IN-001
category VARCHAR(50) NOT NULL, -- PIPE, VALVE, FITTING 등
specification TEXT NOT NULL, -- 상세 사양
-- 기본 정보
material_spec VARCHAR(200), -- ASTM A106 GR B
size_spec VARCHAR(100), -- 4", 1-1/2" x 1"
unit VARCHAR(10) NOT NULL, -- EA, mm, SET
-- BOM 수량 정보
bom_quantity DECIMAL(10,3) NOT NULL, -- BOM상 필요 수량
-- 구매 계산 정보
safety_factor DECIMAL(3,2) DEFAULT 1.10, -- 여유율 (1.1 = 10% 추가)
minimum_order_qty DECIMAL(10,3) DEFAULT 0, -- 최소 주문 수량
order_unit_qty DECIMAL(10,3) DEFAULT 1, -- 주문 단위 (박스, 롤 등)
calculated_qty DECIMAL(10,3), -- 최종 계산된 구매 수량
-- PIPE 전용 정보
cutting_loss DECIMAL(10,3) DEFAULT 0, -- 절단 손실 (mm)
standard_length DECIMAL(10,3), -- 표준 길이 (PIPE: 6000mm)
pipes_count INTEGER, -- 필요한 파이프 본수
waste_length DECIMAL(10,3), -- 여유분 길이
-- 상세 스펙 (JSON)
detailed_spec JSONB, -- 압력등급, 연결방식 등 상세 정보
-- 구매 정보
preferred_supplier VARCHAR(200), -- 선호 공급업체
last_unit_price DECIMAL(10,2), -- 최근 단가
currency VARCHAR(10) DEFAULT 'KRW', -- 통화
lead_time_days INTEGER DEFAULT 30, -- 리드타임
-- 연결 정보
job_no VARCHAR(50) NOT NULL, -- Job 번호
revision VARCHAR(20) DEFAULT 'Rev.0', -- 리비전
file_id INTEGER, -- 원본 파일 ID
-- 관리 정보
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100),
approved_by VARCHAR(100),
approved_at TIMESTAMP,
-- 외래키
FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE SET NULL
);
-- 개별 자재와 구매품목 연결 테이블
CREATE TABLE IF NOT EXISTS material_purchase_mapping (
id SERIAL PRIMARY KEY,
material_id INTEGER NOT NULL, -- materials 테이블 ID
purchase_item_id INTEGER NOT NULL, -- purchase_items 테이블 ID
quantity_ratio DECIMAL(5,2) DEFAULT 1.0, -- 변환 비율
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 외래키
FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE,
FOREIGN KEY (purchase_item_id) REFERENCES purchase_items(id) ON DELETE CASCADE,
-- 유니크 제약
UNIQUE(material_id, purchase_item_id)
);
-- 구매 추적 테이블
CREATE TABLE IF NOT EXISTS material_purchase_tracking (
id SERIAL PRIMARY KEY,
material_hash VARCHAR(64) NOT NULL,
-- 기본 정보
original_description TEXT NOT NULL,
size_spec VARCHAR(50),
material_grade VARCHAR(50),
-- 수량 정보
bom_quantity DECIMAL(10,3) NOT NULL,
confirmed_quantity DECIMAL(10,3),
purchase_quantity DECIMAL(10,3),
-- 상태 관리
status VARCHAR(20) DEFAULT 'pending',
confirmed_by VARCHAR(100),
confirmed_at TIMESTAMP,
ordered_by VARCHAR(100),
ordered_at TIMESTAMP,
approved_by VARCHAR(100),
approved_at TIMESTAMP,
-- 메타데이터
job_no VARCHAR(50),
revision VARCHAR(20),
file_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE SET NULL
);
-- ================================
-- 6. Jobs 테이블 (01_create_jobs_table.sql)
-- ================================
CREATE TABLE IF NOT EXISTS jobs (
-- 기본 정보
job_no VARCHAR(50) PRIMARY KEY,
job_name VARCHAR(200) NOT NULL,
-- 계약 관계 (핵심)
client_name VARCHAR(100) NOT NULL,
-- 프로젝트 정보
end_user VARCHAR(100),
epc_company VARCHAR(100),
project_site VARCHAR(200),
-- 상업 정보
contract_date DATE,
delivery_date DATE,
delivery_terms VARCHAR(100),
-- 상태 관리 (핵심)
status VARCHAR(20) DEFAULT '진행중',
delivery_completed_date DATE,
project_closed_date DATE,
-- 관리 정보
description TEXT,
created_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT true,
-- 사용자 추적 필드 (19_add_user_tracking_fields.sql)
updated_by VARCHAR(100),
assigned_to VARCHAR(100),
-- 프로젝트 타입 필드 (17_add_project_type_column.sql)
project_type VARCHAR(50) DEFAULT '냉동기' NOT NULL
);
-- ================================
-- 7. 자재 규격/재질 기준표 테이블들 (05_create_material_standards_tables.sql)
-- ================================
-- 자재 규격 표준 테이블
CREATE TABLE IF NOT EXISTS material_standards (
id SERIAL PRIMARY KEY,
standard_code VARCHAR(20) UNIQUE NOT NULL,
standard_name VARCHAR(100) NOT NULL,
description TEXT,
country VARCHAR(50),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 제조방식별 카테고리 테이블
CREATE TABLE IF NOT EXISTS material_categories (
id SERIAL PRIMARY KEY,
standard_id INTEGER REFERENCES material_standards(id),
category_code VARCHAR(50) NOT NULL,
category_name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 구체적인 규격 테이블
CREATE TABLE IF NOT EXISTS material_specifications (
id SERIAL PRIMARY KEY,
category_id INTEGER REFERENCES material_categories(id),
spec_code VARCHAR(20) NOT NULL,
spec_name VARCHAR(100) NOT NULL,
description TEXT,
material_type VARCHAR(50),
manufacturing VARCHAR(50),
pressure_rating VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 등급별 상세 정보 테이블
CREATE TABLE IF NOT EXISTS material_grades (
id SERIAL PRIMARY KEY,
specification_id INTEGER REFERENCES material_specifications(id),
grade_code VARCHAR(20) NOT NULL,
grade_name VARCHAR(100),
composition VARCHAR(200),
applications VARCHAR(200),
temp_max VARCHAR(50),
temp_range VARCHAR(100),
yield_strength VARCHAR(50),
tensile_strength VARCHAR(50),
corrosion_resistance VARCHAR(50),
stabilizer VARCHAR(50),
base_grade VARCHAR(20),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 정규식 패턴 테이블
CREATE TABLE IF NOT EXISTS material_patterns (
id SERIAL PRIMARY KEY,
specification_id INTEGER REFERENCES material_specifications(id),
pattern TEXT NOT NULL,
description VARCHAR(200),
priority INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 특수 재질 테이블
CREATE TABLE IF NOT EXISTS special_materials (
id SERIAL PRIMARY KEY,
material_type VARCHAR(50) NOT NULL,
material_name VARCHAR(100) NOT NULL,
description TEXT,
composition VARCHAR(200),
applications TEXT,
temp_max VARCHAR(50),
manufacturing VARCHAR(50),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 특수 재질 등급 테이블
CREATE TABLE IF NOT EXISTS special_material_grades (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES special_materials(id),
grade_code VARCHAR(20) NOT NULL,
composition VARCHAR(200),
applications VARCHAR(200),
temp_max VARCHAR(50),
strength VARCHAR(50),
purity VARCHAR(100),
corrosion VARCHAR(50),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 특수 재질 정규식 패턴 테이블
CREATE TABLE IF NOT EXISTS special_material_patterns (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES special_materials(id),
pattern TEXT NOT NULL,
description VARCHAR(200),
priority INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 8. 자재 비교 및 발주 추적 시스템 (10_add_material_comparison_system.sql)
-- ================================
-- 자재 비교 결과 저장 테이블
CREATE TABLE IF NOT EXISTS material_revisions_comparison (
id SERIAL PRIMARY KEY,
-- 비교 기본 정보
job_no VARCHAR(50) NOT NULL,
current_revision VARCHAR(20) NOT NULL,
previous_revision VARCHAR(20) NOT NULL,
current_file_id INTEGER NOT NULL,
previous_file_id INTEGER NOT NULL,
-- 비교 결과 요약
total_current_items INTEGER DEFAULT 0,
total_previous_items INTEGER DEFAULT 0,
new_items_count INTEGER DEFAULT 0,
modified_items_count INTEGER DEFAULT 0,
removed_items_count INTEGER DEFAULT 0,
unchanged_items_count INTEGER DEFAULT 0,
-- 상세 결과 (JSON)
comparison_details JSONB,
-- 관리 정보
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100),
-- 외래키
FOREIGN KEY (current_file_id) REFERENCES files(id),
FOREIGN KEY (previous_file_id) REFERENCES files(id),
-- 유니크 제약 (같은 비교는 한 번만)
UNIQUE(job_no, current_revision, previous_revision)
);
-- 개별 자재 비교 상세 테이블
CREATE TABLE IF NOT EXISTS material_comparison_details (
id SERIAL PRIMARY KEY,
comparison_id INTEGER NOT NULL,
material_hash VARCHAR(32) NOT NULL,
-- 비교 타입
change_type VARCHAR(20) NOT NULL, -- 'NEW', 'MODIFIED', 'REMOVED', 'UNCHANGED'
-- 자재 정보
description TEXT NOT NULL,
size_spec VARCHAR(100),
material_grade VARCHAR(100),
-- 수량 비교
previous_quantity DECIMAL(10,3) DEFAULT 0,
current_quantity DECIMAL(10,3) DEFAULT 0,
quantity_diff DECIMAL(10,3) DEFAULT 0,
-- 추가 구매 필요량 (핵심!)
additional_purchase_needed DECIMAL(10,3) DEFAULT 0,
-- 분류 정보
classified_category VARCHAR(50),
classification_confidence DECIMAL(3,2),
-- 관리 정보
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 외래키
FOREIGN KEY (comparison_id) REFERENCES material_revisions_comparison(id) ON DELETE CASCADE
);
-- ================================
-- 9. Tubing 시스템 테이블들 (15_create_tubing_system.sql)
-- ================================
-- Tubing 카테고리 테이블
CREATE TABLE IF NOT EXISTS tubing_categories (
id SERIAL PRIMARY KEY,
category_code VARCHAR(20) UNIQUE NOT NULL,
category_name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tubing 규격 마스터 테이블
CREATE TABLE IF NOT EXISTS tubing_specifications (
id SERIAL PRIMARY KEY,
category_id INTEGER REFERENCES tubing_categories(id),
spec_code VARCHAR(50) UNIQUE NOT NULL,
spec_name VARCHAR(200) NOT NULL,
-- 물리적 규격
outer_diameter_mm DECIMAL(8,3), -- 외경 (mm)
wall_thickness_mm DECIMAL(6,3), -- 두께 (mm)
inner_diameter_mm DECIMAL(8,3), -- 내경 (mm, 계산 또는 실측)
-- 재질 정보
material_grade VARCHAR(100), -- SS316, SS316L, Inconel625 등
material_standard VARCHAR(100), -- ASTM A269, JIS G3463 등
-- 압력/온도 등급
max_pressure_bar DECIMAL(8,2), -- 최대 압력 (bar)
max_temperature_c DECIMAL(6,2), -- 최대 온도 (°C)
min_temperature_c DECIMAL(6,2), -- 최소 온도 (°C)
-- 표준 규격
standard_length_m DECIMAL(8,3), -- 표준 길이 (m)
bend_radius_min_mm DECIMAL(8,2), -- 최소 벤딩 반경 (mm)
-- 기타 정보
surface_finish VARCHAR(100), -- 표면 마감 (BA, #4, 2B 등)
hardness VARCHAR(50), -- 경도
notes TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 제조사 정보 테이블
CREATE TABLE IF NOT EXISTS tubing_manufacturers (
id SERIAL PRIMARY KEY,
manufacturer_code VARCHAR(20) UNIQUE NOT NULL,
manufacturer_name VARCHAR(200) NOT NULL,
country VARCHAR(100),
website VARCHAR(500),
contact_info JSONB, -- 연락처 정보 (JSON)
quality_certs JSONB, -- 품질 인증서 정보 (ISO, API 등)
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 제조사별 제품 테이블
CREATE TABLE IF NOT EXISTS tubing_products (
id SERIAL PRIMARY KEY,
specification_id INTEGER REFERENCES tubing_specifications(id),
manufacturer_id INTEGER REFERENCES tubing_manufacturers(id),
-- 제조사 품목번호 정보
manufacturer_part_number VARCHAR(200) NOT NULL, -- 제조사 품목번호
manufacturer_product_name VARCHAR(300), -- 제조사 제품명
-- 가격/공급 정보
list_price DECIMAL(12,2), -- 정가
currency VARCHAR(10) DEFAULT 'KRW', -- 통화
lead_time_days INTEGER, -- 리드타임 (일)
minimum_order_qty DECIMAL(10,3), -- 최소 주문 수량
standard_packaging_qty DECIMAL(10,3), -- 표준 포장 수량
-- 가용성 정보
availability_status VARCHAR(50), -- 재고 상태
last_price_update DATE, -- 마지막 가격 업데이트
-- 추가 정보
datasheet_url VARCHAR(500), -- 데이터시트 URL
catalog_page VARCHAR(100), -- 카탈로그 페이지
notes TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 유니크 제약 (같은 규격의 같은 제조사 제품은 하나만)
UNIQUE(specification_id, manufacturer_id, manufacturer_part_number)
);
-- BOM에서 사용되는 Tubing 매핑 테이블
CREATE TABLE IF NOT EXISTS material_tubing_mapping (
id SERIAL PRIMARY KEY,
material_id INTEGER REFERENCES materials(id) ON DELETE CASCADE,
tubing_product_id INTEGER REFERENCES tubing_products(id),
-- 매핑 정보
confidence_score DECIMAL(3,2), -- 매핑 신뢰도 (0.00-1.00)
mapping_method VARCHAR(50), -- 매핑 방법 (auto/manual)
mapped_by VARCHAR(100), -- 매핑한 사용자
mapped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 수량 정보
required_length_m DECIMAL(10,3), -- 필요 길이 (m)
calculated_quantity DECIMAL(10,3), -- 계산된 주문 수량
-- 검증 정보
is_verified BOOLEAN DEFAULT FALSE,
verified_by VARCHAR(100),
verified_at TIMESTAMP,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 10. 사용자 요구사항 테이블들 (05_create_pipe_details_and_requirements_postgres.sql)
-- ================================
-- 요구사항 타입 마스터 테이블
CREATE TABLE IF NOT EXISTS requirement_types (
id SERIAL PRIMARY KEY,
type_code VARCHAR(50) UNIQUE NOT NULL, -- 'IMPACT_TEST', 'HEAT_TREATMENT' 등
type_name VARCHAR(100) NOT NULL, -- '임팩테스트', '열처리' 등
category VARCHAR(50) NOT NULL, -- 'TEST', 'TREATMENT', 'CERTIFICATION', 'CUSTOM' 등
description TEXT, -- 타입 설명
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 사용자 추가 요구사항 테이블
CREATE TABLE IF NOT EXISTS user_requirements (
id SERIAL PRIMARY KEY,
file_id INTEGER NOT NULL,
material_id INTEGER, -- 자재 ID (개별 자재별 요구사항 연결)
-- 요구사항 타입
requirement_type VARCHAR(50) NOT NULL, -- 'IMPACT_TEST', 'HEAT_TREATMENT', 'CUSTOM_SPEC', 'CERTIFICATION' 등
-- 요구사항 내용
requirement_title VARCHAR(200) NOT NULL, -- '임팩테스트', '열처리', '인증서' 등
requirement_description TEXT, -- 상세 설명
requirement_spec TEXT, -- 구체적 스펙 (예: "Charpy V-notch -20°C")
-- 상태 관리
status VARCHAR(20) DEFAULT 'PENDING', -- 'PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED'
priority VARCHAR(20) DEFAULT 'NORMAL', -- 'LOW', 'NORMAL', 'HIGH', 'URGENT'
-- 담당자 정보
assigned_to VARCHAR(100), -- 담당자명
due_date DATE, -- 완료 예정일
-- 메타데이터
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE,
FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE
);
-- ================================
-- 11. 사용자 활동 로그 테이블 (19_add_user_tracking_fields.sql)
-- ================================
CREATE TABLE IF NOT EXISTS user_activity_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER, -- users 테이블 참조 (외래키 제약 없음 - 유연성)
username VARCHAR(100) NOT NULL, -- 사용자명 (필수)
-- 활동 정보
activity_type VARCHAR(50) NOT NULL, -- 'FILE_UPLOAD', 'PROJECT_CREATE', 'PURCHASE_CONFIRM' 등
activity_description TEXT, -- 상세 활동 내용
-- 대상 정보
target_id INTEGER, -- 대상 ID (파일, 프로젝트 등)
target_type VARCHAR(50), -- 'FILE', 'PROJECT', 'MATERIAL', 'PURCHASE' 등
-- 세션 정보
ip_address VARCHAR(45), -- IP 주소
user_agent TEXT, -- 브라우저 정보
-- 추가 메타데이터 (JSON)
metadata JSONB, -- 추가 정보 (파일 크기, 처리 시간 등)
-- 시간 정보
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ================================
-- 7. 인덱스 생성 (성능 최적화)
-- ================================
-- 프로젝트 관련 인덱스
CREATE INDEX IF NOT EXISTS idx_projects_official_code ON projects(official_project_code);
CREATE INDEX IF NOT EXISTS idx_projects_design_code ON projects(design_project_code);
-- 파일 관련 인덱스
CREATE INDEX IF NOT EXISTS idx_files_project ON files(project_id);
CREATE INDEX IF NOT EXISTS idx_files_active ON files(is_active);
CREATE INDEX IF NOT EXISTS idx_files_uploaded_by ON files(uploaded_by);
CREATE INDEX IF NOT EXISTS idx_files_updated_by ON files(updated_by);
-- 자재 관련 인덱스
CREATE INDEX IF NOT EXISTS idx_materials_file ON materials(file_id);
CREATE INDEX IF NOT EXISTS idx_materials_category ON materials(classified_category, classified_subcategory);
CREATE INDEX IF NOT EXISTS idx_materials_material_size ON materials(material_grade, size_spec);
CREATE INDEX IF NOT EXISTS idx_materials_classified_by ON materials(classified_by);
CREATE INDEX IF NOT EXISTS idx_materials_hash ON materials(material_hash);
-- 자재 상세 테이블 인덱스
CREATE INDEX IF NOT EXISTS idx_pipe_details_material_id ON pipe_details(material_id);
CREATE INDEX IF NOT EXISTS idx_pipe_details_file_id ON pipe_details(file_id);
CREATE INDEX IF NOT EXISTS idx_fitting_details_material_id ON fitting_details(material_id);
CREATE INDEX IF NOT EXISTS idx_fitting_details_file_id ON fitting_details(file_id);
CREATE INDEX IF NOT EXISTS idx_fitting_details_type ON fitting_details(fitting_type);
CREATE INDEX IF NOT EXISTS idx_valve_details_material_id ON valve_details(material_id);
CREATE INDEX IF NOT EXISTS idx_valve_details_file_id ON valve_details(file_id);
CREATE INDEX IF NOT EXISTS idx_valve_details_type ON valve_details(valve_type);
CREATE INDEX IF NOT EXISTS idx_flange_details_material_id ON flange_details(material_id);
CREATE INDEX IF NOT EXISTS idx_flange_details_file_id ON flange_details(file_id);
CREATE INDEX IF NOT EXISTS idx_bolt_details_material_id ON bolt_details(material_id);
CREATE INDEX IF NOT EXISTS idx_bolt_details_file_id ON bolt_details(file_id);
CREATE INDEX IF NOT EXISTS idx_gasket_details_material_id ON gasket_details(material_id);
CREATE INDEX IF NOT EXISTS idx_gasket_details_file_id ON gasket_details(file_id);
CREATE INDEX IF NOT EXISTS idx_instrument_details_material_id ON instrument_details(material_id);
CREATE INDEX IF NOT EXISTS idx_instrument_details_file_id ON instrument_details(file_id);
-- 파이프 끝단 가공 테이블 인덱스
CREATE INDEX IF NOT EXISTS idx_pipe_end_preparations_material_id ON pipe_end_preparations(material_id);
CREATE INDEX IF NOT EXISTS idx_pipe_end_preparations_file_id ON pipe_end_preparations(file_id);
CREATE INDEX IF NOT EXISTS idx_pipe_end_preparations_type ON pipe_end_preparations(end_preparation_type);
-- 인증 시스템 인덱스
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
CREATE INDEX IF NOT EXISTS idx_login_logs_user_id ON login_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_login_logs_login_time ON login_logs(login_time);
CREATE INDEX IF NOT EXISTS idx_login_logs_ip_address ON login_logs(ip_address);
CREATE INDEX IF NOT EXISTS idx_login_logs_status ON login_logs(login_status);
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_user_sessions_refresh_token ON user_sessions(refresh_token);
CREATE INDEX IF NOT EXISTS idx_user_sessions_expires_at ON user_sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_user_sessions_is_active ON user_sessions(is_active);
CREATE INDEX IF NOT EXISTS idx_permissions_module ON permissions(module);
CREATE INDEX IF NOT EXISTS idx_role_permissions_role ON role_permissions(role);
-- 구매 관련 인덱스
CREATE INDEX IF NOT EXISTS idx_purchase_items_job_revision ON purchase_items(job_no, revision);
CREATE INDEX IF NOT EXISTS idx_purchase_items_category ON purchase_items(category);
CREATE INDEX IF NOT EXISTS idx_purchase_items_item_code ON purchase_items(item_code);
CREATE INDEX IF NOT EXISTS idx_purchase_items_active ON purchase_items(is_active);
CREATE INDEX IF NOT EXISTS idx_material_purchase_mapping_material ON material_purchase_mapping(material_id);
CREATE INDEX IF NOT EXISTS idx_material_purchase_mapping_purchase ON material_purchase_mapping(purchase_item_id);
CREATE INDEX IF NOT EXISTS idx_material_purchase_tracking_hash ON material_purchase_tracking(material_hash);
CREATE INDEX IF NOT EXISTS idx_material_purchase_tracking_job_revision ON material_purchase_tracking(job_no, revision);
-- Jobs 테이블 인덱스
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
CREATE INDEX IF NOT EXISTS idx_jobs_client ON jobs(client_name);
CREATE INDEX IF NOT EXISTS idx_jobs_created_at ON jobs(created_at);
CREATE INDEX IF NOT EXISTS idx_jobs_created_by ON jobs(created_by);
CREATE INDEX IF NOT EXISTS idx_jobs_assigned_to ON jobs(assigned_to);
-- 자재 규격/재질 기준표 인덱스
CREATE INDEX IF NOT EXISTS idx_material_standards_code ON material_standards(standard_code);
CREATE INDEX IF NOT EXISTS idx_material_categories_standard ON material_categories(standard_id);
CREATE INDEX IF NOT EXISTS idx_material_specifications_category ON material_specifications(category_id);
CREATE INDEX IF NOT EXISTS idx_material_grades_specification ON material_grades(specification_id);
CREATE INDEX IF NOT EXISTS idx_material_patterns_specification ON material_patterns(specification_id);
CREATE INDEX IF NOT EXISTS idx_special_materials_type ON special_materials(material_type);
CREATE INDEX IF NOT EXISTS idx_special_material_grades_material ON special_material_grades(material_id);
CREATE INDEX IF NOT EXISTS idx_special_material_patterns_material ON special_material_patterns(material_id);
-- 활성 상태 인덱스
CREATE INDEX IF NOT EXISTS idx_material_standards_active ON material_standards(is_active);
CREATE INDEX IF NOT EXISTS idx_material_categories_active ON material_categories(is_active);
CREATE INDEX IF NOT EXISTS idx_material_specifications_active ON material_specifications(is_active);
CREATE INDEX IF NOT EXISTS idx_material_grades_active ON material_grades(is_active);
CREATE INDEX IF NOT EXISTS idx_material_patterns_active ON material_patterns(is_active);
CREATE INDEX IF NOT EXISTS idx_special_materials_active ON special_materials(is_active);
CREATE INDEX IF NOT EXISTS idx_special_material_grades_active ON special_material_grades(is_active);
CREATE INDEX IF NOT EXISTS idx_special_material_patterns_active ON special_material_patterns(is_active);
-- 자재 비교 시스템 인덱스
CREATE INDEX IF NOT EXISTS idx_material_revisions_job ON material_revisions_comparison(job_no);
CREATE INDEX IF NOT EXISTS idx_material_revisions_current ON material_revisions_comparison(current_revision);
CREATE INDEX IF NOT EXISTS idx_material_comparison_hash ON material_comparison_details(material_hash);
CREATE INDEX IF NOT EXISTS idx_material_comparison_type ON material_comparison_details(change_type);
-- Tubing 시스템 인덱스
CREATE INDEX IF NOT EXISTS idx_tubing_specs_category ON tubing_specifications(category_id);
CREATE INDEX IF NOT EXISTS idx_tubing_specs_material ON tubing_specifications(material_grade);
CREATE INDEX IF NOT EXISTS idx_tubing_specs_diameter ON tubing_specifications(outer_diameter_mm, wall_thickness_mm);
CREATE INDEX IF NOT EXISTS idx_tubing_products_spec ON tubing_products(specification_id);
CREATE INDEX IF NOT EXISTS idx_tubing_products_manufacturer ON tubing_products(manufacturer_id);
CREATE INDEX IF NOT EXISTS idx_tubing_products_part_number ON tubing_products(manufacturer_part_number);
CREATE INDEX IF NOT EXISTS idx_material_tubing_mapping_material ON material_tubing_mapping(material_id);
CREATE INDEX IF NOT EXISTS idx_material_tubing_mapping_product ON material_tubing_mapping(tubing_product_id);
-- 사용자 활동 로그 인덱스
CREATE INDEX IF NOT EXISTS idx_user_activity_logs_username ON user_activity_logs(username);
CREATE INDEX IF NOT EXISTS idx_user_activity_logs_activity_type ON user_activity_logs(activity_type);
CREATE INDEX IF NOT EXISTS idx_user_activity_logs_created_at ON user_activity_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_user_activity_logs_target ON user_activity_logs(target_type, target_id);
-- ================================
-- 추가 성능 최적화 인덱스 (16_performance_indexes.sql)
-- ================================
-- 복합 인덱스 (자주 함께 사용되는 컬럼들)
CREATE INDEX IF NOT EXISTS idx_files_job_revision ON files(job_no, revision) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_files_job_date ON files(job_no, upload_date DESC) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_materials_file_category ON materials(file_id, classified_category);
CREATE INDEX IF NOT EXISTS idx_materials_category_grade ON materials(classified_category, material_grade);
-- 검색 성능 향상 인덱스
CREATE INDEX IF NOT EXISTS idx_materials_description_gin ON materials USING gin(to_tsvector('english', original_description));
-- 정렬 성능 향상 인덱스
CREATE INDEX IF NOT EXISTS idx_jobs_status_created ON jobs(status, created_at DESC) WHERE is_active = true;
CREATE INDEX IF NOT EXISTS idx_materials_quantity_desc ON materials(quantity DESC);
-- 조건부 인덱스 (특정 조건에서만 사용)
CREATE INDEX IF NOT EXISTS idx_materials_unverified ON materials(classified_category, classification_confidence) WHERE is_verified = false;
CREATE INDEX IF NOT EXISTS idx_materials_low_confidence ON materials(file_id, classified_category) WHERE classification_confidence < 0.8;
-- 분류 관련 인덱스 (05_add_classification_columns.sql)
CREATE INDEX IF NOT EXISTS idx_materials_subcategory ON materials(subcategory);
CREATE INDEX IF NOT EXISTS idx_materials_standard ON materials(standard);
CREATE INDEX IF NOT EXISTS idx_materials_grade ON materials(grade);
-- Job 연결 인덱스 (02_modify_files_table.sql)
CREATE INDEX IF NOT EXISTS idx_files_job_no ON files(job_no);
-- 프로젝트 타입 인덱스 (17_add_project_type_column.sql)
CREATE INDEX IF NOT EXISTS idx_jobs_project_type ON jobs(project_type);
-- 사용자 요구사항 테이블 인덱스 (05_create_pipe_details_and_requirements_postgres.sql)
CREATE INDEX IF NOT EXISTS idx_user_requirements_file_id ON user_requirements(file_id);
CREATE INDEX IF NOT EXISTS idx_user_requirements_material_id ON user_requirements(material_id);
CREATE INDEX IF NOT EXISTS idx_user_requirements_status ON user_requirements(status);
CREATE INDEX IF NOT EXISTS idx_user_requirements_type ON user_requirements(requirement_type);
-- ================================
-- 8. 트리거 함수 및 트리거 생성
-- ================================
-- updated_at 자동 업데이트 함수
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- 파이프 끝단 가공 테이블 트리거
CREATE OR REPLACE FUNCTION update_pipe_end_preparations_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- 구매 관련 트리거 함수들
CREATE OR REPLACE FUNCTION update_purchase_items_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_files_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- 트리거 적용
CREATE TRIGGER IF NOT EXISTS update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER IF NOT EXISTS update_user_sessions_updated_at BEFORE UPDATE ON user_sessions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER IF NOT EXISTS update_pipe_end_preparations_updated_at BEFORE UPDATE ON pipe_end_preparations
FOR EACH ROW EXECUTE FUNCTION update_pipe_end_preparations_updated_at();
CREATE TRIGGER IF NOT EXISTS trigger_update_purchase_items_timestamp BEFORE UPDATE ON purchase_items
FOR EACH ROW EXECUTE FUNCTION update_purchase_items_timestamp();
CREATE TRIGGER IF NOT EXISTS trigger_files_updated_at BEFORE UPDATE ON files
FOR EACH ROW EXECUTE FUNCTION update_files_updated_at();
-- ================================
-- 해시 생성 함수 및 트리거 (10_add_material_comparison_system.sql)
-- ================================
-- 해시 생성 함수
CREATE OR REPLACE FUNCTION generate_material_hash(
description TEXT,
size_spec TEXT DEFAULT '',
material_grade TEXT DEFAULT ''
) RETURNS VARCHAR(32) AS $$
BEGIN
-- 정규화: 대소문자 통일, 공백 정리
description := UPPER(TRIM(REGEXP_REPLACE(description, '\s+', ' ', 'g')));
size_spec := UPPER(TRIM(COALESCE(size_spec, '')));
material_grade := UPPER(TRIM(COALESCE(material_grade, '')));
-- MD5 해시 생성 (32자리)
RETURN MD5(description || '|' || size_spec || '|' || material_grade);
END;
$$ LANGUAGE plpgsql;
-- 자동 해시 생성 트리거 함수
CREATE OR REPLACE FUNCTION auto_generate_material_hash()
RETURNS TRIGGER AS $$
BEGIN
-- 새로 삽입되거나 업데이트될 때 자동으로 해시 생성
NEW.material_hash := generate_material_hash(
NEW.original_description,
NEW.size_spec,
NEW.material_grade
);
NEW.normalized_description := UPPER(TRIM(REGEXP_REPLACE(NEW.original_description, '\s+', ' ', 'g')));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 자동 해시 생성 트리거 적용
CREATE TRIGGER IF NOT EXISTS trigger_auto_material_hash
BEFORE INSERT OR UPDATE ON materials
FOR EACH ROW
EXECUTE FUNCTION auto_generate_material_hash();
-- ================================
-- 9. 기본 데이터 삽입
-- ================================
-- 기본 권한 데이터 삽입
INSERT INTO permissions (permission_name, description, module) VALUES
-- BOM 관리 권한
('bom.view', 'BOM 조회 권한', 'bom'),
('bom.create', 'BOM 생성 권한', 'bom'),
('bom.edit', 'BOM 수정 권한', 'bom'),
('bom.delete', 'BOM 삭제 권한', 'bom'),
('bom.approve', 'BOM 승인 권한', 'bom'),
-- 프로젝트 관리 권한
('project.view', '프로젝트 조회 권한', 'project'),
('project.create', '프로젝트 생성 권한', 'project'),
('project.edit', '프로젝트 수정 권한', 'project'),
('project.delete', '프로젝트 삭제 권한', 'project'),
('project.manage', '프로젝트 관리 권한', 'project'),
-- 파일 관리 권한
('file.upload', '파일 업로드 권한', 'file'),
('file.download', '파일 다운로드 권한', 'file'),
('file.delete', '파일 삭제 권한', 'file'),
-- 사용자 관리 권한
('user.view', '사용자 조회 권한', 'user'),
('user.create', '사용자 생성 권한', 'user'),
('user.edit', '사용자 수정 권한', 'user'),
('user.delete', '사용자 삭제 권한', 'user'),
-- 시스템 관리 권한
('system.admin', '시스템 관리 권한', 'system'),
('system.logs', '로그 조회 권한', 'system'),
('system.settings', '시스템 설정 권한', 'system')
ON CONFLICT (permission_name) DO NOTHING;
-- 역할별 기본 권한 할당
INSERT INTO role_permissions (role, permission_id)
SELECT 'admin', permission_id FROM permissions
ON CONFLICT (role, permission_id) DO NOTHING;
INSERT INTO role_permissions (role, permission_id)
SELECT 'system', permission_id FROM permissions
ON CONFLICT (role, permission_id) DO NOTHING;
INSERT INTO role_permissions (role, permission_id)
SELECT 'leader', permission_id FROM permissions
WHERE permission_name IN (
'bom.view', 'bom.create', 'bom.edit', 'bom.approve',
'project.view', 'project.create', 'project.edit', 'project.manage',
'file.upload', 'file.download', 'file.delete',
'user.view'
)
ON CONFLICT (role, permission_id) DO NOTHING;
INSERT INTO role_permissions (role, permission_id)
SELECT 'support', permission_id FROM permissions
WHERE permission_name IN (
'bom.view', 'bom.create', 'bom.edit',
'project.view', 'project.create', 'project.edit',
'file.upload', 'file.download'
)
ON CONFLICT (role, permission_id) DO NOTHING;
INSERT INTO role_permissions (role, permission_id)
SELECT 'user', permission_id FROM permissions
WHERE permission_name IN (
'bom.view',
'project.view',
'file.upload', 'file.download'
)
ON CONFLICT (role, permission_id) DO NOTHING;
-- 기본 관리자 계정 생성 (비밀번호: admin123)
-- bcrypt 해시: $2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
INSERT INTO users (username, password, name, email, role, access_level, department, position) VALUES
('admin', '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '시스템 관리자', 'admin@tkmp.com', 'admin', 'admin', 'IT', '시스템 관리자'),
('system', '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '시스템 계정', 'system@tkmp.com', 'system', 'system', 'IT', '시스템 계정')
ON CONFLICT (username) DO NOTHING;
-- 기본 프로젝트 생성
INSERT INTO projects (official_project_code, project_name, client_name, status, description) VALUES
('TK-MP-DEV-001', 'TK-MP 개발 프로젝트', 'TK Engineering', 'active', '개발 및 테스트용 프로젝트'),
('TK-MP-DEMO-001', 'TK-MP 데모 프로젝트', 'Demo Client', 'active', '데모 및 시연용 프로젝트'),
('TK-MP-TEST-001', 'TK-MP 테스트 프로젝트', 'Test Client', 'active', '기능 테스트용 프로젝트')
ON CONFLICT (official_project_code) DO NOTHING;
-- Tubing 카테고리 기초 데이터
INSERT INTO tubing_categories (category_code, category_name, description) VALUES
('GENERAL', '일반 Tubing', '일반적인 스테인리스 스틸 튜빙'),
('VCR', 'VCR Tubing', 'VCR (Vacuum Coupling Radiation) 연결용 튜빙'),
('SANITARY', 'Sanitary Tubing', '위생용 튜빙 (식품, 제약 등)'),
('HVAC', 'HVAC Tubing', '공조용 튜빙'),
('HYDRAULIC', 'Hydraulic Tubing', '유압용 튜빙'),
('PNEUMATIC', 'Pneumatic Tubing', '공압용 튜빙'),
('PROCESS', 'Process Tubing', '공정용 특수 튜빙'),
('EXOTIC', 'Exotic Material', '특수 재질 튜빙 (Hastelloy, Inconel 등)')
ON CONFLICT (category_code) DO NOTHING;
-- 주요 제조사 기초 데이터
INSERT INTO tubing_manufacturers (manufacturer_code, manufacturer_name, country) VALUES
('SWAGELOK', 'Swagelok Company', 'USA'),
('PARKER', 'Parker Hannifin', 'USA'),
('HAM_LET', 'Ham-Let Group', 'Israel'),
('SUPERLOK', 'Superlok USA', 'USA'),
('FITOK', 'Fitok Group', 'China'),
('DK_LOK', 'DK-Lok Corporation', 'South Korea'),
('GYROLOK', 'Gyrolok (Oliver Valves)', 'UK'),
('AS_ONE', 'AS ONE Corporation', 'Japan')
ON CONFLICT (manufacturer_code) DO NOTHING;
-- 기본 요구사항 타입 데이터 삽입 (05_create_pipe_details_and_requirements_postgres.sql)
INSERT INTO requirement_types (type_code, type_name, category, description) VALUES
('IMPACT_TEST', '임팩테스트', 'TEST', 'Charpy V-notch, Izod 등의 충격 시험'),
('HEAT_TREATMENT', '열처리', 'TREATMENT', '정규화, 어닐링, 템퍼링 등의 열처리'),
('CERTIFICATION', '인증서', 'CERTIFICATION', '재질증명서, 시험성적서 등'),
('CUSTOM_SPEC', '특수사양', 'CUSTOM', '고객 특별 요구사항'),
('NDT_TEST', '비파괴검사', 'TEST', '초음파, 방사선, 자분탐상 등'),
('CHEMICAL_ANALYSIS', '화학분석', 'TEST', '화학성분 분석'),
('MECHANICAL_TEST', '기계적성질', 'TEST', '인장, 압축, 굽힘 시험'),
('SURFACE_TREATMENT', '표면처리', 'TREATMENT', '도금, 도장, 패시베이션 등'),
('DIMENSIONAL_CHECK', '치수검사', 'INSPECTION', '치수, 형상 검사'),
('WELDING_SPEC', '용접사양', 'SPEC', '용접 방법, 용접재 등')
ON CONFLICT (type_code) DO NOTHING;
-- ================================
-- 10. 뷰 생성
-- ================================
-- 프로젝트 상태 요약 뷰
CREATE OR REPLACE VIEW project_status_view AS
SELECT
p.id,
COALESCE(p.official_project_code, p.design_project_code) as display_code,
p.project_name,
p.status,
COUNT(f.id) as file_count,
COUNT(m.id) as material_count,
p.created_at
FROM projects p
LEFT JOIN files f ON p.id = f.project_id AND f.is_active = true
LEFT JOIN materials m ON f.id = m.file_id
GROUP BY p.id, p.official_project_code, p.design_project_code,
p.project_name, p.status, p.created_at
ORDER BY p.created_at DESC;
-- 사용자 정보 조회용 뷰
CREATE OR REPLACE VIEW user_info_view AS
SELECT
u.user_id,
u.username,
u.name,
u.email,
u.role,
u.access_level,
u.department,
u.position,
u.is_active,
u.created_at,
u.last_login_at,
COUNT(ll.log_id) as login_count,
MAX(ll.login_time) as last_successful_login
FROM users u
LEFT JOIN login_logs ll ON u.user_id = ll.user_id AND ll.login_status = 'success'
GROUP BY u.user_id, u.username, u.name, u.email, u.role, u.access_level,
u.department, u.position, u.is_active, u.created_at, u.last_login_at;
-- ================================
-- 추가 뷰들 (05_add_classification_columns.sql, 10_add_material_comparison_system.sql)
-- ================================
-- 분류 결과 통계 조회용 뷰
CREATE OR REPLACE VIEW classification_summary AS
SELECT
f.job_no,
f.original_filename,
f.parsed_count,
f.classification_completed,
f.classification_timestamp,
COUNT(*) as total_materials,
COUNT(CASE WHEN m.classified_category = 'BOLT' THEN 1 END) as bolt_count,
COUNT(CASE WHEN m.classified_category = 'FLANGE' THEN 1 END) as flange_count,
COUNT(CASE WHEN m.classified_category = 'FITTING' THEN 1 END) as fitting_count,
COUNT(CASE WHEN m.classified_category = 'GASKET' THEN 1 END) as gasket_count,
COUNT(CASE WHEN m.classified_category = 'INSTRUMENT' THEN 1 END) as instrument_count,
COUNT(CASE WHEN m.classified_category = 'PIPE' THEN 1 END) as pipe_count,
COUNT(CASE WHEN m.classified_category = 'VALVE' THEN 1 END) as valve_count,
COUNT(CASE WHEN m.classified_category = 'MATERIAL' THEN 1 END) as material_count,
COUNT(CASE WHEN m.classified_category = 'OTHER' THEN 1 END) as other_count,
AVG(m.classification_confidence) as avg_confidence,
COUNT(CASE WHEN m.is_verified = TRUE THEN 1 END) as verified_count
FROM files f
LEFT JOIN materials m ON f.id = m.file_id
WHERE f.is_active = TRUE
GROUP BY f.id, f.job_no, f.original_filename, f.parsed_count, f.classification_completed, f.classification_timestamp;
-- 분류 성능 통계 뷰
CREATE OR REPLACE VIEW classification_performance AS
SELECT
classified_category,
subcategory,
standard,
COUNT(*) as total_count,
AVG(classification_confidence) as avg_confidence,
COUNT(CASE WHEN is_verified = TRUE THEN 1 END) as verified_count,
COUNT(CASE WHEN is_verified = FALSE THEN 1 END) as unverified_count,
ROUND(
(COUNT(CASE WHEN is_verified = TRUE THEN 1 END)::DECIMAL / COUNT(*) * 100), 2
) as verification_rate
FROM materials
WHERE classified_category IS NOT NULL
GROUP BY classified_category, subcategory, standard
ORDER BY total_count DESC;
-- 누적 재고 현황 뷰 (10_add_material_comparison_system.sql)
CREATE OR REPLACE VIEW material_inventory_status AS
SELECT
mpt.job_no,
mpt.material_hash,
mpt.description,
mpt.size_spec,
mpt.unit,
-- 누적 수량
SUM(mpt.confirmed_quantity) as total_confirmed,
SUM(mpt.ordered_quantity) as total_ordered,
SUM(mpt.received_quantity) as total_received,
-- 현재 가용 재고
SUM(mpt.received_quantity) as available_stock,
-- 최신 리비전 정보
MAX(mpt.revision) as latest_revision,
MAX(mpt.updated_at) as last_updated
FROM material_purchase_tracking mpt
WHERE mpt.purchase_status != 'CANCELLED'
GROUP BY mpt.job_no, mpt.material_hash, mpt.description, mpt.size_spec, mpt.unit;
-- ================================
-- 완료 메시지
-- ================================
DO $$
BEGIN
RAISE NOTICE '✅ TK-MP-Project 완전 통합 데이터베이스 스키마가 성공적으로 생성되었습니다!';
RAISE NOTICE '📋 생성된 주요 테이블:';
RAISE NOTICE ' - 기본: projects, files, materials, jobs';
RAISE NOTICE ' - 자재상세: pipe_details, fitting_details, valve_details, flange_details, bolt_details, gasket_details, instrument_details';
RAISE NOTICE ' - 파이프: pipe_end_preparations';
RAISE NOTICE ' - 인증: users, login_logs, user_sessions, permissions, role_permissions';
RAISE NOTICE ' - 구매: purchase_items, material_purchase_mapping, material_purchase_tracking';
RAISE NOTICE ' - 자재규격: material_standards, material_categories, material_specifications, material_grades, material_patterns';
RAISE NOTICE ' - 특수재질: special_materials, special_material_grades, special_material_patterns';
RAISE NOTICE ' - 비교시스템: material_revisions_comparison, material_comparison_details';
RAISE NOTICE ' - Tubing: tubing_categories, tubing_specifications, tubing_manufacturers, tubing_products, material_tubing_mapping';
RAISE NOTICE ' - 요구사항: requirement_types, user_requirements';
RAISE NOTICE ' - 로그: user_activity_logs';
RAISE NOTICE '👤 기본 계정: admin/admin123, system/admin123';
RAISE NOTICE '🏗️ 기본 프로젝트: TK-MP-DEV-001, TK-MP-DEMO-001, TK-MP-TEST-001';
RAISE NOTICE '🏭 기본 Tubing 제조사: SWAGELOK, PARKER, HAM_LET, SUPERLOK, FITOK, DK_LOK, GYROLOK, AS_ONE';
RAISE NOTICE '🔐 권한 시스템: 5단계 역할 + 모듈별 세분화된 권한';
RAISE NOTICE '📊 성능 최적화: 복합인덱스, GIN인덱스, 조건부인덱스 포함 107개 인덱스 적용';
RAISE NOTICE '🔄 자동화: updated_at 트리거, 해시 자동생성, 기본 데이터 자동 생성';
RAISE NOTICE '📈 통계뷰: classification_summary, classification_performance, material_inventory_status';
RAISE NOTICE '🔧 함수: generate_material_hash, auto_generate_material_hash';
RAISE NOTICE '🔬 기본 요구사항 타입: 임팩테스트, 열처리, 인증서, 비파괴검사, 화학분석 등 10종';
RAISE NOTICE '📝 총 테이블 수: 38개, 인덱스: 107개, 뷰: 5개, 함수: 15개 이상';
END $$;