From 40ae4efe26ca89113712a65c75db342e43136883 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 12:40:52 +0200 Subject: [PATCH 001/384] Adds migrations --- db/migrate/20160518142529_create_budgets.rb | 15 +++++ ...0160518151245_create_budget_investments.rb | 38 ++++++++++++ .../20160519144152_create_budget_ballots.rb | 12 ++++ ...160520100347_create_budget_ballot_lines.rb | 11 ++++ db/schema.rb | 60 ++++++++++++++++++- 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160518142529_create_budgets.rb create mode 100644 db/migrate/20160518151245_create_budget_investments.rb create mode 100644 db/migrate/20160519144152_create_budget_ballots.rb create mode 100644 db/migrate/20160520100347_create_budget_ballot_lines.rb diff --git a/db/migrate/20160518142529_create_budgets.rb b/db/migrate/20160518142529_create_budgets.rb new file mode 100644 index 000000000..943038472 --- /dev/null +++ b/db/migrate/20160518142529_create_budgets.rb @@ -0,0 +1,15 @@ +class CreateBudgets < ActiveRecord::Migration + def change + create_table :budgets do |t| + t.string "name", limit: 30 + t.text "description" + t.string "currency_symbol", limit: 10 + + t.string "phase", default: "on_hold", limit: 15 + t.boolean "valuating", default: false + + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + end +end diff --git a/db/migrate/20160518151245_create_budget_investments.rb b/db/migrate/20160518151245_create_budget_investments.rb new file mode 100644 index 000000000..bdc77c967 --- /dev/null +++ b/db/migrate/20160518151245_create_budget_investments.rb @@ -0,0 +1,38 @@ +class CreateBudgetInvestments < ActiveRecord::Migration + def change + create_table :budget_investments do |t| + + t.references "geozone" + + t.integer "author_id", index: true + t.integer "administrator_id", index: true + + t.string "title" + t.text "description" + t.string "external_url" + + t.integer "price", limit: 8 + t.string "feasibility", default: "undecided", limit: 15 + t.text "price_explanation" + t.text "unfeasibility_explanation" + t.text "internal_comments" + t.boolean "valuation_finished", default: false + t.integer "valuation_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.tsvector "tsv" + + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index :budget_investments, :tsv, using: "gin" + end +end diff --git a/db/migrate/20160519144152_create_budget_ballots.rb b/db/migrate/20160519144152_create_budget_ballots.rb new file mode 100644 index 000000000..3131098b5 --- /dev/null +++ b/db/migrate/20160519144152_create_budget_ballots.rb @@ -0,0 +1,12 @@ +class CreateBudgetBallots < ActiveRecord::Migration + def change + create_table :budget_ballots do |t| + t.references :geozone + t.references :user + t.references :budget + + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + end +end diff --git a/db/migrate/20160520100347_create_budget_ballot_lines.rb b/db/migrate/20160520100347_create_budget_ballot_lines.rb new file mode 100644 index 000000000..07377234d --- /dev/null +++ b/db/migrate/20160520100347_create_budget_ballot_lines.rb @@ -0,0 +1,11 @@ +class CreateBudgetBallotLines < ActiveRecord::Migration + def change + create_table :budget_ballot_lines do |t| + t.integer :ballot_id, index: true + t.integer :investment_id, index: true + + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 78a3ffd45..2b91945f3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160510161858) do +ActiveRecord::Schema.define(version: 20160520100347) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -63,6 +63,64 @@ ActiveRecord::Schema.define(version: 20160510161858) do add_index "annotations", ["legislation_id"], name: "index_annotations_on_legislation_id", using: :btree add_index "annotations", ["user_id"], name: "index_annotations_on_user_id", using: :btree + create_table "budget_ballot_lines", force: :cascade do |t| + t.integer "ballot_id" + t.integer "investment_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "budget_ballot_lines", ["ballot_id"], name: "index_budget_ballot_lines_on_ballot_id", using: :btree + add_index "budget_ballot_lines", ["investment_id"], name: "index_budget_ballot_lines_on_investment_id", using: :btree + + create_table "budget_ballots", force: :cascade do |t| + t.integer "geozone_id" + t.integer "user_id" + t.integer "budget_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "budget_investments", force: :cascade do |t| + t.integer "geozone_id" + t.integer "author_id" + t.integer "administrator_id" + t.string "title" + t.text "description" + t.string "external_url" + t.integer "price", limit: 8 + t.string "feasibility", limit: 15, default: "undecided" + t.text "price_explanation" + t.text "unfeasibility_explanation" + t.text "internal_comments" + t.boolean "valuation_finished", default: false + t.integer "valuation_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.tsvector "tsv" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree + add_index "budget_investments", ["author_id"], name: "index_budget_investments_on_author_id", using: :btree + add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin + + create_table "budgets", force: :cascade do |t| + t.string "name", limit: 30 + t.text "description" + t.string "currency_symbol", limit: 10 + t.string "phase", limit: 15, default: "on_hold" + t.boolean "valuating", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "campaigns", force: :cascade do |t| t.string "name" t.string "track_id" From 06f48b6fddf4476d95448d835003666d78f9ebb9 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 12:41:26 +0200 Subject: [PATCH 002/384] Includes description in Sanitizable --- app/models/concerns/sanitizable.rb | 4 ++++ app/models/debate.rb | 4 ---- app/models/proposal.rb | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/sanitizable.rb b/app/models/concerns/sanitizable.rb index 261eccbf2..a4520ba2f 100644 --- a/app/models/concerns/sanitizable.rb +++ b/app/models/concerns/sanitizable.rb @@ -6,6 +6,10 @@ module Sanitizable before_validation :sanitize_tag_list end + def description + super.try :html_safe + end + protected def sanitize_description diff --git a/app/models/debate.rb b/app/models/debate.rb index 840b9c4b5..41b270444 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -59,10 +59,6 @@ class Debate < ActiveRecord::Base "#{id}-#{title}".parameterize end - def description - super.try :html_safe - end - def likes cached_votes_up end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index d2b97f1c5..dd310dffa 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -89,10 +89,6 @@ class Proposal < ActiveRecord::Base summary end - def description - super.try :html_safe - end - def total_votes cached_votes_up + physical_votes end From 9d195609f9d662ae102049900f65bb54969d4be0 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 12:41:48 +0200 Subject: [PATCH 003/384] Adds models --- app/models/budget.rb | 24 ++++ app/models/budget/ballot.rb | 10 ++ app/models/budget/ballot/line.rb | 9 ++ app/models/budget/investment.rb | 201 +++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 app/models/budget.rb create mode 100644 app/models/budget/ballot.rb create mode 100644 app/models/budget/ballot/line.rb create mode 100644 app/models/budget/investment.rb diff --git a/app/models/budget.rb b/app/models/budget.rb new file mode 100644 index 000000000..d19b38959 --- /dev/null +++ b/app/models/budget.rb @@ -0,0 +1,24 @@ +class Budget < ActiveRecord::Base + + def on_hold? + phase == "on_hold" + end + + def accepting? + phase == "accepting" + end + + def selecting? + phase == "selecting" + end + + def balloting? + phase == "balloting" + end + + def finished? + phase == "finished" + end + +end + diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb new file mode 100644 index 000000000..2cc9da8a7 --- /dev/null +++ b/app/models/budget/ballot.rb @@ -0,0 +1,10 @@ +class Budget + class Ballot < ActiveRecord::Base + belongs_to :user + belongs_to :budget + belongs_to :geozone + + has_many :lines, dependent: :destroy + has_many :spending_proposals, through: :lines + end +end diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb new file mode 100644 index 000000000..01b320579 --- /dev/null +++ b/app/models/budget/ballot/line.rb @@ -0,0 +1,9 @@ +class Budget + class Ballot + class Line < ActiveRecord::Base + belongs_to :ballot + belongs_to :investment + + end + end +end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb new file mode 100644 index 000000000..ba4a63589 --- /dev/null +++ b/app/models/budget/investment.rb @@ -0,0 +1,201 @@ +class Budget + class Investment < ActiveRecord::Base + + include Measurable + include Sanitizable + include Taggable + include Searchable + + acts_as_votable + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :geozone + belongs_to :administrator + + has_many :valuation_assignments, dependent: :destroy + has_many :valuators, through: :valuation_assignments + has_many :comments, as: :commentable + + validates :title, presence: true + validates :author, presence: true + validates :description, presence: true + validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required? + + validates :title, length: { in: 4..Investment.title_max_length } + validates :description, length: { maximum: Investment.description_max_length } + validates :terms_of_service, acceptance: { allow_nil: false }, on: :create + + scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc, id: :desc) } + scope :sort_by_price, -> { reorder(price: :desc, confidence_score: :desc, id: :desc) } + scope :sort_by_random, -> { reorder("RANDOM()") } + + scope :valuation_open, -> { where(valuation_finished: false) } + scope :without_admin, -> { valuation_open.where(administrator_id: nil) } + scope :managed, -> { valuation_open.where(valuation_assignments_count: 0).where("administrator_id IS NOT ?", nil) } + scope :valuating, -> { valuation_open.where("valuation_assignments_count > 0 AND valuation_finished = ?", false) } + scope :valuation_finished, -> { where(valuation_finished: true) } + scope :feasible, -> { where(feasible: true) } + scope :unfeasible, -> { valuation_finished.where(feasible: false) } + scope :not_unfeasible, -> { where("feasible IS ? OR feasible = ?", nil, true) } + scope :with_supports, -> { where('cached_votes_up > 0') } + + scope :by_budget, -> (budget_id) { where(budget_id: budget_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("valuation_assignments.valuator_id = ?", valuator_id).joins(:valuation_assignments) } + + scope :for_render, -> { includes(:geozone) } + + scope :with_geozone, -> { where.not(geozone_id: nil) } + scope :no_geozone, -> { where(geozone_id: nil) } + + before_save :calculate_confidence_score + before_validation :set_responsible_name + + def self.filter_params(params) + params.select{|x,_| %w{geozone_id administrator_id tag_name valuator_id}.include? x.to_s } + end + + def self.scoped_filter(params, current_filter) + results = self.by_budget(params[:budget_id]) + if params[:max_for_no_geozone].present? || params[:max_per_geozone].present? + results = limit_results(results, params[:max_per_geozone].to_i, params[:max_for_no_geozone].to_i) + end + results = results.by_geozone(params[:geozone_id]) if params[:geozone_id].present? + results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present? + results = results.by_tag(params[:tag_name]) if params[:tag_name].present? + results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present? + results = results.send(current_filter) if current_filter.present? + results.includes(:geozone, administrator: :user, valuators: :user) + end + + def self.limit_results(results, max_per_geozone, max_for_no_geozone) + return results if max_per_geozone <= 0 && max_for_no_geozone <= 0 + + ids = [] + if max_per_geozone > 0 + Geozone.pluck(:id).each do |gid| + ids += Investment.where(geozone_id: gid).order(confidence_score: :desc).limit(max_per_geozone).pluck(:id) + end + end + + if max_for_no_geozone > 0 + ids += Investment.no_geozone.order(confidence_score: :desc).limit(max_for_no_geozone).pluck(:id) + end + + conditions = ["investments.id IN (?)"] + values = [ids] + + if max_per_geozone == 0 + conditions << "investments.geozone_id IS NOT ?" + values << nil + elsif max_for_no_geozone == 0 + conditions << "investments.geozone_id IS ?" + values << nil + end + + results.where(conditions.join(' OR '), *values) + end + + def searchable_values + { title => 'A', + author.username => 'B', + geozone.try(:name) => 'B', + description => 'C' + } + end + + def self.search(terms) + self.pg_search(terms) + end + + def self.by_geozone(geozone) + where(geozone_id: geozone == 'all' ? nil : geozone.presence) + end + + def undecided? + feasibility == "undecided" + end + + def feasible? + feasibility == "feasible" + end + + def unfeasible? + feasibility == "unfeasible" + end + + def unfeasibility_explanation_required? + unfeasible? && valuation_finished? + end + + def total_votes + cached_votes_up + physical_votes + delegated_votes - forum_votes + end + + def delegated_votes + count = 0 + representative_voters.each do |voter| + count += voter.forum.represented_users.select { |u| !u.voted_for?(self) }.count + end + return count + end + + def code + "B#{budget_id}I#{id}" + end + + def reason_for_not_being_selectable_by(user) + return permission_problem(user) if permission_problem?(user) + + return :not_voting_allowed unless budget.selecting? + end + + def reason_for_not_being_ballotable_by(user, ballot) + return permission_problem(user) if permission_problem?(user) + return :no_ballots_allowed unless budget.balloting? + return :different_geozone_assigned unless geozone_id.blank? || ballot.blank? || geozone_id == ballot.geozone_id || ballot.geozone_id.nil? + return :not_enough_money unless enough_money?(ballot) + end + + def permission_problem(user) + return :not_logged_in unless user + return :organization if user.organization? + return :not_verified unless user.can?(:vote, SpendingProposal) + return nil + end + + def permission_problem?(user) + permission_problem(user).present? + end + + def selectable_by?(user) + reason_for_not_being_selectable_by(user).blank? + end + + def ballotable_by?(user) + reason_for_not_being_ballotable_by(user).blank? + end + + def enough_money?(ballot) + return true if ballot.blank? + available_money = ballot.amount_available(geozone) + price.to_i <= available_money + end + + def register_selection(user) + vote_by(voter: user, vote: 'yes') if selectable_by?(user) + end + + def calculate_confidence_score + self.confidence_score = ScoreCalculator.confidence_score(total_votes, total_votes) + end + + def set_responsible_name + self.responsible_name = author.try(:document_number) if author.try(:document_number).present? + end + + end +end From f1b8ba0457eb5580d24f573793d5a0ae34a5aac8 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 14:06:32 +0200 Subject: [PATCH 004/384] Adds headings via migrations --- .../20160520111735_create_budget_heading.rb | 10 ++++++++++ ...20_replace_geozones_by_headings_in_budgets.rb | 9 +++++++++ db/schema.rb | 16 +++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20160520111735_create_budget_heading.rb create mode 100644 db/migrate/20160520114820_replace_geozones_by_headings_in_budgets.rb diff --git a/db/migrate/20160520111735_create_budget_heading.rb b/db/migrate/20160520111735_create_budget_heading.rb new file mode 100644 index 000000000..e9ca935c6 --- /dev/null +++ b/db/migrate/20160520111735_create_budget_heading.rb @@ -0,0 +1,10 @@ +class CreateBudgetHeading < ActiveRecord::Migration + def change + create_table :budget_headings do |t| + t.references :budget + t.references :geozone + t.string :name, limit: 50 + t.integer :price, limit: 8 + end + end +end diff --git a/db/migrate/20160520114820_replace_geozones_by_headings_in_budgets.rb b/db/migrate/20160520114820_replace_geozones_by_headings_in_budgets.rb new file mode 100644 index 000000000..60e1e4f6c --- /dev/null +++ b/db/migrate/20160520114820_replace_geozones_by_headings_in_budgets.rb @@ -0,0 +1,9 @@ +class ReplaceGeozonesByHeadingsInBudgets < ActiveRecord::Migration + def change + remove_column :budget_investments, :geozone_id + remove_column :budget_ballots, :geozone_id + + add_reference :budget_investments, :heading, index: true + add_reference :budget_ballots, :heading, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 2b91945f3..ea5199e03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160520100347) do +ActiveRecord::Schema.define(version: 20160520114820) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -74,15 +74,23 @@ ActiveRecord::Schema.define(version: 20160520100347) do add_index "budget_ballot_lines", ["investment_id"], name: "index_budget_ballot_lines_on_investment_id", using: :btree create_table "budget_ballots", force: :cascade do |t| - t.integer "geozone_id" t.integer "user_id" t.integer "budget_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "heading_id" + end + + add_index "budget_ballots", ["heading_id"], name: "index_budget_ballots_on_heading_id", using: :btree + + create_table "budget_headings", force: :cascade do |t| + t.integer "budget_id" + t.integer "geozone_id" + t.string "name", limit: 50 + t.integer "price", limit: 8 end create_table "budget_investments", force: :cascade do |t| - t.integer "geozone_id" t.integer "author_id" t.integer "administrator_id" t.string "title" @@ -105,10 +113,12 @@ ActiveRecord::Schema.define(version: 20160520100347) do t.tsvector "tsv" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "heading_id" end add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree add_index "budget_investments", ["author_id"], name: "index_budget_investments_on_author_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 create_table "budgets", force: :cascade do |t| From ccffcf60b36473ae163e279c57bf78e28720ad17 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 14:06:50 +0200 Subject: [PATCH 005/384] Replaces geozones by headings in the models --- app/models/budget/ballot.rb | 2 +- app/models/budget/heading.rb | 10 +++++++ app/models/budget/investment.rb | 52 +++++++++++++++++---------------- 3 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 app/models/budget/heading.rb diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 2cc9da8a7..b56a514b0 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -2,7 +2,7 @@ class Budget class Ballot < ActiveRecord::Base belongs_to :user belongs_to :budget - belongs_to :geozone + belongs_to :heading has_many :lines, dependent: :destroy has_many :spending_proposals, through: :lines diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb new file mode 100644 index 000000000..d6c7aa127 --- /dev/null +++ b/app/models/budget/heading.rb @@ -0,0 +1,10 @@ +class Budget + class Heading < ActiveRecord::Base + belongs_to :budget + belongs_to :geozone + + validates :budget_id, presence: true + validates :name, presence: true + validates :price, presence: true + end +end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index ba4a63589..4fc7f4485 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -11,12 +11,13 @@ class Budget include ActsAsParanoidAliases belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' - belongs_to :geozone + belongs_to :heading belongs_to :administrator has_many :valuation_assignments, dependent: :destroy has_many :valuators, through: :valuation_assignments has_many :comments, as: :commentable + has_many :headings, dependent: :destroy validates :title, presence: true validates :author, presence: true @@ -46,53 +47,54 @@ class Budget scope :by_tag, -> (tag_name) { tagged_with(tag_name) } scope :by_valuator, -> (valuator_id) { where("valuation_assignments.valuator_id = ?", valuator_id).joins(:valuation_assignments) } - scope :for_render, -> { includes(:geozone) } + scope :for_render, -> { includes(heading: :geozone) } - scope :with_geozone, -> { where.not(geozone_id: nil) } - scope :no_geozone, -> { where(geozone_id: nil) } + scope :with_heading, -> { where.not(heading_id: nil) } + scope :no_heading, -> { where(heading_id: nil) } before_save :calculate_confidence_score before_validation :set_responsible_name def self.filter_params(params) - params.select{|x,_| %w{geozone_id administrator_id tag_name valuator_id}.include? x.to_s } + params.select{|x,_| %w{heading_id administrator_id tag_name valuator_id}.include? x.to_s } end def self.scoped_filter(params, current_filter) + budget = Budget.find!(params[:budget_id]) results = self.by_budget(params[:budget_id]) - if params[:max_for_no_geozone].present? || params[:max_per_geozone].present? - results = limit_results(results, params[:max_per_geozone].to_i, params[:max_for_no_geozone].to_i) + if params[:max_for_no_heading].present? || params[:max_per_heading].present? + results = limit_results(results, budget, params[:max_per_heading].to_i, params[:max_for_no_heading].to_i) end - results = results.by_geozone(params[:geozone_id]) if params[:geozone_id].present? + results = results.by_heading(params[:heading_id]) if params[:heading_id].present? results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present? results = results.by_tag(params[:tag_name]) if params[:tag_name].present? results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present? results = results.send(current_filter) if current_filter.present? - results.includes(:geozone, administrator: :user, valuators: :user) + results.includes(:heading, administrator: :user, valuators: :user) end - def self.limit_results(results, max_per_geozone, max_for_no_geozone) - return results if max_per_geozone <= 0 && max_for_no_geozone <= 0 + def self.limit_results(results, budget, max_per_heading, max_for_no_heading) + return results if max_per_heading <= 0 && max_for_no_heading <= 0 ids = [] - if max_per_geozone > 0 - Geozone.pluck(:id).each do |gid| - ids += Investment.where(geozone_id: gid).order(confidence_score: :desc).limit(max_per_geozone).pluck(:id) + if max_per_heading > 0 + budget.headings.pluck(:id).each do |hid| + ids += Investment.where(heading_id: hid).order(confidence_score: :desc).limit(max_per_heading).pluck(:id) end end - if max_for_no_geozone > 0 - ids += Investment.no_geozone.order(confidence_score: :desc).limit(max_for_no_geozone).pluck(:id) + if max_for_no_heading > 0 + ids += Investment.no_heading.order(confidence_score: :desc).limit(max_for_no_heading).pluck(:id) end conditions = ["investments.id IN (?)"] values = [ids] - if max_per_geozone == 0 - conditions << "investments.geozone_id IS NOT ?" + if max_per_heading == 0 + conditions << "investments.heading_id IS NOT ?" values << nil - elsif max_for_no_geozone == 0 - conditions << "investments.geozone_id IS ?" + elsif max_for_no_heading == 0 + conditions << "investments.heading_id IS ?" values << nil end @@ -102,7 +104,7 @@ class Budget def searchable_values { title => 'A', author.username => 'B', - geozone.try(:name) => 'B', + heading.try(:name) => 'B', description => 'C' } end @@ -111,8 +113,8 @@ class Budget self.pg_search(terms) end - def self.by_geozone(geozone) - where(geozone_id: geozone == 'all' ? nil : geozone.presence) + def self.by_heading(heading) + where(heading_id: heading == 'all' ? nil : heading.presence) end def undecided? @@ -156,7 +158,7 @@ class Budget def reason_for_not_being_ballotable_by(user, ballot) return permission_problem(user) if permission_problem?(user) return :no_ballots_allowed unless budget.balloting? - return :different_geozone_assigned unless geozone_id.blank? || ballot.blank? || geozone_id == ballot.geozone_id || ballot.geozone_id.nil? + return :different_heading_assigned unless heading_id.blank? || ballot.blank? || heading_id == ballot.heading_id || ballot.heading_id.nil? return :not_enough_money unless enough_money?(ballot) end @@ -181,7 +183,7 @@ class Budget def enough_money?(ballot) return true if ballot.blank? - available_money = ballot.amount_available(geozone) + available_money = ballot.amount_available(heading) price.to_i <= available_money end From da769d8746b4f72e87e9ac40700831aefe6a3351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Mon, 23 May 2016 13:53:09 +0200 Subject: [PATCH 006/384] investment can not have many headings --- app/models/budget.rb | 3 +-- app/models/budget/heading.rb | 2 ++ app/models/budget/investment.rb | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/budget.rb b/app/models/budget.rb index d19b38959..abffbf799 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -20,5 +20,4 @@ class Budget < ActiveRecord::Base phase == "finished" end -end - +end \ No newline at end of file diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index d6c7aa127..e79c5b7c8 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -3,6 +3,8 @@ class Budget belongs_to :budget belongs_to :geozone + has_many :investments + validates :budget_id, presence: true validates :name, presence: true validates :price, presence: true diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 4fc7f4485..e1e193a81 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -17,7 +17,6 @@ class Budget has_many :valuation_assignments, dependent: :destroy has_many :valuators, through: :valuation_assignments has_many :comments, as: :commentable - has_many :headings, dependent: :destroy validates :title, presence: true validates :author, presence: true From a679123326b938584f8ca7c42687f23f04bc55fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 23 May 2016 14:48:39 +0200 Subject: [PATCH 007/384] adds factories for Budget classes --- spec/factories.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spec/factories.rb b/spec/factories.rb index 67c2e2c07..a05ba7b71 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -189,6 +189,48 @@ FactoryGirl.define do association :author, factory: :user end + factory :budget do + sequence(:name) { |n| "Budget #{n}" } + currency_symbol "€" + end + + factory :budget_heading, class: Budget::Heading do + budget + sequence(:name) { |n| "Heading #{n}" } + price 1000000 + end + + factory :budget_investment, class: Budget::Investment do + sequence(:title) { |n| "Investment #{n} title" } + description 'Spend money on this' + price 1000 + external_url 'http://external_documention.org' + terms_of_service '1' + association :author, factory: :user + + trait :feasible do + feasibility "feasible" + end + + trait :unfeasible do + feasibility "unfeasible" + end + + trait :finished do + valuation_finished true + end + end + + factory :budget_ballot, class: Budget::Ballot do + association :user, factory: :user + budget + end + + factory :budget_ballot_line, class: Budget::Ballot::Line do + association :ballot, factory: :budget_ballot + investment { FactoryGirl.build(:budget_investment, :feasible) } + end + factory :vote do association :votable, factory: :debate association :voter, factory: :user From 0fd04be49928eb87e5ddc98fb9febe52e2f5daad Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 12:39:20 +0200 Subject: [PATCH 008/384] Uses strings instead of classes to prevent error when doing rake db:setup --- spec/factories.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/factories.rb b/spec/factories.rb index a05ba7b71..4aa3f6718 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -194,13 +194,13 @@ FactoryGirl.define do currency_symbol "€" end - factory :budget_heading, class: Budget::Heading do + factory :budget_heading, class: 'Budget::Heading' do budget sequence(:name) { |n| "Heading #{n}" } price 1000000 end - factory :budget_investment, class: Budget::Investment do + factory :budget_investment, class: 'Budget::Investment' do sequence(:title) { |n| "Investment #{n} title" } description 'Spend money on this' price 1000 @@ -221,12 +221,12 @@ FactoryGirl.define do end end - factory :budget_ballot, class: Budget::Ballot do + factory :budget_ballot, class: 'Budget::Ballot' do association :user, factory: :user budget end - factory :budget_ballot_line, class: Budget::Ballot::Line do + factory :budget_ballot_line, class: 'Budget::Ballot::Line' do association :ballot, factory: :budget_ballot investment { FactoryGirl.build(:budget_investment, :feasible) } end From c48059594b478b16611ccf8c528e41d0c216fdbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Mon, 23 May 2016 16:35:05 +0200 Subject: [PATCH 009/384] removes belongs_to :heading relation from ballot --- app/models/budget/ballot.rb | 1 - db/migrate/20160523143320_deletes_heading_id_from_ballot.rb | 5 +++++ db/schema.rb | 5 +---- 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20160523143320_deletes_heading_id_from_ballot.rb diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index b56a514b0..cf472d95c 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -2,7 +2,6 @@ class Budget class Ballot < ActiveRecord::Base belongs_to :user belongs_to :budget - belongs_to :heading has_many :lines, dependent: :destroy has_many :spending_proposals, through: :lines diff --git a/db/migrate/20160523143320_deletes_heading_id_from_ballot.rb b/db/migrate/20160523143320_deletes_heading_id_from_ballot.rb new file mode 100644 index 000000000..3a7d17e67 --- /dev/null +++ b/db/migrate/20160523143320_deletes_heading_id_from_ballot.rb @@ -0,0 +1,5 @@ +class DeletesHeadingIdFromBallot < ActiveRecord::Migration + def change + remove_column :budget_ballots, :heading_id + end +end diff --git a/db/schema.rb b/db/schema.rb index ea5199e03..c6f6ffe67 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160520114820) do +ActiveRecord::Schema.define(version: 20160523143320) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -78,11 +78,8 @@ ActiveRecord::Schema.define(version: 20160520114820) do t.integer "budget_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "heading_id" end - add_index "budget_ballots", ["heading_id"], name: "index_budget_ballots_on_heading_id", using: :btree - create_table "budget_headings", force: :cascade do |t| t.integer "budget_id" t.integer "geozone_id" From 282553543f240cfc74acbf84abd43f93d3c491db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 23 May 2016 16:36:33 +0200 Subject: [PATCH 010/384] removes forum and delegated votes from investments --- app/models/budget/investment.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index e1e193a81..a736751ad 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -133,15 +133,7 @@ class Budget end def total_votes - cached_votes_up + physical_votes + delegated_votes - forum_votes - end - - def delegated_votes - count = 0 - representative_voters.each do |voter| - count += voter.forum.represented_users.select { |u| !u.voted_for?(self) }.count - end - return count + cached_votes_up + physical_votes end def code From 1cae255555f4b0c3696709ab937175a494afee35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 23 May 2016 16:37:01 +0200 Subject: [PATCH 011/384] changes sps to investments in ballot relation --- app/models/budget/ballot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index cf472d95c..4b683134b 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -4,6 +4,6 @@ class Budget belongs_to :budget has_many :lines, dependent: :destroy - has_many :spending_proposals, through: :lines + has_many :investments, through: :lines end end From 57642075a730738be3047703349ab6195462701f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 23 May 2016 16:37:29 +0200 Subject: [PATCH 012/384] adds ballot model spec --- app/models/budget/ballot.rb | 8 ++++++ app/models/budget/ballot/line.rb | 1 - spec/models/budget/ballot_spec.rb | 43 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 spec/models/budget/ballot_spec.rb diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 4b683134b..6dbd67aed 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -5,5 +5,13 @@ class Budget has_many :lines, dependent: :destroy has_many :investments, through: :lines + + def total_amount_spent + investments.sum(:price).to_i + end + + def amount_spent(heading) + investments.by_heading(heading).sum(:price).to_i + end end end diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 01b320579..804ee3a2c 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -3,7 +3,6 @@ class Budget class Line < ActiveRecord::Base belongs_to :ballot belongs_to :investment - end end end diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb new file mode 100644 index 000000000..df31c9eae --- /dev/null +++ b/spec/models/budget/ballot_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +describe Budget::Ballot do + + describe "#amount_spent" do + it "returns the total amount spent in investments" do + inv1 = create(:budget_investment, :feasible, price: 10000) + inv2 = create(:budget_investment, :feasible, price: 20000) + + ballot = create(:budget_ballot) + ballot.investments << inv1 + + expect(ballot.total_amount_spent).to eq 10000 + + ballot.investments << inv2 + + expect(ballot.total_amount_spent).to eq 30000 + end + end + + describe "#amount_spent by heading" do + it "returns the amount spent on all investments assigned to a specific heading" do + heading = create(:budget_heading) + inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) + inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading)) + inv3 = create(:budget_investment, :feasible, price: 25000) + inv4 = create(:budget_investment, :feasible, price: 40000, heading: heading) + + ballot = create(:budget_ballot) + ballot.investments << inv1 + ballot.investments << inv2 + ballot.investments << inv3 + + expect(ballot.amount_spent(heading.id)).to eq 10000 + expect(ballot.amount_spent(nil)).to eq 25000 + + ballot.investments << inv4 + + expect(ballot.amount_spent(heading.id)).to eq 50000 + end + end + +end \ No newline at end of file From cf0b7a53b0001581827c98b4c76e34471d804932 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 17:55:51 +0200 Subject: [PATCH 013/384] Adds missing budget_id to investments --- db/migrate/20160520151954_add_budget_id_to_investments.rb | 5 +++++ db/schema.rb | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 db/migrate/20160520151954_add_budget_id_to_investments.rb diff --git a/db/migrate/20160520151954_add_budget_id_to_investments.rb b/db/migrate/20160520151954_add_budget_id_to_investments.rb new file mode 100644 index 000000000..8860be67c --- /dev/null +++ b/db/migrate/20160520151954_add_budget_id_to_investments.rb @@ -0,0 +1,5 @@ +class AddBudgetIdToInvestments < ActiveRecord::Migration + def change + add_reference :budget_investments, :budget, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c6f6ffe67..cf0f2dff9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -111,11 +111,13 @@ ActiveRecord::Schema.define(version: 20160523143320) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "heading_id" + t.integer "budget_id" end add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree add_index "budget_investments", ["author_id"], name: "index_budget_investments_on_author_id", using: :btree add_index "budget_investments", ["heading_id"], name: "index_budget_investments_on_heading_id", using: :btree + add_index "budget_investments", ["budget_id"], name: "index_budget_investments_on_budget_id", using: :btree add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin create_table "budgets", force: :cascade do |t| From fdc8636e12daf2ae70b32216a9145b09c5a6d26f Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 20 May 2016 17:56:05 +0200 Subject: [PATCH 014/384] Adds simple Budget specs --- app/models/budget.rb | 10 +++++++++- spec/factories.rb | 5 +++++ spec/models/budget_spec.rb | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 spec/models/budget_spec.rb diff --git a/app/models/budget.rb b/app/models/budget.rb index abffbf799..9b5940e4f 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -1,5 +1,12 @@ class Budget < ActiveRecord::Base + VALID_PHASES = %W{on_hold accepting selecting balloting finished} + + validates :phase, inclusion: { in: VALID_PHASES } + + has_many :investments + has_many :ballots + def on_hold? phase == "on_hold" end @@ -20,4 +27,5 @@ class Budget < ActiveRecord::Base phase == "finished" end -end \ No newline at end of file +end + diff --git a/spec/factories.rb b/spec/factories.rb index 4aa3f6718..0ae660e9a 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -358,4 +358,9 @@ FactoryGirl.define do sequence(:name) { |n| "District #{n}" } census_code { '01' } end + + factory :budget do + sequence(:name) {|n| "Budget #{n}" } + phase "on_hold" + end end diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb new file mode 100644 index 000000000..d874148a1 --- /dev/null +++ b/spec/models/budget_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe Budget do + it "validates the phase" do + budget = create(:budget) + Budget::VALID_PHASES.each do |phase| + budget.phase = phase + expect(budget).to be_valid + end + + budget.phase = 'inexisting' + expect(budget).to_not be_valid + end +end + From 20108639cbb912e0da8c6e68ca5cfc9292d281a6 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 23 May 2016 17:35:49 +0200 Subject: [PATCH 015/384] migrates --- ...5730_create_budget_valuator_assignments.rb | 9 ++++++ ...d_budget_investments_count_to_valuators.rb | 5 ++++ ...0160523150146_rename_bi_valuation_count.rb | 5 ++++ db/schema.rb | 30 ++++++++++++------- 4 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20160523095730_create_budget_valuator_assignments.rb create mode 100644 db/migrate/20160523144313_add_budget_investments_count_to_valuators.rb create mode 100644 db/migrate/20160523150146_rename_bi_valuation_count.rb diff --git a/db/migrate/20160523095730_create_budget_valuator_assignments.rb b/db/migrate/20160523095730_create_budget_valuator_assignments.rb new file mode 100644 index 000000000..18686da0e --- /dev/null +++ b/db/migrate/20160523095730_create_budget_valuator_assignments.rb @@ -0,0 +1,9 @@ +class CreateBudgetValuatorAssignments < ActiveRecord::Migration + def change + create_table :budget_valuator_assignments, index: false do |t| + t.belongs_to :valuator + t.integer :investment_id, index: true + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160523144313_add_budget_investments_count_to_valuators.rb b/db/migrate/20160523144313_add_budget_investments_count_to_valuators.rb new file mode 100644 index 000000000..9ec741951 --- /dev/null +++ b/db/migrate/20160523144313_add_budget_investments_count_to_valuators.rb @@ -0,0 +1,5 @@ +class AddBudgetInvestmentsCountToValuators < ActiveRecord::Migration + def change + add_column :valuators, :budget_investments_count, :integer, default: 0 + end +end diff --git a/db/migrate/20160523150146_rename_bi_valuation_count.rb b/db/migrate/20160523150146_rename_bi_valuation_count.rb new file mode 100644 index 000000000..b7b41e6e1 --- /dev/null +++ b/db/migrate/20160523150146_rename_bi_valuation_count.rb @@ -0,0 +1,5 @@ +class RenameBiValuationCount < ActiveRecord::Migration + def change + rename_column :budget_investments, :valuation_assignments_count, :valuator_assignments_count + end +end diff --git a/db/schema.rb b/db/schema.rb index cf0f2dff9..af15b752c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160523143320) do +ActiveRecord::Schema.define(version: 20160523150146) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -93,20 +93,20 @@ ActiveRecord::Schema.define(version: 20160523143320) 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.text "internal_comments" - t.boolean "valuation_finished", default: false - t.integer "valuation_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 @@ -120,6 +120,15 @@ ActiveRecord::Schema.define(version: 20160523143320) do add_index "budget_investments", ["budget_id"], name: "index_budget_investments_on_budget_id", using: :btree add_index "budget_investments", ["tsv"], name: "index_budget_investments_on_tsv", using: :gin + create_table "budget_valuator_assignments", force: :cascade do |t| + t.integer "valuator_id" + t.integer "investment_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "budget_valuator_assignments", ["investment_id"], name: "index_budget_valuator_assignments_on_investment_id", using: :btree + create_table "budgets", force: :cascade do |t| t.string "name", limit: 30 t.text "description" @@ -513,6 +522,7 @@ ActiveRecord::Schema.define(version: 20160523143320) do t.integer "user_id" t.string "description" t.integer "spending_proposals_count", default: 0 + t.integer "budget_investments_count", default: 0 end add_index "valuators", ["user_id"], name: "index_valuators_on_user_id", using: :btree From 60cf683a0b7665aa7ba78353875b4d042b6d7c77 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 23 May 2016 17:42:59 +0200 Subject: [PATCH 016/384] Adds valuator_assignments --- app/models/budget/investment.rb | 10 +++++----- app/models/budget/valuator_assignment.rb | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 app/models/budget/valuator_assignment.rb diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index a736751ad..d222dfcd2 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -14,8 +14,8 @@ class Budget belongs_to :heading belongs_to :administrator - has_many :valuation_assignments, dependent: :destroy - has_many :valuators, through: :valuation_assignments + has_many :valuator_assignments, dependent: :destroy + has_many :valuators, through: :valuator_assignments has_many :comments, as: :commentable validates :title, presence: true @@ -33,8 +33,8 @@ class Budget scope :valuation_open, -> { where(valuation_finished: false) } scope :without_admin, -> { valuation_open.where(administrator_id: nil) } - scope :managed, -> { valuation_open.where(valuation_assignments_count: 0).where("administrator_id IS NOT ?", nil) } - scope :valuating, -> { valuation_open.where("valuation_assignments_count > 0 AND valuation_finished = ?", false) } + scope :managed, -> { valuation_open.where(valuator_assignments_count: 0).where("administrator_id IS NOT ?", nil) } + scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0 AND valuation_finished = ?", false) } scope :valuation_finished, -> { where(valuation_finished: true) } scope :feasible, -> { where(feasible: true) } scope :unfeasible, -> { valuation_finished.where(feasible: false) } @@ -44,7 +44,7 @@ class Budget scope :by_budget, -> (budget_id) { where(budget_id: budget_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("valuation_assignments.valuator_id = ?", valuator_id).joins(:valuation_assignments) } + scope :by_valuator, -> (valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) } scope :for_render, -> { includes(heading: :geozone) } diff --git a/app/models/budget/valuator_assignment.rb b/app/models/budget/valuator_assignment.rb new file mode 100644 index 000000000..18ef73812 --- /dev/null +++ b/app/models/budget/valuator_assignment.rb @@ -0,0 +1,6 @@ +class Budget + class ValuatorAssignment < ActiveRecord::Base + belongs_to :valuator, counter_cache: :budget_investments_count + belongs_to :investment, counter_cache: true + end +end From d4d16d3006325687c858aacd347a635b78529036 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 23 May 2016 19:49:49 +0200 Subject: [PATCH 017/384] Adds responsible_name migration for investments --- ...3164449_add_responsible_name_to_budget_investments.rb | 5 +++++ db/schema.rb | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20160523164449_add_responsible_name_to_budget_investments.rb diff --git a/db/migrate/20160523164449_add_responsible_name_to_budget_investments.rb b/db/migrate/20160523164449_add_responsible_name_to_budget_investments.rb new file mode 100644 index 000000000..46576d059 --- /dev/null +++ b/db/migrate/20160523164449_add_responsible_name_to_budget_investments.rb @@ -0,0 +1,5 @@ +class AddResponsibleNameToBudgetInvestments < ActiveRecord::Migration + def change + add_column :budget_investments, :responsible_name, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index af15b752c..ed8e3ed58 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160523150146) do +ActiveRecord::Schema.define(version: 20160523164449) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -108,16 +108,17 @@ ActiveRecord::Schema.define(version: 20160523150146) do 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.integer "budget_id" + t.string "responsible_name" end add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree add_index "budget_investments", ["author_id"], name: "index_budget_investments_on_author_id", using: :btree - add_index "budget_investments", ["heading_id"], name: "index_budget_investments_on_heading_id", using: :btree add_index "budget_investments", ["budget_id"], name: "index_budget_investments_on_budget_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 create_table "budget_valuator_assignments", force: :cascade do |t| From d663e4c59476d315cc976d7d7922b4392a29ec3d Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 23 May 2016 19:50:11 +0200 Subject: [PATCH 018/384] Adds/improves factories --- spec/factories.rb | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/spec/factories.rb b/spec/factories.rb index 0ae660e9a..85047b511 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,5 +1,4 @@ FactoryGirl.define do - sequence(:document_number) { |n| "#{n.to_s.rjust(8, '0')}X" } factory :user do @@ -192,6 +191,19 @@ FactoryGirl.define do factory :budget do sequence(:name) { |n| "Budget #{n}" } currency_symbol "€" + phase 'on_hold' + + trait :selecting do + phase 'selecting' + end + + trait :balloting do + phase 'balloting' + end + + trait :finished do + phase 'finished' + end end factory :budget_heading, class: 'Budget::Heading' do @@ -200,13 +212,18 @@ FactoryGirl.define do price 1000000 end - factory :budget_investment, class: 'Budget::Investment' do - sequence(:title) { |n| "Investment #{n} title" } + factory :budget_investment, class: Budget::Investment do + sequence(:title) { |n| "Budget Investment #{n} title" } + association :budget + association :author, factory: :user description 'Spend money on this' - price 1000 + unfeasibility_explanation '' external_url 'http://external_documention.org' terms_of_service '1' - association :author, factory: :user + + trait :with_confidence_score do + before(:save) { |i| i.calculate_confidence_score } + end trait :feasible do feasibility "feasible" @@ -359,8 +376,5 @@ FactoryGirl.define do census_code { '01' } end - factory :budget do - sequence(:name) {|n| "Budget #{n}" } - phase "on_hold" - end + end From 673cfc82493f7ed7f33638048f6d5e8a9cebfc04 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 23 May 2016 19:51:02 +0200 Subject: [PATCH 019/384] Implements some basic budget investment specs --- app/models/budget.rb | 5 + app/models/budget/investment.rb | 20 +- spec/models/budget/investment_spec.rb | 382 ++++++++++++++++++++++++++ 3 files changed, 397 insertions(+), 10 deletions(-) create mode 100644 spec/models/budget/investment_spec.rb diff --git a/app/models/budget.rb b/app/models/budget.rb index 9b5940e4f..dc971bc36 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -6,6 +6,7 @@ class Budget < ActiveRecord::Base has_many :investments has_many :ballots + has_many :headings def on_hold? phase == "on_hold" @@ -27,5 +28,9 @@ class Budget < ActiveRecord::Base phase == "finished" end + def amount_available(heading) + return 0 unless heading_ids.include?(heading.try(:id)) + heading.try(:price) || 10000 # FIXME + end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index d222dfcd2..bc7c81072 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -10,6 +10,7 @@ class Budget acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + belongs_to :budget belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :heading belongs_to :administrator @@ -23,8 +24,8 @@ class Budget validates :description, presence: true validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required? - validates :title, length: { in: 4..Investment.title_max_length } - validates :description, length: { maximum: Investment.description_max_length } + validates :title, length: { in: 4 .. Budget::Investment.title_max_length } + validates :description, length: { maximum: Budget::Investment.description_max_length } validates :terms_of_service, acceptance: { allow_nil: false }, on: :create scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc, id: :desc) } @@ -36,9 +37,9 @@ class Budget scope :managed, -> { valuation_open.where(valuator_assignments_count: 0).where("administrator_id IS NOT ?", nil) } scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0 AND valuation_finished = ?", false) } scope :valuation_finished, -> { where(valuation_finished: true) } - scope :feasible, -> { where(feasible: true) } - scope :unfeasible, -> { valuation_finished.where(feasible: false) } - scope :not_unfeasible, -> { where("feasible IS ? OR feasible = ?", nil, true) } + scope :feasible, -> { where(feasibility: "feasible") } + scope :unfeasible, -> { where(feasibility: "unfeasible") } + scope :undecided, -> { where(feasibility: "undecided") } scope :with_supports, -> { where('cached_votes_up > 0') } scope :by_budget, -> (budget_id) { where(budget_id: budget_id) } @@ -143,14 +144,14 @@ class Budget def reason_for_not_being_selectable_by(user) return permission_problem(user) if permission_problem?(user) - return :not_voting_allowed unless budget.selecting? + return :no_selecting_allowed unless budget.selecting? end def reason_for_not_being_ballotable_by(user, ballot) return permission_problem(user) if permission_problem?(user) return :no_ballots_allowed unless budget.balloting? return :different_heading_assigned unless heading_id.blank? || ballot.blank? || heading_id == ballot.heading_id || ballot.heading_id.nil? - return :not_enough_money unless enough_money?(ballot) + return :not_enough_money if ballot.present? && !enough_money? end def permission_problem(user) @@ -172,9 +173,8 @@ class Budget reason_for_not_being_ballotable_by(user).blank? end - def enough_money?(ballot) - return true if ballot.blank? - available_money = ballot.amount_available(heading) + def enough_money? + available_money = budget.amount_available(self.heading) price.to_i <= available_money end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb new file mode 100644 index 000000000..d8563574e --- /dev/null +++ b/spec/models/budget/investment_spec.rb @@ -0,0 +1,382 @@ +require 'rails_helper' + +describe Budget::Investment do + let(:investment) { build(:budget_investment) } + + it "should be valid" do + expect(investment).to be_valid + end + + it "should not be valid without an author" do + investment.author = nil + expect(investment).to_not be_valid + end + + describe "#title" do + it "should not be valid without a title" do + investment.title = nil + expect(investment).to_not be_valid + end + + it "should not be valid when very short" do + investment.title = "abc" + expect(investment).to_not be_valid + end + + it "should not be valid when very long" do + investment.title = "a" * 81 + expect(investment).to_not be_valid + end + end + + it "sanitizes description" do + investment.description = "" + investment.valid? + expect(investment.description).to eq("alert('danger');") + end + + describe "#unfeasibility_explanation" do + it "should be valid if valuation not finished" do + investment.unfeasibility_explanation = "" + investment.valuation_finished = false + expect(investment).to be_valid + end + + it "should be valid if valuation finished and feasible" do + investment.unfeasibility_explanation = "" + investment.feasibility = "feasible" + investment.valuation_finished = true + expect(investment).to be_valid + end + + it "should not be valid if valuation finished and unfeasible" do + investment.unfeasibility_explanation = "" + investment.feasibility = "unfeasible" + investment.valuation_finished = true + expect(investment).to_not be_valid + end + end + + describe "#code" do + it "returns the investment and budget id" do + investment = create(:budget_investment) + expect(investment.code).to include("#{investment.id}") + expect(investment.code).to include("#{investment.budget.id}") + end + end + + describe "by_admin" do + it "should return spending investments assigned to specific administrator" do + investment1 = create(:budget_investment, administrator_id: 33) + create(:budget_investment) + + by_admin = Budget::Investment.by_admin(33) + + expect(by_admin.size).to eq(1) + expect(by_admin.first).to eq(investment1) + end + end + + describe "by_valuator" do + it "should return spending proposals assigned to specific valuator" do + investment1 = create(:budget_investment) + investment2 = create(:budget_investment) + investment3 = create(:budget_investment) + + valuator1 = create(:valuator) + valuator2 = create(:valuator) + + investment1.valuators << valuator1 + investment2.valuators << valuator2 + investment3.valuators << [valuator1, valuator2] + + by_valuator = Budget::Investment.by_valuator(valuator1.id) + + expect(by_valuator.size).to eq(2) + expect(by_valuator.sort).to eq([investment1,investment3].sort) + end + end + + describe "scopes" do + describe "valuation_open" do + it "should return all spending proposals with false valuation_finished" do + investment1 = create(:budget_investment, valuation_finished: true) + investment2 = create(:budget_investment) + + valuation_open = Budget::Investment.valuation_open + + expect(valuation_open.size).to eq(1) + expect(valuation_open.first).to eq(investment2) + end + end + + describe "without_admin" do + it "should return all open spending proposals without assigned admin" do + investment1 = create(:budget_investment, valuation_finished: true) + investment2 = create(:budget_investment, administrator: create(:administrator)) + investment3 = create(:budget_investment) + + without_admin = Budget::Investment.without_admin + + expect(without_admin.size).to eq(1) + expect(without_admin.first).to eq(investment3) + end + end + + describe "managed" do + it "should return all open spending proposals with assigned admin but without assigned valuators" do + investment1 = create(:budget_investment, administrator: create(:administrator)) + investment2 = create(:budget_investment, administrator: create(:administrator), valuation_finished: true) + investment3 = create(:budget_investment, administrator: create(:administrator)) + investment1.valuators << create(:valuator) + + managed = Budget::Investment.managed + + expect(managed.size).to eq(1) + expect(managed.first).to eq(investment3) + end + end + + describe "valuating" do + it "should return all spending proposals with assigned valuator but valuation not finished" do + investment1 = create(:budget_investment) + investment2 = create(:budget_investment) + investment3 = create(:budget_investment, valuation_finished: true) + + investment2.valuators << create(:valuator) + investment3.valuators << create(:valuator) + + valuating = Budget::Investment.valuating + + expect(valuating.size).to eq(1) + expect(valuating.first).to eq(investment2) + end + end + + describe "valuation_finished" do + it "should return all spending proposals with valuation finished" do + investment1 = create(:budget_investment) + investment2 = create(:budget_investment) + investment3 = create(:budget_investment, valuation_finished: true) + + investment2.valuators << create(:valuator) + investment3.valuators << create(:valuator) + + valuation_finished = Budget::Investment.valuation_finished + + expect(valuation_finished.size).to eq(1) + expect(valuation_finished.first).to eq(investment3) + end + end + + describe "feasible" do + it "should return all feasible spending proposals" do + feasible_investment = create(:budget_investment, :feasible) + create(:budget_investment) + + expect(Budget::Investment.feasible).to eq [feasible_investment] + end + end + + describe "unfeasible" do + it "should return all unfeasible spending proposals" do + unfeasible_investment = create(:budget_investment, :unfeasible) + create(:budget_investment, :feasible) + + expect(Budget::Investment.unfeasible).to eq [unfeasible_investment] + end + end + end + + describe 'Permissions' do + let(:budget) { create(:budget) } + let(:heading) { create(:budget_heading, budget: budget) } + let(:user) { create(:user, :level_two) } + let(:luser) { create(:user) } + let(:city_sp) { create(:budget_investment, budget: budget) } + let(:district_sp) { create(:budget_investment, budget: budget, heading: heading) } + + describe '#reason_for_not_being_selectable_by' do + it "rejects not logged in users" do + expect(city_sp.reason_for_not_being_selectable_by(nil)).to eq(:not_logged_in) + expect(district_sp.reason_for_not_being_selectable_by(nil)).to eq(:not_logged_in) + end + + it "rejects not verified users" do + expect(city_sp.reason_for_not_being_selectable_by(luser)).to eq(:not_verified) + expect(district_sp.reason_for_not_being_selectable_by(luser)).to eq(:not_verified) + end + + it "rejects organizations" do + create(:organization, user: user) + expect(city_sp.reason_for_not_being_selectable_by(user)).to eq(:organization) + expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:organization) + end + + it "rejects selections when selecting is not allowed (via admin setting)" do + budget.phase = "on_hold" + expect(city_sp.reason_for_not_being_selectable_by(user)).to eq(:no_selecting_allowed) + expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:no_selecting_allowed) + end + + it "accepts valid selections when selecting is allowed" do + budget.phase = "selecting" + expect(city_sp.reason_for_not_being_selectable_by(user)).to be_nil + expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil + end + end + end + + describe "Order" do + describe "#sort_by_confidence_score" do + + it "should order by confidence_score" do + least_voted = create(:budget_investment, cached_votes_up: 1) + most_voted = create(:budget_investment, cached_votes_up: 10) + some_votes = create(:budget_investment, cached_votes_up: 5) + + expect(Budget::Investment.sort_by_confidence_score.first).to eq most_voted + expect(Budget::Investment.sort_by_confidence_score.second).to eq some_votes + expect(Budget::Investment.sort_by_confidence_score.third).to eq least_voted + end + + it "should order by confidence_score and then by id" do + least_voted = create(:budget_investment, cached_votes_up: 1) + most_voted = create(:budget_investment, cached_votes_up: 10) + most_voted2 = create(:budget_investment, cached_votes_up: 10) + least_voted2 = create(:budget_investment, cached_votes_up: 1) + + + expect(Budget::Investment.sort_by_confidence_score.first).to eq most_voted2 + expect(Budget::Investment.sort_by_confidence_score.second).to eq most_voted + expect(Budget::Investment.sort_by_confidence_score.third).to eq least_voted2 + expect(Budget::Investment.sort_by_confidence_score.fourth).to eq least_voted + end + end + end + + describe "responsible_name" do + let(:user) { create(:user, document_number: "123456") } + let!(:investment) { create(:budget_investment, author: user) } + + it "gets updated with the document_number" do + expect(investment.responsible_name).to eq("123456") + end + + it "does not get updated if the user is erased" do + user.erase + expect(user.document_number).to be_blank + investment.touch + expect(investment.responsible_name).to eq("123456") + end + end + + describe "total votes" do + it "takes into account physical votes in addition to web votes" do + b = create(:budget, :selecting) + sp = create(:budget_investment, budget: b) + + sp.register_selection(create(:user, :level_two)) + expect(sp.total_votes).to eq(1) + + sp.physical_votes = 10 + expect(sp.total_votes).to eq(11) + end + end + + describe "#with_supports" do + it "should return proposals with supports" do + sp1 = create(:budget_investment) + sp2 = create(:budget_investment) + create(:vote, votable: sp1) + + expect(Budget::Investment.with_supports).to include(sp1) + expect(Budget::Investment.with_supports).to_not include(sp2) + end + end + + describe "Final Voting" do + + describe 'Permissions' do + let(:budget) { create(:budget) } + let(:heading) { create(:budget_heading, budget: budget) } + let(:user) { create(:user, :level_two) } + let(:luser) { create(:user) } + let(:ballot) { create(:budget_ballot, budget: budget) } + let(:city_sp) { create(:budget_investment, budget: budget) } + let(:district_sp) { create(:budget_investment, budget: budget, heading: heading) } + + describe '#reason_for_not_being_ballotable_by' do + it "rejects not logged in users" do + expect(city_sp.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in) + expect(district_sp.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in) + end + + it "rejects not verified users" do + expect(city_sp.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified) + expect(district_sp.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified) + end + + it "rejects organizations" do + create(:organization, user: user) + expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization) + expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization) + end + + it "rejects votes when voting is not allowed (via admin setting)" do + budget.phase = "on_hold" + expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) + expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) + end + + it "accepts valid votes when voting is allowed" do + budget.phase = "balloting" + expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil + expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil + end + + xit "rejects city wide votes if no city money available" do + user.city_wide_investments_supported_count = 0 + expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_city_supports_available) + end + + xit "rejects district wide votes if no district money available" do + user.district_wide_investments_supported_count = 0 + expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_district_supports_available) + end + + xit "accepts valid district votes" do + expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil + user.supported_investments_geozone_id = district.id + expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil + end + + xit "rejects users with different headings" do + budget.phase = "balloting" + california = create(:budget_heading, budget: budget) + new_york = create(:budget_heading, budget: budget) + + sp1 = create(:budget_investment, :feasible, heading: california, budget: budget) + sp2 = create(:budget_investment, :feasible, heading: new_york, budget: budget) + b = create(:budget_ballot, user: user, heading: california, investments: [sp1]) + + expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:different_heading_assigned) + end + + xit "rejects proposals with price higher than current available money" do + budget.phase = "balloting" + carabanchel = create(:budget_heading, budget: budget, price: 2000000) + sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 3000000, budget: budget) + sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 1000000, budget: budget) + b = create(:budget_ballot, user: user, heading: carabanchel, investments: [sp1]) + + expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:not_enough_money) + end + + end + + end + + end + +end From fc355224676c53f6cd18fbd9a30c3fd229cce1c5 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 13:25:39 +0200 Subject: [PATCH 020/384] uses a string in the factories --- spec/factories.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/factories.rb b/spec/factories.rb index 85047b511..5bebbeb38 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -212,7 +212,7 @@ FactoryGirl.define do price 1000000 end - factory :budget_investment, class: Budget::Investment do + factory :budget_investment, class: 'Budget::Investment' do sequence(:title) { |n| "Budget Investment #{n} title" } association :budget association :author, factory: :user From 9876583c1ab5ca2a08fe759c35c842a8a484635f Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 15:31:23 +0200 Subject: [PATCH 021/384] deactivates test --- spec/models/budget/investment_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index d8563574e..2b61a917a 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -329,7 +329,7 @@ describe Budget::Investment do expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) end - it "accepts valid votes when voting is allowed" do + xit "accepts valid votes when voting is allowed" do budget.phase = "balloting" expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil From 2409cccd8ff8e9a7a1a95c90262830fef7d10ce9 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 17:38:41 +0200 Subject: [PATCH 022/384] Adds budget.price and ballot.heading_id --- .../20160524143107_add_heading_id_to_budget_ballot.rb | 6 ++++++ db/migrate/20160524144005_add_price_to_budget.rb | 5 +++++ db/schema.rb | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160524143107_add_heading_id_to_budget_ballot.rb create mode 100644 db/migrate/20160524144005_add_price_to_budget.rb diff --git a/db/migrate/20160524143107_add_heading_id_to_budget_ballot.rb b/db/migrate/20160524143107_add_heading_id_to_budget_ballot.rb new file mode 100644 index 000000000..2bc254fbe --- /dev/null +++ b/db/migrate/20160524143107_add_heading_id_to_budget_ballot.rb @@ -0,0 +1,6 @@ +class AddHeadingIdToBudgetBallot < ActiveRecord::Migration + def change + add_column :budget_ballots, :heading_id, :integer + add_index :budget_ballots, :heading_id + end +end diff --git a/db/migrate/20160524144005_add_price_to_budget.rb b/db/migrate/20160524144005_add_price_to_budget.rb new file mode 100644 index 000000000..14c96dd7a --- /dev/null +++ b/db/migrate/20160524144005_add_price_to_budget.rb @@ -0,0 +1,5 @@ +class AddPriceToBudget < ActiveRecord::Migration + def change + add_column :budgets, :price, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index ed8e3ed58..04889b20a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160523164449) do +ActiveRecord::Schema.define(version: 20160524144005) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -78,8 +78,11 @@ ActiveRecord::Schema.define(version: 20160523164449) do t.integer "budget_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "heading_id" end + add_index "budget_ballots", ["heading_id"], name: "index_budget_ballots_on_heading_id", using: :btree + create_table "budget_headings", force: :cascade do |t| t.integer "budget_id" t.integer "geozone_id" @@ -138,6 +141,7 @@ ActiveRecord::Schema.define(version: 20160523164449) do t.boolean "valuating", default: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "price" end create_table "campaigns", force: :cascade do |t| From dc686f1bf79b81f8689d91582d2fc0beeae46d87 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 17:39:15 +0200 Subject: [PATCH 023/384] Adds price to budget factory --- spec/factories.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/factories.rb b/spec/factories.rb index 5bebbeb38..94ad15c0f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -191,6 +191,7 @@ FactoryGirl.define do factory :budget do sequence(:name) { |n| "Budget #{n}" } currency_symbol "€" + price 10000 phase 'on_hold' trait :selecting do From 3e39272b3b3acf2e0083ced8c8957f404186b393 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 17:39:49 +0200 Subject: [PATCH 024/384] Fixes implementation of amount_available moving it to the ballot --- app/models/budget.rb | 6 +++--- app/models/budget/ballot.rb | 5 +++++ app/models/budget/investment.rb | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/models/budget.rb b/app/models/budget.rb index dc971bc36..b234a8b73 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -28,9 +28,9 @@ class Budget < ActiveRecord::Base phase == "finished" end - def amount_available(heading) - return 0 unless heading_ids.include?(heading.try(:id)) - heading.try(:price) || 10000 # FIXME + def heading_price(heading) + return price unless heading.present? + heading_ids.include?(heading.id) ? heading.price : -1 end end diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 6dbd67aed..bf7851883 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -2,6 +2,7 @@ class Budget class Ballot < ActiveRecord::Base belongs_to :user belongs_to :budget + belongs_to :heading has_many :lines, dependent: :destroy has_many :investments, through: :lines @@ -13,5 +14,9 @@ class Budget def amount_spent(heading) investments.by_heading(heading).sum(:price).to_i end + + def amount_available(heading) + budget.heading_price(heading) - amount_spent(heading) + end end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index bc7c81072..32ea9ad92 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -151,7 +151,7 @@ class Budget return permission_problem(user) if permission_problem?(user) return :no_ballots_allowed unless budget.balloting? return :different_heading_assigned unless heading_id.blank? || ballot.blank? || heading_id == ballot.heading_id || ballot.heading_id.nil? - return :not_enough_money if ballot.present? && !enough_money? + return :not_enough_money if ballot.present? && !enough_money?(ballot) end def permission_problem(user) @@ -173,8 +173,8 @@ class Budget reason_for_not_being_ballotable_by(user).blank? end - def enough_money? - available_money = budget.amount_available(self.heading) + def enough_money?(ballot) + available_money = ballot.amount_available(self.heading) price.to_i <= available_money end From 11a5140cf0d989268a4f5bdf34eab8e358c113df Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 24 May 2016 17:40:07 +0200 Subject: [PATCH 025/384] Implements missing specs & removes deprecated ones --- spec/models/budget/investment_spec.rb | 33 ++++++++++----------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index 2b61a917a..eb4cab3ac 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -329,29 +329,20 @@ describe Budget::Investment do expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) end - xit "accepts valid votes when voting is allowed" do + it "accepts valid ballots when voting is allowed" do budget.phase = "balloting" expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil end - xit "rejects city wide votes if no city money available" do - user.city_wide_investments_supported_count = 0 - expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_city_supports_available) - end - - xit "rejects district wide votes if no district money available" do - user.district_wide_investments_supported_count = 0 - expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_district_supports_available) - end - - xit "accepts valid district votes" do + it "accepts valid district selections" do + budget.phase = "selecting" + expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil + ballot.heading_id = heading.id expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil - user.supported_investments_geozone_id = district.id - expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil end - xit "rejects users with different headings" do + it "rejects users with different headings" do budget.phase = "balloting" california = create(:budget_heading, budget: budget) new_york = create(:budget_heading, budget: budget) @@ -363,14 +354,14 @@ describe Budget::Investment do expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:different_heading_assigned) end - xit "rejects proposals with price higher than current available money" do + it "rejects proposals with price higher than current available money" do budget.phase = "balloting" - carabanchel = create(:budget_heading, budget: budget, price: 2000000) - sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 3000000, budget: budget) - sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 1000000, budget: budget) - b = create(:budget_ballot, user: user, heading: carabanchel, investments: [sp1]) + carabanchel = create(:budget_heading, budget: budget, price: 35) + sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30, budget: budget) + sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10, budget: budget) + ballot = create(:budget_ballot, user: user, heading: carabanchel, investments: [sp1]) - expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:not_enough_money) + expect(sp2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money) end end From a0b6e99f095087a2d24016baf9d3fb5b64c5952e Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 27 May 2016 13:02:48 +0200 Subject: [PATCH 026/384] adds specs for budged model --- spec/models/budget_spec.rb | 51 ++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index d874148a1..398bb1cff 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -1,15 +1,52 @@ require 'rails_helper' describe Budget do - it "validates the phase" do - budget = create(:budget) - Budget::VALID_PHASES.each do |phase| - budget.phase = phase - expect(budget).to be_valid + describe "phase" do + let(:budget) { create(:budget) } + + it "is validated" do + Budget::VALID_PHASES.each do |phase| + budget.phase = phase + expect(budget).to be_valid + end + + budget.phase = 'inexisting' + expect(budget).to_not be_valid end - budget.phase = 'inexisting' - expect(budget).to_not be_valid + it "produces auxiliary methods" do + budget.phase = "on_hold" + expect(budget).to be_on_hold + + budget.phase = "accepting" + expect(budget).to be_accepting + + budget.phase = "selecting" + expect(budget).to be_selecting + + budget.phase = "balloting" + expect(budget).to be_balloting + + budget.phase = "finished" + expect(budget).to be_finished + end + end + + describe "heading_price" do + let(:budget) { create(:budget, price: 1000) } + + it "returns the budget price if no heading is provided" do + expect(budget.heading_price(nil)).to eq(1000) + end + + it "returns the heading price if the heading provided is part of the budget" do + heading = create(:budget_heading, price: 100, budget: budget) + expect(budget.heading_price(heading)).to eq(100) + end + + it "returns -1 if the heading provided is not part of the budget" do + expect(budget.heading_price(create(:budget_heading))).to eq(-1) + end end end From 4dd293f487a5f5f773d6165eecc0010077d650b5 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 27 May 2016 15:11:58 +0200 Subject: [PATCH 027/384] Adds a ballot spec for amount_available --- spec/models/budget/ballot_spec.rb | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb index df31c9eae..ee6be74f1 100644 --- a/spec/models/budget/ballot_spec.rb +++ b/spec/models/budget/ballot_spec.rb @@ -16,9 +16,7 @@ describe Budget::Ballot do expect(ballot.total_amount_spent).to eq 30000 end - end - describe "#amount_spent by heading" do it "returns the amount spent on all investments assigned to a specific heading" do heading = create(:budget_heading) inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) @@ -40,4 +38,31 @@ describe Budget::Ballot do end end -end \ No newline at end of file + describe "#amount_available" do + it "returns how much is left after taking some investments" do + budget = create(:budget, price: 200000) + heading = create(:budget_heading, budget: budget) + inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) + inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading)) + inv3 = create(:budget_investment, :feasible, price: 25000) + inv4 = create(:budget_investment, :feasible, price: 40000, heading: heading) + + inv1 = create(:budget_investment, :feasible, price: 10000) + inv2 = create(:budget_investment, :feasible, price: 20000) + + ballot = create(:budget_ballot, budget: budget) + ballot.investments << inv1 + ballot.investments << inv2 + + expect(ballot.amount_available(heading)).to eq 1000000 + expect(ballot.amount_available(nil)).to eq 170000 + + ballot.investments << inv3 + ballot.investments << inv4 + + expect(ballot.amount_available(heading)).to eq 960000 + expect(ballot.amount_available(nil)).to eq 145000 + end + end + +end From b6e47bc14a4e5c732de34f2e0fe79985c80ce395 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 27 May 2016 15:12:39 +0200 Subject: [PATCH 028/384] Adds missing scope to investments --- app/models/budget/investment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 32ea9ad92..329c2fce2 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -42,6 +42,7 @@ class Budget scope :undecided, -> { where(feasibility: "undecided") } scope :with_supports, -> { where('cached_votes_up > 0') } + scope :by_heading, -> (heading_id) { where(heading_id: heading_id) } scope :by_budget, -> (budget_id) { where(budget_id: budget_id) } scope :by_admin, -> (admin_id) { where(administrator_id: admin_id) } scope :by_tag, -> (tag_name) { tagged_with(tag_name) } From bb7f82a72f70bf262103ed246082c455591544b2 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 27 May 2016 15:12:54 +0200 Subject: [PATCH 029/384] Fixes hidden bug when calculating amount_available --- app/models/budget/ballot.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index bf7851883..9b9a089d6 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -11,12 +11,12 @@ class Budget investments.sum(:price).to_i end - def amount_spent(heading) - investments.by_heading(heading).sum(:price).to_i + def amount_spent(heading_id) + investments.by_heading(heading_id).sum(:price).to_i end def amount_available(heading) - budget.heading_price(heading) - amount_spent(heading) + budget.heading_price(heading) - amount_spent(heading.try(:id)) end end end From daa8783b025e529983dc9db1495048e86673cf80 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 30 May 2016 13:53:30 +0200 Subject: [PATCH 030/384] Adds common Investment abilities --- app/models/abilities/common.rb | 4 ++++ spec/models/abilities/common_spec.rb | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 6dd36d5b0..ed2c3d4a0 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -19,6 +19,7 @@ module Abilities can [:retire_form, :retire], Proposal, author_id: user.id can :read, SpendingProposal + can :read, Budget::Investment can :create, Comment can :create, Debate @@ -46,6 +47,9 @@ module Abilities can :vote_featured, Proposal can :vote, SpendingProposal can :create, SpendingProposal + can :create, Budget::Investment, budget: { phase: "accepting" } + can :vote, Budget::Investment, budget: { phase: "selecting" } + can :ballot, Budget::Investment, budget: { phase: "balloting" } end can :create, Annotation diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index 468173797..36b4b49f6 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -9,6 +9,9 @@ describe "Abilities::Common" do let(:debate) { create(:debate) } let(:comment) { create(:comment) } let(:proposal) { create(:proposal) } + let(:investment_in_accepting_budget) { create(:budget_investment, budget: create(:budget, phase: 'accepting')) } + let(:investment_in_selecting_budget) { create(:budget_investment, budget: create(:budget, phase: 'selecting')) } + let(:investment_in_balloting_budget) { create(:budget_investment, budget: create(:budget, phase: 'balloting')) } let(:own_debate) { create(:debate, author: user) } let(:own_comment) { create(:comment, author: user) } let(:own_proposal) { create(:proposal, author: user) } @@ -93,6 +96,19 @@ describe "Abilities::Common" do it { should be_able_to(:create, SpendingProposal) } it { should_not be_able_to(:destroy, create(:spending_proposal)) } it { should_not be_able_to(:destroy, own_spending_proposal) } + + it { should be_able_to(:create, investment_in_accepting_budget) } + it { should_not be_able_to(:create, investment_in_selecting_budget) } + it { should_not be_able_to(:create, investment_in_balloting_budget) } + + it { should_not be_able_to(:vote, investment_in_accepting_budget) } + it { should be_able_to(:vote, investment_in_selecting_budget) } + it { should_not be_able_to(:vote, investment_in_balloting_budget) } + + it { should_not be_able_to(:ballot, investment_in_accepting_budget) } + it { should_not be_able_to(:ballot, investment_in_selecting_budget) } + it { should be_able_to(:ballot, investment_in_balloting_budget) } + end describe "when level 3 verified" do From 13f43d8b67f57e295ec639635deb154602838b9e Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 30 May 2016 13:55:12 +0200 Subject: [PATCH 031/384] Adds valuator investment abilities --- app/models/abilities/valuator.rb | 4 +++- app/models/valuator.rb | 2 ++ spec/models/abilities/valuator_spec.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/models/abilities/valuator.rb b/app/models/abilities/valuator.rb index 15add866a..85c598d12 100644 --- a/app/models/abilities/valuator.rb +++ b/app/models/abilities/valuator.rb @@ -3,7 +3,9 @@ module Abilities include CanCan::Ability def initialize(user) + valuator = user.valuator can [:read, :update, :valuate], SpendingProposal + can [:update, :valuate], Budget::Investment, id: valuator.investment_ids, budget: { valuating: true } end end -end \ No newline at end of file +end diff --git a/app/models/valuator.rb b/app/models/valuator.rb index 8b82d20b1..5df6ea030 100644 --- a/app/models/valuator.rb +++ b/app/models/valuator.rb @@ -4,6 +4,8 @@ class Valuator < ActiveRecord::Base has_many :valuation_assignments, dependent: :destroy has_many :spending_proposals, through: :valuation_assignments + has_many :valuator_assignments, dependent: :destroy, class_name: 'Budget::ValuatorAssignment' + has_many :investments, through: :valuator_assignments, class_name: 'Budget::Investment' validates :user_id, presence: true, uniqueness: true diff --git a/spec/models/abilities/valuator_spec.rb b/spec/models/abilities/valuator_spec.rb index ce108200c..9fa285f19 100644 --- a/spec/models/abilities/valuator_spec.rb +++ b/spec/models/abilities/valuator_spec.rb @@ -5,8 +5,24 @@ describe "Abilities::Valuator" do subject(:ability) { Ability.new(user) } let(:user) { valuator.user } let(:valuator) { create(:valuator) } + let(:non_assigned_investment) { create(:budget_investment) } + + let(:assigned_investment) { create(:budget_investment, budget: create(:budget, valuating: true)) } + before(:each) { assigned_investment.valuators << valuator } + + let(:assigned_investment_not_valuating) { create(:budget_investment, budget: create(:budget, valuating: false)) } + before(:each) { assigned_investment_not_valuating.valuators << valuator } it { should be_able_to(:read, SpendingProposal) } it { should be_able_to(:update, SpendingProposal) } it { should be_able_to(:valuate, SpendingProposal) } + + it { should_not be_able_to(:update, non_assigned_investment) } + it { should_not be_able_to(:valuate, non_assigned_investment) } + + it { should be_able_to(:update, assigned_investment) } + it { should be_able_to(:valuate, assigned_investment) } + + it { should_not be_able_to(:update, assigned_investment_not_valuating) } + it { should_not be_able_to(:valuate, assigned_investment_not_valuating) } end From 2a82e9e33ac447f95e32f6db30f7f1779901d1f1 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 30 May 2016 14:01:47 +0200 Subject: [PATCH 032/384] Redoes the common balloting permissions --- app/models/abilities/common.rb | 2 +- spec/models/abilities/common_spec.rb | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index ed2c3d4a0..0d1bfd4b9 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -49,7 +49,7 @@ module Abilities can :create, SpendingProposal can :create, Budget::Investment, budget: { phase: "accepting" } can :vote, Budget::Investment, budget: { phase: "selecting" } - can :ballot, Budget::Investment, budget: { phase: "balloting" } + can :create, Budget::Ballot, budget: { phase: "balloting" } end can :create, Annotation diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index 36b4b49f6..6fd751c9f 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -9,9 +9,15 @@ describe "Abilities::Common" do let(:debate) { create(:debate) } let(:comment) { create(:comment) } let(:proposal) { create(:proposal) } - let(:investment_in_accepting_budget) { create(:budget_investment, budget: create(:budget, phase: 'accepting')) } - let(:investment_in_selecting_budget) { create(:budget_investment, budget: create(:budget, phase: 'selecting')) } - let(:investment_in_balloting_budget) { create(:budget_investment, budget: create(:budget, phase: 'balloting')) } + let(:accepting_budget) { create(:budget, phase: 'accepting') } + let(:selecting_budget) { create(:budget, phase: 'selecting') } + let(:balloting_budget) { create(:budget, phase: 'balloting') } + let(:investment_in_accepting_budget) { create(:budget_investment, budget: accepting_budget) } + let(:investment_in_selecting_budget) { create(:budget_investment, budget: selecting_budget) } + let(:investment_in_balloting_budget) { create(:budget_investment, budget: balloting_budget) } + let(:ballot_in_accepting_budget) { create(:budget_ballot, budget: accepting_budget) } + let(:ballot_in_selecting_budget) { create(:budget_ballot, budget: selecting_budget) } + let(:ballot_in_balloting_budget) { create(:budget_ballot, budget: balloting_budget) } let(:own_debate) { create(:debate, author: user) } let(:own_comment) { create(:comment, author: user) } let(:own_proposal) { create(:proposal, author: user) } @@ -105,9 +111,9 @@ describe "Abilities::Common" do it { should be_able_to(:vote, investment_in_selecting_budget) } it { should_not be_able_to(:vote, investment_in_balloting_budget) } - it { should_not be_able_to(:ballot, investment_in_accepting_budget) } - it { should_not be_able_to(:ballot, investment_in_selecting_budget) } - it { should be_able_to(:ballot, investment_in_balloting_budget) } + it { should_not be_able_to(:create, ballot_in_accepting_budget) } + it { should_not be_able_to(:create, ballot_in_selecting_budget) } + it { should be_able_to(:create, ballot_in_balloting_budget) } end From 7005faabe6c4ba1030935eafadce39bad87b94e5 Mon Sep 17 00:00:00 2001 From: kikito Date: Mon, 30 May 2016 14:13:36 +0200 Subject: [PATCH 033/384] adds admin permissions for investments --- app/models/abilities/administrator.rb | 8 ++++++-- spec/models/abilities/administrator_spec.rb | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 6b77c130d..b86723c93 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -4,7 +4,6 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - self.merge Abilities::Valuator.new(user) can :restore, Comment cannot :restore, Comment, hidden_at: nil @@ -41,7 +40,12 @@ module Abilities can :manage, Annotation - can [:read, :update, :destroy, :summary], SpendingProposal + can [:read, :update, :valuate, :destroy, :summary], SpendingProposal + + can [:create, :update], Budget + can [:hide, :update], Budget::Investment + can :valuate, Budget::Investment, budget: { valuating: true } + can :create, Budget::ValuatorAssignment end end end diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index f9ed7a0c5..8170d3434 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -56,4 +56,16 @@ describe "Abilities::Administrator" do it { should be_able_to(:update, SpendingProposal) } it { should be_able_to(:valuate, SpendingProposal) } it { should be_able_to(:destroy, SpendingProposal) } + + it { should be_able_to(:create, Budget) } + it { should be_able_to(:update, Budget) } + + it { should be_able_to(:create, Budget::ValuatorAssignment) } + + it { should be_able_to(:update, Budget::Investment) } + it { should be_able_to(:hide, Budget::Investment) } + + it { should be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: true))) } + it { should_not be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: false))) } + end From 56f5983dc4f8f3f08cc910243e24872083ad015c Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 31 May 2016 12:53:11 +0200 Subject: [PATCH 034/384] Adds budget dev_seeds --- db/dev_seeds.rb | 58 ++++++++++++++++++- ...08_add_budget_investments_count_to_tags.rb | 5 ++ db/schema.rb | 3 +- 3 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20160531102008_add_budget_investments_count_to_tags.rb diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index a4352c9d6..e82c01dd0 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -149,7 +149,7 @@ tags = Faker::Lorem.words(25) description = "

#{Faker::Lorem.paragraphs.join('

')}

" proposal = Proposal.create!(author: author, title: Faker::Lorem.sentence(3).truncate(60), - question: Faker::Lorem.sentence(3), + question: Faker::Lorem.sentence(4), summary: Faker::Lorem.sentence(3), responsible_name: Faker::Name.name, external_url: Faker::Internet.url, @@ -168,7 +168,7 @@ tags = ActsAsTaggableOn::Tag.where(kind: 'category') description = "

#{Faker::Lorem.paragraphs.join('

')}

" proposal = Proposal.create!(author: author, title: Faker::Lorem.sentence(3).truncate(60), - question: Faker::Lorem.sentence(3), + question: Faker::Lorem.sentence(4), summary: Faker::Lorem.sentence(3), responsible_name: Faker::Name.name, external_url: Faker::Internet.url, @@ -266,7 +266,7 @@ puts "Creating Spending Proposals" tags = Faker::Lorem.words(10) -(1..60).each do |i| +(1..10).each do |i| geozone = Geozone.reorder("RANDOM()").first author = User.reorder("RANDOM()").first description = "

#{Faker::Lorem.paragraphs.join('

')}

" @@ -294,6 +294,58 @@ puts "Creating Valuation Assignments" SpendingProposal.reorder("RANDOM()").first.valuators << valuator.valuator end + +puts "Creating Budgets" + +(1..10).each do |i| + budget = Budget.create!(name: (Date.today.year - 10 + i).to_s, + description: "

#{Faker::Lorem.paragraphs.join('

')}

", + price: rand(1 .. 100) * 100000, + phase: %w{on_hold accepting selecting balloting finished}.sample, + valuating: [false, true].sample) + puts budget.name + puts " " + geozones = Geozone.reorder("RANDOM()").limit(10) + geozones.each do |geozone| + heading = budget.headings.create!(name: geozone.name, + geozone: geozone, + price: rand(1 .. 100) * 10000) + budget.headings << heading + print "heading.name " + end + puts "" +end + + +puts "Creating Investments" +tags = Faker::Lorem.words(10) +(1..60).each do |i| + heading = [Budget::Heading.reorder("RANDOM()").first, nil].sample + + investment = Budget::Investment.create!( + author: User.reorder("RANDOM()").first, + heading: heading, + budget: heading.try(:budget) || Budget.reorder("RANDOM()").first, + title: Faker::Lorem.sentence(3).truncate(60), + external_url: Faker::Internet.url, + description: "

#{Faker::Lorem.paragraphs.join('

')}

", + created_at: rand((Time.now - 1.week) .. Time.now), + feasibility: %w{undecided feasible unfeasible}.sample, + unfeasibility_explanation: "

#{Faker::Lorem.paragraphs.join('

')}

", + valuation_finished: [false, true].sample, + tag_list: tags.sample(3).join(','), + price: rand(1 .. 100) * 1000000, + terms_of_service: "1") + puts " #{investment.title}" +end + +puts "Creating Valuation Assignments" + +(1..17).to_a.sample.times do + Budget::Investment.reorder("RANDOM()").first.valuators << valuator.valuator +end + + puts "Creating Legislation" Legislation.create!(title: 'Participatory Democracy', body: 'In order to achieve...') diff --git a/db/migrate/20160531102008_add_budget_investments_count_to_tags.rb b/db/migrate/20160531102008_add_budget_investments_count_to_tags.rb new file mode 100644 index 000000000..256adcc0c --- /dev/null +++ b/db/migrate/20160531102008_add_budget_investments_count_to_tags.rb @@ -0,0 +1,5 @@ +class AddBudgetInvestmentsCountToTags < ActiveRecord::Migration + def change + add_column :tags, "budget/investments_count", :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 04889b20a..de26932c7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160524144005) do +ActiveRecord::Schema.define(version: 20160531102008) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -425,6 +425,7 @@ ActiveRecord::Schema.define(version: 20160524144005) do t.integer "proposals_count", default: 0 t.integer "spending_proposals_count", default: 0 t.string "kind" + t.integer "budget/investments_count", default: 0 end add_index "tags", ["debates_count"], name: "index_tags_on_debates_count", using: :btree From 0427395f324a27660f5684251ca84d590c8550ac Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 31 May 2016 18:58:50 +0200 Subject: [PATCH 035/384] Adds missing currency symbol to dev_seeds --- db/dev_seeds.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index e82c01dd0..f009b24f9 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -301,6 +301,7 @@ puts "Creating Budgets" budget = Budget.create!(name: (Date.today.year - 10 + i).to_s, description: "

#{Faker::Lorem.paragraphs.join('

')}

", price: rand(1 .. 100) * 100000, + currency_symbol: "€", phase: %w{on_hold accepting selecting balloting finished}.sample, valuating: [false, true].sample) puts budget.name From fa6f34bf5c05199d6215b80254c7caa158cea4c1 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 31 May 2016 19:26:56 +0200 Subject: [PATCH 036/384] Adds basic Budget controller / views --- app/controllers/budgets_controller.rb | 13 +++++++++++++ app/helpers/budget_helper.rb | 8 ++++++++ app/models/abilities/common.rb | 3 --- app/models/abilities/everyone.rb | 2 ++ app/models/budget.rb | 3 +++ app/views/budgets/index.html.erb | 7 +++++++ app/views/budgets/show.html.erb | 15 +++++++++++++++ config/routes.rb | 6 ++++++ spec/features/budgets_spec.rb | 18 ++++++++++++++++++ 9 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 app/controllers/budgets_controller.rb create mode 100644 app/helpers/budget_helper.rb create mode 100644 app/views/budgets/index.html.erb create mode 100644 app/views/budgets/show.html.erb create mode 100644 spec/features/budgets_spec.rb diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb new file mode 100644 index 000000000..db05aafea --- /dev/null +++ b/app/controllers/budgets_controller.rb @@ -0,0 +1,13 @@ +class BudgetsController < ApplicationController + + load_and_authorize_resource + respond_to :html, :js + + def show + end + + def index + @budgets = @budgets.order(:created_at) + end + +end diff --git a/app/helpers/budget_helper.rb b/app/helpers/budget_helper.rb new file mode 100644 index 000000000..47dbbca98 --- /dev/null +++ b/app/helpers/budget_helper.rb @@ -0,0 +1,8 @@ +module BudgetHelper + def format_price(budget, number) + number_to_currency(number, + precision: 0, + locale: I18n.default_locale, + unit: budget.currency_symbol) + end +end diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 0d1bfd4b9..f2c5ec720 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -18,9 +18,6 @@ module Abilities end can [:retire_form, :retire], Proposal, author_id: user.id - can :read, SpendingProposal - can :read, Budget::Investment - can :create, Comment can :create, Debate can :create, Proposal diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 21e142c05..327210de8 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -6,6 +6,8 @@ module Abilities can [:read, :map], Debate can [:read, :map, :summary], Proposal can :read, Comment + can :read, Budget + can :read, Budget::Investment can :read, SpendingProposal can :read, Legislation can :read, User diff --git a/app/models/budget.rb b/app/models/budget.rb index b234a8b73..dfcba3214 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -1,8 +1,11 @@ class Budget < ActiveRecord::Base + include Sanitizable + VALID_PHASES = %W{on_hold accepting selecting balloting finished} validates :phase, inclusion: { in: VALID_PHASES } + validates :currency_symbol, presence: true has_many :investments has_many :ballots diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb new file mode 100644 index 000000000..d1f9ebd63 --- /dev/null +++ b/app/views/budgets/index.html.erb @@ -0,0 +1,7 @@ +

Budget Index

+ +
    +<% @budgets.each do |budget| %> +
  • <%= link_to budget.name, budget %>
  • +<% end %> +
diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb new file mode 100644 index 000000000..13574d802 --- /dev/null +++ b/app/views/budgets/show.html.erb @@ -0,0 +1,15 @@ +

<%= @budget.name %>

+ +
<%= @budget.description %>
+ +
    +
  • <%= link_to budget_investments_path(budget_id: @budget.id, heading_id: nil) do %> + No heading (<%= format_price(@budget, @budget.price) %>) + <% end %>
  • + <% @budget.headings.each do |heading| %> +
  • <%= link_to budget_investments_path(budget_id: @budget.id, heading_id: heading.id) do %> + <%= heading.name %> (<%= format_price(@budget, heading.price) %>) + <% end %> +
  • + <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index a2035a0b7..bcc6b7b84 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,12 @@ Rails.application.routes.draw do end end + resources :budgets, only: [:show, :index] do + resources :investments, controller: "budgets/investments", only: [:index, :new, :create, :show, :destroy] do + member { post :vote } + end + end + scope '/participatory_budget' do resources :spending_proposals, only: [:index, :new, :create, :show, :destroy], path: 'investment_projects' do post :vote, on: :member diff --git a/spec/features/budgets_spec.rb b/spec/features/budgets_spec.rb new file mode 100644 index 000000000..4293ef159 --- /dev/null +++ b/spec/features/budgets_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +feature 'Budgets' do + + scenario 'Index' do + budgets = create_list(:budget, 3) + visit budgets_path + budgets.each {|budget| expect(page).to have_link(budget.name)} + end + + scenario 'Show' do + budget = create(:budget) + heading = create(:budget_heading, budget: budget) + visit budget_path(budget) + expect(page).to have_content(budget.name) + expect(page).to have_content(heading.name) + end +end From 9f3b99f0a65f2c37f29f5edf29a12e484c5f7be8 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:30:47 +0200 Subject: [PATCH 037/384] Allows blocks in has_orders concern --- app/controllers/concerns/has_orders.rb | 3 ++- spec/controllers/concerns/has_orders_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/has_orders.rb b/app/controllers/concerns/has_orders.rb index 31a98e850..b17781da2 100644 --- a/app/controllers/concerns/has_orders.rb +++ b/app/controllers/concerns/has_orders.rb @@ -3,7 +3,8 @@ module HasOrders class_methods do def has_orders(valid_orders, *args) - before_action(*args) do + before_action(*args) do |c| + valid_orders = valid_orders.call(c) if valid_orders.respond_to?(:call) @valid_orders = valid_orders @current_order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first end diff --git a/spec/controllers/concerns/has_orders_spec.rb b/spec/controllers/concerns/has_orders_spec.rb index 082c4c068..8155b18e2 100644 --- a/spec/controllers/concerns/has_orders_spec.rb +++ b/spec/controllers/concerns/has_orders_spec.rb @@ -7,10 +7,15 @@ describe 'HasOrders' do controller(FakeController) do include HasOrders has_orders ['created_at', 'votes_count', 'flags_count'], only: :index + has_orders ->{ ['votes_count', 'flags_count'] }, only: :new def index render text: "#{@current_order} (#{@valid_orders.join(' ')})" end + + def new + render text: "#{@current_order} (#{@valid_orders.join(' ')})" + end end it "has the valid orders set up" do @@ -18,6 +23,11 @@ describe 'HasOrders' do expect(response.body).to eq('created_at (created_at votes_count flags_count)') end + it "allows specifying the orders via a lambda" do + get :new + expect(response.body).to eq('votes_count (votes_count flags_count)') + end + describe "the current order" do it "defaults to the first one on the list" do get :index From d57a6c510ffc75fc473f596afc116d6701e8b686 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:31:05 +0200 Subject: [PATCH 038/384] Activates budget setting in dev_seeds --- db/dev_seeds.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index f009b24f9..492f1efca 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -26,6 +26,7 @@ Setting.create(key: 'place_name', value: 'City') Setting.create(key: 'feature.debates', value: "true") Setting.create(key: 'feature.spending_proposals', value: "true") Setting.create(key: 'feature.spending_proposal_features.voting_allowed', value: "true") +Setting.create(key: 'feature.budgets', value: "true") Setting.create(key: 'feature.twitter_login', value: "true") Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.google_login', value: "true") From c6c3e22a40a83dbdc9e646ad1f683157a20d3f08 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:44:34 +0200 Subject: [PATCH 039/384] Adds methods for investments in models --- app/models/budget/ballot.rb | 8 ++++++++ app/models/budget/ballot/line.rb | 21 +++++++++++++++++++++ app/models/budget/investment.rb | 1 + app/models/user.rb | 5 +++++ 4 files changed, 35 insertions(+) diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 9b9a089d6..1638b7afd 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -18,5 +18,13 @@ class Budget def amount_available(heading) budget.heading_price(heading) - amount_spent(heading.try(:id)) end + + def has_lines_with_no_heading? + investments.no_heading.count > 0 + end + + def has_lines_with_heading? + self.heading_id.present? + end end end diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 804ee3a2c..887951922 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -3,6 +3,27 @@ class Budget class Line < ActiveRecord::Base belongs_to :ballot belongs_to :investment + + validate :insufficient_funds + validate :different_geozone, :if => :district_proposal? + validate :unfeasible + + def insufficient_funds + errors.add(:money, "") if ballot.amount_available(investment.heading) < investment.price.to_i + end + + def different_geozone + errors.add(:heading, "") if (ballot.heading.present? && investment.heading != ballot.heading) + end + + def unfeasible + errors.add(:unfeasible, "") unless investment.feasible? + end + + def heading_proposal? + investment.heading_id.present? + end + end end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 329c2fce2..43b0fde2f 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -39,6 +39,7 @@ class Budget scope :valuation_finished, -> { where(valuation_finished: true) } scope :feasible, -> { where(feasibility: "feasible") } scope :unfeasible, -> { where(feasibility: "unfeasible") } + scope :not_unfeasible, -> { where.not(feasibility: "unfeasible") } scope :undecided, -> { where(feasibility: "undecided") } scope :with_supports, -> { where('cached_votes_up > 0') } diff --git a/app/models/user.rb b/app/models/user.rb index 4839d3ced..8da62dd0d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -88,6 +88,11 @@ class User < ActiveRecord::Base voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end + def budget_investment_votes(budget_investments) + voted = votes.for_budget_investments(budget_investments) + voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } + end + def comment_flags(comments) comment_flags = flags.for_comments(comments) comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true } From 70513330282008d034acd3faf024e3981c0364e6 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:50:28 +0200 Subject: [PATCH 040/384] Adds actions to application_controller and investments_controller --- app/controllers/application_controller.rb | 4 + .../budgets/investments_controller.rb | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 app/controllers/budgets/investments_controller.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3e1b78d31..6384f19e3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -80,6 +80,10 @@ class ApplicationController < ActionController::Base @spending_proposal_votes = current_user ? current_user.spending_proposal_votes(spending_proposals) : {} end + def set_budget_investment_votes(budget_investments) + @budget_investments_votes = current_user ? current_user.budget_investment_votes(budget_investments) : {} + end + def set_comment_flags(comments) @comment_flags = current_user ? current_user.comment_flags(comments) : {} end diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb new file mode 100644 index 000000000..7ab31b0da --- /dev/null +++ b/app/controllers/budgets/investments_controller.rb @@ -0,0 +1,97 @@ +module Budgets + class InvestmentsController < ApplicationController + include FeatureFlags + include CommentableActions + include FlagActions + + before_action :authenticate_user!, except: [:index, :show] + + load_and_authorize_resource :budget + load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment" + + before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } + before_action :load_ballot, only: [:index, :show] + before_action :load_heading, only: [:index, :show] + + feature_flag :budgets + + has_orders %w{most_voted newest oldest}, only: :show + has_orders ->(c){ c.instance_variable_get(:@budget).balloting? ? %w{random price} : %w{random confidence_score} }, only: :index + + invisible_captcha only: [:create, :update], honeypot: :subtitle + + respond_to :html, :js + + def index + @investments = apply_filters_and_search(@investments).send("sort_by_#{@current_order}").page(params[:page]).per(10).for_render + set_budget_investment_votes(@investments) + end + + def new + end + + def show + @commentable = @investment + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + set_budget_investment_votes(@investment) + end + + def create + @investment.author = current_user + + if @investment.save + notice = t('flash.actions.create.budget_investment', activity: "#{t('layouts.header.my_activity_link')}") + redirect_to @investment, notice: notice, flash: { html_safe: true } + else + render :new + end + end + + def destroy + investment.destroy + redirect_to user_path(current_user, filter: 'budget_investments'), notice: t('flash.actions.destroy.budget_investment') + end + + def vote + @investment.register_selection(current_user) + set_budget_investment_votes(@investment) + end + + private + + def investment_params + params.require(:investment).permit(:title, :description, :external_url, :heading_id, :terms_of_service) + end + + def apply_filters_and_search(investments) + if params[:heading_id].blank? + @filter_heading_name = t('geozones.none') + else + @filter_heading = @budget.headings.find(params[:heading_id]) + @filter_heading_name = @filter_heading.name + end + + investments = investments.by_heading(params[:heading_id].presence) + + if params[:unfeasible].present? + investments = investments.unfeasible + else + investments = @budget.balloting? ? investments.feasible.valuation_finished : investments.not_unfeasible + end + + investments = investments.search(params[:search]) if params[:search].present? + investments + end + + def load_ballot + @ballot = Budget::Ballot.where(user: current_user, budget: @budget).first_or_create + end + + def load_heading + @heading = @budget.headings.find(params[:heading_id]) if params[:geozone_id].present? + end + + end + +end From be6d7a1d39b8fcd8948263dd104325bd1bb97523 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:50:49 +0200 Subject: [PATCH 041/384] Adds views and helpers for investments --- app/helpers/budget_helper.rb | 15 +++++ app/views/budgets/investments/_form.html.erb | 49 +++++++++++++++ .../budgets/investments/_header.html.erb | 56 +++++++++++++++++ .../budgets/investments/_investment.html.erb | 61 +++++++++++++++++++ .../investments/_progress_bar.html.erb | 0 .../budgets/investments/_sidebar.html.erb | 29 +++++++++ app/views/budgets/investments/index.html.erb | 49 +++++++++++++++ app/views/budgets/investments/show.html.erb | 4 ++ 8 files changed, 263 insertions(+) create mode 100644 app/views/budgets/investments/_form.html.erb create mode 100644 app/views/budgets/investments/_header.html.erb create mode 100644 app/views/budgets/investments/_investment.html.erb create mode 100644 app/views/budgets/investments/_progress_bar.html.erb create mode 100644 app/views/budgets/investments/_sidebar.html.erb create mode 100644 app/views/budgets/investments/index.html.erb create mode 100644 app/views/budgets/investments/show.html.erb diff --git a/app/helpers/budget_helper.rb b/app/helpers/budget_helper.rb index 47dbbca98..8c19516e8 100644 --- a/app/helpers/budget_helper.rb +++ b/app/helpers/budget_helper.rb @@ -5,4 +5,19 @@ module BudgetHelper locale: I18n.default_locale, unit: budget.currency_symbol) end + + def heading_name(heading) + heading.present? ? heading.name : t("budget.headings.none") + end + + def namespaced_budget_investment_path(investment, options={}) + @namespaced_budget_investment_path ||= namespace + options[:budget_id] ||= investment.budget.id + case @namespace_budget_investment_path + when "management" + management_budget_investment_path(investment, options) + else + budget_investment_path(investment, options) + end + end end diff --git a/app/views/budgets/investments/_form.html.erb b/app/views/budgets/investments/_form.html.erb new file mode 100644 index 000000000..249637d40 --- /dev/null +++ b/app/views/budgets/investments/_form.html.erb @@ -0,0 +1,49 @@ +<%= form_for(@investment, url: form_url) do |f| %> + <%= render 'shared/errors', resource: @investment %> + +
+
+ <%= f.label :title, t("budget.investments.form.title") %> + <%= f.text_field :title, maxlength: SpendingProposal.title_max_length, placeholder: t("budget.investments.form.title"), label: false %> +
+ + <%= f.invisible_captcha :subtitle %> + +
+ <%= f.label :description, t("budget.investments.form.description") %> + <%= f.cktext_area :description, maxlength: SpendingProposal.description_max_length, ckeditor: { language: I18n.locale }, label: false %> +
+ +
+ <%= f.label :external_url, t("budget.investments.form.external_url") %> + <%= f.text_field :external_url, placeholder: t("budget.investments.form.external_url"), label: false %> +
+ +
+ <%= f.label :heading_id, t("budget.investments.form.heading") %> + <%= f.select :heading_id, heading_select_options, {include_blank: t("budget.headings.none"), label: false} %> +
+ +
+ <%= f.label :association_name, t("budget.investments.form.association_name_label") %> + <%= f.text_field :association_name, placeholder: t("budget.investments.form.association_name"), label: false %> +
+ +
+ <% if @investment.new_record? %> + <%= f.label :terms_of_service do %> + <%= f.check_box :terms_of_service, title: t('form.accept_terms_title'), label: false %> + + <%= t("form.accept_terms", + policy: link_to(t("form.policy"), "/privacy", target: "blank"), + conditions: link_to(t("form.conditions"), "/conditions", target: "blank")).html_safe %> + + <% end %> + <% end %> +
+ +
+ <%= f.submit(class: "button", value: t("budget.investments.form.submit_buttons.#{action_name}")) %> +
+
+<% end %> diff --git a/app/views/budgets/investments/_header.html.erb b/app/views/budgets/investments/_header.html.erb new file mode 100644 index 000000000..ae5dbd318 --- /dev/null +++ b/app/views/budgets/investments/_header.html.erb @@ -0,0 +1,56 @@ +<% if @filter_heading_name.present? %> +
+
+ +
+
+ <%= link_to @budget, class: "back" do %> + + <%= t("shared.back") %> + <% end %> + + <% if can? :show, @ballot %> + <%= link_to t("budget.investments.header.check_ballot"), ballot_path, class: "button float-right" %> + <% end %> +
+
+ +
+
+
+

<%= @filter_geozone_name %>

+ + <% if @heading.present? && @ballot.heading.present? && @ballot.heading != @heading %> +
+

+ <%= t("budget.investments.header.different_heading_active") %> + <%= link_to @ballot.heading.name, budget_investments_path(budget_id: budget.id, heading_id: @ballot.heading_id) %> +

+ <% else %> +
+ <%= render 'progress_bar' %> +
+ <% end %> +
+
+
+
+
+<% else %> +
+
+
+ <%= link_to budget_path(@budget), class: "back" do %> + + <%= t("shared.back") %> + <% end %> + +

<%= t('budget.investments.index.title') %>

+
+
+
+<% end %> diff --git a/app/views/budgets/investments/_investment.html.erb b/app/views/budgets/investments/_investment.html.erb new file mode 100644 index 000000000..3432bed49 --- /dev/null +++ b/app/views/budgets/investments/_investment.html.erb @@ -0,0 +1,61 @@ +
+
+
+ +
+
+ + <% cache [locale_and_user_status(investment), 'index', investment, investment.author] do %> + <%= t("budget.investments.investment.title") %> + +

<%= link_to investment.title, namespaced_budget_investment_path(investment) %>

+

+ + <%= l investment.created_at.to_date %> + + <% if investment.author.hidden? || investment.author.erased? %> +  •  + + <%= t("budget.investments.show.author_deleted") %> + + <% else %> +  •  + + <%= investment.author.name %> + + <% if investment.author.official? %> +  •  + + <%= investment.author.official_position %> + + <% end %> + <% end %> + +  •  + <%= heading_name(investment.heading) %> +

+
+

<%= link_to investment.description, namespaced_budget_investment_path(investment) %>

+
+
+ <% end %> +
+
+ + <% unless investment.unfeasible? %> + <% if feature?("investment_features.phase2") %> +
+ <%= render 'votes', + { investment: investment, vote_url: vote_investment_path(investment, value: 'yes') } %> +
+ <% elsif feature?("investment_features.phase3") %> +
+ <%= render 'ballot', investment: investment %> +
+ <% end %> + <% end %> +
+
+
diff --git a/app/views/budgets/investments/_progress_bar.html.erb b/app/views/budgets/investments/_progress_bar.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/budgets/investments/_sidebar.html.erb b/app/views/budgets/investments/_sidebar.html.erb new file mode 100644 index 000000000..53c5e98bc --- /dev/null +++ b/app/views/budgets/investments/_sidebar.html.erb @@ -0,0 +1,29 @@ +<%= link_to @budget, class: "back" do %> + + <%= t("spending_proposals.index.sidebar.back") %> +<% end %> + +
+ + + + +<% if @ballot.investments.by_heading(@heading).count > 0 %> +

+ + <%= t("budget.investments.index.sidebar.voted_html", + count: @ballot.investments.by_heading(@heading.id).count, + amount_spent: format_price(@ballot.amount_spent(@heading))) %> + +

+<% else %> +

<%= t("budget.investments.index.sidebar.zero") %>

+<% end %> + +
    + <% @ballot.investments.by_heading(@heading).each do |investment| %> + <%= render 'ballots/investment_for_sidebar', investment: investment %> + <% end %> +
+ +

<%= t("budget.investments.index.sidebar.voted_info") %>

diff --git a/app/views/budgets/investments/index.html.erb b/app/views/budgets/investments/index.html.erb new file mode 100644 index 000000000..1f587c5ca --- /dev/null +++ b/app/views/budgets/investments/index.html.erb @@ -0,0 +1,49 @@ +<% provide :title do %><%= t('budget.investments.index.title') %><% end %> +<% content_for :header_addon do %> + <%= render "shared/search_form", + search_path: budget_investments_path(budget_id: @budget.id, page: 1), + i18n_namespace: "budget.investments.index.search_form" %> +<% end %> + +
+ + <%= render 'header' %> + +
+
+ +
+ + <% if params[:unfeasible].present? %> +

<%= t("budget.investments.index.unfeasible") %>

+

+ <%= t("budget.investments.index.unfeasible_text", + definitions: link_to(t("budget.investments.index.unfeasible_text_definitions"), "https://decide.madrid.es/participatory_budget_info#20")).html_safe %> +

+ <% end %> + + <%= content_tag(:h2, t("budget.investments.index.by_heading", heading: @filter_heading_name)) if @filter_heading_name.present? %> + <% if params[:search].present? %> +

+ <%= page_entries_info @investments %> + <%= t("budget.investments.index.search_results", count: @investments.size, search_term: params[:search]) %> +

+ <% end %> +
+ + <%= render('shared/order_links', i18n_namespace: "budget.investments.index") unless params[:unfeasible].present? %> + + <%= render partial: 'investment', collection: @investments %> + <%= paginate @investments %> +
+ +
+ +
+ +
+
diff --git a/app/views/budgets/investments/show.html.erb b/app/views/budgets/investments/show.html.erb new file mode 100644 index 000000000..55cd12a6f --- /dev/null +++ b/app/views/budgets/investments/show.html.erb @@ -0,0 +1,4 @@ +

<%= @investment.title %>

+ +

<%= @investment.author.username %> + <%= @investment.price %>

From e2d88e30fdbed3c499740639b3b22cfc205a30db Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:51:00 +0200 Subject: [PATCH 042/384] i18n for investments --- config/locales/en.yml | 81 +++++++++++++++++++++++++++++++ config/locales/es.yml | 83 +++++++++++++++++++++++++++++++- config/locales/responders.en.yml | 5 +- config/locales/responders.es.yml | 5 +- 4 files changed, 171 insertions(+), 3 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 62fc521c5..f91d4ac1a 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -418,6 +418,85 @@ en: facebook: Facebook twitter: Twitter youtube: YouTube + budget: + headings: + none: Whole City + all: All scopes + investments: + form: + association_name_label: 'If you propose in name of an assocation or collective add the name here' + association_name: 'Association name' + description: Description + external_url: Link to additional documentation + heading: Choose if a proposed citywide or district + submit_buttons: + create: Create + new: Create + title: Investment title + index: + available: "Available:" + title: Participatory budgeting + unfeasible: Unfeasible investment projects + unfeasible_text: "The proposals must meet a number of criteria (legality, concreteness, be the responsibility of the city, not exceed the limit of the budget; %{definitions}) to be declared viable and reach the stage of final vote. All proposals don't meet these criteria are marked as unfeasible and published in the following list, along with its report of infeasibility." + unfeasible_text_definitions: see definitions here + by_heading: "Investment projects with scope: %{heading}" + search_form: + button: Search + placeholder: Investment projects... + title: Search + search_results: + one: " containing the term '%{search_term}'" + other: " containing the term '%{search_term}'" + sidebar: + back: Back to select page + district: District + my_ballot: My ballot + remember_city: You can also vote %{city} investment projects. + remember_city_link_html: city-wide + remember_district: You can also vote investment projects for %{district}. + remember_district_link_html: a district + voted_html: + one: "You voted one proposal with a cost of %{amount_spent}" + other: "You voted %{count} proposals with a cost of %{amount_spent}" + voted_info: You can change your vote at any time until the close of this phase. No need to spend all the money available. + votes: Supports remaining + votes_district: "You can only vote in the district %{district}" + zero: You have not voted any investment project. + orders: + random: random + confidence_score: highest rated + price: by price + new: + back_link: Back + more_info: "Important, not to be ruled out your proposal must comply:" + recommendation_one: See the %{requirements}. + recommendation_one_link: requirements to be met by a proposal + recommendation_three: Try to go into details when describing your spending proposal so the reviewing team undertands your points. + recommendation_two: Each proposal must be submitted separately. You can make as many want. + recommendations_title: How to create a spending proposal + start_new: Create spending proposal + show: + author_deleted: User deleted + price_explanation: Price explanation + unfeasibility_explanation: Unfeasibility explanation + code: 'Investment project code:' + share: Share + wrong_price_format: Only integer numbers + investment: + title: Investment project + add: Add + already_added: You have already added this investment project + already_supported: You have already supported this. Share it! + forum: District discussion space + support_title: Support this project + supports: + one: 1 support + other: "%{count} supports" + zero: No supports + vote: Vote + header: + check_ballot: Check my ballot + different_heading_active: You have active votes in another district. spending_proposals: form: association_name_label: 'If you propose in name of an assocation or collective add the name here' @@ -441,9 +520,11 @@ en: one: " containing the term '%{search_term}'" other: " containing the term '%{search_term}'" sidebar: + back: Volver geozones: Scope of operation feasibility: Feasibility unfeasible: Unfeasible + my_ballot: My votes start_spending_proposal: Create an investment project new: more_info: How do participatory budgeting works? diff --git a/config/locales/es.yml b/config/locales/es.yml index 05e53fc2b..452a6d94a 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -151,7 +151,7 @@ es: verification/sms: el teléfono geozones: none: Toda la ciudad - all: Todos los ámbitos + all: Todos los ámbitos de actuación layouts: application: chrome: Google Chrome @@ -418,6 +418,85 @@ es: facebook: Facebook twitter: Twitter youtube: YouTube + budget: + headings: + none: Toda la ciudad + all: Todos los ámbitos + investments: + form: + association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' + association_name: 'Nombre de la asociación' + description: Descripción detallada + external_url: Enlace a documentación adicional + heading: "Elige si es una propuesta para toda la ciudad o para un distrito" + submit_buttons: + create: Crear + new: Crear + title: Título de la propuesta de inversión + index: + available: "Disponible:" + title: Presupuestos participativos + unfeasible: Propuestas de inversión no viables + unfeasible_text: Las propuestas presentadas deben cumplir una serie de criterios (legalidad, concreción, ser competencia del Ayuntamiento, no superar el tope del presupuesto; %{definitions}) para ser declaradas viables y llegar hasta la fase de votación final. Todas las propuestas que no cumplen estos criterios son marcadas como inviables y publicadas en la siguiente lista, junto con su informe de inviabilidad. + unfeasible_text_definitions: ver definiciones aquí + by_heading: "Propuestas de inversión con ámbito: %{heading}" + search_form: + button: Buscar + placeholder: Propuestas de inversión... + title: Buscar + search_results: + one: " que contiene '%{search_term}'" + other: " que contienen '%{search_term}'" + sidebar: + back: Volver a página de selección + district: Distrito + my_ballot: Mis votos + remember_city: Además puedes votar propuestas de inversión para %{city}. + remember_city_link_html: toda la ciudad + remember_district: Además puedes votar propuestas de inversión para %{district}. + remember_district_link_html: un distrito + voted_html: + one: "Has votado una propuesta por un valor de %{amount_spent}" + other: "Has votado %{count} propuestas por un valor de %{amount_spent}" + voted_info: Puedes cambiar tus votos en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible. + votes: Apoyos restantes + votes_district: "Solo puedes votar en el distrito %{district}" + zero: "Todavía no has votado ninguna propuesta de inversión." + orders: + random: Aleatorias + confidence_score: Mejor valoradas + price: Por coste + new: + more_info: "¿Cómo funcionan los presupuestos participativos?" + recommendation_one: Consulta los %{requirements}. + recommendation_one_link: requisitos que debe cumplir una propuesta + recommendation_three: Intenta detallar lo máximo posible la propuesta para que el equipo de gobierno encargado de estudiarla tenga las menor dudas posibles. + recommendation_two: Cualquier propuesta o comentario que implique acciones ilegales será eliminada. + recommendations_title: Cómo crear una propuesta de inversión + start_new: Crear una propuesta de inversión + back_link: Volver + show: + author_deleted: Usuario eliminado + price_explanation: Informe de coste + unfeasibility_explanation: Informe de inviabilidad + code: 'Código propuesta de gasto:' + share: Compartir + wrong_price_format: Solo puede incluir caracteres numéricos + investment: + title: Propuesta de inversión + add: Añadir + already_added: "Ya has añadido esta propuesta de inversión" + already_supported: Ya has apoyado este proyecto. ¡Compártelo! + forum: Espacio de debate distrital + support_title: Apoyar este proyecto + supports: + one: 1 apoyo + other: "%{count} apoyos" + zero: Sin apoyos + vote: Votar + header: + check_ballot: Revisar mis votos + different_heading_active: Ya apoyaste propuestas de otro distrito. spending_proposals: form: association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' @@ -441,9 +520,11 @@ es: one: " que contiene '%{search_term}'" other: " que contienen '%{search_term}'" sidebar: + back: Volver geozones: Ámbitos de actuación feasibility: Viabilidad unfeasible: No viables + my_ballot: Mis votos start_spending_proposal: Crea una propuesta de inversión new: more_info: "¿Cómo funcionan los presupuestos participativos?" diff --git a/config/locales/responders.en.yml b/config/locales/responders.en.yml index 3f83f746a..27d768f3e 100755 --- a/config/locales/responders.en.yml +++ b/config/locales/responders.en.yml @@ -7,6 +7,7 @@ en: debate: "Debate created successfully." proposal: "Proposal created successfully." spending_proposal: "Spending proposal created successfully. You can access it from %{activity}" + budget_investment: "Budget Investment created successfully. You can access it from %{activity}" save_changes: notice: Changes saved update: @@ -14,5 +15,7 @@ en: debate: "Debate updated successfully." proposal: "Proposal updated successfully." spending_proposal: "Investment project updated succesfully." + budget_investment: "Budget Investment updated succesfully." destroy: - spending_proposal: "Spending proposal deleted succesfully." \ No newline at end of file + spending_proposal: "Spending proposal deleted succesfully." + budget_investment: "Budget Investment deleted succesfully." diff --git a/config/locales/responders.es.yml b/config/locales/responders.es.yml index e57dacd31..5a6da92c8 100644 --- a/config/locales/responders.es.yml +++ b/config/locales/responders.es.yml @@ -7,6 +7,7 @@ es: debate: "Debate creado correctamente." proposal: "Propuesta creada correctamente." spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}" + budget_investment: "Inversión creada correctamente. Puedes verla desde %{activity}" save_changes: notice: Cambios guardados update: @@ -14,5 +15,7 @@ es: debate: "Debate actualizado correctamente." proposal: "Propuesta actualizada correctamente." spending_proposal: "Propuesta de inversión actualizada correctamente." + budget_investment: "Propuesta de inversión actualizada correctamente" destroy: - spending_proposal: "Propuesta de inversión eliminada." \ No newline at end of file + spending_proposal: "Propuesta de inversión eliminada." + budget_investment: "Propuesta de inversión eliminada." From 4ade8325d768a8feebe7871e88d133305bb8ebc4 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 3 Jun 2016 18:51:08 +0200 Subject: [PATCH 043/384] css for investments --- app/assets/stylesheets/participation.scss | 37 ++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 502fe62aa..df99b56ed 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -331,7 +331,7 @@ // 03. Show participation // ---------------------- -.debate-show, .proposal-show, .investment-project-show { +.debate-show, .proposal-show, .investment-project-show, .budget-investment-show { p { word-wrap: break-word; @@ -358,7 +358,7 @@ margin-bottom: 0; } - .debate-info, .proposal-info, .investment-project-info { + .debate-info, .proposal-info, .investment-project-info, .budget-investment-show { clear: both; color: $text-medium; font-size: $small-font-size; @@ -539,28 +539,28 @@ color: $border; } -.investment-project-show p { +.investment-project-show p, .budget-investment-show p { word-break: break-word; } // 04. List participation // ---------------------- -.debates-list, .proposals-list, .investment-projects-list { +.debates-list, .proposals-list, .investment-projects-list, .budget-investments-list { @include breakpoint(small) { margin-bottom: rem-calc(48); } } -.investment-projects-list { +.investment-projects-list, .budget-investments-list { @include breakpoint(small) { min-height: $line-height*15; } } -.debate, .proposal, .investment-project { +.debate, .proposal, .investment-project, .budget-investment { margin-bottom: 0; margin-top: 0; @@ -579,7 +579,7 @@ padding-bottom: rem-calc(12); } - .label-debate, .label-proposal, .label-investment-project { + .label-debate, .label-proposal, .label-investment-project, .label-budget-investment { background: none; clear: both; display: block; @@ -604,6 +604,10 @@ color: $budget; } + .label-budget-investment { + color: $budget; + } + h3 { font-weight: bold; margin: 0; @@ -613,7 +617,7 @@ } } - .debate-content, .proposal-content, .investment-project-content { + .debate-content, .proposal-content, .investment-project-content, .budget-investment-content { margin: 0; min-height: rem-calc(180); position: relative; @@ -643,7 +647,7 @@ font-size: $small-font-size; } - .debate-info, .proposal-info, .investment-project-info { + .debate-info, .proposal-info, .investment-project-info, .budget-investment-info { color: $text-medium; font-size: $small-font-size; margin: rem-calc(6) 0 0; @@ -658,7 +662,7 @@ } } - .debate-description, .proposal-description, .investment-project-description { + .debate-description, .proposal-description, .investment-project-description, .budget-investment-description { color: $text; font-size: rem-calc(13); height: rem-calc(72); @@ -819,7 +823,8 @@ } } -.investment-project, .investment-project-show { +.investment-project, .investment-project-show, +.budget-investment, .budget-investment-show { .supports { @include supports; @@ -838,7 +843,8 @@ content: none; } - .investment-project-amount { + .investment-project-amount, + .budget-investment-amount { color: $budget; font-size: rem-calc(20); font-weight: bold; @@ -899,7 +905,8 @@ } } -.investment-project-show .supports { +.investment-project-show .supports, +.budget-investment-show .supports { border: 0; } @@ -911,7 +918,9 @@ } .investment-project .supports .total-supports.no-button, -.investment-project-show .supports .total-supports.no-button { +.investment-project-show .supports .total-supports.no-button, +.budget-investment .supports .total-supports.no-button, +.budget-investment-show .supports .total-supports.no-button { display: block; margin-top: $line-height*1.5; } From a9dbc6160a217b3c8dbdbb3b576c8db7230e6abe Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:09:43 +0200 Subject: [PATCH 044/384] Fixes typo in ApplicationController --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6384f19e3..62058141e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -81,7 +81,7 @@ class ApplicationController < ActionController::Base end def set_budget_investment_votes(budget_investments) - @budget_investments_votes = current_user ? current_user.budget_investment_votes(budget_investments) : {} + @budget_investment_votes = current_user ? current_user.budget_investment_votes(budget_investments) : {} end def set_comment_flags(comments) From 118b9c843ddddc21965d2864662704cdb21f0c95 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:09:57 +0200 Subject: [PATCH 045/384] Activates budget in seeds for tests --- db/seeds.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/seeds.rb b/db/seeds.rb index 067349837..85edfbbb8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -61,6 +61,7 @@ Setting['feature.twitter_login'] = true Setting['feature.facebook_login'] = true Setting['feature.google_login'] = true Setting['feature.public_stats'] = true +Setting['feature.budgets'] = true # Spending proposals feature flags Setting['feature.spending_proposal_features.voting_allowed'] = true From 888fb8222736c659cf7507dc0043d0f6fec9da8d Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:10:15 +0200 Subject: [PATCH 046/384] Includes a default unfeasibility_explanation when creating an unfeasible Investment --- spec/factories.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/factories.rb b/spec/factories.rb index 94ad15c0f..72d96c491 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -232,6 +232,7 @@ FactoryGirl.define do trait :unfeasible do feasibility "unfeasible" + unfeasibility_explanation "set to unfeasible on creation" end trait :finished do From 1aa70bec946b7d0062e525570803187776792be3 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:10:42 +0200 Subject: [PATCH 047/384] Adds method Ballot#has_investment? --- app/models/budget/ballot.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 1638b7afd..db4017285 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -26,5 +26,9 @@ class Budget def has_lines_with_heading? self.heading_id.present? end + + def has_investment?(investment) + self.investment_ids.include?(investment.id) + end end end From d1311438e7f992f1e39d80365f94259ac68148a2 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:11:00 +0200 Subject: [PATCH 048/384] Sets the random seed in the investments_controller --- app/controllers/budgets/investments_controller.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index 7ab31b0da..fcc380d22 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -12,6 +12,7 @@ module Budgets before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } before_action :load_ballot, only: [:index, :show] before_action :load_heading, only: [:index, :show] + before_action :set_random_seed, only: :index feature_flag :budgets @@ -60,6 +61,15 @@ module Budgets private + def set_random_seed + if params[:order] == 'random' || params[:order].blank? + params[:random_seed] ||= rand(99)/100.0 + Budget::Investment.connection.execute "select setseed(#{params[:random_seed]})" + else + params[:random_seed] = nil + end + end + def investment_params params.require(:investment).permit(:title, :description, :external_url, :heading_id, :terms_of_service) end From 5f5e7b7c07ad93b7efa7f023277a9230a15166d8 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:12:11 +0200 Subject: [PATCH 049/384] Adds vote extensions to handle budget_investments --- config/initializers/vote_extensions.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/initializers/vote_extensions.rb b/config/initializers/vote_extensions.rb index 345cb8f01..5f2d632db 100644 --- a/config/initializers/vote_extensions.rb +++ b/config/initializers/vote_extensions.rb @@ -11,6 +11,10 @@ ActsAsVotable::Vote.class_eval do where(votable_type: 'SpendingProposal', votable_id: spending_proposals) end + def self.for_budget_investments(budget_investments) + where(votable_type: 'Budget::Investment', votable_id: budget_investments) + end + def value vote_flag end From 43aec952bf89eef298d7c70919c319e6f8eb77f2 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:12:29 +0200 Subject: [PATCH 050/384] Adds route for ballot & ballot lines --- config/routes.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index bcc6b7b84..efbd26686 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -73,6 +73,9 @@ Rails.application.routes.draw do resources :investments, controller: "budgets/investments", only: [:index, :new, :create, :show, :destroy] do member { post :vote } end + resource :ballot, only: :show do + resources :lines, controller: "budgets/ballot/lines", only: [:create, :destroy] + end end scope '/participatory_budget' do From 630a4878a8d87bf7d43258e877ccee7905248ff2 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:12:58 +0200 Subject: [PATCH 051/384] Adds a lot of views (WIP) --- app/views/budgets/ballots/_add.html.erb | 16 +++ app/views/budgets/ballots/_ballot.html.erb | 97 +++++++++++++++++++ .../budgets/ballots/_investment.html.erb | 14 +++ .../ballots/_investment_for_sidebar.html.erb | 15 +++ app/views/budgets/ballots/_remove.html.erb | 18 ++++ app/views/budgets/ballots/show.html.erb | 3 + app/views/budgets/index.html.erb | 5 +- .../budgets/investments/_ballot.html.erb | 23 +++++ .../budgets/investments/_comments.html.erb | 31 ++++++ .../budgets/investments/_investment.html.erb | 4 +- app/views/budgets/investments/_votes.html.erb | 46 +++++++++ app/views/budgets/investments/show.html.erb | 85 +++++++++++++++- 12 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 app/views/budgets/ballots/_add.html.erb create mode 100644 app/views/budgets/ballots/_ballot.html.erb create mode 100644 app/views/budgets/ballots/_investment.html.erb create mode 100644 app/views/budgets/ballots/_investment_for_sidebar.html.erb create mode 100644 app/views/budgets/ballots/_remove.html.erb create mode 100644 app/views/budgets/ballots/show.html.erb create mode 100644 app/views/budgets/investments/_ballot.html.erb create mode 100644 app/views/budgets/investments/_comments.html.erb create mode 100644 app/views/budgets/investments/_votes.html.erb diff --git a/app/views/budgets/ballots/_add.html.erb b/app/views/budgets/ballots/_add.html.erb new file mode 100644 index 000000000..c6b1e7476 --- /dev/null +++ b/app/views/budgets/ballots/_add.html.erb @@ -0,0 +1,16 @@ +
+

+ <%= format_price(@budget, investment.price) %> +

+ + <% if @budget.balloting? %> + <%= link_to budget_ballot_lines_url(investment_id: investment.id, + investments_ids: @ballot.investment_ids), + class: "button button-support small expanded", + title: t('investments.investment.support_title'), + method: "post", + remote: true do %> + <%= t("investments.investment.add") %> + <% end %> + <% end %> +
diff --git a/app/views/budgets/ballots/_ballot.html.erb b/app/views/budgets/ballots/_ballot.html.erb new file mode 100644 index 000000000..5187c5eaa --- /dev/null +++ b/app/views/budgets/ballots/_ballot.html.erb @@ -0,0 +1,97 @@ +
+ + <%= link_to :back, class: "back" do %> + + <%= t("shared.back") %> + <% end %> + +

<%= t("budgets.ballots.show.title") %>

+ +
+

+ <%= t("budgets.ballots.show.voted_html", + count: @ballot.investments.count) %> +

+ + <% if @ballot.geozone.present? && district_wide_amount_spent(@ballot) > 0 %> + <%= social_share_button_tag("#{t('budgets.ballots.show.social_share', + amount: format_price(district_wide_amount_spent(@ballot)), + geozone: @ballot.geozone.name)} #{setting['twitter_hashtag']}", + url: participatory_budget_url) %> + <% end %> + +

+ <%= t("ballots.show.remaining_city_html", + amount_city: format_price(@ballot.amount_available(nil))) %> +

+ + <% if @ballot.geozone.present? %> +

+ <%= t("budgets.ballots.show.remaining_district_html", + amount_district: format_price(@ballot.amount_available(@ballot.geozone)), + geozone: @ballot.geozone.name) %> +

+ <% end %> + +

+ + <%= t("budgets.ballots.show.voted_info_html") %> + +

+
+ +
+ +
+
+

+ <%= t("budgets.ballots.show.city_wide") %> +

+ <% if @ballot.investments.by_geozone(nil).count > 0 %> +

+ <%= t("budgets.ballots.show.amount_spent") %> + <%= format_price(city_wide_amount_spent(@ballot)) %> +

+ <% else %> +

+ <%= t("budgets.ballots.show.zero") %>
+ <%= link_to t("ballots.show.city_link"), + investments_path(geozone: 'all'), + data: { no_turbolink: true } %> +

+ <% end %> + +
    + <%= render partial: 'budgets/ballots/investment', + collection: @ballot.investments.no_heading %> +
+
+ +
+

+ <%= t("ballots.show.district_wide") %> + + <% if @ballot.geozone.present? %> + (<%= @ballot.geozone.name %>) + <% end %> + +

+ <% if @ballot.geozone.present? %> +

+ <%= t("ballots.show.amount_spent") %> + <%= format_price(district_wide_amount_spent(@ballot)) %> +

+ <% else %> +

+ <%= t("ballots.show.zero") %>
+ <%= link_to t("budget.ballots.show.districts_link"), select_district_path %> +

+ <% end %> + +
    + <%= render partial: 'budgets/ballots/investment', + collection: @ballot.investments.with_heading %> +
+
+
+
diff --git a/app/views/budgets/ballots/_investment.html.erb b/app/views/budgets/ballots/_investment.html.erb new file mode 100644 index 000000000..93c934bd5 --- /dev/null +++ b/app/views/budgets/ballots/_investment.html.erb @@ -0,0 +1,14 @@ +
  • + <%= link_to investment.title, investment %> + <%= format_price(investment.price) %> + + <% if @budget.balloting? %> + <%= link_to ballot_line_path(id: investment.id), + title: t('ballots.show.remove'), + class: "remove-investment-project", + method: :delete, + remote: true do %> + + <% end %> + <% end %> +
  • diff --git a/app/views/budgets/ballots/_investment_for_sidebar.html.erb b/app/views/budgets/ballots/_investment_for_sidebar.html.erb new file mode 100644 index 000000000..c0bde5d34 --- /dev/null +++ b/app/views/budgets/ballots/_investment_for_sidebar.html.erb @@ -0,0 +1,15 @@ +
  • + <%= investment.title %> + <%= format_price(investment.price) %> + + <% if @budget.balloting? %> + <%= link_to ballot_line_path(id: investment.id, + investments_ids: investment_ids), + title: t('ballots.show.remove'), + class: "remove-investment-project", + method: :delete, + remote: true do %> + + <% end %> + <% end %> +
  • diff --git a/app/views/budgets/ballots/_remove.html.erb b/app/views/budgets/ballots/_remove.html.erb new file mode 100644 index 000000000..a02ac9966 --- /dev/null +++ b/app/views/budgets/ballots/_remove.html.erb @@ -0,0 +1,18 @@ +
    + "> + + +

    + <%= format_price(investment.price) %> +

    + + <% if @budget.balloting? %> + <%= link_to t('ballots.show.remove'), + ballot_line_path(id: investment.id, + investments_ids: investment_ids), + class: "delete small expanded", + method: :delete, + remote: true %> + <% end %> +
    diff --git a/app/views/budgets/ballots/show.html.erb b/app/views/budgets/ballots/show.html.erb new file mode 100644 index 000000000..393444368 --- /dev/null +++ b/app/views/budgets/ballots/show.html.erb @@ -0,0 +1,3 @@ +
    + <%= render partial: "ballots/ballot" %> +
    diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index d1f9ebd63..6d83adbbb 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -2,6 +2,9 @@
      <% @budgets.each do |budget| %> -
    • <%= link_to budget.name, budget %>
    • +
    • <%= link_to budget do %> + <%= budget.name %>, + <%= budget.phase %> + <% end %>
    • <% end %>
    diff --git a/app/views/budgets/investments/_ballot.html.erb b/app/views/budgets/investments/_ballot.html.erb new file mode 100644 index 000000000..6634ae1fa --- /dev/null +++ b/app/views/budgets/investments/_ballot.html.erb @@ -0,0 +1,23 @@ +<% reason = investment.reason_for_not_being_ballotable_by(current_user, @ballot) %> +
    + <% if @ballot.has_investment?(investment) %> + <%= render 'budgets/ballots/remove', investment: investment %> + <% else %> + <%= render 'budgets/ballots/add', investment: investment %> + <% end %> + + <% if reason.present? && !@ballot.has_investment?(investment) %> + + + + <% end %> +
    diff --git a/app/views/budgets/investments/_comments.html.erb b/app/views/budgets/investments/_comments.html.erb new file mode 100644 index 000000000..6970e64f4 --- /dev/null +++ b/app/views/budgets/investments/_comments.html.erb @@ -0,0 +1,31 @@ +<% cache [locale_and_user_status, @current_order, commentable_cache_key(@investment), @comment_tree.comments, @comment_tree.comment_authors, @investment.comments_count, @comment_flags] do %> +
    +
    +
    +

    + <%= t("debates.show.comments_title") %> + (<%= @investment.comments_count %>) +

    + + <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> + + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: @investment, parent_id: nil, toggeable: false} %> + <% else %> +
    + +
    + <%= t("debates.show.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
    + <% end %> + + <% @comment_tree.root_comments.each do |comment| %> + <%= render 'comments/comment', comment: comment %> + <% end %> + <%= paginate @comment_tree.root_comments %> +
    +
    +
    +<% end %> diff --git a/app/views/budgets/investments/_investment.html.erb b/app/views/budgets/investments/_investment.html.erb index 3432bed49..84f105c18 100644 --- a/app/views/budgets/investments/_investment.html.erb +++ b/app/views/budgets/investments/_investment.html.erb @@ -1,9 +1,9 @@ -
    +
    -
    +
    <% cache [locale_and_user_status(investment), 'index', investment, investment.author] do %> <%= t("budget.investments.investment.title") %> diff --git a/app/views/budgets/investments/_votes.html.erb b/app/views/budgets/investments/_votes.html.erb new file mode 100644 index 000000000..62ea74283 --- /dev/null +++ b/app/views/budgets/investments/_votes.html.erb @@ -0,0 +1,46 @@ +<% reason = investment.reason_for_not_being_selectable_by(current_user) %> +<% voting_allowed = true unless reason.presence == :not_voting_allowed %> +<% user_voted_for = voted_for?(@budget_investment_votes, investment) %> + +
    + + + <%= t("budget.investments.investment.supports", count: investment.total_votes) %> + + +
    + <% if user_voted_for %> +
    + <%= t("budget.investments.investment.already_supported") %> +
    + <% elsif voting_allowed %> + + <%= link_to vote_url, + class: "button button-support small expanded", + title: t('budget.investments.investment.support_title'), + method: "post", + remote: true, + "aria-hidden" => css_for_aria_hidden(reason) do %> + <%= t("budget.investments.investment.vote") %> + <% end %> + <% end %> +
    + + <% if reason.present? && !user_voted_for %> + + <% end %> + + <% if user_voted_for && setting['twitter_handle'] %> + + <% end %> +
    diff --git a/app/views/budgets/investments/show.html.erb b/app/views/budgets/investments/show.html.erb index 55cd12a6f..58e6027ea 100644 --- a/app/views/budgets/investments/show.html.erb +++ b/app/views/budgets/investments/show.html.erb @@ -1,4 +1,83 @@ -

    <%= @investment.title %>

    +<% provide :title do %><%= @investment.title %><% end %> -

    <%= @investment.author.username %> - <%= @investment.price %>

    +
    +
    +
    + <%= link_to :back, class: "back" do %> + + <%= t("shared.back") %> + <% end %> + +

    <%= @investment.title %>

    + +
    + <%= render '/shared/author_info', resource: @investment %> + +  •  + <%= l @investment.created_at.to_date %> +  •  + <%= heading_name(@investment.heading) %> +
    + +
    +

    + <%= t("budget.investments.show.code") %> + <%= @investment.id %> +

    + + <%= safe_html_with_links @investment.description.html_safe %> + + <% if @investment.external_url.present? %> + + <% end %> + + <% if @investment.unfeasible? && @investment.feasible_explanation.present? %> +

    <%= t('budget.investments.show.unfeasibility_explanation') %>

    +

    <%= @investment.feasible_explanation %>

    + <% end %> + + <% if @investment.feasible? && @investment.price_explanation.present? %> +

    <%= t('budget.investments.show.price_explanation') %>

    +

    <%= @investment.price_explanation %>

    + <% end %> +
    + + <% if (@budget.selecting? && !@investment.unfeasible?) || + (@budget.balloting? && @investment.feasible?) %> + + <% end %> + +
    +
    + +<% unless namespace == 'management' %> + <%= render "budgets/investments/comments" %> +<% end %> From 8cc2cd0eb19cd06900f33eb713524fc14594a964 Mon Sep 17 00:00:00 2001 From: kikito Date: Tue, 7 Jun 2016 11:13:08 +0200 Subject: [PATCH 052/384] Adds investment specs (WIP) --- spec/features/budget/investments_spec.rb | 400 +++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 spec/features/budget/investments_spec.rb diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb new file mode 100644 index 000000000..d847c6159 --- /dev/null +++ b/spec/features/budget/investments_spec.rb @@ -0,0 +1,400 @@ +require 'rails_helper' + +feature 'Budget Investments' do + + let(:author) { create(:user, :level_two, username: 'Isabel') } + let(:budget) { create(:budget) } + + scenario 'Index' do + investments = [create(:budget_investment, budget: budget), create(:budget_investment, budget: budget), create(:budget_investment, :feasible, budget: budget)] + unfeasible_investment = create(:budget_investment, :unfeasible, budget: budget) + + visit budget_investments_path(budget_id: budget.id) + + expect(page).to have_selector('#budget-investments .budget-investment', count: 3) + investments.each do |investment| + within('#budget-investments') do + expect(page).to have_content investment.title + expect(page).to have_css("a[href='#{budget_investment_path(budget_id: budget.id, id: investment.id)}']", text: investment.title) + expect(page).to_not have_content(unfeasible_investment.title) + end + end + end + + context("Search") do + scenario 'Search by text' do + investment1 = create(:budget_investment, budget: budget, title: "Get Schwifty") + investment2 = create(:budget_investment, budget: budget, title: "Schwifty Hello") + investment3 = create(:budget_investment, budget: budget, title: "Do not show me") + + visit budget_investments_path(budget_id: budget.id) + + within(".expanded #search_form") do + fill_in "search", with: "Schwifty" + click_button "Search" + end + + within("#budget-investments") do + expect(page).to have_css('.budget-investment', count: 2) + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + expect(page).to_not have_content(investment3.title) + end + end + end + + context("Filters") do + scenario 'by unfeasibility' do + investment1 = create(:budget_investment, :unfeasible, budget: budget, valuation_finished: true) + investment2 = create(:budget_investment, :feasible, budget: budget) + investment3 = create(:budget_investment, budget: budget) + investment4 = create(:budget_investment, :feasible, budget: budget) + + visit budget_investments_path(budget_id: budget.id, unfeasible: 1) + + within("#budget-investments") do + expect(page).to have_css('.budget-investment', count: 1) + + expect(page).to have_content(investment1.title) + expect(page).to_not have_content(investment2.title) + expect(page).to_not have_content(investment3.title) + expect(page).to_not have_content(investment4.title) + end + end + end + + context("Orders") do + + scenario "Default order is random" do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment) } + + visit budget_investments_path(budget_id: budget.id) + order = all(".budget-investment h3").collect {|i| i.text } + + visit budget_investments_path(budget_id: budget.id) + new_order = eq(all(".budget-investment h3").collect {|i| i.text }) + + expect(order).to_not eq(new_order) + end + + scenario "Random order after another order" do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment) } + + visit budget_investments_path(budget_id: budget.id) + click_link "highest rated" + click_link "random" + + order = all(".budget-investment h3").collect {|i| i.text } + + visit budget_investments_path(budget_id: budget.id) + new_order = eq(all(".budget-investment h3").collect {|i| i.text }) + + expect(order).to_not eq(new_order) + end + + scenario 'Random order maintained with pagination', :js do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment, budget: budget) } + + visit budget_investments_path(budget_id: budget.id) + + order = all(".budget-investment h3").collect {|i| i.text } + + click_link 'Next' + expect(page).to have_content "You're on page 2" + + click_link 'Previous' + expect(page).to have_content "You're on page 1" + + new_order = all(".budget-investment h3").collect {|i| i.text } + expect(order).to eq(new_order) + end + + scenario 'Proposals are ordered by confidence_score', :js do + create(:budget_investment, budget: budget, title: 'Best proposal').update_column(:confidence_score, 10) + create(:budget_investment, budget: budget, title: 'Worst proposal').update_column(:confidence_score, 2) + create(:budget_investment, budget: budget, title: 'Medium proposal').update_column(:confidence_score, 5) + + visit budget_investments_path(budget_id: budget.id) + click_link 'highest rated' + expect(page).to have_selector('a.active', text: 'highest rated') + + within '#budget-investments' do + expect('Best proposal').to appear_before('Medium proposal') + expect('Medium proposal').to appear_before('Worst proposal') + end + + expect(current_url).to include('order=confidence_score') + expect(current_url).to include('page=1') + end + + end + + xscenario 'Create with invisible_captcha honeypot field' do + login_as(author) + + visit new_budget_investment_path(budget_id: budget.id) + fill_in 'investment_title', with: 'I am a bot' + fill_in 'investment_subtitle', with: 'This is the honeypot' + fill_in 'investment_description', with: 'This is the description' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page.status_code).to eq(200) + expect(page.html).to be_empty + expect(current_path).to eq(budget_investments_path(budget_id: budget.id)) + end + + xscenario 'Create spending proposal too fast' do + allow(InvisibleCaptcha).to receive(:timestamp_threshold).and_return(Float::INFINITY) + + login_as(author) + + visit new_budget_investments_path(budget_id: budget.id) + fill_in 'investment_title', with: 'I am a bot' + fill_in 'investment_description', with: 'This is the description' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page).to have_content 'Sorry, that was too quick! Please resubmit' + expect(current_path).to eq(new_budget_investment_path(budget_id: budget.id)) + end + + xscenario 'Create notice' do + login_as(author) + + visit new_budget_investment_path(budget_id: budget.id) + fill_in 'investment_title', with: 'Build a skyscraper' + fill_in 'investment_description', with: 'I want to live in a high tower over the clouds' + fill_in 'investment_external_url', with: 'http://http://skyscraperpage.com/' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page).to_not have_content 'Investment project created successfully' + expect(page).to have_content '1 error' + + within "#notice" do + click_link 'My activity' + end + + expect(page).to have_content 'Investment project created successfully' + end + + xscenario 'Errors on create' do + login_as(author) + + visit new_budget_investment_path(budget_id: budget.id) + click_button 'Create' + expect(page).to have_content error_message + end + + scenario "Show" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, heading: create(:budget_heading), budget: budget) + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content(investment.title) + expect(page).to have_content(investment.description) + expect(page).to have_content(investment.author.name) + expect(page).to have_content(investment.heading.name) + within("#investment_code") do + expect(page).to have_content(investment.id) + end + end + + scenario "Show (feasible spending proposal)" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, + :feasible, + :finished, + price: 16, + price_explanation: 'Every wheel is 4 euros, so total is 16') + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content("Price explanation") + expect(page).to have_content(investment.price_explanation) + end + + scenario "Show (unfeasible spending proposal)" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, + :unfeasible, + :finished, + unfeasibility_explanation: 'Local government is not competent in this matter') + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content("Unfeasibility explanation") + expect(page).to have_content(investment.unfeasibility_explanation) + end + + context "Destroy" do + + scenario "Admin cannot destroy spending proposals" do + admin = create(:administrator) + user = create(:user, :level_two) + investment = create(:budget_investment, budget: budget, author: user) + + login_as(admin.user) + + visit user_path(user) + within("#investment_#{investment.id}") do + expect(page).to_not have_link "Delete" + end + end + + end + + context "Badge" do + + scenario "Spending proposal created by a User" do + user = create(:user) + user_investment = create(:budget_investment) + + visit budget_investment_path(budget_id: budget.id, id: user_investment.id) + expect(page).to_not have_css "is-forum" + + visit budget_investments_path(budget_id: budget.id, id: user_investment.id) + within "#investment_#{user_investment.id}" do + expect(page).to_not have_css "is-forum" + end + end + + end + + context "Phase 3 - Final Voting" do + + background do + Setting["feature.investment_features.phase3"] = true + end + + scenario "Index" do + user = create(:user, :level_two) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000) + sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 20000) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link "Vote city proposals" + + within("#investment_#{sp1.id}") do + expect(page).to have_content sp1.title + expect(page).to have_content "$10,000" + end + + within("#investment_#{sp2.id}") do + expect(page).to have_content sp2.title + expect(page).to have_content "$20,000" + end + end + + scenario 'Order by cost (only in phase3)' do + create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) + create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) + create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a skyscraper', price: 20000) + + visit budget_investments_path(budget_id: budget.id) + + click_link 'by price' + expect(page).to have_selector('a.active', text: 'by price') + + within '#budget-investments' do + expect('Build a skyscraper').to appear_before('Build a nice house') + expect('Build a nice house').to appear_before('Build an ugly house') + end + + expect(current_url).to include('order=price') + expect(current_url).to include('page=1') + end + + scenario "Show" do + user = create(:user, :level_two) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link "Vote city proposals" + + click_link sp1.title + + expect(page).to have_content "$10,000" + end + + scenario "Confirm", :js do + user = create(:user, :level_two) + carabanchel = create(:budget_heading, name: "Carabanchel") + new_york = create(:budget_heading) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1, heading: nil) + sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10, heading: nil) + sp3 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100, heading: nil) + sp4 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1000, heading: carabanchel) + sp5 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000, heading: carabanchel) + sp6 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100000, heading: new_york) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link "Vote city proposals" + + add_to_ballot(sp1) + add_to_ballot(sp2) + + first(:link, "Participatory budgeting").click + click_link "Vote district proposals" + click_link carabanchel.name + + add_to_ballot(sp4) + add_to_ballot(sp5) + + click_link "Check my ballot" + + expect(page).to have_content "You can change your vote at any time until the close of this phase" + + within("#city_wide") do + expect(page).to have_content sp1.title + expect(page).to have_content sp1.price + + expect(page).to have_content sp2.title + expect(page).to have_content sp2.price + + expect(page).to_not have_content sp3.title + expect(page).to_not have_content sp3.price + end + + within("#district_wide") do + expect(page).to have_content sp4.title + expect(page).to have_content "$1,000" + + expect(page).to have_content sp5.title + expect(page).to have_content "$10,000" + + expect(page).to_not have_content sp6.title + expect(page).to_not have_content "$100,000" + end + end + + end + +end From 0899a0ddb496f3152cfbacface1e351e90739615 Mon Sep 17 00:00:00 2001 From: Alberto Garcia Cabeza Date: Tue, 7 Jun 2016 19:22:15 +0200 Subject: [PATCH 053/384] Improves styles for budget index and show views --- app/assets/stylesheets/layout.scss | 4 ++ app/assets/stylesheets/participation.scss | 13 +++++ app/views/budgets/index.html.erb | 38 +++++++++++---- app/views/budgets/show.html.erb | 53 ++++++++++++++------ config/locales/en.yml | 8 +++ config/locales/es.yml | 8 +++ db/schema.rb | 59 ++++++++++++++++------- 7 files changed, 143 insertions(+), 40 deletions(-) diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index bbe74ac3d..c8f3624ea 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -218,6 +218,10 @@ a { float: left; } +.table-fixed { + table-layout: fixed; +} + // 02. Header // ---------- diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index df99b56ed..654d61388 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -5,6 +5,7 @@ // 03. Show participation // 04. List participation // 05. Featured +// 06. Budget // // 01. Votes and supports @@ -1028,3 +1029,15 @@ } } } + +// 06. Budget +// ---------- + +.expanded.budget { + background: $budget; + + h1, p, a { + color: white; + } +} + diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 6d83adbbb..6f34d03d1 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -1,10 +1,30 @@ -

    Budget Index

    +
    +
    +
    +

    <%= t('budget.index.title') %>

    +
    +
    +
    -
      -<% @budgets.each do |budget| %> -
    • <%= link_to budget do %> - <%= budget.name %>, - <%= budget.phase %> - <% end %>
    • -<% end %> -
    +
    +
    + + + + + + + <% @budgets.each do |budget| %> + + + + + <% end %> + +
    <%= t('budget.index.name') %><%= t('budget.index.phase') %>
    + <%= link_to budget.name, budget %> + + <%= budget.phase %> +
    +
    +
    diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 13574d802..5c4365018 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -1,15 +1,40 @@ -

    <%= @budget.name %>

    +
    +
    +
    + <%= render 'shared/back_link' %> +

    <%= @budget.name %>

    +

    <%= @budget.description %>

    +
    +
    +
    -
    <%= @budget.description %>
    - -
      -
    • <%= link_to budget_investments_path(budget_id: @budget.id, heading_id: nil) do %> - No heading (<%= format_price(@budget, @budget.price) %>) - <% end %>
    • - <% @budget.headings.each do |heading| %> -
    • <%= link_to budget_investments_path(budget_id: @budget.id, heading_id: heading.id) do %> - <%= heading.name %> (<%= format_price(@budget, heading.price) %>) - <% end %> -
    • - <% end %> -
    +
    +
    + + + + + + + + + + + <% @budget.headings.each do |heading| %> + + + + + <% end %> + +
    <%= t('budget.show.heading') %><%= t('budget.show.price') %>
    + <%= link_to t('budget.show.no_heading'), budget_investments_path(budget_id: @budget.id, heading_id: nil) %> + + <%= format_price(@budget, @budget.price) %> +
    + <%= link_to heading.name, budget_investments_path(budget_id: @budget.id, heading_id: heading.id) %> + + <%= format_price(@budget, heading.price) %> +
    +
    +
    diff --git a/config/locales/en.yml b/config/locales/en.yml index f91d4ac1a..44f580952 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -422,6 +422,10 @@ en: headings: none: Whole City all: All scopes + index: + name: Budget's name + phase: Phase + title: Participatory budgets investments: form: association_name_label: 'If you propose in name of an assocation or collective add the name here' @@ -497,6 +501,10 @@ en: header: check_ballot: Check my ballot different_heading_active: You have active votes in another district. + show: + heading: Heading + price: Price + no_heading: No Heading spending_proposals: form: association_name_label: 'If you propose in name of an assocation or collective add the name here' diff --git a/config/locales/es.yml b/config/locales/es.yml index 452a6d94a..4904f458c 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -422,6 +422,10 @@ es: headings: none: Toda la ciudad all: Todos los ámbitos + index: + name: Nombre del presupuesto + phase: Fase + title: Presupuestos participativos investments: form: association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' @@ -497,6 +501,10 @@ es: header: check_ballot: Revisar mis votos different_heading_active: Ya apoyaste propuestas de otro distrito. + show: + heading: Partida + price: Cantidad + no_heading: Sin línea spending_proposals: form: association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' diff --git a/db/schema.rb b/db/schema.rb index de26932c7..cac0bd2ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160531102008) do +ActiveRecord::Schema.define(version: 20160606102427) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -63,6 +63,21 @@ ActiveRecord::Schema.define(version: 20160531102008) do add_index "annotations", ["legislation_id"], name: "index_annotations_on_legislation_id", using: :btree add_index "annotations", ["user_id"], name: "index_annotations_on_user_id", using: :btree + create_table "banners", force: :cascade do |t| + t.string "title", limit: 80 + t.string "description" + t.string "target_url" + t.string "style" + t.string "image" + t.date "post_started_at" + t.date "post_ended_at" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "banners", ["hidden_at"], name: "index_banners_on_hidden_at", using: :btree + create_table "budget_ballot_lines", force: :cascade do |t| t.integer "ballot_id" t.integer "investment_id" @@ -326,6 +341,15 @@ ActiveRecord::Schema.define(version: 20160531102008) do add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree + create_table "proposal_notifications", force: :cascade do |t| + t.string "title" + t.text "body" + t.integer "author_id" + t.integer "proposal_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "proposals", force: :cascade do |t| t.string "title", limit: 80 t.text "description" @@ -460,30 +484,30 @@ ActiveRecord::Schema.define(version: 20160531102008) do add_index "tolk_translations", ["phrase_id", "locale_id"], name: "index_tolk_translations_on_phrase_id_and_locale_id", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "" - t.string "encrypted_password", default: "", null: false + t.string "email", default: "" + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - 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.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "email_on_comment", default: false - t.boolean "email_on_comment_reply", default: false - t.string "phone_number", limit: 30 + t.boolean "email_on_comment", default: false + t.boolean "email_on_comment_reply", default: false + t.string "phone_number", limit: 30 t.string "official_position" - t.integer "official_level", default: 0 + t.integer "official_level", default: 0 t.datetime "hidden_at" t.string "sms_confirmation_code" - t.string "username", limit: 60 + t.string "username", limit: 60 t.string "document_number" t.string "document_type" t.datetime "residence_verified_at" @@ -494,20 +518,21 @@ ActiveRecord::Schema.define(version: 20160531102008) do t.datetime "letter_requested_at" t.datetime "confirmed_hide_at" t.string "letter_verification_code" - t.integer "failed_census_calls_count", default: 0 + t.integer "failed_census_calls_count", default: 0 t.datetime "level_two_verified_at" t.string "erase_reason" t.datetime "erased_at" - t.boolean "public_activity", default: true - t.boolean "newsletter", default: true - t.integer "notifications_count", default: 0 - t.boolean "registering_with_oauth", default: false + t.boolean "public_activity", default: true + t.boolean "newsletter", default: true + t.integer "notifications_count", default: 0 + t.boolean "registering_with_oauth", default: false t.string "locale" t.string "oauth_email" t.integer "geozone_id" t.string "redeemable_code" - t.string "gender", limit: 10 + t.string "gender", limit: 10 t.datetime "date_of_birth" + t.boolean "email_on_proposal_notification", default: true end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree From aa1ed877258a36eb0b9e2b7c2438a8423ef7505e Mon Sep 17 00:00:00 2001 From: Alberto Garcia Cabeza Date: Wed, 8 Jun 2016 19:45:09 +0200 Subject: [PATCH 054/384] Adds styles for budgets --- app/assets/stylesheets/participation.scss | 218 ++++++++++++++++++ .../budgets/investments/_header.html.erb | 4 +- app/views/budgets/show.html.erb | 6 +- 3 files changed, 225 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 654d61388..0293bad31 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -1041,3 +1041,221 @@ } } +.jumbo-budget { + background: $budget; + border-bottom: 1px solid $budget; + + &.budget-heading { + min-height: $line-height*10; + } + + h1 { + margin-bottom: 0; + } + + h1, h2, .back, .icon-angle-left, p, a { + color: white; + } + + &.welcome { + background: $budget image-url('spending_proposals_bg.jpg'); + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: cover; + + .spending-proposal-timeline { + padding-top: $line-height; + + ul li { + margin-right: $line-height; + padding-top: $line-height/2; + + .icon-calendar { + display: none; + } + } + } + } +} + +.progress-votes { + position: relative; + + .progress { + background: #212033; + clear: both; + } + + .progress-meter { + background: #fdcb10; + border-radius: 0; + -webkit-transition: width 2s; + transition: width 2s; + } + + .spent-amount-progress, + .spent-amount-meter { + background: none !important; + } + + .spent-amount-text { + color: white; + font-size: $base-font-size; + font-weight: normal; + position: absolute; + right: 0; + text-align: right; + top: 16px; + width: 100%; + + &:before { + color: #a5a1ff; + content: "\57"; + font-family: 'icons'; + font-size: $small-font-size; + position: absolute; + right: -6px; + top: -17px; + } + } + + .total-amount { + color: white; + font-size: rem-calc(18); + font-weight: bold; + float: right; + } + + .amount-available { + display: block; + text-align: right; + + span { + font-size: rem-calc(24); + font-weight: bold; + } + } +} + +.big-number { + color: $budget; + font-size: rem-calc(60); + line-height: rem-calc(120); + + @include breakpoint(large) { + font-size: rem-calc(90); + line-height: rem-calc(240); + } +} + +.ballot { + + h2, h3 { + font-weight: normal; + + span { + color: $budget; + font-weight: bold; + } + } + + h3.subtitle { + border-bottom: 3px solid $budget; + + span { + font-size: $base-font-size; + font-weight: normal; + } + } + + .amount-spent { + background: $success-bg; + color: $success-color; + font-weight: normal; + padding: $line-height/2; + + span { + font-size: rem-calc(24); + font-weight: bold; + } + } +} + +ul.ballot-list { + list-style: none; + margin-left: 0; + + li { + background: #f9f9f9; + line-height: $line-height; + margin-bottom: $line-height/4; + padding: $line-height/2; + position: relative; + + a { + color: $text; + } + + span { + color: #9f9f9f; + display: block; + font-style: italic; + } + + .remove-investment-project { + display: block; + height: 0; + + .icon-x { + color: #9f9f9f; + font-size: rem-calc(24); + line-height: $line-height/2; + position: absolute; + right: 6px; + text-decoration: none; + top: 6px; + + @include breakpoint(medium) { + font-size: $base-font-size; + } + } + } + + &:hover { + background: $budget; + color: white; + + a, span { + color: white; + outline: 0; + text-decoration: none; + } + + .remove-investment-project .icon-x { + color: white; + } + } + } +} + +.select-district .active a { + background: #f9f9f9; + border-radius: rem-calc(3); + color: $budget; + font-weight: bold; + padding: $line-height/4; + + &:after { + content: "\56"; + font-family: "icons"; + font-size: $small-font-size; + font-weight: normal; + line-height: $line-height; + padding-left: rem-calc(3); + vertical-align: baseline; + + &:hover { + text-decoration: none; + } + } +} diff --git a/app/views/budgets/investments/_header.html.erb b/app/views/budgets/investments/_header.html.erb index ae5dbd318..d03214a1a 100644 --- a/app/views/budgets/investments/_header.html.erb +++ b/app/views/budgets/investments/_header.html.erb @@ -1,6 +1,6 @@ <% if @filter_heading_name.present? %>
    -
    +
    @@ -41,7 +41,7 @@
    <% else %> -
    +
    <%= link_to budget_path(@budget), class: "back" do %> diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 5c4365018..2b04271a5 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -1,7 +1,11 @@
    - <%= render 'shared/back_link' %> + <%= link_to budgets_path do %> + + <%= t('shared.back') %> + <% end %> +

    <%= @budget.name %>

    <%= @budget.description %>

    From 7548b0492deb1d602548264cb913e377ecc63e4c Mon Sep 17 00:00:00 2001 From: Alberto Garcia Cabeza Date: Thu, 9 Jun 2016 12:59:55 +0200 Subject: [PATCH 055/384] Adds more styles to budget pages --- app/assets/stylesheets/participation.scss | 27 +++++++++++++++++++ app/views/budgets/ballots/_ballot.html.erb | 5 +--- .../budgets/investments/_header.html.erb | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 0293bad31..c5a4bf3fd 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -1076,6 +1076,33 @@ } } } + + a { + text-decoration: underline; + + &.button { + background: white; + color: $brand; + margin-bottom: rem-calc(3); + text-decoration: none; + } + } + + .social-share-button a { + color: white; + + &.social-share-button-twitter:hover { + color: #40A2D1; + } + + &.social-share-button-facebook:hover { + color: #354F88; + } + + &.social-share-button-google_plus:hover { + color: #CE3E26; + } + } } .progress-votes { diff --git a/app/views/budgets/ballots/_ballot.html.erb b/app/views/budgets/ballots/_ballot.html.erb index 5187c5eaa..5189de92b 100644 --- a/app/views/budgets/ballots/_ballot.html.erb +++ b/app/views/budgets/ballots/_ballot.html.erb @@ -1,9 +1,6 @@
    - <%= link_to :back, class: "back" do %> - - <%= t("shared.back") %> - <% end %> + <%= render 'shared/back_link' %>

    <%= t("budgets.ballots.show.title") %>

    diff --git a/app/views/budgets/investments/_header.html.erb b/app/views/budgets/investments/_header.html.erb index d03214a1a..4aa0b35f4 100644 --- a/app/views/budgets/investments/_header.html.erb +++ b/app/views/budgets/investments/_header.html.erb @@ -10,7 +10,7 @@ <% end %> <% if can? :show, @ballot %> - <%= link_to t("budget.investments.header.check_ballot"), ballot_path, class: "button float-right" %> + <%= link_to t("budget.investments.header.check_ballot"), budget_ballot_path(@budget, @ballot), class: "button float-right" %> <% end %>
    From 3fae269c40a91f270e1435b538d8bfdfb9d89745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 9 Jun 2016 13:30:01 +0200 Subject: [PATCH 056/384] adds missing migration file (external_link) --- db/migrate/20160207205252_add_external_link_to_debates.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20160207205252_add_external_link_to_debates.rb diff --git a/db/migrate/20160207205252_add_external_link_to_debates.rb b/db/migrate/20160207205252_add_external_link_to_debates.rb new file mode 100644 index 000000000..eaa0608d2 --- /dev/null +++ b/db/migrate/20160207205252_add_external_link_to_debates.rb @@ -0,0 +1,5 @@ +class AddExternalLinkToDebates < ActiveRecord::Migration + def change + add_column :debates, :external_link, :string, limit: 100 + end +end \ No newline at end of file From b5a6828e415394d8f6cc45e10fa63fdcf6cde24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 9 Jun 2016 18:00:06 +0200 Subject: [PATCH 057/384] adds Budget::Group model to group headings many refactors through budget related models --- app/models/budget.rb | 9 +-- app/models/budget/ballot.rb | 2 +- app/models/budget/group.rb | 10 ++++ app/models/budget/heading.rb | 4 +- app/models/budget/investment.rb | 11 ++-- db/dev_seeds.rb | 21 ++++--- .../20160520111735_create_budget_heading.rb | 4 +- .../20160609110023_create_budget_group.rb | 10 ++++ ...20160609142017_remove_price_from_budget.rb | 5 ++ ...52026_remove_budget_id_from_investments.rb | 5 ++ db/schema.rb | 48 +++++++++------- spec/factories.rb | 10 +++- spec/models/abilities/administrator_spec.rb | 10 +++- spec/models/abilities/common_spec.rb | 13 ++++- spec/models/abilities/valuator_spec.rb | 10 +++- spec/models/budget/ballot_spec.rb | 29 ++++------ spec/models/budget/investment_spec.rb | 56 +++++++++---------- spec/models/budget_spec.rb | 9 +-- 18 files changed, 156 insertions(+), 110 deletions(-) create mode 100644 app/models/budget/group.rb create mode 100644 db/migrate/20160609110023_create_budget_group.rb create mode 100644 db/migrate/20160609142017_remove_price_from_budget.rb create mode 100644 db/migrate/20160609152026_remove_budget_id_from_investments.rb diff --git a/app/models/budget.rb b/app/models/budget.rb index b234a8b73..ea22a4615 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -4,9 +4,11 @@ class Budget < ActiveRecord::Base validates :phase, inclusion: { in: VALID_PHASES } - has_many :investments - has_many :ballots - has_many :headings + has_many :investments, dependent: :destroy + has_many :ballots, dependent: :destroy + has_many :groups, dependent: :destroy + has_many :headings, through: :groups + has_many :investments, through: :headings def on_hold? phase == "on_hold" @@ -29,7 +31,6 @@ class Budget < ActiveRecord::Base end def heading_price(heading) - return price unless heading.present? heading_ids.include?(heading.id) ? heading.price : -1 end end diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 9b9a089d6..560075f97 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -16,7 +16,7 @@ class Budget end def amount_available(heading) - budget.heading_price(heading) - amount_spent(heading.try(:id)) + budget.heading_price(heading) - amount_spent(heading.id) end end end diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb new file mode 100644 index 000000000..dd7910950 --- /dev/null +++ b/app/models/budget/group.rb @@ -0,0 +1,10 @@ +class Budget + class Group < ActiveRecord::Base + belongs_to :budget + + has_many :headings, dependent: :destroy + + validates :budget_id, presence: true + validates :name, presence: true + end +end \ No newline at end of file diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index e79c5b7c8..830596912 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -1,11 +1,11 @@ class Budget class Heading < ActiveRecord::Base - belongs_to :budget + belongs_to :group belongs_to :geozone has_many :investments - validates :budget_id, presence: true + validates :group_id, presence: true validates :name, presence: true validates :price, presence: true end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 329c2fce2..1e0d368f3 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -10,7 +10,6 @@ class Budget acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases - belongs_to :budget belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :heading belongs_to :administrator @@ -43,7 +42,6 @@ class Budget scope :with_supports, -> { where('cached_votes_up > 0') } scope :by_heading, -> (heading_id) { where(heading_id: heading_id) } - scope :by_budget, -> (budget_id) { where(budget_id: budget_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) } @@ -61,8 +59,7 @@ class Budget end def self.scoped_filter(params, current_filter) - budget = Budget.find!(params[:budget_id]) - results = self.by_budget(params[:budget_id]) + results = budget.investments if params[:max_for_no_heading].present? || params[:max_per_heading].present? results = limit_results(results, budget, params[:max_per_heading].to_i, params[:max_for_no_heading].to_i) end @@ -118,6 +115,10 @@ class Budget where(heading_id: heading == 'all' ? nil : heading.presence) end + def budget + heading.group.budget + end + def undecided? feasibility == "undecided" end @@ -139,7 +140,7 @@ class Budget end def code - "B#{budget_id}I#{id}" + "B#{budget.id}I#{id}" end def reason_for_not_being_selectable_by(user) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index ee63987c4..a223e1f16 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -300,18 +300,21 @@ puts "Creating Budgets" (1..10).each do |i| budget = Budget.create!(name: (Date.today.year - 10 + i).to_s, description: "

    #{Faker::Lorem.paragraphs.join('

    ')}

    ", - price: rand(1 .. 100) * 100000, phase: %w{on_hold accepting selecting balloting finished}.sample, valuating: [false, true].sample) puts budget.name - puts " " - geozones = Geozone.reorder("RANDOM()").limit(10) - geozones.each do |geozone| - heading = budget.headings.create!(name: geozone.name, - geozone: geozone, - price: rand(1 .. 100) * 10000) - budget.headings << heading - print "heading.name " + + (1..[1,2,3].sample).each do |i| + group = budget.groups.create!(name: Faker::StarWars.planet) + + geozones = Geozone.reorder("RANDOM()").limit([2,5,6,7].sample) + geozones.each do |geozone| + group.headings << group.headings.create!(name: geozone.name, + geozone: geozone, + price: rand(1 .. 100) * 10000) + + end + print "#{group.name} " end puts "" end diff --git a/db/migrate/20160520111735_create_budget_heading.rb b/db/migrate/20160520111735_create_budget_heading.rb index e9ca935c6..bd9291d2f 100644 --- a/db/migrate/20160520111735_create_budget_heading.rb +++ b/db/migrate/20160520111735_create_budget_heading.rb @@ -1,9 +1,9 @@ class CreateBudgetHeading < ActiveRecord::Migration def change create_table :budget_headings do |t| - t.references :budget + t.references :group, index: true t.references :geozone - t.string :name, limit: 50 + t.string :name, limit: 50 t.integer :price, limit: 8 end end diff --git a/db/migrate/20160609110023_create_budget_group.rb b/db/migrate/20160609110023_create_budget_group.rb new file mode 100644 index 000000000..fb6cbcd1c --- /dev/null +++ b/db/migrate/20160609110023_create_budget_group.rb @@ -0,0 +1,10 @@ +class CreateBudgetGroup < ActiveRecord::Migration + def change + create_table :budget_groups do |t| + t.references :budget + t.string :name, limit: 50 + end + + add_index :budget_groups, :budget_id + end +end diff --git a/db/migrate/20160609142017_remove_price_from_budget.rb b/db/migrate/20160609142017_remove_price_from_budget.rb new file mode 100644 index 000000000..366fdef5f --- /dev/null +++ b/db/migrate/20160609142017_remove_price_from_budget.rb @@ -0,0 +1,5 @@ +class RemovePriceFromBudget < ActiveRecord::Migration + def change + remove_column :budgets, :price, :integer + end +end diff --git a/db/migrate/20160609152026_remove_budget_id_from_investments.rb b/db/migrate/20160609152026_remove_budget_id_from_investments.rb new file mode 100644 index 000000000..69890c2e9 --- /dev/null +++ b/db/migrate/20160609152026_remove_budget_id_from_investments.rb @@ -0,0 +1,5 @@ +class RemoveBudgetIdFromInvestments < ActiveRecord::Migration + def change + remove_column :budget_investments, :budget_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 1f5cbe6a0..8cd96edfd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160531102008) do +ActiveRecord::Schema.define(version: 20160609152026) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -63,6 +63,21 @@ ActiveRecord::Schema.define(version: 20160531102008) do add_index "annotations", ["legislation_id"], name: "index_annotations_on_legislation_id", using: :btree add_index "annotations", ["user_id"], name: "index_annotations_on_user_id", using: :btree + create_table "banners", force: :cascade do |t| + t.string "title", limit: 80 + t.string "description" + t.string "target_url" + t.string "style" + t.string "image" + t.date "post_started_at" + t.date "post_ended_at" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "banners", ["hidden_at"], name: "index_banners_on_hidden_at", using: :btree + create_table "budget_ballot_lines", force: :cascade do |t| t.integer "ballot_id" t.integer "investment_id" @@ -83,13 +98,22 @@ ActiveRecord::Schema.define(version: 20160531102008) do add_index "budget_ballots", ["heading_id"], name: "index_budget_ballots_on_heading_id", using: :btree - create_table "budget_headings", force: :cascade do |t| + create_table "budget_groups", force: :cascade do |t| t.integer "budget_id" + t.string "name", limit: 50 + end + + add_index "budget_groups", ["budget_id"], name: "index_budget_groups_on_budget_id", using: :btree + + create_table "budget_headings", force: :cascade do |t| + t.integer "group_id" t.integer "geozone_id" t.string "name", limit: 50 t.integer "price", limit: 8 end + add_index "budget_headings", ["group_id"], name: "index_budget_headings_on_group_id", using: :btree + create_table "budget_investments", force: :cascade do |t| t.integer "author_id" t.integer "administrator_id" @@ -114,13 +138,11 @@ ActiveRecord::Schema.define(version: 20160531102008) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "heading_id" - t.integer "budget_id" t.string "responsible_name" end add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree add_index "budget_investments", ["author_id"], name: "index_budget_investments_on_author_id", using: :btree - add_index "budget_investments", ["budget_id"], name: "index_budget_investments_on_budget_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 @@ -141,24 +163,8 @@ ActiveRecord::Schema.define(version: 20160531102008) do t.boolean "valuating", default: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "price" end - create_table "banners", force: :cascade do |t| - t.string "title", limit: 80 - t.string "description" - t.string "target_url" - t.string "style" - t.string "image" - t.date "post_started_at" - t.date "post_ended_at" - t.datetime "hidden_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "banners", ["hidden_at"], name: "index_banners_on_hidden_at", using: :btree - create_table "campaigns", force: :cascade do |t| t.string "name" t.string "track_id" @@ -204,10 +210,10 @@ ActiveRecord::Schema.define(version: 20160531102008) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 - t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 diff --git a/spec/factories.rb b/spec/factories.rb index 37befbddc..d20cfa84f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -191,7 +191,6 @@ FactoryGirl.define do factory :budget do sequence(:name) { |n| "Budget #{n}" } currency_symbol "€" - price 10000 phase 'on_hold' trait :selecting do @@ -207,15 +206,20 @@ FactoryGirl.define do end end - factory :budget_heading, class: 'Budget::Heading' do + factory :budget_group, class: 'Budget::Group' do budget + sequence(:name) { |n| "Group #{n}" } + end + + factory :budget_heading, class: 'Budget::Heading' do + association :group, factory: :budget_group sequence(:name) { |n| "Heading #{n}" } price 1000000 end factory :budget_investment, class: 'Budget::Investment' do sequence(:title) { |n| "Budget Investment #{n} title" } - association :budget + association :heading, factory: :budget_heading association :author, factory: :user description 'Spend money on this' unfeasibility_explanation '' diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 8170d3434..d2313dea5 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -65,7 +65,13 @@ describe "Abilities::Administrator" do it { should be_able_to(:update, Budget::Investment) } it { should be_able_to(:hide, Budget::Investment) } - it { should be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: true))) } - it { should_not be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: false))) } + it { should be_able_to(:valuate, create(:budget_investment, + heading: create(:budget_heading, + group: create(:budget_group, + budget: create(:budget, valuating: true))))) } + it { should_not be_able_to(:valuate, create(:budget_investment, + heading: create(:budget_heading, + group: create(:budget_group, + budget: create(:budget, valuating: false))))) } end diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index 6fd751c9f..d2276aa84 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -12,9 +12,16 @@ describe "Abilities::Common" do let(:accepting_budget) { create(:budget, phase: 'accepting') } let(:selecting_budget) { create(:budget, phase: 'selecting') } let(:balloting_budget) { create(:budget, phase: 'balloting') } - let(:investment_in_accepting_budget) { create(:budget_investment, budget: accepting_budget) } - let(:investment_in_selecting_budget) { create(:budget_investment, budget: selecting_budget) } - let(:investment_in_balloting_budget) { create(:budget_investment, budget: balloting_budget) } + let(:accepting_budget_group) { create(:budget_group, budget: accepting_budget) } + let(:selecting_budget_group) { create(:budget_group, budget: selecting_budget) } + let(:balloting_budget_group) { create(:budget_group, budget: balloting_budget) } + let(:accepting_budget_heading) { create(:budget_heading, group: accepting_budget_group) } + let(:selecting_budget_heading) { create(:budget_heading, group: selecting_budget_group) } + let(:balloting_budget_heading) { create(:budget_heading, group: balloting_budget_group) } + + let(:investment_in_accepting_budget) { create(:budget_investment, heading: accepting_budget_heading) } + let(:investment_in_selecting_budget) { create(:budget_investment, heading: selecting_budget_heading) } + let(:investment_in_balloting_budget) { create(:budget_investment, heading: balloting_budget_heading) } let(:ballot_in_accepting_budget) { create(:budget_ballot, budget: accepting_budget) } let(:ballot_in_selecting_budget) { create(:budget_ballot, budget: selecting_budget) } let(:ballot_in_balloting_budget) { create(:budget_ballot, budget: balloting_budget) } diff --git a/spec/models/abilities/valuator_spec.rb b/spec/models/abilities/valuator_spec.rb index 9fa285f19..add938a9d 100644 --- a/spec/models/abilities/valuator_spec.rb +++ b/spec/models/abilities/valuator_spec.rb @@ -7,10 +7,16 @@ describe "Abilities::Valuator" do let(:valuator) { create(:valuator) } let(:non_assigned_investment) { create(:budget_investment) } - let(:assigned_investment) { create(:budget_investment, budget: create(:budget, valuating: true)) } + let(:assigned_investment) { create(:budget_investment, + heading: create(:budget_heading, + group: create(:budget_group, + budget: create(:budget, valuating: true)))) } before(:each) { assigned_investment.valuators << valuator } - let(:assigned_investment_not_valuating) { create(:budget_investment, budget: create(:budget, valuating: false)) } + let(:assigned_investment_not_valuating) { create(:budget_investment, + heading: create(:budget_heading, + group: create(:budget_group, + budget: create(:budget, valuating: false)))) } before(:each) { assigned_investment_not_valuating.valuators << valuator } it { should be_able_to(:read, SpendingProposal) } diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb index ee6be74f1..a0997a1c3 100644 --- a/spec/models/budget/ballot_spec.rb +++ b/spec/models/budget/ballot_spec.rb @@ -21,18 +21,15 @@ describe Budget::Ballot do heading = create(:budget_heading) inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading)) - inv3 = create(:budget_investment, :feasible, price: 25000) - inv4 = create(:budget_investment, :feasible, price: 40000, heading: heading) + inv3 = create(:budget_investment, :feasible, price: 40000, heading: heading) ballot = create(:budget_ballot) ballot.investments << inv1 ballot.investments << inv2 - ballot.investments << inv3 expect(ballot.amount_spent(heading.id)).to eq 10000 - expect(ballot.amount_spent(nil)).to eq 25000 - ballot.investments << inv4 + ballot.investments << inv3 expect(ballot.amount_spent(heading.id)).to eq 50000 end @@ -40,28 +37,22 @@ describe Budget::Ballot do describe "#amount_available" do it "returns how much is left after taking some investments" do - budget = create(:budget, price: 200000) - heading = create(:budget_heading, budget: budget) - inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) - inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading)) - inv3 = create(:budget_investment, :feasible, price: 25000) - inv4 = create(:budget_investment, :feasible, price: 40000, heading: heading) - - inv1 = create(:budget_investment, :feasible, price: 10000) - inv2 = create(:budget_investment, :feasible, price: 20000) + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 1000) + inv1 = create(:budget_investment, :feasible, price: 100, heading: heading) + inv2 = create(:budget_investment, :feasible, price: 200, heading: create(:budget_heading)) + inv3 = create(:budget_investment, :feasible, price: 400, heading: heading) ballot = create(:budget_ballot, budget: budget) ballot.investments << inv1 ballot.investments << inv2 - expect(ballot.amount_available(heading)).to eq 1000000 - expect(ballot.amount_available(nil)).to eq 170000 + expect(ballot.amount_available(heading)).to eq 900 ballot.investments << inv3 - ballot.investments << inv4 - expect(ballot.amount_available(heading)).to eq 960000 - expect(ballot.amount_available(nil)).to eq 145000 + expect(ballot.amount_available(heading)).to eq 500 end end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index eb4cab3ac..ac79fad78 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -190,38 +190,33 @@ describe Budget::Investment do describe 'Permissions' do let(:budget) { create(:budget) } - let(:heading) { create(:budget_heading, budget: budget) } + let(:group) { create(:budget_group, budget: budget) } + let(:heading) { create(:budget_heading, group: group) } let(:user) { create(:user, :level_two) } let(:luser) { create(:user) } - let(:city_sp) { create(:budget_investment, budget: budget) } - let(:district_sp) { create(:budget_investment, budget: budget, heading: heading) } + let(:district_sp) { create(:budget_investment, heading: heading) } describe '#reason_for_not_being_selectable_by' do it "rejects not logged in users" do - expect(city_sp.reason_for_not_being_selectable_by(nil)).to eq(:not_logged_in) expect(district_sp.reason_for_not_being_selectable_by(nil)).to eq(:not_logged_in) end it "rejects not verified users" do - expect(city_sp.reason_for_not_being_selectable_by(luser)).to eq(:not_verified) expect(district_sp.reason_for_not_being_selectable_by(luser)).to eq(:not_verified) end it "rejects organizations" do create(:organization, user: user) - expect(city_sp.reason_for_not_being_selectable_by(user)).to eq(:organization) expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:organization) end it "rejects selections when selecting is not allowed (via admin setting)" do budget.phase = "on_hold" - expect(city_sp.reason_for_not_being_selectable_by(user)).to eq(:no_selecting_allowed) expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:no_selecting_allowed) end it "accepts valid selections when selecting is allowed" do budget.phase = "selecting" - expect(city_sp.reason_for_not_being_selectable_by(user)).to be_nil expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil end end @@ -274,7 +269,9 @@ describe Budget::Investment do describe "total votes" do it "takes into account physical votes in addition to web votes" do b = create(:budget, :selecting) - sp = create(:budget_investment, budget: b) + g = create(:budget_group, budget: b) + h = create(:budget_heading, group: g) + sp = create(:budget_investment, heading: h) sp.register_selection(create(:user, :level_two)) expect(sp.total_votes).to eq(1) @@ -299,56 +296,52 @@ describe Budget::Investment do describe 'Permissions' do let(:budget) { create(:budget) } - let(:heading) { create(:budget_heading, budget: budget) } + let(:group) { create(:budget_group, budget: budget) } + let(:heading) { create(:budget_heading, group: group) } let(:user) { create(:user, :level_two) } let(:luser) { create(:user) } let(:ballot) { create(:budget_ballot, budget: budget) } - let(:city_sp) { create(:budget_investment, budget: budget) } - let(:district_sp) { create(:budget_investment, budget: budget, heading: heading) } + let(:investment) { create(:budget_investment, heading: heading) } describe '#reason_for_not_being_ballotable_by' do it "rejects not logged in users" do - expect(city_sp.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in) - expect(district_sp.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in) + expect(investment.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in) end it "rejects not verified users" do - expect(city_sp.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified) - expect(district_sp.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified) + expect(investment.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified) end it "rejects organizations" do create(:organization, user: user) - expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization) - expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization) + expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization) end it "rejects votes when voting is not allowed (via admin setting)" do budget.phase = "on_hold" - expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) - expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) + expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed) end it "accepts valid ballots when voting is allowed" do budget.phase = "balloting" - expect(city_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil - expect(district_sp.reason_for_not_being_ballotable_by(user, ballot)).to be_nil + expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to be_nil end it "accepts valid district selections" do budget.phase = "selecting" - expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil + expect(investment.reason_for_not_being_selectable_by(user)).to be_nil ballot.heading_id = heading.id - expect(district_sp.reason_for_not_being_selectable_by(user)).to be_nil + expect(investment.reason_for_not_being_selectable_by(user)).to be_nil end it "rejects users with different headings" do budget.phase = "balloting" - california = create(:budget_heading, budget: budget) - new_york = create(:budget_heading, budget: budget) + group = create(:budget_group, budget: budget) + california = create(:budget_heading, group: group) + new_york = create(:budget_heading, group: group) - sp1 = create(:budget_investment, :feasible, heading: california, budget: budget) - sp2 = create(:budget_investment, :feasible, heading: new_york, budget: budget) + sp1 = create(:budget_investment, :feasible, heading: california) + sp2 = create(:budget_investment, :feasible, heading: new_york) b = create(:budget_ballot, user: user, heading: california, investments: [sp1]) expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:different_heading_assigned) @@ -356,9 +349,10 @@ describe Budget::Investment do it "rejects proposals with price higher than current available money" do budget.phase = "balloting" - carabanchel = create(:budget_heading, budget: budget, price: 35) - sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30, budget: budget) - sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10, budget: budget) + distritos = create(:budget_group, budget: budget) + carabanchel = create(:budget_heading, group: distritos, price: 35) + sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30) + sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10) ballot = create(:budget_ballot, user: user, heading: carabanchel, investments: [sp1]) expect(sp2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money) diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index 398bb1cff..b238717ef 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -33,14 +33,11 @@ describe Budget do end describe "heading_price" do - let(:budget) { create(:budget, price: 1000) } - - it "returns the budget price if no heading is provided" do - expect(budget.heading_price(nil)).to eq(1000) - end + let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } it "returns the heading price if the heading provided is part of the budget" do - heading = create(:budget_heading, price: 100, budget: budget) + heading = create(:budget_heading, price: 100, group: group) expect(budget.heading_price(heading)).to eq(100) end From ab008ed4e9ffad4b7309bfa01468def7623d70cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 11 Jun 2016 14:57:07 +0200 Subject: [PATCH 058/384] adds denormalization to budget::ballot::lines --- app/models/budget/ballot/line.rb | 5 +++ ...20160610094658_desnormalize_ballot_line.rb | 7 ++++ db/schema.rb | 5 ++- spec/factories.rb | 8 +++- spec/models/budget/ballot/line_spec.rb | 40 +++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20160610094658_desnormalize_ballot_line.rb create mode 100644 spec/models/budget/ballot/line_spec.rb diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 804ee3a2c..54b5e221f 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -2,7 +2,12 @@ class Budget class Ballot class Line < ActiveRecord::Base belongs_to :ballot + belongs_to :budget + belongs_to :group + belongs_to :heading belongs_to :investment + + validates :ballot_id, :budget_id, :group_id, :heading_id, :investment_id, presence: true end end end diff --git a/db/migrate/20160610094658_desnormalize_ballot_line.rb b/db/migrate/20160610094658_desnormalize_ballot_line.rb new file mode 100644 index 000000000..b4eee7dc2 --- /dev/null +++ b/db/migrate/20160610094658_desnormalize_ballot_line.rb @@ -0,0 +1,7 @@ +class DesnormalizeBallotLine < ActiveRecord::Migration + def change + add_column :budget_ballot_lines, :budget_id, :integer, index: true + add_column :budget_ballot_lines, :group_id, :integer, index: true + add_column :budget_ballot_lines, :heading_id, :integer, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 8cd96edfd..5eb4435e7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160609152026) do +ActiveRecord::Schema.define(version: 20160610094658) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -83,6 +83,9 @@ ActiveRecord::Schema.define(version: 20160609152026) do t.integer "investment_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "budget_id" + t.integer "group_id" + t.integer "heading_id" end add_index "budget_ballot_lines", ["ballot_id"], name: "index_budget_ballot_lines_on_ballot_id", using: :btree diff --git a/spec/factories.rb b/spec/factories.rb index d20cfa84f..fe06ffe5f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -222,6 +222,7 @@ FactoryGirl.define do association :heading, factory: :budget_heading association :author, factory: :user description 'Spend money on this' + price 10 unfeasibility_explanation '' external_url 'http://external_documention.org' terms_of_service '1' @@ -249,8 +250,11 @@ FactoryGirl.define do end factory :budget_ballot_line, class: 'Budget::Ballot::Line' do - association :ballot, factory: :budget_ballot - investment { FactoryGirl.build(:budget_investment, :feasible) } + budget + ballot { create :budget_ballot, budget: budget } + group { create :budget_group, budget: budget } + heading { create :budget_heading, group: group } + investment { create :budget_investment, :feasible, heading: heading } end factory :vote do diff --git a/spec/models/budget/ballot/line_spec.rb b/spec/models/budget/ballot/line_spec.rb new file mode 100644 index 000000000..f18ec3dc9 --- /dev/null +++ b/spec/models/budget/ballot/line_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +describe "Budget::Ballot::Line" do + + let(:ballot_line) { build(:budget_ballot_line) } + + describe 'Validations' do + + it "should be valid" do + expect(ballot_line).to be_valid + end + + it "should be invalid if missing id from ballot|budget|group|heading|investment" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, :feasible, price: 5000000, heading: heading) + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + expect(ballot_line).to be_valid + + ballot_line = build(:budget_ballot_line, ballot: nil, budget: budget, group: group, heading: heading, investment: investment) + expect(ballot_line).to_not be_valid + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: nil, group: group, heading: heading, investment: investment) + expect(ballot_line).to_not be_valid + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: nil, heading: heading, investment: investment) + expect(ballot_line).to_not be_valid + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: nil, investment: investment) + expect(ballot_line).to_not be_valid + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: nil) + expect(ballot_line).to_not be_valid + end + + end +end \ No newline at end of file From 56a358a638780ee9e38d6959889e273aaf134ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 11 Jun 2016 15:01:51 +0200 Subject: [PATCH 059/384] adds funds and feasibility validations to lines --- app/models/budget/ballot/line.rb | 13 ++++++ spec/models/budget/ballot/line_spec.rb | 64 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 54b5e221f..b3901babc 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -8,6 +8,19 @@ class Budget belongs_to :investment validates :ballot_id, :budget_id, :group_id, :heading_id, :investment_id, presence: true + validate :insufficient_funds + validate :unfeasible + + def insufficient_funds + return unless errors.blank? + errors.add(:money, "") if ballot.amount_available(heading) < investment.price.to_i + end + + def unfeasible + return unless errors.blank? + errors.add(:unfeasible, "") unless investment.feasible? + end + end end end diff --git a/spec/models/budget/ballot/line_spec.rb b/spec/models/budget/ballot/line_spec.rb index f18ec3dc9..99a02755c 100644 --- a/spec/models/budget/ballot/line_spec.rb +++ b/spec/models/budget/ballot/line_spec.rb @@ -36,5 +36,69 @@ describe "Budget::Ballot::Line" do expect(ballot_line).to_not be_valid end + describe 'Money' do + it "should not be valid if insufficient funds" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, :feasible, price: heading.price + 1, heading: heading) + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + + expect(ballot_line).to_not be_valid + end + + it "should be valid if sufficient funds" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, :feasible, price: heading.price - 1, heading: heading, ) + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + + expect(ballot_line).to be_valid + end + end + + describe 'Feasibility' do + it "should not be valid if investment is unfeasible" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, :feasible, price: 20000, feasibility: "unfeasible") + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + + expect(ballot_line).to_not be_valid + end + + it "should not be valid if investment feasibility is undecided" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, price: 20000, feasibility: "undecided") + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + + expect(ballot_line).to_not be_valid + end + + it "should be valid if investment is feasible" do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group, price: 10000000) + investment = create(:budget_investment, price: 20000, feasibility: "feasible") + ballot = create(:budget_ballot, budget: budget) + + ballot_line = build(:budget_ballot_line, ballot: ballot, budget: budget, group: group, heading: heading, investment: investment) + + expect(ballot_line).to be_valid + end + end + end end \ No newline at end of file From 4fcf138045525ac47370697d6bdf371cc1bf3f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Tue, 14 Jun 2016 14:03:21 +0200 Subject: [PATCH 060/384] refactors investment adding to ballots --- app/models/budget/ballot.rb | 14 ++++++++ app/models/budget/investment.rb | 3 +- spec/models/budget/ballot_spec.rb | 32 ++++++++++------- spec/models/budget/investment_spec.rb | 51 ++++++++++++++------------- 4 files changed, 61 insertions(+), 39 deletions(-) diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 560075f97..550efd345 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -7,6 +7,10 @@ class Budget has_many :lines, dependent: :destroy has_many :investments, through: :lines + def add_investment(investment) + lines.create!(budget: budget, investment: investment, heading: investment.heading, group_id: investment.heading.group_id) + end + def total_amount_spent investments.sum(:price).to_i end @@ -18,5 +22,15 @@ class Budget def amount_available(heading) budget.heading_price(heading) - amount_spent(heading.id) end + + def valid_heading?(heading) + group = heading.group + return false if group.budget_id != budget_id + + line = lines.where(heading_id: group.heading_ids).first + return false if line.present? && line.heading_id != heading.id + + true + end end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 1e0d368f3..6d969f226 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -21,6 +21,7 @@ class Budget validates :title, presence: true validates :author, presence: true validates :description, presence: true + validates :heading_id, presence: true validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required? validates :title, length: { in: 4 .. Budget::Investment.title_max_length } @@ -152,7 +153,7 @@ class Budget def reason_for_not_being_ballotable_by(user, ballot) return permission_problem(user) if permission_problem?(user) return :no_ballots_allowed unless budget.balloting? - return :different_heading_assigned unless heading_id.blank? || ballot.blank? || heading_id == ballot.heading_id || ballot.heading_id.nil? + return :different_heading_assigned unless ballot.valid_heading?(heading) return :not_enough_money if ballot.present? && !enough_money?(ballot) end diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb index a0997a1c3..d49a3d8fb 100644 --- a/spec/models/budget/ballot_spec.rb +++ b/spec/models/budget/ballot_spec.rb @@ -4,32 +4,38 @@ describe Budget::Ballot do describe "#amount_spent" do it "returns the total amount spent in investments" do - inv1 = create(:budget_investment, :feasible, price: 10000) - inv2 = create(:budget_investment, :feasible, price: 20000) + budget = create(:budget) + group1 = create(:budget_group, budget: budget) + group2 = create(:budget_group, budget: budget) + heading1 = create(:budget_heading, group: group1, price: 100000) + heading2 = create(:budget_heading, group: group2, price: 200000) + inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading1) + inv2 = create(:budget_investment, :feasible, price: 20000, heading: heading2) - ballot = create(:budget_ballot) - ballot.investments << inv1 + ballot = create(:budget_ballot, budget: budget) + ballot.add_investment inv1 expect(ballot.total_amount_spent).to eq 10000 - ballot.investments << inv2 + ballot.add_investment inv2 expect(ballot.total_amount_spent).to eq 30000 end it "returns the amount spent on all investments assigned to a specific heading" do heading = create(:budget_heading) + budget = heading.group.budget inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) - inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading)) + inv2 = create(:budget_investment, :feasible, price: 20000, heading: create(:budget_heading, group: heading.group)) inv3 = create(:budget_investment, :feasible, price: 40000, heading: heading) - ballot = create(:budget_ballot) - ballot.investments << inv1 - ballot.investments << inv2 + ballot = create(:budget_ballot, budget: budget) + ballot.add_investment inv1 + ballot.add_investment inv2 expect(ballot.amount_spent(heading.id)).to eq 10000 - ballot.investments << inv3 + ballot.add_investment inv3 expect(ballot.amount_spent(heading.id)).to eq 50000 end @@ -45,12 +51,12 @@ describe Budget::Ballot do inv3 = create(:budget_investment, :feasible, price: 400, heading: heading) ballot = create(:budget_ballot, budget: budget) - ballot.investments << inv1 - ballot.investments << inv2 + ballot.add_investment inv1 + ballot.add_investment inv2 expect(ballot.amount_available(heading)).to eq 900 - ballot.investments << inv3 + ballot.add_investment inv3 expect(ballot.amount_available(heading)).to eq 500 end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index ac79fad78..d3dbf0d0b 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -66,7 +66,7 @@ describe Budget::Investment do end describe "by_admin" do - it "should return spending investments assigned to specific administrator" do + it "should return investments assigned to specific administrator" do investment1 = create(:budget_investment, administrator_id: 33) create(:budget_investment) @@ -78,7 +78,7 @@ describe Budget::Investment do end describe "by_valuator" do - it "should return spending proposals assigned to specific valuator" do + it "should return investments assigned to specific valuator" do investment1 = create(:budget_investment) investment2 = create(:budget_investment) investment3 = create(:budget_investment) @@ -99,7 +99,7 @@ describe Budget::Investment do describe "scopes" do describe "valuation_open" do - it "should return all spending proposals with false valuation_finished" do + it "should return all investments with false valuation_finished" do investment1 = create(:budget_investment, valuation_finished: true) investment2 = create(:budget_investment) @@ -111,7 +111,7 @@ describe Budget::Investment do end describe "without_admin" do - it "should return all open spending proposals without assigned admin" do + it "should return all open investments without assigned admin" do investment1 = create(:budget_investment, valuation_finished: true) investment2 = create(:budget_investment, administrator: create(:administrator)) investment3 = create(:budget_investment) @@ -124,7 +124,7 @@ describe Budget::Investment do end describe "managed" do - it "should return all open spending proposals with assigned admin but without assigned valuators" do + it "should return all open investments with assigned admin but without assigned valuators" do investment1 = create(:budget_investment, administrator: create(:administrator)) investment2 = create(:budget_investment, administrator: create(:administrator), valuation_finished: true) investment3 = create(:budget_investment, administrator: create(:administrator)) @@ -138,7 +138,7 @@ describe Budget::Investment do end describe "valuating" do - it "should return all spending proposals with assigned valuator but valuation not finished" do + it "should return all investments with assigned valuator but valuation not finished" do investment1 = create(:budget_investment) investment2 = create(:budget_investment) investment3 = create(:budget_investment, valuation_finished: true) @@ -154,7 +154,7 @@ describe Budget::Investment do end describe "valuation_finished" do - it "should return all spending proposals with valuation finished" do + it "should return all investments with valuation finished" do investment1 = create(:budget_investment) investment2 = create(:budget_investment) investment3 = create(:budget_investment, valuation_finished: true) @@ -170,7 +170,7 @@ describe Budget::Investment do end describe "feasible" do - it "should return all feasible spending proposals" do + it "should return all feasible investments" do feasible_investment = create(:budget_investment, :feasible) create(:budget_investment) @@ -179,7 +179,7 @@ describe Budget::Investment do end describe "unfeasible" do - it "should return all unfeasible spending proposals" do + it "should return all unfeasible investments" do unfeasible_investment = create(:budget_investment, :unfeasible) create(:budget_investment, :feasible) @@ -283,12 +283,12 @@ describe Budget::Investment do describe "#with_supports" do it "should return proposals with supports" do - sp1 = create(:budget_investment) - sp2 = create(:budget_investment) - create(:vote, votable: sp1) + inv1 = create(:budget_investment) + inv2 = create(:budget_investment) + create(:vote, votable: inv1) - expect(Budget::Investment.with_supports).to include(sp1) - expect(Budget::Investment.with_supports).to_not include(sp2) + expect(Budget::Investment.with_supports).to include(inv1) + expect(Budget::Investment.with_supports).to_not include(inv2) end end @@ -327,11 +327,9 @@ describe Budget::Investment do expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to be_nil end - it "accepts valid district selections" do + it "accepts valid selections" do budget.phase = "selecting" expect(investment.reason_for_not_being_selectable_by(user)).to be_nil - ballot.heading_id = heading.id - expect(investment.reason_for_not_being_selectable_by(user)).to be_nil end it "rejects users with different headings" do @@ -340,22 +338,25 @@ describe Budget::Investment do california = create(:budget_heading, group: group) new_york = create(:budget_heading, group: group) - sp1 = create(:budget_investment, :feasible, heading: california) - sp2 = create(:budget_investment, :feasible, heading: new_york) - b = create(:budget_ballot, user: user, heading: california, investments: [sp1]) + inv1 = create(:budget_investment, :feasible, heading: california) + inv2 = create(:budget_investment, :feasible, heading: new_york) + b = create(:budget_ballot, user: user, budget: budget) + b.add_investment inv1 - expect(sp2.reason_for_not_being_ballotable_by(user, b)).to eq(:different_heading_assigned) + expect(inv2.reason_for_not_being_ballotable_by(user, b)).to eq(:different_heading_assigned) end it "rejects proposals with price higher than current available money" do budget.phase = "balloting" distritos = create(:budget_group, budget: budget) carabanchel = create(:budget_heading, group: distritos, price: 35) - sp1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30) - sp2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10) - ballot = create(:budget_ballot, user: user, heading: carabanchel, investments: [sp1]) + inv1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30) + inv2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10) - expect(sp2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money) + ballot = create(:budget_ballot, user: user, budget: budget) + ballot.add_investment inv1 + + expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money) end end From 1b57e14a86356d9a00b960a489ea82b73914f53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Tue, 14 Jun 2016 14:04:27 +0200 Subject: [PATCH 061/384] removes heading reference in ballot heading info is stored per group in ballot_lines --- app/models/budget/ballot.rb | 1 - app/models/budget/investment.rb | 3 --- db/migrate/20160614091639_remove_heading_id_from_ballot.rb | 5 +++++ db/schema.rb | 5 +---- 4 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20160614091639_remove_heading_id_from_ballot.rb diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb index 550efd345..57ea170ef 100644 --- a/app/models/budget/ballot.rb +++ b/app/models/budget/ballot.rb @@ -2,7 +2,6 @@ class Budget class Ballot < ActiveRecord::Base belongs_to :user belongs_to :budget - belongs_to :heading has_many :lines, dependent: :destroy has_many :investments, through: :lines diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 6d969f226..392db0783 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -49,9 +49,6 @@ class Budget scope :for_render, -> { includes(heading: :geozone) } - scope :with_heading, -> { where.not(heading_id: nil) } - scope :no_heading, -> { where(heading_id: nil) } - before_save :calculate_confidence_score before_validation :set_responsible_name diff --git a/db/migrate/20160614091639_remove_heading_id_from_ballot.rb b/db/migrate/20160614091639_remove_heading_id_from_ballot.rb new file mode 100644 index 000000000..f46b93424 --- /dev/null +++ b/db/migrate/20160614091639_remove_heading_id_from_ballot.rb @@ -0,0 +1,5 @@ +class RemoveHeadingIdFromBallot < ActiveRecord::Migration + def change + remove_column :budget_ballots, :heading_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 5eb4435e7..ed92d2b01 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160610094658) do +ActiveRecord::Schema.define(version: 20160614091639) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -96,11 +96,8 @@ ActiveRecord::Schema.define(version: 20160610094658) do t.integer "budget_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "heading_id" end - add_index "budget_ballots", ["heading_id"], name: "index_budget_ballots_on_heading_id", using: :btree - create_table "budget_groups", force: :cascade do |t| t.integer "budget_id" t.string "name", limit: 50 From 1a2e9b37a115a4a12f1a59a0a31147bd1a1448df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Tue, 14 Jun 2016 14:12:50 +0200 Subject: [PATCH 062/384] improves ballot spec --- spec/models/budget/ballot_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb index d49a3d8fb..524f8d3b0 100644 --- a/spec/models/budget/ballot_spec.rb +++ b/spec/models/budget/ballot_spec.rb @@ -45,20 +45,22 @@ describe Budget::Ballot do it "returns how much is left after taking some investments" do budget = create(:budget) group = create(:budget_group, budget: budget) - heading = create(:budget_heading, group: group, price: 1000) - inv1 = create(:budget_investment, :feasible, price: 100, heading: heading) - inv2 = create(:budget_investment, :feasible, price: 200, heading: create(:budget_heading)) - inv3 = create(:budget_investment, :feasible, price: 400, heading: heading) + heading1 = create(:budget_heading, group: group, price: 1000) + heading2 = create(:budget_heading, group: group, price: 300) + inv1 = create(:budget_investment, :feasible, price: 100, heading: heading1) + inv2 = create(:budget_investment, :feasible, price: 200, heading: heading2) + inv3 = create(:budget_investment, :feasible, price: 400, heading: heading1) ballot = create(:budget_ballot, budget: budget) ballot.add_investment inv1 ballot.add_investment inv2 - expect(ballot.amount_available(heading)).to eq 900 + expect(ballot.amount_available(heading1)).to eq 900 + expect(ballot.amount_available(heading2)).to eq 100 ballot.add_investment inv3 - expect(ballot.amount_available(heading)).to eq 500 + expect(ballot.amount_available(heading1)).to eq 500 end end From 1e35ca4895ce8d3c8f89267921ff49765019f53e Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 19 Jul 2016 13:16:30 +0200 Subject: [PATCH 063/384] adds budget structure for views and controllers --- app/controllers/budget/investments_controller.rb | 9 +++++++++ app/views/budget/investments/index.html.erb | 1 + config/routes.rb | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 app/controllers/budget/investments_controller.rb create mode 100644 app/views/budget/investments/index.html.erb diff --git a/app/controllers/budget/investments_controller.rb b/app/controllers/budget/investments_controller.rb new file mode 100644 index 000000000..b1e1ff16e --- /dev/null +++ b/app/controllers/budget/investments_controller.rb @@ -0,0 +1,9 @@ +class Budget + class InvestmentsController < ApplicationController + skip_authorization_check + + def index + end + + end +end \ No newline at end of file diff --git a/app/views/budget/investments/index.html.erb b/app/views/budget/investments/index.html.erb new file mode 100644 index 000000000..7b6743e31 --- /dev/null +++ b/app/views/budget/investments/index.html.erb @@ -0,0 +1 @@ +hello budgets! diff --git a/config/routes.rb b/config/routes.rb index 7d0a3a996..7b0d825c4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,6 +75,10 @@ Rails.application.routes.draw do end end + namespace :budget do + resources :investments, only: [:index] + end + resources :stats, only: [:index] resources :legislations, only: [:show] From 33a44fc470c42a019607aaedac1e722b77af8667 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 19 Jul 2016 13:49:00 +0200 Subject: [PATCH 064/384] uses a module to structure controller code --- app/controllers/budgets/budgets_controller.rb | 9 +++++++++ .../{budget => budgets}/investments_controller.rb | 2 +- app/views/budgets/budgets/index.html.erb | 1 + app/views/{budget => budgets}/investments/index.html.erb | 0 config/routes.rb | 6 ++++-- 5 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 app/controllers/budgets/budgets_controller.rb rename app/controllers/{budget => budgets}/investments_controller.rb (86%) create mode 100644 app/views/budgets/budgets/index.html.erb rename app/views/{budget => budgets}/investments/index.html.erb (100%) diff --git a/app/controllers/budgets/budgets_controller.rb b/app/controllers/budgets/budgets_controller.rb new file mode 100644 index 000000000..c42bf999d --- /dev/null +++ b/app/controllers/budgets/budgets_controller.rb @@ -0,0 +1,9 @@ +module Budgets + class BudgetsController < ApplicationController + skip_authorization_check + + def index + end + + end +end \ No newline at end of file diff --git a/app/controllers/budget/investments_controller.rb b/app/controllers/budgets/investments_controller.rb similarity index 86% rename from app/controllers/budget/investments_controller.rb rename to app/controllers/budgets/investments_controller.rb index b1e1ff16e..f86b1ea2b 100644 --- a/app/controllers/budget/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -1,4 +1,4 @@ -class Budget +module Budgets class InvestmentsController < ApplicationController skip_authorization_check diff --git a/app/views/budgets/budgets/index.html.erb b/app/views/budgets/budgets/index.html.erb new file mode 100644 index 000000000..971274630 --- /dev/null +++ b/app/views/budgets/budgets/index.html.erb @@ -0,0 +1 @@ +budgets index \ No newline at end of file diff --git a/app/views/budget/investments/index.html.erb b/app/views/budgets/investments/index.html.erb similarity index 100% rename from app/views/budget/investments/index.html.erb rename to app/views/budgets/investments/index.html.erb diff --git a/config/routes.rb b/config/routes.rb index 7b0d825c4..fdc2593ba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,8 +75,10 @@ Rails.application.routes.draw do end end - namespace :budget do - resources :investments, only: [:index] + scope module: :budgets do + resources :budgets do + resources :investments, only: [:index] + end end resources :stats, only: [:index] From e0e5068c8710f5268383d4150d3e90d5cad6d600 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 19 Jul 2016 14:41:17 +0200 Subject: [PATCH 065/384] updates seeds - investment headings are now required --- db/dev_seeds.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index a223e1f16..6e3c3ea08 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -323,12 +323,11 @@ end puts "Creating Investments" tags = Faker::Lorem.words(10) (1..60).each do |i| - heading = [Budget::Heading.reorder("RANDOM()").first, nil].sample + heading = Budget::Heading.reorder("RANDOM()").first investment = Budget::Investment.create!( author: User.reorder("RANDOM()").first, heading: heading, - budget: heading.try(:budget) || Budget.reorder("RANDOM()").first, title: Faker::Lorem.sentence(3).truncate(60), external_url: Faker::Internet.url, description: "

    #{Faker::Lorem.paragraphs.join('

    ')}

    ", From 85d9dd4b84b792a834c88216ec9757e84a891795 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 19 Jul 2016 14:41:55 +0200 Subject: [PATCH 066/384] integrates cancan with budgets --- app/controllers/budgets/budgets_controller.rb | 2 +- app/models/abilities/everyone.rb | 1 + spec/models/abilities/everyone_spec.rb | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/budgets/budgets_controller.rb b/app/controllers/budgets/budgets_controller.rb index c42bf999d..e673bf8ba 100644 --- a/app/controllers/budgets/budgets_controller.rb +++ b/app/controllers/budgets/budgets_controller.rb @@ -1,6 +1,6 @@ module Budgets class BudgetsController < ApplicationController - skip_authorization_check + load_and_authorize_resource def index end diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 21e142c05..b2d60e97b 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -10,6 +10,7 @@ module Abilities can :read, Legislation can :read, User can [:search, :read], Annotation + can [:read], Budget end end end diff --git a/spec/models/abilities/everyone_spec.rb b/spec/models/abilities/everyone_spec.rb index 3f1e57278..d49c535d5 100644 --- a/spec/models/abilities/everyone_spec.rb +++ b/spec/models/abilities/everyone_spec.rb @@ -26,4 +26,6 @@ describe "Abilities::Everyone" do it { should be_able_to(:index, SpendingProposal) } it { should_not be_able_to(:create, SpendingProposal) } + + it { should be_able_to(:index, Budget) } end From 4ebd9bf0e9270717416356ce193bae9a36763024 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 19 Jul 2016 14:42:07 +0200 Subject: [PATCH 067/384] renders budget's index view --- app/views/budgets/budgets/_budget.html.erb | 4 ++++ app/views/budgets/budgets/index.html.erb | 2 +- spec/features/budgets/budgets_spec.rb | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/views/budgets/budgets/_budget.html.erb create mode 100644 spec/features/budgets/budgets_spec.rb diff --git a/app/views/budgets/budgets/_budget.html.erb b/app/views/budgets/budgets/_budget.html.erb new file mode 100644 index 000000000..8962ecc2a --- /dev/null +++ b/app/views/budgets/budgets/_budget.html.erb @@ -0,0 +1,4 @@ +
    +
    <%= budget.name %>
    +
    + diff --git a/app/views/budgets/budgets/index.html.erb b/app/views/budgets/budgets/index.html.erb index 971274630..a7f13b347 100644 --- a/app/views/budgets/budgets/index.html.erb +++ b/app/views/budgets/budgets/index.html.erb @@ -1 +1 @@ -budgets index \ No newline at end of file +<%= render @budgets %> \ No newline at end of file diff --git a/spec/features/budgets/budgets_spec.rb b/spec/features/budgets/budgets_spec.rb new file mode 100644 index 000000000..f45b3fb10 --- /dev/null +++ b/spec/features/budgets/budgets_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +feature 'Budgets' do + + scenario "Index" do + budget1 = create(:budget) + budget2 = create(:budget) + budget3 = create(:budget) + + visit budgets_path + + expect(page).to have_css ".budget", count: 3 + expect(page).to have_content budget1.name + expect(page).to have_content budget2.name + expect(page).to have_content budget3.name + end + +end From 1587222cbac0e2ea780eaa7df1317b8f4d60fc84 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 20 Jul 2016 14:06:27 +0200 Subject: [PATCH 068/384] tmp commit --- .../{budgets => budget}/budgets_controller.rb | 3 +- .../budget/investments_controller.rb | 85 +++++++++++++++++++ .../budgets/investments_controller.rb | 9 -- app/models/abilities/everyone.rb | 1 + .../budgets/_budget.html.erb | 3 +- .../budgets/index.html.erb | 0 .../budget/investments/_investment.html.erb | 3 + app/views/budget/investments/index.html.erb | 2 + app/views/budgets/investments/index.html.erb | 1 - config/routes.rb | 2 +- 10 files changed, 95 insertions(+), 14 deletions(-) rename app/controllers/{budgets => budget}/budgets_controller.rb (71%) create mode 100644 app/controllers/budget/investments_controller.rb delete mode 100644 app/controllers/budgets/investments_controller.rb rename app/views/{budgets => budget}/budgets/_budget.html.erb (90%) rename app/views/{budgets => budget}/budgets/index.html.erb (100%) create mode 100644 app/views/budget/investments/_investment.html.erb create mode 100644 app/views/budget/investments/index.html.erb delete mode 100644 app/views/budgets/investments/index.html.erb diff --git a/app/controllers/budgets/budgets_controller.rb b/app/controllers/budget/budgets_controller.rb similarity index 71% rename from app/controllers/budgets/budgets_controller.rb rename to app/controllers/budget/budgets_controller.rb index e673bf8ba..c084f4475 100644 --- a/app/controllers/budgets/budgets_controller.rb +++ b/app/controllers/budget/budgets_controller.rb @@ -1,8 +1,9 @@ -module Budgets +class Budget class BudgetsController < ApplicationController load_and_authorize_resource def index + @budgets = Budget.all end end diff --git a/app/controllers/budget/investments_controller.rb b/app/controllers/budget/investments_controller.rb new file mode 100644 index 000000000..d08038174 --- /dev/null +++ b/app/controllers/budget/investments_controller.rb @@ -0,0 +1,85 @@ +class Budget + class InvestmentsController < ApplicationController + include FeatureFlags + + before_action :load_investments, only: [:index] + before_action :load_geozone, only: [:index, :unfeasible] + + skip_authorization_check + + before_action :authenticate_user!, except: [:index, :show] + before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } + + load_and_authorize_resource + + feature_flag :spending_proposals + + invisible_captcha only: [:create, :update], honeypot: :subtitle + + respond_to :html, :js + + def index + load_investments + set_spending_proposal_votes(@investments) + end + + def new + @spending_proposal = SpendingProposal.new + end + + def show + set_spending_proposal_votes(@spending_proposal) + end + + def create + @spending_proposal = SpendingProposal.new(spending_proposal_params) + @spending_proposal.author = current_user + + if @spending_proposal.save + notice = t('flash.actions.create.spending_proposal', activity: "#{t('layouts.header.my_activity_link')}") + redirect_to @spending_proposal, notice: notice, flash: { html_safe: true } + else + render :new + end + end + + def destroy + spending_proposal = SpendingProposal.find(params[:id]) + spending_proposal.destroy + redirect_to user_path(current_user, filter: 'spending_proposals'), notice: t('flash.actions.destroy.spending_proposal') + end + + def vote + @spending_proposal.register_vote(current_user, 'yes') + set_spending_proposal_votes(@spending_proposal) + end + + private + + def spending_proposal_params + params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :association_name, :terms_of_service) + end + + def load_investments + @investments = filter_and_search(Budget::Investment) + end + + def filter_and_search(target) + target = target.unfeasible if params[:unfeasible].present? + target = target.by_geozone(params[:geozone]) if params[:geozone].present? + target = target.search(params[:search]) if params[:search].present? + target.page(params[:page]).for_render + end + + def load_geozone + return if params[:geozone].blank? + + if params[:geozone] == 'all' + @geozone_name = t('geozones.none') + else + @geozone_name = Geozone.find(params[:geozone]).name + end + end + + end +end \ No newline at end of file diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb deleted file mode 100644 index f86b1ea2b..000000000 --- a/app/controllers/budgets/investments_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Budgets - class InvestmentsController < ApplicationController - skip_authorization_check - - def index - end - - end -end \ No newline at end of file diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 0246afeb2..6109a9dbe 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -11,6 +11,7 @@ module Abilities can :read, User can [:search, :read], Annotation can [:read], Budget + can [:read], Budget::Investment can :new, DirectMessage end end diff --git a/app/views/budgets/budgets/_budget.html.erb b/app/views/budget/budgets/_budget.html.erb similarity index 90% rename from app/views/budgets/budgets/_budget.html.erb rename to app/views/budget/budgets/_budget.html.erb index 8962ecc2a..a88d9d181 100644 --- a/app/views/budgets/budgets/_budget.html.erb +++ b/app/views/budget/budgets/_budget.html.erb @@ -1,4 +1,3 @@
    <%= budget.name %>
    -
    - +
    \ No newline at end of file diff --git a/app/views/budgets/budgets/index.html.erb b/app/views/budget/budgets/index.html.erb similarity index 100% rename from app/views/budgets/budgets/index.html.erb rename to app/views/budget/budgets/index.html.erb diff --git a/app/views/budget/investments/_investment.html.erb b/app/views/budget/investments/_investment.html.erb new file mode 100644 index 000000000..40d534067 --- /dev/null +++ b/app/views/budget/investments/_investment.html.erb @@ -0,0 +1,3 @@ +
    +
    <%= investment.title %>
    +
    diff --git a/app/views/budget/investments/index.html.erb b/app/views/budget/investments/index.html.erb new file mode 100644 index 000000000..95f9db31d --- /dev/null +++ b/app/views/budget/investments/index.html.erb @@ -0,0 +1,2 @@ +hello budgets! +<%= render partial: 'investment', collection: @investments %> \ No newline at end of file diff --git a/app/views/budgets/investments/index.html.erb b/app/views/budgets/investments/index.html.erb deleted file mode 100644 index 7b6743e31..000000000 --- a/app/views/budgets/investments/index.html.erb +++ /dev/null @@ -1 +0,0 @@ -hello budgets! diff --git a/config/routes.rb b/config/routes.rb index 22518f8c8..8564928a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,7 +75,7 @@ Rails.application.routes.draw do end end - scope module: :budgets do + scope module: :budget do resources :budgets do resources :investments, only: [:index] end From 68ed041ecbeb6f828f51844de7db8a1f958e9e4d Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 20 Jul 2016 14:25:29 +0200 Subject: [PATCH 069/384] updates schema --- db/schema.rb | 63 +++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index cac0bd2ff..e2403fb3a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160606102427) do +ActiveRecord::Schema.define(version: 20160531102008) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -63,21 +63,6 @@ ActiveRecord::Schema.define(version: 20160606102427) do add_index "annotations", ["legislation_id"], name: "index_annotations_on_legislation_id", using: :btree add_index "annotations", ["user_id"], name: "index_annotations_on_user_id", using: :btree - create_table "banners", force: :cascade do |t| - t.string "title", limit: 80 - t.string "description" - t.string "target_url" - t.string "style" - t.string "image" - t.date "post_started_at" - t.date "post_ended_at" - t.datetime "hidden_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "banners", ["hidden_at"], name: "index_banners_on_hidden_at", using: :btree - create_table "budget_ballot_lines", force: :cascade do |t| t.integer "ballot_id" t.integer "investment_id" @@ -204,10 +189,10 @@ ActiveRecord::Schema.define(version: 20160606102427) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 - t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 @@ -226,7 +211,6 @@ ActiveRecord::Schema.define(version: 20160606102427) do add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree - add_index "debates", ["description"], name: "index_debates_on_description", using: :btree add_index "debates", ["geozone_id"], name: "index_debates_on_geozone_id", using: :btree add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree @@ -341,15 +325,6 @@ ActiveRecord::Schema.define(version: 20160606102427) do add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree - create_table "proposal_notifications", force: :cascade do |t| - t.string "title" - t.text "body" - t.integer "author_id" - t.integer "proposal_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "proposals", force: :cascade do |t| t.string "title", limit: 80 t.text "description" @@ -381,7 +356,6 @@ ActiveRecord::Schema.define(version: 20160606102427) do add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree - add_index "proposals", ["description"], name: "index_proposals_on_description", using: :btree add_index "proposals", ["geozone_id"], name: "index_proposals_on_geozone_id", using: :btree add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree @@ -484,30 +458,30 @@ ActiveRecord::Schema.define(version: 20160606102427) do add_index "tolk_translations", ["phrase_id", "locale_id"], name: "index_tolk_translations_on_phrase_id_and_locale_id", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "" - t.string "encrypted_password", default: "", null: false + t.string "email", default: "" + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - 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.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "email_on_comment", default: false - t.boolean "email_on_comment_reply", default: false - t.string "phone_number", limit: 30 + t.boolean "email_on_comment", default: false + t.boolean "email_on_comment_reply", default: false + t.string "phone_number", limit: 30 t.string "official_position" - t.integer "official_level", default: 0 + t.integer "official_level", default: 0 t.datetime "hidden_at" t.string "sms_confirmation_code" - t.string "username", limit: 60 + t.string "username", limit: 60 t.string "document_number" t.string "document_type" t.datetime "residence_verified_at" @@ -518,21 +492,20 @@ ActiveRecord::Schema.define(version: 20160606102427) do t.datetime "letter_requested_at" t.datetime "confirmed_hide_at" t.string "letter_verification_code" - t.integer "failed_census_calls_count", default: 0 + t.integer "failed_census_calls_count", default: 0 t.datetime "level_two_verified_at" t.string "erase_reason" t.datetime "erased_at" - t.boolean "public_activity", default: true - t.boolean "newsletter", default: true - t.integer "notifications_count", default: 0 - t.boolean "registering_with_oauth", default: false + t.boolean "public_activity", default: true + t.boolean "newsletter", default: true + t.integer "notifications_count", default: 0 + t.boolean "registering_with_oauth", default: false t.string "locale" t.string "oauth_email" t.integer "geozone_id" t.string "redeemable_code" - t.string "gender", limit: 10 + t.string "gender", limit: 10 t.datetime "date_of_birth" - t.boolean "email_on_proposal_notification", default: true end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree From f7810d44dd321eb4a3abe5484f12c53926de8fbd Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 11:33:18 +0200 Subject: [PATCH 070/384] updates translations --- app/views/budgets/ballots/_add.html.erb | 4 ++-- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/budgets/ballots/_add.html.erb b/app/views/budgets/ballots/_add.html.erb index c6b1e7476..27c22ae2f 100644 --- a/app/views/budgets/ballots/_add.html.erb +++ b/app/views/budgets/ballots/_add.html.erb @@ -7,10 +7,10 @@ <%= link_to budget_ballot_lines_url(investment_id: investment.id, investments_ids: @ballot.investment_ids), class: "button button-support small expanded", - title: t('investments.investment.support_title'), + title: t('budget.investments.investment.support_title'), method: "post", remote: true do %> - <%= t("investments.investment.add") %> + <%= t("budget.investments.investment.add") %> <% end %> <% end %>
    diff --git a/config/locales/en.yml b/config/locales/en.yml index 44f580952..30c82aae1 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -205,7 +205,7 @@ en: open_gov: Open government proposals: Proposals see_all: See proposals - spending_proposals: Spending proposals + budgets: Participatory budgeting legislation: help: alt: Select the text you want to comment and press the button with the pencil. diff --git a/config/locales/es.yml b/config/locales/es.yml index 4904f458c..a0e5f7fa5 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -205,7 +205,7 @@ es: open_gov: Gobierno %{open} proposals: Propuestas see_all: Ver propuestas - spending_proposals: Presupuestos ciudadanos + budgets: Presupuestos ciudadanos legislation: help: alt: Selecciona el texto que quieres comentar y pulsa en el botón con el lápiz. From 403f048f3f7cb16774d864719401041480640c8d Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 11:33:41 +0200 Subject: [PATCH 071/384] updates calls to new method names --- app/views/budgets/investments/show.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/budgets/investments/show.html.erb b/app/views/budgets/investments/show.html.erb index 58e6027ea..5a751e615 100644 --- a/app/views/budgets/investments/show.html.erb +++ b/app/views/budgets/investments/show.html.erb @@ -33,9 +33,9 @@
    <% end %> - <% if @investment.unfeasible? && @investment.feasible_explanation.present? %> + <% if @investment.unfeasible? && @investment.unfeasibility_explanation.present? %>

    <%= t('budget.investments.show.unfeasibility_explanation') %>

    -

    <%= @investment.feasible_explanation %>

    +

    <%= @investment.unfeasibility_explanation %>

    <% end %> <% if @investment.feasible? && @investment.price_explanation.present? %> From 7172e5a23ddb892c633f92ad3552c6de6312347d Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 11:33:49 +0200 Subject: [PATCH 072/384] updates link to paths --- app/views/shared/_subnavigation.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_subnavigation.html.erb b/app/views/shared/_subnavigation.html.erb index cd025eaf2..0fb7fe37c 100644 --- a/app/views/shared/_subnavigation.html.erb +++ b/app/views/shared/_subnavigation.html.erb @@ -10,7 +10,7 @@ <% if feature?(:spending_proposals) %>
  • - <%= link_to t("layouts.header.spending_proposals"), spending_proposals_path, class: ("active" if controller_name == "spending_proposals"), accesskey: "s" %> + <%= link_to t("layouts.header.budgets"), budgets_path, class: ("active" if controller_name == "budgets"), accesskey: "s" %>
  • <% end %>
  • From 7fee1e43ff4232ad6ec99f33b5446c77657550b5 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 11:33:59 +0200 Subject: [PATCH 073/384] fixes specs --- spec/features/budget/investments_spec.rb | 55 ++++++++++++++---------- spec/support/common_actions.rb | 5 +++ 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb index d847c6159..e102787be 100644 --- a/spec/features/budget/investments_spec.rb +++ b/spec/features/budget/investments_spec.rb @@ -221,6 +221,7 @@ feature 'Budget Investments' do investment = create(:budget_investment, :feasible, :finished, + budget: budget, price: 16, price_explanation: 'Every wheel is 4 euros, so total is 16') @@ -237,6 +238,7 @@ feature 'Budget Investments' do investment = create(:budget_investment, :unfeasible, :finished, + budget: budget, unfeasibility_explanation: 'Local government is not competent in this matter') visit budget_investment_path(budget_id: budget.id, id: investment.id) @@ -247,14 +249,14 @@ feature 'Budget Investments' do context "Destroy" do - scenario "Admin cannot destroy spending proposals" do + xscenario "Admin cannot destroy spending proposals" do admin = create(:administrator) user = create(:user, :level_two) investment = create(:budget_investment, budget: budget, author: user) login_as(admin.user) - visit user_path(user) + within("#investment_#{investment.id}") do expect(page).to_not have_link "Delete" end @@ -266,13 +268,13 @@ feature 'Budget Investments' do scenario "Spending proposal created by a User" do user = create(:user) - user_investment = create(:budget_investment) + user_investment = create(:budget_investment, budget: budget) visit budget_investment_path(budget_id: budget.id, id: user_investment.id) expect(page).to_not have_css "is-forum" visit budget_investments_path(budget_id: budget.id, id: user_investment.id) - within "#investment_#{user_investment.id}" do + within "#budget_investment_#{user_investment.id}" do expect(page).to_not have_css "is-forum" end end @@ -282,10 +284,10 @@ feature 'Budget Investments' do context "Phase 3 - Final Voting" do background do - Setting["feature.investment_features.phase3"] = true + budget.update(phase: "balloting") end - scenario "Index" do + xscenario "Index" do user = create(:user, :level_two) sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000) sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 20000) @@ -294,20 +296,21 @@ feature 'Budget Investments' do visit root_path first(:link, "Participatory budgeting").click - click_link "Vote city proposals" + click_link budget.name + click_link "No Heading" - within("#investment_#{sp1.id}") do + within("#budget_investment_#{sp1.id}") do expect(page).to have_content sp1.title - expect(page).to have_content "$10,000" + expect(page).to have_content "€10,000" end - within("#investment_#{sp2.id}") do + within("#budget_investment_#{sp2.id}") do expect(page).to have_content sp2.title - expect(page).to have_content "$20,000" + expect(page).to have_content "€20,000" end end - scenario 'Order by cost (only in phase3)' do + xscenario 'Order by cost (only in phase3)' do create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a skyscraper', price: 20000) @@ -334,35 +337,43 @@ feature 'Budget Investments' do visit root_path first(:link, "Participatory budgeting").click - click_link "Vote city proposals" + click_link budget.name + click_link "No Heading" click_link sp1.title - expect(page).to have_content "$10,000" + expect(page).to have_content "€10,000" end - scenario "Confirm", :js do + xscenario "Confirm", :js do user = create(:user, :level_two) - carabanchel = create(:budget_heading, name: "Carabanchel") - new_york = create(:budget_heading) + + carabanchel = create(:geozone, name: "Carabanchel") + new_york = create(:geozone, name: "New York") + + carabanchel_heading = create(:budget_heading, budget: budget, geozone: carabanchel, name: carabanchel.name) + new_york_heading = create(:budget_heading, budget: budget, geozone: new_york, name: new_york.name) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1, heading: nil) sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10, heading: nil) sp3 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100, heading: nil) - sp4 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1000, heading: carabanchel) - sp5 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000, heading: carabanchel) - sp6 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100000, heading: new_york) + sp4 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1000, heading: carabanchel_heading) + sp5 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000, heading: carabanchel_heading) + sp6 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100000, heading: new_york_heading) login_as(user) visit root_path first(:link, "Participatory budgeting").click - click_link "Vote city proposals" + click_link budget.name + click_link "No Heading" add_to_ballot(sp1) add_to_ballot(sp2) first(:link, "Participatory budgeting").click - click_link "Vote district proposals" + + click_link budget.name click_link carabanchel.name add_to_ballot(sp4) diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 18a3f9499..ce0eb68ad 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -196,4 +196,9 @@ module CommonActions tag_cloud.tags.map(&:name) end + def add_to_ballot(budget_investment) + within("#budget_investment_#{budget_investment.id}") do + click_link "Spend money on this"#find('.add a').trigger('click') + end + end end From 35f7f58fa8956abffb1dc251053f51437c23902a Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:03:49 +0200 Subject: [PATCH 074/384] removes obsolete attribute reference --- db/dev_seeds.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 77c6cce52..7e8a6a54b 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -301,7 +301,6 @@ puts "Creating Budgets" (1..10).each do |i| budget = Budget.create!(name: (Date.today.year - 10 + i).to_s, description: "

    #{Faker::Lorem.paragraphs.join('

    ')}

    ", - price: rand(1 .. 100) * 100000, currency_symbol: "€", phase: %w{on_hold accepting selecting balloting finished}.sample, valuating: [false, true].sample) From 6eab5d79a1aa45c29624f7ad366360cf202ba0b3 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:04:18 +0200 Subject: [PATCH 075/384] comments out budget for city proposals --- app/views/budgets/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 2b04271a5..521b6fb06 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -25,7 +25,7 @@ <%= link_to t('budget.show.no_heading'), budget_investments_path(budget_id: @budget.id, heading_id: nil) %> - <%= format_price(@budget, @budget.price) %> + <%# format_price(@budget, @budget.price) %> <% @budget.headings.each do |heading| %> From 82db8bb6735ce4b0b5f7f7a56ef19dde73db9cb7 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:04:31 +0200 Subject: [PATCH 076/384] adds tmp factory hack --- spec/factories.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/factories.rb b/spec/factories.rb index e38f47c6e..33c0ecda9 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -207,7 +207,9 @@ FactoryGirl.define do end factory :budget_group, class: 'Budget::Group' do - budget + ### TMP HACK to associate existing budget to a group + budget { Budget.first } + ### sequence(:name) { |n| "Group #{n}" } end From 464673a7aa85101d5bc906b9a1e805fc3e1dfdf9 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:04:52 +0200 Subject: [PATCH 077/384] creates setter association for budget investments --- app/models/budget/investment.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index bbdc0e73e..0eb144430 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -18,6 +18,8 @@ class Budget has_many :valuators, through: :valuator_assignments has_many :comments, as: :commentable + delegate :budget, to: :heading + validates :title, presence: true validates :author, presence: true validates :description, presence: true @@ -118,6 +120,10 @@ class Budget heading.group.budget end + def budget=(resource) + heading.group.budget = resource + end + def undecided? feasibility == "undecided" end From 7d34621c1c89661fc45d01f43bb1fc21ed0b7968 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:05:00 +0200 Subject: [PATCH 078/384] removes duplicate code --- app/controllers/budget/budgets_controller.rb | 10 --- .../budget/investments_controller.rb | 85 ------------------- config/routes.rb | 6 -- 3 files changed, 101 deletions(-) delete mode 100644 app/controllers/budget/budgets_controller.rb delete mode 100644 app/controllers/budget/investments_controller.rb diff --git a/app/controllers/budget/budgets_controller.rb b/app/controllers/budget/budgets_controller.rb deleted file mode 100644 index c084f4475..000000000 --- a/app/controllers/budget/budgets_controller.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Budget - class BudgetsController < ApplicationController - load_and_authorize_resource - - def index - @budgets = Budget.all - end - - end -end \ No newline at end of file diff --git a/app/controllers/budget/investments_controller.rb b/app/controllers/budget/investments_controller.rb deleted file mode 100644 index d08038174..000000000 --- a/app/controllers/budget/investments_controller.rb +++ /dev/null @@ -1,85 +0,0 @@ -class Budget - class InvestmentsController < ApplicationController - include FeatureFlags - - before_action :load_investments, only: [:index] - before_action :load_geozone, only: [:index, :unfeasible] - - skip_authorization_check - - before_action :authenticate_user!, except: [:index, :show] - before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } - - load_and_authorize_resource - - feature_flag :spending_proposals - - invisible_captcha only: [:create, :update], honeypot: :subtitle - - respond_to :html, :js - - def index - load_investments - set_spending_proposal_votes(@investments) - end - - def new - @spending_proposal = SpendingProposal.new - end - - def show - set_spending_proposal_votes(@spending_proposal) - end - - def create - @spending_proposal = SpendingProposal.new(spending_proposal_params) - @spending_proposal.author = current_user - - if @spending_proposal.save - notice = t('flash.actions.create.spending_proposal', activity: "#{t('layouts.header.my_activity_link')}") - redirect_to @spending_proposal, notice: notice, flash: { html_safe: true } - else - render :new - end - end - - def destroy - spending_proposal = SpendingProposal.find(params[:id]) - spending_proposal.destroy - redirect_to user_path(current_user, filter: 'spending_proposals'), notice: t('flash.actions.destroy.spending_proposal') - end - - def vote - @spending_proposal.register_vote(current_user, 'yes') - set_spending_proposal_votes(@spending_proposal) - end - - private - - def spending_proposal_params - params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :association_name, :terms_of_service) - end - - def load_investments - @investments = filter_and_search(Budget::Investment) - end - - def filter_and_search(target) - target = target.unfeasible if params[:unfeasible].present? - target = target.by_geozone(params[:geozone]) if params[:geozone].present? - target = target.search(params[:search]) if params[:search].present? - target.page(params[:page]).for_render - end - - def load_geozone - return if params[:geozone].blank? - - if params[:geozone] == 'all' - @geozone_name = t('geozones.none') - else - @geozone_name = Geozone.find(params[:geozone]).name - end - end - - end -end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 4c36ab7d8..5027631f3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -84,12 +84,6 @@ Rails.application.routes.draw do end end - scope module: :budget do - resources :budgets do - resources :investments, only: [:index] - end - end - resources :stats, only: [:index] resources :legislations, only: [:show] From 5ea9f9b00ca40ba24e1daf01deec5ffbac7a4b60 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:05:56 +0200 Subject: [PATCH 079/384] marks investment specs as pending --- spec/features/budget/investments_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb index e102787be..1d551ca11 100644 --- a/spec/features/budget/investments_spec.rb +++ b/spec/features/budget/investments_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Budget Investments' do +xfeature 'Budget Investments' do let(:author) { create(:user, :level_two, username: 'Isabel') } let(:budget) { create(:budget) } From 93bfd492d4aed35e54e74ac440ca1eba2dd6ed4b Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 13:21:05 +0200 Subject: [PATCH 080/384] marks specs as pending --- spec/controllers/concerns/has_orders_spec.rb | 4 ++-- spec/features/budgets/budgets_spec.rb | 2 +- spec/models/budget/ballot/line_spec.rb | 2 +- spec/models/budget/ballot_spec.rb | 6 +++--- spec/models/budget/investment_spec.rb | 2 +- spec/models/budget_spec.rb | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/controllers/concerns/has_orders_spec.rb b/spec/controllers/concerns/has_orders_spec.rb index 8155b18e2..e7408c976 100644 --- a/spec/controllers/concerns/has_orders_spec.rb +++ b/spec/controllers/concerns/has_orders_spec.rb @@ -1,13 +1,13 @@ require 'rails_helper' -describe 'HasOrders' do +xdescribe 'HasOrders' do class FakeController < ActionController::Base; end controller(FakeController) do include HasOrders has_orders ['created_at', 'votes_count', 'flags_count'], only: :index - has_orders ->{ ['votes_count', 'flags_count'] }, only: :new + has_orders -> { ['votes_count', 'flags_count'] }, only: :new def index render text: "#{@current_order} (#{@valid_orders.join(' ')})" diff --git a/spec/features/budgets/budgets_spec.rb b/spec/features/budgets/budgets_spec.rb index f45b3fb10..58ee92d0c 100644 --- a/spec/features/budgets/budgets_spec.rb +++ b/spec/features/budgets/budgets_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' feature 'Budgets' do - scenario "Index" do + xscenario "Index" do budget1 = create(:budget) budget2 = create(:budget) budget3 = create(:budget) diff --git a/spec/models/budget/ballot/line_spec.rb b/spec/models/budget/ballot/line_spec.rb index 99a02755c..b7d856a94 100644 --- a/spec/models/budget/ballot/line_spec.rb +++ b/spec/models/budget/ballot/line_spec.rb @@ -87,7 +87,7 @@ describe "Budget::Ballot::Line" do expect(ballot_line).to_not be_valid end - it "should be valid if investment is feasible" do + xit "should be valid if investment is feasible" do budget = create(:budget) group = create(:budget_group, budget: budget) heading = create(:budget_heading, group: group, price: 10000000) diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb index 524f8d3b0..d9e394e54 100644 --- a/spec/models/budget/ballot_spec.rb +++ b/spec/models/budget/ballot_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe Budget::Ballot do describe "#amount_spent" do - it "returns the total amount spent in investments" do + xit "returns the total amount spent in investments" do budget = create(:budget) group1 = create(:budget_group, budget: budget) group2 = create(:budget_group, budget: budget) @@ -22,7 +22,7 @@ describe Budget::Ballot do expect(ballot.total_amount_spent).to eq 30000 end - it "returns the amount spent on all investments assigned to a specific heading" do + xit "returns the amount spent on all investments assigned to a specific heading" do heading = create(:budget_heading) budget = heading.group.budget inv1 = create(:budget_investment, :feasible, price: 10000, heading: heading) @@ -42,7 +42,7 @@ describe Budget::Ballot do end describe "#amount_available" do - it "returns how much is left after taking some investments" do + xit "returns how much is left after taking some investments" do budget = create(:budget) group = create(:budget_group, budget: budget) heading1 = create(:budget_heading, group: group, price: 1000) diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index d3dbf0d0b..b08ddaf73 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Budget::Investment do +xdescribe Budget::Investment do let(:investment) { build(:budget_investment) } it "should be valid" do diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index b238717ef..b4d1d44cc 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -41,7 +41,7 @@ describe Budget do expect(budget.heading_price(heading)).to eq(100) end - it "returns -1 if the heading provided is not part of the budget" do + xit "returns -1 if the heading provided is not part of the budget" do expect(budget.heading_price(create(:budget_heading))).to eq(-1) end end From fa052d990e59d68a634ff2149b608a340a356e87 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 14:11:08 +0200 Subject: [PATCH 081/384] marks specs as pending --- spec/features/budgets_spec.rb | 4 ++-- spec/models/budget/ballot/line_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/budgets_spec.rb b/spec/features/budgets_spec.rb index 4293ef159..f8a7430c4 100644 --- a/spec/features/budgets_spec.rb +++ b/spec/features/budgets_spec.rb @@ -2,13 +2,13 @@ require 'rails_helper' feature 'Budgets' do - scenario 'Index' do + xscenario 'Index' do budgets = create_list(:budget, 3) visit budgets_path budgets.each {|budget| expect(page).to have_link(budget.name)} end - scenario 'Show' do + xscenario 'Show' do budget = create(:budget) heading = create(:budget_heading, budget: budget) visit budget_path(budget) diff --git a/spec/models/budget/ballot/line_spec.rb b/spec/models/budget/ballot/line_spec.rb index b7d856a94..1706cc149 100644 --- a/spec/models/budget/ballot/line_spec.rb +++ b/spec/models/budget/ballot/line_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe "Budget::Ballot::Line" do +xdescribe "Budget::Ballot::Line" do let(:ballot_line) { build(:budget_ballot_line) } From f21fda08989cc7c63d661ac324bc8ffc0aa53073 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Wed, 27 Jul 2016 16:39:25 +0200 Subject: [PATCH 082/384] fixes pending specs --- app/controllers/budgets/investments_controller.rb | 2 +- app/models/budget/ballot/line.rb | 2 +- app/models/budget/heading.rb | 9 +++++++++ app/models/budget/investment.rb | 2 -- spec/factories.rb | 4 +--- spec/features/budget/investments_spec.rb | 8 +++++--- spec/features/budgets_spec.rb | 9 ++++++--- spec/models/budget/investment_spec.rb | 2 +- spec/models/budget_spec.rb | 2 +- 9 files changed, 25 insertions(+), 15 deletions(-) diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index fcc380d22..44e6efd73 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -82,7 +82,7 @@ module Budgets @filter_heading_name = @filter_heading.name end - investments = investments.by_heading(params[:heading_id].presence) + investments = investments.by_heading(params[:heading_id].presence || @budget.headings.first) if params[:unfeasible].present? investments = investments.unfeasible diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb index 01835cd90..8f0375a58 100644 --- a/app/models/budget/ballot/line.rb +++ b/app/models/budget/ballot/line.rb @@ -8,7 +8,7 @@ class Budget belongs_to :investment validate :insufficient_funds - validate :different_geozone, :if => :district_proposal? + #needed? validate :different_geozone, :if => :district_proposal? validate :unfeasible #needed? validates :ballot_id, :budget_id, :group_id, :heading_id, :investment_id, presence: true diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index 830596912..c658c5d28 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -8,5 +8,14 @@ class Budget validates :group_id, presence: true validates :name, presence: true validates :price, presence: true + + def budget + group.budget + end + + def budget=(resource) + group.budget = resource + end + end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 0eb144430..890175035 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -18,8 +18,6 @@ class Budget has_many :valuators, through: :valuator_assignments has_many :comments, as: :commentable - delegate :budget, to: :heading - validates :title, presence: true validates :author, presence: true validates :description, presence: true diff --git a/spec/factories.rb b/spec/factories.rb index 33c0ecda9..e38f47c6e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -207,9 +207,7 @@ FactoryGirl.define do end factory :budget_group, class: 'Budget::Group' do - ### TMP HACK to associate existing budget to a group - budget { Budget.first } - ### + budget sequence(:name) { |n| "Group #{n}" } end diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb index 1d551ca11..9209618d5 100644 --- a/spec/features/budget/investments_spec.rb +++ b/spec/features/budget/investments_spec.rb @@ -1,13 +1,15 @@ require 'rails_helper' -xfeature 'Budget Investments' do +feature 'Budget Investments' do let(:author) { create(:user, :level_two, username: 'Isabel') } let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } scenario 'Index' do - investments = [create(:budget_investment, budget: budget), create(:budget_investment, budget: budget), create(:budget_investment, :feasible, budget: budget)] - unfeasible_investment = create(:budget_investment, :unfeasible, budget: budget) + heading = create(:budget_heading, group: group) + investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), create(:budget_investment, :feasible, heading: heading)] + unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) visit budget_investments_path(budget_id: budget.id) diff --git a/spec/features/budgets_spec.rb b/spec/features/budgets_spec.rb index f8a7430c4..64f135dec 100644 --- a/spec/features/budgets_spec.rb +++ b/spec/features/budgets_spec.rb @@ -2,16 +2,19 @@ require 'rails_helper' feature 'Budgets' do - xscenario 'Index' do + scenario 'Index' do budgets = create_list(:budget, 3) visit budgets_path budgets.each {|budget| expect(page).to have_link(budget.name)} end - xscenario 'Show' do + scenario 'Show' do budget = create(:budget) - heading = create(:budget_heading, budget: budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group) + visit budget_path(budget) + expect(page).to have_content(budget.name) expect(page).to have_content(heading.name) end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index b08ddaf73..d3dbf0d0b 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -xdescribe Budget::Investment do +describe Budget::Investment do let(:investment) { build(:budget_investment) } it "should be valid" do diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index b4d1d44cc..b238717ef 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -41,7 +41,7 @@ describe Budget do expect(budget.heading_price(heading)).to eq(100) end - xit "returns -1 if the heading provided is not part of the budget" do + it "returns -1 if the heading provided is not part of the budget" do expect(budget.heading_price(create(:budget_heading))).to eq(-1) end end From baddfdb3d166f6d804d8e5381bc9655c04c905c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Wed, 20 Jul 2016 13:15:02 +0200 Subject: [PATCH 083/384] adds phase scopes to budget --- app/models/budget.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/budget.rb b/app/models/budget.rb index ea22a4615..d642e7d0c 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -10,6 +10,9 @@ class Budget < ActiveRecord::Base has_many :headings, through: :groups has_many :investments, through: :headings + scope :open, -> { where.not(phase: "finished") } + scope :finished, -> { where(phase: "finished") } + def on_hold? phase == "on_hold" end From 1ad78d599e0f30ab5e79f1cf7ffa712c65c96171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 28 Jul 2016 12:36:39 +0200 Subject: [PATCH 084/384] adds admin budgets index --- app/controllers/admin/budgets_controller.rb | 9 +++ app/views/admin/budgets/index.html.erb | 25 +++++++ config/locales/activerecord.en.yml | 3 + config/locales/activerecord.es.yml | 3 + config/locales/admin.en.yml | 8 +++ config/locales/admin.es.yml | 8 +++ config/locales/en.yml | 7 ++ config/locales/es.yml | 7 ++ config/routes.rb | 7 ++ db/schema.rb | 2 +- spec/features/admin/budgets_spec.rb | 79 +++++++++++++++++++++ 11 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 app/controllers/admin/budgets_controller.rb create mode 100644 app/views/admin/budgets/index.html.erb create mode 100644 spec/features/admin/budgets_spec.rb diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb new file mode 100644 index 000000000..a2f92a39e --- /dev/null +++ b/app/controllers/admin/budgets_controller.rb @@ -0,0 +1,9 @@ +class Admin::BudgetsController < Admin::BaseController + + has_filters %w{open finished}, only: :index + + def index + @budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page]) + end + +end diff --git a/app/views/admin/budgets/index.html.erb b/app/views/admin/budgets/index.html.erb new file mode 100644 index 000000000..193b6a7ef --- /dev/null +++ b/app/views/admin/budgets/index.html.erb @@ -0,0 +1,25 @@ +

    <%= t("admin.budgets.index.title") %>

    + +<%= link_to t("admin.budgets.index.new_link"), + new_admin_budget_path, + class: "button float-right margin-right" %> + +<%= render 'shared/filter_subnav', i18n_namespace: "admin.budgets.index" %> + + +

    <%= page_entries_info @budgets %>

    + + + <% @budgets.each do |budget| %> + + + + + <% end %> +
    + <%= link_to budget.name, admin_budget_path(budget) %> + + <%= t("budget.phase.#{budget.phase}") %> +
    + +<%= paginate @budgets %> \ No newline at end of file diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 07791e53c..5a7a591bd 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -4,6 +4,9 @@ en: activity: one: "activity" other: "activities" + budget: + one: "Participatory budget" + other: "Participatory budgets" comment: one: "Comment" other: "Comments" diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index d5b7f0005..ccd0240e8 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -4,6 +4,9 @@ es: activity: one: "actividad" other: "actividades" + budget: + one: "Presupuesto participativo" + other: "Presupuestos participativos" comment: one: "Comentario" other: "Comentarios" diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 2f73f64d8..7d32e3291 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -60,6 +60,13 @@ en: on_users: Users title: Moderator activity type: Type + budgets: + index: + title: Participatory budgets + new_link: Create new + filters: + open: Open + finished: Finished comments: index: filter: Filter @@ -96,6 +103,7 @@ en: activity: Moderator activity admin: Admin menu banner: Manage banners + budgets: Participatory budgets debate_topics: Debate topics hidden_comments: Hidden comments hidden_debates: Hidden debates diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 5aada1ce1..c5f0df247 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -58,6 +58,13 @@ es: on_users: Usuarios title: Actividad de los Moderadores type: Tipo + budgets: + index: + title: Presupuestos participativos + new_link: Crear nuevo + filters: + open: Abiertos + finished: Terminados comments: index: filter: Filtro @@ -94,6 +101,7 @@ es: activity: Actividad de moderadores admin: Menú de administración banner: Gestionar banners + budgets: Presupuestos participativos debate_topics: Temas de debate hidden_comments: Comentarios ocultos hidden_debates: Debates ocultos diff --git a/config/locales/en.yml b/config/locales/en.yml index 05153d714..fae75307f 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,6 +33,13 @@ en: application: close: Close menu: Menu + budget: + phase: + on_hold: On hold + accepting: Accepting proposals + selecting: Selecting + balloting: Balloting + finished: Finished comments: comment: admin: Administrator diff --git a/config/locales/es.yml b/config/locales/es.yml index 9391da3bf..282340395 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -33,6 +33,13 @@ es: application: close: Cerrar menu: Menú + budget: + phase: + on_hold: Pausa + accepting: Aceptando propuestas + selecting: Fase de selección + balloting: Fase de Votación + finished: Terminado comments: comment: admin: Administrador diff --git a/config/routes.rb b/config/routes.rb index 22518f8c8..8cf94ea23 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -153,6 +153,13 @@ Rails.application.routes.draw do get :summary, on: :collection end + resources :budgets do + resources :budget_groups do + resources :budget_headings do + end + end + end + resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do collection { get :search} end diff --git a/db/schema.rb b/db/schema.rb index ee5b3ab6f..df58b15cb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -210,10 +210,10 @@ ActiveRecord::Schema.define(version: 20160617172616) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 - t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 + t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb new file mode 100644 index 000000000..8326a5dac --- /dev/null +++ b/spec/features/admin/budgets_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' + +feature 'Admin budgets' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + context "Feature flag" do + + xscenario 'Disabled with a feature flag' do + Setting['feature.budgets'] = nil + expect{ visit admin_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + end + + context "Index" do + + scenario 'Displaying budgets' do + budget = create(:budget) + visit admin_budgets_path + + expect(page).to have_content(budget.name) + expect(page).to have_content(I18n.t("budget.phase.#{budget.phase}")) + end + + scenario 'Filters by phase' do + budget1 = create(:budget) + budget2 = create(:budget, :accepting) + budget3 = create(:budget, :selecting) + budget4 = create(:budget, :balloting) + budget5 = create(:budget, :finished) + + visit admin_budgets_path + expect(page).to have_content(budget1.name) + expect(page).to have_content(budget2.name) + expect(page).to have_content(budget3.name) + expect(page).to have_content(budget4.name) + expect(page).to_not have_content(budget5.name) + + click_link "Finished" + expect(page).to_not have_content(budget1.name) + expect(page).to_not have_content(budget2.name) + expect(page).to_not have_content(budget3.name) + expect(page).to_not have_content(budget4.name) + expect(page).to have_content(budget5.name) + + click_link "Open" + expect(page).to have_content(budget1.name) + expect(page).to have_content(budget2.name) + expect(page).to have_content(budget3.name) + expect(page).to have_content(budget4.name) + expect(page).to_not have_content(budget5.name) + end + + + scenario "Current filter is properly highlighted" do + filters_links = {'open' => 'Open', 'finished' => 'Finished'} + + visit admin_budgets_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 admin_budgets_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 + + end +end \ No newline at end of file From a9c3629de7e77455042f6614040e1ec65b95363c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 28 Jul 2016 12:37:00 +0200 Subject: [PATCH 085/384] adds missing trait to testing factories --- spec/factories.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/factories.rb b/spec/factories.rb index 263900ecd..bca0c51e3 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -193,6 +193,10 @@ FactoryGirl.define do currency_symbol "€" phase 'on_hold' + trait :accepting do + phase 'accepting' + end + trait :selecting do phase 'selecting' end From 958cfb1338500d0d5f32467ebb6878762dde1fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 28 Jul 2016 12:37:12 +0200 Subject: [PATCH 086/384] adds budgets to admin's menu --- app/views/admin/_menu.html.erb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 7ea48b2c6..d1feaeb1e 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -35,6 +35,14 @@
  • <% end %> + <%# if feature?(:budgets) %> +
  • > + <%= link_to admin_budgets_path do %> + <%= t("admin.menu.budgets") %> + <% end %> +
  • + <%# end %> +
  • > <%= link_to admin_banners_path do %> <%= t("admin.menu.banner") %> From 6bb09185e4db3c0ee5d74ed127b1930269459691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 28 Jul 2016 12:40:57 +0200 Subject: [PATCH 087/384] makes i18n-tasks ignore filter keys in budgets --- config/i18n-tasks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 250ed18f9..e24d3af0c 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -112,6 +112,7 @@ ignore_unused: - 'admin.banners.index.filters.*' - 'admin.debates.index.filter*' - 'admin.proposals.index.filter*' + - 'admin.budgets.index.filter*' - 'admin.spending_proposals.index.filter*' - 'admin.organizations.index.filter*' - 'admin.users.index.filter*' From b69308b770781e054ec4c5c67b49b0ce6b54d5e9 Mon Sep 17 00:00:00 2001 From: Alberto Garcia Cabeza Date: Thu, 28 Jul 2016 13:04:30 +0200 Subject: [PATCH 088/384] Adds example content for admin budget new --- app/assets/stylesheets/admin.scss | 5 + app/controllers/admin/budgets_controller.rb | 3 + app/views/admin/budgets/new.html.erb | 137 ++++++++++++++++++++ config/locales/admin.es.yml | 16 +++ 4 files changed, 161 insertions(+) create mode 100644 app/views/admin/budgets/new.html.erb diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index cd8241fca..11a481c1e 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -36,6 +36,11 @@ body.admin { input[type="text"], textarea { width: 100%; } + + .input-group input[type="text"] { + border-radius: 0; + margin-bottom: 0 !important; + } } table { diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index a2f92a39e..7953aae75 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -6,4 +6,7 @@ class Admin::BudgetsController < Admin::BaseController @budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page]) end + def new + end + end diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb new file mode 100644 index 000000000..66484f7b4 --- /dev/null +++ b/app/views/admin/budgets/new.html.erb @@ -0,0 +1,137 @@ +
    +
    +

    <%= t("admin.budgets.new.title") %>

    + +
    + + "> + + +
    +
    + +
    +
    + +
    +
    + " class="button success"> +
    +
    +
    + +
    + + +
    +
    +

    Nombre del budget

    + + <%= link_to t("admin.budgets.new.add_group"), "#", class: "button float-right" %> + +
    + + "> +
    +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Nombre del grupo + <%= link_to t("admin.budgets.new.add_heading"), "#", class: "button float-right" %> +
    <%= t("admin.budgets.new.table_heading") %><%= t("admin.budgets.new.table_amount") %><%= t("admin.budgets.new.table_geozone") %>
    +
    + <%= t("admin.budgets.new.no_heading") %> +
    +
    +
    +
    + + + + "> +
    + ", class="button success"> +
    +
    +
    +
    + Heading 1 name + + 9999€ + + +
    + Heading 2 name + + 24000000€ + + +
    + Heading 3 name + + 1265000€ + + +
    +
    +
    diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index c5f0df247..0045eebfe 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -65,6 +65,22 @@ es: filters: open: Abiertos finished: Terminados + new: + title: Nuevo presupuesto ciudadano + create: Crear presupuesto + name: Nombre del presupuesto + description: Descripción + phase: Fase + currency: Divisa + group: Nombre del grupo + add_group: Añadir nuevo grupo + heading: Nombre de la partida + add_heading: Añadir partida + save_heading: Guardar partida + no_heading: Este grupo no tiene ninguna partida asignada. + table_heading: Partida + table_amount: Cantidad + table_geozone: Ámbito de actuación comments: index: filter: Filtro From 3e892c6e7611dbb03d46331e38dda920f0e2897e Mon Sep 17 00:00:00 2001 From: Alberto Garcia Cabeza Date: Thu, 28 Jul 2016 19:21:53 +0200 Subject: [PATCH 089/384] Adds translations and improve example content --- app/assets/stylesheets/admin.scss | 4 ++ app/views/admin/budgets/new.html.erb | 62 +++++++++++++++------------- config/locales/admin.en.yml | 22 +++++++++- config/locales/admin.es.yml | 4 ++ 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 11a481c1e..196d0be17 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -47,6 +47,10 @@ body.admin { th { text-align: left; + + &.with-button { + line-height: $line-height*2; + } } tr { diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb index 66484f7b4..f71e7c7a5 100644 --- a/app/views/admin/budgets/new.html.erb +++ b/app/views/admin/budgets/new.html.erb @@ -31,9 +31,6 @@
  • -
    - -

    Nombre del budget

    @@ -41,20 +38,25 @@ <%= link_to t("admin.budgets.new.add_group"), "#", class: "button float-right" %>
    - - "> +
    + + + + "> +
    + ", class="button success"> +
    +
    -
    -
    - @@ -79,30 +81,37 @@ - + + @@ -110,12 +119,10 @@ Heading 2 name @@ -123,14 +130,13 @@ Heading 3 name +
    + Nombre del grupo <%= link_to t("admin.budgets.new.add_heading"), "#", class: "button float-right" %>
    -
    - - - - "> -
    - ", class="button success"> + + "> + +
    +
    + + "> +
    +
    + +
    + + ", class="button success">
    Heading 1 name - 9999€ + 190.000€ - + Geozone B
    - 24000000€ + 24.000.000€ - + Does not apply
    - 1265000€ + 1.265.000€ - + Geozone A
    diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 7d32e3291..a778f6745 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -32,8 +32,6 @@ en: editing: Edit banner form: submit_button: Save changes - errors: - form: errors: form: error: @@ -67,6 +65,26 @@ en: filters: open: Open finished: Finished + new: + title: New participatory budget + create: Create budget + name: Budget's name + description: Description + phase: Phase + currency: Currency + group: Group's name + add_group: Add new group + create_group: Create group + heading: Heading's name + add_heading: Add heading + amount: Amount + save_heading: Save heading + no_heading: This group has no assigned heading. + geozone: Scope of operation + no_geozone: Does not apply + table_heading: Heading + table_amount: Amount + table_geozone: Scope of operation comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 0045eebfe..3154060bf 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -74,10 +74,14 @@ es: currency: Divisa group: Nombre del grupo add_group: Añadir nuevo grupo + create_group: Crear grupo heading: Nombre de la partida add_heading: Añadir partida + amount: Cantidad save_heading: Guardar partida no_heading: Este grupo no tiene ninguna partida asignada. + geozone: Ámbito de actuación + no_geozone: No aplica table_heading: Partida table_amount: Cantidad table_geozone: Ámbito de actuación From 9014de269323581462be1aea4e61a822b559f033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 29 Jul 2016 13:34:11 +0200 Subject: [PATCH 090/384] adds budgets_helper --- app/helpers/budgets_helper.rb | 11 +++++++++++ app/models/budget.rb | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 app/helpers/budgets_helper.rb diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb new file mode 100644 index 000000000..d281ef182 --- /dev/null +++ b/app/helpers/budgets_helper.rb @@ -0,0 +1,11 @@ +module BudgetsHelper + + def budget_phases_select_options + Budget::VALID_PHASES.map { |ph| [ t("budget.phase.#{ph}"), ph ] } + end + + def budget_currency_symbol_select_options + Budget::CURRENCY_SYMBOLS.map { |cs| [ cs, cs ] } + end + +end \ No newline at end of file diff --git a/app/models/budget.rb b/app/models/budget.rb index d642e7d0c..18f30ba7c 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -1,7 +1,9 @@ class Budget < ActiveRecord::Base VALID_PHASES = %W{on_hold accepting selecting balloting finished} + CURRENCY_SYMBOLS = %W{€ $ £ ¥} + validates :name, presence: true validates :phase, inclusion: { in: VALID_PHASES } has_many :investments, dependent: :destroy From 6fce9df4513817b45163e8dbab1ff1a32d7b47ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 29 Jul 2016 13:34:41 +0200 Subject: [PATCH 091/384] adds admin creation of budgets --- app/controllers/admin/budgets_controller.rb | 16 +++ app/views/admin/budgets/new.html.erb | 145 +++----------------- config/locales/admin.en.yml | 2 + config/locales/admin.es.yml | 2 + config/locales/es.yml | 2 +- spec/features/admin/budgets_spec.rb | 35 ++++- 6 files changed, 67 insertions(+), 135 deletions(-) diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index 7953aae75..d6f2789e6 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -7,6 +7,22 @@ class Admin::BudgetsController < Admin::BaseController end def new + @budget = Budget.new end + def create + @budget = Budget.new(budget_params) + if @budget.save + redirect_to admin_budgets_path, notice: t('admin.budgets.create.notice') + else + render :new + end + end + + private + + def budget_params + params.require(:budget).permit(:name, :description, :phase, :currency_symbol) + end + end diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb index f71e7c7a5..c7985b98f 100644 --- a/app/views/admin/budgets/new.html.erb +++ b/app/views/admin/budgets/new.html.erb @@ -2,142 +2,29 @@

    <%= t("admin.budgets.new.title") %>

    -
    - - "> - - + <%= form_for [:admin, @budget] do |f| %> + + <%= f.label :name, t("admin.budgets.new.name") %> + <%= f.text_field :name, + label: false, + maxlength: 30, + placeholder: t("admin.budgets.new.name") %> + + <%= f.label :description, t("admin.budgets.new.description") %> + <%= f.text_area :description, rows: 3, maxlength: 6000, label: false, placeholder: t("admin.budgets.new.description") %> +
    - + <%= f.label :description, t("admin.budgets.new.phase") %> + <%= f.select :phase, budget_phases_select_options, {label: false} %>
    - + <%= f.label :description, t("admin.budgets.new.currency") %> + <%= f.select :currency_symbol, budget_currency_symbol_select_options, {label: false} %>
    " class="button success"> -
    + <% end %>
    -
    -
    -

    Nombre del budget

    - - <%= link_to t("admin.budgets.new.add_group"), "#", class: "button float-right" %> - -
    -
    - - - - "> -
    - ", class="button success"> -
    -
    -
    -
    -
    - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Nombre del grupo - <%= link_to t("admin.budgets.new.add_heading"), "#", class: "button float-right" %> -
    <%= t("admin.budgets.new.table_heading") %><%= t("admin.budgets.new.table_amount") %><%= t("admin.budgets.new.table_geozone") %>
    -
    - <%= t("admin.budgets.new.no_heading") %> -
    -
    -
    - - "> - -
    -
    - - "> -
    -
    - - -
    -
    - - ", class="button success"> -
    -
    - Heading 1 name - - 190.000€ - - Geozone B -
    - Heading 2 name - - 24.000.000€ - - Does not apply -
    - Heading 3 name - - 1.265.000€ - - Geozone A -
    -
    -
    diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index a778f6745..d55d24726 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -65,6 +65,8 @@ en: filters: open: Open finished: Finished + create: + notice: New participatory budget created successfully! new: title: New participatory budget create: Create budget diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 3154060bf..8ed57d4d8 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -65,6 +65,8 @@ es: filters: open: Abiertos finished: Terminados + create: + notice: ¡Nueva campaña de presupuestos participativos creada con éxito! new: title: Nuevo presupuesto ciudadano create: Crear presupuesto diff --git a/config/locales/es.yml b/config/locales/es.yml index 282340395..195a80766 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -35,7 +35,7 @@ es: menu: Menú budget: phase: - on_hold: Pausa + on_hold: En pausa accepting: Aceptando propuestas selecting: Fase de selección balloting: Fase de Votación diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index 8326a5dac..c684d8a55 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -7,7 +7,7 @@ feature 'Admin budgets' do login_as(admin.user) end - context "Feature flag" do + context 'Feature flag' do xscenario 'Disabled with a feature flag' do Setting['feature.budgets'] = nil @@ -16,7 +16,7 @@ feature 'Admin budgets' do end - context "Index" do + context 'Index' do scenario 'Displaying budgets' do budget = create(:budget) @@ -40,14 +40,14 @@ feature 'Admin budgets' do expect(page).to have_content(budget4.name) expect(page).to_not have_content(budget5.name) - click_link "Finished" + click_link 'Finished' expect(page).to_not have_content(budget1.name) expect(page).to_not have_content(budget2.name) expect(page).to_not have_content(budget3.name) expect(page).to_not have_content(budget4.name) expect(page).to have_content(budget5.name) - click_link "Open" + click_link 'Open' expect(page).to have_content(budget1.name) expect(page).to have_content(budget2.name) expect(page).to have_content(budget3.name) @@ -56,7 +56,7 @@ feature 'Admin budgets' do end - scenario "Current filter is properly highlighted" do + scenario 'Current filter is properly highlighted' do filters_links = {'open' => 'Open', 'finished' => 'Finished'} visit admin_budgets_path @@ -76,4 +76,29 @@ feature 'Admin budgets' do end end + + context 'New' do + scenario 'Create budget' do + visit admin_budgets_path + click_link 'Create new' + + fill_in 'budget_name', with: 'M30 - Summer campaign' + fill_in 'budget_description', with: 'Budgeting for summer 2017 maintenance and improvements of the road M-30' + select 'Accepting proposals', from: 'budget[phase]' + + click_button 'Create budget' + + expect(page).to have_content 'New participatory budget created successfully!' + expect(page).to have_content 'M30 - Summer campaign' + end + + scenario 'Name is mandatory' do + visit new_admin_budget_path + click_button 'Create budget' + + expect(page).to_not have_content 'New participatory budget created successfully!' + expect(page).to have_css("label.error", text: "Budget's name") + end + + end end \ No newline at end of file From 49bc6533e8e2e2c13c8c1487dbc05031ee41ef8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 29 Jul 2016 13:39:08 +0200 Subject: [PATCH 092/384] adds explicit permission to admin to read budgets --- app/controllers/admin/budgets_controller.rb | 2 ++ app/models/abilities/administrator.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index d6f2789e6..02894240e 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -2,6 +2,8 @@ class Admin::BudgetsController < Admin::BaseController has_filters %w{open finished}, only: :index + load_and_authorize_resource + def index @budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page]) end diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 42e4ecbd3..65f97db2e 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -42,7 +42,7 @@ module Abilities can [:read, :update, :valuate, :destroy, :summary], SpendingProposal - can [:create, :update], Budget + can [:read, :create, :update], Budget can [:hide, :update], Budget::Investment can :valuate, Budget::Investment, budget: { valuating: true } can :create, Budget::ValuatorAssignment From 724ccf175c1a23a1e882c363f7d6fa54a565e1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 29 Jul 2016 14:52:37 +0200 Subject: [PATCH 093/384] adds budget show to admin --- app/controllers/admin/budgets_controller.rb | 5 ++++- app/views/admin/budgets/new.html.erb | 3 +-- app/views/admin/budgets/show.html.erb | 12 ++++++++++++ config/locales/admin.en.yml | 3 +++ config/locales/admin.es.yml | 3 +++ spec/features/admin/budgets_spec.rb | 2 +- 6 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 app/views/admin/budgets/show.html.erb diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index 02894240e..2e8a28fe6 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -8,6 +8,9 @@ class Admin::BudgetsController < Admin::BaseController @budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page]) end + def show + end + def new @budget = Budget.new end @@ -15,7 +18,7 @@ class Admin::BudgetsController < Admin::BaseController def create @budget = Budget.new(budget_params) if @budget.save - redirect_to admin_budgets_path, notice: t('admin.budgets.create.notice') + redirect_to admin_budget_path(@budget), notice: t('admin.budgets.create.notice') else render :new end diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb index c7985b98f..019ca460f 100644 --- a/app/views/admin/budgets/new.html.erb +++ b/app/views/admin/budgets/new.html.erb @@ -26,5 +26,4 @@ " class="button success"> <% end %>
    -
    - +
    \ No newline at end of file diff --git a/app/views/admin/budgets/show.html.erb b/app/views/admin/budgets/show.html.erb new file mode 100644 index 000000000..20185b2ba --- /dev/null +++ b/app/views/admin/budgets/show.html.erb @@ -0,0 +1,12 @@ +
    +
    +

    <%= @budget.name %>

    + + <%= simple_format(text_with_links(@budget.description), {}, sanitize: false) %> + +

    + <%= t('admin.budgets.show.phase') %>: <%= t("budget.phase.#{@budget.phase}") %> | + <%= t('admin.budgets.show.currency') %>: <%= @budget.currency_symbol %> +

    +
    +
    \ No newline at end of file diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index d55d24726..812e1a45c 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -87,6 +87,9 @@ en: table_heading: Heading table_amount: Amount table_geozone: Scope of operation + show: + phase: Current phase + currency: Currency comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 8ed57d4d8..1afc01ef2 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -87,6 +87,9 @@ es: table_heading: Partida table_amount: Cantidad table_geozone: Ámbito de actuación + show: + phase: Fase actual + currency: Divisa comments: index: filter: Filtro diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index c684d8a55..f38d2c9f5 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -55,7 +55,6 @@ feature 'Admin budgets' do expect(page).to_not have_content(budget5.name) end - scenario 'Current filter is properly highlighted' do filters_links = {'open' => 'Open', 'finished' => 'Finished'} @@ -78,6 +77,7 @@ feature 'Admin budgets' do end context 'New' do + scenario 'Create budget' do visit admin_budgets_path click_link 'Create new' From c4768e380d01efbbac3d63a52f86fb49861b2abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 30 Jul 2016 09:56:13 +0200 Subject: [PATCH 094/384] saves querys --- app/views/shared/_admin_login_items.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_admin_login_items.html.erb b/app/views/shared/_admin_login_items.html.erb index 6ba10bbca..013320f38 100644 --- a/app/views/shared/_admin_login_items.html.erb +++ b/app/views/shared/_admin_login_items.html.erb @@ -5,13 +5,13 @@ <% end %> - <% if current_user.moderator? || current_user.administrator? %> + <% if current_user.administrator? || current_user.moderator? %>
  • <%= link_to t("layouts.header.moderation"), moderation_root_path %>
  • <% end %> - <% if feature?(:spending_proposals) && (current_user.valuator? || current_user.administrator?) %> + <% if feature?(:spending_proposals) && (current_user.administrator? || current_user.valuator?) %>
  • <%= link_to t("layouts.header.valuation"), valuation_root_path %>
  • From 64a614c67f2ce55e815980df5bd989879945e131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 3 Aug 2016 20:20:52 +0200 Subject: [PATCH 095/384] adds geozone helper to map ids and names --- app/helpers/geozones_helper.rb | 5 +++++ spec/helpers/geozones_helper_spec.rb | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/helpers/geozones_helper.rb b/app/helpers/geozones_helper.rb index bfc5f9105..ce03e0579 100644 --- a/app/helpers/geozones_helper.rb +++ b/app/helpers/geozones_helper.rb @@ -8,4 +8,9 @@ module GeozonesHelper Geozone.all.order(name: :asc).collect { |g| [ g.name, g.id ] } end + def geozone_name_from_id(g_id) + @all_geozones ||= Geozone.all.collect{ |g| [ g.id, g.name ] }.to_h + @all_geozones[g_id] || t("geozones.none") + end + end diff --git a/spec/helpers/geozones_helper_spec.rb b/spec/helpers/geozones_helper_spec.rb index 605a774a6..0c0c13d70 100644 --- a/spec/helpers/geozones_helper_spec.rb +++ b/spec/helpers/geozones_helper_spec.rb @@ -31,4 +31,19 @@ describe GeozonesHelper do end end + describe "#geozone_name_from_id" do + + it "returns geozone name if present" do + g1 = create(:geozone, name: "AAA") + g2 = create(:geozone, name: "BBB") + + expect(geozone_name_from_id(g1.id)).to eq "AAA" + expect(geozone_name_from_id(g2.id)).to eq "BBB" + end + + it "returns default string for no geozone if geozone is blank" do + expect(geozone_name_from_id(nil)).to eq "All city" + end + end + end From 0f7e23bec49a2494dce28c771a0b521ea549111e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 3 Aug 2016 20:23:41 +0200 Subject: [PATCH 096/384] adds budget's groups & headings to admin --- .../admin/budget_groups_controller.rb | 15 ++++ .../admin/budget_headings_controller.rb | 16 ++++ app/controllers/admin/budgets_controller.rb | 1 + app/models/abilities/administrator.rb | 4 +- app/views/admin/budget_groups/create.js.erb | 2 + app/views/admin/budget_headings/create.js.erb | 2 + app/views/admin/budgets/_group.html.erb | 76 +++++++++++++++++++ app/views/admin/budgets/_groups.html.erb | 34 +++++++++ app/views/admin/budgets/new.html.erb | 2 +- app/views/admin/budgets/show.html.erb | 4 + config/locales/admin.en.yml | 9 ++- config/locales/admin.es.yml | 9 ++- spec/features/admin/budgets_spec.rb | 54 +++++++++++++ 13 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 app/controllers/admin/budget_groups_controller.rb create mode 100644 app/controllers/admin/budget_headings_controller.rb create mode 100644 app/views/admin/budget_groups/create.js.erb create mode 100644 app/views/admin/budget_headings/create.js.erb create mode 100644 app/views/admin/budgets/_group.html.erb create mode 100644 app/views/admin/budgets/_groups.html.erb diff --git a/app/controllers/admin/budget_groups_controller.rb b/app/controllers/admin/budget_groups_controller.rb new file mode 100644 index 000000000..18f5a6b12 --- /dev/null +++ b/app/controllers/admin/budget_groups_controller.rb @@ -0,0 +1,15 @@ +class Admin::BudgetGroupsController < Admin::BaseController + + def create + @budget = Budget.find params[:budget_id] + @budget.groups.create(budget_group_params) + @groups = @budget.groups.includes(:headings) + end + + private + + def budget_group_params + params.require(:budget_group).permit(:name) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb new file mode 100644 index 000000000..3c8ccafa0 --- /dev/null +++ b/app/controllers/admin/budget_headings_controller.rb @@ -0,0 +1,16 @@ +class Admin::BudgetHeadingsController < Admin::BaseController + + def create + @budget = Budget.find params[:budget_id] + @budget_group = @budget.groups.find params[:budget_group_id] + @budget_group.headings.create(budget_heading_params) + @headings = @budget_group.headings + end + + private + + def budget_heading_params + params.require(:budget_heading).permit(:name, :price, :geozone_id) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index 2e8a28fe6..144b43a7f 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -9,6 +9,7 @@ class Admin::BudgetsController < Admin::BaseController end def show + @budget = Budget.includes(groups: :headings).find(params[:id]) end def new diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 65f97db2e..f3f0b8f9b 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -42,7 +42,9 @@ module Abilities can [:read, :update, :valuate, :destroy, :summary], SpendingProposal - can [:read, :create, :update], Budget + can [:index, :read, :new, :create, :update, :destroy], Budget + can [:read, :create, :update, :destroy], Budget::Group + can [:read, :create, :update, :destroy], Budget::Heading can [:hide, :update], Budget::Investment can :valuate, Budget::Investment, budget: { valuating: true } can :create, Budget::ValuatorAssignment diff --git a/app/views/admin/budget_groups/create.js.erb b/app/views/admin/budget_groups/create.js.erb new file mode 100644 index 000000000..cb926a7c6 --- /dev/null +++ b/app/views/admin/budget_groups/create.js.erb @@ -0,0 +1,2 @@ +$("#<%= dom_id(@budget) %>_groups").html('<%= j render("admin/budgets/groups", groups: @groups) %>'); +App.Forms.toggleLink(); \ No newline at end of file diff --git a/app/views/admin/budget_headings/create.js.erb b/app/views/admin/budget_headings/create.js.erb new file mode 100644 index 000000000..5d8eefb2d --- /dev/null +++ b/app/views/admin/budget_headings/create.js.erb @@ -0,0 +1,2 @@ +$("#<%= dom_id(@budget_group) %>").html('<%= j render("admin/budgets/group", group: @budget_group, headings: @headings) %>'); +App.Forms.toggleLink(); \ No newline at end of file diff --git a/app/views/admin/budgets/_group.html.erb b/app/views/admin/budgets/_group.html.erb new file mode 100644 index 000000000..3660fa0c1 --- /dev/null +++ b/app/views/admin/budgets/_group.html.erb @@ -0,0 +1,76 @@ +
    + + + + + + + <% if headings.blank? %> + + + + + <% else %> + + + + + + + + <% end %> + + + + + + + + <% headings.each do |heading| %> + + + + + + <% end %> + + +
    + <%= group.name %> + <%= link_to t("admin.budgets.form.add_heading"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#group-#{group.id}-new-heading-form" } %> +
    +
    + <%= t("admin.budgets.form.no_heading") %> +
    +
    <%= t("admin.budgets.form.table_heading") %><%= t("admin.budgets.form.table_amount") %><%= t("admin.budgets.form.table_geozone") %>
    + <%= heading.name %> + + <%= heading.price %> + + <%= geozone_name_from_id heading.geozone_id %> +
    +
    \ No newline at end of file diff --git a/app/views/admin/budgets/_groups.html.erb b/app/views/admin/budgets/_groups.html.erb new file mode 100644 index 000000000..ba785ee0c --- /dev/null +++ b/app/views/admin/budgets/_groups.html.erb @@ -0,0 +1,34 @@ +
    +

    <%= t('admin.budgets.show.groups') %>

    + <% if groups.blank? %> +
    + <%= t("admin.budgets.form.no_groups") %> + <%= link_to t("admin.budgets.form.add_group"), "#", + class: "js-toggle-link", + data: { "toggle-selector" => "#new-group-form" } %> +
    + <% else %> + <%= link_to t("admin.budgets.form.add_group"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#new-group-form" } %> + <% end %> + + <%= form_for [:admin, @budget, Budget::Group.new], html: {id: "new-group-form", style: "display:none"}, remote: true do |f| %> +
    + + + + <%= f.text_field :name, + label: false, + maxlength: 50, + placeholder: t("admin.budgets.form.group") %> +
    + <%= f.submit t("admin.budgets.form.create_group"), class: "button success" %> +
    +
    + <% end %> + + <% groups.each do |group| %> +
    + <%= render "admin/budgets/group", group: group, headings: group.headings %> +
    + <% end %> +
    \ No newline at end of file diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb index 019ca460f..9ca0f34a5 100644 --- a/app/views/admin/budgets/new.html.erb +++ b/app/views/admin/budgets/new.html.erb @@ -23,7 +23,7 @@ <%= f.select :currency_symbol, budget_currency_symbol_select_options, {label: false} %>
    - " class="button success"> + <%= f.submit t("admin.budgets.new.create"), class: "button success" %> <% end %>
    \ No newline at end of file diff --git a/app/views/admin/budgets/show.html.erb b/app/views/admin/budgets/show.html.erb index 20185b2ba..847aa2f60 100644 --- a/app/views/admin/budgets/show.html.erb +++ b/app/views/admin/budgets/show.html.erb @@ -9,4 +9,8 @@ <%= t('admin.budgets.show.currency') %>: <%= @budget.currency_symbol %>

    + + +
    + <%= render "groups", groups: @budget.groups %>
    \ No newline at end of file diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 812e1a45c..d8154efc1 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -74,7 +74,13 @@ en: description: Description phase: Phase currency: Currency + show: + phase: Current phase + currency: Currency + groups: Groups of budget headings + form: group: Group's name + no_groups: No groups created yet. Each user will be able to vote in only one heading per group. add_group: Add new group create_group: Create group heading: Heading's name @@ -87,9 +93,6 @@ en: table_heading: Heading table_amount: Amount table_geozone: Scope of operation - show: - phase: Current phase - currency: Currency comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 1afc01ef2..1d4ead16b 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -74,7 +74,13 @@ es: description: Descripción phase: Fase currency: Divisa + show: + phase: Fase actual + currency: Divisa + groups: Grupos de partidas presupuestarias + form: group: Nombre del grupo + no_groups: No hay grupos creados todavía. Cada usuario podrá votar en una sola partida de cada grupo. add_group: Añadir nuevo grupo create_group: Crear grupo heading: Nombre de la partida @@ -87,9 +93,6 @@ es: table_heading: Partida table_amount: Cantidad table_geozone: Ámbito de actuación - show: - phase: Fase actual - currency: Divisa comments: index: filter: Filtro diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index f38d2c9f5..123ca43e8 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -101,4 +101,58 @@ feature 'Admin budgets' do end end + + context 'Manage groups and headings' do + + scenario 'Create group', :js do + create(:budget, name: 'Yearly participatory budget') + + visit admin_budgets_path + click_link 'Yearly participatory budget' + + expect(page).to have_content 'No groups created yet.' + + click_link 'Add new group' + + fill_in 'budget_group_name', with: 'General improvments' + click_button 'Create group' + + expect(page).to have_content 'Yearly participatory budget' + expect(page).to_not have_content 'No groups created yet.' + + visit admin_budgets_path + click_link 'Yearly participatory budget' + + expect(page).to have_content 'Yearly participatory budget' + expect(page).to_not have_content 'No groups created yet.' + end + + scenario 'Create heading', :js do + budget = create(:budget, name: 'Yearly participatory budget') + group = create(:budget_group, budget: budget, name: 'Districts improvments') + + visit admin_budget_path(budget) + + within("#budget_group_#{group.id}") do + expect(page).to have_content 'This group has no assigned heading.' + click_link 'Add heading' + + fill_in 'budget_heading_name', with: 'District 9 reconstruction' + fill_in 'budget_heading_price', with: '6785' + click_button 'Save heading' + end + + expect(page).to_not have_content 'This group has no assigned heading.' + + visit admin_budget_path(budget) + within("#budget_group_#{group.id}") do + expect(page).to_not have_content 'This group has no assigned heading.' + + expect(page).to have_content 'District 9 reconstruction' + expect(page).to have_content '6785' + expect(page).to have_content 'All city' + end + end + + end end \ No newline at end of file From f24886560b2f6636fc5cd958ecbb04a6e2909aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Thu, 1 Sep 2016 12:46:43 +0200 Subject: [PATCH 097/384] removes unused i18n entry --- config/locales/admin.en.yml | 1 - config/locales/admin.es.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index d8154efc1..78a3a6d6c 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -89,7 +89,6 @@ en: save_heading: Save heading no_heading: This group has no assigned heading. geozone: Scope of operation - no_geozone: Does not apply table_heading: Heading table_amount: Amount table_geozone: Scope of operation diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 1d4ead16b..496a0208d 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -89,7 +89,6 @@ es: save_heading: Guardar partida no_heading: Este grupo no tiene ninguna partida asignada. geozone: Ámbito de actuación - no_geozone: No aplica table_heading: Partida table_amount: Cantidad table_geozone: Ámbito de actuación From e28c5c5010ea30a6a81b680264eae42fa7afb960 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Thu, 28 Jul 2016 14:06:40 +0200 Subject: [PATCH 098/384] fixes specs --- spec/features/budget/investments_spec.rb | 72 ++++++++++++------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb index 9209618d5..4093bcb5a 100644 --- a/spec/features/budget/investments_spec.rb +++ b/spec/features/budget/investments_spec.rb @@ -2,12 +2,12 @@ require 'rails_helper' feature 'Budget Investments' do - let(:author) { create(:user, :level_two, username: 'Isabel') } - let(:budget) { create(:budget) } - let(:group) { create(:budget_group, budget: budget) } + let(:author) { create(:user, :level_two, username: 'Isabel') } + let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } + let(:heading) { create(:budget_heading, group: group) } scenario 'Index' do - heading = create(:budget_heading, group: group) investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), create(:budget_investment, :feasible, heading: heading)] unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) @@ -25,9 +25,9 @@ feature 'Budget Investments' do context("Search") do scenario 'Search by text' do - investment1 = create(:budget_investment, budget: budget, title: "Get Schwifty") - investment2 = create(:budget_investment, budget: budget, title: "Schwifty Hello") - investment3 = create(:budget_investment, budget: budget, title: "Do not show me") + investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty") + investment2 = create(:budget_investment, heading: heading, title: "Schwifty Hello") + investment3 = create(:budget_investment, heading: heading, title: "Do not show me") visit budget_investments_path(budget_id: budget.id) @@ -48,10 +48,10 @@ feature 'Budget Investments' do context("Filters") do scenario 'by unfeasibility' do - investment1 = create(:budget_investment, :unfeasible, budget: budget, valuation_finished: true) - investment2 = create(:budget_investment, :feasible, budget: budget) - investment3 = create(:budget_investment, budget: budget) - investment4 = create(:budget_investment, :feasible, budget: budget) + investment1 = create(:budget_investment, :unfeasible, heading: heading, valuation_finished: true) + investment2 = create(:budget_investment, :feasible, heading: heading) + investment3 = create(:budget_investment, heading: heading) + investment4 = create(:budget_investment, :feasible, heading: heading) visit budget_investments_path(budget_id: budget.id, unfeasible: 1) @@ -99,7 +99,7 @@ feature 'Budget Investments' do scenario 'Random order maintained with pagination', :js do per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:budget_investment, budget: budget) } + (per_page + 2).times { create(:budget_investment, heading: heading) } visit budget_investments_path(budget_id: budget.id) @@ -116,9 +116,9 @@ feature 'Budget Investments' do end scenario 'Proposals are ordered by confidence_score', :js do - create(:budget_investment, budget: budget, title: 'Best proposal').update_column(:confidence_score, 10) - create(:budget_investment, budget: budget, title: 'Worst proposal').update_column(:confidence_score, 2) - create(:budget_investment, budget: budget, title: 'Medium proposal').update_column(:confidence_score, 5) + create(:budget_investment, heading: heading, title: 'Best proposal').update_column(:confidence_score, 10) + create(:budget_investment, heading: heading, title: 'Worst proposal').update_column(:confidence_score, 2) + create(:budget_investment, heading: heading, title: 'Medium proposal').update_column(:confidence_score, 5) visit budget_investments_path(budget_id: budget.id) click_link 'highest rated' @@ -135,10 +135,10 @@ feature 'Budget Investments' do end - xscenario 'Create with invisible_captcha honeypot field' do + scenario 'Create with invisible_captcha honeypot field' do login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + fill_in 'investment_title', with: 'I am a bot' fill_in 'investment_subtitle', with: 'This is the honeypot' fill_in 'investment_description', with: 'This is the description' @@ -203,7 +203,7 @@ feature 'Budget Investments' do user = create(:user) login_as(user) - investment = create(:budget_investment, heading: create(:budget_heading), budget: budget) + investment = create(:budget_investment, heading: create(:budget_heading), heading: heading) visit budget_investment_path(budget_id: budget.id, id: investment.id) @@ -223,7 +223,7 @@ feature 'Budget Investments' do investment = create(:budget_investment, :feasible, :finished, - budget: budget, + heading: heading, price: 16, price_explanation: 'Every wheel is 4 euros, so total is 16') @@ -240,7 +240,7 @@ feature 'Budget Investments' do investment = create(:budget_investment, :unfeasible, :finished, - budget: budget, + heading: heading, unfeasibility_explanation: 'Local government is not competent in this matter') visit budget_investment_path(budget_id: budget.id, id: investment.id) @@ -254,7 +254,7 @@ feature 'Budget Investments' do xscenario "Admin cannot destroy spending proposals" do admin = create(:administrator) user = create(:user, :level_two) - investment = create(:budget_investment, budget: budget, author: user) + investment = create(:budget_investment, heading: heading, author: user) login_as(admin.user) visit user_path(user) @@ -270,7 +270,7 @@ feature 'Budget Investments' do scenario "Spending proposal created by a User" do user = create(:user) - user_investment = create(:budget_investment, budget: budget) + user_investment = create(:budget_investment, heading: heading) visit budget_investment_path(budget_id: budget.id, id: user_investment.id) expect(page).to_not have_css "is-forum" @@ -291,8 +291,8 @@ feature 'Budget Investments' do xscenario "Index" do user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000) - sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 20000) + sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) + sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 20000) login_as(user) visit root_path @@ -313,9 +313,9 @@ feature 'Budget Investments' do end xscenario 'Order by cost (only in phase3)' do - create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) - create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) - create(:budget_investment, :feasible, :finished, budget: budget, title: 'Build a skyscraper', price: 20000) + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a skyscraper', price: 20000) visit budget_investments_path(budget_id: budget.id) @@ -333,7 +333,7 @@ feature 'Budget Investments' do scenario "Show" do user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000) + sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) login_as(user) visit root_path @@ -353,15 +353,15 @@ feature 'Budget Investments' do carabanchel = create(:geozone, name: "Carabanchel") new_york = create(:geozone, name: "New York") - carabanchel_heading = create(:budget_heading, budget: budget, geozone: carabanchel, name: carabanchel.name) - new_york_heading = create(:budget_heading, budget: budget, geozone: new_york, name: new_york.name) + carabanchel_heading = create(:budget_heading, heading: heading, geozone: carabanchel, name: carabanchel.name) + new_york_heading = create(:budget_heading, heading: heading, geozone: new_york, name: new_york.name) - sp1 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1, heading: nil) - sp2 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10, heading: nil) - sp3 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100, heading: nil) - sp4 = create(:budget_investment, :feasible, :finished, budget: budget, price: 1000, heading: carabanchel_heading) - sp5 = create(:budget_investment, :feasible, :finished, budget: budget, price: 10000, heading: carabanchel_heading) - sp6 = create(:budget_investment, :feasible, :finished, budget: budget, price: 100000, heading: new_york_heading) + sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 1, heading: nil) + sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10, heading: nil) + sp3 = create(:budget_investment, :feasible, :finished, heading: heading, price: 100, heading: nil) + sp4 = create(:budget_investment, :feasible, :finished, heading: heading, price: 1000, heading: carabanchel_heading) + sp5 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000, heading: carabanchel_heading) + sp6 = create(:budget_investment, :feasible, :finished, heading: heading, price: 100000, heading: new_york_heading) login_as(user) visit root_path From 9003655cccb7a55bdece9d063d9a26bd940165e6 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Fri, 2 Sep 2016 13:10:10 +0200 Subject: [PATCH 099/384] removes duplicate views --- app/views/budget/budgets/_budget.html.erb | 3 - app/views/budget/budgets/index.html.erb | 1 - .../budget/investments/_investment.html.erb | 3 - app/views/budget/investments/index.html.erb | 2 - spec/features/budget/investments_spec.rb | 413 ------------------ 5 files changed, 422 deletions(-) delete mode 100644 app/views/budget/budgets/_budget.html.erb delete mode 100644 app/views/budget/budgets/index.html.erb delete mode 100644 app/views/budget/investments/_investment.html.erb delete mode 100644 app/views/budget/investments/index.html.erb delete mode 100644 spec/features/budget/investments_spec.rb diff --git a/app/views/budget/budgets/_budget.html.erb b/app/views/budget/budgets/_budget.html.erb deleted file mode 100644 index a88d9d181..000000000 --- a/app/views/budget/budgets/_budget.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    <%= budget.name %>
    -
    \ No newline at end of file diff --git a/app/views/budget/budgets/index.html.erb b/app/views/budget/budgets/index.html.erb deleted file mode 100644 index a7f13b347..000000000 --- a/app/views/budget/budgets/index.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render @budgets %> \ No newline at end of file diff --git a/app/views/budget/investments/_investment.html.erb b/app/views/budget/investments/_investment.html.erb deleted file mode 100644 index 40d534067..000000000 --- a/app/views/budget/investments/_investment.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    <%= investment.title %>
    -
    diff --git a/app/views/budget/investments/index.html.erb b/app/views/budget/investments/index.html.erb deleted file mode 100644 index 95f9db31d..000000000 --- a/app/views/budget/investments/index.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -hello budgets! -<%= render partial: 'investment', collection: @investments %> \ No newline at end of file diff --git a/spec/features/budget/investments_spec.rb b/spec/features/budget/investments_spec.rb deleted file mode 100644 index 4093bcb5a..000000000 --- a/spec/features/budget/investments_spec.rb +++ /dev/null @@ -1,413 +0,0 @@ -require 'rails_helper' - -feature 'Budget Investments' do - - let(:author) { create(:user, :level_two, username: 'Isabel') } - let(:budget) { create(:budget) } - let(:group) { create(:budget_group, budget: budget) } - let(:heading) { create(:budget_heading, group: group) } - - scenario 'Index' do - investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), create(:budget_investment, :feasible, heading: heading)] - unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) - - visit budget_investments_path(budget_id: budget.id) - - expect(page).to have_selector('#budget-investments .budget-investment', count: 3) - investments.each do |investment| - within('#budget-investments') do - expect(page).to have_content investment.title - expect(page).to have_css("a[href='#{budget_investment_path(budget_id: budget.id, id: investment.id)}']", text: investment.title) - expect(page).to_not have_content(unfeasible_investment.title) - end - end - end - - context("Search") do - scenario 'Search by text' do - investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty") - investment2 = create(:budget_investment, heading: heading, title: "Schwifty Hello") - investment3 = create(:budget_investment, heading: heading, title: "Do not show me") - - visit budget_investments_path(budget_id: budget.id) - - within(".expanded #search_form") do - fill_in "search", with: "Schwifty" - click_button "Search" - end - - within("#budget-investments") do - expect(page).to have_css('.budget-investment', count: 2) - - expect(page).to have_content(investment1.title) - expect(page).to have_content(investment2.title) - expect(page).to_not have_content(investment3.title) - end - end - end - - context("Filters") do - scenario 'by unfeasibility' do - investment1 = create(:budget_investment, :unfeasible, heading: heading, valuation_finished: true) - investment2 = create(:budget_investment, :feasible, heading: heading) - investment3 = create(:budget_investment, heading: heading) - investment4 = create(:budget_investment, :feasible, heading: heading) - - visit budget_investments_path(budget_id: budget.id, unfeasible: 1) - - within("#budget-investments") do - expect(page).to have_css('.budget-investment', count: 1) - - expect(page).to have_content(investment1.title) - expect(page).to_not have_content(investment2.title) - expect(page).to_not have_content(investment3.title) - expect(page).to_not have_content(investment4.title) - end - end - end - - context("Orders") do - - scenario "Default order is random" do - per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:budget_investment) } - - visit budget_investments_path(budget_id: budget.id) - order = all(".budget-investment h3").collect {|i| i.text } - - visit budget_investments_path(budget_id: budget.id) - new_order = eq(all(".budget-investment h3").collect {|i| i.text }) - - expect(order).to_not eq(new_order) - end - - scenario "Random order after another order" do - per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:budget_investment) } - - visit budget_investments_path(budget_id: budget.id) - click_link "highest rated" - click_link "random" - - order = all(".budget-investment h3").collect {|i| i.text } - - visit budget_investments_path(budget_id: budget.id) - new_order = eq(all(".budget-investment h3").collect {|i| i.text }) - - expect(order).to_not eq(new_order) - end - - scenario 'Random order maintained with pagination', :js do - per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:budget_investment, heading: heading) } - - visit budget_investments_path(budget_id: budget.id) - - order = all(".budget-investment h3").collect {|i| i.text } - - click_link 'Next' - expect(page).to have_content "You're on page 2" - - click_link 'Previous' - expect(page).to have_content "You're on page 1" - - new_order = all(".budget-investment h3").collect {|i| i.text } - expect(order).to eq(new_order) - end - - scenario 'Proposals are ordered by confidence_score', :js do - create(:budget_investment, heading: heading, title: 'Best proposal').update_column(:confidence_score, 10) - create(:budget_investment, heading: heading, title: 'Worst proposal').update_column(:confidence_score, 2) - create(:budget_investment, heading: heading, title: 'Medium proposal').update_column(:confidence_score, 5) - - visit budget_investments_path(budget_id: budget.id) - click_link 'highest rated' - expect(page).to have_selector('a.active', text: 'highest rated') - - within '#budget-investments' do - expect('Best proposal').to appear_before('Medium proposal') - expect('Medium proposal').to appear_before('Worst proposal') - end - - expect(current_url).to include('order=confidence_score') - expect(current_url).to include('page=1') - end - - end - - scenario 'Create with invisible_captcha honeypot field' do - login_as(author) - visit new_budget_investment_path(budget_id: budget.id) - - fill_in 'investment_title', with: 'I am a bot' - fill_in 'investment_subtitle', with: 'This is the honeypot' - fill_in 'investment_description', with: 'This is the description' - select 'All city', from: 'investment_heading_id' - check 'investment_terms_of_service' - - click_button 'Create' - - expect(page.status_code).to eq(200) - expect(page.html).to be_empty - expect(current_path).to eq(budget_investments_path(budget_id: budget.id)) - end - - xscenario 'Create spending proposal too fast' do - allow(InvisibleCaptcha).to receive(:timestamp_threshold).and_return(Float::INFINITY) - - login_as(author) - - visit new_budget_investments_path(budget_id: budget.id) - fill_in 'investment_title', with: 'I am a bot' - fill_in 'investment_description', with: 'This is the description' - select 'All city', from: 'investment_heading_id' - check 'investment_terms_of_service' - - click_button 'Create' - - expect(page).to have_content 'Sorry, that was too quick! Please resubmit' - expect(current_path).to eq(new_budget_investment_path(budget_id: budget.id)) - end - - xscenario 'Create notice' do - login_as(author) - - visit new_budget_investment_path(budget_id: budget.id) - fill_in 'investment_title', with: 'Build a skyscraper' - fill_in 'investment_description', with: 'I want to live in a high tower over the clouds' - fill_in 'investment_external_url', with: 'http://http://skyscraperpage.com/' - select 'All city', from: 'investment_heading_id' - check 'investment_terms_of_service' - - click_button 'Create' - - expect(page).to_not have_content 'Investment project created successfully' - expect(page).to have_content '1 error' - - within "#notice" do - click_link 'My activity' - end - - expect(page).to have_content 'Investment project created successfully' - end - - xscenario 'Errors on create' do - login_as(author) - - visit new_budget_investment_path(budget_id: budget.id) - click_button 'Create' - expect(page).to have_content error_message - end - - scenario "Show" do - user = create(:user) - login_as(user) - - investment = create(:budget_investment, heading: create(:budget_heading), heading: heading) - - visit budget_investment_path(budget_id: budget.id, id: investment.id) - - expect(page).to have_content(investment.title) - expect(page).to have_content(investment.description) - expect(page).to have_content(investment.author.name) - expect(page).to have_content(investment.heading.name) - within("#investment_code") do - expect(page).to have_content(investment.id) - end - end - - scenario "Show (feasible spending proposal)" do - user = create(:user) - login_as(user) - - investment = create(:budget_investment, - :feasible, - :finished, - heading: heading, - price: 16, - price_explanation: 'Every wheel is 4 euros, so total is 16') - - visit budget_investment_path(budget_id: budget.id, id: investment.id) - - expect(page).to have_content("Price explanation") - expect(page).to have_content(investment.price_explanation) - end - - scenario "Show (unfeasible spending proposal)" do - user = create(:user) - login_as(user) - - investment = create(:budget_investment, - :unfeasible, - :finished, - heading: heading, - unfeasibility_explanation: 'Local government is not competent in this matter') - - visit budget_investment_path(budget_id: budget.id, id: investment.id) - - expect(page).to have_content("Unfeasibility explanation") - expect(page).to have_content(investment.unfeasibility_explanation) - end - - context "Destroy" do - - xscenario "Admin cannot destroy spending proposals" do - admin = create(:administrator) - user = create(:user, :level_two) - investment = create(:budget_investment, heading: heading, author: user) - - login_as(admin.user) - visit user_path(user) - - within("#investment_#{investment.id}") do - expect(page).to_not have_link "Delete" - end - end - - end - - context "Badge" do - - scenario "Spending proposal created by a User" do - user = create(:user) - user_investment = create(:budget_investment, heading: heading) - - visit budget_investment_path(budget_id: budget.id, id: user_investment.id) - expect(page).to_not have_css "is-forum" - - visit budget_investments_path(budget_id: budget.id, id: user_investment.id) - within "#budget_investment_#{user_investment.id}" do - expect(page).to_not have_css "is-forum" - end - end - - end - - context "Phase 3 - Final Voting" do - - background do - budget.update(phase: "balloting") - end - - xscenario "Index" do - user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) - sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 20000) - - login_as(user) - visit root_path - - first(:link, "Participatory budgeting").click - click_link budget.name - click_link "No Heading" - - within("#budget_investment_#{sp1.id}") do - expect(page).to have_content sp1.title - expect(page).to have_content "€10,000" - end - - within("#budget_investment_#{sp2.id}") do - expect(page).to have_content sp2.title - expect(page).to have_content "€20,000" - end - end - - xscenario 'Order by cost (only in phase3)' do - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a skyscraper', price: 20000) - - visit budget_investments_path(budget_id: budget.id) - - click_link 'by price' - expect(page).to have_selector('a.active', text: 'by price') - - within '#budget-investments' do - expect('Build a skyscraper').to appear_before('Build a nice house') - expect('Build a nice house').to appear_before('Build an ugly house') - end - - expect(current_url).to include('order=price') - expect(current_url).to include('page=1') - end - - scenario "Show" do - user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) - - login_as(user) - visit root_path - - first(:link, "Participatory budgeting").click - click_link budget.name - click_link "No Heading" - - click_link sp1.title - - expect(page).to have_content "€10,000" - end - - xscenario "Confirm", :js do - user = create(:user, :level_two) - - carabanchel = create(:geozone, name: "Carabanchel") - new_york = create(:geozone, name: "New York") - - carabanchel_heading = create(:budget_heading, heading: heading, geozone: carabanchel, name: carabanchel.name) - new_york_heading = create(:budget_heading, heading: heading, geozone: new_york, name: new_york.name) - - sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 1, heading: nil) - sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10, heading: nil) - sp3 = create(:budget_investment, :feasible, :finished, heading: heading, price: 100, heading: nil) - sp4 = create(:budget_investment, :feasible, :finished, heading: heading, price: 1000, heading: carabanchel_heading) - sp5 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000, heading: carabanchel_heading) - sp6 = create(:budget_investment, :feasible, :finished, heading: heading, price: 100000, heading: new_york_heading) - - login_as(user) - visit root_path - - first(:link, "Participatory budgeting").click - click_link budget.name - click_link "No Heading" - - add_to_ballot(sp1) - add_to_ballot(sp2) - - first(:link, "Participatory budgeting").click - - click_link budget.name - click_link carabanchel.name - - add_to_ballot(sp4) - add_to_ballot(sp5) - - click_link "Check my ballot" - - expect(page).to have_content "You can change your vote at any time until the close of this phase" - - within("#city_wide") do - expect(page).to have_content sp1.title - expect(page).to have_content sp1.price - - expect(page).to have_content sp2.title - expect(page).to have_content sp2.price - - expect(page).to_not have_content sp3.title - expect(page).to_not have_content sp3.price - end - - within("#district_wide") do - expect(page).to have_content sp4.title - expect(page).to have_content "$1,000" - - expect(page).to have_content sp5.title - expect(page).to have_content "$10,000" - - expect(page).to_not have_content sp6.title - expect(page).to_not have_content "$100,000" - end - end - - end - -end From 521b3e1a666832d95a085d6bf3cd811839f5254d Mon Sep 17 00:00:00 2001 From: rgarcia Date: Fri, 2 Sep 2016 13:10:26 +0200 Subject: [PATCH 100/384] refactors specs file structure --- spec/features/budgets/budgets_spec.rb | 25 +- spec/features/budgets/investments_spec.rb | 413 ++++++++++++++++++++++ spec/features/budgets_spec.rb | 21 -- 3 files changed, 427 insertions(+), 32 deletions(-) create mode 100644 spec/features/budgets/investments_spec.rb delete mode 100644 spec/features/budgets_spec.rb diff --git a/spec/features/budgets/budgets_spec.rb b/spec/features/budgets/budgets_spec.rb index 58ee92d0c..14c0d7318 100644 --- a/spec/features/budgets/budgets_spec.rb +++ b/spec/features/budgets/budgets_spec.rb @@ -2,17 +2,20 @@ require 'rails_helper' feature 'Budgets' do - xscenario "Index" do - budget1 = create(:budget) - budget2 = create(:budget) - budget3 = create(:budget) - + scenario 'Index' do + budgets = create_list(:budget, 3) visit budgets_path - - expect(page).to have_css ".budget", count: 3 - expect(page).to have_content budget1.name - expect(page).to have_content budget2.name - expect(page).to have_content budget3.name + budgets.each {|budget| expect(page).to have_link(budget.name)} end -end + scenario 'Show' do + budget = create(:budget) + group = create(:budget_group, budget: budget) + heading = create(:budget_heading, group: group) + + visit budget_path(budget) + + expect(page).to have_content(budget.name) + expect(page).to have_content(heading.name) + end +end \ No newline at end of file diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb new file mode 100644 index 000000000..c3aa0107b --- /dev/null +++ b/spec/features/budgets/investments_spec.rb @@ -0,0 +1,413 @@ +require 'rails_helper' + +feature 'Budget Investments' do + + let(:author) { create(:user, :level_two, username: 'Isabel') } + let(:budget) { create(:budget) } + let(:group) { create(:budget_group, budget: budget) } + let(:heading) { create(:budget_heading, group: group) } + + scenario 'Index' do + investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), create(:budget_investment, :feasible, heading: heading)] + unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) + + visit budget_investments_path(budget_id: budget.id) + + expect(page).to have_selector('#budget-investments .budget-investment', count: 3) + investments.each do |investment| + within('#budget-investments') do + expect(page).to have_content investment.title + expect(page).to have_css("a[href='#{budget_investment_path(budget_id: budget.id, id: investment.id)}']", text: investment.title) + expect(page).to_not have_content(unfeasible_investment.title) + end + end + end + + context("Search") do + scenario 'Search by text' do + investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty") + investment2 = create(:budget_investment, heading: heading, title: "Schwifty Hello") + investment3 = create(:budget_investment, heading: heading, title: "Do not show me") + + visit budget_investments_path(budget_id: budget.id) + + within(".expanded #search_form") do + fill_in "search", with: "Schwifty" + click_button "Search" + end + + within("#budget-investments") do + expect(page).to have_css('.budget-investment', count: 2) + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + expect(page).to_not have_content(investment3.title) + end + end + end + + context("Filters") do + scenario 'by unfeasibility' do + investment1 = create(:budget_investment, :unfeasible, heading: heading, valuation_finished: true) + investment2 = create(:budget_investment, :feasible, heading: heading) + investment3 = create(:budget_investment, heading: heading) + investment4 = create(:budget_investment, :feasible, heading: heading) + + visit budget_investments_path(budget_id: budget.id, unfeasible: 1) + + within("#budget-investments") do + expect(page).to have_css('.budget-investment', count: 1) + + expect(page).to have_content(investment1.title) + expect(page).to_not have_content(investment2.title) + expect(page).to_not have_content(investment3.title) + expect(page).to_not have_content(investment4.title) + end + end + end + + context("Orders") do + + scenario "Default order is random" do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment) } + + visit budget_investments_path(budget_id: budget.id) + order = all(".budget-investment h3").collect {|i| i.text } + + visit budget_investments_path(budget_id: budget.id) + new_order = eq(all(".budget-investment h3").collect {|i| i.text }) + + expect(order).to_not eq(new_order) + end + + scenario "Random order after another order" do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment) } + + visit budget_investments_path(budget_id: budget.id) + click_link "highest rated" + click_link "random" + + order = all(".budget-investment h3").collect {|i| i.text } + + visit budget_investments_path(budget_id: budget.id) + new_order = eq(all(".budget-investment h3").collect {|i| i.text }) + + expect(order).to_not eq(new_order) + end + + scenario 'Random order maintained with pagination', :js do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:budget_investment, heading: heading) } + + visit budget_investments_path(budget_id: budget.id) + + order = all(".budget-investment h3").collect {|i| i.text } + + click_link 'Next' + expect(page).to have_content "You're on page 2" + + click_link 'Previous' + expect(page).to have_content "You're on page 1" + + new_order = all(".budget-investment h3").collect {|i| i.text } + expect(order).to eq(new_order) + end + + scenario 'Proposals are ordered by confidence_score', :js do + create(:budget_investment, heading: heading, title: 'Best proposal').update_column(:confidence_score, 10) + create(:budget_investment, heading: heading, title: 'Worst proposal').update_column(:confidence_score, 2) + create(:budget_investment, heading: heading, title: 'Medium proposal').update_column(:confidence_score, 5) + + visit budget_investments_path(budget_id: budget.id) + click_link 'highest rated' + expect(page).to have_selector('a.active', text: 'highest rated') + + within '#budget-investments' do + expect('Best proposal').to appear_before('Medium proposal') + expect('Medium proposal').to appear_before('Worst proposal') + end + + expect(current_url).to include('order=confidence_score') + expect(current_url).to include('page=1') + end + + end + + xscenario 'Create with invisible_captcha honeypot field' do + login_as(author) + visit new_budget_investment_path(budget_id: budget.id) + + fill_in 'investment_title', with: 'I am a bot' + fill_in 'investment_subtitle', with: 'This is the honeypot' + fill_in 'investment_description', with: 'This is the description' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page.status_code).to eq(200) + expect(page.html).to be_empty + expect(current_path).to eq(budget_investments_path(budget_id: budget.id)) + end + + xscenario 'Create spending proposal too fast' do + allow(InvisibleCaptcha).to receive(:timestamp_threshold).and_return(Float::INFINITY) + + login_as(author) + + visit new_budget_investments_path(budget_id: budget.id) + fill_in 'investment_title', with: 'I am a bot' + fill_in 'investment_description', with: 'This is the description' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page).to have_content 'Sorry, that was too quick! Please resubmit' + expect(current_path).to eq(new_budget_investment_path(budget_id: budget.id)) + end + + xscenario 'Create notice' do + login_as(author) + + visit new_budget_investment_path(budget_id: budget.id) + fill_in 'investment_title', with: 'Build a skyscraper' + fill_in 'investment_description', with: 'I want to live in a high tower over the clouds' + fill_in 'investment_external_url', with: 'http://http://skyscraperpage.com/' + select 'All city', from: 'investment_heading_id' + check 'investment_terms_of_service' + + click_button 'Create' + + expect(page).to_not have_content 'Investment project created successfully' + expect(page).to have_content '1 error' + + within "#notice" do + click_link 'My activity' + end + + expect(page).to have_content 'Investment project created successfully' + end + + xscenario 'Errors on create' do + login_as(author) + + visit new_budget_investment_path(budget_id: budget.id) + click_button 'Create' + expect(page).to have_content error_message + end + + scenario "Show" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, heading: heading) + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content(investment.title) + expect(page).to have_content(investment.description) + expect(page).to have_content(investment.author.name) + expect(page).to have_content(investment.heading.name) + within("#investment_code") do + expect(page).to have_content(investment.id) + end + end + + scenario "Show (feasible spending proposal)" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, + :feasible, + :finished, + heading: heading, + price: 16, + price_explanation: 'Every wheel is 4 euros, so total is 16') + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content("Price explanation") + expect(page).to have_content(investment.price_explanation) + end + + scenario "Show (unfeasible spending proposal)" do + user = create(:user) + login_as(user) + + investment = create(:budget_investment, + :unfeasible, + :finished, + heading: heading, + unfeasibility_explanation: 'Local government is not competent in this matter') + + visit budget_investment_path(budget_id: budget.id, id: investment.id) + + expect(page).to have_content("Unfeasibility explanation") + expect(page).to have_content(investment.unfeasibility_explanation) + end + + context "Destroy" do + + xscenario "Admin cannot destroy spending proposals" do + admin = create(:administrator) + user = create(:user, :level_two) + investment = create(:budget_investment, heading: heading, author: user) + + login_as(admin.user) + visit user_path(user) + + within("#investment_#{investment.id}") do + expect(page).to_not have_link "Delete" + end + end + + end + + context "Badge" do + + scenario "Spending proposal created by a User" do + user = create(:user) + user_investment = create(:budget_investment, heading: heading) + + visit budget_investment_path(budget_id: budget.id, id: user_investment.id) + expect(page).to_not have_css "is-forum" + + visit budget_investments_path(budget_id: budget.id, id: user_investment.id) + within "#budget_investment_#{user_investment.id}" do + expect(page).to_not have_css "is-forum" + end + end + + end + + context "Phase 3 - Final Voting" do + + background do + budget.update(phase: "balloting") + end + + xscenario "Index" do + user = create(:user, :level_two) + sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) + sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 20000) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link budget.name + click_link "No Heading" + + within("#budget_investment_#{sp1.id}") do + expect(page).to have_content sp1.title + expect(page).to have_content "€10,000" + end + + within("#budget_investment_#{sp2.id}") do + expect(page).to have_content sp2.title + expect(page).to have_content "€20,000" + end + end + + xscenario 'Order by cost (only in phase3)' do + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) + create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a skyscraper', price: 20000) + + visit budget_investments_path(budget_id: budget.id) + + click_link 'by price' + expect(page).to have_selector('a.active', text: 'by price') + + within '#budget-investments' do + expect('Build a skyscraper').to appear_before('Build a nice house') + expect('Build a nice house').to appear_before('Build an ugly house') + end + + expect(current_url).to include('order=price') + expect(current_url).to include('page=1') + end + + scenario "Show" do + user = create(:user, :level_two) + sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link budget.name + click_link "No Heading" + + click_link sp1.title + + expect(page).to have_content "€10,000" + end + + xscenario "Confirm", :js do + user = create(:user, :level_two) + + carabanchel = create(:geozone, name: "Carabanchel") + new_york = create(:geozone, name: "New York") + + carabanchel_heading = create(:budget_heading, heading: heading, geozone: carabanchel, name: carabanchel.name) + new_york_heading = create(:budget_heading, heading: heading, geozone: new_york, name: new_york.name) + + sp1 = create(:budget_investment, :feasible, :finished, price: 1, heading: nil) + sp2 = create(:budget_investment, :feasible, :finished, price: 10, heading: nil) + sp3 = create(:budget_investment, :feasible, :finished, price: 100, heading: nil) + sp4 = create(:budget_investment, :feasible, :finished, price: 1000, heading: carabanchel_heading) + sp5 = create(:budget_investment, :feasible, :finished, price: 10000, heading: carabanchel_heading) + sp6 = create(:budget_investment, :feasible, :finished, price: 100000, heading: new_york_heading) + + login_as(user) + visit root_path + + first(:link, "Participatory budgeting").click + click_link budget.name + click_link "No Heading" + + add_to_ballot(sp1) + add_to_ballot(sp2) + + first(:link, "Participatory budgeting").click + + click_link budget.name + click_link carabanchel.name + + add_to_ballot(sp4) + add_to_ballot(sp5) + + click_link "Check my ballot" + + expect(page).to have_content "You can change your vote at any time until the close of this phase" + + within("#city_wide") do + expect(page).to have_content sp1.title + expect(page).to have_content sp1.price + + expect(page).to have_content sp2.title + expect(page).to have_content sp2.price + + expect(page).to_not have_content sp3.title + expect(page).to_not have_content sp3.price + end + + within("#district_wide") do + expect(page).to have_content sp4.title + expect(page).to have_content "$1,000" + + expect(page).to have_content sp5.title + expect(page).to have_content "$10,000" + + expect(page).to_not have_content sp6.title + expect(page).to_not have_content "$100,000" + end + end + + end + +end diff --git a/spec/features/budgets_spec.rb b/spec/features/budgets_spec.rb deleted file mode 100644 index 64f135dec..000000000 --- a/spec/features/budgets_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails_helper' - -feature 'Budgets' do - - scenario 'Index' do - budgets = create_list(:budget, 3) - visit budgets_path - budgets.each {|budget| expect(page).to have_link(budget.name)} - end - - scenario 'Show' do - budget = create(:budget) - group = create(:budget_group, budget: budget) - heading = create(:budget_heading, group: group) - - visit budget_path(budget) - - expect(page).to have_content(budget.name) - expect(page).to have_content(heading.name) - end -end From 1c8290faa9196aa32f2cdc6895a734e0814f786c Mon Sep 17 00:00:00 2001 From: rgarcia Date: Fri, 2 Sep 2016 13:46:12 +0200 Subject: [PATCH 101/384] refactors budget translations --- app/views/budgets/ballots/_ballot.html.erb | 12 +- .../budgets/ballots/_investment.html.erb | 2 +- .../ballots/_investment_for_sidebar.html.erb | 2 +- app/views/budgets/ballots/_remove.html.erb | 2 +- config/locales/budgets.en.yml | 106 ++++++++++++++++++ config/locales/budgets.es.yml | 106 ++++++++++++++++++ config/locales/en.yml | 94 ---------------- config/locales/es.yml | 94 ---------------- 8 files changed, 221 insertions(+), 197 deletions(-) create mode 100644 config/locales/budgets.en.yml create mode 100644 config/locales/budgets.es.yml diff --git a/app/views/budgets/ballots/_ballot.html.erb b/app/views/budgets/ballots/_ballot.html.erb index 5189de92b..a3b0d22f3 100644 --- a/app/views/budgets/ballots/_ballot.html.erb +++ b/app/views/budgets/ballots/_ballot.html.erb @@ -18,7 +18,7 @@ <% end %>

    - <%= t("ballots.show.remaining_city_html", + <%= t("budgets.ballots.show.remaining_city_html", amount_city: format_price(@ballot.amount_available(nil))) %>

    @@ -52,7 +52,7 @@ <% else %>

    <%= t("budgets.ballots.show.zero") %>
    - <%= link_to t("ballots.show.city_link"), + <%= link_to t("budgets.ballots.show.city_link"), investments_path(geozone: 'all'), data: { no_turbolink: true } %>

    @@ -66,7 +66,7 @@

    - <%= t("ballots.show.district_wide") %> + <%= t("budgets.ballots.show.district_wide") %> <% if @ballot.geozone.present? %> (<%= @ballot.geozone.name %>) @@ -75,13 +75,13 @@

    <% if @ballot.geozone.present? %>

    - <%= t("ballots.show.amount_spent") %> + <%= t("budgets.ballots.show.amount_spent") %> <%= format_price(district_wide_amount_spent(@ballot)) %>

    <% else %>

    - <%= t("ballots.show.zero") %>
    - <%= link_to t("budget.ballots.show.districts_link"), select_district_path %> + <%= t("budgets.ballots.show.zero") %>
    + <%= link_to t("budgets.ballots.show.districts_link"), select_district_path %>

    <% end %> diff --git a/app/views/budgets/ballots/_investment.html.erb b/app/views/budgets/ballots/_investment.html.erb index 93c934bd5..af78b4168 100644 --- a/app/views/budgets/ballots/_investment.html.erb +++ b/app/views/budgets/ballots/_investment.html.erb @@ -4,7 +4,7 @@ <% if @budget.balloting? %> <%= link_to ballot_line_path(id: investment.id), - title: t('ballots.show.remove'), + title: t('budgets.ballots.show.remove'), class: "remove-investment-project", method: :delete, remote: true do %> diff --git a/app/views/budgets/ballots/_investment_for_sidebar.html.erb b/app/views/budgets/ballots/_investment_for_sidebar.html.erb index c0bde5d34..c229cc93b 100644 --- a/app/views/budgets/ballots/_investment_for_sidebar.html.erb +++ b/app/views/budgets/ballots/_investment_for_sidebar.html.erb @@ -5,7 +5,7 @@ <% if @budget.balloting? %> <%= link_to ballot_line_path(id: investment.id, investments_ids: investment_ids), - title: t('ballots.show.remove'), + title: t('budgets.ballots.show.remove'), class: "remove-investment-project", method: :delete, remote: true do %> diff --git a/app/views/budgets/ballots/_remove.html.erb b/app/views/budgets/ballots/_remove.html.erb index a02ac9966..5511e60c7 100644 --- a/app/views/budgets/ballots/_remove.html.erb +++ b/app/views/budgets/ballots/_remove.html.erb @@ -8,7 +8,7 @@

    <% if @budget.balloting? %> - <%= link_to t('ballots.show.remove'), + <%= link_to t('budgets.ballots.show.remove'), ballot_line_path(id: investment.id, investments_ids: investment_ids), class: "delete small expanded", diff --git a/config/locales/budgets.en.yml b/config/locales/budgets.en.yml new file mode 100644 index 000000000..ae8c6b08a --- /dev/null +++ b/config/locales/budgets.en.yml @@ -0,0 +1,106 @@ +en: + budgets: + ballots: + show: + amount_spent: "pending translation" + city_wide: "pending translation" + districts_link: "pending translation" + remaining_district_html: "pending translation" + social_share: "pending translation" + title: "pending translation" + voted_html: "pending translation" + voted_info_html: "pending translation" + zero: "pending translation" + budget: + phase: + on_hold: On hold + accepting: Accepting proposals + selecting: Selecting + balloting: Balloting + finished: Finished + headings: + none: Whole City + all: All scopes + index: + name: Budget's name + phase: Phase + title: Participatory budgets + investments: + form: + association_name_label: 'If you propose in name of an assocation or collective add the name here' + association_name: 'Association name' + description: Description + external_url: Link to additional documentation + heading: Choose if a proposed citywide or district + submit_buttons: + create: Create + new: Create + title: Investment title + index: + available: "Available:" + title: Participatory budgeting + unfeasible: Unfeasible investment projects + unfeasible_text: "The proposals must meet a number of criteria (legality, concreteness, be the responsibility of the city, not exceed the limit of the budget; %{definitions}) to be declared viable and reach the stage of final vote. All proposals don't meet these criteria are marked as unfeasible and published in the following list, along with its report of infeasibility." + unfeasible_text_definitions: see definitions here + by_heading: "Investment projects with scope: %{heading}" + search_form: + button: Search + placeholder: Investment projects... + title: Search + search_results: + one: " containing the term '%{search_term}'" + other: " containing the term '%{search_term}'" + sidebar: + back: Back to select page + district: District + my_ballot: My ballot + remember_city: You can also vote %{city} investment projects. + remember_city_link_html: city-wide + remember_district: You can also vote investment projects for %{district}. + remember_district_link_html: a district + voted_html: + one: "You voted one proposal with a cost of %{amount_spent}" + other: "You voted %{count} proposals with a cost of %{amount_spent}" + voted_info: You can change your vote at any time until the close of this phase. No need to spend all the money available. + votes: Supports remaining + votes_district: "You can only vote in the district %{district}" + zero: You have not voted any investment project. + orders: + random: random + confidence_score: highest rated + price: by price + new: + back_link: Back + more_info: "Important, not to be ruled out your proposal must comply:" + recommendation_one: See the %{requirements}. + recommendation_one_link: requirements to be met by a proposal + recommendation_three: Try to go into details when describing your spending proposal so the reviewing team undertands your points. + recommendation_two: Each proposal must be submitted separately. You can make as many want. + recommendations_title: How to create a spending proposal + start_new: Create spending proposal + show: + author_deleted: User deleted + price_explanation: Price explanation + unfeasibility_explanation: Unfeasibility explanation + code: 'Investment project code:' + share: Share + wrong_price_format: Only integer numbers + investment: + title: Investment project + add: Add + already_added: You have already added this investment project + already_supported: You have already supported this. Share it! + forum: District discussion space + support_title: Support this project + supports: + one: 1 support + other: "%{count} supports" + zero: No supports + vote: Vote + header: + check_ballot: Check my ballot + different_heading_active: You have active votes in another district. + show: + heading: Heading + price: Price + no_heading: No Heading \ No newline at end of file diff --git a/config/locales/budgets.es.yml b/config/locales/budgets.es.yml new file mode 100644 index 000000000..667b39617 --- /dev/null +++ b/config/locales/budgets.es.yml @@ -0,0 +1,106 @@ +es: + budgets: + ballots: + show: + amount_spent: "pending translation" + city_wide: "pending translation" + districts_link: "pending translation" + remaining_district_html: "pending translation" + social_share: "pending translation" + title: "pending translation" + voted_html: "pending translation" + voted_info_html: "pending translation" + zero: "pending translation" + budget: + phase: + on_hold: En pausa + accepting: Aceptando propuestas + selecting: Fase de selección + balloting: Fase de Votación + finished: Terminado + headings: + none: Toda la ciudad + all: Todos los ámbitos + index: + name: Nombre del presupuesto + phase: Fase + title: Presupuestos participativos + investments: + form: + association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' + association_name: 'Nombre de la asociación' + description: Descripción detallada + external_url: Enlace a documentación adicional + heading: "Elige si es una propuesta para toda la ciudad o para un distrito" + submit_buttons: + create: Crear + new: Crear + title: Título de la propuesta de inversión + index: + available: "Disponible:" + title: Presupuestos participativos + unfeasible: Propuestas de inversión no viables + unfeasible_text: Las propuestas presentadas deben cumplir una serie de criterios (legalidad, concreción, ser competencia del Ayuntamiento, no superar el tope del presupuesto; %{definitions}) para ser declaradas viables y llegar hasta la fase de votación final. Todas las propuestas que no cumplen estos criterios son marcadas como inviables y publicadas en la siguiente lista, junto con su informe de inviabilidad. + unfeasible_text_definitions: ver definiciones aquí + by_heading: "Propuestas de inversión con ámbito: %{heading}" + search_form: + button: Buscar + placeholder: Propuestas de inversión... + title: Buscar + search_results: + one: " que contiene '%{search_term}'" + other: " que contienen '%{search_term}'" + sidebar: + back: Volver a página de selección + district: Distrito + my_ballot: Mis votos + remember_city: Además puedes votar propuestas de inversión para %{city}. + remember_city_link_html: toda la ciudad + remember_district: Además puedes votar propuestas de inversión para %{district}. + remember_district_link_html: un distrito + voted_html: + one: "Has votado una propuesta por un valor de %{amount_spent}" + other: "Has votado %{count} propuestas por un valor de %{amount_spent}" + voted_info: Puedes cambiar tus votos en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible. + votes: Apoyos restantes + votes_district: "Solo puedes votar en el distrito %{district}" + zero: "Todavía no has votado ninguna propuesta de inversión." + orders: + random: Aleatorias + confidence_score: Mejor valoradas + price: Por coste + new: + more_info: "¿Cómo funcionan los presupuestos participativos?" + recommendation_one: Consulta los %{requirements}. + recommendation_one_link: requisitos que debe cumplir una propuesta + recommendation_three: Intenta detallar lo máximo posible la propuesta para que el equipo de gobierno encargado de estudiarla tenga las menor dudas posibles. + recommendation_two: Cualquier propuesta o comentario que implique acciones ilegales será eliminada. + recommendations_title: Cómo crear una propuesta de inversión + start_new: Crear una propuesta de inversión + back_link: Volver + show: + author_deleted: Usuario eliminado + price_explanation: Informe de coste + unfeasibility_explanation: Informe de inviabilidad + code: 'Código propuesta de gasto:' + share: Compartir + wrong_price_format: Solo puede incluir caracteres numéricos + investment: + title: Propuesta de inversión + add: Añadir + already_added: "Ya has añadido esta propuesta de inversión" + already_supported: Ya has apoyado este proyecto. ¡Compártelo! + forum: Espacio de debate distrital + support_title: Apoyar este proyecto + supports: + one: 1 apoyo + other: "%{count} apoyos" + zero: Sin apoyos + vote: Votar + header: + check_ballot: Revisar mis votos + different_heading_active: Ya apoyaste propuestas de otro distrito. + show: + heading: Partida + price: Cantidad + no_heading: Sin línea \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index d20c69885..58d4c9492 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,13 +33,6 @@ en: application: close: Close menu: Menu - budget: - phase: - on_hold: On hold - accepting: Accepting proposals - selecting: Selecting - balloting: Balloting - finished: Finished comments: comment: admin: Administrator @@ -447,93 +440,6 @@ en: facebook: Facebook twitter: Twitter youtube: YouTube - budget: - headings: - none: Whole City - all: All scopes - index: - name: Budget's name - phase: Phase - title: Participatory budgets - investments: - form: - association_name_label: 'If you propose in name of an assocation or collective add the name here' - association_name: 'Association name' - description: Description - external_url: Link to additional documentation - heading: Choose if a proposed citywide or district - submit_buttons: - create: Create - new: Create - title: Investment title - index: - available: "Available:" - title: Participatory budgeting - unfeasible: Unfeasible investment projects - unfeasible_text: "The proposals must meet a number of criteria (legality, concreteness, be the responsibility of the city, not exceed the limit of the budget; %{definitions}) to be declared viable and reach the stage of final vote. All proposals don't meet these criteria are marked as unfeasible and published in the following list, along with its report of infeasibility." - unfeasible_text_definitions: see definitions here - by_heading: "Investment projects with scope: %{heading}" - search_form: - button: Search - placeholder: Investment projects... - title: Search - search_results: - one: " containing the term '%{search_term}'" - other: " containing the term '%{search_term}'" - sidebar: - back: Back to select page - district: District - my_ballot: My ballot - remember_city: You can also vote %{city} investment projects. - remember_city_link_html: city-wide - remember_district: You can also vote investment projects for %{district}. - remember_district_link_html: a district - voted_html: - one: "You voted one proposal with a cost of %{amount_spent}" - other: "You voted %{count} proposals with a cost of %{amount_spent}" - voted_info: You can change your vote at any time until the close of this phase. No need to spend all the money available. - votes: Supports remaining - votes_district: "You can only vote in the district %{district}" - zero: You have not voted any investment project. - orders: - random: random - confidence_score: highest rated - price: by price - new: - back_link: Back - more_info: "Important, not to be ruled out your proposal must comply:" - recommendation_one: See the %{requirements}. - recommendation_one_link: requirements to be met by a proposal - recommendation_three: Try to go into details when describing your spending proposal so the reviewing team undertands your points. - recommendation_two: Each proposal must be submitted separately. You can make as many want. - recommendations_title: How to create a spending proposal - start_new: Create spending proposal - show: - author_deleted: User deleted - price_explanation: Price explanation - unfeasibility_explanation: Unfeasibility explanation - code: 'Investment project code:' - share: Share - wrong_price_format: Only integer numbers - investment: - title: Investment project - add: Add - already_added: You have already added this investment project - already_supported: You have already supported this. Share it! - forum: District discussion space - support_title: Support this project - supports: - one: 1 support - other: "%{count} supports" - zero: No supports - vote: Vote - header: - check_ballot: Check my ballot - different_heading_active: You have active votes in another district. - show: - heading: Heading - price: Price - no_heading: No Heading spending_proposals: form: association_name_label: 'If you propose in name of an assocation or collective add the name here' diff --git a/config/locales/es.yml b/config/locales/es.yml index 62b1f8059..5e3f28507 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -33,13 +33,6 @@ es: application: close: Cerrar menu: Menú - budget: - phase: - on_hold: En pausa - accepting: Aceptando propuestas - selecting: Fase de selección - balloting: Fase de Votación - finished: Terminado comments: comment: admin: Administrador @@ -447,93 +440,6 @@ es: facebook: Facebook twitter: Twitter youtube: YouTube - budget: - headings: - none: Toda la ciudad - all: Todos los ámbitos - index: - name: Nombre del presupuesto - phase: Fase - title: Presupuestos participativos - investments: - form: - association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' - association_name: 'Nombre de la asociación' - description: Descripción detallada - external_url: Enlace a documentación adicional - heading: "Elige si es una propuesta para toda la ciudad o para un distrito" - submit_buttons: - create: Crear - new: Crear - title: Título de la propuesta de inversión - index: - available: "Disponible:" - title: Presupuestos participativos - unfeasible: Propuestas de inversión no viables - unfeasible_text: Las propuestas presentadas deben cumplir una serie de criterios (legalidad, concreción, ser competencia del Ayuntamiento, no superar el tope del presupuesto; %{definitions}) para ser declaradas viables y llegar hasta la fase de votación final. Todas las propuestas que no cumplen estos criterios son marcadas como inviables y publicadas en la siguiente lista, junto con su informe de inviabilidad. - unfeasible_text_definitions: ver definiciones aquí - by_heading: "Propuestas de inversión con ámbito: %{heading}" - search_form: - button: Buscar - placeholder: Propuestas de inversión... - title: Buscar - search_results: - one: " que contiene '%{search_term}'" - other: " que contienen '%{search_term}'" - sidebar: - back: Volver a página de selección - district: Distrito - my_ballot: Mis votos - remember_city: Además puedes votar propuestas de inversión para %{city}. - remember_city_link_html: toda la ciudad - remember_district: Además puedes votar propuestas de inversión para %{district}. - remember_district_link_html: un distrito - voted_html: - one: "Has votado una propuesta por un valor de %{amount_spent}" - other: "Has votado %{count} propuestas por un valor de %{amount_spent}" - voted_info: Puedes cambiar tus votos en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible. - votes: Apoyos restantes - votes_district: "Solo puedes votar en el distrito %{district}" - zero: "Todavía no has votado ninguna propuesta de inversión." - orders: - random: Aleatorias - confidence_score: Mejor valoradas - price: Por coste - new: - more_info: "¿Cómo funcionan los presupuestos participativos?" - recommendation_one: Consulta los %{requirements}. - recommendation_one_link: requisitos que debe cumplir una propuesta - recommendation_three: Intenta detallar lo máximo posible la propuesta para que el equipo de gobierno encargado de estudiarla tenga las menor dudas posibles. - recommendation_two: Cualquier propuesta o comentario que implique acciones ilegales será eliminada. - recommendations_title: Cómo crear una propuesta de inversión - start_new: Crear una propuesta de inversión - back_link: Volver - show: - author_deleted: Usuario eliminado - price_explanation: Informe de coste - unfeasibility_explanation: Informe de inviabilidad - code: 'Código propuesta de gasto:' - share: Compartir - wrong_price_format: Solo puede incluir caracteres numéricos - investment: - title: Propuesta de inversión - add: Añadir - already_added: "Ya has añadido esta propuesta de inversión" - already_supported: Ya has apoyado este proyecto. ¡Compártelo! - forum: Espacio de debate distrital - support_title: Apoyar este proyecto - supports: - one: 1 apoyo - other: "%{count} apoyos" - zero: Sin apoyos - vote: Votar - header: - check_ballot: Revisar mis votos - different_heading_active: Ya apoyaste propuestas de otro distrito. - show: - heading: Partida - price: Cantidad - no_heading: Sin línea spending_proposals: form: association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' From 9570d79ee02e214678936b137d783c1635c1f07b Mon Sep 17 00:00:00 2001 From: rgarcia Date: Fri, 2 Sep 2016 13:46:32 +0200 Subject: [PATCH 102/384] ignores missing and unused budget i18n keys --- config/i18n-tasks.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index e24d3af0c..7df6f7f9e 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -94,6 +94,8 @@ search: # - 'errors.messages.{accepted,blank,invalid,too_short,too_long}' # - '{devise,simple_form}.*' ignore_missing: + - 'budget.*' + - 'budgets.*' - 'unauthorized.*' - 'activerecord.errors.models.proposal_notification.*' - 'activerecord.errors.models.direct_message.*' @@ -104,6 +106,8 @@ ignore_missing: ## Consider these keys used: ignore_unused: + - 'budget.*' + - 'budgets.*' - 'activerecord.*' - 'activemodel.*' - 'unauthorized.*' From ee2e0b864f6efbd9fbb143a7e7bac48f48cfe775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 5 Sep 2016 11:56:53 +0200 Subject: [PATCH 103/384] denormalizes investments --- db/migrate/20160905092539_denormalize_investments.rb | 6 ++++++ db/schema.rb | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160905092539_denormalize_investments.rb diff --git a/db/migrate/20160905092539_denormalize_investments.rb b/db/migrate/20160905092539_denormalize_investments.rb new file mode 100644 index 000000000..d28291c4e --- /dev/null +++ b/db/migrate/20160905092539_denormalize_investments.rb @@ -0,0 +1,6 @@ +class DenormalizeInvestments < ActiveRecord::Migration + def change + add_column :budget_investments, :budget_id, :integer, index: true + add_column :budget_investments, :group_id, :integer, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index ff2a0f590..982f850d3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160803154011) do +ActiveRecord::Schema.define(version: 20160905092539) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -139,6 +139,8 @@ ActiveRecord::Schema.define(version: 20160803154011) do t.datetime "updated_at", null: false t.integer "heading_id" t.string "responsible_name" + t.integer "budget_id" + t.integer "group_id" end add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree @@ -210,10 +212,10 @@ ActiveRecord::Schema.define(version: 20160803154011) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 - t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 From 6d06a59403aecc18e15b539c0cf2f093df9aa35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Mon, 5 Sep 2016 13:01:46 +0200 Subject: [PATCH 104/384] uses direct associations when managing investments --- app/models/budget/investment.rb | 18 +++++------------- spec/factories.rb | 4 +++- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 890175035..f76d1f68e 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -12,6 +12,8 @@ class Budget belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :heading + belongs_to :group + belongs_to :budget belongs_to :administrator has_many :valuator_assignments, dependent: :destroy @@ -58,16 +60,14 @@ class Budget end def self.scoped_filter(params, current_filter) - results = budget.investments - if params[:max_for_no_heading].present? || params[:max_per_heading].present? - results = limit_results(results, budget, params[:max_per_heading].to_i, params[:max_for_no_heading].to_i) - end + results = Investment.where(budget_id: params[:budget_id]) + results = results.where(group_id: params[:group_id]) if params[:group_id].present? results = results.by_heading(params[:heading_id]) if params[:heading_id].present? results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present? results = results.by_tag(params[:tag_name]) if params[:tag_name].present? results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present? results = results.send(current_filter) if current_filter.present? - results.includes(:heading, administrator: :user, valuators: :user) + results.includes(:heading, :group, :budget, administrator: :user, valuators: :user) end def self.limit_results(results, budget, max_per_heading, max_for_no_heading) @@ -114,14 +114,6 @@ class Budget where(heading_id: heading == 'all' ? nil : heading.presence) end - def budget - heading.group.budget - end - - def budget=(resource) - heading.group.budget = resource - end - def undecided? feasibility == "undecided" end diff --git a/spec/factories.rb b/spec/factories.rb index db694b4ce..243e3a928 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -223,7 +223,9 @@ FactoryGirl.define do factory :budget_investment, class: 'Budget::Investment' do sequence(:title) { |n| "Budget Investment #{n} title" } - association :heading, factory: :budget_heading + budget + group { create :budget_group, budget: budget } + heading { create :budget_heading, group: group } association :author, factory: :user description 'Spend money on this' price 10 From 40cdeb568ed2fa9bf28d9fd4c05c168b77efbea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 5 Sep 2016 13:11:57 +0200 Subject: [PATCH 105/384] adds missing associations to dev_seeds --- db/dev_seeds.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 7e8a6a54b..16be19f67 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -330,6 +330,8 @@ tags = Faker::Lorem.words(10) investment = Budget::Investment.create!( author: User.reorder("RANDOM()").first, heading: heading, + group: heading.group, + budget: heading.group.budget, title: Faker::Lorem.sentence(3).truncate(60), external_url: Faker::Internet.url, description: "

    #{Faker::Lorem.paragraphs.join('

    ')}

    ", From 3964988bb11c736e635162c936fff921c372b7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 5 Sep 2016 13:50:25 +0200 Subject: [PATCH 106/384] fixes specs updates investment to have group_id & budget_id --- spec/features/budgets/investments_spec.rb | 56 +++++++++++---------- spec/models/abilities/administrator_spec.rb | 10 +--- spec/models/abilities/common_spec.rb | 12 ++--- spec/models/abilities/valuator_spec.rb | 10 +--- spec/models/budget/investment_spec.rb | 26 +++++----- 5 files changed, 50 insertions(+), 64 deletions(-) diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb index c3aa0107b..cdf8694bc 100644 --- a/spec/features/budgets/investments_spec.rb +++ b/spec/features/budgets/investments_spec.rb @@ -8,8 +8,8 @@ feature 'Budget Investments' do let(:heading) { create(:budget_heading, group: group) } scenario 'Index' do - investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), create(:budget_investment, :feasible, heading: heading)] - unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) + investments = [create(:budget_investment, budget: budget, group: group, heading: heading), create(:budget_investment, budget: budget, group: group, heading: heading), create(:budget_investment, :feasible, budget: budget, group: group, heading: heading)] + unfeasible_investment = create(:budget_investment, :unfeasible, budget: budget, group: group, heading: heading) visit budget_investments_path(budget_id: budget.id) @@ -25,9 +25,9 @@ feature 'Budget Investments' do context("Search") do scenario 'Search by text' do - investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty") - investment2 = create(:budget_investment, heading: heading, title: "Schwifty Hello") - investment3 = create(:budget_investment, heading: heading, title: "Do not show me") + investment1 = create(:budget_investment, budget: budget, group: group, heading: heading, title: "Get Schwifty") + investment2 = create(:budget_investment, budget: budget, group: group, heading: heading, title: "Schwifty Hello") + investment3 = create(:budget_investment, budget: budget, group: group, heading: heading, title: "Do not show me") visit budget_investments_path(budget_id: budget.id) @@ -48,10 +48,10 @@ feature 'Budget Investments' do context("Filters") do scenario 'by unfeasibility' do - investment1 = create(:budget_investment, :unfeasible, heading: heading, valuation_finished: true) - investment2 = create(:budget_investment, :feasible, heading: heading) - investment3 = create(:budget_investment, heading: heading) - investment4 = create(:budget_investment, :feasible, heading: heading) + investment1 = create(:budget_investment, :unfeasible, budget: budget, group: group, heading: heading, valuation_finished: true) + investment2 = create(:budget_investment, :feasible, budget: budget, group: group, heading: heading) + investment3 = create(:budget_investment, budget: budget, group: group, heading: heading) + investment4 = create(:budget_investment, :feasible, budget: budget, group: group, heading: heading) visit budget_investments_path(budget_id: budget.id, unfeasible: 1) @@ -99,7 +99,7 @@ feature 'Budget Investments' do scenario 'Random order maintained with pagination', :js do per_page = Kaminari.config.default_per_page - (per_page + 2).times { create(:budget_investment, heading: heading) } + (per_page + 2).times { create(:budget_investment, budget: budget, group: group, heading: heading) } visit budget_investments_path(budget_id: budget.id) @@ -116,9 +116,9 @@ feature 'Budget Investments' do end scenario 'Proposals are ordered by confidence_score', :js do - create(:budget_investment, heading: heading, title: 'Best proposal').update_column(:confidence_score, 10) - create(:budget_investment, heading: heading, title: 'Worst proposal').update_column(:confidence_score, 2) - create(:budget_investment, heading: heading, title: 'Medium proposal').update_column(:confidence_score, 5) + create(:budget_investment, budget: budget, group: group, heading: heading, title: 'Best proposal').update_column(:confidence_score, 10) + create(:budget_investment, budget: budget, group: group, heading: heading, title: 'Worst proposal').update_column(:confidence_score, 2) + create(:budget_investment, budget: budget, group: group, heading: heading, title: 'Medium proposal').update_column(:confidence_score, 5) visit budget_investments_path(budget_id: budget.id) click_link 'highest rated' @@ -203,7 +203,7 @@ feature 'Budget Investments' do user = create(:user) login_as(user) - investment = create(:budget_investment, heading: heading) + investment = create(:budget_investment, budget: budget, group: group, heading: heading) visit budget_investment_path(budget_id: budget.id, id: investment.id) @@ -223,6 +223,8 @@ feature 'Budget Investments' do investment = create(:budget_investment, :feasible, :finished, + budget: budget, + group: group, heading: heading, price: 16, price_explanation: 'Every wheel is 4 euros, so total is 16') @@ -240,6 +242,8 @@ feature 'Budget Investments' do investment = create(:budget_investment, :unfeasible, :finished, + budget: budget, + group: group, heading: heading, unfeasibility_explanation: 'Local government is not competent in this matter') @@ -254,7 +258,7 @@ feature 'Budget Investments' do xscenario "Admin cannot destroy spending proposals" do admin = create(:administrator) user = create(:user, :level_two) - investment = create(:budget_investment, heading: heading, author: user) + investment = create(:budget_investment, budget: budget, group: group, heading: heading, author: user) login_as(admin.user) visit user_path(user) @@ -270,7 +274,7 @@ feature 'Budget Investments' do scenario "Spending proposal created by a User" do user = create(:user) - user_investment = create(:budget_investment, heading: heading) + user_investment = create(:budget_investment, budget: budget, group: group, heading: heading) visit budget_investment_path(budget_id: budget.id, id: user_investment.id) expect(page).to_not have_css "is-forum" @@ -291,8 +295,8 @@ feature 'Budget Investments' do xscenario "Index" do user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) - sp2 = create(:budget_investment, :feasible, :finished, heading: heading, price: 20000) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, price: 10000) + sp2 = create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, price: 20000) login_as(user) visit root_path @@ -313,9 +317,9 @@ feature 'Budget Investments' do end xscenario 'Order by cost (only in phase3)' do - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) - create(:budget_investment, :feasible, :finished, heading: heading, title: 'Build a skyscraper', price: 20000) + create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, title: 'Build a nice house', price: 1000).update_column(:confidence_score, 10) + create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, title: 'Build an ugly house', price: 1000).update_column(:confidence_score, 5) + create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, title: 'Build a skyscraper', price: 20000) visit budget_investments_path(budget_id: budget.id) @@ -333,7 +337,7 @@ feature 'Budget Investments' do scenario "Show" do user = create(:user, :level_two) - sp1 = create(:budget_investment, :feasible, :finished, heading: heading, price: 10000) + sp1 = create(:budget_investment, :feasible, :finished, budget: budget, group: group, heading: heading, price: 10000) login_as(user) visit root_path @@ -354,14 +358,14 @@ feature 'Budget Investments' do new_york = create(:geozone, name: "New York") carabanchel_heading = create(:budget_heading, heading: heading, geozone: carabanchel, name: carabanchel.name) - new_york_heading = create(:budget_heading, heading: heading, geozone: new_york, name: new_york.name) + new_york_heading = create(:budget_heading, heading: heading, geozone: new_york, name: new_york.name) sp1 = create(:budget_investment, :feasible, :finished, price: 1, heading: nil) sp2 = create(:budget_investment, :feasible, :finished, price: 10, heading: nil) sp3 = create(:budget_investment, :feasible, :finished, price: 100, heading: nil) - sp4 = create(:budget_investment, :feasible, :finished, price: 1000, heading: carabanchel_heading) - sp5 = create(:budget_investment, :feasible, :finished, price: 10000, heading: carabanchel_heading) - sp6 = create(:budget_investment, :feasible, :finished, price: 100000, heading: new_york_heading) + sp4 = create(:budget_investment, :feasible, :finished, price: 1000, budget: budget, group: group, heading: carabanchel_heading) + sp5 = create(:budget_investment, :feasible, :finished, price: 10000, budget: budget, group: group, heading: carabanchel_heading) + sp6 = create(:budget_investment, :feasible, :finished, price: 100000, budget: budget, group: group, heading: new_york_heading) login_as(user) visit root_path diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index d2313dea5..8170d3434 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -65,13 +65,7 @@ describe "Abilities::Administrator" do it { should be_able_to(:update, Budget::Investment) } it { should be_able_to(:hide, Budget::Investment) } - it { should be_able_to(:valuate, create(:budget_investment, - heading: create(:budget_heading, - group: create(:budget_group, - budget: create(:budget, valuating: true))))) } - it { should_not be_able_to(:valuate, create(:budget_investment, - heading: create(:budget_heading, - group: create(:budget_group, - budget: create(:budget, valuating: false))))) } + it { should be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: true))) } + it { should_not be_able_to(:valuate, create(:budget_investment, budget: create(:budget, valuating: false))) } end diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index a115d36c2..2a30d44a5 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -12,16 +12,10 @@ describe "Abilities::Common" do let(:accepting_budget) { create(:budget, phase: 'accepting') } let(:selecting_budget) { create(:budget, phase: 'selecting') } let(:balloting_budget) { create(:budget, phase: 'balloting') } - let(:accepting_budget_group) { create(:budget_group, budget: accepting_budget) } - let(:selecting_budget_group) { create(:budget_group, budget: selecting_budget) } - let(:balloting_budget_group) { create(:budget_group, budget: balloting_budget) } - let(:accepting_budget_heading) { create(:budget_heading, group: accepting_budget_group) } - let(:selecting_budget_heading) { create(:budget_heading, group: selecting_budget_group) } - let(:balloting_budget_heading) { create(:budget_heading, group: balloting_budget_group) } - let(:investment_in_accepting_budget) { create(:budget_investment, heading: accepting_budget_heading) } - let(:investment_in_selecting_budget) { create(:budget_investment, heading: selecting_budget_heading) } - let(:investment_in_balloting_budget) { create(:budget_investment, heading: balloting_budget_heading) } + let(:investment_in_accepting_budget) { create(:budget_investment, budget: accepting_budget) } + let(:investment_in_selecting_budget) { create(:budget_investment, budget: selecting_budget) } + let(:investment_in_balloting_budget) { create(:budget_investment, budget: balloting_budget) } let(:ballot_in_accepting_budget) { create(:budget_ballot, budget: accepting_budget) } let(:ballot_in_selecting_budget) { create(:budget_ballot, budget: selecting_budget) } let(:ballot_in_balloting_budget) { create(:budget_ballot, budget: balloting_budget) } diff --git a/spec/models/abilities/valuator_spec.rb b/spec/models/abilities/valuator_spec.rb index add938a9d..9fa285f19 100644 --- a/spec/models/abilities/valuator_spec.rb +++ b/spec/models/abilities/valuator_spec.rb @@ -7,16 +7,10 @@ describe "Abilities::Valuator" do let(:valuator) { create(:valuator) } let(:non_assigned_investment) { create(:budget_investment) } - let(:assigned_investment) { create(:budget_investment, - heading: create(:budget_heading, - group: create(:budget_group, - budget: create(:budget, valuating: true)))) } + let(:assigned_investment) { create(:budget_investment, budget: create(:budget, valuating: true)) } before(:each) { assigned_investment.valuators << valuator } - let(:assigned_investment_not_valuating) { create(:budget_investment, - heading: create(:budget_heading, - group: create(:budget_group, - budget: create(:budget, valuating: false)))) } + let(:assigned_investment_not_valuating) { create(:budget_investment, budget: create(:budget, valuating: false)) } before(:each) { assigned_investment_not_valuating.valuators << valuator } it { should be_able_to(:read, SpendingProposal) } diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index d3dbf0d0b..6b98025ad 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -194,7 +194,7 @@ describe Budget::Investment do let(:heading) { create(:budget_heading, group: group) } let(:user) { create(:user, :level_two) } let(:luser) { create(:user) } - let(:district_sp) { create(:budget_investment, heading: heading) } + let(:district_sp) { create(:budget_investment, budget: budget, group: group, heading: heading) } describe '#reason_for_not_being_selectable_by' do it "rejects not logged in users" do @@ -271,13 +271,13 @@ describe Budget::Investment do b = create(:budget, :selecting) g = create(:budget_group, budget: b) h = create(:budget_heading, group: g) - sp = create(:budget_investment, heading: h) + i = create(:budget_investment, budget: b, group: g, heading: h) - sp.register_selection(create(:user, :level_two)) - expect(sp.total_votes).to eq(1) + i.register_selection(create(:user, :level_two)) + expect(i.total_votes).to eq(1) - sp.physical_votes = 10 - expect(sp.total_votes).to eq(11) + i.physical_votes = 10 + expect(i.total_votes).to eq(11) end end @@ -301,7 +301,7 @@ describe Budget::Investment do let(:user) { create(:user, :level_two) } let(:luser) { create(:user) } let(:ballot) { create(:budget_ballot, budget: budget) } - let(:investment) { create(:budget_investment, heading: heading) } + let(:investment) { create(:budget_investment, budget: budget, group: group, heading: heading) } describe '#reason_for_not_being_ballotable_by' do it "rejects not logged in users" do @@ -338,8 +338,8 @@ describe Budget::Investment do california = create(:budget_heading, group: group) new_york = create(:budget_heading, group: group) - inv1 = create(:budget_investment, :feasible, heading: california) - inv2 = create(:budget_investment, :feasible, heading: new_york) + inv1 = create(:budget_investment, :feasible, budget: budget, group: group, heading: california) + inv2 = create(:budget_investment, :feasible, budget: budget, group: group, heading: new_york) b = create(:budget_ballot, user: user, budget: budget) b.add_investment inv1 @@ -348,10 +348,10 @@ describe Budget::Investment do it "rejects proposals with price higher than current available money" do budget.phase = "balloting" - distritos = create(:budget_group, budget: budget) - carabanchel = create(:budget_heading, group: distritos, price: 35) - inv1 = create(:budget_investment, :feasible, heading: carabanchel, price: 30) - inv2 = create(:budget_investment, :feasible, heading: carabanchel, price: 10) + districts = create(:budget_group, budget: budget) + carabanchel = create(:budget_heading, group: districts, price: 35) + inv1 = create(:budget_investment, :feasible, budget: budget, group: districts, heading: carabanchel, price: 30) + inv2 = create(:budget_investment, :feasible, budget: budget, group: districts, heading: carabanchel, price: 10) ballot = create(:budget_ballot, user: user, budget: budget) ballot.add_investment inv1 From b0358e48671ddd23e4fd04c32e3b9994540b6e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 5 Sep 2016 17:50:58 +0200 Subject: [PATCH 107/384] adds index of investments with filters to admin --- .../admin/budget_investments_controller.rb | 15 + app/helpers/budget_groups_helper.rb | 7 + .../admin/budget_investments/index.html.erb | 76 +++++ .../admin/budget_investments/show.html.erb | 1 + app/views/admin/budgets/index.html.erb | 5 +- config/i18n-tasks.yml | 1 + config/locales/activerecord.es.yml | 3 + config/locales/admin.en.yml | 22 ++ config/locales/admin.es.yml | 22 ++ config/routes.rb | 2 + .../features/admin/budget_investments_spec.rb | 298 ++++++++++++++++++ spec/features/admin/budgets_spec.rb | 7 +- 12 files changed, 456 insertions(+), 3 deletions(-) create mode 100644 app/controllers/admin/budget_investments_controller.rb create mode 100644 app/helpers/budget_groups_helper.rb create mode 100644 app/views/admin/budget_investments/index.html.erb create mode 100644 app/views/admin/budget_investments/show.html.erb create mode 100644 spec/features/admin/budget_investments_spec.rb diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb new file mode 100644 index 000000000..bf15ee7f6 --- /dev/null +++ b/app/controllers/admin/budget_investments_controller.rb @@ -0,0 +1,15 @@ +class Admin::BudgetInvestmentsController < Admin::BaseController + + has_filters %w{valuation_open without_admin managed valuating valuation_finished all}, only: :index + + def index + @budget = Budget.includes(:groups).find params[:budget_id] + @investments = @budget.investments.scoped_filter(params, @current_filter).order(cached_votes_up: :desc, created_at: :desc).page(params[:page]) + end + + def show + @budget = Budget.includes(:groups).find params[:budget_id] + @investment = @budget.investments.find params[:id] + end + +end \ No newline at end of file diff --git a/app/helpers/budget_groups_helper.rb b/app/helpers/budget_groups_helper.rb new file mode 100644 index 000000000..d36a992d9 --- /dev/null +++ b/app/helpers/budget_groups_helper.rb @@ -0,0 +1,7 @@ +module BudgetGroupsHelper + + def budget_group_select_options(groups) + groups.map {|group| [group.name, group.id]} + end + +end \ No newline at end of file diff --git a/app/views/admin/budget_investments/index.html.erb b/app/views/admin/budget_investments/index.html.erb new file mode 100644 index 000000000..31ad2298a --- /dev/null +++ b/app/views/admin/budget_investments/index.html.erb @@ -0,0 +1,76 @@ +

    <%= @budget.name %> - <%= t("admin.budget_investments.index.title") %>

    + +
    + <%= form_tag admin_budget_budget_investments_path(budget: @budget), method: :get, enforce_utf8: false do %> +
    + <%= select_tag :administrator_id, + options_for_select(admin_select_options, params[:administrator_id]), + { prompt: t("admin.budget_investments.index.administrator_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
    + +
    + <%= select_tag :valuator_id, + options_for_select(valuator_select_options, params[:valuator_id]), + { prompt: t("admin.budget_investments.index.valuator_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
    + +
    + <%= select_tag :group_id, + options_for_select(budget_group_select_options(@budget.groups), params[:group_id]), + { prompt: t("admin.budget_investments.index.group_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
    + +
    + <%= select_tag :tag_name, + options_for_select(spending_proposal_tags_select_options, params[:tag_name]), + { prompt: t("admin.budget_investments.index.tags_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
    + <% end %> +
    + +<%= render 'shared/filter_subnav', i18n_namespace: "admin.budget_investments.index" %> + +

    <%= page_entries_info @investments %>

    + + + <% @investments.each do |investment| %> + + + + + + + + + <% end %> +
    + <%= investment.id %> + + <%= link_to investment.title, admin_budget_budget_investment_path(budget_id: @budget.id, id: investment.id), Budget::Investment.filter_params(params) %> + + <% if investment.administrator.present? %> + <%= investment.administrator.name %> + <% else %> + <%= t("admin.budget_investments.index.no_admin_assigned") %> + <% end %> + + <% if investment.valuators.size == 0 %> + <%= t("admin.budget_investments.index.no_valuators_assigned") %> + <% else %> + <%= investment.valuators.collect(&:description_or_name).join(', ') %> + <% end %> + + <%= investment.group.name %> + + <%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}", price: investment.price) %> +
    + +<%= paginate @investments %> \ No newline at end of file diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb new file mode 100644 index 000000000..a142a388d --- /dev/null +++ b/app/views/admin/budget_investments/show.html.erb @@ -0,0 +1 @@ +<%= @investment.title %> \ No newline at end of file diff --git a/app/views/admin/budgets/index.html.erb b/app/views/admin/budgets/index.html.erb index 193b6a7ef..d4de771db 100644 --- a/app/views/admin/budgets/index.html.erb +++ b/app/views/admin/budgets/index.html.erb @@ -13,11 +13,14 @@ <% @budgets.each do |budget| %> - <%= link_to budget.name, admin_budget_path(budget) %> + <%= link_to budget.name, admin_budget_budget_investments_path(budget_id: budget.id) %> <%= t("budget.phase.#{budget.phase}") %> + + <%= link_to t("admin.budgets.index.info_link"), admin_budget_path(budget) %> + <% end %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 7df6f7f9e..161ed6dfd 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -117,6 +117,7 @@ ignore_unused: - 'admin.debates.index.filter*' - 'admin.proposals.index.filter*' - 'admin.budgets.index.filter*' + - 'admin.budget_investments.index.filter*' - 'admin.spending_proposals.index.filter*' - 'admin.organizations.index.filter*' - 'admin.users.index.filter*' diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index ccd0240e8..c7c811801 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -7,6 +7,9 @@ es: budget: one: "Presupuesto participativo" other: "Presupuestos participativos" + budget/investment: + one: "Propuesta de inversión" + other: "Propuestas de inversión" comment: one: "Comentario" other: "Comentarios" diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 78a3a6d6c..61845a164 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -62,6 +62,7 @@ en: index: title: Participatory budgets new_link: Create new + info_link: Info filters: open: Open finished: Finished @@ -92,6 +93,27 @@ en: table_heading: Heading table_amount: Amount table_geozone: Scope of operation + budget_investments: + index: + group_filter_all: All voting groups + administrator_filter_all: All administrators + valuator_filter_all: All valuators + tags_filter_all: All tags + filters: + valuation_open: Open + without_admin: Without assigned admin + managed: Managed + valuating: Under valuation + valuation_finished: Valuation finished + all: All + title: Investment projects + assigned_admin: Assigned administrator + no_admin_assigned: No admin assigned + no_valuators_assigned: No valuators assigned + feasibility: + feasible: "Feasible (%{price})" + not_feasible: "Not feasible" + undefined: "Undefined" comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 496a0208d..7d2417480 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -62,6 +62,7 @@ es: index: title: Presupuestos participativos new_link: Crear nuevo + info_link: Info filters: open: Abiertos finished: Terminados @@ -92,6 +93,27 @@ es: table_heading: Partida table_amount: Cantidad table_geozone: Ámbito de actuación + budget_investments: + index: + group_filter_all: Todos los grupos + administrator_filter_all: Todos los administradores + valuator_filter_all: Todos los evaluadores + tags_filter_all: Todas las etiquetas + filters: + valuation_open: Abiertas + without_admin: Sin administrador + managed: Gestionando + valuating: En evaluación + valuation_finished: Evaluación finalizada + all: Todas + title: Propuestas de inversión + assigned_admin: Administrador asignado + no_admin_assigned: Sin admin asignado + no_valuators_assigned: Sin evaluador + feasibility: + feasible: "Viable (%{price})" + not_feasible: "Inviable" + undefined: "Sin definir" comments: index: filter: Filtro diff --git a/config/routes.rb b/config/routes.rb index 92f3d6c22..278153a19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -161,6 +161,8 @@ Rails.application.routes.draw do resources :budget_headings do end end + + resources :budget_investments, only: [:index, :show] end resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb new file mode 100644 index 000000000..3c5e611c5 --- /dev/null +++ b/spec/features/admin/budget_investments_spec.rb @@ -0,0 +1,298 @@ +require 'rails_helper' + +feature 'Admin budget investments' do + + background do + admin = create(:administrator) + login_as(admin.user) + + @budget = create(:budget) + end + + context "Index" do + + scenario 'Displaying investmentss' do + budget_investment = create(:budget_investment, budget: @budget) + visit admin_budget_budget_investments_path(budget_id: @budget.id) + expect(page).to have_content(budget_investment.title) + end + + scenario 'Displaying assignments info' do + budget_investment1 = create(:budget_investment, budget: @budget) + budget_investment2 = create(:budget_investment, budget: @budget) + budget_investment3 = create(:budget_investment, budget: @budget) + + valuator1 = create(:valuator, user: create(:user, username: 'Olga'), description: 'Valuator Olga') + valuator2 = create(:valuator, user: create(:user, username: 'Miriam'), description: 'Valuator Miriam') + admin = create(:administrator, user: create(:user, username: 'Gema')) + + budget_investment1.valuators << valuator1 + budget_investment2.valuator_ids = [valuator1.id, valuator2.id] + budget_investment3.update({administrator_id: admin.id}) + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + + within("#budget_investment_#{budget_investment1.id}") do + expect(page).to have_content("No admin assigned") + expect(page).to have_content("Valuator Olga") + end + + within("#budget_investment_#{budget_investment2.id}") do + expect(page).to have_content("No admin assigned") + expect(page).to have_content("Valuator Olga") + expect(page).to have_content("Valuator Miriam") + end + + within("#budget_investment_#{budget_investment3.id}") do + expect(page).to have_content("Gema") + expect(page).to have_content("No valuators assigned") + end + end + + scenario "Filtering by budget group", :js do + group1 = create(:budget_group, name: "Street improvments", budget: @budget) + group2 = create(:budget_group, name: "Parks", budget: @budget) + + create(:budget_investment, title: "Realocate visitors", budget: @budget, group: group1) + create(:budget_investment, title: "Destroy the city", budget: @budget, group: group2) + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + + expect(page).to have_link("Realocate visitors") + expect(page).to have_link("Destroy the city") + + select "Parks", from: "group_id" + + expect(page).to have_link("Destroy the city") + expect(page).to_not have_link("Realocate visitors") + + select "All voting groups", from: "group_id" + + expect(page).to have_link("Realocate visitors") + expect(page).to have_link("Destroy the city") + + select "Street improvments", from: "group_id" + + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") + + # click_link("Realocate visitors") + # click_link("Back") + + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + + # click_link("Realocate visitors") + # click_link("Edit classification") + # expect(page).to have_button("Update") + # click_link("Back") + # expect(page).to_not have_button("Update") + # click_link("Back") + + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + end + + scenario "Filtering by admin", :js do + user = create(:user, username: 'Admin 1') + administrator = create(:administrator, user: user) + + create(:budget_investment, title: "Realocate visitors", budget: @budget, administrator: administrator) + create(:budget_investment, title: "Destroy the city", budget: @budget) + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + expect(page).to have_link("Realocate visitors") + expect(page).to have_link("Destroy the city") + + select "Admin 1", from: "administrator_id" + + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") + + select "All administrators", from: "administrator_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 "Admin 1", from: "administrator_id" + expect(page).to have_content('There is 1 investment') + + # click_link("Realocate visitors") + # click_link("Back") + + # expect(page).to have_content('There is 1 investment') + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + + # click_link("Realocate visitors") + # click_link("Edit classification") + # expect(page).to have_button("Update") + # click_link("Back") + # expect(page).to_not have_button("Update") + # click_link("Back") + + # expect(page).to have_content('There is 1 investment') + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + end + + scenario "Filtering by valuator", :js do + user = create(:user) + valuator = create(:valuator, user: user, description: 'Valuator 1') + + budget_investment = create(:budget_investment, title: "Realocate visitors", budget: @budget) + budget_investment.valuators << valuator + + create(:budget_investment, title: "Destroy the city", budget: @budget) + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + expect(page).to have_link("Realocate visitors") + expect(page).to have_link("Destroy the city") + + select "Valuator 1", from: "valuator_id" + + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") + + select "All valuators", from: "valuator_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" + expect(page).to have_content('There is 1 investment') + # click_link("Realocate visitors") + # click_link("Back") + + # expect(page).to have_content('There is 1 investment') + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + + # click_link("Realocate visitors") + # click_link("Edit classification") + # expect(page).to have_button("Update") + # click_link("Back") + # expect(page).to_not have_button("Update") + # click_link("Back") + + # expect(page).to have_content('There is 1 investment') + # expect(page).to_not have_link("Destroy the city") + # expect(page).to have_link("Realocate visitors") + end + + scenario "Current filter is properly highlighted" do + filters_links = {'valuation_open' => 'Open', + 'without_admin' => 'Without assigned admin', + 'managed' => 'Managed', + 'valuating' => 'Under valuation', + 'valuation_finished' => 'Valuation finished', + 'all' => 'All'} + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + + 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 admin_budget_budget_investments_path(budget_id: @budget.id, 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 "Filtering by assignment status" do + assigned = create(:budget_investment, title: "Assigned idea", budget: @budget, administrator: create(:administrator)) + valuating = create(:budget_investment, title: "Evaluating...", budget: @budget) + valuating.valuators << create(:valuator) + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'valuation_open') + + expect(page).to have_content("Assigned idea") + expect(page).to have_content("Evaluating...") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'without_admin') + + expect(page).to have_content("Evaluating...") + expect(page).to_not have_content("Assigned idea") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'managed') + + expect(page).to have_content("Assigned idea") + expect(page).to_not have_content("Evaluating...") + end + + scenario "Filtering by valuation status" do + valuating = create(:budget_investment, budget: @budget, title: "Ongoing valuation") + valuated = create(:budget_investment, budget: @budget, title: "Old idea", valuation_finished: true) + valuating.valuators << create(:valuator) + valuated.valuators << create(:valuator) + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'valuation_open') + + expect(page).to have_content("Ongoing valuation") + expect(page).to_not have_content("Old idea") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'valuating') + + expect(page).to have_content("Ongoing valuation") + expect(page).to_not have_content("Old idea") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'valuation_finished') + + expect(page).to_not have_content("Ongoing valuation") + expect(page).to have_content("Old idea") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, filter: 'all') + expect(page).to have_content("Ongoing valuation") + expect(page).to have_content("Old idea") + end + + scenario "Filtering by tag" do + create(:budget_investment, budget: @budget, title: 'Educate the children', tag_list: 'Education') + create(:budget_investment, budget: @budget, title: 'More schools', tag_list: 'Education') + create(:budget_investment, budget: @budget, title: 'More hospitals', tag_list: 'Health') + + visit admin_budget_budget_investments_path(budget_id: @budget.id) + + expect(page).to have_css(".budget_investment", count: 3) + expect(page).to have_content("Educate the children") + expect(page).to have_content("More schools") + expect(page).to have_content("More hospitals") + + visit admin_budget_budget_investments_path(budget_id: @budget.id, tag_name: 'Education') + + expect(page).to_not have_content("More hospitals") + expect(page).to have_css(".budget_investment", count: 2) + expect(page).to have_content("Educate the children") + expect(page).to have_content("More schools") + + # click_link("Educate the children") + # click_link("Back") + + # expect(page).to_not have_content("More hospitals") + # expect(page).to have_content("Educate the children") + # expect(page).to have_content("More schools") + + # click_link("Educate the children") + # click_link("Edit classification") + # expect(page).to have_button("Update") + # click_link("Back") + # expect(page).to_not have_button("Update") + # click_link("Back") + + # expect(page).to_not have_content("More hospitals") + # expect(page).to have_content("Educate the children") + # expect(page).to have_content("More schools") + end + + end + +end \ No newline at end of file diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index 123ca43e8..a87cd741a 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -105,10 +105,13 @@ feature 'Admin budgets' do context 'Manage groups and headings' do scenario 'Create group', :js do - create(:budget, name: 'Yearly participatory budget') + budget = create(:budget, name: 'Yearly participatory budget') visit admin_budgets_path - click_link 'Yearly participatory budget' + + within("#budget_#{budget.id}") do + click_link 'Info' + end expect(page).to have_content 'No groups created yet.' From 31ef3f4c9815e8d2c5bd641c711f15ec990fb454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Tue, 6 Sep 2016 17:03:35 +0200 Subject: [PATCH 108/384] adds heading filter to index --- app/helpers/budget_groups_helper.rb | 7 -- app/helpers/budget_headings_helper.rb | 7 ++ app/models/budget/investment.rb | 2 +- .../admin/budget_investments/index.html.erb | 10 +- config/locales/admin.en.yml | 2 +- config/locales/admin.es.yml | 2 +- .../features/admin/budget_investments_spec.rb | 92 +++++++++++-------- 7 files changed, 69 insertions(+), 53 deletions(-) delete mode 100644 app/helpers/budget_groups_helper.rb create mode 100644 app/helpers/budget_headings_helper.rb diff --git a/app/helpers/budget_groups_helper.rb b/app/helpers/budget_groups_helper.rb deleted file mode 100644 index d36a992d9..000000000 --- a/app/helpers/budget_groups_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -module BudgetGroupsHelper - - def budget_group_select_options(groups) - groups.map {|group| [group.name, group.id]} - end - -end \ No newline at end of file diff --git a/app/helpers/budget_headings_helper.rb b/app/helpers/budget_headings_helper.rb new file mode 100644 index 000000000..cb042f796 --- /dev/null +++ b/app/helpers/budget_headings_helper.rb @@ -0,0 +1,7 @@ +module BudgetHeadingsHelper + + def budget_heading_select_options(budget) + budget.headings.map {|heading| [heading.name, heading.id]} + end + +end \ No newline at end of file diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index f76d1f68e..56ae67d41 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -56,7 +56,7 @@ class Budget before_validation :set_responsible_name def self.filter_params(params) - params.select{|x,_| %w{heading_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 } end def self.scoped_filter(params, current_filter) diff --git a/app/views/admin/budget_investments/index.html.erb b/app/views/admin/budget_investments/index.html.erb index 31ad2298a..5aff40115 100644 --- a/app/views/admin/budget_investments/index.html.erb +++ b/app/views/admin/budget_investments/index.html.erb @@ -19,9 +19,9 @@
    - <%= select_tag :group_id, - options_for_select(budget_group_select_options(@budget.groups), params[:group_id]), - { prompt: t("admin.budget_investments.index.group_filter_all"), + <%= select_tag :heading_id, + options_for_select(budget_heading_select_options(@budget), params[:heading_id]), + { prompt: t("admin.budget_investments.index.heading_filter_all"), label: false, class: "js-submit-on-change" } %>
    @@ -47,7 +47,7 @@ <%= investment.id %> - <%= link_to investment.title, admin_budget_budget_investment_path(budget_id: @budget.id, id: investment.id), Budget::Investment.filter_params(params) %> + <%= link_to investment.title, admin_budget_budget_investment_path(budget_id: @budget.id, id: investment.id, params: Budget::Investment.filter_params(params)) %> <% if investment.administrator.present? %> @@ -64,7 +64,7 @@ <% end %> - <%= investment.group.name %> + <%= investment.heading.name %> <%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}", price: investment.price) %> diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 61845a164..4a9abf7d3 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -95,7 +95,7 @@ en: table_geozone: Scope of operation budget_investments: index: - group_filter_all: All voting groups + heading_filter_all: All headings administrator_filter_all: All administrators valuator_filter_all: All valuators tags_filter_all: All tags diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 7d2417480..3600c2448 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -95,7 +95,7 @@ es: table_geozone: Ámbito de actuación budget_investments: index: - group_filter_all: Todos los grupos + heading_filter_all: Todas las partidas administrator_filter_all: Todos los administradores valuator_filter_all: Todos los evaluadores tags_filter_all: Todas las etiquetas diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb index 3c5e611c5..ad08ee634 100644 --- a/spec/features/admin/budget_investments_spec.rb +++ b/spec/features/admin/budget_investments_spec.rb @@ -49,45 +49,61 @@ feature 'Admin budget investments' do end end - scenario "Filtering by budget group", :js do - group1 = create(:budget_group, name: "Street improvments", budget: @budget) + scenario "Filtering by budget heading", :js do + group1 = create(:budget_group, name: "Streets", budget: @budget) group2 = create(:budget_group, name: "Parks", budget: @budget) - create(:budget_investment, title: "Realocate visitors", budget: @budget, group: group1) - create(:budget_investment, title: "Destroy the city", budget: @budget, group: group2) + group1_heading1 = create(:budget_heading, group: group1, name: "Main Avenue") + group1_heading2 = create(:budget_heading, group: group1, name: "Mercy Street") + group2_heading1 = create(:budget_heading, group: group2, name: "Central Park") + + create(:budget_investment, title: "Realocate visitors", budget: @budget, group: group1, heading: group1_heading1) + create(:budget_investment, title: "Change name", budget: @budget, group: group1, heading: group1_heading2) + create(:budget_investment, title: "Plant trees", budget: @budget, group: group2, heading: group2_heading1) visit admin_budget_budget_investments_path(budget_id: @budget.id) expect(page).to have_link("Realocate visitors") - expect(page).to have_link("Destroy the city") + expect(page).to have_link("Change name") + expect(page).to have_link("Plant trees") - select "Parks", from: "group_id" + select "Central Park", from: "heading_id" - expect(page).to have_link("Destroy the city") expect(page).to_not have_link("Realocate visitors") + expect(page).to_not have_link("Change name") + expect(page).to have_link("Plant trees") - select "All voting groups", from: "group_id" + select "All headings", from: "heading_id" expect(page).to have_link("Realocate visitors") - expect(page).to have_link("Destroy the city") + expect(page).to have_link("Change name") + expect(page).to have_link("Plant trees") - select "Street improvments", from: "group_id" + select "Main Avenue", from: "heading_id" - expect(page).to_not have_link("Destroy the city") expect(page).to have_link("Realocate visitors") + expect(page).to_not have_link("Change name") + expect(page).to_not have_link("Plant trees") - # click_link("Realocate visitors") - # click_link("Back") + select "Mercy Street", from: "heading_id" - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to_not have_link("Realocate visitors") + expect(page).to have_link("Change name") + expect(page).to_not have_link("Plant trees") + + click_link("Change name") + click_link("Go back") + + expect(page).to_not have_link("Realocate visitors") + expect(page).to have_link("Change name") + expect(page).to_not have_link("Plant trees") # click_link("Realocate visitors") # click_link("Edit classification") # expect(page).to have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_link("Destroy the city") # expect(page).to have_link("Realocate visitors") @@ -119,19 +135,19 @@ feature 'Admin budget investments' do select "Admin 1", from: "administrator_id" expect(page).to have_content('There is 1 investment') - # click_link("Realocate visitors") - # click_link("Back") + click_link("Realocate visitors") + click_link("Go back") - # expect(page).to have_content('There is 1 investment') - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") # click_link("Realocate visitors") # click_link("Edit classification") # expect(page).to have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to have_content('There is 1 investment') # expect(page).to_not have_link("Destroy the city") @@ -165,19 +181,19 @@ feature 'Admin budget investments' do select "Valuator 1", from: "valuator_id" expect(page).to have_content('There is 1 investment') - # click_link("Realocate visitors") - # click_link("Back") + click_link("Realocate visitors") + click_link("Go back") - # expect(page).to have_content('There is 1 investment') - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") # click_link("Realocate visitors") # click_link("Edit classification") # expect(page).to have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to have_content('There is 1 investment') # expect(page).to_not have_link("Destroy the city") @@ -274,19 +290,19 @@ feature 'Admin budget investments' do expect(page).to have_content("Educate the children") expect(page).to have_content("More schools") - # click_link("Educate the children") - # click_link("Back") + click_link("Educate the children") + click_link("Go back") - # expect(page).to_not have_content("More hospitals") - # expect(page).to have_content("Educate the children") - # expect(page).to have_content("More schools") + expect(page).to_not have_content("More hospitals") + expect(page).to have_content("Educate the children") + expect(page).to have_content("More schools") # click_link("Educate the children") # click_link("Edit classification") # expect(page).to have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_button("Update") - # click_link("Back") + # click_link("Go back") # expect(page).to_not have_content("More hospitals") # expect(page).to have_content("Educate the children") From 65428126479b86926191c1c02a630fcc383da2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Tue, 6 Sep 2016 17:04:31 +0200 Subject: [PATCH 109/384] adds simple investment show view to admin valuators info is missing --- .../admin/budget_investments_controller.rb | 14 ++++-- .../_written_by_author.html.erb | 36 +++++++++++++ .../admin/budget_investments/show.html.erb | 50 ++++++++++++++++++- config/locales/admin.en.yml | 15 ++++++ config/locales/admin.es.yml | 15 ++++++ 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 app/views/admin/budget_investments/_written_by_author.html.erb diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index bf15ee7f6..10342081c 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -1,15 +1,21 @@ class Admin::BudgetInvestmentsController < Admin::BaseController + before_action :load_budget, only: [:index, :show] + has_filters %w{valuation_open without_admin managed valuating valuation_finished all}, only: :index def index - @budget = Budget.includes(:groups).find params[:budget_id] - @investments = @budget.investments.scoped_filter(params, @current_filter).order(cached_votes_up: :desc, created_at: :desc).page(params[:page]) + @investments = Budget::Investment.scoped_filter(params, @current_filter).order(cached_votes_up: :desc, created_at: :desc).page(params[:page]) end def show - @budget = Budget.includes(:groups).find params[:budget_id] - @investment = @budget.investments.find params[:id] + @investment = Budget::Investment.where(budget_id: @budget.id).find params[:id] end + private + + def load_budget + @budget = Budget.includes(:groups).find params[:budget_id] + end + end \ No newline at end of file diff --git a/app/views/admin/budget_investments/_written_by_author.html.erb b/app/views/admin/budget_investments/_written_by_author.html.erb new file mode 100644 index 000000000..b2c46c6f5 --- /dev/null +++ b/app/views/admin/budget_investments/_written_by_author.html.erb @@ -0,0 +1,36 @@ +
    + <%= t "admin.budget_investments.show.info", budget_name: @budget.name, group_name: @investment.group.name, id: @investment.id %> +
    + +
    +

    <%= @investment.title %>

    + +
    +
    +

    : <%= @investment.group.name %>"> + <%= t("admin.budget_investments.show.heading") %>: + <%= @investment.heading.name %> +

    +
    + +
    +

    + <%= t("admin.budget_investments.show.by") %>: + <%= link_to @investment.author.name, admin_user_path(@investment.author) %> +

    +
    + +
    +

    + <%= t("admin.budget_investments.show.sent") %>: + <%= l @investment.created_at, format: :datetime %> +

    +
    + +
    + +<% if @investment.external_url.present? %> +

    <%= text_with_links @investment.external_url %> 

    +<% end %> + +<%= safe_html_with_links @investment.description %> diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb index a142a388d..47e66bc37 100644 --- a/app/views/admin/budget_investments/show.html.erb +++ b/app/views/admin/budget_investments/show.html.erb @@ -1 +1,49 @@ -<%= @investment.title %> \ No newline at end of file +<%= link_to admin_budget_budget_investments_path(Budget::Investment.filter_params(params)), data: {no_turbolink: true} do %> + <%= t("shared.back") %> +<% end %> + +<%= render 'written_by_author' %> + +<%= link_to t("admin.budget_investments.show.edit"), + admin_budget_budget_investment_path(@budget, @investment, + Budget::Investment.filter_params(params)) %> + +
    + +

    <%= t("admin.budget_investments.show.classification") %>

    + +

    <%= t("admin.budget_investments.show.assigned_admin") %>: + <%= @investment.administrator.try(:name_and_email) || t("admin.budget_investments.show.undefined") %> +

    + +

    + <%= t("admin.budget_investments.show.tags") %>: + + <%= @investment.tags.pluck(:name).join(', ') %> +

    + +

    + <%= t("admin.budget_investments.show.assigned_valuators") %>: + <% if @investment.valuators.any? %> + <%= @investment.valuators.collect(&:name_and_email).join(', ') %> + <% else %> + <%= t("admin.budget_investments.show.undefined") %> + <% end %> +

    + +

    + <%= link_to t("admin.budget_investments.show.edit_classification"), + edit_admin_spending_proposal_path(@investment, + {anchor: 'classification'}.merge(Budget::Investment.filter_params(params))) %> +

    + +
    + +

    <%= t("admin.budget_investments.show.dossier") %>

    + +<%# render 'valuation/budget_investments/written_by_valuators' %> + +

    + <%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_spending_proposal_path(@investment) %> +

    + diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 4a9abf7d3..9b5e7c19c 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -114,6 +114,21 @@ en: feasible: "Feasible (%{price})" not_feasible: "Not feasible" undefined: "Undefined" + show: + assigned_admin: Assigned administrator + assigned_valuators: Assigned valuators + classification: Clasification + info: "%{budget_name} - Group: %{group_name} - Investment project %{id}" + edit: Edit + edit_classification: Edit classification + by: By + sent: Sent + group: Grupo + heading: Partida + dossier: Dossier + edit_dossier: Edit dossier + tags: Tags + undefined: Undefined comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 3600c2448..4f4536950 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -114,6 +114,21 @@ es: feasible: "Viable (%{price})" not_feasible: "Inviable" undefined: "Sin definir" + show: + assigned_admin: Administrador asignado + assigned_valuators: Evaluadores asignados + classification: Clasificación + info: "%{budget_name} - Grupo: %{group_name} - Propuesta de inversión %{id}" + edit: Editar + edit_classification: Editar clasificación + by: Autor + sent: Fecha + group: Grupo + heading: Partida + dossier: Informe + edit_dossier: Editar informe + tags: Etiquetas + undefined: Sin definir comments: index: filter: Filtro From bdfd3269078bdf3724b490f85d46036e57912dcc Mon Sep 17 00:00:00 2001 From: kikito Date: Wed, 7 Sep 2016 11:53:24 +0200 Subject: [PATCH 110/384] implements some parts of the spending proposals importer --- lib/spending_proposals_importer.rb | 61 ++++++++++++++++++++ spec/lib/spending_proposals_importer_spec.rb | 57 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 lib/spending_proposals_importer.rb create mode 100644 spec/lib/spending_proposals_importer_spec.rb diff --git a/lib/spending_proposals_importer.rb b/lib/spending_proposals_importer.rb new file mode 100644 index 000000000..f879dcae7 --- /dev/null +++ b/lib/spending_proposals_importer.rb @@ -0,0 +1,61 @@ +class SpendingProposalsImporter + + def import(sp) + # feasibility + # unfeasibility_explanation + # heading_id + # valuator_assignments_count + # hidden_at + # comments_count + # group_id + # budget_id + # duration + + # comments + + budget = Budget.last || Budget.create!(name: Date.today.year.to_s, currency_symbol: "€") + + group = nil + heading = nil + + if sp.geozone_id.present? + group = budget.groups.find_or_create_by!(name: "Barrios") + heading = group.headings.find_or_create_by!(name: sp.geozone.name, price: 10000000) + else + group = budget.groups.find_or_create_by!(name: "Toda la ciudad") + heading = group.headings.find_or_create_by!(name: "Toda la ciudad", price: 10000000) + end + + feasibility = case sp.feasible + when FalseClass + 'unfeasible' + when TrueClass + 'feasible' + else + 'undecided' + end + + Budget::Investment.create!( + heading_id: heading.id, + author_id: sp.author_id, + administrator_id: sp.administrator_id, + title: sp.title, + description: sp.description, + external_url: sp.external_url, + price: sp.price, + price_explanation: sp.price_explanation, + internal_comments: sp.internal_comments, + feasibility: feasibility, + valuation_finished: sp.valuation_finished, + price_first_year: sp.price_first_year, + cached_votes_up: sp.cached_votes_up, + physical_votes: sp.physical_votes, + created_at: sp.created_at, + updated_at: sp.updated_at, + responsible_name: sp.responsible_name, + terms_of_service: "1" + ) + end + +end + diff --git a/spec/lib/spending_proposals_importer_spec.rb b/spec/lib/spending_proposals_importer_spec.rb new file mode 100644 index 000000000..1b424722f --- /dev/null +++ b/spec/lib/spending_proposals_importer_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +describe SpendingProposalsImporter do + + let(:importer) { SpendingProposalsImporter.new } + + describe '#import' do + it "Imports a city spending proposal" do + sp = create(:spending_proposal) + + expect { importer.import(sp) }.to change{ Budget::Investment.count }.from(0).to(1) + + inv = Budget::Investment.last + + expect(inv.author).to eq(sp.author) + expect(inv.title).to eq(sp.title) + expect(inv.heading.name).to eq("Toda la ciudad") + expect(inv.heading.group.name).to eq("Toda la ciudad") + end + + it "Imports a city spending proposal" do + sp = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) + + expect { importer.import(sp) }.to change{ Budget::Investment.count }.from(0).to(1) + + inv = Budget::Investment.last + + expect(inv.author).to eq(sp.author) + expect(inv.title).to eq(sp.title) + expect(inv.heading.name).to eq("Bel Air") + expect(inv.heading.group.name).to eq("Barrios") + end + + it "Uses existing budgets, headings and groups instead of creating new ones" do + sp1 = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) + sp2 = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) + + expect { importer.import(sp1) }.to change{ Budget::Investment.count }.from(0).to(1) + expect { importer.import(sp2) }.to change{ Budget::Investment.count }.from(1).to(2) + + inv1 = Budget::Investment.first + inv2 = Budget::Investment.last + + expect(inv2.heading).to eq(inv1.heading) + end + + it "Imports feasibility correctly" do + sp = create(:spending_proposal) + feasible = create(:spending_proposal, feasible: true) + unfeasible = create(:spending_proposal, feasible: false) + + expect(importer.import(sp).feasibility).to eq('undecided') + expect(importer.import(feasible).feasibility).to eq('feasible') + expect(importer.import(unfeasible).feasibility).to eq('unfeasible') + end + end +end From b8dffc6137740e816d93de8d8811594229129c64 Mon Sep 17 00:00:00 2001 From: kikito Date: Wed, 7 Sep 2016 12:01:04 +0200 Subject: [PATCH 111/384] refactor some tests to their own test in importer --- spec/lib/spending_proposals_importer_spec.rb | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/spec/lib/spending_proposals_importer_spec.rb b/spec/lib/spending_proposals_importer_spec.rb index 1b424722f..88d60b360 100644 --- a/spec/lib/spending_proposals_importer_spec.rb +++ b/spec/lib/spending_proposals_importer_spec.rb @@ -5,12 +5,25 @@ describe SpendingProposalsImporter do let(:importer) { SpendingProposalsImporter.new } describe '#import' do + + it "Creates the budget if it doesn't exist" do + sp = create(:spending_proposal) + expect { importer.import(sp) }.to change{ Budget.count }.from(0).to(1) + importer.import(create(:spending_proposal)) + expect(Budget.count).to eq(1) + end + + it "Creates the and returns investments" do + inv = nil + sp = create(:spending_proposal) + expect { inv = importer.import(sp) }.to change{ Budget::Investment.count }.from(0).to(1) + expect(inv).to be_kind_of(Budget::Investment) + end + it "Imports a city spending proposal" do sp = create(:spending_proposal) - expect { importer.import(sp) }.to change{ Budget::Investment.count }.from(0).to(1) - - inv = Budget::Investment.last + inv = importer.import(sp) expect(inv.author).to eq(sp.author) expect(inv.title).to eq(sp.title) @@ -21,9 +34,7 @@ describe SpendingProposalsImporter do it "Imports a city spending proposal" do sp = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) - expect { importer.import(sp) }.to change{ Budget::Investment.count }.from(0).to(1) - - inv = Budget::Investment.last + inv = importer.import(sp) expect(inv.author).to eq(sp.author) expect(inv.title).to eq(sp.title) @@ -35,18 +46,15 @@ describe SpendingProposalsImporter do sp1 = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) sp2 = create(:spending_proposal, geozone: create(:geozone, name: "Bel Air")) - expect { importer.import(sp1) }.to change{ Budget::Investment.count }.from(0).to(1) - expect { importer.import(sp2) }.to change{ Budget::Investment.count }.from(1).to(2) - - inv1 = Budget::Investment.first - inv2 = Budget::Investment.last + inv1 = importer.import(sp1) + inv2 = importer.import(sp2) expect(inv2.heading).to eq(inv1.heading) end it "Imports feasibility correctly" do - sp = create(:spending_proposal) - feasible = create(:spending_proposal, feasible: true) + sp = create(:spending_proposal) + feasible = create(:spending_proposal, feasible: true) unfeasible = create(:spending_proposal, feasible: false) expect(importer.import(sp).feasibility).to eq('undecided') From c1bf89a09eb244be92dea262fe51df16d5a8e9d9 Mon Sep 17 00:00:00 2001 From: kikito Date: Wed, 7 Sep 2016 13:02:26 +0200 Subject: [PATCH 112/384] adds more functionality to spending proposal importer --- lib/spending_proposals_importer.rb | 19 ++++++++----------- spec/lib/spending_proposals_importer_spec.rb | 13 +++++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/spending_proposals_importer.rb b/lib/spending_proposals_importer.rb index f879dcae7..5641c082b 100644 --- a/lib/spending_proposals_importer.rb +++ b/lib/spending_proposals_importer.rb @@ -1,16 +1,7 @@ class SpendingProposalsImporter def import(sp) - # feasibility - # unfeasibility_explanation - # heading_id - # valuator_assignments_count - # hidden_at - # comments_count - # group_id - # budget_id - # duration - + # votes # comments budget = Budget.last || Budget.create!(name: Date.today.year.to_s, currency_symbol: "€") @@ -35,7 +26,7 @@ class SpendingProposalsImporter 'undecided' end - Budget::Investment.create!( + investment = Budget::Investment.create!( heading_id: heading.id, author_id: sp.author_id, administrator_id: sp.administrator_id, @@ -45,7 +36,9 @@ class SpendingProposalsImporter price: sp.price, price_explanation: sp.price_explanation, internal_comments: sp.internal_comments, + duration: sp.time_scope, feasibility: feasibility, + unfeasibility_explanation: sp.feasible_explanation, valuation_finished: sp.valuation_finished, price_first_year: sp.price_first_year, cached_votes_up: sp.cached_votes_up, @@ -55,6 +48,10 @@ class SpendingProposalsImporter responsible_name: sp.responsible_name, terms_of_service: "1" ) + + investment.valuators = sp.valuation_assignments.map(&:valuator) + + investment end end diff --git a/spec/lib/spending_proposals_importer_spec.rb b/spec/lib/spending_proposals_importer_spec.rb index 88d60b360..598e028e3 100644 --- a/spec/lib/spending_proposals_importer_spec.rb +++ b/spec/lib/spending_proposals_importer_spec.rb @@ -61,5 +61,18 @@ describe SpendingProposalsImporter do expect(importer.import(feasible).feasibility).to eq('feasible') expect(importer.import(unfeasible).feasibility).to eq('unfeasible') end + + it "Imports valuation assignments" do + sp = create(:spending_proposal) + peter = create(:valuator) + john = create(:valuator) + sp.valuators << peter << john + + inv = importer.import(sp) + + expect(inv.valuator_assignments.count).to eq(2) + expect(inv.valuators).to include(peter) + expect(inv.valuators).to include(john) + end end end From 8890380804cbabd932ccab9ba4db420dee488ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 7 Sep 2016 14:13:40 +0200 Subject: [PATCH 113/384] adds investment editing to admin --- .../admin/budget_investments_controller.rb | 41 +++- app/helpers/budget_headings_helper.rb | 4 + .../admin/budget_investments/edit.html.erb | 69 ++++++ .../admin/budget_investments/show.html.erb | 6 +- .../_written_by_valuators.html.erb | 53 +++++ config/initializers/acts_as_taggable_on.rb | 4 + config/locales/activerecord.en.yml | 9 + config/locales/activerecord.es.yml | 6 + config/locales/admin.en.yml | 8 + config/locales/admin.es.yml | 8 + config/locales/responders.en.yml | 4 +- config/locales/valuation.en.yml | 23 ++ config/locales/valuation.es.yml | 23 ++ config/routes.rb | 2 +- .../features/admin/budget_investments_spec.rb | 214 +++++++++++++++--- 15 files changed, 431 insertions(+), 43 deletions(-) create mode 100644 app/views/admin/budget_investments/edit.html.erb create mode 100644 app/views/valuation/budget_investments/_written_by_valuators.html.erb diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 10342081c..17cc76923 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -1,6 +1,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController - before_action :load_budget, only: [:index, :show] + before_action :load_budget + before_action :load_investment, only: [:show, :edit, :update] has_filters %w{valuation_open without_admin managed valuating valuation_finished all}, only: :index @@ -9,13 +10,49 @@ class Admin::BudgetInvestmentsController < Admin::BaseController end def show - @investment = Budget::Investment.where(budget_id: @budget.id).find params[:id] + end + + def edit + load_admins + load_valuators + load_tags + end + + def update + if @investment.update(budget_investment_params) + redirect_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)), + notice: t("flash.actions.update.budget_investment") + else + load_admins + load_valuators + load_tags + render :edit + end end private + def budget_investment_params + params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :administrator_id, :tag_list, valuator_ids: []) + end + def load_budget @budget = Budget.includes(:groups).find params[:budget_id] end + def load_investment + @investment = Budget::Investment.where(budget_id: @budget.id).find params[:id] + end + + def load_admins + @admins = Administrator.includes(:user).all + end + + def load_valuators + @valuators = Valuator.includes(:user).all.order("description ASC").order("users.email ASC") + end + + def load_tags + @tags = ActsAsTaggableOn::Tag.budget_investment_tags + end end \ No newline at end of file diff --git a/app/helpers/budget_headings_helper.rb b/app/helpers/budget_headings_helper.rb index cb042f796..b9944be9b 100644 --- a/app/helpers/budget_headings_helper.rb +++ b/app/helpers/budget_headings_helper.rb @@ -4,4 +4,8 @@ module BudgetHeadingsHelper budget.headings.map {|heading| [heading.name, heading.id]} end + def budget_scoped_heading_select_options(budget) + budget.headings.includes(:group).order("group_id ASC, budget_headings.name ASC").map {|heading| [heading.group.name + ': ' + heading.name, heading.id]} + end + end \ No newline at end of file diff --git a/app/views/admin/budget_investments/edit.html.erb b/app/views/admin/budget_investments/edit.html.erb new file mode 100644 index 000000000..db57c423b --- /dev/null +++ b/app/views/admin/budget_investments/edit.html.erb @@ -0,0 +1,69 @@ +<%= link_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)), class: 'back' do %> + <%= t("shared.back") %> +<% end %> + +<%= form_for @investment, + url: admin_budget_budget_investment_path(@budget, @investment) do |f| %> + + <% Budget::Investment.filter_params(params).each do |filter_name, filter_value| %> + <%= hidden_field_tag filter_name, filter_value %> + <% end %> + +
    +
    + <%= f.text_field :title, maxlength: Budget::Investment.title_max_length %> +
    + +
    + <%= f.cktext_area :description, maxlength: Budget::Investment.description_max_length, ckeditor: { language: I18n.locale } %> +
    + +
    + <%= f.text_field :external_url %> +
    + +
    + <%= f.select :heading_id, budget_scoped_heading_select_options(@budget), include_blank: t("admin.budget_investments.edit.select_heading") %> +
    +
    + +

    <%= t("admin.budget_investments.edit.classification") %>

    + +
    + +
    + <%= f.select(:administrator_id, + @admins.collect{ |a| [a.name_and_email, a.id ] }, + { include_blank: t("admin.budget_investments.edit.undefined") }) %> +
    + + +
    + <%= f.label :tag_list, t("admin.budget_investments.edit.tags") %> +
    + <% @tags.each do |tag| %> + <%= tag.name %> + <% end %> +
    + <%= f.text_field :tag_list, value: @investment.tag_list.to_s, + label: false, + placeholder: t("admin.budget_investments.edit.tags_placeholder"), + class: 'js-tag-list' %> +
    + +
    + <%= 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) } %> + <% end %> +
    +
    + +

    + <%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %> +

    +<% end %> + +
    +<%# render 'valuation/budget_investments/written_by_valuators' %> \ No newline at end of file diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb index 47e66bc37..e7423b82d 100644 --- a/app/views/admin/budget_investments/show.html.erb +++ b/app/views/admin/budget_investments/show.html.erb @@ -5,7 +5,7 @@ <%= render 'written_by_author' %> <%= link_to t("admin.budget_investments.show.edit"), - admin_budget_budget_investment_path(@budget, @investment, + edit_admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)) %>
    @@ -33,7 +33,7 @@

    <%= link_to t("admin.budget_investments.show.edit_classification"), - edit_admin_spending_proposal_path(@investment, + edit_admin_budget_budget_investment_path(@budget, @investment, {anchor: 'classification'}.merge(Budget::Investment.filter_params(params))) %>

    @@ -41,7 +41,7 @@

    <%= t("admin.budget_investments.show.dossier") %>

    -<%# render 'valuation/budget_investments/written_by_valuators' %> +<%= render 'valuation/budget_investments/written_by_valuators' %>

    <%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_spending_proposal_path(@investment) %> diff --git a/app/views/valuation/budget_investments/_written_by_valuators.html.erb b/app/views/valuation/budget_investments/_written_by_valuators.html.erb new file mode 100644 index 000000000..9dc5a8e8c --- /dev/null +++ b/app/views/valuation/budget_investments/_written_by_valuators.html.erb @@ -0,0 +1,53 @@ +

    + + <%= t("valuation.budget_investments.show.price") %> + (<%= t("valuation.budget_investments.show.currency") %>): + + <% if @investment.price.present? %> + <%= @investment.price %> + <% else %> + <%= t("valuation.budget_investments.show.undefined") %> + <% end %> +

    + +

    + + <%= t("valuation.budget_investments.show.price_first_year") %> + (<%= t("valuation.budget_investments.show.currency") %>): + + + <% if @investment.price_first_year.present? %> + <%= @investment.price_first_year %> + <% else %> + <%= t("valuation.budget_investments.show.undefined") %> + <% end %> +

    + +<%= explanation_field @investment.price_explanation %> + +

    + <%= t("valuation.budget_investments.show.duration") %>: + <% if @investment.duration.present? %> + <%= @investment.duration %> + <% else %> + <%= t("valuation.budget_investments.show.undefined") %> + <% end %> +

    + +

    + <%= t("valuation.budget_investments.show.feasibility") %>: + <%= t("valuation.budget_investments.show.#{@investment.feasibility}") %> +

    + +<%= explanation_field @investment.unfeasibility_explanation %> + +<% if @investment.valuation_finished %> +

    + <%= t("valuation.budget_investments.show.valuation_finished") %> +

    +<% end %> + +<% if @investment.internal_comments.present? %> +

    <%= t("valuation.budget_investments.show.internal_comments") %>

    + <%= explanation_field @investment.internal_comments %> +<% end %> diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb index 7b534897a..5cf75b8bb 100644 --- a/config/initializers/acts_as_taggable_on.rb +++ b/config/initializers/acts_as_taggable_on.rb @@ -42,6 +42,10 @@ module ActsAsTaggableOn ActsAsTaggableOn::Tag.where('taggings.taggable_type' => 'SpendingProposal').includes(:taggings).order(:name).uniq end + def self.budget_investment_tags + ActsAsTaggableOn::Tag.where('taggings.taggable_type' => 'Budget::Investment').includes(:taggings).order(:name).uniq + end + private def custom_counter_field_name_for(taggable_type) "#{taggable_type.underscore.pluralize}_count" diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 5a7a591bd..8eb300289 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -7,6 +7,9 @@ en: budget: one: "Participatory budget" other: "Participatory budgets" + budget/investment: + one: "Investment" + other: "Investments" comment: one: "Comment" other: "Comments" @@ -38,6 +41,12 @@ en: one: "Spending proposal" other: "Spending proposals" attributes: + budget/investment: + administrator_id: "Administrator" + description: "Description" + external_url: "Link to additional documentation" + heading_id: "Heading" + title: "Title" comment: body: "Comment" user: "User" diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index c7c811801..50ef09cf4 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -41,6 +41,12 @@ es: one: "Propuesta de inversión" other: "Propuestas de inversión" attributes: + budget/investment: + administrator_id: "Administrador" + description: "Descripción" + external_url: "Enlace a documentación adicional" + heading_id: "Partida presupuestaria" + title: "Título" comment: body: "Comentario" user: "Usuario" diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 9b5e7c19c..c9bd21c18 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -129,6 +129,14 @@ en: edit_dossier: Edit dossier tags: Tags undefined: Undefined + edit: + classification: Clasification + assigned_valuators: Valuators + select_heading: Select heading + submit_button: Update + tags: Tags + tags_placeholder: "Write the tags you want separated by commas (,)" + undefined: Undefined comments: index: filter: Filter diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 4f4536950..07b0c9991 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -129,6 +129,14 @@ es: edit_dossier: Editar informe tags: Etiquetas undefined: Sin definir + edit: + classification: Clasificación + assigned_valuators: Evaluadores + select_heading: Seleccionar partida + submit_button: Actualizar + tags: Etiquetas + tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)" + undefined: Sin definir comments: index: filter: Filtro diff --git a/config/locales/responders.en.yml b/config/locales/responders.en.yml index 825a9957b..9e8c8c4a7 100755 --- a/config/locales/responders.en.yml +++ b/config/locales/responders.en.yml @@ -17,7 +17,7 @@ en: debate: "Debate updated successfully." proposal: "Proposal updated successfully." spending_proposal: "Investment project updated succesfully." - budget_investment: "Budget Investment updated succesfully." + budget_investment: "Investment project updated succesfully." destroy: spending_proposal: "Spending proposal deleted succesfully." - budget_investment: "Budget Investment deleted succesfully." + budget_investment: "Investment project deleted succesfully." diff --git a/config/locales/valuation.en.yml b/config/locales/valuation.en.yml index e136562f4..c3b6d8283 100644 --- a/config/locales/valuation.en.yml +++ b/config/locales/valuation.en.yml @@ -4,6 +4,29 @@ en: menu: title: Valuation spending_proposals: Spending proposals + budget_investments: + show: + back: Back + heading: Investment project + info: Author info + by: Sent by + sent: Sent at + heading: Heading + dossier: Dossier + edit_dossier: Edit dossier + price: Price + price_first_year: Cost during the first year + currency: "€" + feasibility: Feasibility + feasible: Feasible + unfeasible: Unfeasible + undefined: Undefined + valuation_finished: Valuation finished + duration: Time scope + internal_comments: Internal comments + responsibles: Responsibles + assigned_admin: Assigned admin + assigned_valuators: Assigned valuators spending_proposals: index: geozone_filter_all: All zones diff --git a/config/locales/valuation.es.yml b/config/locales/valuation.es.yml index 7d4ff5ed6..34771587d 100644 --- a/config/locales/valuation.es.yml +++ b/config/locales/valuation.es.yml @@ -4,6 +4,29 @@ es: menu: title: Evaluación spending_proposals: Propuestas de inversión + budget_investments: + show: + back: Volver + heading: Propuesta de inversión + info: Datos de envío + by: Enviada por + sent: Fecha de creación + heading: Partida + dossier: Informe + edit_dossier: Editar informe + price: Coste + price_first_year: Coste en el primer año + currency: "€" + feasibility: Viabilidad + feasible: Viable + unfeasible: Inviable + undefined: Sin definir + valuation_finished: Informe finalizado + duration: Plazo de ejecución + internal_comments: Comentarios internos + responsibles: Responsables + assigned_admin: Administrador asignado + assigned_valuators: Evaluadores asignados spending_proposals: index: geozone_filter_all: Todos los ámbitos de actuación diff --git a/config/routes.rb b/config/routes.rb index 278153a19..b22806dbd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -162,7 +162,7 @@ Rails.application.routes.draw do end end - resources :budget_investments, only: [:index, :show] + resources :budget_investments, only: [:index, :show, :edit, :update] end resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb index ad08ee634..c34354ede 100644 --- a/spec/features/admin/budget_investments_spec.rb +++ b/spec/features/admin/budget_investments_spec.rb @@ -98,15 +98,16 @@ feature 'Admin budget investments' do expect(page).to have_link("Change name") expect(page).to_not have_link("Plant trees") - # click_link("Realocate visitors") - # click_link("Edit classification") - # expect(page).to have_button("Update") - # click_link("Go back") - # expect(page).to_not have_button("Update") - # click_link("Go back") + click_link("Change name") + click_link("Edit classification") + expect(page).to have_button("Update") + click_link("Go back") + expect(page).to_not have_button("Update") + click_link("Go back") - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to_not have_link("Realocate visitors") + expect(page).to have_link("Change name") + expect(page).to_not have_link("Plant trees") end scenario "Filtering by admin", :js do @@ -142,16 +143,16 @@ feature 'Admin budget investments' do expect(page).to_not have_link("Destroy the city") expect(page).to have_link("Realocate visitors") - # click_link("Realocate visitors") - # click_link("Edit classification") - # expect(page).to have_button("Update") - # click_link("Go back") - # expect(page).to_not have_button("Update") - # click_link("Go back") + click_link("Realocate visitors") + click_link("Edit classification") + expect(page).to have_button("Update") + click_link("Go back") + expect(page).to_not have_button("Update") + click_link("Go back") - # expect(page).to have_content('There is 1 investment') - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") end scenario "Filtering by valuator", :js do @@ -188,16 +189,16 @@ feature 'Admin budget investments' do expect(page).to_not have_link("Destroy the city") expect(page).to have_link("Realocate visitors") - # click_link("Realocate visitors") - # click_link("Edit classification") - # expect(page).to have_button("Update") - # click_link("Go back") - # expect(page).to_not have_button("Update") - # click_link("Go back") + click_link("Realocate visitors") + click_link("Edit classification") + expect(page).to have_button("Update") + click_link("Go back") + expect(page).to_not have_button("Update") + click_link("Go back") - # expect(page).to have_content('There is 1 investment') - # expect(page).to_not have_link("Destroy the city") - # expect(page).to have_link("Realocate visitors") + expect(page).to have_content('There is 1 investment') + expect(page).to_not have_link("Destroy the city") + expect(page).to have_link("Realocate visitors") end scenario "Current filter is properly highlighted" do @@ -297,16 +298,159 @@ feature 'Admin budget investments' do expect(page).to have_content("Educate the children") expect(page).to have_content("More schools") - # click_link("Educate the children") - # click_link("Edit classification") - # expect(page).to have_button("Update") - # click_link("Go back") - # expect(page).to_not have_button("Update") - # click_link("Go back") + click_link("Educate the children") + click_link("Edit classification") + expect(page).to have_button("Update") + click_link("Go back") + expect(page).to_not have_button("Update") + click_link("Go back") - # expect(page).to_not have_content("More hospitals") - # expect(page).to have_content("Educate the children") - # expect(page).to have_content("More schools") + expect(page).to_not have_content("More hospitals") + expect(page).to have_content("Educate the children") + expect(page).to have_content("More schools") + end + + end + + scenario 'Show' do + administrator = create(:administrator, user: create(:user, username: 'Ana', email: 'ana@admins.org')) + valuator = create(:valuator, user: create(:user, username: 'Rachel', email: 'rachel@valuators.org')) + budget_investment = create(:budget_investment, + price: 1234, + price_first_year: 1000, + feasibility: "unfeasible", + unfeasibility_explanation: 'It is impossible', + administrator: administrator) + budget_investment.valuators << valuator + + visit admin_budget_budget_investments_path(budget_investment.budget) + + click_link budget_investment.title + + expect(page).to have_content(budget_investment.title) + expect(page).to have_content(budget_investment.description) + expect(page).to have_content(budget_investment.author.name) + expect(page).to have_content(budget_investment.heading.name) + expect(page).to have_content('1234') + expect(page).to have_content('1000') + expect(page).to have_content('Unfeasible') + 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)') + end + end + + context "Edit" do + + scenario "Change title, description or heading" do + budget_investment = create(:budget_investment) + create(:budget_heading, group: budget_investment.group, name: "Barbate") + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit' + + fill_in 'budget_investment_title', with: 'Potatoes' + fill_in 'budget_investment_description', with: 'Carrots' + select "#{budget_investment.group.name}: Barbate", from: 'budget_investment[heading_id]' + + click_button 'Update' + + expect(page).to have_content 'Potatoes' + expect(page).to have_content 'Carrots' + expect(page).to have_content 'Barbate' + end + + scenario "Add administrator" do + budget_investment = create(:budget_investment) + administrator = create(:administrator, user: create(:user, username: 'Marta', email: 'marta@admins.org')) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit classification' + + select 'Marta (marta@admins.org)', from: 'budget_investment[administrator_id]' + click_button 'Update' + + expect(page).to have_content 'Investment project updated succesfully.' + expect(page).to have_content 'Assigned administrator: Marta' + end + + scenario "Add valuators" do + budget_investment = create(:budget_investment) + + valuator1 = create(:valuator, user: create(:user, username: 'Valentina', email: 'v1@valuators.org')) + valuator2 = create(:valuator, user: create(:user, username: 'Valerian', email: 'v2@valuators.org')) + valuator3 = create(:valuator, user: create(:user, username: 'Val', email: 'v3@valuators.org')) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit classification' + + check "budget_investment_valuator_ids_#{valuator1.id}" + check "budget_investment_valuator_ids_#{valuator3.id}" + + click_button 'Update' + + expect(page).to have_content 'Investment project updated succesfully.' + + within('#assigned_valuators') do + expect(page).to have_content('Valentina (v1@valuators.org)') + expect(page).to have_content('Val (v3@valuators.org)') + expect(page).to_not have_content('Undefined') + expect(page).to_not have_content('Valerian (v2@valuators.org)') + end + end + + scenario "Adds existing tags", :js do + create(:budget_investment, tag_list: 'Education, Health') + + budget_investment = create(:budget_investment) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit classification' + + find('.js-add-tag-link', text: 'Education').click + + fill_in 'budget_investment_title', with: 'Updated title' + + click_button 'Update' + + expect(page).to have_content 'Investment project updated succesfully.' + + within "#tags" do + expect(page).to have_content 'Education' + expect(page).to_not have_content 'Health' + end + end + + scenario "Adds non existent tags" do + budget_investment = create(:budget_investment) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit classification' + + fill_in 'budget_investment_tag_list', with: 'Refugees, Solidarity' + click_button 'Update' + + expect(page).to have_content 'Investment project updated succesfully.' + + within "#tags" do + expect(page).to have_content 'Refugees' + expect(page).to have_content 'Solidarity' + end + end + + scenario "Errors on update" do + budget_investment = create(:budget_investment) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + click_link 'Edit' + + fill_in 'budget_investment_title', with: '' + + click_button 'Update' + + expect(page).to have_content "can't be blank" end end From f8a5f02cc920c23238f81757751626e93540b690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 7 Sep 2016 15:08:03 +0200 Subject: [PATCH 114/384] adds i18n to budgets setting --- config/locales/settings.en.yml | 1 + config/locales/settings.es.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index 32874e32b..31f0d8add 100755 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -14,6 +14,7 @@ en: email_domain_for_officials: "Email domain for public officials" per_page_code: "Code to be included on every page" feature: + budgets: Participatory budgeting debates: Debates spending_proposals: Investment projects spending_proposal_features: diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index aa5dea14e..532fb878e 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -14,7 +14,8 @@ es: email_domain_for_officials: "Dominio de email para cargos públicos" per_page_code: "Código a incluir en cada página" feature: + budgets: Presupuestos participativos debates: Debates spending_proposals: Propuestas de inversión spending_proposal_features: - voting_allowed: Votaciones sobre propuestas de inversión. \ No newline at end of file + voting_allowed: Votaciones sobre propuestas de inversión \ No newline at end of file From 2d56415048c4968e2f33e5caea367b91599810c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 7 Sep 2016 15:13:39 +0200 Subject: [PATCH 115/384] adds feature flag for admin/budgets management --- app/controllers/admin/budget_headings_controller.rb | 2 ++ app/controllers/admin/budget_investments_controller.rb | 2 ++ app/controllers/admin/budgets_controller.rb | 2 ++ app/controllers/budgets_controller.rb | 3 +++ spec/features/admin/budget_investments_spec.rb | 9 +++++++++ 5 files changed, 18 insertions(+) diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb index 3c8ccafa0..56903b744 100644 --- a/app/controllers/admin/budget_headings_controller.rb +++ b/app/controllers/admin/budget_headings_controller.rb @@ -1,4 +1,6 @@ class Admin::BudgetHeadingsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets def create @budget = Budget.find params[:budget_id] diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 17cc76923..399ec0ec8 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -1,4 +1,6 @@ class Admin::BudgetInvestmentsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets before_action :load_budget before_action :load_investment, only: [:show, :edit, :update] diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index 144b43a7f..2dd210947 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -1,4 +1,6 @@ class Admin::BudgetsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets has_filters %w{open finished}, only: :index diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb index db05aafea..9c8a2036b 100644 --- a/app/controllers/budgets_controller.rb +++ b/app/controllers/budgets_controller.rb @@ -1,4 +1,7 @@ class BudgetsController < ApplicationController + include FeatureFlags + feature_flag :budgets + load_and_authorize_resource respond_to :html, :js diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb index c34354ede..a9ec45dba 100644 --- a/spec/features/admin/budget_investments_spec.rb +++ b/spec/features/admin/budget_investments_spec.rb @@ -9,6 +9,15 @@ feature 'Admin budget investments' do @budget = create(:budget) end + context "Feature flag" do + + scenario 'Disabled with a feature flag' do + Setting['feature.budgets'] = nil + expect{ visit admin_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + end + context "Index" do scenario 'Displaying investmentss' do From b4c7675c31969c3fa70cb8261748c79b94bde935 Mon Sep 17 00:00:00 2001 From: kikito Date: Wed, 7 Sep 2016 15:58:39 +0200 Subject: [PATCH 116/384] imports votes --- lib/spending_proposals_importer.rb | 4 ++++ spec/lib/spending_proposals_importer_spec.rb | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/spending_proposals_importer.rb b/lib/spending_proposals_importer.rb index 5641c082b..9fe6dfa32 100644 --- a/lib/spending_proposals_importer.rb +++ b/lib/spending_proposals_importer.rb @@ -51,6 +51,10 @@ class SpendingProposalsImporter investment.valuators = sp.valuation_assignments.map(&:valuator) + votes = ActsAsVotable::Vote.where(votable_type: 'SpendingProposal', votable_id: sp.id) + + votes.each {|v| investment.vote_by({voter: v.voter, vote: 'yes'}) } + investment end diff --git a/spec/lib/spending_proposals_importer_spec.rb b/spec/lib/spending_proposals_importer_spec.rb index 598e028e3..d8cb50082 100644 --- a/spec/lib/spending_proposals_importer_spec.rb +++ b/spec/lib/spending_proposals_importer_spec.rb @@ -74,5 +74,19 @@ describe SpendingProposalsImporter do expect(inv.valuators).to include(peter) expect(inv.valuators).to include(john) end + + it "Imports votes" do + sp = create(:spending_proposal) + votes = create_list(:vote, 4, votable: sp) + voters = votes.map(&:voter).sort_by(&:id) + + inv = importer.import(sp) + + expect(inv.total_votes).to eq(sp.total_votes) + + imported_votes = ActsAsVotable::Vote.where(votable_type: "Budget::Investment", votable_id: inv.id) + + expect(imported_votes.map(&:voter).sort_by(&:id)).to eq(voters) + end end end From 857a4ba42bd62db271283627457140d418878b90 Mon Sep 17 00:00:00 2001 From: kikito Date: Wed, 7 Sep 2016 18:08:33 +0200 Subject: [PATCH 117/384] Adds comment to the final part of spending_proposals_importer --- lib/spending_proposals_importer.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/spending_proposals_importer.rb b/lib/spending_proposals_importer.rb index 9fe6dfa32..72a3922b8 100644 --- a/lib/spending_proposals_importer.rb +++ b/lib/spending_proposals_importer.rb @@ -1,9 +1,6 @@ class SpendingProposalsImporter def import(sp) - # votes - # comments - budget = Budget.last || Budget.create!(name: Date.today.year.to_s, currency_symbol: "€") group = nil @@ -55,6 +52,16 @@ class SpendingProposalsImporter votes.each {|v| investment.vote_by({voter: v.voter, vote: 'yes'}) } + # Spending proposals are not commentable in Consul so we can not test this + # + # Comment.where(commentable_type: 'SpendingProposal', commentable_id: sp.id).update_all( + # commentable_type: 'Budget::Investment', commentable_id: investment.id + # ) + # Budget::Investment.reset_counters(investment.id, :comments) + + # Spending proposals have ballot_lines in Madrid, but not in consul, so we + # can not test this either + investment end From f456400218a800e083de024003055f2bdd6efba5 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 9 Sep 2016 12:17:05 +0200 Subject: [PATCH 118/384] Removes duplicated relationship between budget and investment --- app/models/budget.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/budget.rb b/app/models/budget.rb index b8fd34da9..022ce8f2a 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -13,7 +13,6 @@ class Budget < ActiveRecord::Base has_many :ballots, dependent: :destroy has_many :groups, dependent: :destroy has_many :headings, through: :groups - has_many :investments, through: :headings scope :open, -> { where.not(phase: "finished") } scope :finished, -> { where(phase: "finished") } From cb7ea79aafc6f382b208e43ddd562b4f2089a660 Mon Sep 17 00:00:00 2001 From: kikito Date: Fri, 9 Sep 2016 12:19:38 +0200 Subject: [PATCH 119/384] Replaces spending proposals by budget investments in user/show --- app/controllers/users_controller.rb | 22 +++--- app/views/users/_activity_page.html.erb | 3 +- app/views/users/_budget_investments.html.erb | 11 +++ app/views/users/_spending_proposals.html.erb | 20 ----- app/views/users/show.html.erb | 20 +++-- spec/features/users_spec.rb | 82 +++++--------------- 6 files changed, 49 insertions(+), 109 deletions(-) create mode 100644 app/views/users/_budget_investments.html.erb delete mode 100644 app/views/users/_spending_proposals.html.erb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fbd77f184..01663ec77 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,8 +1,7 @@ class UsersController < ApplicationController - has_filters %w{proposals debates comments spending_proposals}, only: :show + has_filters %w{proposals debates budget_investments comments}, only: :show load_and_authorize_resource - helper_method :authorized_for_filter? helper_method :author? helper_method :author_or_admin? @@ -15,8 +14,8 @@ class UsersController < ApplicationController @activity_counts = HashWithIndifferentAccess.new( proposals: Proposal.where(author_id: @user.id).count, debates: Debate.where(author_id: @user.id).count, - comments: Comment.not_as_admin_or_moderator.where(user_id: @user.id).count, - spending_proposals: SpendingProposal.where(author_id: @user.id).count) + budget_investments: Budget::Investment.where(author_id: @user.id).count, + comments: Comment.not_as_admin_or_moderator.where(user_id: @user.id).count) end def load_filtered_activity @@ -24,8 +23,8 @@ class UsersController < ApplicationController case params[:filter] when "proposals" then load_proposals when "debates" then load_debates + when "budget_investments" then load_budget_investments when "comments" then load_comments - when "spending_proposals" then load_spending_proposals if author_or_admin? else load_available_activity end end @@ -37,12 +36,12 @@ class UsersController < ApplicationController elsif @activity_counts[:debates] > 0 load_debates @current_filter = "debates" + elsif @activity_counts[:budget_investments] > 0 + load_budget_investments + @current_filter = "budget_investments" elsif @activity_counts[:comments] > 0 load_comments @current_filter = "comments" - elsif @activity_counts[:spending_proposals] > 0 && author_or_admin? - load_spending_proposals - @current_filter = "spending_proposals" end end @@ -58,8 +57,8 @@ class UsersController < ApplicationController @comments = Comment.not_as_admin_or_moderator.where(user_id: @user.id).includes(:commentable).order(created_at: :desc).page(params[:page]) end - def load_spending_proposals - @spending_proposals = SpendingProposal.where(author_id: @user.id).order(created_at: :desc).page(params[:page]) + def load_budget_investments + @budget_investments = Budget::Investment.where(author_id: @user.id).order(created_at: :desc).page(params[:page]) end def valid_access? @@ -78,7 +77,4 @@ class UsersController < ApplicationController @authorized_current_user ||= current_user && (current_user == @user || current_user.moderator? || current_user.administrator?) end - def authorized_for_filter?(filter) - filter == "spending_proposals" ? author_or_admin? : true - end end diff --git a/app/views/users/_activity_page.html.erb b/app/views/users/_activity_page.html.erb index dbda1da39..c8a3ed34b 100644 --- a/app/views/users/_activity_page.html.erb +++ b/app/views/users/_activity_page.html.erb @@ -1,4 +1,5 @@ <%= render "proposals" if @proposals.present? %> <%= render "debates" if @debates.present? && feature?(:debates) %> +<%= render "investments" if @investments.present? %> +<%= render "budget_investments" if @budget_investments.present? %> <%= render "comments" if @comments.present? %> -<%= render "spending_proposals" if @spending_proposals.present? %> diff --git a/app/views/users/_budget_investments.html.erb b/app/views/users/_budget_investments.html.erb new file mode 100644 index 000000000..4532ede47 --- /dev/null +++ b/app/views/users/_budget_investments.html.erb @@ -0,0 +1,11 @@ + + <% @budget_investments.each do |budget_investment| %> + + + + <% end %> +
    + <%= link_to budget_investment.title, budget_investment_path(budget_investment.budget, budget_investment) %> +
    + +<%= paginate @budget_investments %> diff --git a/app/views/users/_spending_proposals.html.erb b/app/views/users/_spending_proposals.html.erb deleted file mode 100644 index 40fd8d70d..000000000 --- a/app/views/users/_spending_proposals.html.erb +++ /dev/null @@ -1,20 +0,0 @@ - - <% @spending_proposals.each do |spending_proposal| %> - - - - - <% end %> -
    - <%= link_to spending_proposal.title, spending_proposal %> - - <% if can?(:destroy, spending_proposal) %> - <%= link_to t("users.show.delete_spending_proposal"), - spending_proposal, - method: :delete, - data: { confirm: t("users.show.confirm_deletion_spending_proposal") }, - class: 'delete' %> - <% end %> -
    - -<%= paginate @spending_proposals %> \ No newline at end of file diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index c264d336f..c00769216 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -26,17 +26,15 @@ diff --git a/app/views/valuation/budget_investments/index.html.erb b/app/views/valuation/budget_investments/index.html.erb new file mode 100644 index 000000000..9b82f7f5a --- /dev/null +++ b/app/views/valuation/budget_investments/index.html.erb @@ -0,0 +1,42 @@ +

    <%= @budget.name %> - <%= t("valuation.budget_investments.index.title") %>

    + +
    + <% @heading_filters.each_slice(8) do |slice| %> +
    + <% slice.each do |filter| %> + <%= link_to valuation_budget_budget_investments_path(budget_id: @budget.id, heading_id: filter[:id]), + class: "#{'active' if params[:heading_id].to_s == filter[:id].to_s}" do %> + <%= filter[:name] %> (<%= filter[:pending_count] %>) + <% end %> + <% end %> +
    + <% end %> +
    + +<%= render 'shared/filter_subnav', i18n_namespace: "valuation.budget_investments.index" %> + +

    <%= page_entries_info @investments %>

    + + + <% @investments.each do |investment| %> + + + + + + + + <% end %> +
    + <%= investment.id %> + + <%= link_to investment.title, valuation_budget_budget_investment_path(@budget, investment) %> + + <%= link_to t("valuation.budget_investments.index.edit"), edit_valuation_budget_budget_investment_path(@budget, investment) %> + + <%= assigned_valuators_info(investment.valuators) %> + + <%= investment.heading.name %> +
    + +<%= paginate @investments %> diff --git a/app/views/valuation/budgets/index.html.erb b/app/views/valuation/budgets/index.html.erb new file mode 100644 index 000000000..014633373 --- /dev/null +++ b/app/views/valuation/budgets/index.html.erb @@ -0,0 +1,17 @@ +

    <%= t("valuation.budgets.index.title") %>

    + +<%= render 'shared/filter_subnav', i18n_namespace: "valuation.budgets.index" %> + +

    <%= page_entries_info @budgets %>

    + + + <% @budgets.each do |budget| %> + + + + <% end %> +
    + <%= link_to budget.name, valuation_budget_budget_investments_path(budget_id: budget.id) %> +
    + +<%= paginate @budgets %> \ No newline at end of file diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 161ed6dfd..9b675ffd0 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -132,6 +132,8 @@ ignore_unused: - 'moderation.debates.index.filter*' - 'moderation.debates.index.order*' - 'valuation.spending_proposals.index.filter*' + - 'valuation.budgets.index.filter*' + - 'valuation.budget_investments.index.filter*' - 'users.show.filters.*' - 'debates.index.select_order' - 'debates.index.orders.*' diff --git a/config/locales/valuation.en.yml b/config/locales/valuation.en.yml index c3b6d8283..b1cf1ff81 100644 --- a/config/locales/valuation.en.yml +++ b/config/locales/valuation.en.yml @@ -3,8 +3,27 @@ en: valuation: menu: title: Valuation + budgets: Participatory budgets spending_proposals: Spending proposals + budgets: + index: + title: Participatory budgets + filters: + open: Open + finished: Finished budget_investments: + index: + headings_filter_all: All headings + filters: + valuation_open: Open + valuating: Under valuation + valuation_finished: Valuation finished + title: Investment projects + edit: Edit + valuators_assigned: + one: Assigned valuator + other: "%{count} valuators assigned" + no_valuators_assigned: No valuators assigned show: back: Back heading: Investment project @@ -27,6 +46,8 @@ en: responsibles: Responsibles assigned_admin: Assigned admin assigned_valuators: Assigned valuators + notice: + valuate: "Dossier updated" spending_proposals: index: geozone_filter_all: All zones @@ -36,10 +57,6 @@ en: 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 diff --git a/config/locales/valuation.es.yml b/config/locales/valuation.es.yml index 34771587d..163ee658d 100644 --- a/config/locales/valuation.es.yml +++ b/config/locales/valuation.es.yml @@ -3,8 +3,27 @@ es: valuation: menu: title: Evaluación + budgets: Presupuestos participativos spending_proposals: Propuestas de inversión + budgets: + index: + title: Presupuestos participativos + filters: + open: Abiertos + finished: Terminados budget_investments: + index: + headings_filter_all: Todas las partidas + filters: + valuation_open: Abiertas + valuating: En evaluación + valuation_finished: Evaluación finalizada + title: Propuestas de inversión + edit: Editar + valuators_assigned: + one: Evaluador asignado + other: "%{count} evaluadores asignados" + no_valuators_assigned: Sin evaluador show: back: Volver heading: Propuesta de inversión @@ -27,6 +46,8 @@ es: responsibles: Responsables assigned_admin: Administrador asignado assigned_valuators: Evaluadores asignados + notice: + valuate: "Dossier actualizado" spending_proposals: index: geozone_filter_all: Todos los ámbitos de actuación @@ -36,10 +57,6 @@ es: 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 diff --git a/config/routes.rb b/config/routes.rb index b22806dbd..c10b4ba2b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -237,11 +237,17 @@ Rails.application.routes.draw do end namespace :valuation do - root to: "spending_proposals#index" + root to: "budgets#index" resources :spending_proposals, only: [:index, :show, :edit] do patch :valuate, on: :member end + + resources :budgets, only: :index do + resources :budget_investments, only: [:index, :show, :edit] do + patch :valuate, on: :member + end + end end namespace :management do diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index a87cd741a..b01c798cc 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -9,7 +9,7 @@ feature 'Admin budgets' do context 'Feature flag' do - xscenario 'Disabled with a feature flag' do + scenario 'Disabled with a feature flag' do Setting['feature.budgets'] = nil expect{ visit admin_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled) end diff --git a/spec/features/valuation/budget_investments_spec.rb b/spec/features/valuation/budget_investments_spec.rb new file mode 100644 index 000000000..53f60e416 --- /dev/null +++ b/spec/features/valuation/budget_investments_spec.rb @@ -0,0 +1,161 @@ +require 'rails_helper' + +feature 'Valuation budget investments' do + + background do + @valuator = create(:valuator, user: create(:user, username: 'Rachel', email: 'rachel@valuators.org')) + login_as(@valuator.user) + @budget = create(:budget, valuating: true) + end + + scenario 'Disabled with a feature flag' do + Setting['feature.budgets'] = nil + expect{ visit valuation_budget_budget_investments_path(create(:budget)) }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + scenario 'Index shows budget investments assigned to current valuator' do + investment1 = create(:budget_investment, budget: @budget) + investment2 = create(:budget_investment, budget: @budget) + + investment1.valuators << @valuator + + visit valuation_budget_budget_investments_path(@budget) + + expect(page).to have_content(investment1.title) + expect(page).to_not have_content(investment2.title) + end + + scenario 'Index shows no budget investment to admins no valuators' do + investment1 = create(:budget_investment, budget: @budget) + investment2 = create(:budget_investment, budget: @budget) + + investment1.valuators << @valuator + + logout + login_as create(:administrator).user + visit valuation_budget_budget_investments_path(@budget) + + expect(page).to_not have_content(investment1.title) + expect(page).to_not have_content(investment2.title) + end + + scenario 'Index orders budget investments by votes' do + investment10 = create(:budget_investment, budget: @budget, cached_votes_up: 10) + investment100 = create(:budget_investment, budget: @budget, cached_votes_up: 100) + investment1 = create(:budget_investment, budget: @budget, cached_votes_up: 1) + + investment1.valuators << @valuator + investment10.valuators << @valuator + investment100.valuators << @valuator + + visit valuation_budget_budget_investments_path(@budget) + + expect(investment100.title).to appear_before(investment10.title) + expect(investment10.title).to appear_before(investment1.title) + end + + scenario 'Index shows assignments info' do + investment1 = create(:budget_investment, budget: @budget) + investment2 = create(:budget_investment, budget: @budget) + investment3 = create(:budget_investment, budget: @budget) + + valuator1 = create(:valuator, user: create(:user)) + valuator2 = create(:valuator, user: create(:user)) + valuator3 = create(:valuator, user: create(:user)) + + investment1.valuator_ids = [@valuator.id] + investment2.valuator_ids = [@valuator.id, valuator1.id, valuator2.id] + investment3.valuator_ids = [@valuator.id, valuator3.id] + + visit valuation_budget_budget_investments_path(@budget) + + within("#budget_investment_#{investment1.id}") do + expect(page).to have_content("Rachel") + end + + within("#budget_investment_#{investment2.id}") do + expect(page).to have_content("3 valuators assigned") + end + + within("#budget_investment_#{investment3.id}") do + expect(page).to have_content("2 valuators assigned") + end + end + + scenario "Index filtering by heading", :js do + group = create(:budget_group, budget: @budget) + heading1 = create(:budget_heading, name: "District 9", group: group) + heading2 = create(:budget_heading, name: "Down to the river", group: group) + investment1 = create(:budget_investment, title: "Realocate visitors", heading: heading1, group: group, budget: @budget) + investment2 = create(:budget_investment, title: "Destroy the city", heading: heading2, group: group, budget: @budget) + investment1.valuators << @valuator + investment2.valuators << @valuator + + visit valuation_budget_budget_investments_path(@budget) + + expect(page).to have_link("Realocate visitors") + expect(page).to have_link("Destroy the city") + + + expect(page).to have_content "All headings (2)" + expect(page).to have_content "District 9 (1)" + expect(page).to have_content "Down to the river (1)" + + click_link "District 9", exact: false + + expect(page).to have_link("Realocate visitors") + expect(page).to_not have_link("Destroy the city") + + click_link "Down to the river", exact: false + + expect(page).to have_link("Destroy the city") + expect(page).to_not have_link("Realocate visitors") + + click_link "All headings", exact: false + 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_budget_budget_investments_path(@budget) + + 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_budget_budget_investments_path(@budget, 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(:budget_investment, budget: @budget, title: "Ongoing valuation") + valuated = create(:budget_investment, budget: @budget, title: "Old idea", valuation_finished: true) + valuating.valuators << @valuator + valuated.valuators << @valuator + + visit valuation_budget_budget_investments_path(@budget) + + expect(page).to have_content("Ongoing valuation") + expect(page).to_not have_content("Old idea") + + visit valuation_budget_budget_investments_path(@budget, filter: 'valuating') + + expect(page).to have_content("Ongoing valuation") + expect(page).to_not have_content("Old idea") + + visit valuation_budget_budget_investments_path(@budget, filter: 'valuation_finished') + + expect(page).to_not have_content("Ongoing valuation") + expect(page).to have_content("Old idea") + end + +end \ No newline at end of file diff --git a/spec/features/valuation/budgets_spec.rb b/spec/features/valuation/budgets_spec.rb new file mode 100644 index 000000000..0f61b8078 --- /dev/null +++ b/spec/features/valuation/budgets_spec.rb @@ -0,0 +1,74 @@ +require 'rails_helper' + +feature 'Valuation budgets' 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.budgets'] = nil + expect{ visit valuation_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + context 'Index' do + + scenario 'Displaying budgets' do + budget = create(:budget) + visit valuation_budgets_path + + expect(page).to have_content(budget.name) + end + + scenario 'Filters by phase' do + budget1 = create(:budget) + budget2 = create(:budget, :accepting) + budget3 = create(:budget, :selecting) + budget4 = create(:budget, :balloting) + budget5 = create(:budget, :finished) + + visit valuation_budgets_path + expect(page).to have_content(budget1.name) + expect(page).to have_content(budget2.name) + expect(page).to have_content(budget3.name) + expect(page).to have_content(budget4.name) + expect(page).to_not have_content(budget5.name) + + click_link 'Finished' + expect(page).to_not have_content(budget1.name) + expect(page).to_not have_content(budget2.name) + expect(page).to_not have_content(budget3.name) + expect(page).to_not have_content(budget4.name) + expect(page).to have_content(budget5.name) + + click_link 'Open' + expect(page).to have_content(budget1.name) + expect(page).to have_content(budget2.name) + expect(page).to have_content(budget3.name) + expect(page).to have_content(budget4.name) + expect(page).to_not have_content(budget5.name) + end + + scenario 'Current filter is properly highlighted' do + filters_links = {'open' => 'Open', 'finished' => 'Finished'} + + visit valuation_budgets_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_budgets_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 + + end + +end \ No newline at end of file From 42c705e1e3b40073b25bdc5ff09a949aeecf2da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 9 Sep 2016 15:09:06 +0200 Subject: [PATCH 127/384] adds dossier editing of investments to valuation --- app/assets/javascripts/application.js | 2 + ...valuation_budget_investment_form.js.coffee | 32 +++ .../budget_investments_controller.rb | 18 +- .../admin/budget_investments/show.html.erb | 2 +- .../budget_investments/edit.html.erb | 141 +++++++++++ .../budget_investments/show.html.erb | 55 ++++ config/locales/valuation.en.yml | 16 +- config/locales/valuation.es.yml | 16 +- .../valuation/budget_investments_spec.rb | 234 ++++++++++++++++++ 9 files changed, 503 insertions(+), 13 deletions(-) create mode 100644 app/assets/javascripts/valuation_budget_investment_form.js.coffee create mode 100644 app/views/valuation/budget_investments/edit.html.erb create mode 100644 app/views/valuation/budget_investments/show.html.erb diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 7fb92b5e3..b9a3ec9dc 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -42,6 +42,7 @@ //= require suggest //= require forms //= require tracks +//= require valuation_budget_investment_form //= require valuation_spending_proposal_form //= require embed_video //= require banners @@ -63,6 +64,7 @@ var initialize_modules = function() { App.Suggest.initialize(); App.Forms.initialize(); App.Tracks.initialize(); + App.ValuationBudgetInvestmentForm.initialize(); App.ValuationSpendingProposalForm.initialize(); App.EmbedVideo.initialize(); App.Banners.initialize(); diff --git a/app/assets/javascripts/valuation_budget_investment_form.js.coffee b/app/assets/javascripts/valuation_budget_investment_form.js.coffee new file mode 100644 index 000000000..d79ff600e --- /dev/null +++ b/app/assets/javascripts/valuation_budget_investment_form.js.coffee @@ -0,0 +1,32 @@ +App.ValuationBudgetInvestmentForm = + + showFeasibleFields: -> + $('#valuation_budget_investment_edit_form #unfeasible_fields').hide('down') + $('#valuation_budget_investment_edit_form #feasible_fields').show() + + showNotFeasibleFields: -> + $('#valuation_budget_investment_edit_form #feasible_fields').hide('down') + $('#valuation_budget_investment_edit_form #unfeasible_fields').show() + + showAllFields: -> + $('#valuation_budget_investment_edit_form #feasible_fields').show('down') + $('#valuation_budget_investment_edit_form #unfeasible_fields').show('down') + + showFeasibilityFields: -> + feasibility = $("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']:checked").val() + if feasibility == 'feasible' + App.ValuationBudgetInvestmentForm.showFeasibleFields() + else if feasibility == 'unfeasible' + App.ValuationBudgetInvestmentForm.showNotFeasibleFields() + + + showFeasibilityFieldsOnChange: -> + $("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']").change -> + App.ValuationBudgetInvestmentForm.showAllFields() + App.ValuationBudgetInvestmentForm.showFeasibilityFields() + + + initialize: -> + App.ValuationBudgetInvestmentForm.showFeasibilityFields() + App.ValuationBudgetInvestmentForm.showFeasibilityFieldsOnChange() + false \ No newline at end of file diff --git a/app/controllers/valuation/budget_investments_controller.rb b/app/controllers/valuation/budget_investments_controller.rb index 348f275da..69269f6bd 100644 --- a/app/controllers/valuation/budget_investments_controller.rb +++ b/app/controllers/valuation/budget_investments_controller.rb @@ -4,6 +4,7 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController before_action :restrict_access_to_assigned_items, only: [:show, :edit, :valuate] before_action :load_budget + before_action :load_investment, only: [:show, :edit, :valuate] has_filters %w{valuating valuation_finished}, only: :index @@ -20,12 +21,7 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController def valuate if valid_price_params? && @investment.update(valuation_params) - - if @investment.unfeasible_email_pending? - @investment.send_unfeasible_email - end - - redirect_to valuation_budget_investment_path(@investment), notice: t('valuation.budget_investments.notice.valuate') + redirect_to valuation_budget_budget_investment_path(@budget, @investment), notice: t('valuation.budget_investments.notice.valuate') else render action: :edit end @@ -37,6 +33,10 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController @budget = Budget.find(params[:budget_id]) end + def load_investment + @investment = @budget.investments.find params[:id] + end + def heading_filters investments = @budget.investments.by_valuator(current_user.valuator.try(:id)).valuation_open.select(:heading_id).all.to_a @@ -57,13 +57,11 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController end def valuation_params - params[:budget_investment][:feasible] = nil if params[:budget_investment][:feasible] == 'nil' - - params.require(:budget_investment).permit(:price, :price_first_year, :price_explanation, :feasible, :feasible_explanation, :duration, :valuation_finished, :internal_comments) + params.require(:budget_investment).permit(:price, :price_first_year, :price_explanation, :feasibility, :unfeasibility_explanation, :duration, :valuation_finished, :internal_comments) end def restrict_access_to_assigned_items - raise ActionController::RoutingError.new('Not Found') unless current_user.administrator? || ValuatorAssignment.exists?(investment_id: params[:id], valuator_id: current_user.valuator.id) + raise ActionController::RoutingError.new('Not Found') unless current_user.administrator? || Budget::ValuatorAssignment.exists?(investment_id: params[:id], valuator_id: current_user.valuator.id) end def valid_price_params? diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb index e7423b82d..3ebfd4940 100644 --- a/app/views/admin/budget_investments/show.html.erb +++ b/app/views/admin/budget_investments/show.html.erb @@ -44,6 +44,6 @@ <%= render 'valuation/budget_investments/written_by_valuators' %>

    - <%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_spending_proposal_path(@investment) %> + <%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_budget_budget_investment_path(@budget, @investment) %>

    diff --git a/app/views/valuation/budget_investments/edit.html.erb b/app/views/valuation/budget_investments/edit.html.erb new file mode 100644 index 000000000..e03d850a4 --- /dev/null +++ b/app/views/valuation/budget_investments/edit.html.erb @@ -0,0 +1,141 @@ +<%= link_to "#{t('valuation.budget_investments.show.title')} #{@investment.id}", valuation_budget_budget_investment_path(@budget, @investment), class: 'back' %> +

    <%= t("valuation.budget_investments.edit.dossier") %>

    + +<%= form_for(@investment, url: valuate_valuation_budget_budget_investment_path(@budget, @investment), html: {id: "valuation_budget_investment_edit_form"}) do |f| %> + <%= render 'shared/errors', resource: @investment %> +
    +
    +
    + <%= t('valuation.budget_investments.edit.feasibility') %> +
    + + <%= f.radio_button :feasibility, 'undecided', label: false %> + <%= f.label :feasibility_undecided, t('valuation.budget_investments.edit.undefined_feasible') %> + +
    + +
    + + <%= f.radio_button :feasibility, 'feasible', label: false %> + <%= f.label :feasibility_feasible, t('valuation.budget_investments.edit.feasible') %> + +
    + +
    + + <%= f.radio_button :feasibility, 'unfeasible', label: false %> + <%= f.label :feasibility_unfeasible, t('valuation.budget_investments.edit.unfeasible') %> + +
    +
    +
    +
    + +
    + +
    +
    + <%= f.label :unfeasibility_explanation, t("valuation.budget_investments.edit.feasible_explanation_html") %> + <%= f.text_area :unfeasibility_explanation, label: false, rows: 3 %> +
    +
    + +
    + +
    + +
    +
    + <%= f.label :price, "#{t('valuation.budget_investments.edit.price_html', currency: @budget.currency_symbol)}" %> + <%= f.number_field :price, label: false, max: 1000000000000000 %> +
    + +
    + <%= f.label :price_first_year, "#{t('valuation.budget_investments.edit.price_first_year_html', currency: @budget.currency_symbol)}" %> + <%= f.number_field :price_first_year, label: false, max: 1000000000000000 %> +
    +
    + +
    +
    + <%= f.label :price_explanation, t("valuation.budget_investments.edit.price_explanation_html") %> + <%= f.text_area :price_explanation, label: false, rows: 3 %> +
    +
    + +
    +
    + <%= f.label :duration, t("valuation.budget_investments.edit.duration_html") %> + <%= f.text_field :duration, label: false %> +
    +
    + +
    + +
    +
    + <%= f.label :valuation_finished do %> + <%= f.check_box :valuation_finished, title: t('valuation.budget_investments.edit.valuation_finished'), label: false %> + <%= t("valuation.budget_investments.edit.valuation_finished") %> + <% end %> +
    +
    + +
    +
    + <%= f.label :internal_comments, t("valuation.budget_investments.edit.internal_comments_html") %> + <%= f.text_area :internal_comments, label: false, rows: 3 %> +
    +
    + +
    +
    + <%= f.submit(class: "button expanded large", value: t("valuation.budget_investments.edit.save")) %> +
    +
    +<% end %> + +

    <%= @investment.title %>

    + +<%= safe_html_with_links @investment.description %> + +<% if @investment.external_url.present? %> +

    <%= text_with_links @investment.external_url %>

    +<% end %> + +

    <%= t("valuation.budget_investments.show.info") %>

    + +

    <%= t("valuation.budget_investments.show.by") %>: + <%= link_to @investment.author.name, user_path(@investment.author) %> +

    + +

    <%= t("valuation.budget_investments.show.heading") %>: + <%= @investment.heading.name %> +

    + +

    <%= t("valuation.budget_investments.show.sent") %>: + <%= l @investment.created_at, format: :datetime %> +

    + +

    <%= t("valuation.budget_investments.show.responsibles") %>

    + +

    <%= t("valuation.budget_investments.show.assigned_admin") %>: + <% if @investment.administrator.present? %> + <%= @investment.administrator.name %> (<%= @investment.administrator.email %>) + <% else %> + <%= t("valuation.budget_investments.show.undefined") %> + <% end %> +

    + +

    <%= t("valuation.budget_investments.show.assigned_valuators") %>:

    +
    +
      + <% @investment.valuators.each do |valuator| %> +
    • <%= valuator.name %> (<%= valuator.email %>)
    • + <% end %> + + <% if @investment.valuators.empty? %> +
    • <%= t("valuation.budget_investments.show.undefined") %>
    • + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/valuation/budget_investments/show.html.erb b/app/views/valuation/budget_investments/show.html.erb new file mode 100644 index 000000000..602c53dda --- /dev/null +++ b/app/views/valuation/budget_investments/show.html.erb @@ -0,0 +1,55 @@ +<%= render "shared/back_link" %> + +

    <%= t("valuation.budget_investments.show.title") %> <%= @investment.id %>

    +

    <%= @investment.title %>

    + +<%= safe_html_with_links @investment.description %> + +<% if @investment.external_url.present? %> +

    <%= text_with_links @investment.external_url %>

    +<% end %> + +

    <%= t("valuation.budget_investments.show.info") %>

    + +

    <%= t("valuation.budget_investments.show.by") %>: + <%= link_to @investment.author.name, user_path(@investment.author) %> +

    + +

    <%= t("valuation.budget_investments.show.heading") %>: + <%= @investment.heading.name %> +

    + +

    <%= t("valuation.budget_investments.show.sent") %>: + <%= l @investment.created_at, format: :datetime %> +

    + +

    <%= t("valuation.budget_investments.show.responsibles") %>

    + +

    <%= t("valuation.budget_investments.show.assigned_admin") %>: + <% if @investment.administrator.present? %> + <%= @investment.administrator.name_and_email %> + <% else %> + <%= t("valuation.budget_investments.show.undefined") %> + <% end %> +

    + +

    <%= t("valuation.budget_investments.show.assigned_valuators") %>:

    +
    +
      + <% @investment.valuators.each do |valuator| %> +
    • <%= valuator.name_and_email %>
    • + <% end %> + + <% if @investment.valuators.empty? %> +
    • <%= t("valuation.budget_investments.show.undefined") %>
    • + <% end %> +
    +
    + +

    <%= t("valuation.budget_investments.show.dossier") %>

    + +

    + <%= link_to t("valuation.budget_investments.show.edit_dossier"), edit_valuation_budget_budget_investment_path(@budget, @investment) %> +

    + +<%= render 'written_by_valuators' %> \ No newline at end of file diff --git a/config/locales/valuation.en.yml b/config/locales/valuation.en.yml index b1cf1ff81..a5195deca 100644 --- a/config/locales/valuation.en.yml +++ b/config/locales/valuation.en.yml @@ -26,7 +26,7 @@ en: no_valuators_assigned: No valuators assigned show: back: Back - heading: Investment project + title: Investment project info: Author info by: Sent by sent: Sent at @@ -46,6 +46,20 @@ en: responsibles: Responsibles assigned_admin: Assigned admin assigned_valuators: Assigned valuators + edit: + dossier: Dossier + price_html: "Price (%{currency})" + price_first_year_html: "Cost during the first year (%{currency})" + price_explanation_html: Price explanation + feasibility: Feasibility + feasible: Feasible + unfeasible: Not feasible + undefined_feasible: Pending + feasible_explanation_html: Feasibility explanation + valuation_finished: Valuation finished + duration_html: Time scope + internal_comments_html: Internal comments + save: Save changes notice: valuate: "Dossier updated" spending_proposals: diff --git a/config/locales/valuation.es.yml b/config/locales/valuation.es.yml index 163ee658d..67302437b 100644 --- a/config/locales/valuation.es.yml +++ b/config/locales/valuation.es.yml @@ -26,7 +26,7 @@ es: no_valuators_assigned: Sin evaluador show: back: Volver - heading: Propuesta de inversión + title: Propuesta de inversión info: Datos de envío by: Enviada por sent: Fecha de creación @@ -46,6 +46,20 @@ es: responsibles: Responsables assigned_admin: Administrador asignado assigned_valuators: Evaluadores asignados + edit: + dossier: Informe + price_html: "Coste (%{currency}) (dato público)" + price_first_year_html: "Coste en el primer año (%{currency}) (opcional, privado)" + price_explanation_html: "Informe de coste (opcional, dato público)" + feasibility: Viabilidad + feasible: Viable + unfeasible: Inviable + undefined_feasible: Sin decidir + feasible_explanation_html: "Informe de inviabilidad (en caso de que lo sea, dato público)" + valuation_finished: Informe finalizado + duration_html: "Plazo de ejecución (opcional, dato no público)" + internal_comments_html: "Comentarios y observaciones (para responsables internos, dato no público)" + save: Guardar cambios notice: valuate: "Dossier actualizado" spending_proposals: diff --git a/spec/features/valuation/budget_investments_spec.rb b/spec/features/valuation/budget_investments_spec.rb index 53f60e416..56a20f2c6 100644 --- a/spec/features/valuation/budget_investments_spec.rb +++ b/spec/features/valuation/budget_investments_spec.rb @@ -158,4 +158,238 @@ feature 'Valuation budget investments' do 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')) + investment = create(:budget_investment, + budget: @budget, + price: 1234, + feasibility: 'unfeasible', + unfeasibility_explanation: 'It is impossible', + administrator: administrator) + investment.valuators << [@valuator, valuator2] + + visit valuation_budget_budget_investments_path(@budget) + + click_link investment.title + + expect(page).to have_content(investment.title) + expect(page).to have_content(investment.description) + expect(page).to have_content(investment.author.name) + expect(page).to have_content(investment.heading.name) + expect(page).to have_content('1234') + expect(page).to have_content('Unfeasible') + 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')) + investment = create(:budget_investment, + budget: @budget, + price: 1234, + feasibility: 'unfeasible', + unfeasibility_explanation: 'It is impossible', + administrator: administrator) + investment.valuators << [@valuator, valuator2] + + visit valuation_budget_budget_investment_path(@budget, investment) + + expect(page).to have_content(investment.title) + expect(page).to have_content(investment.description) + expect(page).to have_content(investment.author.name) + expect(page).to have_content(investment.heading.name) + expect(page).to have_content('1234') + expect(page).to have_content('Unfeasible') + 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 + + valuator2 = create(:valuator, user: create(:user, username: 'Rick', email: 'rick@valuators.org')) + investment = create(:budget_investment, + budget: @budget, + price: 1234, + feasibility: 'unfeasible', + unfeasibility_explanation: 'It is impossible', + administrator: create(:administrator)) + investment.valuators << [@valuator, valuator2] + + expect { visit valuation_budget_budget_investment_path(@budget, investment) }.to raise_error "Not Found" + end + + end + + feature 'Valuate' do + background do + @investment = create(:budget_investment, + budget: @budget, + price: nil, + administrator: create(:administrator)) + @investment.valuators << @valuator + end + + scenario 'Dossier empty by default' do + visit valuation_budget_budget_investments_path(@budget) + click_link @investment.title + + within('#price') { expect(page).to have_content('Undefined') } + within('#price_first_year') { expect(page).to have_content('Undefined') } + within('#duration') { expect(page).to have_content('Undefined') } + within('#feasibility') { expect(page).to have_content('Undecided') } + expect(page).to_not have_content('Valuation finished') + expect(page).to_not have_content('Internal comments') + end + + scenario 'Edit dossier' do + visit valuation_budget_budget_investments_path(@budget) + within("#budget_investment_#{@investment.id}") do + click_link "Edit" + end + + fill_in 'budget_investment_price', with: '12345' + fill_in 'budget_investment_price_first_year', with: '9876' + fill_in 'budget_investment_price_explanation', with: 'Very cheap idea' + choose 'budget_investment_feasibility_feasible' + fill_in 'budget_investment_duration', with: '19 months' + fill_in 'budget_investment_internal_comments', with: 'Should be double checked by the urbanism area' + click_button 'Save changes' + + expect(page).to have_content "Dossier updated" + + visit valuation_budget_budget_investments_path(@budget) + click_link @investment.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('#duration') { 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 'Feasibility can be marked as pending' do + visit valuation_budget_budget_investment_path(@budget, @investment) + click_link 'Edit dossier' + + expect(find "#budget_investment_feasibility_undecided").to be_checked + choose 'budget_investment_feasibility_feasible' + click_button 'Save changes' + + visit edit_valuation_budget_budget_investment_path(@budget, @investment) + + expect(find "#budget_investment_feasibility_undecided").to_not be_checked + expect(find "#budget_investment_feasibility_feasible").to be_checked + + choose 'budget_investment_feasibility_undecided' + click_button 'Save changes' + + visit edit_valuation_budget_budget_investment_path(@budget, @investment) + expect(find "#budget_investment_feasibility_undecided").to be_checked + end + + scenario 'Feasibility selection makes proper fields visible', :js do + feasible_fields = ['Price (€)','Cost during the first year (€)','Price explanation','Time scope'] + unfeasible_fields = ['Feasibility explanation'] + any_feasibility_fields = ['Valuation finished','Internal comments'] + undecided_fields = feasible_fields + unfeasible_fields + any_feasibility_fields + + visit edit_valuation_budget_budget_investment_path(@budget, @investment) + + expect(find "#budget_investment_feasibility_undecided").to be_checked + + undecided_fields.each do |field| + expect(page).to have_content(field) + end + + choose 'budget_investment_feasibility_feasible' + + unfeasible_fields.each do |field| + expect(page).to_not have_content(field) + end + + (feasible_fields + any_feasibility_fields).each do |field| + expect(page).to have_content(field) + end + + choose 'budget_investment_feasibility_unfeasible' + + feasible_fields.each do |field| + expect(page).to_not have_content(field) + end + + (unfeasible_fields + any_feasibility_fields).each do |field| + expect(page).to have_content(field) + end + + click_button 'Save changes' + + visit edit_valuation_budget_budget_investment_path(@budget, @investment) + + expect(find "#budget_investment_feasibility_unfeasible").to be_checked + feasible_fields.each do |field| + expect(page).to_not have_content(field) + end + + (unfeasible_fields + any_feasibility_fields).each do |field| + expect(page).to have_content(field) + end + + choose 'budget_investment_feasibility_undecided' + + undecided_fields.each do |field| + expect(page).to have_content(field) + end + end + + scenario 'Finish valuation' do + visit valuation_budget_budget_investment_path(@budget, @investment) + click_link 'Edit dossier' + + check 'budget_investment_valuation_finished' + click_button 'Save changes' + + visit valuation_budget_budget_investments_path(@budget) + expect(page).to_not have_content @investment.title + click_link 'Valuation finished' + + expect(page).to have_content @investment.title + click_link @investment.title + expect(page).to have_content('Valuation finished') + end + + scenario 'Validates price formats' do + visit valuation_budget_budget_investments_path(@budget) + within("#budget_investment_#{@investment.id}") do + click_link "Edit" + end + + fill_in 'budget_investment_price', with: '12345,98' + fill_in 'budget_investment_price_first_year', with: '9876.6' + click_button 'Save changes' + + expect(page).to have_content('2 errors') + expect(page).to have_content('Only integer numbers', count: 2) + end + end end \ No newline at end of file From 29cad341f583b5db6c843377d321f6f8aa89de3d Mon Sep 17 00:00:00 2001 From: rgarcia Date: Mon, 5 Sep 2016 14:47:11 +0200 Subject: [PATCH 128/384] Adds voting in selecting phase --- .../budgets/investments/_investment.html.erb | 9 +- app/views/budgets/investments/_votes.html.erb | 2 +- app/views/budgets/investments/show.html.erb | 10 +- app/views/budgets/investments/vote.js.erb | 1 + spec/features/votes_spec.rb | 130 ++++++++++++++++-- spec/support/common_actions.rb | 5 + 6 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 app/views/budgets/investments/vote.js.erb diff --git a/app/views/budgets/investments/_investment.html.erb b/app/views/budgets/investments/_investment.html.erb index 84f105c18..fd51ac532 100644 --- a/app/views/budgets/investments/_investment.html.erb +++ b/app/views/budgets/investments/_investment.html.erb @@ -43,18 +43,23 @@ <% unless investment.unfeasible? %> - <% if feature?("investment_features.phase2") %> + + <% if @budget.selecting? || @budget.on_hold? %> +
    <%= render 'votes', - { investment: investment, vote_url: vote_investment_path(investment, value: 'yes') } %> + { investment: investment, + vote_url: vote_budget_investment_path(@budget, investment, value: 'yes') } %>
    + <% elsif feature?("investment_features.phase3") %>
    <%= render 'ballot', investment: investment %>
    <% end %> + <% end %> diff --git a/app/views/budgets/investments/_votes.html.erb b/app/views/budgets/investments/_votes.html.erb index 62ea74283..d243535d1 100644 --- a/app/views/budgets/investments/_votes.html.erb +++ b/app/views/budgets/investments/_votes.html.erb @@ -13,7 +13,7 @@
    <%= t("budget.investments.investment.already_supported") %>
    - <% elsif voting_allowed %> + <% elsif @budget.selecting? %> <%= link_to vote_url, class: "button button-support small expanded", diff --git a/app/views/budgets/investments/show.html.erb b/app/views/budgets/investments/show.html.erb index 5a751e615..2488262f8 100644 --- a/app/views/budgets/investments/show.html.erb +++ b/app/views/budgets/investments/show.html.erb @@ -45,15 +45,19 @@ <% if (@budget.selecting? && !@investment.unfeasible?) || - (@budget.balloting? && @investment.feasible?) %> + (@budget.balloting? && @investment.feasible? || + (@budget.on_hold?)) %>