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
@@ -22,8 +22,7 @@ module Decidim
22
22
  def call
23
23
  return broadcast(:invalid) unless form.valid?
24
24
 
25
- import_proposals
26
- broadcast(:ok)
25
+ broadcast(:ok, import_proposals)
27
26
  end
28
27
 
29
28
  private
@@ -31,11 +30,65 @@ module Decidim
31
30
  attr_reader :form
32
31
 
33
32
  def import_proposals
34
- ImportProposalsJob.perform_later(form.as_json.merge({
35
- "current_user_id" => form.current_user.id,
36
- "current_organization_id" => form.current_organization.id,
37
- "current_component_id" => form.current_component.id
38
- }))
33
+ proposals.map do |original_proposal|
34
+ next if proposal_already_copied?(original_proposal, target_component)
35
+
36
+ Decidim::Proposals::ProposalBuilder.copy(
37
+ original_proposal,
38
+ author: proposal_author,
39
+ action_user: form.current_user,
40
+ extra_attributes: {
41
+ "component" => target_component
42
+ }.merge(proposal_answer_attributes(original_proposal))
43
+ )
44
+ end.compact
45
+ end
46
+
47
+ def proposals
48
+ @proposals = Decidim::Proposals::Proposal
49
+ .where(component: origin_component)
50
+
51
+ @proposals = if @form.states.include?("not_answered")
52
+ @proposals.not_answered.or(@proposals.where(id: @proposals.only_status(@form.states).pluck(:id)))
53
+ else
54
+ @proposals.only_status(@form.states)
55
+ end
56
+
57
+ @proposals
58
+ end
59
+
60
+ def origin_component
61
+ @form.origin_component
62
+ end
63
+
64
+ def target_component
65
+ @form.current_component
66
+ end
67
+
68
+ def proposal_already_copied?(original_proposal, target_component)
69
+ # Note: we are including also proposals from unpublished components
70
+ # because otherwise duplicates could be created until the component is
71
+ # published.
72
+ original_proposal.linked_resources(:proposals, "copied_from_component", component_published: false).any? do |proposal|
73
+ proposal.component == target_component
74
+ end
75
+ end
76
+
77
+ def proposal_author
78
+ form.keep_authors ? nil : @form.current_organization
79
+ end
80
+
81
+ def proposal_answer_attributes(original_proposal)
82
+ return {} unless form.keep_answers
83
+
84
+ state = Decidim::Proposals::ProposalState.where(component: target_component, token: original_proposal.state).first
85
+
86
+ {
87
+ answer: original_proposal.answer,
88
+ answered_at: original_proposal.answered_at,
89
+ proposal_state: state,
90
+ state_published_at: original_proposal.state_published_at
91
+ }
39
92
  end
40
93
  end
41
94
  end
@@ -32,7 +32,7 @@ module Decidim
32
32
  def merge_proposals
33
33
  transaction do
34
34
  merged_proposal = create_new_proposal
35
- merged_proposal.link_resources(proposals_to_link, "copied_from_component")
35
+ merged_proposal.link_resources(proposals_to_link, "merged_from_component")
36
36
  form.proposals.each(&:destroy!) if form.same_component?
37
37
  merged_proposal
38
38
  end
@@ -46,7 +46,7 @@ module Decidim
46
46
 
47
47
  def previous_links
48
48
  @previous_links ||= form.proposals.flat_map do |proposal|
49
- proposal.linked_resources(:proposals, "copied_from_component")
49
+ proposal.linked_resources(:proposals, "merged_from_component")
50
50
  end
51
51
  end
