Merge pull request #2510 from consul/valuator-groups

Add valuator groups
This commit is contained in:
Raimond Garcia
2018-04-05 00:21:04 +02:00
committed by GitHub
48 changed files with 777 additions and 94 deletions

View File

@@ -7,11 +7,12 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
has_orders %w{oldest}, only: [:show, :edit]
has_filters(%w{all without_admin without_valuator under_valuation
valuation_finished winners},
only: [:index, :toggle_selection])
only: [:index, :toggle_selection])
before_action :load_budget
before_action :load_investment, only: [:show, :edit, :update, :toggle_selection]
before_action :load_ballot, only: [:show, :index]
before_action :parse_valuation_filters
before_action :load_investments, only: [:index, :toggle_selection]
def index
@@ -32,6 +33,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
def edit
load_admins
load_valuators
load_valuator_groups
load_tags
end
@@ -45,6 +47,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
else
load_admins
load_valuators
load_valuator_groups
load_tags
render :edit
end
@@ -93,7 +96,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
def budget_investment_params
params.require(:budget_investment)
.permit(:title, :description, :external_url, :heading_id, :administrator_id, :tag_list,
:valuation_tag_list, :incompatible, :selected, valuator_ids: [])
:valuation_tag_list, :incompatible, :selected, valuator_ids: [], valuator_group_ids: [])
end
def load_budget
@@ -112,6 +115,10 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
@valuators = Valuator.includes(:user).all.order(description: :asc).order("users.email ASC")
end
def load_valuator_groups
@valuator_groups = ValuatorGroup.all.order(name: :asc)
end
def load_tags
@tags = Budget::Investment.tags_on(:valuation).order(:name).uniq
end
@@ -126,4 +133,16 @@ class Admin::BudgetInvestmentsController < Admin::BaseController
params[:budget_investment] = params[:budget_investment].except(:valuation_tag_list)
end
def parse_valuation_filters
if params[:valuator_or_group_id]
model, id = params[:valuator_or_group_id].split("_")
if model == "group"
params[:valuator_group_id] = id
else
params[:valuator_id] = id
end
end
end
end

View File

@@ -0,0 +1,52 @@
class Admin::ValuatorGroupsController < Admin::BaseController
def index
@groups = ValuatorGroup.all.page(params[:page])
end
def show
@group = ValuatorGroup.find(params[:id])
end
def new
@group = ValuatorGroup.new
end
def edit
@group = ValuatorGroup.find(params[:id])
end
def create
@group = ValuatorGroup.new(group_params)
if @group.save
notice = t("flash.actions.create.valuator_group")
redirect_to [:admin, :valuator_groups], notice: notice
else
render :new
end
end
def update
@group = ValuatorGroup.find(params[:id])
if @group.update(group_params)
notice = t("flash.actions.update.valuator_group")
redirect_to [:admin, @group], notice: notice
else
render :edit
end
end
def destroy
@group = ValuatorGroup.find(params[:id])
@group.destroy
notice = t("flash.actions.destroy.valuator_group")
redirect_to [:admin, :valuator_groups], notice: notice
end
private
def group_params
params.require(:valuator_group).permit(:name)
end
end

View File

@@ -1,6 +1,10 @@
class Admin::ValuatorsController < Admin::BaseController
load_and_authorize_resource
def show
@valuator = Valuator.find(params[:id])
end
def index
@valuators = @valuators.page(params[:page])
end
@@ -13,12 +17,27 @@ class Admin::ValuatorsController < Admin::BaseController
end
def create
@valuator = Valuator.new(create_params)
@valuator = Valuator.new(valuator_params)
@valuator.save
redirect_to admin_valuators_path
end
def edit
@valuator = Valuator.find(params[:id])
@valuator_groups = ValuatorGroup.all
end
def update
@valuator = Valuator.find(params[:id])
if @valuator.update(valuator_params)
notice = t("admin.valuators.form.updated")
redirect_to [:admin, @valuator], notice: notice
else
render :edit
end
end
def destroy
@valuator.destroy
redirect_to admin_valuators_path
@@ -30,9 +49,9 @@ class Admin::ValuatorsController < Admin::BaseController
private
def create_params
def valuator_params
params[:valuator][:description] = nil if params[:valuator][:description].blank?
params.require(:valuator).permit(:user_id, :description)
params.require(:valuator).permit(:user_id, :description, :valuator_group_id)
end
end

View File

@@ -1,13 +1,21 @@
module ValuationHelper
def valuator_select_options(valuator = nil)
if valuator.present?
Valuator.where.not(id: valuator.id).order("description ASC").order("users.email ASC")
.includes(:user).collect { |v| [ v.description_or_email, v.id ] }
.prepend([valuator.description_or_email, valuator.id])
else
Valuator.all.order("description ASC").order("users.email ASC").includes(:user).collect { |v| [ v.description_or_email, v.id ] }
end
def valuator_or_group_select_options
valuator_group_select_options + valuator_select_options
end
def valuator_select_options
Valuator.order("description ASC").order("users.email ASC").includes(:user).
collect { |v| [ v.description_or_email, "valuator_#{v.id}"] }
end
def valuator_group_select_options
ValuatorGroup.order("name ASC").collect { |g| [ g.name, "group_#{g.id}"] }
end
def assigned_valuators(investment)
[investment.valuator_groups.collect(&:name) +
investment.valuators.collect(&:description_or_name)].flatten.join(', ')
end
def assigned_valuators_info(valuators)

View File

@@ -43,7 +43,7 @@ module Abilities
can [:search, :create, :index, :destroy], ::Administrator
can [:search, :create, :index, :destroy], ::Moderator
can [:search, :create, :index, :destroy, :summary], ::Valuator
can [:search, :show, :edit, :update, :create, :index, :destroy, :summary], ::Valuator
can [:search, :create, :index, :destroy], ::Manager
can [:search, :index], ::User

