Merge branch 'master' into budget_map
This commit is contained in:
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
- Added Capistrano task to automate maintenance mode https://github.com/consul/consul/pull/1932
|
||||
- Added actions to edit and delete a budget's headings https://github.com/consul/consul/pull/1917
|
||||
- Allow Budget Investments to be Related to other content https://github.com/consul/consul/pull/2311
|
||||
- New Budget::Phase model to add dates, enabling and more https://github.com/consul/consul/pull/2323
|
||||
|
||||
### Changed
|
||||
- Updated multiple minor & patch gem versions thanks to [Depfu](https://depfu.com)
|
||||
@@ -28,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
- Design Improvements https://github.com/consul/consul/pull/2327
|
||||
|
||||
### Deprecated
|
||||
- Budget's `description_*` columns will be erased from database in next release. Please run rake task `budgets:phases:generate_missing` to migrate them. Details at Warning section of https://github.com/consul/consul/pull/2323
|
||||
|
||||
### Removed
|
||||
- Spending Proposals urls from sitemap, that model is getting entirely deprecated soon.
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
//= require polls
|
||||
//= require sortable
|
||||
//= require table_sortable
|
||||
//= require investment_report_alert
|
||||
|
||||
var initialize_modules = function() {
|
||||
App.Comments.initialize();
|
||||
@@ -115,6 +116,7 @@ var initialize_modules = function() {
|
||||
App.Polls.initialize();
|
||||
App.Sortable.initialize();
|
||||
App.TableSortable.initialize();
|
||||
App.InvestmentReportAlert.initialize();
|
||||
};
|
||||
|
||||
$(function(){
|
||||
|
||||
7
app/assets/javascripts/investment_report_alert.js.coffee
Normal file
7
app/assets/javascripts/investment_report_alert.js.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
App.InvestmentReportAlert =
|
||||
initialize: ->
|
||||
$('#js-investment-report-alert').on 'click', ->
|
||||
if this.checked && $('#budget_investment_feasibility_unfeasible').is(':checked')
|
||||
confirm(this.dataset.alert + "\n" + this.dataset.notFeasibleAlert);
|
||||
else if this.checked
|
||||
confirm(this.dataset.alert);
|
||||
@@ -368,7 +368,7 @@ a {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.aling-middle {
|
||||
.align-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// 03. Show participation
|
||||
// 04. List participation
|
||||
// 05. Featured
|
||||
// 06. Budget
|
||||
// 06. Budgets
|
||||
// 07. Proposals successful
|
||||
// 08. Polls
|
||||
// 09. Polls results and stats
|
||||
@@ -1096,31 +1096,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 06. Budget
|
||||
// ----------
|
||||
// 06. Budgets
|
||||
// -----------
|
||||
|
||||
.expanded.budget {
|
||||
background: $budget;
|
||||
.expanded {
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p,
|
||||
.back,
|
||||
.icon-angle-left {
|
||||
color: #fff;
|
||||
}
|
||||
&.budget {
|
||||
background: $budget;
|
||||
|
||||
.button {
|
||||
background: #fff;
|
||||
color: $budget;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p,
|
||||
a,
|
||||
.back,
|
||||
.icon-angle-left {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #6a2a72;
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
.button {
|
||||
background: #fff;
|
||||
color: $budget;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #6a2a72;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1182,7 +1190,7 @@
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #fff;
|
||||
@@ -1212,6 +1220,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.groups-and-headings {
|
||||
|
||||
.heading {
|
||||
border: 1px solid $border;
|
||||
border-radius: rem-calc(3);
|
||||
display: inline-block;
|
||||
margin-bottom: $line-height / 2;
|
||||
|
||||
&:hover {
|
||||
background: $highlight;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: $line-height / 2;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: $text;
|
||||
display: block;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-votes {
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class Admin::BudgetsController < Admin::BaseController
|
||||
private
|
||||
|
||||
def budget_params
|
||||
descriptions = Budget::PHASES.map{|p| "description_#{p}"}.map(&:to_sym)
|
||||
descriptions = Budget::Phase::PHASE_KINDS.map{|p| "description_#{p}"}.map(&:to_sym)
|
||||
valid_attributes = [:name, :phase, :currency_symbol] + descriptions
|
||||
params.require(:budget).permit(*valid_attributes)
|
||||
end
|
||||
|
||||
@@ -15,7 +15,8 @@ class BudgetsController < ApplicationController
|
||||
|
||||
def index
|
||||
@budgets = @budgets.order(:created_at)
|
||||
budgets_map_locations = Budget.where.not(phase: 'drafting').map{ |budget| budget.investments.map{|investment| investment.map_location}}.flatten.compact
|
||||
@budget = current_budget
|
||||
budgets_map_locations = current_budget.investments.map{ |investment| investment.map_location }.compact
|
||||
@budgets_coordinates = budgets_map_locations.map{ |ml| {lat: ml.latitude, long: ml.longitude, investment_title: ml.investment.title , investment_id: ml.investment.id, budget_id: ml.investment.budget.id}}
|
||||
end
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ class Management::BaseController < ActionController::Base
|
||||
before_action :set_locale
|
||||
|
||||
helper_method :managed_user
|
||||
helper_method :current_user
|
||||
|
||||
private
|
||||
|
||||
@@ -40,4 +41,7 @@ class Management::BaseController < ActionController::Base
|
||||
I18n.locale = session[:locale]
|
||||
end
|
||||
|
||||
def current_budget
|
||||
Budget.current
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ class Management::BudgetsController < Management::BaseController
|
||||
end
|
||||
|
||||
def print_investments
|
||||
@budget = Budget.current
|
||||
@budget = current_budget
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -5,7 +5,7 @@ class Valuation::BudgetsController < Valuation::BaseController
|
||||
load_and_authorize_resource
|
||||
|
||||
def index
|
||||
@budget = Budget.current
|
||||
@budget = current_budget
|
||||
if @budget.present?
|
||||
@investments_with_valuation_open = {}
|
||||
@investments_with_valuation_open = @budget.investments
|
||||
|
||||
@@ -7,7 +7,7 @@ module BudgetsHelper
|
||||
end
|
||||
|
||||
def budget_phases_select_options
|
||||
Budget::PHASES.map { |ph| [ t("budgets.phase.#{ph}"), ph ] }
|
||||
Budget::Phase::PHASE_KINDS.map { |ph| [ t("budgets.phase.#{ph}"), ph ] }
|
||||
end
|
||||
|
||||
def budget_currency_symbol_select_options
|
||||
|
||||
@@ -3,14 +3,10 @@ class Budget < ActiveRecord::Base
|
||||
include Measurable
|
||||
include Sluggable
|
||||
|
||||
PHASES = %w(drafting accepting reviewing selecting valuating publishing_prices
|
||||
balloting reviewing_ballots finished).freeze
|
||||
PUBLISHED_PRICES_PHASES = %w(publishing_prices balloting reviewing_ballots finished).freeze
|
||||
|
||||
CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :phase, inclusion: { in: PHASES }
|
||||
validates :phase, inclusion: { in: Budget::Phase::PHASE_KINDS }
|
||||
validates :currency_symbol, presence: true
|
||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||
|
||||
@@ -18,9 +14,12 @@ class Budget < ActiveRecord::Base
|
||||
has_many :ballots, dependent: :destroy
|
||||
has_many :groups, dependent: :destroy
|
||||
has_many :headings, through: :groups
|
||||
has_many :phases, class_name: Budget::Phase
|
||||
|
||||
before_validation :sanitize_descriptions
|
||||
|
||||
after_create :generate_phases
|
||||
|
||||
scope :drafting, -> { where(phase: "drafting") }
|
||||
scope :accepting, -> { where(phase: "accepting") }
|
||||
scope :reviewing, -> { where(phase: "reviewing") }
|
||||
@@ -30,18 +29,27 @@ class Budget < ActiveRecord::Base
|
||||
scope :balloting, -> { where(phase: "balloting") }
|
||||
scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") }
|
||||
scope :finished, -> { where(phase: "finished") }
|
||||
|
||||
scope :open, -> { where.not(phase: "finished") }
|
||||
|
||||
def self.current
|
||||
where.not(phase: "drafting").last
|
||||
where.not(phase: "drafting").order(:created_at).last
|
||||
end
|
||||
|
||||
def current_phase
|
||||
phases.send(phase)
|
||||
end
|
||||
|
||||
def description
|
||||
send("description_#{phase}").try(:html_safe)
|
||||
description_for_phase(phase)
|
||||
end
|
||||
|
||||
def self.description_max_length
|
||||
2000
|
||||
def description_for_phase(phase)
|
||||
if phases.exists? && phases.send(phase).description.present?
|
||||
phases.send(phase).description
|
||||
else
|
||||
send("description_#{phase}").try(:html_safe)
|
||||
end
|
||||
end
|
||||
|
||||
def self.title_max_length
|
||||
@@ -85,7 +93,7 @@ class Budget < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def published_prices?
|
||||
PUBLISHED_PRICES_PHASES.include?(phase)
|
||||
Budget::Phase::PUBLISHED_PRICES_PHASES.include?(phase)
|
||||
end
|
||||
|
||||
def balloting_process?
|
||||
@@ -144,12 +152,25 @@ class Budget < ActiveRecord::Base
|
||||
|
||||
private
|
||||
|
||||
def sanitize_descriptions
|
||||
s = WYSIWYGSanitizer.new
|
||||
PHASES.each do |phase|
|
||||
sanitized = s.sanitize(send("description_#{phase}"))
|
||||
send("description_#{phase}=", sanitized)
|
||||
end
|
||||
def sanitize_descriptions
|
||||
s = WYSIWYGSanitizer.new
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
sanitized = s.sanitize(send("description_#{phase}"))
|
||||
send("description_#{phase}=", sanitized)
|
||||
end
|
||||
end
|
||||
|
||||
def generate_phases
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
Budget::Phase.create(
|
||||
budget: self,
|
||||
kind: phase,
|
||||
prev_phase: phases&.last,
|
||||
starts_at: phases&.last&.ends_at || Date.current,
|
||||
ends_at: (phases&.last&.ends_at || Date.current) + 1.month
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
86
app/models/budget/phase.rb
Normal file
86
app/models/budget/phase.rb
Normal file
@@ -0,0 +1,86 @@
|
||||
class Budget
|
||||
class Phase < ActiveRecord::Base
|
||||
PHASE_KINDS = %w(drafting accepting reviewing selecting valuating publishing_prices balloting
|
||||
reviewing_ballots finished).freeze
|
||||
PUBLISHED_PRICES_PHASES = %w(publishing_prices balloting reviewing_ballots finished).freeze
|
||||
DESCRIPTION_MAX_LENGTH = 2000
|
||||
|
||||
belongs_to :budget
|
||||
belongs_to :next_phase, class_name: 'Budget::Phase', foreign_key: :next_phase_id
|
||||
has_one :prev_phase, class_name: 'Budget::Phase', foreign_key: :next_phase_id
|
||||
|
||||
validates :budget, presence: true
|
||||
validates :kind, presence: true, uniqueness: { scope: :budget }, inclusion: { in: PHASE_KINDS }
|
||||
validates :description, length: { maximum: DESCRIPTION_MAX_LENGTH }
|
||||
validate :invalid_dates_range?
|
||||
validate :prev_phase_dates_valid?
|
||||
validate :next_phase_dates_valid?
|
||||
|
||||
before_validation :sanitize_description
|
||||
|
||||
after_save :adjust_date_ranges
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :published, -> { enabled.where.not(kind: 'drafting') }
|
||||
scope :drafting, -> { find_by_kind('drafting') }
|
||||
scope :accepting, -> { find_by_kind('accepting')}
|
||||
scope :reviewing, -> { find_by_kind('reviewing')}
|
||||
scope :selecting, -> { find_by_kind('selecting')}
|
||||
scope :valuating, -> { find_by_kind('valuating')}
|
||||
scope :publishing_prices, -> { find_by_kind('publishing_prices')}
|
||||
scope :balloting, -> { find_by_kind('balloting')}
|
||||
scope :reviewing_ballots, -> { find_by_kind('reviewing_ballots')}
|
||||
scope :finished, -> { find_by_kind('finished')}
|
||||
|
||||
def next_enabled_phase
|
||||
next_phase&.enabled? ? next_phase : next_phase&.next_enabled_phase
|
||||
end
|
||||
|
||||
def prev_enabled_phase
|
||||
prev_phase&.enabled? ? prev_phase : prev_phase&.prev_enabled_phase
|
||||
end
|
||||
|
||||
def adjust_date_ranges
|
||||
if enabled?
|
||||
next_enabled_phase&.update_column(:starts_at, ends_at)
|
||||
prev_enabled_phase&.update_column(:ends_at, starts_at)
|
||||
elsif enabled_changed?
|
||||
next_enabled_phase&.update_column(:starts_at, starts_at)
|
||||
end
|
||||
end
|
||||
|
||||
def invalid_dates_range?
|
||||
if starts_at.present? && ends_at.present? && starts_at >= ends_at
|
||||
errors.add(:starts_at, I18n.t('budgets.phases.errors.dates_range_invalid'))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prev_phase_dates_valid?
|
||||
if enabled? && starts_at.present? && prev_enabled_phase.present?
|
||||
prev_enabled_phase.assign_attributes(ends_at: starts_at)
|
||||
if prev_enabled_phase.invalid_dates_range?
|
||||
phase_name = I18n.t("budgets.phase.#{prev_enabled_phase.kind}")
|
||||
error = I18n.t('budgets.phases.errors.prev_phase_dates_invalid', phase_name: phase_name)
|
||||
errors.add(:starts_at, error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def next_phase_dates_valid?
|
||||
if enabled? && ends_at.present? && next_enabled_phase.present?
|
||||
next_enabled_phase.assign_attributes(starts_at: ends_at)
|
||||
if next_enabled_phase.invalid_dates_range?
|
||||
phase_name = I18n.t("budgets.phase.#{next_enabled_phase.kind}")
|
||||
error = I18n.t('budgets.phases.errors.next_phase_dates_invalid', phase_name: phase_name)
|
||||
errors.add(:ends_at, error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_description
|
||||
self.description = WYSIWYGSanitizer.new.sanitize(description)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<%= f.text_field :name, maxlength: Budget.title_max_length %>
|
||||
|
||||
<% Budget::PHASES.each do |phase| %>
|
||||
<% Budget::Phase::PHASE_KINDS.each do |phase| %>
|
||||
<div class="margin-top">
|
||||
<%= f.cktext_area "description_#{phase}", maxlength: Budget.description_max_length, ckeditor: { language: I18n.locale } %>
|
||||
<%= f.cktext_area "description_#{phase}", maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH, ckeditor: { language: I18n.locale } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -3,43 +3,158 @@
|
||||
<%= render "shared/canonical", href: budgets_url %>
|
||||
<% end %>
|
||||
|
||||
<%= render "shared/section_header", i18n_namespace: "budgets.index.section_header", image: "budgets" %>
|
||||
<%= render_map(nil, "budgets", false, nil, @budgets_coordinates) %>
|
||||
<div class="row margin-top">
|
||||
<div class="small-12 medium-9 column">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= Budget.human_attribute_name(:name) %></th>
|
||||
<th scope="col"><%= Budget.human_attribute_name(:phase) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @budgets.each do |budget| %>
|
||||
<% if budget_published?(budget) %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= link_to budget.name, budget %>
|
||||
</td>
|
||||
<td>
|
||||
<%= budget.translated_phase %>
|
||||
</td>
|
||||
</tr>
|
||||
<div id="budget_heading" class="expanded budget no-margin-top">
|
||||
<div class="row" data-equalizer data-equalizer-on="medium">
|
||||
<div class="small-12 medium-9 column padding" data-equalizer-watch>
|
||||
|
||||
<h1><%= @budget.name %></h1>
|
||||
|
||||
<%= safe_html_with_links @budget.description %>
|
||||
<%= link_to t("budgets.index.section_header.help"), "#section_help" %>
|
||||
</div>
|
||||
<div class="small-12 medium-3 column info padding" data-equalizer-watch>
|
||||
<p>
|
||||
<% published_phases = @budget.phases.published %>
|
||||
<% current_phase_number = published_phases.index(@budget.current_phase) + 1 || 0 %>
|
||||
<% phases_progress_numbers = "(#{current_phase_number}/#{published_phases.count})" %>
|
||||
<strong><%= t('budgets.show.phase') %> <%= phases_progress_numbers %></strong>
|
||||
</p>
|
||||
<h2><%= t("budgets.phase.#{@budget.phase}") %></h2>
|
||||
|
||||
<%= link_to t("budgets.index.section_header.all_phases"), "#all_phases" %>
|
||||
|
||||
<% if @budget.accepting? %>
|
||||
<% if current_user %>
|
||||
<% if current_user.level_two_or_three_verified? %>
|
||||
<%= link_to t("budgets.investments.index.sidebar.create"),
|
||||
new_budget_investment_path(@budget),
|
||||
class: "button margin-top expanded" %>
|
||||
<% else %>
|
||||
<div class="callout warning margin-top">
|
||||
<%= t("budgets.investments.index.sidebar.verified_only",
|
||||
verify: link_to(t("budgets.investments.index.sidebar.verify_account"),
|
||||
verification_path)).html_safe %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="callout primary margin-top">
|
||||
<%= t("budgets.investments.index.sidebar.not_logged_in",
|
||||
sign_in: link_to(t("budgets.investments.index.sidebar.sign_in"),
|
||||
new_user_session_path),
|
||||
sign_up: link_to(t("budgets.investments.index.sidebar.sign_up"),
|
||||
new_user_registration_path)).html_safe %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="section_help" class="margin" data-magellan-target="section_help">
|
||||
<p class="lead">
|
||||
<strong><%= t("budgets.index.section_footer.title") %></strong>
|
||||
</p>
|
||||
<p><%= t("budgets.index.section_footer.description") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_1") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_2") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_3",
|
||||
org: link_to(setting['org_name'], new_user_registration_path)).html_safe %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_4") %></p>
|
||||
|
||||
<% if @budget.finished? || (@budget.balloting? && can?(:read_results, @budget)) %>
|
||||
<%= link_to t("budgets.show.see_results"),
|
||||
budget_results_path(@budget, heading_id: @budget.headings.first),
|
||||
class: "button margin-top expanded" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="budget_info">
|
||||
<div class="row margin-top">
|
||||
<div class="small-12 column">
|
||||
|
||||
<div id="groups_and_headings" class="groups-and-headings">
|
||||
<% @budget.groups.each do |group| %>
|
||||
<h2><%= group.name %></h2>
|
||||
<ul class="no-bullet">
|
||||
<% group.headings.each do |heading| %>
|
||||
<li class="heading small-12 medium-4 large-2">
|
||||
<%= link_to budget_investments_path(@budget.id, heading_id: heading.id) do %>
|
||||
<%= heading.name %>
|
||||
<span><%= @budget.formatted_heading_price(heading) %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="map">
|
||||
<h3><%= t("budgets.index.map") %></h3>
|
||||
<%= render_map(nil, "budgets", false, nil, @budgets_coordinates) %>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<%= link_to budget_investments_path(@budget.id) do %>
|
||||
<small><%= t("budgets.index.investment_proyects") %></small>
|
||||
<% end %><br>
|
||||
<%= link_to budget_investments_path(budget_id: @budget.id, filter: 'unfeasible') do %>
|
||||
<small><%= t("budgets.index.unfeasible_investment_proyects") %></small>
|
||||
<% end %><br>
|
||||
<%= link_to budget_investments_path(budget_id: @budget.id, filter: 'unselected') do %>
|
||||
<small><%= t("budgets.index.not_selected_investment_proyects") %></small>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<div id="all_phases">
|
||||
<h2><%= t("budgets.index.all_phases") %></h2>
|
||||
<!--
|
||||
Pending to include here the list of phases from
|
||||
https://github.com/consul/consul/issues/2289
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row margin-top">
|
||||
<div class="small-12 medium-9 column">
|
||||
<ul class="no-bullet submenu">
|
||||
<li class="inline-block">
|
||||
<%= link_to "#other_budgets", class: "active" do %>
|
||||
<h2>
|
||||
<%= t("budgets.index.finished_budgets") %>
|
||||
</h2>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="budget-investments-list">
|
||||
<% @budgets.each do |budget| %>
|
||||
<% if budget_published?(budget) %>
|
||||
<div class="budget-investment clear">
|
||||
<div class="panel">
|
||||
<div class="row" data-equalizer data-equalizer-on="medium">
|
||||
<div class="small-9 column" data-equalizer-watch>
|
||||
<h3><%= budget.name %></h3>
|
||||
<%= budget.description %>
|
||||
</div>
|
||||
<div class="small-3 column table" data-equalizer-watch>
|
||||
<div class="table-cell align-middle">
|
||||
<%= link_to t("budgets.index.see_results"),
|
||||
budget_results_path(budget.id),
|
||||
class: "button expanded" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="small-12 column">
|
||||
<div id="section_help" class="margin" data-magellan-target="section_help">
|
||||
<p class="lead">
|
||||
<strong><%= t("budgets.index.section_footer.title") %></strong>
|
||||
</p>
|
||||
<p><%= t("budgets.index.section_footer.description") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_1") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_2") %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_3",
|
||||
org: link_to(setting['org_name'], new_user_registration_path)).html_safe %></p>
|
||||
<p><%= t("budgets.index.section_footer.help_text_4") %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<% provide :title, t("budgets.results.page_title", budget: @budget.name) %>
|
||||
<% content_for :meta_description do %><%= @budget.description_finished %><% end %>
|
||||
<% content_for :meta_description do %><%= @budget.description_for_phase('finished') %><% end %>
|
||||
<% provide :social_media_meta_tags do %>
|
||||
<%= render "shared/social_media_meta_tags",
|
||||
social_url: budget_results_url(@budget),
|
||||
social_title: @budget.name,
|
||||
social_description: @budget.description_finished %>
|
||||
social_description: @budget.description_for_phase('finished') %>
|
||||
<% end %>
|
||||
<% content_for :canonical do %>
|
||||
<%= render "shared/canonical", href: budget_results_url(@budget) %>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="small-12 medium-3 column table" data-equalizer-watch>
|
||||
<div class="table-cell aling-middle">
|
||||
<div class="table-cell align-middle">
|
||||
<%= link_to poll, class: "button hollow expanded" do %>
|
||||
<% if poll.expired? %>
|
||||
<%= t("polls.index.participate_button_expired") %>
|
||||
|
||||
@@ -79,7 +79,11 @@
|
||||
<div class="row">
|
||||
<div class="small-12 medium-8 column">
|
||||
<%= f.label :valuation_finished do %>
|
||||
<%= f.check_box :valuation_finished, title: t('valuation.budget_investments.edit.valuation_finished'), label: false %>
|
||||
<%= f.check_box :valuation_finished,
|
||||
title: t('valuation.budget_investments.edit.valuation_finished'),
|
||||
label: false, id: 'js-investment-report-alert',
|
||||
"data-alert": t("valuation.budget_investments.edit.valuation_finished_alert"),
|
||||
"data-not-feasible-alert": t("valuation.budget_investments.edit.not_feasible_alert") %>
|
||||
<span class="checkbox"><%= t("valuation.budget_investments.edit.valuation_finished") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,14 @@ en:
|
||||
icon_alt: Participatory budgets icon
|
||||
title: Participatory budgets
|
||||
help: Help about participatory budgets
|
||||
all_phases: See all phases
|
||||
all_phases: Budget investment's phases
|
||||
map: Budget investments' proposals located geographically
|
||||
investment_proyects: List of all investment projects
|
||||
unfeasible_investment_proyects: List of all unfeasible investment projects
|
||||
not_selected_investment_proyects: List of all investment projects not selected for balloting
|
||||
finished_budgets: Finished participatory budgets
|
||||
see_results: See results
|
||||
section_footer:
|
||||
title: Help about participatory budgets
|
||||
description: With the participatory budgets the citizens decide to which projects presented by the neighbors is destined a part of the municipal budget.
|
||||
@@ -158,3 +166,8 @@ en:
|
||||
accepted: "Accepted spending proposal: "
|
||||
discarded: "Discarded spending proposal: "
|
||||
incompatibles: Incompatibles
|
||||
phases:
|
||||
errors:
|
||||
dates_range_invalid: "Start date can't be equal or later than End date"
|
||||
prev_phase_dates_invalid: "Start date must be later than the start date of the previous enabled phase (%{phase_name})"
|
||||
next_phase_dates_invalid: "End date must be earlier than the end date of the next enabled phase (%{phase_name})"
|
||||
@@ -68,6 +68,8 @@ en:
|
||||
undefined_feasible: Pending
|
||||
feasible_explanation_html: Feasibility explanation
|
||||
valuation_finished: Valuation finished
|
||||
valuation_finished_alert: "Are you sure you want to mark this report as completed? If you do it, it can no longer be modified."
|
||||
not_feasible_alert: "An email will be sent immediately to the author of the project with the report of unfeasibility."
|
||||
duration_html: Time scope
|
||||
internal_comments_html: Internal comments
|
||||
save: Save changes
|
||||
@@ -121,4 +123,4 @@ en:
|
||||
internal_comments_html: Internal comments
|
||||
save: Save changes
|
||||
notice:
|
||||
valuate: "Dossier updated"
|
||||
valuate: "Dossier updated"
|
||||
|
||||
@@ -44,6 +44,14 @@ es:
|
||||
icon_alt: Icono de Presupuestos participativos
|
||||
title: Presupuestos participativos
|
||||
help: Ayuda sobre presupuestos participativos
|
||||
all_phases: Ver todas las fases
|
||||
all_phases: Fases de los presupuestos participativos
|
||||
map: Propuestas de los presupuestos participativos localizables geográficamente
|
||||
investment_proyects: Ver lista completa de proyectos de inversión
|
||||
unfeasible_investment_proyects: Ver lista de proyectos de inversión inviables
|
||||
not_selected_investment_proyects: Ver lista de proyectos de inversión no seleccionados para la votación final
|
||||
finished_budgets: Presupuestos participativos terminados
|
||||
see_results: Ver resultados
|
||||
section_footer:
|
||||
title: Ayuda sobre presupuestos participativos
|
||||
description: Con los presupuestos participativos la ciudadanía decide a qué proyectos presentados por los vecinos y vecinas va destinada una parte del presupuesto municipal.
|
||||
@@ -158,3 +166,8 @@ es:
|
||||
accepted: 'Propuesta de inversión aceptada: '
|
||||
discarded: 'Propuesta de inversión descartada: '
|
||||
incompatibles: Incompatibles
|
||||
phases:
|
||||
errors:
|
||||
dates_range_invalid: "La fecha de comienzo no puede ser igual o superior a la de finalización"
|
||||
prev_phase_dates_invalid: "La fecha de inicio debe ser posterior a la fecha de inicio de la anterior fase habilitada (%{phase_name})"
|
||||
next_phase_dates_invalid: "La fecha de fin debe ser anterior a la fecha de fin de la siguiente fase habilitada (%{phase_name}) "
|
||||
@@ -68,6 +68,8 @@ es:
|
||||
undefined_feasible: Sin decidir
|
||||
feasible_explanation_html: Informe de inviabilidad <small>(en caso de que lo sea, dato público)</small>
|
||||
valuation_finished: Informe finalizado
|
||||
valuation_finished_alert: "¿Estás seguro/a de querer marcar este informe como completado? Una vez hecho, no se puede deshacer la acción."
|
||||
not_feasible_alert: "Un email será enviado inmediatamente al autor del proyecto con el informe de inviabilidad."
|
||||
duration_html: Plazo de ejecución <small>(opcional, dato no público)</small>
|
||||
internal_comments_html: Comentarios y observaciones <small>(para responsables internos, dato no público)</small>
|
||||
save: Guardar Cambios
|
||||
|
||||
@@ -401,8 +401,8 @@ section "Creating Valuation Assignments" do
|
||||
end
|
||||
|
||||
section "Creating Budgets" do
|
||||
Budget::PHASES.each_with_index do |phase, i|
|
||||
descriptions = Hash[Budget::PHASES.map do |p|
|
||||
Budget::Phase::PHASE_KINDS.each_with_index do |phase, i|
|
||||
descriptions = Hash[Budget::Phase::PHASE_KINDS.map do |p|
|
||||
["description_#{p}",
|
||||
"<p>#{Faker::Lorem.paragraphs(2).join('</p><p>')}</p>"]
|
||||
end]
|
||||
|
||||
14
db/migrate/20180112123641_create_budget_phases.rb
Normal file
14
db/migrate/20180112123641_create_budget_phases.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class CreateBudgetPhases < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :budget_phases do |t|
|
||||
t.references :budget
|
||||
t.references :next_phase, index: true
|
||||
t.string :kind, null: false, index: true
|
||||
t.text :summary
|
||||
t.text :description
|
||||
t.datetime :starts_at, index: true
|
||||
t.datetime :ends_at, index: true
|
||||
t.boolean :enabled, default: true
|
||||
end
|
||||
end
|
||||
end
|
||||
18
db/schema.rb
18
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180109175851) do
|
||||
ActiveRecord::Schema.define(version: 20180112123641) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -170,6 +170,22 @@ ActiveRecord::Schema.define(version: 20180109175851) do
|
||||
add_index "budget_investments", ["heading_id"], name: "index_budget_investments_on_heading_id", using: :btree
|
||||
add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin
|
||||
|
||||
create_table "budget_phases", force: :cascade do |t|
|
||||
t.integer "budget_id"
|
||||
t.integer "next_phase_id"
|
||||
t.string "kind", null: false
|
||||
t.text "summary"
|
||||
t.text "description"
|
||||
t.datetime "starts_at"
|
||||
t.datetime "ends_at"
|
||||
t.boolean "enabled", default: true
|
||||
end
|
||||
|
||||
add_index "budget_phases", ["ends_at"], name: "index_budget_phases_on_ends_at", using: :btree
|
||||
add_index "budget_phases", ["kind"], name: "index_budget_phases_on_kind", using: :btree
|
||||
add_index "budget_phases", ["next_phase_id"], name: "index_budget_phases_on_next_phase_id", using: :btree
|
||||
add_index "budget_phases", ["starts_at"], name: "index_budget_phases_on_starts_at", using: :btree
|
||||
|
||||
create_table "budget_reclassified_votes", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "investment_id"
|
||||
|
||||
@@ -13,4 +13,22 @@ namespace :budgets do
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
namespace :phases do
|
||||
desc "Generates Phases for existing Budgets without them & migrates description_* attributes"
|
||||
task generate_missing: :environment do
|
||||
Budget.where.not(id: Budget::Phase.all.pluck(:budget_id).uniq.compact).each do |budget|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
Budget::Phase.create(
|
||||
budget: budget,
|
||||
kind: phase,
|
||||
description: budget.send("description_#{phase}"),
|
||||
prev_phase: budget.phases&.last,
|
||||
starts_at: budget.phases&.last&.ends_at || Date.current,
|
||||
ends_at: (budget.phases&.last&.ends_at || Date.current) + 1.month
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -341,7 +341,16 @@ FactoryBot.define do
|
||||
feasibility "feasible"
|
||||
valuation_finished true
|
||||
end
|
||||
end
|
||||
|
||||
factory :budget_phase, class: 'Budget::Phase' do
|
||||
budget
|
||||
kind :balloting
|
||||
summary Faker::Lorem.sentence(3)
|
||||
description Faker::Lorem.sentence(10)
|
||||
starts_at Date.yesterday
|
||||
ends_at Date.tomorrow
|
||||
enabled true
|
||||
end
|
||||
|
||||
factory :image do
|
||||
@@ -378,7 +387,7 @@ FactoryBot.define do
|
||||
association :investment, factory: :budget_investment
|
||||
sequence(:title) { |n| "Budget investment milestone #{n} title" }
|
||||
description 'Milestone description'
|
||||
publication_date Time.zone.today
|
||||
publication_date Date.current
|
||||
end
|
||||
|
||||
factory :vote do
|
||||
|
||||
@@ -40,12 +40,12 @@ feature 'Admin budget investment milestones' do
|
||||
click_link 'Create new milestone'
|
||||
|
||||
fill_in 'budget_investment_milestone_description', with: 'New description milestone'
|
||||
fill_in 'budget_investment_milestone_publication_date', with: Time.zone.today
|
||||
fill_in 'budget_investment_milestone_publication_date', with: Date.current
|
||||
|
||||
click_button 'Create milestone'
|
||||
|
||||
expect(page).to have_content 'New description milestone'
|
||||
expect(page).to have_content Time.zone.today
|
||||
expect(page).to have_content Date.current
|
||||
end
|
||||
|
||||
scenario "Show validation errors on milestone form" do
|
||||
@@ -78,13 +78,13 @@ feature 'Admin budget investment milestones' do
|
||||
expect(page).to have_css("img[alt='#{milestone.image.title}']")
|
||||
|
||||
fill_in 'budget_investment_milestone_description', with: 'Changed description'
|
||||
fill_in 'budget_investment_milestone_publication_date', with: Time.zone.today.to_date
|
||||
fill_in 'budget_investment_milestone_publication_date', with: Date.current
|
||||
fill_in 'budget_investment_milestone_documents_attributes_0_title', with: 'New document title'
|
||||
|
||||
click_button 'Update milestone'
|
||||
|
||||
expect(page).to have_content 'Changed description'
|
||||
expect(page).to have_content Time.zone.today.to_date
|
||||
expect(page).to have_content Date.current
|
||||
expect(page).to have_link 'Show image'
|
||||
expect(page).to have_link 'New document title'
|
||||
end
|
||||
|
||||
@@ -508,6 +508,50 @@ feature 'Admin budget investments' do
|
||||
expect(page).not_to have_content "Refugees, Solidarity"
|
||||
end
|
||||
|
||||
scenario "Shows alert when 'Valuation finished' is checked", :js do
|
||||
budget_investment = create(:budget_investment)
|
||||
|
||||
visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment)
|
||||
click_link 'Edit dossier'
|
||||
|
||||
expect(page).to have_content 'Valuation finished'
|
||||
|
||||
find_field('budget_investment[valuation_finished]').click
|
||||
|
||||
page.accept_confirm("Are you sure you want to mark this report as completed? If you do it, it can no longer be modified.")
|
||||
|
||||
expect(page).to have_field('budget_investment[valuation_finished]', checked: true)
|
||||
end
|
||||
|
||||
scenario "Shows alert with unfeasible status when 'Valuation finished' is checked", :js do
|
||||
budget_investment = create(:budget_investment)
|
||||
|
||||
visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment)
|
||||
click_link 'Edit dossier'
|
||||
|
||||
expect(page).to have_content 'Valuation finished'
|
||||
|
||||
find_field('budget_investment_feasibility_unfeasible').click
|
||||
find_field('budget_investment[valuation_finished]').click
|
||||
|
||||
page.accept_confirm("Are you sure you want to mark this report as completed? If you do it, it can no longer be modified.\nAn email will be sent immediately to the author of the project with the report of unfeasibility.")
|
||||
|
||||
expect(page).to have_field('budget_investment[valuation_finished]', checked: true)
|
||||
end
|
||||
|
||||
scenario "Undoes check in 'Valuation finished' if user clicks 'cancel' on alert", :js do
|
||||
budget_investment = create(:budget_investment)
|
||||
|
||||
visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment)
|
||||
click_link 'Edit dossier'
|
||||
|
||||
dismiss_confirm do
|
||||
find_field('budget_investment[valuation_finished]').click
|
||||
end
|
||||
|
||||
expect(page).to have_field('budget_investment[valuation_finished]', checked: false)
|
||||
end
|
||||
|
||||
scenario "Errors on update" do
|
||||
budget_investment = create(:budget_investment)
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ feature 'Admin budgets' do
|
||||
click_link 'Delete'
|
||||
end
|
||||
|
||||
expect(page).to_not have_content 'District 1'
|
||||
expect(page).not_to have_content 'District 1'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -14,13 +14,13 @@ feature 'Admin shifts' do
|
||||
booth1 = create(:poll_booth)
|
||||
booth2 = create(:poll_booth)
|
||||
|
||||
shift1 = create(:poll_shift, officer: officer, booth: booth1, date: Time.zone.today)
|
||||
shift1 = create(:poll_shift, officer: officer, booth: booth1, date: Date.current)
|
||||
shift2 = create(:poll_shift, officer: officer, booth: booth2, date: Time.zone.tomorrow)
|
||||
|
||||
visit new_admin_booth_shift_path(booth1)
|
||||
|
||||
expect(page).to have_css(".shift", count: 1)
|
||||
expect(page).to have_content I18n.l(Time.zone.today, format: :long)
|
||||
expect(page).to have_content I18n.l(Date.current, format: :long)
|
||||
expect(page).to have_content officer.name
|
||||
|
||||
visit new_admin_booth_shift_path(booth2)
|
||||
@@ -98,11 +98,11 @@ feature 'Admin shifts' do
|
||||
assignment = create(:poll_booth_assignment, poll: poll, booth: booth)
|
||||
officer = create(:poll_officer)
|
||||
|
||||
shift1 = create(:poll_shift, :vote_collection_task, officer: officer, booth: booth, date: Time.zone.today)
|
||||
shift1 = create(:poll_shift, :vote_collection_task, officer: officer, booth: booth, date: Date.current)
|
||||
shift2 = create(:poll_shift, :recount_scrutiny_task, officer: officer, booth: booth, date: Time.zone.tomorrow)
|
||||
|
||||
vote_collection_dates = (Date.current..poll.ends_at.to_date).to_a
|
||||
.reject { |date| date == Time.zone.today }
|
||||
.reject { |date| date == Date.current }
|
||||
.map { |date| I18n.l(date, format: :long) }
|
||||
recount_scrutiny_dates = (poll.ends_at.to_date..poll.ends_at.to_date + 1.week).to_a
|
||||
.reject { |date| date == Time.zone.tomorrow }
|
||||
|
||||
@@ -7,8 +7,42 @@ feature 'Budgets' do
|
||||
|
||||
scenario 'Index' do
|
||||
budgets = create_list(:budget, 3)
|
||||
last_budget = budgets.last
|
||||
group1 = create(:budget_group, budget: last_budget)
|
||||
group2 = create(:budget_group, budget: last_budget)
|
||||
|
||||
heading1 = create(:budget_heading, group: group1)
|
||||
heading2 = create(:budget_heading, group: group2)
|
||||
|
||||
visit budgets_path
|
||||
budgets.each {|budget| expect(page).to have_link(budget.name)}
|
||||
|
||||
within("#budget_heading") do
|
||||
expect(page).to have_content(last_budget.name)
|
||||
expect(page).to have_content(last_budget.description)
|
||||
expect(page).to have_content("Actual phase (1/8)")
|
||||
expect(page).to have_content("Accepting projects")
|
||||
expect(page).to have_link 'Help about participatory budgets'
|
||||
expect(page).to have_link 'See all phases'
|
||||
end
|
||||
|
||||
last_budget.update_attributes(phase: 'publishing_prices')
|
||||
visit budgets_path
|
||||
|
||||
within("#budget_heading") do
|
||||
expect(page).to have_content("Actual phase (5/8)")
|
||||
end
|
||||
|
||||
within('#budget_info') do
|
||||
expect(page).to have_content group1.name
|
||||
expect(page).to have_content group2.name
|
||||
expect(page).to have_content heading1.name
|
||||
expect(page).to have_content last_budget.formatted_heading_price(heading1)
|
||||
expect(page).to have_content heading2.name
|
||||
expect(page).to have_content last_budget.formatted_heading_price(heading2)
|
||||
|
||||
expect(page).to have_content budgets.first.name
|
||||
expect(page).to have_content budgets[2].name
|
||||
end
|
||||
end
|
||||
|
||||
context 'Show' do
|
||||
@@ -70,6 +104,7 @@ feature 'Budgets' do
|
||||
background do
|
||||
logout
|
||||
budget.update(phase: 'drafting')
|
||||
create(:budget)
|
||||
end
|
||||
|
||||
context "Listed" do
|
||||
|
||||
@@ -426,7 +426,7 @@ feature 'Budget Investments' do
|
||||
context "When investment with price is selected" do
|
||||
|
||||
scenario "Price & explanation is shown when Budget is on published prices phase" do
|
||||
Budget::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
visit budget_investment_path(budget_id: budget.id, id: investment.id)
|
||||
|
||||
@@ -440,7 +440,7 @@ feature 'Budget Investments' do
|
||||
end
|
||||
|
||||
scenario "Price & explanation isn't shown when Budget is not on published prices phase" do
|
||||
(Budget::PHASES - Budget::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
(Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
budget.update(phase: phase)
|
||||
visit budget_investment_path(budget_id: budget.id, id: investment.id)
|
||||
|
||||
@@ -461,7 +461,7 @@ feature 'Budget Investments' do
|
||||
end
|
||||
|
||||
scenario "Price & explanation isn't shown for any Budget's phase" do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
visit budget_investment_path(budget_id: budget.id, id: investment.id)
|
||||
|
||||
@@ -570,7 +570,7 @@ feature 'Budget Investments' do
|
||||
|
||||
within("#tab-milestones") do
|
||||
expect(page).to have_content(milestone.description)
|
||||
expect(page).to have_content(Time.zone.today.to_date)
|
||||
expect(page).to have_content(Date.current)
|
||||
expect(page.find("#image_#{milestone.id}")['alt']).to have_content image.title
|
||||
expect(page).to have_link document.title
|
||||
end
|
||||
@@ -778,8 +778,7 @@ feature 'Budget Investments' do
|
||||
visit root_path
|
||||
|
||||
first(:link, "Participatory budgeting").click
|
||||
click_link budget.name
|
||||
click_link "Health"
|
||||
click_link "More hospitals"
|
||||
|
||||
within("#budget_investment_#{sp1.id}") do
|
||||
expect(page).to have_content sp1.title
|
||||
|
||||
@@ -65,7 +65,7 @@ feature 'Results' do
|
||||
end
|
||||
|
||||
scenario "If budget is in a phase different from finished results can't be accessed" do
|
||||
budget.update(phase: (Budget::PHASES - ['drafting', 'finished']).sample)
|
||||
budget.update(phase: (Budget::Phase::PHASE_KINDS - ['drafting', 'finished']).sample)
|
||||
visit budget_path(budget)
|
||||
expect(page).not_to have_link "See results"
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ feature 'Emails' do
|
||||
|
||||
choose 'budget_investment_feasibility_unfeasible'
|
||||
fill_in 'budget_investment_unfeasibility_explanation', with: 'This is not legal as stated in Article 34.9'
|
||||
check 'budget_investment_valuation_finished'
|
||||
find_field('budget_investment[valuation_finished]').click
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content "Dossier updated"
|
||||
|
||||
@@ -183,7 +183,7 @@ feature 'Tags' do
|
||||
let!(:investment3) { create(:budget_investment, heading: heading, tag_list: newer_tag) }
|
||||
|
||||
scenario 'Display user tags' do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
login_as(admin) if budget.drafting?
|
||||
@@ -197,7 +197,7 @@ feature 'Tags' do
|
||||
end
|
||||
|
||||
scenario "Filter by user tags" do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
if budget.balloting?
|
||||
@@ -230,7 +230,7 @@ feature 'Tags' do
|
||||
let!(:investment3) { create(:budget_investment, heading: heading, tag_list: tag_economia.name) }
|
||||
|
||||
scenario 'Display category tags' do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
login_as(admin) if budget.drafting?
|
||||
@@ -244,7 +244,7 @@ feature 'Tags' do
|
||||
end
|
||||
|
||||
scenario "Filter by category tags" do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
if budget.balloting?
|
||||
|
||||
@@ -345,7 +345,7 @@ feature 'Valuation budget investments' do
|
||||
visit valuation_budget_budget_investment_path(@budget, @investment)
|
||||
click_link 'Edit dossier'
|
||||
|
||||
check 'budget_investment_valuation_finished'
|
||||
find_field('budget_investment[valuation_finished]').click
|
||||
click_button 'Save changes'
|
||||
|
||||
visit valuation_budget_budget_investments_path(@budget)
|
||||
|
||||
@@ -30,8 +30,8 @@ feature 'Valuation budgets' do
|
||||
|
||||
visit valuation_budgets_path
|
||||
|
||||
expect(page).to_not have_content(budget1.name)
|
||||
expect(page).to_not have_content(budget2.name)
|
||||
expect(page).not_to have_content(budget1.name)
|
||||
expect(page).not_to have_content(budget2.name)
|
||||
expect(page).to have_content(budget3.name)
|
||||
end
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false in any other phase" do
|
||||
Budget::PHASES.reject {|phase| phase == "selecting"}.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.reject {|phase| phase == "selecting"}.each do |phase|
|
||||
budget = create(:budget, phase: phase)
|
||||
investment = create(:budget_investment, budget: budget)
|
||||
|
||||
@@ -159,7 +159,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false in any other phase" do
|
||||
Budget::PHASES.reject {|phase| phase == "valuating"}.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.reject {|phase| phase == "valuating"}.each do |phase|
|
||||
budget = create(:budget, phase: phase)
|
||||
investment = create(:budget_investment, budget: budget)
|
||||
|
||||
@@ -184,7 +184,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false in any other phase" do
|
||||
Budget::PHASES.reject {|phase| phase == "balloting"}.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.reject {|phase| phase == "balloting"}.each do |phase|
|
||||
budget = create(:budget, phase: phase)
|
||||
investment = create(:budget_investment, :selected, budget: budget)
|
||||
|
||||
@@ -200,7 +200,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns true for selected investments which budget's phase is publishing_prices or later" do
|
||||
Budget::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
expect(investment.should_show_price?).to eq(true)
|
||||
@@ -208,7 +208,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false in any other phase" do
|
||||
(Budget::PHASES - Budget::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
(Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
expect(investment.should_show_price?).to eq(false)
|
||||
@@ -235,7 +235,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns true for selected with price_explanation & budget in publishing_prices or later" do
|
||||
Budget::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
expect(investment.should_show_price_explanation?).to eq(true)
|
||||
@@ -243,7 +243,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false in any other phase" do
|
||||
(Budget::PHASES - Budget::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
(Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase|
|
||||
budget.update(phase: phase)
|
||||
|
||||
expect(investment.should_show_price_explanation?).to eq(false)
|
||||
@@ -785,7 +785,7 @@ describe Budget::Investment do
|
||||
end
|
||||
|
||||
it "returns false if budget is not balloting phase" do
|
||||
Budget::PHASES.reject {|phase| phase == "balloting"}.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.reject {|phase| phase == "balloting"}.each do |phase|
|
||||
budget.update(phase: phase)
|
||||
investment = create(:budget_investment, budget: budget)
|
||||
|
||||
|
||||
230
spec/models/budget/phase_spec.rb
Normal file
230
spec/models/budget/phase_spec.rb
Normal file
@@ -0,0 +1,230 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Budget::Phase do
|
||||
|
||||
let(:budget) { create(:budget) }
|
||||
let(:first_phase) { budget.phases.drafting }
|
||||
let(:second_phase) { budget.phases.accepting }
|
||||
let(:third_phase) { budget.phases.reviewing }
|
||||
let(:fourth_phase) { budget.phases.selecting }
|
||||
let(:final_phase) { budget.phases.finished}
|
||||
|
||||
before do
|
||||
first_phase.update_attributes(starts_at: Date.current - 3.days, ends_at: Date.current - 1.day)
|
||||
second_phase.update_attributes(starts_at: Date.current - 1.days, ends_at: Date.current + 1.day)
|
||||
third_phase.update_attributes(starts_at: Date.current + 1.days, ends_at: Date.current + 3.day)
|
||||
fourth_phase.update_attributes(starts_at: Date.current + 3.days, ends_at: Date.current + 5.day)
|
||||
end
|
||||
|
||||
describe "validates" do
|
||||
it "is not valid without a budget" do
|
||||
expect(build(:budget_phase, budget: nil)).not_to be_valid
|
||||
end
|
||||
|
||||
describe "kind validations" do
|
||||
it "is not valid without a kind" do
|
||||
expect(build(:budget_phase, kind: nil)).not_to be_valid
|
||||
end
|
||||
|
||||
it "is not valid with a kind not in valid budget phases" do
|
||||
expect(build(:budget_phase, kind: 'invalid_phase_kind')).not_to be_valid
|
||||
end
|
||||
|
||||
it "is not valid with the same kind as another budget's phase" do
|
||||
expect(build(:budget_phase, budget: budget)).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dates_range_valid?" do
|
||||
it "is valid when start & end dates are different & consecutive" do
|
||||
first_phase.update_attributes(starts_at: Date.today, ends_at: Date.tomorrow)
|
||||
|
||||
expect(first_phase).to be_valid
|
||||
end
|
||||
|
||||
it "is not valid when dates are equal" do
|
||||
first_phase.update_attributes(starts_at: Date.today, ends_at: Date.today)
|
||||
|
||||
expect(first_phase).not_to be_valid
|
||||
end
|
||||
|
||||
it "is not valid when start date is later than end date" do
|
||||
first_phase.update_attributes(starts_at: Date.tomorrow, ends_at: Date.today)
|
||||
|
||||
expect(first_phase).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#prev_phase_dates_valid?" do
|
||||
let(:error) do
|
||||
"Start date must be later than the start date of the previous enabled phase"\
|
||||
" (Draft (Not visible to the public))"
|
||||
end
|
||||
|
||||
it "is invalid when start date is same as previous enabled phase start date" do
|
||||
second_phase.assign_attributes(starts_at: second_phase.prev_enabled_phase.starts_at)
|
||||
|
||||
expect(second_phase).not_to be_valid
|
||||
expect(second_phase.errors.messages[:starts_at]).to include(error)
|
||||
end
|
||||
|
||||
it "is invalid when start date is earlier than previous enabled phase start date" do
|
||||
second_phase.assign_attributes(starts_at: second_phase.prev_enabled_phase.starts_at - 1.day)
|
||||
|
||||
expect(second_phase).not_to be_valid
|
||||
expect(second_phase.errors.messages[:starts_at]).to include(error)
|
||||
end
|
||||
|
||||
it "is valid when start date is in between previous enabled phase start & end dates" do
|
||||
second_phase.assign_attributes(starts_at: second_phase.prev_enabled_phase.starts_at + 1.day)
|
||||
|
||||
expect(second_phase).to be_valid
|
||||
end
|
||||
|
||||
it "is valid when start date is later than previous enabled phase end date" do
|
||||
second_phase.assign_attributes(starts_at: second_phase.prev_enabled_phase.ends_at + 1.day)
|
||||
|
||||
expect(second_phase).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#next_phase_dates_valid?" do
|
||||
let(:error) do
|
||||
"End date must be earlier than the end date of the next enabled phase (Reviewing projects)"
|
||||
end
|
||||
|
||||
it "is invalid when end date is same as next enabled phase end date" do
|
||||
second_phase.assign_attributes(ends_at: second_phase.next_enabled_phase.ends_at)
|
||||
|
||||
expect(second_phase).not_to be_valid
|
||||
expect(second_phase.errors.messages[:ends_at]).to include(error)
|
||||
end
|
||||
|
||||
it "is invalid when end date is later than next enabled phase end date" do
|
||||
second_phase.assign_attributes(ends_at: second_phase.next_enabled_phase.ends_at + 1.day)
|
||||
|
||||
expect(second_phase).not_to be_valid
|
||||
expect(second_phase.errors.messages[:ends_at]).to include(error)
|
||||
end
|
||||
|
||||
it "is valid when end date is in between next enabled phase start & end dates" do
|
||||
second_phase.assign_attributes(ends_at: second_phase.next_enabled_phase.ends_at - 1.day)
|
||||
|
||||
expect(second_phase).to be_valid
|
||||
end
|
||||
|
||||
it "is valid when end date is earlier than next enabled phase start date" do
|
||||
second_phase.assign_attributes(ends_at: second_phase.next_enabled_phase.starts_at - 1.day)
|
||||
|
||||
expect(second_phase).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#adjust_date_ranges" do
|
||||
let(:prev_enabled_phase) { second_phase.prev_enabled_phase }
|
||||
let(:next_enabled_phase) { second_phase.next_enabled_phase }
|
||||
|
||||
describe "when enabled" do
|
||||
it "adjusts previous enabled phase end date to its own start date" do
|
||||
expect(prev_enabled_phase.ends_at).to eq(second_phase.starts_at)
|
||||
end
|
||||
|
||||
it "adjusts next enabled phase start date to its own end date" do
|
||||
expect(next_enabled_phase.starts_at).to eq(second_phase.ends_at)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when being enabled" do
|
||||
before do
|
||||
second_phase.update_attributes(enabled: false,
|
||||
starts_at: Date.current,
|
||||
ends_at: Date.current + 2.days)
|
||||
end
|
||||
|
||||
it "adjusts previous enabled phase end date to its own start date" do
|
||||
expect{
|
||||
second_phase.update_attributes(enabled: true)
|
||||
}.to change{
|
||||
prev_enabled_phase.ends_at.to_date
|
||||
}.to(Date.current)
|
||||
end
|
||||
|
||||
it "adjusts next enabled phase start date to its own end date" do
|
||||
expect{
|
||||
second_phase.update_attributes(enabled: true)
|
||||
}.to change{
|
||||
next_enabled_phase.starts_at.to_date
|
||||
}.to(Date.current + 2.days)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when disabled" do
|
||||
before do
|
||||
second_phase.update_attributes(enabled: false)
|
||||
end
|
||||
|
||||
it "doesn't change previous enabled phase end date" do
|
||||
expect {
|
||||
second_phase.update_attributes(starts_at: Date.current,
|
||||
ends_at: Date.current + 2.days)
|
||||
}.not_to (change{ prev_enabled_phase.ends_at })
|
||||
end
|
||||
|
||||
it "doesn't change next enabled phase start date" do
|
||||
expect{
|
||||
second_phase.update_attributes(starts_at: Date.current,
|
||||
ends_at: Date.current + 2.days)
|
||||
}.not_to (change{ next_enabled_phase.starts_at })
|
||||
end
|
||||
end
|
||||
|
||||
describe "when being disabled" do
|
||||
it "doesn't adjust previous enabled phase end date to its own start date" do
|
||||
expect {
|
||||
second_phase.update_attributes(enabled: false,
|
||||
starts_at: Date.current,
|
||||
ends_at: Date.current + 2.days)
|
||||
}.not_to (change{ prev_enabled_phase.ends_at })
|
||||
end
|
||||
|
||||
it "adjusts next enabled phase start date to its own start date" do
|
||||
expect {
|
||||
second_phase.update_attributes(enabled: false,
|
||||
starts_at: Date.current,
|
||||
ends_at: Date.current + 2.days)
|
||||
}.to change{ next_enabled_phase.starts_at.to_date }.to(Date.current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "next & prev enabled phases" do
|
||||
before do
|
||||
second_phase.update_attributes(enabled: false)
|
||||
end
|
||||
|
||||
describe "#next_enabled_phase" do
|
||||
it "returns the right next enabled phase" do
|
||||
expect(first_phase.reload.next_enabled_phase).to eq(third_phase)
|
||||
expect(third_phase.reload.next_enabled_phase).to eq(fourth_phase)
|
||||
expect(final_phase.reload.next_enabled_phase).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#prev_enabled_phase" do
|
||||
it "returns the right previous enabled phase" do
|
||||
expect(first_phase.reload.prev_enabled_phase).to eq(nil)
|
||||
expect(third_phase.reload.prev_enabled_phase).to eq(first_phase)
|
||||
expect(fourth_phase.reload.prev_enabled_phase).to eq(third_phase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sanitize_description" do
|
||||
it "removes html entities from the description" do
|
||||
expect{
|
||||
first_phase.update_attributes(description: "<a>a</p> <javascript>javascript</javascript>")
|
||||
}.to change{ first_phase.description }.to('a javascript')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,8 @@ require 'rails_helper'
|
||||
|
||||
describe Budget do
|
||||
|
||||
let(:budget) { create(:budget) }
|
||||
|
||||
it_behaves_like "sluggable"
|
||||
|
||||
describe "name" do
|
||||
@@ -15,22 +17,40 @@ describe Budget do
|
||||
end
|
||||
|
||||
describe "description" do
|
||||
it "changes depending on the phase" do
|
||||
budget = create(:budget)
|
||||
describe "Without Budget::Phase associated" do
|
||||
before do
|
||||
budget.phases.destroy_all
|
||||
end
|
||||
|
||||
Budget::PHASES.each do |phase|
|
||||
budget.phase = phase
|
||||
expect(budget.description).to eq(budget.send("description_#{phase}"))
|
||||
expect(budget.description).to be_html_safe
|
||||
it "changes depending on the phase, falling back to budget description attributes" do
|
||||
Budget::Phase::PHASE_KINDS.each do |phase_kind|
|
||||
budget.phase = phase_kind
|
||||
expect(budget.description).to eq(budget.send("description_#{phase_kind}"))
|
||||
expect(budget.description).to be_html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "With associated Budget::Phases" do
|
||||
before do
|
||||
budget.phases.each do |phase|
|
||||
phase.description = phase.kind.humanize
|
||||
phase.save
|
||||
end
|
||||
end
|
||||
|
||||
it "changes depending on the phase" do
|
||||
Budget::Phase::PHASE_KINDS.each do |phase_kind|
|
||||
budget.phase = phase_kind
|
||||
expect(budget.description).to eq(phase_kind.humanize)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "phase" do
|
||||
let(:budget) { create(:budget) }
|
||||
|
||||
it "is validated" do
|
||||
Budget::PHASES.each do |phase|
|
||||
Budget::Phase::PHASE_KINDS.each do |phase|
|
||||
budget.phase = phase
|
||||
expect(budget).to be_valid
|
||||
end
|
||||
@@ -103,13 +123,13 @@ describe Budget do
|
||||
it "returns nil if there is only one budget and it is still in drafting phase" do
|
||||
budget = create(:budget, phase: "drafting")
|
||||
|
||||
expect(Budget.current).to eq(nil)
|
||||
expect(described_class.current).to eq(nil)
|
||||
end
|
||||
|
||||
it "returns the budget if there is only one and not in drafting phase" do
|
||||
budget = create(:budget, phase: "accepting")
|
||||
|
||||
expect(Budget.current).to eq(budget)
|
||||
expect(described_class.current).to eq(budget)
|
||||
end
|
||||
|
||||
it "returns the last budget created that is not in drafting phase" do
|
||||
@@ -118,7 +138,7 @@ describe Budget do
|
||||
current_budget = create(:budget, phase: "accepting", created_at: 1.month.ago)
|
||||
next_budget = create(:budget, phase: "drafting", created_at: 1.week.ago)
|
||||
|
||||
expect(Budget.current).to eq(current_budget)
|
||||
expect(described_class.current).to eq(current_budget)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -126,17 +146,15 @@ describe Budget do
|
||||
describe "#open" do
|
||||
|
||||
it "returns all budgets that are not in the finished phase" do
|
||||
phases = Budget::PHASES - ["finished"]
|
||||
phases.each do |phase|
|
||||
(Budget::Phase::PHASE_KINDS - ["finished"]).each do |phase|
|
||||
budget = create(:budget, phase: phase)
|
||||
expect(Budget.open).to include(budget)
|
||||
expect(described_class.open).to include(budget)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "heading_price" do
|
||||
let(:budget) { create(:budget) }
|
||||
let(:group) { create(:budget_group, budget: budget) }
|
||||
|
||||
it "returns the heading price if the heading provided is part of the budget" do
|
||||
@@ -150,8 +168,6 @@ describe Budget do
|
||||
end
|
||||
|
||||
describe "investments_orders" do
|
||||
let(:budget) { create(:budget) }
|
||||
|
||||
it "is random when accepting and reviewing" do
|
||||
budget.phase = 'accepting'
|
||||
expect(budget.investments_orders).to eq(['random'])
|
||||
@@ -173,5 +189,40 @@ describe Budget do
|
||||
expect(budget.investments_orders).to eq(['random', 'confidence_score'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#generate_phases" do
|
||||
let(:drafting_phase) { budget.phases.drafting }
|
||||
let(:accepting_phase) { budget.phases.accepting }
|
||||
let(:reviewing_phase) { budget.phases.reviewing }
|
||||
let(:selecting_phase) { budget.phases.selecting }
|
||||
let(:valuating_phase) { budget.phases.valuating }
|
||||
let(:publishing_prices_phase) { budget.phases.publishing_prices }
|
||||
let(:balloting_phase) { budget.phases.balloting }
|
||||
let(:reviewing_ballots_phase) { budget.phases.reviewing_ballots }
|
||||
let(:finished_phase) { budget.phases.finished }
|
||||
|
||||
it "generates all phases linked in correct order" do
|
||||
expect(budget.phases.count).to eq(Budget::Phase::PHASE_KINDS.count)
|
||||
|
||||
expect(drafting_phase.next_phase).to eq(accepting_phase)
|
||||
expect(accepting_phase.next_phase).to eq(reviewing_phase)
|
||||
expect(reviewing_phase.next_phase).to eq(selecting_phase)
|
||||
expect(selecting_phase.next_phase).to eq(valuating_phase)
|
||||
expect(valuating_phase.next_phase).to eq(publishing_prices_phase)
|
||||
expect(publishing_prices_phase.next_phase).to eq(balloting_phase)
|
||||
expect(balloting_phase.next_phase).to eq(reviewing_ballots_phase)
|
||||
expect(reviewing_ballots_phase.next_phase).to eq(finished_phase)
|
||||
expect(finished_phase.next_phase).to eq(nil)
|
||||
|
||||
expect(drafting_phase.prev_phase).to eq(nil)
|
||||
expect(accepting_phase.prev_phase).to eq(drafting_phase)
|
||||
expect(reviewing_phase.prev_phase).to eq(accepting_phase)
|
||||
expect(selecting_phase.prev_phase).to eq(reviewing_phase)
|
||||
expect(valuating_phase.prev_phase).to eq(selecting_phase)
|
||||
expect(publishing_prices_phase.prev_phase).to eq(valuating_phase)
|
||||
expect(balloting_phase.prev_phase).to eq(publishing_prices_phase)
|
||||
expect(reviewing_ballots_phase.prev_phase).to eq(balloting_phase)
|
||||
expect(finished_phase.prev_phase).to eq(reviewing_ballots_phase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user