decidim-proposals 0.30.0.rc2 → 0.30.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +1 -13
  3. data/app/cells/decidim/proposals/proposal_g_cell.rb +4 -2
  4. data/app/cells/decidim/proposals/proposal_l/show.erb +1 -1
  5. data/app/cells/decidim/proposals/proposal_l_cell.rb +6 -1
  6. data/app/cells/decidim/proposals/proposal_vote/show.erb +12 -8
  7. data/app/commands/decidim/proposals/admin/import_proposals.rb +7 -60
  8. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +2 -2
  9. data/app/controllers/decidim/proposals/proposals_controller.rb +0 -9
  10. data/app/controllers/decidim/proposals/versions_controller.rb +1 -1
  11. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +0 -15
  12. data/app/helpers/decidim/proposals/application_helper.rb +0 -13
  13. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +39 -16
  14. data/app/helpers/decidim/proposals/proposals_helper.rb +1 -3
  15. data/app/jobs/decidim/proposals/admin/import_proposals_job.rb +86 -0
  16. data/app/mailers/decidim/proposals/admin/import_proposals_mailer.rb +30 -0
  17. data/app/models/decidim/proposals/collaborative_draft.rb +2 -3
  18. data/app/models/decidim/proposals/proposal.rb +0 -5
  19. data/app/models/decidim/proposals/proposal_vote.rb +1 -0
  20. data/app/packs/stylesheets/decidim/proposals/proposals.scss +34 -2
  21. data/app/services/decidim/proposals/proposal_builder.rb +1 -0
  22. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_failure.html.erb +1 -0
  23. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_success.html.erb +2 -0
  24. data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +3 -6
  25. data/app/views/decidim/proposals/proposals/_exit_modal.html.erb +3 -3
  26. data/app/views/decidim/proposals/proposals/_proposal.html.erb +1 -1
  27. data/app/views/decidim/proposals/proposals/_proposals.html.erb +2 -2
  28. data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +1 -1
  29. data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +3 -3
  30. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +2 -2
  31. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +1 -1
  32. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +27 -24
  33. data/app/views/decidim/proposals/proposals/index.html.erb +1 -6
  34. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +2 -2
  35. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_votes_count.html.erb +2 -2
  36. data/app/views/decidim/proposals/proposals/show.html.erb +1 -1
  37. data/config/locales/ar.yml +3 -3
  38. data/config/locales/bg.yml +2 -6
  39. data/config/locales/bs-BA.yml +2 -2
  40. data/config/locales/ca-IT.yml +1079 -0
  41. data/config/locales/ca.yml +18 -9
  42. data/config/locales/cs.yml +10 -6
  43. data/config/locales/de.yml +15 -6
  44. data/config/locales/el.yml +4 -4
  45. data/config/locales/en.yml +14 -5
  46. data/config/locales/es-MX.yml +17 -8
  47. data/config/locales/es-PY.yml +17 -8
  48. data/config/locales/es.yml +18 -9
  49. data/config/locales/eu.yml +110 -101
  50. data/config/locales/fi-plain.yml +13 -5
  51. data/config/locales/fi.yml +12 -4
  52. data/config/locales/fr-CA.yml +34 -6
  53. data/config/locales/fr.yml +35 -7
  54. data/config/locales/ga-IE.yml +2 -1
  55. data/config/locales/gl.yml +2 -0
  56. data/config/locales/hu.yml +4 -7
  57. data/config/locales/id-ID.yml +3 -1
  58. data/config/locales/is-IS.yml +5 -0
  59. data/config/locales/it.yml +5 -4
  60. data/config/locales/ja.yml +81 -6
  61. data/config/locales/lt.yml +4 -5
  62. data/config/locales/lv.yml +4 -2
  63. data/config/locales/nl.yml +6 -5
  64. data/config/locales/no.yml +2 -2
  65. data/config/locales/pl.yml +2 -6
  66. data/config/locales/pt-BR.yml +2 -4
  67. data/config/locales/pt.yml +4 -2
  68. data/config/locales/ro-RO.yml +26 -2
  69. data/config/locales/ru.yml +5 -1
  70. data/config/locales/sk.yml +2 -2
  71. data/config/locales/sr-CS.yml +2 -2
  72. data/config/locales/sv.yml +2 -7
  73. data/config/locales/tr-TR.yml +4 -2
  74. data/config/locales/uk.yml +5 -1
  75. data/config/locales/zh-CN.yml +4 -2
  76. data/config/locales/zh-TW.yml +4 -4
  77. data/lib/decidim/proposals/version.rb +1 -1
  78. data/lib/tasks/proposals/upgrade/decidim_proposals_upgrade_tasks.rake +22 -0
  79. metadata +25 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57ef0ea1b3c03d7770041d74f7f95183817088ae774fcfe6c1b3ffdb5be89569
