From 389a4c2026c41e1337013f7f602b4a00b0b643e8 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 10 Sep 2025 07:44:01 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=99=84=EC=A0=84=ED=95=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20DB=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=99=84?= =?UTF-8?q?=EC=84=B1=20-=20=EB=AA=A8=EB=93=A0=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ—„๏ธ ์ถ”๊ฐ€๋œ ์ฃผ์š” ํ…Œ์ด๋ธ”: - 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 ํŒŒ์ผ ํ†ตํ•ฉ ์™„๋ฃŒ - ๋‹ค๋ฅธ ํ™˜๊ฒฝ ๋ฐฐํฌ ์‹œ ํ•œ ๋ฒˆ์— ๋ชจ๋“  ํ…Œ์ด๋ธ” ์ƒ์„ฑ ๊ฐ€๋Šฅ --- database/init/99_complete_schema.sql | 608 ++++++++++++++++++++++++++- 1 file changed, 601 insertions(+), 7 deletions(-) diff --git a/database/init/99_complete_schema.sql b/database/init/99_complete_schema.sql index f44f653..1a23e88 100644 --- a/database/init/99_complete_schema.sql +++ b/database/init/99_complete_schema.sql @@ -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 $$;