decidim-proposals 0.9.3 → 0.10.0

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 (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