diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index e6a262d07..5bcba8bbd 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -275,6 +275,7 @@ .debate-new, .debate-edit, .proposal-new, .proposal-edit, +.budget-proposal-new, .budget-proposal-edit, .spending-proposal-new, .spending-proposal-edit { .icon-debates { diff --git a/app/controllers/management/budget_investments_controller.rb b/app/controllers/management/budget_investments_controller.rb new file mode 100644 index 000000000..31706ce89 --- /dev/null +++ b/app/controllers/management/budget_investments_controller.rb @@ -0,0 +1,78 @@ +class Management::BudgetInvestmentsController < Management::BaseController + + before_action :only_verified_users, except: :print + before_action :set_budget_investment, only: [:vote, :show] + + def index + @budget_investments = apply_filters_and_search(Budget::Investment).order(cached_votes_up: :desc).page(params[:page]).for_render + set_budget_investment_votes(@budget_investments) + end + + def new + @investment = Budget::Investment.new + end + + def create + @budget_investment = Budget::Investment.new(budget_investment_params) + @budget_investment.author = managed_user + + if @budget_investment.save + redirect_to management_budget_investment_path(@budget_investment), notice: t('flash.actions.create.notice', resource_name: t("activerecord.models.budget_investment", count: 1)) + else + render :new + end + end + + def show + set_budget_investment_votes(@budget_investment) + end + + def vote + @budget_investment.register_vote(managed_user, 'yes') + set_budget_investment_votes(@budget_investment) + end + + def print + params[:geozone] ||= 'all' + @budget_investments = apply_filters_and_search(Budget::Investment).order(cached_votes_up: :desc).for_render.limit(15) + set_budget_investment_votes(@budget_investments) + end + + private + + def set_budget_investment + @budget_investment = Budget::Investment.find(params[:id]) + end + + def budget_investment_params + params.require(:budget_investment).permit(:title, :description, :external_url, :geozone_id, :terms_of_service) + end + + def only_verified_users + check_verified_user t("management.budget_investments.alert.unverified_user") + end + + # This should not be necessary. Maybe we could create a specific show view for managers. + def set_budget_investment_votes(budget_investments) + @budget_investment_votes = managed_user ? managed_user.budget_investment_votes(budget_investments) : {} + end + + def set_geozone_name + if params[:geozone] == 'all' + @geozone_name = t('geozones.none') + else + @geozone_name = Geozone.find(params[:geozone]).name + end + end + + def apply_filters_and_search(target) + target = params[:unfeasible].present? ? target.unfeasible : target.not_unfeasible + if params[:geozone].present? + target = target.by_geozone(params[:geozone]) + set_geozone_name + end + target = target.search(params[:search]) if params[:search].present? + target + end + +end diff --git a/app/views/admin/shared/_budget_investment_search.html.erb b/app/views/admin/shared/_budget_investment_search.html.erb new file mode 100644 index 000000000..92bd8e24a --- /dev/null +++ b/app/views/admin/shared/_budget_investment_search.html.erb @@ -0,0 +1,10 @@ +<%= form_for(Budget::Investment.new, url: url, as: :budget_investment, method: :get) do |f| %> +
+
+ <%= text_field_tag :search, "", placeholder: t("admin.shared.budget_investment_search.placeholder") %> +
+
+ <%= f.submit t("admin.shared.budget_investment_search.button"), class: "button" %> +
+
+<% end %> diff --git a/app/views/management/_menu.html.erb b/app/views/management/_menu.html.erb index aa154212e..1a24bba29 100644 --- a/app/views/management/_menu.html.erb +++ b/app/views/management/_menu.html.erb @@ -44,6 +44,20 @@ <% end %> +
  • > + <%= link_to new_management_budget_investment_path do %> + + <%= t("management.menu.create_budget_investment") %> + <% end %> +
  • + +
  • > + <%= link_to management_budget_investments_path do %> + + <%= t("management.menu.support_budget_investments") %> + <% end %> +
  • +
  • > <%= link_to print_management_proposals_path do %> @@ -58,6 +72,14 @@ <% end %>
  • +
  • > + <%= link_to print_management_budget_investments_path do %> + + <%= t("management.menu.print_budget_investments") %> + <% end %> +
  • + +
  • <%= link_to new_management_user_invite_path do %> diff --git a/app/views/management/budget_investments/_budget_investment.html.erb b/app/views/management/budget_investments/_budget_investment.html.erb new file mode 100644 index 000000000..345e8b377 --- /dev/null +++ b/app/views/management/budget_investments/_budget_investment.html.erb @@ -0,0 +1 @@ +<%= render partial: 'budgets/investments/investment', locals: {investment: budget_investment} %> diff --git a/app/views/management/budget_investments/_votes.html.erb b/app/views/management/budget_investments/_votes.html.erb new file mode 100644 index 000000000..579c5d6c3 --- /dev/null +++ b/app/views/management/budget_investments/_votes.html.erb @@ -0,0 +1,2 @@ +<%= render 'budgets/investments/votes', + { investment: budget_investment, vote_url: vote_management_budget_investment_path(budget_investment.budget, budget_investment, value: 'yes') } %> diff --git a/app/views/management/budget_investments/index.html.erb b/app/views/management/budget_investments/index.html.erb new file mode 100644 index 000000000..1f52524ef --- /dev/null +++ b/app/views/management/budget_investments/index.html.erb @@ -0,0 +1,25 @@ +
    + + <%= render 'admin/shared/budget_investment_search', url: management_budget_investments_path %> + + +
    +
    + + +
    + <%= content_tag(:h2, t("management.budget_investments.filters.unfeasible")) if params[:unfeasible].present? %> + <%= content_tag(:h2, t("management.budget_investments.filters.by_geozone", geozone: @geozone_name)) if @geozone_name.present? %> + <% if params[:search].present? %> +

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

    + <% end %> +
    + + <%= render @budget_investments %> + <%= paginate @budget_investments %> +
    +
    +
    diff --git a/app/views/management/budget_investments/new.html.erb b/app/views/management/budget_investments/new.html.erb new file mode 100644 index 000000000..59e4d30e9 --- /dev/null +++ b/app/views/management/budget_investments/new.html.erb @@ -0,0 +1,12 @@ +
    + +
    + <%= render '/shared/print' %> +
    + +
    +

    <%= t("management.budget_investments.create") %>

    + <%= render "budgets/investments/form", form_url: management_budget_investments_url %> +
    + +
    diff --git a/app/views/management/budget_investments/print.html.erb b/app/views/management/budget_investments/print.html.erb new file mode 100644 index 000000000..785202e1c --- /dev/null +++ b/app/views/management/budget_investments/print.html.erb @@ -0,0 +1,34 @@ +
    +
    +
    + +
    + <%= form_tag print_management_budget_investments_path, method: :get, enforce_utf8: false do %> +
    + <%= select_tag :geozone, + options_for_select(geozone_select_options.unshift([t("geozones.none"), "all"]), params[:geozone]), + { label: false, + class: "js-submit-on-change" } %> +
    + <% end %> + + + <%= t('management.budget_investments.print.print_button') %> + +
    + +
    + <%= content_tag(:h2, t("management.budget_investments.filters.unfeasible"), class: "inline-block") if params[:unfeasible].present? %> + <%= content_tag(:h2, t("management.budget_investments.filters.by_geozone", geozone: @geozone_name), class: "inline-block") if @geozone_name.present? %> + <%= content_tag(:h2, t("management.budget_investments.search_results", count: @budget_investments.size, search_term: params[:search]), class: "inline-block") if params[:search].present? %> +
    + + <%= render @budget_investments %> + +
    +

    <%= t("management.print.budget_investments_info") %>
    + <%= t("management.print.budget_investments_note") %>

    +
    +
    +
    +
    diff --git a/app/views/management/budget_investments/show.html.erb b/app/views/management/budget_investments/show.html.erb new file mode 100644 index 000000000..9b558b845 --- /dev/null +++ b/app/views/management/budget_investments/show.html.erb @@ -0,0 +1,3 @@ +<%= render '/shared/print' %> + +<%= render template: 'budgets/investments/show' %> diff --git a/app/views/management/budget_investments/vote.js.erb b/app/views/management/budget_investments/vote.js.erb new file mode 100644 index 000000000..74705cf2b --- /dev/null +++ b/app/views/management/budget_investments/vote.js.erb @@ -0,0 +1 @@ +<%= render template: 'budgets/investments/vote' %> diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index c9bd21c18..8511dbfc4 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -283,6 +283,9 @@ en: proposal_search: button: Search placeholder: Search proposals by title, code, description or question + budget_investment_search: + button: Search + placeholder: Search investments by title or description spending_proposal_search: button: Search placeholder: Search spending proposals by title or description diff --git a/config/locales/management.en.yml b/config/locales/management.en.yml index fa02e5bc3..8daa53235 100644 --- a/config/locales/management.en.yml +++ b/config/locales/management.en.yml @@ -50,6 +50,9 @@ en: create_spending_proposal: Create spending proposal print_spending_proposals: Print spending proposals support_spending_proposals: Support spending proposals + create_budget_investment: Create budget investment + print_budget_investment: Print budget investment + support_budget_investment: Support budget investment users: Users edit_user_accounts: Edit user account user_invites: User's invites @@ -62,8 +65,8 @@ en: proposals_info: Create yor proposal on http://url.consul proposals_note: The proposals more supported will be voted. If are accepted by a majority, the city Council shall be carried out. proposals_title: 'Proposals:' - spending_proposals_info: Participate at http://url.consul - spending_proposals_note: Participatory budget will be assigned to the most votes spending proposals. + budget_investments_info: Participate at http://url.consul + budget_investments_note: Participatory budget will be assigned to the most voted budget investment. print_info: Print this info proposals: alert: @@ -71,6 +74,18 @@ en: create_proposal: Create proposal print: print_button: Print + budget_investments: + alert: + unverified_user: User is not verified + create: Create budget investment + filters: + unfeasible: Unfeasible investment + by_geozone: "Investment with scope: %{geozone}" + print: + print_button: Print + search_results: + one: " containing the term '%{search_term}'" + other: " containing the term '%{search_term}'" spending_proposals: alert: unverified_user: User is not verified @@ -106,4 +121,4 @@ en: title: User's invites create: success_html: %{count} invitations have been sent. - title: User's invites \ No newline at end of file + title: User's invites diff --git a/config/routes.rb b/config/routes.rb index c10b4ba2b..245701634 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -282,6 +282,11 @@ Rails.application.routes.draw do post :vote, on: :member get :print, on: :collection end + + resources :budget_investments, only: [:index, :new, :create, :show] do + post :vote, on: :member + get :print, on: :collection + end end if Rails.env.development? diff --git a/spec/features/management/budget_investments_spec.rb b/spec/features/management/budget_investments_spec.rb new file mode 100644 index 000000000..7d793bfd3 --- /dev/null +++ b/spec/features/management/budget_investments_spec.rb @@ -0,0 +1,224 @@ +require 'rails_helper' + +feature 'Budget Investments' do + + background do + login_as_manager + end + + context "Create" do + + scenario 'Creating budget investments on behalf of someone' do + user = create(:user, :level_two) + login_managed_user(user) + + save_and_open_page + click_link "Create budget investment" + + within(".account-info") do + expect(page).to have_content "Identified as" + expect(page).to have_content "#{user.username}" + expect(page).to have_content "#{user.email}" + expect(page).to have_content "#{user.document_number}" + end + + fill_in 'budget_investment_title', with: 'Build a park in my neighborhood' + fill_in 'budget_investment_description', with: 'There is no parks here...' + fill_in 'budget_investment_external_url', with: 'http://moarparks.com' + check 'budget_investment_terms_of_service' + + click_button 'Create' + + expect(page).to have_content 'budget investment created successfully.' + + expect(page).to have_content 'Build a park in my neighborhood' + expect(page).to have_content 'There is no parks here...' + expect(page).to have_content 'All city' + expect(page).to have_content 'http://moarparks.com' + expect(page).to have_content user.name + expect(page).to have_content I18n.l(Budget::Investment.last.created_at.to_date) + + expect(current_path).to eq(management_budget_investment_path(Budget::Investment.last)) + end + + scenario "Should not allow unverified users to create budget investments" do + user = create(:user) + login_managed_user(user) + + click_link "Create budget investment" + + expect(page).to have_content "User is not verified" + end + end + + context "Searching" do + scenario "by title" do + budget_investment1 = create(:budget_investment, title: "Show me what you got") + budget_investment2 = create(:budget_investment, title: "Get Schwifty") + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Support budget investments" + + fill_in "search", with: "what you got" + click_button "Search" + + expect(current_path).to eq(management_budget_investments_path) + + within("#investment-projects") do + expect(page).to have_css('.investment-project', count: 1) + expect(page).to have_content(budget_investment1.title) + expect(page).to_not have_content(budget_investment2.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment1)}']", text: budget_investment1.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment1)}']", text: budget_investment1.description) + end + end + + scenario "by district" do + budget_investment1 = create(:budget_investment, title: "Hey ho", geozone_id: create(:geozone, name: "District 9").id) + budget_investment2 = create(:budget_investment, title: "Let's go", geozone_id: create(:geozone, name: "Area 52").id) + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Support budget investments" + + fill_in "search", with: "Area 52" + click_button "Search" + + expect(current_path).to eq(management_budget_investments_path) + + within("#investment-projects") do + expect(page).to have_css('.investment-project', count: 1) + expect(page).to_not have_content(budget_investment1.title) + expect(page).to have_content(budget_investment2.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment2)}']", text: budget_investment2.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment2)}']", text: budget_investment2.description) + end + end + end + + scenario "Listing" do + budget_investment1 = create(:budget_investment, title: "Show me what you got") + budget_investment2 = create(:budget_investment, title: "Get Schwifty") + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Support budget investments" + + expect(current_path).to eq(management_budget_investments_path) + + within(".account-info") do + expect(page).to have_content "Identified as" + expect(page).to have_content "#{user.username}" + expect(page).to have_content "#{user.email}" + expect(page).to have_content "#{user.document_number}" + end + + within("#investment-projects") do + expect(page).to have_css('.investment-project', count: 2) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment1)}']", text: budget_investment1.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment1)}']", text: budget_investment1.description) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment2)}']", text: budget_investment2.title) + expect(page).to have_css("a[href='#{management_budget_investment_path(budget_investment2)}']", text: budget_investment2.description) + end + end + + context "Voting" do + + scenario 'Voting budget investments on behalf of someone in index view', :js do + budget_investment = create(:budget_investment) + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Support budget investments" + + within("#investment-projects") do + find('.in-favor a').click + + expect(page).to have_content "1 support" + expect(page).to have_content "You have already supported this. Share it!" + end + expect(current_path).to eq(management_budget_investments_path) + end + + scenario 'Voting budget investments on behalf of someone in show view', :js do + budget_investment = create(:budget_investment) + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Support budget investments" + + within("#investment-projects") do + click_link budget_investment.title + end + + find('.in-favor a').click + expect(page).to have_content "1 support" + expect(page).to have_content "You have already supported this. Share it!" + expect(current_path).to eq(management_budget_investment_path(budget_investment)) + end + + scenario "Should not allow unverified users to vote proposals" do + budget_investment = create(:budget_investment) + + user = create(:user) + login_managed_user(user) + + click_link "Support budget investments" + + expect(page).to have_content "User is not verified" + end + end + + context "Printing" do + + scenario 'Printing budget investments' do + 16.times { create(:budget_investment, geozone_id: nil) } + + click_link "Print budget investments" + + expect(page).to have_css('.investment-project', count: 15) + expect(page).to have_css("a[href='javascript:window.print();']", text: 'Print') + end + + scenario "Filtering budget investments by geozone to be printed", :js do + district_9 = create(:geozone, name: "District Nine") + create(:budget_investment, title: 'Change district 9', geozone: district_9, cached_votes_up: 10) + create(:budget_investment, title: 'Destroy district 9', geozone: district_9, cached_votes_up: 100) + create(:budget_investment, title: 'Nuke district 9', geozone: district_9, cached_votes_up: 1) + create(:budget_investment, title: 'Add new districts to the city', geozone_id: nil) + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Print budget investments" + + expect(page).to have_content "Budget investments with scope: All city" + + within '#investment-projects' do + expect(page).to have_content('Add new districts to the city') + expect(page).to_not have_content('Change district 9') + expect(page).to_not have_content('Destroy district 9') + expect(page).to_not have_content('Nuke district 9') + end + + select 'District Nine', from: 'geozone' + + expect(page).to have_content "Investment projects with scope: District Nine" + expect(current_url).to include("geozone=#{district_9.id}") + + within '#investment-projects' do + expect(page).to_not have_content('Add new districts to the city') + expect('Destroy district 9').to appear_before('Change district 9') + expect('Change district 9').to appear_before('Nuke district 9') + end + end + + end + +end