decidim-proposals 0.29.2 → 0.30.0.rc2
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.
- checksums.yaml +4 -4
- data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +1 -1
- 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 +13 -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 +9 -0
- 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 +5 -1
- data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
- data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +55 -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 +7 -32
- 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 +1 -0
- data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
- data/app/forms/decidim/proposals/admin/proposal_form.rb +11 -6
- data/app/forms/decidim/proposals/admin/proposals_import_form.rb +0 -5
- 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 +25 -0
- data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
- data/app/helpers/decidim/proposals/application_helper.rb +24 -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 +66 -5
- 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 +248 -3
- data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
- data/app/permissions/decidim/proposals/permissions.rb +42 -0
- 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/proposal_builder.rb +1 -1
- 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 +17 -24
- 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 +14 -26
- data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +0 -3
- 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 +1 -1
- data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
- data/app/views/decidim/proposals/proposals/show.html.erb +35 -15
- data/config/locales/ar.yml +18 -72
- data/config/locales/bg.yml +7 -89
- data/config/locales/bs-BA.yml +0 -13
- data/config/locales/ca.yml +212 -72
- data/config/locales/cs.yml +213 -73
- data/config/locales/de.yml +215 -75
- data/config/locales/el.yml +8 -82
- data/config/locales/en.yml +209 -69
- data/config/locales/es-MX.yml +213 -73
- data/config/locales/es-PY.yml +213 -73
- data/config/locales/es.yml +215 -75
- data/config/locales/eu.yml +217 -78
- data/config/locales/fi-plain.yml +216 -75
- data/config/locales/fi.yml +216 -75
- data/config/locales/fr-CA.yml +118 -87
- data/config/locales/fr.yml +118 -87
- data/config/locales/ga-IE.yml +0 -19
- data/config/locales/gl.yml +8 -43
- data/config/locales/hu.yml +6 -66
- data/config/locales/id-ID.yml +8 -40
- data/config/locales/is-IS.yml +0 -14
- data/config/locales/it.yml +8 -53
- data/config/locales/ja.yml +162 -87
- data/config/locales/lt.yml +8 -83
- data/config/locales/lv.yml +8 -50
- data/config/locales/nl.yml +6 -55
- data/config/locales/no.yml +8 -42
- data/config/locales/pl.yml +6 -88
- data/config/locales/pt-BR.yml +6 -74
- data/config/locales/pt.yml +8 -54
- data/config/locales/ro-RO.yml +10 -54
- data/config/locales/ru.yml +0 -18
- data/config/locales/sk.yml +8 -50
- data/config/locales/sr-CS.yml +0 -14
- data/config/locales/sv.yml +128 -85
- data/config/locales/tr-TR.yml +8 -51
- data/config/locales/uk.yml +0 -18
- data/config/locales/zh-CN.yml +8 -51
- data/config/locales/zh-TW.yml +8 -84
- 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 +1 -1
- 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 +1 -1
- 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 +17 -25
- data/lib/decidim/api/proposals_type.rb +4 -19
- 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 +12 -29
- data/lib/decidim/proposals/seeds.rb +21 -17
- data/lib/decidim/proposals/test/factories.rb +2 -1
- data/lib/decidim/proposals/version.rb +1 -1
- data/lib/decidim/proposals.rb +4 -0
- metadata +65 -29
- 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
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A module with common methods of proposal notes commands
|
7
|
+
module ProposalNotesMethods
|
8
|
+
private
|
9
|
+
|
10
|
+
def parsed_body
|
11
|
+
@parsed_body ||= Decidim::ContentProcessor.parse_with_processor(:user, form.body, current_organization: form.current_organization)
|
12
|
+
end
|
13
|
+
|
14
|
+
def mentioned_users
|
15
|
+
@mentioned_users ||= parsed_body.metadata[:user].users
|
16
|
+
end
|
17
|
+
|
18
|
+
def rewritten_body
|
19
|
+
@rewritten_body ||= parsed_body.rewrite
|
20
|
+
end
|
21
|
+
|
22
|
+
def proposal_valuators
|
23
|
+
@proposal_valuators ||= Decidim::Proposals::ValuationAssignment.where(proposal:).filter_map do |assignment|
|
24
|
+
assignment.valuator unless assignment.valuator == form.current_user
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def admins
|
29
|
+
@admins ||= Decidim::User.org_admins_except_me(form.current_user).all
|
30
|
+
end
|
31
|
+
|
32
|
+
def space_admins
|
33
|
+
@space_admins ||= proposal.participatory_space.user_roles("admin").includes(:user).filter_map do |role|
|
34
|
+
role.user unless role.user == form.current_user
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def mentioned_admins_or_valuators
|
39
|
+
mentioned_users.select do |user|
|
40
|
+
admins.exists?(user.id) ||
|
41
|
+
space_admins.include?(user) ||
|
42
|
+
proposal_valuators.include?(user)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when an admin creates a private note proposal reply.
|
7
|
+
class ReplyProposalNote < Decidim::Command
|
8
|
+
include ProposalNotesMethods
|
9
|
+
|
10
|
+
# Public: Initializes the command.
|
11
|
+
#
|
12
|
+
# form - A form object with the params.
|
13
|
+
# parent - the note to reply.
|
14
|
+
def initialize(form, parent)
|
15
|
+
@form = form
|
16
|
+
@parent = parent
|
17
|
+
@proposal = parent.proposal
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes the command. Broadcasts these events:
|
21
|
+
#
|
22
|
+
# - :ok when everything is valid, together with the note proposal.
|
23
|
+
# - :invalid if the form was not valid and we could not proceed.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def call
|
27
|
+
return broadcast(:invalid) if form.invalid? || invalid_parent?
|
28
|
+
|
29
|
+
create_proposal_note_reply
|
30
|
+
notify_parent_author
|
31
|
+
notify_mentioned
|
32
|
+
|
33
|
+
broadcast(:ok, proposal_note)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :form, :proposal_note, :parent, :proposal
|
39
|
+
|
40
|
+
def invalid_parent?
|
41
|
+
parent.blank? || parent.reply?
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_proposal_note_reply
|
45
|
+
@proposal_note = Decidim.traceability.create!(
|
46
|
+
ProposalNote,
|
47
|
+
form.current_user,
|
48
|
+
{
|
49
|
+
body: rewritten_body,
|
50
|
+
proposal:,
|
51
|
+
parent:,
|
52
|
+
author: form.current_user
|
53
|
+
},
|
54
|
+
resource: {
|
55
|
+
title: proposal.title
|
56
|
+
}
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def parent_author
|
61
|
+
@parent_author ||= parent.author
|
62
|
+
end
|
63
|
+
|
64
|
+
def notify_parent_author
|
65
|
+
return if form.current_user == parent_author
|
66
|
+
|
67
|
+
Decidim::EventsManager.publish(
|
68
|
+
event: "decidim.events.proposals.admin.proposal_note_replied",
|
69
|
+
event_class: Decidim::Proposals::Admin::ProposalNoteCreatedEvent,
|
70
|
+
resource: proposal,
|
71
|
+
affected_users: [parent_author],
|
72
|
+
extra: { note_author_id: form.current_user.id }
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def notify_mentioned
|
77
|
+
affected_users = mentioned_admins_or_valuators - [parent_author]
|
78
|
+
|
79
|
+
return if affected_users.blank?
|
80
|
+
|
81
|
+
Decidim::EventsManager.publish(
|
82
|
+
event: "decidim.events.proposals.admin.proposal_note_mentioned",
|
83
|
+
event_class: Decidim::Proposals::Admin::ProposalNoteCreatedEvent,
|
84
|
+
resource: proposal,
|
85
|
+
affected_users:,
|
86
|
+
extra: { note_author_id: form.current_user.id }
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -53,13 +53,13 @@ module Decidim
|
|
53
53
|
)
|
54
54
|
|
55
55
|
proposals_to_link = links_for(original_proposal)
|
56
|
-
split_proposal.link_resources(proposals_to_link, "
|
56
|
+
split_proposal.link_resources(proposals_to_link, "splitted_from_component")
|
57
57
|
end
|
58
58
|
|
59
59
|
def links_for(proposal)
|
60
60
|
return proposal unless form.same_component?
|
61
61
|
|
62
|
-
proposal.linked_resources(:proposals, "
|
62
|
+
proposal.linked_resources(:proposals, "splitted_from_component")
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
@@ -5,8 +5,7 @@ module Decidim
|
|
5
5
|
module Admin
|
6
6
|
# A command with all the business logic when a user updates a proposal.
|
7
7
|
class UpdateProposal < Decidim::Command
|
8
|
-
include ::Decidim::
|
9
|
-
include GalleryMethods
|
8
|
+
include ::Decidim::MultipleAttachmentsMethods
|
10
9
|
include HashtagsMethods
|
11
10
|
|
12
11
|
# Public: Initializes the command.
|
@@ -31,23 +30,15 @@ module Decidim
|
|
31
30
|
delete_attachment(form.attachment) if delete_attachment?
|
32
31
|
|
33
32
|
if process_attachments?
|
34
|
-
|
35
|
-
|
36
|
-
build_attachment
|
37
|
-
return broadcast(:invalid) if attachment_invalid?
|
38
|
-
end
|
39
|
-
|
40
|
-
if process_gallery?
|
41
|
-
build_gallery
|
42
|
-
return broadcast(:invalid) if gallery_invalid?
|
33
|
+
build_attachments
|
34
|
+
return broadcast(:invalid) if attachments_invalid?
|
43
35
|
end
|
44
36
|
|
45
37
|
transaction do
|
46
38
|
update_proposal
|
47
39
|
update_proposal_author
|
48
|
-
|
49
|
-
|
50
|
-
photo_cleanup!
|
40
|
+
document_cleanup!(include_all_attachments: true)
|
41
|
+
create_attachments(first_weight: first_attachment_weight) if process_attachments?
|
51
42
|
end
|
52
43
|
|
53
44
|
broadcast(:ok, proposal)
|
@@ -69,8 +60,7 @@ module Decidim
|
|
69
60
|
form.current_user,
|
70
61
|
title: parsed_title,
|
71
62
|
body: parsed_body,
|
72
|
-
|
73
|
-
scope: form.scope,
|
63
|
+
taxonomizations: form.taxonomizations,
|
74
64
|
address: form.address,
|
75
65
|
latitude: form.latitude,
|
76
66
|
longitude: form.longitude,
|
@@ -90,6 +80,10 @@ module Decidim
|
|
90
80
|
|
91
81
|
proposal.photos.count
|
92
82
|
end
|
83
|
+
|
84
|
+
def delete_attachment?
|
85
|
+
@form.attachment&.delete_file.present?
|
86
|
+
end
|
93
87
|
end
|
94
88
|
end
|
95
89
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when an admin batch updates proposals taxonomies.
|
7
|
+
class UpdateProposalTaxonomies < UpdateResourcesTaxonomies
|
8
|
+
include TranslatableAttributes
|
9
|
+
# Public: Initializes the command.
|
10
|
+
#
|
11
|
+
# taxonomy_ids - the taxonomy ids to update
|
12
|
+
# proposal_ids - the proposals ids to update.
|
13
|
+
def initialize(taxonomy_ids, proposal_ids, organization)
|
14
|
+
super(taxonomy_ids, Decidim::Proposals::Proposal.where(id: proposal_ids), organization)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_after_hooks(resource)
|
18
|
+
notify_author(resource) if resource.coauthorships.any?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def notify_author(proposal)
|
24
|
+
Decidim::EventsManager.publish(
|
25
|
+
event: "decidim.events.proposals.proposal_update_taxonomies",
|
26
|
+
event_class: Decidim::Proposals::UpdateProposalTaxonomiesEvent,
|
27
|
+
resource: proposal,
|
28
|
+
affected_users: proposal.notifiable_identities
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# A command with all the business logic when a user cancels an invitation to be a coauthor.
|
6
|
+
class CancelCoauthorship < Decidim::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# proposal - The proposal to add a coauthor to.
|
10
|
+
# coauthor - The user to invite as coauthor.
|
11
|
+
def initialize(proposal, coauthor)
|
12
|
+
@proposal = proposal
|
13
|
+
@coauthor = coauthor
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid, together with the proposal.
|
19
|
+
# - :invalid if the coauthor is not valid.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless @coauthor
|
24
|
+
return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
|
25
|
+
|
26
|
+
@proposal.coauthor_invitations_for(@coauthor).destroy_all
|
27
|
+
|
28
|
+
broadcast(:ok)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -63,8 +63,7 @@ module Decidim
|
|
63
63
|
draft = CollaborativeDraft.new(
|
64
64
|
title: title_with_hashtags,
|
65
65
|
body: body_with_hashtags,
|
66
|
-
|
67
|
-
scope: form.scope,
|
66
|
+
taxonomizations: form.taxonomizations,
|
68
67
|
component: form.component,
|
69
68
|
address: form.address,
|
70
69
|
latitude: form.latitude,
|
@@ -82,8 +82,7 @@ module Decidim
|
|
82
82
|
component: form.component
|
83
83
|
)
|
84
84
|
|
85
|
-
proposal.
|
86
|
-
proposal.scope = form.scope if form.scope_id.present?
|
85
|
+
proposal.taxonomizations = form.taxonomizations if form.taxonomizations.present?
|
87
86
|
proposal.documents = form.documents if form.documents.present?
|
88
87
|
proposal.address = form.address if form.has_address? && !form.geocoded?
|
89
88
|
proposal.add_coauthor(@current_user, user_group:)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# A command with all the business logic when a user invites a coauthor to a proposal
|
6
|
+
class InviteCoauthor < Decidim::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# proposal - The proposal to add a coauthor to.
|
10
|
+
# coauthor - The user to invite as coauthor.
|
11
|
+
def initialize(proposal, coauthor)
|
12
|
+
@proposal = proposal
|
13
|
+
@coauthor = coauthor
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid, together with the proposal.
|
19
|
+
# - :invalid if the coauthor is not valid.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless @coauthor
|
24
|
+
return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
|
25
|
+
|
26
|
+
transaction do
|
27
|
+
generate_notifications
|
28
|
+
end
|
29
|
+
|
30
|
+
broadcast(:ok)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def generate_notifications
|
36
|
+
Decidim::EventsManager.publish(
|
37
|
+
event: "decidim.events.proposals.coauthor_invited",
|
38
|
+
event_class: Decidim::Proposals::CoauthorInvitedEvent,
|
39
|
+
resource: @proposal,
|
40
|
+
affected_users: [@coauthor]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -62,7 +62,6 @@ module Decidim
|
|
62
62
|
fields[:title] = { I18n.locale => parsed_title }
|
63
63
|
fields[:body] = { I18n.locale => parsed_body }
|
64
64
|
fields[:component] = @collaborative_draft.component
|
65
|
-
fields[:scope] = @collaborative_draft.scope
|
66
65
|
fields[:address] = @collaborative_draft.address
|
67
66
|
fields[:published_at] = Time.current
|
68
67
|
|
@@ -78,7 +77,7 @@ module Decidim
|
|
78
77
|
) do
|
79
78
|
new_proposal = Proposal.new(proposal_attributes)
|
80
79
|
new_proposal.coauthorships = @collaborative_draft.coauthorships
|
81
|
-
new_proposal.
|
80
|
+
new_proposal.taxonomies = @collaborative_draft.taxonomies
|
82
81
|
new_proposal.attachments = @collaborative_draft.attachments
|
83
82
|
new_proposal.save!
|
84
83
|
new_proposal
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# A command with all the business logic when a user rejects and invitation to be a proposal co-author
|
6
|
+
class RejectCoauthorship < Decidim::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# proposal - The proposal to add a coauthor to.
|
10
|
+
# coauthor - The user to invite as coauthor.
|
11
|
+
def initialize(proposal, coauthor)
|
12
|
+
@proposal = proposal
|
13
|
+
@coauthor = coauthor
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid, together with the proposal.
|
19
|
+
# - :invalid if the coauthor is not valid.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless @coauthor
|
24
|
+
return broadcast(:invalid) if @proposal.authors.include?(@coauthor)
|
25
|
+
|
26
|
+
@proposal.coauthor_invitations_for(@coauthor).destroy_all
|
27
|
+
generate_notifications
|
28
|
+
|
29
|
+
broadcast(:ok)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def generate_notifications
|
35
|
+
# notify the author that the co-author has rejected the invitation
|
36
|
+
Decidim::EventsManager.publish(
|
37
|
+
event: "decidim.events.proposals.coauthor_rejected_invite",
|
38
|
+
event_class: Decidim::Proposals::CoauthorRejectedInviteEvent,
|
39
|
+
resource: @proposal,
|
40
|
+
affected_users: @proposal.authors,
|
41
|
+
extra: { coauthor_id: @coauthor.id }
|
42
|
+
)
|
43
|
+
|
44
|
+
# notify the co-author of his own decision
|
45
|
+
Decidim::EventsManager.publish(
|
46
|
+
event: "decidim.events.proposals.rejected_coauthorship",
|
47
|
+
event_class: Decidim::Proposals::RejectedCoauthorshipEvent,
|
48
|
+
resource: @proposal,
|
49
|
+
affected_users: [@coauthor]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -19,7 +19,11 @@ module Decidim
|
|
19
19
|
def base_query
|
20
20
|
return collection.order(:position) if current_component.settings.participatory_texts_enabled?
|
21
21
|
|
22
|
-
accessible_proposals_collection
|
22
|
+
return accessible_proposals_collection unless taxonomy_order_or_search?
|
23
|
+
|
24
|
+
# this is a trick to avoid duplicates when using search in associations as suggested in:
|
25
|
+
# https://activerecord-hackery.github.io/ransack/going-further/other-notes/#problem-with-distinct-selects
|
26
|
+
accessible_proposals_collection.includes(:taxonomies).joins(:taxonomies)
|
23
27
|
end
|
24
28
|
|
25
29
|
def accessible_proposals_collection
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Proposals
|
7
|
+
module Admin
|
8
|
+
module NeedsInterpolations
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
def populate_interpolations(text, proposal)
|
13
|
+
return populate_string_interpolations(text, proposal) if text.is_a?(String)
|
14
|
+
|
15
|
+
populate_hash_interpolations(text, proposal)
|
16
|
+
end
|
17
|
+
|
18
|
+
def populate_hash_interpolations(hash, proposal)
|
19
|
+
return hash unless hash.is_a?(Hash)
|
20
|
+
|
21
|
+
hash.transform_values do |value|
|
22
|
+
populate_interpolations(value, proposal)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def populate_string_interpolations(value, proposal)
|
27
|
+
value = value.gsub("%{organization}", translated_attribute(proposal.organization.name))
|
28
|
+
value = value.gsub("%{name}", author_name(proposal))
|
29
|
+
value.gsub("%{admin}", current_user.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def author_name(proposal)
|
33
|
+
name = proposal.creator_author.try(:title) || proposal.creator_author.try(:name)
|
34
|
+
translated_attribute(name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -5,6 +5,10 @@ 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::NeedsInterpolations
|
10
|
+
include Decidim::Proposals::Admin::Filterable
|
11
|
+
|
8
12
|
helper_method :proposal
|
9
13
|
|
10
14
|
helper Proposals::ApplicationHelper
|
@@ -14,13 +18,13 @@ module Decidim
|
|
14
18
|
|
15
19
|
def edit
|
16
20
|
enforce_permission_to(:create, :proposal_answer, proposal:)
|
17
|
-
@form = form(
|
21
|
+
@form = form(ProposalAnswerForm).from_model(proposal)
|
18
22
|
end
|
19
23
|
|
20
24
|
def update
|
21
25
|
enforce_permission_to(:create, :proposal_answer, proposal:)
|
22
26
|
@notes_form = form(ProposalNoteForm).instance
|
23
|
-
@answer_form = form(
|
27
|
+
@answer_form = form(ProposalAnswerForm).from_params(params)
|
24
28
|
|
25
29
|
Admin::AnswerProposal.call(@answer_form, proposal) do
|
26
30
|
on(:ok) do
|
@@ -35,6 +39,33 @@ module Decidim
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
42
|
+
def update_multiple_answers
|
43
|
+
valid_proposals = []
|
44
|
+
failed_proposals = []
|
45
|
+
proposals.each do |proposal|
|
46
|
+
proposal_answer_form = answer_form(proposal)
|
47
|
+
if allowed_to?(:create, :proposal_answer, proposal:) && proposal_answer_form.valid? && !proposal.emendation?
|
48
|
+
valid_proposals << proposal.id
|
49
|
+
ProposalAnswerJob.perform_later(proposal, proposal_answer_form.attributes, { current_organization:, current_component:, current_user: })
|
50
|
+
else
|
51
|
+
failed_proposals << proposal.id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if failed_proposals.any?
|
56
|
+
flash[:alert] =
|
57
|
+
t("proposals.answer.bulk_answer_error", scope: "decidim.proposals.admin", template: strip_tags(translated_attribute(template&.name)),
|
58
|
+
proposals: failed_proposals.join(", "))
|
59
|
+
end
|
60
|
+
if valid_proposals.any?
|
61
|
+
flash[:notice] =
|
62
|
+
I18n.t("proposals.answer.bulk_answer_success", scope: "decidim.proposals.admin", template: strip_tags(translated_attribute(template&.name)),
|
63
|
+
count: valid_proposals.count)
|
64
|
+
end
|
65
|
+
|
66
|
+
redirect_to EngineRouter.admin_proxy(current_component).root_path
|
67
|
+
end
|
68
|
+
|
38
69
|
private
|
39
70
|
|
40
71
|
def skip_manage_component_permission
|
@@ -44,6 +75,28 @@ module Decidim
|
|
44
75
|
def proposal
|
45
76
|
@proposal ||= Proposal.where(component: current_component).find(params[:id])
|
46
77
|
end
|
78
|
+
|
79
|
+
def proposals
|
80
|
+
@proposals ||= Proposal.where(component: current_component).where(id: params[:proposal_ids])
|
81
|
+
end
|
82
|
+
|
83
|
+
def collection
|
84
|
+
@collection ||= Proposal.where(component: current_component).not_hidden.published
|
85
|
+
end
|
86
|
+
|
87
|
+
def template
|
88
|
+
return unless Decidim.module_installed?(:templates)
|
89
|
+
|
90
|
+
@template ||= Decidim::Templates::Template.find_by(id: params[:template][:template_id])
|
91
|
+
end
|
92
|
+
|
93
|
+
def answer_form(proposal)
|
94
|
+
form(ProposalAnswerForm).from_params(answer: populate_interpolations(template&.description, proposal), internal_state: proposal_state&.token)
|
95
|
+
end
|
96
|
+
|
97
|
+
def proposal_state
|
98
|
+
@proposal_state ||= Decidim::Proposals::ProposalState.find_by(id: template&.field_values&.dig("proposal_state_id"))
|
99
|
+
end
|
47
100
|
end
|
48
101
|
end
|
49
102
|
end
|
@@ -7,6 +7,24 @@ module Decidim
|
|
7
7
|
class ProposalNotesController < Admin::ApplicationController
|
8
8
|
helper_method :proposal
|
9
9
|
|
10
|
+
def reply
|
11
|
+
enforce_permission_to(:create, :proposal_note, proposal:)
|
12
|
+
parent_note = proposal.notes.find(params[:id])
|
13
|
+
@form = form(ProposalNoteForm).from_params(params)
|
14
|
+
|
15
|
+
ReplyProposalNote.call(@form, parent_note) do
|
16
|
+
on(:ok) do
|
17
|
+
flash[:notice] = I18n.t("proposal_notes.reply.success", scope: "decidim.proposals.admin")
|
18
|
+
redirect_to proposal_path(id: proposal.id)
|
19
|
+
end
|
20
|
+
|
21
|
+
on(:invalid) do
|
22
|
+
flash.keep[:alert] = I18n.t("proposal_notes.reply.error", scope: "decidim.proposals.admin")
|
23
|
+
redirect_to proposal_path(id: proposal.id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
10
28
|
def create
|
11
29
|
enforce_permission_to(:create, :proposal_note, proposal:)
|
12
30
|
@form = form(ProposalNoteForm).from_params(params)
|