View File

@@ -4,9 +4,10 @@ module Abilities
def initialize(user)
valuator = user.valuator
can [:read, :update, :valuate], SpendingProposal
can [:read, :update, :comment_valuation], Budget::Investment, id: valuator.investment_ids
can [:valuate], Budget::Investment, { id: valuator.investment_ids, valuation_finished: false }
can [:read, :update, :comment_valuation], Budget::Investment, id: valuator.assigned_investment_ids
can [:valuate], Budget::Investment, { id: valuator.assigned_investment_ids, valuation_finished: false }
cannot [:update, :valuate, :comment_valuation], Budget::Investment, budget: { phase: 'finished' }
end
end

View File

@@ -35,6 +35,9 @@ class Budget
has_many :valuator_assignments, dependent: :destroy
has_many :valuators, through: :valuator_assignments
has_many :valuator_group_assignments, dependent: :destroy
has_many :valuator_groups, through: :valuator_group_assignments
has_many :comments, -> {where(valuation: false)}, as: :commentable, class_name: 'Comment'
has_many :valuations, -> {where(valuation: true)}, as: :commentable, class_name: 'Comment'
@@ -59,9 +62,9 @@ class Budget
scope :valuation_open, -> { where(valuation_finished: false) }
scope :without_admin, -> { valuation_open.where(administrator_id: nil) }
scope :without_valuator, -> { valuation_open.where(valuator_assignments_count: 0) }
scope :under_valuation, -> { valuation_open.where("valuator_assignments_count > 0 AND administrator_id IS NOT ?", nil) }
scope :under_valuation, -> { valuation_open.valuating.where("administrator_id IS NOT ?", nil) }
scope :managed, -> { valuation_open.where(valuator_assignments_count: 0).where("administrator_id IS NOT ?", nil) }
scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0") }
scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0 OR valuator_group_assignments_count > 0" ) }
scope :valuation_finished, -> { where(valuation_finished: true) }
scope :valuation_finished_feasible, -> { where(valuation_finished: true, feasibility: "feasible") }
scope :feasible, -> { where(feasibility: "feasible") }
@@ -76,12 +79,13 @@ class Budget
scope :unselected, -> { not_unfeasible.where(selected: false) }
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
scope :by_group, ->(group_id) { where(group_id: group_id) }
scope :by_heading, ->(heading_id) { where(heading_id: heading_id) }
scope :by_admin, ->(admin_id) { where(administrator_id: admin_id) }
scope :by_tag, ->(tag_name) { tagged_with(tag_name) }
scope :by_valuator, ->(valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) }
scope :by_budget, ->(budget) { where(budget: budget) }
scope :by_budget, ->(budget) { where(budget: budget) }
scope :by_group, ->(group_id) { where(group_id: group_id) }
scope :by_heading, ->(heading_id) { where(heading_id: heading_id) }
scope :by_admin, ->(admin_id) { where(administrator_id: admin_id) }
scope :by_tag, ->(tag_name) { tagged_with(tag_name) }
scope :by_valuator, ->(valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) }
scope :by_valuator_group, ->(valuator_group_id) { where("budget_valuator_group_assignments.valuator_group_id = ?", valuator_group_id).joins(:valuator_group_assignments) }
scope :for_render, -> { includes(:heading) }
@@ -106,13 +110,14 @@ class Budget
budget = Budget.find_by(slug: params[:budget_id]) || Budget.find_by(id: params[:budget_id])
results = Investment.by_budget(budget)
results = limit_results(budget, params, results) if params[:max_per_heading].present?
results = results.where(group_id: params[:group_id]) if params[:group_id].present?
results = results.by_tag(params[:tag_name]) if params[:tag_name].present?
results = results.by_heading(params[:heading_id]) if params[:heading_id].present?
results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present?
results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present?
results = advanced_filters(params, results) if params[:advanced_filters].present?
results = limit_results(budget, params, results) if params[:max_per_heading].present?
results = results.where(group_id: params[:group_id]) if params[:group_id].present?
results = results.by_tag(params[:tag_name]) if params[:tag_name].present?
results = results.by_heading(params[:heading_id]) if params[:heading_id].present?
results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present?
results = results.by_valuator_group(params[:valuator_group_id]) if params[:valuator_group_id].present?
results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present?
results = advanced_filters(params, results) if params[:advanced_filters].present?
results = results.send(current_filter) if current_filter.present?
results.includes(:heading, :group, :budget, administrator: :user, valuators: :user)

View File

@@ -0,0 +1,6 @@
class Budget
class ValuatorGroupAssignment < ActiveRecord::Base
belongs_to :valuator_group, counter_cache: :budget_investments_count
belongs_to :investment, counter_cache: true
end
end

View File

@@ -1,5 +1,7 @@
class Valuator < ActiveRecord::Base
belongs_to :user, touch: true
belongs_to :valuator_group
delegate :name, :email, :name_and_email, to: :user
has_many :valuation_assignments, dependent: :destroy
@@ -16,4 +18,9 @@ class Valuator < ActiveRecord::Base
def description_or_name
description.present? ? description : name
end
def assigned_investment_ids
investment_ids + [valuator_group.try(:investment_ids)].flatten
end
end

View File

@@ -0,0 +1,5 @@
class ValuatorGroup < ActiveRecord::Base
has_many :valuators
has_many :valuator_group_assignments, dependent: :destroy, class_name: 'Budget::ValuatorGroupAssignment'
has_many :investments, through: :valuator_group_assignments, class_name: 'Budget::Investment'
end

View File

@@ -60,10 +60,10 @@
<% end %>
</td>
<td class="small">
<% if investment.valuators.size.zero? %>
<% if investment.valuators.size == 0 && investment.valuator_groups.size == 0 %>
<%= t("admin.budget_investments.index.no_valuators_assigned") %>
<% else %>
<%= investment.valuators.collect(&:description_or_name).join(", ") %>
<%= assigned_valuators(investment) %>
<% end %>
</td>
<td class="small">

