Fix entry registration

This commit is contained in:
2025-09-06 00:11:57 +09:00
parent 86ea3a4b0c
commit 99e4561694
6 changed files with 382 additions and 4 deletions

View File

@ -0,0 +1,114 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from rog.models import GpsLog
class Command(BaseCommand):
help = '外部システム登録の状況を確認します'
def add_arguments(self, parser):
parser.add_argument(
'--event-code',
type=str,
help='特定のイベントコードでフィルタ'
)
parser.add_argument(
'--status',
type=str,
choices=['pending', 'success', 'failed', 'retry'],
help='特定のステータスでフィルタ'
)
parser.add_argument(
'--show-details',
action='store_true',
help='詳細情報を表示'
)
def handle(self, *args, **options):
event_code = options.get('event_code')
status = options.get('status')
show_details = options['show_details']
self.stdout.write(
self.style.SUCCESS('外部システム登録状況を確認しています...')
)
# フィルタ条件を構築
queryset = GpsLog.objects.filter(
serial_number=-1,
cp_number="EXTERNAL_REG"
)
if event_code:
queryset = queryset.filter(event_code=event_code)
if status:
queryset = queryset.filter(external_registration_status=status)
# 統計情報
total_count = queryset.count()
pending_count = queryset.filter(external_registration_status='pending').count()
success_count = queryset.filter(external_registration_status='success').count()
failed_count = queryset.filter(external_registration_status='failed').count()
retry_count = queryset.filter(external_registration_status='retry').count()
self.stdout.write(f'\n=== 外部システム登録統計 ===')
self.stdout.write(f'合計記録数: {total_count}')
self.stdout.write(f'保留中: {pending_count}')
self.stdout.write(f'成功: {success_count}')
self.stdout.write(f'失敗: {failed_count}')
self.stdout.write(f'リトライ要求: {retry_count}')
if show_details and queryset.exists():
self.stdout.write(f'\n=== 詳細情報 ===')
for record in queryset.order_by('-create_at')[:20]: # 最新20件
status_color = self.get_status_color(record.external_registration_status)
self.stdout.write(
f'[{record.create_at.strftime("%Y-%m-%d %H:%M")}] '
f'ゼッケン: {record.zekken_number} | '
f'イベント: {record.event_code} | '
f'チーム: {record.team_name} | '
f'ステータス: {status_color(record.external_registration_status)} | '
f'試行回数: {record.external_registration_attempts}'
)
if record.external_registration_error:
self.stdout.write(f' エラー: {record.external_registration_error[:100]}...')
# リトライが必要な記録をハイライト
needs_retry = queryset.filter(
external_registration_status__in=['pending', 'retry'],
external_registration_attempts__lt=3
)
if needs_retry.exists():
self.stdout.write(
self.style.WARNING(
f'\n注意: {needs_retry.count()}件の記録がリトライを待機中です。'
' `python manage.py retry_external_registrations` でリトライできます。'
)
)
# 最大試行回数に達した記録をチェック
max_attempts_reached = queryset.filter(
external_registration_attempts__gte=3,
external_registration_status__in=['pending', 'failed', 'retry']
)
if max_attempts_reached.exists():
self.stdout.write(
self.style.ERROR(
f'\n警告: {max_attempts_reached.count()}件の記録が最大試行回数に達しています。'
' 手動での確認が必要です。'
)
)
def get_status_color(self, status):
"""ステータスに応じた色付き表示を返す"""
if status == 'success':
return self.style.SUCCESS
elif status == 'failed':
return self.style.ERROR
elif status in ['pending', 'retry']:
return self.style.WARNING
else:
return lambda x: x

View File

