Compare commits

..

10 Commits

Author SHA1 Message Date
Cal Corum
6935465210 Bump version to 2.29.2 2026-02-01 21:28:31 -06:00
Cal Corum
060287b7ca Fix API parameter name: use 'demotion_week' instead of 'dem_week'
The API expects 'demotion_week' as the query parameter name, not 'dem_week'.
Updated service to send correct parameter name and tests to verify.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 21:27:52 -06:00
Cal Corum
c7820eaea0 Bump version to 2.29.1 2026-02-01 21:16:48 -06:00
Cal Corum
788b7b30fc Fix missing Optional import in transaction_freeze.py
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 21:16:25 -06:00
Cal Corum
dca629a279 Bump version to 2.29.0 2026-02-01 21:15:51 -06:00
Cal Corum
00f8f4f0aa Merge branch 'feature/add-dem-week-to-player-updates' 2026-02-01 21:13:09 -06:00
Cal Corum
be3dbf1f8d Add dem_week parameter to player team updates
- Add optional dem_week parameter to PlayerService.update_player_team()
- Transaction freeze sets dem_week to current.week + 2
- /ilmove command sets dem_week to current.week
- Draft picks (manual and auto) set dem_week to current.week + 2
- Backwards compatible - admin commands don't set dem_week
- Add 4 unit tests for dem_week scenarios
- Enhanced logging shows dem_week values

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 21:12:06 -06:00
Cal Corum
f4523b99f2 Clear confirmation message content on delete result
Prevents double emoji display by clearing the " Confirmed!" content
when showing the delete result embed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 16:40:03 -06:00
Cal Corum
b5365f01f9 Fix custom command delete permission check using wrong ID field
Changed from command.creator_id (database ID) to command.creator.discord_id
to properly compare against the deleter's Discord user ID.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 16:10:52 -06:00
Cal Corum
4fb3bcef51 Bump version to 2.28.1
Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-28 16:02:31 -06:00
9 changed files with 232 additions and 25 deletions

View File

@ -1 +1 @@
2.28.0
2.29.2

View File

@ -304,10 +304,15 @@ class DraftPicksCog(commands.Cog):
await interaction.followup.send(embed=embed)
return
# Update player team
# Get current league state for dem_week calculation
from services.league_service import league_service
current = await league_service.get_current_state()
# Update player team with dem_week set to current.week + 2 for draft picks
updated_player = await player_service.update_player_team(
player_obj.id,
team.id
team.id,
dem_week=current.week + 2 if current else None
)
if not updated_player:

View File

@ -239,7 +239,7 @@ class CustomCommandsService(BaseService[CustomCommand]):
command = await self.get_command_by_name(name)
# Check permissions (unless force delete)
if not force and command.creator_id != deleter_discord_id:
if not force and command.creator.discord_id != deleter_discord_id:
raise CustomCommandPermissionError("You can only delete commands you created")
# Delete via API

View File

@ -372,33 +372,65 @@ class PlayerService(BaseService[Player]):
return None
async def update_player_team(
self, player_id: int, new_team_id: int
self,
player_id: int,
new_team_id: int,
dem_week: Optional[int] = None
) -> 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.
Update a player's team assignment.
Args:
player_id: Player ID to update
new_team_id: New team ID to assign
dem_week: Optional demotion/designation week to set.
- Transaction freeze: Current.week + 2
- Draft picks: Current.week + 2
- IL moves: Current.week
- Admin/Other: Not set (None)
Returns:
Updated player instance or None
Raises:
APIException: If player update fails
Examples:
# Transaction freeze (Monday morning)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week + 2
)
# Draft pick (manual or auto-draft)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week + 2
)
# IL move (/ilmove command)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week
)
# Admin move (no dem_week)
await player_service.update_player_team(player_id, new_team_id)
"""
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}
logger.info(
f"Updating player {player_id} team to {new_team_id}"
+ (f" with dem_week={dem_week}" if dem_week is not None else "")
)
# Build update dictionary
updates = {"team_id": new_team_id}
if dem_week is not None:
updates["demotion_week"] = dem_week
updated_player = await self.update_player(player_id, updates)
if updated_player:
logger.info(
f"Successfully updated player {player_id} to team {new_team_id}"
+ (f" with dem_week={dem_week}" if dem_week is not None else "")
)
return updated_player
else:

View File

@ -340,11 +340,16 @@ class DraftMonitorTask:
self.logger.error(f"Failed to update pick {draft_pick.id}")
return False
# Update player team
# Get current league state for dem_week calculation
from services.player_service import player_service
from services.league_service import league_service
current = await league_service.get_current_state()
# Update player team with dem_week set to current.week + 2 for draft picks
updated_player = await player_service.update_player_team(
player.id,
draft_pick.owner.id
draft_pick.owner.id,
dem_week=current.week + 2 if current else None
)
if not updated_player:

View File

@ -7,7 +7,7 @@ Runs on a schedule to increment weeks and process contested transactions.
import asyncio
import random
from datetime import datetime, UTC
from typing import Dict, List, Tuple, Set
from typing import Dict, List, Tuple, Set, Optional
from dataclasses import dataclass
import discord
@ -465,11 +465,12 @@ class TransactionFreezeTask:
for transaction in transactions:
try:
# Update player's team via PATCH /players/{player_id}?team_id={new_team_id}
# Update player's team via PATCH /players/{player_id}?team_id={new_team_id}&dem_week={current.week+2}
await self._execute_player_update(
player_id=transaction.player.id,
new_team_id=transaction.newteam.id,
player_name=transaction.player.name
player_name=transaction.player.name,
dem_week=current.week + 2
)
success_count += 1
@ -683,7 +684,8 @@ class TransactionFreezeTask:
self,
player_id: int,
new_team_id: int,
player_name: str
player_name: str,
dem_week: Optional[int] = None
) -> bool:
"""
Execute a player roster update via API PATCH.
@ -692,6 +694,7 @@ class TransactionFreezeTask:
player_id: Player database ID
new_team_id: New team ID to assign
player_name: Player name for logging
dem_week: Optional designation week to set (typically Current.week + 2)
Returns:
True if update successful, False otherwise
@ -704,10 +707,15 @@ class TransactionFreezeTask:
"Updating player roster",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
updated_player = await player_service.update_player_team(player_id, new_team_id)
updated_player = await player_service.update_player_team(
player_id,
new_team_id,
dem_week=dem_week
)
# Verify response (200 or 204 indicates success)
if updated_player is not None:
@ -715,7 +723,8 @@ class TransactionFreezeTask:
"Successfully updated player",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
return True
else:
@ -723,7 +732,8 @@ class TransactionFreezeTask:
"Player update returned no response",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
return False
@ -733,6 +743,7 @@ class TransactionFreezeTask:
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id,
dem_week=dem_week,
error=str(e),
exc_info=True
)

