CLAUDE: Add /ilmove command for real-time roster moves with organizational affiliate support
Implemented comprehensive /ilmove command system for immediate roster changes (IL moves, activations, etc.) that execute instantly for the current week, complementing the existing /dropadd system which schedules moves for next week. New Features: - /ilmove command: Interactive transaction builder for THIS week (immediate execution) - /clearilmove command: Clear current IL move transaction builder - Dual-mode transaction system: Scheduled (/dropadd) vs Immediate (/ilmove) Key Fixes: - Organizational team matching: Minor League players (WVMiL) now correctly recognized as valid targets for their Major League organization (WV) - Transaction POST format: Fixed to use correct batch API format with count/moves structure - RosterType to team affiliate mapping: Moves to IL now correctly assign players to WVIL instead of WV, and moves from MiL correctly reference WVMiL as source team - Player team updates: Added update_player_team() method for immediate team assignments Technical Changes: - commands/transactions/ilmove.py: New command with organizational validation - commands/transactions/__init__.py: Register ILMoveCommands cog - services/transaction_service.py: create_transaction_batch() with correct batch format - services/player_service.py: update_player_team() for immediate updates - services/transaction_builder.py: RosterType affiliate resolution with async team lookups - views/transaction_embed.py: Dual-mode support with context-aware instructions Code Reuse: - 95% code sharing between /dropadd and /ilmove via shared TransactionBuilder - Same validation, UI, and move tracking - only submission differs - Context-aware command_name parameter for dynamic UI instructions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1575d4f096
commit
36ecd1b3ff
@ -12,6 +12,7 @@ from discord.ext import commands
|
||||
from .management import TransactionCommands
|
||||
from .dropadd import DropAddCommands
|
||||
from .trade import TradeCommands
|
||||
from .ilmove import ILMoveCommands
|
||||
|
||||
logger = logging.getLogger(f'{__name__}.setup_transactions')
|
||||
|
||||
@ -27,6 +28,7 @@ async def setup_transactions(bot: commands.Bot) -> Tuple[int, int, List[str]]:
|
||||
("TransactionCommands", TransactionCommands),
|
||||
("DropAddCommands", DropAddCommands),
|
||||
("TradeCommands", TradeCommands),
|
||||
("ILMoveCommands", ILMoveCommands),
|
||||
]
|
||||
|
||||
successful = 0
|
||||
|
||||
@ -74,8 +74,8 @@ class DropAddCommands(commands.Cog):
|
||||
|
||||
if success:
|
||||
# Move added successfully - show updated transaction builder
|
||||
embed = await create_transaction_embed(builder)
|
||||
view = TransactionEmbedView(builder, interaction.user.id)
|
||||
embed = await create_transaction_embed(builder, command_name="/dropadd")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="scheduled", command_name="/dropadd")
|
||||
|
||||
success_msg = f"✅ **Added {player} → {destination.upper()}**"
|
||||
if builder.move_count > 1:
|
||||
@ -91,8 +91,8 @@ class DropAddCommands(commands.Cog):
|
||||
|
||||
else:
|
||||
# Failed to add move - still show current transaction state
|
||||
embed = await create_transaction_embed(builder)
|
||||
view = TransactionEmbedView(builder, interaction.user.id)
|
||||
embed = await create_transaction_embed(builder, command_name="/dropadd")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="scheduled", command_name="/dropadd")
|
||||
|
||||
await interaction.followup.send(
|
||||
content=f"❌ **{error_message}**\n"
|
||||
@ -104,8 +104,8 @@ class DropAddCommands(commands.Cog):
|
||||
self.logger.warning(f"Failed to add move: {player} → {destination}: {error_message}")
|
||||
else:
|
||||
# No parameters or incomplete parameters - show current transaction state
|
||||
embed = await create_transaction_embed(builder)
|
||||
view = TransactionEmbedView(builder, interaction.user.id)
|
||||
embed = await create_transaction_embed(builder, command_name="/dropadd")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="scheduled", command_name="/dropadd")
|
||||
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
async def _add_quick_move(
|
||||
|
||||
242
commands/transactions/ilmove.py
Normal file
242
commands/transactions/ilmove.py
Normal file
@ -0,0 +1,242 @@
|
||||
"""
|
||||
/ilmove Command - Real-time IL/Roster Moves
|
||||
|
||||
Interactive transaction builder for immediate roster changes (current week).
|
||||
Unlike /dropadd which schedules moves for next week, /ilmove:
|
||||
- Creates transactions for THIS week
|
||||
- Immediately posts transactions to database
|
||||
- Immediately updates player team assignments
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
|
||||
from config import get_config
|
||||
from utils.logging import get_contextual_logger
|
||||
from utils.decorators import logged_command
|
||||
from utils.autocomplete import player_autocomplete
|
||||
from utils.team_utils import validate_user_has_team
|
||||
|
||||
from services.transaction_builder import (
|
||||
TransactionBuilder,
|
||||
RosterType,
|
||||
TransactionMove,
|
||||
get_transaction_builder,
|
||||
clear_transaction_builder
|
||||
)
|
||||
from services.player_service import player_service
|
||||
from services.team_service import team_service
|
||||
from views.transaction_embed import TransactionEmbedView, create_transaction_embed
|
||||
|
||||
|
||||
class ILMoveCommands(commands.Cog):
|
||||
"""Real-time roster move commands (IL, activations, etc)."""
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
self.logger = get_contextual_logger(f'{__name__}.ILMoveCommands')
|
||||
|
||||
|
||||
@app_commands.command(
|
||||
name="ilmove",
|
||||
description="Build a real-time roster move (executed immediately for this week)"
|
||||
)
|
||||
@app_commands.describe(
|
||||
player="Player name; begin typing for autocomplete",
|
||||
destination="Where to move the player: Major League, Minor League, or Injured List"
|
||||
)
|
||||
@app_commands.autocomplete(player=player_autocomplete)
|
||||
@app_commands.choices(destination=[
|
||||
app_commands.Choice(name="Major League", value="ml"),
|
||||
app_commands.Choice(name="Minor League", value="mil"),
|
||||
app_commands.Choice(name="Injured List", value="il")
|
||||
])
|
||||
@logged_command("/ilmove")
|
||||
async def ilmove(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
player: Optional[str] = None,
|
||||
destination: Optional[str] = None
|
||||
):
|
||||
"""Interactive transaction builder for immediate roster moves."""
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
# Get user's major league team
|
||||
team = await validate_user_has_team(interaction)
|
||||
if not team:
|
||||
return
|
||||
|
||||
# Get or create transaction builder
|
||||
builder = get_transaction_builder(interaction.user.id, team)
|
||||
|
||||
# Handle different scenarios based on builder state and parameters
|
||||
if player and destination:
|
||||
# User provided both parameters - try to add the move
|
||||
success, error_message = await self._add_quick_move(builder, player, destination)
|
||||
|
||||
if success:
|
||||
# Move added successfully - show updated transaction builder
|
||||
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
||||
|
||||
success_msg = f"✅ **Added {player} → {destination.upper()}**"
|
||||
if builder.move_count > 1:
|
||||
success_msg += f"\n📊 Transaction now has {builder.move_count} moves"
|
||||
|
||||
await interaction.followup.send(
|
||||
content=success_msg,
|
||||
embed=embed,
|
||||
view=view,
|
||||
ephemeral=True
|
||||
)
|
||||
self.logger.info(f"Move added for {team.abbrev}: {player} → {destination}")
|
||||
|
||||
else:
|
||||
# Failed to add move - still show current transaction state
|
||||
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
||||
|
||||
await interaction.followup.send(
|
||||
content=f"❌ **{error_message}**\n"
|
||||
f"💡 Try using autocomplete for player names",
|
||||
embed=embed,
|
||||
view=view,
|
||||
ephemeral=True
|
||||
)
|
||||
self.logger.warning(f"Failed to add move: {player} → {destination}: {error_message}")
|
||||
else:
|
||||
# No parameters or incomplete parameters - show current transaction state
|
||||
embed = await create_transaction_embed(builder, command_name="/ilmove")
|
||||
view = TransactionEmbedView(builder, interaction.user.id, submission_handler="immediate", command_name="/ilmove")
|
||||
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
||||
|
||||
async def _add_quick_move(
|
||||
self,
|
||||
builder: TransactionBuilder,
|
||||
player_name: str,
|
||||
destination_str: str
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Add a move quickly from command parameters by auto-determining the action.
|
||||
|
||||
Args:
|
||||
builder: TransactionBuilder instance
|
||||
player_name: Name of player to move
|
||||
destination_str: Destination string (ml, mil, il)
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, error_message: str)
|
||||
"""
|
||||
try:
|
||||
# Find player using the new search endpoint
|
||||
players = await player_service.search_players(player_name, limit=10, season=get_config().sba_current_season)
|
||||
if not players:
|
||||
self.logger.error(f"Player not found: {player_name}")
|
||||
return False, f"Player '{player_name}' not found"
|
||||
|
||||
# Use exact match if available, otherwise first result
|
||||
player = None
|
||||
for p in players:
|
||||
if p.name.lower() == player_name.lower():
|
||||
player = p
|
||||
break
|
||||
|
||||
if not player:
|
||||
player = players[0] # Use first match
|
||||
|
||||
# Check if player belongs to another team (not user's team and not Free Agency)
|
||||
if player.team and hasattr(player.team, 'abbrev'):
|
||||
# Player belongs to another team if:
|
||||
# 1. They have a team assigned AND
|
||||
# 2. That team is not Free Agency (abbrev != 'FA') AND
|
||||
# 3. That team is not in the same organization as the user's team
|
||||
if (player.team.abbrev != 'FA' and
|
||||
not builder.team.is_same_organization(player.team)):
|
||||
self.logger.warning(f"Player {player.name} belongs to {player.team.abbrev}, cannot add to {builder.team.abbrev} transaction")
|
||||
return False, f"{player.name} belongs to {player.team.abbrev} and cannot be added to your transaction"
|
||||
|
||||
# Parse destination
|
||||
destination_map = {
|
||||
"ml": RosterType.MAJOR_LEAGUE,
|
||||
"mil": RosterType.MINOR_LEAGUE,
|
||||
"il": RosterType.INJURED_LIST,
|
||||
}
|
||||
|
||||
to_roster = destination_map.get(destination_str.lower())
|
||||
if not to_roster:
|
||||
self.logger.error(f"Invalid destination: {destination_str}")
|
||||
return False, f"Invalid destination: {destination_str}"
|
||||
|
||||
# Determine player's current roster status by checking actual roster data
|
||||
# Note: Minor League players have different team_id than Major League team
|
||||
self.logger.debug(f"Player {player.name} team_id: {player.team_id}, Builder team_id: {builder.team.id}")
|
||||
|
||||
await builder.load_roster_data()
|
||||
if builder._current_roster:
|
||||
# Check which roster section the player is on (regardless of team_id)
|
||||
player_on_active = any(p.id == player.id for p in builder._current_roster.active_players)
|
||||
player_on_minor = any(p.id == player.id for p in builder._current_roster.minor_league_players)
|
||||
player_on_il = any(p.id == player.id for p in builder._current_roster.il_players)
|
||||
|
||||
if player_on_active:
|
||||
from_roster = RosterType.MAJOR_LEAGUE
|
||||
self.logger.debug(f"Player {player.name} found on active roster (Major League)")
|
||||
elif player_on_minor:
|
||||
from_roster = RosterType.MINOR_LEAGUE
|
||||
self.logger.debug(f"Player {player.name} found on minor league roster")
|
||||
elif player_on_il:
|
||||
from_roster = RosterType.INJURED_LIST
|
||||
self.logger.debug(f"Player {player.name} found on injured list")
|
||||
else:
|
||||
# Player not found on user's roster - cannot move with /ilmove
|
||||
from_roster = None
|
||||
self.logger.warning(f"Player {player.name} not found on {builder.team.abbrev} roster")
|
||||
return False, f"{player.name} is not on your roster (use /dropadd for FA signings)"
|
||||
else:
|
||||
# Couldn't load roster data
|
||||
self.logger.error(f"Could not load roster data for {builder.team.abbrev}")
|
||||
return False, "Could not load roster data. Please try again."
|
||||
|
||||
if from_roster is None:
|
||||
return False, f"{player.name} is not on your roster"
|
||||
|
||||
# Create move
|
||||
move = TransactionMove(
|
||||
player=player,
|
||||
from_roster=from_roster,
|
||||
to_roster=to_roster,
|
||||
from_team=builder.team,
|
||||
to_team=builder.team
|
||||
)
|
||||
|
||||
success, error_message = builder.add_move(move)
|
||||
if not success:
|
||||
self.logger.warning(f"Failed to add quick move: {error_message}")
|
||||
return False, error_message
|
||||
return True, ""
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error adding quick move: {e}")
|
||||
return False, f"Error adding move: {str(e)}"
|
||||
|
||||
@app_commands.command(
|
||||
name="clearilmove",
|
||||
description="Clear your current IL move transaction builder"
|
||||
)
|
||||
@logged_command("/clearilmove")
|
||||
async def clear_ilmove(self, interaction: discord.Interaction):
|
||||
"""Clear the user's current IL move transaction builder."""
|
||||
clear_transaction_builder(interaction.user.id)
|
||||
|
||||
await interaction.response.send_message(
|
||||
"✅ Your IL move transaction builder has been cleared.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
"""Setup function for the cog."""
|
||||
await bot.add_cog(ILMoveCommands(bot))
|
||||
@ -298,6 +298,38 @@ class PlayerService(BaseService[Player]):
|
||||
logger.error(f"Failed to update player {player_id}: {e}")
|
||||
return None
|
||||
|
||||
async def update_player_team(self, player_id: int, new_team_id: int) -> Optional[Player]:
|
||||
"""
|
||||
Update a player's team assignment (for real-time IL moves).
|
||||
|
||||
This is used for immediate roster changes where the player needs to show
|
||||
up on their new team right away, rather than waiting for transaction processing.
|
||||
|
||||
Args:
|
||||
player_id: Player ID to update
|
||||
new_team_id: New team ID to assign
|
||||
|
||||
Returns:
|
||||
Updated player instance or None
|
||||
|
||||
Raises:
|
||||
APIException: If player update fails
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Updating player {player_id} team to {new_team_id}")
|
||||
updated_player = await self.update_player(player_id, {'team_id': new_team_id})
|
||||
|
||||
if updated_player:
|
||||
logger.info(f"Successfully updated player {player_id} to team {new_team_id}")
|
||||
return updated_player
|
||||
else:
|
||||
logger.error(f"Failed to update player {player_id} team - no response from API")
|
||||
raise APIException(f"Failed to update player {player_id} team assignment")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating player {player_id} team: {e}")
|
||||
raise APIException(f"Failed to update player team: {e}")
|
||||
|
||||
|
||||
# Global service instance - will be properly initialized in __init__.py
|
||||
player_service = PlayerService()
|
||||
@ -452,16 +452,35 @@ class TransactionBuilder:
|
||||
|
||||
for move in self.moves:
|
||||
# Determine old and new teams based on roster locations
|
||||
# We need to map RosterType to the actual team (ML, MiL, or IL affiliate)
|
||||
if move.from_roster == RosterType.FREE_AGENCY:
|
||||
old_team = fa_team
|
||||
else:
|
||||
old_team = move.from_team or self.team
|
||||
|
||||
base_team = move.from_team or self.team
|
||||
# Get the appropriate affiliate based on roster type
|
||||
if move.from_roster == RosterType.MAJOR_LEAGUE:
|
||||
old_team = base_team # Already ML team
|
||||
elif move.from_roster == RosterType.MINOR_LEAGUE:
|
||||
old_team = await base_team.minor_league_affiliate()
|
||||
elif move.from_roster == RosterType.INJURED_LIST:
|
||||
old_team = await base_team.injured_list_affiliate()
|
||||
else:
|
||||
old_team = base_team
|
||||
|
||||
if move.to_roster == RosterType.FREE_AGENCY:
|
||||
new_team = fa_team
|
||||
else:
|
||||
new_team = move.to_team or self.team
|
||||
|
||||
base_team = move.to_team or self.team
|
||||
# Get the appropriate affiliate based on roster type
|
||||
if move.to_roster == RosterType.MAJOR_LEAGUE:
|
||||
new_team = base_team # Already ML team
|
||||
elif move.to_roster == RosterType.MINOR_LEAGUE:
|
||||
new_team = await base_team.minor_league_affiliate()
|
||||
elif move.to_roster == RosterType.INJURED_LIST:
|
||||
new_team = await base_team.injured_list_affiliate()
|
||||
else:
|
||||
new_team = base_team
|
||||
|
||||
# For cases where we don't have specific teams, fall back to defaults
|
||||
if not old_team:
|
||||
continue
|
||||
|
||||
@ -187,7 +187,80 @@ class TransactionService(BaseService[Transaction]):
|
||||
is_legal=False,
|
||||
errors=[f"Validation error: {str(e)}"]
|
||||
)
|
||||
|
||||
|
||||
async def create_transaction_batch(self, transactions: List[Transaction]) -> List[Transaction]:
|
||||
"""
|
||||
Create multiple transactions via API POST (for immediate execution).
|
||||
|
||||
This is used for real-time transactions (like IL moves) that need to be
|
||||
posted to the database immediately rather than scheduled for later processing.
|
||||
|
||||
The API expects a TransactionList format:
|
||||
{
|
||||
"count": 2,
|
||||
"moves": [
|
||||
{
|
||||
"week": 17,
|
||||
"player_id": 123,
|
||||
"oldteam_id": 10,
|
||||
"newteam_id": 11,
|
||||
"season": 12,
|
||||
"moveid": "Season-012-Week-17-123456",
|
||||
"cancelled": false,
|
||||
"frozen": false
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
Args:
|
||||
transactions: List of Transaction objects to create
|
||||
|
||||
Returns:
|
||||
List of created Transaction objects with API-assigned IDs
|
||||
|
||||
Raises:
|
||||
APIException: If transaction creation fails
|
||||
"""
|
||||
try:
|
||||
# Convert Transaction objects to API format (simple ID references only)
|
||||
moves = []
|
||||
for transaction in transactions:
|
||||
move = {
|
||||
"week": transaction.week,
|
||||
"player_id": transaction.player.id,
|
||||
"oldteam_id": transaction.oldteam.id,
|
||||
"newteam_id": transaction.newteam.id,
|
||||
"season": transaction.season,
|
||||
"moveid": transaction.moveid,
|
||||
"cancelled": transaction.cancelled or False,
|
||||
"frozen": transaction.frozen or False
|
||||
}
|
||||
moves.append(move)
|
||||
|
||||
# Create batch request payload
|
||||
batch_data = {
|
||||
"count": len(moves),
|
||||
"moves": moves
|
||||
}
|
||||
|
||||
# POST batch to API
|
||||
client = await self.get_client()
|
||||
response = await client.post(self.endpoint, data=batch_data)
|
||||
|
||||
# API returns a string like "2 transactions have been added"
|
||||
# We need to return the original Transaction objects (they won't have IDs assigned by API)
|
||||
if response and isinstance(response, str) and "transactions have been added" in response:
|
||||
logger.info(f"Successfully created batch: {response}")
|
||||
return transactions
|
||||
else:
|
||||
logger.error(f"Unexpected API response: {response}")
|
||||
raise APIException(f"Unexpected API response: {response}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating transaction batch: {e}")
|
||||
raise APIException(f"Failed to create transactions: {e}")
|
||||
|
||||
async def cancel_transaction(self, transaction_id: str) -> bool:
|
||||
"""
|
||||
Cancel a pending transaction.
|
||||
|
||||
@ -13,18 +13,22 @@ from views.embeds import EmbedColors, EmbedTemplate
|
||||
|
||||
class TransactionEmbedView(discord.ui.View):
|
||||
"""Interactive view for the transaction builder embed."""
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, user_id: int):
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, user_id: int, submission_handler: str = "scheduled", command_name: str = "/dropadd"):
|
||||
"""
|
||||
Initialize the transaction embed view.
|
||||
|
||||
|
||||
Args:
|
||||
builder: TransactionBuilder instance
|
||||
user_id: Discord user ID (for permission checking)
|
||||
submission_handler: Type of submission ("scheduled" for /dropadd, "immediate" for /ilmove)
|
||||
command_name: Name of the command being used (for UI instructions)
|
||||
"""
|
||||
super().__init__(timeout=900.0) # 15 minute timeout
|
||||
self.builder = builder
|
||||
self.user_id = user_id
|
||||
self.submission_handler = submission_handler
|
||||
self.command_name = command_name
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
"""Check if user has permission to interact with this view."""
|
||||
@ -54,9 +58,9 @@ class TransactionEmbedView(discord.ui.View):
|
||||
return
|
||||
|
||||
# Create select menu for move removal
|
||||
select_view = RemoveMoveView(self.builder, self.user_id)
|
||||
embed = await create_transaction_embed(self.builder)
|
||||
|
||||
select_view = RemoveMoveView(self.builder, self.user_id, self.command_name)
|
||||
embed = await create_transaction_embed(self.builder, self.command_name)
|
||||
|
||||
await interaction.response.edit_message(embed=embed, view=select_view)
|
||||
|
||||
@discord.ui.button(label="Submit Transaction", style=discord.ButtonStyle.primary, emoji="📤")
|
||||
@ -83,20 +87,20 @@ class TransactionEmbedView(discord.ui.View):
|
||||
return
|
||||
|
||||
# Show confirmation modal
|
||||
modal = SubmitConfirmationModal(self.builder)
|
||||
modal = SubmitConfirmationModal(self.builder, self.submission_handler)
|
||||
await interaction.response.send_modal(modal)
|
||||
|
||||
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary, emoji="❌")
|
||||
async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
"""Handle cancel button click."""
|
||||
self.builder.clear_moves()
|
||||
embed = await create_transaction_embed(self.builder)
|
||||
|
||||
embed = await create_transaction_embed(self.builder, self.command_name)
|
||||
|
||||
# Disable all buttons after cancellation
|
||||
for item in self.children:
|
||||
if isinstance(item, discord.ui.Button):
|
||||
item.disabled = True
|
||||
|
||||
|
||||
await interaction.response.edit_message(
|
||||
content="❌ **Transaction cancelled and cleared.**",
|
||||
embed=embed,
|
||||
@ -107,25 +111,28 @@ class TransactionEmbedView(discord.ui.View):
|
||||
|
||||
class RemoveMoveView(discord.ui.View):
|
||||
"""View for selecting which move to remove."""
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, user_id: int):
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, user_id: int, command_name: str = "/dropadd"):
|
||||
super().__init__(timeout=300.0) # 5 minute timeout
|
||||
self.builder = builder
|
||||
self.user_id = user_id
|
||||
|
||||
self.command_name = command_name
|
||||
|
||||
# Create select menu with current moves
|
||||
if not builder.is_empty:
|
||||
self.add_item(RemoveMoveSelect(builder))
|
||||
|
||||
self.add_item(RemoveMoveSelect(builder, command_name))
|
||||
|
||||
# Add back button
|
||||
back_button = discord.ui.Button(label="Back", style=discord.ButtonStyle.secondary, emoji="⬅️")
|
||||
back_button.callback = self.back_callback
|
||||
self.add_item(back_button)
|
||||
|
||||
|
||||
async def back_callback(self, interaction: discord.Interaction):
|
||||
"""Handle back button to return to main view."""
|
||||
main_view = TransactionEmbedView(self.builder, self.user_id)
|
||||
embed = await create_transaction_embed(self.builder)
|
||||
# Determine submission_handler from command_name
|
||||
submission_handler = "immediate" if self.command_name == "/ilmove" else "scheduled"
|
||||
main_view = TransactionEmbedView(self.builder, self.user_id, submission_handler, self.command_name)
|
||||
embed = await create_transaction_embed(self.builder, self.command_name)
|
||||
await interaction.response.edit_message(embed=embed, view=main_view)
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
@ -135,10 +142,11 @@ class RemoveMoveView(discord.ui.View):
|
||||
|
||||
class RemoveMoveSelect(discord.ui.Select):
|
||||
"""Select menu for choosing which move to remove."""
|
||||
|
||||
def __init__(self, builder: TransactionBuilder):
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, command_name: str = "/dropadd"):
|
||||
self.builder = builder
|
||||
|
||||
self.command_name = command_name
|
||||
|
||||
# Create options from current moves
|
||||
options = []
|
||||
for i, move in enumerate(builder.moves[:25]): # Discord limit of 25 options
|
||||
@ -147,30 +155,32 @@ class RemoveMoveSelect(discord.ui.Select):
|
||||
description=move.description[:100], # Discord description limit
|
||||
value=str(move.player.id)
|
||||
))
|
||||
|
||||
|
||||
super().__init__(
|
||||
placeholder="Select a move to remove...",
|
||||
min_values=1,
|
||||
max_values=1,
|
||||
options=options
|
||||
)
|
||||
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
"""Handle move removal selection."""
|
||||
player_id = int(self.values[0])
|
||||
move = self.builder.get_move_for_player(player_id)
|
||||
|
||||
|
||||
if move:
|
||||
self.builder.remove_move(player_id)
|
||||
await interaction.response.send_message(
|
||||
f"✅ Removed: {move.description}",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
|
||||
# Update the embed
|
||||
main_view = TransactionEmbedView(self.builder, interaction.user.id)
|
||||
embed = await create_transaction_embed(self.builder)
|
||||
|
||||
# Determine submission_handler from command_name
|
||||
submission_handler = "immediate" if self.command_name == "/ilmove" else "scheduled"
|
||||
main_view = TransactionEmbedView(self.builder, interaction.user.id, submission_handler, self.command_name)
|
||||
embed = await create_transaction_embed(self.builder, self.command_name)
|
||||
|
||||
# Edit the original message
|
||||
await interaction.edit_original_response(embed=embed, view=main_view)
|
||||
else:
|
||||
@ -183,10 +193,11 @@ class RemoveMoveSelect(discord.ui.Select):
|
||||
|
||||
class SubmitConfirmationModal(discord.ui.Modal):
|
||||
"""Modal for confirming transaction submission."""
|
||||
|
||||
def __init__(self, builder: TransactionBuilder):
|
||||
|
||||
def __init__(self, builder: TransactionBuilder, submission_handler: str = "scheduled"):
|
||||
super().__init__(title="Confirm Transaction Submission")
|
||||
self.builder = builder
|
||||
self.submission_handler = submission_handler
|
||||
|
||||
self.confirmation = discord.ui.TextInput(
|
||||
label="Type 'CONFIRM' to submit",
|
||||
@ -205,54 +216,89 @@ class SubmitConfirmationModal(discord.ui.Modal):
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
|
||||
try:
|
||||
from services.league_service import LeagueService
|
||||
|
||||
from services.league_service import league_service
|
||||
from services.transaction_service import transaction_service
|
||||
from services.player_service import player_service
|
||||
|
||||
# Get current league state
|
||||
league_service = LeagueService()
|
||||
current_state = await league_service.get_current_state()
|
||||
|
||||
|
||||
if not current_state:
|
||||
await interaction.followup.send(
|
||||
"❌ Could not get current league state. Please try again later.",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
# Submit the transaction (for next week)
|
||||
transactions = await self.builder.submit_transaction(week=current_state.week + 1)
|
||||
|
||||
# Create success message
|
||||
success_msg = f"✅ **Transaction Submitted Successfully!**\n\n"
|
||||
success_msg += f"**Move ID:** `{transactions[0].moveid}`\n"
|
||||
success_msg += f"**Moves:** {len(transactions)}\n"
|
||||
success_msg += f"**Effective Week:** {transactions[0].week}\n\n"
|
||||
|
||||
success_msg += "**Transaction Details:**\n"
|
||||
for move in self.builder.moves:
|
||||
success_msg += f"• {move.description}\n"
|
||||
|
||||
success_msg += f"\n💡 Use `/mymoves` to check transaction status"
|
||||
|
||||
await interaction.followup.send(success_msg, ephemeral=True)
|
||||
|
||||
|
||||
if self.submission_handler == "scheduled":
|
||||
# SCHEDULED SUBMISSION (/dropadd behavior)
|
||||
# Submit the transaction for NEXT week
|
||||
transactions = await self.builder.submit_transaction(week=current_state.week + 1)
|
||||
|
||||
# Create success message
|
||||
success_msg = f"✅ **Transaction Submitted Successfully!**\n\n"
|
||||
success_msg += f"**Move ID:** `{transactions[0].moveid}`\n"
|
||||
success_msg += f"**Moves:** {len(transactions)}\n"
|
||||
success_msg += f"**Effective Week:** {transactions[0].week}\n\n"
|
||||
|
||||
success_msg += "**Transaction Details:**\n"
|
||||
for move in self.builder.moves:
|
||||
success_msg += f"• {move.description}\n"
|
||||
|
||||
success_msg += f"\n💡 Use `/mymoves` to check transaction status"
|
||||
|
||||
await interaction.followup.send(success_msg, ephemeral=True)
|
||||
|
||||
elif self.submission_handler == "immediate":
|
||||
# IMMEDIATE SUBMISSION (/ilmove behavior)
|
||||
# Submit the transaction for THIS week
|
||||
transactions = await self.builder.submit_transaction(week=current_state.week)
|
||||
|
||||
# POST transactions to database
|
||||
created_transactions = await transaction_service.create_transaction_batch(transactions)
|
||||
|
||||
# Update each player's team assignment
|
||||
player_updates = []
|
||||
for txn in created_transactions:
|
||||
updated_player = await player_service.update_player_team(
|
||||
txn.player.id,
|
||||
txn.newteam.id
|
||||
)
|
||||
player_updates.append(updated_player)
|
||||
|
||||
# Create success message
|
||||
success_msg = f"✅ **IL Move Executed Successfully!**\n\n"
|
||||
success_msg += f"**Move ID:** `{created_transactions[0].moveid}`\n"
|
||||
success_msg += f"**Moves:** {len(created_transactions)}\n"
|
||||
success_msg += f"**Week:** {created_transactions[0].week} (Current)\n\n"
|
||||
|
||||
success_msg += "**Executed Moves:**\n"
|
||||
for txn in created_transactions:
|
||||
success_msg += f"• {txn.move_description}\n"
|
||||
|
||||
success_msg += f"\n✅ **All players have been moved to their new teams immediately**"
|
||||
|
||||
await interaction.followup.send(success_msg, ephemeral=True)
|
||||
|
||||
# Clear the builder after successful submission
|
||||
from services.transaction_builder import clear_transaction_builder
|
||||
clear_transaction_builder(interaction.user.id)
|
||||
|
||||
|
||||
# Update the original embed to show completion
|
||||
completion_title = "✅ Transaction Submitted" if self.submission_handler == "scheduled" else "✅ IL Move Executed"
|
||||
completion_embed = discord.Embed(
|
||||
title="✅ Transaction Submitted",
|
||||
description=f"Your transaction has been submitted successfully!\n\nMove ID: `{transactions[0].moveid}`",
|
||||
title=completion_title,
|
||||
description=f"Your transaction has been processed successfully!",
|
||||
color=0x00ff00
|
||||
)
|
||||
|
||||
|
||||
# Disable all buttons
|
||||
view = discord.ui.View()
|
||||
|
||||
|
||||
try:
|
||||
# Find and update the original message
|
||||
async for message in interaction.channel.history(limit=50): # type: ignore
|
||||
@ -262,7 +308,7 @@ class SubmitConfirmationModal(discord.ui.Modal):
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
except Exception as e:
|
||||
await interaction.followup.send(
|
||||
f"❌ Error submitting transaction: {str(e)}",
|
||||
@ -270,19 +316,26 @@ class SubmitConfirmationModal(discord.ui.Modal):
|
||||
)
|
||||
|
||||
|
||||
async def create_transaction_embed(builder: TransactionBuilder) -> discord.Embed:
|
||||
async def create_transaction_embed(builder: TransactionBuilder, command_name: str = "/dropadd") -> discord.Embed:
|
||||
"""
|
||||
Create the main transaction builder embed.
|
||||
|
||||
|
||||
Args:
|
||||
builder: TransactionBuilder instance
|
||||
|
||||
command_name: Name of the command to use for adding more moves (default: "/dropadd")
|
||||
|
||||
Returns:
|
||||
Discord embed with current transaction state
|
||||
"""
|
||||
# Determine description based on command
|
||||
if command_name == "/ilmove":
|
||||
description = "Build your real-time roster move for this week"
|
||||
else:
|
||||
description = "Build your transaction for next week"
|
||||
|
||||
embed = EmbedTemplate.create_base_embed(
|
||||
title=f"📋 Transaction Builder - {builder.team.abbrev}",
|
||||
description=f"Build your transaction for next week",
|
||||
description=description,
|
||||
color=EmbedColors.PRIMARY
|
||||
)
|
||||
|
||||
@ -354,7 +407,7 @@ async def create_transaction_embed(builder: TransactionBuilder) -> discord.Embed
|
||||
# Add instructions for adding more moves
|
||||
embed.add_field(
|
||||
name="➕ Add More Moves",
|
||||
value="Use `/dropadd` to add more moves",
|
||||
value=f"Use `{command_name}` to add more moves",
|
||||
inline=False
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user