52
52
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A module with common methods of proposal notes commands
7
+ module ProposalNotesMethods
8
+ private
9
+
10
+ def parsed_body
11
+ @parsed_body ||= Decidim::ContentProcessor.parse_with_processor(:user, form.body, current_organization: form.current_organization)
12
+ end
13
+
14
+ def mentioned_users
15
+ @mentioned_users ||= parsed_body.metadata[:user].users
16
+ end
17
+
18
+ def rewritten_body
19
+ @rewritten_body ||= parsed_body.rewrite
20
+ end
21
+
22
+ def proposal_valuators
23
+ @proposal_valuators ||= Decidim::Proposals::ValuationAssignment.where(proposal:).filter_map do |assignment|
24
+ assignment.valuator unless assignment.valuator == form.current_user
25
+ end
26
+ end
27
+
28
+ def admins
29
+ @admins ||= Decidim::User.org_admins_except_me(form.current_user).all
30
+ end
31
+
32
+ def space_admins
33
+ @space_admins ||= proposal.participatory_space.user_roles("admin").includes(:user).filter_map do |role|
34
+ role.user unless role.user == form.current_user
35
+ end
36
+ end
37
+
38
+ def mentioned_admins_or_valuators
39
+ mentioned_users.select do |user|
40
+ admins.exists?(user.id) ||
41
+ space_admins.include?(user) ||
42
+ proposal_valuators.include?(user)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A command with all the business logic when an admin creates a private note proposal reply.
7
+ class ReplyProposalNote < Decidim::Command
8
+ include ProposalNotesMethods
9
+
10
+ # Public: Initializes the command.
11
+ #
12
+ # form - A form object with the params.
13
+ # parent - the note to reply.
14
+ def initialize(form, parent)
15
+ @form = form
16
+ @parent = parent
17
+ @proposal = parent.proposal
18
+ end
19
+
20
+ # Executes the command. Broadcasts these events:
21
+ #
22
+ # - :ok when everything is valid, together with the note proposal.
23
+ # - :invalid if the form was not valid and we could not proceed.
24
+ #
25
+ # Returns nothing.
26
+ def call
27
+ return broadcast(:invalid) if form.invalid? || invalid_parent?
28
+
29
+ create_proposal_note_reply
30
+ notify_parent_author
31
+ notify_mentioned
32
+
33
+ broadcast(:ok, proposal_note)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :form, :proposal_note, :parent, :proposal
39
+
40
+ def invalid_parent?
41
+ parent.blank? || parent.reply?
42
+ end
43
+
44
+ def create_proposal_note_reply
45
+ @proposal_note = Decidim.traceability.create!(
46
+ ProposalNote,
47
+ form.current_user,
48
+ {
49
+ body: rewritten_body,
50
+ proposal:,
51
+ parent:,
52
+ author: form.current_user
53
+ },
54
+ resource: {
55
+ title: proposal.title
56
+ }
57
+ )
58
+ end
59
+
60
+ def parent_author
61
+ @parent_author ||= parent.author
62
+ end
63
+
64
+ def notify_parent_author
65
+ return if form.current_user == parent_author
66
+
67
+ Decidim::EventsManager.publish(
68
+ event: "decidim.events.proposals.admin.proposal_note_replied",
69
+ event_class: Decidim::Proposals::Admin::ProposalNoteCreatedEvent,
70
+ resource: proposal,
71
+ affected_users: [parent_author],
72
+ extra: { note_author_id: form.current_user.id }
73
+ )
74
+ end
75
+
76
+ def notify_mentioned
77
+ affected_users = mentioned_admins_or_valuators - [parent_author]
78
+
79
+ return if affected_users.blank?
80
+
81
+ Decidim::EventsManager.publish(
82
+ event: "decidim.events.proposals.admin.proposal_note_mentioned",
83
+ event_class: Decidim::Proposals::Admin::ProposalNoteCreatedEvent,
84
+ resource: proposal,
85
+ affected_users:,
86
+ extra: { note_author_id: form.current_user.id }
87
+ )
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -53,13 +53,13 @@ module Decidim
53
53
  )
54
54
 
55
55
  proposals_to_link = links_for(original_proposal)
56
- split_proposal.link_resources(proposals_to_link, "copied_from_component")
56
+ split_proposal.link_resources(proposals_to_link, "splitted_from_component")
57
57
  end
58
58
 
59
59
  def links_for(proposal)
60
60
  return proposal unless form.same_component?
61
61
 
62
- proposal.linked_resources(:proposals, "copied_from_component")
62
+ proposal.linked_resources(:proposals, "splitted_from_component")
63
63
  end
64
64
  end
65
65
  end
@@ -5,8 +5,7 @@ module Decidim
5
5
  module Admin
6
6
  # A command with all the business logic when a user updates a proposal.
7
7
  class UpdateProposal < Decidim::Command
8
- include ::Decidim::AttachmentMethods
9
- include GalleryMethods
8
+ include ::Decidim::MultipleAttachmentsMethods
10
9
  include HashtagsMethods
11
10
 
12
11
  # Public: Initializes the command.
@@ -31,23 +30,15 @@ module Decidim
31
30
  delete_attachment(form.attachment) if delete_attachment?
32
31
 
33
32
  if process_attachments?
34
- @proposal.attachments.destroy_all
35
-
36
- build_attachment
37
- return broadcast(:invalid) if attachment_invalid?
38
- end
39
-
40
- if process_gallery?
41
- build_gallery
42
- return broadcast(:invalid) if gallery_invalid?
33
+ build_attachments
34
+ return broadcast(:invalid) if attachments_invalid?
43
35
  end
44
36
 
45
37
  transaction do
46
38
  update_proposal
47
39
  update_proposal_author
