diff --git a/app/controllers/proposals_dashboard_controller.rb b/app/controllers/proposals_dashboard_controller.rb
index 775b4ff04..0e235664c 100644
--- a/app/controllers/proposals_dashboard_controller.rb
+++ b/app/controllers/proposals_dashboard_controller.rb
@@ -4,7 +4,7 @@
class ProposalsDashboardController < ApplicationController
before_action :authenticate_user!
- helper_method :proposal
+ helper_method :proposal, :proposed_actions
respond_to :html
layout 'proposals_dashboard'
@@ -19,9 +19,23 @@ class ProposalsDashboardController < ApplicationController
redirect_to proposal_dashboard_index_path(proposal), notice: t('proposals.notice.published')
end
+ def execute
+ authorize! :dashboard, proposal
+ ProposalExecutedDashboardAction.create(proposal: proposal, proposal_dashboard_action: proposal_dashboard_action, executed_at: Time.now)
+ redirect_to proposal_dashboard_index_path(proposal.to_param)
+ end
+
private
+ def proposal_dashboard_action
+ @proposal_dashboard_action ||= ProposalDashboardAction.find(params[:id])
+ end
+
def proposal
@proposal ||= Proposal.find(params[:proposal_id])
end
+
+ def proposed_actions
+ @proposed_actions ||= ProposalDashboardAction.proposed_actions.active_for(proposal)
+ end
end
diff --git a/app/models/proposal.rb b/app/models/proposal.rb
index 0f0407ebb..488ffced4 100644
--- a/app/models/proposal.rb
+++ b/app/models/proposal.rb
@@ -31,6 +31,8 @@ class Proposal < ActiveRecord::Base
belongs_to :geozone
has_many :comments, as: :commentable, dependent: :destroy
has_many :proposal_notifications, dependent: :destroy
+ has_many :proposal_executed_dashboard_actions, dependent: :destroy
+ has_many :proposal_dashboard_actions, through: :proposal_executed_dashboard_actions
validates :title, presence: true
validates :question, presence: true
diff --git a/app/models/proposal_dashboard_action.rb b/app/models/proposal_dashboard_action.rb
index 6dd581485..e438d9f76 100644
--- a/app/models/proposal_dashboard_action.rb
+++ b/app/models/proposal_dashboard_action.rb
@@ -4,6 +4,9 @@ class ProposalDashboardAction < ActiveRecord::Base
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
+ has_many :proposal_executed_dashboard_actions, dependent: :restrict_with_error
+ has_many :proposals, through: :proposal_executed_dashboard_actions
+
enum action_type: %i[proposed_action resource]
validates :title,
@@ -41,6 +44,17 @@ class ProposalDashboardAction < ActiveRecord::Base
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
+ scope :resources, -> { where(action_type: 'resource') }
+ scope :proposed_actions, -> { where(action_type: 'proposed_action') }
+ scope :active_for, ->(proposal) do
+ published_at = proposal.published_at || Date.today
+
+ active
+ .where('required_supports <= ?', proposal.votes_for.size)
+ .where('day_offset <= ?', (Date.today - published_at).to_i)
+ end
+
+ default_scope { order(order: :asc, title: :asc) }
def request_to_administrators?
request_to_administrators || false
diff --git a/app/models/proposal_executed_dashboard_action.rb b/app/models/proposal_executed_dashboard_action.rb
new file mode 100644
index 000000000..6d205ac90
--- /dev/null
+++ b/app/models/proposal_executed_dashboard_action.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ProposalExecutedDashboardAction < ActiveRecord::Base
+ belongs_to :proposal
+ belongs_to :proposal_dashboard_action
+
+ validates :proposal, presence: true, uniqueness: { scope: :proposal_dashboard_action }
+ validates :proposal_dashboard_action, presence: true
+ validates :executed_at, presence: true
+end
diff --git a/app/views/proposals_dashboard/_proposed_action.html.erb b/app/views/proposals_dashboard/_proposed_action.html.erb
new file mode 100644
index 000000000..237ad83bf
--- /dev/null
+++ b/app/views/proposals_dashboard/_proposed_action.html.erb
@@ -0,0 +1,25 @@
+
+ |
+ <% if action.link.blank? %>
+
+ <%= action.title %>
+
+ <% else %>
+ <%= link_to action.link, target: '_blank' do %>
+
+ <%= action.title %>
+
+ <% end %>
+ <% end %>
+ |
+
+ <% if action.proposals.where(id: proposal.id).any? %>
+ <%=l action.proposal_executed_dashboard_actions.find_by(proposal: proposal).executed_at, format: :short %>
+ <% else %>
+ <%= link_to t('.execute'),
+ execute_proposal_dashboard_path(proposal.to_param, action.to_param),
+ method: 'post',
+ data: { confirm: t('admin.actions.confirm') } %>
+ <% end %>
+ |
+
diff --git a/app/views/proposals_dashboard/index.html.erb b/app/views/proposals_dashboard/index.html.erb
index 932a90d0a..77439e6e1 100644
--- a/app/views/proposals_dashboard/index.html.erb
+++ b/app/views/proposals_dashboard/index.html.erb
@@ -41,13 +41,12 @@
<%= link_to t('.publish'), publish_proposal_dashboard_index_path(proposal), method: :patch, class: 'menu-entry' if can?(:publish, proposal) %>
<% end %>
-
+ <%= link_to t('.actions'), proposal_dashboard_index_path(proposal.to_param), class: 'menu-entry' %>
+
+
+
+
+
+ <%= t('.actions') %>
+ <% proposed_actions.each do |action| %>
+ <%= render partial: 'proposed_action', locals: { action: action } %>
+ <% end %>
+
+
diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml
index 033bc0f05..216ad41cc 100644
--- a/config/locales/en/general.yml
+++ b/config/locales/en/general.yml
@@ -498,6 +498,10 @@ en:
edit_proposal_link: Edit
send_notification: Send notification
retire: Retire
+ progress: Progress
+ actions: Actions
+ proposed_action:
+ execute: Execute
polls:
all: "All"
no_dates: "no date assigned"
diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml
index 6529ee239..29f2bc68d 100644
--- a/config/locales/es/general.yml
+++ b/config/locales/es/general.yml
@@ -498,6 +498,10 @@ es:
edit_proposal_link: Editar propuesta
send_notification: Enviar notificación
retire: Retirar
+ progress: Progreso
+ actions: Acciones
+ proposed_action:
+ execute: Ejecutar
polls:
all: "Todas"
no_dates: "sin fecha asignada"
diff --git a/config/routes/proposal.rb b/config/routes/proposal.rb
index e00a180ec..9666d77f7 100644
--- a/config/routes/proposal.rb
+++ b/config/routes/proposal.rb
@@ -3,6 +3,10 @@ resources :proposals do
collection do
patch :publish
end
+
+ member do
+ post :execute
+ end
end
member do
diff --git a/db/migrate/20180614122630_create_proposal_executed_dashboard_actions.rb b/db/migrate/20180614122630_create_proposal_executed_dashboard_actions.rb
new file mode 100644
index 000000000..1af6170cc
--- /dev/null
+++ b/db/migrate/20180614122630_create_proposal_executed_dashboard_actions.rb
@@ -0,0 +1,12 @@
+class CreateProposalExecutedDashboardActions < ActiveRecord::Migration
+ def change
+ create_table :proposal_executed_dashboard_actions do |t|
+ t.references :proposal, index: true, foreign_key: true
+ t.references :proposal_dashboard_action, index: { name: 'index_proposal_action' }, foreign_key: true
+ t.datetime :executed_at
+ t.text :comments
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e4decd4e8..031c55128 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -891,6 +891,18 @@ ActiveRecord::Schema.define(version: 20180711224810) do
t.integer "action_type", default: 0, null: false
end
+ create_table "proposal_executed_dashboard_actions", force: :cascade do |t|
+ t.integer "proposal_id"
+ t.integer "proposal_dashboard_action_id"
+ t.datetime "executed_at"
+ t.text "comments"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "proposal_executed_dashboard_actions", ["proposal_dashboard_action_id"], name: "index_proposal_action", using: :btree
+ add_index "proposal_executed_dashboard_actions", ["proposal_id"], name: "index_proposal_executed_dashboard_actions_on_proposal_id", using: :btree
+
create_table "proposal_notifications", force: :cascade do |t|
t.string "title"
t.text "body"
@@ -1324,6 +1336,8 @@ ActiveRecord::Schema.define(version: 20180711224810) do
add_foreign_key "poll_recounts", "poll_booth_assignments", column: "booth_assignment_id"
add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id"
add_foreign_key "poll_voters", "polls"
+ add_foreign_key "proposal_executed_dashboard_actions", "proposal_dashboard_actions"
+ add_foreign_key "proposal_executed_dashboard_actions", "proposals"
add_foreign_key "proposals", "communities"
add_foreign_key "related_content_scores", "related_contents"
add_foreign_key "related_content_scores", "users"
diff --git a/spec/factories.rb b/spec/factories.rb
index 3707da650..2012f8008 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1071,4 +1071,14 @@ LOREM_IPSUM
action_type 'resource'
end
end
+
+ factory :proposal_executed_dashboard_action, class: 'ProposalExecutedDashboardAction' do
+ proposal
+ proposal_dashboard_action
+ executed_at { Time.now }
+
+ trait :with_comments do
+ comments { Faker::Lorem.sentence(10) }
+ end
+ end
end
diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb
index e6cb210bb..ed12c797d 100644
--- a/spec/models/abilities/common_spec.rb
+++ b/spec/models/abilities/common_spec.rb
@@ -160,6 +160,19 @@ describe Abilities::Common do
it { should_not be_able_to(:destroy, proposal_image) }
it { should_not be_able_to(:destroy, proposal_document) }
end
+
+ describe 'proposals dashboard' do
+ it { is_expected.to be_able_to(:dashboard, own_proposal) }
+ it { is_expected.not_to be_able_to(:dashboard, proposal) }
+ end
+
+ describe 'publishing proposals' do
+ let(:draft_own_proposal) { create(:proposal, :draft, author: user) }
+
+ it { is_expected.to be_able_to(:publish, draft_own_proposal) }
+ it { is_expected.not_to be_able_to(:publish, own_proposal) }
+ it { is_expected.not_to be_able_to(:publish, proposal) }
+ end
describe "when level 2 verified" do
let(:own_spending_proposal) { create(:spending_proposal, author: user) }
diff --git a/spec/models/proposal_executed_dashboard_action_spec.rb b/spec/models/proposal_executed_dashboard_action_spec.rb
new file mode 100644
index 000000000..bc76d6800
--- /dev/null
+++ b/spec/models/proposal_executed_dashboard_action_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe ProposalExecutedDashboardAction do
+ subject do
+ build :proposal_executed_dashboard_action,
+ proposal: proposal,
+ proposal_dashboard_action: proposal_dashboard_action,
+ executed_at: executed_at
+ end
+
+ let(:proposal) { create :proposal }
+ let(:proposal_dashboard_action) { create :proposal_dashboard_action }
+ let(:executed_at) { Time.now }
+
+ it { is_expected.to be_valid }
+
+ context 'when proposal is nil' do
+ let(:proposal) { nil }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when proposal_dashboard_action is nil' do
+ let(:proposal_dashboard_action) { nil }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when executed_at is nil' do
+ let(:executed_at) { nil }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when it has been already executed' do
+ let!(:executed) { create(:proposal_executed_dashboard_action, proposal: proposal, proposal_dashboard_action: proposal_dashboard_action) }
+
+ it { is_expected.not_to be_valid }
+ end
+end