diff --git a/.rubocop_rails.yml b/.rubocop_rails.yml
index e3067271ee..b0e45aa030 100644
--- a/.rubocop_rails.yml
+++ b/.rubocop_rails.yml
@@ -109,3 +109,7 @@ RSpec/MultipleMemoizedHelpers:
RSpec/AnyInstance:
Enabled: false
+
+RSpec/BeEq:
+ Exclude:
+ - spec/events/decidim/proposals/author_confirmation_proposal_event_spec.rb
\ No newline at end of file
diff --git a/app/commands/decidim/proposals/publish_proposal.rb b/app/commands/decidim/proposals/publish_proposal.rb
new file mode 100644
index 0000000000..a637eaed4f
--- /dev/null
+++ b/app/commands/decidim/proposals/publish_proposal.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Proposals
+ # A command with all the business logic when a user publishes a draft proposal.
+ class PublishProposal < Decidim::Command
+ include Decidim::AnonymousProposals::AnonymousBehaviorCommandsConcern
+
+ # Public: Initializes the command.
+ #
+ # proposal - The proposal to publish.
+ # current_user - The current user.
+ # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb
+ def initialize(proposal, current_user)
+ @proposal = proposal
+ @is_anonymous = allow_anonymous_proposals? && (current_user.blank? || proposal.authored_by?(anonymous_group))
+ set_current_user(current_user)
+ end
+
+ # Executes the command. Broadcasts these events:
+ #
+ # - :ok when everything is valid and the proposal is published.
+ # - :invalid if the proposal's author is not the current user.
+ #
+ # Returns nothing.
+ def call
+ return broadcast(:invalid) unless @proposal.authored_by?(@current_user)
+
+ transaction do
+ publish_proposal
+ increment_scores
+ send_notification
+ send_notification_to_participatory_space
+ send_publication_notification
+ end
+
+ broadcast(:ok, @proposal)
+ end
+
+ private
+
+ # This will be the PaperTrail version that is
+ # shown in the version control feature (1 of 1)
+ #
+ # For an attribute to appear in the new version it has to be reset
+ # and reassigned, as PaperTrail only keeps track of object CHANGES.
+ def publish_proposal
+ title = reset(:title)
+ body = reset(:body)
+
+ Decidim.traceability.perform_action!(
+ "publish",
+ @proposal,
+ @current_user,
+ visibility: "public-only"
+ ) do
+ @proposal.update title: title, body: body, published_at: Time.current
+ end
+ end
+
+ # Reset the attribute to an empty string and return the old value
+ def reset(attribute)
+ attribute_value = @proposal[attribute]
+ PaperTrail.request(enabled: false) do
+ # rubocop:disable Rails/SkipsModelValidations
+ @proposal.update_attribute attribute, ""
+ # rubocop:enable Rails/SkipsModelValidations
+ end
+ attribute_value
+ end
+
+ def send_notification
+ return if @proposal.coauthorships.empty?
+
+ Decidim::EventsManager.publish(
+ event: "decidim.events.proposals.proposal_published",
+ event_class: Decidim::Proposals::PublishProposalEvent,
+ resource: @proposal,
+ followers: coauthors_followers
+ )
+ end
+
+ def send_publication_notification
+ Decidim::EventsManager.publish(
+ event: "decidim.events.proposals.author_confirmation_proposal_event",
+ event_class: Decidim::Proposals::AuthorConfirmationProposalEvent,
+ resource: @proposal,
+ affected_users: [@proposal.creator_identity],
+ extra: { force_email: true },
+ force_send: true
+ )
+ end
+
+ def send_notification_to_participatory_space
+ Decidim::EventsManager.publish(
+ event: "decidim.events.proposals.proposal_published",
+ event_class: Decidim::Proposals::PublishProposalEvent,
+ resource: @proposal,
+ followers: @proposal.participatory_space.followers - coauthors_followers,
+ extra: {
+ participatory_space: true
+ }
+ )
+ end
+
+ def coauthors_followers
+ @coauthors_followers ||= @proposal.authors.flat_map(&:followers)
+ end
+
+ def increment_scores
+ @proposal.coauthorships.find_each do |coauthorship|
+ if coauthorship.user_group
+ Decidim::Gamification.increment_score(coauthorship.user_group, :proposals)
+ else
+ Decidim::Gamification.increment_score(coauthorship.author, :proposals)
+ end
+ end
+ end
+
+ # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb
+ def component
+ @component ||= @proposal.component
+ end
+ end
+ end
+end
diff --git a/app/events/decidim/proposals/author_confirmation_proposal_event.rb b/app/events/decidim/proposals/author_confirmation_proposal_event.rb
new file mode 100644
index 0000000000..22745650a4
--- /dev/null
+++ b/app/events/decidim/proposals/author_confirmation_proposal_event.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Proposals
+ class AuthorConfirmationProposalEvent < Decidim::Events::SimpleEvent
+ def self.model_name
+ ActiveModel::Name.new(self, nil, I18n.t("decidim.events.proposals.author_confirmation_proposal_event.email_subject"))
+ end
+
+ def resource_title
+ translated_attribute(resource.title)
+ end
+ end
+ end
+end
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index be5b5b9147..fef7c18f22 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -164,5 +164,8 @@ ignore_unused:
- decidim.newsletters.unsubscribe.error
- decidim.newsletters.unsubscribe.token_error
- decidim.half_signup.quick_auth.sms_verification.text_message
+ - decidim.events.proposals.author_confirmation_proposal_event.email_intro
+ - decidim.events.proposals.author_confirmation_proposal_event.email_outro
+ - decidim.events.proposals.author_confirmation_proposal_event.notification_title
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 477839a9f7..56be2f097d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -77,6 +77,12 @@ en:
email_outro: You have received this notification because you are following the initiative "%{resource_title}".
email_subject: Initiative "%{resource_title}" has been answered
notification_title: The initiative %{resource_title} has been answered.
+ proposals:
+ author_confirmation_proposal_event:
+ email_intro: 'Your proposal " %{resource_title} " was successfully received and is now public. Thank you for participating ! You can view it here:'
+ email_outro: You received this notification because you are the author of the proposal. You can unfollow it by visiting the proposal page (" %{resource_title} ") and clicking on " Unfollow ".
+ email_subject: Your proposal has been published!
+ notification_title: Your proposal %{resource_title} is now live.
users:
user_officialized:
email_intro: Participant %{name} (%{nickname}) has been officialized.
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a82163e17b..6e725c5912 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -79,6 +79,12 @@ fr:
email_outro: Vous avez reçu cette notification parce que vous suivez la pétition "%{resource_title}".
email_subject: La pétition "%{resource_title}" a reçu une réponse.
notification_title: La pétition %{resource_title} a reçu une réponse.
+ proposals:
+ author_confirmation_proposal_event:
+ email_intro: 'Votre proposition « %{resource_title} » a été reçue avec succès et est maintenant publique. Merci pour votre participation ! Vous pouvez la consulter ici :'
+ email_outro: Vous recevez cette notification car vous êtes l’auteur de la proposition. Vous pouvez vous désabonner en visitant la page de la proposition (« %{resource_title} ») et en cliquant sur « Ne plus suivre ».
+ email_subject: Votre proposition a été publiée !
+ notification_title: Votre proposition %{resource_title} est maintenant en ligne.
users:
user_officialized:
email_intro: Le participant %{name} (%{nickname}) a été officialisé.