diff --git a/app/components/dsfr/card_vertical_component.rb b/app/components/dsfr/card_vertical_component.rb index b50369c0b..01412fcb5 100644 --- a/app/components/dsfr/card_vertical_component.rb +++ b/app/components/dsfr/card_vertical_component.rb @@ -1,10 +1,12 @@ class Dsfr::CardVerticalComponent < ApplicationComponent renders_many :footer_buttons - attr_reader :title, :desc + attr_reader :title, :desc, :tags, :error - def initialize(title: nil, desc: nil) + def initialize(title: nil, desc: nil, tags: nil, error: nil) @title = title @desc = desc + @tags = tags + @error = error end end diff --git a/app/components/dsfr/card_vertical_component/card_vertical_component.html.haml b/app/components/dsfr/card_vertical_component/card_vertical_component.html.haml index 2ce25b445..70e4ea6c5 100644 --- a/app/components/dsfr/card_vertical_component/card_vertical_component.html.haml +++ b/app/components/dsfr/card_vertical_component/card_vertical_component.html.haml @@ -7,6 +7,16 @@ - if desc %p.fr-card__desc = desc + .fr-card__start + - if tags + %ul.fr-tags-group + - tags.each do |tag| + %li + %p.fr-tag=tag + .fr-card__end + - if error + .fr-card__detail.fr-alert.fr-alert--error + %p=error - if footer_buttons? .fr-card__footer diff --git a/app/components/procedure/card/champs_component.rb b/app/components/procedure/card/champs_component.rb index 939644b48..21acff9de 100644 --- a/app/components/procedure/card/champs_component.rb +++ b/app/components/procedure/card/champs_component.rb @@ -11,6 +11,9 @@ class Procedure::Card::ChampsComponent < ApplicationComponent end def error_messages - @procedure.errors.messages_for(:draft_types_de_champ).to_sentence + [ + @procedure.errors.messages_for(:draft_types_de_champ), + @procedure.errors.messages_for(:draft_revision) + ].flatten.to_sentence end end diff --git a/app/components/procedure/card/emails_component.rb b/app/components/procedure/card/emails_component.rb index 01f10f483..e773ceb66 100644 --- a/app/components/procedure/card/emails_component.rb +++ b/app/components/procedure/card/emails_component.rb @@ -2,4 +2,16 @@ class Procedure::Card::EmailsComponent < ApplicationComponent def initialize(procedure:) @procedure = procedure end + + private + + def error_messages + [ + @procedure.errors.messages_for(:initiated_mail), + @procedure.errors.messages_for(:received_mail), + @procedure.errors.messages_for(:closed_mail), + @procedure.errors.messages_for(:refused_mail), + @procedure.errors.messages_for(:without_continuation_mail) + ].flatten.to_sentence + end end diff --git a/app/components/procedure/card/emails_component/emails_component.html.haml b/app/components/procedure/card/emails_component/emails_component.html.haml index 224bfa9e5..a81399470 100644 --- a/app/components/procedure/card/emails_component/emails_component.html.haml +++ b/app/components/procedure/card/emails_component/emails_component.html.haml @@ -2,8 +2,12 @@ = link_to admin_procedure_mail_templates_path(@procedure), class: 'fr-tile fr-enlarge-link' do .fr-tile__body.flex.justify-between %div - %span.icon.clock - %p.fr-tile-status-todo À configurer + - if error_messages.present? + %span.icon.refuse + %p.fr-tile-status-error À modifier + - else + %span.icon.clock + %p.fr-tile-status-todo À configurer %div %h3.fr-h6.fr-mt-10v= t('.title') %p.fr-tile-subtitle Notifications automatiques diff --git a/app/components/procedure/email_template_card_component.rb b/app/components/procedure/email_template_card_component.rb new file mode 100644 index 000000000..8c636d061 --- /dev/null +++ b/app/components/procedure/email_template_card_component.rb @@ -0,0 +1,35 @@ +class Procedure::EmailTemplateCardComponent < ApplicationComponent + def initialize(email_template:) + @email_template = email_template + end + + private + + def title + @email_template.class.const_get(:DISPLAYED_NAME) + end + + def desc + @email_template.subject if edited? + end + + def error + @email_template.errors.full_messages.first if @email_template.errors.present? + end + + def tag + if edited? + "modifié le #{@email_template.updated_at.strftime('%d-%m-%Y')}" + else + "Modèle standard" + end + end + + def edited? + @email_template.updated_at.present? + end + + def edit_path + edit_admin_procedure_mail_template_path(@email_template.procedure, @email_template.class.const_get(:SLUG)) + end +end diff --git a/app/components/procedure/email_template_card_component/email_template_card_component.html.haml b/app/components/procedure/email_template_card_component/email_template_card_component.html.haml new file mode 100644 index 000000000..c3ce89d3e --- /dev/null +++ b/app/components/procedure/email_template_card_component/email_template_card_component.html.haml @@ -0,0 +1,3 @@ += render Dsfr::CardVerticalComponent.new(title: title, desc: desc, error: error, tags: [tag]) do |c| + - c.with_footer_button do + = link_to 'Modifier', edit_path, class: 'fr-btn' diff --git a/app/controllers/administrateurs/mail_templates_controller.rb b/app/controllers/administrateurs/mail_templates_controller.rb index bb1ac0988..bc39ebc8d 100644 --- a/app/controllers/administrateurs/mail_templates_controller.rb +++ b/app/controllers/administrateurs/mail_templates_controller.rb @@ -4,34 +4,43 @@ module Administrateurs def index @mail_templates = mail_templates + @mail_templates.each(&:validate) end def edit - @procedure = procedure @mail_template = find_mail_template_by_slug(params[:id]) + if !@mail_template.valid? + flash.now.alert = @mail_template.errors.full_messages + end + end + + def show + redirect_to edit_admin_procedure_mail_template_path(procedure.id, params[:id]) end def update - @procedure = procedure mail_template = find_mail_template_by_slug(params[:id]) if mail_template.update(update_params) flash.notice = "Email mis à jour" + redirect_to edit_admin_procedure_mail_template_path(mail_template.procedure_id, params[:id]) else - flash.alert = mail_template.errors.full_messages - end + flash.now.alert = mail_template.errors.full_messages + mail_template.rich_body = mail_template.body - redirect_to edit_admin_procedure_mail_template_path(mail_template.procedure_id, params[:id]) + @mail_template = mail_template + render :edit + end end def preview mail_template = find_mail_template_by_slug(params[:id]) - dossier = Dossier.new(id: '1', procedure: procedure) + dossier = procedure.active_revision.dossier_for_preview(current_user) @dossier = dossier @logo_url = procedure.logo_url @service = procedure.service - @rendered_template = sanitize(mail_template.rich_body.body.to_html) + @rendered_template = sanitize(mail_template.body_for_dossier(dossier)) @actions = mail_template.actions_for_dossier(dossier) render(template: 'notification_mailer/send_notification', layout: 'mailers/notifications_layout') diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 6cc900557..1d969d7b8 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -181,7 +181,7 @@ module TagsSubstitutionConcern { libelle: 'groupe instructeur', description: 'Le groupe instructeur en charge du dossier', - lambda: -> (d) { d.groupe_instructeur.label }, + lambda: -> (d) { d.groupe_instructeur&.label }, available_for_states: Dossier::SOUMIS } ] diff --git a/app/models/mails/closed_mail.rb b/app/models/mails/closed_mail.rb index 7d32ebba8..60660e23a 100644 --- a/app/models/mails/closed_mail.rb +++ b/app/models/mails/closed_mail.rb @@ -15,6 +15,9 @@ module Mails belongs_to :procedure, optional: false + validates :subject, tags: true + validates :body, tags: true + SLUG = "closed_mail" DISPLAYED_NAME = "Accusé d’acceptation" DEFAULT_SUBJECT = 'Votre dossier nº --numéro du dossier-- a été accepté (--libellé démarche--)' diff --git a/app/models/mails/initiated_mail.rb b/app/models/mails/initiated_mail.rb index 627473b43..0178971ff 100644 --- a/app/models/mails/initiated_mail.rb +++ b/app/models/mails/initiated_mail.rb @@ -15,6 +15,9 @@ module Mails belongs_to :procedure, optional: false + validates :subject, tags: true + validates :body, tags: true + SLUG = "initiated_mail" DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/initiated_mail" DISPLAYED_NAME = I18n.t('activerecord.models.mail.initiated_mail.proof_of_receipt') diff --git a/app/models/mails/received_mail.rb b/app/models/mails/received_mail.rb index 3eb4ecbfe..ccfc6ecc1 100644 --- a/app/models/mails/received_mail.rb +++ b/app/models/mails/received_mail.rb @@ -15,6 +15,9 @@ module Mails belongs_to :procedure, optional: false + validates :subject, tags: true + validates :body, tags: true + SLUG = "received_mail" DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/received_mail" DISPLAYED_NAME = I18n.t('activerecord.models.mail.received_mail.under_instruction') diff --git a/app/models/mails/refused_mail.rb b/app/models/mails/refused_mail.rb index 2f67c33b7..50dfea0ea 100644 --- a/app/models/mails/refused_mail.rb +++ b/app/models/mails/refused_mail.rb @@ -15,6 +15,9 @@ module Mails belongs_to :procedure, optional: false + validates :subject, tags: true + validates :body, tags: true + SLUG = "refused_mail" DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/refused_mail" DISPLAYED_NAME = 'Accusé de rejet du dossier' diff --git a/app/models/mails/without_continuation_mail.rb b/app/models/mails/without_continuation_mail.rb index 0c185b363..28558f330 100644 --- a/app/models/mails/without_continuation_mail.rb +++ b/app/models/mails/without_continuation_mail.rb @@ -15,6 +15,9 @@ module Mails belongs_to :procedure, optional: false + validates :subject, tags: true + validates :body, tags: true + SLUG = "without_continuation" DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/without_continuation_mail" DISPLAYED_NAME = 'Accusé de classement sans suite' diff --git a/app/models/procedure.rb b/app/models/procedure.rb index a9e016e5f..d24768804 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -181,6 +181,14 @@ class Procedure < ApplicationRecord types_de_champ_for_tags.private_only end + def revision_ids_with_pending_dossiers + dossiers + .where.not(revision_id: [draft_revision_id, published_revision_id].compact) + .state_en_construction_ou_instruction + .distinct(:revision_id) + .pluck(:revision_id) + end + has_many :administrateurs_procedures, dependent: :delete_all has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! } has_many :groupe_instructeurs, dependent: :destroy @@ -264,11 +272,11 @@ class Procedure < ApplicationRecord validates :draft_types_de_champ, 'types_de_champ/no_empty_block': true, 'types_de_champ/no_empty_drop_down': true, - if: :validate_for_publication? + on: :publication validates :draft_types_de_champ_private, 'types_de_champ/no_empty_block': true, 'types_de_champ/no_empty_drop_down': true, - if: :validate_for_publication? + on: :publication validate :check_juridique validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false } validates :duree_conservation_dossiers_dans_ds, allow_nil: false, @@ -287,6 +295,13 @@ class Procedure < ApplicationRecord validates :lien_dpo, email_or_link: true, allow_nil: true validates_with MonAvisEmbedValidator + validates_associated :draft_revision, on: :publication + validates_associated :initiated_mail, on: :publication + validates_associated :received_mail, on: :publication + validates_associated :closed_mail, on: :publication + validates_associated :refused_mail, on: :publication + validates_associated :without_continuation_mail, on: :publication + FILE_MAX_SIZE = 20.megabytes validates :notice, content_type: [ "application/msword", @@ -790,8 +805,12 @@ class Procedure < ApplicationRecord end def publish_revision! - update!(draft_revision: create_new_revision, published_revision: draft_revision) - published_revision.touch(:published_at) + transaction do + self.published_revision = draft_revision + self.draft_revision = create_new_revision + save!(context: :publication) + published_revision.touch(:published_at) + end dossiers .state_not_termine .find_each { |dossier| DossierRebaseJob.perform_later(dossier) } @@ -851,16 +870,15 @@ class Procedure < ApplicationRecord new_draft.revision_types_de_champ.reload end - def validate_for_publication? - validation_context == :publication || publiee? - end - def before_publish assign_attributes(closed_at: nil, unpublished_at: nil) end def after_publish(canonical_procedure = nil) - update!(canonical_procedure: canonical_procedure, draft_revision: create_new_revision, published_revision: draft_revision) + self.canonical_procedure = canonical_procedure + self.published_revision = draft_revision + self.draft_revision = create_new_revision + save!(context: :publication) touch(:published_at) published_revision.touch(:published_at) end diff --git a/app/validators/tags_validator.rb b/app/validators/tags_validator.rb new file mode 100644 index 000000000..d45a858db --- /dev/null +++ b/app/validators/tags_validator.rb @@ -0,0 +1,64 @@ +class TagsValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + procedure = record.procedure + tags = record.used_type_de_champ_tags(value || '') + + invalid_tags = tags.filter_map do |(tag, stable_id)| + tag if stable_id.nil? + end + + invalid_for_draft_revision = invalid_tags_for_revision(record, attribute, tags, procedure.draft_revision_id) + + invalid_for_published_revision = if procedure.published_revision_id.present? + invalid_tags_for_revision(record, attribute, tags, procedure.published_revision_id) + else + [] + end + + invalid_for_previous_revision = procedure + .revision_ids_with_pending_dossiers + .flat_map do |revision_id| + invalid_tags_for_revision(record, attribute, tags, revision_id) + end.uniq + + # champ is added in draft revision but not yet published + champ_missing_in_published_revision = (invalid_for_published_revision - invalid_for_draft_revision) + add_errors(record, attribute, :champ_missing_in_published_revision, champ_missing_in_published_revision) + + # champ is removed but the removal is not yet published + champ_missing_in_draft_revision = (invalid_for_draft_revision - invalid_for_published_revision) + add_errors(record, attribute, :champ_missing_in_draft_revision, champ_missing_in_draft_revision) + + # champ is removed and the removal is published + champ_missing_in_published_and_draft_revision = invalid_for_published_revision.intersection(invalid_for_draft_revision) + add_errors(record, attribute, :champ_missing_in_published_and_draft_revision, champ_missing_in_published_and_draft_revision) + + # champ is missing from one of the revisions in pending dossiers + add_errors(record, attribute, :champ_missing_in_previous_revision, invalid_for_previous_revision) + + # unknown champ + add_errors(record, attribute, :champ_missing, invalid_tags) + end + + private + + def add_errors(record, attribute, message, tags) + tags.each do |tag| + record.errors.add(attribute, message, tag: tag) + end + end + + def invalid_tags_for_revision(record, attribute, tags, revision_id) + revision_stable_ids = TypeDeChamp + .joins(:revision_types_de_champ) + .where(procedure_revision_types_de_champ: { revision_id: revision_id, parent_id: nil }) + .distinct(:stable_id) + .pluck(:stable_id) + + tags.filter_map do |(tag, stable_id)| + if stable_id.present? && !stable_id.in?(revision_stable_ids) + tag + end + end + end +end diff --git a/app/views/administrateurs/mail_templates/_form.html.haml b/app/views/administrateurs/mail_templates/_form.html.haml index bc981e8d8..a47af6d01 100644 --- a/app/views/administrateurs/mail_templates/_form.html.haml +++ b/app/views/administrateurs/mail_templates/_form.html.haml @@ -17,7 +17,7 @@ .tag Balise .description Description .items - - @mail_template.tags.each do |tag| + - tags.each do |tag| .item %code.tag = "--#{tag[:libelle]}--" diff --git a/app/views/administrateurs/mail_templates/edit.html.haml b/app/views/administrateurs/mail_templates/edit.html.haml index 38d0663bc..a087da95d 100644 --- a/app/views/administrateurs/mail_templates/edit.html.haml +++ b/app/views/administrateurs/mail_templates/edit.html.haml @@ -5,7 +5,7 @@ = render partial: 'administrateurs/breadcrumbs', locals: { steps: [['Démarches', admin_procedures_path], [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)], - ["Emails", admin_procedure_mail_templates_path(@procedure)], + ["Configuration des emails", admin_procedure_mail_templates_path(@procedure)], [@mail_template.class.const_get(:DISPLAYED_NAME)]] } .procedure-form @@ -17,7 +17,7 @@ html: { class: 'form procedure-form__column--form' } do |f| %h1.page-title= @mail_template.class.const_get(:DISPLAYED_NAME) - = render partial: 'form', locals: { f: f } + = render partial: 'form', locals: { f: f, tags: @mail_template.tags } .procedure-form__actions.sticky--bottom .actions-right = f.submit 'Enregistrer', class: 'button primary send' @@ -30,4 +30,4 @@ .notice Cet aperçu est mis à jour après chaque sauvegarde. .procedure-preview - = render partial: 'apercu', locals: { procedure: @procedure } + = render partial: 'apercu', locals: { procedure: @mail_template.procedure } diff --git a/app/views/administrateurs/mail_templates/index.html.haml b/app/views/administrateurs/mail_templates/index.html.haml index 88412f14d..07345a1fa 100644 --- a/app/views/administrateurs/mail_templates/index.html.haml +++ b/app/views/administrateurs/mail_templates/index.html.haml @@ -4,15 +4,7 @@ ["Configuration des emails"]] } .container - - @mail_templates.each do |mail_template| - .card - .flex.justify-between - %div - .card-title= mail_template.class.const_get(:DISPLAYED_NAME) - - if mail_template.updated_at.blank? - %p.notice= mail_template.class.const_get(:DISPLAYED_NAME) === 'Accusé de réception' ? 'Personnalisé' : 'Modèle standard' - - else - %span.badge.baseline modifié le #{mail_template.updated_at.strftime('%d-%m-%Y')} - - %div - = link_to 'Modifier', edit_admin_procedure_mail_template_path(@procedure, mail_template.class.const_get(:SLUG)), class: 'fr-btn' + .fr-grid-row.fr-grid-row--gutters.fr-py-5w + - @mail_templates.each do |mail_template| + .fr-col-md-6.fr-col-12 + = render Procedure::EmailTemplateCardComponent.new(email_template: mail_template) diff --git a/app/views/administrateurs/procedures/_publication_form.html.haml b/app/views/administrateurs/procedures/_publication_form.html.haml index 48120ded3..14962ad13 100644 --- a/app/views/administrateurs/procedures/_publication_form.html.haml +++ b/app/views/administrateurs/procedures/_publication_form.html.haml @@ -22,4 +22,4 @@ = render partial: 'publication_form_inputs', locals: { procedure: procedure, closed_procedures: @closed_procedures } .flex.justify-end - = submit_tag procedure_publish_label(procedure, :submit), { disabled: publication_errors.present?, class: "button primary", id: 'publish' } + = submit_tag procedure_publish_label(procedure, :submit), { disabled: publication_errors.present?, class: "fr-btn fr-btn--primary", id: 'publish' } diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml index 4d73b256c..afff192c3 100644 --- a/app/views/administrateurs/procedures/show.html.haml +++ b/app/views/administrateurs/procedures/show.html.haml @@ -42,7 +42,7 @@ .container = render TypesDeChampEditor::ErrorsSummary.new(revision: @procedure.draft_revision) -- if @procedure.draft_changed? && @procedure.draft_revision.valid? +- if @procedure.draft_changed? .container .card.featured .card-title @@ -50,7 +50,7 @@ = render partial: 'revision_changes', locals: { changes: @procedure.revision_changes } .flex.mt-2.justify-end = button_to "Réinitialiser les modifications", admin_procedure_reset_draft_path(@procedure), class: 'fr-btn fr-btn--secondary fr-mr-2w', method: :put - = link_to 'Publier les modifications', admin_procedure_publication_path(@procedure), class: 'fr-btn', id: 'publish-procedure-link', data: { disable_with: "Publication..." } + = button_to 'Publier les modifications', admin_procedure_publication_path(@procedure), class: 'fr-btn', id: 'publish-procedure-link', data: { disable_with: "Publication..." }, disabled: !@procedure.draft_revision.valid?, method: :get - if !@procedure.procedure_expires_when_termine_enabled? = render partial: 'administrateurs/procedures/suggest_expires_when_termine', locals: { procedure: @procedure } diff --git a/config/locales/models/attestation_template/fr.yml b/config/locales/models/attestation_template/fr.yml new file mode 100644 index 000000000..08efd9914 --- /dev/null +++ b/config/locales/models/attestation_template/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + attestation_template: + attributes: + title: + format: Le titre du modèle de l’attestation %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui à été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu du modèle de l’attestation %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/locales/models/closed_mail/fr.yml b/config/locales/models/closed_mail/fr.yml new file mode 100644 index 000000000..16b1b3467 --- /dev/null +++ b/config/locales/models/closed_mail/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + mails/closed_mail: + attributes: + subject: + format: Le titre de l’email de notification d’acceptation de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu de l’email de notification d’acceptation de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/locales/models/initiated_mail/fr.yml b/config/locales/models/initiated_mail/fr.yml new file mode 100644 index 000000000..7ea8d2582 --- /dev/null +++ b/config/locales/models/initiated_mail/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + mails/initiated_mail: + attributes: + subject: + format: Le titre de l’email de notification de passage du dossier en instruction %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu de l’email de notification de passage du dossier en instruction %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/locales/models/received_mail/fr.yml b/config/locales/models/received_mail/fr.yml new file mode 100644 index 000000000..9270210d8 --- /dev/null +++ b/config/locales/models/received_mail/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + mails/received_mail: + attributes: + subject: + format: Le titre de l’email de notification de dépot de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu de l’email de notification de dépot de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/locales/models/refused_mail/fr.yml b/config/locales/models/refused_mail/fr.yml new file mode 100644 index 000000000..26104067f --- /dev/null +++ b/config/locales/models/refused_mail/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + mails/refused_mail: + attributes: + subject: + format: Le titre de l’email de notification de refus de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu de l’email de notification de refus de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publiée + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/locales/models/without_continuation_mail/fr.yml b/config/locales/models/without_continuation_mail/fr.yml new file mode 100644 index 000000000..06d549920 --- /dev/null +++ b/config/locales/models/without_continuation_mail/fr.yml @@ -0,0 +1,20 @@ +fr: + activerecord: + errors: + models: + mails/without_continuation_mail: + attributes: + subject: + format: Le titre de l’email de notification de classement sans suite de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement + body: + format: Le contenu de l’email de notification de classement sans suite de dossier %{message} + champ_missing: réfère au champ "%{tag}" qui n’existe pas + champ_missing_in_draft_revision: réfère au champ "%{tag}" qui a été supprimé mais la suppression n’est pas encore publiée + champ_missing_in_published_revision: réfère au champ "%{tag}" qui n’est pas encore publié + champ_missing_in_published_and_draft_revision: réfère au champ "%{tag}" qui a été supprimé + champ_missing_in_previous_revision: réfère au champ "%{tag}" qui n’existe pas sur un des dossiers en cours de traitement diff --git a/config/routes.rb b/config/routes.rb index 0d3fc9a68..d8280e90a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -468,7 +468,7 @@ Rails.application.routes.draw do get 'transfert' => 'procedures#transfert', as: :transfert get 'close' => 'procedures#close', as: :close post 'transfer' => 'procedures#transfer', as: :transfer - resources :mail_templates, only: [:edit, :update] + resources :mail_templates, only: [:edit, :update, :show] resources :groupe_instructeurs, only: [:index, :show, :create, :update, :destroy] do member do diff --git a/spec/controllers/administrateurs/mail_templates_controller_spec.rb b/spec/controllers/administrateurs/mail_templates_controller_spec.rb index 6095e772e..7d0a650f8 100644 --- a/spec/controllers/administrateurs/mail_templates_controller_spec.rb +++ b/spec/controllers/administrateurs/mail_templates_controller_spec.rb @@ -45,7 +45,7 @@ describe Administrateurs::MailTemplatesController, type: :controller do describe 'PATCH update' do let(:mail_subject) { 'Mise à jour de votre démarche' } - let(:mail_body) { '
Une mise à jour a été effectuée sur votre démarche n° --demarche-id--.
' } + let(:mail_body) { '
Une mise à jour a été effectuée sur votre démarche n° --numéro du dossier--.
' } before :each do patch :update, @@ -58,11 +58,23 @@ describe Administrateurs::MailTemplatesController, type: :controller do it { expect(response).to redirect_to edit_admin_procedure_mail_template_path(procedure, initiated_mail.class.const_get(:SLUG)) } - context 'the mail template' do + context 'with valid email template' do subject { procedure.reload; procedure.initiated_mail_template } - it { expect(subject.subject).to eq(mail_subject) } - it { expect(subject.body).to eq(mail_body) } + it do + expect(subject.subject).to eq(mail_subject) + expect(subject.body).to eq(mail_body) + end + end + + context 'with invalid email template' do + subject { procedure.reload; procedure.initiated_mail_template } + let(:mail_body) { '
Une mise à jour a été effectuée sur votre démarche n° --numéro--.
' } + + it do + expect(subject.body).not_to eq(mail_body) + expect(response.body).to match("Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"numéro\" qui n’existe pas") + end end end end diff --git a/spec/models/mail_template_spec.rb b/spec/models/mail_template_spec.rb new file mode 100644 index 000000000..6f981af2b --- /dev/null +++ b/spec/models/mail_template_spec.rb @@ -0,0 +1,87 @@ +describe Mails::InitiatedMail, type: :model do + let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :text, libelle: 'nom' }]) } + let(:type_de_champ) { procedure.draft_revision.types_de_champ_public.first } + let(:mail) { described_class.default_for_procedure(procedure) } + + let(:email_subject) { '' } + let(:email_body) { '' } + + subject do + mail.subject = email_subject + mail.body = email_body + mail.validate + mail + end + + describe 'body' do + context 'empty template' do + it { expect(subject.errors).to be_empty } + end + + context 'template with valid tag' do + let(:email_body) { 'foo --numéro du dossier-- bar' } + + it { expect(subject.errors).to be_empty } + end + + context 'template with new valid tag' do + let(:email_body) { 'foo --age-- bar' } + + before do + procedure.draft_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age') + procedure.publish_revision! + end + + it { expect(subject.errors).to be_empty } + end + + context 'template with invalid tag' do + let(:email_body) { 'foo --numéro du -- bar' } + + it { expect(subject.errors.full_messages).to eq(["Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"numéro du \" qui n’existe pas"]) } + end + + context 'template with unpublished tag' do + let(:email_body) { 'foo --age-- bar' } + + before do + procedure.draft_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age') + end + + it { expect(subject.errors.full_messages).to eq(["Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"age\" qui n’est pas encore publié"]) } + end + + context 'template with removed but unpublished tag' do + let(:email_body) { 'foo --nom-- bar' } + + before do + procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id) + end + + it { expect(subject.errors.full_messages).to eq(["Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"nom\" qui a été supprimé mais la suppression n’est pas encore publiée"]) } + end + + context 'template with removed tag' do + let(:email_body) { 'foo --nom-- bar' } + + before do + procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id) + procedure.publish_revision! + end + + it { expect(subject.errors.full_messages).to eq(["Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"nom\" qui a été supprimé"]) } + end + + context 'template with new tag and old dossier' do + let(:email_body) { 'foo --age-- bar' } + + before do + create(:dossier, :en_construction, procedure: procedure) + procedure.draft_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age') + procedure.publish_revision! + end + + it { expect(subject.errors.full_messages).to eq(["Le contenu de l’email de notification de passage du dossier en instruction réfère au champ \"age\" qui n’existe pas sur un des dossiers en cours de traitement"]) } + end + end +end diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index 0dc5e5a50..e95f5236e 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -327,11 +327,9 @@ describe Procedure do end end - context 'on a published procedure' do - before { procedure.publish } - + context 'when validating for publication' do it 'validates that no repetition type de champ is empty' do - procedure.validate + procedure.validate(:publication) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message) new_draft = procedure.draft_revision @@ -339,32 +337,20 @@ describe Procedure do parent_coordinate = new_draft.revision_types_de_champ.find_by(type_de_champ: repetition) new_draft.revision_types_de_champ.create(type_de_champ: create(:type_de_champ), position: 0, parent: parent_coordinate) - procedure.validate + procedure.validate(:publication) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_repetition_error_message) end it 'validates that no drop-down type de champ is empty' do - procedure.validate + procedure.validate(:publication) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message) drop_down.update!(drop_down_list_value: "--title--\r\nsome value") - procedure.reload.validate + procedure.reload.validate(:publication) expect(procedure.errors.full_messages_for(:draft_types_de_champ)).not_to include(invalid_drop_down_error_message) end end - context 'when validating for publication' do - it 'validates that no repetition type de champ is empty' do - procedure.validate(:publication) - expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_repetition_error_message) - end - - it 'validates that no drop-down type de champ is empty' do - procedure.validate(:publication) - expect(procedure.errors.full_messages_for(:draft_types_de_champ)).to include(invalid_drop_down_error_message) - end - end - context 'when the champ is private' do before do repetition.update(private: true)