View File

@@ -9,7 +9,7 @@
<%= hidden_field_tag filter_name, filter_value %>
<% end %>
<div class="row">
<div class="row expanded">
<div class="small-12 column">
<%= f.text_field :title, maxlength: Budget::Investment.title_max_length %>
</div>
@@ -26,22 +26,26 @@
</div>
<div class="small-12 column">
<%= f.text_field :external_url %>
</div>
<div class="small-12 medium-6">
<%= f.text_field :external_url %>
</div>
<div class="small-12 column">
<%= f.select :heading_id, budget_heading_select_options(@budget), include_blank: t("admin.budget_investments.edit.select_heading") %>
<div class="small-12 medium-6">
<%= f.select :heading_id, budget_heading_select_options(@budget), include_blank: t("admin.budget_investments.edit.select_heading") %>
</div>
</div>
</div>
<h2 id="classification"><%= t("admin.budget_investments.edit.classification") %></h2>
<div class="row">
<div class="row expanded margin-top">
<div class="small-12 column">
<%= f.select(:administrator_id,
@admins.collect{ |a| [a.name_and_email, a.id ] },
{ include_blank: t("admin.budget_investments.edit.undefined") }) %>
<h2 id="classification"><%= t("admin.budget_investments.edit.classification") %></h2>
<div class="small-12 medium-6">
<%= f.select(:administrator_id,
@admins.collect{ |a| [a.name_and_email, a.id ] },
{ include_blank: t("admin.budget_investments.edit.undefined") }) %>
</div>
</div>
@@ -59,17 +63,27 @@
class: 'js-tag-list' %>
</div>
<div class="small-12 column">
<%= f.label :valuator_ids, t("admin.budget_investments.edit.assigned_valuators") %>
<%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %>
<%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %>
<div class="small-12 column margin-top">
<%= f.label :valuator_ids, t("admin.budget_investments.edit.user_groups") %>
<ul>
<%= f.collection_check_boxes :valuator_group_ids, @valuator_groups, :id, :name do |group| %>
<li><%= group.label(title: group.object.name) { group.check_box + truncate(group.object.name, length: 60) } %></li>
<% end %>
</ul>
</div>
<div class="small-12 column">
<hr>
<%= f.label :valuator_ids, t("admin.budget_investments.edit.assigned_valuators") %>
<ul>
<%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %>
<li><%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %></li>
<% end %>
</ul>
</div>
</div>
<div class="row margin-top">
<div class="row expanded margin-top">
<% if @investment.incompatible? || @investment.winner? %>
<div class="small-12 medium-3 column">
<h2 id="incompatible"><%= t("admin.budget_investments.edit.compatibility") %></h2>
@@ -88,7 +102,7 @@
</div>
</div>
<div class="margin-top">
<div class="small-12 column margin-top">
<%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %>
</div>
<% end %>

View File

@@ -12,8 +12,8 @@
</div>
<div class="small-12 medium-3 column">
<%= select_tag :valuator_id,
options_for_select(valuator_select_options, params[:valuator_id]),
<%= select_tag :valuator_or_group_id,
options_for_select(valuator_or_group_select_options, params[:valuator_or_group_id]),
{ prompt: t("admin.budget_investments.index.valuator_filter_all"),
label: false,
class: "js-submit-on-change" } %>

View File

@@ -22,6 +22,15 @@
<%= @investment.tags_on(:valuation).pluck(:name).sort.join(', ') %>
</p>
<p id="assigned_valuator_groups">
<strong><%= t("admin.budget_investments.show.valuator_groups") %>:</strong>
<% if @investment.valuator_groups.any? %>
<%= @investment.valuator_groups.collect(&:name).join(', ') %>
<% else %>
<%= t("admin.budget_investments.show.undefined") %>
<% end %>
</p>
<p id="assigned_valuators">
<strong><%= t("admin.budget_investments.show.assigned_valuators") %>:</strong>
<% if @investment.valuators.any? %>

View File

@@ -0,0 +1,17 @@
<tr id="<%= dom_id(group) %>">
<td>
<%= link_to group.name, admin_valuator_group_path(group) %>
</td>
<td>
<%= group.valuators.count %>
</td>
<td>
<%= link_to t("admin.actions.delete"),
admin_valuator_group_path(group),
method: :delete, class: "button hollow alert" %>
<%= link_to t("admin.actions.edit"),
edit_admin_valuator_group_path(group),
class: "button hollow" %>
</td>
</tr>

View File

@@ -0,0 +1,4 @@
<li>
<%= link_to valuator.description_or_email,
admin_valuator_path(valuator) %>
</li>

View File

@@ -0,0 +1,9 @@
<%= back_link_to admin_valuator_groups_path %>
<div class="small-12 medium-6 margin-top">
<%= form_for [:admin, @group] do |f| %>
<%= f.label :name, t("admin.valuator_groups.form.name") %>
<%= f.text_field :name, label: false %>
<%= f.submit t("admin.valuator_groups.form.edit"), class: "button success" %>
<% end %>
</div>

View File

@@ -0,0 +1,24 @@
<h2 class="inline-block"><%= t("admin.valuator_groups.index.title") %></h2>
<%= link_to t("admin.valuator_groups.index.new"), new_admin_valuator_group_path, class: "button float-right" %>
<% if @groups.any? %>
<h3 class="margin"><%= page_entries_info @groups %></h3>
<table>
<thead>
<th scope="col"><%= t("admin.valuator_groups.index.name") %></th>
<th scope="col"><%= t("admin.valuator_groups.index.members") %></th>
<th scope="col" class="small-3"><%= t("admin.shared.actions") %></th>
</thead>
<tbody>
<%= render partial: 'group', collection: @groups %>
</tbody>
</table>
<%= paginate @groups %>
<% else %>
<div class="callout primary">
<%= t("admin.valuator_groups.index.no_groups") %>
</div>
<% end %>

