decidim-proposals 0.14.4 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/app/assets/images/decidim/gamification/badges/accepted_proposals.svg +194 -104
  4. data/app/assets/images/decidim/gamification/badges/proposals.svg +192 -108
  5. data/app/assets/javascripts/decidim/proposals/admin/proposals.es6 +25 -20
  6. data/app/cells/decidim/proposals/proposal_activity_cell.rb +19 -0
  7. data/app/commands/decidim/proposals/admin/answer_proposal.rb +6 -2
  8. data/app/commands/decidim/proposals/admin/create_proposal.rb +4 -4
  9. data/app/commands/decidim/proposals/admin/import_participatory_text.rb +49 -0
  10. data/app/commands/decidim/proposals/admin/import_proposals.rb +7 -21
  11. data/app/commands/decidim/proposals/admin/merge_proposals.rb +68 -0
  12. data/app/commands/decidim/proposals/admin/publish_participatory_text.rb +65 -0
  13. data/app/commands/decidim/proposals/admin/split_proposals.rb +67 -0
  14. data/app/commands/decidim/proposals/admin/update_proposal.rb +67 -0
  15. data/app/commands/decidim/proposals/create_collaborative_draft.rb +19 -14
  16. data/app/commands/decidim/proposals/create_proposal.rb +4 -2
  17. data/app/commands/decidim/proposals/endorse_proposal.rb +5 -1
  18. data/app/commands/decidim/proposals/publish_proposal.rb +15 -3
  19. data/app/commands/decidim/proposals/unvote_proposal.rb +34 -3
  20. data/app/commands/decidim/proposals/vote_proposal.rb +32 -2
  21. data/app/controllers/decidim/proposals/admin/participatory_texts_controller.rb +62 -0
  22. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +28 -1
  23. data/app/controllers/decidim/proposals/admin/proposals_merges_controller.rb +27 -0
  24. data/app/controllers/decidim/proposals/admin/proposals_splits_controller.rb +27 -0
  25. data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +0 -1
  26. data/app/controllers/decidim/proposals/proposal_endorsements_controller.rb +6 -2
  27. data/app/controllers/decidim/proposals/proposal_votes_controller.rb +15 -0
  28. data/app/controllers/decidim/proposals/proposals_controller.rb +1 -1
  29. data/app/forms/decidim/proposals/admin/import_participatory_text_form.rb +28 -0
  30. data/app/forms/decidim/proposals/admin/preview_participatory_text_form.rb +21 -0
  31. data/app/forms/decidim/proposals/admin/proposal_form.rb +4 -1
  32. data/app/forms/decidim/proposals/admin/proposals_fork_form.rb +57 -0
  33. data/app/forms/decidim/proposals/admin/proposals_merge_form.rb +13 -0
  34. data/app/forms/decidim/proposals/admin/proposals_split_form.rb +12 -0
  35. data/app/helpers/decidim/proposals/application_helper.rb +23 -0
  36. data/app/helpers/decidim/proposals/map_helper.rb +1 -1
  37. data/app/helpers/decidim/proposals/participatory_texts_helper.rb +18 -0
  38. data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +1 -1
  39. data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +2 -1
  40. data/app/jobs/decidim/proposals/notify_proposals_mentioned_job.rb +3 -4
  41. data/app/models/decidim/proposals/participatory_text.rb +13 -0
  42. data/app/models/decidim/proposals/proposal.rb +31 -9
  43. data/app/models/decidim/proposals/proposal_vote.rb +21 -1
  44. data/app/permissions/decidim/proposals/admin/permissions.rb +30 -0
  45. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +5 -1
  46. data/app/presenters/decidim/proposals/log/resource_presenter.rb +18 -0
  47. data/app/presenters/decidim/proposals/official_author_presenter.rb +4 -0
  48. data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +29 -0
  49. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +53 -0
  50. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +57 -0
  51. data/app/services/decidim/proposals/proposal_builder.rb +72 -0
  52. data/app/services/decidim/proposals/proposal_search.rb +2 -2
  53. data/app/views/decidim/proposals/admin/participatory_texts/_article-preview.html.erb +13 -0
  54. data/app/views/decidim/proposals/admin/participatory_texts/_bulk-actions.html.erb +1 -0
  55. data/app/views/decidim/proposals/admin/participatory_texts/index.html.erb +43 -0
  56. data/app/views/decidim/proposals/admin/participatory_texts/new_import.html.erb +39 -0
  57. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +7 -2
  58. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +4 -0
  59. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +36 -0
  60. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_merge.html.erb +15 -0
  61. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +15 -0
  62. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_split.html.erb +15 -0
  63. data/app/views/decidim/proposals/admin/proposals/edit.html.erb +7 -0
  64. data/app/views/decidim/proposals/admin/proposals/index.html.erb +2 -2
  65. data/app/views/decidim/proposals/collaborative_drafts/compare.html.erb +2 -2
  66. data/app/views/decidim/proposals/collaborative_drafts/complete.html.erb +1 -1
  67. data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +1 -1
  68. data/app/views/decidim/proposals/proposal_endorsements/_identity.html.erb +5 -1
  69. data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +1 -1
  70. data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +13 -4
  71. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +2 -2
  72. data/app/views/decidim/proposals/proposals/_endorsement_identities_cabin.html.erb +1 -1
  73. data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +1 -1
  74. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +15 -4
  75. data/app/views/decidim/proposals/proposals/new.html.erb +1 -1
  76. data/config/locales/ca.yml +72 -4
  77. data/config/locales/de.yml +71 -3
  78. data/config/locales/en.yml +71 -3
  79. data/config/locales/es-PY.yml +71 -3
  80. data/config/locales/es.yml +73 -5
  81. data/config/locales/eu.yml +71 -3
  82. data/config/locales/fi.yml +81 -13
  83. data/config/locales/fr.yml +71 -3
  84. data/config/locales/gl.yml +71 -3
  85. data/config/locales/hu.yml +71 -3
  86. data/config/locales/it.yml +71 -3
  87. data/config/locales/nl.yml +71 -3
  88. data/config/locales/pl.yml +71 -3
  89. data/config/locales/pt-BR.yml +72 -4
  90. data/config/locales/pt.yml +71 -3
  91. data/config/locales/sv.yml +71 -3
  92. data/db/migrate/20180927111721_create_participatory_texts.rb +13 -0
  93. data/db/migrate/20180930125321_add_participatory_text_level_to_proposals.rb +7 -0
  94. data/db/migrate/20180930125321_add_position_to_proposals.rb +7 -0
  95. data/db/migrate/20181003074440_fix_user_groups_ids_in_proposals_endorsements.rb +16 -0
  96. data/db/migrate/20181010114622_add_temporary_votes.rb +9 -0
  97. data/db/migrate/20181016132225_add_organization_as_author.rb +13 -0
  98. data/db/migrate/20181017084221_make_author_polymorhpic_for_proposal_endorsements.rb +31 -0
  99. data/lib/decidim/content_parsers/proposal_parser.rb +9 -3
  100. data/lib/decidim/proposals.rb +3 -1
  101. data/lib/decidim/proposals/admin_engine.rb +12 -1
  102. data/lib/decidim/proposals/component.rb +60 -23
  103. data/lib/decidim/proposals/engine.rb +55 -12
  104. data/lib/decidim/proposals/markdown_to_proposals.rb +90 -0
  105. data/lib/decidim/proposals/participatory_text_section.rb +21 -0
  106. data/lib/decidim/proposals/test/factories.rb +35 -7
  107. data/lib/decidim/proposals/version.rb +1 -1
  108. metadata +86 -19
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A form object to be used when admin users want to import a collection of proposals
7
+ # from a participatory text.
8
+ class ImportParticipatoryTextForm < Decidim::Form
9
+ include TranslatableAttributes
10
+
11
+ translatable_attribute :title, String
12
+ translatable_attribute :description, String
13
+ attribute :document
14
+
15
+ validates :document, presence: true
16
+ validates :title, translatable_presence: true
17
+
18
+ def default_locale
19
+ current_participatory_space.organization.default_locale
20
+ end
21
+
22
+ def document_text
23
+ document&.read
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A form object to be used when admin users want to review a collection of proposals
7
+ # from a participatory text.
8
+ class PreviewParticipatoryTextForm < Decidim::Form
9
+ attribute :proposals, Array[ProposalForm]
10
+
11
+ def from_models(proposals)
12
+ self.proposals = proposals.collect do |proposal|
13
+ ProposalForm.from_model(proposal)
14
+ end
15
+ end
16
+
17
+ def proposals_attributes=(attributes); end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -15,8 +15,10 @@ module Decidim
15
15
  attribute :category_id, Integer
