From f674f3b3509ec328a8df87138c778185fbfc873d Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 10 Sep 2025 07:32:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=99=84=EC=A0=84=ED=95=9C=20=EC=9E=90?= =?UTF-8?q?=EC=9E=AC=20=EA=B7=B8=EB=A3=B9=ED=95=91=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20DB=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐ŸŽฏ ์ฃผ์š” ๊ธฐ๋Šฅ: - ๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ ์ž์žฌ ๊ทธ๋ฃนํ•‘ (ํŒŒ์ดํ”„, ํ”ผํŒ…, ํ”Œ๋žœ์ง€, ๋ฐธ๋ธŒ, ๋ณผํŠธ, ๊ฐ€์Šค์ผ“, UNKNOWN) - ๊ฐ™์€ ์žฌ์งˆ/์‚ฌ์ด์ฆˆ ์ž์žฌ ์ž๋™ ํ†ตํ•ฉ ํ‘œ์‹œ - ๋ฆฌ๋น„์ „ ์—…๋กœ๋“œ ์‹œ ์ฐจ์ด๋ถ„๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์Šค๋งˆํŠธ ์‹œ์Šคํ…œ ๐ŸŽจ UI/UX ๊ฐœ์„ : - NewMaterialsPage: DevonThink ์Šคํƒ€์ผ ๊น”๋”ํ•œ ์ž์žฌ ๋ชฉ๋ก - SystemSettingsPage: ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์™„์„ฑ - ๊ณผ๋„ํ•œ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ์ œ๊ฑฐ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: - ํ†ตํ•ฉ ์ดˆ๊ธฐํ™” ์Šคํ‚ค๋งˆ (99_complete_schema.sql) - ๋‹ค๋ฅธ ํ™˜๊ฒฝ ๋ฐฐํฌ ์‹œ ๋ชจ๋“  ํ…Œ์ด๋ธ” ์ž๋™ ์ƒ์„ฑ - ๊ธฐ๋ณธ ๊ณ„์ •/ํ”„๋กœ์ ํŠธ/๊ถŒํ•œ ์ž๋™ ์„ค์ • ๐Ÿš€ ๋ฐฐํฌ ๊ฐœ์„ : - docker-run.sh ์Šคํฌ๋ฆฝํŠธ ๊ฐœ์„  - ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ๊ฐ€์ด๋“œ ์—…๋ฐ์ดํŠธ --- .../20_add_pipe_end_preparation_table.sql | 1 + database/init/99_complete_schema.sql | 907 ++++++++++++++++++ env.example | 1 + frontend/src/pages/NewMaterialsPage.jsx | 1 + frontend/src/pages/SystemSettingsPage.jsx | 1 + scripts/docker-run.sh | 1 + 6 files changed, 912 insertions(+) create mode 100644 database/init/99_complete_schema.sql diff --git a/backend/scripts/20_add_pipe_end_preparation_table.sql b/backend/scripts/20_add_pipe_end_preparation_table.sql index 339938f..0ab3f8f 100644 --- a/backend/scripts/20_add_pipe_end_preparation_table.sql +++ b/backend/scripts/20_add_pipe_end_preparation_table.sql @@ -47,3 +47,4 @@ CREATE TRIGGER update_pipe_end_preparations_updated_at BEFORE UPDATE ON pipe_end_preparations FOR EACH ROW EXECUTE FUNCTION update_pipe_end_preparations_updated_at(); + diff --git a/database/init/99_complete_schema.sql b/database/init/99_complete_schema.sql new file mode 100644 index 0000000..f44f653 --- /dev/null +++ b/database/init/99_complete_schema.sql @@ -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 $$; diff --git a/env.example b/env.example index 0f95871..147a09b 100644 --- a/env.example +++ b/env.example @@ -34,3 +34,4 @@ LOG_LEVEL=DEBUG # ๋ณด์•ˆ ์„ค์ • CORS_ORIGINS=http://localhost:3000,http://localhost:13000,http://localhost:5173 + diff --git a/frontend/src/pages/NewMaterialsPage.jsx b/frontend/src/pages/NewMaterialsPage.jsx index 6b06b8a..d9925f5 100644 --- a/frontend/src/pages/NewMaterialsPage.jsx +++ b/frontend/src/pages/NewMaterialsPage.jsx @@ -1164,3 +1164,4 @@ const NewMaterialsPage = ({ }; export default NewMaterialsPage; + diff --git a/frontend/src/pages/SystemSettingsPage.jsx b/frontend/src/pages/SystemSettingsPage.jsx index a9b09b7..a6bec6b 100644 --- a/frontend/src/pages/SystemSettingsPage.jsx +++ b/frontend/src/pages/SystemSettingsPage.jsx @@ -453,3 +453,4 @@ const SystemSettingsPage = ({ onNavigate, user }) => { }; export default SystemSettingsPage; + diff --git a/scripts/docker-run.sh b/scripts/docker-run.sh index d41bb55..1a198db 100755 --- a/scripts/docker-run.sh +++ b/scripts/docker-run.sh @@ -137,3 +137,4 @@ case $COMMAND in exit 1 ;; esac +