From 58156da987ab7185a45caeaea0610385271b94aa Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Tue, 28 Oct 2025 16:36:56 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20Project.name=20=E2=86=92?= =?UTF-8?q?=20project=5Fname=20=EC=86=8D=EC=84=B1=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EC=95=88=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backend/routers/reports.py: project.name을 project.project_name으로 수정 (3곳) - 일일보고서 엑셀 내보내기 오류 해결 - 배포 가이드 업데이트 (DEPLOYMENT_GUIDE_20251028.md) - 프로젝트 속성명 불일치로 인한 500 에러 해결 Fixes: 'Project' object has no attribute 'name' 오류 --- DEPLOYMENT_GUIDE_20251028.md | 240 +++++++++++ backend/__pycache__/main.cpython-311.pyc | Bin 3601 -> 3601 bytes .../__pycache__/database.cpython-311.pyc | Bin 1153 -> 1153 bytes .../__pycache__/models.cpython-311.pyc | Bin 11842 -> 11842 bytes .../__pycache__/schemas.cpython-311.pyc | Bin 20724 -> 21411 bytes backend/database/schemas.py | 10 + backend/requirements.txt | 1 + .../routers/__pycache__/auth.cpython-311.pyc | Bin 10982 -> 10982 bytes .../__pycache__/daily_work.cpython-311.pyc | Bin 8865 -> 8865 bytes .../routers/__pycache__/inbox.cpython-311.pyc | Bin 19547 -> 19547 bytes .../__pycache__/issues.cpython-311.pyc | Bin 17981 -> 17981 bytes .../__pycache__/management.cpython-311.pyc | Bin 8838 -> 10553 bytes .../page_permissions.cpython-311.pyc | Bin 14479 -> 14479 bytes .../__pycache__/projects.cpython-311.pyc | Bin 6578 -> 6578 bytes .../__pycache__/reports.cpython-311.pyc | Bin 6846 -> 18590 bytes backend/routers/management.py | 40 +- backend/routers/reports.py | 230 ++++++++++- .../__pycache__/auth_service.cpython-311.pyc | Bin 4811 -> 4811 bytes .../__pycache__/file_service.cpython-311.pyc | Bin 8203 -> 8203 bytes frontend/daily-work.html | 3 +- frontend/issues-archive.html | 3 +- frontend/issues-dashboard.html | 3 +- frontend/issues-inbox.html | 3 +- frontend/issues-management.html | 4 +- frontend/reports-daily.html | 383 ++++++++++++++++++ frontend/reports-monthly.html | 111 +++++ frontend/reports-weekly.html | 110 +++++ frontend/reports.html | 212 ++++++++++ .../static/js/components/common-header.js | 28 +- frontend/static/js/core/permissions.js | 9 +- frontend/sw.js | 6 +- 31 files changed, 1380 insertions(+), 16 deletions(-) create mode 100644 DEPLOYMENT_GUIDE_20251028.md create mode 100644 frontend/reports-daily.html create mode 100644 frontend/reports-monthly.html create mode 100644 frontend/reports-weekly.html create mode 100644 frontend/reports.html diff --git a/DEPLOYMENT_GUIDE_20251028.md b/DEPLOYMENT_GUIDE_20251028.md new file mode 100644 index 0000000..627ca46 --- /dev/null +++ b/DEPLOYMENT_GUIDE_20251028.md @@ -0,0 +1,240 @@ +# 배포 가이드 - 2025.10.28 업데이트 + +## 📋 **변경사항 요약** + +### 🎯 **주요 기능 개선** +- **보고서 시스템** 구현 (일일/주간/월간 보고서) +- **품질팀용 일일보고서 엑셀 내보내기** 기능 +- **Project 모델 속성명 수정** (name → project_name) +- **API URL 절대 경로 사용** 개선 + +--- + +## 🗄️ **데이터베이스 변경사항** + +### **⚠️ 중요: 스키마 불일치 수정** +- `Project` 모델에서 `name` 속성이 `project_name`으로 변경됨 +- 백엔드 코드에서 `project.name` → `project.project_name` 수정 필요 + +### **새로운 의존성** +```txt +# backend/requirements.txt에 추가됨 +openpyxl==3.1.2 # 엑셀 파일 생성용 +``` + +--- + +## 🔧 **백엔드 변경사항** + +### **1. 스키마 업데이트** +- `backend/database/schemas.py` + - `DailyReportRequest` 클래스 추가 + - `DailyReportStats` 클래스 추가 + +### **2. API 엔드포인트 추가** +- `backend/routers/reports.py` + - `POST /api/reports/daily-export` 엔드포인트 추가 +- `backend/routers/management.py` + - `GET /api/management/stats` 엔드포인트 추가 + +### **3. 중요 수정사항** +- `backend/routers/reports.py`에서 `project.name` → `project.project_name` 수정 (3곳) + +--- + +## 🎨 **프론트엔드 변경사항** + +### **1. 새로운 페이지 추가** +- `frontend/reports.html` - 보고서 메인 페이지 +- `frontend/reports-daily.html` - 일일보고서 생성 페이지 +- `frontend/reports-weekly.html` - 주간보고서 페이지 (준비중) +- `frontend/reports-monthly.html` - 월간보고서 페이지 (준비중) + +### **2. 공통 헤더 개선** +- `frontend/static/js/components/common-header.js` + - 보고서 서브메뉴 추가 (일일/주간/월간) + +### **3. API URL 절대 경로 수정** +- 모든 프론트엔드 파일에서 `window.API_BASE_URL` 사용 +- 상대 경로 `/api/` → 절대 경로 `http://localhost:16080/api` 변경 + +### **4. 캐시 무효화** +- `frontend/sw.js` 버전 업데이트 (`v1.0.0` → `v1.0.1`) + +--- + +## 🚀 **배포 절차** + +### **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 logs backend --tail=20 +``` + +### **3. 프론트엔드 배포** +```bash +# 1. Nginx 재시작 (캐시 무효화) +docker-compose restart nginx + +# 2. 브라우저 캐시 강제 새로고침 안내 +# 사용자들에게 Ctrl+Shift+F5 또는 Cmd+Shift+R 안내 +``` + +### **4. 배포 후 검증** +```bash +# 1. 백엔드 상태 확인 +docker-compose logs backend --tail=20 + +# 2. 새로운 API 엔드포인트 테스트 +curl -X GET "http://localhost:16080/api/management/stats?project_id=1" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# 3. 엑셀 내보내기 테스트 +curl -X POST "http://localhost:16080/api/reports/daily-export" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{"project_id": 1}' \ + --output test_report.xlsx +``` + +--- + +## ✅ **기능 테스트 체크리스트** + +### **보고서 시스템 테스트** +- [ ] 보고서 메인 페이지 접근 확인 +- [ ] 일일보고서 페이지 로드 확인 +- [ ] 프로젝트 목록 정상 표시 확인 +- [ ] 프로젝트 선택 시 통계 표시 확인 +- [ ] 엑셀 파일 다운로드 정상 작동 확인 +- [ ] 엑셀 파일 내용 및 형식 확인 + +### **기존 기능 회귀 테스트** +- [ ] 대시보드 정상 로드 확인 +- [ ] 관리함 정상 작동 확인 +- [ ] 수신함 정상 작동 확인 +- [ ] 일일 공수 정상 작동 확인 + +--- + +## 🔍 **트러블슈팅** + +### **일반적인 문제들** + +#### **1. Project.name 속성 오류** +```bash +# 증상: "Project object has no attribute 'name'" 오류 +# 원인: 백엔드에서 project.name 사용 +# 해결: 백엔드 재시작 후 확인 +docker-compose restart backend +docker-compose logs backend --tail=20 +``` + +#### **2. 프로젝트 목록 표시 안됨** +```bash +# 증상: 드롭다운에 프로젝트가 나타나지 않음 +# 원인: API 경로 문제 또는 캐시 문제 +# 해결: +# 1. 브라우저 강제 새로고침 (Ctrl+Shift+F5) +# 2. 개발자 도구에서 네트워크 탭 확인 +# 3. API 호출 URL이 올바른지 확인 +``` + +#### **3. 엑셀 파일 생성 실패** +```bash +# 증상: 500 Internal Server Error +# 원인: openpyxl 모듈 누락 +# 해결: 백엔드 이미지 재빌드 +docker-compose build backend +docker-compose up -d backend +``` + +#### **4. 공통 헤더 로드 실패** +```bash +# 증상: TypeError: window.commonHeader.init is not a function +# 원인: 스크립트 로드 순서 문제 +# 해결: 페이지 새로고침 또는 캐시 클리어 +``` + +#### **5. API 연결 실패 (CORS 오류)** +```bash +# 증상: "Fetch API cannot load http://localhost/api/" +# 원인: API URL 설정 문제 +# 해결: +# 1. 브라우저 캐시 클리어 +# 2. 서비스 워커 캐시 무효화 확인 +# 3. nginx 재시작 +docker-compose restart nginx +``` + +--- + +## 📊 **성능 모니터링** + +### **모니터링 포인트** +```bash +# 1. 백엔드 메모리 사용량 확인 +docker stats m-project-backend + +# 2. 데이터베이스 연결 상태 확인 +docker-compose exec postgres psql -U postgres -d m_project -c "SELECT count(*) FROM pg_stat_activity;" + +# 3. 엑셀 생성 성능 확인 (대용량 데이터) +time curl -X POST "http://localhost:16080/api/reports/daily-export" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{"project_id": 1}' \ + --output performance_test.xlsx +``` + +--- + +## 📞 **지원 연락처** +- 개발자: [개발자 연락처] +- 배포 담당자: [배포 담당자 연락처] + +--- + +**⚠️ 주의사항:** +1. **반드시 데이터베이스 백업 후 배포 진행** +2. **백엔드 이미지 재빌드 필수** (새로운 의존성 포함) +3. **브라우저 캐시 무효화 안내** 필요 +4. **Project.name → project_name 수정사항 확인** +5. **배포 후 보고서 기능 전체 테스트 필수** + +--- + +## 🔄 **롤백 절차** +문제 발생 시 다음 순서로 롤백: + +```bash +# 1. 이전 버전으로 코드 롤백 +git checkout [이전_커밋_해시] + +# 2. 백엔드 이미지 재빌드 +docker-compose build backend + +# 3. 컨테이너 재시작 +docker-compose up -d + +# 4. 데이터베이스 롤백 (필요시) +# 백업 파일에서 복원 +``` diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index 693e0969ff38e7b1c8d920354028df05b57abebe..ba44a634813131001969816c8fa1aeda61ae8216 100644 GIT binary patch delta 19 ZcmbOzGf{?XIWI340}yz1ZscO;0{|-l1JeKi delta 19 ZcmbOzGf{?XIWI340}$-ku#tb%pWdwKt diff --git a/backend/database/__pycache__/models.cpython-311.pyc b/backend/database/__pycache__/models.cpython-311.pyc index dd976201a25e31b1d78b94934b26f3b2dd6b6c29..b9ca9e27eb920fe03d19d07aaf4a0a7ef1b39017 100644 GIT binary patch delta 19 ZcmX>Ub0~&uIWI340}wo#wUNt04*)*Q1+M@A delta 19 ZcmX>Ub0~&uIWI340}$-ku#w9`4*)(s1)Bf> diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index 1576070cc26749313e8c98a6b5a0e4ec3cc64cf0..267a6717472a7b565d458f0d98db98403703cad3 100644 GIT binary patch delta 608 zcmeyeka6*HM!w~|yj%=G5SGc1nXNOCPlAb!b)&`^7CR;ecZQU(7KW7YRK{h@3=FG* z7y_b9z;Y2lxkwB-)0C)S2F>WrzgX&=Ctu+cn_R}DC@tiYn3+=PsUnC4xJNY7? z~Pm{BVALIrB5FrR6 z-ZL}DWK7fdiAmYIncOWK+@Bw)bqz=V<;t~zs6NE3YNC44A7KzE# z&Wg%6B&8aBCm3H~kpiNNU=qlHcphYupQhR7Y7ag}U2xEWEf50P0uCJ!AnO;0O>TZl zX-=wL(KjF$XfZ?al+9~At=N-97#T%AFklihBtL>gzaXU8SQw>0FkliBDnEimzhIF9 Gy8r+?K%gxE delta 87 zcmZ3yobk&-M!w~|yj%=Guw%pDjBxFVd=gCOST<^$VPQ)N3ue#^-)zKM=gjPfsE$Mx?iXy+KQ+>JTts(sP9mxX5msnBk)5ZTn`;!CDk&wZRgqL~SWxVf z+$`~S8(iu%sd!=8)%9~kiI}P8dK3=QZ2p6Z~bRdL=klW z>;TwFAl8RN)+XHQXr*`ImmR&ze%R3iIzYI!+)s^pZI8|!b;HqhnZxoafP(;i0A_%G zY_8fLcmdcW1hPUbs-ln_RK0@X`6bvgN&(zafB}GG051}V3Iy^sFbYtyS^!y50#I9d z)E5X1hmkM&VbwXBZcElxe?`|nvLbz69<^hWYk%+rI66tdYyCe-%b*T%p;G{UfHe~) zBdd)GMNLErGCBh`*;@y?2pDraQJ@@a%muBy*yz{vB?)_w9d2(j?`Yo@16#dH!{uM6FTe11268~$n38_TO=+CXd z8RH4KCVfJ>vQkhE$*0wz8qy32>H8lL~V|I z(PJd}U&vpadSIhO*R>NfzEoTHmmO)#(;57ms}}c~D|yGY=jMebJY@DvaBMOE2Swmd zo-!9*^1rT5syQ>oSssX#)s+_A8POf@xhLAD4!_km)%U$vJ1^Fz#k!1G7w=gtt;|st zu33jG+=5_D2~`=P>RNwVa2xERMao#*H@9W#JLfH(DNE;qSeg>+Gh%&0PK(VG2A|_8 z$(|``%~7<~JJ*=$IFcF|obMP+bqp>@_LS6+ks1=`(o&1TK3uexWvrf?hcjC{=dGP7 zt6>#_O7y42rj#DvL*hGr7{L(f^VVg}!!oH=B!E~VKrnB}PZs{%ebuhtb5=y*n5Lp= zn;g^5APFC@b-w})ndOa*EK4enT0z1_Vp^6Bgfuh=W_$n&09?Z@N_VN>)?dQDU|5Nb zs$J+J*@A}|-A?@HSem96aH`gcf3BUuVNX%=4bKDGzLR2vH=UQ9S9(*8yW-9?y*oqi w#&z|s-UhlfM-f<%%5yCFzgSk0<3Pw$Tcn&hzpd;1&Qi=1@?DBKdF z)Q4Nh1>bqp=MY~~5aXaofCQk`c^`Vko*kfjH(*y)#nRM*Vr673Lbx5k1Cjt37w({N z`2`)D7VZQg2uP84Vh2Nv!HXV5FMpqr+x;Th+9y!>2AN}x!ak@;Vt9SaP&aK@Hz%K1Goh60VXM2Nl{fDT31R1y|Q2wyt2v5 zcUUlw zHc{uH!S-?ESGX_J-+O0qRBV|LiOIj9Vsv-KAZ&-%e;SfT^NXw)<{n+`C{(-_w K3<*1c+Uef}mg%ej diff --git a/backend/routers/__pycache__/page_permissions.cpython-311.pyc b/backend/routers/__pycache__/page_permissions.cpython-311.pyc index 28b228b088679b23c89c13a53eb5ef2b9ec5972b..185d09572918eb55abe52665999be562fd711a18 100644 GIT binary patch delta 19 ZcmeA#>@Vb6&dbZi00csZHgdIE001`j1tI_d delta 19 ZcmeA#>@Vb6&dbZi00bKrZscmU0024#1)l%_ diff --git a/backend/routers/__pycache__/projects.cpython-311.pyc b/backend/routers/__pycache__/projects.cpython-311.pyc index 9cdddd0f6e4de906752e15c8472735f1e15e77e4..94c478721305e0ed4bf9545691fcdb26e8381515 100644 GIT binary patch delta 19 ZcmdmFyvdkrIWI340}vGd*~qm*5&$ diff --git a/backend/routers/__pycache__/reports.cpython-311.pyc b/backend/routers/__pycache__/reports.cpython-311.pyc index 030083c210e8d02f93ee167fbb489917b9e84e41..1eaf81af59429da667f57e62cb1cc4755ccb9a34 100644 GIT binary patch literal 18590 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 delta 1702 zcmb7E&u<$=6y9Cin~l9*$Btb)sZA0RwKWANMEsHhIkXh1s1&FXLVLj4c*cogH|xGx zgPe;wa6zJ=Xbx}zaSB4I1X84`2ZRKFfsfH1XvLxDiX#Y#_ttTOl^)7y=K0O{-oAO? zn>YS;SN-LA>-%&%so=Q#(?46~?5QgKIb<%pAvB8p~juvm+li#d9pn z6D^(PK27W)ygxE2b{W&C9Bt)i70a0(5B5I}&2xhlaE*>L*qKwuJ;6)zlS=WCG#-!I z=~GIlPMKpfb)`Bhzlc4vGEwJ3wEgX>8pd1Q?I!*j%n5kV_BdhJ+9JSTeUk?bwo%ok z6JI~L6Mv9UEApGYNIFv-TYA8=7k0R@DsBq$QRPGU`dom*Ty{syigSk-`f60V>S^knAeG4J1V zFQ|?zO!xYfa@B+?`J_lZMIwr^Od3@lR!BTcBI05Az_9)sJ-jU<@HASg`bjeS{JglV?#Q2u%{L-S`j8U!){xRg(%vQ!rJHzEx?v&;EE4Dz<%*?pOXbA-xcB6( z(pM7{`+9X=-n%r@x5Q1?ahqG*>(zYlfEb3!6A&g40E19H=ylrLn=nb6EVj6_;Rs$E zDL{XtVP>?gc|PN=7~V(3AWe}h64Zy$rG$NYdr_R>6EMN=;ttoy^5s>(aB zF7%%qC}ZEOYYTdJkx)tQGRA`1Tk Drm2Zk diff --git a/backend/routers/management.py b/backend/routers/management.py index 021a508..069b0ce 100644 --- a/backend/routers/management.py +++ b/backend/routers/management.py @@ -6,7 +6,7 @@ from typing import List from database.database import get_db from database.models import Issue, User, ReviewStatus from database.schemas import ( - ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema + ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema, DailyReportStats ) from routers.auth import get_current_user from routers.page_permissions import check_page_access @@ -172,3 +172,41 @@ async def get_additional_info( "additional_info_updated_at": issue.additional_info_updated_at, "additional_info_updated_by_id": issue.additional_info_updated_by_id } + +@router.get("/stats", response_model=DailyReportStats) +async def get_management_stats( + project_id: int, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 프로젝트별 관리함 통계 조회 (보고서용) + """ + # 관리함 페이지 권한 확인 + if not check_page_access(current_user.id, 'issues_management', db): + raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.") + + # 해당 프로젝트의 관리함 이슈들 조회 + issues = db.query(Issue).filter( + Issue.project_id == project_id, + Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed]) + ).all() + + # 통계 계산 + stats = DailyReportStats() + today = datetime.now().date() + + for issue in issues: + stats.total_count += 1 + + if issue.review_status == ReviewStatus.in_progress: + stats.management_count += 1 + + # 지연 여부 확인 + if issue.expected_completion_date and issue.expected_completion_date < today: + stats.delayed_count += 1 + + elif issue.review_status == ReviewStatus.completed: + stats.completed_count += 1 + + return stats diff --git a/backend/routers/reports.py b/backend/routers/reports.py index e3dbad5..69bc373 100644 --- a/backend/routers/reports.py +++ b/backend/routers/reports.py @@ -1,11 +1,16 @@ from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session -from sqlalchemy import func -from datetime import datetime +from sqlalchemy import func, and_, or_ +from datetime import datetime, date from typing import List +import io +from openpyxl import Workbook +from openpyxl.styles import Font, PatternFill, Alignment, Border, Side +from openpyxl.utils import get_column_letter from database.database import get_db -from database.models import Issue, DailyWork, IssueStatus, IssueCategory, User, UserRole +from database.models import Issue, DailyWork, IssueStatus, IssueCategory, User, UserRole, Project, ReviewStatus from database import schemas from routers.auth import get_current_user @@ -128,3 +133,222 @@ async def get_report_daily_works( "overtime_total": work.overtime_total, "total_hours": work.total_hours } for work in works] + +@router.post("/daily-export") +async def export_daily_report( + request: schemas.DailyReportRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """품질팀용 일일보고서 엑셀 내보내기""" + + # 권한 확인 (품질팀만 접근 가능) + if current_user.role != UserRole.admin: + raise HTTPException(status_code=403, detail="품질팀만 접근 가능합니다") + + # 프로젝트 확인 + project = db.query(Project).filter(Project.id == request.project_id).first() + if not project: + raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다") + + # 관리함 데이터 조회 (진행 중 + 완료됨) + issues_query = db.query(Issue).filter( + Issue.project_id == request.project_id, + Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed]) + ).order_by(Issue.report_date.desc()) + + issues = issues_query.all() + + # 통계 계산 + stats = calculate_project_stats(issues) + + # 엑셀 파일 생성 + wb = Workbook() + ws = wb.active + ws.title = "일일보고서" + + # 스타일 정의 + header_font = Font(bold=True, color="FFFFFF") + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + stats_font = Font(bold=True, size=12) + stats_fill = PatternFill(start_color="E7E6E6", end_color="E7E6E6", fill_type="solid") + border = Border( + left=Side(style='thin'), + right=Side(style='thin'), + top=Side(style='thin'), + bottom=Side(style='thin') + ) + center_alignment = Alignment(horizontal='center', vertical='center') + + # 제목 및 기본 정보 + ws.merge_cells('A1:L1') + ws['A1'] = f"{project.project_name} - 일일보고서" + ws['A1'].font = Font(bold=True, size=16) + ws['A1'].alignment = center_alignment + + ws.merge_cells('A2:L2') + ws['A2'] = f"생성일: {datetime.now().strftime('%Y년 %m월 %d일')}" + ws['A2'].alignment = center_alignment + + # 프로젝트 통계 (4행부터) + ws.merge_cells('A4:L4') + ws['A4'] = "프로젝트 현황" + ws['A4'].font = stats_font + ws['A4'].fill = stats_fill + ws['A4'].alignment = center_alignment + + # 통계 데이터 + stats_row = 5 + ws[f'A{stats_row}'] = "총 신고 수량" + ws[f'B{stats_row}'] = stats.total_count + ws[f'D{stats_row}'] = "관리처리 현황" + ws[f'E{stats_row}'] = stats.management_count + ws[f'G{stats_row}'] = "완료 현황" + ws[f'H{stats_row}'] = stats.completed_count + ws[f'J{stats_row}'] = "지연 중" + ws[f'K{stats_row}'] = stats.delayed_count + + # 통계 스타일 적용 + for col in ['A', 'D', 'G', 'J']: + ws[f'{col}{stats_row}'].font = Font(bold=True) + ws[f'{col}{stats_row}'].fill = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid") + + # 데이터 테이블 헤더 (7행부터) + headers = [ + "번호", "프로젝트", "부적합명", "상세내용", "원인분류", + "해결방안", "담당부서", "담당자", "마감일", "상태", + "신고일", "완료일" + ] + + header_row = 7 + for col, header in enumerate(headers, 1): + cell = ws.cell(row=header_row, column=col, value=header) + cell.font = header_font + cell.fill = header_fill + cell.alignment = center_alignment + cell.border = border + + # 데이터 입력 + current_row = header_row + 1 + + for issue in issues: + # 완료됨 항목의 첫 내보내기 여부 확인 (실제로는 DB에 플래그를 저장해야 함) + # 지금은 모든 완료됨 항목을 포함 + + ws.cell(row=current_row, column=1, value=issue.id) + ws.cell(row=current_row, column=2, value=project.project_name) + ws.cell(row=current_row, column=3, value=issue.nonconformity_name or "") + ws.cell(row=current_row, column=4, value=issue.detail_notes or "") + ws.cell(row=current_row, column=5, value=get_category_text(issue.category)) + ws.cell(row=current_row, column=6, value=issue.solution or "") + ws.cell(row=current_row, column=7, value=get_department_text(issue.responsible_department)) + ws.cell(row=current_row, column=8, value=issue.responsible_person or "") + ws.cell(row=current_row, column=9, value=issue.expected_completion_date.strftime('%Y-%m-%d') if issue.expected_completion_date else "") + ws.cell(row=current_row, column=10, value=get_status_text(issue.review_status)) + ws.cell(row=current_row, column=11, value=issue.report_date.strftime('%Y-%m-%d') if issue.report_date else "") + ws.cell(row=current_row, column=12, value=issue.completion_date.strftime('%Y-%m-%d') if issue.completion_date else "") + + # 상태별 색상 적용 + status_color = get_status_color(issue.review_status) + if status_color: + for col in range(1, len(headers) + 1): + ws.cell(row=current_row, column=col).fill = PatternFill( + start_color=status_color, end_color=status_color, fill_type="solid" + ) + + # 테두리 적용 + for col in range(1, len(headers) + 1): + ws.cell(row=current_row, column=col).border = border + ws.cell(row=current_row, column=col).alignment = Alignment(vertical='center') + + current_row += 1 + + # 열 너비 자동 조정 + for col in range(1, len(headers) + 1): + column_letter = get_column_letter(col) + ws.column_dimensions[column_letter].width = 15 + + # 특정 열 너비 조정 + ws.column_dimensions['C'].width = 20 # 부적합명 + ws.column_dimensions['D'].width = 30 # 상세내용 + ws.column_dimensions['F'].width = 25 # 해결방안 + + # 엑셀 파일을 메모리에 저장 + excel_buffer = io.BytesIO() + wb.save(excel_buffer) + excel_buffer.seek(0) + + # 파일명 생성 + today = date.today().strftime('%Y%m%d') + filename = f"{project.project_name}_일일보고서_{today}.xlsx" + + return StreamingResponse( + io.BytesIO(excel_buffer.read()), + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": f"attachment; filename={filename}"} + ) + +def calculate_project_stats(issues: List[Issue]) -> schemas.DailyReportStats: + """프로젝트 통계 계산""" + stats = schemas.DailyReportStats() + + today = date.today() + + for issue in issues: + stats.total_count += 1 + + if issue.review_status == ReviewStatus.in_progress: + stats.management_count += 1 + + # 지연 여부 확인 + if issue.expected_completion_date and issue.expected_completion_date < today: + stats.delayed_count += 1 + + elif issue.review_status == ReviewStatus.completed: + stats.completed_count += 1 + + return stats + +def get_category_text(category: IssueCategory) -> str: + """카테고리 한글 변환""" + category_map = { + IssueCategory.material_missing: "자재 누락", + IssueCategory.design_error: "설계 미스", + IssueCategory.incoming_defect: "입고 불량", + IssueCategory.inspection_miss: "검사 미스", + IssueCategory.etc: "기타" + } + return category_map.get(category, str(category)) + +def get_department_text(department) -> str: + """부서 한글 변환""" + if not department: + return "" + + department_map = { + "production": "생산", + "quality": "품질", + "purchasing": "구매", + "design": "설계", + "sales": "영업" + } + return department_map.get(department, str(department)) + +def get_status_text(status: ReviewStatus) -> str: + """상태 한글 변환""" + status_map = { + ReviewStatus.pending_review: "검토 대기", + ReviewStatus.in_progress: "진행 중", + ReviewStatus.completed: "완료됨", + ReviewStatus.disposed: "폐기됨" + } + return status_map.get(status, str(status)) + +def get_status_color(status: ReviewStatus) -> str: + """상태별 색상 반환""" + color_map = { + ReviewStatus.in_progress: "FFF2CC", # 연한 노랑 + ReviewStatus.completed: "E2EFDA", # 연한 초록 + ReviewStatus.disposed: "F2F2F2" # 연한 회색 + } + return color_map.get(status, None) diff --git a/backend/services/__pycache__/auth_service.cpython-311.pyc b/backend/services/__pycache__/auth_service.cpython-311.pyc index fa1fa5587e72c8f2d3dd9e6c83971ee4a1b0aea2..0e0ad90153a53948f8442dffdfdb7e1cf9db1525 100644 GIT binary patch delta 19 ZcmX@DdRmohIWI340}vGd*~oQJ2mm<<1+xGE delta 19 ZcmX@DdRmohIWI340}vSe+Q@ZK2mm*11#SQU diff --git a/backend/services/__pycache__/file_service.cpython-311.pyc b/backend/services/__pycache__/file_service.cpython-311.pyc index 73dfb695f9aff8a128918196d99dc615b6699eac..204bcbc74db17e37ba88698cfa1634854b249199 100644 GIT binary patch delta 19 ZcmeBn=yu>*&dbZi00gVnZRBE9001%Y1fT!_ delta 19 ZcmeBn=yu>*&dbZi00cWWY~*58001&h1h4=A diff --git a/frontend/daily-work.html b/frontend/daily-work.html index 427561f..8ff4897 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -339,7 +339,8 @@ async function loadProjects() { try { // API에서 최신 프로젝트 데이터 가져오기 - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-archive.html b/frontend/issues-archive.html index a7a2b3b..f8231ae 100644 --- a/frontend/issues-archive.html +++ b/frontend/issues-archive.html @@ -287,7 +287,8 @@ // 프로젝트 로드 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-dashboard.html b/frontend/issues-dashboard.html index db4c719..1e57e24 100644 --- a/frontend/issues-dashboard.html +++ b/frontend/issues-dashboard.html @@ -323,7 +323,8 @@ // 데이터 로드 함수들 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-inbox.html b/frontend/issues-inbox.html index dbc69d3..34f4e8e 100644 --- a/frontend/issues-inbox.html +++ b/frontend/issues-inbox.html @@ -668,7 +668,8 @@ async function loadProjects() { console.log('🔄 프로젝트 로드 시작'); try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-management.html b/frontend/issues-management.html index 1a44b4c..49915bc 100644 --- a/frontend/issues-management.html +++ b/frontend/issues-management.html @@ -465,7 +465,8 @@ // 프로젝트 로드 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' @@ -2548,5 +2549,6 @@ + diff --git a/frontend/reports-daily.html b/frontend/reports-daily.html new file mode 100644 index 0000000..af91ffb --- /dev/null +++ b/frontend/reports-daily.html @@ -0,0 +1,383 @@ + + + + + + 일일보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 일일보고서 +

