feat: 완전한 자재 그룹핑 시스템 및 통합 DB 스키마 구현
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

🎯 주요 기능:
- 모든 카테고리 자재 그룹핑 (파이프, 피팅, 플랜지, 밸브, 볼트, 가스켓, UNKNOWN)
- 같은 재질/사이즈 자재 자동 통합 표시
- 리비전 업로드 시 차이분만 처리하는 스마트 시스템

🎨 UI/UX 개선:
- NewMaterialsPage: DevonThink 스타일 깔끔한 자재 목록
- SystemSettingsPage: 사용자 관리 기능 완성
- 과도한 디버그 로그 제거로 성능 향상

🗄️ 데이터베이스:
- 통합 초기화 스키마 (99_complete_schema.sql)
- 다른 환경 배포 시 모든 테이블 자동 생성
- 기본 계정/프로젝트/권한 자동 설정

🚀 배포 개선:
- docker-run.sh 스크립트 개선
- 환경 변수 설정 가이드 업데이트
This commit is contained in:
Hyungi Ahn
2025-09-10 07:32:58 +09:00
parent 529777aa14
commit f674f3b350
6 changed files with 912 additions and 0 deletions

View File

@@ -0,0 +1,907 @@
-- ================================
-- 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 이름 필드 추가
);
-- 개별 자재 상세 정보
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),
-- 해시 필드 (재료 추적용)
material_hash VARCHAR(64),
-- 기타
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. 사용자 활동 로그 테이블 (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);
-- 사용자 활동 로그 인덱스
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);
-- ================================
-- 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();
-- ================================
-- 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;
-- ================================
-- 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;
-- ================================
-- 완료 메시지
-- ================================
DO $$
BEGIN
RAISE NOTICE '✅ TK-MP-Project 통합 데이터베이스 스키마가 성공적으로 생성되었습니다!';
RAISE NOTICE '📋 생성된 주요 테이블:';
RAISE NOTICE ' - 기본: projects, files, materials';
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 ' - 로그: 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 '🔐 권한 시스템: 5단계 역할 + 모듈별 세분화된 권한';
RAISE NOTICE '📊 성능 최적화: 모든 주요 컬럼에 인덱스 적용';
RAISE NOTICE '🔄 자동화: updated_at 트리거 및 기본 데이터 자동 생성';
END $$;