View File

@ -392,3 +392,156 @@ class TestGlobalPlayerServiceInstance:
# But same configuration
assert service1.model_class == service2.model_class
assert service1.endpoint == service2.endpoint
class TestPlayerTeamUpdateWithDemWeek:
"""Test player team update functionality with dem_week parameter."""
@pytest.fixture
def mock_client(self):
"""Mock API client."""
client = AsyncMock()
return client
@pytest.fixture
def player_service_instance(self, mock_client):
"""Create PlayerService instance with mocked client."""
service = PlayerService()
service._client = mock_client
return service
def create_player_data(
self, player_id: int, name: str, team_id: int = 5, position: str = "C", **kwargs
):
"""Create complete player data for testing."""
base_data = {
"id": player_id,
"name": name,
"wara": 2.5,
"season": 12,
"team_id": team_id,
"image": f"https://example.com/player{player_id}.jpg",
"pos_1": position,
}
base_data.update(kwargs)
return base_data
@pytest.mark.asyncio
async def test_update_player_team_with_dem_week(
self, player_service_instance, mock_client
):
"""
Test player team update with dem_week parameter.
This test verifies that when dem_week is provided, it is correctly
included in the update dictionary passed to the API.
"""
player_id = 123
new_team_id = 5
dem_week = 15
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
result = await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=dem_week
)
# Verify result
assert result is not None
assert result.team_id == new_team_id
# Verify API call included demotion_week
mock_client.patch.assert_called_once_with(
"players",
{"team_id": new_team_id, "demotion_week": dem_week},
player_id,
use_query_params=True
)
@pytest.mark.asyncio
async def test_update_player_team_without_dem_week(
self, player_service_instance, mock_client
):
"""
Test player team update without dem_week parameter maintains backwards compatibility.
This test verifies that when dem_week is not provided (None), it is NOT
included in the update dictionary, maintaining backwards compatibility with
admin commands that don't need to set dem_week.
"""
player_id = 123
new_team_id = 5
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
result = await player_service_instance.update_player_team(
player_id, new_team_id
)
# Verify result
assert result is not None
assert result.team_id == new_team_id
# Verify API call did NOT include demotion_week
call_args = mock_client.patch.call_args[0][1]
assert "demotion_week" not in call_args
assert call_args == {"team_id": new_team_id}
@pytest.mark.asyncio
async def test_update_player_team_dem_week_zero(
self, player_service_instance, mock_client
):
"""
Test that dem_week=0 IS included in the update.
This test verifies that dem_week=0 is treated as a valid value and included
in the update, since 0 is not None and may be a valid week number.
"""
player_id = 123
new_team_id = 5
dem_week = 0
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=dem_week
)
# Verify API call included demotion_week=0
call_args = mock_client.patch.call_args[0][1]
assert call_args == {"team_id": new_team_id, "demotion_week": 0}
@pytest.mark.asyncio
async def test_update_player_team_dem_week_none_explicit(
self, player_service_instance, mock_client
):
"""
Test that explicitly passing dem_week=None does NOT include it in updates.
This test verifies that when dem_week=None is explicitly passed, it behaves
the same as not passing the parameter at all.
"""
player_id = 123
new_team_id = 5
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=None
)
# Verify API call did NOT include demotion_week
call_args = mock_client.patch.call_args[0][1]
assert "demotion_week" not in call_args

View File

@ -607,14 +607,14 @@ class SingleCommandManagementView(BaseView):
title="Delete Failed",
description=f"Failed to delete command: {e}"
)
await interaction.edit_original_response(embed=embed, view=None)
await interaction.edit_original_response(content=None, embed=embed, view=None)
else:
# User cancelled
embed = EmbedTemplate.info(
title="Deletion Cancelled",
description=f"The command `/cc {self.command.name}` was not deleted."
)
await interaction.edit_original_response(embed=embed, view=None)
await interaction.edit_original_response(content=None, embed=embed, view=None)
class CustomCommandListView(PaginationView):

View File

@ -289,7 +289,8 @@ class SubmitConfirmationModal(discord.ui.Modal):
for txn in created_transactions:
updated_player = await player_service.update_player_team(
txn.player.id,
txn.newteam.id
txn.newteam.id,
dem_week=current_state.week
)
player_updates.append(updated_player)