View File

@@ -0,0 +1,9 @@
<%= back_link_to admin_valuator_groups_path %>
<div class="small-12 medium-6 margin-top">
<%= form_for [:admin, @group] do |f| %>
<%= f.label :name, t("admin.valuator_groups.form.name") %>
<%= f.text_field :name, label: false %>
<%= f.submit t("admin.valuator_groups.form.new"), class: "button success" %>
<% end %>
</div>

View File

@@ -0,0 +1,13 @@
<%= back_link_to admin_valuator_groups_path %>
<h2><%= t("admin.valuator_groups.show.title", group: @group.name) %></h2>
<ul id="valuators">
<% if @group.valuators.any? %>
<%= render partial: 'valuator', collection: @group.valuators %>
<% else %>
<div class="callout primary">
<%= t("admin.valuator_groups.show.no_valuators") %>
</div>
<% end %>
</ul>

View File

@@ -0,0 +1,20 @@
<%= back_link_to admin_valuators_path %>
<h2><%= t("admin.valuators.form.edit_title") %></h2>
<div class="callout highlight">
<strong><%= @valuator.name %></strong><br>
<%= @valuator.email %>
</div>
<div class="margin-top">
<%= form_for [:admin, @valuator] do |f| %>
<%= f.text_field :description %>
<div class="small-12 medium-6">
<%= f.select :valuator_group_id,
@valuator_groups.map {|group| [group.name, group.id] },
{ include_blank: true } %>
</div>
<%= f.submit t("admin.valuators.form.update"), class: "button success" %>
<% end %>
</div>

View File

@@ -1,4 +1,6 @@
<h2><%= t("admin.valuators.index.title") %></h2>
<h2 class="inline-block"><%= t("admin.valuators.index.title") %></h2>
<%= link_to t("admin.valuators.index.valuator_groups"), admin_valuator_groups_path, class: "button hollow float-right" %>
<%= render "admin/shared/user_search", url: search_admin_valuators_path %>
@@ -11,12 +13,13 @@
<th scope="col"><%= t("admin.valuators.index.name") %></th>
<th scope="col"><%= t("admin.valuators.index.email") %></th>
<th scope="col"><%= t("admin.valuators.index.description") %></th>
<th scope="col"><%= t("admin.valuators.index.group") %></th>
<th scope="col"><%= t("admin.actions.actions") %></th>
</thead>
<tbody>
<% @valuators.each do |valuator| %>
<tr>
<td><%= valuator.name %></td>
<td><%= link_to valuator.name, admin_valuator_path(valuator) %></td>
<td><%= valuator.email %></td>
<td>
<% if valuator.description.present? %>
@@ -26,10 +29,20 @@
<% end %>
</td>
<td>
<% if valuator.valuator_group.present? %>
<%= valuator.valuator_group.try(:name) %>
<% else %>
<%= t("admin.valuators.index.no_group") %>
<% end %>
</td>
<td>
<%= link_to t("admin.actions.edit"),
edit_admin_valuator_path(valuator),
class: "button hollow" %>
<%= link_to t("admin.valuators.valuator.delete"),
admin_valuator_path(valuator),
method: :delete,
class: "button hollow alert expanded" %>
class: "button hollow alert" %>
</td>
</tr>
<% end %>

View File

@@ -30,17 +30,19 @@
<% end %>
<td>
<% if user.valuator? %>
<%= link_to t("admin.actions.edit"),
edit_admin_valuator_path(user.valuator),
class: "button hollow" %>
<%= link_to t("admin.valuators.valuator.delete"),
admin_valuator_path(user),
method: :delete,
class: "button hollow alert expanded" %>
<% else %>
<%= form_for Valuator.new(user: user), url: admin_valuators_path do |f| %>
<%= f.text_field :description,
label: false,
placeholder: t("admin.valuators.valuator.description_placeholder") %>
<%= f.hidden_field :user_id %>
<%= f.submit t("admin.valuators.valuator.add"), class: "button success expanded" %>
<%= f.submit t("admin.valuators.valuator.add"),
class: "button success expanded" %>
<% end %>
<% end %>
</td>

View File

@@ -0,0 +1,30 @@
<%= back_link_to admin_valuators_path %>
<%= link_to t("admin.actions.edit"), edit_admin_valuator_path(@valuator), class: "button hollow float-right" %>
<div class="clear"></div>
<h2><%= @valuator.name %></h2>
<div class="callout highlight">
<p>
<strong><%= t("admin.valuators.show.email") %></strong><br>
<%= @valuator.email %>
</p>
<p>
<strong><%= t("admin.valuators.show.description") %></strong><br>
<% if @valuator.description.present? %>
<%= @valuator.description %>
<% else %>
<%= t("admin.valuators.show.no_description") %>
<% end %>
</p>
<p>
<strong><%= t("admin.valuators.show.group") %></strong><br>
<% if @valuator.valuator_group.present? %>
<%= @valuator.valuator_group.try(:name) %>
<% else %>
<%= t("admin.valuators.show.no_group") %>
<% end %>
</p>
</div>

View File

@@ -42,6 +42,7 @@ data:
- config/locales/%{locale}/documents.yml
- config/locales/%{locale}/images.yml
- config/locales/%{locale}/guides.yml
- config/locales/%{locale}/user_groups.yml
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
# `i18n-tasks normalize -p` will force move the keys according to these rules

View File

@@ -32,8 +32,11 @@ en:
one: "Administrator"
other: "Administrators"
valuator:
one: "Evaluador"
other: "Evaluadores"
one: "Valuator"
other: "Valuators"
valuator_group:
one: "Valuator group"
other: "Valuator groups"
manager:
one: "Manager"
other: "Managers"

