diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index d1b1325ba..8485b5468 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -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%; diff --git a/app/controllers/budgets/ballot/lines_controller.rb b/app/controllers/budgets/ballot/lines_controller.rb index 124bfba83..e9df6ab2c 100644 --- a/app/controllers/budgets/ballot/lines_controller.rb +++ b/app/controllers/budgets/ballot/lines_controller.rb @@ -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 diff --git a/app/controllers/budgets/ballots_controller.rb b/app/controllers/budgets/ballots_controller.rb index 7015c8285..52b68c630 100644 --- a/app/controllers/budgets/ballots_controller.rb +++ b/app/controllers/budgets/ballots_controller.rb @@ -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] diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index 9728c8ef3..541f40e44 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -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 diff --git a/app/helpers/ballots_helper.rb b/app/helpers/ballots_helper.rb deleted file mode 100644 index c6fde9c57..000000000 --- a/app/helpers/ballots_helper.rb +++ /dev/null @@ -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 diff --git a/app/models/budget.rb b/app/models/budget.rb index 26489419c..8d2a6ef57 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -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" diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index ccdf1b289..34939adcd 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -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 diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 091ceaea7..58078de98 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -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 diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index fc4d31ff7..4adf23556 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -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 diff --git a/app/models/budget/voting_styles/base.rb b/app/models/budget/voting_styles/base.rb new file mode 100644 index 000000000..87bca2d59 --- /dev/null +++ b/app/models/budget/voting_styles/base.rb @@ -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 diff --git a/app/models/budget/voting_styles/knapsack.rb b/app/models/budget/voting_styles/knapsack.rb new file mode 100644 index 000000000..4f2c72a19 --- /dev/null +++ b/app/models/budget/voting_styles/knapsack.rb @@ -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 diff --git a/app/views/budgets/ballot/_ballot.html.erb b/app/views/budgets/ballot/_ballot.html.erb index cd70aea12..e8e79300f 100644 --- a/app/views/budgets/ballot/_ballot.html.erb +++ b/app/views/budgets/ballot/_ballot.html.erb @@ -19,22 +19,19 @@
<% ballot_groups = @ballot.groups.sort_by_name %> <% ballot_groups.each do |group| %> + <% heading = @ballot.heading_for_group(group) %>

- <%= group.name %> - <%= @ballot.heading_for_group(group).name %> + <%= group.name %> - <%= heading.name %>

- <%= 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) %>
<% if @ballot.has_lines_in_group?(group) %>

- <%= t("budgets.ballots.show.amount_spent") %> - - <%= @ballot.formatted_amount_spent(@ballot.heading_for_group(group)) %> - + <%= sanitize(@ballot.amount_spent_info(heading)) %>

<% else %>

diff --git a/app/views/budgets/ballot/_progress_bar.html.erb b/app/views/budgets/ballot/_progress_bar.html.erb index 4c7035b41..b3c563240 100644 --- a/app/views/budgets/ballot/_progress_bar.html.erb +++ b/app/views/budgets/ballot/_progress_bar.html.erb @@ -1,29 +1,18 @@ - <%= @budget.formatted_heading_price(@heading) %> + <%= sanitize(ballot.amount_limit_info(heading)) %>

+ aria-valuenow="<%= ballot.percentage_spent(heading) %>" aria-valuemin="0" aria-valuemax="100">
+ style="width: <%= ballot.percentage_spent(heading) %>%">
-
- -

- <%= t("budgets.progress_bar.assigned") %><%= @ballot.formatted_amount_spent(@heading) %> - - <%= t("budgets.progress_bar.available") %> - <%= @ballot.formatted_amount_available(@heading) %> - -

+

+ <%= t("budgets.progress_bar.assigned") %><%= ballot.formatted_amount_spent(heading) %> + + <%= t("budgets.progress_bar.available") %> + <%= ballot.formatted_amount_available(heading) %> -

+

diff --git a/app/views/budgets/ballot/lines/create.js.erb b/app/views/budgets/ballot/lines/create.js.erb index 24e9a5aef..48e56054f 100644 --- a/app/views/budgets/ballot/lines/create.js.erb +++ b/app/views/budgets/ballot/lines/create.js.erb @@ -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 %> diff --git a/app/views/budgets/ballot/lines/destroy.js.erb b/app/views/budgets/ballot/lines/destroy.js.erb index 0503609e1..c93517414 100644 --- a/app/views/budgets/ballot/lines/destroy.js.erb +++ b/app/views/budgets/ballot/lines/destroy.js.erb @@ -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 %> diff --git a/app/views/budgets/investments/_header.html.erb b/app/views/budgets/investments/_header.html.erb index a5c63f160..0b18c3e1f 100644 --- a/app/views/budgets/investments/_header.html.erb +++ b/app/views/budgets/investments/_header.html.erb @@ -27,7 +27,7 @@ <%= t("budgets.investments.index.by_heading", heading: @heading.name) %>
- <%= render "budgets/ballot/progress_bar" %> + <%= render "budgets/ballot/progress_bar", ballot: @ballot, heading: @heading %>
<% else %> diff --git a/app/views/budgets/investments/_sidebar.html.erb b/app/views/budgets/investments/_sidebar.html.erb index 823c23a89..f33abf8b7 100644 --- a/app/views/budgets/investments/_sidebar.html.erb +++ b/app/views/budgets/investments/_sidebar.html.erb @@ -14,8 +14,8 @@ <% if @heading && can?(:show, @ballot) %>

- <%= 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)))) %>

<% end %> @@ -39,11 +39,7 @@ <% if @ballot.investments.by_heading(@heading.id).count > 0 %>

- - <%= sanitize(t("budgets.investments.index.sidebar.voted", - count: @ballot.investments.by_heading(@heading.id).count, - amount_spent: @ballot.formatted_amount_spent(@heading))) %> - + <%= sanitize(@ballot.voted_info(@heading)) %>

<% elsif @assigned_heading.present? %>

diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 29706ed60..f03a38d09 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -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.*" diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index f3c5713b5..8047f44ad 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -3,8 +3,12 @@ en: ballots: show: title: Your ballot - amount_spent: Amount spent - remaining: "You still have %{amount} to invest." + amount_available: + knapsack: "You still have %{amount} to invest." + amount_spent: + knapsack: "Amount spent %{amount}" + 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 '%{search_term}'" sidebar: my_ballot: My ballot - voted: - one: "You voted one proposal with a cost of %{amount_spent}" - other: "You voted %{count} proposals with a cost of %{amount_spent}" - 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 + voted_info: + knapsack: + one: "You voted one proposal with a cost of %{amount_spent}" + other: "You voted %{count} proposals with a cost of %{amount_spent}" + 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" diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index e096dcc3b..9ccc52159 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -3,8 +3,12 @@ es: ballots: show: title: Mis votos - amount_spent: Coste total - remaining: "Te quedan %{amount} para invertir" + amount_available: + knapsack: "Te quedan %{amount} para invertir" + amount_spent: + knapsack: "Coste total %{amount}" + 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 '%{search_term}'" sidebar: my_ballot: Mis votos - voted: - one: "Has votado un proyecto por un valor de %{amount_spent}" - other: "Has votado %{count} proyectos por un valor de %{amount_spent}" - 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 + voted_info: + knapsack: + one: "Has votado un proyecto por un valor de %{amount_spent}" + other: "Has votado %{count} proyectos por un valor de %{amount_spent}" + 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"