decidim-proposals 0.29.1 → 0.29.3

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +0 -12
  3. data/app/cells/decidim/proposals/proposal_g_cell.rb +21 -0
  4. data/app/cells/decidim/proposals/proposal_l_cell.rb +17 -18
  5. data/app/commands/decidim/proposals/admin/import_proposals.rb +7 -65
  6. data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +1 -1
  7. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +7 -0
  8. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +2 -2
  9. data/app/controllers/decidim/proposals/proposals_controller.rb +4 -8
  10. data/app/controllers/decidim/proposals/versions_controller.rb +1 -1
  11. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +3 -18
  12. data/app/forms/decidim/proposals/admin/proposal_form.rb +1 -1
  13. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +6 -9
  14. data/app/forms/decidim/proposals/admin/valuation_assignment_form.rb +4 -1
  15. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +1 -1
  16. data/app/helpers/decidim/proposals/application_helper.rb +1 -0
  17. data/app/helpers/decidim/proposals/map_helper.rb +1 -1
  18. data/app/helpers/decidim/proposals/proposals_helper.rb +1 -3
  19. data/app/jobs/decidim/proposals/admin/import_proposals_job.rb +91 -0
  20. data/app/mailers/decidim/proposals/admin/import_proposals_mailer.rb +30 -0
  21. data/app/models/decidim/proposals/collaborative_draft.rb +2 -3
  22. data/app/models/decidim/proposals/proposal.rb +2 -11
  23. data/app/packs/stylesheets/decidim/proposals/proposals.scss +1 -5
  24. data/app/permissions/decidim/proposals/permissions.rb +4 -3
  25. data/app/services/decidim/proposals/collaborative_draft_diff_renderer.rb +22 -0
  26. data/app/services/decidim/proposals/diff_renderer.rb +2 -0
  27. data/app/services/decidim/proposals/proposal_builder.rb +2 -1
  28. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_failure.html.erb +1 -0
  29. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_success.html.erb +2 -0
  30. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
  31. data/app/views/decidim/proposals/admin/proposals/index.html.erb +2 -2
  32. data/app/views/decidim/proposals/admin/proposals/show.html.erb +4 -4
  33. data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +2 -2
  34. data/app/views/decidim/proposals/proposals/_proposals.html.erb +1 -1
  35. data/app/views/decidim/proposals/proposals/index.html.erb +9 -2
  36. data/app/views/decidim/proposals/proposals/index.js.erb +12 -0
  37. data/app/views/decidim/proposals/proposals/show.html.erb +1 -1
  38. data/config/locales/ar.yml +8 -6
  39. data/config/locales/bg.yml +5 -12
  40. data/config/locales/bn-BD.yml +1 -0
  41. data/config/locales/bs-BA.yml +98 -0
  42. data/config/locales/ca-IT.yml +945 -0
  43. data/config/locales/ca.yml +27 -12
  44. data/config/locales/cs.yml +33 -8
  45. data/config/locales/de.yml +27 -12
  46. data/config/locales/el.yml +5 -9
  47. data/config/locales/en.yml +18 -3
  48. data/config/locales/es-MX.yml +29 -14
  49. data/config/locales/es-PY.yml +24 -9
  50. data/config/locales/es.yml +32 -17
  51. data/config/locales/eu.yml +185 -163
  52. data/config/locales/fi-plain.yml +25 -10
  53. data/config/locales/fi.yml +42 -27
  54. data/config/locales/fr-CA.yml +20 -10
  55. data/config/locales/fr.yml +19 -9
  56. data/config/locales/ga-IE.yml +2 -1
  57. data/config/locales/gl.yml +5 -6
  58. data/config/locales/he-IL.yml +7 -0
  59. data/config/locales/hu.yml +4 -9
  60. data/config/locales/id-ID.yml +12 -5
  61. data/config/locales/is-IS.yml +5 -7
  62. data/config/locales/it.yml +31 -11
  63. data/config/locales/ja.yml +22 -8
  64. data/config/locales/lt.yml +3 -8
  65. data/config/locales/lv.yml +7 -4
  66. data/config/locales/nl.yml +11 -9
  67. data/config/locales/no.yml +3 -6
  68. data/config/locales/pl.yml +2 -9
  69. data/config/locales/pt-BR.yml +3 -8
  70. data/config/locales/pt.yml +4 -6
  71. data/config/locales/ro-RO.yml +25 -11
  72. data/config/locales/ru.yml +5 -7
  73. data/config/locales/sk.yml +8 -5
  74. data/config/locales/sl.yml +0 -4
  75. data/config/locales/sr-CS.yml +0 -2
  76. data/config/locales/sv.yml +11 -8
  77. data/config/locales/tr-TR.yml +7 -9
  78. data/config/locales/uk.yml +6 -8
  79. data/config/locales/zh-CN.yml +3 -5
  80. data/config/locales/zh-TW.yml +5 -9
  81. data/db/migrate/20240110203504_create_default_proposal_states.rb +3 -2
  82. data/decidim-proposals.gemspec +1 -1
  83. data/lib/decidim/api/proposal_type.rb +13 -0
  84. data/lib/decidim/api/proposals_type.rb +1 -3
  85. data/lib/decidim/proposals/proposal_serializer.rb +7 -4
  86. data/lib/decidim/proposals/test/factories.rb +6 -5
  87. data/lib/decidim/proposals/version.rb +1 -1
  88. data/lib/tasks/proposals/upgrade/decidim_proposals_upgrade_tasks.rake +22 -0
  89. metadata +29 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2109029e1165afe1fe039f4f5e1d2f0b8af26d8e80bb2e0db0c11053da033e61
