Merge pull request #8783 from tchak/improuve-champ-commune

refactor(commune): choisir la commune par son code postal
This commit is contained in:
Paul Chavard 2023-03-29 13:51:06 +00:00 committed by GitHub
commit acc8584cdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 735 additions and 365 deletions

View file

@ -1,2 +1,17 @@
class EditableChamp::CommunesComponent < EditableChamp::ComboSearchComponent
class EditableChamp::CommunesComponent < EditableChamp::EditableChampBaseComponent
include ApplicationHelper
private
def commune_options
@champ.communes.map { ["#{_1[:name]} (#{_1[:postal_code]})", _1[:code]] }
end
def code_postal_input_id
"#{@champ.input_id}-code_postal"
end
def commune_select_options
{ selected: @champ.selected }.merge(@champ.mandatory? ? { prompt: '' } : { include_blank: '' })
end
end

View file

@ -0,0 +1,4 @@
---
en:
postal_code: Postal code of the municipality
not_found: No municipality found for postal code %{postal_code}

View file

@ -0,0 +1,4 @@
---
fr:
postal_code: Code postal de la commune
not_found: Aucune commune trouvée pour le code postal %{postal_code}

View file

@ -1,12 +1,13 @@
- render_parent
= @form.hidden_field :value
= @form.hidden_field :external_id
= @form.hidden_field :departement
= @form.hidden_field :code_departement
= react_component("ComboCommunesSearch",
required: @champ.required?,
id: @champ.input_id,
classNameDepartement: "width-33-desktop width-100-mobile",
className: "width-66-desktop width-100-mobile",
describedby: @champ.describedby_id,
**react_combo_props)
%label.notice{ for: code_postal_input_id }= t('.postal_code')
= @form.text_field :code_postal, required: @champ.required?, id: code_postal_input_id, class: "width-33-desktop width-100-mobile small-margin"
- if @champ.code_postal_with_fallback?
- if commune_options.empty?
.fr-error-text.mb-4= t('.not_found', postal_code: @champ.code_postal_with_fallback)
- elsif commune_options.size <= 3
%fieldset.radios
- commune_options.each.with_index do |(option, value), index|
%label
= @form.radio_button :value, value, checked: @champ.selected == value, id: index == 0 ? @champ.input_id : nil
= option
- else
= @form.select :value, commune_options, commune_select_options, required: @champ.required?, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, class: "width-33-desktop width-100-mobile"

View file

