Merge pull request #10749 from mfo/US/commune-without-insee

correctif: ETQ administrateur/instructeur, je souhaite que les champs de type commune aient tjr un code insee afin de router de maniere fiable
This commit is contained in:
mfo 2024-09-06 11:44:00 +00:00 committed by GitHub
commit 915779c9b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 305 additions and 68 deletions

View file

@ -3,12 +3,12 @@
class DataSources::CommuneController < ApplicationController
def search
if params[:q].present? && params[:q].length > 1
response = fetch_results
response = APIGeoService.commune_by_name_or_postal_code(params[:q])
if response.success?
results = JSON.parse(response.body, symbolize_names: true)
render json: format_results(results)
render json: APIGeoService.format_commune_response(results, params[:with_combined_code])
else
render json: []
end
@ -16,70 +16,4 @@ class DataSources::CommuneController < ApplicationController
render json: []
end
end
private
def fetch_results
if postal_code?(params[:q])
fetch_by_postal_code(params[:q])
else
fetch_by_name(params[:q])
end
end
def fetch_by_name(name)
Typhoeus.get("#{API_GEO_URL}/communes", params: {
type: 'commune-actuelle,arrondissement-municipal',
nom: name,
boost: 'population',
limit: 100
}, timeout: 3)
end
def fetch_by_postal_code(postal_code)
Typhoeus.get("#{API_GEO_URL}/communes", params: {
type: 'commune-actuelle,arrondissement-municipal',
codePostal: postal_code,
boost: 'population',
limit: 50
}, timeout: 3)
end
def postal_code?(string)
string.match?(/\A[-+]?\d+\z/) ? true : false
end
def format_results(results)
results.reject(&method(:code_metropole?)).flat_map do |result|
item = {
name: result[:nom].tr("'", ''),
code: result[:code],
epci_code: result[:codeEpci],
departement_code: result[:codeDepartement]
}.compact
if result[:codesPostaux].present?
result[:codesPostaux].map { item.merge(postal_code: _1) }
else
[item]
end.map do |item|
if params[:with_combined_code].present?
{
label: "#{item[:name]} (#{item[:postal_code]})",
value: "#{item[:code]}-#{item[:postal_code]}"
}
else
{
label: "#{item[:name]} (#{item[:postal_code]})",
value: item[:code],
data: item[:postal_code]
}
end
end
end
end
def code_metropole?(result)
result[:code].in?(['75056', '13055', '69123'])
end
end

View file

@ -4,6 +4,9 @@ class Champs::CommuneChamp < Champs::TextChamp
store_accessor :value_json, :code_departement, :code_postal, :code_region
before_save :on_codes_change, if: :should_refresh_after_code_change?
validates :external_id, presence: true, if: -> { validate_champ_value_or_prefill? && value.present? }
after_validation :instrument_external_id_error, if: -> { errors.include?(:external_id) }
def departement_name
APIGeoService.departement_name(code_departement)
end
@ -101,4 +104,11 @@ class Champs::CommuneChamp < Champs::TextChamp
def should_refresh_after_code_change?
!departement? || code_postal_changed? || external_id_changed?
end
def instrument_external_id_error
Sentry.capture_message(
"Commune with value and no external id Edge case reached",
extra: { request_id: Current.request_id }
)
end
end

View file

@ -84,6 +84,14 @@ class APIGeoService
communes(departement_code).find { _1[:code] == code }&.dig(:name)
end
def commune_by_name_or_postal_code(query)
if postal_code?(query)
fetch_by_postal_code(query)
else
fetch_by_name(query)
end
end
def commune_code(departement_code, name)
communes(departement_code).find { _1[:name] == name }&.dig(:code)
end
@ -215,8 +223,42 @@ class APIGeoService
commune_name(department_code, city_code) || fallback
end
def format_commune_response(results, with_combined_code)
results.reject(&method(:code_metropole?)).flat_map do |result|
item = {
name: result[:nom].tr("'", ''),
code: result[:code],
epci_code: result[:codeEpci],
departement_code: result[:codeDepartement]
}.compact
if result[:codesPostaux].present?
result[:codesPostaux].map { item.merge(postal_code: _1) }
else
[item]
end.map do |item|
if with_combined_code.present?
{
label: "#{item[:name]} (#{item[:postal_code]})",
value: "#{item[:code]}-#{item[:postal_code]}"
}
else
{
label: "#{item[:name]} (#{item[:postal_code]})",
value: item[:code],
data: item[:postal_code]
}
end
end
end
end
private
def code_metropole?(result)
result[:code].in?(['75056', '13055', '69123'])
end
def communes_by_postal_code_map
Rails.cache.fetch('api_geo_communes', expires_in: 1.day, version: 3) do
departements
@ -251,6 +293,28 @@ class APIGeoService
private
def fetch_by_name(name)
Typhoeus.get("#{API_GEO_URL}/communes", params: {
type: 'commune-actuelle,arrondissement-municipal',
nom: name,
boost: 'population',
limit: 100
}, timeout: 3)
end
def fetch_by_postal_code(postal_code)
Typhoeus.get("#{API_GEO_URL}/communes", params: {
type: 'commune-actuelle,arrondissement-municipal',
codePostal: postal_code,
boost: 'population',
limit: 50
}, timeout: 3)
end
def postal_code?(string)
string.match?(/\A[-+]?\d+\z/) ? true : false
end
def ban_address_schema
JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json'))
end

View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
module Maintenance
# some of our Champs::CommuneChamp had been corrupted, ie: missing external_id
# this tasks fix this issue
class FixChampsCommuneHavingValueButNotExternalIdTask < MaintenanceTasks::Task
DEFAULT_INSTRUCTEUR_EMAIL = ENV.fetch('DEFAULT_INSTRUCTEUR_EMAIL') { CONTACT_EMAIL }
def collection
Champs::CommuneChamp.where.not(value: nil)
end
def process(champ)
return if !fixable?(champ)
response = APIGeoService.commune_by_name_or_postal_code(champ.value)
if !response.success?
notify("Strange case of existing commune not requestable", champ)
else
results = JSON.parse(response.body, symbolize_names: true)
formated_results = APIGeoService.format_commune_response(results, true)
case formated_results.size
when 1
champ.code = formated_results.first[:value]
champ.save!
else # otherwise, we can't find the expected departement
champ.code_departement = nil
champ.code_postal = nil
champ.external_id = nil
champ.value = nil
champ.save(validate: false)
ask_user_correction(champ)
end
end
end
def count
# 2.4M champs, count is not an option
end
private
def ask_user_correction(champ)
dossier = champ.dossier
commentaire = CommentaireService.build(current_instructeur, dossier, { body: "Suite à un problème technique, Veuillez re-remplir le champs : #{champ.libelle}" })
dossier.flag_as_pending_correction!(commentaire, :incomplete)
end
def current_instructeur
user = User.find_by(email: DEFAULT_INSTRUCTEUR_EMAIL)
user ||= User.create(email: DEFAULT_INSTRUCTEUR_EMAIL,
password: Random.srand,
confirmed_at: Time.zone.now,
email_verified_at: Time.zone.now)
instructeur = user.instructeur
instructeur ||= user.create_instructeur!
instructeur
end
def fixable?(champ)
champ.value.present? && [champ.dossier.en_instruction? || champ.dossier.en_construction?]
end
def notify(message, champ) = Sentry.capture_message(message, extra: { champ: })
end
end