4
- data.tar.gz: d289bc845f005d511a24cc72e7051b65a30d3756165b431c7b9bc6e140bcc702
3
+ metadata.gz: 81bba03dc800ce062b503bcaad10e09f1b3bc016dd02c647dc08f1e893a64f68
4
+ data.tar.gz: 4770709bececf57eef406cf4e879f7be42132a81782911c79c3dc8f16d690035
5
5
  SHA512:
6
- metadata.gz: 9a7c6d25457ecc074a0bc261a4155c6d8040cc8ec3b39e444369ff52b48711bfb6e66a49fd970d23dfd6515556448ac7cc5b1befc8e1fef65369fd900df2cb98
7
- data.tar.gz: 1defeac441eec1dae4c002710b4c51efbb70d40b1e4a4e6edba3cd4151cb05ebd81c4c4a2e5344d39159f9c763dfcddcd5df320e3ccaac4356a37103d9379fd9
6
+ metadata.gz: dc1bf3583428e26ca578f26d84a24cb0b5cb36da2938e143d9fbb23d009b8487a8270e1dc40ab3df9e370cd79bf636a974f22c1b238581aa6e770a995bbe6d2c
7
+ data.tar.gz: 551a0f369ace9f5881edb71d6fc4bc9b4c212e597e5df2cd67b01bbf0300f321db1729ac5fa132d02ebd55763fa18b7247c9522cf22ac8a7ac034738ec958cc9
@@ -10,22 +10,10 @@
10
10
  <% end %>
11
11
  <% end %>
12
12
  </div>
13
-
14
- <div class="flex items-center justify-between space-x-6">
15
- <span class="content-block__span flex-shrink-0">
16
- <%= t("decidim.participatory_spaces.highlighted_proposals.last") %>
17
- </span>
18
- <% if single_component? %>
19
- <%= link_to decidim_proposals.new_proposal_path, class: "button button__xs md:button__lg button__secondary" do %>
20
- <span class="text-center"><%= t("decidim.proposals.actions.new") %></span>
21
- <%= icon "add-line" %>
22
- <% end %>
23
- <% end %>
24
- </div>
25
13
  <div class="flex items-start justify-between">
26
14
  <div class="grow space-y-6">
27
15
  <% proposals_to_render.each do |p| %>
28
- <%= card_for p, link_whole_card: true, hide_voting: true, title_tag: :h3, **options.slice(:show_space) %>
16
+ <%= card_for p, link_whole_card: true, title_tag: :h3, **options.slice(:show_space) %>
29
17
  <% end %>
30
18
  </div>
31
19
  </div>
@@ -46,6 +46,7 @@ module Decidim
46
46
 
47
47
  private
48
48
 
49
+ # rubocop:disable Metrics/CyclomaticComplexity
49
50
  def cache_hash
50
51
  @cache_hash ||= begin
51
52
  hash = []
@@ -53,7 +54,7 @@ module Decidim
53
54
  hash << self.class.name.demodulize.underscore
54
55
  hash << model.cache_key_with_version
55
56
  hash << model.proposal_votes_count
56
- hash << options[:hide_voting] ? 1 : 0
57
+ hash << options[:show_voting] ? 0 : 1
57
58
  hash << model.endorsements_count
58
59
  hash << model.comments_count
59
60
  hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
