fix: 일일보고서 CORS 에러 및 datetime 호환성 문제 수정

- 전역 예외 처리기에 CORS 헤더 추가 (500 에러에서도 CORS 헤더 포함)
- datetime.date 객체의 timestamp/tzinfo 호환성 문제 해결
- 일일보고서 접근 권한을 모든 로그인 사용자로 확대

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-06 13:51:28 +09:00
parent c4af58d849
commit ef5c5e63cb
2 changed files with 32 additions and 15 deletions

View File

@@ -60,9 +60,15 @@ async def health_check():
# 전역 예외 처리 # 전역 예외 처리
@app.exception_handler(Exception) @app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception): async def global_exception_handler(request: Request, exc: Exception):
# CORS 헤더 추가 (500 에러에서도 CORS 헤더가 필요)
return JSONResponse( return JSONResponse(
status_code=500, status_code=500,
content={"detail": f"Internal server error: {str(exc)}"} content={"detail": f"Internal server error: {str(exc)}"},
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "*"
}
) )
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -145,10 +145,6 @@ async def preview_daily_report(
): ):
"""일일보고서 미리보기 - 추출될 항목 목록""" """일일보고서 미리보기 - 추출될 항목 목록"""
# 권한 확인
if current_user.role != UserRole.admin:
raise HTTPException(status_code=403, detail="품질팀만 접근 가능합니다")
# 프로젝트 확인 # 프로젝트 확인
project = db.query(Project).filter(Project.id == project_id).first() project = db.query(Project).filter(Project.id == project_id).first()
if not project: if not project:
@@ -169,7 +165,7 @@ async def preview_daily_report(
issues = issues_query.all() issues = issues_query.all()
# 정렬: 지연 -> 진행중 -> 완료됨 순으로, 같은 상태 내에서는 신고일 최신순 # 정렬: 지연 -> 진행중 -> 완료됨 순으로, 같은 상태 내에서는 신고일 최신순
issues = sorted(issues, key=lambda x: (get_issue_priority(x), -x.report_date.timestamp() if x.report_date else 0)) issues = sorted(issues, key=lambda x: (get_issue_priority(x), -get_timestamp(x.report_date)))
# 통계 계산 # 통계 계산
stats = calculate_project_stats(issues) stats = calculate_project_stats(issues)
@@ -190,11 +186,7 @@ async def export_daily_report(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: Session = Depends(get_db) 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() project = db.query(Project).filter(Project.id == request.project_id).first()
@@ -223,8 +215,18 @@ async def export_daily_report(
not_exported_after_completion.append(issue) not_exported_after_completion.append(issue)
elif issue.actual_completion_date: elif issue.actual_completion_date:
# actual_completion_date가 있는 경우: 완료일과 마지막 추출일 비교 # actual_completion_date가 있는 경우: 완료일과 마지막 추출일 비교
completion_date = issue.actual_completion_date.replace(tzinfo=None) if issue.actual_completion_date.tzinfo else issue.actual_completion_date # actual_completion_date는 date 또는 datetime일 수 있음
export_date = issue.last_exported_at.replace(tzinfo=None) if issue.last_exported_at.tzinfo else issue.last_exported_at if isinstance(issue.actual_completion_date, datetime):
completion_date = issue.actual_completion_date.replace(tzinfo=None) if issue.actual_completion_date.tzinfo else issue.actual_completion_date
else:
# date 타입인 경우 datetime으로 변환
completion_date = datetime.combine(issue.actual_completion_date, datetime.min.time())
if isinstance(issue.last_exported_at, datetime):
export_date = issue.last_exported_at.replace(tzinfo=None) if issue.last_exported_at.tzinfo else issue.last_exported_at
else:
export_date = datetime.combine(issue.last_exported_at, datetime.min.time())
if completion_date > export_date: if completion_date > export_date:
# 완료일이 마지막 추출일보다 나중 -> 완료 후 아직 추출 안됨 -> 진행 중 시트에 표시 # 완료일이 마지막 추출일보다 나중 -> 완료 후 아직 추출 안됨 -> 진행 중 시트에 표시
not_exported_after_completion.append(issue) not_exported_after_completion.append(issue)
@@ -236,14 +238,14 @@ async def export_daily_report(
in_progress_issues = in_progress_only + not_exported_after_completion in_progress_issues = in_progress_only + not_exported_after_completion
# 진행 중 시트 정렬: 지연중 -> 진행중 -> 완료됨 순서 # 진행 중 시트 정렬: 지연중 -> 진행중 -> 완료됨 순서
in_progress_issues = sorted(in_progress_issues, key=lambda x: (get_issue_priority(x), -x.report_date.timestamp() if x.report_date else 0)) in_progress_issues = sorted(in_progress_issues, key=lambda x: (get_issue_priority(x), -get_timestamp(x.report_date)))
# "완료됨" 시트용: 완료 항목 중 "완료 후 추출된 것"만 (진행 중 시트에 표시되는 것 제외) # "완료됨" 시트용: 완료 항목 중 "완료 후 추출된 것"만 (진행 중 시트에 표시되는 것 제외)
not_exported_ids = {issue.id for issue in not_exported_after_completion} not_exported_ids = {issue.id for issue in not_exported_after_completion}
completed_issues = [issue for issue in all_completed if issue.id not in not_exported_ids] completed_issues = [issue for issue in all_completed if issue.id not in not_exported_ids]
# 완료됨 시트도 정렬 (완료일 최신순) # 완료됨 시트도 정렬 (완료일 최신순)
completed_issues = sorted(completed_issues, key=lambda x: -x.actual_completion_date.timestamp() if x.actual_completion_date else 0) completed_issues = sorted(completed_issues, key=lambda x: -get_timestamp(x.actual_completion_date))
# 웹과 동일한 로직: 진행중 + 완료를 함께 정렬하여 순번 할당 # 웹과 동일한 로직: 진행중 + 완료를 함께 정렬하여 순번 할당
# (웹에서는 in_progress와 completed를 함께 가져와서 전체를 reviewed_at 순으로 정렬 후 순번 매김) # (웹에서는 in_progress와 completed를 함께 가져와서 전체를 reviewed_at 순으로 정렬 후 순번 매김)
@@ -871,3 +873,12 @@ def get_issue_status_header_color(issue: Issue) -> str:
elif priority == 3: # 완료 elif priority == 3: # 완료
return "92D050" # 진한 초록색 return "92D050" # 진한 초록색
return "4472C4" # 기본 파란색 return "4472C4" # 기본 파란색
def get_timestamp(dt) -> float:
"""date 또는 datetime 객체에서 timestamp 반환"""
if dt is None:
return 0
if isinstance(dt, datetime):
return dt.timestamp()
# date 타입인 경우 datetime으로 변환
return datetime.combine(dt, datetime.min.time()).timestamp()