View File

@@ -13,6 +13,7 @@ en:
unmark_featured: Unmark featured
edit: Edit
configure: Configure
delete: Delete
banners:
index:
title: Banners
@@ -176,7 +177,6 @@ en:
undecided: "Undecided"
selected: "Selected"
select: "Select"
download_current_selection: "Download current selection"
table_id: "ID"
table_title: "Title"
table_supports: "Supports"
@@ -223,6 +223,7 @@ en:
documents: "Documents"
see_documents: "See documents (%{count})"
no_documents: "Without documents"
valuator_groups: "Valuator Groups"
edit:
classification: Classification
compatibility: Compatibility
@@ -236,6 +237,7 @@ en:
tags: Tags
tags_placeholder: "Write the tags you want separated by commas (,)"
undefined: Undefined
user_groups: "Groups"
search_unfeasible: Search unfeasible
milestones:
index:
@@ -581,8 +583,10 @@ en:
description: Description
no_description: No description
no_valuators: There are no valuators.
valuator_groups: "Valuator Groups"
group: "Group"
no_group: "No group"
valuator:
description_placeholder: 'Description (optional)'
add: Add to valuators
delete: Delete
search:
@@ -596,6 +600,30 @@ en:
in_evaluation_count: In evaluation
total_count: Total
cost: Cost
form:
edit_title: "Valuators: Edit valuator"
update: "Update valuator"
updated: "Valuator updated successfully"
show:
description: "Description"
email: "Email"
group: "Group"
no_description: "Without description"
no_group: "Without group"
valuator_groups:
index:
title: "Valuator groups"
new: "Create valuators group"
name: "Name"
members: "Members"
no_groups: "There are no valuator groups"
show:
title: "Valuators group: %{group}"
no_valuators: "There are no valuators assigned to this group"
form:
name: "Group name"
new: "Create valuators group"
edit: "Save valuators group"
poll_officers:
index:
title: Poll officers

View File

@@ -16,6 +16,7 @@ en:
budget_investment: "Budget Investment created successfully."
signature_sheet: "Signature sheet created successfully"
topic: "Topic created successfully."
valuator_group: "Valuator group created successfully"
save_changes:
notice: Changes saved
update:
@@ -27,9 +28,11 @@ en:
spending_proposal: "Investment project updated succesfully."
budget_investment: "Investment project updated succesfully."
topic: "Topic updated successfully."
valuator_group: "Valuator group updated successfully"
destroy:
spending_proposal: "Spending proposal deleted succesfully."
budget_investment: "Investment project deleted succesfully."
error: "Could not delete"
topic: "Topic deleted successfully."
poll_question_answer_video: "Answer video deleted successfully."
valuator_group: "Valuator group deleted successfully"

View File

@@ -34,6 +34,9 @@ es:
valuator:
one: "Evaluador"
other: "Evaluadores"
valuator_group:
one: "Grupo de Evaluadores"
other: "Grupos de Evaluadores"
manager:
one: "Gestor"
other: "Gestores"

View File

@@ -13,6 +13,7 @@ es:
unmark_featured: Quitar destacado
edit: Editar
configure: Configurar
delete: Borrar
banners:
index:
title: Banners
@@ -222,6 +223,7 @@ es:
documents: "Documentos"
see_documents: "Ver documentos (%{count})"
no_documents: "Sin documentos"
valuator_groups: "Grupos de evaluadores"
edit:
classification: Clasificación
compatibility: Compatibilidad
@@ -235,6 +237,7 @@ es:
tags: Etiquetas
tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)"
undefined: Sin definir
user_groups: "Grupos"
search_unfeasible: Buscar inviables
milestones:
index:
@@ -580,8 +583,10 @@ es:
description: Descripción
no_description: Sin descripción
no_valuators: No hay evaluadores.
valuator_groups: "Grupo de evaluadores"
group: "Grupo"
no_group: "Sin grupo"
valuator:
description_placeholder: 'Descripción (opcional)'
add: Añadir como evaluador
delete: Borrar
search:
@@ -595,6 +600,30 @@ es:
in_evaluation_count: En evaluación
total_count: Total
cost: Coste total
form:
edit_title: "Evaluadores: Editar evaluador"
update: "Actualizar evaluador"
updated: "Evaluador actualizado correctamente"
show:
description: "Descripción"
email: "Email"
group: "Grupo"
no_description: "Sin descripción"
no_group: "Sin grupo"
valuator_groups:
index:
title: "Grupos de evaluadores"
new: "Crear grupo de evaluadores"
name: "Nombre"
members: "Miembros"
no_groups: "No hay grupos de evaluadores"
show:
title: "Grupo de evaluadores: %{group}"
no_valuators: "No hay evaluadores asigandos a este grupo"
form:
name: "Nombre del grupo"
new: "Crear grupo de evaluadores"
edit: "Guardar grupo de evaluadores"
poll_officers:
index:
title: Presidentes de mesa

View File

@@ -16,6 +16,7 @@ es:
budget_investment: "Proyecto de gasto creado correctamente."
signature_sheet: "Hoja de firmas creada correctamente"
topic: "Tema creado correctamente."
valuator_group: "Grupo de evaluadores creado correctamente"
save_changes:
notice: Cambios guardados
update:
@@ -27,9 +28,11 @@ es:
spending_proposal: "Propuesta de inversión actualizada correctamente."
budget_investment: "Proyecto de gasto actualizado correctamente"
topic: "Tema actualizado correctamente."
valuator_group: "Grupo de evaluadores actualizado correctamente"
destroy:
spending_proposal: "Propuesta de inversión eliminada."
budget_investment: "Proyecto de gasto eliminado."
error: "No se pudo borrar"
topic: "Tema eliminado."
poll_question_answer_video: "Vídeo de respuesta eliminado."
valuator_group: "Grupo de evaluadores eliminado correctamente"

View File

