decidim-proposals 0.29.1 → 0.30.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +12 -12
  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 +34 -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 +26 -18
  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 +0 -5
  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 +6 -2
  33. data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
  34. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +50 -2
  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/collaborative_drafts_controller.rb +2 -4
  38. data/app/controllers/decidim/proposals/invite_coauthors_controller.rb +87 -0
  39. data/app/controllers/decidim/proposals/proposals_controller.rb +11 -40
  40. data/app/events/decidim/proposals/accepted_coauthorship_event.rb +8 -0
  41. data/app/events/decidim/proposals/admin/proposal_assigned_to_valuator_event.rb +27 -0
  42. data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +5 -0
  43. data/app/events/decidim/proposals/coauthor_accepted_invite_event.rb +49 -0
  44. data/app/events/decidim/proposals/coauthor_invited_event.rb +45 -0
  45. data/app/events/decidim/proposals/coauthor_rejected_invite_event.rb +8 -0
  46. data/app/events/decidim/proposals/rejected_coauthorship_event.rb +8 -0
  47. data/app/events/decidim/proposals/update_proposal_taxonomies_event.rb +9 -0
  48. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +4 -3
  49. data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
  50. data/app/forms/decidim/proposals/admin/proposal_form.rb +12 -7
  51. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +6 -14
  52. data/app/forms/decidim/proposals/admin/valuation_assignment_form.rb +4 -1
  53. data/app/forms/decidim/proposals/collaborative_draft_form.rb +0 -8
  54. data/app/forms/decidim/proposals/proposal_form.rb +5 -32
  55. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +26 -1
  56. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
  57. data/app/helpers/decidim/proposals/application_helper.rb +23 -14
  58. data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +7 -7
  59. data/app/helpers/decidim/proposals/map_helper.rb +0 -18
  60. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +15 -2
  61. data/app/jobs/decidim/proposals/admin/proposal_answer_job.rb +20 -0
  62. data/app/models/decidim/proposals/collaborative_draft.rb +10 -1
  63. data/app/models/decidim/proposals/proposal.rb +67 -10
  64. data/app/models/decidim/proposals/proposal_note.rb +11 -0
  65. data/app/models/decidim/proposals/proposal_state.rb +1 -1
  66. data/app/packs/entrypoints/decidim_proposals.js +1 -0
  67. data/app/packs/entrypoints/decidim_proposals_geocoding.js +2 -0
  68. data/app/packs/src/decidim/proposals/admin/proposals.js +16 -1
  69. data/app/packs/src/decidim/proposals/exit_handler.js +73 -0
  70. data/app/packs/stylesheets/decidim/proposals/proposals.scss +246 -5
  71. data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
  72. data/app/permissions/decidim/proposals/permissions.rb +46 -3
  73. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
  74. data/app/presenters/decidim/proposals/proposal_presenter.rb +1 -1
  75. data/app/queries/decidim/proposals/filtered_proposals.rb +2 -2
  76. data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +2 -2
  77. data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +10 -10
  78. data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +4 -4
  79. data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +6 -6
  80. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +6 -6
  81. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +6 -6
  82. data/app/services/decidim/proposals/collaborative_draft_diff_renderer.rb +22 -0
  83. data/app/services/decidim/proposals/diff_renderer.rb +2 -0
  84. data/app/services/decidim/proposals/proposal_builder.rb +2 -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 +16 -23
  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 +10 -22
  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 +2 -5
  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/_remaining_votes_count.html.erb +2 -2
  118. data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +12 -0
  119. data/app/views/decidim/proposals/proposals/_update_proposal_voting_rules.html.erb +6 -0
  120. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +12 -8
  121. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +2 -1
  122. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +1 -7
  123. data/app/views/decidim/proposals/proposals/index.html.erb +10 -18
  124. data/app/views/decidim/proposals/proposals/index.js.erb +12 -0
  125. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
  126. data/app/views/decidim/proposals/proposals/show.html.erb +36 -16
  127. data/config/locales/ar.yml +19 -75
  128. data/config/locales/bg.yml +7 -91
  129. data/config/locales/bn-BD.yml +1 -0
  130. data/config/locales/bs-BA.yml +87 -0
  131. data/config/locales/ca.yml +213 -73
  132. data/config/locales/cs.yml +229 -75
  133. data/config/locales/de.yml +217 -78
  134. data/config/locales/el.yml +9 -85
  135. data/config/locales/en.yml +209 -69
  136. data/config/locales/es-MX.yml +220 -80
  137. data/config/locales/es-PY.yml +215 -75
  138. data/config/locales/es.yml +222 -82
  139. data/config/locales/eu.yml +293 -147
  140. data/config/locales/fi-plain.yml +222 -81
  141. data/config/locales/fi.yml +239 -98
  142. data/config/locales/fr-CA.yml +119 -90
  143. data/config/locales/fr.yml +118 -89
  144. data/config/locales/ga-IE.yml +0 -19
  145. data/config/locales/gl.yml +9 -47
  146. data/config/locales/he-IL.yml +7 -0
  147. data/config/locales/hu.yml +7 -68
  148. data/config/locales/id-ID.yml +9 -36
  149. data/config/locales/is-IS.yml +0 -21
  150. data/config/locales/it.yml +10 -56
  151. data/config/locales/ja.yml +164 -91
  152. data/config/locales/lt.yml +9 -86
  153. data/config/locales/lv.yml +8 -47
  154. data/config/locales/nl.yml +7 -54
  155. data/config/locales/no.yml +9 -46
  156. data/config/locales/pl.yml +7 -91
  157. data/config/locales/pt-BR.yml +7 -77
  158. data/config/locales/pt.yml +9 -57
  159. data/config/locales/ro-RO.yml +17 -63
  160. data/config/locales/ru.yml +0 -25
  161. data/config/locales/sk.yml +9 -48
  162. data/config/locales/sl.yml +0 -4
  163. data/config/locales/sr-CS.yml +0 -14
  164. data/config/locales/sv.yml +132 -88
  165. data/config/locales/tr-TR.yml +9 -54
  166. data/config/locales/uk.yml +0 -25
  167. data/config/locales/zh-CN.yml +9 -54
  168. data/config/locales/zh-TW.yml +9 -87
  169. data/db/migrate/20171220084719_add_published_at_to_proposals.rb +1 -1
  170. data/db/migrate/20181016132225_add_organization_as_author.rb +1 -1
  171. data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +1 -1
  172. data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +3 -3
  173. data/db/migrate/20210310102839_add_followable_counter_cache_to_proposals.rb +1 -1
  174. data/db/migrate/20240110203504_create_default_proposal_states.rb +4 -3
  175. data/db/migrate/20240404202756_add_valuation_assignments_count_to_decidim_proposals_proposals.rb +1 -1
  176. data/db/migrate/20240617091140_add_email_on_assigned_proposals_to_users.rb +7 -0
  177. data/db/migrate/20240617170052_add_parent_relation_to_decidim_proposal_notes.rb +7 -0
  178. data/db/migrate/20240828103755_add_deleted_at_to_decidim_proposals_proposals.rb +8 -0
  179. data/decidim-proposals.gemspec +2 -2
  180. data/lib/decidim/api/functions/proposal_finder_helper.rb +12 -0
  181. data/lib/decidim/api/functions/proposal_list_helper.rb +12 -0
  182. data/lib/decidim/api/proposal_type.rb +30 -25
  183. data/lib/decidim/api/proposals_type.rb +5 -22
  184. data/lib/decidim/proposals/admin_engine.rb +12 -3
  185. data/lib/decidim/proposals/admin_filter.rb +3 -6
  186. data/lib/decidim/proposals/component.rb +4 -5
  187. data/lib/decidim/proposals/download_your_data_proposal_serializer.rb +15 -0
  188. data/lib/decidim/proposals/engine.rb +5 -0
  189. data/lib/decidim/proposals/import/proposal_creator.rb +4 -4
  190. data/lib/decidim/proposals/proposal_serializer.rb +15 -29
  191. data/lib/decidim/proposals/seeds.rb +21 -17
  192. data/lib/decidim/proposals/test/factories.rb +8 -6
  193. data/lib/decidim/proposals/version.rb +1 -1
  194. data/lib/decidim/proposals.rb +4 -0
  195. metadata +69 -30
  196. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +0 -70
  197. data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +0 -75
  198. data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +0 -11
  199. data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +0 -11
  200. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +0 -15
  201. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +0 -21
  202. data/app/views/decidim/proposals/collaborative_drafts/_actions.html.erb +0 -7