@ -326,8 +326,8 @@ module Instructeurs
def champs_private_params
champs_params = params.require(:dossier).permit(champs_private_attributes: [
:id, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :departement, :code_departement, :value, value: [],
champs_attributes: [:id, :_destroy, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :departement, :code_departement, value: []]
:id, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, :value, 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_params[:champs_private_all_attributes] = champs_params.delete(:champs_private_attributes) || {}
champs_params

View file

@ -387,7 +387,7 @@ module Users
def champs_public_params
champs_params = params.require(:dossier).permit(champs_public_attributes: [
:id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :identifiant, :numero_fiscal, :reference_avis, :ine, :piece_justificative_file, :departement, :code_departement, value: [],
:id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :identifiant, :numero_fiscal, :reference_avis, :ine, :piece_justificative_file, :code_departement, value: [],
champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :identifiant, :numero_fiscal, :reference_avis, :ine, :piece_justificative_file, :departement, :code_departement, value: []]
])
champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {}

View file

@ -404,7 +404,16 @@ type Commune {
Le code INSEE
"""
code: String!
"""
Le nom de la commune
"""
name: String!
"""
Le code postal
"""
postalCode: String
}
type CommuneChamp implements Champ {

View file

@ -3,29 +3,20 @@ module Types::Champs
implements Types::ChampType
class CommuneType < Types::BaseObject
field :name, String, null: false
field :name, String, "Le nom de la commune", null: false
field :code, String, "Le code INSEE", null: false
field :postal_code, String, "Le code postal", null: true, method: :code_postal
end
field :commune, CommuneType, null: true
field :departement, Types::Champs::DepartementChampType::DepartementType, null: true
def commune
if object.code?
{
name: object.to_s,
code: object.code
}
end
object if object.code?
end
def departement
if object.departement?
{
name: object.name_departement,
code: object.code_departement
}
end
object.departement if object.departement?
end
end
end

View file

@ -1,124 +0,0 @@
import React from 'react';
import { QueryClientProvider } from 'react-query';
import { matchSorter } from 'match-sorter';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
import { queryClient } from './shared/queryClient';
import { ComboDepartementsSearch } from './ComboDepartementsSearch';
import { useHiddenField, groupId } from './shared/hooks';
type CommuneResult = { code: string; nom: string; codesPostaux: string[] };
// Avoid hiding similar matches for precise queries (like "Sainte Marie")
function searchResultsLimit(term: string) {
return term.length > 5 ? 10 : 5;
}
function expandResultsWithMultiplePostalCodes(term: string, result: unknown) {
const results = result as CommuneResult[];
// A single result may have several associated postal codes.
// To make the search results more precise, we want to generate
// an actual result for each postal code.
const expandedResults = results.flatMap((result) =>
result.codesPostaux.map((codePostal) => ({
...result,
codesPostaux: [codePostal]
}))
);
// Some very large cities (like Paris) have A LOT of associated postal codes.
// As we generated one result per postal code, we now have a lot of results
// for the same city. If the number of results is above the threshold, we use
// local search to narrow the results.
const limit = searchResultsLimit(term);
if (expandedResults.length > limit) {
return matchSorter(expandedResults, term, {
keys: [(item) => `${item.nom} (${item.codesPostaux[0]})`, 'code'],
sorter: (rankedItems) => rankedItems
}).slice(0, limit + 1);
}
return expandedResults;
}
const placeholderDepartements = [
['63 Puy-de-Dôme', 'Clermont-Ferrand'],
['77 Seine-et-Marne', 'Melun'],
['22 Côtes dArmor', 'Saint-Brieuc'],
['47 Lot-et-Garonne', 'Agen']
] as const;
const [placeholderDepartement, placeholderCommune] =
placeholderDepartements[
Math.floor(Math.random() * (placeholderDepartements.length - 1))
];
export default function ComboCommunesSearch({
id,
classNameDepartement,
...props
}: ComboSearchProps<CommuneResult> & {
id: string;
classNameDepartement?: string;
}) {
const group = groupId(id);
const [departementValue, setDepartementValue] = useHiddenField(
group,
'departement'
);
const [codeDepartement, setCodeDepartement] = useHiddenField(
group,
'code_departement'
);
const departementDescribedBy = `${id}_departement_notice`;
const communeDescribedBy = `${id}_commune_notice`;
return (
<QueryClientProvider client={queryClient}>
<div>
<div className="notice" id={departementDescribedBy}>
<p>
Choisissez le département dans lequel se situe la commune. Vous
pouvez entrer le nom ou le code.
</p>
</div>
<ComboDepartementsSearch
{...props}
id={!codeDepartement ? id : undefined}
describedby={departementDescribedBy}
placeholder={placeholderDepartement}
addForeignDepartement={false}
value={departementValue}
className={classNameDepartement}
onChange={(_, result) => {
setDepartementValue(result?.nom ?? '');
setCodeDepartement(result?.code ?? '');
}}
/>
</div>
{codeDepartement ? (
<div>
<div className="notice" id={communeDescribedBy}>
<p>
Choisissez la commune. Vous pouvez entrer le nom ou le code
postal.
</p>
</div>
<ComboSearch
{...props}
id={id}
describedby={communeDescribedBy}
placeholder={placeholderCommune}
scope="communes"
scopeExtra={codeDepartement}
minimumInputLength={2}
transformResult={({ code, nom, codesPostaux }) => [
code,
`${nom} (${codesPostaux[0]})`
]}
transformResults={expandResultsWithMultiplePostalCodes}
/>
</div>
) : null}
</QueryClientProvider>
);
}

View file

@ -1,42 +0,0 @@
import React from 'react';
import { matchSorter } from 'match-sorter';
import ComboSearch, { ComboSearchProps } from './ComboSearch';
type DepartementResult = { code: string; nom: string };
const extraTerms = [{ code: '99', nom: 'Etranger' }];
function expandResultsWithForeignDepartement(term: string, result: unknown) {
const results = result as DepartementResult[];
return [
...results,
...matchSorter(extraTerms, term, {
keys: ['nom', 'code']
})
];
}
type ComboDepartementsSearchProps = Omit<
ComboSearchProps<DepartementResult> & {
addForeignDepartement: boolean;
},
'transformResult' | 'transformResults'
>;
export function ComboDepartementsSearch({
addForeignDepartement = true,
...props
}: ComboDepartementsSearchProps) {
return (
<ComboSearch
{...props}
scope="departements"
minimumInputLength={1}
transformResult={({ code, nom }) => [code, `${code} - ${nom}`]}
transformResults={
addForeignDepartement ? expandResultsWithForeignDepartement : undefined
}
/>
);
}

View file

@ -1,22 +1,11 @@
import { QueryClient, QueryFunction } from 'react-query';
import { httpRequest, isNumeric, getConfig } from '@utils';
import { matchSorter } from 'match-sorter';
import { httpRequest, getConfig } from '@utils';
const API_EDUCATION_QUERY_LIMIT = 5;
const API_GEO_QUERY_LIMIT = 5;
const API_ADRESSE_QUERY_LIMIT = 5;
// When searching for short strings like "mer", le exact match shows up quite far in
// the ordering (~50).
//
// That's why we deliberately fetch a lot of results, and then let the local matching
// (match-sorter) do the work.
//
// NB: 60 is arbitrary, we may add more if needed.
const API_GEO_COMMUNES_QUERY_LIMIT = 60;
const {
autocomplete: { api_geo_url, api_adresse_url, api_education_url }
autocomplete: { api_adresse_url, api_education_url }
} = getConfig();
type QueryKey = readonly [
@ -25,10 +14,10 @@ type QueryKey = readonly [
extra: string | undefined
];
function buildURL(scope: string, term: string, extra?: string) {
function buildURL(scope: string, term: string) {
term = term.replace(/\(|\)/g, '');
const params = new URLSearchParams();
let path = `${api_geo_url}/${scope}`;
let path = '';
if (scope == 'adresse') {
path = `${api_adresse_url}/search`;
@ -39,40 +28,15 @@ function buildURL(scope: string, term: string, extra?: string) {
params.set('q', term);
params.set('rows', `${API_EDUCATION_QUERY_LIMIT}`);
params.set('dataset', 'fr-en-annuaire-education');
} else if (scope == 'communes') {
if (extra) {
params.set('codeDepartement', extra);
}
if (isNumeric(term)) {
params.set('codePostal', term);
} else {
params.set('nom', term);
params.set('boost', 'population');
}
params.set('limit', `${API_GEO_COMMUNES_QUERY_LIMIT}`);
} else {
if (isNumeric(term)) {
params.set('code', term.padStart(2, '0'));
} else {
params.set('nom', term);
}
if (scope == 'departements') {
params.set('zone', 'metro,drom,com');
}
params.set('limit', `${API_GEO_QUERY_LIMIT}`);
}
return `${path}?${params}`;
}
const defaultQueryFn: QueryFunction<unknown, QueryKey> = async ({
queryKey: [scope, term, extra],
queryKey: [scope, term],
signal
}) => {
if (scope == 'pays') {
return matchSorter(await getPays(signal), term, { keys: ['label'] });
}
// BAN will error with queries less then 3 chars long
if (scope == 'adresse' && term.length < 3) {
return {
@ -83,23 +47,10 @@ const defaultQueryFn: QueryFunction<unknown, QueryKey> = async ({
};
}
const url = buildURL(scope, term, extra);
const url = buildURL(scope, term);
return httpRequest(url, { csrf: false, signal }).json();
};
let paysCache: { label: string }[];
async function getPays(signal?: AbortSignal): Promise<{ label: string }[]> {
if (!paysCache) {
const data = await httpRequest('/api/pays', { signal }).json<
typeof paysCache
>();
if (data) {
paysCache = data;
}
}
return paysCache;
}
export const queryClient = new QueryClient({
defaultOptions: {
queries: {

View file

@ -21,30 +21,110 @@
# type_de_champ_id :integer
#
class Champs::CommuneChamp < Champs::TextChamp
store_accessor :value_json, :departement, :code_departement
store_accessor :value_json, :code_departement, :code_postal
before_validation :on_code_postal_change
def for_export
[value, external_id, departement? ? departement_code_and_name : '']
[name, code, departement? ? departement_code_and_name : '']
end
def name_departement
# FIXME we originaly saved already formatted departement with the code in the name
departement&.gsub(/^(.[0-9])\s-\s/, '')
def departement_name
APIGeoService.departement_name(code_departement)
end
def departement_code_and_name
"#{code_departement} - #{name_departement}"
"#{code_departement} #{departement_name}"
end
def departement
{ code: code_departement, name: departement_name }
end
def departement?
departement.present?
code_departement.present?
end
def code?
code.present?
end
def code_postal?
code_postal.present?
end
def name
if code?
"#{APIGeoService.commune_name(code_departement, code)} (#{code_postal_with_fallback})"
else
value
end
end
def to_s
name
end
def code
external_id
end
def selected
code
end
def communes
if code_postal_with_fallback?
APIGeoService.communes_by_postal_code(code_postal_with_fallback)
else
[]
end
end
def value=(code)
if code.blank? || !code_postal_with_fallback?
self.code_departement = nil
self.external_id = nil
super(nil)
else
commune = communes.find { _1[:code] == code }
if commune.present?
self.code_departement = commune[:departement_code]
self.external_id = commune[:code]
super(commune[:name])
else
self.code_departement = nil
self.external_id = nil
super(nil)
end
end
end
def code_postal_with_fallback?
code_postal_with_fallback.present?
end
# We try to extract the postal code from the value, which is the name of the commune and the
# postal code in brackets. This is temporary until we do a full data migration.
def code_postal_with_fallback
if code_postal?
code_postal
elsif value.present?
match = value.match(/[^(]\(([^\)]*)\)$/)
match[1] if match.present?
else
nil
end
end
private
def on_code_postal_change
if code_postal_changed?
if communes.one?
self.value = communes.first[:code]
else
self.value = nil
end
end
end
end

View file

@ -514,7 +514,7 @@ class TypeDeChamp < ApplicationRecord
def self.refresh_after_update?(type_champ)
case type_champ
when type_champs.fetch(:epci)
when type_champs.fetch(:epci), type_champs.fetch(:communes)
true
else
false

View file

@ -1,47 +1,38 @@
class TypesDeChamp::PrefillCommuneTypeDeChamp < TypesDeChamp::PrefillTypeDeChamp
def all_possible_values
departements.map do |departement|
"#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/communes?codeDepartement=#{departement[:code]}"
end
[]
end
def example_value
departement_code = departements.pick(:code)
commune_code = APIGeoService.communes(departement_code).pick(:code)
[departement_code, commune_code]
APIGeoService.communes(departement_code).pick(:postal_code, :code)
end
def to_assignable_attributes(champ, value)
return if value.blank? || !value.is_a?(Array)
return if (departement_code = value.first).blank?
return if (departement_name = APIGeoService.departement_name(departement_code)).blank?
return if (postal_code = value.first).blank?
return if APIGeoService.communes_by_postal_code(postal_code).empty?
return if !value.one? && (commune_code = value.second).blank?
return if !value.one? && (commune_name = APIGeoService.commune_name(departement_code, commune_code)).blank?
return if !value.one? && !APIGeoService.communes_by_postal_code(postal_code).any? { _1[:code] == commune_code }
if value.one?
departement_attributes(champ, departement_code, departement_name)
code_postal_attributes(champ, postal_code)
else
departement_and_commune_attributes(champ, departement_code, departement_name, commune_code, commune_name)
code_postal_and_commune_attributes(champ, postal_code, commune_code)
end
end
private
def departement_attributes(champ, departement_code, departement_name)
def code_postal_attributes(champ, postal_code)
{
id: champ.id,
code_departement: departement_code,
departement: departement_name
code_postal: postal_code
}
end
def departement_and_commune_attributes(champ, departement_code, departement_name, commune_code, commune_name)
postal_code = APIGeoService.commune_postal_codes(departement_code, commune_code).first
departement_attributes(champ, departement_code, departement_name).merge(
external_id: commune_code,
value: "#{commune_name} (#{postal_code})"
)
def code_postal_and_commune_attributes(champ, postal_code, commune_code)
code_postal_attributes(champ, postal_code).merge(value: commune_code)
end
def departements

View file

@ -47,25 +47,6 @@ class APIGeoService
departements.find { _1[:name] == name }&.dig(:code)
end
def communes(departement_code)
get_from_api_geo(
"communes?codeDepartement=#{departement_code}",
additional_keys: { postal_codes: :codesPostaux }
).sort_by { I18n.transliterate(_1[:name]) }
end
def commune_name(departement_code, code)
communes(departement_code).find { _1[:code] == code }&.dig(:name)
end
def commune_code(departement_code, name)
communes(departement_code).find { _1[:name] == name }&.dig(:code)
end
def commune_postal_codes(departement_code, code)
communes(departement_code).find { _1[:code] == code }&.dig(:postal_codes)
end
def epcis(departement_code)
get_from_api_geo("epcis?codeDepartement=#{departement_code}").sort_by { I18n.transliterate(_1[:name]) }
end
@ -78,18 +59,55 @@ class APIGeoService
epcis(departement_code).find { _1[:name] == name }&.dig(:code)
end
def communes(departement_code)
get_from_api_geo("communes?codeDepartement=#{departement_code}&type=commune-actuelle,arrondissement-municipal").sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) }
end
def communes_by_postal_code(postal_code)
if postal_code.size > 3
metro_code = postal_code[0..1]
drom_com_code = postal_code[0..2]
if metro_code == '20'
communes('2A') + communes('2B')
elsif metro_code == '97' || metro_code == '98'
departement_name(drom_com_code) ? communes(drom_com_code) : []
else
departement_name(metro_code) ? communes(metro_code) : []
end
.filter { _1[:postal_code] == postal_code }
.sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) }
else
[]
end
end
def commune_name(departement_code, code)
communes(departement_code).find { _1[:code] == code }&.dig(:name)
end
def commune_code(departement_code, name)
communes(departement_code).find { _1[:name] == name }&.dig(:code)
end
private
def get_from_api_geo(scope, additional_keys: {})
def get_from_api_geo(scope)
Rails.cache.fetch("api_geo_#{scope}", expires_in: 1.year) do
response = Typhoeus.get("#{API_GEO_URL}/#{scope}")
JSON.parse(response.body)
.map(&:symbolize_keys)
.map do |result|
data = { name: result[:nom].tr("'", ''), code: result[:code] }
additional_keys.each { |key, value| data = data.merge(key => result[value]) }
data
JSON.parse(response.body).map(&:symbolize_keys).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
end
end
end

View file

@ -1,4 +1,4 @@
= format_text_value(champ.to_s)
= champ.name
- if champ.code?
%p.text-sm
Code INSEE :

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@ RSpec.describe Types::DossierType, type: :graphql do
end
end
describe 'dossier with champs' do
describe 'dossier with champs', vcr: { cassette_name: 'graphql_communes' } do
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :communes }, { type: :address }, { type: :siret }]) }
let(:dossier) { create(:dossier, :accepte, :with_populated_champs, procedure: procedure) }
let(:query) { DOSSIER_WITH_CHAMPS_QUERY }

View file

@ -1,19 +1,43 @@
describe Champs::CommuneChamp do
let(:type_de_champ) { create(:type_de_champ_communes, libelle: 'Ma commune') }
let(:champ) { Champs::CommuneChamp.new(value: value, external_id: code_insee, departement: departement, code_departement: code_departement, type_de_champ: type_de_champ) }
let(:value) { 'Châteldon (63290)' }
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
before do
allow(Rails).to receive(:cache).and_return(memory_store)
Rails.cache.clear
end
let(:code_insee) { '63102' }
let(:departement) { '' }
let(:code_departement) { '' }
let(:code_postal) { '63290' }
let(:code_departement) { '63' }
it { expect(champ.value).to eq('Châteldon (63290)') }
it { expect(champ.external_id).to eq('63102') }
it { expect(champ.for_export).to eq(['Châteldon (63290)', '63102', '']) }
describe 'value', vcr: { cassette_name: 'api_geo_communes' } do
let(:champ) { create(:champ_communes, code_postal: code_postal) }
context do
let(:departement) { 'Puy-de-Dôme' }
let(:code_departement) { '63' }
it 'with code_postal' do
champ.update(value: code_insee)
expect(champ.name).to eq('Châteldon (63290)')
expect(champ.external_id).to eq(code_insee)
expect(champ.code).to eq(code_insee)
expect(champ.code_departement).to eq(code_departement)
expect(champ.code_postal).to eq(code_postal)
expect(champ.for_export).to eq(['Châteldon (63290)', '63102', '63 Puy-de-Dôme'])
expect(champ.communes.size).to eq(8)
end
it { expect(champ.for_export).to eq(['Châteldon (63290)', '63102', '63 - Puy-de-Dôme']) }
context 'when code_postal is nil', vcr: { cassette_name: 'api_geo_communes' } do
let(:champ) { create(:champ_communes, external_id: code_insee, code_departement:) }
it 'with value' do
champ.update_column(:value, 'Châteldon (63290)')
expect(champ.name).to eq('Châteldon (63290)')
expect(champ.external_id).to eq(code_insee)
expect(champ.code).to eq(code_insee)
expect(champ.code_departement).to eq(code_departement)
expect(champ.code_postal).to be_nil
expect(champ.code_postal_with_fallback).to eq(code_postal)
expect(champ.for_export).to eq(['Châteldon (63290)', '63102', '63 Puy-de-Dôme'])
expect(champ.communes.size).to eq(8)
end
end
end
end

View file

@ -1573,7 +1573,7 @@ describe Dossier do
end
end
describe "champs_for_export" do
describe "champs_for_export", vcr: { cassette_name: 'api_geo_communes' } do
context 'with a unconditionnal procedure' do
let(:procedure) { create(:procedure, :with_type_de_champ, :with_datetime, :with_yes_no, :with_explication, :with_commune, :with_repetition, zones: [create(:zone)]) }
let(:text_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:text) } }

View file

@ -139,7 +139,7 @@ RSpec.describe PrefillParams do
it_behaves_like "a champ public value that is authorized", :checkbox, "false"
it_behaves_like "a champ public value that is authorized", :drop_down_list, "value"
it_behaves_like "a champ public value that is authorized", :departements, "03"
it_behaves_like "a champ public value that is authorized", :communes, ['01', '01457']
it_behaves_like "a champ public value that is authorized", :communes, ['01540', '01457']
it_behaves_like "a champ public value that is authorized", :address, "20 avenue de Ségur 75007 Paris"
it_behaves_like "a champ public value that is authorized", :annuaire_education, "0050009H"
it_behaves_like "a champ public value that is authorized", :multiple_drop_down_list, ["val1", "val2"]
@ -183,7 +183,7 @@ RSpec.describe PrefillParams do
it_behaves_like "a champ private value that is authorized", :rna, "value"
it_behaves_like "a champ private value that is authorized", :siret, "13002526500013"
it_behaves_like "a champ private value that is authorized", :departements, "03"
it_behaves_like "a champ private value that is authorized", :communes, ['01', '01457']
it_behaves_like "a champ private value that is authorized", :communes, ['01540', '01457']
it_behaves_like "a champ private value that is authorized", :address, "20 avenue de Ségur 75007 Paris"
it_behaves_like "a champ private value that is authorized", :annuaire_education, "0050009H"
it_behaves_like "a champ private value that is authorized", :multiple_drop_down_list, ["val1", "val2"]

View file

@ -26,21 +26,21 @@ RSpec.describe TypesDeChamp::PrefillCommuneTypeDeChamp do
it { is_expected.to be_kind_of(TypesDeChamp::PrefillTypeDeChamp) }
end
describe '#all_possible_values' do
let(:expected_values) do
departements.map { |departement| "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/communes?codeDepartement=#{departement[:code]}" }
end
subject(:all_possible_values) { described_class.new(type_de_champ, procedure.active_revision).all_possible_values }
# describe '#all_possible_values' do
# let(:expected_values) do
# departements.map { |departement| "#{departement[:code]} (#{departement[:name]}) : https://geo.api.gouv.fr/communes?codeDepartement=#{departement[:code]}" }
# end
# subject(:all_possible_values) { described_class.new(type_de_champ, procedure.active_revision).all_possible_values }
it { expect(all_possible_values).to match(expected_values) }
end
# it { expect(all_possible_values).to match(expected_values) }
# end
describe '#example_value' do
let(:departement_code) { departements.pick(:code) }
let(:commune_code) { APIGeoService.communes(departement_code).pick(:code) }
let(:value) { APIGeoService.communes(departement_code).pick(:postal_code, :code) }
subject(:example_value) { described_class.new(type_de_champ, procedure.active_revision).example_value }
it { is_expected.to eq([departement_code, commune_code]) }
it { is_expected.to eq(value) }
end
describe '#to_assignable_attributes' do
@ -65,22 +65,22 @@ RSpec.describe TypesDeChamp::PrefillCommuneTypeDeChamp do
end
context 'when the value is an array of one element' do
context 'when the first element is a valid departement code' do
let(:value) { ['01'] }
it { is_expected.to match({ id: champ.id, code_departement: '01', departement: 'Ain' }) }
context 'when the first element is a valid postal code' do
let(:value) { ['01540'] }
it { is_expected.to match({ id: champ.id, code_postal: '01540' }) }
end
context 'when the first element is not a valid departement code' do
context 'when the first element is not a valid postal code' do
let(:value) { ['totoro'] }
it { is_expected.to match(nil) }
end
end
context 'when the value is an array of two elements' do
context 'when the first element is a valid departement code' do
context 'when the first element is a valid postal code' do
context 'when the second element is a valid insee code' do
let(:value) { ['01', '01457'] }
it { is_expected.to match({ id: champ.id, code_departement: '01', departement: 'Ain', external_id: '01457', value: 'Vonnas (01540)' }) }
let(:value) { ['01540', '01457'] }
it { is_expected.to match({ id: champ.id, code_postal: '01540', value: '01457' }) }
end
context 'when the second element is not a valid insee code' do
@ -89,26 +89,26 @@ RSpec.describe TypesDeChamp::PrefillCommuneTypeDeChamp do
end
end
context 'when the first element is not a valid departement code' do
context 'when the first element is not a valid postal code' do
let(:value) { ['totoro', '01457'] }
it { is_expected.to match(nil) }
end
end
context 'when the value is an array of three or more elements' do
context 'when the first element is a valid departement code' do
context 'when the first element is a valid postal code' do
context 'when the second element is a valid insee code' do
let(:value) { ['01', '01457', 'hello'] }
it { is_expected.to match({ id: champ.id, code_departement: '01', departement: 'Ain', external_id: '01457', value: 'Vonnas (01540)' }) }
let(:value) { ['01540', '01457', 'hello'] }
it { is_expected.to match({ id: champ.id, code_postal: '01540', value: '01457' }) }
end
context 'when the second element is not a valid insee code' do
let(:value) { ['01', 'totoro', 'hello'] }
let(:value) { ['01540', 'totoro', 'hello'] }
it { is_expected.to match(nil) }
end
end
context 'when the first element is not a valid departement code' do
context 'when the first element is not a valid postal code' do
let(:value) { ['totoro', '01457', 'hello'] }
it { is_expected.to match(nil) }
end

View file

@ -45,9 +45,19 @@ describe APIGeoService do
describe 'communes', vcr: { cassette_name: 'api_geo_communes' } do
it 'return sorted results' do
expect(APIGeoService.communes('01').size).to eq(393)
expect(APIGeoService.communes('01').first).to eq(code: '01004', name: 'Ambérieu-en-Bugey', postal_codes: ['01500'])
expect(APIGeoService.communes('01').last).to eq(code: '01457', name: 'Vonnas', postal_codes: ['01540'])
expect(APIGeoService.communes('01').size).to eq(399)
expect(APIGeoService.communes('01').first).to eq(code: '01004', name: 'Ambérieu-en-Bugey', postal_code: '01500', departement_code: '01', epci_code: '240100883')
expect(APIGeoService.communes('01').last).to eq(code: '01457', name: 'Vonnas', postal_code: '01540', departement_code: '01', epci_code: '200070555')
end
end
describe 'communes_by_postal_code', vcr: { cassette_name: 'api_geo_communes' } do
it 'return results' do
expect(APIGeoService.communes_by_postal_code('75019').size).to eq(2)
expect(APIGeoService.communes_by_postal_code('69005').size).to eq(2)
expect(APIGeoService.communes_by_postal_code('13006').size).to eq(2)
expect(APIGeoService.communes_by_postal_code('73480').size).to eq(3)
expect(APIGeoService.communes_by_postal_code('20000').first[:code]).to eq('2A004')
end
end
@ -61,11 +71,6 @@ describe APIGeoService do
it { is_expected.to eq('01457') }
end
describe 'commune_postal_codes', vcr: { cassette_name: 'api_geo_communes' } do
subject { APIGeoService.commune_postal_codes('01', '01457') }
it { is_expected.to eq(['01540']) }
end
describe 'epcis', vcr: { cassette_name: 'api_geo_epcis' } do
it 'return sorted results' do
expect(APIGeoService.epcis('01').size).to eq(17)

View file

@ -25,7 +25,7 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do
expect(page).to have_content(multiple_drop_down_list_values.last)
expect(page).to have_field(type_de_champ_epci.libelle, with: epci_value.last)
expect(page).to have_field(type_de_champ_dossier_link.libelle, with: dossier_link_value)
expect(page).to have_selector("input[value='Vonnas (01540)']")
expect(page).to have_field(commune_libelle, with: '01457')
expect(page).to have_content(annuaire_education_value.last)
expect(page).to have_content(address_value.last)
end

View file

@ -44,8 +44,8 @@ describe 'The user' do
select('Australie', from: form_id_for('pays'))
select('Martinique', from: form_id_for('regions'))
select('02 Aisne', from: form_id_for('departements'))
select_combobox('communes', 'Ai', '02 - Aisne', check: false)
select_combobox('communes', 'Ambl', 'Ambléon (01300)')
fill_in('Code postal de la commune', with: '60400')
select('Brétigny (60400)', from: form_id_for('communes'))
fill_in('dossier_link', with: '123')
find('.editable-champ-piece_justificative input[type=file]').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
@ -74,7 +74,7 @@ describe 'The user' do
expect(champ_value_for('pays')).to eq('Australie')
expect(champ_value_for('regions')).to eq('Martinique')
expect(champ_value_for('departements')).to eq('Aisne')
expect(champ_value_for('communes')).to eq('Ambléon (01300)')
expect(champ_value_for('communes')).to eq('Brétigny')
expect(champ_value_for('dossier_link')).to eq('123')
expect(champ_value_for('piece_justificative')).to be_nil # antivirus hasn't approved the file yet
@ -98,7 +98,8 @@ describe 'The user' do
expect(page).to have_selected_value('regions', selected: 'Martinique')
expect(page).to have_selected_value('departements', selected: '02 Aisne')
check_selected_value('multiple_choice_drop_down_list_long', with: ['alpha', 'charly'])
check_selected_value('communes', with: 'Ambléon (01300)')
expect(page).to have_selected_value('communes', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_field('dossier_link', with: '123')
expect(page).to have_text('file.pdf')
expect(page).to have_text('Analyse antivirus en cours')

View file

@ -32,7 +32,8 @@ describe 'Prefilling a dossier (with a GET request):', js: true do
}
let(:epci_value) { ['01', '200029999'] }
let(:dossier_link_value) { '42' }
let(:commune_value) { ['01', '01457'] } # Vonnas (01540)
let(:commune_value) { ['01540', '01457'] } # Vonnas (01540)
let(:commune_libelle) { 'Vonnas (01540)' }
let(:address_value) { "20 Avenue de Ségur 75007 Paris" }
let(:sub_type_de_champs_repetition) { procedure.active_revision.children_of(type_de_champ_repetition) }
let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle }

View file

@ -31,7 +31,8 @@ describe 'Prefilling a dossier (with a POST request):', js: true do
]
}
let(:epci_value) { ['01', '200029999'] }
let(:commune_value) { ['01', '01457'] } # Vonnas (01540)
let(:commune_value) { ['01540', '01457'] } # Vonnas (01540)
let(:commune_libelle) { 'Vonnas (01540)' }
let(:address_value) { "20 Avenue de Ségur 75007 Paris" }
let(:sub_type_de_champs_repetition) { procedure.active_revision.children_of(type_de_champ_repetition) }
let(:text_repetition_libelle) { sub_type_de_champs_repetition.first.libelle }