decidim-plans 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +52 -32
  4. data/app/commands/decidim/plans/close_plan.rb +44 -0
  5. data/app/commands/decidim/plans/reopen_plan.rb +44 -0
  6. data/app/controllers/decidim/plans/admin/plans_controller.rb +32 -0
  7. data/app/controllers/decidim/plans/admin/sections_controller.rb +1 -6
  8. data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +6 -0
  9. data/app/controllers/decidim/plans/plans_controller.rb +40 -4
  10. data/app/controllers/decidim/plans/versions_controller.rb +6 -0
  11. data/app/helpers/decidim/plans/attached_proposals_helper.rb +2 -0
  12. data/app/helpers/decidim/plans/traceability_helper.rb +8 -12
  13. data/app/models/decidim/plans/plan.rb +15 -0
  14. data/app/permissions/decidim/plans/admin/permissions.rb +2 -0
  15. data/app/permissions/decidim/plans/permissions.rb +7 -1
  16. data/app/services/decidim/plans/diff_renderer/base.rb +1 -1
  17. data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +2 -0
  18. data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +8 -0
  19. data/app/views/decidim/plans/plans/show.html.erb +7 -0
  20. data/config/locales/en.yml +23 -0
  21. data/config/locales/fi.yml +23 -0
  22. data/config/locales/sv.yml +24 -0
  23. data/db/migrate/20190214124014_add_closed_at_to_decidim_plans.rb +8 -0
  24. data/lib/decidim/plans/admin_engine.rb +4 -0
  25. data/lib/decidim/plans/component.rb +16 -3
  26. data/lib/decidim/plans/engine.rb +2 -0
  27. data/lib/decidim/plans/plan_serializer.rb +82 -0
  28. data/lib/decidim/plans/test/factories.rb +9 -2
  29. data/lib/decidim/plans/version.rb +1 -1
  30. data/lib/decidim/plans.rb +4 -2
  31. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f496c0cd1c6ee3f73085fc296508701d44434eb5a1c9dc3375c33ac99ea3a45
4
- data.tar.gz: 2893280cdc00ae08eb9096e94da371496e43a2d993ea49ef6345def50a767176
3
+ metadata.gz: a0beab872d687c4002db352e84e5de788545493a03a5c9e06d22af045d014a10
4
+ data.tar.gz: b422c03351c34cb8919b22bef233b85451c808edaf90b638e213d027493f2484
5
5
  SHA512:
6
- metadata.gz: 2751ff4b10252cf2b3bbf59deccb36692b7866dcd9ec90d649b3ca0cb640812601c2fa4ab938228a5425b8a4c96adfd9cd6ca71b147bcaa9834a2df4a815ccd3
7
- data.tar.gz: f2db56458ab76aade2d06b91e67061b6bdd857e33a848ae720579fe851e18b3007e4f89847cb51aa71daf8c50947ba0d926b33aac09b4315f7d35788869a4ebf
6
+ metadata.gz: e3a45ded830363d9e5c1bffe7f9f22b52fff04f62ba8f5d2dea1cb44cf22a716466ce945c67b5bf9f8230becc3102add69f7196fc90f36cb4fb3207fb0b3e9ec
7
+ data.tar.gz: 5fc28c2575d607f354eb8fe677f9d33886239b948614c939b04f40ddfc095a816c61d0cfaa272af340922184d3c32fe418a956c92163626c83f0989dc5fb1ee8
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Decidim::Plans
2
2
 