4
- data.tar.gz: 9e72ff8d19ba6f4442eed68fa4c5625e2d22367b3c00a8faf2b36fa58c888dbe
3
+ metadata.gz: f68f4460c5148fc90b2ff8d92c25b7c1c5afae4347c4ce8bdcdfe2eec7e533cb
4
+ data.tar.gz: 06c7f3d44d8534c22b816d9ba8e216d1fa854337ada6158056a06bcb70a46598
5
5
  SHA512:
6
- metadata.gz: 3d2b4e79ae0fb506248ca119acca6ce128649457f53b6047a9def36168110a63a36bccb6abde7fd8300194cd8bf785546d9d21d066e4f628da6742c2601fb8d6
7
- data.tar.gz: 3ad98b72afdc5b9b2601cffa7f5c872b5bf5202ed35f9bff830014af78d7b1de2c5aaae4b5ffc4e721f2d21594a8229bb96ef549f653a0caf36ae8f2a36b0cf9
6
+ metadata.gz: e10e7ef879ce34a18a5b9e101ca6fef2cc8d4c7434b6f0599fa1d5cf830e34e00fbd8fd9cb571b280e2c2113fc375f8233d6bfa4b32fea9c864958c0ab62c342
7
+ data.tar.gz: 5363d5e2789296bb4f969919776c406a2ede64f6e1651a27c56c9fab3b55c82e35f00b60c4a8c564f5192ce7b4d03a581f557b217c2185810c37f237e961d740
@@ -10,22 +10,10 @@
10
10
  <% end %>
11
11
  <% end %>
12
12
  </div>
13
-
14
13
  <div class="flex items-start justify-between">
15
14
  <div class="grow space-y-6">
16
- <span class="content-block__span">
17
- <%= t("decidim.participatory_spaces.highlighted_proposals.last") %>
18
- </span>
19
-
20
15
  <% proposals_to_render.each do |p| %>
21
16
  <%= card_for p, link_whole_card: true, title_tag: :h3, **options.slice(:show_space) %>
22
17
  <% end %>
23
18
  </div>
24
-
25
- <% if single_component? %>
26
- <%= link_to decidim_proposals.new_proposal_path, class: "button button__sm md:button__lg button__secondary" do %>
27
- <span><%= t("decidim.proposals.actions.new") %></span>
28
- <%= icon "add-line" %>
29
- <% end %>
30
- <% end %>
31
19
  </div>
@@ -34,6 +34,27 @@ module Decidim
34
34
 
35
35
  private
36
36
 
