|
| 1 | +# Правки плана тренировки: предлагает участник, судьбу решает организатор, а |
| 2 | +# при голосовании — состав игры. |
| 3 | +class TrainingPlanProposalsController < ApplicationController |
| 4 | + before_action :set_game |
| 5 | + before_action :set_proposal, except: :create |
| 6 | + before_action :authorize_team_member!, only: :create |
| 7 | + before_action :authorize_game_manager!, only: %i[update approve reject] |
| 8 | + |
| 9 | + def create |
| 10 | + @proposal = @game.training_plan_proposals.new(proposal_attributes.merge(user: current_user)) |
| 11 | + |
| 12 | + if @proposal.save |
| 13 | + # Организатору спрашивать разрешения не у кого — его правка идёт сразу. |
| 14 | + can_manage?(@game) ? approve_and_notify : TrainingPlanProposalNotifier.approval_requested(@proposal) |
| 15 | + redirect_to @game, notice: t("games.training_plan_proposals.#{created_notice_key}") |
| 16 | + else |
| 17 | + redirect_to @game, alert: @proposal.errors.full_messages.to_sentence |
| 18 | + end |
| 19 | + end |
| 20 | + |
| 21 | + # Организатор может поправить предложенное, прежде чем пускать его дальше. |
| 22 | + def update |
| 23 | + return redirect_to @game, alert: t("games.training_plan_proposals.closed") unless @proposal.pending? |
| 24 | + |
| 25 | + if @proposal.update(proposal_attributes) |
| 26 | + redirect_to @game, notice: t("games.training_plan_proposals.updated") |
| 27 | + else |
| 28 | + redirect_to @game, alert: @proposal.errors.full_messages.to_sentence |
| 29 | + end |
| 30 | + end |
| 31 | + |
| 32 | + def approve |
| 33 | + return redirect_to @game, alert: t("games.training_plan_proposals.closed") unless @proposal.pending? |
| 34 | + |
| 35 | + approve_and_notify |
| 36 | + redirect_to @game, notice: t("games.training_plan_proposals.#{@proposal.applied? ? "applied" : "vote_started"}") |
| 37 | + end |
| 38 | + |
| 39 | + def reject |
| 40 | + return redirect_to @game, alert: t("games.training_plan_proposals.closed") unless @proposal.open? |
| 41 | + |
| 42 | + @proposal.reject! |
| 43 | + TrainingPlanProposalNotifier.settled(@proposal) |
| 44 | + redirect_to @game, notice: t("games.training_plan_proposals.rejected") |
| 45 | + end |
| 46 | + |
| 47 | + def vote |
| 48 | + unless @proposal.vote!(current_user, ActiveModel::Type::Boolean.new.cast(params[:in_favor])) |
| 49 | + return redirect_to @game, alert: t("games.training_plan_proposals.vote_not_allowed") |
| 50 | + end |
| 51 | + |
| 52 | + TrainingPlanProposalNotifier.settled(@proposal) unless @proposal.open? |
| 53 | + redirect_to @game, notice: t("games.training_plan_proposals.vote_counted") |
| 54 | + end |
| 55 | + |
| 56 | + # Автор забирает правку назад, организатор — убирает лишнее из списка. |
| 57 | + def destroy |
| 58 | + unless @proposal.user_id == current_user.id || can_manage?(@game) |
| 59 | + return head :forbidden |
| 60 | + end |
| 61 | + |
| 62 | + @proposal.destroy |
| 63 | + redirect_to @game, notice: t("games.training_plan_proposals.removed") |
| 64 | + end |
| 65 | + |
| 66 | + private |
| 67 | + |
| 68 | + def set_game |
| 69 | + @game = Game.find(params[:game_id]) |
| 70 | + end |
| 71 | + |
| 72 | + def set_proposal |
| 73 | + @proposal = @game.training_plan_proposals.find(params[:id]) |
| 74 | + end |
| 75 | + |
| 76 | + def authorize_team_member! |
| 77 | + head :forbidden unless team_member? |
| 78 | + end |
| 79 | + |
| 80 | + def authorize_game_manager! |
| 81 | + head :forbidden unless can_manage?(@game) |
| 82 | + end |
| 83 | + |
| 84 | + def team_member? |
| 85 | + current_user.admin? || @game.team_member_ids.include?(current_user.id) |
| 86 | + end |
| 87 | + |
| 88 | + def approve_and_notify |
| 89 | + @proposal.approve! |
| 90 | + |
| 91 | + if @proposal.voting? |
| 92 | + TrainingPlanProposalNotifier.vote_started(@proposal) |
| 93 | + else |
| 94 | + TrainingPlanProposalNotifier.settled(@proposal) |
| 95 | + end |
| 96 | + end |
| 97 | + |
| 98 | + def created_notice_key |
| 99 | + return "created" unless can_manage?(@game) |
| 100 | + |
| 101 | + @proposal.applied? ? "applied" : "vote_started" |
| 102 | + end |
| 103 | + |
| 104 | + def proposal_attributes |
| 105 | + permitted = params.require(:training_plan_proposal).permit(:comment, :mode, training_block_ids: []) |
| 106 | + |
| 107 | + permitted.to_h.merge("training_block_ids" => allowed_block_ids(permitted[:training_block_ids])) |
| 108 | + end |
| 109 | + |
| 110 | + # Блок из чужой библиотеки в план не попадает, даже если его id прислали в форме. |
| 111 | + # Своё предложение — исключение: организатор правит его целиком, не теряя блок, |
| 112 | + # который принёс из личной библиотеки автор правки. |
| 113 | + def allowed_block_ids(ids) |
| 114 | + ids = Array(ids).map(&:to_i).uniq.reject(&:zero?) |
| 115 | + owner_ids = ([ current_user.id, @game.user_id ] + @game.assigned_coach_ids).compact.uniq |
| 116 | + known_ids = @game.training_block_ids + Array(@proposal&.training_block_ids) |
| 117 | + allowed = TrainingBlock.available_for(owner_ids, known_ids).where(id: ids).pluck(:id) |
| 118 | + |
| 119 | + ids.select { |id| allowed.include?(id) } |
| 120 | + end |
| 121 | +end |
0 commit comments