Merge pull request #4062 from consul/voting_styles

Refactor ballot / ballot line voting logic
This commit is contained in:
Javier Martín
2020-07-30 22:01:43 +02:00
committed by GitHub
20 changed files with 171 additions and 118 deletions

View File

@@ -1285,6 +1285,7 @@
.progress {
background: #212033;
clear: both;
margin-bottom: 0;
}
.progress-meter {
@@ -1293,29 +1294,20 @@
transition: width 2s;
}
.spent-amount-progress,
.spent-amount-meter {
background: none !important;
}
.spent-amount-text {
color: #fff;
font-size: $base-font-size;
font-weight: normal;
position: absolute;
right: 0;
margin-bottom: 0;
position: relative;
text-align: right;
top: 16px;
width: 100%;
white-space: nowrap;
&::before {
color: #a5a1ff;
content: "\57";
font-family: "icons";
font-size: $small-font-size;
line-height: 0;
position: absolute;
right: -6px;
top: -17px;
right: -0.5em;
}
}
@@ -1328,7 +1320,6 @@
.amount-available {
display: block;
text-align: right;
span {
font-size: rem-calc(24);
@@ -1486,6 +1477,7 @@
height: auto;
left: 0;
padding: $line-height;
padding-bottom: $line-height / 2;
position: fixed;
top: 0;
width: 100%;

View File

@@ -9,8 +9,8 @@ module Budgets
before_action :load_investments
before_action :load_ballot_referer
load_and_authorize_resource :budget
load_and_authorize_resource :ballot, class: "Budget::Ballot", through: :budget
authorize_resource :budget
authorize_resource :ballot
load_and_authorize_resource :line, through: :ballot, find_by: :investment_id, class: "Budget::Ballot::Line"
def create

View File

@@ -2,7 +2,7 @@ module Budgets
class BallotsController < ApplicationController
before_action :authenticate_user!
before_action :load_budget
load_and_authorize_resource :budget
authorize_resource :budget
before_action :load_ballot
after_action :store_referer, only: [:show]

View File

@@ -148,7 +148,7 @@ module Budgets
def load_heading
if params[:heading_id].present?
@heading = @budget.headings.find_by_slug_or_id! params[:heading_id]
@assigned_heading = @ballot&.heading_for_group(@heading&.group)
@assigned_heading = @ballot&.heading_for_group(@heading.group)
load_map
end
end

View File

@@ -1,5 +0,0 @@
module BallotsHelper
def progress_bar_width(amount_available, amount_spent)
(amount_spent / amount_available.to_f * 100).to_s + "%"
end
end

View File

@@ -163,10 +163,6 @@ class Budget < ApplicationRecord
formatted_amount(heading_price(heading))
end
def formatted_heading_amount_spent(heading)
formatted_amount(amount_spent(heading))
end
def investments_orders
case phase
when "accepting", "reviewing"

View File

@@ -17,22 +17,6 @@ class Budget
investments.sum(:price).to_i
end
def amount_spent(heading)
investments.by_heading(heading.id).sum(:price).to_i
end
def formatted_amount_spent(heading)
budget.formatted_amount(amount_spent(heading))
end
def amount_available(heading)
budget.heading_price(heading) - amount_spent(heading)
end
def formatted_amount_available(heading)
budget.formatted_amount(amount_available(heading))
end
def has_lines_in_group?(group)
groups.include?(group)
end
@@ -75,5 +59,20 @@ class Budget
def casted_offline?
budget.poll&.voted_by?(user)
end
def voting_style
@voting_style ||= voting_style_class.new(self)
end
delegate :amount_available, :amount_available_info, :amount_spent, :amount_spent_info,
:amount_limit_info, :change_vote_info, :enough_resources?, :formatted_amount_available,
:formatted_amount_limit, :formatted_amount_spent, :not_enough_resources_error,
:percentage_spent, :reason_for_not_being_ballotable, :voted_info,
to: :voting_style
private
def voting_style_class
Budget::VotingStyles::Knapsack
end
end
end

View File

@@ -10,7 +10,7 @@ class Budget
validates :ballot_id, :investment_id, :heading_id, :group_id, :budget_id, presence: true
validate :check_selected
validate :check_sufficient_funds
validate :check_enough_resources
validate :check_valid_heading
scope :by_investment, ->(investment_id) { where(investment_id: investment_id) }
@@ -18,9 +18,12 @@ class Budget
before_validation :set_denormalized_ids
after_save :store_user_heading
def check_sufficient_funds
def check_enough_resources
ballot.lock!
errors.add(:money, "insufficient funds") if ballot.amount_available(investment.heading) < investment.price.to_i
unless ballot.enough_resources?(investment)
errors.add(:resources, ballot.not_enough_resources_error)
end
end
def check_valid_heading

View File

@@ -268,8 +268,9 @@ class Budget
return :not_selected unless selected?
return :no_ballots_allowed unless budget.balloting?
return :different_heading_assigned unless ballot.valid_heading?(heading)
return :not_enough_money if ballot.present? && !enough_money?(ballot)
return :casted_offline if ballot.casted_offline?
ballot.reason_for_not_being_ballotable(self)
end
def permission_problem(user)
@@ -301,15 +302,6 @@ class Budget
user.headings_voted_within_group(group).where(id: heading.id).exists?
end
def ballotable_by?(user)
reason_for_not_being_ballotable_by(user).blank?
end
def enough_money?(ballot)
available_money = ballot.amount_available(heading)
price.to_i <= available_money
end
def register_selection(user)
vote_by(voter: user, vote: "yes") if selectable_by?(user)
end

View File

@@ -0,0 +1,50 @@
class Budget::VotingStyles::Base
attr_reader :ballot
def initialize(ballot)
@ballot = ballot
end
def name
self.class.name.split("::").last.underscore
end
def change_vote_info(link:)
I18n.t("budgets.investments.index.sidebar.change_vote_info.#{name}", link: link)
end
def voted_info(heading)
I18n.t("budgets.investments.index.sidebar.voted_info.#{name}",
count: investments(heading).count,
amount_spent: ballot.budget.formatted_amount(investments_price(heading)))
end
def amount_available_info(heading)
I18n.t("budgets.ballots.show.amount_available.#{name}",
amount: formatted_amount_available(heading))
end
def amount_spent_info(heading)
I18n.t("budgets.ballots.show.amount_spent.#{name}",
amount: formatted_amount_spent(heading))
end
def amount_limit_info(heading)
I18n.t("budgets.ballots.show.amount_limit.#{name}",
amount: formatted_amount_limit(heading))
end
def percentage_spent(heading)
100.0 * amount_spent(heading) / amount_limit(heading)
end
private
def investments(heading)
ballot.investments.by_heading(heading.id)
end
def investments_price(heading)
investments(heading).sum(:price).to_i
end
end

View File

@@ -0,0 +1,41 @@
class Budget::VotingStyles::Knapsack < Budget::VotingStyles::Base
def enough_resources?(investment)
investment.price.to_i <= amount_available(investment.heading)
end
def reason_for_not_being_ballotable(investment)
:not_enough_money unless enough_resources?(investment)
end
def not_enough_resources_error
"insufficient funds"
end
def amount_available(heading)
amount_limit(heading) - amount_spent(heading)
end
def amount_spent(heading)
investments_price(heading)
end
def amount_limit(heading)
ballot.budget.heading_price(heading)
end
def formatted_amount_available(heading)
format(amount_available(heading))
end
def formatted_amount_spent(heading)
format(amount_spent(heading))
end
def formatted_amount_limit(heading)
format(amount_limit(heading))
end
def format(amount)
ballot.budget.formatted_amount(amount)
end
end

View File

@@ -19,22 +19,19 @@
<div class="row ballot">
<% ballot_groups = @ballot.groups.sort_by_name %>
<% ballot_groups.each do |group| %>
<% heading = @ballot.heading_for_group(group) %>
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
<div class="margin-top ballot-content">
<div class="subtitle">
<h3>
<%= group.name %> - <%= @ballot.heading_for_group(group).name %>
<%= group.name %> - <%= heading.name %>
</h3>
<%= link_to sanitize(t("budgets.ballots.show.remaining",
amount: @ballot.formatted_amount_available(@ballot.heading_for_group(group)))),
<%= link_to sanitize(@ballot.amount_available_info(heading)),
budget_group_path(@budget, group) %>
</div>
<% if @ballot.has_lines_in_group?(group) %>
<h4 class="amount-spent text-right">
<%= t("budgets.ballots.show.amount_spent") %>
<span>
<%= @ballot.formatted_amount_spent(@ballot.heading_for_group(group)) %>
</span>
<%= sanitize(@ballot.amount_spent_info(heading)) %>
</h4>
<% else %>
<p>

View File

@@ -1,29 +1,18 @@
<span class="total-amount">
<%= @budget.formatted_heading_price(@heading) %>
<%= sanitize(ballot.amount_limit_info(heading)) %>
</span>
<div class="progress" role="progressbar" tabindex="0"
id="progress"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
aria-valuenow="<%= ballot.percentage_spent(heading) %>" aria-valuemin="0" aria-valuemax="100">
<div class="progress-meter"
style="width:
<%= progress_bar_width(@budget.heading_price(@heading),
@ballot.amount_spent(@heading)) %>">
style="width: <%= ballot.percentage_spent(heading) %>%">
</div>
</div>
<div class="progress spent-amount-progress" role="progressbar" tabindex="0"
aria-valuenow="20" aria-valuemin="0" aria-valuetext="25 percent" aria-valuemax="100">
<span class="progress-meter spent-amount-meter"
style="width:
<%= progress_bar_width(@budget.heading_price(@heading),
@ballot.amount_spent(@heading)) %>">
<p id="amount-spent" class="progress-meter-text spent-amount-text">
<small><%= t("budgets.progress_bar.assigned") %></small><%= @ballot.formatted_amount_spent(@heading) %>
<p id="amount-spent" class="spent-amount-text" style="width: <%= ballot.percentage_spent(heading) %>%">
<small><%= t("budgets.progress_bar.assigned") %></small><%= ballot.formatted_amount_spent(heading) %>
<span id="amount-available" class="amount-available">
<small><%= t("budgets.progress_bar.available") %></small>
<span><%= @ballot.formatted_amount_available(@heading) %></span>
<span><%= ballot.formatted_amount_available(heading) %></span>
</span>
</p>
</span>
</div>

View File

@@ -1,12 +1,7 @@
$("#progress_bar").html("<%= j render("/budgets/ballot/progress_bar", ballot: @ballot) %>");
$("#progress_bar").html("<%= j render("/budgets/ballot/progress_bar", ballot: @ballot, heading: @heading) %>");
$("#sidebar").html("<%= j render("/budgets/investments/sidebar") %>");
$("#<%= dom_id(@investment) %>_ballot").html("<%= j render("/budgets/investments/ballot",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot) %>");
<%= render "refresh_ballots",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot %>

View File

@@ -1,13 +1,8 @@
$("#progress_bar").html("<%= j render("budgets/ballot/progress_bar", ballot: @ballot) %>");
$("#progress_bar").html("<%= j render("budgets/ballot/progress_bar", ballot: @ballot, heading: @heading) %>");
$("#sidebar").html("<%= j render("budgets/investments/sidebar") %>");
$("#ballot").html("<%= j render("budgets/ballot/ballot") %>")
$("#<%= dom_id(@investment) %>_ballot").html("<%= j render("/budgets/investments/ballot",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot) %>");
<%= render "refresh_ballots",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot %>

View File

@@ -27,7 +27,7 @@
<%= t("budgets.investments.index.by_heading", heading: @heading.name) %>
</h2>
<div id="progress_bar" class="no-margin-top">
<%= render "budgets/ballot/progress_bar" %>
<%= render "budgets/ballot/progress_bar", ballot: @ballot, heading: @heading %>
</div>
</div>
<% else %>

View File

@@ -14,8 +14,8 @@
<% if @heading && can?(:show, @ballot) %>
<p class="callout">
<%= sanitize(t("budgets.investments.index.sidebar.voted_info",
link: link_to(t("budgets.investments.index.sidebar.voted_info_link"),
<%= sanitize(@ballot.change_vote_info(
link: link_to(t("budgets.investments.index.sidebar.change_vote_link"),
budget_ballot_path(@budget)))) %>
</p>
<% end %>
@@ -39,11 +39,7 @@
<% if @ballot.investments.by_heading(@heading.id).count > 0 %>
<p>
<em>
<%= sanitize(t("budgets.investments.index.sidebar.voted",
count: @ballot.investments.by_heading(@heading.id).count,
amount_spent: @ballot.formatted_amount_spent(@heading))) %>
</em>
<em><%= sanitize(@ballot.voted_info(@heading)) %></em>
</p>
<% elsif @assigned_heading.present? %>
<p>

View File

@@ -127,6 +127,7 @@ ignore_unused:
- "budgets.phase.*"
- "budgets.investments.index.orders.*"
- "budgets.index.section_header.*"
- "budgets.investments.index.sidebar.voted_info.*"
- "activerecord.*"
- "activemodel.*"
- "attributes.*"

View File

@@ -3,8 +3,12 @@ en:
ballots:
show:
title: Your ballot
amount_spent: Amount spent
remaining: "You still have <span>%{amount}</span> to invest."
amount_available:
knapsack: "You still have <span>%{amount}</span> to invest."
amount_spent:
knapsack: "Amount spent <span>%{amount}</span>"
amount_limit:
knapsack: "%{amount}"
no_balloted_group_yet: "You have not voted on this group yet, go vote!"
remove: Remove vote
voted:
@@ -84,11 +88,13 @@ en:
other: " containing the term <strong>'%{search_term}'</strong>"
sidebar:
my_ballot: My ballot
voted:
voted_info:
knapsack:
one: "<strong>You voted one proposal with a cost of %{amount_spent}</strong>"
other: "<strong>You voted %{count} proposals with a cost of %{amount_spent}</strong>"
voted_info: You can %{link} at any time until the close of this phase. No need to spend all the money available.
voted_info_link: change your vote
change_vote_info:
knapsack: "You can %{link} at any time until the close of this phase. No need to spend all the money available."
change_vote_link: "change your vote"
different_heading_assigned: "You have active votes in another heading: %{heading_link}"
change_ballot: "If your change your mind you can remove your votes in %{check_ballot} and start again."
check_ballot_link: "check and confirm my ballot"

View File

@@ -3,8 +3,12 @@ es:
ballots:
show:
title: Mis votos
amount_spent: Coste total
remaining: "Te quedan <span>%{amount}</span> para invertir"
amount_available:
knapsack: "Te quedan <span>%{amount}</span> para invertir"
amount_spent:
knapsack: "Coste total <span>%{amount}</span>"
amount_limit:
knapsack: "%{amount}"
no_balloted_group_yet: "Todavía no has votado proyectos de este grupo, ¡vota!"
remove: Quitar voto
voted:
@@ -84,11 +88,13 @@ es:
other: " que contienen <strong>'%{search_term}'</strong>"
sidebar:
my_ballot: Mis votos
voted:
voted_info:
knapsack:
one: "<strong>Has votado un proyecto por un valor de %{amount_spent}</strong>"
other: "<strong>Has votado %{count} proyectos por un valor de %{amount_spent}</strong>"
voted_info: Puedes %{link} en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible.
voted_info_link: cambiar tus votos
change_vote_info:
knapsack: "Puedes %{link} en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible."
change_vote_link: "cambiar tus votos"
different_heading_assigned: "Ya apoyaste proyectos de otra sección del presupuesto: %{heading_link}"
change_ballot: "Si cambias de opinión puedes borrar tus votos en %{check_ballot} y volver a empezar."
check_ballot_link: "revisar y confirmar mis votos"