decidim-proposals 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -1
  3. data/app/assets/config/admin/decidim_proposals_manifest.js +1 -0
  4. data/app/assets/javascripts/decidim/proposals/admin/proposals.es6 +113 -0
  5. data/app/assets/javascripts/decidim/proposals/identity_selector_dialog.js.es6 +56 -0
  6. data/app/commands/decidim/proposals/admin/answer_proposal.rb +11 -5
  7. data/app/commands/decidim/proposals/admin/create_proposal.rb +25 -3
  8. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +13 -8
  9. data/app/commands/decidim/proposals/admin/import_proposals.rb +83 -0
  10. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +68 -0
  11. data/app/commands/decidim/proposals/create_proposal.rb +0 -12
  12. data/app/commands/decidim/proposals/endorse_proposal.rb +56 -0
  13. data/app/commands/decidim/proposals/publish_proposal.rb +60 -0
  14. data/app/commands/decidim/proposals/unendorse_proposal.rb +40 -0
  15. data/app/commands/decidim/proposals/update_proposal.rb +3 -3
  16. data/app/commands/decidim/proposals/vote_proposal.rb +1 -1
  17. data/app/commands/decidim/proposals/withdraw_proposal.rb +1 -1
  18. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +1 -1
  19. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +2 -2
  20. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +50 -1
  21. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +35 -0
  22. data/app/controllers/decidim/proposals/proposal_endorsements_controller.rb +56 -0
  23. data/app/controllers/decidim/proposals/proposals_controller.rb +82 -9
  24. data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +11 -0
  25. data/app/events/decidim/proposals/creation_enabled_event.rb +8 -0
  26. data/app/events/decidim/proposals/endorsing_enabled_event.rb +8 -0
  27. data/app/events/decidim/proposals/proposal_endorsed_event.rb +29 -0
  28. data/app/events/decidim/proposals/publish_proposal_event.rb +21 -0
  29. data/app/events/decidim/proposals/voting_enabled_event.rb +8 -0
  30. data/app/forms/decidim/proposals/admin/proposal_form.rb +9 -2
  31. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +60 -0
  32. data/app/forms/decidim/proposals/proposal_form.rb +16 -5
  33. data/app/helpers/decidim/proposals/application_helper.rb +1 -0
  34. data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +117 -0
  35. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +13 -6
  36. data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +105 -0
  37. data/app/jobs/decidim/proposals/settings_change_job.rb +48 -0
  38. data/app/models/decidim/proposals/abilities/current_user_ability.rb +30 -8
  39. data/app/models/decidim/proposals/proposal.rb +38 -38
  40. data/app/models/decidim/proposals/proposal_endorsement.rb +31 -0
  41. data/app/models/decidim/proposals/proposal_note.rb +7 -0
  42. data/app/presenters/decidim/proposals/admin_log/proposal_note_presenter.rb +39 -0
  43. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +47 -0
  44. data/app/presenters/decidim/proposals/admin_log/value_types/proposal_state_presenter.rb +16 -0
  45. data/app/queries/decidim/proposals/similar_proposals.rb +53 -0
  46. data/app/types/decidim/proposals/proposal_type.rb +34 -0
  47. data/app/types/decidim/proposals/proposals_type.rb +34 -0
  48. data/app/views/decidim/participatory_processes/participatory_process_groups/_highlighted_proposals.html.erb +8 -0
  49. data/app/views/decidim/participatory_processes/participatory_process_groups/_proposal.html.erb +27 -0
  50. data/app/views/decidim/participatory_spaces/_highlighted_proposals.html.erb +10 -0
  51. data/app/views/decidim/participatory_spaces/_proposal.html.erb +27 -0
  52. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +15 -0
  53. data/app/views/decidim/proposals/admin/proposals/_js-callout.html.erb +6 -0
  54. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +63 -0
  55. data/app/views/decidim/proposals/admin/proposals/index.html.erb +12 -73
  56. data/app/views/decidim/proposals/admin/proposals/update_category.js.erb +25 -0
  57. data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +28 -0
  58. data/app/views/decidim/proposals/proposal_endorsements/_identity.html.erb +4 -0
  59. data/app/views/decidim/proposals/proposal_endorsements/identities.html.erb +12 -0
  60. data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +9 -0
  61. data/app/views/decidim/proposals/proposals/_endorsement_button.html.erb +11 -0
  62. data/app/views/decidim/proposals/proposals/_endorsement_identities_cabin.html.erb +13 -0
  63. data/app/views/decidim/proposals/proposals/_endorsement_xxs.html.erb +9 -0
  64. data/app/views/decidim/proposals/proposals/_endorsements_card_row.html.erb +22 -0
  65. data/app/views/decidim/proposals/proposals/_endorsements_count.html.erb +5 -0
  66. data/app/views/decidim/proposals/proposals/_endorsements_listing.html.erb +34 -0
  67. data/app/views/decidim/proposals/proposals/_proposal.html.erb +2 -2
  68. data/app/views/decidim/proposals/proposals/_proposal_preview.html.erb +36 -0
  69. data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +21 -0
  70. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +8 -8
  71. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +23 -6
  72. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +7 -3
  73. data/app/views/decidim/proposals/proposals/_wizard_aside.html.erb +16 -0
  74. data/app/views/decidim/proposals/proposals/_wizard_header.html.erb +31 -0
  75. data/app/views/decidim/proposals/proposals/compare.html.erb +19 -0
  76. data/app/views/decidim/proposals/proposals/edit_draft.html.erb +55 -0
  77. data/app/views/decidim/proposals/proposals/new.html.erb +7 -20
  78. data/app/views/decidim/proposals/proposals/preview.html.erb +18 -0
  79. data/app/views/decidim/proposals/proposals/show.html.erb +13 -4
  80. data/config/locales/ca.yml +156 -15
  81. data/config/locales/en.yml +156 -15
  82. data/config/locales/es.yml +157 -16
  83. data/config/locales/eu.yml +151 -7
  84. data/config/locales/fi.yml +151 -7
  85. data/config/locales/fr.yml +153 -9
  86. data/config/locales/gl.yml +151 -7
  87. data/config/locales/it.yml +151 -7
  88. data/config/locales/nl.yml +151 -7
  89. data/config/locales/pl.yml +148 -22
  90. data/config/locales/pt-BR.yml +151 -7
  91. data/config/locales/pt.yml +151 -7
  92. data/config/locales/ru.yml +0 -9
  93. data/config/locales/sv.yml +151 -7
  94. data/config/locales/uk.yml +87 -13
  95. data/db/migrate/20170307085300_migrate_proposal_reports_data_to_reports.rb +1 -1
  96. data/db/migrate/20171201115434_create_proposal_endorsements.rb +16 -0
  97. data/db/migrate/20171201122623_add_counter_cache_endorsements_to_proposals.rb +8 -0
  98. data/db/migrate/20171212102250_enable_pg_extensions.rb +7 -0
  99. data/db/migrate/20171220084719_add_published_at_to_proposals.rb +14 -0
  100. data/lib/decidim/proposals.rb +15 -0
  101. data/lib/decidim/proposals/admin_engine.rb +8 -0
  102. data/lib/decidim/proposals/commentable_proposal.rb +39 -0
  103. data/lib/decidim/proposals/engine.rb +69 -1
  104. data/lib/decidim/proposals/feature.rb +51 -6
  105. data/lib/decidim/proposals/test/factories.rb +78 -2
  106. data/lib/decidim/proposals/version.rb +1 -1
  107. metadata +76 -20
  108. data/app/events/decidim/proposals/create_proposal_event.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c56ff21d3615e0cfe8ab8467e9ca5d23298868d09d689beec2eb0f50a52d54c