3
+ [![Build Status](https://travis-ci.com/mainio/decidim-module-plans.svg?branch=master)](https://travis-ci.com/mainio/decidim-module-plans)
4
+ [![codecov](https://codecov.io/gh/mainio/decidim-module-plans/branch/master/graph/badge.svg)](https://codecov.io/gh/mainio/decidim-module-plans)
5
+
6
+ The gem has been developed by [Mainio Tech](https://www.mainiotech.fi/).
7
+
3
8
  A [Decidim](https://github.com/decidim/decidim) module that provides a new
4
9
  component that can be added to any participatory space in Decidim. The component
5
10
  allows users to write plans together that link to specific proposals. Further on
@@ -26,6 +31,9 @@ following differences:
26
31
  converted to sub-headings in the budgeting project's description and the
27
32
  budget needs to be specified by the user doing the conversion.
28
33
 
34
+ Development of this gem has been sponsored by the
35
+ [City of Helsinki](https://www.hel.fi/).
36
+
29
37
  ## Installation
30
38
 
31
39
  Add this line to your application's Gemfile:
@@ -4,38 +4,58 @@
4
4
  // = require_self
5
5
 
6
6
  $(function() {
7
- $(document).on("open.zf.reveal", "#data_picker-modal", function () {
8
- let xhr = null;
7
+ $(document).on(
8
+ "open.zf.reveal", "#data_picker-modal",
9
9
 
10
- $("#data_picker-autocomplete").autoComplete({
11
- minChars: 2,
12
- source: function(term, response) {
13
- try {
14
- xhr.abort();
15
- } catch (exception) { xhr = null; }
10
+ /* @this HTMLElement */
11
+ function () {
12
+ let xhr = null;
16
13
 
17
- let url = $("#proposal-picker-choose").attr("href");
18
- xhr = $.getJSON(
19
- url,
20
- { term: term },
21
- function(data) { response(data); }
22
- );
23
- },
24
- renderItem: function (item, search) {
25
- let sanitizedSearch = search.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
26
- let re = new RegExp(`(${sanitizedSearch.split(" ").join("|")})`, "gi");
27
- let title = item[0];
28
- let modelId = item[1];
29
- return `<div class="autocomplete-suggestion" data-model-id="${modelId}" data-val ="${title}">${title.replace(re, "<b>$1</b>")}</div>`;
30
- },
31
- onSelect: function(event, term, item) {
32
- let choose = $("#proposal-picker-choose");
33
- let modelId = item.data("modelId");
34
- let val = item.data("val");
35
- choose.data("picker-value", modelId);
36
- choose.data("picker-text", val);
37
- choose.data("picker-choose", "");
38
- }
39
- });
40
- });
14
+ $("#data_picker-autocomplete").autoComplete({
15
+ minChars: 2,
16
+ source: function(term, response) {
17
+ try {
18
+ xhr.abort();
19
+ } catch (exception) { xhr = null; }
20
+
21
+ let url = $("#proposal-picker-choose").attr("href");
22
+ xhr = $.getJSON(
23
+ url,
24
+ { term: term },
25
+ function(data) { response(data); }
26
+ );
27
+ },
28
+ renderItem: function (item, search) {
29
+ let sanitizedSearch = search.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
30
+ let re = new RegExp(`(${sanitizedSearch.split(" ").join("|")})`, "gi");
31
+ let title = item[0];
32
+ let modelId = item[1];
33
+ return `<div class="autocomplete-suggestion" data-model-id="${modelId}" data-val ="${title}">${title.replace(re, "<b>$1</b>")}</div>`;
34
+ },
35
+ onSelect: function(event, term, item) {
36
+ let choose = $("#proposal-picker-choose");
37
+ let modelId = item.data("modelId");
38
+ let val = item.data("val");
39
+ choose.data("picker-value", modelId);
40
+ choose.data("picker-text", val);
41
+ choose.data("picker-choose", "");
42
+ }
43
+ });
44
+
45
+ // Remove all the empty values after the selection is made to prevent
46
+ // these empty values appearing in the list.
47
+ // This is needed until the following is merged to the core:
48
+ // https://github.com/decidim/decidim/pull/4842
49
+ const $choose = $("#proposal-picker-choose", this);
50
+ $choose.on("click", function() {
51
+ const $values = $("#plan_proposals .picker-values");
52
+ $("input[type='checkbox']", $values).each(function(_ev, input) {
53
+ const $input = $(input);
54
+ if ($input.val().length < 1) {
55
+ $input.parent().remove();
56
+ }
57
+ });
58
+ });
59
+ }
60
+ );
41
61
  });
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A command with all the business logic when a user closes a plan.
6
+ class ClosePlan < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # plan - The plan to publish.
10
+ # current_user - The current user.
11
+ def initialize(plan, current_user)
12
+ @plan = plan
13
+ @current_user = current_user
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid and the plan is published.
19
+ # - :invalid if the plan's author is not the current user.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if @current_user.nil?
24
+
25
+ close_plan
26
+
27
+ broadcast(:ok, @plan)
28
+ end
29
+
30
+ private
31
+
32
+ def close_plan
33
+ Decidim.traceability.perform_action!(
34
+ "close",
35
+ @plan,
36
+ @current_user,
37
+ visibility: "public-only"
38
+ ) do
39
+ @plan.update closed_at: Time.current
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A command with all the business logic when a user reopens a plan.
6
+ class ReopenPlan < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # plan - The plan to publish.
10
+ # current_user - The current user.
11
+ def initialize(plan, current_user)
12
+ @plan = plan
13
+ @current_user = current_user
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid and the plan is published.
19
+ # - :invalid if the plan's author is not the current user.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if @current_user.nil?
24
+
25
+ close_plan
26
+
27
+ broadcast(:ok, @plan)
28
+ end
29
+
30
+ private
31
+
32
+ def close_plan
33
+ Decidim.traceability.perform_action!(
34
+ "reopen",
35
+ @plan,
36
+ @current_user,
37
+ visibility: "public-only"
38
+ ) do
39
+ @plan.update closed_at: nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -57,6 +57,38 @@ module Decidim
57
57
  end
58
58
  end
59
59
 
60
+ def close
61
+ enforce_permission_to :close, :plan, plan: plan
62
+
63
+ ClosePlan.call(plan, current_user) do
64
+ on(:ok) do
65
+ flash[:notice] = I18n.t("close.success", scope: "decidim.plans.plans.plan")
66
+ redirect_to plans_path
67
+ end
68
+
69
+ on(:invalid) do
70
+ flash.now[:alert] = t("close.error", scope: "decidim.plans.plans.plan")
71
+ redirect_to plans_path
72
+ end
73
+ end
74
+ end
75
+
76
+ def reopen
77
+ enforce_permission_to :close, :plan, plan: plan
78
+
79
+ ReopenPlan.call(plan, current_user) do
80
+ on(:ok) do
81
+ flash[:notice] = I18n.t("reopen.success", scope: "decidim.plans.plans.plan")
82
+ redirect_to plans_path
83
+ end
84
+
85
+ on(:invalid) do
86
+ flash.now[:alert] = t("reopen.error", scope: "decidim.plans.plans.plan")
87
+ redirect_to plans_path
88
+ end
89
+ end
90
+ end
91
+
60
92
  private
61
93
 
62
94
  def query
@@ -16,11 +16,6 @@ module Decidim
16
16
  @form = form(Admin::PlanSectionsForm).from_model(sections)
17
17
  end
18
18
 
19
- def new
20
- enforce_permission_to :create, :sections
21
- # TODO
22
- end
23
-
24
19
  def create
25
20
  enforce_permission_to :create, :section
26
21
  @form = form(Admin::PlanSectionsForm).from_params(params)
@@ -33,7 +28,7 @@ module Decidim
33
28
 
34
29
  on(:invalid) do
35
30
  flash.now[:alert] = I18n.t("update.invalid", scope: i18n_flashes_scope)
36
- render template: "index"
31
+ render action: :index
37
32
  end
38
33
  end
39
34
  end
@@ -8,6 +8,8 @@ module Decidim
8
8
  before_action :retrieve_plan, only: [:request_access, :request_accept, :request_reject]
9
9
 
10
10
  def request_access
11
+ enforce_permission_to :request_access, :plan, plan: @plan
12
+
11
13
  @request_access_form = form(RequestAccessToPlanForm).from_params(params)
12
14
  RequestAccessToPlan.call(@request_access_form, current_user) do
13
15
  on(:ok) do |_plan|
@@ -22,6 +24,8 @@ module Decidim
22
24
  end
23
25
 
24
26
  def request_accept
27
+ enforce_permission_to :edit, :plan, plan: @plan
28
+
25
29
  @accept_request_form = form(AcceptAccessToPlanForm).from_params(params)
26
30
  AcceptAccessToPlan.call(@accept_request_form, current_user) do
27
31
  on(:ok) do |requester_user|
@@ -36,6 +40,8 @@ module Decidim
36
40
  end
37
41
 
38
42
  def request_reject
43
+ enforce_permission_to :edit, :plan, plan: @plan
44
+
39
45
  @reject_request_form = form(RejectAccessToPlanForm).from_params(params)
40
46
  RejectAccessToPlan.call(@reject_request_form, current_user) do
41
47
  on(:ok) do |requester_user|
@@ -17,9 +17,9 @@ module Decidim
17
17
 
18
18
  helper_method :attached_proposals_picker_field
19
19
 
20
- before_action :authenticate_user!, only: [:new, :create, :edit, :update, :withdraw, :preview, :publish, :destroy]
20
+ before_action :authenticate_user!, only: [:new, :create, :edit, :update, :withdraw, :preview, :publish, :close, :reopen, :destroy]
21
21
  before_action :check_draft, only: [:new]
22
- before_action :retrieve_plan, only: [:show, :edit, :update, :withdraw, :preview, :publish, :destroy]
22
+ before_action :retrieve_plan, only: [:show, :edit, :update, :withdraw, :preview, :publish, :close, :reopen, :destroy]
23
23
  before_action :ensure_published!, only: [:show, :withdraw]
24
24
 
25
25
  def index
@@ -129,6 +129,8 @@ module Decidim
129
129
  def preview; end
130
130
 
131
131
  def publish
132
+ enforce_permission_to :publish, :plan, plan: @plan
133
+
132
134
  PublishPlan.call(@plan, current_user) do
133
135
  on(:ok) do |plan|
134
136
  flash[:notice] = I18n.t("publish.success", scope: "decidim.plans.plans.plan")
@@ -142,6 +144,38 @@ module Decidim
142
144
  end
143
145
  end
144
146
 
147
+ def close
148
+ enforce_permission_to :close, :plan, plan: @plan
149
+
150
+ ClosePlan.call(@plan, current_user) do
151
+ on(:ok) do |plan|
152
+ flash[:notice] = I18n.t("close.success", scope: "decidim.plans.plans.plan")
153
+ redirect_to Decidim::ResourceLocatorPresenter.new(plan).path
154
+ end
155
+
156
+ on(:invalid) do
157
+ flash.now[:alert] = t("close.error", scope: "decidim.plans.plans.plan")
158
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
159
+ end
160
+ end
161
+ end
162
+
163
+ def reopen
164
+ enforce_permission_to :close, :plan, plan: @plan
165
+
166
+ ReopenPlan.call(@plan, current_user) do
167
+ on(:ok) do |plan|
168
+ flash[:notice] = I18n.t("reopen.success", scope: "decidim.plans.plans.plan")
169
+ redirect_to Decidim::ResourceLocatorPresenter.new(plan).path
170
+ end
171
+
172
+ on(:invalid) do
173
+ flash.now[:alert] = t("reopen.error", scope: "decidim.plans.plans.plan")
174
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
175
+ end
176
+ end
177
+ end
178
+
145
179
  private
146
180
 
147
181
  def check_draft
@@ -149,12 +183,14 @@ module Decidim
149
183
  end
150
184
 
151
185
  def plan_draft
152
- Plan.drafts.from_all_author_identities(current_user).not_hidden.where(component: current_component)
186
+ @plan_draft ||= Plan.drafts.from_all_author_identities(current_user).not_hidden.find_by(component: current_component)
153
187
  end
154
188
 
189
+ # rubocop:disable Naming/MemoizedInstanceVariableName
155
190
  def retrieve_plan
156
- @plan = Plan.where(component: current_component).find(params[:id])
191
+ @plan ||= Plan.where(component: current_component).find(params[:id])
157
192
  end
193
+ # rubocop:enable Naming/MemoizedInstanceVariableName
158
194
 
159
195
  def ensure_published!
160
196
  return unless @plan
@@ -21,6 +21,8 @@ module Decidim
21
21
  end
22
22
 
23
23
  def item_versions
24
+ return [] if current_version.transaction_id.nil?
25
+
24
26
  # There may be multiple updates on the item during the same transaction
25
27
  Decidim::Plans::PaperTrail::Version.where(
26
28
  transaction_id: current_version.transaction_id,
@@ -29,6 +31,8 @@ module Decidim
29
31
  end
30
32
 
31
33
  def associated_versions
34
+ return [] if current_version.transaction_id.nil?
35
+
32
36
  @associated_versions ||= Decidim::Plans::PaperTrail::Version.where(
33
37
  transaction_id: current_version.transaction_id
34
38
  ).where.not(
@@ -37,6 +41,8 @@ module Decidim
37
41
  end
38
42
 
39
43
  def content_versions
44
+ return [] if current_version.transaction_id.nil?
45
+
40
46
  @content_versions ||= item.sections.map do |section|
41
47
  content = item.contents.find_by(section: section)
42
48
  next unless content
@@ -35,6 +35,8 @@ module Decidim
35
35
  .try(:resource_scope, current_component)
36
36
  &.order(title: :asc)
37
37
  &.where("title ilike ?", "%#{params[:term]}%")
38
+ &.where&.not(state: "rejected")
39
+ &.where&.not(published_at: nil)
38
40
  render json: query.all.collect { |p| [present(p).title, p.id] }
39
41
  end
40
42
  end
@@ -32,21 +32,17 @@ module Decidim
32
32
  private
33
33
 
34
34
  def renderer_for(version)
35
+ return nil if version.item.nil?
36
+
35
37
  locale = current_locale unless component_settings.multilingual_answers?
36
38
 
37
- renderer_klass =
38
- case version.item.class.name
39
- when "Decidim::Plans::Plan"
40
- Decidim::Plans::DiffRenderer::Plan
41
- when "Decidim::Plans::Content"
42
- Decidim::Plans::DiffRenderer::Content
43
- when "Decidim::Categorization"
44
- Decidim::Plans::DiffRenderer::Categorization
45
- when "Decidim::Component"
46
- Decidim::Plans::DiffRenderer::Component
47
- end
39
+ item_klass = version.item.class.name
40
+ lastpart = item_klass.split("::").last
41
+ renderer_klass = "Decidim::Plans::DiffRenderer::#{lastpart}"
42
+
43
+ return nil unless defined?(renderer_klass)
48
44
 
49
- renderer_klass.new(version, locale)
45
+ renderer_klass.constantize.new(version, locale)
50
46
  end
51
47
 
52
48
  # Renders the given value in a user-friendly way based on the value class.
@@ -99,6 +99,13 @@ module Decidim
99
99
  published_at.present?
100
100
  end
101
101
 
102
+ # Public: Checks if the plan has been closed or not.
103
+ #
104
+ # Returns Boolean.
105
+ def closed?
106
+ closed_at.present?
107
+ end
108
+
102
109
  # Public: Checks if the organization has given an answer for the plan.
103
110
  #
104
111
  # Returns Boolean.
@@ -191,6 +198,14 @@ module Decidim
191
198
  Arel.sql(query)
192
199
  end
193
200
 
201
+ def self.export_serializer
202
+ Decidim::Plans::PlanSerializer
203
+ end
204
+
205
+ def self.data_portability_images(user)
206
+ user_collection(user).map { |p| p.attachments.collect(&:file) }
207
+ end
208
+
194
209
  # Checks whether the plan is inside the time window to be editable or not once published.
195
210
  def within_edit_time_limit?
196
211
  true
@@ -23,6 +23,8 @@ module Decidim
23
23
  permission_action.allow!
24
24
  when :edit, :update, :destroy
25
25
  permission_action.allow! if plan.present? || section.present?
26
+ when :close
27
+ permission_action.allow! if plan.present?
26
28
  end
27
29
 
28
30
  permission_action
@@ -35,6 +35,8 @@ module Decidim
35
35
  can_withdraw_plan?
36
36
  when :publish
37
37
  can_publish_plan?
38
+ when :close
39
+ can_close_plan?
38
40
  when :request_access
39
41
  can_request_access_plan?
40
42
  end
@@ -45,7 +47,7 @@ module Decidim
45
47
  end
46
48
 
47
49
  def can_edit_plan?
48
- toggle_allow(plan.open? && plan.editable_by?(user))
50
+ toggle_allow(plan.open? && !plan.closed? && plan.editable_by?(user))
49
51
  end
50
52
 
51
53
  def can_withdraw_plan?
@@ -56,6 +58,10 @@ module Decidim
56
58
  toggle_allow(plan.open? && plan.editable_by?(user))
57
59
  end
58
60
 
61
+ def can_close_plan?
62
+ toggle_allow(plan && plan.created_by?(user))
63
+ end
64
+
59
65
  def can_request_access_plan?
60
66
  return toggle_allow(false) unless plan.open?
61
67
  return toggle_allow(false) if plan.editable_by?(user)
@@ -82,7 +82,7 @@ module Decidim
82
82
  )
83
83
  end
84
84
 
85
- def parse_i18n_changeset(attribute, values, type, diff)
85
+ def parse_i18n_changeset(attribute, values, _type, diff)
86
86
  values.last.each_key do |locale, _value|
87
87
  next if display_locale && display_locale != locale
88
88
 
@@ -5,5 +5,7 @@
5
5
  <% if allowed_to? :create, :plan %>
6
6
  <%= link_to t("actions.new", scope: "decidim.plans"), new_plan_path, class: "button tiny button--simple" %>
7
7
  <% end %>
8
+
9
+ <%= export_dropdown %>
8
10
  </div>
9
11
  </div>
@@ -40,6 +40,14 @@
40
40
  <%= icon_link_to "pencil", edit_plan_path(plan), t("actions.edit_plan", scope: "decidim.plans"), class: "action-icon--edit-plan" %>
41
41
  <% end %>
42
42
 
43
+ <% if allowed_to? :close, :plan, plan: plan %>
44
+ <% if plan.closed? %>
45
+ <%= icon_link_to "action-undo", reopen_plan_path(plan), t("actions.reopen_plan", scope: "decidim.plans"), method: :post, class: "action-icon--reopen-plan" %>
46
+ <% else %>
47
+ <%= icon_link_to "ban", close_plan_path(plan), t("actions.close_plan", scope: "decidim.plans"), method: :post, class: "action-icon--close-plan" %>
48
+ <% end %>
49
+ <% end %>
50
+
43
51
  <% if allowed_to? :create, :plan_answer %>
44
52
  <%= icon_link_to "comment-square", edit_plan_plan_answer_path(plan_id: plan.id, id: plan.id), t("actions.answer", scope: "decidim.plans"), class: " icon--small" %>
45
53
  <% else %>
@@ -45,6 +45,13 @@
45
45
  <% if allowed_to?(:edit, :plan, plan: @plan) %>
46
46
  <%= link_to t("edit", scope:"decidim.plans.plans.show"), edit_plan_path(@plan), class: "button secondary hollow expanded button--sc mt-s", id: "plan_edit" %>
47
47
  <% end %>
48
+ <% if allowed_to?(:close, :plan, plan: @plan) %>
49
+ <% if @plan.closed? %>
50
+ <%= link_to t("reopen", scope:"decidim.plans.plans.show"), reopen_plan_path(@plan), method: :post, class: "button secondary hollow expanded button--sc mt-s", id: "plan_reopen", data: { confirm: t("reopen_confirm", scope:"decidim.plans.plans.show") } %>
51
+ <% else %>
52
+ <%= link_to t("close", scope:"decidim.plans.plans.show"), close_plan_path(@plan), method: :post, class: "button secondary hollow expanded button--sc mt-s", id: "plan_close", data: { confirm: t("close_confirm", scope:"decidim.plans.plans.show") } %>
53
+ <% end %>
54
+ <% end %>
48
55
 
49
56
  <%= render "request_access_form" %>
50
57
 
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  en:
3
+ activerecord:
4
+ models:
5
+ decidim/plans/plan: Plan
3
6
  activemodel:
4
7
  attributes:
5
8
  plan:
6
9
  proposals: Proposals
7
10
  category_id: Category
8
11
  decidim_category_id: Category
12
+ decidim_component_id: Component
9
13
  decidim_scope_id: Scope
10
14
  state: State
11
15
  title: Title
@@ -39,19 +43,27 @@ en:
39
43
  comments_blocked: Comments blocked
40
44
  creation_enabled: Creation enabled
41
45
  plan_answering_enabled: Answering enabled
46
+ pages:
47
+ home:
48
+ statistics:
49
+ plans_count: Plans
42
50
  participatory_processes:
43
51
  statistics:
44
52
  plans_count: Plans
45
53
  plans:
46
54
  actions:
47
55
  answer: Answer
56
+ close_plan: Mark as done
48
57
  edit_plan: Edit
49
58
  new: Submit new plan
59
+ reopen_plan: Open for editing
50
60
  sections: Sections
51
61
  title: Actions
52
62
  admin:
53
63
  actions:
54
64
  preview: Preview
65
+ exports:
66
+ plans: Plans
55
67
  plan_answers:
56
68
  edit:
57
69
  accepted: Accepted
@@ -207,15 +219,24 @@ en:
207
219
  random: Random
208
220
  recent: Recent
209
221
  plan:
222
+ close:
223
+ error: Error marking the item as done.
224
+ success: Item marked done successfully.
210
225
  publish:
211
226
  error: Error publishing new content.
212
227
  success: Published successfully.
228
+ reopen:
229
+ error: Error opening item for editing.
230
+ success: Item opened for editing successfully.
213
231
  view_plan: View plan
214
232
  withdraw:
215
233
  error: Error withdrawing.
216
234
  success: Withdrawn successfully.
217
235
  show:
218
236
  back: Back
237
+ close: Mark as done
238
+ close_confirm: Are you sure you want to mark this item done? After
239
+ marking done, the item can no longer be edited.
219
240
  edit: Edit
220
241
  hidden_authors_count:
221
242
  one: and %{count} more person
@@ -229,6 +250,8 @@ en:
229
250
  publish: Publish
230
251
  publish_info: Publish this version of the draft or
231
252
  published_proposal: published plan
253
+ reopen: Open for editing
254
+ reopen_confirm: Are you sure you want to open this item for editing?
232
255
  report: Report
233
256
  request_access: Request editing permissions
234
257
  requested_access: Editing permissions requested
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  fi:
3
+ activerecord:
4
+ models:
5
+ decidim/plans/plan: Suunnitelma
3
6
  activemodel:
4
7
  attributes:
5
8
  plan:
6
9
  proposals: Ehdotukset
7
10
  category_id: Kategoria
8
11
  decidim_category_id: Kategoria
12
+ decidim_component_id: Komponentti
9
13
  decidim_scope_id: Teema
10
14
  state: Tila
11
15
  title: Otsikko
@@ -39,19 +43,27 @@ fi:
39
43
  comments_blocked: Kommentointi estetty
40
44
  creation_enabled: Luonti sallittu
41
45
  plan_answering_enabled: Vastaaminen sallittu
46
+ pages:
47
+ home:
48
+ statistics:
49
+ plans_count: Suunnitelmia
42
50
  participatory_processes:
43
51
  statistics:
44
52
  plans_count: Suunnitelmaa
45
53
  plans:
46
54
  actions:
47
55
  answer: Vastaa
56
+ close_plan: Merkitse valmiiksi
48
57
  edit_plan: Muokkaa
49
58
  new: Tee uusi suunnitelma
59
+ reopen_plan: Avaa muokattavaksi
50
60
  sections: Osiot
51
61
  title: Toiminnot
52
62
  admin:
53
63
  actions:
54
64
  preview: Esikatsele
65
+ exports:
66
+ plans: Suunnitelmat
55
67
  plan_answers:
56
68
  edit:
57
69
  accepted: Hyväksytty
@@ -207,15 +219,24 @@ fi:
207
219
  random: Satunnainen
208
220
  recent: Uusimmat
209
221
  plan:
222
+ close:
223
+ error: Valmiiksi merkitseminen epäonnistui.
224
+ success: Valmiiksi merkitseminen onnistui.
210
225
  publish:
211
226
  error: Uuden sisällön julkaiseminen epäonnistui.
212
227
  success: Julkaisu onnistui.
228
+ reopen:
229
+ error: Muokattavaksi avaaminen epäonnistui.
230
+ success: Muokattavaksi avaaminen onnistui.
213
231
  view_plan: Näytä suunnitelma
214
232
  withdraw:
215
233
  error: Peruuttaminen epäonnistui.
216
234
  success: Peruuttaminen onnistui.
217
235
  show:
218
236
  back: Takaisin
237
+ close: Merkitse valmiiksi
238
+ close_confirm: Haluatko varmasti merkitä tämän kohteen valmiiksi? Kun
239
+ kohde on merkitty valmiiksi, sitä ei voi enää muokata.
219
240
  edit: Muokkaa
220
241
  hidden_authors_count:
221
242
  one: ja %{count} muu henkilö
@@ -229,6 +250,8 @@ fi:
229
250
  publish: Julkaise
230
251
  publish_info: Julkaise tämä luonnosversio tai
231
252
  published_proposal: julkaistu suunnitelma
253
+ reopen: Avaa muokattavaksi
254
+ reopen_confirm: Haluatko varmasti avata tämän kohteen muokattavaksi?
232
255
  report: Ilmoita
233
256
  request_access: Pyydä muokkausoikeudet
234
257
  requested_access: Muokkausoikeudet pyydetty
@@ -1,11 +1,16 @@
1
1
  ---
2
2
  sv:
3
+ activerecord:
4
+ models:
5
+ decidim/plans/plan: Plan
3
6
  activemodel:
4
7
  attributes:
5
8
  plan:
6
9
  proposals: Proposals
7
10
  category_id: Category
8
11
  decidim_category_id: Category
12
+ decidim_component_id: Component
13
+ decidim_proposal_id: Proposal
9
14
  decidim_scope_id: Scope
10
15
  state: State
11
16
  title: Title
@@ -39,19 +44,27 @@ sv:
39
44
  comments_blocked: Comments blocked
40
45
  creation_enabled: Creation enabled
41
46
  plan_answering_enabled: Answering enabled
47
+ pages:
48
+ home:
49
+ statistics:
50
+ plans_count: Plans
42
51
  participatory_processes:
43
52
  statistics:
44
53
  plans_count: Plans
45
54
  plans:
46
55
  actions:
47
56
  answer: Answer
57
+ close_plan: Mark as done
48
58
  edit_plan: Edit
49
59
  new: Submit new plan
60
+ reopen_plan: Open for editing
50
61
  sections: Sections
51
62
  title: Actions
52
63
  admin:
53
64
  actions:
54
65
  preview: Preview
66
+ exports:
67
+ plans: Plans
55
68
  plan_answers:
56
69
  edit:
57
70
  accepted: Accepted
@@ -207,15 +220,24 @@ sv:
207
220
  random: Random
208
221
  recent: Recent
209
222
  plan:
223
+ close:
224
+ error: Error marking the item as done.
225
+ success: Item marked done successfully.
210
226
  publish:
211
227
  error: Error publishing new content.
212
228
  success: Published successfully.
229
+ reopen:
230
+ error: Error opening item for editing.
231
+ success: Item opened for editing successfully.
213
232
  view_plan: View plan
214
233
  withdraw:
215
234
  error: Error withdrawing.
216
235
  success: Withdrawn successfully.
217
236
  show:
218
237
  back: Back
238
+ close: Mark as done
239
+ close_confirm: Are you sure you want to mark this item done? After
240
+ marking done, the item can no longer be edited.
219
241
  edit: Edit
220
242
  hidden_authors_count:
221
243
  one: and %{count} more person
@@ -229,6 +251,8 @@ sv:
229
251
  publish: Publish
230
252
  publish_info: Publish this version of the draft or
231
253
  published_proposal: published plan
254
+ reopen: Open for editing
255
+ reopen_confirm: Are you sure you want to open this item for editing?
232
256
  report: Report
233
257
  request_access: Request editing permissions
234
258
  requested_access: Editing permissions requested
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddClosedAtToDecidimPlans < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_column :decidim_plans_plans, :closed_at, :datetime
6
+ add_index :decidim_plans_plans, :closed_at
7
+ end
8
+ end
@@ -13,6 +13,10 @@ module Decidim
13
13
  resources :plans, only: [:index, :new, :create, :edit, :update] do
14
14
  get :search_proposals
15
15
  resources :plan_answers, only: [:edit, :update]
16
+ member do
17
+ post :close
18
+ post :reopen
19
+ end
16
20
  end
17
21
  resources :sections, only: [:index, :new, :create, :edit, :update]
18
22
 
@@ -13,7 +13,7 @@ Decidim.register_component(:plans) do |component|
13
13
 
14
14
  component.data_portable_entities = ["Decidim::Plans::Plan"]
15
15
 
16
- component.actions = %w(create withdraw)
16
+ component.actions = %w(create withdraw close reopen)
17
17
 
18
18
  component.permissions_class_name = "Decidim::Plans::Permissions"
19
19
 
@@ -23,7 +23,7 @@ Decidim.register_component(:plans) do |component|
23
23
  settings.attribute :comments_enabled, type: :boolean, default: true
24
24
  settings.attribute :announcement, type: :text, translated: true, editor: true
25
25
  settings.attribute :attachments_allowed, type: :boolean, default: false
26
- settings.attribute :multilingual_answers, type: :boolean, default: true
26
+ settings.attribute :multilingual_answers, type: :boolean
27
27
  end
28
28
 
29
29
  component.settings(:step) do |settings|
@@ -51,6 +51,19 @@ Decidim.register_component(:plans) do |component|
51
51
  Decidim::Comments::Comment.where(root_commentable: plans).count
52
52
  end
53
53
 
54
+ component.exports :plans do |exports|
55
+ exports.collection do |component_instance|
56
+ Decidim::Plans::Plan
57
+ .published
58
+ .where(component: component_instance)
59
+ .includes(:category, component: { participatory_space: :organization })
60
+ end
61
+
62
+ exports.include_in_open_data = true
63
+
64
+ exports.serializer Decidim::Plans::PlanSerializer
65
+ end
66
+
54
67
  component.seeds do |participatory_space|
55
68
  admin_user = Decidim::User.find_by(
56
69
  organization: participatory_space.organization,
@@ -69,7 +82,7 @@ Decidim.register_component(:plans) do |component|
69
82
  published_at: Time.current,
70
83
  participatory_space: participatory_space,
71
84
  settings: {
72
- multilingual_answers: true
85
+ multilingual_answers: false
73
86
  },
74
87
  step_settings: step_settings
75
88
  }
@@ -13,6 +13,8 @@ module Decidim
13
13
  member do
14
14
  get :preview
15
15
  post :publish
16
+ post :close
17
+ post :reopen
16
18
  put :withdraw
17
19
  post :request_access, controller: "plan_collaborator_requests"
18
20
  post :request_accept, controller: "plan_collaborator_requests"
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # This class serializes a Proposal so can be exported to CSV, JSON or other
6
+ # formats.
7
+ class PlanSerializer < Decidim::Exporters::Serializer
8
+ include Decidim::ApplicationHelper
9
+ include Decidim::ResourceHelper
10
+
11
+ # Public: Initializes the serializer with a plan.
12
+ def initialize(plan)
13
+ @plan = plan
14
+ end
15
+
16
+ # Public: Exports a hash with the serialized data for this proposal.
17
+ def serialize
18
+ values = {
19
+ id: plan.id,
20
+ category: {
21
+ id: plan.category.try(:id),
22
+ name: plan.category.try(:name)
23
+ },
24
+ scope: {
25
+ id: plan.scope.try(:id),
26
+ name: plan.scope.try(:name)
27
+ },
28
+ participatory_space: {
29
+ id: plan.participatory_space.id,
30
+ url: Decidim::ResourceLocatorPresenter.new(plan.participatory_space).url
31
+ },
32
+ component: { id: component.id },
33
+ state: plan.state.to_s,
34
+ comments: plan.comments.count,
35
+ attachments: plan.attachments.count,
36
+ followers: plan.followers.count,
37
+ published_at: plan.published_at,
38
+ closed_at: plan.closed_at,
39
+ url: url,
40
+ related_proposals: {
41
+ ids: related_proposal_ids,
42
+ urls: related_proposal_urls
43
+ },
44
+ title: plan.title
45
+ }
46
+
47
+ # Add section content
48
+ plan.sections.each do |sect|
49
+ content = plan.contents.find_by(section: sect)
50
+ values["section_#{sect.id}".to_sym] = {
51
+ title: sect.body,
52
+ value: content.try(:body)
53
+ }
54
+ end
55
+
56
+ values
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :plan
62
+
63
+ def component
64
+ plan.component
65
+ end
66
+
67
+ def related_proposal_ids
68
+ plan.proposals.map(&:id)
69
+ end
70
+
71
+ def related_proposal_urls
72
+ plan.proposals.map do |proposal|
73
+ Decidim::ResourceLocatorPresenter.new(proposal).url
74
+ end
75
+ end
76
+
77
+ def url
78
+ Decidim::ResourceLocatorPresenter.new(plan).url
79
+ end
80
+ end
81
+ end
82
+ end
@@ -41,10 +41,10 @@ FactoryBot.define do
41
41
  end
42
42
  end
43
43
 
44
- trait :with_single_language_answers do
44
+ trait :with_multilingual_answers do
45
45
  settings do
46
46
  {
47
- multilingual_answers: false
47
+ multilingual_answers: true
48
48
  }
49
49
  end
50
50
  end
@@ -89,6 +89,13 @@ FactoryBot.define do
89
89
  published_at { nil }
90
90
  end
91
91
 
92
+ trait :official do
93
+ after :build do |plan|
94
+ plan.coauthorships.clear
95
+ plan.coauthorships.build(author: plan.organization)
96
+ end
97
+ end
98
+
92
99
  trait :evaluating do
93
100
  state { "evaluating" }
94
101
  answered_at { Time.current }
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Decidim
4
4
  module Plans
5
- VERSION = "0.16.0"
5
+ VERSION = "0.16.1"
6
6
  DECIDIM_VERSION = "~> 0.16.0"
7
7
  end
8
8
  end
data/lib/decidim/plans.rb CHANGED
@@ -6,11 +6,13 @@ require_relative "plans/engine"
6
6
  require_relative "plans/admin_engine"
7
7
  require_relative "plans/paper_trail"
8
8
  require_relative "plans/component"
9
- require_relative "plans/locale_aware"
10
- require_relative "plans/optionally_translatable_attributes"
11
9
 
12
10
  module Decidim
13
11
  module Plans
12
+ autoload :LocaleAware, "decidim/plans/locale_aware"
13
+ autoload :OptionallyTranslatableAttributes, "decidim/plans/optionally_translatable_attributes"
14
+ autoload :PlanSerializer, "decidim/plans/plan_serializer"
15
+
14
16
  # Public: Stores an instance of Loggability
15
17
  def self.loggability
16
18
  @loggability ||= Loggability.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decidim-plans
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antti Hukkanen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-06 00:00:00.000000000 Z
11
+ date: 2019-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: decidim-core
@@ -151,11 +151,13 @@ files:
151
151
  - app/commands/decidim/plans/admin/update_plan.rb
152
152
  - app/commands/decidim/plans/admin/update_sections.rb
153
153
  - app/commands/decidim/plans/attachment_methods.rb
154
+ - app/commands/decidim/plans/close_plan.rb
154
155
  - app/commands/decidim/plans/create_plan.rb
155
156
  - app/commands/decidim/plans/destroy_plan.rb
156
157
  - app/commands/decidim/plans/nested_updater.rb
157
158
  - app/commands/decidim/plans/publish_plan.rb
158
159
  - app/commands/decidim/plans/reject_access_to_plan.rb
160
+ - app/commands/decidim/plans/reopen_plan.rb
159
161
  - app/commands/decidim/plans/request_access_to_plan.rb
160
162
  - app/commands/decidim/plans/respond_to_access_request.rb
161
163
  - app/commands/decidim/plans/update_plan.rb
@@ -286,6 +288,7 @@ files:
286
288
  - db/migrate/20190202123044_create_version_associations.rb
287
289
  - db/migrate/20190202123045_add_transaction_id_column_to_versions.rb
288
290
  - db/migrate/20190202200716_add_update_token_to_decidim_plans.rb
291
+ - db/migrate/20190214124014_add_closed_at_to_decidim_plans.rb
289
292
  - lib/decidim/plans.rb
290
293
  - lib/decidim/plans/admin.rb
291
294
  - lib/decidim/plans/admin_engine.rb
@@ -295,6 +298,7 @@ files:
295
298
  - lib/decidim/plans/optionally_translatable_attributes.rb
296
299
  - lib/decidim/plans/paper_trail.rb
297
300
  - lib/decidim/plans/paper_trail/record_trail.rb
301
+ - lib/decidim/plans/plan_serializer.rb
298
302
  - lib/decidim/plans/test/factories.rb
299
303
  - lib/decidim/plans/version.rb
300
304
  homepage: https://github.com/mainio/decidim-module-plans