feat(emails): validate tags in notification emails
This commit is contained in:
parent
4bc30bf2f9
commit
dd4c1f2fac
14 changed files with 294 additions and 0 deletions
|
@ -15,6 +15,9 @@ module Mails
|
||||||
|
|
||||||
belongs_to :procedure, optional: false
|
belongs_to :procedure, optional: false
|
||||||
|
|
||||||
|
validates :subject, tags: true
|
||||||
|
validates :body, tags: true
|
||||||
|
|
||||||
SLUG = "closed_mail"
|
SLUG = "closed_mail"
|
||||||
DISPLAYED_NAME = "Accusé d’acceptation"
|
DISPLAYED_NAME = "Accusé d’acceptation"
|
||||||
DEFAULT_SUBJECT = 'Votre dossier nº --numéro du dossier-- a été accepté (--libellé démarche--)'
|
DEFAULT_SUBJECT = 'Votre dossier nº --numéro du dossier-- a été accepté (--libellé démarche--)'
|
||||||
|
|
|
@ -15,6 +15,9 @@ module Mails
|
||||||
|
|
||||||
belongs_to :procedure, optional: false
|
belongs_to :procedure, optional: false
|
||||||
|
|
||||||
|
validates :subject, tags: true
|
||||||
|
validates :body, tags: true
|
||||||
|
|
||||||
SLUG = "initiated_mail"
|
SLUG = "initiated_mail"
|
||||||
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/initiated_mail"
|
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/initiated_mail"
|
||||||
DISPLAYED_NAME = I18n.t('activerecord.models.mail.initiated_mail.proof_of_receipt')
|
DISPLAYED_NAME = I18n.t('activerecord.models.mail.initiated_mail.proof_of_receipt')
|
||||||
|
|
|
@ -15,6 +15,9 @@ module Mails
|
||||||
|
|
||||||
belongs_to :procedure, optional: false
|
belongs_to :procedure, optional: false
|
||||||
|
|
||||||
|
validates :subject, tags: true
|
||||||
|
validates :body, tags: true
|
||||||
|
|
||||||
SLUG = "received_mail"
|
SLUG = "received_mail"
|
||||||
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/received_mail"
|
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/received_mail"
|
||||||
DISPLAYED_NAME = I18n.t('activerecord.models.mail.received_mail.under_instruction')
|
DISPLAYED_NAME = I18n.t('activerecord.models.mail.received_mail.under_instruction')
|
||||||
|
|
|
@ -15,6 +15,9 @@ module Mails
|
||||||
|
|
||||||
belongs_to :procedure, optional: false
|
belongs_to :procedure, optional: false
|
||||||
|
|
||||||
|
validates :subject, tags: true
|
||||||
|
validates :body, tags: true
|
||||||
|
|
||||||
SLUG = "refused_mail"
|
SLUG = "refused_mail"
|
||||||
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/refused_mail"
|
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/refused_mail"
|
||||||
DISPLAYED_NAME = 'Accusé de rejet du dossier'
|
DISPLAYED_NAME = 'Accusé de rejet du dossier'
|
||||||
|
|
|
@ -15,6 +15,9 @@ module Mails
|
||||||
|
|
||||||
belongs_to :procedure, optional: false
|
belongs_to :procedure, optional: false
|
||||||
|
|
||||||
|
validates :subject, tags: true
|
||||||
|
validates :body, tags: true
|
||||||
|
|
||||||
SLUG = "without_continuation"
|
SLUG = "without_continuation"
|
||||||
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/without_continuation_mail"
|
DEFAULT_TEMPLATE_NAME = "notification_mailer/default_templates/without_continuation_mail"
|
||||||
DISPLAYED_NAME = 'Accusé de classement sans suite'
|
DISPLAYED_NAME = 'Accusé de classement sans suite'
|
||||||
|
|
|
@ -181,6 +181,14 @@ class Procedure < ApplicationRecord
|
||||||
types_de_champ_for_tags.private_only
|
types_de_champ_for_tags.private_only
|
||||||
end
|
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_procedures, dependent: :delete_all
|
||||||
has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
|
has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
|
||||||
has_many :groupe_instructeurs, dependent: :destroy
|
has_many :groupe_instructeurs, dependent: :destroy
|
||||||
|
|
64
app/validators/tags_validator.rb
Normal file
64
app/validators/tags_validator.rb
Normal file
|
@ -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
|
20
config/locales/models/attestation_template/fr.yml
Normal file
20
config/locales/models/attestation_template/fr.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
fr:
|
||||||
|
activerecord:
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
attestation_template:
|
||||||
|
attributes:
|
||||||
|
title:
|
||||||
|
format: Le titre du modèl 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 supression 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 à é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èl 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 supression 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 à é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
|
20
config/locales/models/closed_mail/fr.yml
Normal file
20
config/locales/models/closed_mail/fr.yml
Normal file
|
@ -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 à été supprimé mais la supression 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 à é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 à été supprimé mais la supression 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 à é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
|
20
config/locales/models/initiated_mail/fr.yml
Normal file
20
config/locales/models/initiated_mail/fr.yml
Normal file
|
@ -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 à été supprimé mais la supression 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 à é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 à été supprimé mais la supression 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 à é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
|
20
config/locales/models/received_mail/fr.yml
Normal file
20
config/locales/models/received_mail/fr.yml
Normal file
|
@ -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 à été supprimé mais la supression 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 à é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 à été supprimé mais la supression 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 à é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
|
20
config/locales/models/refused_mail/fr.yml
Normal file
20
config/locales/models/refused_mail/fr.yml
Normal file
|
@ -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 à été supprimé mais la supression 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 à é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 à été supprimé mais la supression 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 à é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
|
20
config/locales/models/without_continuation_mail/fr.yml
Normal file
20
config/locales/models/without_continuation_mail/fr.yml
Normal file
|
@ -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 à été supprimé mais la supression 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 à é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 à été supprimé mais la supression 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 à é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
|
87
spec/models/mail_template_spec.rb
Normal file
87
spec/models/mail_template_spec.rb
Normal file
|
@ -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ée"]) }
|
||||||
|
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 à été supprimé mais la supression 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 à é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
|
Loading…
Reference in a new issue