Merge pull request #15 from calcorum/claude/admin-command-weekly-transactions-011CUsYbhHsFWsL883TCv9tA
CLAUDE: Add admin command to manually process weekly transactions
This commit is contained in:
commit
3583cbb92c
@ -4,6 +4,7 @@ Admin Management Commands
|
||||
Administrative commands for league management and bot maintenance.
|
||||
"""
|
||||
import asyncio
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
@ -15,6 +16,9 @@ from utils.decorators import logged_command
|
||||
from utils.discord_helpers import set_channel_visibility
|
||||
from utils.permissions import league_admin_only
|
||||
from views.embeds import EmbedColors, EmbedTemplate
|
||||
from services.league_service import league_service
|
||||
from services.transaction_service import transaction_service
|
||||
from services.player_service import player_service
|
||||
|
||||
|
||||
class AdminCommands(commands.Cog):
|
||||
@ -124,12 +128,13 @@ class AdminCommands(commands.Cog):
|
||||
inline=False
|
||||
)
|
||||
|
||||
# League Management
|
||||
# League Management
|
||||
embed.add_field(
|
||||
name="League Management",
|
||||
value="**`/admin-season <season>`** - Set current season\n"
|
||||
"**`/admin-announce <message>`** - Send announcement to channel\n"
|
||||
"**`/admin-maintenance <on/off>`** - Toggle maintenance mode",
|
||||
"**`/admin-maintenance <on/off>`** - Toggle maintenance mode\n"
|
||||
"**`/admin-process-transactions [week]`** - Manually process weekly transactions",
|
||||
inline=False
|
||||
)
|
||||
|
||||
@ -607,6 +612,283 @@ class AdminCommands(commands.Cog):
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
@app_commands.command(
|
||||
name="admin-process-transactions",
|
||||
description="Manually process all transactions for the current week (or specified week)"
|
||||
)
|
||||
@app_commands.describe(
|
||||
week="Week number to process (optional, defaults to current week)"
|
||||
)
|
||||
@league_admin_only()
|
||||
@logged_command("/admin-process-transactions")
|
||||
async def admin_process_transactions(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
week: int | None = None
|
||||
):
|
||||
"""
|
||||
Manually process all transactions for the current week.
|
||||
|
||||
This is a fallback mechanism if the Monday morning task fails to run.
|
||||
It will:
|
||||
1. Get all non-frozen, non-cancelled transactions for the specified week
|
||||
2. Execute each transaction by updating player rosters via the API
|
||||
3. Report success/failure counts
|
||||
|
||||
Args:
|
||||
week: Optional week number to process. If not provided, uses current week.
|
||||
"""
|
||||
await interaction.response.defer()
|
||||
|
||||
try:
|
||||
# Get current league state
|
||||
current = await league_service.get_current_state()
|
||||
if not current:
|
||||
await interaction.followup.send(
|
||||
"❌ Could not get current league state from the API.",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
# Use provided week or current week
|
||||
target_week = week if week is not None else current.week
|
||||
target_season = current.season
|
||||
|
||||
self.logger.info(
|
||||
f"Processing transactions for week {target_week}, season {target_season}",
|
||||
requested_by=str(interaction.user)
|
||||
)
|
||||
|
||||
# Get all non-frozen, non-cancelled transactions for the target week using service layer
|
||||
transactions = await transaction_service.get_all_items(params=[
|
||||
('season', str(target_season)),
|
||||
('week_start', str(target_week)),
|
||||
('week_end', str(target_week)),
|
||||
('frozen', 'false'),
|
||||
('cancelled', 'false')
|
||||
])
|
||||
|
||||
if not transactions:
|
||||
embed = EmbedTemplate.info(
|
||||
title="No Transactions to Process",
|
||||
description=f"No non-frozen, non-cancelled transactions found for Week {target_week}"
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Search Criteria",
|
||||
value=f"**Season:** {target_season}\n"
|
||||
f"**Week:** {target_week}\n"
|
||||
f"**Frozen:** No\n"
|
||||
f"**Cancelled:** No",
|
||||
inline=False
|
||||
)
|
||||
|
||||
await interaction.followup.send(embed=embed)
|
||||
return
|
||||
|
||||
# Count total transactions
|
||||
total_count = len(transactions)
|
||||
|
||||
self.logger.info(f"Found {total_count} transactions to process for week {target_week}")
|
||||
|
||||
# Process each transaction
|
||||
success_count = 0
|
||||
failure_count = 0
|
||||
errors: List[Dict[str, Any]] = []
|
||||
|
||||
# Create initial status embed
|
||||
processing_embed = EmbedTemplate.loading(
|
||||
title="Processing Transactions",
|
||||
description=f"Processing {total_count} transactions for Week {target_week}..."
|
||||
)
|
||||
processing_embed.add_field(
|
||||
name="Progress",
|
||||
value="Starting...",
|
||||
inline=False
|
||||
)
|
||||
|
||||
status_message = await interaction.followup.send(embed=processing_embed)
|
||||
|
||||
for idx, transaction in enumerate(transactions, start=1):
|
||||
try:
|
||||
# Execute player roster update via service layer
|
||||
await self._execute_player_update(
|
||||
player_id=transaction.player.id,
|
||||
new_team_id=transaction.newteam.id,
|
||||
player_name=transaction.player.name
|
||||
)
|
||||
|
||||
success_count += 1
|
||||
|
||||
# Update progress every 5 transactions or on last transaction
|
||||
if idx % 5 == 0 or idx == total_count:
|
||||
processing_embed.set_field_at(
|
||||
0,
|
||||
name="Progress",
|
||||
value=f"Processed {idx}/{total_count} transactions\n"
|
||||
f"✅ Successful: {success_count}\n"
|
||||
f"❌ Failed: {failure_count}",
|
||||
inline=False
|
||||
)
|
||||
await status_message.edit(embed=processing_embed)
|
||||
|
||||
# Rate limiting: 100ms delay between requests
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
failure_count += 1
|
||||
error_info = {
|
||||
'player': transaction.player.name,
|
||||
'player_id': transaction.player.id,
|
||||
'new_team': transaction.newteam.abbrev,
|
||||
'error': str(e)
|
||||
}
|
||||
errors.append(error_info)
|
||||
|
||||
self.logger.error(
|
||||
f"Failed to execute transaction for {error_info['player']}",
|
||||
player_id=error_info['player_id'],
|
||||
new_team=error_info['new_team'],
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
# Create completion embed
|
||||
if failure_count == 0:
|
||||
completion_embed = EmbedTemplate.success(
|
||||
title="Transactions Processed Successfully",
|
||||
description=f"All {total_count} transactions for Week {target_week} have been processed."
|
||||
)
|
||||
elif success_count == 0:
|
||||
completion_embed = EmbedTemplate.error(
|
||||
title="Transaction Processing Failed",
|
||||
description=f"Failed to process all {total_count} transactions for Week {target_week}."
|
||||
)
|
||||
else:
|
||||
completion_embed = EmbedTemplate.warning(
|
||||
title="Transactions Partially Processed",
|
||||
description=f"Some transactions for Week {target_week} failed to process."
|
||||
)
|
||||
|
||||
completion_embed.add_field(
|
||||
name="Processing Summary",
|
||||
value=f"**Total Transactions:** {total_count}\n"
|
||||
f"**✅ Successful:** {success_count}\n"
|
||||
f"**❌ Failed:** {failure_count}\n"
|
||||
f"**Week:** {target_week}\n"
|
||||
f"**Season:** {target_season}",
|
||||
inline=False
|
||||
)
|
||||
|
||||
# Add error details if there were failures
|
||||
if errors:
|
||||
error_text = ""
|
||||
for error in errors[:5]: # Show first 5 errors
|
||||
error_text += f"• **{error['player']}** → {error['new_team']}: {error['error'][:50]}\n"
|
||||
|
||||
if len(errors) > 5:
|
||||
error_text += f"\n... and {len(errors) - 5} more errors"
|
||||
|
||||
completion_embed.add_field(
|
||||
name="Errors",
|
||||
value=error_text,
|
||||
inline=False
|
||||
)
|
||||
|
||||
completion_embed.add_field(
|
||||
name="Next Steps",
|
||||
value="• Verify transactions in the database\n"
|
||||
"• Check #transaction-log channel for posted moves\n"
|
||||
"• Review any errors and retry if necessary",
|
||||
inline=False
|
||||
)
|
||||
|
||||
completion_embed.set_footer(
|
||||
text=f"Processed by {interaction.user.display_name} • {discord.utils.utcnow().strftime('%H:%M:%S UTC')}"
|
||||
)
|
||||
|
||||
# Update the status message with final results
|
||||
await status_message.edit(embed=completion_embed)
|
||||
|
||||
self.logger.info(
|
||||
f"Transaction processing complete for week {target_week}",
|
||||
success=success_count,
|
||||
failures=failure_count,
|
||||
total=total_count
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing transactions: {e}", exc_info=True)
|
||||
|
||||
embed = EmbedTemplate.error(
|
||||
title="Transaction Processing Failed",
|
||||
description=f"An error occurred while processing transactions: {str(e)}"
|
||||
)
|
||||
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
|
||||
async def _execute_player_update(
|
||||
self,
|
||||
player_id: int,
|
||||
new_team_id: int,
|
||||
player_name: str
|
||||
) -> bool:
|
||||
"""
|
||||
Execute a player roster update via service layer.
|
||||
|
||||
Args:
|
||||
player_id: Player database ID
|
||||
new_team_id: New team ID to assign
|
||||
player_name: Player name for logging
|
||||
|
||||
Returns:
|
||||
True if update successful, False otherwise
|
||||
|
||||
Raises:
|
||||
Exception: If API call fails
|
||||
"""
|
||||
try:
|
||||
self.logger.info(
|
||||
f"Updating player roster",
|
||||
player_id=player_id,
|
||||
player_name=player_name,
|
||||
new_team_id=new_team_id
|
||||
)
|
||||
|
||||
# Execute player team update via service layer
|
||||
updated_player = await player_service.update_player_team(
|
||||
player_id=player_id,
|
||||
new_team_id=new_team_id
|
||||
)
|
||||
|
||||
# Verify update was successful
|
||||
if updated_player:
|
||||
self.logger.info(
|
||||
f"Successfully updated player roster",
|
||||
player_id=player_id,
|
||||
player_name=player_name,
|
||||
new_team_id=new_team_id
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(
|
||||
f"Player update returned no response",
|
||||
player_id=player_id,
|
||||
player_name=player_name,
|
||||
new_team_id=new_team_id
|
||||
)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Failed to update player roster",
|
||||
player_id=player_id,
|
||||
player_name=player_name,
|
||||
new_team_id=new_team_id,
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
"""Load the admin commands cog."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user