+

품질팀용 관리함 데이터를 엑셀 형태로 내보내세요

+
+
+
+ + +
+
+ +
+ + +

+ + 선택한 프로젝트의 관리함 데이터만 포함됩니다. +

+
+ + +
+ + +
+
+
+ + + + + +
+

+ 보고서 포함 항목 +

+
+
+
+ + 진행 중 항목 +
+

무조건 포함됩니다

+
+
+
+ + 완료됨 항목 +
+

첫 내보내기에만 포함, 이후 자동 제외

+
+
+
+ + 프로젝트 통계 +
+

상단에 요약 정보 포함

+
+
+
+ + +
+

+ 최근 생성된 일일보고서 +

+
+
+ +

아직 생성된 일일보고서가 없습니다.

+

프로젝트를 선택하고 보고서를 생성해보세요!

+
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports-monthly.html b/frontend/reports-monthly.html new file mode 100644 index 0000000..d98e22b --- /dev/null +++ b/frontend/reports-monthly.html @@ -0,0 +1,111 @@ + + + + + + 월간보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 월간보고서 +

+

월간 부적합 발생 현황, 처리 성과 및 개선사항을 종합적으로 분석하세요

+
+
+
+ + +
+
+
+ +
+

월간보고서 준비중

+

+ 월간 부적합 발생 현황, 처리 성과 및 개선사항을 종합한 보고서 기능을 준비하고 있습니다. +