@@ -246,7 +246,6 @@ fr:
index:
title: Évaluateurs
valuator:
description_placeholder: 'Description (optionnel)'
user_found: Utilisateur trouvé
add: Ajouter un évaluateur
search:

View File

@@ -179,7 +179,6 @@ he:
index:
title: Valuators
valuator:
description_placeholder: 'Description (optional)'
add: Add to valuators
summary:
title: Valuator summary for investment projects

View File

@@ -246,7 +246,6 @@ nl:
index:
title: Valuators
valuator:
description_placeholder: 'Description (optional)'
user_found: User found
add: Add to valuators
search:

View File

@@ -501,7 +501,6 @@ val:
no_description: Sense descripció
no_valuators: No hi ha avaluadors.
valuator:
description_placeholder: 'Descripció (opcional)'
add: Afegir com a avaluador
delete: Borrar
search:

View File

@@ -80,10 +80,11 @@ namespace :admin do
get :search, on: :collection
end
resources :valuators, only: [:index, :create, :destroy] do
resources :valuators, only: [:show, :index, :edit, :update, :create, :destroy] do
get :search, on: :collection
get :summary, on: :collection
end
resources :valuator_groups
resources :managers, only: [:index, :create, :destroy] do
get :search, on: :collection

View File

@@ -0,0 +1,7 @@
class CreateValuatorGroups < ActiveRecord::Migration
def change
create_table :valuator_groups do |t|
t.string :name
end
end
end

View File

@@ -0,0 +1,5 @@
class AddValuatorGroupToValuators < ActiveRecord::Migration
def change
add_column :valuators, :valuator_group_id, :integer
end
end

View File

@@ -0,0 +1,8 @@
class CreateBudgetValuatorGroupAssignment < ActiveRecord::Migration
def change
create_table :budget_valuator_group_assignments do |t|
t.integer :valuator_group_id
t.integer :investment_id
end
end
end

View File

@@ -0,0 +1,6 @@
class AddBudgetValuatorGroupAssignmentsCounters < ActiveRecord::Migration
def change
add_column :budget_investments, :valuator_group_assignments_count, :integer, default: 0
add_column :valuator_groups, :budget_investments_count, :integer, default: 0
end
end

View File

@@ -133,35 +133,36 @@ ActiveRecord::Schema.define(version: 20180320104823) do
t.string "title"
t.text "description"
t.string "external_url"
t.integer "price", limit: 8
t.string "feasibility", limit: 15, default: "undecided"
t.integer "price", limit: 8
t.string "feasibility", limit: 15, default: "undecided"
t.text "price_explanation"
t.text "unfeasibility_explanation"
t.boolean "valuation_finished", default: false
t.integer "valuator_assignments_count", default: 0
t.integer "price_first_year", limit: 8
t.boolean "valuation_finished", default: false
t.integer "valuator_assignments_count", default: 0
t.integer "price_first_year", limit: 8
t.string "duration"
t.datetime "hidden_at"
t.integer "cached_votes_up", default: 0
t.integer "comments_count", default: 0
t.integer "confidence_score", default: 0, null: false
t.integer "physical_votes", default: 0
t.integer "cached_votes_up", default: 0
t.integer "comments_count", default: 0
t.integer "confidence_score", default: 0, null: false
t.integer "physical_votes", default: 0
t.tsvector "tsv"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "heading_id"
t.string "responsible_name"
t.integer "budget_id"
t.integer "group_id"
t.boolean "selected", default: false
t.boolean "selected", default: false
t.string "location"
t.string "organization_name"
t.datetime "unfeasible_email_sent_at"
t.integer "ballot_lines_count", default: 0
t.integer "ballot_lines_count", default: 0
t.integer "previous_heading_id"
t.boolean "winner", default: false
t.boolean "incompatible", default: false
t.boolean "winner", default: false
t.boolean "incompatible", default: false
t.integer "community_id"
t.integer "valuator_group_assignments_count", default: 0
end
add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree
@@ -203,6 +204,11 @@ ActiveRecord::Schema.define(version: 20180320104823) do
add_index "budget_valuator_assignments", ["investment_id"], name: "index_budget_valuator_assignments_on_investment_id", using: :btree
create_table "budget_valuator_group_assignments", force: :cascade do |t|
t.integer "valuator_group_id"
t.integer "investment_id"
end
create_table "budgets", force: :cascade do |t|
t.string "name", limit: 80
t.string "currency_symbol", limit: 10
@@ -1128,11 +1134,17 @@ ActiveRecord::Schema.define(version: 20180320104823) do
t.datetime "updated_at", null: false
end
create_table "valuator_groups", force: :cascade do |t|
t.string "name"
t.integer "budget_investments_count", default: 0
end
create_table "valuators", force: :cascade do |t|
t.integer "user_id"
t.string "description"
t.integer "spending_proposals_count", default: 0
t.integer "budget_investments_count", default: 0
t.integer "valuator_group_id"
end
add_index "valuators", ["user_id"], name: "index_valuators_on_user_id", using: :btree

View File

@@ -61,6 +61,9 @@ FactoryBot.define do
end
end
factory :valuator_group, class: ValuatorGroup do
end
factory :identity do
user nil
provider "Twitter"

View File

