diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index bc9d05e48..4f6d1f7a4 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -168,7 +168,7 @@ body.admin {
clear: both;
.checkbox {
- font-size: rem-calc(12);
+ font-size: $small-font-size;
}
}
diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss
index 26931ef86..98cb62258 100644
--- a/app/assets/stylesheets/layout.scss
+++ b/app/assets/stylesheets/layout.scss
@@ -641,6 +641,10 @@ form {
line-height: $line-height;
}
+ fieldset legend {
+ font-weight: bold;
+ }
+
input, textarea {
height: 48px\9;
line-height: 48px\9;
@@ -648,13 +652,28 @@ form {
width: 100%\9;
}
- input[type="checkbox"] {
+ input[type="checkbox"],
+ input[type="radio"] {
height: auto\9;
line-height: inherit\9;
width: auto\9;
}
- input[type]:not([type=submit]):not([type=file]):not([type=checkbox]) {
+ input[type="radio"] {
+ height: $line-height !important;
+ vertical-align: top;
+ width: 18px\9;
+
+ + label {
+ font-weight: normal;
+ }
+
+ &:checked + label {
+ font-weight: bold;
+ }
+ }
+
+ input[type]:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]) {
background: $input-bg;
height: $line-height*2;
margin-bottom: rem-calc(16);
diff --git a/app/controllers/valuation/base_controller.rb b/app/controllers/valuation/base_controller.rb
new file mode 100644
index 000000000..fb42fb694
--- /dev/null
+++ b/app/controllers/valuation/base_controller.rb
@@ -0,0 +1,15 @@
+class Valuation::BaseController < ApplicationController
+ layout 'admin'
+
+ before_action :authenticate_user!
+ before_action :verify_valuator
+
+ skip_authorization_check
+
+ private
+
+ def verify_valuator
+ raise CanCan::AccessDenied unless current_user.try(:valuator?) || current_user.try(:administrator?)
+ end
+
+end
diff --git a/app/controllers/valuation/spending_proposals_controller.rb b/app/controllers/valuation/spending_proposals_controller.rb
new file mode 100644
index 000000000..f5074ab1e
--- /dev/null
+++ b/app/controllers/valuation/spending_proposals_controller.rb
@@ -0,0 +1,38 @@
+class Valuation::SpendingProposalsController < Valuation::BaseController
+ include FeatureFlags
+ feature_flag :spending_proposals
+
+ before_action :restrict_access_to_assigned_items, only: [:show, :edit, :valuate]
+
+ has_filters %w{valuating valuation_finished}, only: :index
+
+ load_and_authorize_resource
+
+ def index
+ if current_user.valuator?
+ @spending_proposals = SpendingProposal.search(params_for_current_valuator, @current_filter).order(created_at: :desc).page(params[:page])
+ else
+ @spending_proposals = SpendingProposal.none.page(params[:page])
+ end
+ end
+
+ def valuate
+ @spending_proposal.update_attributes(valuation_params)
+ redirect_to valuation_spending_proposal_path(@spending_proposal), notice: t('valuation.spending_proposals.notice.valuate')
+ end
+
+ private
+
+ def valuation_params
+ params.require(:spending_proposal).permit(:price, :price_first_year, :price_explanation, :feasible, :feasible_explanation, :time_scope, :valuation_finished, :internal_comments)
+ end
+
+ def params_for_current_valuator
+ params.merge({valuator_id: current_user.valuator.id})
+ end
+
+ def restrict_access_to_assigned_items
+ raise ActionController::RoutingError.new('Not Found') unless current_user.administrator? || ValuationAssignment.exists?(spending_proposal_id: params[:id], valuator_id: current_user.valuator.id)
+ end
+
+end
diff --git a/app/helpers/text_with_links_helper.rb b/app/helpers/text_with_links_helper.rb
index fe726c51e..ac965dafa 100644
--- a/app/helpers/text_with_links_helper.rb
+++ b/app/helpers/text_with_links_helper.rb
@@ -11,4 +11,8 @@ module TextWithLinksHelper
Rinku.auto_link(html, :all, 'target="_blank" rel="nofollow"').html_safe
end
+ def simple_format_no_tags_no_sanitize(html)
+ simple_format(html, {}, sanitize: false)
+ end
+
end
diff --git a/app/helpers/valuation_helper.rb b/app/helpers/valuation_helper.rb
new file mode 100644
index 000000000..9e3a054c1
--- /dev/null
+++ b/app/helpers/valuation_helper.rb
@@ -0,0 +1,30 @@
+module ValuationHelper
+
+ def valuator_select_options(valuator=nil)
+ if valuator.present?
+ Valuator.where.not(id: valuator.id).order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] }.prepend([valuator.name, valuator.id])
+ else
+ Valuator.all.order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] }
+ end
+ end
+
+ def assigned_valuators_info(valuators)
+ case valuators.size
+ when 0
+ t("valuation.spending_proposals.index.no_valuators_assigned")
+ when 1
+ "".html_safe +
+ valuators.first.name +
+ " ".html_safe
+ else
+ "".html_safe +
+ t('valuation.spending_proposals.index.valuators_assigned', count: valuators.size) +
+ " ".html_safe
+ end
+ end
+
+ def explanation_field(field)
+ simple_format_no_tags_no_sanitize(safe_html_with_links(field.html_safe)) if field.present?
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb
index 6bf2b0b53..49184c447 100644
--- a/app/models/abilities/administrator.rb
+++ b/app/models/abilities/administrator.rb
@@ -4,6 +4,7 @@ module Abilities
def initialize(user)
self.merge Abilities::Moderation.new(user)
+ self.merge Abilities::Valuator.new(user)
can :restore, Comment
cannot :restore, Comment, hidden_at: nil
diff --git a/app/models/spending_proposal.rb b/app/models/spending_proposal.rb
index 48473640c..cceac00c1 100644
--- a/app/models/spending_proposal.rb
+++ b/app/models/spending_proposal.rb
@@ -34,6 +34,7 @@ class SpendingProposal < ActiveRecord::Base
results = self
results = results.by_geozone(params[:geozone_id]) if params[:geozone_id].present?
results = results.by_administrator(params[:administrator_id]) if params[:administrator_id].present?
+ results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present?
results = results.send(current_filter) if current_filter.present?
results.for_render
end
@@ -50,6 +51,10 @@ class SpendingProposal < ActiveRecord::Base
where(administrator_id: administrator.presence)
end
+ def self.by_valuator(valuator)
+ joins(:valuation_assignments).includes(:valuators).where("valuation_assignments.valuator_id = ?", valuator.presence)
+ end
+
def feasibility
case feasible
when true
diff --git a/app/views/admin/spending_proposals/show.html.erb b/app/views/admin/spending_proposals/show.html.erb
index cbdfbb2ad..6be63d9ff 100644
--- a/app/views/admin/spending_proposals/show.html.erb
+++ b/app/views/admin/spending_proposals/show.html.erb
@@ -62,6 +62,10 @@
<%= t("admin.spending_proposals.show.price") %> (<%= t("admin.spending_proposals.show.currency") %>):
<%= @spending_proposal.price.present? ? @spending_proposal.price : t("admin.spending_proposals.show.undefined") %>
+<%= t("admin.spending_proposals.show.price_first_year") %> (<%= t("admin.spending_proposals.show.currency") %>):
+ <%= @spending_proposal.price_first_year.present? ? @spending_proposal.price_first_year : t("admin.spending_proposals.show.undefined") %>
+
+
<%= simple_format(safe_html_with_links(@spending_proposal.price_explanation.html_safe), {}, sanitize: false) if @spending_proposal.price_explanation.present? %>
<%= t("admin.spending_proposals.show.feasibility") %>:
diff --git a/app/views/shared/_admin_login_items.html.erb b/app/views/shared/_admin_login_items.html.erb
index 2636f3a73..304c04fb0 100644
--- a/app/views/shared/_admin_login_items.html.erb
+++ b/app/views/shared/_admin_login_items.html.erb
@@ -10,4 +10,10 @@
<%= link_to t("layouts.header.moderation"), moderation_root_path %>
<% end %>
+
+ <% if current_user.valuator? || current_user.administrator? %>
+
+ <%= link_to t("layouts.header.valuation"), valuation_root_path %>
+
+ <% end %>
<% end %>
diff --git a/app/views/valuation/_menu.html.erb b/app/views/valuation/_menu.html.erb
new file mode 100644
index 000000000..76f6ff434
--- /dev/null
+++ b/app/views/valuation/_menu.html.erb
@@ -0,0 +1,17 @@
+
diff --git a/app/views/valuation/spending_proposals/edit.html.erb b/app/views/valuation/spending_proposals/edit.html.erb
new file mode 100644
index 000000000..a3dba0b4b
--- /dev/null
+++ b/app/views/valuation/spending_proposals/edit.html.erb
@@ -0,0 +1,132 @@
+<%= link_to "#{t('valuation.spending_proposals.show.heading')} #{@spending_proposal.id}", valuation_spending_proposal_path(@spending_proposal), class: 'back' %>
+<%= t("valuation.spending_proposals.edit.dossier") %>
+
+<%= form_for(@spending_proposal, url: valuate_valuation_spending_proposal_path(@spending_proposal)) do |f| %>
+ <%= render 'shared/errors', resource: @spending_proposal %>
+
+
+ <%= f.label :price, "#{t('valuation.spending_proposals.edit.price')} (#{t('valuation.spending_proposals.edit.currency')})" %>
+ <%= f.number_field :price, label: false %>
+
+
+
+ <%= f.label :price_first_year, "#{t('valuation.spending_proposals.edit.price_first_year')} ( #{t('valuation.spending_proposals.edit.currency')})" %>
+ <%= f.number_field :price_first_year, label: false %>
+
+
+
+
+
+ <%= f.label :price_explanation, t("valuation.spending_proposals.edit.price_explanation") %>
+ <%= f.text_area :price_explanation, label: false, rows: 3 %>
+
+
+
+
+
+
+ <%= t('valuation.spending_proposals.edit.feasibility') %>
+
+
+ <%= f.radio_button :feasible, true, value: true, label: false %>
+ <%= f.label :feasible_true, t('valuation.spending_proposals.edit.feasible') %>
+
+
+
+
+
+ <%= f.radio_button :feasible, false, value: false, label: false %>
+ <%= f.label :feasible_false, t('valuation.spending_proposals.edit.not_feasible') %>
+
+
+
+
+
+
+
+
+ <%= f.label :feasible_explanation, t("valuation.spending_proposals.edit.feasible_explanation") %>
+ <%= f.text_area :feasible_explanation, label: false, rows: 3 %>
+
+
+
+
+
+ <%= f.label :time_scope, t("valuation.spending_proposals.edit.time_scope") %>
+ <%= f.text_field :time_scope, label: false %>
+
+
+
+
+
+ <%= f.label :valuation_finished do %>
+ <%= f.check_box :valuation_finished, label: false %>
+ <%= t("valuation.spending_proposals.edit.valuation_finished") %>
+ <% end %>
+
+
+
+
+
+ <%= f.label :internal_comments, t("valuation.spending_proposals.edit.internal_comments") %>
+ <%= f.text_area :internal_comments, label: false, rows: 3 %>
+
+
+
+
+
+ <%= f.submit(class: "button expanded large", value: t("valuation.spending_proposals.edit.save")) %>
+
+
+<% end %>
+
+<%= @spending_proposal.title %>
+
+<%= safe_html_with_links @spending_proposal.description %>
+
+<% if @spending_proposal.external_url.present? %>
+ <%= text_with_links @spending_proposal.external_url %>
+<% end %>
+
+<%= t("valuation.spending_proposals.show.info") %>
+
+<%= t("valuation.spending_proposals.show.by") %>:
+ <%= link_to @spending_proposal.author.name, user_path(@spending_proposal.author) %>
+
+
+<% if @spending_proposal.association_name.present? %>
+ <%= t("valuation.spending_proposals.show.association_name") %>:
+ <%= @spending_proposal.association_name %>
+
+<% end %>
+
+<%= t("valuation.spending_proposals.show.geozone") %>:
+ <%= geozone_name(@spending_proposal) %>
+
+
+<%= t("valuation.spending_proposals.show.sent") %>:
+ <%= l @spending_proposal.created_at, format: :datetime %>
+
+
+<%= t("valuation.spending_proposals.show.responsibles") %>
+
+<%= t("valuation.spending_proposals.show.assigned_admin") %>:
+ <% if @spending_proposal.administrator.present? %>
+ <%= @spending_proposal.administrator.name %> (<%= @spending_proposal.administrator.email %>)
+ <% else %>
+ <%= t("valuation.spending_proposals.show.undefined") %>
+ <% end %>
+
+
+<%= t("valuation.spending_proposals.show.assigned_valuators") %>:
+
+
+ <% @spending_proposal.valuators.each do |valuator| %>
+ <%= valuator.name %> (<%= valuator.email %>)
+ <% end %>
+
+ <% if @spending_proposal.valuators.empty? %>
+ <%= t("valuation.spending_proposals.show.undefined") %>
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/valuation/spending_proposals/index.html.erb b/app/views/valuation/spending_proposals/index.html.erb
new file mode 100644
index 000000000..e6c05d6c7
--- /dev/null
+++ b/app/views/valuation/spending_proposals/index.html.erb
@@ -0,0 +1,41 @@
+<%= t("valuation.spending_proposals.index.title") %>
+
+
+ <%= form_tag valuation_spending_proposals_path, method: :get, enforce_utf8: false do %>
+
+ <%= select_tag :geozone_id,
+ options_for_select(geozone_select_options.unshift([t("geozones.none"), "all"]), params[:geozone_id]),
+ { prompt: t("valuation.spending_proposals.index.geozone_filter_all"),
+ label: false,
+ class: "js-submit-on-change" } %>
+
+ <% end %>
+
+
+<%= render 'shared/filter_subnav', i18n_namespace: "valuation.spending_proposals.index" %>
+
+<%= page_entries_info @spending_proposals %>
+
+
+ <% @spending_proposals.each do |spending_proposal| %>
+
+
+ <%= spending_proposal.id %>
+
+
+ <%= link_to spending_proposal.title, valuation_spending_proposal_path(spending_proposal) %>
+
+
+ <%= link_to t("valuation.spending_proposals.index.edit"), edit_valuation_spending_proposal_path(spending_proposal) %>
+
+
+ <%= assigned_valuators_info(spending_proposal.valuators) %>
+
+
+ <%= geozone_name(spending_proposal) %>
+
+
+ <% end %>
+
+
+<%= paginate @spending_proposals %>
\ No newline at end of file
diff --git a/app/views/valuation/spending_proposals/show.html.erb b/app/views/valuation/spending_proposals/show.html.erb
new file mode 100644
index 000000000..16f83dbe4
--- /dev/null
+++ b/app/views/valuation/spending_proposals/show.html.erb
@@ -0,0 +1,92 @@
+<%= link_to t("valuation.spending_proposals.show.back"), :back, class: 'back' %>
+<%= t("valuation.spending_proposals.show.heading") %> <%= @spending_proposal.id %>
+<%= @spending_proposal.title %>
+
+<%= safe_html_with_links @spending_proposal.description %>
+
+<% if @spending_proposal.external_url.present? %>
+ <%= text_with_links @spending_proposal.external_url %>
+<% end %>
+
+<%= t("valuation.spending_proposals.show.info") %>
+
+<%= t("valuation.spending_proposals.show.by") %>:
+ <%= link_to @spending_proposal.author.name, user_path(@spending_proposal.author) %>
+
+
+<% if @spending_proposal.association_name.present? %>
+ <%= t("valuation.spending_proposals.show.association_name") %>:
+ <%= @spending_proposal.association_name %>
+
+<% end %>
+
+<%= t("valuation.spending_proposals.show.geozone") %>:
+ <%= geozone_name(@spending_proposal) %>
+
+
+<%= t("valuation.spending_proposals.show.sent") %>:
+ <%= l @spending_proposal.created_at, format: :datetime %>
+
+
+<%= t("valuation.spending_proposals.show.responsibles") %>
+
+<%= t("valuation.spending_proposals.show.assigned_admin") %>:
+ <% if @spending_proposal.administrator.present? %>
+ <%= @spending_proposal.administrator.name %> (<%= @spending_proposal.administrator.email %>)
+ <% else %>
+ <%= t("valuation.spending_proposals.show.undefined") %>
+ <% end %>
+
+
+<%= t("valuation.spending_proposals.show.assigned_valuators") %>:
+
+
+ <% @spending_proposal.valuators.each do |valuator| %>
+ <%= valuator.name %> (<%= valuator.email %>)
+ <% end %>
+
+ <% if @spending_proposal.valuators.empty? %>
+ <%= t("valuation.spending_proposals.show.undefined") %>
+ <% end %>
+
+
+
+<%= t("valuation.spending_proposals.show.dossier") %>
+
+
+ <%= link_to t("valuation.spending_proposals.show.edit_dossier"), edit_valuation_spending_proposal_path(@spending_proposal) %>
+
+
+
+ <%= t("valuation.spending_proposals.show.price") %> (<%= t("valuation.spending_proposals.show.currency") %>):
+ <%= @spending_proposal.price.presence or t("valuation.spending_proposals.show.undefined") %>
+
+
+ <%= t("valuation.spending_proposals.show.price_first_year") %> (<%= t("valuation.spending_proposals.show.currency") %>):
+ <%= @spending_proposal.price_first_year.presence or t("valuation.spending_proposals.show.undefined") %>
+
+
+<%= explanation_field @spending_proposal.price_explanation %>
+
+
+ <%= t("valuation.spending_proposals.show.time_scope") %>:
+ <%= @spending_proposal.time_scope.presence or t("valuation.spending_proposals.show.undefined") %>
+
+
+
+ <%= t("valuation.spending_proposals.show.feasibility") %>:
+ <%= t("valuation.spending_proposals.show.#{@spending_proposal.feasibility}") %>
+
+
+<%= explanation_field @spending_proposal.feasible_explanation %>
+
+<% if @spending_proposal.valuation_finished %>
+
+ <%= t("valuation.spending_proposals.show.valuation_finished") %>
+
+<% end %>
+
+<% if @spending_proposal.internal_comments.present? %>
+ <%= t("valuation.spending_proposals.show.internal_comments") %>
+ <%= simple_format_no_tags_no_sanitize(safe_html_with_links(@spending_proposal.internal_comments.html_safe)) %>
+<% end %>
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index e003e2842..c5e88d8dd 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -24,6 +24,7 @@ data:
- config/locales/%{locale}.yml
- config/locales/admin.%{locale}.yml
- config/locales/moderation.%{locale}.yml
+ - config/locales/valuation.%{locale}.yml
- config/locales/management.%{locale}.yml
- config/locales/verification.%{locale}.yml
- config/locales/mailers.%{locale}.yml
@@ -122,6 +123,7 @@ ignore_unused:
- 'moderation.proposals.index.order*'
- 'moderation.debates.index.filter*'
- 'moderation.debates.index.order*'
+ - 'valuation.spending_proposals.index.filter*'
- 'users.show.filters.*'
- 'debates.index.select_order'
- 'debates.index.orders.*'
diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml
index bd31ad1ad..07f236456 100755
--- a/config/locales/admin.en.yml
+++ b/config/locales/admin.en.yml
@@ -173,6 +173,7 @@ en:
geozone: Scope
dossier: Dossier
price: Price
+ price_first_year: Cost during the first year
currency: "€"
feasibility: Feasibility
feasible: Feasible
diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml
index 53f615c1a..442294de0 100644
--- a/config/locales/admin.es.yml
+++ b/config/locales/admin.es.yml
@@ -173,6 +173,7 @@ es:
geozone: Ámbito
dossier: Informe
price: Coste
+ price_first_year: Coste en el primer año
currency: "€"
feasibility: Viabilidad
feasible: Viable
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 5514d2401..e3f51219c 100755
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -190,6 +190,7 @@ en:
locale: 'Language:'
logo: Madrid
moderation: Moderation
+ valuation: Valuation
more_information: More information
my_account_link: My account
my_activity_link: My activity
diff --git a/config/locales/es.yml b/config/locales/es.yml
index bf256fa13..e8c65c08a 100755
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -190,6 +190,7 @@ es:
locale: 'Idioma:'
logo: Madrid
moderation: Moderar
+ valuation: Evaluación
more_information: Más información
my_account_link: Mi cuenta
my_activity_link: Mi actividad
diff --git a/config/locales/valuation.en.yml b/config/locales/valuation.en.yml
new file mode 100644
index 000000000..2aba2e89c
--- /dev/null
+++ b/config/locales/valuation.en.yml
@@ -0,0 +1,60 @@
+---
+en:
+ valuation:
+ menu:
+ title: Valuation
+ spending_proposals: Spending proposals
+ spending_proposals:
+ index:
+ geozone_filter_all: All zones
+ filters:
+ valuation_open: Open
+ valuating: Under valuation
+ valuation_finished: Valuation finished
+ title: Investment projects for participatory budgeting
+ edit: Edit
+ valuators_assigned:
+ one: Assigned valuator
+ other: "%{count} valuators assigned"
+ no_valuators_assigned: No valuators assigned
+ show:
+ back: Back
+ heading: Investment project
+ info: Author info
+ association_name: Asociación
+ by: Sent by
+ sent: Sent at
+ geozone: Scope
+ dossier: Dossier
+ edit_dossier: Edit dossier
+ price: Price
+ price_first_year: Cost during the first year
+ currency: "€"
+ feasibility: Feasibility
+ feasible: Feasible
+ not_feasible: Not feasible
+ undefined: Undefined
+ valuation_finished: Valuation finished
+ time_scope: Time scope
+ internal_comments: Internal comments
+ responsibles: Responsibles
+ assigned_admin: Assigned admin
+ assigned_valuators: Assigned valuators
+ edit:
+ dossier: Dossier
+ price: Price
+ price_first_year: Cost during the first year
+ currency: "€"
+ price_explanation: Price explanation
+ feasibility: Feasibility
+ feasible: Feasible
+ not_feasible: Not feasible
+ feasible_explanation: Feasibility explanation
+ valuation_finished: Valuation finished
+ time_scope: Time scope
+ internal_comments: Internal comments
+ save: Save changes
+ notice:
+ valuate: "Dossier updated"
+
+
diff --git a/config/locales/valuation.es.yml b/config/locales/valuation.es.yml
new file mode 100644
index 000000000..94e9d1ec0
--- /dev/null
+++ b/config/locales/valuation.es.yml
@@ -0,0 +1,58 @@
+---
+es:
+ valuation:
+ menu:
+ title: Evaluación
+ spending_proposals: Propuestas de inversión
+ spending_proposals:
+ index:
+ geozone_filter_all: Todos los ámbitos de actuación
+ filters:
+ valuation_open: Abiertas
+ valuating: En evaluación
+ valuation_finished: Evaluación finalizada
+ title: Propuestas de inversión para presupuestos participativos
+ edit: Editar
+ valuators_assigned:
+ one: Evaluador asignado
+ other: "%{count} evaluadores asignados"
+ no_valuators_assigned: Sin evaluador
+ show:
+ back: Volver
+ heading: Propuesta de inversión
+ info: Datos de envío
+ association_name: Asociación
+ by: Enviada por
+ sent: Fecha de creación
+ geozone: Ámbito
+ dossier: Informe
+ edit_dossier: Editar informe
+ price: Coste
+ price_first_year: Coste en el primer año
+ currency: "€"
+ feasibility: Viabilidad
+ feasible: Viable
+ not_feasible: No viable
+ undefined: Sin definir
+ valuation_finished: Informe finalizado
+ time_scope: Plazo de ejecución
+ internal_comments: Comentarios internos
+ responsibles: Responsables
+ assigned_admin: Administrador asignado
+ assigned_valuators: Evaluadores asignados
+ edit:
+ dossier: Informe
+ price: Coste
+ price_first_year: Coste en el primer año
+ currency: "€"
+ price_explanation: Justificación del precio
+ feasibility: Viabilidad
+ feasible: Viable
+ not_feasible: No viable
+ feasible_explanation: Justificación de la viabilidad
+ valuation_finished: Informe finalizado
+ time_scope: Plazo de ejecución
+ internal_comments: Comentarios internos
+ save: Guardar cambios
+ notice:
+ valuate: "Informe actualizado"
diff --git a/config/routes.rb b/config/routes.rb
index e743e29d7..77491bb5e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -208,6 +208,16 @@ Rails.application.routes.draw do
end
end
+ namespace :valuation do
+ root to: "spending_proposals#index"
+
+ resources :spending_proposals, only: [:index, :show, :edit] do
+ member do
+ patch :valuate
+ end
+ end
+ end
+
namespace :management do
root to: "dashboard#index"
diff --git a/db/migrate/20160305113707_add_price_fields_to_spending_proposals.rb b/db/migrate/20160305113707_add_price_fields_to_spending_proposals.rb
new file mode 100644
index 000000000..dad22583d
--- /dev/null
+++ b/db/migrate/20160305113707_add_price_fields_to_spending_proposals.rb
@@ -0,0 +1,5 @@
+class AddPriceFieldsToSpendingProposals < ActiveRecord::Migration
+ def change
+ add_column :spending_proposals, :price_first_year, :float
+ end
+end
diff --git a/db/migrate/20160307150804_add_time_scope_to_spending_proposals.rb b/db/migrate/20160307150804_add_time_scope_to_spending_proposals.rb
new file mode 100644
index 000000000..550c57061
--- /dev/null
+++ b/db/migrate/20160307150804_add_time_scope_to_spending_proposals.rb
@@ -0,0 +1,5 @@
+class AddTimeScopeToSpendingProposals < ActiveRecord::Migration
+ def change
+ add_column :spending_proposals, :time_scope, :string
+ end
+end
diff --git a/db/migrate/20160308103548_change_price_fields_in_spending_proposals.rb b/db/migrate/20160308103548_change_price_fields_in_spending_proposals.rb
new file mode 100644
index 000000000..532d2662b
--- /dev/null
+++ b/db/migrate/20160308103548_change_price_fields_in_spending_proposals.rb
@@ -0,0 +1,11 @@
+class ChangePriceFieldsInSpendingProposals < ActiveRecord::Migration
+ def up
+ change_column :spending_proposals, :price, :integer
+ change_column :spending_proposals, :price_first_year, :integer
+ end
+
+ def down
+ change_column :spending_proposals, :price, :float
+ change_column :spending_proposals, :price_first_year, :float
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bd85c43a6..04f41722d 100644
--- a/db/schema.rb
+++ b/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: 20160225171916) do
+ActiveRecord::Schema.define(version: 20160308103548) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -299,7 +299,7 @@ ActiveRecord::Schema.define(version: 20160225171916) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "geozone_id"
- t.float "price"
+ t.integer "price"
t.boolean "feasible"
t.string "association_name"
t.text "price_explanation"
@@ -309,6 +309,8 @@ ActiveRecord::Schema.define(version: 20160225171916) do
t.text "explanations_log"
t.integer "administrator_id"
t.integer "valuation_assignments_count", default: 0
+ t.integer "price_first_year"
+ t.string "time_scope"
end
add_index "spending_proposals", ["author_id"], name: "index_spending_proposals_on_author_id", using: :btree
diff --git a/spec/features/admin/spending_proposals_spec.rb b/spec/features/admin/spending_proposals_spec.rb
index f4bd4582c..da17c3f35 100644
--- a/spec/features/admin/spending_proposals_spec.rb
+++ b/spec/features/admin/spending_proposals_spec.rb
@@ -168,7 +168,8 @@ feature 'Admin spending proposals' do
spending_proposal = create(:spending_proposal,
geozone: create(:geozone),
association_name: 'People of the neighbourhood',
- price: 1234.56,
+ price: 1234,
+ price_first_year: 1000,
feasible: false,
feasible_explanation: 'It is impossible',
administrator: administrator)
@@ -183,7 +184,8 @@ feature 'Admin spending proposals' do
expect(page).to have_content(spending_proposal.author.name)
expect(page).to have_content(spending_proposal.association_name)
expect(page).to have_content(spending_proposal.geozone.name)
- expect(page).to have_content('1234.56')
+ expect(page).to have_content('1234')
+ expect(page).to have_content('1000')
expect(page).to have_content('Not feasible')
expect(page).to have_content('It is impossible')
expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Ana (ana@admins.org)')
diff --git a/spec/features/admin_spec.rb b/spec/features/admin_spec.rb
index 644c20f9d..87c60355b 100644
--- a/spec/features/admin_spec.rb
+++ b/spec/features/admin_spec.rb
@@ -6,10 +6,6 @@ feature 'Admin' do
create(:administrator, user: user)
user
end
- let(:moderator) do
- create(:moderator, user: user)
- user
- end
scenario 'Access as regular user is not authorized' do
login_as(user)
@@ -21,7 +17,18 @@ feature 'Admin' do
end
scenario 'Access as a moderator is not authorized' do
- login_as(moderator)
+ create(:moderator, user: user)
+ login_as(user)
+ visit admin_root_path
+
+ expect(current_path).not_to eq(admin_root_path)
+ expect(current_path).to eq(proposals_path)
+ expect(page).to have_content "You do not have permission to access this page"
+ end
+
+ scenario 'Access as a valuator is not authorized' do
+ create(:valuator, user: user)
+ login_as(user)
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
@@ -42,15 +49,8 @@ feature 'Admin' do
visit root_path
expect(page).to have_link('Administration')
- expect(page).to_not have_link('Moderator')
- end
-
- scenario "Moderation access links" do
- login_as(moderator)
- visit root_path
-
expect(page).to have_link('Moderation')
- expect(page).to_not have_link('Administration')
+ expect(page).to have_link('Valuation')
end
scenario 'Admin dashboard' do
@@ -62,17 +62,7 @@ feature 'Admin' do
expect(current_path).to eq(admin_root_path)
expect(page).to have_css('#admin_menu')
expect(page).to_not have_css('#moderation_menu')
- end
-
- scenario 'Moderation dashboard' do
- login_as(moderator)
- visit root_path
-
- click_link 'Moderation'
-
- expect(current_path).to eq(moderation_root_path)
- expect(page).to have_css('#moderation_menu')
- expect(page).to_not have_css('#admin_menu')
+ expect(page).to_not have_css('#valuation_menu')
end
context 'Tags' do
diff --git a/spec/features/moderation_spec.rb b/spec/features/moderation_spec.rb
index c13a23461..12eb07358 100644
--- a/spec/features/moderation_spec.rb
+++ b/spec/features/moderation_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Admin' do
+feature 'Moderation' do
let(:user) { create(:user) }
scenario 'Access as regular user is not authorized' do
@@ -15,6 +15,20 @@ feature 'Admin' do
expect(page).to have_content "You do not have permission to access this page"
end
+ scenario 'Access as valuator is not authorized' do
+ create(:valuator, user: user)
+
+ login_as(user)
+ visit root_path
+
+ expect(page).to_not have_link("Moderation")
+ visit moderation_root_path
+
+ expect(current_path).not_to eq(moderation_root_path)
+ expect(current_path).to eq(proposals_path)
+ expect(page).to have_content "You do not have permission to access this page"
+ end
+
scenario 'Access as a moderator is authorized' do
create(:moderator, user: user)
@@ -41,4 +55,27 @@ feature 'Admin' do
expect(page).to_not have_content "You do not have permission to access this page"
end
+ scenario "Moderation access links" do
+ create(:moderator, user: user)
+ login_as(user)
+ visit root_path
+
+ expect(page).to have_link('Moderation')
+ expect(page).to_not have_link('Administration')
+ expect(page).to_not have_link('Valuation')
+ end
+
+ scenario 'Moderation dashboard' do
+ create(:moderator, user: user)
+ login_as(user)
+ visit root_path
+
+ click_link 'Moderation'
+
+ expect(current_path).to eq(moderation_root_path)
+ expect(page).to have_css('#moderation_menu')
+ expect(page).to_not have_css('#admin_menu')
+ expect(page).to_not have_css('#valuation_menu')
+ end
+
end
diff --git a/spec/features/valuation/spending_proposals_spec.rb b/spec/features/valuation/spending_proposals_spec.rb
new file mode 100644
index 000000000..e5bde98d4
--- /dev/null
+++ b/spec/features/valuation/spending_proposals_spec.rb
@@ -0,0 +1,289 @@
+require 'rails_helper'
+
+feature 'Valuation spending proposals' do
+
+ background do
+ @valuator = create(:valuator, user: create(:user, username: 'Rachel', email: 'rachel@valuators.org'))
+ login_as(@valuator.user)
+ end
+
+ scenario 'Disabled with a feature flag' do
+ Setting['feature.spending_proposals'] = nil
+ expect{ visit valuation_spending_proposals_path }.to raise_exception(FeatureFlags::FeatureDisabled)
+ end
+
+ scenario 'Index shows spending proposals assigned to current valuator' do
+ spending_proposal1 = create(:spending_proposal)
+ spending_proposal2 = create(:spending_proposal)
+
+ spending_proposal1.valuators << @valuator
+
+ visit valuation_spending_proposals_path
+
+ expect(page).to have_content(spending_proposal1.title)
+ expect(page).to_not have_content(spending_proposal2.title)
+ end
+
+ scenario 'Index shows no spending proposals to admins no valuators' do
+ spending_proposal1 = create(:spending_proposal)
+ spending_proposal2 = create(:spending_proposal)
+ spending_proposal1.valuators << @valuator
+
+ logout
+ login_as create(:administrator).user
+ visit valuation_spending_proposals_path
+
+ expect(page).to_not have_content(spending_proposal1.title)
+ expect(page).to_not have_content(spending_proposal2.title)
+ end
+
+ scenario 'Index shows assignments info' do
+ spending_proposal1 = create(:spending_proposal)
+ spending_proposal2 = create(:spending_proposal)
+ spending_proposal3 = create(:spending_proposal)
+
+ valuator1 = create(:valuator, user: create(:user))
+ valuator2 = create(:valuator, user: create(:user))
+ valuator3 = create(:valuator, user: create(:user))
+
+ spending_proposal1.valuator_ids = [@valuator.id]
+ spending_proposal2.valuator_ids = [@valuator.id, valuator1.id, valuator2.id]
+ spending_proposal3.valuator_ids = [@valuator.id, valuator3.id]
+
+ visit valuation_spending_proposals_path
+
+ within("#spending_proposal_#{spending_proposal1.id}") do
+ expect(page).to have_content("Rachel")
+ end
+
+ within("#spending_proposal_#{spending_proposal2.id}") do
+ expect(page).to have_content("3 valuators assigned")
+ end
+
+ within("#spending_proposal_#{spending_proposal3.id}") do
+ expect(page).to have_content("2 valuators assigned")
+ end
+ end
+
+ scenario "Index filtering by geozone", :js do
+ geozone = create(:geozone, name: "District 9")
+ spending_proposal1 = create(:spending_proposal, title: "Realocate visitors", geozone: geozone)
+ spending_proposal2 = create(:spending_proposal, title: "Destroy the city")
+ spending_proposal1.valuators << @valuator
+ spending_proposal2.valuators << @valuator
+
+ visit valuation_spending_proposals_path
+ expect(page).to have_link("Realocate visitors")
+ expect(page).to have_link("Destroy the city")
+
+ select "District 9", from: "geozone_id"
+
+ expect(page).to have_link("Realocate visitors")
+ expect(page).to_not have_link("Destroy the city")
+
+ select "All city", from: "geozone_id"
+
+ expect(page).to have_link("Destroy the city")
+ expect(page).to_not have_link("Realocate visitors")
+
+ select "All zones", from: "geozone_id"
+ expect(page).to have_link("Realocate visitors")
+ expect(page).to have_link("Destroy the city")
+ end
+
+ scenario "Current filter is properly highlighted" do
+ filters_links = {'valuating' => 'Under valuation',
+ 'valuation_finished' => 'Valuation finished'}
+
+ visit valuation_spending_proposals_path
+
+ expect(page).to_not have_link(filters_links.values.first)
+ filters_links.keys.drop(1).each { |filter| expect(page).to have_link(filters_links[filter]) }
+
+ filters_links.each_pair do |current_filter, link|
+ visit valuation_spending_proposals_path(filter: current_filter)
+
+ expect(page).to_not have_link(link)
+
+ (filters_links.keys - [current_filter]).each do |filter|
+ expect(page).to have_link(filters_links[filter])
+ end
+ end
+ end
+
+ scenario "Index filtering by valuation status" do
+ valuating = create(:spending_proposal, title: "Ongoing valuation")
+ valuated = create(:spending_proposal, title: "Old idea", valuation_finished: true)
+ valuating.valuators << @valuator
+ valuated.valuators << @valuator
+
+ visit valuation_spending_proposals_path(filter: 'valuation_open')
+
+ expect(page).to have_content("Ongoing valuation")
+ expect(page).to_not have_content("Old idea")
+
+ visit valuation_spending_proposals_path(filter: 'valuating')
+
+ expect(page).to have_content("Ongoing valuation")
+ expect(page).to_not have_content("Old idea")
+
+ visit valuation_spending_proposals_path(filter: 'valuation_finished')
+
+ expect(page).to_not have_content("Ongoing valuation")
+ expect(page).to have_content("Old idea")
+ end
+
+ feature 'Show' do
+ scenario 'visible for assigned valuators' do
+ administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org'))
+ valuator2 = create(:valuator, user: create(:user, username: 'Rick', email: 'rick@valuators.org'))
+ spending_proposal = create(:spending_proposal,
+ geozone: create(:geozone),
+ association_name: 'People of the neighbourhood',
+ price: 1234,
+ feasible: false,
+ feasible_explanation: 'It is impossible',
+ administrator: administrator)
+ spending_proposal.valuators << [@valuator, valuator2]
+
+ visit valuation_spending_proposals_path
+
+ click_link spending_proposal.title
+
+ expect(page).to have_content(spending_proposal.title)
+ expect(page).to have_content(spending_proposal.description)
+ expect(page).to have_content(spending_proposal.author.name)
+ expect(page).to have_content(spending_proposal.association_name)
+ expect(page).to have_content(spending_proposal.geozone.name)
+ expect(page).to have_content('1234')
+ expect(page).to have_content('Not feasible')
+ expect(page).to have_content('It is impossible')
+ expect(page).to have_content('Ana (ana@admins.org)')
+
+ within('#assigned_valuators') do
+ expect(page).to have_content('Rachel (rachel@valuators.org)')
+ expect(page).to have_content('Rick (rick@valuators.org)')
+ end
+ end
+
+ scenario 'visible for admins' do
+ logout
+ login_as create(:administrator).user
+
+ administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org'))
+ valuator2 = create(:valuator, user: create(:user, username: 'Rick', email: 'rick@valuators.org'))
+ spending_proposal = create(:spending_proposal,
+ geozone: create(:geozone),
+ association_name: 'People of the neighbourhood',
+ price: 1234,
+ feasible: false,
+ feasible_explanation: 'It is impossible',
+ administrator: administrator)
+ spending_proposal.valuators << [@valuator, valuator2]
+
+ visit valuation_spending_proposal_path(spending_proposal)
+
+ expect(page).to have_content(spending_proposal.title)
+ expect(page).to have_content(spending_proposal.description)
+ expect(page).to have_content(spending_proposal.author.name)
+ expect(page).to have_content(spending_proposal.association_name)
+ expect(page).to have_content(spending_proposal.geozone.name)
+ expect(page).to have_content('1234')
+ expect(page).to have_content('Not feasible')
+ expect(page).to have_content('It is impossible')
+ expect(page).to have_content('Ana (ana@admins.org)')
+
+ within('#assigned_valuators') do
+ expect(page).to have_content('Rachel (rachel@valuators.org)')
+ expect(page).to have_content('Rick (rick@valuators.org)')
+ end
+ end
+
+ scenario 'not visible for not assigned valuators' do
+ logout
+ login_as create(:valuator).user
+
+ administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org'))
+ valuator2 = create(:valuator, user: create(:user, username: 'Rick', email: 'rick@valuators.org'))
+ spending_proposal = create(:spending_proposal,
+ geozone: create(:geozone),
+ association_name: 'People of the neighbourhood',
+ price: 1234,
+ feasible: false,
+ feasible_explanation: 'It is impossible',
+ administrator: administrator)
+ spending_proposal.valuators << [@valuator, valuator2]
+
+ expect { visit valuation_spending_proposal_path(spending_proposal) }.to raise_error "Not Found"
+ end
+
+ end
+
+ feature 'Valuate' do
+ background do
+ @spending_proposal = create(:spending_proposal,
+ geozone: create(:geozone),
+ administrator: create(:administrator))
+ @spending_proposal.valuators << @valuator
+ end
+
+ scenario 'Dossier empty by default' do
+ visit valuation_spending_proposals_path
+ click_link @spending_proposal.title
+
+ within('#price') { expect(page).to have_content('Undefined') }
+ within('#price_first_year') { expect(page).to have_content('Undefined') }
+ within('#time_scope') { expect(page).to have_content('Undefined') }
+ within('#feasibility') { expect(page).to have_content('Undefined') }
+ expect(page).to_not have_content('Valuation finished')
+ expect(page).to_not have_content('Internal comments')
+ end
+
+ scenario 'Edit dossier' do
+ visit valuation_spending_proposals_path
+ within("#spending_proposal_#{@spending_proposal.id}") do
+ click_link "Edit"
+ end
+
+ fill_in 'spending_proposal_price', with: '12345'
+ fill_in 'spending_proposal_price_first_year', with: '9876'
+ fill_in 'spending_proposal_price_explanation', with: 'Very cheap idea'
+ choose 'spending_proposal_feasible_true'
+ fill_in 'spending_proposal_feasible_explanation', with: 'Everything is legal and easy to do'
+ fill_in 'spending_proposal_time_scope', with: '19 months'
+ fill_in 'spending_proposal_internal_comments', with: 'Should be double checked by the urbanism area'
+ click_button 'Save changes'
+
+ expect(page).to have_content "Dossier updated"
+
+ visit valuation_spending_proposals_path
+ click_link @spending_proposal.title
+
+ within('#price') { expect(page).to have_content('12345') }
+ within('#price_first_year') { expect(page).to have_content('9876') }
+ expect(page).to have_content('Very cheap idea')
+ within('#time_scope') { expect(page).to have_content('19 months') }
+ within('#feasibility') { expect(page).to have_content('Feasible') }
+ expect(page).to_not have_content('Valuation finished')
+ expect(page).to have_content('Internal comments')
+ expect(page).to have_content('Should be double checked by the urbanism area')
+ end
+
+ scenario 'Finish valuation' do
+ visit valuation_spending_proposal_path(@spending_proposal)
+ click_link 'Edit dossier'
+
+ check 'spending_proposal_valuation_finished'
+ click_button 'Save changes'
+
+ visit valuation_spending_proposals_path
+ expect(page).to_not have_content @spending_proposal.title
+ click_link 'Valuation finished'
+
+ expect(page).to have_content @spending_proposal.title
+ click_link @spending_proposal.title
+ expect(page).to have_content('Valuation finished')
+ end
+ end
+
+end
diff --git a/spec/features/valuation_spec.rb b/spec/features/valuation_spec.rb
new file mode 100644
index 000000000..571a4323f
--- /dev/null
+++ b/spec/features/valuation_spec.rb
@@ -0,0 +1,78 @@
+require 'rails_helper'
+
+feature 'Valuation' do
+ let(:user) { create(:user) }
+
+ scenario 'Access as regular user is not authorized' do
+ login_as(user)
+ visit root_path
+
+ expect(page).to_not have_link("Valuation")
+ visit valuation_root_path
+
+ expect(current_path).not_to eq(valuation_root_path)
+ expect(current_path).to eq(proposals_path)
+ expect(page).to have_content "You do not have permission to access this page"
+ end
+
+ scenario 'Access as moderator is not authorized' do
+ create(:moderator, user: user)
+ login_as(user)
+ visit root_path
+
+ expect(page).to_not have_link("Valuation")
+ visit valuation_root_path
+
+ expect(current_path).not_to eq(valuation_root_path)
+ expect(current_path).to eq(proposals_path)
+ expect(page).to have_content "You do not have permission to access this page"
+ end
+
+ scenario 'Access as a valuator is authorized' do
+ create(:valuator, user: user)
+ login_as(user)
+ visit root_path
+
+ expect(page).to have_link("Valuation")
+ click_on "Valuation"
+
+ expect(current_path).to eq(valuation_root_path)
+ expect(page).to_not have_content "You do not have permission to access this page"
+ end
+
+ scenario 'Access as an administrator is authorized' do
+ create(:administrator, user: user)
+ login_as(user)
+ visit root_path
+
+ expect(page).to have_link("Valuation")
+ click_on "Valuation"
+
+ expect(current_path).to eq(valuation_root_path)
+ expect(page).to_not have_content "You do not have permission to access this page"
+ end
+
+ scenario "Valuation access links" do
+ create(:valuator, user: user)
+ login_as(user)
+ visit root_path
+
+ expect(page).to have_link('Valuation')
+ expect(page).to_not have_link('Administration')
+ expect(page).to_not have_link('Moderation')
+ end
+
+ scenario 'Valuation dashboard' do
+ create(:valuator, user: user)
+ login_as(user)
+ visit root_path
+
+ click_link 'Valuation'
+
+ expect(current_path).to eq(valuation_root_path)
+ expect(page).to have_css('#valuation_menu')
+ expect(page).to_not have_css('#admin_menu')
+ expect(page).to_not have_css('#moderation_menu')
+ end
+
+end
diff --git a/spec/models/abilities/valuator_spec.rb b/spec/models/abilities/valuator_spec.rb
new file mode 100644
index 000000000..d9542220d
--- /dev/null
+++ b/spec/models/abilities/valuator_spec.rb
@@ -0,0 +1,10 @@
+require 'rails_helper'
+require 'cancan/matchers'
+
+describe "Abilities::Valuator" do
+ subject(:ability) { Ability.new(user) }
+ let(:user) { valuator.user }
+ let(:valuator) { create(:valuator) }
+
+ it { should be_able_to(:manage, SpendingProposal) }
+end
diff --git a/spec/models/spending_proposal_spec.rb b/spec/models/spending_proposal_spec.rb
index a3800e85c..9ed96da59 100644
--- a/spec/models/spending_proposal_spec.rb
+++ b/spec/models/spending_proposal_spec.rb
@@ -61,6 +61,38 @@ describe SpendingProposal do
end
end
+ describe "by_administrator" do
+ it "should return spending proposals assigned to specific administrator" do
+ spending_proposal1 = create(:spending_proposal, administrator_id: 33)
+ spending_proposal2 = create(:spending_proposal)
+
+ by_administrator = SpendingProposal.by_administrator(33)
+
+ expect(by_administrator.size).to eq(1)
+ expect(by_administrator.first).to eq(spending_proposal1)
+ end
+ end
+
+ describe "by_valuator" do
+ it "should return spending proposals assigned to specific valuator" do
+ spending_proposal1 = create(:spending_proposal)
+ spending_proposal2 = create(:spending_proposal)
+ spending_proposal3 = create(:spending_proposal)
+
+ valuator1 = create(:valuator)
+ valuator2 = create(:valuator)
+
+ spending_proposal1.valuators << valuator1
+ spending_proposal2.valuators << valuator2
+ spending_proposal3.valuators << [valuator1, valuator2]
+
+ by_valuator = SpendingProposal.by_valuator(valuator1.id)
+
+ expect(by_valuator.size).to eq(2)
+ expect(by_valuator.sort).to eq([spending_proposal1,spending_proposal3].sort)
+ end
+ end
+
describe "scopes" do
describe "valuation_open" do
it "should return all spending proposals with false valuation_finished" do