@@ -63,10 +64,11 @@ module Decidim
63
64
  hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
64
65
  hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
65
66
  hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
66
-
67
+ hash << (current_user&.id || 0)
67
68
  hash.join(Decidim.cache_key_separator)
68
69
  end
69
70
  end
71
+ # rubocop:enable Metrics/CyclomaticComplexity
70
72
 
71
73
  def classes
72
74
  super.merge(metadata: "card__list-metadata")
@@ -4,7 +4,7 @@
4
4
  <%= render :content %>
5
5
  <%= render :extra_data if render_extra_data? %>
6
6
  <% end %>
7
- <% if has_actions? && options.fetch(:hide_voting, false) == false %>
7
+ <% if has_actions? && options.fetch(:show_voting, false) == true %>
8
8
  <% if current_settings.votes_hidden? %>
9
9
  <div class="card__proposals-votes-hidden">
10
10
  <%= cell proposal_vote_cell, resource, **options %>
@@ -27,6 +27,8 @@ module Decidim
27
27
  "decidim/proposals/proposal_vote"
28
28
  end
29
29
 
30
+ # rubocop:disable Metrics/CyclomaticComplexity
31
+ # rubocop:disable Metrics/PerceivedComplexity
30
32
  def cache_hash
31
33
  @cache_hash ||= begin
32
34
  hash = []
@@ -34,7 +36,7 @@ module Decidim
34
36
  hash << self.class.name.demodulize.underscore
35
37
  hash << model.cache_key_with_version
36
38
  hash << model.proposal_votes_count
37
- hash << options[:hide_voting] ? 1 : 0
39
+ hash << options[:show_voting] ? 0 : 1
38
40
  hash << model.endorsements_count
39
41
  hash << model.comments_count
40
42
  hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
@@ -44,9 +46,12 @@ module Decidim
44
46
  hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
45
47
  hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
46
48
  hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
49
+ hash << (current_user&.id || 0)
47
50
 
48
51
  hash.join(Decidim.cache_key_separator)
49
52
  end
53
+ # rubocop:enable Metrics/PerceivedComplexity
54
+ # rubocop:enable Metrics/CyclomaticComplexity
50
55
  end
51
56
  end
52
57
  end
@@ -1,4 +1,4 @@
1
- <% if !current_settings.votes_hidden? && current_component.participatory_space.can_participate?(current_user) %>
1
+ <% if !current_settings.votes_hidden? && (current_component.participatory_space.can_participate?(current_user) || current_user.admin?) %>
2
2
  <% if component_settings.participatory_texts_enabled? && from_proposals_list %>
3
3
  <%= render partial: "decidim/proposals/proposals/participatory_texts/proposal_votes_count", locals: { proposal: resource, from_proposals_list: true } %>
4
4
  <% else %>
@@ -24,7 +24,7 @@
24
24
  <div id="proposal-<%= resource.id %>-vote-button" class="card__proposals-votes-container">
25
25
  <% if !current_user %>
26
26
  <% if current_settings.votes_blocked? %>
27
- <%= action_authorized_button_to :vote, t("decidim.proposals.proposals.vote_button.votes_blocked"), proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , class: button_classes, disabled: true %>
27
+ <%= action_authorized_button_to :vote, t("decidim.proposals.proposals.vote_button.votes_blocked"), proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , class: button_classes, disabled: true, data: { "proposal-vote-button": true } %>
28
28
  <% else %>
29
29
  <%= action_authorized_button_to :vote, proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , class: button_classes, data: { "proposal-vote-button": true, disable: true, "redirect-url": proposal_path(resource) } do %>
30
30
  <%= t("decidim.proposals.proposals.vote_button.vote") %>
@@ -32,7 +32,7 @@
32
32
  <% end %>
33
33
  <% end %>
34
34
  <% else %>
35
- <% if @voted_proposals ? @voted_proposals.include?(resource.id) : resource.voted_by?(current_user) %>
35
+ <% if proposal_voted_by_user?(resource) %>
36
36
  <%= action_authorized_button_to(
37
37
  :vote,
38
38
  proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:),
