Feature/training plan proposals - #167
Conversation
Расписание висело сверху страницы «Мои игры»: игроку оно там не нужно вовсе, а тренеру мешало искать свои игры. Теперь это отдельная кнопка кабинета рядом с библиотекой блоков, а игроку раздел не показывается. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014tEZQY5PDtiqnKibQkGtRk
Галку «с тренером» ставят при создании тренировки, а тренера ищут потом. До выбора (и после отказа) напоминание с приглашением звали «с тренером» на занятие, которое вести некому. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014tEZQY5PDtiqnKibQkGtRk
Раньше план занятия правил только организатор: участнику оставалось написать ему в личку. Теперь любой, кто выйдет на корт, собирает свою версию плана прямо на странице игры и выбирает, как её принимать — вынести на голосование состава или применить без него. Последнее слово в обоих случаях за организатором: он подтверждает правку, а перед этим может её поправить — блок из личной библиотеки автора при этом не теряется. Голосование уходит в телеграм кнопками «За» и «Против» и закрывается, как только одна из сторон взяла большинство: ждать последний голос незачем. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014tEZQY5PDtiqnKibQkGtRk
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bcaab3518b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| def votes_in_favor | ||
| training_plan_votes.where(in_favor: true).count + 1 | ||
| end | ||
|
|
||
| def votes_against | ||
| training_plan_votes.where(in_favor: false).count | ||
| end | ||
|
|
||
| def votes_expected | ||
| voter_ids.size + 1 |
There was a problem hiding this comment.
Exclude departed members' ballots from vote totals
If a participant votes and then leaves the game while the proposal remains open, votes_expected recalculates from the current team but these counters still include that former participant's stored ballot. For example, a departed yes vote can combine with the author's implicit vote to apply the plan even when the remaining members have not produced a majority. Either snapshot the electorate when voting starts or restrict all ballot counts to the same current voter IDs used by votes_expected.
Useful? React with 👍 / 👎.
| def apply! | ||
| transaction do | ||
| game.replace_training_plan!(training_block_ids) | ||
| update!(status: "applied") |
There was a problem hiding this comment.
Handle training blocks deleted while proposals are open
If the proposal author deletes one of their library blocks while the proposal is pending or voting, its ID remains in this JSON array and applying the proposal attempts to create a game_training_blocks row for a nonexistent block. The foreign key then raises, returning a 500 from approval or the decisive vote and leaving the proposal unable to settle; filter or invalidate missing blocks before replacing the plan.
Useful? React with 👍 / 👎.
Разбор правки плана вскрыл два случая, когда голосование расходится с реальностью. Голос участника оставался в подсчёте после того, как он ушёл из игры: знаменатель пересчитывался по живому составу, а бюллетень ушедшего решал за оставшихся. И блок, удалённый из библиотеки, пока правка ждала своей очереди, ронял её применение на внешнем ключе — правка навсегда застревала в голосовании. Теперь считаем только бюллетени тех, кто ещё в игре, а в план ставим уцелевшие блоки; если не уцелело ни одного — применять нечего. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014tEZQY5PDtiqnKibQkGtRk
No description provided.