Merge pull request #1550 from consul/budgets-unfeasible

Investments reclassified as unfeasible
This commit is contained in:
Juanjo Bazán
2017-05-18 14:37:32 +02:00
committed by GitHub
11 changed files with 236 additions and 47 deletions

View File

@@ -13,6 +13,8 @@ class Budget
validate :check_sufficient_funds
validate :check_valid_heading
scope :by_investment, -> (investment_id) { where(investment_id: investment_id) }
before_validation :set_denormalized_ids
def check_sufficient_funds

View File

@@ -5,6 +5,7 @@ class Budget
include Sanitizable
include Taggable
include Searchable
include Reclassification
acts_as_votable
acts_as_paranoid column: :hidden_at
@@ -61,7 +62,6 @@ class Budget
before_save :calculate_confidence_score
before_validation :set_responsible_name
before_validation :set_denormalized_ids
after_save :check_for_reclassification
def self.filter_params(params)
params.select{|x,_| %w{heading_id group_id administrator_id tag_name valuator_id}.include? x.to_s }
@@ -243,25 +243,6 @@ class Budget
investments
end
def check_for_reclassification
if reclassified?
log_reclassification
remove_reclassified_votes
end
end
def reclassified?
budget.balloting? && heading_id_changed?
end
def log_reclassification
update_column(:previous_heading_id, heading_id_was)
end
def remove_reclassified_votes
Budget::Ballot::Line.where(investment: self).destroy_all
end
private
def set_denormalized_ids

View File

@@ -0,0 +1,50 @@
class Budget
module Reclassification
extend ActiveSupport::Concern
included do
after_save :check_for_reclassification
end
def check_for_reclassification
if heading_changed?
log_heading_change
store_reclassified_votes("heading_changed")
remove_reclassified_votes
elsif marked_as_unfeasible?
store_reclassified_votes("unfeasible")
remove_reclassified_votes
end
end
def heading_changed?
budget.balloting? && heading_id_changed?
end
def marked_as_unfeasible?
budget.balloting? && feasibility_changed? && unfeasible?
end
def log_heading_change
update_column(:previous_heading_id, heading_id_was)
end
def store_reclassified_votes(reason)
ballot_lines_for_investment.each do |line|
attrs = { user: line.ballot.user,
investment: self,
reason: reason }
Budget::ReclassifiedVote.create!(attrs)
end
end
def remove_reclassified_votes
ballot_lines_for_investment.destroy_all
end
def ballot_lines_for_investment
Budget::Ballot::Line.by_investment(self.id)
end
end
end

View File

@@ -0,0 +1,12 @@
class Budget
class ReclassifiedVote < ActiveRecord::Base
REASONS = %w(heading_changed unfeasible)
belongs_to :user
belongs_to :investment
validates :user, presence: true
validates :investment, presence: true
validates :reason, inclusion: {in: REASONS, allow_nil: false}
end
end

View File

@@ -0,0 +1,11 @@
class CreateBudgetReclassifiedVotes < ActiveRecord::Migration
def change
create_table :budget_reclassified_votes do |t|
t.integer :user_id
t.integer :investment_id
t.string :reason, default: nil
t.timestamps null: false
end
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170513110025) do
ActiveRecord::Schema.define(version: 20170517123042) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -154,6 +154,14 @@ ActiveRecord::Schema.define(version: 20170513110025) do
add_index "budget_investments", ["heading_id"], name: "index_budget_investments_on_heading_id", using: :btree
add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin
create_table "budget_reclassified_votes", force: :cascade do |t|
t.integer "user_id"
t.integer "investment_id"
t.string "reason"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "budget_valuator_assignments", force: :cascade do |t|
t.integer "valuator_id"
t.integer "investment_id"

View File

@@ -306,6 +306,12 @@ FactoryGirl.define do
association :investment, factory: :budget_investment
end
factory :budget_reclassified_vote, class: 'Budget::ReclassifiedVote' do
user
association :investment, factory: :budget_investment
reason "unfeasible"
end
factory :vote do
association :votable, factory: :debate
association :voter, factory: :user