@@ -5,6 +5,8 @@ module Decidim
5
5
  module Admin
6
6
  # A command with all the business logic when an admin creates a private note proposal.
7
7
  class CreateProposalNote < Decidim::Command
8
+ include ProposalNotesMethods
9
+
8
10
  # Public: Initializes the command.
9
11
  #
10
12
  # form - A form object with the params.
@@ -24,7 +26,8 @@ module Decidim
24
26
  return broadcast(:invalid) if form.invalid?
25
27
 
26
28
  create_proposal_note
27
- notify_admins_and_valuators
29
+ notify_mentioned
30
+ notify_not_mentioned_valuators
28
31
 
29
32
  broadcast(:ok, proposal_note)
30
33
  end
@@ -38,7 +41,7 @@ module Decidim
38
41
  ProposalNote,
39
42
  form.current_user,
40
43
  {
41
- body: form.body,
44
+ body: rewritten_body,
42
45
  proposal:,
43
46
  author: form.current_user
44
47
  },
@@ -48,18 +51,24 @@ module Decidim
48
51
  )
49
52
  end
50
53
 
51
- def notify_admins_and_valuators
52
- affected_users = Decidim::User.org_admins_except_me(form.current_user).all
53
- affected_users += Decidim::Proposals::ValuationAssignment.includes(valuator_role: :user).where.not(id: form.current_user.id).where(proposal:).map(&:valuator)
54
+ def notify_mentioned
55
+ notify_creation(mentioned_admins_or_valuators, "decidim.events.proposals.admin.proposal_note_mentioned")
56
+ end
57
+
58
+ def notify_not_mentioned_valuators
59
+ notify_creation(proposal_valuators - mentioned_admins_or_valuators, "decidim.events.proposals.admin.proposal_note_created")
60
+ end
54
61
 