4
- data.tar.gz: aac6fab0f26a702ede9b9f9d52d1ac6b9b166ad48552064669db6aba420e518e
3
+ metadata.gz: 9f936f41817cbb42ca4bdb52d9501300941433847adf1dbcda717d67a31c8892
4
+ data.tar.gz: 9671316b0995837019afe72384b2dae2c4bc514c785c2d4011cc0341303cf7ee
5
5
  SHA512:
6
- metadata.gz: 5b6f958d8e5f2bde840f2b225db063ab0c9e4dcd34b04dc2bf16718a74470abb3860a02012ed5175b92d62eb884e9e14e2c591b2674cf14263a73de0c7ef6195
7
- data.tar.gz: 3d49678e75ef26d74e8fd0dd08d639b25e34311d2f180363db155c97aebc56014d17c82db217dfa561f70d07c56bcd7a13846a78fe3ec9ce60c0e14521f3235c
6
+ metadata.gz: 4e0e027c43986db153e3a0dea73d24b951d9b766650f4eae49f35826c870bacc63cf42476650014be7158840b043cb7dbc0329d42c1d28c393d181e7f525d51f
7
+ data.tar.gz: c2dab6b2845c246cc9b8254222e584fd1bd37ca16b2d7d3f77f4a4ac7453a7fa294044cb8c997f9770523c50bce6ac3452b2a12f3d2090b7576a2ec0d027a046
data/README.md CHANGED
@@ -11,7 +11,7 @@ Proposals will be available as a Feature for a Participatory Process.
11
11
  Add this line to your application's Gemfile:
