diff --git a/app/form_models/admin/user_form.rb b/app/form_models/admin/user_form.rb index 55241bf764..fbfaaa7c7f 100644 --- a/app/form_models/admin/user_form.rb +++ b/app/form_models/admin/user_form.rb @@ -5,17 +5,9 @@ class Admin::UserForm validate :validate_duplicates - delegate :ignore_benign_errors, :ignore_benign_errors=, :add_benign_error, :benign_errors, :not_benign_errors, :errors_are_all_benign?, to: :user + delegate :ants_pre_demande_number, :ignore_benign_errors, :ignore_benign_errors=, :add_benign_error, :benign_errors, :not_benign_errors, :errors_are_all_benign?, to: :user validate :warn_duplicates - validate do - if @user.ants_pre_demande_number.present? - ValidateAntsPreDemandeNumber.perform( - user: @user, - ants_pre_demande_number: @user.ants_pre_demande_number, - ignore_benign_errors: ignore_benign_errors - ) - end - end + validates_with AntsPreDemandeNumberValidation, if: -> { @user.ants_pre_demande_number.present? } delegate :errors, to: :user diff --git a/app/form_models/beneficiaire_form.rb b/app/form_models/beneficiaire_form.rb index 13e4d877a0..81379855dc 100644 --- a/app/form_models/beneficiaire_form.rb +++ b/app/form_models/beneficiaire_form.rb @@ -15,15 +15,7 @@ class BeneficiaireForm validates_presence_of :first_name, :last_name validate :warn_no_contact_information validate :validate_phone_number - validate do - if Motif.find_by(id: motif_id)&.requires_ants_predemande_number? - ValidateAntsPreDemandeNumber.perform( - user: self, - ants_pre_demande_number: ants_pre_demande_number, - ignore_benign_errors: ignore_benign_errors - ) - end - end + validates_with AntsPreDemandeNumberValidation, if: :requires_ants_predemande_number? def warn_no_contact_information return if ignore_benign_errors @@ -39,4 +31,8 @@ def validate_phone_number errors.add(:phone_number, :invalid) if PhoneNumberValidation.parsed_number(phone_number).blank? errors.add(:phone_number, "ne permet pas de recevoir des SMS") unless PhoneNumberValidation.number_is_mobile?(phone_number) end + + def requires_ants_predemande_number? + Motif.find_by(id: motif_id)&.requires_ants_predemande_number? + end end diff --git a/app/form_models/user_rdv_wizard.rb b/app/form_models/user_rdv_wizard.rb index fdd1a89528..113dfaa278 100644 --- a/app/form_models/user_rdv_wizard.rb +++ b/app/form_models/user_rdv_wizard.rb @@ -83,29 +83,28 @@ class Step1 < Base delegate :errors, to: :user validate :phone_number_present_for_motif_by_phone - validate do - if rdv.requires_ants_predemande_number? - ValidateAntsPreDemandeNumber.perform( - user: @user, - ants_pre_demande_number: @user_attributes[:ants_pre_demande_number], - ignore_benign_errors: @user_attributes[:ignore_benign_errors] - ) - end - end def phone_number_present_for_motif_by_phone - errors.add(:phone_number, :missing_for_phone_motif) if rdv.motif.phone? && @user_attributes[:phone_number].blank? + errors.add(:phone_number, :missing_for_phone_motif) if rdv.motif.phone? && user.phone_number.blank? end def initialize(user, attributes) super - @user_attributes = @attributes[:user]&.with_indifferent_access + @user&.assign_attributes(@attributes.fetch(:user, {})) end def save # we make sure the email can be updated only if it is blank @user.skip_reconfirmation! if @user.email_was.blank? - valid? && @user.update(@user_attributes) + + # dans la vue on appelle form_for(user) plutôt que form_for(user_rdv_wizard) les erreurs doivent donc être + # définies sur le user et on ne peut pas simplement appeler `validates_with AntsPreDemandeNumberValidation` + # dans ce form model + if rdv.requires_ants_predemande_number? + @user.singleton_class.validates_with(AntsPreDemandeNumberValidation) + end + + valid? && @user.save end end diff --git a/app/services/validate_ants_pre_demande_number.rb b/app/services/validate_ants_pre_demande_number.rb deleted file mode 100644 index 61f496360d..0000000000 --- a/app/services/validate_ants_pre_demande_number.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ValidateAntsPreDemandeNumber - extend ActionView::Helpers::TranslationHelper # allows getting a SafeBuffer instead of a String when using #translate (which a direct call to I18n.t doesn't do) - - def self.perform(user:, ants_pre_demande_number:, ignore_benign_errors:) - ants_pre_demande_number = ants_pre_demande_number.upcase - - unless ants_pre_demande_number.match?(/\A[A-Z0-9]{10}\z/) - user.errors.add(:ants_pre_demande_number, :invalid_format) - return - end - - application_hash = AntsApi.status(ants_pre_demande_number:, timeout: 4) - - status = application_hash["status"] - - if status == "validated" - - if application_hash["appointments"].any? - appointment = OpenStruct.new(application_hash["appointments"].first) - user.add_benign_error(warning_message(appointment)) unless ignore_benign_errors - end - - else - user.errors.add(:ants_pre_demande_number, AntsApi::ERROR_STATUSES.fetch(status)) - end - rescue AntsApi::ApiRequestError, Typhoeus::Errors::TimeoutError => e - # Si l'API de l'ANTS est fiable, donc si elle renvoie une erreur ou un timeout, - # on préfère bloquer la réservation et logguer l'erreur. - user.errors.add(:ants_pre_demande_number, :unexpected_api_error) - Sentry.capture_exception(e) - end - - def self.warning_message(appointment) - translate( - "activerecord.warnings.models.user.ants_pre_demande_number_already_used_html", - management_url: appointment.management_url, - meeting_point: appointment.meeting_point - ) - end -end diff --git a/app/validators/ants_pre_demande_number_validation.rb b/app/validators/ants_pre_demande_number_validation.rb new file mode 100644 index 0000000000..a28542879e --- /dev/null +++ b/app/validators/ants_pre_demande_number_validation.rb @@ -0,0 +1,43 @@ +# Cette validation ne peut être utilisée que si le record (ou le form model) +# a un attribut ants_pre_demande_number et inclut le module BenignErrors +# +# Cette validation fait des requêtes HTTP externes à l’API de l’ANTS +# +# cf /docs/interconnexions/ants.md + +class AntsPreDemandeNumberValidation < ActiveModel::Validator + def validate(record) + ants_pre_demande_number = record.ants_pre_demande_number.upcase + + unless ants_pre_demande_number.match?(/\A[A-Z0-9]{10}\z/) + record.errors.add(:ants_pre_demande_number, "doit comporter 10 chiffres et lettres") + return + end + + application_hash = AntsApi.status(ants_pre_demande_number:, timeout: 4) + + status = application_hash["status"] + + if status == "validated" + if application_hash["appointments"].any? + appointment = OpenStruct.new(application_hash["appointments"].first) + record.add_benign_error(warning_message(appointment)) unless record.ignore_benign_errors + end + else + record.errors.add(:ants_pre_demande_number, AntsApi::ERROR_STATUSES.fetch(status)) + end + rescue AntsApi::ApiRequestError, Typhoeus::Errors::TimeoutError => e + # Si l'API de l'ANTS est fiable, donc si elle renvoie une erreur ou un timeout, + # on préfère bloquer la réservation et logguer l'erreur. + record.errors.add(:ants_pre_demande_number, "n'a pas pu être validé à cause d'une erreur inattendue. Merci de réessayer dans 30 secondes.") + Sentry.capture_exception(e) + end + + def warning_message(appointment) + I18n.t( + "activerecord.warnings.models.user.ants_pre_demande_number_already_used_html", + management_url: appointment.management_url, + meeting_point: appointment.meeting_point + ).html_safe + end +end diff --git a/spec/form_models/user_rdv_wizard_spec.rb b/spec/form_models/user_rdv_wizard_spec.rb index 147b40ffd2..19c37553af 100644 --- a/spec/form_models/user_rdv_wizard_spec.rb +++ b/spec/form_models/user_rdv_wizard_spec.rb @@ -117,7 +117,7 @@ expect(form.errors.first.attribute).to eq(:ants_pre_demande_number) expect(form.errors.first.message).to eq("doit comporter 10 chiffres et lettres") # le message affiché est en fait celui sur le user - expect(form.user.errors.first.full_message).to eq("Numéro de pré-demande ANTS doit comporter 10 chiffres et lettres") + expect(form.errors.first.full_message).to eq("Numéro de pré-demande ANTS doit comporter 10 chiffres et lettres") end end @@ -147,7 +147,7 @@ expect(res).to be false expect(form.errors.count).to eq(1) expect(form.errors.first.attribute).to eq(:ants_pre_demande_number) - expect(form.user.errors.first.full_message).to eq("Numéro de pré-demande ANTS n'est pas reconnu par l'ANTS") + expect(form.errors.first.full_message).to eq("Numéro de pré-demande ANTS n'est pas reconnu par l'ANTS") end end @@ -252,7 +252,7 @@ expect(res).to be false expect(form.errors.count).to eq(1) expect(form.errors.first.attribute).to eq(:ants_pre_demande_number) - expect(form.user.errors.first.full_message).to eq("Numéro de pré-demande ANTS n'a pas pu être validé à cause d'une erreur inattendue. Merci de réessayer dans 30 secondes.") + expect(form.errors.first.full_message).to eq("Numéro de pré-demande ANTS n'a pas pu être validé à cause d'une erreur inattendue. Merci de réessayer dans 30 secondes.") end end end