Merge pull request #10943 from colinux/feat-admin-prefill-service

ETQ admin mes informations de nouveau service sont pré-remplies à partir d'API publiques
This commit is contained in:
Colin Darie 2024-11-05 09:54:53 +00:00 committed by GitHub
commit 594fc517d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1272 additions and 33 deletions

View file

@ -6,6 +6,8 @@ class Dsfr::InputComponent < ApplicationComponent
delegate :object, to: :@form
delegate :errors, to: :object
attr_reader :attribute
# use it to indicate detailed about the inputs, ex: https://www.systeme-de-design.gouv.fr/elements-d-interface/modeles-et-blocs-fonctionnels/demande-de-mot-de-passe
# it uses aria-describedby on input and link it to yielded content
renders_one :describedby

View file

@ -23,7 +23,7 @@ module Dsfr
{
"#{dsfr_group_classname}--error" => errors_on_attribute?,
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute?
"#{dsfr_group_classname}--valid" => !errors_on_attribute? && errors_on_another_attribute? && object.public_send(attribute).present?
}
end
@ -51,9 +51,9 @@ module Dsfr
def attribute_or_rich_body
case @input_type
when :rich_text_area
@attribute.to_s.sub(/\Arich_/, '').to_sym
attribute.to_s.sub(/\Arich_/, '').to_sym
else
@attribute
attribute
end
end

View file

@ -3,6 +3,8 @@
class EditableChamp::ChampLabelComponent < ApplicationComponent
include Dsfr::InputErrorable
attr_reader :attribute
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value

View file

@ -4,6 +4,8 @@ class EditableChamp::ChampLabelContentComponent < ApplicationComponent
include ApplicationHelper
include Dsfr::InputErrorable
attr_reader :attribute
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value

View file

@ -3,6 +3,13 @@
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
include Dsfr::InputErrorable
attr_reader :attribute
def initialize(form:, champ:, seen_at: nil, opts: {})
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
@attribute = :value
end
def dsfr_champ_container
:div
end
@ -14,9 +21,4 @@ class EditableChamp::EditableChampBaseComponent < ApplicationComponent
def describedby_id
@champ.describedby_id
end
def initialize(form:, champ:, seen_at: nil, opts: {})
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
@attribute = :value
end
end

View file

@ -12,6 +12,12 @@ module Administrateurs
def new
@procedure = procedure
@service = Service.new
siret = current_administrateur.instructeur.last_agent_connect_information&.siret
if siret
@service.siret = siret
@prefilled = handle_siret_prefill
end
end
def create
@ -52,6 +58,19 @@ module Administrateurs
end
end
def prefill
@procedure = procedure
@service = Service.new(siret: params[:siret])
prefilled = handle_siret_prefill
render turbo_stream: turbo_stream.replace(
"service_form",
partial: "administrateurs/services/form",
locals: { service: @service, prefilled:, procedure: @procedure }
)
end
def add_to_procedure
procedure = current_administrateur.procedures.find(procedure_params[:id])
service = services.find(procedure_params[:service_id])
@ -108,5 +127,28 @@ module Administrateurs
def procedure
current_administrateur.procedures.find(params[:procedure_id])
end
def handle_siret_prefill
@service.validate
if !@service.errors.include?(:siret)
prefilled = case @service.prefill_from_siret
in [Dry::Monads::Result::Success, Dry::Monads::Result::Success]
:success
in [Dry::Monads::Result::Failure, Dry::Monads::Result::Success] | [Dry::Monads::Result::Success, Dry::Monads::Result::Failure]
:partial
else
:failure
end
end
# On prefill from SIRET, we only want to display errors for the SIRET input
# so we have to remove other errors (ie. required attributes not yet filled)
siret_errors = @service.errors.where(:siret)
@service.errors.clear
siret_errors.each { @service.errors.import(_1) }
prefilled
end
end
end

View file

@ -68,7 +68,7 @@ class RecoveriesController < ApplicationController
def structure_name
# we know that the structure exists because
# of the ensure_collectivite_territoriale guard
APIRechercheEntreprisesService.new.(siret:).value![:nom_complet]
APIRechercheEntreprisesService.new.call(siret:).value![:nom_complet]
end
def ensure_agent_connect_is_used

View file

@ -252,7 +252,10 @@ export class AutosaveController extends ApplicationController {
return httpRequest(form.action, {
method: 'post',
body: formData,
headers: { 'x-http-method-override': 'PATCH' },
headers: {
'x-http-method-override':
form.dataset.turboMethod?.toUpperCase() || 'PATCH'
},
signal: this.#abortController.signal,
timeout: AUTOSAVE_TIMEOUT_DELAY
}).turbo();

View file

@ -0,0 +1,91 @@
# frozen_string_literal: true
module PrefillableFromServicePublicConcern
extend ActiveSupport::Concern
included do
def prefill_from_siret
future_sp = Concurrent::Future.execute { AnnuaireServicePublicService.new.call(siret:) }
future_api_ent = Concurrent::Future.execute { APIRechercheEntreprisesService.new.call(siret:) }
result_sp = future_sp.value!
result_api_ent = future_api_ent.value!
prefill_from_service_public(result_sp)
prefill_from_api_entreprise(result_api_ent)
[result_sp, result_api_ent]
end
private
def prefill_from_service_public(result)
case result
in Dry::Monads::Success(data)
self.nom = data[:nom] if nom.blank?
self.email = data[:adresse_courriel] if email.blank?
self.telephone = data[:telephone]&.first&.dig("valeur") if telephone.blank?
self.horaires = denormalize_plage_ouverture(data[:plage_ouverture]) if horaires.blank?
self.adresse = APIGeoService.inline_service_public_address(data[:adresse]&.first) if adresse.blank?
else
# NOOP
end
end
def prefill_from_api_entreprise(result)
case result
in Dry::Monads::Success(data)
self.type_organisme = detect_type_organisme(data) if type_organisme.blank?
self.nom = data[:nom_complet] if nom.blank?
self.adresse = data.dig(:siege, :geo_adresse) if adresse.blank?
else
# NOOP
end
end
def denormalize_plage_ouverture(data)
return if data.blank?
data.map do |range|
day_range = range.values_at('nom_jour_debut', 'nom_jour_fin').uniq.join(' au ')
hours_range = (1..2).each_with_object([]) do |i, hours|
start_hour = range["valeur_heure_debut_#{i}"]
end_hour = range["valeur_heure_fin_#{i}"]
if start_hour.present? && end_hour.present?
hours << "de #{format_time(start_hour)} à #{format_time(end_hour)}"
end
end
result = day_range
result += " : #{hours_range.join(' et ')}" if hours_range.present?
result += " (#{range['commentaire']})" if range['commentaire'].present?
result
end.join("\n")
end
def detect_type_organisme(data)
# Cf https://recherche-entreprises.api.gouv.fr/docs/#tag/Recherche-textuelle/paths/~1search/get
type = if data.dig(:complements, :collectivite_territoriale).present?
:collectivite_territoriale
elsif data.dig(:complements, :est_association)
:association
elsif data[:section_activite_principale] == "P"
:etablissement_enseignement
elsif data[:nom_complet].match?(/MINISTERE|MINISTERIEL/)
:administration_centrale
else # we can't differentiate between operateur d'état, administration centrale and service déconcentré de l'état, set the most frequent
:service_deconcentre_de_l_etat
end
Service.type_organismes[type]
end
def format_time(str_time)
Time.zone
.parse(str_time)
.strftime("%-H:%M")
end
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Service < ApplicationRecord
include PrefillableFromServicePublicConcern
has_many :procedures
belongs_to :administrateur, optional: false

View file

@ -0,0 +1,76 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://demarches-simplifiees.fr/service-public.schema.json",
"title": "Service Public",
"type": "object",
"properties": {
"total_count": {
"type": "integer"
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"plage_ouverture": { "type": ["string", "null"] },
"site_internet": { "type": ["string", "null"] },
"copyright": { "type": ["string", "null"] },
"siren": { "type": ["string", "null"] },
"ancien_code_pivot": { "type": ["string", "null"] },
"reseau_social": { "type": ["string", "null"] },
"texte_reference": { "type": ["string", "null"] },
"partenaire": { "type": ["string", "null"] },
"telecopie": { "type": ["string", "null"] },
"nom": { "type": "string" },
"siret": { "type": ["string", "null"] },
"itm_identifiant": { "type": ["string", "null"] },
"sigle": { "type": ["string", "null"] },
"affectation_personne": { "type": ["string", "null"] },
"date_modification": { "type": "string" },
"adresse_courriel": { "type": ["string", "null"] },
"service_disponible": { "type": ["string", "null"] },
"organigramme": { "type": ["string", "null"] },
"pivot": { "type": ["string", "null"] },
"partenaire_identifiant": { "type": ["string", "null"] },
"ancien_identifiant": { "type": ["string", "null"] },
"id": { "type": "string" },
"ancien_nom": { "type": ["string", "null"] },
"commentaire_plage_ouverture": { "type": ["string", "null"] },
"annuaire": { "type": ["string", "null"] },
"tchat": { "type": ["string", "null"] },
"hierarchie": { "type": ["string", "null"] },
"categorie": { "type": "string" },
"sve": { "type": ["string", "null"] },
"telephone_accessible": { "type": ["string", "null"] },
"application_mobile": { "type": ["string", "null"] },
"version_type": { "type": "string" },
"type_repertoire": { "type": ["string", "null"] },
"telephone": { "type": ["string", "null"] },
"version_etat_modification": { "type": ["string", "null"] },
"date_creation": { "type": "string" },
"partenaire_date_modification": { "type": ["string", "null"] },
"mission": { "type": ["string", "null"] },
"formulaire_contact": { "type": ["string", "null"] },
"version_source": { "type": ["string", "null"] },
"type_organisme": { "type": ["string", "null"] },
"code_insee_commune": { "type": ["string", "null"] },
"statut_de_diffusion": { "type": ["string", "null"] },
"adresse": { "type": ["string", "null"] },
"url_service_public": { "type": ["string", "null"] },
"information_complementaire": { "type": ["string", "null"] },
"date_diffusion": { "type": ["string", "null"] }
},
"required": [
"id",
"nom",
"categorie",
"adresse",
"adresse_courriel",
"telephone",
"plage_ouverture"
]
}
}
},
"required": ["total_count", "results"]
}

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
class AnnuaireServicePublicService
include Dry::Monads[:result]
def call(siret:)
result = API::Client.new.call(url: url(siret), schema:, timeout: 1.second)
case result
in Success(body:)
result = body[:results].first
if result.present?
Success(
result.slice(:nom, :adresse, :adresse_courriel).merge(
telephone: maybe_json_parse(result[:telephone]),
plage_ouverture: maybe_json_parse(result[:plage_ouverture]),
adresse: maybe_json_parse(result[:adresse])
)
)
else
Failure(API::Client::Error[:not_found, 404, false, "No result found for this SIRET."])
end
in Failure(code:, reason:) if code.in?(401..403)
Sentry.capture_message("#{self.class.name}: #{reason} code: #{code}", extra: { siret: })
Failure(API::Client::Error[:unauthorized, code, false, reason])
in Failure(type: :schema, code:, reason:)
reason.errors[0].first
Sentry.capture_exception(reason, extra: { siret:, code: })
Failure(API::Client::Error[:schema, code, false, reason])
else
result
end
end
private
def schema
JSONSchemer.schema(Rails.root.join('app/schemas/service-public.json'))
end
def url(siret)
"https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:#{siret}"
end
def maybe_json_parse(value)
return nil if value.blank?
JSON.parse(value)
end
end

