decidim-proposals 0.29.3 → 0.30.0.rc1

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +13 -1
  3. data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
  4. data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
  5. data/app/cells/decidim/proposals/proposal_g/show.erb +13 -0
  6. data/app/cells/decidim/proposals/proposal_g_cell.rb +13 -0
  7. data/app/cells/decidim/proposals/proposal_history_cell.rb +107 -0
  8. data/app/cells/decidim/proposals/proposal_l/show.erb +37 -0
  9. data/app/cells/decidim/proposals/proposal_l_cell.rb +9 -0
  10. data/app/cells/decidim/proposals/proposal_metadata_cell.rb +2 -2
  11. data/app/cells/decidim/proposals/proposal_vote/show.erb +75 -0
  12. data/app/cells/decidim/proposals/proposal_vote_cell.rb +43 -0
  13. data/app/commands/decidim/proposals/accept_coauthorship.rb +62 -0
  14. data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +14 -0
  15. data/app/commands/decidim/proposals/admin/create_proposal.rb +6 -14
  16. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +20 -11
  17. data/app/commands/decidim/proposals/admin/import_proposals.rb +60 -7
  18. data/app/commands/decidim/proposals/admin/merge_proposals.rb +2 -2
  19. data/app/commands/decidim/proposals/admin/proposal_notes_methods.rb +48 -0
  20. data/app/commands/decidim/proposals/admin/reply_proposal_note.rb +92 -0
  21. data/app/commands/decidim/proposals/admin/split_proposals.rb +2 -2
  22. data/app/commands/decidim/proposals/admin/update_proposal.rb +10 -16
  23. data/app/commands/decidim/proposals/admin/update_proposal_taxonomies.rb +34 -0
  24. data/app/commands/decidim/proposals/cancel_coauthorship.rb +32 -0
  25. data/app/commands/decidim/proposals/create_collaborative_draft.rb +1 -2
  26. data/app/commands/decidim/proposals/create_proposal.rb +1 -2
  27. data/app/commands/decidim/proposals/invite_coauthor.rb +45 -0
  28. data/app/commands/decidim/proposals/publish_collaborative_draft.rb +1 -2
  29. data/app/commands/decidim/proposals/reject_coauthorship.rb +54 -0
  30. data/app/commands/decidim/proposals/update_collaborative_draft.rb +1 -2
  31. data/app/commands/decidim/proposals/update_proposal.rb +1 -2
  32. data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +5 -1
  33. data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
  34. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +46 -5
  35. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +18 -0
  36. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +41 -85
  37. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +2 -2
  38. data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +2 -4
  39. data/app/controllers/decidim/proposals/invite_coauthors_controller.rb +87 -0
  40. data/app/controllers/decidim/proposals/proposals_controller.rb +7 -32
  41. data/app/controllers/decidim/proposals/versions_controller.rb +1 -1
  42. data/app/events/decidim/proposals/accepted_coauthorship_event.rb +8 -0
  43. data/app/events/decidim/proposals/admin/proposal_assigned_to_valuator_event.rb +27 -0
  44. data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +5 -0
  45. data/app/events/decidim/proposals/coauthor_accepted_invite_event.rb +49 -0
  46. data/app/events/decidim/proposals/coauthor_invited_event.rb +45 -0
  47. data/app/events/decidim/proposals/coauthor_rejected_invite_event.rb +8 -0
  48. data/app/events/decidim/proposals/rejected_coauthorship_event.rb +8 -0
  49. data/app/events/decidim/proposals/update_proposal_taxonomies_event.rb +9 -0
  50. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +16 -0
  51. data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
  52. data/app/forms/decidim/proposals/admin/proposal_form.rb +11 -6
  53. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +0 -5
  54. data/app/forms/decidim/proposals/collaborative_draft_form.rb +0 -8
  55. data/app/forms/decidim/proposals/proposal_form.rb +5 -32
  56. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +25 -0
  57. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
  58. data/app/helpers/decidim/proposals/application_helper.rb +23 -15
  59. data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +7 -7
  60. data/app/helpers/decidim/proposals/map_helper.rb +0 -18
  61. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +15 -2
  62. data/app/helpers/decidim/proposals/proposals_helper.rb +3 -1
  63. data/app/jobs/decidim/proposals/admin/proposal_answer_job.rb +20 -0
  64. data/app/models/decidim/proposals/collaborative_draft.rb +13 -3
  65. data/app/models/decidim/proposals/proposal.rb +71 -5
  66. data/app/models/decidim/proposals/proposal_note.rb +11 -0
  67. data/app/models/decidim/proposals/proposal_state.rb +1 -1
  68. data/app/packs/entrypoints/decidim_proposals.js +1 -0
  69. data/app/packs/entrypoints/decidim_proposals_geocoding.js +2 -0
  70. data/app/packs/src/decidim/proposals/admin/proposals.js +16 -1
  71. data/app/packs/src/decidim/proposals/exit_handler.js +73 -0
  72. data/app/packs/stylesheets/decidim/proposals/proposals.scss +248 -3
  73. data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
  74. data/app/permissions/decidim/proposals/permissions.rb +42 -0
  75. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
  76. data/app/presenters/decidim/proposals/proposal_presenter.rb +1 -1
  77. data/app/queries/decidim/proposals/filtered_proposals.rb +2 -2
  78. data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +2 -2
  79. data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +10 -10
  80. data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +4 -4
  81. data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +6 -6
  82. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +6 -6
  83. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +6 -6
  84. data/app/services/decidim/proposals/proposal_builder.rb +1 -2
  85. data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +3 -3
  86. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note.html.erb +28 -0
  87. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note_reply.html.erb +9 -0
  88. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +4 -28
  89. data/app/views/decidim/proposals/admin/proposal_states/_form.html.erb +1 -1
  90. data/app/views/decidim/proposals/admin/proposals/_actions.html.erb +21 -0
  91. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +3 -2
  92. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +17 -24
  93. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +12 -28
  94. data/app/views/decidim/proposals/admin/proposals/_proposals-thead.html.erb +45 -0
  95. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_apply_answer_template.html.erb +22 -0
  96. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +15 -11
  97. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_taxonomy_change.html.erb +23 -0
  98. data/app/views/decidim/proposals/admin/proposals/index.html.erb +17 -48
  99. data/app/views/decidim/proposals/admin/proposals/manage_trash.html.erb +18 -0
  100. data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +1 -1
  101. data/app/views/decidim/proposals/admin/proposals/show.html.erb +14 -26
  102. data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +1 -1
  103. data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +0 -3
  104. data/app/views/decidim/proposals/collaborative_drafts/_collaborative_actions.html.erb +9 -0
  105. data/app/views/decidim/proposals/collaborative_drafts/_collaborative_draft_aside.html.erb +0 -15
  106. data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +4 -6
  107. data/app/views/decidim/proposals/collaborative_drafts/index.html.erb +6 -2
  108. data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +27 -11
  109. data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +29 -9
  110. data/app/views/decidim/proposals/proposals/_actions.html.erb +4 -7
  111. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +17 -22
  112. data/app/views/decidim/proposals/proposals/_exit_modal.html.erb +17 -0
  113. data/app/views/decidim/proposals/proposals/_notification_alert_box.html.erb +1 -0
  114. data/app/views/decidim/proposals/proposals/_proposal_actions.html.erb +19 -0
  115. data/app/views/decidim/proposals/proposals/_proposal_aside.html.erb +9 -32
  116. data/app/views/decidim/proposals/proposals/_proposal_voting_rules.html.erb +33 -0
  117. data/app/views/decidim/proposals/proposals/_proposals.html.erb +1 -1
  118. data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +2 -2
  119. data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +12 -0
  120. data/app/views/decidim/proposals/proposals/_update_proposal_voting_rules.html.erb +6 -0
  121. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +12 -8
  122. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +2 -1
  123. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +1 -7
  124. data/app/views/decidim/proposals/proposals/index.html.erb +3 -18
  125. data/app/views/decidim/proposals/proposals/index.js.erb +1 -1
  126. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
  127. data/app/views/decidim/proposals/proposals/show.html.erb +35 -15
  128. data/config/locales/ar.yml +15 -73
  129. data/config/locales/bg.yml +12 -89
  130. data/config/locales/bs-BA.yml +2 -13
  131. data/config/locales/ca.yml +209 -84
  132. data/config/locales/cs.yml +210 -81
  133. data/config/locales/de.yml +213 -89
  134. data/config/locales/el.yml +12 -84
  135. data/config/locales/en.yml +206 -81
  136. data/config/locales/es-MX.yml +213 -88
  137. data/config/locales/es-PY.yml +213 -88
  138. data/config/locales/es.yml +214 -89
  139. data/config/locales/eu.yml +288 -164
  140. data/config/locales/fi-plain.yml +213 -87
  141. data/config/locales/fi.yml +213 -87
  142. data/config/locales/fr-CA.yml +116 -97
  143. data/config/locales/fr.yml +116 -97
  144. data/config/locales/ga-IE.yml +1 -21
  145. data/config/locales/gl.yml +8 -45
  146. data/config/locales/hu.yml +12 -68
  147. data/config/locales/id-ID.yml +9 -43
  148. data/config/locales/is-IS.yml +0 -19
  149. data/config/locales/it.yml +11 -77
  150. data/config/locales/ja.yml +165 -106
  151. data/config/locales/lt.yml +13 -85
  152. data/config/locales/lv.yml +10 -52
  153. data/config/locales/nl.yml +10 -59
  154. data/config/locales/no.yml +10 -44
  155. data/config/locales/pl.yml +11 -88
  156. data/config/locales/pt-BR.yml +9 -74
  157. data/config/locales/pt.yml +10 -56
  158. data/config/locales/ro-RO.yml +12 -72
  159. data/config/locales/ru.yml +0 -23
  160. data/config/locales/sk.yml +10 -52
  161. data/config/locales/sr-CS.yml +2 -14
  162. data/config/locales/sv.yml +127 -86
  163. data/config/locales/tr-TR.yml +10 -53
  164. data/config/locales/uk.yml +0 -23
  165. data/config/locales/zh-CN.yml +10 -53
  166. data/config/locales/zh-TW.yml +12 -86
  167. data/db/migrate/20171220084719_add_published_at_to_proposals.rb +1 -1
  168. data/db/migrate/20181016132225_add_organization_as_author.rb +1 -1
  169. data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +1 -1
  170. data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +3 -3
  171. data/db/migrate/20210310102839_add_followable_counter_cache_to_proposals.rb +1 -1
  172. data/db/migrate/20240110203504_create_default_proposal_states.rb +1 -1
  173. data/db/migrate/20240404202756_add_valuation_assignments_count_to_decidim_proposals_proposals.rb +1 -1
  174. data/db/migrate/20240617091140_add_email_on_assigned_proposals_to_users.rb +7 -0
  175. data/db/migrate/20240617170052_add_parent_relation_to_decidim_proposal_notes.rb +7 -0
  176. data/db/migrate/20240828103755_add_deleted_at_to_decidim_proposals_proposals.rb +8 -0
  177. data/decidim-proposals.gemspec +1 -1
  178. data/lib/decidim/api/functions/proposal_finder_helper.rb +12 -0
  179. data/lib/decidim/api/functions/proposal_list_helper.rb +12 -0
  180. data/lib/decidim/api/proposal_type.rb +17 -25
  181. data/lib/decidim/api/proposals_type.rb +4 -19
  182. data/lib/decidim/proposals/admin_engine.rb +12 -3
  183. data/lib/decidim/proposals/admin_filter.rb +3 -6
  184. data/lib/decidim/proposals/component.rb +4 -5
  185. data/lib/decidim/proposals/download_your_data_proposal_serializer.rb +15 -0
  186. data/lib/decidim/proposals/engine.rb +5 -0
  187. data/lib/decidim/proposals/import/proposal_creator.rb +4 -4
  188. data/lib/decidim/proposals/proposal_serializer.rb +12 -29
  189. data/lib/decidim/proposals/seeds.rb +21 -17
  190. data/lib/decidim/proposals/test/factories.rb +2 -1
  191. data/lib/decidim/proposals/version.rb +1 -1
  192. data/lib/decidim/proposals.rb +4 -0
  193. data/lib/tasks/proposals/upgrade/decidim_proposals_upgrade_tasks.rake +0 -22
  194. metadata +65 -34
  195. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +0 -70
  196. data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +0 -75
  197. data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +0 -11
  198. data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +0 -11
  199. data/app/jobs/decidim/proposals/admin/import_proposals_job.rb +0 -91
  200. data/app/mailers/decidim/proposals/admin/import_proposals_mailer.rb +0 -30
  201. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_failure.html.erb +0 -1
  202. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_success.html.erb +0 -2
  203. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +0 -15
  204. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +0 -21
  205. data/app/views/decidim/proposals/collaborative_drafts/_actions.html.erb +0 -7
  206. data/config/locales/ca-IT.yml +0 -945
