fix: 완전한 통합 DB 스키마 완성 - 모든 누락 테이블 추가
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

🗄️ 추가된 주요 테이블:
- Jobs 테이블 (프로젝트 관리, project_type 포함)
- 자재 규격/재질 기준표 (8개 테이블)
- 자재 비교 시스템 (리비전 비교, 해시 기반)
- Tubing 시스템 (5개 테이블 + 제조사 데이터)

📊 성능 최적화:
- 복합 인덱스, GIN 인덱스, 조건부 인덱스
- 총 50+ 인덱스로 검색/정렬 성능 극대화

🔧 자동화 기능:
- 해시 자동 생성 함수 및 트리거
- updated_at 자동 갱신 트리거
- 정규화된 description 자동 생성

📈 통계 및 뷰:
- classification_summary (분류 통계)
- classification_performance (분류 성능)
- material_inventory_status (재고 현황)

📝 완전성:
- 총 30+ 테이블, 50+ 인덱스, 6개 뷰, 10+ 함수
- 모든 backend/scripts SQL 파일 통합 완료
- 다른 환경 배포 시 한 번에 모든 테이블 생성 가능
This commit is contained in:
Hyungi Ahn
2025-09-10 07:44:01 +09:00
parent f674f3b350
commit 389a4c2026

View File

@@ -50,7 +50,18 @@ CREATE TABLE IF NOT EXISTS files (
-- 추가 필드 (19_add_user_tracking_fields.sql)
updated_by VARCHAR(100),
bom_name VARCHAR(200) -- BOM 이름 필드 추가
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
);
-- 개별 자재 상세 정보
@@ -103,8 +114,14 @@ CREATE TABLE IF NOT EXISTS materials (
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),
@@ -584,7 +601,362 @@ CREATE TABLE IF NOT EXISTS material_purchase_tracking (
);
-- ================================
-- 6. 사용자 활동 로그 테이블 (19_add_user_tracking_fields.sql)
-- 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. 사용자 활동 로그 테이블 (19_add_user_tracking_fields.sql)
-- ================================
CREATE TABLE IF NOT EXISTS user_activity_logs (
@@ -693,12 +1065,87 @@ CREATE INDEX IF NOT EXISTS idx_material_purchase_mapping_purchase ON material_pu
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);
-- ================================
-- 8. 트리거 함수 및 트리거 생성
-- ================================
@@ -754,6 +1201,49 @@ CREATE TRIGGER IF NOT EXISTS trigger_update_purchase_items_timestamp BEFORE UPDA
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. 기본 데이터 삽입
-- ================================
@@ -843,6 +1333,30 @@ INSERT INTO projects (official_project_code, project_name, client_name, status,
('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;
-- ================================
-- 10. 뷰 생성
-- ================================
@@ -885,23 +1399,103 @@ 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 '✅ TK-MP-Project 완전 통합 데이터베이스 스키마가 성공적으로 생성되었습니다!';
RAISE NOTICE '📋 생성된 주요 테이블:';
RAISE NOTICE ' - 기본: projects, files, materials';
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 ' - 로그: 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 '📊 성능 최적화: 모든 주요 컬럼에 인덱스 적용';
RAISE NOTICE '🔄 자동화: updated_at 트리거 기본 데이터 자동 생성';
RAISE NOTICE '📊 성능 최적화: 복합인덱스, GIN인덱스, 조건부인덱스 포함 50+ 인덱스 적용';
RAISE NOTICE '🔄 자동화: updated_at 트리거, 해시 자동생성, 기본 데이터 자동 생성';
RAISE NOTICE '📈 통계뷰: classification_summary, classification_performance, material_inventory_status';
RAISE NOTICE '🔧 함수: generate_material_hash, auto_generate_material_hash';
RAISE NOTICE '📝 총 테이블 수: 30+개, 인덱스: 50+개, 뷰: 6개, 함수: 10+개';
END $$;