48
- create_gallery if process_gallery?
49
- create_attachment(weight: first_attachment_weight) if process_attachments?
50
- photo_cleanup!
40
+ document_cleanup!(include_all_attachments: true)
41
+ create_attachments(first_weight: first_attachment_weight) if process_attachments?
51
42
  end
52
43
 
53
44
  broadcast(:ok, proposal)
@@ -69,8 +60,7 @@ module Decidim
69
60
  form.current_user,
70
61
  title: parsed_title,
71
62
  body: parsed_body,
72
- category: form.category,
73
- scope: form.scope,
63
+ taxonomizations: form.taxonomizations,
74
64
  address: form.address,
75
65
  latitude: form.latitude,
76
66
  longitude: form.longitude,
@@ -90,6 +80,10 @@ module Decidim
90
80
 
91
81
  proposal.photos.count
92
82
  end
83
+
84
+ def delete_attachment?
85
+ @form.attachment&.delete_file.present?
86
+ end
93
87
  end
94
88
  end
95
89
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A command with all the business logic when an admin batch updates proposals taxonomies.
7
+ class UpdateProposalTaxonomies < UpdateResourcesTaxonomies
8
+ include TranslatableAttributes
9
+ # Public: Initializes the command.
10
+ #
11
+ # taxonomy_ids - the taxonomy ids to update
12
+ # proposal_ids - the proposals ids to update.
13
+ def initialize(taxonomy_ids, proposal_ids, organization)
14
+ super(taxonomy_ids, Decidim::Proposals::Proposal.where(id: proposal_ids), organization)
15
+ end
16
+
17
+ def run_after_hooks(resource)
18
+ notify_author(resource) if resource.coauthorships.any?
19
+ end
20
+
21
+ private
22
+
23
+ def notify_author(proposal)
24
+ Decidim::EventsManager.publish(
25
+ event: "decidim.events.proposals.proposal_update_taxonomies",
26
+ event_class: Decidim::Proposals::UpdateProposalTaxonomiesEvent,
27
+ resource: proposal,
28
+ affected_users: proposal.notifiable_identities
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A command with all the business logic when a user cancels an invitation to be a coauthor.
6
+ class CancelCoauthorship < Decidim::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # proposal - The proposal to add a coauthor to.
10
+ # coauthor - The user to invite as coauthor.
11
+ def initialize(proposal, coauthor)
12
+ @proposal = proposal
13
+ @coauthor = coauthor
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid, together with the proposal.
19
+ # - :invalid if the coauthor is not valid.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) unless @coauthor
24
+ return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
25
+
26
+ @proposal.coauthor_invitations_for(@coauthor).destroy_all
27
+
28
+ broadcast(:ok)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -63,8 +63,7 @@ module Decidim
63
63
  draft = CollaborativeDraft.new(
64
64
  title: title_with_hashtags,
65
65
  body: body_with_hashtags,
66
- category: form.category,
67
- scope: form.scope,
66
+ taxonomizations: form.taxonomizations,
68
67
  component: form.component,
69
68
  address: form.address,
70
69
  latitude: form.latitude,
@@ -82,8 +82,7 @@ module Decidim
82
82
  component: form.component
83
83
  )
84
84
 
85
- proposal.category = form.category if form.category_id.present?
86
- proposal.scope = form.scope if form.scope_id.present?
85
+ proposal.taxonomizations = form.taxonomizations if form.taxonomizations.present?
87
86
  proposal.documents = form.documents if form.documents.present?
88
87
  proposal.address = form.address if form.has_address? && !form.geocoded?
89
88
  proposal.add_coauthor(@current_user, user_group:)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A command with all the business logic when a user invites a coauthor to a proposal
6
+ class InviteCoauthor < Decidim::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # proposal - The proposal to add a coauthor to.
10
+ # coauthor - The user to invite as coauthor.
11
+ def initialize(proposal, coauthor)
12
+ @proposal = proposal
13
+ @coauthor = coauthor
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid, together with the proposal.
19
+ # - :invalid if the coauthor is not valid.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) unless @coauthor
24
+ return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
25
+
26
+ transaction do
27
+ generate_notifications
28
+ end
29
+
30
+ broadcast(:ok)
31
+ end
32
+
33
+ private
34
+
35
+ def generate_notifications
36
+ Decidim::EventsManager.publish(
37
+ event: "decidim.events.proposals.coauthor_invited",
38
+ event_class: Decidim::Proposals::CoauthorInvitedEvent,
39
+ resource: @proposal,
40
+ affected_users: [@coauthor]
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -62,7 +62,6 @@ module Decidim
62
62
  fields[:title] = { I18n.locale => parsed_title }
63
63
  fields[:body] = { I18n.locale => parsed_body }
64
64
  fields[:component] = @collaborative_draft.component
65
- fields[:scope] = @collaborative_draft.scope
66
65
  fields[:address] = @collaborative_draft.address
67
66
  fields[:published_at] = Time.current
68
67
 
@@ -78,7 +77,7 @@ module Decidim
78
77
  ) do
79
78
  new_proposal = Proposal.new(proposal_attributes)
80
79
  new_proposal.coauthorships = @collaborative_draft.coauthorships
81
- new_proposal.category = @collaborative_draft.category
80
+ new_proposal.taxonomies = @collaborative_draft.taxonomies
82
81
  new_proposal.attachments = @collaborative_draft.attachments
83
82
  new_proposal.save!
84
83
  new_proposal
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A command with all the business logic when a user rejects and invitation to be a proposal co-author
6
+ class RejectCoauthorship < Decidim::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # proposal - The proposal to add a coauthor to.
10
+ # coauthor - The user to invite as coauthor.
11
+ def initialize(proposal, coauthor)
12
+ @proposal = proposal
13
+ @coauthor = coauthor
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid, together with the proposal.
19
+ # - :invalid if the coauthor is not valid.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) unless @coauthor
24
+ return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
25
+
26
+ @proposal.coauthor_invitations_for(@coauthor).destroy_all
27
+ generate_notifications
28
+
29
+ broadcast(:ok)
30
+ end
31
+
32
+ private
33
+
34
+ def generate_notifications
35
+ # notify the author that the co-author has rejected the invitation
36
+ Decidim::EventsManager.publish(
37
+ event: "decidim.events.proposals.coauthor_rejected_invite",
38
+ event_class: Decidim::Proposals::CoauthorRejectedInviteEvent,
39
+ resource: @proposal,
40
+ affected_users: @proposal.authors,
41
+ extra: { coauthor_id: @coauthor.id }
42
+ )
43
+
44
+ # notify the co-author of his own decision
45
+ Decidim::EventsManager.publish(
46
+ event: "decidim.events.proposals.rejected_coauthorship",
47
+ event_class: Decidim::Proposals::RejectedCoauthorshipEvent,
48
+ resource: @proposal,
49
+ affected_users: [@coauthor]
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -61,8 +61,7 @@ module Decidim
61
61
  {
62
62
  title: title_with_hashtags,
63
63
  body: body_with_hashtags,
64
- category: form.category,
65
- scope: form.scope,
64
+ taxonomizations: form.taxonomizations,
66
65
  address: form.address,
67
66
  latitude: form.latitude,
68
67
  longitude: form.longitude
@@ -98,8 +98,7 @@ module Decidim
98
98
  body: {
99
99
  I18n.locale => body_with_hashtags
100
100
  },
101
- category: form.category,
102
- scope: form.scope,
101
+ taxonomizations: form.taxonomizations,
103
102
  address: form.address,
104
103
  latitude: form.latitude,
105
104
  longitude: form.longitude
@@ -19,7 +19,11 @@ module Decidim
19
19
  def base_query
20
20
  return collection.order(:position) if current_component.settings.participatory_texts_enabled?
21
21
 
22
- accessible_proposals_collection
22
+ return accessible_proposals_collection unless taxonomy_order_or_search?
23
+
24
+ # this is a trick to avoid duplicates when using search in associations as suggested in:
25
+ # https://activerecord-hackery.github.io/ransack/going-further/other-notes/#problem-with-distinct-selects
26
+ accessible_proposals_collection.includes(:taxonomies).joins(:taxonomies)
23
27
  end
24
28
 
25
29
  def accessible_proposals_collection
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Proposals
7
+ module Admin
8
+ module NeedsInterpolations
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ def populate_interpolations(text, proposal)
13
+ return populate_string_interpolations(text, proposal) if text.is_a?(String)
14
+
15
+ populate_hash_interpolations(text, proposal)
16
+ end
17
+
18
+ def populate_hash_interpolations(hash, proposal)
19
+ return hash unless hash.is_a?(Hash)
20
+
21
+ hash.transform_values do |value|
22
+ populate_interpolations(value, proposal)
23
+ end
24
+ end
25
+
26
+ def populate_string_interpolations(value, proposal)
27
+ value = value.gsub("%{organization}", translated_attribute(proposal.organization.name))
28
+ value = value.gsub("%{name}", author_name(proposal))
29
+ value.gsub("%{admin}", current_user.name)
30
+ end
31
+
32
+ def author_name(proposal)
33
+ name = proposal.creator_author.try(:title) || proposal.creator_author.try(:name)
34
+ translated_attribute(name)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end