55
- data = {
56
- event: "decidim.events.proposals.admin.proposal_note_created",
62
+ def notify_creation(affected_users, event)
63
+ return if affected_users.blank?
64
+
65
+ Decidim::EventsManager.publish(
66
+ event:,
57
67
  event_class: Decidim::Proposals::Admin::ProposalNoteCreatedEvent,
58
68
  resource: proposal,
59
- affected_users:
60
- }
61
-
62
- Decidim::EventsManager.publish(**data)
69
+ affected_users:,
70
+ extra: { note_author_id: form.current_user.id }
71
+ )
63
72
  end
64
73
  end
65
74
  end
@@ -47,7 +47,6 @@ module Decidim
47
47
  def proposals
48
48
  @proposals = Decidim::Proposals::Proposal
49
49
  .where(component: origin_component)
50
- @proposals = @proposals.where(scope: proposal_scopes) unless proposal_scopes.empty?
51
50
 
52
51
  @proposals = if @form.states.include?("not_answered")
53
52
  @proposals.not_answered.or(@proposals.where(id: @proposals.only_status(@form.states).pluck(:id)))
@@ -58,10 +57,6 @@ module Decidim
58
57
  @proposals
59
58
  end
60
59
 
61
- def proposal_scopes
62
- @form.scopes
63
- end
64
-
65
60
  def origin_component
66
61
  @form.origin_component
67
62
  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
@@ -47,7 +51,7 @@ module Decidim
47
51
  end
48
52
 
49
53
  def valuator_role_ids
50
- current_participatory_space.user_roles(:valuator).pluck(:id)
54
+ current_participatory_space.user_roles(:valuator).order_by_name.pluck(:id)
51
55
  end
52
56
 
53
57
  def translated_valuator_role_ids_has(valuator_role_id)
@@ -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