Merge branch 'master' into budget_map

This commit is contained in:
BertoCQ
2018-01-18 00:36:43 +01:00
committed by GitHub
41 changed files with 868 additions and 142 deletions

View File

@@ -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.

View File

@@ -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(){

View 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);

View File

@@ -368,7 +368,7 @@ a {
vertical-align: top;
}
.aling-middle {
.align-middle {
vertical-align: middle;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -19,7 +19,7 @@ class Management::BudgetsController < Management::BaseController
end
def print_investments
@budget = Budget.current
@budget = current_budget
end
private

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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 %>

View File

@@ -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>

View File

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

View File

@@ -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") %>

View File

@@ -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>

View File

@@ -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})"

View File

@@ -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"

View File

@@ -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}) "

View File

@@ -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

View File

@@ -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]

View 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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?

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View 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

View File

@@ -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