@ -0,0 +1,84 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from rog.models import GpsLog
class Command(BaseCommand):
help = '失敗した外部システム登録をリトライします'
def add_arguments(self, parser):
parser.add_argument(
'--max-attempts',
type=int,
default=3,
help='最大試行回数 (デフォルト: 3)'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='実際の処理を実行せず、処理予定の記録のみを表示'
)
def handle(self, *args, **options):
max_attempts = options['max_attempts']
dry_run = options['dry_run']
self.stdout.write(
self.style.SUCCESS(f'外部システム登録リトライ処理を開始します (最大試行回数: {max_attempts})')
)
# 保留中の外部システム登録を取得
pending_records = GpsLog.get_pending_external_registrations().filter(
external_registration_attempts__lt=max_attempts
)
if not pending_records.exists():
self.stdout.write(
self.style.WARNING('リトライが必要な記録が見つかりませんでした。')
)
return
self.stdout.write(f'リトライ対象の記録数: {pending_records.count()}')
if dry_run:
self.stdout.write(self.style.WARNING('-- DRY RUN モード --'))
for record in pending_records:
self.stdout.write(
f' ゼッケン番号: {record.zekken_number}, '
f'イベント: {record.event_code}, '
f'チーム名: {record.team_name}, '
f'試行回数: {record.external_registration_attempts}'
)
return
# 実際のリトライ処理
result = GpsLog.retry_failed_external_registrations(max_attempts)
self.stdout.write(
self.style.SUCCESS(
f'リトライ完了: 成功 {result["success_count"]}件, '
f'失敗 {result["failed_count"]}件, '
f'合計処理数 {result["total_processed"]}'
)
)
# 最大試行回数に達した記録があるかチェック
max_attempts_reached = GpsLog.get_pending_external_registrations().filter(
external_registration_attempts__gte=max_attempts
)
if max_attempts_reached.exists():
self.stdout.write(
self.style.ERROR(
f'警告: {max_attempts_reached.count()}件の記録が最大試行回数に達しました。'
' 手動での確認が必要です。'
)
)
# 詳細を表示
for record in max_attempts_reached[:5]: # 最初の5件のみ表示
self.stdout.write(
f' ゼッケン番号: {record.zekken_number}, '
f'イベント: {record.event_code}, '
f'エラー: {record.external_registration_error}'
)

View File

@ -0,0 +1,58 @@
# Generated manually on 2025-09-06 00:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0013_add_competition_status_fields'),
]
operations = [
migrations.AddField(
model_name='gpslog',
name='external_registration_status',
field=models.CharField(
choices=[
('pending', 'Pending'),
('success', 'Success'),
('failed', 'Failed'),
('retry', 'Retry Required')
],
default='pending',
help_text='外部システム登録状況',
max_length=20
),
),
migrations.AddField(
model_name='gpslog',
name='external_registration_attempts',
field=models.IntegerField(default=0, help_text='外部システム登録試行回数'),
),
migrations.AddField(
model_name='gpslog',
name='external_registration_error',
field=models.TextField(blank=True, help_text='外部システム登録エラー詳細', null=True),
),
migrations.AddField(
model_name='gpslog',
name='last_external_registration_attempt',
field=models.DateTimeField(blank=True, help_text='最後の外部システム登録試行時刻', null=True),
),
migrations.AddField(
model_name='gpslog',
name='team_name',
field=models.CharField(blank=True, help_text='チーム名(外部システム登録用)', max_length=255, null=True),
),
migrations.AddField(
model_name='gpslog',
name='category_name',
field=models.CharField(blank=True, help_text='カテゴリ名(外部システム登録用)', max_length=255, null=True),
),
migrations.AddField(
model_name='gpslog',
name='user_password_hash',
field=models.TextField(blank=True, help_text='ユーザーパスワードハッシュ(外部システム登録用)', null=True),
),
]

View File