@@ -40,6 +40,7 @@
40
40
  method: :delete,
41
41
  remote: true,
42
42
  data: {
43
+ "proposal-vote-button": true,
43
44
  disable: true,
44
45
  original: t("decidim.proposals.proposals.vote_button.already_voted"),
45
46
  replace: t("decidim.proposals.proposals.vote_button.already_voted_hover"),
@@ -55,14 +56,17 @@
55
56
  <% end %>
56
57
  <% else %>
57
58
  <% if resource.maximum_votes_reached? && !resource.can_accumulate_votes_beyond_threshold && current_component.participatory_space.can_participate?(current_user) %>
58
- <%= content_tag :button, t("decidim.proposals.proposals.vote_button.maximum_votes_reached"), class: button_classes, disabled: true %>
59
+ <%= content_tag :button, t("decidim.proposals.proposals.vote_button.maximum_votes_reached"), class: button_classes, disabled: true, data: { "proposal-vote-button": true } %>
59
60
  <% else %>
60
- <% if vote_limit_enabled? && remaining_votes_count_for(current_user) == 0 %>
61
- <%= content_tag :button, t("decidim.proposals.proposals.vote_button.no_votes_remaining"), class: button_classes, disabled: true %>
61
+ <% if vote_limit_enabled? && remaining_votes_count_for_user == 0 %>
62
+ <%= action_authorized_button_to :vote, proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , remote: true, disabled: true, data: { "proposal-vote-button": true, disable: true, "redirect-url": proposal_path(resource) }, class: button_classes do %>
63
+ <%= t("decidim.proposals.proposals.vote_button.vote") %>
64
+ <span class="sr-only"><%= decidim_html_escape(present(resource).title) %></span>
65
+ <% end %>
62
66
  <% elsif current_settings.votes_blocked? || !current_component.participatory_space.can_participate?(current_user) %>
63
- <%= content_tag :button, t("decidim.proposals.proposals.vote_button.votes_blocked"), class: button_classes, disabled: true %>
67
+ <%= content_tag :button, t("decidim.proposals.proposals.vote_button.votes_blocked"), class: button_classes, disabled: true, data: { "proposal-vote-button": true } %>
64
68
  <% else %>
65
- <%= action_authorized_button_to :vote, proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , remote: true, data: { disable: true, "redirect-url": proposal_path(resource) }, class: button_classes do %>
69
+ <%= action_authorized_button_to :vote, proposal_proposal_vote_path(proposal_id: resource, from_proposals_list:), resource: , remote: true, data: { "proposal-vote-button": true, "redirect-url": proposal_path(resource) }, class: button_classes do %>
66
70
  <%= t("decidim.proposals.proposals.vote_button.vote") %>
67
71
  <span class="sr-only"><%= decidim_html_escape(present(resource).title) %></span>
68
72
  <% end %>
@@ -22,7 +22,8 @@ module Decidim
22
22
  def call
23
23
  return broadcast(:invalid) unless form.valid?
24
24
 
25
- broadcast(:ok, import_proposals)
25
+ import_proposals
26
+ broadcast(:ok)
26
27
  end
27
28
 
28
29
  private
@@ -30,65 +31,11 @@ module Decidim
30
31
  attr_reader :form
31
32
 
32
33
  def import_proposals
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
- }
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
+ }))
92
39
  end
93
40
  end
94
41
  end
@@ -16,8 +16,8 @@ module Decidim
16
16
  @form = form(Admin::ProposalsImportForm).from_params(params)
17
17
 
18
18
  Admin::ImportProposals.call(@form) do
19
- on(:ok) do |proposals|
20
- flash[:notice] = I18n.t("proposals_imports.create.success", scope: "decidim.proposals.admin", number: proposals.length)
19
+ on(:ok) do
20
+ flash[:notice] = I18n.t("proposals_imports.create.success", scope: "decidim.proposals.admin")
21
21
  redirect_to EngineRouter.admin_proxy(current_component).root_path
22
22
  end
23
23
 