@@ -54,7 +54,7 @@ feature 'Admin budget investments' do
end
end
scenario 'Displaying assignments info' do
scenario 'Display admin and valuator assignments' do
budget_investment1 = create(:budget_investment, budget: budget)
budget_investment2 = create(:budget_investment, budget: budget)
budget_investment3 = create(:budget_investment, budget: budget)
@@ -86,6 +86,33 @@ feature 'Admin budget investments' do
end
end
scenario 'Display valuator group assignments' do
budget_investment1 = create(:budget_investment, budget: budget)
budget_investment2 = create(:budget_investment, budget: budget)
budget_investment3 = create(:budget_investment, budget: budget)
health_group = create(:valuator_group, name: "Health")
culture_group = create(:valuator_group, name: "Culture")
budget_investment1.valuator_groups << health_group
budget_investment2.valuator_group_ids = [health_group.id, culture_group.id]
visit admin_budget_budget_investments_path(budget_id: budget)
within("#budget_investment_#{budget_investment1.id}") do
expect(page).to have_content("Health")
end
within("#budget_investment_#{budget_investment2.id}") do
expect(page).to have_content("Health")
expect(page).to have_content("Culture")
end
within("#budget_investment_#{budget_investment3.id}") do
expect(page).to have_content("No valuators assigned")
end
end
scenario "Filtering by budget heading", :js do
group1 = create(:budget_group, name: "Streets", budget: budget)
group2 = create(:budget_group, name: "Parks", budget: budget)
@@ -171,24 +198,57 @@ feature 'Admin budget investments' do
expect(page).to have_link("Realocate visitors")
expect(page).to have_link("Destroy the city")
select "Valuator 1", from: "valuator_id"
select "Valuator 1", from: "valuator_or_group_id"
expect(page).to have_content('There is 1 investment')
expect(page).not_to have_link("Destroy the city")
expect(page).to have_link("Realocate visitors")
select "All valuators", from: "valuator_id"
select "All valuators", from: "valuator_or_group_id"
expect(page).to have_content('There are 2 investments')
expect(page).to have_link("Destroy the city")
expect(page).to have_link("Realocate visitors")
select "Valuator 1", from: "valuator_id"
select "Valuator 1", from: "valuator_or_group_id"
expect(page).to have_content('There is 1 investment')
expect(page).not_to have_link("Destroy the city")
expect(page).to have_link("Realocate visitors")
end
scenario "Filtering by valuator group", :js do
user = create(:user)
health_group = create(:valuator_group, name: "Health")
culture_group = create(:valuator_group, name: "Culture")
budget_investment1 = create(:budget_investment, title: "Build a hospital", budget: budget)
budget_investment1.valuator_groups << health_group
budget_investment2 = create(:budget_investment, title: "Build a theatre", budget: budget)
budget_investment2.valuator_groups << culture_group
visit admin_budget_budget_investments_path(budget_id: budget)
expect(page).to have_link("Build a hospital")
expect(page).to have_link("Build a theatre")
select "Health", from: "valuator_or_group_id"
expect(page).to have_content('There is 1 investment')
expect(page).to have_link("Build a hospital")
expect(page).not_to have_link("Build a theatre")
select "All valuators", from: "valuator_or_group_id"
expect(page).to have_content('There are 2 investments')
expect(page).to have_link("Build a hospital")
expect(page).to have_link("Build a theatre")
select "Culture", from: "valuator_or_group_id"
expect(page).to have_content('There is 1 investment')
expect(page).to have_link("Build a theatre")
expect(page).not_to have_link("Build a hospital")
end
scenario "Current filter is properly highlighted" do
filters_links = { 'all' => 'All',
'without_admin' => 'Without assigned admin',
@@ -579,6 +639,33 @@ feature 'Admin budget investments' do
end
end
scenario "Add valuator group" do
budget_investment = create(:budget_investment)
health_group = create(:valuator_group, name: "Health")
economy_group = create(:valuator_group, name: "Economy")
culture_group = create(:valuator_group, name: "Culture")
visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment)
click_link 'Edit classification'
check "budget_investment_valuator_group_ids_#{health_group.id}"
check "budget_investment_valuator_group_ids_#{culture_group.id}"
click_button 'Update'
expect(page).to have_content 'Investment project updated succesfully.'
within('#assigned_valuator_groups') do
expect(page).to have_content('Health')
expect(page).to have_content('Culture')
expect(page).not_to have_content('Undefined')
expect(page).not_to have_content('Economy')
end
end
pending "Do not display valuators of an assigned group"
scenario "Adds existing valuation tags", :js do
budget_investment1 = create(:budget_investment)
budget_investment1.set_tag_list_on(:valuation, 'Education, Health')

View File

@@ -0,0 +1,126 @@
require 'rails_helper'
feature "Valuator groups" do
let(:admin) { create(:administrator).user }
background do
login_as(admin)
end
scenario "Index" do
group1 = create(:valuator_group)
group2 = create(:valuator_group)
3.times { create(:valuator, valuator_group: group1) }
visit admin_valuator_groups_path
within("#valuator_group_#{group1.id}") do
expect(page).to have_content group1.name
expect(page).to have_content 3
end
within("#valuator_group_#{group2.id}") do
expect(page).to have_content group2.name
expect(page).to have_content 0
end
end
scenario "Show" do
group = create(:valuator_group)
valuator1 = create(:valuator, valuator_group: group)
valuator2 = create(:valuator, valuator_group: group)
valuator3 = create(:valuator, valuator_group: nil)
visit admin_valuator_group_path(group)
expect(page).to have_content group.name
within("#valuators") do
expect(page).to have_link(valuator1.email, href: admin_valuator_path(valuator1))
expect(page).to have_link(valuator2.email, href: admin_valuator_path(valuator2))
end
end
scenario "Create" do
visit admin_valuators_path
click_link "Valuator Groups"
click_link "Create valuators group"
fill_in "valuator_group_name", with: "Health"
click_button "Create valuators group"
expect(page).to have_content "Valuator group created successfully"
expect(page).to have_content "There is 1 valuator group"
expect(page).to have_content "Health"
end
scenario "Update" do
group = create(:valuator_group, name: "Health")
visit admin_valuator_groups_path
click_link "Edit"
fill_in "valuator_group_name", with: "Health and Sports"
click_button "Save valuators group"
expect(page).to have_content "Valuator group updated successfully"
expect(page).to have_content "Health and Sports"
end
scenario "Delete" do
group = create(:valuator_group)
visit admin_valuator_groups_path
click_link "Delete"
expect(page).to have_content "Valuator group deleted successfully"
expect(page).to have_content "There are no valuator groups"
end
context "Assign valuators to groups" do
scenario "Add a valuator to a group" do
valuator = create(:valuator)
group = create(:valuator_group, name: "Health")
visit edit_admin_valuator_path(valuator)
select "Health", from: "valuator_valuator_group_id"
click_button "Update valuator"
expect(page).to have_content "Valuator updated successfully"
expect(page).to have_content "Health"
end
scenario "Update a valuator's group" do
valuator = create(:valuator)
group1 = create(:valuator_group, name: "Health")
group2 = create(:valuator_group, name: "Economy")
valuator.update(valuator_group: group1)
visit edit_admin_valuator_path(valuator)
select "Economy", from: "valuator_valuator_group_id"
click_button "Update valuator"
expect(page).to have_content "Valuator updated successfully"
expect(page).to have_content "Economy"
end
scenario "Remove a valuator from a group" do
valuator = create(:valuator)
group1 = create(:valuator_group, name: "Health")
valuator.update(valuator_group: group1)
visit edit_admin_valuator_path(valuator)
select "", from: "valuator_valuator_group_id"
click_button "Update valuator"
expect(page).to have_content "Valuator updated successfully"
expect(page).to_not have_content "Health"
end
end
end

