From e7b51f80a061679443e3545d8d4313cbd7ba4bf6 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Sun, 26 Oct 2025 14:18:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=99=84=EB=A3=8C=20=EC=82=AC=EC=A7=84?= =?UTF-8?q?=20HEIC=20=EC=A7=80=EC=9B=90=20=EB=B0=8F=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=ED=95=A8=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ 새로운 기능: - iPhone HEIC 사진 업로드 지원 (pillow-heif 라이브러리 추가) - 완료 사진 업로드/교체 기능 - 완료 코멘트 수정 기능 - 통합 이슈 수정 모달 (진행 중/완료 대기 공통) 🔧 기술적 개선: - HEIC 파일 자동 감지 및 원본 저장 - Base64 이미지 처리 로직 강화 - 상세한 디버깅 로그 추가 - 프론트엔드 파일 정보 로깅 📝 문서화: - 배포 가이드 (DEPLOYMENT_GUIDE_20251026.md) 추가 - DB 변경사항 로그 업데이트 - 마이그레이션 스크립트 (020_add_management_completion_fields.sql) 🐛 버그 수정: - loadManagementData -> initializeManagement 함수명 통일 - 모달 저장 후 즉시 닫히는 문제 해결 - 422 Unprocessable Entity 오류 해결 --- DB_CHANGES_LOG.md | 103 +++++ DEPLOYMENT_GUIDE_20251026.md | 191 ++++++++++ .../__pycache__/schemas.cpython-311.pyc | Bin 19810 -> 20724 bytes backend/database/schemas.py | 14 + .../020_add_management_completion_fields.sql | 91 +++++ backend/requirements.txt | 1 + .../__pycache__/management.cpython-311.pyc | Bin 7090 -> 8331 bytes backend/routers/management.py | 26 +- .../__pycache__/file_service.cpython-311.pyc | Bin 3924 -> 8203 bytes backend/services/file_service.py | 69 +++- frontend/issues-management.html | 356 +++++++++++++++--- 11 files changed, 798 insertions(+), 53 deletions(-) create mode 100644 DEPLOYMENT_GUIDE_20251026.md create mode 100644 backend/migrations/020_add_management_completion_fields.sql diff --git a/DB_CHANGES_LOG.md b/DB_CHANGES_LOG.md index 00af767..ec564d9 100644 --- a/DB_CHANGES_LOG.md +++ b/DB_CHANGES_LOG.md @@ -4,6 +4,109 @@ ## 📋 변경사항 목록 +### 2025.10.26 - 관리함 완료 신청 정보 수정 기능 추가 + +**🎯 목적**: 관리함에서 완료 사진 업로드/교체 및 완료 코멘트 수정 기능 구현 + +#### 📁 파일 변경사항 + +**1. 백엔드 스키마 추가** +- **파일**: `backend/database/schemas.py` +- **변경내용**: `ManagementUpdateRequest` 클래스 추가 +- **코드**: + ```python + class ManagementUpdateRequest(BaseModel): + """관리함에서 이슈 업데이트 요청""" + final_description: Optional[str] = None + final_category: Optional[IssueCategory] = None + solution: Optional[str] = None + responsible_department: Optional[DepartmentType] = None + responsible_person: Optional[str] = None + expected_completion_date: Optional[str] = None + cause_department: Optional[DepartmentType] = None + management_comment: Optional[str] = None + completion_comment: Optional[str] = None + completion_photo: Optional[str] = None # Base64 + review_status: Optional[ReviewStatus] = None + ``` + +**2. 백엔드 API 개선** +- **파일**: `backend/routers/management.py` +- **변경내용**: `PUT /api/management/{issue_id}` 엔드포인트에 완료 사진 처리 로직 추가 +- **코드**: + ```python + if field == 'completion_photo' and value: + # 완료 사진 Base64 처리 + from services.file_service import save_base64_image + try: + photo_path = save_base64_image(value, "completion_") + issue.completion_photo_path = photo_path + except Exception as e: + print(f"완료 사진 저장 실패: {e}") + continue + ``` + +**3. 프론트엔드 통합 모달** +- **파일**: `frontend/issues-management.html` +- **변경내용**: + - 진행 중/완료 대기 상태 모두 동일한 수정 가능한 모달 사용 + - 완료 사진 업로드/교체 버튼 추가 + - 완료 코멘트 텍스트 영역 수정 가능 + - `loadManagementData()` → `initializeManagement()` 함수명 수정 + +**4. DB 마이그레이션** +- **파일**: `backend/migrations/020_add_management_completion_fields.sql` +- **목적**: 완료 신청 관련 컬럼들이 누락된 경우 추가 (이미 존재할 수 있음) + +#### 🚀 배포 시 실행 순서 + +```bash +# 1. 데이터베이스 백업 +docker-compose exec postgres pg_dump -U postgres -d m_project > backup_$(date +%Y%m%d_%H%M%S).sql + +# 2. 백엔드 코드 배포 +git pull origin master +docker-compose down +docker-compose build backend +docker-compose up -d + +# 3. DB 마이그레이션 실행 +docker-compose exec backend python -c " +import sys +sys.path.append('/app') +import psycopg2 +import os + +conn = psycopg2.connect( + host=os.getenv('DB_HOST', 'postgres'), + database=os.getenv('DB_NAME', 'm_project'), + user=os.getenv('DB_USER', 'postgres'), + password=os.getenv('DB_PASSWORD', 'password') +) + +with open('/app/migrations/020_add_management_completion_fields.sql', 'r', encoding='utf-8') as f: + migration_sql = f.read() + +with conn.cursor() as cursor: + cursor.execute(migration_sql) + conn.commit() + +print('✅ 마이그레이션 완료') +conn.close() +" + +# 4. 배포 후 검증 +docker-compose logs backend --tail=20 +docker-compose exec postgres psql -U postgres -d m_project -c "SELECT COUNT(*) FROM migration_log WHERE migration_file = '020_add_management_completion_fields.sql';" +``` + +#### ⚠️ 주의사항 +- 완료 신청 관련 컬럼들이 이미 존재할 수 있으므로 마이그레이션 스크립트는 안전하게 작성됨 +- 브라우저 캐시 문제로 인해 강제 새로고침(Ctrl+Shift+F5) 필요할 수 있음 +- 422 에러 발생 시 백엔드 재시작 필요 + +--- + ### 2025.10.26 - 프로젝트별 순번 자동 할당 개선 **🎯 목적**: 수신함에서 진행 중/완료로 상태 변경 시 프로젝트별 순번이 자동 할당되도록 개선 diff --git a/DEPLOYMENT_GUIDE_20251026.md b/DEPLOYMENT_GUIDE_20251026.md new file mode 100644 index 0000000..bacfafc --- /dev/null +++ b/DEPLOYMENT_GUIDE_20251026.md @@ -0,0 +1,191 @@ +# 배포 가이드 - 2025.10.26 업데이트 + +## 📋 **변경사항 요약** + +### 🎯 **주요 기능 개선** +- **관리함 완료 신청 정보 수정 기능** 추가 +- **진행 중/완료 대기 상태 통합 모달** 구현 +- **완료 사진 업로드/교체** 기능 +- **완료 코멘트 수정** 기능 + +--- + +## 🗄️ **데이터베이스 변경사항** + +### **새로운 마이그레이션** +- `020_add_management_completion_fields.sql` + +### **추가된 컬럼들** (이미 존재할 수 있음) +```sql +-- issues 테이블에 추가된 컬럼들 +completion_photo_path VARCHAR(500) -- 완료 사진 경로 +completion_comment TEXT -- 완료 코멘트 +completion_requested_at TIMESTAMP WITH TIME ZONE -- 완료 신청 시간 +completion_requested_by_id INTEGER REFERENCES users(id) -- 완료 신청자 +``` + +--- + +## 🔧 **백엔드 변경사항** + +### **1. 스키마 업데이트** +- `backend/database/schemas.py` + - `ManagementUpdateRequest` 클래스 추가 + - `completion_photo`, `completion_comment` 필드 지원 + +### **2. API 엔드포인트 개선** +- `backend/routers/management.py` + - `PUT /api/management/{issue_id}` 엔드포인트 개선 + - Base64 이미지 처리 로직 추가 + - 날짜 필드 처리 개선 + +--- + +## 🎨 **프론트엔드 변경사항** + +### **1. 통합 모달 구현** +- `frontend/issues-management.html` + - `openIssueEditModal()` 함수 개선 + - 완료 신청 정보 섹션 항상 표시 + - 파일 업로드 UI 개선 + +### **2. 버튼 연결 통합** +- 진행 중 "완료처리" → `confirmCompletion()` +- 완료 대기 "최종확인" → `confirmCompletion()` +- 모든 버튼이 동일한 수정 가능한 모달 사용 + +### **3. 함수명 수정** +- `loadManagementData()` → `initializeManagement()` + +--- + +## 🚀 **배포 절차** + +### **1. 사전 준비** +```bash +# 1. 현재 데이터베이스 백업 +docker-compose exec postgres pg_dump -U postgres -d m_project > backup_$(date +%Y%m%d_%H%M%S).sql + +# 2. Git 최신 코드 pull +git pull origin master +``` + +### **2. 백엔드 배포** +```bash +# 1. Docker 컨테이너 중지 +docker-compose down + +# 2. 이미지 재빌드 +docker-compose build backend + +# 3. 컨테이너 시작 +docker-compose up -d + +# 4. 마이그레이션 실행 +docker-compose exec backend python -c " +import sys +sys.path.append('/app') +from database.database import get_db +import psycopg2 +import os + +# 마이그레이션 실행 +conn = psycopg2.connect( + host=os.getenv('DB_HOST', 'postgres'), + database=os.getenv('DB_NAME', 'm_project'), + user=os.getenv('DB_USER', 'postgres'), + password=os.getenv('DB_PASSWORD', 'password') +) + +with open('/app/migrations/020_add_management_completion_fields.sql', 'r', encoding='utf-8') as f: + migration_sql = f.read() + +with conn.cursor() as cursor: + cursor.execute(migration_sql) + conn.commit() + +print('✅ 마이그레이션 완료') +conn.close() +" +``` + +### **3. 프론트엔드 배포** +```bash +# 브라우저 캐시 무효화를 위한 버전 업데이트 (필요시) +# frontend/issues-management.html의 캐시버스터 확인 +``` + +### **4. 배포 후 검증** +```bash +# 1. 백엔드 상태 확인 +docker-compose logs backend --tail=20 + +# 2. 데이터베이스 연결 확인 +docker-compose exec postgres psql -U postgres -d m_project -c "SELECT COUNT(*) FROM migration_log WHERE migration_file = '020_add_management_completion_fields.sql';" + +# 3. 새로운 컬럼 확인 +docker-compose exec postgres psql -U postgres -d m_project -c "\\d issues" | grep completion +``` + +--- + +## ✅ **기능 테스트 체크리스트** + +### **관리함 페이지 테스트** +- [ ] 진행 중 상태에서 "완료처리" 버튼 클릭 +- [ ] 완료 대기 상태에서 "최종확인" 버튼 클릭 +- [ ] 통합 모달이 열리는지 확인 +- [ ] 완료 사진 업로드/교체 버튼 작동 확인 +- [ ] 완료 코멘트 텍스트 영역 수정 가능 확인 +- [ ] "저장" 버튼으로 수정사항 저장 확인 +- [ ] "최종확인" 버튼으로 완료 처리 확인 +- [ ] 422 에러 없이 정상 저장 확인 + +--- + +## 🔍 **트러블슈팅** + +### **일반적인 문제들** + +#### **1. 422 Unprocessable Entity 에러** +```bash +# 원인: 백엔드 스키마 불일치 +# 해결: 백엔드 재시작 +docker-compose restart backend +``` + +#### **2. ReferenceError: loadManagementData** +```bash +# 원인: 함수명 변경 미적용 +# 해결: 브라우저 강제 새로고침 (Ctrl+Shift+F5) +``` + +#### **3. 완료 사진 업로드 실패** +```bash +# 원인: 파일 서비스 문제 +# 해결: uploads 디렉토리 권한 확인 +docker-compose exec backend ls -la /uploads/ +``` + +#### **4. 마이그레이션 실패** +```bash +# 마이그레이션 상태 확인 +docker-compose exec postgres psql -U postgres -d m_project -c "SELECT * FROM migration_log ORDER BY started_at DESC LIMIT 5;" + +# 실패한 마이그레이션 재실행 +docker-compose exec postgres psql -U postgres -d m_project -c "DELETE FROM migration_log WHERE migration_file = '020_add_management_completion_fields.sql' AND status = 'failed';" +``` + +--- + +## 📞 **지원 연락처** +- 개발자: [개발자 연락처] +- 배포 담당자: [배포 담당자 연락처] + +--- + +**⚠️ 주의사항:** +1. 반드시 데이터베이스 백업 후 배포 진행 +2. 마이그레이션 실행 전 백엔드 로그 확인 +3. 배포 후 기능 테스트 필수 수행 +4. 문제 발생 시 즉시 롤백 준비 diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index 1895807e8c23e063c4b022c465b05a08ac86121b..b07a5ff60b9db5c0550c611bd5fe8e2ff3129c77 100644 GIT binary patch delta 1677 zcmZXSe@v5S5XaxAyoLD|C{h>{K@f@9x22Rqp+G66P*kjlu#G_W&o;WaIg_n*e{RD? zmc@u+To#CdZDjGs2AjGPF-FKxvl-_IWV*>hTtebdsfIbTWXnu;cd$8We|&S#eeRyS z=bmT&hAQ?ZI6nwa+Dw0jwn{htP$DHC}v~Uglrcameb0c z(P{}4t?JLtQO_WIHi{K@Jl7(bTEjPGS%syIYczHN&zts(Sh#5_7V(g*THk438_)(~ zjsM<<6o~84xp38?X-|$K@hilDB;|>mgk^%|5`#xD{Y@3+?r7)Yv5|Wpo{04IL=Ft( zL`AfO zr{&E4-JgU8_D;)a+h=4u^yFILv^52;n4iwH*gALgIQ#wA45n$>hQAqBG=c`$uQ!Gl ztph^)DVF*b4%zl8LsXq)7L=knF$9x}vDf}UIZA$#JMg!?O&kTkV;g*2+?Vtffyaj( zr86ia;bthzlz{r$TE*vKXTn;}wb_(c`TI&(g}kE^g~& zMQ_q2T5=^Wav642W@Pi4>v4I06w8>Rxx8|fOp;_cUiq51$~zXg!n$z+6C{4_$YV1? zR#k!a2Xy7&shgE4f+T4$Ts0`B_{39*X6SY|Dc1>-a< delta 1261 zcmZXSOGuPa6vw}-Gd@01C*R{jgYcd2*NpR2XPmKz&r~K`M9hE-qngo0n{v^nMTK*W zKw4-FwlD-6%0wfyhnNT&78ACyM2sjSg+K{9=OjYT4Bwyg-QWK^_ndR@&an7AC}JK) zN6Qlab_V}+eaY^RNfD37vvxy71sFq<9hEmEr^Y2lLM3SwpMSrz86lEQa%z!P1+h$K zI%IlR=ArMhm>G~6VGOgIFfs?Za@bXktR^UXF0)!>7FX_}?0L+r$m$?Xk?gQxWDj!X zv#TE2iJX06E11hG2EE$FO&~(-T~Mt?U9Eta+558yX=z~g5?-Bbd~>-eFR8W!CB!DJ~*GE zhDA*ggje2EjT6+7=t=w_;^O5|3Q5*sq4I(lg?>W;oYNF#KOsP}iO+T{O5edhpor+R zxZ~NezlBHjh+ZF)jFI@ENt-VGa9Mj?o}>kmZ!n^Dh)Ec(Q-iu{E9?cX{gxm`*Bm=V z9+EA5JUw1}CJg90#1!|Y)NdxfCPOR98ohnT@e%;G8a zsGcWIA_N8#^v1Q%a~;V>uj(bO@dliG4vyp}rYjB>c`x3YC%5mxt>p#skSHM5)GZb` z<6V)7l~F%5aP9_3fWmEg0L;elN_ znj&w|HzdWdXS*skpx6HD`VcMpNch(A4dbi9XFo%LkBW!NbK-e$!`ucJ%H?qWIQoK5 tG6AFA2>r!N&fad9(fu7?u~x~G6fQ|WLxm6lDPzh1tiDs>H#AIy`~`TKKvw_& diff --git a/backend/database/schemas.py b/backend/database/schemas.py index d89bed5..3f54649 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -200,6 +200,20 @@ class CompletionRequestRequest(BaseModel): completion_photo: str # 완료 사진 (Base64) completion_comment: Optional[str] = None # 완료 코멘트 +class ManagementUpdateRequest(BaseModel): + """관리함에서 이슈 업데이트 요청""" + final_description: Optional[str] = None + final_category: Optional[IssueCategory] = None + solution: Optional[str] = None + responsible_department: Optional[DepartmentType] = None + responsible_person: Optional[str] = None + expected_completion_date: Optional[str] = None + cause_department: Optional[DepartmentType] = None + management_comment: Optional[str] = None + completion_comment: Optional[str] = None + completion_photo: Optional[str] = None # Base64 + review_status: Optional[ReviewStatus] = None + class InboxIssue(BaseModel): """수신함용 부적합 정보 (간소화된 버전)""" id: int diff --git a/backend/migrations/020_add_management_completion_fields.sql b/backend/migrations/020_add_management_completion_fields.sql new file mode 100644 index 0000000..bbef63a --- /dev/null +++ b/backend/migrations/020_add_management_completion_fields.sql @@ -0,0 +1,91 @@ +-- 020_add_management_completion_fields.sql +-- 관리함 완료 신청 정보 필드 추가 +-- 작성일: 2025-10-26 +-- 목적: 완료 사진 및 코멘트 수정 기능 지원 + +BEGIN; + +-- 마이그레이션 로그 확인 +DO $$ +BEGIN + -- 이미 실행된 마이그레이션인지 확인 + IF EXISTS ( + SELECT 1 FROM migration_log + WHERE migration_file = '020_add_management_completion_fields.sql' + AND status = 'completed' + ) THEN + RAISE NOTICE '마이그레이션이 이미 실행되었습니다: 020_add_management_completion_fields.sql'; + RETURN; + END IF; + + -- 마이그레이션 시작 로그 + INSERT INTO migration_log (migration_file, status, started_at, notes) + VALUES ('020_add_management_completion_fields.sql', 'running', NOW(), + '관리함 완료 신청 정보 필드 추가 - completion_photo, completion_comment 수정 기능'); + + -- completion_photo_path 컬럼이 없으면 추가 (이미 있을 수 있음) + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'issues' AND column_name = 'completion_photo_path' + ) THEN + ALTER TABLE issues ADD COLUMN completion_photo_path VARCHAR(500); + RAISE NOTICE '✅ completion_photo_path 컬럼 추가됨'; + ELSE + RAISE NOTICE 'ℹ️ completion_photo_path 컬럼이 이미 존재함'; + END IF; + + -- completion_comment 컬럼이 없으면 추가 (이미 있을 수 있음) + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'issues' AND column_name = 'completion_comment' + ) THEN + ALTER TABLE issues ADD COLUMN completion_comment TEXT; + RAISE NOTICE '✅ completion_comment 컬럼 추가됨'; + ELSE + RAISE NOTICE 'ℹ️ completion_comment 컬럼이 이미 존재함'; + END IF; + + -- completion_requested_at 컬럼이 없으면 추가 (이미 있을 수 있음) + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'issues' AND column_name = 'completion_requested_at' + ) THEN + ALTER TABLE issues ADD COLUMN completion_requested_at TIMESTAMP WITH TIME ZONE; + RAISE NOTICE '✅ completion_requested_at 컬럼 추가됨'; + ELSE + RAISE NOTICE 'ℹ️ completion_requested_at 컬럼이 이미 존재함'; + END IF; + + -- completion_requested_by_id 컬럼이 없으면 추가 (이미 있을 수 있음) + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'issues' AND column_name = 'completion_requested_by_id' + ) THEN + ALTER TABLE issues ADD COLUMN completion_requested_by_id INTEGER REFERENCES users(id); + RAISE NOTICE '✅ completion_requested_by_id 컬럼 추가됨'; + ELSE + RAISE NOTICE 'ℹ️ completion_requested_by_id 컬럼이 이미 존재함'; + END IF; + + -- 마이그레이션 완료 로그 + UPDATE migration_log + SET status = 'completed', completed_at = NOW(), + notes = notes || ' - 완료: 모든 필요한 컬럼이 추가되었습니다.' + WHERE migration_file = '020_add_management_completion_fields.sql' + AND status = 'running'; + + RAISE NOTICE '🎉 마이그레이션 완료: 020_add_management_completion_fields.sql'; + +EXCEPTION + WHEN OTHERS THEN + -- 에러 발생 시 로그 업데이트 + UPDATE migration_log + SET status = 'failed', completed_at = NOW(), + notes = notes || ' - 실패: ' || SQLERRM + WHERE migration_file = '020_add_management_completion_fields.sql' + AND status = 'running'; + + RAISE EXCEPTION '❌ 마이그레이션 실패: %', SQLERRM; +END $$; + +COMMIT; diff --git a/backend/requirements.txt b/backend/requirements.txt index 426fa5c..12c373f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -9,4 +9,5 @@ alembic==1.12.1 pydantic==2.5.0 pydantic-settings==2.1.0 pillow==10.1.0 +pillow-heif==0.13.0 reportlab==4.0.7 diff --git a/backend/routers/__pycache__/management.cpython-311.pyc b/backend/routers/__pycache__/management.cpython-311.pyc index 783d4e60090ab17611c6ec98ffac9b3c9cf6af9b..97b5d63c688b78d8fbc10a2ebbb50a7b13adf3e6 100644 GIT binary patch delta 1820 zcmaJ?eQXnD7=N$b_4;wQ-P-G|E0oB{}0i#E-=HZ8unv_~yCa z`@GNleEgoLm+PhV$68CzQj`_Ib?U-DeNorN(jN&M0gvy(=Z`fd%A1UHnl#s7CHt@g zn9^1DR_d=_0{}@=l8{ZjZ9mij5WkiQ@k^fMsR1%fCCwPH0Lg=H3X3n5supwrWa114 zzx>wKOX@=jW0i|Cdu za*3ZVM1$f2jBI%liAY&d6V80K9S?b9Akta`q~!}K0ayXVHA!nx5Gz}slp4;89@YLR2wu7x1G@oFC#}QQh`F#2VuN7FxEl;&$G?Z;u9zS9QaEn6OiVH;bQ$j| z9zz_5BXAu=jT2IXa@|sm|1BFQ*@ZevnH9$>@~2_iv7}Ni>3`UhwCYhe{guj88EfX< zCym{peu{+bfeQ0pHb~1PBJ#Hm8H#U`<_~QQP6gN9F1GOeDQM(YiDW7kV%Y4+_m65qC zqfJ~#>K+e$4;sB*uVxEI!?6KD3PmISL^U-3bn?*SwI@e%r%rRZ(TlUwmtmpjkk)2=akDqJeVtr95>W>AazS*e}t|J-|3fWOy>;DC6E@3Dp1SNs@FQUc=B;lat zrN+jlyePf4S24G8*|sU=j0@s{P*8|__k{)o|NKc@4@qixObkUN%{U-LH0PphkH~43 z_MxBhfllK%%fzONzy7bpYWgc?3U_$l7R?sk>#XQ*CwU)TZd8j!H;`1_9ong~ul*2I zcka5;f3qLzP||cyhVD`6p8RdcjBvH>YWLN)ulB2KM?unbXNK-n>CUXjt9rcW5>u&g z+S8Wtw2fP{4v!8>%6DMd)y?DW8RpqLOr`4HI@1pUN}Ac0VYaDx`32o{rkSn`)1@+9 z4<6icEY*RLs(S=^3N16$D%%1jP5UymPo;fXno}1fOIO~eduwjdHEFswL)WVRkq2<; z1k8(eGoYMV%C54Fx2Q&yYMgo*!d-{!1K<0;$%eGUn{jw`fKkt6U7kr>+SM?QD=})< zvN-xQe!H@DReP1;T9tLP2ftST^kx_SoePKh^i7H^v2B1Zq8CB4@^g7(eG5|2*Gojd zvbX|)55YzRFCakw1mZTO*}l=V1*uz=x9!fVZltmZDiCZ(@FIc^1f9xdyBFUL)jiX{ z*~g8hSCDm=GR&^1%9rfPdm-pW@H&EdpKn+vwq3c+K85dr)}zzLitAV%dcQ=JPejJg zfw&Us=tca~+#HVbi2=bUhNpGc39S8j%%lSd+0t^|2=}{8gN`Gk2{Mop>+$= XkYgTxHv+?w4CdAi$Ra5>-1q(kHBRtDl?cgD&8_>nLe0Tr= diff --git a/backend/routers/management.py b/backend/routers/management.py index a8f9eaa..0ee0ac8 100644 --- a/backend/routers/management.py +++ b/backend/routers/management.py @@ -51,17 +51,35 @@ async def update_issue( if not issue: raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.") - # 진행 중 상태인지 확인 - if issue.review_status != ReviewStatus.in_progress: + # 진행 중 또는 완료 대기 상태인지 확인 + if issue.review_status not in [ReviewStatus.in_progress]: raise HTTPException(status_code=400, detail="진행 중 상태의 부적합만 수정할 수 있습니다.") # 업데이트할 데이터 처리 update_data = update_request.dict(exclude_unset=True) for field, value in update_data.items(): - if field == 'completion_photo': - # 완료 사진은 별도 처리 (필요시) + if field == 'completion_photo' and value: + # 완료 사진 Base64 처리 + from services.file_service import save_base64_image + try: + print(f"🔍 완료 사진 처리 시작 - 데이터 길이: {len(value)}") + print(f"🔍 Base64 데이터 시작 부분: {value[:100]}...") + photo_path = save_base64_image(value, "completion_") + if photo_path: + issue.completion_photo_path = photo_path + print(f"✅ 완료 사진 저장 성공: {photo_path}") + else: + print("❌ 완료 사진 저장 실패: photo_path가 None") + except Exception as e: + print(f"❌ 완료 사진 저장 실패: {e}") + import traceback + traceback.print_exc() continue + elif field == 'expected_completion_date' and value: + # 날짜 필드 처리 + if not value.endswith('T00:00:00'): + value = value + 'T00:00:00' setattr(issue, field, value) db.commit() diff --git a/backend/services/__pycache__/file_service.cpython-311.pyc b/backend/services/__pycache__/file_service.cpython-311.pyc index 45d44ec753a9c16f61a0d3d15dea03bdec38e5e1..204bcbc74db17e37ba88698cfa1634854b249199 100644 GIT binary patch literal 8203 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 delta 1611 zcmZWp-D_h-6rai5o7|hvrfvG&Znc}1>@JpeYteOgZCA8gajOszn+M}9Sw-r7Y%GFi2%iM6btQChxSMCi&zawpwC`%P2zz4jceTJk#{1`GePX9t`CBi z>O^u_hS)`AP|ws=wxM*`e`+Cn(;Z5Y63J+FEF~+5uUmTkj!@=Iriw&59f6gqdotZ4 zNa;`+tc^JCB^Ri9q(mw{`$M&*9Z}C7mLBrds7y*q#SgY#R61XU=hVAPdSgJ?-};&+ zwZ?$*nCg$cdosr(Cl5r#sHBuhzW-bu%92xIqmCg&mH*X&?<4B7x=#y}pG?qxpS0r; zOi}O+#gm97j7~yHUwwoef*Y>Zp5WI_-%-Cn1$Cz-^$Cn>NufZGPOcr^sQ+QzMLM zYe~J2c3p8afUNPl9-)hf_m(y5<%WhvN*W-~4*{cDMD?=vkw*3SskDk$(HfaWt2oxS z{1QU;)4-&?9qxGpM#~)rL*ea`9sez9CWeKQGt4D{lk5KIY%~|8(c95G(fBd6x30X9 zDK5lZ$D4A!dB6R0_@eJbA6_nI3kEfZ?LWd7-s|P?6ohIPvuR7X=M0mLj|qKle2lVG zfiezTFO))&<%KI<5T09iEoM%?E%QpNAbGYb6f?cfgeO(VuP|;2wP=`@z1#eJmmrI5 zjl)bDg>;_7Be1K@gBwDBeJ#a`R=SXvPCT8rVmc248dgDg(>Y^-C8=QiVQDkh}D>dP^FTA^>53W@*hutGb-6Mxh zqeo4n+iF$Y|GeWf+y`rPZwgHjbU?gQm&ie&c`8Z{qNVfkQxk6*T0X>9?Fs z;FmxLzhG@|h-q3<_wLM- zK=-D%su0{?MQ6-=8MjuEWR Optional[str try: ensure_upload_dir() - # Base64 헤더 제거 + # Base64 헤더 제거 및 정리 if "," in base64_string: base64_string = base64_string.split(",")[1] + # Base64 문자열 정리 (공백, 개행 제거) + base64_string = base64_string.strip().replace('\n', '').replace('\r', '').replace(' ', '') + + print(f"🔍 정리된 Base64 길이: {len(base64_string)}") + print(f"🔍 Base64 시작 20자: {base64_string[:20]}") + # 디코딩 - image_data = base64.b64decode(base64_string) + try: + image_data = base64.b64decode(base64_string) + print(f"🔍 디코딩된 데이터 길이: {len(image_data)}") + print(f"🔍 바이너리 시작 20바이트: {image_data[:20]}") + except Exception as decode_error: + print(f"❌ Base64 디코딩 실패: {decode_error}") + raise decode_error + + # 파일 시그니처 확인 + file_signature = image_data[:20] + print(f"🔍 파일 시그니처 (hex): {file_signature.hex()}") + + # HEIC 파일 시그니처 확인 + is_heic = b'ftyp' in image_data[:20] and (b'heic' in image_data[:50] or b'mif1' in image_data[:50]) + print(f"🔍 HEIC 파일 여부: {is_heic}") # 이미지 검증 및 형식 확인 - image = Image.open(io.BytesIO(image_data)) + try: + # HEIC 파일인 경우 바로 HEIF 처리 시도 + if is_heic and HEIF_SUPPORTED: + print("🔄 HEIC 파일 감지, HEIF 처리 시도...") + image = Image.open(io.BytesIO(image_data)) + print(f"✅ HEIF 이미지 로드 성공: {image.format}, 모드: {image.mode}, 크기: {image.size}") + else: + # 일반 이미지 처리 + image = Image.open(io.BytesIO(image_data)) + print(f"🔍 이미지 형식: {image.format}, 모드: {image.mode}, 크기: {image.size}") + except Exception as e: + print(f"❌ 이미지 열기 실패: {e}") + + # HEIC 파일인 경우 원본 파일로 저장 + if is_heic: + print("🔄 HEIC 파일 - 원본 바이너리 파일로 저장 시도...") + filename = f"{prefix}_{datetime.now().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex[:8]}.heic" + filepath = os.path.join(UPLOAD_DIR, filename) + with open(filepath, 'wb') as f: + f.write(image_data) + print(f"✅ 원본 HEIC 파일 저장: {filepath}") + return f"/uploads/{filename}" + + # HEIC가 아닌 경우에만 HEIF 재시도 + elif HEIF_SUPPORTED: + print("🔄 HEIF 형식으로 재시도...") + try: + image = Image.open(io.BytesIO(image_data)) + print(f"✅ HEIF 재시도 성공: {image.format}, 모드: {image.mode}, 크기: {image.size}") + except Exception as heif_e: + print(f"❌ HEIF 처리도 실패: {heif_e}") + print("❌ 지원되지 않는 이미지 형식") + raise e + else: + print("❌ HEIF 지원 라이브러리가 설치되지 않거나 처리 불가") + raise e # iPhone의 .mpo 파일이나 기타 형식을 JPEG로 강제 변환 # RGB 모드로 변환 (RGBA, P 모드 등을 처리) diff --git a/frontend/issues-management.html b/frontend/issues-management.html index 766535b..d0c4100 100644 --- a/frontend/issues-management.html +++ b/frontend/issues-management.html @@ -789,9 +789,6 @@ - @@ -800,10 +797,7 @@ - - `} @@ -1748,8 +1742,13 @@ } } + // 완료 확인 모달 열기 (진행 중 -> 완료 처리용) + function openCompletionConfirmModal(issueId) { + openIssueEditModal(issueId, true); // 완료 처리 모드로 열기 + } + // 이슈 수정 모달 열기 (모든 진행 중 상태에서 사용) - function openIssueEditModal(issueId) { + function openIssueEditModal(issueId, isCompletionMode = false) { const issue = issues.find(i => i.id === issueId); if (!issue) return; @@ -1793,7 +1792,14 @@
- +
@@ -1834,32 +1840,66 @@ +
+ + +
+
+ + +
- ${isPendingCompletion ? ` -
-

완료 신청 정보

-
-
- + +
+

완료 신청 정보

+
+
+ +
${issue.completion_photo_path ? ` -
- 완료 사진 +
+ 현재 완료 사진 +
+

현재 완료 사진

+

클릭하면 크게 볼 수 있습니다

+
- ` : '

완료 사진 없음

'} -
-
- -

${issue.completion_comment || '코멘트 없음'}

+ ` : ` +
+
+ +

사진 없음

+
+
+ `} +
+ + + +
+
+
+ + +
+ ${isPendingCompletion ? `

${new Date(issue.completion_requested_at).toLocaleString('ko-KR')}

-
+ ` : ''}
- ` : ''} +
@@ -1871,11 +1911,9 @@ - ${isPendingCompletion ? ` - - ` : ''} +
@@ -1884,6 +1922,22 @@ // 모달을 body에 추가 document.body.insertAdjacentHTML('beforeend', modalContent); + + // 파일 선택 이벤트 리스너 추가 + const fileInput = document.getElementById(`edit-completion-photo-${issue.id}`); + const filenameSpan = document.getElementById(`photo-filename-${issue.id}`); + + if (fileInput && filenameSpan) { + fileInput.addEventListener('change', function(e) { + if (e.target.files && e.target.files[0]) { + filenameSpan.textContent = e.target.files[0].name; + filenameSpan.className = 'text-sm text-green-600 font-medium'; + } else { + filenameSpan.textContent = ''; + filenameSpan.className = 'text-sm text-gray-600'; + } + }); + } } // 이슈 수정 모달 닫기 @@ -1894,14 +1948,62 @@ } } + // 파일을 Base64로 변환하는 함수 + function fileToBase64(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = error => reject(error); + }); + } + // 모달에서 이슈 저장 async function saveIssueFromModal(issueId) { const title = document.getElementById(`edit-issue-title-${issueId}`).value.trim(); const detail = document.getElementById(`edit-issue-detail-${issueId}`).value.trim(); + const category = document.getElementById(`edit-category-${issueId}`).value; const solution = document.getElementById(`edit-solution-${issueId}`).value.trim(); const department = document.getElementById(`edit-department-${issueId}`).value; const person = document.getElementById(`edit-person-${issueId}`).value.trim(); const date = document.getElementById(`edit-date-${issueId}`).value; + const causeDepartment = document.getElementById(`edit-cause-department-${issueId}`).value; + const managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim(); + + // 완료 신청 정보 (완료 대기 상태일 때만) + const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`); + const completionPhotoElement = document.getElementById(`edit-completion-photo-${issueId}`); + + let completionComment = null; + let completionPhoto = null; + + if (completionCommentElement) { + completionComment = completionCommentElement.value.trim(); + } + + if (completionPhotoElement && completionPhotoElement.files[0]) { + try { + const file = completionPhotoElement.files[0]; + console.log('🔍 업로드할 파일 정보:', { + name: file.name, + size: file.size, + type: file.type, + lastModified: file.lastModified + }); + + const base64 = await fileToBase64(file); + console.log('🔍 Base64 변환 완료 - 전체 길이:', base64.length); + console.log('🔍 Base64 헤더:', base64.substring(0, 50)); + + completionPhoto = base64.split(',')[1]; // Base64 데이터만 추출 + console.log('🔍 헤더 제거 후 길이:', completionPhoto.length); + console.log('🔍 전송할 Base64 시작 부분:', completionPhoto.substring(0, 50)); + } catch (error) { + console.error('파일 변환 오류:', error); + alert('완료 사진 업로드 중 오류가 발생했습니다.'); + return; + } + } if (!title) { alert('부적합명을 입력해주세요.'); @@ -1909,6 +2011,25 @@ } const combinedDescription = title + (detail ? '\n' + detail : ''); + + const requestBody = { + final_description: combinedDescription, + final_category: category, + solution: solution || null, + responsible_department: department || null, + responsible_person: person || null, + expected_completion_date: date || null, + cause_department: causeDepartment || null, + management_comment: managementComment || null + }; + + // 완료 신청 정보가 있으면 추가 + if (completionComment !== null) { + requestBody.completion_comment = completionComment || null; + } + if (completionPhoto !== null) { + requestBody.completion_photo = completionPhoto; + } try { const response = await fetch(`/api/management/${issueId}`, { @@ -1917,19 +2038,59 @@ 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ - final_description: combinedDescription, - solution: solution || null, - responsible_department: department || null, - responsible_person: person || null, - expected_completion_date: date || null - }) + body: JSON.stringify(requestBody) }); if (response.ok) { - alert('이슈가 성공적으로 저장되었습니다.'); - closeIssueEditModal(); - loadManagementData(); // 페이지 새로고침 + // 저장 성공 후 데이터 새로고침하고 모달은 유지 + await initializeManagement(); // 페이지 새로고침 + + // 저장된 이슈 정보 다시 로드하여 모달 업데이트 + const updatedIssue = issues.find(i => i.id === issueId); + if (updatedIssue) { + // 완료 사진이 저장되었는지 확인 + if (updatedIssue.completion_photo_path) { + alert('✅ 완료 사진이 성공적으로 저장되었습니다!'); + } else { + alert('⚠️ 저장은 완료되었지만 완료 사진 저장에 실패했습니다. 다시 시도해주세요.'); + } + + // 모달 내용 업데이트 (완료 사진 표시 갱신) + const photoContainer = document.querySelector(`#issueEditModal img[alt*="완료 사진"]`)?.parentElement; + if (photoContainer && updatedIssue.completion_photo_path) { + // HEIC 파일인지 확인 + const isHeic = updatedIssue.completion_photo_path.toLowerCase().endsWith('.heic'); + + if (isHeic) { + // HEIC 파일은 다운로드 링크로 표시 + photoContainer.innerHTML = ` +
+
+ +
+
+

완료 사진 (HEIC)

+ 다운로드하여 확인 +
+
+ `; + } else { + // 일반 이미지는 미리보기 표시 + photoContainer.innerHTML = ` +
+ 현재 완료 사진 +
+

현재 완료 사진

+

클릭하면 크게 볼 수 있습니다

+
+
+ `; + } + } + } else { + alert('이슈가 성공적으로 저장되었습니다.'); + closeIssueEditModal(); + } } else { const error = await response.json(); alert(`저장 실패: ${error.detail || '알 수 없는 오류'}`); @@ -1958,8 +2119,8 @@ } function confirmCompletion(issueId) { - // 모든 정보 확인 모달 열기 - openCompletionConfirmModal(issueId); + // 완료 확인 모달 열기 (수정 가능) - 통합 모달 사용 + openIssueEditModal(issueId, true); } // 완료 신청 초기화 (수정 모드로 전환) @@ -1975,7 +2136,7 @@ if (response.ok) { alert('완료 대기 상태가 해제되었습니다. 수정이 가능합니다.'); - loadManagementData(); // 페이지 새로고침 + initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`상태 변경 실패: ${error.detail || '알 수 없는 오류'}`); @@ -2002,7 +2163,7 @@ if (response.ok) { alert('완료 신청이 반려되었습니다.'); - loadManagementData(); // 페이지 새로고침 + initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`반려 처리 실패: ${error.detail || '알 수 없는 오류'}`); @@ -2121,7 +2282,110 @@ } } - // 최종 완료 확인 + // 저장 후 완료 처리 (최종확인) + async function saveAndCompleteIssue(issueId) { + if (!confirm('수정 내용을 저장하고 이 부적합을 최종 완료 처리하시겠습니까?\n완료 처리 후에는 수정할 수 없습니다.')) { + return; + } + + const title = document.getElementById(`edit-issue-title-${issueId}`).value.trim(); + const detail = document.getElementById(`edit-issue-detail-${issueId}`).value.trim(); + const category = document.getElementById(`edit-category-${issueId}`).value; + const solution = document.getElementById(`edit-solution-${issueId}`).value.trim(); + const department = document.getElementById(`edit-department-${issueId}`).value; + const person = document.getElementById(`edit-person-${issueId}`).value.trim(); + const date = document.getElementById(`edit-date-${issueId}`).value; + const causeDepartment = document.getElementById(`edit-cause-department-${issueId}`).value; + const managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim(); + + // 완료 신청 정보 (완료 대기 상태일 때만) + const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`); + const completionPhotoElement = document.getElementById(`edit-completion-photo-${issueId}`); + + let completionComment = null; + let completionPhoto = null; + + if (completionCommentElement) { + completionComment = completionCommentElement.value.trim(); + } + + if (completionPhotoElement && completionPhotoElement.files[0]) { + try { + const file = completionPhotoElement.files[0]; + console.log('🔍 업로드할 파일 정보:', { + name: file.name, + size: file.size, + type: file.type, + lastModified: file.lastModified + }); + + const base64 = await fileToBase64(file); + console.log('🔍 Base64 변환 완료 - 전체 길이:', base64.length); + console.log('🔍 Base64 헤더:', base64.substring(0, 50)); + + completionPhoto = base64.split(',')[1]; // Base64 데이터만 추출 + console.log('🔍 헤더 제거 후 길이:', completionPhoto.length); + console.log('🔍 전송할 Base64 시작 부분:', completionPhoto.substring(0, 50)); + } catch (error) { + console.error('파일 변환 오류:', error); + alert('완료 사진 업로드 중 오류가 발생했습니다.'); + return; + } + } + + if (!title) { + alert('부적합명을 입력해주세요.'); + return; + } + + const combinedDescription = title + (detail ? '\n' + detail : ''); + + const requestBody = { + final_description: combinedDescription, + final_category: category, + solution: solution || null, + responsible_department: department || null, + responsible_person: person || null, + expected_completion_date: date || null, + cause_department: causeDepartment || null, + management_comment: managementComment || null, + review_status: 'completed' // 완료 상태로 변경 + }; + + // 완료 신청 정보가 있으면 추가 + if (completionComment !== null) { + requestBody.completion_comment = completionComment || null; + } + if (completionPhoto !== null) { + requestBody.completion_photo = completionPhoto; + } + + try { + // 1. 먼저 수정 내용 저장 + const saveResponse = await fetch(`/api/management/${issueId}`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestBody) + }); + + if (saveResponse.ok) { + alert('부적합이 수정되고 최종 완료 처리되었습니다.'); + closeIssueEditModal(); + initializeManagement(); // 페이지 새로고침 + } else { + const error = await saveResponse.json(); + alert(`저장 및 완료 처리 실패: ${error.detail || '알 수 없는 오류'}`); + } + } catch (error) { + console.error('저장 및 완료 처리 오류:', error); + alert('저장 및 완료 처리 중 오류가 발생했습니다.'); + } + } + + // 최종 완료 확인 (기존 함수 - 필요시 사용) async function finalConfirmCompletion(issueId) { if (!confirm('이 부적합을 최종 완료 처리하시겠습니까?\n완료 처리 후에는 수정할 수 없습니다.')) { return; @@ -2138,8 +2402,8 @@ if (response.ok) { alert('부적합이 최종 완료 처리되었습니다.'); - closeCompletionConfirmModal(); - loadManagementData(); // 페이지 새로고침 + closeIssueEditModal(); + initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`완료 처리 실패: ${error.detail || '알 수 없는 오류'}`);