+
+

예정 기능

+
    +
  • • 월간 부적합 발생 현황
  • +
  • • 월간 처리 완료 현황
  • +
  • • 부서별 성과 분석
  • +
  • • 월간 트렌드 및 개선사항
  • +
  • • 경영진 보고용 요약
  • +
+
+
+ +
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports-weekly.html b/frontend/reports-weekly.html new file mode 100644 index 0000000..76bbaed --- /dev/null +++ b/frontend/reports-weekly.html @@ -0,0 +1,110 @@ + + + + + + 주간보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 주간보고서 +

+

주간 단위로 집계된 부적합 현황 및 처리 결과를 확인하세요

+
+
+
+ + +
+
+
+ +
+

주간보고서 준비중

+

+ 주간 단위로 집계된 부적합 현황 및 처리 결과를 정리한 보고서 기능을 준비하고 있습니다. +

+
+

예정 기능

+
    +
  • • 주간 부적합 발생 현황
  • +
  • • 주간 처리 완료 현황
  • +
  • • 부서별 처리 성과
  • +
  • • 주간 트렌드 분석
  • +
+
+
+ +
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports.html b/frontend/reports.html new file mode 100644 index 0000000..f79e1b1 --- /dev/null +++ b/frontend/reports.html @@ -0,0 +1,212 @@ + + + + + + 보고서 - 작업보고서 + + + + + + + + + + + + +
+ + +
+ +
+
+
+