@@ -6,6 +6,7 @@ module Decidim
6
6
  # A form object to be used when admin users want to answer a proposal.
7
7
  class ProposalAnswerForm < Decidim::Form
8
8
  include TranslatableAttributes
9
+
9
10
  mimic :proposal_answer
10
11
 
11
12
  translatable_attribute :answer, Decidim::Attributes::RichText
@@ -15,9 +16,20 @@ module Decidim
15
16
  attribute :internal_state, String
16
17
 
17
18
  validates :internal_state, presence: true, inclusion: { in: :proposal_states }
19
+ validates :answer, translatable_presence: true, if: ->(form) { form.state == "rejected" }
20
+
21
+ with_options if: :costs_required? do
22
+ validates :cost, numericality: true, presence: true
23
+ validates :cost_report, translatable_presence: true
24
+ validates :execution_period, translatable_presence: true
25
+ end
18
26
 
19
27
  alias state internal_state
20
28
 
29
+ def costs_required?
30
+ costs_enabled? && state == "accepted"
31
+ end
32
+
21
33
  def publish_answer?
22
34
  current_component.current_settings.publish_answers_immediately?
23
35
  end
@@ -27,6 +39,10 @@ module Decidim
27
39
  def proposal_states
28
40
  Decidim::Proposals::ProposalState.where(component: current_component).pluck(:token).map(&:to_s) + ["not_answered"]