37
+ def cache_hash
38
+ @cache_hash ||= begin
39
+ hash = []
40
+ hash << I18n.locale.to_s
41
+ hash << self.class.name.demodulize.underscore
42
+ hash << model.cache_key_with_version
43
+ hash << model.proposal_votes_count
44
+ hash << model.endorsements_count
45
+ hash << model.comments_count
46
+ hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
47
+ hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
48
+ hash << 0 # render space
49
+ hash << model.follows_count
50
+ hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
51
+ hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
52
+ hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
53
+
54
+ hash.join(Decidim.cache_key_separator)
55
+ end
56
+ end
57
+
37
58
  def classes
38
59
  super.merge(metadata: "card__list-metadata")
39
60
  end
@@ -20,25 +20,24 @@ module Decidim
20
20
  end
21
21
 
22
22
  def cache_hash
23
- hash = []
24
- hash << I18n.locale.to_s
25
- hash << model.cache_key_with_version
26
- hash << model.proposal_votes_count
27
- hash << model.endorsements_count
28
- hash << model.comments_count
29
- hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
30
- hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
31
- hash << render_space? ? 1 : 0
32
- if current_user
33
- hash << current_user.cache_key_with_version
34
- hash << current_user.follows?(model) ? 1 : 0
35
- end
36
- hash << model.follows_count
37
- hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
38
- hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
39
- hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
23
+ @cache_hash ||= begin
24
+ hash = []
25
+ hash << I18n.locale.to_s
26
+ hash << self.class.name.demodulize.underscore
27
+ hash << model.cache_key_with_version
28
+ hash << model.proposal_votes_count
29
+ hash << model.endorsements_count
30
+ hash << model.comments_count
31
+ hash << Digest::MD5.hexdigest(model.component.cache_key_with_version)
32
+ hash << Digest::MD5.hexdigest(resource_image_url) if resource_image_url
33
+ hash << render_space? ? 1 : 0
34
+ hash << model.follows_count
35
+ hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
36
+ hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
37
+ hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
40
38
 
41
- hash.join(Decidim.cache_key_separator)
39
+ hash.join(Decidim.cache_key_separator)
40
+ end
42
41
  end
43
42
  end
44
43
  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,70 +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
- @proposals = @proposals.where(scope: proposal_scopes) unless proposal_scopes.empty?
51
-
52
- @proposals = if @form.states.include?("not_answered")
53
- @proposals.not_answered.or(@proposals.where(id: @proposals.only_status(@form.states).pluck(:id)))
54
- else
55
- @proposals.only_status(@form.states)
56
- end
57
-
58
- @proposals
59
- end
60
-
61
- def proposal_scopes
62
- @form.scopes
63
- end
64
-
65
- def origin_component
66
- @form.origin_component
67
- end
68
-
69
- def target_component
70
- @form.current_component
71
- end
72
-
73
- def proposal_already_copied?(original_proposal, target_component)
74
- # Note: we are including also proposals from unpublished components
75
- # because otherwise duplicates could be created until the component is
76
- # published.
77
- original_proposal.linked_resources(:proposals, "copied_from_component", component_published: false).any? do |proposal|
78
- proposal.component == target_component
79
- end
80
- end
81
-
82
- def proposal_author
83
- form.keep_authors ? nil : @form.current_organization
84
- end
85
-
86
- def proposal_answer_attributes(original_proposal)
87
- return {} unless form.keep_answers
88
-
89
- state = Decidim::Proposals::ProposalState.where(component: target_component, token: original_proposal.state).first
90
-
91
- {
92
- answer: original_proposal.answer,
93
- answered_at: original_proposal.answered_at,
94
- proposal_state: state,
95
- state_published_at: original_proposal.state_published_at
96
- }
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
+ }))
97
39
  end
98
40
  end
99
41
  end
@@ -47,7 +47,7 @@ module Decidim
47
47
  end
48
48
 
49
49
  def valuator_role_ids
50
- current_participatory_space.user_roles(:valuator).pluck(:id)
50
+ current_participatory_space.user_roles(:valuator).order_by_name.pluck(:id)
51
51
  end
52
52
 
53
53
  def translated_valuator_role_ids_has(valuator_role_id)
@@ -5,6 +5,9 @@ module Decidim
5
5
  module Admin
6
6
  # This controller allows admins to answer proposals in a participatory process.
7
7
  class ProposalAnswersController < Admin::ApplicationController
8
+ include ActionView::Helpers::SanitizeHelper
9
+ include Decidim::Proposals::Admin::Filterable
10
+
8
11
  helper_method :proposal