@@ -48,15 +48,6 @@ module Decidim
48
48
  @proposals = reorder(@proposals)
49
49
  @proposals = paginate(@proposals)
50
50
  @proposals = @proposals.includes(:component, :coauthorships, :attachments)
51
-
52
- @voted_proposals = if current_user
53
- ProposalVote.where(
54
- author: current_user,
55
- proposal: @proposals.pluck(:id)
56
- ).pluck(:decidim_proposal_id)
57
- else
58
- []
59
- end
60
51
  end
61
52
  end
62
53
 
@@ -11,7 +11,7 @@ module Decidim
11
11
  def versioned_resource
12
12
  @versioned_resource ||=
13
13
  if params[:proposal_id]
14
- present(Proposal.where(component: current_component).find(params[:proposal_id]))
14
+ present(Proposal.not_hidden.published.where(component: current_component).find(params[:proposal_id]))
15
15
  else
16
16
  CollaborativeDraft.where(component: current_component).find(params[:collaborative_draft_id])
17
17
  end
@@ -16,20 +16,9 @@ module Decidim
16
16
  attribute :internal_state, String
17
17
 
18
18
  validates :internal_state, presence: true, inclusion: { in: :proposal_states }
19
- validates :answer, translatable_presence: true, if: ->(form) { form.state == "rejected" }
20
-
21
- with_options if: :costs_required? do
22
- validates :cost, numericality: true, presence: true
23
- validates :cost_report, translatable_presence: true
24
- validates :execution_period, translatable_presence: true
25
- end
26
19
 
27
20
  alias state internal_state
28
21
 
29
- def costs_required?
30
- costs_enabled? && state == "accepted"
31
- end
32
-
33
22
  def publish_answer?
34
23
  current_component.current_settings.publish_answers_immediately?
35
24
  end
@@ -39,10 +28,6 @@ module Decidim
39
28
  def proposal_states
40
29
  Decidim::Proposals::ProposalState.where(component: current_component).pluck(:token).map(&:to_s) + ["not_answered"]
41
30
  end
42
-
43
- def costs_enabled?
44
- current_component.current_settings.answers_with_costs?
45
- end
46
31
  end
47
32
  end
48
33
  end
@@ -17,8 +17,6 @@ module Decidim
17
17
  include Decidim::RichTextEditorHelper
18
18
  include Decidim::CheckBoxesTreeHelper
19
19
 
20
- delegate :minimum_votes_per_user, to: :component_settings
21
-
22
20
  # Public: The state of a proposal in a way a human can understand.
23
21
  #
24
22
  # state - The String state of the proposal.
@@ -85,10 +83,6 @@ module Decidim
85
83
  proposal_limit.present?
86
84
  end
87
85
 
88
- def minimum_votes_per_user_enabled?
89
- minimum_votes_per_user.positive?
90
- end
91
-
92
86
  def not_from_collaborative_draft(proposal)
93
87
  proposal.linked_resources(:proposals, "created_from_collaborative_draft").empty?
94
88
  end
@@ -138,13 +132,6 @@ module Decidim
138
132
  component_settings.proposal_limit
139
133
  end
140
134
 
141
- def votes_given
142
- @votes_given ||= ProposalVote.where(
143
- proposal: Proposal.where(component: current_component),
144
- author: current_user
145
- ).count
146
- end
147
-
148
135
  def layout_item_classes
149
136
  if show_voting_rules?
150
137
  "layout-item lg:pt-4"
@@ -4,14 +4,7 @@ module Decidim
4
4
  module Proposals
5
5
  # Simple helpers to handle markup variations for proposal votes partials
6
6
  module ProposalVotesHelper
7
- # Public: Gets the vote limit for each user, if set.
8
- #
9
- # Returns an Integer if set, nil otherwise.
10
- def vote_limit
11
- return nil if component_settings.vote_limit&.zero?
12
-
13
- component_settings.vote_limit
14
- end
7
+ delegate :minimum_votes_per_user, to: :component_settings
15
8
 
16
9
  # Check if the vote limit is enabled for the current component
17
10
  #