16
16
  attribute :scope_id, Integer
17
17
  attribute :attachment, AttachmentForm
18
+ attribute :position, Integer
18
19
 
19
- validates :title, :body, presence: true
20
+ validates :title, :body, presence: true, etiquette: true
21
+ validates :title, length: { maximum: 150 }
20
22
  validates :address, geocoding: true, if: -> { current_component.settings.geocoding_enabled? }
21
23
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
22
24
  validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
@@ -31,6 +33,7 @@ module Decidim
31
33
  return unless model.categorization
32
34
 
33
35
  self.category_id = model.categorization.decidim_category_id
36
+ self.scope_id = model.decidim_scope_id
34
37
  end
35
38
 
36
39
  alias component current_component
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A common abstract to be used by the Merge and Split proposals forms.
7
+ class ProposalsForkForm < Decidim::Form
8
+ mimic :proposals_import
9
+
10
+ attribute :target_component_id, Integer
11
+ attribute :proposal_ids, Array
12
+
13
+ validates :target_component, :proposals, :current_component, presence: true
14
+ validate :same_participatory_space
15
+ validate :mergeable_to_same_component
16
+
17
+ def proposals
18
+ @proposals ||= Decidim::Proposals::Proposal.where(component: current_component, id: proposal_ids).uniq
19
+ end
20
+
21
+ def target_component
22
+ return current_component if clean_target_component_id == current_component.id
23
+ @target_component ||= current_component.siblings.find_by(id: target_component_id)
24
+ end
25
+
26
+ def same_component?
27
+ target_component == current_component
28
+ end
29
+
30
+ private
31
+
32
+ def mergeable_to_same_component
33
+ return true unless same_component?
34
+
35
+ public_proposals = proposals.any? do |proposal|
36
+ !proposal.official? || proposal.votes.any? || proposal.endorsements.any?
37
+ end
38
+
39
+ errors.add(:proposal_ids, :invalid) if public_proposals
40
+ end
41
+
42
+ def same_participatory_space
43
+ return if !target_component || !current_component
44
+
45
+ errors.add(:target_component, :invalid) if current_component.participatory_space != target_component.participatory_space
46
+ end
47
+
48
+ # Private: Returns the id of the target component.
49
+ #
50
+ # We receive this as ["id"] since it's from a select in a form.
51
+ def clean_target_component_id
52
+ target_component_id.first.to_i
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A form object to be used when admin users wants to merge two or more
7
+ # proposals into a new one to another proposal component in the same space.
8
+ class ProposalsMergeForm < ProposalsForkForm
9
+ validates :proposals, length: { minimum: 2 }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A form object to be used when admin users wants to split two or more
7
+ # proposals into a new one to another proposal component in the same space.
8
+ class ProposalsSplitForm < ProposalsForkForm
9
+ end
10
+ end
11
+ end
12
+ end
@@ -13,6 +13,8 @@ module Decidim
13
13
  include Decidim::Proposals::MapHelper