View File

@@ -670,25 +670,51 @@ feature 'Budget Investments' do
expect(page).to_not have_link("Vote")
end
scenario "Reclassification" do
user = create(:user, :level_two)
investment = create(:budget_investment, :selected, heading: heading)
heading2 = create(:budget_heading, group: group)
feature "Reclassification" do
ballot = create(:budget_ballot, user: user, budget: budget)
ballot.investments << investment
scenario "Due to heading change" do
user = create(:user, :level_two)
investment = create(:budget_investment, :selected, heading: heading)
heading2 = create(:budget_heading, group: group)
login_as(user)
visit budget_ballot_path(budget)
ballot = create(:budget_ballot, user: user, budget: budget)
ballot.investments << investment
expect(page).to have_content("You have voted one investment")
login_as(user)
visit budget_ballot_path(budget)
investment.heading = heading2
investment.save
expect(page).to have_content("You have voted one investment")
visit budget_ballot_path(budget)
investment.heading = heading2
investment.save
visit budget_ballot_path(budget)
expect(page).to have_content("You have voted 0 investment")
end
scenario "Due to being unfeasible" do
user = create(:user, :level_two)
investment = create(:budget_investment, :selected, heading: heading)
heading2 = create(:budget_heading, group: group)
ballot = create(:budget_ballot, user: user, budget: budget)
ballot.investments << investment
login_as(user)
visit budget_ballot_path(budget)
expect(page).to have_content("You have voted one investment")
investment.feasibility = "unfeasible"
investment.unfeasibility_explanation = "too expensive"
investment.save
visit budget_ballot_path(budget)
expect(page).to have_content("You have voted 0 investment")
end
expect(page).to have_content("You have voted 0 investment")
end
end
end

View File

@@ -2,13 +2,14 @@ require 'rails_helper'
describe "Budget::Ballot::Line" do
let(:budget){ create(:budget) }
let(:group){ create(:budget_group, budget: budget) }
let(:heading){ create(:budget_heading, group: group, price: 10000000) }
let(:investment){ create(:budget_investment, :selected, price: 5000000, heading: heading) }
let(:ballot) { create(:budget_ballot, budget: budget) }
let(:ballot_line) { build(:budget_ballot_line, ballot: ballot, investment: investment) }
describe 'Validations' do
let(:budget){ create(:budget) }
let(:group){ create(:budget_group, budget: budget) }
let(:heading){ create(:budget_heading, group: group, price: 10000000) }
let(:investment){ create(:budget_investment, :selected, price: 5000000, heading: heading) }
let(:ballot) { create(:budget_ballot, budget: budget) }
let(:ballot_line) { build(:budget_ballot_line, ballot: ballot, investment: investment) }
it "should be valid and automatically denormallyze budget, group and heading when validated" do
expect(ballot_line).to be_valid
@@ -42,4 +43,30 @@ describe "Budget::Ballot::Line" do
end
end
describe "scopes" do
describe "by_investment" do
it "should return ballot lines for an investment" do
investment1 = create(:budget_investment, :selected, heading: heading)
investment2 = create(:budget_investment, :selected, heading: heading)
ballot1 = create(:budget_ballot, budget: budget)
ballot2 = create(:budget_ballot, budget: budget)
ballot3 = create(:budget_ballot, budget: budget)
ballot_line1 = create(:budget_ballot_line, ballot: ballot1, investment: investment1)
ballot_line2 = create(:budget_ballot_line, ballot: ballot2, investment: investment1)
ballot_line3 = create(:budget_ballot_line, ballot: ballot3, investment: investment2)
ballot_lines_by_investment = Budget::Ballot::Line.by_investment(investment1.id)
expect(ballot_lines_by_investment).to include ballot_line1
expect(ballot_lines_by_investment).to include ballot_line2
expect(ballot_lines_by_investment).to_not include ballot_line3
end
end
end
end

