From 95de0d6239e1ed6634cbf0fb84afa59ec363eff2 Mon Sep 17 00:00:00 2001 From: mfo Date: Fri, 12 Apr 2024 17:59:50 +0200 Subject: [PATCH 1/4] tech(refactor): identity blocks --- .../dossiers/individual_form_component.rb | 11 +++ .../individual_form_component.en.yml | 7 ++ .../individual_form_component.fr.yml | 7 ++ .../individual_form_component.html.haml | 76 +++++++++++++++ app/controllers/users/dossiers_controller.rb | 7 ++ .../controllers/for_tiers_controller.ts | 93 ++++--------------- app/views/users/dossiers/identite.html.haml | 81 +--------------- .../users/dossiers/identite.turbo_stream.haml | 1 + config/locales/en.yml | 5 - config/locales/fr.yml | 5 - config/routes.rb | 1 + .../shared_examples_for_prefilled_dossier.rb | 4 +- spec/system/accessibilite/wcag_usager_spec.rb | 4 +- .../api_particulier/api_particulier_spec.rb | 16 +++- .../routing/rules_full_scenario_spec.rb | 4 +- spec/system/users/brouillon_spec.rb | 4 +- spec/system/users/dossier_creation_spec.rb | 20 +++- spec/system/users/dropdown_spec.rb | 4 +- spec/system/users/linked_dropdown_spec.rb | 4 +- 19 files changed, 180 insertions(+), 174 deletions(-) create mode 100644 app/components/dossiers/individual_form_component.rb create mode 100644 app/components/dossiers/individual_form_component/individual_form_component.en.yml create mode 100644 app/components/dossiers/individual_form_component/individual_form_component.fr.yml create mode 100644 app/components/dossiers/individual_form_component/individual_form_component.html.haml create mode 100644 app/views/users/dossiers/identite.turbo_stream.haml diff --git a/app/components/dossiers/individual_form_component.rb b/app/components/dossiers/individual_form_component.rb new file mode 100644 index 000000000..59ce27d9f --- /dev/null +++ b/app/components/dossiers/individual_form_component.rb @@ -0,0 +1,11 @@ +class Dossiers::IndividualFormComponent < ApplicationComponent + delegate :for_tiers?, to: :@dossier + + def initialize(dossier:) + @dossier = dossier + end + + def email_notifications?(individual) + individual.object.notification_method == Individual.notification_methods[:email] + end +end diff --git a/app/components/dossiers/individual_form_component/individual_form_component.en.yml b/app/components/dossiers/individual_form_component/individual_form_component.en.yml new file mode 100644 index 000000000..fbaffd2e5 --- /dev/null +++ b/app/components/dossiers/individual_form_component/individual_form_component.en.yml @@ -0,0 +1,7 @@ +--- +en: + self_title: Your identity + callout_text: "You are acting as a proxy for a principal, either professionally (such as accountant, lawyer, civil servant…) or personally (family). Make sure to respect the conditions of" + callout_link: Articles 1984 and following of the Civil Code. + callout_link_title: Articles 1984 and following of the Civil Code + beneficiaire_title: "Identity of the beneficiary" diff --git a/app/components/dossiers/individual_form_component/individual_form_component.fr.yml b/app/components/dossiers/individual_form_component/individual_form_component.fr.yml new file mode 100644 index 000000000..86512c87b --- /dev/null +++ b/app/components/dossiers/individual_form_component/individual_form_component.fr.yml @@ -0,0 +1,7 @@ +--- +fr: + self_title: Votre identité + callout_text: Vous agissez en tant que mandataire, soit professionnellement (comme expert-comptable, avocat, agent public…) soit personnellement (famille). Assurez-vous de respecter les conditions + callout_link: des Articles 1984 et suivants du Code civil. + callout_link_title: Articles 1984 et suivants du Code civil + beneficiaire_title: Identité du bénéficiaire diff --git a/app/components/dossiers/individual_form_component/individual_form_component.html.haml b/app/components/dossiers/individual_form_component/individual_form_component.html.haml new file mode 100644 index 000000000..ceab537b3 --- /dev/null +++ b/app/components/dossiers/individual_form_component/individual_form_component.html.haml @@ -0,0 +1,76 @@ += form_for @dossier, url: update_identite_dossier_path(@dossier), html: { id: 'identite-form', class: "form", "data-controller" => "for-tiers" } do |f| + - if for_tiers? + .fr-alert.fr-alert--info.fr-mb-2w + %p.fr-notice__text + = t('.callout_text') + = link_to(t('.callout_link'), + 'https://www.legifrance.gouv.fr/codes/section_lc/LEGITEXT000006070721/LEGISCTA000006136404/#LEGISCTA000006136404', + title: helpers.new_tab_suffix(t('.callout_link_title')), + **helpers.external_link_attributes) + + %fieldset.fr-fieldset.mandataire-infos + %legend.fr-fieldset__legend--regular.fr-fieldset__legend + %h2.fr-h4 + = t('.self_title') + + .fr-fieldset__element.fr-fieldset__element--short-text + = render Dsfr::InputComponent.new(form: f, attribute: :mandataire_first_name, opts: { autocomplete: 'given-name' }) + + .fr-fieldset__element.fr-fieldset__element--short-text + = render Dsfr::InputComponent.new(form: f, attribute: :mandataire_last_name, opts: { autocomplete: 'family-name' }) + + = f.fields_for :individual, include_id: false do |individual| + %fieldset.fr-fieldset.fr-mb-0.individual-infos + %legend.fr-fieldset__legend--regular.fr-fieldset__legend + %h2.fr-h4 + - if for_tiers? + = t('.beneficiaire_title') + - else + = t('.self_title') + + .fr-fieldset__element.fr-mb-0 + %fieldset.fr-fieldset + %legend.fr-fieldset__legend--regular.fr-fieldset__legend + = t('activerecord.attributes.individual.gender') + = render EditableChamp::AsteriskMandatoryComponent.new + + .fr-fieldset__element + .fr-radio-group + = individual.radio_button :gender, Individual::GENDER_FEMALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_FEMALE}" + %label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_FEMALE}" } + = Individual.human_attribute_name('gender.female') + .fr-fieldset__element + .fr-radio-group + = individual.radio_button :gender, Individual::GENDER_MALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_MALE}" + %label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_MALE}" } + = Individual.human_attribute_name('gender.male') + .fr-fieldset__element.fr-mb-0 + %fieldset.fr-fieldset.width-100 + .fr-fieldset__element.fr-fieldset__element--short-text + = render Dsfr::InputComponent.new(form: individual, attribute: :prenom, opts: { autocomplete: (for_tiers? ? false : 'given-name') }) + .fr-fieldset__element.fr-fieldset__element--short-text + = render Dsfr::InputComponent.new(form: individual, attribute: :nom, opts: { autocomplete: (for_tiers? ? false : 'family-name') }) + + - if @dossier.procedure.ask_birthday? + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: individual, attribute: :birthdate, input_type: :date_field, + opts: { placeholder: 'Format : AAAA-MM-JJ', max: Date.today.iso8601, min: "1900-01-01", autocomplete: 'bday' }) + + - if for_tiers? + %fieldset.fr-fieldset + %legend.fr-fieldset__legend--regular.fr-fieldset__legend + = t('activerecord.attributes.individual.notification_method') + = render EditableChamp::AsteriskMandatoryComponent.new + + - Individual.notification_methods.each do |method, _| + .fr-fieldset__element + .fr-radio-group + = individual.radio_button :notification_method, method, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleEmailInput", "data-for-tiers-target" => "notificationMethod" + %label.fr-label{ for: "notification_method_#{method}" } + = t("activerecord.attributes.individual.notification_methods.#{method}") + + + .fr-fieldset__element.fr-fieldset__element--short-text{ "data-for-tiers-target" => "emailContainer", class: class_names(hidden: !email_notifications?(individual)) } + = render Dsfr::InputComponent.new(form: individual, attribute: :email, input_type: :email_field, opts: { "data-for-tiers-target" => "emailInput" }) + + = f.submit t('views.users.dossiers.identite.continue'), class: "fr-btn" diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 92f4d00a8..08ecd2f55 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -135,6 +135,13 @@ module Users @dossier = dossier @user = current_user @no_description = true + + respond_to do |format| + format.html + format.turbo_stream do + @dossier.update_columns(params.require(:dossier).permit(:for_tiers).to_h) + end + end end def update_identite diff --git a/app/javascript/controllers/for_tiers_controller.ts b/app/javascript/controllers/for_tiers_controller.ts index 7aa955da0..6ec3a821c 100644 --- a/app/javascript/controllers/for_tiers_controller.ts +++ b/app/javascript/controllers/for_tiers_controller.ts @@ -1,87 +1,32 @@ +import { toggle } from '@utils'; import { ApplicationController } from './application_controller'; +function onVisibleEnableInputs(element: HTMLInputElement) { + element.disabled = false; + element.required = true; +} + +function onHiddenDisableInputs(element: HTMLInputElement) { + element.disabled = true; + element.required = false; +} + export class ForTiersController extends ApplicationController { - static targets = [ - 'mandataireFirstName', - 'mandataireLastName', - 'forTiers', - 'mandataireBlock', - 'beneficiaireNotificationBlock', - 'email', - 'notificationMethod', - 'mandataireTitle', - 'beneficiaireTitle', - 'emailInput' - ]; + static targets = ['emailContainer', 'emailInput', 'notificationMethod']; - declare mandataireFirstNameTarget: HTMLInputElement; - declare mandataireLastNameTarget: HTMLInputElement; - declare forTiersTargets: NodeListOf; - declare mandataireBlockTarget: HTMLElement; - declare beneficiaireNotificationBlockTarget: HTMLElement; declare notificationMethodTargets: NodeListOf; - declare emailTarget: HTMLInputElement; - declare mandataireTitleTarget: HTMLElement; - declare beneficiaireTitleTarget: HTMLElement; - declare emailInput: HTMLInputElement; + declare emailContainerTarget: HTMLElement; + declare emailInputTarget: HTMLInputElement; - connect() { - const emailInputElement = this.emailTarget.querySelector('input'); - if (emailInputElement) { - this.emailInput = emailInputElement; - } - this.toggleFieldRequirements(); - this.addAllEventListeners(); - } - - addAllEventListeners() { - this.forTiersTargets.forEach((radio) => { - radio.addEventListener('change', () => this.toggleFieldRequirements()); - }); - this.notificationMethodTargets.forEach((radio) => { - radio.addEventListener('change', () => this.toggleEmailInput()); - }); - } - - toggleFieldRequirements() { - const forTiersSelected = this.isForTiersSelected(); - this.toggleDisplay(this.mandataireBlockTarget, forTiersSelected); - this.toggleDisplay( - this.beneficiaireNotificationBlockTarget, - forTiersSelected - ); - this.mandataireFirstNameTarget.required = forTiersSelected; - this.mandataireLastNameTarget.required = forTiersSelected; - this.mandataireTitleTarget.classList.toggle('hidden', forTiersSelected); - this.beneficiaireTitleTarget.classList.toggle('hidden', !forTiersSelected); - this.notificationMethodTargets.forEach((radio) => { - radio.required = forTiersSelected; - }); - - this.toggleEmailInput(); - } - - isForTiersSelected() { - return Array.from(this.forTiersTargets).some( - (radio) => radio.checked && radio.value === 'true' - ); - } - - toggleDisplay(element: HTMLElement, shouldDisplay: boolean) { - element.classList.toggle('hidden', !shouldDisplay); - } toggleEmailInput() { const isEmailSelected = this.isEmailSelected(); - const forTiersSelected = this.isForTiersSelected(); - if (this.emailInput) { - this.emailInput.required = forTiersSelected && isEmailSelected; + toggle(this.emailContainerTarget, isEmailSelected); - if (!isEmailSelected) { - this.emailInput.value = ''; - } - - this.toggleDisplay(this.emailTarget, forTiersSelected && isEmailSelected); + if (isEmailSelected) { + onVisibleEnableInputs(this.emailInputTarget); + } else { + onHiddenDisableInputs(this.emailInputTarget); } } diff --git a/app/views/users/dossiers/identite.html.haml b/app/views/users/dossiers/identite.html.haml index ed0f5f432..afb250715 100644 --- a/app/views/users/dossiers/identite.html.haml +++ b/app/views/users/dossiers/identite.html.haml @@ -3,7 +3,7 @@ = render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier } - if !dossier_submission_is_closed?(@dossier) - = form_for @dossier, url: update_identite_dossier_path(@dossier), html: { class: "form", "data-controller" => "for-tiers" } do |f| + = form_for @dossier, url: identite_dossier_path(@dossier), method: :patch, html: { class: "form" }, data: {turbo: true, controller: :autosubmit} do |f| %fieldset#radio-rich-hint.fr-fieldset{ "aria-labelledby" => "radio-rich-hint-legend radio-rich-hint-messages" } %legend#radio-rich-hint-legend.fr-fieldset__legend--regular.fr-fieldset__legend @@ -11,90 +11,19 @@ .fr-fieldset__element .fr-radio-group.fr-radio-rich - = f.radio_button :for_tiers, false, required: true, id: "radio-self-manage", "data-action" => "click->for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "forTiers" + = f.radio_button :for_tiers, false, required: true, id: "radio-self-manage" %label.fr-label{ for: "radio-self-manage" } = t('activerecord.attributes.dossier.for_tiers.false') .fr-radio-rich__img %span.fr-icon-user-fill .fr-fieldset__element .fr-radio-group.fr-radio-rich - = f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage", "data-action" => "click->for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "forTiers" + = f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage" %label.fr-label{ for: "radio-tiers-manage" } = t('activerecord.attributes.dossier.for_tiers.true') .fr-radio-rich__img %span.fr-icon-parent-fill - .mandataire-infos{ "data-for-tiers-target" => "mandataireBlock" } - .fr-alert.fr-alert--info.fr-mb-2w - %p.fr-notice__text - = t('views.users.dossiers.identite.callout_text') - = link_to(t('views.users.dossiers.identite.callout_link'), - 'https://www.legifrance.gouv.fr/codes/section_lc/LEGITEXT000006070721/LEGISCTA000006136404/#LEGISCTA000006136404', - title: new_tab_suffix(t('views.users.dossiers.identite.callout_link_title')), - **external_link_attributes) + = f.submit t('views.users.dossiers.identite.continue'), class: 'visually-hidden' - - %fieldset.fr-fieldset - %legend.fr-fieldset__legend--regular.fr-fieldset__legend - %h2.fr-h4= t('views.users.dossiers.identite.self_title') - - .fr-fieldset__element.fr-fieldset__element--short-text - = render Dsfr::InputComponent.new(form: f, attribute: :mandataire_first_name, opts: { "data-for-tiers-target" => "mandataireFirstName" }) - - .fr-fieldset__element.fr-fieldset__element--short-text - = render Dsfr::InputComponent.new(form: f, attribute: :mandataire_last_name, opts: { "data-for-tiers-target" => "mandataireLastName" }) - - = f.fields_for :individual, include_id: false do |individual| - .individual-infos - %fieldset.fr-fieldset - %legend.fr-fieldset__legend--regular.fr-fieldset__legend{ "data-for-tiers-target" => "mandataireTitle" } - %h2.fr-h4= t('views.users.dossiers.identite.self_title') - - %legend.fr-fieldset__legend--regular.fr-fieldset__legend.hidden{ "data-for-tiers-target" => "beneficiaireTitle" } - %h2.fr-h4= t('views.users.dossiers.identite.beneficiaire_title') - - - %legend.fr-fieldset__legend--regular.fr-fieldset__legend - = t('activerecord.attributes.individual.gender') - = render EditableChamp::AsteriskMandatoryComponent.new - .fr-fieldset__element - .fr-radio-group - = individual.radio_button :gender, Individual::GENDER_FEMALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_FEMALE}" - %label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_FEMALE}" } - = Individual.human_attribute_name('gender.female') - .fr-fieldset__element - .fr-radio-group - = individual.radio_button :gender, Individual::GENDER_MALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_MALE}" - %label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_MALE}" } - = Individual.human_attribute_name('gender.male') - - .fr-fieldset__element.fr-fieldset__element--short-text - = render Dsfr::InputComponent.new(form: individual, attribute: :prenom, opts: { autocomplete: 'given-name' }) - - .fr-fieldset__element.fr-fieldset__element--short-text - = render Dsfr::InputComponent.new(form: individual, attribute: :nom, opts: { autocomplete: 'family-name' }) - - %fieldset.fr-fieldset{ "data-for-tiers-target" => "beneficiaireNotificationBlock" } - %legend.fr-fieldset__legend--regular.fr-fieldset__legend - = t('activerecord.attributes.individual.notification_method') - = render EditableChamp::AsteriskMandatoryComponent.new - - - Individual.notification_methods.each do |method, _| - .fr-fieldset__element - .fr-radio-group - = individual.radio_button :notification_method, method, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "notificationMethod" - %label.fr-label{ for: "notification_method_#{method}" } - = t("activerecord.attributes.individual.notification_methods.#{method}") - - - .fr-fieldset__element.fr-fieldset__element--short-text.hidden{ "data-for-tiers-target" => "email" } - = render Dsfr::InputComponent.new(form: individual, attribute: :email) - - - - if @dossier.procedure.ask_birthday? - .fr-fieldset__element - = render Dsfr::InputComponent.new(form: individual, attribute: :birthdate, input_type: :date_field, - opts: { placeholder: 'Format : AAAA-MM-JJ', max: Date.today.iso8601, min: "1900-01-01", autocomplete: 'bday' }) - - - = f.submit t('views.users.dossiers.identite.continue'), class: "fr-btn" + = render Dossiers::IndividualFormComponent.new(dossier: @dossier) diff --git a/app/views/users/dossiers/identite.turbo_stream.haml b/app/views/users/dossiers/identite.turbo_stream.haml new file mode 100644 index 000000000..b7c032d59 --- /dev/null +++ b/app/views/users/dossiers/identite.turbo_stream.haml @@ -0,0 +1 @@ += turbo_stream.replace 'identite-form', render(Dossiers::IndividualFormComponent.new(dossier: @dossier)) diff --git a/config/locales/en.yml b/config/locales/en.yml index ef9cb539a..897ee5ac0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -423,11 +423,6 @@ en: archived_dossier: "Your file will be kept %{duree_conservation_dossiers_dans_ds} more months" identite: legend: 'This file is:' - self_title: Your identity - callout_text: "You are acting as a proxy for a principal, either professionally (such as accountant, lawyer, civil servant…) or personally (family). Make sure to respect the conditions of" - callout_link: Articles 1984 and following of the Civil Code. - callout_link_title: Articles 1984 and following of the Civil Code - beneficiaire_title: "Identity of the beneficiary" identity_siret: Identify your establishment civility: Civility first_name: First Name diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6b5dfe622..a1b2568e6 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -426,11 +426,6 @@ fr: archived_dossier: "Votre dossier sera conservé %{duree_conservation_dossiers_dans_ds} mois supplémentaire" identite: legend: 'Ce dossier est : ' - self_title: Votre identité - callout_text: Vous agissez en tant que mandataire, soit professionnellement (comme expert-comptable, avocat, agent public…) soit personnellement (famille). Assurez-vous de respecter les conditions - callout_link: des Articles 1984 et suivants du Code civil. - callout_link_title: Articles 1984 et suivants du Code civil - beneficiaire_title: Identité du bénéficiaire identity_siret: Identifier votre établissement civility: Civilité first_name: Prénom diff --git a/config/routes.rb b/config/routes.rb index caf5a3358..9256e08a2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -361,6 +361,7 @@ Rails.application.routes.draw do resources :dossiers, only: [:index, :show, :destroy, :new] do member do get 'identite' + patch 'identite' patch 'update_identite' post 'clone' get 'siret' diff --git a/spec/support/shared_examples_for_prefilled_dossier.rb b/spec/support/shared_examples_for_prefilled_dossier.rb index 4a31f3668..3ed07a18b 100644 --- a/spec/support/shared_examples_for_prefilled_dossier.rb +++ b/spec/support/shared_examples_for_prefilled_dossier.rb @@ -8,7 +8,9 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do expect(page).to have_field('Prénom', with: prenom_value) expect(page).to have_field('Nom', with: nom_value) end - click_on 'Continuer' + within "#identite-form" do + click_on 'Continuer' + end expect(page).to have_current_path(brouillon_dossier_path(dossier)) expect(page).to have_field(type_de_champ_text.libelle, with: text_value) diff --git a/spec/system/accessibilite/wcag_usager_spec.rb b/spec/system/accessibilite/wcag_usager_spec.rb index a262774c5..d8ad6fd10 100644 --- a/spec/system/accessibilite/wcag_usager_spec.rb +++ b/spec/system/accessibilite/wcag_usager_spec.rb @@ -126,7 +126,9 @@ describe 'wcag rules for usager', js: true do fill_in('Prénom', with: 'prenom') fill_in('Nom', with: 'nom') end - click_on 'Continuer' + within "#identite-form" do + click_on 'Continuer' + end expect(page).to be_axe_clean end diff --git a/spec/system/api_particulier/api_particulier_spec.rb b/spec/system/api_particulier/api_particulier_spec.rb index ecd9a1982..aa06327db 100644 --- a/spec/system/api_particulier/api_particulier_spec.rb +++ b/spec/system/api_particulier/api_particulier_spec.rb @@ -272,7 +272,9 @@ describe 'fetch API Particulier Data', js: true do fill_in('Nom', with: 'nom') end - click_button('Continuer') + within "#identite-form" do + click_on 'Continuer' + end fill_in 'Le numéro d’allocataire CAF', with: numero_allocataire fill_in 'Le code postal', with: 'wrong_code' @@ -331,7 +333,9 @@ describe 'fetch API Particulier Data', js: true do fill_in('Prénom', with: 'Georges') fill_in('Nom', with: 'Moustaki') end - click_button('Continuer') + within "#identite-form" do + click_on 'Continuer' + end fill_in "Identifiant", with: 'wrong code' @@ -405,7 +409,9 @@ describe 'fetch API Particulier Data', js: true do fill_in('Prénom', with: 'Angela Claire Louise') fill_in('Nom', with: 'Dubois') end - click_button('Continuer') + within "#identite-form" do + click_on 'Continuer' + end fill_in "INE", with: 'wrong code' @@ -469,7 +475,9 @@ describe 'fetch API Particulier Data', js: true do fill_in('Prénom', with: 'Karine') fill_in('Nom', with: 'FERRI') end - click_button('Continuer') + within "#identite-form" do + click_on 'Continuer' + end fill_in 'Le numéro fiscal', with: numero_fiscal fill_in "La référence d’avis d’imposition", with: 'wrong_code' diff --git a/spec/system/routing/rules_full_scenario_spec.rb b/spec/system/routing/rules_full_scenario_spec.rb index 4163ca067..8a94b15b4 100644 --- a/spec/system/routing/rules_full_scenario_spec.rb +++ b/spec/system/routing/rules_full_scenario_spec.rb @@ -258,7 +258,9 @@ describe 'The routing with rules', js: true do find('label', text: 'Monsieur').click fill_in('Prénom', with: 'prenom', visible: true) fill_in('Nom', with: 'Nom', visible: true) - click_button('Continuer') + within "#identite-form" do + click_button('Continuer') + end # the old system should not be present expect(page).not_to have_selector("#dossier_groupe_instructeur_id") diff --git a/spec/system/users/brouillon_spec.rb b/spec/system/users/brouillon_spec.rb index cd20a584b..97498b83b 100644 --- a/spec/system/users/brouillon_spec.rb +++ b/spec/system/users/brouillon_spec.rb @@ -665,7 +665,9 @@ describe 'The user' do find('label', text: 'Monsieur').click fill_in('Prénom', with: 'prenom', visible: true) fill_in('Nom', with: 'Nom', visible: true) - click_on 'Continuer' + within "#identite-form" do + click_on 'Continuer' + end expect(page).to have_current_path(brouillon_dossier_path(user_dossier)) end end diff --git a/spec/system/users/dossier_creation_spec.rb b/spec/system/users/dossier_creation_spec.rb index 531b0735d..a930672cd 100644 --- a/spec/system/users/dossier_creation_spec.rb +++ b/spec/system/users/dossier_creation_spec.rb @@ -28,7 +28,9 @@ describe 'Creating a new dossier:', js: true do shared_examples 'the user can create a new draft' do it do - click_button('Continuer') + within "#identite-form" do + click_button('Continuer') + end expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last)) expect(user.dossiers.first.individual.birthdate).to eq(expected_birthday) @@ -58,7 +60,6 @@ describe 'Creating a new dossier:', js: true do context 'when individual fill dossier for a tiers' do it 'completes the form with email notification method selected' do find('label', text: 'Pour un bénéficiaire : membre de la famille, proche, mandant, professionnel en charge du suivi du dossier…').click - within('.mandataire-infos') do fill_in('Prénom', with: 'John') fill_in('Nom', with: 'Doe') @@ -73,7 +74,15 @@ describe 'Creating a new dossier:', js: true do find('label', text: 'Par e-mail').click fill_in('dossier_individual_attributes_email', with: 'prenom.nom@mail.com') - click_button('Continuer') + find('label', text: 'Monsieur').click # force focus out + within "#identite-form" do + within '.suspect-email' do + expect(page).to have_content("Information : Voulez-vous dire ?") + click_button("Oui") + end + click_button("Continuer") + end + expect(procedure.dossiers.last.individual.notification_method == 'email') expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last)) end @@ -93,7 +102,10 @@ describe 'Creating a new dossier:', js: true do end find('label', text: 'Pas de notification').click - click_button('Continuer') + within "#identite-form" do + click_button('Continuer') + end + expect(procedure.dossiers.last.individual.notification_method.empty?) expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last)) end diff --git a/spec/system/users/dropdown_spec.rb b/spec/system/users/dropdown_spec.rb index 25645c02e..987570962 100644 --- a/spec/system/users/dropdown_spec.rb +++ b/spec/system/users/dropdown_spec.rb @@ -79,7 +79,9 @@ describe 'dropdown list with other option activated', js: true do fill_in('Prénom', with: 'prenom') fill_in('Nom', with: 'nom') end - click_on 'Continuer' + within "#identite-form" do + click_on 'Continuer' + end expect(page).to have_current_path(brouillon_dossier_path(user_dossier)) end end diff --git a/spec/system/users/linked_dropdown_spec.rb b/spec/system/users/linked_dropdown_spec.rb index 83b8fd459..c6f3b7cac 100644 --- a/spec/system/users/linked_dropdown_spec.rb +++ b/spec/system/users/linked_dropdown_spec.rb @@ -85,7 +85,9 @@ describe 'linked dropdown lists' do fill_in('Prénom', with: 'prenom') fill_in('Nom', with: 'nom') end - click_on 'Continuer' + within "#identite-form" do + click_on 'Continuer' + end expect(page).to have_current_path(brouillon_dossier_path(user_dossier)) end end From f6046d801fa6932c2e9d03e198f3c917527e32b0 Mon Sep 17 00:00:00 2001 From: mfo Date: Wed, 17 Apr 2024 07:04:15 +0200 Subject: [PATCH 2/4] feat(individual_form_component): add missing required on notification method --- .../individual_form_component.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/dossiers/individual_form_component/individual_form_component.html.haml b/app/components/dossiers/individual_form_component/individual_form_component.html.haml index ceab537b3..97b91976c 100644 --- a/app/components/dossiers/individual_form_component/individual_form_component.html.haml +++ b/app/components/dossiers/individual_form_component/individual_form_component.html.haml @@ -65,7 +65,7 @@ - Individual.notification_methods.each do |method, _| .fr-fieldset__element .fr-radio-group - = individual.radio_button :notification_method, method, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleEmailInput", "data-for-tiers-target" => "notificationMethod" + = individual.radio_button :notification_method, method, required: true, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleEmailInput", "data-for-tiers-target" => "notificationMethod" %label.fr-label{ for: "notification_method_#{method}" } = t("activerecord.attributes.individual.notification_methods.#{method}") From b40cc2a54e6bdc310a45044be9e8a9aa9e55daa5 Mon Sep 17 00:00:00 2001 From: mfo Date: Wed, 17 Apr 2024 07:04:51 +0200 Subject: [PATCH 3/4] feat(individual.validation): add missing strict_email validation --- app/models/individual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/individual.rb b/app/models/individual.rb index c3d92b9eb..76257a20c 100644 --- a/app/models/individual.rb +++ b/app/models/individual.rb @@ -15,7 +15,7 @@ class Individual < ApplicationRecord if: -> { dossier.for_tiers? }, on: :update - validates :email, presence: true, if: -> { dossier.for_tiers? && self.email? }, on: :update + validates :email, strict_email: true, presence: true, if: -> { dossier.for_tiers? && self.email? }, on: :update GENDER_MALE = "M." GENDER_FEMALE = 'Mme' From 84c1a485e592be07cd0d05e905aca43a7f3e0af8 Mon Sep 17 00:00:00 2001 From: mfo Date: Thu, 18 Apr 2024 11:28:49 +0200 Subject: [PATCH 4/4] feat(Procedure.for_tiers_enabled): allow super admin to disable for_tiers --- app/dashboards/procedure_dashboard.rb | 3 ++ app/views/users/dossiers/identite.html.haml | 39 ++++++++--------- config/locales/models/procedure/en.yml | 1 + config/locales/models/procedure/fr.yml | 1 + ...d_column_for_tiers_enabled_to_procedure.rb | 5 +++ db/schema.rb | 3 +- .../users/dossiers/identite.html.haml_spec.rb | 42 +++++++++++++++---- 7 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 db/migrate/20240417053843_add_column_for_tiers_enabled_to_procedure.rb diff --git a/app/dashboards/procedure_dashboard.rb b/app/dashboards/procedure_dashboard.rb index b7d889d37..134208df4 100644 --- a/app/dashboards/procedure_dashboard.rb +++ b/app/dashboards/procedure_dashboard.rb @@ -46,6 +46,7 @@ class ProcedureDashboard < Administrate::BaseDashboard max_duree_conservation_dossiers_dans_ds: Field::Number, estimated_duration_visible: Field::Boolean, piece_justificative_multiple: Field::Boolean, + for_tiers_enabled: Field::Boolean, replaced_by_procedure_id: Field::String, tags: Field::Text, template: Field::Boolean @@ -109,6 +110,7 @@ class ProcedureDashboard < Administrate::BaseDashboard :max_duree_conservation_dossiers_dans_ds, :estimated_duration_visible, :piece_justificative_multiple, + :for_tiers_enabled, :replaced_by_procedure_id ].freeze @@ -121,6 +123,7 @@ class ProcedureDashboard < Administrate::BaseDashboard :max_duree_conservation_dossiers_dans_ds, :estimated_duration_visible, :piece_justificative_multiple, + :for_tiers_enabled, :replaced_by_procedure_id ].freeze diff --git a/app/views/users/dossiers/identite.html.haml b/app/views/users/dossiers/identite.html.haml index afb250715..8f79877b4 100644 --- a/app/views/users/dossiers/identite.html.haml +++ b/app/views/users/dossiers/identite.html.haml @@ -3,27 +3,28 @@ = render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier } - if !dossier_submission_is_closed?(@dossier) - = form_for @dossier, url: identite_dossier_path(@dossier), method: :patch, html: { class: "form" }, data: {turbo: true, controller: :autosubmit} do |f| + - if @dossier.procedure.for_tiers_enabled? + = form_for @dossier, url: identite_dossier_path(@dossier), method: :patch, html: { class: "form" }, data: {turbo: true, controller: :autosubmit} do |f| - %fieldset#radio-rich-hint.fr-fieldset{ "aria-labelledby" => "radio-rich-hint-legend radio-rich-hint-messages" } - %legend#radio-rich-hint-legend.fr-fieldset__legend--regular.fr-fieldset__legend - = t('views.users.dossiers.identite.legend') + %fieldset#radio-rich-hint.fr-fieldset{ "aria-labelledby" => "radio-rich-hint-legend radio-rich-hint-messages" } + %legend#radio-rich-hint-legend.fr-fieldset__legend--regular.fr-fieldset__legend + = t('views.users.dossiers.identite.legend') - .fr-fieldset__element - .fr-radio-group.fr-radio-rich - = f.radio_button :for_tiers, false, required: true, id: "radio-self-manage" - %label.fr-label{ for: "radio-self-manage" } - = t('activerecord.attributes.dossier.for_tiers.false') - .fr-radio-rich__img - %span.fr-icon-user-fill - .fr-fieldset__element - .fr-radio-group.fr-radio-rich - = f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage" - %label.fr-label{ for: "radio-tiers-manage" } - = t('activerecord.attributes.dossier.for_tiers.true') - .fr-radio-rich__img - %span.fr-icon-parent-fill + .fr-fieldset__element + .fr-radio-group.fr-radio-rich + = f.radio_button :for_tiers, false, required: true, id: "radio-self-manage" + %label.fr-label{ for: "radio-self-manage" } + = t('activerecord.attributes.dossier.for_tiers.false') + .fr-radio-rich__img + %span.fr-icon-user-fill + .fr-fieldset__element + .fr-radio-group.fr-radio-rich + = f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage" + %label.fr-label{ for: "radio-tiers-manage" } + = t('activerecord.attributes.dossier.for_tiers.true') + .fr-radio-rich__img + %span.fr-icon-parent-fill - = f.submit t('views.users.dossiers.identite.continue'), class: 'visually-hidden' + = f.submit t('views.users.dossiers.identite.continue'), class: 'visually-hidden' = render Dossiers::IndividualFormComponent.new(dossier: @dossier) diff --git a/config/locales/models/procedure/en.yml b/config/locales/models/procedure/en.yml index 19aca8bdf..2f1d1c8b8 100644 --- a/config/locales/models/procedure/en.yml +++ b/config/locales/models/procedure/en.yml @@ -37,6 +37,7 @@ en: lien_dpo: Link or email to contact the data protection officer (DPO) duree_conservation_dossiers_dans_ds: Duration files will be kept max_duree_conservation_dossiers_dans_ds: Max duration allowed to keep files + for_tiers_enabled: Enable a third party to submit a file aasm_state: brouillon: Draft publiee: Published diff --git a/config/locales/models/procedure/fr.yml b/config/locales/models/procedure/fr.yml index cdf2309b8..78e2bcf26 100644 --- a/config/locales/models/procedure/fr.yml +++ b/config/locales/models/procedure/fr.yml @@ -20,6 +20,7 @@ fr: organisation: Organisme duree_conservation_dossiers_dans_ds: Durée de conservation des dossiers max_duree_conservation_dossiers_dans_ds: Durée maximale de conservation des dossiers (autorisée par un super admin) + for_tiers_enabled: Activer le dépot par un tiers id: Id libelle: Titre de la démarche description: Quel est l’objet de la démarche ? diff --git a/db/migrate/20240417053843_add_column_for_tiers_enabled_to_procedure.rb b/db/migrate/20240417053843_add_column_for_tiers_enabled_to_procedure.rb new file mode 100644 index 000000000..c2e40a0d4 --- /dev/null +++ b/db/migrate/20240417053843_add_column_for_tiers_enabled_to_procedure.rb @@ -0,0 +1,5 @@ +class AddColumnForTiersEnabledToProcedure < ActiveRecord::Migration[7.0] + def change + add_column :procedures, :for_tiers_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 1caaf322c..b936bf6b3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_04_16_062900) do +ActiveRecord::Schema[7.0].define(version: 2024_04_17_053843) do # These are extensions that must be enabled in order to support this database enable_extension "pg_buffercache" enable_extension "pg_stat_statements" @@ -894,6 +894,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_04_16_062900) do t.boolean "euro_flag", default: false t.boolean "experts_require_administrateur_invitation", default: false t.boolean "for_individual", default: false + t.boolean "for_tiers_enabled", default: true, null: false t.datetime "hidden_at", precision: nil t.datetime "hidden_at_as_template", precision: nil t.boolean "instructeurs_self_management_enabled", default: false diff --git a/spec/views/users/dossiers/identite.html.haml_spec.rb b/spec/views/users/dossiers/identite.html.haml_spec.rb index 7c065f980..0ca066eee 100644 --- a/spec/views/users/dossiers/identite.html.haml_spec.rb +++ b/spec/views/users/dossiers/identite.html.haml_spec.rb @@ -1,5 +1,4 @@ describe 'users/dossiers/identite', type: :view do - let(:procedure) { create(:simple_procedure, :for_individual) } let(:dossier) { create(:dossier, :with_service, state: Dossier.states.fetch(:brouillon), procedure: procedure) } before do @@ -9,18 +8,43 @@ describe 'users/dossiers/identite', type: :view do subject! { render } - it 'has identity fields' do - within('.individual-infos') do - expect(rendered).to have_field(id: 'Prenom') - expect(rendered).to have_field(id: 'Nom') + context 'when procedure has for_tiers_enabled' do + let(:procedure) { create(:simple_procedure, :for_individual) } + + it 'has choice for you or a tiers' do + expect(rendered).to have_content "Pour vous" + expect(rendered).to have_content "Pour un bénéficiaire : membre de la famille, proche, mandant, professionnel en charge du suivi du dossier…" + end + + it 'has identity fields' do + within('.individual-infos') do + expect(rendered).to have_field(id: 'Prenom') + expect(rendered).to have_field(id: 'Nom') + end + end + + context 'when the demarche asks for the birthdate' do + let(:procedure) { create(:simple_procedure, for_individual: true, ask_birthday: true) } + + it 'has a birthday field' do + expect(rendered).to have_field('Date de naissance') + end end end - context 'when the demarche asks for the birthdate' do - let(:procedure) { create(:simple_procedure, for_individual: true, ask_birthday: true) } + context 'when procedure has for_tiers_enabled' do + let(:procedure) { create(:simple_procedure, :for_individual, for_tiers_enabled: false) } - it 'has a birthday field' do - expect(rendered).to have_field('Date de naissance') + it 'has choice for you or a tiers' do + expect(rendered).not_to have_content "Pour vous" + expect(rendered).not_to have_content "Pour un bénéficiaire : membre de la famille, proche, mandant, professionnel en charge du suivi du dossier…" + end + + it 'has identity fields' do + within('.individual-infos') do + expect(rendered).to have_field(id: 'Prenom') + expect(rendered).to have_field(id: 'Nom') + end end end end