29
41
  end
42
+
43
+ def costs_enabled?
44
+ current_component.current_settings.answers_with_costs?
45
+ end
30
46
  end
31
47
  end
32
48
  end
@@ -8,14 +8,13 @@ module Decidim
8
8
  include Decidim::TranslatableAttributes
9
9
  include Decidim::AttachmentAttributes
10
10
  include Decidim::ApplicationHelper
11
+ include Decidim::HasTaxonomyFormAttributes
11
12
 
12
13
  mimic :proposal
13
14
 
14
15
  attribute :address, String
15
16
  attribute :latitude, Float
16
17
  attribute :longitude, Float
17
- attribute :category_id, Integer
18
- attribute :scope_id, Integer
19
18
  attribute :attachment, AttachmentForm
20
19
  attribute :position, Integer
21
20
  attribute :created_in_meeting, Boolean
@@ -25,46 +24,19 @@ module Decidim
25
24
  attachments_attribute :photos
26
25
 
27
26
  validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
28
- validates :category, presence: true, if: ->(form) { form.category_id.present? }
29
- validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
30
- validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
31
27
  validates :meeting_as_author, presence: true, if: ->(form) { form.created_in_meeting? }
32
28
 
33
29
  validate :notify_missing_attachment_if_errored
34
30
 
