decidim-proposals 0.29.1 → 0.30.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +12 -12
- data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
- data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
- data/app/cells/decidim/proposals/proposal_g/show.erb +13 -0
- data/app/cells/decidim/proposals/proposal_g_cell.rb +34 -0
- data/app/cells/decidim/proposals/proposal_history_cell.rb +107 -0
- data/app/cells/decidim/proposals/proposal_l/show.erb +37 -0
- data/app/cells/decidim/proposals/proposal_l_cell.rb +26 -18
- data/app/cells/decidim/proposals/proposal_metadata_cell.rb +2 -2
- data/app/cells/decidim/proposals/proposal_vote/show.erb +75 -0
- data/app/cells/decidim/proposals/proposal_vote_cell.rb +43 -0
- data/app/commands/decidim/proposals/accept_coauthorship.rb +62 -0
- data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +14 -0
- data/app/commands/decidim/proposals/admin/create_proposal.rb +6 -14
- data/app/commands/decidim/proposals/admin/create_proposal_note.rb +20 -11
- data/app/commands/decidim/proposals/admin/import_proposals.rb +0 -5
- data/app/commands/decidim/proposals/admin/merge_proposals.rb +2 -2
- data/app/commands/decidim/proposals/admin/proposal_notes_methods.rb +48 -0
- data/app/commands/decidim/proposals/admin/reply_proposal_note.rb +92 -0
- data/app/commands/decidim/proposals/admin/split_proposals.rb +2 -2
- data/app/commands/decidim/proposals/admin/update_proposal.rb +10 -16
- data/app/commands/decidim/proposals/admin/update_proposal_taxonomies.rb +34 -0
- data/app/commands/decidim/proposals/cancel_coauthorship.rb +32 -0
- data/app/commands/decidim/proposals/create_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/create_proposal.rb +1 -2
- data/app/commands/decidim/proposals/invite_coauthor.rb +45 -0
- data/app/commands/decidim/proposals/publish_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/reject_coauthorship.rb +54 -0
- data/app/commands/decidim/proposals/update_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/update_proposal.rb +1 -2
- data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +6 -2
- data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
- data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +50 -2
- data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +18 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +41 -85
- data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +2 -4
- data/app/controllers/decidim/proposals/invite_coauthors_controller.rb +87 -0
- data/app/controllers/decidim/proposals/proposals_controller.rb +11 -40
- data/app/events/decidim/proposals/accepted_coauthorship_event.rb +8 -0
- data/app/events/decidim/proposals/admin/proposal_assigned_to_valuator_event.rb +27 -0
- data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +5 -0
- data/app/events/decidim/proposals/coauthor_accepted_invite_event.rb +49 -0
- data/app/events/decidim/proposals/coauthor_invited_event.rb +45 -0
- data/app/events/decidim/proposals/coauthor_rejected_invite_event.rb +8 -0
- data/app/events/decidim/proposals/rejected_coauthorship_event.rb +8 -0
- data/app/events/decidim/proposals/update_proposal_taxonomies_event.rb +9 -0
- data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +4 -3
- data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
- data/app/forms/decidim/proposals/admin/proposal_form.rb +12 -7
- data/app/forms/decidim/proposals/admin/proposals_import_form.rb +6 -14
- data/app/forms/decidim/proposals/admin/valuation_assignment_form.rb +4 -1
- data/app/forms/decidim/proposals/collaborative_draft_form.rb +0 -8
- data/app/forms/decidim/proposals/proposal_form.rb +5 -32
- data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +26 -1
- data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
- data/app/helpers/decidim/proposals/application_helper.rb +23 -14
- data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +7 -7
- data/app/helpers/decidim/proposals/map_helper.rb +0 -18
- data/app/helpers/decidim/proposals/proposal_votes_helper.rb +15 -2
- data/app/jobs/decidim/proposals/admin/proposal_answer_job.rb +20 -0
- data/app/models/decidim/proposals/collaborative_draft.rb +10 -1
- data/app/models/decidim/proposals/proposal.rb +67 -10
- data/app/models/decidim/proposals/proposal_note.rb +11 -0
- data/app/models/decidim/proposals/proposal_state.rb +1 -1
- data/app/packs/entrypoints/decidim_proposals.js +1 -0
- data/app/packs/entrypoints/decidim_proposals_geocoding.js +2 -0
- data/app/packs/src/decidim/proposals/admin/proposals.js +16 -1
- data/app/packs/src/decidim/proposals/exit_handler.js +73 -0
- data/app/packs/stylesheets/decidim/proposals/proposals.scss +246 -5
- data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
- data/app/permissions/decidim/proposals/permissions.rb +46 -3
- data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
- data/app/presenters/decidim/proposals/proposal_presenter.rb +1 -1
- data/app/queries/decidim/proposals/filtered_proposals.rb +2 -2
- data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +2 -2
- data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +10 -10
- data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +4 -4
- data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +6 -6
- data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +6 -6
- data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +6 -6
- data/app/services/decidim/proposals/collaborative_draft_diff_renderer.rb +22 -0
- data/app/services/decidim/proposals/diff_renderer.rb +2 -0
- data/app/services/decidim/proposals/proposal_builder.rb +2 -2
- data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +3 -3
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note.html.erb +28 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note_reply.html.erb +9 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +4 -28
- data/app/views/decidim/proposals/admin/proposal_states/_form.html.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/_actions.html.erb +21 -0
- data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +3 -2
- data/app/views/decidim/proposals/admin/proposals/_form.html.erb +16 -23
- data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +12 -28
- data/app/views/decidim/proposals/admin/proposals/_proposals-thead.html.erb +45 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_apply_answer_template.html.erb +22 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +15 -11
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_taxonomy_change.html.erb +23 -0
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +17 -48
- data/app/views/decidim/proposals/admin/proposals/manage_trash.html.erb +18 -0
- data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/show.html.erb +10 -22
- data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +2 -5
- data/app/views/decidim/proposals/collaborative_drafts/_collaborative_actions.html.erb +9 -0
- data/app/views/decidim/proposals/collaborative_drafts/_collaborative_draft_aside.html.erb +0 -15
- data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +4 -6
- data/app/views/decidim/proposals/collaborative_drafts/index.html.erb +6 -2
- data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +27 -11
- data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +29 -9
- data/app/views/decidim/proposals/proposals/_actions.html.erb +4 -7
- data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +17 -22
- data/app/views/decidim/proposals/proposals/_exit_modal.html.erb +17 -0
- data/app/views/decidim/proposals/proposals/_notification_alert_box.html.erb +1 -0
- data/app/views/decidim/proposals/proposals/_proposal_actions.html.erb +19 -0
- data/app/views/decidim/proposals/proposals/_proposal_aside.html.erb +9 -32
- data/app/views/decidim/proposals/proposals/_proposal_voting_rules.html.erb +33 -0
- data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +12 -0
- data/app/views/decidim/proposals/proposals/_update_proposal_voting_rules.html.erb +6 -0
- data/app/views/decidim/proposals/proposals/_vote_button.html.erb +12 -8
- data/app/views/decidim/proposals/proposals/_votes_count.html.erb +2 -1
- data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +1 -7
- data/app/views/decidim/proposals/proposals/index.html.erb +10 -18
- data/app/views/decidim/proposals/proposals/index.js.erb +12 -0
- data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
- data/app/views/decidim/proposals/proposals/show.html.erb +36 -16
- data/config/locales/ar.yml +19 -75
- data/config/locales/bg.yml +7 -91
- data/config/locales/bn-BD.yml +1 -0
- data/config/locales/bs-BA.yml +87 -0
- data/config/locales/ca.yml +213 -73
- data/config/locales/cs.yml +229 -75
- data/config/locales/de.yml +217 -78
- data/config/locales/el.yml +9 -85
- data/config/locales/en.yml +209 -69
- data/config/locales/es-MX.yml +220 -80
- data/config/locales/es-PY.yml +215 -75
- data/config/locales/es.yml +222 -82
- data/config/locales/eu.yml +293 -147
- data/config/locales/fi-plain.yml +222 -81
- data/config/locales/fi.yml +239 -98
- data/config/locales/fr-CA.yml +119 -90
- data/config/locales/fr.yml +118 -89
- data/config/locales/ga-IE.yml +0 -19
- data/config/locales/gl.yml +9 -47
- data/config/locales/he-IL.yml +7 -0
- data/config/locales/hu.yml +7 -68
- data/config/locales/id-ID.yml +9 -36
- data/config/locales/is-IS.yml +0 -21
- data/config/locales/it.yml +10 -56
- data/config/locales/ja.yml +164 -91
- data/config/locales/lt.yml +9 -86
- data/config/locales/lv.yml +8 -47
- data/config/locales/nl.yml +7 -54
- data/config/locales/no.yml +9 -46
- data/config/locales/pl.yml +7 -91
- data/config/locales/pt-BR.yml +7 -77
- data/config/locales/pt.yml +9 -57
- data/config/locales/ro-RO.yml +17 -63
- data/config/locales/ru.yml +0 -25
- data/config/locales/sk.yml +9 -48
- data/config/locales/sl.yml +0 -4
- data/config/locales/sr-CS.yml +0 -14
- data/config/locales/sv.yml +132 -88
- data/config/locales/tr-TR.yml +9 -54
- data/config/locales/uk.yml +0 -25
- data/config/locales/zh-CN.yml +9 -54
- data/config/locales/zh-TW.yml +9 -87
- data/db/migrate/20171220084719_add_published_at_to_proposals.rb +1 -1
- data/db/migrate/20181016132225_add_organization_as_author.rb +1 -1
- data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +1 -1
- data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +3 -3
- data/db/migrate/20210310102839_add_followable_counter_cache_to_proposals.rb +1 -1
- data/db/migrate/20240110203504_create_default_proposal_states.rb +4 -3
- data/db/migrate/20240404202756_add_valuation_assignments_count_to_decidim_proposals_proposals.rb +1 -1
- data/db/migrate/20240617091140_add_email_on_assigned_proposals_to_users.rb +7 -0
- data/db/migrate/20240617170052_add_parent_relation_to_decidim_proposal_notes.rb +7 -0
- data/db/migrate/20240828103755_add_deleted_at_to_decidim_proposals_proposals.rb +8 -0
- data/decidim-proposals.gemspec +2 -2
- data/lib/decidim/api/functions/proposal_finder_helper.rb +12 -0
- data/lib/decidim/api/functions/proposal_list_helper.rb +12 -0
- data/lib/decidim/api/proposal_type.rb +30 -25
- data/lib/decidim/api/proposals_type.rb +5 -22
- data/lib/decidim/proposals/admin_engine.rb +12 -3
- data/lib/decidim/proposals/admin_filter.rb +3 -6
- data/lib/decidim/proposals/component.rb +4 -5
- data/lib/decidim/proposals/download_your_data_proposal_serializer.rb +15 -0
- data/lib/decidim/proposals/engine.rb +5 -0
- data/lib/decidim/proposals/import/proposal_creator.rb +4 -4
- data/lib/decidim/proposals/proposal_serializer.rb +15 -29
- data/lib/decidim/proposals/seeds.rb +21 -17
- data/lib/decidim/proposals/test/factories.rb +8 -6
- data/lib/decidim/proposals/version.rb +1 -1
- data/lib/decidim/proposals.rb +4 -0
- metadata +69 -30
- data/app/commands/decidim/proposals/admin/update_proposal_category.rb +0 -70
- data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +0 -75
- data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +0 -11
- data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +0 -11
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +0 -15
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +0 -21
- data/app/views/decidim/proposals/collaborative_drafts/_actions.html.erb +0 -7
@@ -8,14 +8,13 @@ module Decidim
|
|
8
8
|
include Decidim::TranslatableAttributes
|
9
9
|
include Decidim::AttachmentAttributes
|
10
10
|
include Decidim::ApplicationHelper
|
11
|
+
include Decidim::HasTaxonomyFormAttributes
|
11
12
|
|
12
13
|
mimic :proposal
|
13
14
|
|
14
15
|
attribute :address, String
|
15
16
|
attribute :latitude, Float
|
16
17
|
attribute :longitude, Float
|
17
|
-
attribute :category_id, Integer
|
18
|
-
attribute :scope_id, Integer
|
19
18
|
attribute :attachment, AttachmentForm
|
20
19
|
attribute :position, Integer
|
21
20
|
attribute :created_in_meeting, Boolean
|
@@ -25,46 +24,19 @@ module Decidim
|
|
25
24
|
attachments_attribute :photos
|
26
25
|
|
27
26
|
validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
|
28
|
-
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
29
|
-
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
30
|
-
validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
|
31
27
|
validates :meeting_as_author, presence: true, if: ->(form) { form.created_in_meeting? }
|
32
28
|
|
33
29
|
validate :notify_missing_attachment_if_errored
|
34
30
|
|
35
|
-
delegate :categories, to: :current_component
|
36
|
-
|
37
31
|
def map_model(model)
|
38
32
|
body = translated_attribute(model.body)
|
39
33
|
@suggested_hashtags = Decidim::ContentRenderers::HashtagRenderer.new(body).extra_hashtags.map(&:name).map(&:downcase)
|
40
|
-
|
41
|
-
return unless model.categorization
|
42
|
-
|
43
|
-
self.category_id = model.categorization.decidim_category_id
|
44
|
-
self.scope_id = model.decidim_scope_id
|
45
34
|
end
|
46
35
|
|
47
36
|
alias component current_component
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
# Returns a Decidim::Category
|
52
|
-
def category
|
53
|
-
@category ||= categories.find_by(id: category_id)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
|
57
|
-
#
|
58
|
-
# Returns a Decidim::Scope
|
59
|
-
def scope
|
60
|
-
@scope ||= @attributes["scope_id"].value ? current_component.scopes.find_by(id: @attributes["scope_id"].value) : current_component.scope
|
61
|
-
end
|
62
|
-
|
63
|
-
# Scope identifier
|
64
|
-
#
|
65
|
-
# Returns the scope identifier related to the proposal
|
66
|
-
def scope_id
|
67
|
-
super || scope&.id
|
38
|
+
def participatory_space_manifest
|
39
|
+
@participatory_space_manifest ||= current_component.participatory_space.manifest.name
|
68
40
|
end
|
69
41
|
|
70
42
|
def geocoding_enabled?
|
@@ -6,27 +6,32 @@ module Decidim
|
|
6
6
|
# A form object to be used when admin users want to create a proposal.
|
7
7
|
class ProposalForm < Decidim::Proposals::Admin::ProposalBaseForm
|
8
8
|
include Decidim::HasUploadValidations
|
9
|
+
include Decidim::AttachmentAttributes
|
9
10
|
|
10
11
|
translatable_attribute :title, String do |field, _locale|
|
11
12
|
validates field, length: { in: 15..150 }, if: proc { |resource| resource.send(field).present? }
|
12
13
|
end
|
13
|
-
translatable_attribute :body,
|
14
|
+
translatable_attribute :body, Decidim::Attributes::RichText
|
15
|
+
attribute :attachment, AttachmentForm
|
16
|
+
|
17
|
+
attachments_attribute :documents
|
14
18
|
|
15
19
|
validates :title, :body, translatable_presence: true
|
20
|
+
validates :title, :body, translated_etiquette: true
|
16
21
|
|
17
22
|
validate :notify_missing_attachment_if_errored
|
18
23
|
|
19
24
|
def map_model(model)
|
20
|
-
super
|
25
|
+
super
|
21
26
|
presenter = ProposalPresenter.new(model)
|
22
27
|
|
23
28
|
self.title = presenter.title(all_locales: title.is_a?(Hash))
|
24
29
|
self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
|
25
|
-
self.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
self.documents = model.attachments
|
31
|
+
end
|
32
|
+
|
33
|
+
def notify_missing_attachment_if_errored
|
34
|
+
errors.add(:add_documents, :needs_to_be_reattached) if errors.any? && add_documents.present?
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
@@ -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
|
@@ -13,31 +14,22 @@ module Decidim
|
|
13
14
|
attribute :keep_answers, Boolean
|
14
15
|
attribute :keep_authors, Boolean
|
15
16
|
attribute :states, Array
|
16
|
-
attribute :scope_ids, Array
|
17
17
|
|
18
18
|
validates :origin_component_id, :origin_component, :states, :current_component, presence: true
|
19
19
|
validates :import_proposals, allow_nil: false, acceptance: true
|
20
20
|
validate :valid_states
|
21
21
|
|
22
|
-
VALID_STATES = %w(accepted not_answered evaluating rejected).freeze
|
23
|
-
|
24
22
|
def states_collection
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
30
|
-
end
|
23
|
+
@states_collection ||= ProposalState.where(component: current_component) + [ProposalState.new(token: "not_answered",
|
24
|
+
title: I18n.t(
|
25
|
+
:not_answered, scope: "decidim.proposals.answers"
|
26
|
+
))]
|
31
27
|
end
|
32
28
|
|
33
29
|
def states
|
34
30
|
super.compact_blank
|
35
31
|
end
|
36
32
|
|
37
|
-
def scopes
|
38
|
-
Decidim::Scope.where(organization: current_organization, id: scope_ids)
|
39
|
-
end
|
40
|
-
|
41
33
|
def origin_component
|
42
34
|
@origin_component ||= origin_components.find_by(id: origin_component_id)
|
43
35
|
end
|
@@ -56,7 +48,7 @@ module Decidim
|
|
56
48
|
|
57
49
|
def valid_states
|
58
50
|
return if states.all? do |state|
|
59
|
-
|
51
|
+
states_collection.pluck(:token).include?(state)
|
60
52
|
end
|
61
53
|
|
62
54
|
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
|
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
|
@@ -4,14 +4,6 @@ module Decidim
|
|
4
4
|
module Proposals
|
5
5
|
# A form object to be used when public users want to create a Collaborative Draft.
|
6
6
|
class CollaborativeDraftForm < Decidim::Proposals::ProposalForm
|
7
|
-
def map_model(model)
|
8
|
-
super
|
9
|
-
|
10
|
-
return unless model.categorization
|
11
|
-
|
12
|
-
self.category_id = model.categorization.decidim_category_id
|
13
|
-
end
|
14
|
-
|
15
7
|
def user_group
|
16
8
|
@user_group ||= Decidim::UserGroup.find user_group_id if user_group_id.present?
|
17
9
|
end
|
@@ -7,6 +7,7 @@ module Decidim
|
|
7
7
|
include Decidim::TranslatableAttributes
|
8
8
|
include Decidim::AttachmentAttributes
|
9
9
|
include Decidim::HasUploadValidations
|
10
|
+
include Decidim::HasTaxonomyFormAttributes
|
10
11
|
|
11
12
|
mimic :proposal
|
12
13
|
|
@@ -17,29 +18,24 @@ module Decidim
|
|
17
18
|
attribute :address, String
|
18
19
|
attribute :latitude, Float
|
19
20
|
attribute :longitude, Float
|
20
|
-
attribute :category_id, Integer
|
21
|
-
attribute :scope_id, Integer
|
22
21
|
attribute :attachment, AttachmentForm
|
23
22
|
attribute :suggested_hashtags, Array[String]
|
24
23
|
|
25
24
|
attachments_attribute :documents
|
26
25
|
|
27
|
-
validates :title, :body, presence: true
|
26
|
+
validates :title, :body, presence: true
|
27
|
+
validates :title, :body, etiquette: true
|
28
28
|
validates :title, length: { in: 15..150 }
|
29
29
|
validates :body, proposal_length: {
|
30
30
|
minimum: 15,
|
31
31
|
maximum: ->(record) { record.component.settings.proposal_length }
|
32
32
|
}
|
33
33
|
validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
|
34
|
-
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
35
|
-
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
36
|
-
validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
|
37
34
|
|
38
35
|
validate :body_is_not_bare_template
|
39
36
|
validate :notify_missing_attachment_if_errored
|
40
37
|
|
41
38
|
alias component current_component
|
42
|
-
delegate :categories, to: :current_component
|
43
39
|
|
44
40
|
def map_model(model)
|
45
41
|
self.title = translated_attribute(model.title)
|
@@ -50,34 +46,11 @@ module Decidim
|
|
50
46
|
self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
|
51
47
|
|
52
48
|
self.user_group_id = model.user_groups.first&.id
|
53
|
-
self.category_id = model.categorization.decidim_category_id if model.categorization
|
54
|
-
|
55
|
-
# The scope attribute is with different key (decidim_scope_id), so it
|
56
|
-
# has to be manually mapped.
|
57
|
-
self.scope_id = model.scope.id if model.scope
|
58
|
-
|
59
49
|
self.documents = model.attachments
|
60
50
|
end
|
61
51
|
|
62
|
-
|
63
|
-
|
64
|
-
# Returns a Decidim::Category
|
65
|
-
def category
|
66
|
-
@category ||= categories.find_by(id: category_id)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Finds the Scope from the given scope_id, uses participatory space scope if missing.
|
70
|
-
#
|
71
|
-
# Returns a Decidim::Scope
|
72
|
-
def scope
|
73
|
-
@scope ||= @attributes["scope_id"].value ? current_component.scopes.find_by(id: @attributes["scope_id"].value) : current_component.scope
|
74
|
-
end
|
75
|
-
|
76
|
-
# Scope identifier
|
77
|
-
#
|
78
|
-
# Returns the scope identifier related to the proposal
|
79
|
-
def scope_id
|
80
|
-
super || scope&.id
|
52
|
+
def participatory_space_manifest
|
53
|
+
@participatory_space_manifest ||= current_component.participatory_space.manifest.name
|
81
54
|
end
|
82
55
|
|
83
56
|
def geocoding_enabled?
|
@@ -8,9 +8,34 @@ module Decidim
|
|
8
8
|
Decidim::Proposals::Proposal.find(id)
|
9
9
|
end
|
10
10
|
|
11
|
+
# Public: Generates a select field with the templates of the given component.
|
12
|
+
#
|
13
|
+
# component - A component instance.
|
14
|
+
# prompt - An i18n string to show as prompt
|
15
|
+
#
|
16
|
+
# Returns a String.
|
17
|
+
def bulk_templates_select(component, prompt, id: nil)
|
18
|
+
options_for_select = find_templates_for_select(component)
|
19
|
+
select(:template, :template_id, options_for_select, prompt:, id:)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_templates_for_select(component)
|
23
|
+
return [] unless Decidim.module_installed? :templates
|
24
|
+
return @templates_for_select if @templates_for_select
|
25
|
+
|
26
|
+
templates = Decidim::Templates::Template.where(
|
27
|
+
target: :proposal_answer,
|
28
|
+
templatable: component
|
29
|
+
).order(:templatable_id)
|
30
|
+
|
31
|
+
@templates_for_select = templates.map do |template|
|
32
|
+
[translated_attribute(template.name), template.id]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
11
36
|
# find the valuators for the current space.
|
12
37
|
def find_valuators_for_select(participatory_space, current_user)
|
13
|
-
valuator_roles = participatory_space.user_roles(:valuator)
|
38
|
+
valuator_roles = participatory_space.user_roles(:valuator).order_by_name
|
14
39
|
valuators = Decidim::User.where(id: valuator_roles.pluck(:decidim_user_id)).to_a
|
15
40
|
|
16
41
|
filtered_valuator_roles = valuator_roles.filter do |role|
|
@@ -144,8 +144,16 @@ module Decidim
|
|
144
144
|
).count
|
145
145
|
end
|
146
146
|
|
147
|
+
def layout_item_classes
|
148
|
+
if show_voting_rules?
|
149
|
+
"layout-item lg:pt-4"
|
150
|
+
else
|
151
|
+
"layout-item"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
147
155
|
def show_voting_rules?
|
148
|
-
return false if !votes_enabled? ||
|
156
|
+
return false if !votes_enabled? || votes_blocked?
|
149
157
|
|
150
158
|
return true if vote_limit_enabled?
|
151
159
|
return true if threshold_per_proposal_enabled?
|
@@ -206,41 +214,38 @@ module Decidim
|
|
206
214
|
end
|
207
215
|
|
208
216
|
# rubocop:disable Metrics/CyclomaticComplexity
|
209
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
210
217
|
def filter_sections
|
211
218
|
@filter_sections ||= begin
|
212
219
|
items = []
|
213
220
|
if component_settings.proposal_answering_enabled && current_settings.proposal_answering_enabled
|
214
|
-
items.append(method: :with_any_state, collection: filter_proposals_state_values,
|
221
|
+
items.append(method: :with_any_state, collection: filter_proposals_state_values, label: t("decidim.proposals.proposals.filters.state"), id: "state")
|
215
222
|
end
|
216
|
-
|
217
|
-
items.append(method:
|
218
|
-
|
219
|
-
|
220
|
-
|
223
|
+
current_component.available_taxonomy_filters.each do |taxonomy_filter|
|
224
|
+
items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
|
225
|
+
collection: filter_taxonomy_values_for(taxonomy_filter),
|
226
|
+
label: decidim_sanitize_translated(taxonomy_filter.name),
|
227
|
+
id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
|
221
228
|
end
|
222
229
|
if component_settings.official_proposals_enabled
|
223
|
-
items.append(method: :with_any_origin, collection: filter_origin_values,
|
230
|
+
items.append(method: :with_any_origin, collection: filter_origin_values, label: t("decidim.proposals.proposals.filters.origin"), id: "origin")
|
224
231
|
end
|
225
232
|
if current_user
|
226
|
-
items.append(method: :activity, collection: activity_filter_values,
|
233
|
+
items.append(method: :activity, collection: activity_filter_values, label: t("decidim.proposals.proposals.filters.activity"), id: "activity", type: :radio_buttons)
|
227
234
|
end
|
228
235
|
if @proposals.only_emendations.any?
|
229
|
-
items.append(method: :type, collection: filter_type_values,
|
236
|
+
items.append(method: :type, collection: filter_type_values, label: t("decidim.proposals.proposals.filters.amendment_type"), id: "amendment_type", type: :radio_buttons)
|
230
237
|
end
|
231
238
|
if linked_classes_for(Decidim::Proposals::Proposal).any?
|
232
239
|
items.append(
|
233
240
|
method: :related_to,
|
234
241
|
collection: linked_classes_filter_values_for(Decidim::Proposals::Proposal),
|
235
|
-
|
242
|
+
label: t("decidim.proposals.proposals.filters.related_to"),
|
236
243
|
id: "related_to",
|
237
244
|
type: :radio_buttons
|
238
245
|
)
|
239
246
|
end
|
240
247
|
end
|
241
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
242
248
|
# rubocop:enable Metrics/CyclomaticComplexity
|
243
|
-
|
244
249
|
items.reject { |item| item[:collection].blank? }
|
245
250
|
end
|
246
251
|
|
@@ -248,6 +253,10 @@ module Decidim
|
|
248
253
|
i18n_key = controller_name == "collaborative_drafts" ? "decidim.proposals.collaborative_drafts.name" : "decidim.components.proposals.name"
|
249
254
|
(defined?(current_component) && translated_attribute(current_component&.name).presence) || t(i18n_key)
|
250
255
|
end
|
256
|
+
|
257
|
+
def templates_available?
|
258
|
+
Decidim.module_installed?(:templates) && defined?(Decidim::Templates::Template) && Decidim::Templates::Template.exists?(templatable: current_component)
|
259
|
+
end
|
251
260
|
end
|
252
261
|
end
|
253
262
|
end
|
@@ -43,20 +43,20 @@ module Decidim
|
|
43
43
|
items = [{
|
44
44
|
method: :with_any_state,
|
45
45
|
collection: filter_collaborative_drafts_state_values,
|
46
|
-
|
46
|
+
label: t("decidim.proposals.collaborative_drafts.filters.state"),
|
47
47
|
id: "state"
|
48
48
|
}]
|
49
|
-
|
50
|
-
items.append(method:
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
current_component.available_taxonomy_filters.each do |taxonomy_filter|
|
50
|
+
items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
|
51
|
+
collection: filter_taxonomy_values_for(taxonomy_filter),
|
52
|
+
label: decidim_sanitize_translated(taxonomy_filter.name),
|
53
|
+
id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
|
54
54
|
end
|
55
55
|
if linked_classes_for(Decidim::Proposals::CollaborativeDraft).any?
|
56
56
|
items.append(
|
57
57
|
method: :related_to,
|
58
58
|
collection: linked_classes_filter_values_for(Decidim::Proposals::CollaborativeDraft),
|
59
|
-
|
59
|
+
label: t("decidim.proposals.collaborative_drafts.filters.related_to"),
|
60
60
|
id: "related_to",
|
61
61
|
type: :radio_buttons
|
62
62
|
)
|
@@ -5,24 +5,6 @@ module Decidim
|
|
5
5
|
# This helper include some methods for rendering proposals dynamic maps.
|
6
6
|
module MapHelper
|
7
7
|
include Decidim::ApplicationHelper
|
8
|
-
# Serialize a collection of geocoded proposals to be used by the dynamic map component
|
9
|
-
#
|
10
|
-
# geocoded_proposals - A collection of geocoded proposals
|
11
|
-
def proposals_data_for_map(geocoded_proposals)
|
12
|
-
geocoded_proposals.map do |proposal|
|
13
|
-
proposal_data_for_map(proposal)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def proposal_data_for_map(proposal)
|
18
|
-
proposal
|
19
|
-
.slice(:latitude, :longitude, :address)
|
20
|
-
.merge(
|
21
|
-
title: decidim_html_escape(present(proposal).title),
|
22
|
-
link: proposal_path(proposal),
|
23
|
-
items: cell("decidim/proposals/proposal_metadata", proposal).send(:proposal_items_for_map).to_json
|
24
|
-
)
|
25
|
-
end
|
26
8
|
|
27
9
|
def proposal_preview_data_for_map(proposal)
|
28
10
|
{
|
@@ -47,14 +47,14 @@ module Decidim
|
|
47
47
|
#
|
48
48
|
# Returns true if enabled, false otherwise.
|
49
49
|
def votes_enabled?
|
50
|
-
current_settings.votes_enabled
|
50
|
+
current_settings.respond_to?(:votes_enabled) && current_settings.votes_enabled
|
51
51
|
end
|
52
52
|
|
53
53
|
# Public: Checks if voting is blocked in this step.
|
54
54
|
#
|
55
55
|
# Returns true if blocked, false otherwise.
|
56
56
|
def votes_blocked?
|
57
|
-
current_settings.votes_blocked
|
57
|
+
current_settings.respond_to?(:votes_blocked) && current_settings.votes_blocked
|
58
58
|
end
|
59
59
|
|
60
60
|
# Public: Checks if the current user is allowed to vote in this step.
|
@@ -76,6 +76,19 @@ module Decidim
|
|
76
76
|
votes_count = ProposalVote.where(author: user, proposal: proposals).size
|
77
77
|
component_settings.vote_limit - votes_count
|
78
78
|
end
|
79
|
+
|
80
|
+
# Return the remaining minimum votes for a user if the current component has a vote limit
|
81
|
+
#
|
82
|
+
# user - A User object
|
83
|
+
#
|
84
|
+
# 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?
|
87
|
+
|
88
|
+
votes_count = Decidim::Proposals::ProposalVote.joins(:proposal).where(decidim_proposals_proposals: { decidim_component_id: current_component.id }).where(author: user).count
|
89
|
+
|
90
|
+
component_settings.minimum_votes_per_user - votes_count
|
91
|
+
end
|
79
92
|
end
|
80
93
|
end
|
81
94
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
class ProposalAnswerJob < ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(proposal, attributes, context)
|
10
|
+
answer_form = ProposalAnswerForm.from_params(attributes).with_context(**context)
|
11
|
+
|
12
|
+
Admin::AnswerProposal.call(answer_form, proposal) do
|
13
|
+
on(:ok) { Rails.logger.info "Proposal #{proposal.id} answered successfully." }
|
14
|
+
on(:invalid) { Rails.logger.error "Proposal ID #{proposal.id} could not be updated. Errors: #{answer_form.errors.full_messages}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,6 +5,7 @@ module Decidim
|
|
5
5
|
class CollaborativeDraft < Proposals::ApplicationRecord
|
6
6
|
include Decidim::Resourceable
|
7
7
|
include Decidim::Coauthorable
|
8
|
+
include Decidim::Taxonomizable
|
8
9
|
include Decidim::HasComponent
|
9
10
|
include Decidim::ScopableResource
|
10
11
|
include Decidim::HasReference
|
@@ -64,7 +65,15 @@ module Decidim
|
|
64
65
|
ransacker_text_multi :search_text, [:title, :body]
|
65
66
|
|
66
67
|
def self.ransackable_scopes(_auth_object = nil)
|
67
|
-
[:with_any_state, :related_to, :
|
68
|
+
[:with_any_state, :related_to, :with_any_taxonomies]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.ransackable_attributes(_auth_object = nil)
|
72
|
+
%w(id_string search_text title body)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.ransackable_associations(_auth_object = nil)
|
76
|
+
%w(taxonomies)
|
68
77
|
end
|
69
78
|
end
|
70
79
|
end
|
@@ -7,6 +7,7 @@ module Decidim
|
|
7
7
|
include Decidim::Resourceable
|
8
8
|
include Decidim::Coauthorable
|
9
9
|
include Decidim::HasComponent
|
10
|
+
include Decidim::Taxonomizable
|
10
11
|
include Decidim::ScopableResource
|
11
12
|
include Decidim::HasReference
|
12
13
|
include Decidim::HasCategory
|
@@ -28,6 +29,7 @@ module Decidim
|
|
28
29
|
include Decidim::TranslatableResource
|
29
30
|
include Decidim::TranslatableAttributes
|
30
31
|
include Decidim::FilterableResource
|
32
|
+
include Decidim::SoftDeletable
|
31
33
|
|
32
34
|
def assign_state(token)
|
33
35
|
proposal_state = Decidim::Proposals::ProposalState.where(component:, token:).first
|
@@ -402,18 +404,22 @@ module Decidim
|
|
402
404
|
where(query, value:)
|
403
405
|
end
|
404
406
|
|
405
|
-
def self.ransackable_scopes(
|
406
|
-
|
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]
|
407
|
+
def self.ransackable_scopes(_auth_object = nil)
|
408
|
+
[:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_taxonomies, :valuator_role_ids_has]
|
411
409
|
end
|
412
410
|
|
413
411
|
# Create i18n ransackers for :title and :body.
|
414
412
|
# Create the :search_text ransacker alias for searching from both of these.
|
415
413
|
ransacker_i18n_multi :search_text, [:title, :body]
|
416
414
|
|
415
|
+
def self.ransackable_attributes(_auth_object = nil)
|
416
|
+
%w(id_string search_text title body is_emendation comments_count proposal_votes_count published_at proposal_notes_count)
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.ransackable_associations(_auth_object = nil)
|
420
|
+
%w(taxonomies proposal_state)
|
421
|
+
end
|
422
|
+
|
417
423
|
ransacker :state_published do
|
418
424
|
Arel.sql("CASE
|
419
425
|
WHEN EXISTS (
|
@@ -459,7 +465,7 @@ module Decidim
|
|
459
465
|
end
|
460
466
|
|
461
467
|
def self.export_serializer
|
462
|
-
Decidim::Proposals::
|
468
|
+
Decidim::Proposals::DownloadYourDataProposalSerializer
|
463
469
|
end
|
464
470
|
|
465
471
|
def self.download_your_data_images(user)
|
@@ -476,8 +482,18 @@ module Decidim
|
|
476
482
|
return true if draft?
|
477
483
|
return true if component.settings.proposal_edit_time == "infinite"
|
478
484
|
|
479
|
-
|
480
|
-
|
485
|
+
time_value, time_unit = component.settings.edit_time
|
486
|
+
|
487
|
+
limit_time = case time_unit
|
488
|
+
when "minutes"
|
489
|
+
updated_at + time_value.minutes
|
490
|
+
when "hours"
|
491
|
+
updated_at + time_value.hours
|
492
|
+
else
|
493
|
+
updated_at + time_value.days
|
494
|
+
end
|
495
|
+
|
496
|
+
Time.current < limit_time
|
481
497
|
end
|
482
498
|
|
483
499
|
def process_amendment_state_change!
|
@@ -490,10 +506,51 @@ module Decidim
|
|
490
506
|
end
|
491
507
|
end
|
492
508
|
|
509
|
+
def user_has_actions?(user)
|
510
|
+
return false if authors.include?(user)
|
511
|
+
return false if user&.blocked?
|
512
|
+
return false if user&.deleted?
|
513
|
+
return false unless user&.confirmed?
|
514
|
+
|
515
|
+
true
|
516
|
+
end
|
517
|
+
|
518
|
+
def actions_for_comment(comment, current_user)
|
519
|
+
return if comment.commentable != self
|
520
|
+
return unless authors.include?(current_user)
|
521
|
+
return unless user_has_actions?(comment.author)
|
522
|
+
|
523
|
+
if coauthor_invitations_for(comment.author).any?
|
524
|
+
[
|
525
|
+
{
|
526
|
+
label: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation"),
|
527
|
+
url: EngineRouter.main_proxy(component).cancel_proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
|
528
|
+
icon: "user-forbid-line",
|
529
|
+
method: :delete,
|
530
|
+
data: { confirm: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation_confirm") }
|
531
|
+
}
|
532
|
+
]
|
533
|
+
else
|
534
|
+
[
|
535
|
+
{
|
536
|
+
label: I18n.t("decidim.proposals.actions.mark_as_coauthor"),
|
537
|
+
url: EngineRouter.main_proxy(component).proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
|
538
|
+
icon: "user-add-line",
|
539
|
+
method: :post,
|
540
|
+
data: { confirm: I18n.t("decidim.proposals.actions.mark_as_coauthor_confirm") }
|
541
|
+
}
|
542
|
+
]
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def coauthor_invitations_for(user)
|
547
|
+
Decidim::Notification.where(event_class: "Decidim::Proposals::CoauthorInvitedEvent", resource: self, user:)
|
548
|
+
end
|
549
|
+
|
493
550
|
private
|
494
551
|
|
495
552
|
def copied_from_other_component?
|
496
|
-
linked_resources(:proposals,
|
553
|
+
linked_resources(:proposals, %w(splitted_from_component merged_from_component copied_from_component)).any?
|
497
554
|
end
|
498
555
|
end
|
499
556
|
end
|