View file

@ -263,6 +263,21 @@ class APIGeoService
end
end
def inline_service_public_address(address_data)
return nil if address_data.blank?
components = [
address_data['numero_voie'],
address_data['complement1'],
address_data['complement2'],
address_data['service_distribution'],
address_data['code_postal'],
address_data['nom_commune']
].compact_blank
components.join(' ')
end
private
def code_metropole?(result)

View file

@ -1,4 +1,26 @@
= form_with model: [ :admin, service], local: true do |f|
= form_with model: [:admin, service], id: "service_form" do |f|
= render Dsfr::InputComponent.new(form: f, attribute: :siret, input_type: :text_field,
opts: { placeholder: "14 chiffres, sans espace",
onblur: token_list("Turbo.visit('#{prefill_admin_services_path(procedure_id: procedure.id)}?siret=' + this.value)" => service.new_record?) }) do |c|
- if service.etablissement_infos.blank? && local_assigns[:prefilled].nil?
- c.with_hint do
= "Indiquez le numéro de SIRET de lorganisme dont ce service dépend. Rechercher le SIRET sur "
= link_to("annuaire-entreprises.data.gouv.fr", annuaire_link, **external_link_attributes)
- if service.new_record?
%br
= "Nous préremplirons les informations de contact à partir de lAnnuaire Service Public correspondant."
.fr-mb-2w
- if local_assigns[:prefilled] == :success
%p.fr-info-text Génial ! La plupart des informations du service ont été préremplies ci-dessous. Vérifiez-les et complétez-les le cas échéant.
- elsif local_assigns[:prefilled] == :partial
%p.fr-info-text
Nous avons prérempli certaines informations correspondant à ce SIRET. Complétez les autres manuellement.
- elsif local_assigns[:prefilled] == :failure
%p.fr-error-text
Une erreur a empêché le préremplissage des informations.
Vérifiez que le numéro de SIRET est correct et complétez les informations manuellement le cas échéant.
= render Dsfr::InputComponent.new(form: f, attribute: :nom, input_type: :text_field)
@ -7,13 +29,9 @@
.fr-input-group
= f.label :type_organisme, class: "fr-label" do
Type dorganisme
= render EditableChamp::AsteriskMandatoryComponent.new
= f.select :type_organisme, Service.type_organismes.keys.map { |key| [ I18n.t("type_organisme.#{key}"), key] }, {}, class: 'fr-select'
= render Dsfr::InputComponent.new(form: f, attribute: :siret, input_type: :text_field, opts: { placeholder: "14 chiffres, sans espace" }) do |c|
- c.with_hint do
= "Indiquez le numéro de SIRET de lorganisme dont ce service dépend. Rechercher le SIRET sur "
= link_to("annuaire-entreprises.data.gouv.fr", annuaire_link, **external_link_attributes)
= f.select :type_organisme, Service.type_organismes.keys.map { |key| [ I18n.t("type_organisme.#{key}"), key] }, { include_blank: true }, { class: "fr-select" , required: true }
= render Dsfr::CalloutComponent.new(title: "Informations de contact") do |c|
- c.with_body do
@ -31,7 +49,6 @@
= render Dsfr::InputComponent.new(form: f, attribute: :horaires, input_type: :text_area)
= render Dsfr::InputComponent.new(form: f, attribute: :adresse, input_type: :text_area)
- if procedure_id.present?
= hidden_field_tag :procedure_id, procedure_id
= render Procedure::FixedFooterComponent.new(procedure: @procedure, form: f)
- if local_assigns[:procedure].present?
= hidden_field_tag :procedure_id, procedure.id
= render Procedure::FixedFooterComponent.new(procedure: procedure, form: f)

View file

@ -23,4 +23,4 @@
%p.mt-3 Si vous souhaitez modifier uniquement les informations pour ce service, créez un nouveau service puis associez-le à la démarche
= render partial: 'form',
locals: { service: @service, procedure_id: @procedure.id }
locals: { service: @service, procedure: @procedure }

View file

@ -8,4 +8,4 @@
%h1 Nouveau Service
= render partial: 'form',
locals: { service: @service, procedure_id: @procedure.id }
locals: { service: @service, procedure: @procedure, prefilled: @prefilled }

View file

@ -14,6 +14,7 @@ en:
service:
attributes:
siret:
format: "SIRET number %{message}"
length: "must contain exactly 14 digits"
checksum: "is invalid"
format: 'SIRET number %{message}'
length: 'must contain exactly 14 digits'
checksum: 'is invalid'
not_prefillable: 'Unable to pre-fill information for this SIRET, please fill it manually'

View file

@ -34,9 +34,10 @@ fr:
service:
attributes:
siret:
format: "Le numéro SIRET %{message}"
length: "doit comporter exactement 14 chiffres"
checksum: "est invalide"
format: 'Le numéro SIRET %{message}'
length: 'doit comporter exactement 14 chiffres'
checksum: 'est invalide'
not_prefillable: 'Impossible de préremplir les informations pour ce SIRET, veuillez les saisir manuellement'
type_organisme:
administration_centrale: 'Administration centrale'
association: 'Association'

View file

