From 20307f1d218cac2e6e7644f3b4ebde5889b9a038 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 13 Feb 2024 13:40:33 +0100 Subject: [PATCH] refactor(address): use directly BAN data --- .../address_component.html.haml | 1 + .../data_sources/adresse_controller.rb | 2 +- .../instructeurs/dossiers_controller.rb | 2 +- app/controllers/users/dossiers_controller.rb | 1 + app/lib/api_address/address_adapter.rb | 46 ---------------- app/models/champs/address_champ.rb | 39 +++++++++---- app/models/champs/departement_champ.rb | 2 +- app/models/champs/epci_champ.rb | 2 +- app/services/api_geo_service.rb | 48 +++++++++++++++- .../shared/champs/address/_show.html.haml | 11 ++-- spec/lib/api_address/address_adapter_spec.rb | 55 ------------------- 11 files changed, 85 insertions(+), 124 deletions(-) delete mode 100644 app/lib/api_address/address_adapter.rb delete mode 100644 spec/lib/api_address/address_adapter_spec.rb diff --git a/app/components/editable_champ/address_component/address_component.html.haml b/app/components/editable_champ/address_component/address_component.html.haml index e69322a16..e1029f05a 100644 --- a/app/components/editable_champ/address_component/address_component.html.haml +++ b/app/components/editable_champ/address_component/address_component.html.haml @@ -1,2 +1,3 @@ = render Dsfr::ComboboxComponent.new form: @form, url: data_sources_data_source_adresse_path, selected: @champ.value, allows_custom_value: true, input_html_options: { name: :value, id: @champ.input_id, class: 'fr-select', describedby: @champ.describedby_id } do = @form.hidden_field :external_id, data: { value_slot: 'value' } + = @form.hidden_field :feature, data: { value_slot: 'data' } diff --git a/app/controllers/data_sources/adresse_controller.rb b/app/controllers/data_sources/adresse_controller.rb index a68df5eac..eeeada45a 100644 --- a/app/controllers/data_sources/adresse_controller.rb +++ b/app/controllers/data_sources/adresse_controller.rb @@ -26,7 +26,7 @@ class DataSources::AdresseController < ApplicationController { label: _1[:properties][:label], value: _1[:properties][:label], - data: _1[:properties] + data: _1 } end end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 675aa39d3..44db9f2de 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -401,7 +401,7 @@ module Instructeurs def champs_private_params champs_params = params.require(:dossier).permit(champs_private_attributes: [ :id, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, value: [], - champs_attributes: [:id, :_destroy, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, value: []] + champs_attributes: [:id, :_destroy, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, :feature, value: []] ]) champs_params[:champs_private_all_attributes] = champs_params.delete(:champs_private_attributes) || {} champs_params diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 9094633ce..ea7bd5ad0 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -488,6 +488,7 @@ module Users :code_departement, :accreditation_number, :accreditation_birthdate, + :feature, value: [] ]) champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {} diff --git a/app/lib/api_address/address_adapter.rb b/app/lib/api_address/address_adapter.rb deleted file mode 100644 index 7f9d406ab..000000000 --- a/app/lib/api_address/address_adapter.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'json_schemer' - -class APIAddress::AddressAdapter - class InvalidSchemaError < ::StandardError - def initialize(errors) - super(errors.map(&:to_json).join("\n")) - end - end - - def initialize(search_term) - @search_term = search_term - end - - def to_params - result = Geocoder.search(@search_term, limit: 1).first - if result.present? && result.national_address == @search_term - feature = result.data['features'].first - if schemer.valid?(feature) - { - label: result.national_address, - type: result.result_type, - street_address: result.street_address, - street_number: result.street_number, - street_name: result.street_name, - postal_code: result.postal_code.presence || "", - city_name: result.city_name, - city_code: result.city_code, - department_name: result.department_name, - department_code: result.department_code, - region_name: result.region_name, - region_code: result.region_code, - geometry: result.geometry - } - else - errors = schemer.validate(feature).to_a - raise InvalidSchemaError.new(errors) - end - end - end - - private - - def schemer - @schemer ||= JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json')) - end -end diff --git a/app/models/champs/address_champ.rb b/app/models/champs/address_champ.rb index d929a73bb..b35489f58 100644 --- a/app/models/champs/address_champ.rb +++ b/app/models/champs/address_champ.rb @@ -3,6 +3,18 @@ class Champs::AddressChamp < Champs::TextChamp data.present? end + def feature + '' + end + + def feature=(value) + return if value.blank? + feature = JSON.parse(value) + self.data = APIGeoService.parse_ban_address(feature) + rescue JSON::ParserError + self.data = nil + end + def address full_address? ? data : nil end @@ -13,7 +25,7 @@ class Champs::AddressChamp < Champs::TextChamp def search_terms if full_address? - [data['label'], data['departement'], data['region'], data['city']] + [data['label'], data['department_name'], data['region_name'], data['city_name']] else [address_label] end @@ -35,45 +47,48 @@ class Champs::AddressChamp < Champs::TextChamp address_label end - def fetch_external_data? - true + def code_departement + if full_address? + address.fetch('department_code') + end end - def fetch_external_data - APIAddress::AddressAdapter.new(external_id).to_params + def code_region + if full_address? + address.fetch('region_code') + end end def departement_name - APIGeoService.departement_name(address.fetch('department_code')) + APIGeoService.departement_name(code_departement) end def departement_code_and_name if full_address? - "#{address.fetch('department_code')} – #{departement_name}" + "#{code_departement} – #{departement_name}" end end def departement if full_address? - { code: address.fetch('department_code'), name: departement_name } + { code: code_departement, name: departement_name } end end def commune_name if full_address? - "#{APIGeoService.commune_name(address.fetch('department_code'), address['city_code'])} (#{address['postal_code']})" + "#{APIGeoService.commune_name(code_departement, address['city_code'])} (#{address['postal_code']})" end end def commune if full_address? - department_code = address.fetch('department_code') city_code = address.fetch('city_code') city_name = address.fetch('city_name') postal_code = address.fetch('postal_code') - commune_name = APIGeoService.commune_name(department_code, city_code) - commune_code = APIGeoService.commune_code(department_code, city_name) + commune_name = APIGeoService.commune_name(code_departement, city_code) + commune_code = APIGeoService.commune_code(code_departement, city_name) if commune_name.present? { diff --git a/app/models/champs/departement_champ.rb b/app/models/champs/departement_champ.rb index 008006316..c6f9dfa01 100644 --- a/app/models/champs/departement_champ.rb +++ b/app/models/champs/departement_champ.rb @@ -42,7 +42,7 @@ class Champs::DepartementChamp < Champs::TextChamp end def code_region - APIGeoService.region_code_by_departement(name) + APIGeoService.region_code_by_departement(code) end def value=(code) diff --git a/app/models/champs/epci_champ.rb b/app/models/champs/epci_champ.rb index a388e6368..1d1d06f88 100644 --- a/app/models/champs/epci_champ.rb +++ b/app/models/champs/epci_champ.rb @@ -44,7 +44,7 @@ class Champs::EpciChamp < Champs::TextChamp end def code_region - APIGeoService.region_code_by_departement(departement_name) + APIGeoService.region_code_by_departement(code_departement) end def selected diff --git a/app/services/api_geo_service.rb b/app/services/api_geo_service.rb index d66a465d0..e5563f702 100644 --- a/app/services/api_geo_service.rb +++ b/app/services/api_geo_service.rb @@ -34,9 +34,9 @@ class APIGeoService regions.find { _1[:name] == name }&.dig(:code) end - def region_code_by_departement(name) - return if name.nil? - departements.find { _1[:name] == name }&.dig(:region_code) + def region_code_by_departement(code) + return if code.nil? + departements.find { _1[:code] == code }&.dig(:region_code) end def departements @@ -81,6 +81,42 @@ class APIGeoService communes(departement_code).find { _1[:name] == name }&.dig(:code) end + def parse_ban_address(feature) + return unless ban_address_schema.valid?(feature) + + properties = feature.fetch('properties') + city_code = properties.fetch('citycode') + + territory = if properties['context'].present? + department_code = properties.fetch('context').split(',').first + region_code = region_code_by_departement(department_code) + + { + department_name: departement_name(department_code), + department_code:, + region_name: region_name(region_code), + region_code:, + city_name: commune_name(department_code, city_code), + city_code: + } + else + { + city_name: properties['city'], + city_code: + } + end + + { + label: properties.fetch('label'), + type: properties.fetch('type'), + street_address: properties.fetch('name'), + postal_code: properties.fetch('postcode'), + street_number: properties['housenumber'], + street_name: properties['street'], + geometry: feature['geometry'] + }.merge(territory) + end + private def communes_by_postal_code_map @@ -114,5 +150,11 @@ class APIGeoService 'EN' => { 'XK' => 'Kosovo' } } end + + private + + def ban_address_schema + JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json')) + end end end diff --git a/app/views/shared/champs/address/_show.html.haml b/app/views/shared/champs/address/_show.html.haml index 6851e9d21..600957b73 100644 --- a/app/views/shared/champs/address/_show.html.haml +++ b/app/views/shared/champs/address/_show.html.haml @@ -1,5 +1,8 @@ -- if champ.to_s.present? - = format_text_value(champ.to_s) +%p= champ.to_s - if champ.full_address? - Code INSEE : - = champ.commune&.fetch(:code) + %p.fr-text--sm + Code INSEE : + = champ.commune&.fetch(:code) + %p.fr-text--sm + Departement : + = champ.departement_code_and_name diff --git a/spec/lib/api_address/address_adapter_spec.rb b/spec/lib/api_address/address_adapter_spec.rb deleted file mode 100644 index 55ff5b1ea..000000000 --- a/spec/lib/api_address/address_adapter_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -describe APIAddress::AddressAdapter do - let(:search_term) { 'Paris' } - let(:adapter) { described_class.new(search_term) } - let(:status) { 200 } - subject { adapter.to_params } - - before do - Geocoder.configure(lookup: :ban_data_gouv_fr, use_https: true) - stub_request(:get, /https:\/\/api-adresse.data.gouv.fr\/search/) - .to_return(body: body, status: status) - end - - after do - Geocoder.configure(lookup: :test) - end - - context "when responds with valid schema" do - let(:body) { File.read('spec/fixtures/files/api_address/address.json') } - - it '#to_params returns a valid' do - expect(subject).to be_an_instance_of(Hash) - expect(subject[:city_name]).to eq(search_term) - expect(subject[:city_code]).to eq('75056') - end - end - - context "when responds with an address which is not a direct match to search term" do - let(:body) { File.read('spec/fixtures/files/api_address/address.json') } - let(:search_term) { 'Lyon' } - - it '#to_params ignores the response' do - expect(subject).to be_nil - end - end - - context "when responds with invalid schema" do - let(:body) { File.read('spec/fixtures/files/api_address/address_invalid.json') } - - it '#to_params raise exception' do - expect { subject }.to raise_exception(APIAddress::AddressAdapter::InvalidSchemaError) - end - end - - context "when responds without postcode" do - let(:body) { - json = JSON.parse(File.read('spec/fixtures/files/api_address/address.json')) - json["features"][0]["properties"].delete("postcode") - json.to_json - } - - it "#to_params default to an empty postcode" do - expect(subject[:postal_code]).to eq("") - end - end -end