decidim-plans 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +157 -0
- data/Rakefile +48 -0
- data/app/assets/config/admin/decidim_plans_manifest.js +3 -0
- data/app/assets/config/decidim_plans_manifest.css +4 -0
- data/app/assets/config/decidim_plans_manifest.js +2 -0
- data/app/assets/images/decidim/plans/icon.svg +3 -0
- data/app/assets/javascripts/decidim/plans/admin/plans.js.es6 +3 -0
- data/app/assets/javascripts/decidim/plans/admin/sections.js.es6 +78 -0
- data/app/assets/javascripts/decidim/plans/multifield/component.js.es6 +101 -0
- data/app/assets/javascripts/decidim/plans/multifield.js.es6 +5 -0
- data/app/assets/javascripts/decidim/plans/plans_form.js.es6 +9 -0
- data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +41 -0
- data/app/assets/javascripts/decidim/plans/remaining_characters.es6 +42 -0
- data/app/assets/javascripts/decidim/plans/social_share.js +2 -0
- data/app/assets/javascripts/decidim/plans/tab_focus.es6 +20 -0
- data/app/assets/stylesheets/decidim/plans/plans_form.scss +26 -0
- data/app/assets/stylesheets/decidim/plans/proposal_picker.scss +3 -0
- data/app/assets/stylesheets/decidim/plans/social_share.css.scss +22 -0
- data/app/cells/decidim/plans/author/flag.erb +5 -0
- data/app/cells/decidim/plans/author/withdraw.erb +6 -0
- data/app/cells/decidim/plans/author_cell.rb +9 -0
- data/app/cells/decidim/plans/coauthorships_cell.rb +23 -0
- data/app/cells/decidim/plans/collapsible_authors/show.erb +17 -0
- data/app/cells/decidim/plans/collapsible_authors_cell.rb +9 -0
- data/app/cells/decidim/plans/plan_activity_cell.rb +19 -0
- data/app/cells/decidim/plans/plan_cell.rb +59 -0
- data/app/cells/decidim/plans/plan_m/footer.erb +8 -0
- data/app/cells/decidim/plans/plan_m_cell.rb +62 -0
- data/app/commands/decidim/plans/accept_access_to_plan.rb +53 -0
- data/app/commands/decidim/plans/admin/answer_plan.rb +82 -0
- data/app/commands/decidim/plans/admin/create_plan.rb +102 -0
- data/app/commands/decidim/plans/admin/update_plan.rb +98 -0
- data/app/commands/decidim/plans/admin/update_sections.rb +54 -0
- data/app/commands/decidim/plans/attachment_methods.rb +78 -0
- data/app/commands/decidim/plans/create_plan.rb +90 -0
- data/app/commands/decidim/plans/destroy_plan.rb +33 -0
- data/app/commands/decidim/plans/nested_updater.rb +26 -0
- data/app/commands/decidim/plans/publish_plan.rb +79 -0
- data/app/commands/decidim/plans/reject_access_to_plan.rb +46 -0
- data/app/commands/decidim/plans/request_access_to_plan.rb +50 -0
- data/app/commands/decidim/plans/respond_to_access_request.rb +76 -0
- data/app/commands/decidim/plans/update_plan.rb +90 -0
- data/app/commands/decidim/plans/withdraw_plan.rb +50 -0
- data/app/controllers/concerns/decidim/plans/orderable.rb +53 -0
- data/app/controllers/decidim/plans/admin/application_controller.rb +16 -0
- data/app/controllers/decidim/plans/admin/plan_answers_controller.rb +40 -0
- data/app/controllers/decidim/plans/admin/plans_controller.rb +80 -0
- data/app/controllers/decidim/plans/admin/sections_controller.rb +75 -0
- data/app/controllers/decidim/plans/application_controller.rb +30 -0
- data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +53 -0
- data/app/controllers/decidim/plans/plan_widgets_controller.rb +19 -0
- data/app/controllers/decidim/plans/plans_controller.rb +192 -0
- data/app/controllers/decidim/plans/versions_controller.rb +51 -0
- data/app/events/decidim/plans/accepted_plan_event.rb +9 -0
- data/app/events/decidim/plans/evaluating_plan_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_rejected_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_request_event.rb +27 -0
- data/app/events/decidim/plans/plan_access_requested_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_rejected_event.rb +8 -0
- data/app/events/decidim/plans/publish_plan_event.rb +25 -0
- data/app/events/decidim/plans/rejected_plan_event.rb +9 -0
- data/app/forms/decidim/plans/accept_access_to_plan_form.rb +10 -0
- data/app/forms/decidim/plans/access_to_plan_form.rb +35 -0
- data/app/forms/decidim/plans/admin/plan_answer_form.rb +19 -0
- data/app/forms/decidim/plans/admin/plan_form.rb +88 -0
- data/app/forms/decidim/plans/admin/plan_sections_form.rb +20 -0
- data/app/forms/decidim/plans/admin/section_form.rb +34 -0
- data/app/forms/decidim/plans/attachment_form.rb +20 -0
- data/app/forms/decidim/plans/content_form.rb +58 -0
- data/app/forms/decidim/plans/plan_form.rb +85 -0
- data/app/forms/decidim/plans/reject_access_to_plan_form.rb +9 -0
- data/app/forms/decidim/plans/request_access_to_plan_form.rb +20 -0
- data/app/helpers/decidim/plans/admin/application_helper.rb +19 -0
- data/app/helpers/decidim/plans/application_helper.rb +78 -0
- data/app/helpers/decidim/plans/attached_proposals_helper.rb +44 -0
- data/app/helpers/decidim/plans/attachments_helper.rb +59 -0
- data/app/helpers/decidim/plans/cells_helper.rb +26 -0
- data/app/helpers/decidim/plans/plan_cells_helper.rb +67 -0
- data/app/helpers/decidim/plans/remaining_characters_helper.rb +32 -0
- data/app/helpers/decidim/plans/traceability_helper.rb +79 -0
- data/app/helpers/decidim/plans/user_group_helper.rb +23 -0
- data/app/models/concerns/decidim/plans/traceable.rb +48 -0
- data/app/models/decidim/plans/application_record.rb +10 -0
- data/app/models/decidim/plans/attached_proposal.rb +12 -0
- data/app/models/decidim/plans/content.rb +21 -0
- data/app/models/decidim/plans/paper_trail/version.rb +26 -0
- data/app/models/decidim/plans/paper_trail/version_association.rb +14 -0
- data/app/models/decidim/plans/plan.rb +206 -0
- data/app/models/decidim/plans/plan_collaborator_request.rb +13 -0
- data/app/models/decidim/plans/section.rb +16 -0
- data/app/permissions/decidim/plans/admin/permissions.rb +44 -0
- data/app/permissions/decidim/plans/permissions.rb +67 -0
- data/app/presenters/decidim/plans/admin_log/plan_presenter.rb +49 -0
- data/app/presenters/decidim/plans/admin_log/value_types/plan_state_presenter.rb +16 -0
- data/app/presenters/decidim/plans/content_presenter.rb +25 -0
- data/app/presenters/decidim/plans/log/resource_presenter.rb +18 -0
- data/app/presenters/decidim/plans/official_author_presenter.rb +38 -0
- data/app/presenters/decidim/plans/plan_presenter.rb +48 -0
- data/app/queries/decidim/plans/filtered_plans.rb +37 -0
- data/app/services/decidim/plans/diff_renderer/base.rb +108 -0
- data/app/services/decidim/plans/diff_renderer/categorization.rb +23 -0
- data/app/services/decidim/plans/diff_renderer/content.rb +33 -0
- data/app/services/decidim/plans/diff_renderer/plan.rb +25 -0
- data/app/services/decidim/plans/loggability.rb +19 -0
- data/app/services/decidim/plans/plan_builder.rb +68 -0
- data/app/services/decidim/plans/plan_search.rb +95 -0
- data/app/services/decidim/plans/tracer.rb +29 -0
- data/app/views/decidim/plans/admin/plan_answers/edit.html.erb +22 -0
- data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +9 -0
- data/app/views/decidim/plans/admin/plans/_form.html.erb +62 -0
- data/app/views/decidim/plans/admin/plans/_js-callout.html.erb +6 -0
- data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +53 -0
- data/app/views/decidim/plans/admin/plans/edit.html.erb +7 -0
- data/app/views/decidim/plans/admin/plans/index.html.erb +64 -0
- data/app/views/decidim/plans/admin/plans/new.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_bulk-actions.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_form.html.erb +27 -0
- data/app/views/decidim/plans/admin/sections/_section.html.erb +66 -0
- data/app/views/decidim/plans/admin/sections/index.html.erb +7 -0
- data/app/views/decidim/plans/admin/shared/_info_plan.html.erb +17 -0
- data/app/views/decidim/plans/attached_proposals/_proposals.html.erb +12 -0
- data/app/views/decidim/plans/plan_widgets/show.html.erb +3 -0
- data/app/views/decidim/plans/plans/_accept_request_access_form.html.erb +8 -0
- data/app/views/decidim/plans/plans/_attached_proposals.html.erb +10 -0
- data/app/views/decidim/plans/plans/_collaborator_requests.html.erb +20 -0
- data/app/views/decidim/plans/plans/_content.html.erb +6 -0
- data/app/views/decidim/plans/plans/_contents.html.erb +8 -0
- data/app/views/decidim/plans/plans/_count.html.erb +1 -0
- data/app/views/decidim/plans/plans/_filters.html.erb +32 -0
- data/app/views/decidim/plans/plans/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/plans/plans/_form.html.erb +57 -0
- data/app/views/decidim/plans/plans/_new_plan_button.html.erb +11 -0
- data/app/views/decidim/plans/plans/_plan.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plan_preview.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plans.html.erb +14 -0
- data/app/views/decidim/plans/plans/_reject_request_access_form.html.erb +10 -0
- data/app/views/decidim/plans/plans/_request_access_form.html.erb +7 -0
- data/app/views/decidim/plans/plans/edit.html.erb +21 -0
- data/app/views/decidim/plans/plans/index.html.erb +28 -0
- data/app/views/decidim/plans/plans/index.js.erb +10 -0
- data/app/views/decidim/plans/plans/new.html.erb +15 -0
- data/app/views/decidim/plans/plans/preview.html.erb +15 -0
- data/app/views/decidim/plans/plans/show.html.erb +133 -0
- data/app/views/decidim/plans/shared/_attachment_fields.html.erb +45 -0
- data/app/views/decidim/plans/shared/_attachments.html.erb +23 -0
- data/app/views/decidim/plans/shared/_remaining_characters_container.html.erb +12 -0
- data/app/views/decidim/plans/shared/_section_content_field.html.erb +39 -0
- data/app/views/decidim/plans/versions/_version.html.erb +20 -0
- data/app/views/decidim/plans/versions/index.html.erb +34 -0
- data/app/views/decidim/plans/versions/show.html.erb +41 -0
- data/config/locales/en.yml +341 -0
- data/config/locales/fi.yml +341 -0
- data/config/locales/sv.yml +341 -0
- data/db/migrate/20181230110225_enable_pg_trgm_extension_for_plans.rb +18 -0
- data/db/migrate/20181230110848_create_decidim_plans.rb +20 -0
- data/db/migrate/20181230111731_create_decidim_plans_sections.rb +13 -0
- data/db/migrate/20181230111931_create_decidim_plans_plan_contents.rb +14 -0
- data/db/migrate/20181230122035_create_decidim_plans_plan_collaborator_requests.rb +12 -0
- data/db/migrate/20190110125855_create_decidim_plans_attached_proposals.rb +12 -0
- data/db/migrate/20190129140441_add_help_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129143607_add_mandatory_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129172040_add_section_type_to_decidim_plans_sections.rb +12 -0
- data/db/migrate/20190129231439_add_answer_length_to_decidim_plans_sections.rb +11 -0
- data/db/migrate/20190202123044_create_version_associations.rb +25 -0
- data/db/migrate/20190202123045_add_transaction_id_column_to_versions.rb +15 -0
- data/db/migrate/20190202200716_add_update_token_to_decidim_plans.rb +7 -0
- data/lib/decidim/plans/admin.rb +10 -0
- data/lib/decidim/plans/admin_engine.rb +29 -0
- data/lib/decidim/plans/component.rb +163 -0
- data/lib/decidim/plans/engine.rb +43 -0
- data/lib/decidim/plans/locale_aware.rb +32 -0
- data/lib/decidim/plans/optionally_translatable_attributes.rb +85 -0
- data/lib/decidim/plans/paper_trail/record_trail.rb +24 -0
- data/lib/decidim/plans/paper_trail.rb +10 -0
- data/lib/decidim/plans/test/factories.rb +130 -0
- data/lib/decidim/plans/version.rb +8 -0
- data/lib/decidim/plans.rb +24 -0
- metadata +323 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A cell to display when a plan has been published.
|
6
|
+
class PlanActivityCell < ActivityCell
|
7
|
+
def title
|
8
|
+
I18n.t(
|
9
|
+
"decidim.plans.last_activity.new_plan_at_html",
|
10
|
+
link: participatory_space_link
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource_link_text
|
15
|
+
Decidim::Plans::PlanPresenter.new(resource).title
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cell/partial"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Plans
|
7
|
+
# This cell renders the plan card for an instance of a Plan
|
8
|
+
# the default size is the Medium Card (:m)
|
9
|
+
class PlanCell < Decidim::ViewModel
|
10
|
+
include PlanCellsHelper
|
11
|
+
include Cell::ViewModel::Partial
|
12
|
+
include Messaging::ConversationHelper
|
13
|
+
|
14
|
+
delegate :user_signed_in?, to: :parent_controller
|
15
|
+
|
16
|
+
def show
|
17
|
+
cell card_size, model, options
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def current_user
|
23
|
+
context[:current_user]
|
24
|
+
end
|
25
|
+
|
26
|
+
def card_size
|
27
|
+
"decidim/plans/plan_m"
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource_path
|
31
|
+
resource_locator(model).path
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_participatory_space
|
35
|
+
model.component.participatory_space
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_component
|
39
|
+
model.component
|
40
|
+
end
|
41
|
+
|
42
|
+
def component_name
|
43
|
+
translated_attribute model.component.name
|
44
|
+
end
|
45
|
+
|
46
|
+
def component_type_name
|
47
|
+
model.class.model_name.human
|
48
|
+
end
|
49
|
+
|
50
|
+
def participatory_space_name
|
51
|
+
translated_attribute current_participatory_space.title
|
52
|
+
end
|
53
|
+
|
54
|
+
def participatory_space_type_name
|
55
|
+
translated_attribute current_participatory_space.model_name.human
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<div class="card__footer">
|
2
|
+
<div class="card__support">
|
3
|
+
<div class="card__support__data"></div>
|
4
|
+
<% if has_link_to_resource? %>
|
5
|
+
<%= link_to t("decidim.plans.plans.plan.view_plan"), resource_path, class: "card__button button--sc light button small secondary" %>
|
6
|
+
<% end %>
|
7
|
+
</div>
|
8
|
+
</div>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cell/partial"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Plans
|
7
|
+
# This cell renders a plan with its M-size card.
|
8
|
+
class PlanMCell < Decidim::CardMCell
|
9
|
+
include PlanCellsHelper
|
10
|
+
|
11
|
+
def badge
|
12
|
+
render if has_badge?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def title
|
18
|
+
present(model).title
|
19
|
+
end
|
20
|
+
|
21
|
+
def body
|
22
|
+
present(model).body
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_state?
|
26
|
+
model.published?
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_badge?
|
30
|
+
answered? || withdrawn?
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_link_to_resource?
|
34
|
+
model.published?
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
model_body = strip_tags(body)
|
39
|
+
|
40
|
+
if options[:full_description]
|
41
|
+
model_body.gsub(/\n/, "<br>")
|
42
|
+
else
|
43
|
+
truncate(model_body, length: 100)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def badge_classes
|
48
|
+
return super unless options[:full_badge]
|
49
|
+
|
50
|
+
state_classes.concat(["label", "plan-status"]).join(" ")
|
51
|
+
end
|
52
|
+
|
53
|
+
def statuses
|
54
|
+
[:creation_date, :follow, :comments_count]
|
55
|
+
end
|
56
|
+
|
57
|
+
def creation_date_status
|
58
|
+
l(model.published_at.to_date, format: :decidim_short)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic to accept a user request to
|
6
|
+
# contribute to a plan.
|
7
|
+
class AcceptAccessToPlan < RespondToAccessRequest
|
8
|
+
# Executes the command. Broadcasts these events:
|
9
|
+
#
|
10
|
+
# - :ok when everything is valid.
|
11
|
+
# - :invalid if it wasn't valid and we couldn't proceed.
|
12
|
+
#
|
13
|
+
# Returns nothing.
|
14
|
+
def call
|
15
|
+
return broadcast(:invalid) if @form.invalid?
|
16
|
+
return broadcast(:invalid) if @current_user.nil?
|
17
|
+
|
18
|
+
transaction do
|
19
|
+
@plan.requesters.delete @requester_user
|
20
|
+
|
21
|
+
Decidim::Coauthorship.create(
|
22
|
+
coauthorable: @plan,
|
23
|
+
author: @requester_user
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
notify_plan_requester
|
28
|
+
notify_plan_authors
|
29
|
+
broadcast(:ok, @requester_user)
|
30
|
+
end
|
31
|
+
|
32
|
+
def recipient_ids
|
33
|
+
@plan.authors.pluck(:id) - [@requester_user.id]
|
34
|
+
end
|
35
|
+
|
36
|
+
def authors_event
|
37
|
+
"decidim.events.plans.plan_access_accepted"
|
38
|
+
end
|
39
|
+
|
40
|
+
def authors_event_class
|
41
|
+
Decidim::Plans::PlanAccessAcceptedEvent
|
42
|
+
end
|
43
|
+
|
44
|
+
def requester_event
|
45
|
+
"decidim.events.plans.plan_access_requester_accepted"
|
46
|
+
end
|
47
|
+
|
48
|
+
def requester_event_class
|
49
|
+
Decidim::Plans::PlanAccessRequesterAcceptedEvent
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when an admin answers a plan.
|
7
|
+
class AnswerPlan < Rectify::Command
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# form - A form object with the params.
|
11
|
+
# plan - The plan to write the answer for.
|
12
|
+
def initialize(form, plan)
|
13
|
+
@form = form
|
14
|
+
@plan = plan
|
15
|
+
end
|
16
|
+
|
17
|
+
# Executes the command. Broadcasts these events:
|
18
|
+
#
|
19
|
+
# - :ok when everything is valid.
|
20
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
21
|
+
#
|
22
|
+
# Returns nothing.
|
23
|
+
def call
|
24
|
+
return broadcast(:invalid) if form.invalid?
|
25
|
+
|
26
|
+
answer_plan
|
27
|
+
notify_followers
|
28
|
+
|
29
|
+
broadcast(:ok)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :form, :plan
|
35
|
+
|
36
|
+
def answer_plan
|
37
|
+
Decidim.traceability.perform_action!(
|
38
|
+
"answer",
|
39
|
+
plan,
|
40
|
+
form.current_user
|
41
|
+
) do
|
42
|
+
plan.update!(
|
43
|
+
state: @form.state,
|
44
|
+
answer: @form.answer,
|
45
|
+
answered_at: Time.current
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def notify_followers
|
51
|
+
return if (plan.previous_changes.keys & %w(state)).empty?
|
52
|
+
|
53
|
+
if plan.accepted?
|
54
|
+
publish_event(
|
55
|
+
"decidim.events.plans.plan_accepted",
|
56
|
+
Decidim::Plans::AcceptedPlanEvent
|
57
|
+
)
|
58
|
+
elsif plan.rejected?
|
59
|
+
publish_event(
|
60
|
+
"decidim.events.plans.plan_rejected",
|
61
|
+
Decidim::Plans::RejectedPlanEvent
|
62
|
+
)
|
63
|
+
elsif plan.evaluating?
|
64
|
+
publish_event(
|
65
|
+
"decidim.events.plans.plan_evaluating",
|
66
|
+
Decidim::Plans::EvaluatingPlanEvent
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def publish_event(event, event_class)
|
72
|
+
Decidim::EventsManager.publish(
|
73
|
+
event: event,
|
74
|
+
event_class: event_class,
|
75
|
+
resource: plan,
|
76
|
+
recipient_ids: plan.followers.pluck(:id)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when a user creates a new plan.
|
7
|
+
class CreatePlan < Rectify::Command
|
8
|
+
include AttachmentMethods
|
9
|
+
|
10
|
+
# Public: Initializes the command.
|
11
|
+
#
|
12
|
+
# form - A form object with the params.
|
13
|
+
def initialize(form)
|
14
|
+
@form = form
|
15
|
+
end
|
16
|
+
|
17
|
+
# Executes the command. Broadcasts these events:
|
18
|
+
#
|
19
|
+
# - :ok when everything is valid, together with the plan.
|
20
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
21
|
+
#
|
22
|
+
# Returns nothing.
|
23
|
+
def call
|
24
|
+
if process_attachments?
|
25
|
+
return broadcast(:invalid) if attachments_invalid?
|
26
|
+
end
|
27
|
+
|
28
|
+
if form.invalid?
|
29
|
+
mark_attachment_reattachment
|
30
|
+
return broadcast(:invalid)
|
31
|
+
end
|
32
|
+
|
33
|
+
Decidim::Plans.tracer.trace!(@form.current_user) do
|
34
|
+
transaction do
|
35
|
+
create_plan
|
36
|
+
create_plan_contents
|
37
|
+
update_attachments if process_attachments?
|
38
|
+
send_notification
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
broadcast(:ok, plan)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :form, :plan, :attachment
|
48
|
+
|
49
|
+
def create_plan
|
50
|
+
@plan = Decidim::Plans.loggability.perform_action!(
|
51
|
+
:create,
|
52
|
+
Plan,
|
53
|
+
@form.current_user
|
54
|
+
) do
|
55
|
+
plan = Plan.new(
|
56
|
+
{ published_at: Time.current }.merge(attributes)
|
57
|
+
)
|
58
|
+
plan.coauthorships.build(author: form.author)
|
59
|
+
plan.save!
|
60
|
+
plan.proposals << form.proposals
|
61
|
+
plan
|
62
|
+
end
|
63
|
+
|
64
|
+
@attached_to = @plan
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_plan_contents
|
68
|
+
@form.contents.each do |content|
|
69
|
+
@plan.contents.create!(
|
70
|
+
body: content.body,
|
71
|
+
section: content.section,
|
72
|
+
user: form.current_user
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def attributes
|
78
|
+
{
|
79
|
+
title: form.title,
|
80
|
+
category: form.category,
|
81
|
+
scope: form.scope,
|
82
|
+
component: form.component,
|
83
|
+
state: "open",
|
84
|
+
published_at: Time.current
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def send_notification
|
89
|
+
Decidim::EventsManager.publish(
|
90
|
+
event: "decidim.events.plans.plan_published",
|
91
|
+
event_class: Decidim::Plans::PublishPlanEvent,
|
92
|
+
resource: plan,
|
93
|
+
recipient_ids: @plan.participatory_space.followers.pluck(:id),
|
94
|
+
extra: {
|
95
|
+
participatory_space: true
|
96
|
+
}
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when a user updates a plan.
|
7
|
+
class UpdatePlan < Rectify::Command
|
8
|
+
include AttachmentMethods
|
9
|
+
include NestedUpdater
|
10
|
+
|
11
|
+
# Public: Initializes the command.
|
12
|
+
#
|
13
|
+
# form - A form object with the params.
|
14
|
+
# plan - the plan to update.
|
15
|
+
def initialize(form, plan)
|
16
|
+
@form = form
|
17
|
+
@plan = plan
|
18
|
+
@attached_to = plan
|
19
|
+
end
|
20
|
+
|
21
|
+
# Executes the command. Broadcasts these events:
|
22
|
+
#
|
23
|
+
# - :ok when everything is valid, together with the plan.
|
24
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def call
|
28
|
+
return broadcast(:invalid) if form.invalid?
|
29
|
+
|
30
|
+
if process_attachments?
|
31
|
+
prepare_attachments
|
32
|
+
return broadcast(:invalid) if attachments_invalid?
|
33
|
+
end
|
34
|
+
|
35
|
+
if form.invalid?
|
36
|
+
mark_attachment_reattachment
|
37
|
+
return broadcast(:invalid)
|
38
|
+
end
|
39
|
+
|
40
|
+
Decidim::Plans.tracer.trace!(form.current_user) do
|
41
|
+
transaction do
|
42
|
+
update_plan
|
43
|
+
update_plan_contents
|
44
|
+
update_plan_author
|
45
|
+
update_attachments if process_attachments?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
broadcast(:ok, plan)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :form, :plan, :attachment
|
55
|
+
|
56
|
+
def update_plan
|
57
|
+
Decidim::Plans.loggability.update!(
|
58
|
+
plan,
|
59
|
+
form.current_user,
|
60
|
+
attributes
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_plan_contents
|
65
|
+
@form.contents.each do |content|
|
66
|
+
update_nested_model(
|
67
|
+
content,
|
68
|
+
{ body: content.body,
|
69
|
+
section: content.section,
|
70
|
+
user: @form.current_user },
|
71
|
+
@plan.contents
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_plan_author
|
77
|
+
plan.coauthorships.clear
|
78
|
+
plan.add_coauthor(form.author)
|
79
|
+
plan.save!
|
80
|
+
plan
|
81
|
+
end
|
82
|
+
|
83
|
+
def attributes
|
84
|
+
{
|
85
|
+
title: form.title,
|
86
|
+
category: form.category,
|
87
|
+
scope: form.scope,
|
88
|
+
proposals: form.proposals,
|
89
|
+
# The update token ensures a new version is always created even if
|
90
|
+
# the other attributes have not changed. This is needed to force a
|
91
|
+
# new version to show the changes to associated models.
|
92
|
+
update_token: Time.now.to_f
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# This command is executed when the user modifies sections from
|
7
|
+
# the admin panel.
|
8
|
+
class UpdateSections < Rectify::Command
|
9
|
+
include NestedUpdater
|
10
|
+
|
11
|
+
# Initializes a UpdateSections Command.
|
12
|
+
#
|
13
|
+
# form - The form from which to get the data.
|
14
|
+
# sections - The current set of the sections to be updated.
|
15
|
+
def initialize(form, sections)
|
16
|
+
@form = form
|
17
|
+
@sections = sections
|
18
|
+
end
|
19
|
+
|
20
|
+
# Updates the sections if valid.
|
21
|
+
#
|
22
|
+
# Broadcasts :ok if successful, :invalid otherwise.
|
23
|
+
def call
|
24
|
+
return broadcast(:invalid) if @form.invalid?
|
25
|
+
|
26
|
+
Decidim::Plans::Section.transaction do
|
27
|
+
update_sections
|
28
|
+
end
|
29
|
+
|
30
|
+
broadcast(:ok)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def update_sections
|
36
|
+
@form.sections.each do |form_section|
|
37
|
+
update_nested_model(
|
38
|
+
form_section,
|
39
|
+
{
|
40
|
+
body: form_section.body,
|
41
|
+
help: form_section.help,
|
42
|
+
mandatory: form_section.mandatory,
|
43
|
+
answer_length: form_section.answer_length,
|
44
|
+
position: form_section.position,
|
45
|
+
section_type: form_section.section_type
|
46
|
+
},
|
47
|
+
@sections
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A module with all the attachment methods for plan commands.
|
6
|
+
module AttachmentMethods
|
7
|
+
include NestedUpdater
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def prepare_attachments
|
12
|
+
@form.attachments.each do |atform|
|
13
|
+
next if atform.file.present? || atform.id.blank?
|
14
|
+
|
15
|
+
attachment = @plan.attachments.find_by(id: atform.id)
|
16
|
+
atform.file = attachment.file if attachment
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def mark_attachment_reattachment
|
21
|
+
if @form.invalid? || @form.errors.any?
|
22
|
+
@form.attachments.each do |at|
|
23
|
+
at.errors.add(:file, :needs_to_be_reattached) if at.present? && at.id.blank?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def attachments_invalid?
|
29
|
+
@form.attachments.each do |atform|
|
30
|
+
next if atform.deleted?
|
31
|
+
|
32
|
+
attachment = Attachment.new(attachment_params(atform))
|
33
|
+
|
34
|
+
next if attachment.valid? || !attachment.errors.has_key?(:file)
|
35
|
+
|
36
|
+
atform.errors.add :file, attachment.errors[:file]
|
37
|
+
end
|
38
|
+
|
39
|
+
@form.attachments.any? { |at| at.errors.any? }
|
40
|
+
end
|
41
|
+
|
42
|
+
def attachments_present?
|
43
|
+
attachments_allowed? && @form.attachments.any? do |at|
|
44
|
+
at.title.present? || at.file.present? || at.id.present?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_attachments
|
49
|
+
@form.attachments.each do |attachment|
|
50
|
+
update_nested_model(
|
51
|
+
attachment,
|
52
|
+
attachment_params(attachment),
|
53
|
+
@plan.attachments
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def attachments_allowed?
|
59
|
+
@form.current_component.settings.attachments_allowed?
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_attachments?
|
63
|
+
attachments_allowed? && attachments_present?
|
64
|
+
end
|
65
|
+
|
66
|
+
def attachment_params(form)
|
67
|
+
params = {
|
68
|
+
weight: form.weight,
|
69
|
+
title: form.title,
|
70
|
+
attached_to: @attached_to
|
71
|
+
}
|
72
|
+
params[:file] = form.file if form.file.present?
|
73
|
+
|
74
|
+
params
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user creates a new plan.
|
6
|
+
class CreatePlan < Rectify::Command
|
7
|
+
include AttachmentMethods
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# form - A form object with the params.
|
11
|
+
# current_user - The current user.
|
12
|
+
def initialize(form, current_user)
|
13
|
+
@form = form
|
14
|
+
@current_user = current_user
|
15
|
+
@attached_to = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Executes the command. Broadcasts these events:
|
19
|
+
#
|
20
|
+
# - :ok when everything is valid, together with the plan.
|
21
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
22
|
+
#
|
23
|
+
# Returns nothing.
|
24
|
+
def call
|
25
|
+
if process_attachments?
|
26
|
+
return broadcast(:invalid) if attachments_invalid?
|
27
|
+
end
|
28
|
+
|
29
|
+
if form.invalid?
|
30
|
+
mark_attachment_reattachment
|
31
|
+
return broadcast(:invalid)
|
32
|
+
end
|
33
|
+
|
34
|
+
Decidim::Plans.tracer.trace!(@form.current_user) do
|
35
|
+
transaction do
|
36
|
+
create_plan
|
37
|
+
create_plan_contents
|
38
|
+
update_attachments if process_attachments?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
broadcast(:ok, plan)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :form, :plan, :attachments
|
48
|
+
|
49
|
+
def create_plan
|
50
|
+
@plan = Decidim::Plans.loggability.perform_action!(
|
51
|
+
:create,
|
52
|
+
Plan,
|
53
|
+
@form.current_user
|
54
|
+
) do
|
55
|
+
plan = Plan.new(
|
56
|
+
title: form.title,
|
57
|
+
category: form.category,
|
58
|
+
scope: form.scope,
|
59
|
+
component: form.component,
|
60
|
+
state: "open"
|
61
|
+
)
|
62
|
+
plan.coauthorships.build(author: @current_user, user_group: @form.user_group)
|
63
|
+
plan.save!
|
64
|
+
plan.proposals << form.proposals
|
65
|
+
plan
|
66
|
+
end
|
67
|
+
|
68
|
+
@attached_to = @plan
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_plan_contents
|
72
|
+
@form.contents.each do |content|
|
73
|
+
@plan.contents.create!(
|
74
|
+
body: content.body,
|
75
|
+
section: content.section,
|
76
|
+
user: @current_user
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_group
|
82
|
+
@user_group ||= Decidim::UserGroup.find_by(organization: organization, id: form.user_group_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
def organization
|
86
|
+
@organization ||= @current_user.organization
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|