@ -737,6 +737,7 @@ Rails.application.routes.draw do
resources :services, except: [:show] do
collection do
patch 'add_to_procedure'
get ':procedure_id/prefill' => :prefill, as: :prefill
end
end

View file

@ -4,12 +4,112 @@ describe Administrateurs::ServicesController, type: :controller do
let(:admin) { administrateurs(:default_admin) }
let(:procedure) { create(:procedure, administrateur: admin) }
describe '#new' do
let(:admin) { administrateurs(:default_admin) }
let(:procedure) { create(:procedure, administrateur: admin) }
before do
sign_in(admin.user)
end
subject { get :new, params: { procedure_id: procedure.id } }
context 'when admin has a SIRET from AgentConnect' do
let(:siret) { "20004021000060" }
before do
agi = build(:agent_connect_information, siret:)
admin.instructeur.agent_connect_information << agi
end
it 'prefills the SIRET and fetches service information' do
VCR.use_cassette("annuaire_service_public_success_#{siret}") do
subject
expect(assigns[:service].siret).to eq(siret)
expect(assigns[:service].nom).to eq("Communauté de communes - Lacs et Gorges du Verdon")
expect(assigns[:service].adresse).to eq("242 avenue Albert-1er 83630 Aups")
expect(assigns[:prefilled]).to eq(:success)
end
end
end
context 'when admin has no SIRET from AgentConnect' do
it 'does not prefill the SIRET' do
subject
expect(assigns[:service].siret).to be_nil
expect(assigns[:prefilled]).to be_nil
end
end
end
describe '#prefill' do
before do
sign_in(admin.user)
end
subject { get :prefill, params:, xhr: true }
context 'when prefilling from a SIRET' do
let(:params) do
{
procedure_id: procedure.id,
siret: "20004021000060"
}
end
it "prefill from annuaire public" do
VCR.use_cassette('annuaire_service_public_success_20004021000060') do
subject
expect(response.body).to include('turbo-stream')
expect(assigns[:service].nom).to eq("Communauté de communes - Lacs et Gorges du Verdon")
expect(assigns[:service].adresse).to eq("242 avenue Albert-1er 83630 Aups")
end
end
end
context 'when attempting to prefilling from invalid SIRET' do
let(:params) do
{
procedure_id: procedure.id,
siret: "20004021000000"
}
end
it "render an error" do
subject
expect(response.body).to include('turbo-stream')
expect(assigns[:service].nom).to be_nil
expect(assigns[:service].errors.key?(:siret)).to be_present
end
end
context 'when attempting to prefilling from not service public SIRET' do
let(:params) do
{
procedure_id: procedure.id,
siret: "41816609600051"
}
end
it "render partial information" do
VCR.use_cassette('annuaire_service_public_success_41816609600051') do
subject
expect(response.body).to include('turbo-stream')
expect(assigns[:service].nom).to eq("OCTO-TECHNOLOGY")
expect(assigns[:service].horaires).to be_nil
expect(assigns[:service].errors.key?(:siret)).not_to be_present
end
end
end
end
describe '#create' do
before do
sign_in(admin.user)
post :create, params: params
end
subject { post :create, params: }
context 'when submitting a new service' do
let(:params) do
{
@ -28,6 +128,7 @@ describe Administrateurs::ServicesController, type: :controller do
end
it do
subject
expect(flash.alert).to be_nil
expect(flash.notice).to eq('super service créé')
expect(Service.last.nom).to eq('super service')
@ -47,9 +148,12 @@ describe Administrateurs::ServicesController, type: :controller do
context 'when submitting an invalid service' do
let(:params) { { service: { nom: 'super service' }, procedure_id: procedure.id } }
it { expect(flash.alert).not_to be_nil }
it { expect(response).to render_template(:new) }
it { expect(assigns(:service).nom).to eq('super service') }
it do
subject
expect(flash.alert).not_to be_nil
expect(response).to render_template(:new)
expect(assigns(:service).nom).to eq('super service')
end
end
end

View file

@ -0,0 +1,70 @@
---
http_interactions:
- request:
method: get
uri: https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:20004021000000
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches.gouv.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- openresty
Date:
- Mon, 07 Oct 2024 14:41:59 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '33'
X-Ratelimit-Remaining:
- '999978'
X-Ratelimit-Limit:
- '1000000'
X-Ratelimit-Reset:
- '2024-10-08 00:00:00+00:00'
Cache-Control:
- no-cache, no-store, max-age=0, must-revalidate
Vary:
- Accept-Language, Cookie, Host
Content-Language:
- fr-fr
Access-Control-Allow-Origin:
- "*"
Access-Control-Allow-Methods:
- POST, GET, OPTIONS
Access-Control-Max-Age:
- '1000'
Access-Control-Allow-Headers:
- Authorization, X-Requested-With, Origin, ODS-API-Analytics-App, ODS-API-Analytics-Embed-Type,
ODS-API-Analytics-Embed-Referrer, ODS-Widgets-Version, Accept
Access-Control-Expose-Headers:
- ODS-Explore-API-Deprecation, Link, X-RateLimit-Remaining, X-RateLimit-Limit,
X-RateLimit-Reset, X-RateLimit-dataset-Remaining, X-RateLimit-dataset-Limit,
X-RateLimit-dataset-Reset
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
Referrer-Policy:
- strict-origin-when-cross-origin
Permissions-Policy:
- midi=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Content-Security-Policy:
- upgrade-insecure-requests;
X-Ua-Compatible:
- IE=edge
body:
encoding: ASCII-8BIT
string: '{"total_count": 0, "results": []}'
recorded_at: Mon, 07 Oct 2024 14:41:59 GMT
recorded_with: VCR 6.2.0

View file

@ -0,0 +1,116 @@
---
http_interactions:
- request:
method: get
uri: https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:11004601800013
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- openresty
Date:
- Tue, 15 Oct 2024 16:24:17 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '33'
X-Ratelimit-Remaining:
- '999918'
X-Ratelimit-Limit:
- '1000000'
X-Ratelimit-Reset:
- '2024-10-16 00:00:00+00:00'
Cache-Control:
- no-cache, no-store, max-age=0, must-revalidate
Vary:
- Accept-Language, Cookie, Host
Content-Language:
- fr-fr
Access-Control-Allow-Origin:
- "*"
Access-Control-Allow-Methods:
- POST, GET, OPTIONS
Access-Control-Max-Age:
- '1000'
Access-Control-Allow-Headers:
- Authorization, X-Requested-With, Origin, ODS-API-Analytics-App, ODS-API-Analytics-Embed-Type,
ODS-API-Analytics-Embed-Referrer, ODS-Widgets-Version, Accept
Access-Control-Expose-Headers:
- ODS-Explore-API-Deprecation, Link, X-RateLimit-Remaining, X-RateLimit-Limit,
X-RateLimit-Reset, X-RateLimit-dataset-Remaining, X-RateLimit-dataset-Limit,
X-RateLimit-dataset-Reset
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
Referrer-Policy:
- strict-origin-when-cross-origin
Permissions-Policy:
- midi=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Content-Security-Policy:
- upgrade-insecure-requests;
X-Ua-Compatible:
- IE=edge
body:
encoding: ASCII-8BIT
string: '{"total_count": 0, "results": []}'
recorded_at: Tue, 15 Oct 2024 16:24:17 GMT
- request:
method: get
uri: https://recherche-entreprises.api.gouv.fr/search?q=11004601800013
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- nginx/1.27.1
Date:
- Tue, 15 Oct 2024 16:24:17 GMT
Content-Type:
- application/json
Content-Length:
- '3289'
Vary:
- Accept-Encoding
- Accept-Encoding
Annuaire-Entreprises-Instance-Number:
- '02'
X-Frame-Options:
- DENY
X-Content-Type-Options:
- nosniff
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
Access-Control-Allow-Headers:
- Content-Type
Access-Control-Allow-Origin:
- "*"
body:
encoding: ASCII-8BIT
string: !binary |-
eyJyZXN1bHRzIjpbeyJzaXJlbiI6IjExMDA0NjAxOCIsIm5vbV9jb21wbGV0IjoiTUlOSVNURVJFIERFIExBIENVTFRVUkUiLCJub21fcmFpc29uX3NvY2lhbGUiOiJNSU5JU1RFUkUgREUgTEEgQ1VMVFVSRSIsInNpZ2xlIjpudWxsLCJub21icmVfZXRhYmxpc3NlbWVudHMiOjMsIm5vbWJyZV9ldGFibGlzc2VtZW50c19vdXZlcnRzIjoxLCJzaWVnZSI6eyJhY3Rpdml0ZV9wcmluY2lwYWxlIjoiODQuMTFaIiwiYWN0aXZpdGVfcHJpbmNpcGFsZV9yZWdpc3RyZV9tZXRpZXIiOm51bGwsImFubmVlX3RyYW5jaGVfZWZmZWN0aWZfc2FsYXJpZSI6IjIwMjIiLCJhZHJlc3NlIjoiMTgyIFJVRSBTQUlOVC1IT05PUkUgNzUwMDEgUEFSSVMiLCJjYXJhY3RlcmVfZW1wbG95ZXVyIjoiTiIsImNlZGV4IjpudWxsLCJjb2RlX3BheXNfZXRyYW5nZXIiOm51bGwsImNvZGVfcG9zdGFsIjoiNzUwMDEiLCJjb21tdW5lIjoiNzUxMDEiLCJjb21wbGVtZW50X2FkcmVzc2UiOm51bGwsImNvb3Jkb25uZWVzIjoiNDguODYyMzk4LDIuMzM4OTAzIiwiZGF0ZV9jcmVhdGlvbiI6IjE5ODMtMDMtMDEiLCJkYXRlX2RlYnV0X2FjdGl2aXRlIjoiMjAwOC0wMS0wMSIsImRhdGVfZmVybWV0dXJlIjpudWxsLCJkYXRlX21pc2VfYV9qb3VyIjpudWxsLCJkYXRlX21pc2VfYV9qb3VyX2luc2VlIjoiMjAyNC0wMy0zMFQxMTozMDowNiIsImRlcGFydGVtZW50IjoiNzUiLCJkaXN0cmlidXRpb25fc3BlY2lhbGUiOm51bGwsImVwY2kiOiIyMDAwNTQ3ODEiLCJlc3Rfc2llZ2UiOnRydWUsImV0YXRfYWRtaW5pc3RyYXRpZiI6IkEiLCJnZW9fYWRyZXNzZSI6IjE4MiBSdWUgU2FpbnQtSG9ub3LDqSA3NTAwMSBQYXJpcyIsImdlb19pZCI6Ijc1MTAxXzg2MzVfMDAxODIiLCJpbmRpY2VfcmVwZXRpdGlvbiI6bnVsbCwibGF0aXR1ZGUiOiI0OC44NjIzOTgiLCJsaWJlbGxlX2NlZGV4IjpudWxsLCJsaWJlbGxlX2NvbW11bmUiOiJQQVJJUyIsImxpYmVsbGVfY29tbXVuZV9ldHJhbmdlciI6bnVsbCwibGliZWxsZV9wYXlzX2V0cmFuZ2VyIjpudWxsLCJsaWJlbGxlX3ZvaWUiOiJTQUlOVC1IT05PUkUiLCJsaXN0ZV9lbnNlaWduZXMiOm51bGwsImxpc3RlX2ZpbmVzcyI6bnVsbCwibGlzdGVfaWRfYmlvIjpudWxsLCJsaXN0ZV9pZGNjIjpudWxsLCJsaXN0ZV9pZF9vcmdhbmlzbWVfZm9ybWF0aW9uIjpudWxsLCJsaXN0ZV9yZ2UiOm51bGwsImxpc3RlX3VhaSI6bnVsbCwibG9uZ2l0dWRlIjoiMi4zMzg5MDMiLCJub21fY29tbWVyY2lhbCI6bnVsbCwibnVtZXJvX3ZvaWUiOiIxODIiLCJyZWdpb24iOiIxMSIsInNpcmV0IjoiMTEwMDQ2MDE4MDAwMTMiLCJzdGF0dXRfZGlmZnVzaW9uX2V0YWJsaXNzZW1lbnQiOiJPIiwidHJhbmNoZV9lZmZlY3RpZl9zYWxhcmllIjoiMjIiLCJ0eXBlX3ZvaWUiOiJSVUUifSwiYWN0aXZpdGVfcHJpbmNpcGFsZSI6Ijg0LjExWiIsImNhdGVnb3JpZV9lbnRyZXByaXNlIjoiUE1FIiwiY2FyYWN0ZXJlX2VtcGxveWV1ciI6bnVsbCwiYW5uZWVfY2F0ZWdvcmllX2VudHJlcHJpc2UiOiIyMDIyIiwiZGF0ZV9jcmVhdGlvbiI6IjE5ODEtMDYtMjMiLCJkYXRlX2Zlcm1ldHVyZSI6bnVsbCwiZGF0ZV9taXNlX2Ffam91ciI6IjIwMjQtMTAtMTRUMTQ6NTk6NDkiLCJkYXRlX21pc2VfYV9qb3VyX2luc2VlIjoiMjAyNC0wOS0yN1QxMToxMjoyOSIsImRhdGVfbWlzZV9hX2pvdXJfcm5lIjpudWxsLCJkaXJpZ2VhbnRzIjpbXSwiZXRhdF9hZG1pbmlzdHJhdGlmIjoiQSIsIm5hdHVyZV9qdXJpZGlxdWUiOiI3MTEzIiwic2VjdGlvbl9hY3Rpdml0ZV9wcmluY2lwYWxlIjoiTyIsInRyYW5jaGVfZWZmZWN0aWZfc2FsYXJpZSI6IjIyIiwiYW5uZWVfdHJhbmNoZV9lZmZlY3RpZl9zYWxhcmllIjoiMjAyMiIsInN0YXR1dF9kaWZmdXNpb24iOiJPIiwibWF0Y2hpbmdfZXRhYmxpc3NlbWVudHMiOlt7ImFjdGl2aXRlX3ByaW5jaXBhbGUiOiI4NC4xMVoiLCJhbmNpZW5fc2llZ2UiOmZhbHNlLCJhbm5lZV90cmFuY2hlX2VmZmVjdGlmX3NhbGFyaWUiOiIyMDIyIiwiYWRyZXNzZSI6IjE4MiBSVUUgU0FJTlQtSE9OT1JFIDc1MDAxIFBBUklTIiwiY2FyYWN0ZXJlX2VtcGxveWV1ciI6Ik4iLCJjb2RlX3Bvc3RhbCI6Ijc1MDAxIiwiY29tbXVuZSI6Ijc1MTAxIiwiZGF0ZV9jcmVhdGlvbiI6IjE5ODMtMDMtMDEiLCJkYXRlX2RlYnV0X2FjdGl2aXRlIjoiMjAwOC0wMS0wMSIsImRhdGVfZmVybWV0dXJlIjpudWxsLCJlcGNpIjoiMjAwMDU0NzgxIiwiZXN0X3NpZWdlIjp0cnVlLCJldGF0X2FkbWluaXN0cmF0aWYiOiJBIiwiZ2VvX2lkIjoiNzUxMDFfODYzNV8wMDE4MiIsImxhdGl0dWRlIjoiNDguODYyMzk4IiwibGliZWxsZV9jb21tdW5lIjoiUEFSSVMiLCJsaXN0ZV9lbnNlaWduZXMiOm51bGwsImxpc3RlX2ZpbmVzcyI6bnVsbCwibGlzdGVfaWRfYmlvIjpudWxsLCJsaXN0ZV9pZGNjIjpudWxsLCJsaXN0ZV9pZF9vcmdhbmlzbWVfZm9ybWF0aW9uIjpudWxsLCJsaXN0ZV9yZ2UiOm51bGwsImxpc3RlX3VhaSI6bnVsbCwibG9uZ2l0dWRlIjoiMi4zMzg5MDMiLCJub21fY29tbWVyY2lhbCI6bnVsbCwicmVnaW9uIjoiMTEiLCJzaXJldCI6IjExMDA0NjAxODAwMDEzIiwic3RhdHV0X2RpZmZ1c2lvbl9ldGFibGlzc2VtZW50IjoiTyIsInRyYW5jaGVfZWZmZWN0aWZfc2FsYXJpZSI6IjIyIn1dLCJmaW5hbmNlcyI6bnVsbCwiY29tcGxlbWVudHMiOnsiY29sbGVjdGl2aXRlX3RlcnJpdG9yaWFsZSI6bnVsbCwiY29udmVudGlvbl9jb2xsZWN0aXZlX3JlbnNlaWduZWUiOmZhbHNlLCJsaXN0ZV9pZGNjIjpudWxsLCJlZ2Fwcm9fcmVuc2VpZ25lZSI6ZmFsc2UsImVzdF9hc3NvY2lhdGlvbiI6ZmFsc2UsImVzdF9iaW8iOmZhbHNlLCJlc3RfZW50cmVwcmVuZXVyX2luZGl2aWR1ZWwiOmZhbHNlLCJlc3RfZW50cmVwcmVuZXVyX3NwZWN0YWNsZSI6ZmFsc2UsImVzdF9lc3MiOmZhbHNlLCJlc3RfZmluZXNzIjpmYWxzZSwiZXN0X29yZ2FuaXNtZV9mb3JtYXRpb24iOnRydWUsImVzdF9xdWFsaW9waSI6ZmFsc2UsImxpc3RlX2lkX29yZ2FuaXNtZV9mb3JtYXRpb24iOlsiMTE3NTU2MDg5NzUiXSwiZXN0X3JnZSI6ZmFsc2UsImVzdF9zZXJ2aWNlX3B1YmxpYyI6dHJ1ZSwiZXN0X3NpYWUiOmZhbHNlLCJlc3Rfc29jaWV0ZV9taXNzaW9uIjpmYWxzZSwiZXN0X3VhaSI6ZmFsc2UsImlkZW50aWZpYW50X2Fzc29jaWF0aW9uIjpudWxsLCJzdGF0dXRfZW50cmVwcmVuZXVyX3NwZWN0YWNsZSI6bnVsbCwidHlwZV9zaWFlIjpudWxsfX1dLCJ0b3RhbF9yZXN1bHRzIjoxLCJwYWdlIjoxLCJwZXJfcGFnZSI6MTAsInRvdGFsX3BhZ2VzIjoxfQ==
recorded_at: Tue, 15 Oct 2024 16:24:17 GMT
recorded_with: VCR 6.2.0

View file

@ -0,0 +1,120 @@
---
http_interactions:
- request:
method: get
uri: https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:19750664500013
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- openresty
Date:
- Tue, 15 Oct 2024 14:57:11 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '33'
X-Ratelimit-Remaining:
- '999997'
X-Ratelimit-Limit:
- '1000000'
X-Ratelimit-Reset:
- '2024-10-16 00:00:00+00:00'
Cache-Control:
- no-cache, no-store, max-age=0, must-revalidate
Vary:
- Accept-Language, Cookie, Host
Content-Language:
- fr-fr
Access-Control-Allow-Origin:
- "*"
Access-Control-Allow-Methods:
- POST, GET, OPTIONS
Access-Control-Max-Age:
- '1000'
Access-Control-Allow-Headers:
- Authorization, X-Requested-With, Origin, ODS-API-Analytics-App, ODS-API-Analytics-Embed-Type,
ODS-API-Analytics-Embed-Referrer, ODS-Widgets-Version, Accept
Access-Control-Expose-Headers:
- ODS-Explore-API-Deprecation, Link, X-RateLimit-Remaining, X-RateLimit-Limit,
X-RateLimit-Reset, X-RateLimit-dataset-Remaining, X-RateLimit-dataset-Limit,
X-RateLimit-dataset-Reset
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
Referrer-Policy:
- strict-origin-when-cross-origin
Permissions-Policy:
- midi=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Content-Security-Policy:
- upgrade-insecure-requests;
X-Ua-Compatible:
- IE=edge
body:
encoding: ASCII-8BIT
string: '{"total_count": 0, "results": []}'
recorded_at: Tue, 15 Oct 2024 14:57:11 GMT
- request:
method: get
uri: https://recherche-entreprises.api.gouv.fr/search?q=19750664500013
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- nginx/1.27.1
Date:
- Tue, 15 Oct 2024 14:57:12 GMT
Content-Type:
- application/json
Content-Length:
- '3310'
Vary:
- Accept-Encoding
- Accept-Encoding
Annuaire-Entreprises-Instance-Number:
- '02'
X-Frame-Options:
- DENY
X-Content-Type-Options:
- nosniff
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
Access-Control-Allow-Headers:
- Content-Type
Access-Control-Allow-Origin:
- "*"
body:
encoding: ASCII-8BIT
string: '{"results":[{"siren":"197506645","nom_complet":"LYCEE GENERAL ET TECHNOLOGIQUE
RACINE","nom_raison_sociale":"LYCEE GENERAL ET TECHNOLOGIQUE RACINE","sigle":null,"nombre_etablissements":1,"nombre_etablissements_ouverts":1,"siege":{"activite_principale":"85.31Z","activite_principale_registre_metier":null,"annee_tranche_effectif_salarie":"2022","adresse":"20
RUE DU ROCHER 75008 PARIS","caractere_employeur":"N","cedex":null,"code_pays_etranger":null,"code_postal":"75008","commune":"75108","complement_adresse":null,"coordonnees":"48.876451,2.3223","date_creation":"1983-03-01","date_debut_activite":"2008-01-01","date_fermeture":null,"date_mise_a_jour":null,"date_mise_a_jour_insee":"2024-03-30T03:25:45","departement":"75","distribution_speciale":null,"epci":"200054781","est_siege":true,"etat_administratif":"A","geo_adresse":"20
Rue du Rocher 75008 Paris","geo_id":"75108_8291_00020","indice_repetition":null,"latitude":"48.876451","libelle_cedex":null,"libelle_commune":"PARIS","libelle_commune_etranger":null,"libelle_pays_etranger":null,"libelle_voie":"DU
ROCHER","liste_enseignes":null,"liste_finess":null,"liste_id_bio":null,"liste_idcc":["9999"],"liste_id_organisme_formation":null,"liste_rge":null,"liste_uai":["0750664P"],"longitude":"2.3223","nom_commercial":null,"numero_voie":"20","region":"11","siret":"19750664500013","statut_diffusion_etablissement":"O","tranche_effectif_salarie":"22","type_voie":"RUE"},"activite_principale":"85.31Z","categorie_entreprise":"PME","caractere_employeur":null,"annee_categorie_entreprise":"2022","date_creation":"1965-05-01","date_fermeture":null,"date_mise_a_jour":"2024-10-14T15:00:03","date_mise_a_jour_insee":"2024-03-22T14:26:06","date_mise_a_jour_rne":null,"dirigeants":[],"etat_administratif":"A","nature_juridique":"7331","section_activite_principale":"P","tranche_effectif_salarie":"22","annee_tranche_effectif_salarie":"2022","statut_diffusion":"O","matching_etablissements":[{"activite_principale":"85.31Z","ancien_siege":false,"annee_tranche_effectif_salarie":"2022","adresse":"20
RUE DU ROCHER 75008 PARIS","caractere_employeur":"N","code_postal":"75008","commune":"75108","date_creation":"1983-03-01","date_debut_activite":"2008-01-01","date_fermeture":null,"epci":"200054781","est_siege":true,"etat_administratif":"A","geo_id":"75108_8291_00020","latitude":"48.876451","libelle_commune":"PARIS","liste_enseignes":null,"liste_finess":null,"liste_id_bio":null,"liste_idcc":["9999"],"liste_id_organisme_formation":null,"liste_rge":null,"liste_uai":["0750664P"],"longitude":"2.3223","nom_commercial":null,"region":"11","siret":"19750664500013","statut_diffusion_etablissement":"O","tranche_effectif_salarie":"22"}],"finances":null,"complements":{"collectivite_territoriale":null,"convention_collective_renseignee":true,"liste_idcc":["9999"],"egapro_renseignee":false,"est_association":false,"est_bio":false,"est_entrepreneur_individuel":false,"est_entrepreneur_spectacle":false,"est_ess":false,"est_finess":false,"est_organisme_formation":false,"est_qualiopi":false,"liste_id_organisme_formation":null,"est_rge":false,"est_service_public":true,"est_siae":false,"est_societe_mission":false,"est_uai":true,"identifiant_association":null,"statut_entrepreneur_spectacle":null,"type_siae":null}}],"total_results":1,"page":1,"per_page":10,"total_pages":1}'
recorded_at: Tue, 15 Oct 2024 14:57:11 GMT
recorded_with: VCR 6.2.0

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,122 @@
---
http_interactions:
- request:
method: get
uri: https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:35600082800018
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- openresty
Date:
- Tue, 15 Oct 2024 16:24:16 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '33'
X-Ratelimit-Remaining:
- '999919'
X-Ratelimit-Limit:
- '1000000'
X-Ratelimit-Reset:
- '2024-10-16 00:00:00+00:00'
Cache-Control:
- no-cache, no-store, max-age=0, must-revalidate
Vary:
- Accept-Language, Cookie, Host
Content-Language:
- fr-fr
Access-Control-Allow-Origin:
- "*"
Access-Control-Allow-Methods:
- POST, GET, OPTIONS
Access-Control-Max-Age:
- '1000'
Access-Control-Allow-Headers:
- Authorization, X-Requested-With, Origin, ODS-API-Analytics-App, ODS-API-Analytics-Embed-Type,
ODS-API-Analytics-Embed-Referrer, ODS-Widgets-Version, Accept
Access-Control-Expose-Headers:
- ODS-Explore-API-Deprecation, Link, X-RateLimit-Remaining, X-RateLimit-Limit,
X-RateLimit-Reset, X-RateLimit-dataset-Remaining, X-RateLimit-dataset-Limit,
X-RateLimit-dataset-Reset
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
Referrer-Policy:
- strict-origin-when-cross-origin
Permissions-Policy:
- midi=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Content-Security-Policy:
- upgrade-insecure-requests;
X-Ua-Compatible:
- IE=edge
body:
encoding: ASCII-8BIT
string: '{"total_count": 0, "results": []}'
recorded_at: Tue, 15 Oct 2024 16:24:16 GMT
- request:
method: get
uri: https://recherche-entreprises.api.gouv.fr/search?q=35600082800018
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- nginx/1.27.1
Date:
- Tue, 15 Oct 2024 16:24:16 GMT
Content-Type:
- application/json
Content-Length:
- '3374'
Vary:
- Accept-Encoding
- Accept-Encoding
- Accept-Encoding
Annuaire-Entreprises-Instance-Number:
- '01'
- '02'
X-Frame-Options:
- DENY
X-Content-Type-Options:
- nosniff
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
Access-Control-Allow-Headers:
- Content-Type
Access-Control-Allow-Origin:
- "*"
body:
encoding: ASCII-8BIT
string: '{"results":[{"siren":"356000828","nom_complet":"LA POSTE (REGION RHONE
ALPES)","nom_raison_sociale":"LA POSTE","sigle":null,"nombre_etablissements":2948,"nombre_etablissements_ouverts":0,"siege":{"activite_principale":"53.10Z","activite_principale_registre_metier":null,"annee_tranche_effectif_salarie":null,"adresse":"4
QUAI DU POINT DU JOUR 92100 BOULOGNE-BILLANCOURT","caractere_employeur":"N","cedex":null,"code_pays_etranger":null,"code_postal":"92100","commune":"92012","complement_adresse":null,"coordonnees":"48.832893,2.259972","date_creation":"1991-01-01","date_debut_activite":"2011-12-31","date_fermeture":"2011-12-31","date_mise_a_jour":null,"date_mise_a_jour_insee":"2024-03-22T15:40:57","departement":"92","distribution_speciale":null,"epci":"200054781","est_siege":true,"etat_administratif":"F","geo_adresse":"4
Quai du Point du Jour 92100 Boulogne-Billancourt","geo_id":"92012_7231_00004","indice_repetition":null,"latitude":"48.832893","libelle_cedex":null,"libelle_commune":"BOULOGNE-BILLANCOURT","libelle_commune_etranger":null,"libelle_pays_etranger":null,"libelle_voie":"DU
POINT DU JOUR","liste_enseignes":null,"liste_finess":null,"liste_id_bio":null,"liste_idcc":null,"liste_id_organisme_formation":null,"liste_rge":null,"liste_uai":null,"longitude":"2.259972","nom_commercial":null,"numero_voie":"4","region":"11","siret":"35600082800018","statut_diffusion_etablissement":"O","tranche_effectif_salarie":null,"type_voie":"QUAI"},"activite_principale":"53.10Z","categorie_entreprise":null,"caractere_employeur":null,"annee_categorie_entreprise":null,"date_creation":"1991-01-01","date_fermeture":"2011-12-31","date_mise_a_jour":"2024-10-14T15:38:40","date_mise_a_jour_insee":"2024-03-22T14:26:06","date_mise_a_jour_rne":null,"dirigeants":[],"etat_administratif":"C","nature_juridique":"4130","section_activite_principale":"H","tranche_effectif_salarie":null,"annee_tranche_effectif_salarie":null,"statut_diffusion":"O","matching_etablissements":[{"activite_principale":"53.10Z","ancien_siege":false,"annee_tranche_effectif_salarie":null,"adresse":"4
QUAI DU POINT DU JOUR 92100 BOULOGNE-BILLANCOURT","caractere_employeur":"N","code_postal":"92100","commune":"92012","date_creation":"1991-01-01","date_debut_activite":"2011-12-31","date_fermeture":"2011-12-31","epci":"200054781","est_siege":true,"etat_administratif":"F","geo_id":"92012_7231_00004","latitude":"48.832893","libelle_commune":"BOULOGNE-BILLANCOURT","liste_enseignes":null,"liste_finess":null,"liste_id_bio":null,"liste_idcc":null,"liste_id_organisme_formation":null,"liste_rge":null,"liste_uai":null,"longitude":"2.259972","nom_commercial":null,"region":"11","siret":"35600082800018","statut_diffusion_etablissement":"O","tranche_effectif_salarie":null}],"finances":null,"complements":{"collectivite_territoriale":null,"convention_collective_renseignee":false,"liste_idcc":null,"egapro_renseignee":false,"est_association":false,"est_bio":false,"est_entrepreneur_individuel":false,"est_entrepreneur_spectacle":false,"est_ess":false,"est_finess":false,"est_organisme_formation":false,"est_qualiopi":false,"liste_id_organisme_formation":null,"est_rge":false,"est_service_public":true,"est_siae":false,"est_societe_mission":false,"est_uai":false,"identifiant_association":null,"statut_entrepreneur_spectacle":null,"type_siae":null}}],"total_results":1,"page":1,"per_page":10,"total_pages":1}'
recorded_at: Tue, 15 Oct 2024 16:24:16 GMT
recorded_with: VCR 6.2.0