9
12
 
10
13
  helper Proposals::ApplicationHelper
@@ -44,6 +47,10 @@ module Decidim
44
47
  def proposal
45
48
  @proposal ||= Proposal.where(component: current_component).find(params[:id])
46
49
  end
50
+
51
+ def collection
52
+ @collection ||= Proposal.where(component: current_component).not_hidden.published
53
+ end
47
54
  end
48
55
  end
49
56
  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
 
@@ -43,13 +43,11 @@ module Decidim
43
43
  .order(position: :asc)
44
44
  render "decidim/proposals/proposals/participatory_texts/participatory_text"
45
45
  else
46
- @base_query = search
47
- .result
48
- .published
49
- .not_hidden
46
+ @proposals = search.result
50
47
 
51
- @proposals = @base_query.includes(:component, :coauthorships, :attachments)
52
- @all_geocoded_proposals = @base_query.geocoded
48
+ @proposals = reorder(@proposals)
49
+ @proposals = paginate(@proposals)
50
+ @proposals = @proposals.includes(:component, :coauthorships, :attachments)
53
51
 
54
52
  @voted_proposals = if current_user
55
53
  ProposalVote.where(
@@ -59,8 +57,6 @@ module Decidim
59
57
  else
60
58
  []
61
59
  end
62
- @proposals = reorder(@proposals)
63
- @proposals = paginate(@proposals)
64
60
  end
65
61
  end
66
62
 
@@ -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
@@ -8,27 +8,16 @@ module Decidim
8
8
  include TranslatableAttributes
9
9
  mimic :proposal_answer
10
10
 
11
- translatable_attribute :answer, String
12
- translatable_attribute :cost_report, String
13
- translatable_attribute :execution_period, String
11
+ translatable_attribute :answer, Decidim::Attributes::RichText
12
+ translatable_attribute :cost_report, Decidim::Attributes::RichText
13
+ translatable_attribute :execution_period, Decidim::Attributes::RichText
14
14
  attribute :cost, Float
15
15
  attribute :internal_state, String
16
16
 
17
17
  validates :internal_state, presence: true, inclusion: { in: :proposal_states }
18
- validates :answer, translatable_presence: true, if: ->(form) { form.state == "rejected" }
19
-
20
- with_options if: :costs_required? do
21
- validates :cost, numericality: true, presence: true
22
- validates :cost_report, translatable_presence: true
23
- validates :execution_period, translatable_presence: true
24
- end
25
18
 
26
19
  alias state internal_state
27
20
 
28
- def costs_required?
29
- costs_enabled? && state == "accepted"
30
- end
31
-
32
21
  def publish_answer?
33
22
  current_component.current_settings.publish_answers_immediately?
34
23
  end
@@ -38,10 +27,6 @@ module Decidim
38
27
  def proposal_states
39
28
  Decidim::Proposals::ProposalState.where(component: current_component).pluck(:token).map(&:to_s) + ["not_answered"]
40
29
  end
41
-
42
- def costs_enabled?
43
- current_component.current_settings.answers_with_costs?
44
- end
45
30
  end
46
31
  end
47
32
  end
@@ -10,7 +10,7 @@ module Decidim
10
10
  translatable_attribute :title, String do |field, _locale|
11
11
  validates field, length: { in: 15..150 }, if: proc { |resource| resource.send(field).present? }
12
12
  end
13
- translatable_attribute :body, String
13
+ translatable_attribute :body, Decidim::Attributes::RichText
14
14
 
15
15
  validates :title, :body, translatable_presence: true
16
16
 
@@ -6,6 +6,7 @@ module Decidim
6
6
  # A form object to be used when admin users want to import a collection of proposals
7
7
  # from another component.
8
8
  class ProposalsImportForm < Decidim::Form
9
+ include TranslatableAttributes
9
10
  mimic :proposals_import
10
11
 
11
12
  attribute :origin_component_id, Integer
@@ -19,15 +20,11 @@ module Decidim
19
20
  validates :import_proposals, allow_nil: false, acceptance: true
20
21
  validate :valid_states
21
22
 
22
- VALID_STATES = %w(accepted not_answered evaluating rejected).freeze
23
-
24
23
  def states_collection
25
- VALID_STATES.map do |state|
26
- OpenStruct.new(
27
- name: I18n.t(state, scope: "decidim.proposals.answers"),
28
- value: state
29
- )
30
- end
24
+ @states_collection ||= ProposalState.where(component: current_component) + [ProposalState.new(token: "not_answered",
25
+ title: I18n.t(
26
+ :not_answered, scope: "decidim.proposals.answers"
27
+ ))]
31
28
  end
32
29
 
33
30
  def states
@@ -56,7 +53,7 @@ module Decidim
56
53
 
57
54
  def valid_states
58
55
  return if states.all? do |state|
59
- VALID_STATES.include?(state)
56
+ states_collection.pluck(:token).include?(state)
60
57
  end
61
58
 
62
59
  errors.add(:states, :invalid)
@@ -16,7 +16,10 @@ module Decidim
16
16
  end
17
17
 
18
18
  def valuator_roles
19
- @valuator_roles ||= current_component.participatory_space.user_roles(:valuator).where(id: valuator_role_ids)
19
+ @valuator_roles ||= current_component.participatory_space
20
+ .user_roles(:valuator)
21
+ .order_by_name
22
+ .where(id: valuator_role_ids)
20
23
  end
21
24
 
22
25
  def same_participatory_space
@@ -10,7 +10,7 @@ module Decidim
10
10
 
11
11
  # find the valuators for the current space.
12
12
  def find_valuators_for_select(participatory_space, current_user)
13
- valuator_roles = participatory_space.user_roles(:valuator)
13
+ valuator_roles = participatory_space.user_roles(:valuator).order_by_name
14
14
  valuators = Decidim::User.where(id: valuator_roles.pluck(:decidim_user_id)).to_a
15
15
 
16
16
  filtered_valuator_roles = valuator_roles.filter do |role|
@@ -30,6 +30,7 @@ module Decidim
30
30
 
31
31
  def proposal_state_css_style(proposal)
32
32
  return "" if proposal.emendation?
33
+ return "" if proposal.withdrawn?
33
34
 
34
35
  proposal.proposal_state&.css_style
35
36
  end
@@ -9,7 +9,7 @@ module Decidim
9
9
  #
10
10
  # geocoded_proposals - A collection of geocoded proposals
11
11
  def proposals_data_for_map(geocoded_proposals)
12
- geocoded_proposals.map do |proposal|
12
+ geocoded_proposals.select(&:geocoded_and_valid?).map do |proposal|
13
13
  proposal_data_for_map(proposal)
14
14
  end
15
15
  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,91 @@
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
+ proposals = proposals.where(scope: proposal_scopes) unless proposal_scopes.empty?
35
+
36
+ if @form["states"].include?("not_answered")
37
+ proposals.not_answered.or(proposals.where(id: proposals.only_status(@form["states"]).pluck(:id)))
38
+ else
39
+ proposals.only_status(@form["states"])
40
+ end
41
+ end
42
+
43
+ def origin_component
44
+ @origin_component ||= Decidim::Component.find(@form["origin_component_id"])
45
+ end
46
+
47
+ def target_component
48
+ @target_component ||= Decidim::Component.find(@form["current_component_id"])
49
+ end
50
+
51
+ def current_user
52
+ @current_user ||= Decidim::User.find(@form["current_user_id"])
53
+ end
54
+
55
+ def current_organization
56
+ @current_organization ||= Decidim::Organization.find(@form["current_organization_id"])
57
+ end
58
+
59
+ def proposal_already_copied?(original_proposal, target_component)
60
+ # Note: we are including also proposals from unpublished components
61
+ # because otherwise duplicates could be created until the component is
62
+ # published.
63
+ original_proposal.linked_resources(:proposals, "copied_from_component", component_published: false).any? do |proposal|
64
+ proposal.component == target_component
65
+ end
66
+ end
67
+
68
+ def proposal_author
69
+ @form["keep_authors"] ? nil : current_organization
70
+ end
71
+
72
+ def proposal_scopes
73
+ @form["scopes"] || []
74
+ end
75
+
76
+ def proposal_answer_attributes(original_proposal)
77
+ return {} unless @form["keep_answers"]
78
+
79
+ state = Decidim::Proposals::ProposalState.where(component: target_component, token: original_proposal.proposal_state&.token).first
80
+
81
+ {
82
+ answer: original_proposal.answer,
83
+ answered_at: original_proposal.answered_at,
84
+ proposal_state: state,
85
+ state_published_at: original_proposal.state_published_at
86
+ }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ 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
@@ -45,9 +45,8 @@ module Decidim
45
45
  authored_by?(user)
46
46
  end
47
47
 
48
- # Public: Overrides the `reported_content_url` Reportable concern method.
49
- def reported_content_url
50
- ResourceLocatorPresenter.new(self).url
48
+ def presenter
49
+ Decidim::Proposals::CollaborativeDraftPresenter.new(self)
51
50
  end
52
51
 
53
52
  # Public: Overrides the `reported_attributes` Reportable concern method.
@@ -301,11 +301,6 @@ module Decidim
301
301
  state == "evaluating"
302
302
  end
303
303
 
304
- # Public: Overrides the `reported_content_url` Reportable concern method.
305
- def reported_content_url
306
- ResourceLocatorPresenter.new(self).url
307
- end
308
-
309
304
  # Returns the presenter for this author, to be used in the views.
310
305
  # Required by ResourceRenderer.
311
306
  def presenter
@@ -402,12 +397,8 @@ module Decidim
402
397
  where(query, value:)
403
398
  end
404
399
 
405
- def self.ransackable_scopes(auth_object = nil)
406
- base = [:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_scope, :with_any_category]
407
- return base unless auth_object&.admin?
408
-
409
- # Add extra scopes for admins for the admin panel searches
410
- base + [:valuator_role_ids_has]
400
+ def self.ransackable_scopes(_auth_object = nil)
401
+ [:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_scope, :with_any_category, :valuator_role_ids_has]
411
402
  end
412
403
 
413
404
  # Create i18n ransackers for :title and :body.
@@ -103,11 +103,7 @@
103
103
  }