14
14
  include CollaborativeDraftHelper
15
15
 
16
+ delegate :minimum_votes_per_user, to: :component_settings
17
+
16
18
  # Public: The state of a proposal in a way a human can understand.
17
19
  #
18
20
  # state - The String state of the proposal.
@@ -69,12 +71,23 @@ module Decidim
69
71
  proposal_limit.present?
70
72
  end
71
73
 
74
+ def minimum_votes_per_user_enabled?
75
+ minimum_votes_per_user.positive?
76
+ end
77
+
72
78
  def proposal_limit
73
79
  return if component_settings.proposal_limit.zero?
74
80
 
75
81
  component_settings.proposal_limit
76
82
  end
77
83
 
84
+ def votes_given
85
+ @votes_given ||= ProposalVote.where(
86
+ proposal: Proposal.where(component: current_component),
87
+ author: current_user
88
+ ).count
89
+ end
90
+
78
91
  def current_user_proposals
79
92
  Proposal.where(component: current_component, author: current_user)
80
93
  end
@@ -102,6 +115,16 @@ module Decidim
102
115
  def authors_for(collaborative_draft)
103
116
  collaborative_draft.identities.map { |identity| present(identity) }
104
117
  end
118
+
119
+ def show_voting_rules?
120
+ return false unless votes_enabled?
121
+
122
+ return true if vote_limit_enabled?
123
+ return true if threshold_per_proposal_enabled?
124
+ return true if proposal_limit_enabled?
125
+ return true if can_accumulate_supports_beyond_threshold?
126
+ return true if minimum_votes_per_user_enabled?
127
+ end
105
128
  end
