refactor(address): use directly BAN data

This commit is contained in:
Paul Chavard 2024-02-13 13:40:33 +01:00
parent 0b3a0d5840
commit 20307f1d21
11 changed files with 85 additions and 124 deletions

View file

@ -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 = 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 :external_id, data: { value_slot: 'value' }
= @form.hidden_field :feature, data: { value_slot: 'data' }

View file

@ -26,7 +26,7 @@ class DataSources::AdresseController < ApplicationController
{ {
label: _1[:properties][:label], label: _1[:properties][:label],
value: _1[:properties][:label], value: _1[:properties][:label],
data: _1[:properties] data: _1
} }
end end
end end

View file

@ -401,7 +401,7 @@ module Instructeurs
def champs_private_params def champs_private_params
champs_params = params.require(:dossier).permit(champs_private_attributes: [ 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: [], :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[:champs_private_all_attributes] = champs_params.delete(:champs_private_attributes) || {}
champs_params champs_params

View file

@ -488,6 +488,7 @@ module Users
:code_departement, :code_departement,
:accreditation_number, :accreditation_number,
:accreditation_birthdate, :accreditation_birthdate,
:feature,
value: [] value: []
]) ])
champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {} champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {}

View file

@ -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

View file

@ -3,6 +3,18 @@ class Champs::AddressChamp < Champs::TextChamp
data.present? data.present?
end 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 def address
full_address? ? data : nil full_address? ? data : nil
end end
@ -13,7 +25,7 @@ class Champs::AddressChamp < Champs::TextChamp
def search_terms def search_terms
if full_address? if full_address?
[data['label'], data['departement'], data['region'], data['city']] [data['label'], data['department_name'], data['region_name'], data['city_name']]
else else
[address_label] [address_label]
end end
@ -35,45 +47,48 @@ class Champs::AddressChamp < Champs::TextChamp
address_label address_label
end end
def fetch_external_data? def code_departement
true if full_address?
address.fetch('department_code')
end
end end
def fetch_external_data def code_region
APIAddress::AddressAdapter.new(external_id).to_params if full_address?
address.fetch('region_code')
end
end end
def departement_name def departement_name
APIGeoService.departement_name(address.fetch('department_code')) APIGeoService.departement_name(code_departement)
end end
def departement_code_and_name def departement_code_and_name
if full_address? if full_address?
"#{address.fetch('department_code')} #{departement_name}" "#{code_departement} #{departement_name}"
end end
end end
def departement def departement
if full_address? if full_address?
{ code: address.fetch('department_code'), name: departement_name } { code: code_departement, name: departement_name }
end end
end end
def commune_name def commune_name
if full_address? 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
end end
def commune def commune
if full_address? if full_address?
department_code = address.fetch('department_code')
city_code = address.fetch('city_code') city_code = address.fetch('city_code')
city_name = address.fetch('city_name') city_name = address.fetch('city_name')
postal_code = address.fetch('postal_code') postal_code = address.fetch('postal_code')
commune_name = APIGeoService.commune_name(department_code, city_code) commune_name = APIGeoService.commune_name(code_departement, city_code)
commune_code = APIGeoService.commune_code(department_code, city_name) commune_code = APIGeoService.commune_code(code_departement, city_name)
if commune_name.present? if commune_name.present?
{ {

View file

@ -42,7 +42,7 @@ class Champs::DepartementChamp < Champs::TextChamp
end end
def code_region def code_region
APIGeoService.region_code_by_departement(name) APIGeoService.region_code_by_departement(code)
end end
def value=(code) def value=(code)

View file

@ -44,7 +44,7 @@ class Champs::EpciChamp < Champs::TextChamp
end end
def code_region def code_region
APIGeoService.region_code_by_departement(departement_name) APIGeoService.region_code_by_departement(code_departement)
end end
def selected def selected

View file

@ -34,9 +34,9 @@ class APIGeoService
regions.find { _1[:name] == name }&.dig(:code) regions.find { _1[:name] == name }&.dig(:code)
end end
def region_code_by_departement(name) def region_code_by_departement(code)
return if name.nil? return if code.nil?
departements.find { _1[:name] == name }&.dig(:region_code) departements.find { _1[:code] == code }&.dig(:region_code)
end end
def departements def departements
@ -81,6 +81,42 @@ class APIGeoService
communes(departement_code).find { _1[:name] == name }&.dig(:code) communes(departement_code).find { _1[:name] == name }&.dig(:code)
end 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 private
def communes_by_postal_code_map def communes_by_postal_code_map
@ -114,5 +150,11 @@ class APIGeoService
'EN' => { 'XK' => 'Kosovo' } 'EN' => { 'XK' => 'Kosovo' }
} }
end end
private
def ban_address_schema
JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json'))
end
end end
end end

View file

@ -1,5 +1,8 @@
- if champ.to_s.present? %p= champ.to_s
= format_text_value(champ.to_s)
- if champ.full_address? - if champ.full_address?
%p.fr-text--sm
Code INSEE : Code INSEE :
= champ.commune&.fetch(:code) = champ.commune&.fetch(:code)
%p.fr-text--sm
Departement :
= champ.departement_code_and_name

View file

@ -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