35
- delegate :categories, to: :current_component
36
-
37
31
  def map_model(model)
38
32
  body = translated_attribute(model.body)
39
33
  @suggested_hashtags = Decidim::ContentRenderers::HashtagRenderer.new(body).extra_hashtags.map(&:name).map(&:downcase)
40
-
41
- return unless model.categorization
42
-
43
- self.category_id = model.categorization.decidim_category_id
44
- self.scope_id = model.decidim_scope_id
45
34
  end
46
35
 
47
36
  alias component current_component
48
37
 
49
- # Finds the Category from the category_id.
50
- #
51
- # Returns a Decidim::Category
52
- def category
53
- @category ||= categories.find_by(id: category_id)
54
- end
55
-
56
- # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
57
- #
58
- # Returns a Decidim::Scope
59
- def scope
60
- @scope ||= @attributes["scope_id"].value ? current_component.scopes.find_by(id: @attributes["scope_id"].value) : current_component.scope
61
- end
62
-
63
- # Scope identifier
64
- #
65
- # Returns the scope identifier related to the proposal
66
- def scope_id
67
- super || scope&.id
38
+ def participatory_space_manifest
39
+ @participatory_space_manifest ||= current_component.participatory_space.manifest.name
68
40
  end
69
41
 
70
42
  def geocoding_enabled?
@@ -6,27 +6,32 @@ module Decidim
6
6
  # A form object to be used when admin users want to create a proposal.
7
7
  class ProposalForm < Decidim::Proposals::Admin::ProposalBaseForm
8
8
  include Decidim::HasUploadValidations
9
+ include Decidim::AttachmentAttributes
9
10
 
10
11
  translatable_attribute :title, String do |field, _locale|
11
12
  validates field, length: { in: 15..150 }, if: proc { |resource| resource.send(field).present? }
12
13
  end
13
14
  translatable_attribute :body, Decidim::Attributes::RichText
15
+ attribute :attachment, AttachmentForm
16
+
17
+ attachments_attribute :documents
14
18
 
15
19
  validates :title, :body, translatable_presence: true
20
+ validates :title, :body, translated_etiquette: true
16
21
 
