@@ -168,7 +168,7 @@ body.admin {
|
|||||||
clear: both;
|
clear: both;
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
font-size: rem-calc(12);
|
font-size: $small-font-size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
15
app/controllers/valuation/base_controller.rb
Normal file
15
app/controllers/valuation/base_controller.rb
Normal 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
|
||||||
38
app/controllers/valuation/spending_proposals_controller.rb
Normal file
38
app/controllers/valuation/spending_proposals_controller.rb
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
30
app/helpers/valuation_helper.rb
Normal file
30
app/helpers/valuation_helper.rb
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 %>
|
||||||
|
|||||||
17
app/views/valuation/_menu.html.erb
Normal file
17
app/views/valuation/_menu.html.erb
Normal 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>
|
||||||
132
app/views/valuation/spending_proposals/edit.html.erb
Normal file
132
app/views/valuation/spending_proposals/edit.html.erb
Normal 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>
|
||||||
41
app/views/valuation/spending_proposals/index.html.erb
Normal file
41
app/views/valuation/spending_proposals/index.html.erb
Normal 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 %>
|
||||||
92
app/views/valuation/spending_proposals/show.html.erb
Normal file
92
app/views/valuation/spending_proposals/show.html.erb
Normal 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 %>
|
||||||
@@ -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.*'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
60
config/locales/valuation.en.yml
Normal file
60
config/locales/valuation.en.yml
Normal 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"
|
||||||
|
|
||||||
|
|
||||||
58
config/locales/valuation.es.yml
Normal file
58
config/locales/valuation.es.yml
Normal 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"
|
||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddPriceFieldsToSpendingProposals < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :spending_proposals, :price_first_year, :float
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddTimeScopeToSpendingProposals < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :spending_proposals, :time_scope, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
289
spec/features/valuation/spending_proposals_spec.rb
Normal file
289
spec/features/valuation/spending_proposals_spec.rb
Normal 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
|
||||||
78
spec/features/valuation_spec.rb
Normal file
78
spec/features/valuation_spec.rb
Normal 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
|
||||||
10
spec/models/abilities/valuator_spec.rb
Normal file
10
spec/models/abilities/valuator_spec.rb
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user