@@ -20,6 +13,10 @@ module Decidim
20
13
  vote_limit.present?
21
14
  end
22
15
 
16
+ def minimum_votes_per_user_enabled?
17
+ minimum_votes_per_user.positive?
18
+ end
19
+
23
20
  # Public: Checks if threshold per proposal are set.
24
21
  #
25
22
  # Returns true if set, false otherwise.
@@ -64,17 +61,21 @@ module Decidim
64
61
  current_user && votes_enabled? && vote_limit_enabled? && !votes_blocked?
65
62
  end
66
63
 
64
+ def proposal_voted_by_user?(proposal)
65
+ return false if current_user.blank? || proposal.blank?
66
+
67
+ all_voted_proposals_by_user.include?(proposal.id)
68
+ end
69
+
67
70
  # Return the remaining votes for a user if the current component has a vote limit
68
71
  #
69
72
  # user - A User object
70
73
  #
71
74
  # Returns a number with the remaining votes for that user
72
- def remaining_votes_count_for(user)
75
+ def remaining_votes_count_for_user
73
76
  return 0 unless vote_limit_enabled?
74
77
 
75
- proposals = Proposal.where(component: current_component)
76
- votes_count = ProposalVote.where(author: user, proposal: proposals).size
77
- component_settings.vote_limit - votes_count
78
+ component_settings.vote_limit - votes_given
78
79
  end
79
80
 
80
81
  # Return the remaining minimum votes for a user if the current component has a vote limit
@@ -82,12 +83,34 @@ module Decidim
82
83
  # user - A User object
83
84
  #
84
85
  # Returns a number with the remaining minimum votes for that user
85
- def remaining_minimum_votes_count_for(user)
86
- return 0 unless vote_limit_enabled?
86
+ def remaining_minimum_votes_count_for_user
87
+ return 0 unless minimum_votes_per_user_enabled?
88
+
89
+ component_settings.minimum_votes_per_user - votes_given
90
+ end
91
+
92
+ private
93
+
94
+ def votes_given
95
+ @votes_given ||= all_voted_proposals_by_user.length
96
+ end
97
+
98
+ # Gets the vote limit for each user, if set.
99
+ #
100
+ # Returns an Integer if set, nil otherwise.
101
+ def vote_limit
102
+ return nil if component_settings.vote_limit&.zero?
103
+
104
+ component_settings.vote_limit
105
+ end
87
106
 
88
- votes_count = Decidim::Proposals::ProposalVote.joins(:proposal).where(decidim_proposals_proposals: { decidim_component_id: current_component.id }).where(author: user).count
107
+ def all_voted_proposals_by_user
108
+ return [] if current_user.blank?
89
109
 
90
- component_settings.minimum_votes_per_user - votes_count
110
+ @all_voted_proposals ||= ProposalVote.where(
111
+ proposal: Proposal.where(component: current_component),
112
+ author: current_user
113
+ ).pluck(:decidim_proposal_id)
91
114
  end
92
115
  end
93
116
  end
@@ -12,9 +12,7 @@ module Decidim
12
12
  end
13
13
 
14
14
  def proposal_has_costs?
15
- @proposal.cost.present? &&
16
- translated_attribute(@proposal.cost_report).present? &&
17
- translated_attribute(@proposal.execution_period).present?
15
+ @proposal.cost.present?
18
16
  end
19
17
 