17
22
  validate :notify_missing_attachment_if_errored
18
23
 
19
24
  def map_model(model)
20
- super(model)
25
+ super
21
26
  presenter = ProposalPresenter.new(model)
22
27
 
23
28
  self.title = presenter.title(all_locales: title.is_a?(Hash))
24
29
  self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
25
- self.attachment = if model.documents.first.present?
26
- { file: model.documents.first.file, title: translated_attribute(model.documents.first.title) }
27
- else
28
- {}
29
- end
30
+ self.documents = model.attachments
31
+ end
32
+
33
+ def notify_missing_attachment_if_errored
34
+ errors.add(:add_documents, :needs_to_be_reattached) if errors.any? && add_documents.present?
30
35
  end
31
36
  end
32
37
  end
@@ -14,7 +14,6 @@ module Decidim
14
14
  attribute :keep_answers, Boolean
15
15
  attribute :keep_authors, Boolean
16
16
  attribute :states, Array
17
- attribute :scope_ids, Array
18
17
 
19
18
  validates :origin_component_id, :origin_component, :states, :current_component, presence: true
20
19
  validates :import_proposals, allow_nil: false, acceptance: true
@@ -31,10 +30,6 @@ module Decidim
31
30
  super.compact_blank
32
31
  end
33
32
 
34
- def scopes
35
- Decidim::Scope.where(organization: current_organization, id: scope_ids)
36
- end
37
-
38
33
  def origin_component
39
34
  @origin_component ||= origin_components.find_by(id: origin_component_id)
40
35
  end
@@ -4,14 +4,6 @@ module Decidim
4
4
  module Proposals
5
5
  # A form object to be used when public users want to create a Collaborative Draft.
6
6
  class CollaborativeDraftForm < Decidim::Proposals::ProposalForm
7
- def map_model(model)
8
- super
9
-
10
- return unless model.categorization
11
-
12
- self.category_id = model.categorization.decidim_category_id
13
- end
14
-
15
7
  def user_group
16
8
  @user_group ||= Decidim::UserGroup.find user_group_id if user_group_id.present?
17
9
  end
@@ -7,6 +7,7 @@ module Decidim
7
7
  include Decidim::TranslatableAttributes
8
8
  include Decidim::AttachmentAttributes
9
9
  include Decidim::HasUploadValidations
10
+ include Decidim::HasTaxonomyFormAttributes
10
11
 
11
12
  mimic :proposal
12
13
 
@@ -17,29 +18,24 @@ module Decidim
17
18
  attribute :address, String
18
19
  attribute :latitude, Float
19
20
  attribute :longitude, Float
20
- attribute :category_id, Integer
21
- attribute :scope_id, Integer
22
21
  attribute :attachment, AttachmentForm
23
22
  attribute :suggested_hashtags, Array[String]
24
23
 
25
24
  attachments_attribute :documents
26
25
 
27
- validates :title, :body, presence: true, etiquette: true
26
+ validates :title, :body, presence: true
27
+ validates :title, :body, etiquette: true
28
28
  validates :title, length: { in: 15..150 }
29
29
  validates :body, proposal_length: {
30
30
  minimum: 15,
31
31
  maximum: ->(record) { record.component.settings.proposal_length }
32
32
  }
33
33
  validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
34
- validates :category, presence: true, if: ->(form) { form.category_id.present? }
35
- validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
36
- validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
37
34
 
38
35
  validate :body_is_not_bare_template
39
36
  validate :notify_missing_attachment_if_errored
40
37
 
41
38
  alias component current_component
42
- delegate :categories, to: :current_component
43
39
 
44
40
  def map_model(model)
45
41
  self.title = translated_attribute(model.title)
@@ -50,34 +46,11 @@ module Decidim
50
46
  self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
51
47
 
52
48
  self.user_group_id = model.user_groups.first&.id
53
- self.category_id = model.categorization.decidim_category_id if model.categorization
54
-
55
- # The scope attribute is with different key (decidim_scope_id), so it
56
- # has to be manually mapped.
57
- self.scope_id = model.scope.id if model.scope
58
-
59
49
  self.documents = model.attachments
60
50
  end
61
51
 
