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:
@@ -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__":
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user