20
18
  def toggle_view_mode_link(current_mode, target_mode, title, params)
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ class ImportProposalsJob < ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(form)
10
+ @form = form
11
+ ActiveRecord::Base.transaction do
12
+ proposals.map do |original_proposal|
13
+ next if proposal_already_copied?(original_proposal, target_component)
14
+
15
+ Decidim::Proposals::ProposalBuilder.copy(
16
+ original_proposal,
17
+ author: proposal_author,
18
+ action_user: current_user,
19
+ extra_attributes: {
20
+ "component" => target_component
21
+ }.merge(proposal_answer_attributes(original_proposal))
22
+ )
23
+ end
24
+ end
25
+ ImportProposalsMailer.notify_success(current_user, origin_component, target_component, proposals.count).deliver_later
26
+ rescue ActiveRecord::RecordNotFound, NoMethodError
27
+ ImportProposalsMailer.notify_failure(current_user, origin_component, target_component).deliver_later
28
+ end
29
+
30
+ private
31
+
32
+ def proposals
33
+ proposals = Decidim::Proposals::Proposal.where(component: origin_component)
34
+
35
+ if @form["states"].include?("not_answered")
36
+ proposals.not_answered.or(proposals.where(id: proposals.only_status(@form["states"]).pluck(:id)))
37
+ else
38
+ proposals.only_status(@form["states"])
39
+ end
40
+ end
41
+
42
+ def origin_component
43
+ @origin_component ||= Decidim::Component.find(@form["origin_component_id"])
44
+ end
45
+
46
+ def target_component
47
+ @target_component ||= Decidim::Component.find(@form["current_component_id"])
48
+ end
49
+
50
+ def current_user
51
+ @current_user ||= Decidim::User.find(@form["current_user_id"])
52
+ end
53
+
54
+ def current_organization
55
+ @current_organization ||= Decidim::Organization.find(@form["current_organization_id"])
56
+ end
57
+
58
+ def proposal_already_copied?(original_proposal, target_component)
59
+ # Note: we are including also proposals from unpublished components
60
+ # because otherwise duplicates could be created until the component is
61
+ # published.
62
+ original_proposal.linked_resources(:proposals, "copied_from_component", component_published: false).any? do |proposal|
63
+ proposal.component == target_component
64
+ end
65
+ end
66
+
67
+ def proposal_author
68
+ @form["keep_authors"] ? nil : current_organization
69
+ end
70
+
71
+ def proposal_answer_attributes(original_proposal)
72
+ return {} unless @form["keep_answers"]
73
+
74
+ state = Decidim::Proposals::ProposalState.where(component: target_component, token: original_proposal.proposal_state&.token).first
75
+
76
+ {
77
+ answer: original_proposal.answer,
78
+ answered_at: original_proposal.answered_at,
79
+ proposal_state: state,
80
+ state_published_at: original_proposal.state_published_at
81
+ }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ class ImportProposalsMailer < Decidim::ApplicationMailer
7
+ def notify_success(user, origin_component, target_component, count)
8
+ @organization = user.organization
9
+ @origin_component = origin_component
10
+ @target_component = target_component
11
+ @count = count
12
+
13
+ with_user(user) do
14
+ mail(to: user.email, subject: t(".subject"))
15
+ end
16
+ end
17
+
18
+ def notify_failure(user, origin_component, target_component)
19
+ @organization = user.organization
20
+ @origin_component = origin_component
21
+ @target_component = target_component
22
+
23
+ with_user(user) do
24
+ mail(to: user.email, subject: t(".subject"))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -46,9 +46,8 @@ module Decidim
46
46
  authored_by?(user)
47
47
  end
48
48
 
49
- # Public: Overrides the `reported_content_url` Reportable concern method.
50
- def reported_content_url
51
- ResourceLocatorPresenter.new(self).url
49
+ def presenter
50
+ Decidim::Proposals::CollaborativeDraftPresenter.new(self)
52
51
  end
53
52
 
54
53
  # Public: Overrides the `reported_attributes` Reportable concern method.
@@ -303,11 +303,6 @@ module Decidim
303
303
  state == "evaluating"
304
304
  end
305
305
 
306
- # Public: Overrides the `reported_content_url` Reportable concern method.
307
- def reported_content_url
308
- ResourceLocatorPresenter.new(self).url
309
- end
310
-
311
306
  # Returns the presenter for this author, to be used in the views.
312
307
  # Required by ResourceRenderer.
313
308
  def presenter
@@ -31,6 +31,7 @@ module Decidim
31
31
 
32
32
  def update_proposal_votes_count
33
33
  proposal.update_votes_count
34
+ proposal.touch # rubocop:disable Rails/SkipsModelValidations
34
35
  end
35
36
 
36
37
  # Private: check if the proposal and the author have the same organization