View file

@ -0,0 +1,119 @@
---
http_interactions:
- request:
method: get
uri: https://recherche-entreprises.api.gouv.fr/search?q=41816609600051
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- nginx/1.27.1
Date:
- Thu, 17 Oct 2024 07:51:06 GMT
Content-Type:
- application/json
Content-Length:
- '3471'
Vary:
- Accept-Encoding
- Accept-Encoding
- Accept-Encoding
Annuaire-Entreprises-Instance-Number:
- '02'
X-Frame-Options:
- DENY
X-Content-Type-Options:
- nosniff
Strict-Transport-Security:
- max-age=31536000
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
- 1; mode=block
Access-Control-Allow-Headers:
- Content-Type
Access-Control-Allow-Origin:
- "*"
body:
encoding: ASCII-8BIT
string: !binary |-
eyJyZXN1bHRzIjpbeyJzaXJlbiI6IjQxODE2NjA5NiIsIm5vbV9jb21wbGV0IjoiT0NUTy1URUNITk9MT0dZIiwibm9tX3JhaXNvbl9zb2NpYWxlIjoiT0NUTy1URUNITk9MT0dZIiwic2lnbGUiOm51bGwsIm5vbWJyZV9ldGFibGlzc2VtZW50cyI6Nywibm9tYnJlX2V0YWJsaXNzZW1lbnRzX291dmVydHMiOjEsInNpZWdlIjp7ImFjdGl2aXRlX3ByaW5jaXBhbGUiOiI2Mi4wMkEiLCJhY3Rpdml0ZV9wcmluY2lwYWxlX3JlZ2lzdHJlX21ldGllciI6bnVsbCwiYW5uZWVfdHJhbmNoZV9lZmZlY3RpZl9zYWxhcmllIjoiMjAyMiIsImFkcmVzc2UiOiIzNCBBVkVOVUUgREUgTCdPUEVSQSA3NTAwMiBQQVJJUyIsImNhcmFjdGVyZV9lbXBsb3lldXIiOiJPIiwiY2VkZXgiOm51bGwsImNvZGVfcGF5c19ldHJhbmdlciI6bnVsbCwiY29kZV9wb3N0YWwiOiI3NTAwMiIsImNvbW11bmUiOiI3NTEwMiIsImNvbXBsZW1lbnRfYWRyZXNzZSI6bnVsbCwiY29vcmRvbm5lZXMiOiI0OC44Njg2NjIsMi4zMzMzODIiLCJkYXRlX2NyZWF0aW9uIjoiMjAxNi0xMS0yOCIsImRhdGVfZGVidXRfYWN0aXZpdGUiOiIyMDE2LTExLTI4IiwiZGF0ZV9mZXJtZXR1cmUiOm51bGwsImRhdGVfbWlzZV9hX2pvdXIiOm51bGwsImRhdGVfbWlzZV9hX2pvdXJfaW5zZWUiOiIyMDI0LTA1LTMxVDA1OjI5OjU4IiwiZGVwYXJ0ZW1lbnQiOiI3NSIsImRpc3RyaWJ1dGlvbl9zcGVjaWFsZSI6bnVsbCwiZXBjaSI6IjIwMDA1NDc4MSIsImVzdF9zaWVnZSI6dHJ1ZSwiZXRhdF9hZG1pbmlzdHJhdGlmIjoiQSIsImdlb19hZHJlc3NlIjoiMzQgQXZlbnVlIGRlIGwnT3DDqXJhIDc1MDAyIFBhcmlzIiwiZ2VvX2lkIjoiNzUxMDJfNjkwNF8wMDAzNCIsImluZGljZV9yZXBldGl0aW9uIjpudWxsLCJsYXRpdHVkZSI6IjQ4Ljg2ODY2MiIsImxpYmVsbGVfY2VkZXgiOm51bGwsImxpYmVsbGVfY29tbXVuZSI6IlBBUklTIiwibGliZWxsZV9jb21tdW5lX2V0cmFuZ2VyIjpudWxsLCJsaWJlbGxlX3BheXNfZXRyYW5nZXIiOm51bGwsImxpYmVsbGVfdm9pZSI6IkRFIEwnT1BFUkEiLCJsaXN0ZV9lbnNlaWduZXMiOm51bGwsImxpc3RlX2ZpbmVzcyI6bnVsbCwibGlzdGVfaWRfYmlvIjpudWxsLCJsaXN0ZV9pZGNjIjpbIjE0ODYiXSwibGlzdGVfaWRfb3JnYW5pc21lX2Zvcm1hdGlvbiI6bnVsbCwibGlzdGVfcmdlIjpudWxsLCJsaXN0ZV91YWkiOm51bGwsImxvbmdpdHVkZSI6IjIuMzMzMzgyIiwibm9tX2NvbW1lcmNpYWwiOm51bGwsIm51bWVyb192b2llIjoiMzQiLCJyZWdpb24iOiIxMSIsInNpcmV0IjoiNDE4MTY2MDk2MDAwNjkiLCJzdGF0dXRfZGlmZnVzaW9uX2V0YWJsaXNzZW1lbnQiOiJPIiwidHJhbmNoZV9lZmZlY3RpZl9zYWxhcmllIjoiNDEiLCJ0eXBlX3ZvaWUiOiJBVkVOVUUifSwiYWN0aXZpdGVfcHJpbmNpcGFsZSI6IjYyLjAyQSIsImNhdGVnb3JpZV9lbnRyZXByaXNlIjoiR0UiLCJjYXJhY3RlcmVfZW1wbG95ZXVyIjpudWxsLCJhbm5lZV9jYXRlZ29yaWVfZW50cmVwcmlzZSI6IjIwMjIiLCJkYXRlX2NyZWF0aW9uIjoiMTk5OC0wNC0wMSIsImRhdGVfZmVybWV0dXJlIjpudWxsLCJkYXRlX21pc2VfYV9qb3VyIjoiMjAyNC0xMC0xNlQxMzowODowMyIsImRhdGVfbWlzZV9hX2pvdXJfaW5zZWUiOiIyMDI0LTA1LTMxVDA1OjI5OjU4IiwiZGF0ZV9taXNlX2Ffam91cl9ybmUiOiIyMDI0LTA1LTE5VDE2OjQ5OjMzIiwiZGlyaWdlYW50cyI6W3sic2lyZW4iOm51bGwsImRlbm9taW5hdGlvbiI6IktQTUciLCJxdWFsaXRlIjoiQ29tbWlzc2FpcmUgYXV4IGNvbXB0ZXMgdGl0dWxhaXJlIiwidHlwZV9kaXJpZ2VhbnQiOiJwZXJzb25uZSBtb3JhbGUifV0sImV0YXRfYWRtaW5pc3RyYXRpZiI6IkEiLCJuYXR1cmVfanVyaWRpcXVlIjoiNTcxMCIsInNlY3Rpb25fYWN0aXZpdGVfcHJpbmNpcGFsZSI6IkoiLCJ0cmFuY2hlX2VmZmVjdGlmX3NhbGFyaWUiOiI0MSIsImFubmVlX3RyYW5jaGVfZWZmZWN0aWZfc2FsYXJpZSI6IjIwMjIiLCJzdGF0dXRfZGlmZnVzaW9uIjoiTyIsIm1hdGNoaW5nX2V0YWJsaXNzZW1lbnRzIjpbeyJhY3Rpdml0ZV9wcmluY2lwYWxlIjoiNjIuMDJBIiwiYW5jaWVuX3NpZWdlIjp0cnVlLCJhbm5lZV90cmFuY2hlX2VmZmVjdGlmX3NhbGFyaWUiOm51bGwsImFkcmVzc2UiOiI1MCBBVkVOVUUgREVTIENIQU1QUyBFTFlTRUVTIDc1MDA4IFBBUklTIiwiY2FyYWN0ZXJlX2VtcGxveWV1ciI6Ik8iLCJjb2RlX3Bvc3RhbCI6Ijc1MDA4IiwiY29tbXVuZSI6Ijc1MTA4IiwiZGF0ZV9jcmVhdGlvbiI6IjIwMDUtMDItMTciLCJkYXRlX2RlYnV0X2FjdGl2aXRlIjoiMjAxNi0xMS0yOCIsImRhdGVfZmVybWV0dXJlIjoiMjAxNi0xMS0yOCIsImVwY2kiOiIyMDAwNTQ3ODEiLCJlc3Rfc2llZ2UiOmZhbHNlLCJldGF0X2FkbWluaXN0cmF0aWYiOiJGIiwiZ2VvX2lkIjoiNzUxMDhfMTczM18wMDA1MCIsImxhdGl0dWRlIjoiNDguODcwMzcxIiwibGliZWxsZV9jb21tdW5lIjoiUEFSSVMiLCJsaXN0ZV9lbnNlaWduZXMiOm51bGwsImxpc3RlX2ZpbmVzcyI6bnVsbCwibGlzdGVfaWRfYmlvIjpudWxsLCJsaXN0ZV9pZGNjIjpudWxsLCJsaXN0ZV9pZF9vcmdhbmlzbWVfZm9ybWF0aW9uIjpudWxsLCJsaXN0ZV9yZ2UiOm51bGwsImxpc3RlX3VhaSI6bnVsbCwibG9uZ2l0dWRlIjoiMi4zMDY4OTkiLCJub21fY29tbWVyY2lhbCI6bnVsbCwicmVnaW9uIjoiMTEiLCJzaXJldCI6IjQxODE2NjA5NjAwMDUxIiwic3RhdHV0X2RpZmZ1c2lvbl9ldGFibGlzc2VtZW50IjoiTyIsInRyYW5jaGVfZWZmZWN0aWZfc2FsYXJpZSI6bnVsbH1dLCJmaW5hbmNlcyI6eyIyMDIzIjp7ImNhIjoxNDYxMzk4MDksInJlc3VsdGF0X25ldCI6MTA2MjY2Mzh9fSwiY29tcGxlbWVudHMiOnsiY29sbGVjdGl2aXRlX3RlcnJpdG9yaWFsZSI6bnVsbCwiY29udmVudGlvbl9jb2xsZWN0aXZlX3JlbnNlaWduZWUiOnRydWUsImxpc3RlX2lkY2MiOlsiMTQ4NiJdLCJlZ2Fwcm9fcmVuc2VpZ25lZSI6dHJ1ZSwiZXN0X2Fzc29jaWF0aW9uIjpmYWxzZSwiZXN0X2JpbyI6ZmFsc2UsImVzdF9lbnRyZXByZW5ldXJfaW5kaXZpZHVlbCI6ZmFsc2UsImVzdF9lbnRyZXByZW5ldXJfc3BlY3RhY2xlIjpmYWxzZSwiZXN0X2VzcyI6ZmFsc2UsImVzdF9maW5lc3MiOmZhbHNlLCJlc3Rfb3JnYW5pc21lX2Zvcm1hdGlvbiI6dHJ1ZSwiZXN0X3F1YWxpb3BpIjp0cnVlLCJsaXN0ZV9pZF9vcmdhbmlzbWVfZm9ybWF0aW9uIjpbIjExNzU0ODkzNjc1Il0sImVzdF9yZ2UiOmZhbHNlLCJlc3Rfc2VydmljZV9wdWJsaWMiOmZhbHNlLCJlc3Rfc2lhZSI6ZmFsc2UsImVzdF9zb2NpZXRlX21pc3Npb24iOmZhbHNlLCJlc3RfdWFpIjpmYWxzZSwiaWRlbnRpZmlhbnRfYXNzb2NpYXRpb24iOm51bGwsInN0YXR1dF9lbnRyZXByZW5ldXJfc3BlY3RhY2xlIjpudWxsLCJ0eXBlX3NpYWUiOm51bGx9fV0sInRvdGFsX3Jlc3VsdHMiOjEsInBhZ2UiOjEsInBlcl9wYWdlIjoxMCwidG90YWxfcGFnZXMiOjF9
recorded_at: Thu, 17 Oct 2024 07:51:06 GMT
- request:
method: get
uri: https://api-lannuaire.service-public.fr/api/explore/v2.1/catalog/datasets/api-lannuaire-administration/records?where=siret:41816609600051
body:
encoding: US-ASCII
string: ''
headers:
User-Agent:
- demarches-simplifiees.fr
Expect:
- ''
response:
status:
code: 200
message: ''
headers:
Server:
- openresty
Date:
- Thu, 17 Oct 2024 07:51:06 GMT
Content-Type:
- application/json; charset=utf-8
Content-Length:
- '33'
X-Ratelimit-Remaining:
- '999989'
X-Ratelimit-Limit:
- '1000000'
X-Ratelimit-Reset:
- '2024-10-18 00:00:00+00:00'
Cache-Control:
- no-cache, no-store, max-age=0, must-revalidate
Vary:
- Accept-Language, Cookie, Host
Content-Language:
- fr-fr
Access-Control-Allow-Origin:
- "*"
Access-Control-Allow-Methods:
- POST, GET, OPTIONS
Access-Control-Max-Age:
- '1000'
Access-Control-Allow-Headers:
- Authorization, X-Requested-With, Origin, ODS-API-Analytics-App, ODS-API-Analytics-Embed-Type,
ODS-API-Analytics-Embed-Referrer, ODS-Widgets-Version, Accept
Access-Control-Expose-Headers:
- ODS-Explore-API-Deprecation, Link, X-RateLimit-Remaining, X-RateLimit-Limit,
X-RateLimit-Reset, X-RateLimit-dataset-Remaining, X-RateLimit-dataset-Limit,
X-RateLimit-dataset-Reset
Strict-Transport-Security:
- max-age=31536000
X-Xss-Protection:
- 1; mode=block
X-Content-Type-Options:
- nosniff
Referrer-Policy:
- strict-origin-when-cross-origin
Permissions-Policy:
- midi=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Content-Security-Policy:
- upgrade-insecure-requests;
X-Ua-Compatible:
- IE=edge
body:
encoding: ASCII-8BIT
string: '{"total_count": 0, "results": []}'
recorded_at: Thu, 17 Oct 2024 07:51:06 GMT
recorded_with: VCR 6.2.0

