diff --git a/DB_CHANGES_LOG.md b/DB_CHANGES_LOG.md new file mode 100644 index 0000000..00af767 --- /dev/null +++ b/DB_CHANGES_LOG.md @@ -0,0 +1,156 @@ +# πŸ—„οΈ DB 변경사항 둜그 + +> **μ€‘μš”**: 배포 μ‹œ λ°˜λ“œμ‹œ 이 λ¬Έμ„œλ₯Ό ν™•μΈν•˜μ—¬ DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ„ μˆœμ„œλŒ€λ‘œ μ‹€ν–‰ν•˜μ„Έμš”. + +## πŸ“‹ 변경사항 λͺ©λ‘ + +### 2025.10.26 - ν”„λ‘œμ νŠΈλ³„ 순번 μžλ™ ν• λ‹Ή κ°œμ„  + +**🎯 λͺ©μ **: μˆ˜μ‹ ν•¨μ—μ„œ μ§„ν–‰ 쀑/μ™„λ£Œλ‘œ μƒνƒœ λ³€κ²½ μ‹œ ν”„λ‘œμ νŠΈλ³„ 순번이 μžλ™ ν• λ‹Ήλ˜λ„λ‘ κ°œμ„  + +#### πŸ“ 파일 변경사항 + +**1. λ°±μ—”λ“œ 둜직 μˆ˜μ •** +- **파일**: `backend/routers/inbox.py` +- **λ³€κ²½λ‚΄μš©**: `update_issue_status` ν•¨μˆ˜μ— `project_sequence_no` μžλ™ ν• λ‹Ή 둜직 μΆ”κ°€ +- **μ½”λ“œ**: + ```python + # μ§„ν–‰ 쀑 λ˜λŠ” μ™„λ£Œ μƒνƒœλ‘œ λ³€κ²½ μ‹œ ν”„λ‘œμ νŠΈλ³„ 순번 μžλ™ ν• λ‹Ή + if status_request.review_status in [ReviewStatus.in_progress, ReviewStatus.completed]: + if not issue.project_sequence_no: + from sqlalchemy import text + result = db.execute( + text("SELECT generate_project_sequence_no(:project_id)"), + {"project_id": issue.project_id} + ) + issue.project_sequence_no = result.scalar() + ``` + +**2. 데이터 보정 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜** +- **파일**: `backend/migrations/017_fix_project_sequence_no.sql` +- **λͺ©μ **: 기쑴에 μ§„ν–‰ 쀑/μ™„λ£Œ μƒνƒœμΈλ° `project_sequence_no`κ°€ λˆ„λ½λœ 데이터 보정 + +#### πŸš€ 배포 μ‹œ μ‹€ν–‰ μˆœμ„œ + +```bash +# 1. λ°±μ—”λ“œ μ½”λ“œ 배포 +git pull origin master + +# 2. DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹€ν–‰ (Docker ν™˜κ²½) +docker-compose exec backend python -c " +import psycopg2 +import os + +database_url = os.getenv('DATABASE_URL', 'postgresql://postgres:password@db:5432/mproject') +conn = psycopg2.connect(database_url) +cur = conn.cursor() + +with open('migrations/017_fix_project_sequence_no.sql', 'r', encoding='utf-8') as f: + cur.execute(f.read()) +conn.commit() +conn.close() +print('βœ… λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ™„λ£Œ') +" + +# 3. μ„œλΉ„μŠ€ μž¬μ‹œμž‘ +docker-compose restart backend +``` + +#### πŸ” 검증 방법 + +**1. DBμ—μ„œ 직접 확인**: +```sql +-- 관리 쀑인 이슈 쀑 순번이 λˆ„λ½λœ 것이 μžˆλŠ”μ§€ 확인 +SELECT COUNT(*) as missing_count +FROM issues +WHERE review_status IN ('in_progress', 'completed') +AND project_sequence_no IS NULL; +-- κ²°κ³Ό: 0이어야 함 + +-- ν”„λ‘œμ νŠΈλ³„ 순번이 μ˜¬λ°”λ₯΄κ²Œ ν• λ‹Ήλ˜μ—ˆλŠ”μ§€ 확인 +SELECT project_id, COUNT(*) as total_issues, + MIN(project_sequence_no) as min_no, + MAX(project_sequence_no) as max_no +FROM issues +WHERE review_status IN ('in_progress', 'completed') +GROUP BY project_id +ORDER BY project_id; +-- κ²°κ³Ό: 각 ν”„λ‘œμ νŠΈλ³„λ‘œ 1λΆ€ν„° μ—°μ†λœ λ²ˆν˜Έκ°€ ν• λ‹Ήλ˜μ–΄μ•Ό 함 +``` + +**2. ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ 확인**: +- μˆ˜μ‹ ν•¨μ—μ„œ μƒˆλ‘œμš΄ 이슈λ₯Ό "μ§„ν–‰ 쀑"으둜 λ³€κ²½ +- ν˜„ν™©νŒμ—μ„œ "No.1, No.2..." ν˜•νƒœλ‘œ ν”„λ‘œμ νŠΈλ³„ 순번이 ν‘œμ‹œλ˜λŠ”μ§€ 확인 + +#### ⚠️ μ£Όμ˜μ‚¬ν•­ + +1. **λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹€ν–‰ μ „ λ°±μ—…**: μ€‘μš”ν•œ λ°μ΄ν„°μ΄λ―€λ‘œ μ‹€ν–‰ μ „ DB λ°±μ—… ꢌμž₯ +2. **μˆœμ„œ μ€€μˆ˜**: λ°˜λ“œμ‹œ `016_add_management_fields.sql`이 λ¨Όμ € μ‹€ν–‰λœ μƒνƒœμ—¬μ•Ό 함 +3. **Docker ν™˜κ²½**: λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ€ Docker μ»¨ν…Œμ΄λ„ˆ λ‚΄μ—μ„œ μ‹€ν–‰ν•΄μ•Ό 함 +4. **migration_log ν…Œμ΄λΈ” ꡬ쑰**: + - 컬럼λͺ…: `migration_file` (migration_name μ•„λ‹˜) + - ν•„μˆ˜ 컬럼: `migration_file`, `executed_at`, `status`, `notes` +5. **λ‘€λ°± 방법**: 문제 λ°œμƒ μ‹œ `project_sequence_no` μ»¬λŸΌμ„ NULL둜 μ„€μ • ν›„ μž¬μ‹€ν–‰ + +#### πŸ“Š μ˜ˆμƒ κ²°κ³Ό + +**Before**: +``` +ν˜„ν™©νŒ: No. (λΉˆκ°’) +ν”„λ‘œμ νŠΈ A: μ΄μŠˆλ“€μ΄ 전체 톡합 번호둜 ν‘œμ‹œ (No.2, No.5, No.8...) +``` + +**After**: +``` +ν˜„ν™©νŒ: No.1, No.2, No.3... +ν”„λ‘œμ νŠΈ A: No.1, No.2, No.3 +ν”„λ‘œμ νŠΈ B: No.1, No.2, No.3 +각 ν”„λ‘œμ νŠΈλ³„λ‘œ 1λΆ€ν„° μ‹œμž‘ν•˜λŠ” κΉ”λ”ν•œ 순번 +``` + +--- + +## πŸ“ 이전 변경사항 + +### 2025.10.25 - 관리함 ν•„λ“œ μΆ”κ°€ +- **파일**: `backend/migrations/016_add_management_fields.sql` +- **λ‚΄μš©**: κ΄€λ¦¬ν•¨μ—μ„œ μ‚¬μš©ν•  μΆ”κ°€ ν•„λ“œλ“€ 및 `generate_project_sequence_no()` ν•¨μˆ˜ 생성 + +### 2025.10.24 - μ‚¬μš©μž λΆ€μ„œ 정보 μΆ”κ°€ +- **파일**: `backend/migrations/015_add_user_department.sql` +- **λ‚΄μš©**: μ‚¬μš©μž ν…Œμ΄λΈ”μ— λΆ€μ„œ 정보 컬럼 μΆ”κ°€ + +--- + +## πŸ”§ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹€ν–‰ 도ꡬ + +**μžλ™ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 슀크립트** (μΆ”ν›„ 개발 μ˜ˆμ •): +```bash +#!/bin/bash +# run_migrations.sh +# λͺ¨λ“  λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ„ μˆœμ„œλŒ€λ‘œ μ‹€ν–‰ν•˜λŠ” 슀크립트 +``` + +**λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μƒνƒœ 확인**: +```sql +-- 졜근 μ‹€ν–‰λœ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 확인 +SELECT migration_file, executed_at, status, notes +FROM migration_log +ORDER BY executed_at DESC +LIMIT 10; + +-- νŠΉμ • λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 확인 +SELECT * FROM migration_log +WHERE migration_file = '017_fix_project_sequence_no.sql'; +``` + +#### πŸ“‹ μ‹€ν–‰ μ™„λ£Œ μƒνƒœ (2025.10.26) + +βœ… **λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ¨** +- **μ‹€ν–‰ μ‹œκ°„**: 2025-10-26 11:15:44+09:00 +- **μƒνƒœ**: SUCCESS +- **확인사항**: + - `generate_project_sequence_no()` ν•¨μˆ˜ 쑴재 βœ… + - `issues.project_sequence_no` 컬럼 쑴재 βœ… + - 순번이 λˆ„λ½λœ 관리 쀑인 이슈: 0개 βœ… + - λ°±μ—”λ“œ μ„œλΉ„μŠ€ μž¬μ‹œμž‘ μ™„λ£Œ βœ… diff --git a/backend/migrations/017_fix_project_sequence_no.sql b/backend/migrations/017_fix_project_sequence_no.sql new file mode 100644 index 0000000..7e97fcc --- /dev/null +++ b/backend/migrations/017_fix_project_sequence_no.sql @@ -0,0 +1,87 @@ +-- ν”„λ‘œμ νŠΈλ³„ 순번(project_sequence_no) μžλ™ ν• λ‹Ή κ°œμ„  +-- μˆ˜μ‹ ν•¨μ—μ„œ μ§„ν–‰ 쀑/μ™„λ£Œλ‘œ μƒνƒœ λ³€κ²½ μ‹œ ν”„λ‘œμ νŠΈλ³„ 순번이 μžλ™ ν• λ‹Ήλ˜λ„λ‘ κ°œμ„  + +DO $migration$ +DECLARE + issue_record RECORD; + seq_no INTEGER; + updated_count INTEGER := 0; +BEGIN + RAISE NOTICE '=== ν”„λ‘œμ νŠΈλ³„ 순번 μžλ™ ν• λ‹Ή κ°œμ„  λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹œμž‘ ==='; + + -- 1. generate_project_sequence_no ν•¨μˆ˜κ°€ μ‘΄μž¬ν•˜λŠ”μ§€ 확인 + IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'generate_project_sequence_no') THEN + RAISE EXCEPTION '❌ generate_project_sequence_no ν•¨μˆ˜κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 016_add_management_fields.sql을 λ¨Όμ € μ‹€ν–‰ν•˜μ„Έμš”.'; + END IF; + + -- 2. μ§„ν–‰ 쀑 λ˜λŠ” μ™„λ£Œ μƒνƒœμΈλ° project_sequence_noκ°€ NULL인 μ΄μŠˆλ“€ μ°ΎκΈ° + RAISE NOTICE 'πŸ” project_sequence_noκ°€ λˆ„λ½λœ μ΄μŠˆλ“€μ„ μ°ΎλŠ” 쀑...'; + + FOR issue_record IN + SELECT id, project_id, review_status + FROM issues + WHERE review_status IN ('in_progress', 'completed') + AND project_sequence_no IS NULL + ORDER BY project_id, reviewed_at NULLS LAST, report_date + LOOP + -- ν”„λ‘œμ νŠΈλ³„ 순번 생성 + SELECT generate_project_sequence_no(issue_record.project_id) INTO seq_no; + + -- 순번 ν• λ‹Ή + UPDATE issues + SET project_sequence_no = seq_no + WHERE id = issue_record.id; + + updated_count := updated_count + 1; + + RAISE NOTICE 'βœ… 이슈 ID: %, ν”„λ‘œμ νŠΈ ID: %, ν• λ‹Ήλœ 순번: %', + issue_record.id, issue_record.project_id, seq_no; + END LOOP; + + -- 3. κ²°κ³Ό μš”μ•½ + RAISE NOTICE '=== λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ™„λ£Œ ==='; + RAISE NOTICE 'πŸ“Š 총 %개의 μ΄μŠˆμ— ν”„λ‘œμ νŠΈλ³„ 순번이 ν• λ‹Ήλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', updated_count; + + -- 4. 검증 + DECLARE + missing_count INTEGER; + total_managed_count INTEGER; + BEGIN + -- 관리 쀑인 이슈 쀑 순번이 μ—†λŠ” 것듀 확인 + SELECT COUNT(*) INTO missing_count + FROM issues + WHERE review_status IN ('in_progress', 'completed') + AND project_sequence_no IS NULL; + + -- 전체 관리 쀑인 이슈 수 + SELECT COUNT(*) INTO total_managed_count + FROM issues + WHERE review_status IN ('in_progress', 'completed'); + + RAISE NOTICE '=== 검증 κ²°κ³Ό ==='; + RAISE NOTICE '전체 관리 쀑인 이슈: %개', total_managed_count; + RAISE NOTICE '순번이 λˆ„λ½λœ 이슈: %개', missing_count; + + IF missing_count > 0 THEN + RAISE WARNING '⚠️ μ—¬μ „νžˆ %개의 μ΄μŠˆμ— 순번이 λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.', missing_count; + ELSE + RAISE NOTICE 'βœ… λͺ¨λ“  관리 쀑인 μ΄μŠˆμ— 순번이 μ •μƒμ μœΌλ‘œ ν• λ‹Ήλ˜μ—ˆμŠ΅λ‹ˆλ‹€.'; + END IF; + END; + +EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION '❌ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: %', SQLERRM; +END $migration$; + +-- λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 둜그 기둝 +INSERT INTO migration_log (migration_file, executed_at, status, notes) +VALUES ( + '017_fix_project_sequence_no.sql', + NOW(), + 'SUCCESS', + 'ν”„λ‘œμ νŠΈλ³„ 순번 μžλ™ ν• λ‹Ή κ°œμ„ : μˆ˜μ‹ ν•¨μ—μ„œ 진행쀑/μ™„λ£Œλ‘œ μƒνƒœ λ³€κ²½μ‹œ project_sequence_no μžλ™ ν• λ‹Ήλ˜λ„λ‘ λ°±μ—”λ“œ 둜직 κ°œμ„  및 κΈ°μ‘΄ 데이터 보정' +) ON CONFLICT (migration_file) DO UPDATE SET + executed_at = NOW(), + status = EXCLUDED.status, + notes = EXCLUDED.notes; diff --git a/backend/routers/__pycache__/inbox.cpython-311.pyc b/backend/routers/__pycache__/inbox.cpython-311.pyc index 6a2583c..71b14d9 100644 Binary files a/backend/routers/__pycache__/inbox.cpython-311.pyc and b/backend/routers/__pycache__/inbox.cpython-311.pyc differ diff --git a/backend/routers/inbox.py b/backend/routers/inbox.py index b181471..17c5f78 100644 --- a/backend/routers/inbox.py +++ b/backend/routers/inbox.py @@ -261,6 +261,16 @@ async def update_issue_status( issue.reviewed_by_id = current_user.id issue.reviewed_at = datetime.now() + # μ§„ν–‰ 쀑 λ˜λŠ” μ™„λ£Œ μƒνƒœλ‘œ λ³€κ²½ μ‹œ ν”„λ‘œμ νŠΈλ³„ 순번 μžλ™ ν• λ‹Ή + if status_request.review_status in [ReviewStatus.in_progress, ReviewStatus.completed]: + if not issue.project_sequence_no: + from sqlalchemy import text + result = db.execute( + text("SELECT generate_project_sequence_no(:project_id)"), + {"project_id": issue.project_id} + ) + issue.project_sequence_no = result.scalar() + # μ™„λ£Œ 사진 μ—…λ‘œλ“œ 처리 if status_request.completion_photo and status_request.review_status == ReviewStatus.completed: try: