Merge pull request #971 from consul/valuation

Valuation interface
This commit is contained in:
Raimond Garcia
2016-03-08 14:16:08 +01:00
33 changed files with 1031 additions and 32 deletions

View File

@@ -168,7 +168,7 @@ body.admin {
clear: both; clear: both;
.checkbox { .checkbox {
font-size: rem-calc(12); font-size: $small-font-size;
} }
} }

View File

@@ -641,6 +641,10 @@ form {
line-height: $line-height; line-height: $line-height;
} }
fieldset legend {
font-weight: bold;
}
input, textarea { input, textarea {
height: 48px\9; height: 48px\9;
line-height: 48px\9; line-height: 48px\9;
@@ -648,13 +652,28 @@ form {
width: 100%\9; width: 100%\9;
} }
input[type="checkbox"] { input[type="checkbox"],
input[type="radio"] {
height: auto\9; height: auto\9;
line-height: inherit\9; line-height: inherit\9;
width: auto\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; background: $input-bg;
height: $line-height*2; height: $line-height*2;
margin-bottom: rem-calc(16); margin-bottom: rem-calc(16);

View File

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

View File

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

View File

@@ -11,4 +11,8 @@ module TextWithLinksHelper
Rinku.auto_link(html, :all, 'target="_blank" rel="nofollow"').html_safe Rinku.auto_link(html, :all, 'target="_blank" rel="nofollow"').html_safe
end end
def simple_format_no_tags_no_sanitize(html)
simple_format(html, {}, sanitize: false)
end
end end

View File

@@ -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
"<span title=\"#{t('valuation.spending_proposals.index.valuators_assigned', count: 1)}\">".html_safe +
valuators.first.name +
"</span>".html_safe
else
"<span title=\"".html_safe + valuators.map(&:name).join(', ') + "\">".html_safe +
t('valuation.spending_proposals.index.valuators_assigned', count: valuators.size) +
"</span>".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

View File

@@ -4,6 +4,7 @@ module Abilities
def initialize(user) def initialize(user)
self.merge Abilities::Moderation.new(user) self.merge Abilities::Moderation.new(user)
self.merge Abilities::Valuator.new(user)
can :restore, Comment can :restore, Comment
cannot :restore, Comment, hidden_at: nil cannot :restore, Comment, hidden_at: nil

View File

@@ -34,6 +34,7 @@ class SpendingProposal < ActiveRecord::Base
results = self results = self
results = results.by_geozone(params[:geozone_id]) if params[:geozone_id].present? 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_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 = results.send(current_filter) if current_filter.present?
results.for_render results.for_render
end end
@@ -50,6 +51,10 @@ class SpendingProposal < ActiveRecord::Base
where(administrator_id: administrator.presence) where(administrator_id: administrator.presence)
end end
def self.by_valuator(valuator)
joins(:valuation_assignments).includes(:valuators).where("valuation_assignments.valuator_id = ?", valuator.presence)
end
def feasibility def feasibility
case feasible case feasible
when true when true

View File

@@ -62,6 +62,10 @@
<p><strong><%= t("admin.spending_proposals.show.price") %> (<%= t("admin.spending_proposals.show.currency") %>):</strong> <p><strong><%= t("admin.spending_proposals.show.price") %> (<%= t("admin.spending_proposals.show.currency") %>):</strong>
<%= @spending_proposal.price.present? ? @spending_proposal.price : t("admin.spending_proposals.show.undefined") %> <%= @spending_proposal.price.present? ? @spending_proposal.price : t("admin.spending_proposals.show.undefined") %>
</p> </p>
<p><strong><%= t("admin.spending_proposals.show.price_first_year") %> (<%= t("admin.spending_proposals.show.currency") %>):</strong>
<%= @spending_proposal.price_first_year.present? ? @spending_proposal.price_first_year : t("admin.spending_proposals.show.undefined") %>
</p>
<%= simple_format(safe_html_with_links(@spending_proposal.price_explanation.html_safe), {}, sanitize: false) if @spending_proposal.price_explanation.present? %> <%= simple_format(safe_html_with_links(@spending_proposal.price_explanation.html_safe), {}, sanitize: false) if @spending_proposal.price_explanation.present? %>
<p><strong><%= t("admin.spending_proposals.show.feasibility") %>:</strong> <p><strong><%= t("admin.spending_proposals.show.feasibility") %>:</strong>

View File

@@ -10,4 +10,10 @@
<%= link_to t("layouts.header.moderation"), moderation_root_path %> <%= link_to t("layouts.header.moderation"), moderation_root_path %>
</li> </li>
<% end %> <% end %>
<% if current_user.valuator? || current_user.administrator? %>
<li>
<%= link_to t("layouts.header.valuation"), valuation_root_path %>
</li>
<% end %>
<% end %> <% end %>

View File

@@ -0,0 +1,17 @@
<nav class="admin-sidebar">
<ul id="valuation_menu">
<li>
<%= link_to t("valuation.menu.title"), valuation_root_path %>
</li>
<% if feature?(:spending_proposals) %>
<li <%= "class=active" if controller_name == "spending_proposals" %>>
<%= link_to valuation_spending_proposals_path do %>
<i class="icon-budget"></i>
<%= t("valuation.menu.spending_proposals") %>
<% end %>
</li>
<% end %>
</ul>
</nav>

View File

@@ -0,0 +1,132 @@
<%= link_to "#{t('valuation.spending_proposals.show.heading')} #{@spending_proposal.id}", valuation_spending_proposal_path(@spending_proposal), class: 'back' %>
<h2><%= t("valuation.spending_proposals.edit.dossier") %></h2>
<%= form_for(@spending_proposal, url: valuate_valuation_spending_proposal_path(@spending_proposal)) do |f| %>
<%= render 'shared/errors', resource: @spending_proposal %>
<div class="row">
<div class="small-12 medium-4 column">
<%= f.label :price, "#{t('valuation.spending_proposals.edit.price')} (#{t('valuation.spending_proposals.edit.currency')})" %>
<%= f.number_field :price, label: false %>
</div>
<div class="small-12 medium-4 column end">
<%= 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 %>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.label :price_explanation, t("valuation.spending_proposals.edit.price_explanation") %>
<%= f.text_area :price_explanation, label: false, rows: 3 %>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<fieldset class="fieldset">
<legend><%= t('valuation.spending_proposals.edit.feasibility') %></legend>
<div class="small-6 column">
<span class="checkbox">
<%= f.radio_button :feasible, true, value: true, label: false %>
<%= f.label :feasible_true, t('valuation.spending_proposals.edit.feasible') %>
</span>
</div>
<div class="small-6 column">
<span class="checkbox">
<%= f.radio_button :feasible, false, value: false, label: false %>
<%= f.label :feasible_false, t('valuation.spending_proposals.edit.not_feasible') %>
</span>
</div>
</fieldset>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.label :feasible_explanation, t("valuation.spending_proposals.edit.feasible_explanation") %>
<%= f.text_area :feasible_explanation, label: false, rows: 3 %>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.label :time_scope, t("valuation.spending_proposals.edit.time_scope") %>
<%= f.text_field :time_scope, label: false %>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.label :valuation_finished do %>
<%= f.check_box :valuation_finished, label: false %>
<span class="checkbox"><%= t("valuation.spending_proposals.edit.valuation_finished") %></span>
<% end %>
</div>
</div>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.label :internal_comments, t("valuation.spending_proposals.edit.internal_comments") %>
<%= f.text_area :internal_comments, label: false, rows: 3 %>
</div>
</div>
<div class="row">
<div class="actions small-12 medium-4 column">
<%= f.submit(class: "button expanded large", value: t("valuation.spending_proposals.edit.save")) %>
</div>
</div>
<% end %>
<h1><%= @spending_proposal.title %></h1>
<%= safe_html_with_links @spending_proposal.description %>
<% if @spending_proposal.external_url.present? %>
<p><%= text_with_links @spending_proposal.external_url %></p>
<% end %>
<h2><%= t("valuation.spending_proposals.show.info") %></h2>
<p><strong><%= t("valuation.spending_proposals.show.by") %>:</strong>
<%= link_to @spending_proposal.author.name, user_path(@spending_proposal.author) %>
</p>
<% if @spending_proposal.association_name.present? %>
<p><strong><%= t("valuation.spending_proposals.show.association_name") %>:</strong>
<%= @spending_proposal.association_name %>
</p>
<% end %>
<p><strong><%= t("valuation.spending_proposals.show.geozone") %>:</strong>
<%= geozone_name(@spending_proposal) %>
</p>
<p><strong><%= t("valuation.spending_proposals.show.sent") %>:</strong>
<%= l @spending_proposal.created_at, format: :datetime %>
</p>
<h2><%= t("valuation.spending_proposals.show.responsibles") %></h2>
<p><strong><%= t("valuation.spending_proposals.show.assigned_admin") %>:</strong>
<% if @spending_proposal.administrator.present? %>
<%= @spending_proposal.administrator.name %> (<%= @spending_proposal.administrator.email %>)
<% else %>
<%= t("valuation.spending_proposals.show.undefined") %></li>
<% end %>
</p>
<p><strong><%= t("valuation.spending_proposals.show.assigned_valuators") %>:</strong></p>
<div id="assigned_valuators">
<ul>
<% @spending_proposal.valuators.each do |valuator| %>
<li><%= valuator.name %> (<%= valuator.email %>)</li>
<% end %>
<% if @spending_proposal.valuators.empty? %>
<li><%= t("valuation.spending_proposals.show.undefined") %></li>
<% end %>
</ul>
</div>

View File

@@ -0,0 +1,41 @@
<h2><%= t("valuation.spending_proposals.index.title") %></h2>
<div>
<%= form_tag valuation_spending_proposals_path, method: :get, enforce_utf8: false do %>
<div class="small-12 medium-4 column float-right">
<%= 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" } %>
</div>
<% end %>
</div>
<%= render 'shared/filter_subnav', i18n_namespace: "valuation.spending_proposals.index" %>
<h3><%= page_entries_info @spending_proposals %></h3>
<table>
<% @spending_proposals.each do |spending_proposal| %>
<tr id="<%= dom_id(spending_proposal) %>">
<td>
<strong><%= spending_proposal.id %></strong>
</td>
<td>
<%= link_to spending_proposal.title, valuation_spending_proposal_path(spending_proposal) %>
</td>
<td class="small">
<%= link_to t("valuation.spending_proposals.index.edit"), edit_valuation_spending_proposal_path(spending_proposal) %>
</td>
<td class="small">
<%= assigned_valuators_info(spending_proposal.valuators) %>
</td>
<td class="small">
<%= geozone_name(spending_proposal) %>
</td>
</tr>
<% end %>
</table>
<%= paginate @spending_proposals %>

View File

@@ -0,0 +1,92 @@
<%= link_to t("valuation.spending_proposals.show.back"), :back, class: 'back' %>
<h2><%= t("valuation.spending_proposals.show.heading") %> <%= @spending_proposal.id %> </h2>
<h1><%= @spending_proposal.title %></h1>
<%= safe_html_with_links @spending_proposal.description %>
<% if @spending_proposal.external_url.present? %>
<p><%= text_with_links @spending_proposal.external_url %></p>
<% end %>
<h2><%= t("valuation.spending_proposals.show.info") %></h2>
<p><strong><%= t("valuation.spending_proposals.show.by") %>:</strong>
<%= link_to @spending_proposal.author.name, user_path(@spending_proposal.author) %>
</p>
<% if @spending_proposal.association_name.present? %>
<p><strong><%= t("valuation.spending_proposals.show.association_name") %>:</strong>
<%= @spending_proposal.association_name %>
</p>
<% end %>
<p><strong><%= t("valuation.spending_proposals.show.geozone") %>:</strong>
<%= geozone_name(@spending_proposal) %>
</p>
<p><strong><%= t("valuation.spending_proposals.show.sent") %>:</strong>
<%= l @spending_proposal.created_at, format: :datetime %>
</p>
<h2><%= t("valuation.spending_proposals.show.responsibles") %></h2>
<p><strong><%= t("valuation.spending_proposals.show.assigned_admin") %>:</strong>
<% if @spending_proposal.administrator.present? %>
<%= @spending_proposal.administrator.name %> (<%= @spending_proposal.administrator.email %>)
<% else %>
<%= t("valuation.spending_proposals.show.undefined") %></li>
<% end %>
</p>
<p><strong><%= t("valuation.spending_proposals.show.assigned_valuators") %>:</strong></p>
<div id="assigned_valuators">
<ul>
<% @spending_proposal.valuators.each do |valuator| %>
<li><%= valuator.name %> (<%= valuator.email %>)</li>
<% end %>
<% if @spending_proposal.valuators.empty? %>
<li><%= t("valuation.spending_proposals.show.undefined") %></li>
<% end %>
</ul>
</div>
<h2><%= t("valuation.spending_proposals.show.dossier") %></h2>
<p>
<%= link_to t("valuation.spending_proposals.show.edit_dossier"), edit_valuation_spending_proposal_path(@spending_proposal) %>
</p>
<p id="price">
<strong><%= t("valuation.spending_proposals.show.price") %> (<%= t("valuation.spending_proposals.show.currency") %>):</strong>
<%= @spending_proposal.price.presence or t("valuation.spending_proposals.show.undefined") %>
</p>
<p id="price_first_year">
<strong><%= t("valuation.spending_proposals.show.price_first_year") %> (<%= t("valuation.spending_proposals.show.currency") %>):</strong>
<%= @spending_proposal.price_first_year.presence or t("valuation.spending_proposals.show.undefined") %>
</p>
<%= explanation_field @spending_proposal.price_explanation %>
<p id="time_scope">
<strong><%= t("valuation.spending_proposals.show.time_scope") %>:</strong>
<%= @spending_proposal.time_scope.presence or t("valuation.spending_proposals.show.undefined") %>
</p>
<p id="feasibility">
<strong><%= t("valuation.spending_proposals.show.feasibility") %>:</strong>
<%= t("valuation.spending_proposals.show.#{@spending_proposal.feasibility}") %>
</p>
<%= explanation_field @spending_proposal.feasible_explanation %>
<% if @spending_proposal.valuation_finished %>
<p>
<strong><%= t("valuation.spending_proposals.show.valuation_finished") %></strong>
</p>
<% end %>
<% if @spending_proposal.internal_comments.present? %>
<h2><%= t("valuation.spending_proposals.show.internal_comments") %></h2>
<%= simple_format_no_tags_no_sanitize(safe_html_with_links(@spending_proposal.internal_comments.html_safe)) %>
<% end %>

View File

@@ -24,6 +24,7 @@ data:
- config/locales/%{locale}.yml - config/locales/%{locale}.yml
- config/locales/admin.%{locale}.yml - config/locales/admin.%{locale}.yml
- config/locales/moderation.%{locale}.yml - config/locales/moderation.%{locale}.yml
- config/locales/valuation.%{locale}.yml
- config/locales/management.%{locale}.yml - config/locales/management.%{locale}.yml
- config/locales/verification.%{locale}.yml - config/locales/verification.%{locale}.yml
- config/locales/mailers.%{locale}.yml - config/locales/mailers.%{locale}.yml
@@ -122,6 +123,7 @@ ignore_unused:
- 'moderation.proposals.index.order*' - 'moderation.proposals.index.order*'
- 'moderation.debates.index.filter*' - 'moderation.debates.index.filter*'
- 'moderation.debates.index.order*' - 'moderation.debates.index.order*'
- 'valuation.spending_proposals.index.filter*'
- 'users.show.filters.*' - 'users.show.filters.*'
- 'debates.index.select_order' - 'debates.index.select_order'
- 'debates.index.orders.*' - 'debates.index.orders.*'

View File

@@ -173,6 +173,7 @@ en:
geozone: Scope geozone: Scope
dossier: Dossier dossier: Dossier
price: Price price: Price
price_first_year: Cost during the first year
currency: "€" currency: "€"
feasibility: Feasibility feasibility: Feasibility
feasible: Feasible feasible: Feasible

View File

@@ -173,6 +173,7 @@ es:
geozone: Ámbito geozone: Ámbito
dossier: Informe dossier: Informe
price: Coste price: Coste
price_first_year: Coste en el primer año
currency: "€" currency: "€"
feasibility: Viabilidad feasibility: Viabilidad
feasible: Viable feasible: Viable

View File

@@ -190,6 +190,7 @@ en:
locale: 'Language:' locale: 'Language:'
logo: Madrid logo: Madrid
moderation: Moderation moderation: Moderation
valuation: Valuation
more_information: More information more_information: More information
my_account_link: My account my_account_link: My account
my_activity_link: My activity my_activity_link: My activity

View File

@@ -190,6 +190,7 @@ es:
locale: 'Idioma:' locale: 'Idioma:'
logo: Madrid logo: Madrid
moderation: Moderar moderation: Moderar
valuation: Evaluación
more_information: Más información more_information: Más información
my_account_link: Mi cuenta my_account_link: Mi cuenta
my_activity_link: Mi actividad my_activity_link: Mi actividad

View File

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

View File

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

View File

@@ -208,6 +208,16 @@ Rails.application.routes.draw do
end end
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 namespace :management do
root to: "dashboard#index" root to: "dashboard#index"

View File

@@ -0,0 +1,5 @@
class AddPriceFieldsToSpendingProposals < ActiveRecord::Migration
def change
add_column :spending_proposals, :price_first_year, :float
end
end

View File

@@ -0,0 +1,5 @@
class AddTimeScopeToSpendingProposals < ActiveRecord::Migration
def change
add_column :spending_proposals, :time_scope, :string
end
end

View File

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

View File

@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -299,7 +299,7 @@ ActiveRecord::Schema.define(version: 20160225171916) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "geozone_id" t.integer "geozone_id"
t.float "price" t.integer "price"
t.boolean "feasible" t.boolean "feasible"
t.string "association_name" t.string "association_name"
t.text "price_explanation" t.text "price_explanation"
@@ -309,6 +309,8 @@ ActiveRecord::Schema.define(version: 20160225171916) do
t.text "explanations_log" t.text "explanations_log"
t.integer "administrator_id" t.integer "administrator_id"
t.integer "valuation_assignments_count", default: 0 t.integer "valuation_assignments_count", default: 0
t.integer "price_first_year"
t.string "time_scope"
end end
add_index "spending_proposals", ["author_id"], name: "index_spending_proposals_on_author_id", using: :btree add_index "spending_proposals", ["author_id"], name: "index_spending_proposals_on_author_id", using: :btree

View File

@@ -168,7 +168,8 @@ feature 'Admin spending proposals' do
spending_proposal = create(:spending_proposal, spending_proposal = create(:spending_proposal,
geozone: create(:geozone), geozone: create(:geozone),
association_name: 'People of the neighbourhood', association_name: 'People of the neighbourhood',
price: 1234.56, price: 1234,
price_first_year: 1000,
feasible: false, feasible: false,
feasible_explanation: 'It is impossible', feasible_explanation: 'It is impossible',
administrator: administrator) 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.author.name)
expect(page).to have_content(spending_proposal.association_name) expect(page).to have_content(spending_proposal.association_name)
expect(page).to have_content(spending_proposal.geozone.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('Not feasible')
expect(page).to have_content('It is impossible') expect(page).to have_content('It is impossible')
expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Ana (ana@admins.org)') expect(page).to have_select('spending_proposal[administrator_id]', selected: 'Ana (ana@admins.org)')

View File

@@ -6,10 +6,6 @@ feature 'Admin' do
create(:administrator, user: user) create(:administrator, user: user)
user user
end end
let(:moderator) do
create(:moderator, user: user)
user
end
scenario 'Access as regular user is not authorized' do scenario 'Access as regular user is not authorized' do
login_as(user) login_as(user)
@@ -21,7 +17,18 @@ feature 'Admin' do
end end
scenario 'Access as a moderator is not authorized' do 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 visit admin_root_path
expect(current_path).not_to eq(admin_root_path) expect(current_path).not_to eq(admin_root_path)
@@ -42,15 +49,8 @@ feature 'Admin' do
visit root_path visit root_path
expect(page).to have_link('Administration') 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 have_link('Moderation')
expect(page).to_not have_link('Administration') expect(page).to have_link('Valuation')
end end
scenario 'Admin dashboard' do scenario 'Admin dashboard' do
@@ -62,17 +62,7 @@ feature 'Admin' do
expect(current_path).to eq(admin_root_path) expect(current_path).to eq(admin_root_path)
expect(page).to have_css('#admin_menu') expect(page).to have_css('#admin_menu')
expect(page).to_not have_css('#moderation_menu') expect(page).to_not have_css('#moderation_menu')
end expect(page).to_not have_css('#valuation_menu')
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')
end end
context 'Tags' do context 'Tags' do

View File

@@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
feature 'Admin' do feature 'Moderation' do
let(:user) { create(:user) } let(:user) { create(:user) }
scenario 'Access as regular user is not authorized' do 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" expect(page).to have_content "You do not have permission to access this page"
end 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 scenario 'Access as a moderator is authorized' do
create(:moderator, user: user) 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" expect(page).to_not have_content "You do not have permission to access this page"
end 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 end

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,38 @@ describe SpendingProposal do
end end
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 "scopes" do
describe "valuation_open" do describe "valuation_open" do
it "should return all spending proposals with false valuation_finished" do it "should return all spending proposals with false valuation_finished" do