diff --git a/app/components/dossiers/champs_rows_show_component/champs_rows_show_component.html.haml b/app/components/dossiers/champs_rows_show_component/champs_rows_show_component.html.haml index 3dbe364946b..2d674662e77 100644 --- a/app/components/dossiers/champs_rows_show_component/champs_rows_show_component.html.haml +++ b/app/components/dossiers/champs_rows_show_component/champs_rows_show_component.html.haml @@ -48,6 +48,8 @@ = render partial: "shared/champs/regions/show", locals: { champ: champ } - when TypeDeChamp.type_champs.fetch(:rna) = render partial: "shared/champs/rna/show", locals: { champ: champ, profile: @profile } + - when TypeDeChamp.type_champs.fetch(:rnf) + = render partial: "shared/champs/rnf/show", locals: { champ: champ, profile: @profile } - when TypeDeChamp.type_champs.fetch(:epci) = render partial: "shared/champs/epci/show", locals: { champ: champ } - when TypeDeChamp.type_champs.fetch(:cojo) @@ -60,4 +62,3 @@ %p= helpers.number_with_html_delimiter(champ.to_s) - else = helpers.format_text_value(champ.to_s.strip) # format already wrap in p - diff --git a/app/components/editable_champ/rnf_component.rb b/app/components/editable_champ/rnf_component.rb new file mode 100644 index 00000000000..fc6835c2c2e --- /dev/null +++ b/app/components/editable_champ/rnf_component.rb @@ -0,0 +1,2 @@ +class EditableChamp::RNFComponent < EditableChamp::EditableChampBaseComponent +end diff --git a/app/components/editable_champ/rnf_component/rnf_component.en.yml b/app/components/editable_champ/rnf_component/rnf_component.en.yml new file mode 100644 index 00000000000..ab32743d927 --- /dev/null +++ b/app/components/editable_champ/rnf_component/rnf_component.en.yml @@ -0,0 +1,5 @@ +--- +en: + rnf_info_error: No fondation found + rnf_info_pending: RNF verification pending + rnf_info_success: "This RNF matches %{title}" diff --git a/app/components/editable_champ/rnf_component/rnf_component.fr.yml b/app/components/editable_champ/rnf_component/rnf_component.fr.yml new file mode 100644 index 00000000000..4040fd1587a --- /dev/null +++ b/app/components/editable_champ/rnf_component/rnf_component.fr.yml @@ -0,0 +1,5 @@ +--- +fr: + rnf_info_error: Aucune fondation trouvée + rnf_info_pending: Vérification du RNF en cours + rnf_info_success: "Ce RNF correspond à %{title}" diff --git a/app/components/editable_champ/rnf_component/rnf_component.html.haml b/app/components/editable_champ/rnf_component/rnf_component.html.haml new file mode 100644 index 00000000000..edf5f07f4a3 --- /dev/null +++ b/app/components/editable_champ/rnf_component/rnf_component.html.haml @@ -0,0 +1,9 @@ += @form.text_field :external_id, required: @champ.required?, class: "width-33-desktop fr-input small-margin", id: @champ.input_id + +.rnf-info{ id: dom_id(@champ, :rnf_info) } + - if @champ.fetch_external_data_error? + %p.fr-error-text= t('.rnf_info_error') + - elsif @champ.fetch_external_data_pending? + %p.fr-info-text= t('.rnf_info_pending') + - elsif @champ.data? + %p.fr-info-text= t('.rnf_info_success', title: @champ.title) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 5a4c047d1d0..af4dba163fd 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -483,6 +483,7 @@ def champs_public_params :code_departement, :accreditation_number, :accreditation_birthdate, + :rnf_id, value: [] ]) champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {} diff --git a/app/models/champs/rnf_champ.rb b/app/models/champs/rnf_champ.rb new file mode 100644 index 00000000000..d96b6ebde53 --- /dev/null +++ b/app/models/champs/rnf_champ.rb @@ -0,0 +1,23 @@ +class Champs::RNFChamp < Champ + store_accessor :data, :title, :email, :phone, :createdAt, :updatedAt, :dissolvedAt, :address, :status + + def rnf_id + external_id + end + + def fetch_external_data + RNFService.new.(rnf_id:) + end + + def fetch_external_data? + true + end + + def poll_external_data? + true + end + + def blank? + rnf_id.blank? + end +end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index da383554980..8f196cb719f 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -51,6 +51,7 @@ class TypeDeChamp < ApplicationRecord yes_no: CHOICE, annuaire_education: REFERENTIEL_EXTERNE, rna: REFERENTIEL_EXTERNE, + rnf: REFERENTIEL_EXTERNE, carte: REFERENTIEL_EXTERNE, cnaf: REFERENTIEL_EXTERNE, dgfip: REFERENTIEL_EXTERNE, @@ -91,6 +92,7 @@ class TypeDeChamp < ApplicationRecord yes_no: 'yes_no', annuaire_education: 'annuaire_education', rna: 'rna', + rnf: 'rnf', carte: 'carte', cnaf: 'cnaf', dgfip: 'dgfip', diff --git a/app/models/types_de_champ/rnf_type_de_champ.rb b/app/models/types_de_champ/rnf_type_de_champ.rb new file mode 100644 index 00000000000..ce3b24c9c7e --- /dev/null +++ b/app/models/types_de_champ/rnf_type_de_champ.rb @@ -0,0 +1,2 @@ +class TypesDeChamp::RNFTypeDeChamp < TypesDeChamp::TextTypeDeChamp +end diff --git a/app/schemas/rnf.json b/app/schemas/rnf.json new file mode 100644 index 00000000000..866821090d5 --- /dev/null +++ b/app/schemas/rnf.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://demarches-simplifiees.fr/rnf.schema.json", + "title": "RNF", + "type": "object", + "properties": { + "id": { "type": "integer" }, + "rnfId": { "type": "string" }, + "type": { "type": "string" }, + "department": { "type": "string" }, + "title": { "type": "string" }, + "dissolvedAt": { "type": ["string", "null"] }, + "phone": { "type": "string" }, + "email": { "type": "string" }, + "addressId": { "type": "integer" }, + "address": { + "id": { "type": "integer" }, + "createdAt":{ "type": "string" }, + "updatedAt":{ "type": "string" }, + "label":{ "type": "string" }, + "type":{ "type": "string" }, + "streetAddress":{ "type": "string" }, + "streetNumber":{ "type": "string" }, + "streetName":{ "type": "string" }, + "postalCode":{ "type": "string" }, + "cityName":{ "type": "string" }, + "cityCode":{ "type": "string" }, + "departmentName":{ "type": "string" }, + "departmentCode":{ "type": "string" }, + "regionName":{ "type": "string" }, + "regionCode":{ "type": "string" } + }, + "status": { "type": ["string", "null"] }, + "persons": { "type": "array" } + } +} diff --git a/app/services/rnf_service.rb b/app/services/rnf_service.rb new file mode 100644 index 00000000000..11410918de8 --- /dev/null +++ b/app/services/rnf_service.rb @@ -0,0 +1,25 @@ +class RNFService + include Dry::Monads[:result] + + def call(rnf_id:) + result = API::Client.new.(url: "#{url}/#{rnf_id}", schema:) + case result + in Success(body:) + Success(body) + in Failure(code:, reason:) if code.in?(401..403) + Failure(API::Client::Error[:unauthorized, code, false, reason]) + else + result + end + end + + private + + def schema + JSONSchemer.schema(Rails.root.join('app/schemas/rnf.json')) + end + + def url + "#{API_RNF_URL}/api/foundations" + end +end diff --git a/config/initializers/02_urls.rb b/config/initializers/02_urls.rb index f10a271e183..dc8c105b791 100644 --- a/config/initializers/02_urls.rb +++ b/config/initializers/02_urls.rb @@ -7,6 +7,7 @@ API_PARTICULIER_URL = ENV.fetch("API_PARTICULIER_URL", "https://particulier.api.gouv.fr/api") API_TCHAP_URL = ENV.fetch("API_TCHAP_URL", "https://matrix.agent.tchap.gouv.fr/_matrix/identity/api/v1") API_COJO_URL = ENV.fetch("API_COJO_URL", nil) +API_RNF_URL = ENV.fetch("API_RNF_URL", "https://rnf.dso.numerique-interieur.com") HELPSCOUT_API_URL = ENV.fetch("HELPSCOUT_API_URL", "https://api.helpscout.net/v2") SENDINBLUE_API_URL = ENV.fetch("SENDINBLUE_API_URL", "https://in-automate.sendinblue.com/api/v2") SENDINBLUE_API_V3_URL = ENV.fetch("SENDINBLUE_API_V3_URL", "https://api.sendinblue.com/v3") diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 850d1a9d547..6b101ffa203 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -14,6 +14,7 @@ inflect.acronym 'IP' inflect.acronym 'JSON' inflect.acronym 'RNA' + inflect.acronym 'RNF' inflect.acronym 'URL' inflect.acronym 'SVA' inflect.acronym 'SVR' diff --git a/config/locales/models/type_de_champ/en.yml b/config/locales/models/type_de_champ/en.yml index 18e9e3f3fad..91899e67c80 100644 --- a/config/locales/models/type_de_champ/en.yml +++ b/config/locales/models/type_de_champ/en.yml @@ -48,6 +48,7 @@ en: yes_no_false: 'no' annuaire_education: 'Schooling directory' rna: 'RNA' + rnf: 'RNF' carte: 'Card' cnaf: 'Data from Caisse nationale des allocations familiales' dgfip: 'Data from Direction générale des Finances publiques' diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml index 9e33a3eb373..0dbd10730f9 100644 --- a/config/locales/models/type_de_champ/fr.yml +++ b/config/locales/models/type_de_champ/fr.yml @@ -48,6 +48,7 @@ fr: yes_no_false: 'non' annuaire_education: 'Annuaire de l’éducation' rna: 'RNA' + rnf: 'RNF' carte: 'Carte' cnaf: 'Données de la Caisse nationale des allocations familiales' dgfip: 'Données de la Direction générale des Finances publiques' diff --git a/config/locales/shared.en.yml b/config/locales/shared.en.yml index cd59bdb0ef8..0d4d670c162 100644 --- a/config/locales/shared.en.yml +++ b/config/locales/shared.en.yml @@ -30,6 +30,9 @@ en: data_fetched: "This RNA number is linked to %{title}" not_found: "No association found" network_error: "A network error has prevented the association associated with this RNA to be fetched" + rnf: + show: + not_found: "RNF %{rnf} (no fondation found)" dgfip: show: not_filled: not filled diff --git a/config/locales/shared.fr.yml b/config/locales/shared.fr.yml index 2cd62ffd71b..4feb8936bf1 100644 --- a/config/locales/shared.fr.yml +++ b/config/locales/shared.fr.yml @@ -32,6 +32,9 @@ fr: data_fetched: "Ce RNA correspond à %{title}" not_found: "Aucun établissement trouvé" network_error: "Une erreur réseau a empêché l’association liée à ce RNA d’être trouvée" + rnf: + show: + not_found: "RNF %{rnf} (aucune fondation trouvée)" dgfip: show: not_filled: non renseigné diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index 304e51c89f6..889fd0a3b7b 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -243,6 +243,10 @@ type_de_champ { association :type_de_champ_cojo, procedure: dossier.procedure } end + factory :champ_rnf, class: 'Champs::RNFChamp' do + type_de_champ { association :type_de_champ_rnf, procedure: dossier.procedure } + end + factory :champ_expression_reguliere, class: 'Champs::ExpressionReguliereChamp' do type_de_champ { association :type_de_champ_expression_reguliere, procedure: dossier.procedure } end diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb index 17cee985d4e..4d49abd7c93 100644 --- a/spec/factories/type_de_champ.rb +++ b/spec/factories/type_de_champ.rb @@ -187,6 +187,9 @@ factory :type_de_champ_cojo do type_champ { TypeDeChamp.type_champs.fetch(:cojo) } end + factory :type_de_champ_rnf do + type_champ { TypeDeChamp.type_champs.fetch(:rnf) } + end factory :type_de_champ_repetition do type_champ { TypeDeChamp.type_champs.fetch(:repetition) } diff --git a/spec/fixtures/files/api_rnf/invalid.json b/spec/fixtures/files/api_rnf/invalid.json new file mode 100644 index 00000000000..9d956df9e93 --- /dev/null +++ b/spec/fixtures/files/api_rnf/invalid.json @@ -0,0 +1,6 @@ +{ + "id":3, + "createdAt":"2023-09-07T13:26:10.358Z", + "updatedAt":"2023-09-07T13:26:10.358Z", + "rnfId": 750000301 +} diff --git a/spec/fixtures/files/api_rnf/valid.json b/spec/fixtures/files/api_rnf/valid.json new file mode 100644 index 00000000000..c8436b21444 --- /dev/null +++ b/spec/fixtures/files/api_rnf/valid.json @@ -0,0 +1,32 @@ +{ + "id":3, + "createdAt":"2023-09-07T13:26:10.358Z", + "updatedAt":"2023-09-07T13:26:10.358Z", + "rnfId":"075-FDD-00003-01", + "type":"FDD", + "department":"75", + "title":"Fondation SFR", + "dissolvedAt":null, + "phone":"+33185060000", + "email":"fondation@sfr.fr", + "addressId":3, + "address": { + "id":3, + "createdAt":"2023-09-07T13:26:10.358Z", + "updatedAt":"2023-09-07T13:26:10.358Z", + "label":"16 Rue du Général de Boissieu 75015 Paris", + "type":"housenumber", + "streetAddress":"16 Rue du Général de Boissieu", + "streetNumber":"16", + "streetName":"Rue du Général de Boissieu", + "postalCode":"75015", + "cityName":"Paris", + "cityCode":"75115", + "departmentName":"Paris", + "departmentCode":"75", + "regionName":"Île-de-France", + "regionCode":"11" + }, + "status":null, + "persons":[] +} diff --git a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb index a00707ba971..bdf1b78485a 100644 --- a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb +++ b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb @@ -14,9 +14,9 @@ describe 'remove_unused_champs' do it "with bad champs" do - expect(Champ.where(dossier: dossier).count).to eq(42) + expect(Champ.where(dossier: dossier).count).to eq(43) run_task - expect(Champ.where(dossier: dossier).count).to eq(41) + expect(Champ.where(dossier: dossier).count).to eq(42) end end end diff --git a/spec/models/champs/rnf_champ_spec.rb b/spec/models/champs/rnf_champ_spec.rb new file mode 100644 index 00000000000..22c414f93be --- /dev/null +++ b/spec/models/champs/rnf_champ_spec.rb @@ -0,0 +1,76 @@ +describe Champs::RNFChamp, type: :model do + let(:champ) { build(:champ_rnf, external_id:) } + let(:stub) { stub_request(:get, "#{url}/#{external_id}").to_return(body:, status:) } + let(:url) { RNFService.new.send(:url) } + let(:body) { Rails.root.join('spec', 'fixtures', 'files', 'api_rnf', "#{response_type}.json").read } + let(:external_id) { '075-FDD-00003-01' } + let(:status) { 200 } + let(:response_type) { 'valid' } + + describe 'fetch_external_data' do + subject { stub; champ.fetch_external_data } + + context 'success' do + it do + expect(subject.value!).to eq({ + id: 3, + rnfId: '075-FDD-00003-01', + type: 'FDD', + department: '75', + title: 'Fondation SFR', + dissolvedAt: nil, + phone: '+33185060000', + email: 'fondation@sfr.fr', + addressId: 3, + createdAt: "2023-09-07T13:26:10.358Z", + updatedAt: "2023-09-07T13:26:10.358Z", + address: { + id: 3, + createdAt: "2023-09-07T13:26:10.358Z", + updatedAt: "2023-09-07T13:26:10.358Z", + label: "16 Rue du Général de Boissieu 75015 Paris", + type: "housenumber", + streetAddress: "16 Rue du Général de Boissieu", + streetNumber: "16", + streetName: "Rue du Général de Boissieu", + postalCode: "75015", + cityName: "Paris", + cityCode: "75115", + departmentName: "Paris", + departmentCode: "75", + regionName: "Île-de-France", + regionCode: "11" + }, + status: nil, + persons: [] + }) + end + end + + context 'failure (schema)' do + let(:response_type) { 'invalid' } + it { + expect(subject.failure.retryable).to be_falsey + expect(subject.failure.reason).to be_a(API::Client::SchemaError) + } + end + + context 'failure (http 500)' do + let(:status) { 500 } + let(:response_type) { 'invalid' } + it { + expect(subject.failure.retryable).to be_truthy + expect(subject.failure.reason).to be_a(API::Client::HTTPError) + } + end + + context 'failure (http 401)' do + let(:status) { 401 } + let(:response_type) { 'invalid' } + it { + expect(subject.failure.retryable).to be_falsey + expect(subject.failure.reason).to be_a(API::Client::HTTPError) + } + end + end +end diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb index ca2f0918463..2b758b437c6 100644 --- a/spec/services/procedure_export_service_spec.rb +++ b/spec/services/procedure_export_service_spec.rb @@ -90,7 +90,8 @@ "epci (Code)", "epci (Département)", "cojo", - "expression_reguliere" + "expression_reguliere", + "rnf" ] end @@ -202,7 +203,8 @@ "epci (Code)", "epci (Département)", "cojo", - "expression_reguliere" + "expression_reguliere", + "rnf" ] end @@ -297,7 +299,8 @@ "epci (Code)", "epci (Département)", "cojo", - "expression_reguliere" + "expression_reguliere", + "rnf" ] end