diff --git a/commands/transactions/dropadd.py b/commands/transactions/dropadd.py index 9183c22..b4851fd 100644 --- a/commands/transactions/dropadd.py +++ b/commands/transactions/dropadd.py @@ -168,32 +168,60 @@ class DropAddCommands(commands.Cog): # 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}") + self.logger.debug(f"🔍 DIAGNOSTIC: Player {player.name} (ID={player.id}) team_id: {player.team_id}, Builder team_id: {builder.team.id}") + if player.team: + self.logger.debug(f"🔍 DIAGNOSTIC: Player team abbrev: {player.team.abbrev}") await builder.load_roster_data() if builder._current_roster: + # Log roster composition for diagnostics + ml_count = len(builder._current_roster.active_players) + mil_count = len(builder._current_roster.minor_league_players) + il_count = len(builder._current_roster.il_players) + + self.logger.debug(f"🔍 DIAGNOSTIC: Roster loaded for {builder.team.abbrev}: " + f"ML={ml_count}, MiL={mil_count}, IL={il_count}") + + # Log ALL player IDs in each roster section + ml_ids = [p.id for p in builder._current_roster.active_players] + mil_ids = [p.id for p in builder._current_roster.minor_league_players] + il_ids = [p.id for p in builder._current_roster.il_players] + + self.logger.debug(f"🔍 DIAGNOSTIC: ML player IDs: {ml_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: MiL player IDs: {mil_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: IL player IDs: {il_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: Searching for player ID: {player.id}") + # 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) + self.logger.debug(f"🔍 DIAGNOSTIC: Player {player.name} found - ML:{player_on_active}, MiL:{player_on_minor}, IL:{player_on_il}") + if player_on_active: from_roster = RosterType.MAJOR_LEAGUE - self.logger.debug(f"Player {player.name} found on active roster (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") + 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") + self.logger.debug(f"✅ Player {player.name} found on injured list") else: # Player not found on user's roster - they're from another team or free agency from_roster = RosterType.FREE_AGENCY - self.logger.debug(f"Player {player.name} not found on user's roster, treating as free agency") + self.logger.warning(f"⚠️ Player {player.name} (ID={player.id}) not found on user's roster, treating as free agency") + + # Additional diagnostic: Check if player's team suggests they should be on roster + if player.team and builder.team.is_same_organization(player.team): + self.logger.error(f"❌ BUG DETECTED: Player {player.name} belongs to {player.team.abbrev} " + f"(same organization as {builder.team.abbrev}) but not found in roster lists!") + self.logger.error(f"❌ Player team_id={player.team_id}, roster team_id={builder._current_roster.team_id}") else: # Couldn't load roster data, assume free agency as safest fallback from_roster = RosterType.FREE_AGENCY - self.logger.warning(f"Could not load roster data, assuming {player.name} is free agency") + self.logger.error(f"❌ Could not load roster data, assuming {player.name} is free agency") # Create move move = TransactionMove( diff --git a/commands/transactions/ilmove.py b/commands/transactions/ilmove.py index cfc7662..64a88fe 100644 --- a/commands/transactions/ilmove.py +++ b/commands/transactions/ilmove.py @@ -173,32 +173,61 @@ class ILMoveCommands(commands.Cog): # 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}") + self.logger.debug(f"🔍 DIAGNOSTIC: Player {player.name} (ID={player.id}) team_id: {player.team_id}, Builder team_id: {builder.team.id}") + if player.team: + self.logger.debug(f"🔍 DIAGNOSTIC: Player team abbrev: {player.team.abbrev}") await builder.load_roster_data() if builder._current_roster: + # Log roster composition for diagnostics + ml_count = len(builder._current_roster.active_players) + mil_count = len(builder._current_roster.minor_league_players) + il_count = len(builder._current_roster.il_players) + + self.logger.debug(f"🔍 DIAGNOSTIC: Roster loaded for {builder.team.abbrev}: " + f"ML={ml_count}, MiL={mil_count}, IL={il_count}") + + # Log ALL player IDs in each roster section + ml_ids = [p.id for p in builder._current_roster.active_players] + mil_ids = [p.id for p in builder._current_roster.minor_league_players] + il_ids = [p.id for p in builder._current_roster.il_players] + + self.logger.debug(f"🔍 DIAGNOSTIC: ML player IDs: {ml_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: MiL player IDs: {mil_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: IL player IDs: {il_ids}") + self.logger.debug(f"🔍 DIAGNOSTIC: Searching for player ID: {player.id}") + # 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) + self.logger.debug(f"🔍 DIAGNOSTIC: Player {player.name} found - ML:{player_on_active}, MiL:{player_on_minor}, IL:{player_on_il}") + if player_on_active: from_roster = RosterType.MAJOR_LEAGUE - self.logger.debug(f"Player {player.name} found on active roster (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") + 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") + 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") + self.logger.warning(f"⚠️ Player {player.name} (ID={player.id}) not found on {builder.team.abbrev} roster") + + # Additional diagnostic: Check if player's team suggests they should be on roster + if player.team and builder.team.is_same_organization(player.team): + self.logger.error(f"❌ BUG DETECTED: Player {player.name} belongs to {player.team.abbrev} " + f"(same organization as {builder.team.abbrev}) but not found in roster lists!") + self.logger.error(f"❌ Player team_id={player.team_id}, roster team_id={builder._current_roster.team_id}") + 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}") + 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: diff --git a/services/transaction_builder.py b/services/transaction_builder.py index 495f546..659bb12 100644 --- a/services/transaction_builder.py +++ b/services/transaction_builder.py @@ -338,21 +338,29 @@ class TransactionBuilder: # Note: IL players don't count toward roster limits, so no changes needed for move in self.moves: + # Log move being processed for diagnostics + logger.debug(f"🔍 VALIDATION: Processing move - {move.player.name} (ID={move.player.id})") + logger.debug(f"🔍 VALIDATION: from_roster={move.from_roster.value}, to_roster={move.to_roster.value}") + # Calculate roster changes based on from/to locations if move.from_roster == RosterType.MAJOR_LEAGUE: ml_changes -= 1 ml_swar_changes -= move.player.wara + logger.debug(f"🔍 VALIDATION: ML decrement - ml_changes now {ml_changes}") elif move.from_roster == RosterType.MINOR_LEAGUE: mil_changes -= 1 mil_swar_changes -= move.player.wara + logger.debug(f"🔍 VALIDATION: MiL decrement - mil_changes now {mil_changes}") # Note: INJURED_LIST and FREE_AGENCY don't count toward ML roster limit if move.to_roster == RosterType.MAJOR_LEAGUE: ml_changes += 1 ml_swar_changes += move.player.wara + logger.debug(f"🔍 VALIDATION: ML increment - ml_changes now {ml_changes}") elif move.to_roster == RosterType.MINOR_LEAGUE: mil_changes += 1 mil_swar_changes += move.player.wara + logger.debug(f"🔍 VALIDATION: MiL increment - mil_changes now {mil_changes}") # Note: INJURED_LIST and FREE_AGENCY don't count toward ML roster limit # Calculate projected roster sizes and sWAR @@ -360,10 +368,16 @@ class TransactionBuilder: current_ml_size = len(self._current_roster.active_players) current_mil_size = len(self._current_roster.minor_league_players) + logger.debug(f"🔍 VALIDATION: Current roster - ML:{current_ml_size}, MiL:{current_mil_size}") + logger.debug(f"🔍 VALIDATION: Changes calculated - ml_changes:{ml_changes}, mil_changes:{mil_changes}") + projected_ml_size = current_ml_size + ml_changes projected_mil_size = current_mil_size + mil_changes projected_ml_swar = current_ml_swar + ml_swar_changes projected_mil_swar = current_mil_swar + mil_swar_changes + + logger.debug(f"🔍 VALIDATION: Projected roster - ML:{projected_ml_size}, MiL:{projected_mil_size}") + logger.debug(f"🔍 VALIDATION: Projected sWAR - ML:{projected_ml_swar:.2f}, MiL:{projected_mil_swar:.2f}") # Get current week to determine roster limits try: @@ -421,20 +435,28 @@ class TransactionBuilder: pre_existing_transaction_count=pre_existing_count ) - async def submit_transaction(self, week: int) -> List[Transaction]: + async def submit_transaction(self, week: int, check_existing_transactions: bool = True) -> List[Transaction]: """ Submit the transaction by creating individual Transaction models. - + Args: week: Week the transaction is effective for - + check_existing_transactions: Whether to include pre-existing transactions in validation. + Set to True for /dropadd (scheduled moves - need to check against other scheduled moves). + Set to False for /ilmove (immediate moves - already in database, don't double-count). + Returns: List of created Transaction objects """ if not self.moves: raise ValueError("Cannot submit empty transaction") - - validation = await self.validate_transaction(next_week=week) + + # For immediate moves (/ilmove), don't check pre-existing transactions + if check_existing_transactions: + validation = await self.validate_transaction(next_week=week) + else: + validation = await self.validate_transaction() + if not validation.is_legal: raise ValueError(f"Cannot submit illegal transaction: {', '.join(validation.errors)}") diff --git a/views/transaction_embed.py b/views/transaction_embed.py index 186678a..433e380 100644 --- a/views/transaction_embed.py +++ b/views/transaction_embed.py @@ -261,7 +261,11 @@ class SubmitConfirmationModal(discord.ui.Modal): 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) + # Don't check existing transactions - they're already in DB and would cause double-counting + transactions = await self.builder.submit_transaction( + week=current_state.week, + check_existing_transactions=False + ) # POST transactions to database created_transactions = await transaction_service.create_transaction_batch(transactions)