""" 通過履歴承認API ユーザーが自分のチームの通過履歴を確認し、承認確定する処理を行う """ import logging import uuid from django.http import JsonResponse from django.utils import timezone from django.db import transaction from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status from knox.auth import TokenAuthentication from ..models import NewEvent2, Entry, GpsLog # ログ設定 logger = logging.getLogger(__name__) @api_view(['POST']) @permission_classes([IsAuthenticated]) def approve_checkin_history(request): """ ユーザーがアプリ上で通過履歴を確認し、承認確定する処理 パラメータ: - event_code: イベントコード (必須) - zekken_number: ゼッケン番号 (必須) - checkin_ids: 承認するチェックインIDのリスト (必須) - approval_comment: 承認コメント (任意) """ # リクエストID生成(デバッグ用) request_id = str(uuid.uuid4())[:8] client_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR', 'Unknown')) logger.info(f"[APPROVE_CHECKIN] 🎯 API call started - ID: {request_id}, User: {request.user.email if request.user.is_authenticated else 'Anonymous'}, Client IP: {client_ip}") try: # リクエストデータの取得 data = request.data event_code = data.get('event_code') zekken_number = data.get('zekken_number') checkin_ids = data.get('checkin_ids', []) approval_comment = data.get('approval_comment', '') logger.info(f"[APPROVE_CHECKIN] 📝 Request data - ID: {request_id}, event_code: '{event_code}', zekken_number: '{zekken_number}', checkin_ids: {checkin_ids}") # 必須パラメータの検証 if not all([event_code, zekken_number, checkin_ids]): logger.warning(f"[APPROVE_CHECKIN] ❌ Missing required parameters - ID: {request_id}") return Response({ "status": "ERROR", "message": "イベントコード、ゼッケン番号、チェックインIDが必要です" }, status=status.HTTP_400_BAD_REQUEST) if not isinstance(checkin_ids, list) or len(checkin_ids) == 0: logger.warning(f"[APPROVE_CHECKIN] ❌ Invalid checkin_ids format - ID: {request_id}") return Response({ "status": "ERROR", "message": "チェックインIDは空でない配列で指定してください" }, status=status.HTTP_400_BAD_REQUEST) # イベントの存在確認 event = NewEvent2.objects.filter(event_name=event_code).first() if not event: logger.warning(f"[APPROVE_CHECKIN] ❌ Event not found - ID: {request_id}, event_code: '{event_code}'") return Response({ "status": "ERROR", "message": "指定されたイベントが見つかりません" }, status=status.HTTP_404_NOT_FOUND) logger.info(f"[APPROVE_CHECKIN] ✅ Event found - ID: {request_id}, event: '{event_code}', event_id: {event.id}") # チームの存在確認とオーナー権限の検証 entry = Entry.objects.filter( event=event, team__zekken_number=zekken_number ).first() if not entry: logger.warning(f"[APPROVE_CHECKIN] ❌ Team not found - ID: {request_id}, zekken_number: '{zekken_number}', event_code: '{event_code}'") return Response({ "status": "ERROR", "message": "指定されたゼッケン番号のチームが見つかりません" }, status=status.HTTP_404_NOT_FOUND) logger.info(f"[APPROVE_CHECKIN] ✅ Team found - ID: {request_id}, team_name: '{entry.team.team_name}', zekken: {zekken_number}, entry_id: {entry.id}") # オーナー権限の確認 if entry.owner != request.user: logger.warning(f"[APPROVE_CHECKIN] ❌ Permission denied - ID: {request_id}, user: {request.user.email}, team_owner: {entry.owner.email}") return Response({ "status": "ERROR", "message": "このチームの通過履歴を承認する権限がありません" }, status=status.HTTP_403_FORBIDDEN) # 指定されたチェックインIDの存在確認 existing_checkins = GpsLog.objects.filter( id__in=checkin_ids, zekken_number=zekken_number, event_code=event_code ) existing_ids = list(existing_checkins.values_list('id', flat=True)) invalid_ids = [cid for cid in checkin_ids if cid not in existing_ids] if invalid_ids: logger.warning(f"[APPROVE_CHECKIN] ⚠️ Invalid checkin IDs found - ID: {request_id}, invalid_ids: {invalid_ids}, valid_ids: {existing_ids}") return Response({ "status": "ERROR", "message": "指定されたチェックイン記録が見つかりません", "error_details": { "invalid_checkin_ids": invalid_ids, "valid_checkin_ids": existing_ids } }, status=status.HTTP_404_NOT_FOUND) logger.info(f"[APPROVE_CHECKIN] ✅ All checkin IDs validated - ID: {request_id}, count: {len(existing_ids)}") # 承認処理(現時点ではACK応答のみ) # TODO: 実際のDB更新処理をここに実装 # - validation_statusの更新 # - approval_commentの保存 # - approved_atタイムスタンプの設定 # - approved_byユーザーの記録 approval_time = timezone.now() approved_checkins = [] for checkin in existing_checkins: approved_checkins.append({ "checkin_id": checkin.id, "cp_number": checkin.cp_number, "approved_at": approval_time.strftime("%Y-%m-%d %H:%M:%S") }) logger.info(f"[APPROVE_CHECKIN] ✅ Approval completed - ID: {request_id}, approved_count: {len(approved_checkins)}, comment: '{approval_comment[:50]}...' if len(approval_comment) > 50 else approval_comment") # 成功レスポンス return Response({ "status": "OK", "message": "通過履歴の承認が完了しました", "approved_count": len(approved_checkins), "approved_checkins": approved_checkins, "team_info": { "team_name": entry.team.team_name, "zekken_number": zekken_number, "event_code": event_code } }) except Exception as e: logger.error(f"[APPROVE_CHECKIN] 💥 Unexpected error - ID: {request_id}, error: {str(e)}", exc_info=True) return Response({ "status": "ERROR", "message": "サーバーエラーが発生しました" }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)