104
104
 
105
105
  &__grid-text-title {
106
- @apply flex justify-between flex-row md:flex-col md:items-start lg:flex-row lg:items-center;
107
-
108
- .label {
109
- @apply md:mt-2;
110
- }
106
+ @apply flex justify-between flex-row md:flex-col items-start lg:flex-row;
111
107
  }
112
108
 
113
109
  &__list-metadata {
@@ -4,15 +4,16 @@ module Decidim
4
4
  module Proposals
5
5
  class Permissions < Decidim::DefaultPermissions
6
6
  def permissions
7
- return permission_action unless user
8
-
9
7
  # Delegate the admin permission checks to the admin permissions class
10
8
  return Decidim::Proposals::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
11
9
  return permission_action if permission_action.scope != :public
12
10
 
11
+ toggle_allow(!proposal.hidden?) if permission_action.subject == :proposal && permission_action.action == :read
12
+ return permission_action unless user
13
+
13
14
  case permission_action.subject
14
15
  when :proposal
15
- apply_proposal_permissions(permission_action)
16
+ apply_proposal_permissions(permission_action) unless permission_action.action == :read
16
17
  when :collaborative_draft
17
18
  apply_collaborative_draft_permissions(permission_action)
18
19
  else
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class CollaborativeDraftDiffRenderer < DiffRenderer
6
+ private
7
+
8
+ def attribute_types
9
+ {
10
+ title: :string,
11
+ body: :string,
12
+ decidim_category_id: :category,
13
+ decidim_scope_id: :scope,
14
+ address: :string,
15
+ latitude: :string,
16
+ longitude: :string,
17
+ decidim_proposals_proposal_state_id: :string
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -21,8 +21,10 @@ module Decidim
21
21
 
22
22
  # Parses the values before parsing the changeset.
23
23
  def parse_changeset(attribute, values, type, diff)
24
+ return parse_i18n_changeset(attribute, values, type, diff) if [:i18n, :i18n_html].include?(type)
24
25
  return parse_scope_changeset(attribute, values, type, diff) if type == :scope
25
26
  return parse_state_changeset(attribute, values, type, diff) if type == :state
27
+ return parse_user_group_changeset(attribute, values, type, diff) if type == :user_group
26
28
 
27
29
  values = parse_values(attribute, values)
28
30
  old_value = values[0]