62
- # Finds the Category from the category_id.
63
- #
64
- # Returns a Decidim::Category
65
- def category
66
- @category ||= categories.find_by(id: category_id)
67
- end
68
-
69
- # Finds the Scope from the given scope_id, uses participatory space scope if missing.
70
- #
71
- # Returns a Decidim::Scope
72
- def scope
73
- @scope ||= @attributes["scope_id"].value ? current_component.scopes.find_by(id: @attributes["scope_id"].value) : current_component.scope
74
- end
75
-
76
- # Scope identifier
77
- #
78
- # Returns the scope identifier related to the proposal
79
- def scope_id
80
- super || scope&.id
52
+ def participatory_space_manifest
53
+ @participatory_space_manifest ||= current_component.participatory_space.manifest.name
81
54
  end
82
55
 
83
56
  def geocoding_enabled?
@@ -8,6 +8,31 @@ module Decidim
8
8
  Decidim::Proposals::Proposal.find(id)
9
9
  end
10
10
 
11
+ # Public: Generates a select field with the templates of the given component.
12
+ #
13
+ # component - A component instance.
14
+ # prompt - An i18n string to show as prompt
15
+ #
16
+ # Returns a String.
17
+ def bulk_templates_select(component, prompt, id: nil)
18
+ options_for_select = find_templates_for_select(component)
19
+ select(:template, :template_id, options_for_select, prompt:, id:)
20
+ end
21
+
22
+ def find_templates_for_select(component)
23
+ return [] unless Decidim.module_installed? :templates
24
+ return @templates_for_select if @templates_for_select
25
+
26
+ templates = Decidim::Templates::Template.where(
27
+ target: :proposal_answer,
28
+ templatable: component
29
+ ).order(:templatable_id)
30
+
31
+ @templates_for_select = templates.map do |template|
32
+ [translated_attribute(template.name), template.id]
33
+ end
34
+ end
35
+
11
36
  # find the valuators for the current space.
12
37
  def find_valuators_for_select(participatory_space, current_user)
13
38
  valuator_roles = participatory_space.user_roles(:valuator).order_by_name
@@ -7,7 +7,6 @@ module Decidim
7
7
  # in order to use them in select forms for Proposals.
8
8
  #
9
9
  module ProposalsHelper
10
- include Decidim::Admin::ResourceScopeHelper
11
10
  include Decidim::TranslatableAttributes
12
11
 
13
12
  def available_states
@@ -30,7 +30,6 @@ module Decidim
30
30
 
31
31
  def proposal_state_css_style(proposal)
32
32
  return "" if proposal.emendation?
33
- return "" if proposal.withdrawn?
34
33
 
35
34
  proposal.proposal_state&.css_style
36
35
  end
@@ -145,8 +144,16 @@ module Decidim
145
144
  ).count
146
145
  end
147
146
 
147
+ def layout_item_classes
148
+ if show_voting_rules?
149
+ "layout-item lg:pt-4"
150
+ else
151
+ "layout-item"
152
+ end
153
+ end
154
+
148
155
  def show_voting_rules?
149
- return false if !votes_enabled? || current_settings.votes_blocked?
156
+ return false if !votes_enabled? || votes_blocked?
150
157
 
151
158
  return true if vote_limit_enabled?
152
159
  return true if threshold_per_proposal_enabled?
@@ -207,41 +214,38 @@ module Decidim
207
214
  end
208
215
 
209
216
  # rubocop:disable Metrics/CyclomaticComplexity
210
- # rubocop:disable Metrics/PerceivedComplexity
211
217
  def filter_sections
212
218
  @filter_sections ||= begin
213
219
  items = []
214
220
  if component_settings.proposal_answering_enabled && current_settings.proposal_answering_enabled
215
- items.append(method: :with_any_state, collection: filter_proposals_state_values, label_scope: "decidim.proposals.proposals.filters", id: "state")
221
+ items.append(method: :with_any_state, collection: filter_proposals_state_values, label: t("decidim.proposals.proposals.filters.state"), id: "state")
216
222
  end
217
- if current_component.has_subscopes?
218
- items.append(method: :with_any_scope, collection: filter_scopes_values, label_scope: "decidim.proposals.proposals.filters", id: "scope")
219
- end
220
- if current_component.categories.any?
221
- items.append(method: :with_any_category, collection: filter_categories_values, label_scope: "decidim.proposals.proposals.filters", id: "category")
223
+ current_component.available_taxonomy_filters.each do |taxonomy_filter|
224
+ items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
225
+ collection: filter_taxonomy_values_for(taxonomy_filter),
226
+ label: decidim_sanitize_translated(taxonomy_filter.name),
227
+ id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
222
228
  end
223
229
  if component_settings.official_proposals_enabled
