From 236e1ca49357ec861f58d5212bb2c30470c3ae22 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Sat, 25 Oct 2025 13:29:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=97=90=20=EB=B6=80=EC=84=9C=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=8E=B8=EC=A7=91=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🏒 Department Management System: - 5개 λΆ€μ„œ 지원: 생산, ν’ˆμ§ˆ, ꡬ맀, 섀계, μ˜μ—… - μ‚¬μš©μž 생성/μˆ˜μ • μ‹œ λΆ€μ„œ 선택 κ°€λŠ₯ - λΆ€μ„œλ³„ μ‚¬μš©μž λΆ„λ₯˜ 및 ν‘œμ‹œ πŸ“Š Database Schema Updates: - department_type ENUM μΆ”κ°€ (production, quality, purchasing, design, sales) - users ν…Œμ΄λΈ”μ— department 컬럼 μΆ”κ°€ - idx_users_department 인덱슀 생성 (μ„±λŠ₯ μ΅œμ ν™”) - 014_add_user_department.sql λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹€ν–‰ πŸ”§ Backend Enhancements: - DepartmentType ENUM 클래슀 μΆ”κ°€ (models.py, schemas.py) - User λͺ¨λΈμ— department ν•„λ“œ μΆ”κ°€ - UserBase, UserUpdate μŠ€ν‚€λ§ˆμ— department ν•„λ“œ 포함 - κΈ°μ‘΄ API μ—”λ“œν¬μΈνŠΈ μžλ™ ν˜Έν™˜ 🎨 Frontend UI Improvements: - μ‚¬μš©μž μΆ”κ°€ 폼에 λΆ€μ„œ 선택 λ“œλ‘­λ‹€μš΄ μΆ”κ°€ - μ‚¬μš©μž λͺ©λ‘μ— λΆ€μ„œ 정보 λ°°μ§€ ν‘œμ‹œ (녹색 λ°°κ²½) - μ‚¬μš©μž νŽΈμ§‘ λͺ¨λ‹¬ μƒˆλ‘œ κ΅¬ν˜„ - λΆ€μ„œλͺ… ν•œκΈ€ λ³€ν™˜ ν•¨μˆ˜ (AuthAPI.getDepartmentLabel) ✨ User Management Features: - νŽΈμ§‘ λ²„νŠΌμœΌλ‘œ μ‚¬μš©μž 정보 μˆ˜μ • κ°€λŠ₯ - λΆ€μ„œ, 이름, κΆŒν•œ μ‹€μ‹œκ°„ λ³€κ²½ - μ‚¬μš©μž IDλŠ” μˆ˜μ • λΆˆκ°€ (읽기 μ „μš©) - λͺ¨λ‹¬ 기반 직관적 UI πŸ” Visual Enhancements: - λΆ€μ„œ 정보 μ•„μ΄μ½˜ (fas fa-building) - 색상 μ½”λ”©: λΆ€μ„œ(녹색), κΆŒν•œ(λΉ¨κ°•/νŒŒλž‘) - λ°˜μ‘ν˜• λ ˆμ΄μ•„μ›ƒ (flex-1, gap-3) - ν˜Έλ²„ 효과 및 νŠΈλžœμ§€μ…˜ πŸš€ API Integration: - AuthAPI.getDepartments() - λΆ€μ„œ λͺ©λ‘ λ°˜ν™˜ - AuthAPI.getDepartmentLabel() - λΆ€μ„œλͺ… λ³€ν™˜ - AuthAPI.updateUser() - λΆ€μ„œ 정보 포함 μ—…λ°μ΄νŠΈ - κΈ°μ‘΄ createUser API ν™•μž₯ 지원 Expected Result: βœ… μ‚¬μš©μž 생성 μ‹œ λΆ€μ„œ 선택 κ°€λŠ₯ βœ… μ‚¬μš©μž λͺ©λ‘μ— λΆ€μ„œ 정보 ν‘œμ‹œ βœ… νŽΈμ§‘ λ²„νŠΌμœΌλ‘œ λΆ€μ„œ λ³€κ²½ κ°€λŠ₯ βœ… 5개 λΆ€μ„œ λΆ„λ₯˜ μ‹œμŠ€ν…œ μ™„μ„± βœ… 직관적인 μ‚¬μš©μž 관리 UI --- .../__pycache__/models.cpython-311.pyc | Bin 9665 -> 10090 bytes .../__pycache__/schemas.cpython-311.pyc | Bin 15858 -> 16320 bytes backend/database/models.py | 8 + backend/database/schemas.py | 9 ++ .../migrations/014_add_user_department.sql | 92 +++++++++++ frontend/admin.html | 153 +++++++++++++++++- frontend/issues-archive.html | 4 +- frontend/issues-management.html | 4 +- frontend/static/js/api.js | 18 ++- 9 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 backend/migrations/014_add_user_department.sql diff --git a/backend/database/__pycache__/models.cpython-311.pyc b/backend/database/__pycache__/models.cpython-311.pyc index b9bb1985c2e78adf601a8a6a459da8b78278cddd..02df15bb0b067df87233111f26dd18fad6a176a7 100644 GIT binary patch delta 1512 zcmaiy$!`-$6vn$ciR0Mu(uub?4iFOpo(U4MhCy7y8Vm_!Az=$*W}I}BI5Cc=Y#%~E zG|K@bX5>%^5JJiUiA5Lz>N5!D!eK60LL%}%fZ)I)af1<4Uv-3M3=&E?Vs}6&PSsU#S13(FQNE!Bh0>rls-A+q zBInKQbKTDkJGi%!HI+*yn*~X|A&uZ$gqci-Q z-K=QaLPE@IKjQAfmp1iFx!6hjaaT8Aq`+D}Gg0QK*8_?_@4HgX$1lY|$MZl(QCa<5 zS*`c5^YzzBx3R`?o{$6Vp)*8ctmJHK-O0DUisGPMfH-m-(I7`erbBG0>qOfKR?tYa z8aN2pfiz$NDErH$+YTTEwi`R$t8f*spz){jw;bw+G{!MZW1QP4t~PI-WI4~_fh>Q| z;4EFbzgw5g77&Kug$@Jzfqfi(k&epVt2Al+=A?7l3oCHGks1XDyIk*s5mr#A?S0Ludab`khKRluNy^J2gCt4uoplQ zXcHj1ycvlz{$D+C0a-}cy{bCXkiFBFy@qI5ptg|H-y@*{Q zK-evttneqC0OyQ_7CRw(L}C9~(n(I3)x3l=jsl{t<$Q@hgRj1b=X;j0y%x9(h+K*` zJB?Q>;069?`iI5T=AnrER#=~h09<7+mOZl|inO!4tuKQjTj*A^l)|a_(Rp^y<7JP+ zR{Jfk5Cw-e|e|j&J5C7HMS*h*i EzY{V;S^xk5 delta 1173 zcmZ{i&rcIU6vubA*mk>ZZ7BCk;|keqsb{2p-xH*A9N!UpfiL%VJt5!43jmU!9@FC~d&)}w9kRa!|K zM|E-Au_3Zb_7kO{C%mG0Cf-L)+j`v2Lb6m#xvPg%ce%s}3yIBIx} zABY@?266LedwHU1TMD ztqimm(SeviTxL*3+7AzW%kdHX!u%9y1tNuz5igby)@7=xDjfQw5(n4m;<$Po7~A4!li8Wvq+ zD2m(?%k_y^!S!*a+_ogF)K&Y+ZGp|Ytg?pX5d_y%P{KvpK)RfpSAxaTmcY;`iw$E}aMk@sCVT(J^$K4sx-NFhs1w zK;w=bBlQ?GHSM0~7m*(jm&N~zSBG#5zgo2{`r$k`x1eV;MhAV$mV~|Lo@n*QKaf@* WSt8$r|8uo%pJ%@RosYxq=H1^h&+8=s diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index 34df5b74f6e4e22214711d019d65fe70f0ecba44..fa6b2cc916426a473ca5cefa0e7a22476cca5621 100644 GIT binary patch delta 3486 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 LvduXg-!T6JIu4%U diff --git a/backend/database/models.py b/backend/database/models.py index 33578ad..dd9983a 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -43,6 +43,13 @@ class DisposalReasonType(str, enum.Enum): spam = "spam" # 슀팸/였λ₯˜ custom = "custom" # 직접 μž…λ ₯ +class DepartmentType(str, enum.Enum): + production = "production" # 생산 + quality = "quality" # ν’ˆμ§ˆ + purchasing = "purchasing" # ꡬ맀 + design = "design" # 섀계 + sales = "sales" # μ˜μ—… + class User(Base): __tablename__ = "users" @@ -51,6 +58,7 @@ class User(Base): hashed_password = Column(String, nullable=False) full_name = Column(String) role = Column(Enum(UserRole), default=UserRole.user) + department = Column(Enum(DepartmentType)) # λΆ€μ„œ 정보 μΆ”κ°€ is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=get_kst_now) diff --git a/backend/database/schemas.py b/backend/database/schemas.py index 17fd630..8e59d1d 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -32,11 +32,19 @@ class DisposalReasonType(str, Enum): spam = "spam" # 슀팸/였λ₯˜ custom = "custom" # 직접 μž…λ ₯ +class DepartmentType(str, Enum): + production = "production" # 생산 + quality = "quality" # ν’ˆμ§ˆ + purchasing = "purchasing" # ꡬ맀 + design = "design" # 섀계 + sales = "sales" # μ˜μ—… + # User schemas class UserBase(BaseModel): username: str full_name: Optional[str] = None role: UserRole = UserRole.user + department: Optional[DepartmentType] = None class UserCreate(UserBase): password: str @@ -45,6 +53,7 @@ class UserUpdate(BaseModel): full_name: Optional[str] = None password: Optional[str] = None role: Optional[UserRole] = None + department: Optional[DepartmentType] = None is_active: Optional[bool] = None class PasswordChange(BaseModel): diff --git a/backend/migrations/014_add_user_department.sql b/backend/migrations/014_add_user_department.sql new file mode 100644 index 0000000..ed4385b --- /dev/null +++ b/backend/migrations/014_add_user_department.sql @@ -0,0 +1,92 @@ +-- 014_add_user_department.sql +-- μ‚¬μš©μž λΆ€μ„œ 정보 μΆ”κ°€ + +BEGIN; + +-- migration_log ν…Œμ΄λΈ” 생성 (λ©±λ“±μ„±) +CREATE TABLE IF NOT EXISTS migration_log ( + id SERIAL PRIMARY KEY, + migration_file VARCHAR(255) NOT NULL UNIQUE, + executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + status VARCHAR(50), + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 이름 +DO $$ +DECLARE + migration_name VARCHAR(255) := '014_add_user_department.sql'; + migration_notes TEXT := 'μ‚¬μš©μž λΆ€μ„œ 정보 μΆ”κ°€: department ENUM νƒ€μž… 및 users ν…Œμ΄λΈ”μ— department 컬럼 μΆ”κ°€'; + current_status VARCHAR(50); +BEGIN + SELECT status INTO current_status FROM migration_log WHERE migration_file = migration_name; + + IF current_status IS NULL THEN + RAISE NOTICE '--- λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ % μ‹œμž‘ ---', migration_name; + + -- department ENUM νƒ€μž… 생성 + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'department_type') THEN + CREATE TYPE department_type AS ENUM ( + 'production', -- 생산 + 'quality', -- ν’ˆμ§ˆ + 'purchasing', -- ꡬ맀 + 'design', -- 섀계 + 'sales' -- μ˜μ—… + ); + RAISE NOTICE 'βœ… department_type ENUM νƒ€μž…μ΄ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'; + ELSE + RAISE NOTICE 'ℹ️ department_type ENUM νƒ€μž…μ΄ 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€.'; + END IF; + + -- users ν…Œμ΄λΈ”μ— department 컬럼 μΆ”κ°€ + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'department') THEN + ALTER TABLE users ADD COLUMN department department_type; + RAISE NOTICE 'βœ… users.department 컬럼이 μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'; + ELSE + RAISE NOTICE 'ℹ️ users.department 컬럼이 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€.'; + END IF; + + -- 인덱슀 μΆ”κ°€ (λΆ€μ„œλ³„ 쑰회 μ„±λŠ₯ ν–₯상) + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'users' AND indexname = 'idx_users_department') THEN + CREATE INDEX idx_users_department ON users (department) WHERE department IS NOT NULL; + RAISE NOTICE 'βœ… idx_users_department μΈλ±μŠ€κ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'; + ELSE + RAISE NOTICE 'ℹ️ idx_users_department μΈλ±μŠ€κ°€ 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€.'; + END IF; + + -- λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 검증 + DECLARE + col_count INTEGER; + enum_count INTEGER; + idx_count INTEGER; + BEGIN + SELECT COUNT(*) INTO col_count FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'department'; + SELECT COUNT(*) INTO enum_count FROM pg_type WHERE typname = 'department_type'; + SELECT COUNT(*) INTO idx_count FROM pg_indexes WHERE tablename = 'users' AND indexname = 'idx_users_department'; + + RAISE NOTICE '=== λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 검증 κ²°κ³Ό ==='; + RAISE NOTICE 'μΆ”κ°€λœ 컬럼: %/1개', col_count; + RAISE NOTICE 'μƒμ„±λœ ENUM: %/1개', enum_count; + RAISE NOTICE 'μƒμ„±λœ 인덱슀: %/1개', idx_count; + + IF col_count = 1 AND enum_count = 1 AND idx_count = 1 THEN + RAISE NOTICE 'βœ… λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ΄ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!'; + INSERT INTO migration_log (migration_file, status, notes) VALUES (migration_name, 'SUCCESS', migration_notes); + ELSE + RAISE EXCEPTION '❌ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 검증 μ‹€νŒ¨!'; + END IF; + END; + + -- λΆ€μ„œ ENUM κ°’ 확인 + RAISE NOTICE '=== λΆ€μ„œ ENUM κ°’ ==='; + PERFORM dblink_exec('dbname=' || current_database(), 'SELECT enumlabel FROM pg_enum WHERE enumtypid = ''department_type''::regtype ORDER BY enumsortorder'); + + ELSIF current_status = 'SUCCESS' THEN + RAISE NOTICE 'ℹ️ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ %λŠ” 이미 μ„±κ³΅μ μœΌλ‘œ μ‹€ν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μŠ€ν‚΅ν•©λ‹ˆλ‹€.', migration_name; + ELSE + RAISE NOTICE '⚠️ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ %λŠ” 이전에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. μˆ˜λ™ 확인이 ν•„μš”ν•©λ‹ˆλ‹€.', migration_name; + END IF; +END $$; + +COMMIT; diff --git a/frontend/admin.html b/frontend/admin.html index 6e0c874..62d1985 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -141,6 +141,18 @@ > +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + @@ -361,6 +447,7 @@ username: document.getElementById('newUsername').value.trim(), full_name: document.getElementById('newFullName').value.trim(), password: document.getElementById('newPassword').value, + department: document.getElementById('newDepartment').value || null, role: document.getElementById('newRole').value }; @@ -430,14 +517,19 @@ container.innerHTML = users.map(user => `
-
+
${user.full_name || user.username}
-
- ID: ${user.username} - + ID: ${user.username} + ${user.department ? ` + + ${AuthAPI.getDepartmentLabel(user.department)} + + ` : ''} + +