View file

@ -0,0 +1,133 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe PrefillableFromServicePublicConcern, type: :model do
let(:siret) { '20004021000060' }
let(:service) { build(:service, siret:) }
describe '#prefill_from_siret' do
let(:service) { Service.new(siret:) }
subject { service.prefill_from_siret }
context 'when API call is successful with collectivite' do
it 'prefills service attributes' do
VCR.use_cassette('annuaire_service_public_success_20004021000060') do
expect(subject.all?(&:success?)).to be_truthy
expect(service.nom).to eq("Communauté de communes - Lacs et Gorges du Verdon")
expect(service).to be_collectivite_territoriale
expect(service.email).to eq("redacted@email.fr")
expect(service.telephone).to eq("04 94 70 00 00")
expect(service.horaires).to eq("Lundi au Jeudi : de 8:00 à 12:00 et de 13:30 à 17:30\nVendredi : de 8:00 à 12:00")
expect(service.adresse).to eq("242 avenue Albert-1er 83630 Aups")
end
end
it 'does not overwrite existing attributes' do
service.nom = "Existing Name"
service.email = "existing@email.com"
VCR.use_cassette('annuaire_service_public_success_20004021000060') do
service.prefill_from_siret
expect(service.nom).to eq("Existing Name")
expect(service.email).to eq("existing@email.com")
end
end
end
context 'when API call do not find siret' do
let(:siret) { '20004021000000' }
it 'returns a failure result' do
VCR.use_cassette('annuaire_service_public_failure_20004021000000') do
expect(subject.all?(&:failure?)).to be_truthy
end
end
end
context 'when SIRET is enseignement' do
let(:siret) { '19750664500013' }
it 'prefills for enseignement' do
VCR.use_cassette('annuaire_service_public_success_19750664500013') do
expect(subject.one?(&:success?)).to be_truthy
expect(service.nom).to eq("LYCEE GENERAL ET TECHNOLOGIQUE RACINE")
expect(service).to be_etablissement_enseignement
expect(service.adresse).to eq("20 Rue du Rocher 75008 Paris")
expect(service.horaires).to be_nil
end
end
end
context 'when SIRET is a ministere' do
let(:siret) { '11004601800013' }
it 'prefills for administration centrale' do
VCR.use_cassette('annuaire_service_public_success_11004601800013') do
expect(subject.one?(&:success?)).to be_truthy
expect(service.nom).to eq("MINISTERE DE LA CULTURE")
expect(service).to be_administration_centrale
expect(service.adresse).to eq("182 Rue Saint-Honoré 75001 Paris")
expect(service.horaires).to be_nil
end
end
end
context 'when SIRET is La Poste' do
let(:siret) { '35600082800018' }
it 'prefills for administration centrale' do
VCR.use_cassette('annuaire_service_public_success_35600082800018') do
expect(subject.one?(&:success?)).to be_truthy
expect(service.nom).to eq("LA POSTE (REGION RHONE ALPES)")
expect(service).to be_service_deconcentre_de_l_etat
expect(service.adresse).to eq("4 Quai du Point du Jour 92100 Boulogne-Billancourt")
expect(service.horaires).to be_nil
end
end
end
end
describe '#denormalize_plage_ouverture' do
it 'correctly formats opening hours with one time range' do
data = [
{
"nom_jour_debut" => "Lundi",
"nom_jour_fin" => "Vendredi",
"valeur_heure_debut_1" => "09:00:00",
"valeur_heure_fin_1" => "17:00:00"
}
]
expect(service.send(:denormalize_plage_ouverture, data)).to eq("Lundi au Vendredi : de 9:00 à 17:00")
end
it 'correctly formats opening hours with two time ranges' do
data = [
{
"nom_jour_debut" => "Lundi",
"nom_jour_fin" => "Jeudi",
"valeur_heure_debut_1" => "08:00:00",
"valeur_heure_fin_1" => "12:00:00",
"valeur_heure_debut_2" => "13:30:00",
"valeur_heure_fin_2" => "17:30:00"
}, {
"nom_jour_debut" => "Vendredi",
"nom_jour_fin" => "Vendredi",
"valeur_heure_debut_1" => "08:00:00",
"valeur_heure_fin_1" => "12:00:00"
}
]
expect(service.send(:denormalize_plage_ouverture, data)).to eq("Lundi au Jeudi : de 8:00 à 12:00 et de 13:30 à 17:30\nVendredi : de 8:00 à 12:00")
end
it 'includes comments when present' do
data = [
{
"nom_jour_debut" => "Lundi",
"nom_jour_fin" => "Vendredi",
"valeur_heure_debut_1" => "09:00:00",
"valeur_heure_fin_1" => "17:00:00",
"commentaire" => "Fermé les jours fériés"
}
]
expect(service.send(:denormalize_plage_ouverture, data)).to eq("Lundi au Vendredi : de 9:00 à 17:00 (Fermé les jours fériés)")
end
end
end