224
- items.append(method: :with_any_origin, collection: filter_origin_values, label_scope: "decidim.proposals.proposals.filters", id: "origin")
230
+ items.append(method: :with_any_origin, collection: filter_origin_values, label: t("decidim.proposals.proposals.filters.origin"), id: "origin")
225
231
  end
226
232
  if current_user
227
- items.append(method: :activity, collection: activity_filter_values, label_scope: "decidim.proposals.proposals.filters", id: "activity", type: :radio_buttons)
233
+ items.append(method: :activity, collection: activity_filter_values, label: t("decidim.proposals.proposals.filters.activity"), id: "activity", type: :radio_buttons)
228
234
  end
229
235
  if @proposals.only_emendations.any?
230
- items.append(method: :type, collection: filter_type_values, label_scope: "decidim.proposals.proposals.filters", id: "amendment_type", type: :radio_buttons)
236
+ items.append(method: :type, collection: filter_type_values, label: t("decidim.proposals.proposals.filters.amendment_type"), id: "amendment_type", type: :radio_buttons)
231
237
  end
232
238
  if linked_classes_for(Decidim::Proposals::Proposal).any?
233
239
  items.append(
234
240
  method: :related_to,
235
241
  collection: linked_classes_filter_values_for(Decidim::Proposals::Proposal),
236
- label_scope: "decidim.proposals.proposals.filters",
242
+ label: t("decidim.proposals.proposals.filters.related_to"),
237
243
  id: "related_to",
238
244
  type: :radio_buttons
239
245
  )
240
246
  end
241
247
  end
242
- # rubocop:enable Metrics/PerceivedComplexity
243
248
  # rubocop:enable Metrics/CyclomaticComplexity
244
-
245
249
  items.reject { |item| item[:collection].blank? }
246
250
  end
247
251
 
@@ -249,6 +253,10 @@ module Decidim
249
253
  i18n_key = controller_name == "collaborative_drafts" ? "decidim.proposals.collaborative_drafts.name" : "decidim.components.proposals.name"
250
254
  (defined?(current_component) && translated_attribute(current_component&.name).presence) || t(i18n_key)
251
255
  end
256
+
257
+ def templates_available?
258
+ Decidim.module_installed?(:templates) && defined?(Decidim::Templates::Template) && Decidim::Templates::Template.exists?(templatable: current_component)
259
+ end
252
260
  end
253
261
  end
254
262
  end
@@ -43,20 +43,20 @@ module Decidim
43
43
  items = [{
44
44
  method: :with_any_state,
45
45
  collection: filter_collaborative_drafts_state_values,
46
- label_scope: "decidim.proposals.collaborative_drafts.filters",
46
+ label: t("decidim.proposals.collaborative_drafts.filters.state"),
47
47
  id: "state"
48
48
  }]
49
- if current_component.has_subscopes?
50
- items.append(method: :with_any_scope, collection: filter_scopes_values, label_scope: "decidim.proposals.collaborative_drafts.filters", id: "scope")
51
- end
52
- if current_component.categories.any?
53
- items.append(method: :with_any_category, collection: filter_categories_values, label_scope: "decidim.proposals.collaborative_drafts.filters", id: "category")
49
+ current_component.available_taxonomy_filters.each do |taxonomy_filter|
50
+ items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
51
+ collection: filter_taxonomy_values_for(taxonomy_filter),
52
+ label: decidim_sanitize_translated(taxonomy_filter.name),
53
+ id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
54
54
  end
55
55
  if linked_classes_for(Decidim::Proposals::CollaborativeDraft).any?
56
56
  items.append(
57
57
  method: :related_to,
58
58
  collection: linked_classes_filter_values_for(Decidim::Proposals::CollaborativeDraft),
59
- label_scope: "decidim.proposals.collaborative_drafts.filters",
59
+ label: t("decidim.proposals.collaborative_drafts.filters.related_to"),
60
60
  id: "related_to",
61
61
  type: :radio_buttons
62
62
  )
@@ -5,24 +5,6 @@ module Decidim
5
5
  # This helper include some methods for rendering proposals dynamic maps.
6
6
  module MapHelper
7
7
  include Decidim::ApplicationHelper
8
- # Serialize a collection of geocoded proposals to be used by the dynamic map component
9
- #
10
- # geocoded_proposals - A collection of geocoded proposals
11
- def proposals_data_for_map(geocoded_proposals)
12
- geocoded_proposals.select(&:geocoded_and_valid?).map do |proposal|
13
- proposal_data_for_map(proposal)
14
- end
15
- end
16
-
17
- def proposal_data_for_map(proposal)
18
- proposal
19
- .slice(:latitude, :longitude, :address)
20
- .merge(
21
- title: decidim_html_escape(present(proposal).title),
22
- link: proposal_path(proposal),
23
- items: cell("decidim/proposals/proposal_metadata", proposal).send(:proposal_items_for_map).to_json
24
- )
25
- end
26
8
 