106
129
  end
107
130
  end
@@ -10,7 +10,7 @@ module Decidim
10
10
  # geocoded_proposals - A collection of geocoded proposals
11
11
  def proposals_data_for_map(geocoded_proposals)
12
12
  geocoded_proposals.map do |proposal|
13
- proposal.slice(:latitude, :longitude, :address).merge(title: present(proposal).title,
13
+ proposal.slice(:latitude, :longitude, :address).merge(title: present(proposal).title,
14
14
  body: truncate(present(proposal).body, length: 100),
15
15
  icon: icon("proposals", width: 40, height: 70, remove_icon_class: true),
16
16
  link: proposal_path(proposal))
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # Simple helper to handle markup variations for participatory texts related partials
6
+ module ParticipatoryTextsHelper
7
+ # Returns the title for a given participatory text section.
8
+ #
9
+ # proposal - The current proposal item.
10
+ #
11
+ # Returns a string with the title of the section, subsection or article.
12
+ def preview_participatory_text_section_title(proposal)
13
+ translated = t(proposal.participatory_text_level, scope: "decidim.proposals.admin.participatory_texts.sections", title: proposal.title)
14
+ translated.html_safe
15
+ end
16
+ end
17
+ end
18
+ end
@@ -79,7 +79,7 @@ module Decidim
79
79
  def fully_endorsed?(proposal, user)
80
80
  return false unless user
81
81
 
82
- user_group_endorsements = user.user_groups.verified.all? { |user_group| proposal.endorsed_by?(user, user_group) }
82
+ user_group_endorsements = Decidim::UserGroups::ManageableUserGroups.for(user).verified.all? { |user_group| proposal.endorsed_by?(user, user_group) }
83
83
 
84
84
  user_group_endorsements && proposal.endorsed_by?(user)
85
85
  end
@@ -101,9 +101,10 @@ module Decidim
101
101
  # Returns nothing.
102
102
  def user_group_select_field(form, name)
103
103
  selected = @form.user_group_id.presence
104
+ user_groups = Decidim::UserGroups::ManageableUserGroups.for(current_user).verified
104
105
  form.select(
105
106
  name,
106
- current_user.user_groups.verified.map { |g| [g.name, g.id] },
107
+ user_groups.map { |g| [g.name, g.id] },
107
108
  selected: selected,
108
109
  include_blank: current_user.name
109
110
  )
@@ -3,14 +3,13 @@
3
3
  module Decidim
4
4
  module Proposals
5
5
  class NotifyProposalsMentionedJob < ApplicationJob
6
- def perform(comment_id, proposal_metadata)
6
+ def perform(comment_id, linked_proposals)
7
7
  comment = Decidim::Comments::Comment.find(comment_id)
8
- linked_proposals = proposal_metadata.linked_proposals
8
+
9
9
  linked_proposals.each do |proposal_id|
10
10
  proposal = Proposal.find(proposal_id)
11
- next if proposal.official?
11
+ recipient_ids = proposal.notifiable_authors.map(&:id)
12
12
 
13
- recipient_ids = proposal.authors.pluck(:decidim_author_id)
14
13
  Decidim::EventsManager.publish(
15
14
  event: "decidim.events.proposals.proposal_mentioned",
16
15
  event_class: Decidim::Proposals::ProposalMentionedEvent,
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # Contains the meta data of the document, like title and description.
6
+ #
7
+ class ParticipatoryText < Proposals::ApplicationRecord
8
+ include Decidim::HasComponent
9
+ include Decidim::Traceable
10
+ include Decidim::Loggable
11
+ end
12
+ end
13
+ end
@@ -20,13 +20,21 @@ module Decidim
20
20
  include Decidim::Fingerprintable
21
21
  include Decidim::DataPortability
22
22
  include Decidim::Hashtaggable
23
+ include Decidim::Proposals::ParticipatoryTextSection
23
24
 
24
25
  fingerprint fields: [:title, :body]
25
26
 
26
27
  component_manifest_name "proposals"
27
28
 
28
29
  has_many :endorsements, foreign_key: "decidim_proposal_id", class_name: "ProposalEndorsement", dependent: :destroy, counter_cache: "proposal_endorsements_count"
29
- has_many :votes, foreign_key: "decidim_proposal_id", class_name: "ProposalVote", dependent: :destroy, counter_cache: "proposal_votes_count"
30
+
31
+ has_many :votes,
32
+ -> { final },
33
+ foreign_key: "decidim_proposal_id",
34
+ class_name: "Decidim::Proposals::ProposalVote",
35
+ dependent: :destroy,
36
+ counter_cache: "proposal_votes_count"
37
+
30
38
  has_many :notes, foreign_key: "decidim_proposal_id", class_name: "ProposalNote", dependent: :destroy, counter_cache: "proposal_notes_count"
31
39
 
32
40
  validates :title, :body, presence: true
@@ -39,8 +47,11 @@ module Decidim
39
47
  scope :withdrawn, -> { where(state: "withdrawn") }
40
48
  scope :except_rejected, -> { where.not(state: "rejected").or(where(state: nil)) }
41
49
  scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
50
+ scope :drafts, -> { where(published_at: nil) }
42
51
  scope :published, -> { where.not(published_at: nil) }
43
52
 
53
+ acts_as_list scope: :decidim_component_id
54
+
44
55
  searchable_fields({
45
56
  scope_id: :decidim_scope_id,
46
57
  participatory_space: { component: :participatory_space },
@@ -62,19 +73,30 @@ module Decidim
62
73
  Decidim::Proposals::AdminLog::ProposalPresenter
63
74
  end
64
75
 
65
- # Returns a collection scoped by user.
76
+ # Returns a collection scoped by an author.
66
77
  # Overrides this method in DataPortability to support Coauthorable.
67
- def self.user_collection(user)
78
+ def self.user_collection(author)
79
+ return unless author.is_a?(Decidim::User)
80
+
68
81
  joins(:coauthorships)
69
82
  .where("decidim_coauthorships.coauthorable_type = ?", name)
70
- .where("decidim_coauthorships.decidim_author_id = ?", user.id)
83
+ .where("decidim_coauthorships.decidim_author_id = ? AND decidim_coauthorships.decidim_author_type = ? ", author.id, author.class.base_class.name)
71
84
  end
72
85
 
86
+ # Public: Updates the vote count of this proposal.
87
+ #
88
+ # Returns nothing.
89
+ # rubocop:disable Rails/SkipsModelValidations
90
+ def update_votes_count
91
+ update_columns(proposal_votes_count: votes.count)
92
+ end
93
+ # rubocop:enable Rails/SkipsModelValidations
94
+
73
95
  # Public: Check if the user has voted the proposal.
74
96
  #
75
97
  # Returns Boolean.
76
98
  def voted_by?(user)
77
- votes.where(author: user).any?
99
+ ProposalVote.where(proposal: self, author: user).any?
78
100
  end
79
101
 
80
102
  # Public: Check if the user has endorsed the proposal.
@@ -134,7 +156,7 @@ module Decidim
134
156
 
135
157
  # Public: Whether the proposal is official or not.
136
158
  def official?
137
- authors.empty?
159
+ authors.first.is_a?(Decidim::Organization)
138
160
  end
139
161
 
140
162
  # Public: The maximum amount of votes allowed for this proposal.
@@ -201,7 +223,7 @@ module Decidim
201
223
  end
202
224
 
203
225
  def self.data_portability_images(user)
204
- user_collection(user).map { |p| p.attachments.collect(&:file_url) }
226
+ user_collection(user).map { |p| p.attachments.collect(&:file) }
205
227
  end
206
228
 
207
229
  # Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
@@ -209,8 +231,6 @@ module Decidim
209
231
  component.settings.resources_permissions_enabled
210
232
  end
211
233
 
212
- private
213
-
214
234
  # Checks whether the proposal is inside the time window to be editable or not once published.
215
235
  def within_edit_time_limit?
216
236
  return true if draft?
@@ -218,6 +238,8 @@ module Decidim
218
238
  Time.current < limit
219
239
  end
220
240
 
241
+ private
242
+
221
243
  def copied_from_other_component?
222
244
  linked_resources(:proposals, "copied_from_component").any?
223
245
  end
@@ -4,15 +4,35 @@ module Decidim
4
4
  module Proposals
5
5
  # A proposal can include a vote per user.
6
6
  class ProposalVote < ApplicationRecord
7
- belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
7
+ belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal"
8
8
  belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
9
9
 
10
10
  validates :proposal, uniqueness: { scope: :author }
11
11
  validate :author_and_proposal_same_organization
12
12
  validate :proposal_not_rejected
13
13
 
14
+ after_save :update_proposal_votes_count
15
+ after_destroy :update_proposal_votes_count
16
+
17
+ # Temporary votes are used when a minimum amount of votes is configured in
18
+ # a component. They aren't taken into account unless the amount of votes
19
+ # exceeds a threshold - meanwhile, they're marked as temporary.
20
+ def self.temporary
21
+ where(temporary: true)
22
+ end
23
+
24
+ # Final votes are votes that will be taken into account, that is, they're
25
+ # not temporary.
26
+ def self.final
27
+ where(temporary: false)
28
+ end
29
+
14
30
  private
15
31
 
32
+ def update_proposal_votes_count
33
+ proposal.update_votes_count
34
+ end
35
+
16
36
  # Private: check if the proposal and the author have the same organization
17
37
  def author_and_proposal_same_organization
18
38
  return if !proposal || !author
@@ -22,22 +22,48 @@ module Decidim
22
22
  toggle_allow(admin_proposal_answering_is_enabled?) if permission_action.subject == :proposal_answer
23
23
  end
24
24
 
25
+ # Admins can only edit official proposals if they are within the
26
+ # time limit.
27
+ allow! if permission_action.subject == :proposal && permission_action.action == :edit && admin_edition_is_available?
28
+
25
29
  # Every user allowed by the space can update the category of the proposal
26
30
  allow! if permission_action.subject == :proposal_category && permission_action.action == :update
27
31
 
28
32
  # Every user allowed by the space can import proposals from another_component
29
33
  allow! if permission_action.subject == :proposals && permission_action.action == :import
30
34
 
35
+ # Every user allowed by the space can merge proposals to another component
36
+ allow! if permission_action.subject == :proposals && permission_action.action == :merge
37
+
38
+ # Every user allowed by the space can split proposals to another component
39
+ allow! if permission_action.subject == :proposals && permission_action.action == :split
40
+
41
+ if permission_action.subject == :participatory_texts && participatory_texts_are_enabled?
42
+ # Every user allowed by the space can import participatory texts to proposals
43
+ allow! if permission_action.action == :import
44
+ # Every user allowed by the space can publish participatory texts to proposals
45
+ allow! if permission_action.action == :publish
46
+ end
47
+
31
48
  permission_action
32
49
  end
33
50
 
34
51
  private
35
52
 
53
+ def proposal
54
+ @proposal ||= context.fetch(:proposal, nil)
55
+ end
56
+
36
57
  def admin_creation_is_enabled?
37
58
  current_settings.try(:creation_enabled?) &&
38
59
  component_settings.try(:official_proposals_enabled)
39
60
  end
40
61
 
62
+ def admin_edition_is_available?
63
+ return unless proposal
64
+ proposal.official? && proposal.votes.empty?
65
+ end
66
+
41
67
  def admin_proposal_answering_is_enabled?
42
68
  current_settings.try(:proposal_answering_enabled) &&
43
69
  component_settings.try(:proposal_answering_enabled)
@@ -46,6 +72,10 @@ module Decidim
46
72
  def create_permission_action?
47
73
  permission_action.action == :create
48
74
  end
75
+
76
+ def participatory_texts_are_enabled?
77
+ component_settings.participatory_texts_enabled?
78
+ end
49
79
  end
50
80
  end
51
81
  end