View File

@@ -728,20 +728,20 @@ describe Budget::Investment do
let(:heading1) { create(:budget_heading, group: group) }
let(:heading2) { create(:budget_heading, group: group) }
describe "reclassified?" do
describe "heading_changed?" do
it "returns true if budget is in balloting phase and heading has changed" do
investment = create(:budget_investment, heading: heading1)
investment.heading = heading2
expect(investment.reclassified?).to eq(true)
expect(investment.heading_changed?).to eq(true)
end
it "returns false if heading has not changed" do
investment = create(:budget_investment)
investment.heading = investment.heading
expect(investment.reclassified?).to eq(false)
expect(investment.heading_changed?).to eq(false)
end
it "returns false if budget is not balloting phase" do
@@ -751,13 +751,13 @@ describe Budget::Investment do
investment.heading = heading2
expect(investment.reclassified?).to eq(false)
expect(investment.heading_changed?).to eq(false)
end
end
end
describe "log_reclassification" do
describe "log_heading_change" do
it "stores the previous heading before being reclassified" do
investment = create(:budget_investment, heading: heading1)
@@ -775,6 +775,30 @@ describe Budget::Investment do
end
describe "store_reclassified_votes" do
it "stores the votes for a reclassified investment" do
investment = create(:budget_investment, :selected, heading: heading1)
3.times do
ballot = create(:budget_ballot, budget: budget)
ballot.investments << investment
end
expect(investment.ballot_lines_count).to eq(3)
investment.heading = heading2
investment.store_reclassified_votes("heading_changed")
reclassified_vote = Budget::ReclassifiedVote.first
expect(Budget::ReclassifiedVote.count).to eq(3)
expect(reclassified_vote.investment_id).to eq(investment.id)
expect(reclassified_vote.user_id).to eq(Budget::Ballot.first.user.id)
expect(reclassified_vote.reason).to eq("heading_changed")
end
end
describe "remove_reclassified_votes" do
it "removes votes from invesment" do
@@ -798,7 +822,7 @@ describe Budget::Investment do
describe "check_for_reclassification" do
it "removes votes if an investment has been reclassified" do
it "stores reclassfied votes and removes actual votes if an investment has been reclassified" do
investment = create(:budget_investment, :selected, heading: heading1)
3.times do
@@ -813,9 +837,10 @@ describe Budget::Investment do
investment.reload
expect(investment.ballot_lines_count).to eq(0)
expect(Budget::ReclassifiedVote.count).to eq(3)
end
it "does not remove votes if the investment has not been reclassifed" do
it "does not store reclassified votes nor remove actual votes if the investment has not been reclassifed" do
investment = create(:budget_investment, :selected, heading: heading1)
3.times do
@@ -829,6 +854,7 @@ describe Budget::Investment do
investment.reload
expect(investment.ballot_lines_count).to eq(3)
expect(Budget::ReclassifiedVote.count).to eq(0)
end
end

View File

@@ -0,0 +1,40 @@
require 'rails_helper'
describe Budget::ReclassifiedVote do
describe "Validations" do
let(:reclassified_vote) { build(:budget_reclassified_vote) }
it "should be valid" do
expect(reclassified_vote).to be_valid
end
it "should not be valid without a user" do
reclassified_vote.user_id = nil
expect(reclassified_vote).to_not be_valid
end
it "should not be valid without an investment" do
reclassified_vote.investment_id = nil
expect(reclassified_vote).to_not be_valid
end
it "should not be valid without a valid reason" do
reclassified_vote.reason = nil
expect(reclassified_vote).to_not be_valid
reclassified_vote.reason = ""
expect(reclassified_vote).to_not be_valid
reclassified_vote.reason = "random"
expect(reclassified_vote).to_not be_valid
reclassified_vote.reason = "heading_changed"
expect(reclassified_vote).to be_valid
reclassified_vote.reason = "unfeasible"
expect(reclassified_vote).to be_valid
end
end
end