@ -9,6 +9,7 @@ from django.contrib.gis.db import models
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from django.conf import settings
try: try:
from django.db.models import JSONField from django.db.models import JSONField
except ImportError: except ImportError:
@ -2007,6 +2008,27 @@ class GpsLog(models.Model):
score = models.IntegerField(default=0, null=True, blank=True) score = models.IntegerField(default=0, null=True, blank=True)
scoreboard_url = models.URLField(blank=True, null=True) scoreboard_url = models.URLField(blank=True, null=True)
# 外部システム登録管理用フィールド
external_registration_status = models.CharField(
max_length=20,
choices=[
('pending', 'Pending'),
('success', 'Success'),
('failed', 'Failed'),
('retry', 'Retry Required')
],
default='pending',
help_text="外部システム登録状況"
)
external_registration_attempts = models.IntegerField(default=0, help_text="外部システム登録試行回数")
external_registration_error = models.TextField(null=True, blank=True, help_text="外部システム登録エラー詳細")
last_external_registration_attempt = models.DateTimeField(null=True, blank=True, help_text="最後の外部システム登録試行時刻")
# エントリー関連情報(外部システム登録用)
team_name = models.CharField(max_length=255, null=True, blank=True, help_text="チーム名(外部システム登録用)")
category_name = models.CharField(max_length=255, null=True, blank=True, help_text="カテゴリ名(外部システム登録用)")
user_password_hash = models.TextField(null=True, blank=True, help_text="ユーザーパスワードハッシュ(外部システム登録用)")
class Meta: class Meta:
db_table = 'gps_information' db_table = 'gps_information'
# 複合主キーの設定 # 複合主キーの設定
@ -2084,6 +2106,102 @@ class GpsLog(models.Model):
return self.create_at return self.create_at
return None return None
@classmethod
def record_external_registration_request(cls, entry):
"""
外部システム登録要求を記録する
Entry作成時に外部システム登録が失敗した場合の情報を保存
"""
return cls.objects.create(
serial_number=-1, # 外部システム登録要求を表す特別な値
zekken_number=str(entry.zekken_number),
event_code=entry.event.event_name,
cp_number="EXTERNAL_REG",
team_name=entry.team.team_name,
category_name=entry.category.category_name,
user_password_hash=entry.team.owner.password,
external_registration_status='pending',
external_registration_attempts=0,
create_at=timezone.now(),
update_at=timezone.now(),
buy_flag=False,
colabo_company_memo="Entry registration - external system pending"
)
def update_external_registration_status(self, status, error_message=None):
"""
外部システム登録状況を更新する
"""
self.external_registration_status = status
self.external_registration_attempts += 1
self.last_external_registration_attempt = timezone.now()
if error_message:
self.external_registration_error = error_message
self.update_at = timezone.now()
self.save()
def retry_external_registration(self):
"""
外部システム登録をリトライする
"""
import requests
api_url = f"{settings.FRONTEND_URL}/gifuroge/register_team"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"zekken_number": self.zekken_number,
"event_code": self.event_code,
"team_name": self.team_name,
"class_name": self.category_name,
"password": self.user_password_hash
}
try:
response = requests.post(api_url, headers=headers, data=data, timeout=30)
response.raise_for_status()
self.update_external_registration_status('success')
return True
except requests.RequestException as e:
self.update_external_registration_status('failed', str(e))
return False
@classmethod
def get_pending_external_registrations(cls):
"""
外部システム登録が保留中の記録を取得する
"""
return cls.objects.filter(
serial_number=-1,
cp_number="EXTERNAL_REG",
external_registration_status__in=['pending', 'retry']
).order_by('create_at')
@classmethod
def retry_failed_external_registrations(cls, max_attempts=3):
"""
失敗した外部システム登録を一括でリトライする
"""
pending_records = cls.get_pending_external_registrations().filter(
external_registration_attempts__lt=max_attempts
)
success_count = 0
failed_count = 0
for record in pending_records:
if record.retry_external_registration():
success_count += 1
else:
failed_count += 1
return {
'success_count': success_count,
'failed_count': failed_count,
'total_processed': success_count + failed_count
}
class Waypoint(models.Model): class Waypoint(models.Model):

View File

@ -1694,7 +1694,7 @@ class EntryViewSet(viewsets.ModelViewSet):
logger.info(f"team.owner = {team.owner}, event_name = {event_name}") logger.info(f"team.owner = {team.owner}, event_name = {event_name}")
logger.info(f"team = {team}") logger.info(f"team = {team}")
# 外部システムの更新 # 外部システムの更新を試行(失敗してもエントリー作成は継続)
success = self.register_team( success = self.register_team(
entry.zekken_number, entry.zekken_number,
event_name, event_name,
@ -1703,8 +1703,12 @@ class EntryViewSet(viewsets.ModelViewSet):
team.owner.password team.owner.password
) )
if not success: if not success:
logger.error("Failed to register external system") logger.warning("Failed to register external system, but entry was created successfully")
raise serializers.ValidationError("外部システムの更新に失敗しました。") # 外部システム登録失敗をGpsLogに記録後でリトライ可能
from .models import GpsLog
GpsLog.record_external_registration_request(entry)
logger.info("External system registration request recorded in GpsLog for later retry")
else:
logger.info("External system registered successfully") logger.info("External system registered successfully")
except Exception as e: except Exception as e:
logger.exception(f"Error creating Entry: {str(e)}") logger.exception(f"Error creating Entry: {str(e)}")