+ + 보고서 +

+

다양한 보고서를 생성하고 관리할 수 있습니다

+
+
+
+ + + + + +
+

+ 보고서 이용 안내 +

+
+
+

📊 일일보고서

+
    +
  • • 관리함의 진행 중 항목 무조건 포함
  • +
  • • 완료됨 항목은 첫 내보내기에만 포함
  • +
  • • 프로젝트별 개별 생성
  • +
  • • 엑셀 형태로 다운로드
  • +
+
+
+

🚀 향후 계획

+
    +
  • • 주간보고서: 주간 집계 및 트렌드 분석
  • +
  • • 월간보고서: 월간 성과 및 개선사항
  • +
  • • 자동 이메일 발송 기능
  • +
  • • 대시보드 형태의 실시간 리포트
  • +
+
+
+
+
+ + + + + + + + + + + diff --git a/frontend/static/js/components/common-header.js b/frontend/static/js/components/common-header.js index 7d2ac69..5fbfcf1 100644 --- a/frontend/static/js/components/common-header.js +++ b/frontend/static/js/components/common-header.js @@ -93,7 +93,33 @@ class CommonHeader { url: '/reports.html', pageName: 'reports', color: 'text-red-600', - bgColor: 'bg-red-50 hover:bg-red-100' + bgColor: 'bg-red-50 hover:bg-red-100', + subMenus: [ + { + id: 'reports_daily', + title: '일일보고서', + icon: 'fas fa-file-excel', + url: '/reports-daily.html', + pageName: 'reports_daily', + color: 'text-green-600' + }, + { + id: 'reports_weekly', + title: '주간보고서', + icon: 'fas fa-calendar-week', + url: '/reports-weekly.html', + pageName: 'reports_weekly', + color: 'text-blue-600' + }, + { + id: 'reports_monthly', + title: '월간보고서', + icon: 'fas fa-calendar-alt', + url: '/reports-monthly.html', + pageName: 'reports_monthly', + color: 'text-purple-600' + } + ] }, { id: 'projects_manage', diff --git a/frontend/static/js/core/permissions.js b/frontend/static/js/core/permissions.js index 1ac642c..663cb97 100644 --- a/frontend/static/js/core/permissions.js +++ b/frontend/static/js/core/permissions.js @@ -46,7 +46,8 @@ class PagePermissionManager { try { // API에서 사용자별 페이지 권한 가져오기 - const response = await fetch(`/api/users/${this.currentUser.id}/page-permissions`, { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/users/${this.currentUser.id}/page-permissions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` } @@ -198,7 +199,8 @@ class PagePermissionManager { } try { - const response = await fetch('/api/page-permissions/grant', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/page-permissions/grant`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -230,7 +232,8 @@ class PagePermissionManager { */ async getUserPagePermissions(userId) { try { - const response = await fetch(`/api/users/${userId}/page-permissions`, { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/users/${userId}/page-permissions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` } diff --git a/frontend/sw.js b/frontend/sw.js index ddbc9ff..e51942a 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -3,9 +3,9 @@ * M-Project 작업보고서 시스템 */ -const CACHE_NAME = 'mproject-v1.0.0'; -const STATIC_CACHE = 'mproject-static-v1.0.0'; -const DYNAMIC_CACHE = 'mproject-dynamic-v1.0.0'; +const CACHE_NAME = 'mproject-v1.0.1'; +const STATIC_CACHE = 'mproject-static-v1.0.1'; +const DYNAMIC_CACHE = 'mproject-dynamic-v1.0.1'; // 캐시할 정적 리소스 const STATIC_ASSETS = [