12
12
 
13
13
  ```ruby
14
- gem 'decidim-proposals
14
+ gem 'decidim-proposals'
15
15
  ```
16
16
 
17
17
  And then execute:
@@ -20,6 +20,23 @@ And then execute:
20
20
  bundle
21
21
  ```
22
22
 
23
+ ### Configuring Similarity
24
+
25
+ `pg_trgm` is a PostgreSQL extension providing simple fuzzy string matching used in the Proposal wizard to find similar published proposals (title and the body).
26
+
27
+ Create config variables in your app's `/config/initializers/decidim-proposals.rb`:
28
+
29
+ ```ruby
30
+ Decidim::Proposals.configure do |config|
31
+ config.similarity_threshold = 0.25 # default value
32
+ config.similarity_limit = 10 # default value
33
+ end
34
+ ```
35
+
36
+ `similarity_threshold`(real): Sets the current similarity threshold that is used by the % operator. The threshold must be between 0 and 1 (default is 0.3).
37
+
38
+ `similarity_limit`: number of maximum results.
39
+
23
40
  ## Contributing
24
41
 
25
42
  See [Decidim](https://github.com/decidim/decidim).
@@ -0,0 +1 @@
1
+ //= link decidim/proposals/admin/proposals.js
@@ -0,0 +1,113 @@
1
+ // = require_self
2
+ $(document).ready(function () {
3
+ let selectedProposalsCount = function() {
4
+ return $('.table-list .js-check-all-proposal:checked').length
5
+ }
6
+
7
+ let selectedProposalsCountUpdate = function() {
8
+ if(selectedProposalsCount() == 0){
9
+ $("#js-recategorize-proposals-count").text("")
10
+ } else {
11
+ $("#js-recategorize-proposals-count").text(selectedProposalsCount());
12
+ }
13
+ }
14
+
15
+ let showBulkActionsButton = function() {
16
+ if(selectedProposalsCount() > 0){
17
+ $("#js-bulk-actions-button").removeClass('hide');
18
+ }
19
+ }
20
+
21
+ let hideBulkActionsButton = function(force = false) {
22
+ if(selectedProposalsCount() == 0 || force == true){
23
+ $("#js-bulk-actions-button").addClass('hide');
24
+ $("#js-bulk-actions-dropdown").removeClass('is-open');
25
+ }
26
+ }
27
+
28
+ let showOtherActionsButtons = function() {
29
+ $("#js-other-actions-wrapper").removeClass('hide');
30
+ }
31
+
32
+ let hideOtherActionsButtons = function() {
33
+ $("#js-other-actions-wrapper").addClass('hide');
34
+ }
35
+
36
+ let showRecategorizeProposalActions = function() {
37
+ $("#js-recategorize-proposals-actions").removeClass('hide');
38
+ }
39
+
40
+ let hideRecategorizeProposalActions = function() {
41
+ $("#js-recategorize-proposals-actions").addClass('hide');
42
+ }
43
+
44
+
45
+ if ($('#js-form-recategorize-proposals').length) {
46
+ hideRecategorizeProposalActions();
47
+ $("#js-bulk-actions-button").addClass('hide');
48
+
49
+ $("#js-bulk-actions-recategorize").click(function(e){
50
+ e.preventDefault();
51
+
52
+ $('#js-form-recategorize-proposals').submit(function(){
53
+ $('.layout-content > .callout-wrapper').html("");
54
+ })
55
+
56
+ showRecategorizeProposalActions();
57
+ hideBulkActionsButton(true);
58
+ hideOtherActionsButtons();
59
+ })
60
+
61
+ // select all checkboxes
62
+ $(".js-check-all").change(function() {
63
+ $(".js-check-all-proposal").prop('checked', $(this).prop("checked"));
64
+
65
+ if ($(this).prop("checked")) {
66
+ $(".js-check-all-proposal").closest('tr').addClass('selected');
67
+ showBulkActionsButton();
68
+ } else {
69
+ $(".js-check-all-proposal").closest('tr').removeClass('selected');
70
+ hideBulkActionsButton();
71
+ }
72
+
73
+ selectedProposalsCountUpdate();
74
+ });
75
+
76
+ // proposal checkbox change
77
+ $('.table-list').on('change', '.js-check-all-proposal', function (e) {
78
+ let proposal_id = $(this).val()
79
+ let checked = $(this).prop("checked")
80
+
81
+ // uncheck "select all", if one of the listed checkbox item is unchecked
82
+ if ($(this).prop("checked") === false) {
83
+ $(".js-check-all").prop('checked', false);
84
+ }
85
+ // check "select all" if all checkbox proposals are checked
86
+ if ($('.js-check-all-proposal:checked').length === $('.js-check-all-proposal').length) {
87
+ $(".js-check-all").prop('checked', true);
88
+ showBulkActionsButton();
89
+ }
90
+
91
+ if ($(this).prop("checked")) {
92
+ showBulkActionsButton();
93
+ $(this).closest('tr').addClass('selected');
94
+ } else {
95
+ hideBulkActionsButton();
96
+ $(this).closest('tr').removeClass('selected');
97
+ }
98
+
99
+ if ($('.js-check-all-proposal:checked').length === 0) {
100
+ hideBulkActionsButton();
101
+ }
102
+
103
+ $('#js-form-recategorize-proposals').find(".js-proposal-id-"+proposal_id).prop('checked', checked);
104
+ selectedProposalsCountUpdate();
105
+ });
106
+
107
+ $('#js-cancel-edit-category').on('click', function (e) {
108
+ hideRecategorizeProposalActions();
109
+ showBulkActionsButton();
110
+ showOtherActionsButtons();
111
+ });
112
+ }
113
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Makes the #select-identity-button to open a popup for the user to
3
+ * select with which identity he wants to perform an action.
4
+ *
5
+ */
6
+ $(document).ready(function () {
7
+
8
+ let button = $('#select-identity-button'),
9
+ refreshUrl = null,
10
+ userIdentitiesDialog = $('#user-identities');
11
+
12
+ if (userIdentitiesDialog.length) {
13
+ refreshUrl = userIdentitiesDialog.data('refresh-url');
14
+
15
+ button.click(function () {
16
+ $.ajax(refreshUrl).done(function(response) {
17
+ userIdentitiesDialog.html(response).foundation('open');
18
+ button.trigger('ajax:success')
19
+ });
20
+ });
21
+ }
22
+ });
23
+
24
+
25
+ /**
26
+ * Manage the identity selector for endorsements.
27
+ *
28
+ */
29
+ $(document).ready(function () {
30
+ $("#select-identity-button").on('ajax:success', function() {
31
+ // once reveal popup has been rendered register event callbacks
32
+ $("#user-identities ul.reveal__list li").each(function(index, elem) {
33
+ let liTag = $(elem)
34
+ liTag.on('click', function() {
35
+ let method = liTag.data('method')
36
+ let url = liTag.data('url')
37
+ $.ajax({
38
+ url: url,
39
+ method: method,
40
+ dataType: 'script',
41
+ success: function() {
42
+ if (liTag.hasClass('selected')) {
43
+ liTag.removeClass('selected')
44
+ liTag.find('.icon--circle-check').addClass('invisible')
45
+ liTag.data('method', 'post')
46
+ } else {
47
+ liTag.addClass('selected')
48
+ liTag.find('.icon--circle-check').removeClass('invisible')
49
+ liTag.data('method', 'delete')
50
+ }
51
+ }
52
+ })
53
+ })
54
+ });
55
+ });
56
+ })
@@ -34,11 +34,17 @@ module Decidim
34
34
  attr_reader :form, :proposal
35
35
 
36
36
  def answer_proposal
37
- proposal.update_attributes!(
38
- state: @form.state,
39
- answer: @form.answer,
40
- answered_at: Time.current
41
- )
37
+ Decidim.traceability.perform_action!(
38
+ "answer",
39
+ proposal,
40
+ form.current_user
41
+ ) do
42
+ proposal.update!(
43
+ state: @form.state,
44
+ answer: @form.answer,
45
+ answered_at: Time.current
46
+ )
47
+ end
42
48
  end
43
49
 
44
50
  def notify_followers
@@ -29,6 +29,7 @@ module Decidim
29
29
  transaction do
30
30
  create_proposal
31
31
  create_attachment if process_attachments?
32
+ send_notification
32
33
  end
33
34
 
34
35
  broadcast(:ok, proposal)
@@ -39,7 +40,15 @@ module Decidim
39
40
  attr_reader :form, :proposal, :attachment
40
41
 
41
42
  def create_proposal
42
- @proposal = Proposal.create!(
43
+ @proposal = Decidim.traceability.create!(
44
+ Proposal,
45
+ form.current_user,
46
+ attributes
47
+ )
48
+ end
49
+
50
+ def attributes
51
+ {
43
52
  title: form.title,
44
53
  body: form.body,
45
54
  category: form.category,
@@ -47,8 +56,9 @@ module Decidim
47
56
  feature: form.feature,
48
57
  address: form.address,
49
58
  latitude: form.latitude,
50
- longitude: form.longitude
51
- )
59
+ longitude: form.longitude,
60
+ published_at: Time.current
61
+ }
52
62
  end
53
63
 
54
64
  def build_attachment
@@ -82,6 +92,18 @@ module Decidim
82
92
  def process_attachments?
83
93
  attachments_allowed? && attachment_present?
84
94
  end
95
+
96
+ def send_notification
97
+ Decidim::EventsManager.publish(
98
+ event: "decidim.events.proposals.proposal_published",
99
+ event_class: Decidim::Proposals::PublishProposalEvent,
100
+ resource: proposal,
101
+ recipient_ids: @proposal.participatory_space.followers.pluck(:id),
102
+ extra: {
103
+ participatory_space: true
104
+ }
105
+ )
106
+ end
85
107
  end
86
108
  end
87
109
  end
@@ -8,12 +8,10 @@ module Decidim
8
8
  # Public: Initializes the command.
9
9
  #
10
10
  # form - A form object with the params.
11
- # current_user - The current user.
12
11
  # proposal - the proposal to relate.
13
- def initialize(form, proposal, current_user)
12
+ def initialize(form, proposal)
14
13
  @form = form
15
14
  @proposal = proposal
16
- @current_user = current_user
17
15
  end
18
16
 
19
17
  # Executes the command. Broadcasts these events:
@@ -32,13 +30,20 @@ module Decidim
32
30
 
33
31
  private
34
32
 
35
- attr_reader :form, :proposal_note
33
+ attr_reader :form, :proposal_note, :proposal
36
34
 
37
35
  def create_proposal_note
38
- @proposal_note = ProposalNote.create!(
39
- body: form.body,
40
- proposal: @proposal,
41
- author: @current_user
36
+ @proposal_note = Decidim.traceability.create!(
37
+ ProposalNote,
38
+ form.current_user,
39
+ {
40
+ body: form.body,
41
+ proposal: proposal,
42
+ author: form.current_user
43
+ },
44
+ resource: {
45
+ title: proposal.title
46
+ }
42
47
  )
43
48
  end
44
49
  end
@@ -0,0 +1,83 @@
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 imports proposals from
7
+ # one component to another.
8
+ class ImportProposals < Rectify::Command
9
+ # Public: Initializes the command.
10
+ #
11
+ # form - A form object with the params.
12
+ def initialize(form)
13
+ @form = form
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid.
19
+ # - :invalid if the form wasn't valid and we couldn't proceed.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) unless form.valid?
24
+
25
+ broadcast(:ok, import_proposals)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :form
31
+
32
+ def import_proposals
33
+ proposals.map do |original_proposal|
34
+ origin_attributes = original_proposal.attributes.except(
35
+ "id",
36
+ "created_at",
37
+ "updated_at",
38
+ "state",
39
+ "answer",
40
+ "answered_at",
41
+ "decidim_feature_id",
42
+ "reference",
43
+ "proposal_votes_count",
44
+ "proposal_notes_count"
45
+ )
46
+
47
+ proposal = Decidim::Proposals::Proposal.new(origin_attributes)
48
+ proposal.category = original_proposal.category
49
+ proposal.feature = target_feature
50
+ proposal.save!
51
+
52
+ proposal.link_resources([original_proposal], "copied_from_component")
53
+ end
54
+ end
55
+
56
+ def proposals
57
+ Decidim::Proposals::Proposal
58
+ .where(feature: origin_feature)
59
+ .where(state: proposal_states)
60
+ end
61
+
62
+ def proposal_states
63
+ @proposal_states = @form.states
64
+
65
+ if @form.states.include?("not_answered")
66
+ @proposal_states.delete("not_answered")
67
+ @proposal_states.push(nil)
68
+ end
69
+
70
+ @proposal_states
71
+ end
72
+
73
+ def origin_feature
74
+ @form.origin_feature
75
+ end
76
+
77
+ def target_feature
78
+ @form.current_feature
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,68 @@
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 category.
7
+ class UpdateProposalCategory < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # category_id - the category id to update
11
+ # proposal_ids - the proposals ids to update.
12
+ def initialize(category_id, proposal_ids)
13
+ @category = Decidim::Category.find_by id: category_id
14
+ @proposal_ids = proposal_ids
15
+ @response = { category_name: "", successful: [], errored: [] }
16
+ end
17
+
18
+ # Executes the command. Broadcasts these events:
19
+ #
20
+ # - :update_proposals_category - when everything is ok, returns @response.
21
+ # - :invalid_category - if the category is blank.
22
+ # - :invalid_proposal_ids - if the proposal_ids is blank.
23
+ #
24
+ # Returns @response hash:
25
+ #
26
+ # - :category_name - the translated_name of the category assigned
27
+ # - :successful - Array of names of the updated proposals
28
+ # - :errored - Array of names of the proposals not updated because they already had the category assigned
29
+ def call
30
+ return broadcast(:invalid_category) if @category.blank?
31
+ return broadcast(:invalid_proposal_ids) if @proposal_ids.blank?
32
+
33
+ @response[:category_name] = @category.translated_name
34
+ Proposal.where(id: @proposal_ids).find_each do |proposal|
35
+ if @category == proposal.category
36
+ @response[:errored] << proposal.title
37
+ else
38
+ transaction do
39
+ update_proposal_category proposal
40
+ notify_author proposal if proposal.author.present?
41
+ end
42
+ @response[:successful] << proposal.title
43
+ end
44
+ end
45
+
46
+ broadcast(:update_proposals_category, @response)
47
+ end
48
+
49
+ private
50
+
51
+ def update_proposal_category(proposal)
52
+ proposal.update!(
53
+ category: @category
54
+ )
55
+ end
56
+
57
+ def notify_author(proposal)
58
+ Decidim::EventsManager.publish(
59
+ event: "decidim.events.proposals.proposal_update_category",
60
+ event_class: Decidim::Proposals::Admin::UpdateProposalCategoryEvent,
61
+ resource: proposal,
62
+ recipient_ids: [proposal.decidim_author_id]
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end