Merge pull request #1550 from consul/budgets-unfeasible
Investments reclassified as unfeasible
This commit is contained in:
@@ -13,6 +13,8 @@ class Budget
|
|||||||
validate :check_sufficient_funds
|
validate :check_sufficient_funds
|
||||||
validate :check_valid_heading
|
validate :check_valid_heading
|
||||||
|
|
||||||
|
scope :by_investment, -> (investment_id) { where(investment_id: investment_id) }
|
||||||
|
|
||||||
before_validation :set_denormalized_ids
|
before_validation :set_denormalized_ids
|
||||||
|
|
||||||
def check_sufficient_funds
|
def check_sufficient_funds
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class Budget
|
|||||||
include Sanitizable
|
include Sanitizable
|
||||||
include Taggable
|
include Taggable
|
||||||
include Searchable
|
include Searchable
|
||||||
|
include Reclassification
|
||||||
|
|
||||||
acts_as_votable
|
acts_as_votable
|
||||||
acts_as_paranoid column: :hidden_at
|
acts_as_paranoid column: :hidden_at
|
||||||
@@ -61,7 +62,6 @@ class Budget
|
|||||||
before_save :calculate_confidence_score
|
before_save :calculate_confidence_score
|
||||||
before_validation :set_responsible_name
|
before_validation :set_responsible_name
|
||||||
before_validation :set_denormalized_ids
|
before_validation :set_denormalized_ids
|
||||||
after_save :check_for_reclassification
|
|
||||||
|
|
||||||
def self.filter_params(params)
|
def self.filter_params(params)
|
||||||
params.select{|x,_| %w{heading_id group_id administrator_id tag_name valuator_id}.include? x.to_s }
|
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
|
investments
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def set_denormalized_ids
|
def set_denormalized_ids
|
||||||
|
|||||||
50
app/models/budget/reclassification.rb
Normal file
50
app/models/budget/reclassification.rb
Normal 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
|
||||||
12
app/models/budget/reclassified_vote.rb
Normal file
12
app/models/budget/reclassified_vote.rb
Normal 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
|
||||||
@@ -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
|
||||||
10
db/schema.rb
10
db/schema.rb
@@ -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: 20170513110025) do
|
ActiveRecord::Schema.define(version: 20170517123042) 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"
|
||||||
@@ -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", ["heading_id"], name: "index_budget_investments_on_heading_id", using: :btree
|
||||||
add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin
|
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|
|
create_table "budget_valuator_assignments", force: :cascade do |t|
|
||||||
t.integer "valuator_id"
|
t.integer "valuator_id"
|
||||||
t.integer "investment_id"
|
t.integer "investment_id"
|
||||||
|
|||||||
@@ -306,6 +306,12 @@ FactoryGirl.define do
|
|||||||
association :investment, factory: :budget_investment
|
association :investment, factory: :budget_investment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :budget_reclassified_vote, class: 'Budget::ReclassifiedVote' do
|
||||||
|
user
|
||||||
|
association :investment, factory: :budget_investment
|
||||||
|
reason "unfeasible"
|
||||||
|
end
|
||||||
|
|
||||||
factory :vote do
|
factory :vote do
|
||||||
association :votable, factory: :debate
|
association :votable, factory: :debate
|
||||||
association :voter, factory: :user
|
association :voter, factory: :user
|
||||||
|
|||||||
@@ -670,7 +670,9 @@ feature 'Budget Investments' do
|
|||||||
expect(page).to_not have_link("Vote")
|
expect(page).to_not have_link("Vote")
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "Reclassification" do
|
feature "Reclassification" do
|
||||||
|
|
||||||
|
scenario "Due to heading change" do
|
||||||
user = create(:user, :level_two)
|
user = create(:user, :level_two)
|
||||||
investment = create(:budget_investment, :selected, heading: heading)
|
investment = create(:budget_investment, :selected, heading: heading)
|
||||||
heading2 = create(:budget_heading, group: group)
|
heading2 = create(:budget_heading, group: group)
|
||||||
@@ -690,5 +692,29 @@ feature 'Budget Investments' do
|
|||||||
|
|
||||||
expect(page).to have_content("You have voted 0 investment")
|
expect(page).to have_content("You have voted 0 investment")
|
||||||
end
|
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
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ require 'rails_helper'
|
|||||||
|
|
||||||
describe "Budget::Ballot::Line" do
|
describe "Budget::Ballot::Line" do
|
||||||
|
|
||||||
describe 'Validations' do
|
|
||||||
let(:budget){ create(:budget) }
|
let(:budget){ create(:budget) }
|
||||||
let(:group){ create(:budget_group, budget: budget) }
|
let(:group){ create(:budget_group, budget: budget) }
|
||||||
let(:heading){ create(:budget_heading, group: group, price: 10000000) }
|
let(:heading){ create(:budget_heading, group: group, price: 10000000) }
|
||||||
@@ -10,6 +9,8 @@ describe "Budget::Ballot::Line" do
|
|||||||
let(:ballot) { create(:budget_ballot, budget: budget) }
|
let(:ballot) { create(:budget_ballot, budget: budget) }
|
||||||
let(:ballot_line) { build(:budget_ballot_line, ballot: ballot, investment: investment) }
|
let(:ballot_line) { build(:budget_ballot_line, ballot: ballot, investment: investment) }
|
||||||
|
|
||||||
|
describe 'Validations' do
|
||||||
|
|
||||||
it "should be valid and automatically denormallyze budget, group and heading when validated" do
|
it "should be valid and automatically denormallyze budget, group and heading when validated" do
|
||||||
expect(ballot_line).to be_valid
|
expect(ballot_line).to be_valid
|
||||||
expect(ballot_line.budget).to eq(budget)
|
expect(ballot_line.budget).to eq(budget)
|
||||||
@@ -42,4 +43,30 @@ describe "Budget::Ballot::Line" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -728,20 +728,20 @@ describe Budget::Investment do
|
|||||||
let(:heading1) { create(:budget_heading, group: group) }
|
let(:heading1) { create(:budget_heading, group: group) }
|
||||||
let(:heading2) { 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
|
it "returns true if budget is in balloting phase and heading has changed" do
|
||||||
investment = create(:budget_investment, heading: heading1)
|
investment = create(:budget_investment, heading: heading1)
|
||||||
investment.heading = heading2
|
investment.heading = heading2
|
||||||
|
|
||||||
expect(investment.reclassified?).to eq(true)
|
expect(investment.heading_changed?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false if heading has not changed" do
|
it "returns false if heading has not changed" do
|
||||||
investment = create(:budget_investment)
|
investment = create(:budget_investment)
|
||||||
investment.heading = investment.heading
|
investment.heading = investment.heading
|
||||||
|
|
||||||
expect(investment.reclassified?).to eq(false)
|
expect(investment.heading_changed?).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false if budget is not balloting phase" do
|
it "returns false if budget is not balloting phase" do
|
||||||
@@ -751,13 +751,13 @@ describe Budget::Investment do
|
|||||||
|
|
||||||
investment.heading = heading2
|
investment.heading = heading2
|
||||||
|
|
||||||
expect(investment.reclassified?).to eq(false)
|
expect(investment.heading_changed?).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "log_reclassification" do
|
describe "log_heading_change" do
|
||||||
|
|
||||||
it "stores the previous heading before being reclassified" do
|
it "stores the previous heading before being reclassified" do
|
||||||
investment = create(:budget_investment, heading: heading1)
|
investment = create(:budget_investment, heading: heading1)
|
||||||
@@ -775,6 +775,30 @@ describe Budget::Investment do
|
|||||||
|
|
||||||
end
|
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
|
describe "remove_reclassified_votes" do
|
||||||
|
|
||||||
it "removes votes from invesment" do
|
it "removes votes from invesment" do
|
||||||
@@ -798,7 +822,7 @@ describe Budget::Investment do
|
|||||||
|
|
||||||
describe "check_for_reclassification" 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)
|
investment = create(:budget_investment, :selected, heading: heading1)
|
||||||
|
|
||||||
3.times do
|
3.times do
|
||||||
@@ -813,9 +837,10 @@ describe Budget::Investment do
|
|||||||
investment.reload
|
investment.reload
|
||||||
|
|
||||||
expect(investment.ballot_lines_count).to eq(0)
|
expect(investment.ballot_lines_count).to eq(0)
|
||||||
|
expect(Budget::ReclassifiedVote.count).to eq(3)
|
||||||
end
|
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)
|
investment = create(:budget_investment, :selected, heading: heading1)
|
||||||
|
|
||||||
3.times do
|
3.times do
|
||||||
@@ -829,6 +854,7 @@ describe Budget::Investment do
|
|||||||
investment.reload
|
investment.reload
|
||||||
|
|
||||||
expect(investment.ballot_lines_count).to eq(3)
|
expect(investment.ballot_lines_count).to eq(3)
|
||||||
|
expect(Budget::ReclassifiedVote.count).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
40
spec/models/budget/reclassified_vote_spec.rb
Normal file
40
spec/models/budget/reclassified_vote_spec.rb
Normal 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
|
||||||
Reference in New Issue
Block a user