27
9
  def proposal_preview_data_for_map(proposal)
28
10
  {
@@ -47,14 +47,14 @@ module Decidim
47
47
  #
48
48
  # Returns true if enabled, false otherwise.
49
49
  def votes_enabled?
50
- current_settings.votes_enabled
50
+ current_settings.respond_to?(:votes_enabled) && current_settings.votes_enabled
51
51
  end
52
52
 
53
53
  # Public: Checks if voting is blocked in this step.
54
54
  #
55
55
  # Returns true if blocked, false otherwise.
56
56
  def votes_blocked?
57
- current_settings.votes_blocked
57
+ current_settings.respond_to?(:votes_blocked) && current_settings.votes_blocked
58
58
  end
59
59
 
60
60
  # Public: Checks if the current user is allowed to vote in this step.
@@ -76,6 +76,19 @@ module Decidim
76
76
  votes_count = ProposalVote.where(author: user, proposal: proposals).size
77
77
  component_settings.vote_limit - votes_count
78
78
  end
79
+
80
+ # Return the remaining minimum votes for a user if the current component has a vote limit
81
+ #
82
+ # user - A User object
83
+ #
84
+ # Returns a number with the remaining minimum votes for that user
85
+ def remaining_minimum_votes_count_for(user)
86
+ return 0 unless vote_limit_enabled?
87
+
88
+ votes_count = Decidim::Proposals::ProposalVote.joins(:proposal).where(decidim_proposals_proposals: { decidim_component_id: current_component.id }).where(author: user).count
89
+
90
+ component_settings.minimum_votes_per_user - votes_count
91
+ end
79
92
  end
80
93
  end
81
94
  end
@@ -12,7 +12,9 @@ module Decidim
12
12
  end
13
13
 
14
14
  def proposal_has_costs?
15
- @proposal.cost.present?
15
+ @proposal.cost.present? &&
16
+ translated_attribute(@proposal.cost_report).present? &&
17
+ translated_attribute(@proposal.execution_period).present?
16
18
  end
17
19
 
18
20
  def toggle_view_mode_link(current_mode, target_mode, title, params)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ class ProposalAnswerJob < ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(proposal, attributes, context)
10
+ answer_form = ProposalAnswerForm.from_params(attributes).with_context(**context)
11
+
12
+ Admin::AnswerProposal.call(answer_form, proposal) do
13
+ on(:ok) { Rails.logger.info "Proposal #{proposal.id} answered successfully." }
14
+ on(:invalid) { Rails.logger.error "Proposal ID #{proposal.id} could not be updated. Errors: #{answer_form.errors.full_messages}" }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -5,6 +5,7 @@ module Decidim
5
5
  class CollaborativeDraft < Proposals::ApplicationRecord
6
6
  include Decidim::Resourceable
7
7
  include Decidim::Coauthorable
8
+ include Decidim::Taxonomizable
8
9
  include Decidim::HasComponent
9
10
  include Decidim::ScopableResource
10
11
  include Decidim::HasReference
@@ -45,8 +46,9 @@ module Decidim
45
46
  authored_by?(user)
46
47
  end
47
48
 
48
- def presenter
49
- Decidim::Proposals::CollaborativeDraftPresenter.new(self)
49
+ # Public: Overrides the `reported_content_url` Reportable concern method.
50
+ def reported_content_url
51
+ ResourceLocatorPresenter.new(self).url
50
52
  end
51
53
 
52
54
  # Public: Overrides the `reported_attributes` Reportable concern method.
@@ -63,7 +65,15 @@ module Decidim
63
65
  ransacker_text_multi :search_text, [:title, :body]
64
66
 
65
67
  def self.ransackable_scopes(_auth_object = nil)
66
- [:with_any_state, :related_to, :with_any_scope, :with_any_category]
68
+ [:with_any_state, :related_to, :with_any_taxonomies]
69
+ end
70
+
71
+ def self.ransackable_attributes(_auth_object = nil)
72
+ %w(id_string search_text title body)
73
+ end
74
+
75
+ def self.ransackable_associations(_auth_object = nil)
76
+ %w(taxonomies)
67
77
  end
68
78
  end
69
79
  end