View File

@@ -1,6 +1,7 @@
require 'rails_helper'
feature 'Admin valuators' do
background do
@admin = create(:administrator)
@user = create(:user, username: 'Jose Luis Balbin')
@@ -9,27 +10,44 @@ feature 'Admin valuators' do
visit admin_valuators_path
end
scenario "Show" do
visit admin_valuator_path(@valuator)
expect(page).to have_content @valuator.name
expect(page).to have_content @valuator.description
expect(page).to have_content @valuator.email
end
scenario 'Index' do
expect(page).to have_content(@valuator.name)
expect(page).to have_content(@valuator.email)
expect(page).not_to have_content(@user.name)
end
scenario 'Create Valuator', :js do
scenario 'Create', :js do
fill_in 'name_or_email', with: @user.email
click_button 'Search'
expect(page).to have_content(@user.name)
fill_in 'valuator_description', with: 'environmental expert'
click_button 'Add to valuators'
within('#valuators') do
expect(page).to have_content(@user.name)
expect(page).to have_content('environmental expert')
end
end
scenario 'Delete Valuator' do
scenario "Edit" do
visit edit_admin_valuator_path(@valuator)
fill_in 'valuator_description', with: 'Valuator for health'
click_button "Update valuator"
expect(page).to have_content "Valuator updated successfully"
expect(page).to have_content @valuator.email
expect(page).to have_content "Valuator for health"
end
scenario 'Destroy' do
click_link 'Delete'
within('#valuators') do

View File

@@ -6,12 +6,18 @@ describe Abilities::Valuator do
let(:user) { valuator.user }
let(:valuator) { create(:valuator) }
let(:group) { create(:valuator_group) }
let(:non_assigned_investment) { create(:budget_investment) }
let(:assigned_investment) { create(:budget_investment, budget: create(:budget, phase: 'valuating')) }
let(:group_assigned_investment) { create(:budget_investment, budget: create(:budget, phase: 'valuating')) }
let(:finished_assigned_investment) { create(:budget_investment, budget: create(:budget, phase: 'finished')) }
before do
assigned_investment.valuators << valuator
group_assigned_investment.valuator_groups << group
valuator.update(valuator_group: group)
finished_assigned_investment.valuators << valuator
end
@@ -31,6 +37,9 @@ describe Abilities::Valuator do
it { should be_able_to(:update, assigned_investment) }
it { should be_able_to(:valuate, assigned_investment) }
it { should be_able_to(:update, group_assigned_investment) }
it { should be_able_to(:valuate, group_assigned_investment) }
it { should_not be_able_to(:update, finished_assigned_investment) }
it { should_not be_able_to(:valuate, finished_assigned_investment) }
end

View File

@@ -349,6 +349,20 @@ describe Budget::Investment do
expect(valuating.size).to eq(1)
expect(valuating.first).to eq(investment2)
end
it "returns all investments with assigned valuator groups but valuation not finished" do
investment1 = create(:budget_investment)
investment2 = create(:budget_investment)
investment3 = create(:budget_investment, valuation_finished: true)
investment2.valuator_groups << create(:valuator_group)
investment3.valuator_groups << create(:valuator_group)
valuating = described_class.valuating
expect(valuating.size).to eq(1)
expect(valuating.first).to eq(investment2)
end
end
describe "valuation_finished" do

View File

@@ -15,4 +15,39 @@ describe Valuator do
expect(valuator.description_or_email).to eq(valuator.email)
end
end
describe "#assigned_investment_ids" do
it "returns investments assigned to a valuator" do
valuator = create(:valuator)
investment1 = create(:budget_investment)
investment2 = create(:budget_investment)
investment3 = create(:budget_investment)
investment1.valuators << valuator
investment2.valuators << valuator
assigned_investment_ids = valuator.assigned_investment_ids
expect(assigned_investment_ids).to include investment1.id
expect(assigned_investment_ids).to include investment2.id
expect(assigned_investment_ids).to_not include investment3.id
end
it "returns investments assigned to a valuator group" do
group = create(:valuator_group)
valuator = create(:valuator, valuator_group: group)
investment1 = create(:budget_investment)
investment2 = create(:budget_investment)
investment3 = create(:budget_investment)
investment1.valuator_groups << group
investment2.valuator_groups << group
assigned_investment_ids = valuator.assigned_investment_ids
expect(assigned_investment_ids).to include investment1.id
expect(assigned_investment_ids).to include investment2.id
expect(assigned_investment_ids).to_not include investment3.id
end
end
end