feat: passe à l'API de recherche d’entreprises de api.gouv.fr

pull/2943/head
Jalil Arfaoui 2024-04-15 17:49:23 +02:00
parent 6e4e630d68
commit 269ca6fbcc
13 changed files with 132 additions and 26 deletions

View File

@ -36,6 +36,7 @@ describe(`Navigation to income simulator using company name (${
let pendingRequests = new Set()
let responses = {}
const hostnamesToRecord = [
'recherche-entreprises.api.gouv.fr',
'api.recherche-entreprises.fabrique.social.gouv.fr',
'geo.api.gouv.fr',
]
@ -64,8 +65,8 @@ describe(`Navigation to income simulator using company name (${
it('should allow to retrieve company and show link corresponding to the legal status', function () {
cy.intercept({
method: 'GET',
hostname: 'api.recherche-entreprises.fabrique.social.gouv.fr',
url: '/api/v1/search*',
hostname: 'recherche-entreprises.api.gouv.fr',
url: '/search?q=*',
}).as('search')
cy.get('input[data-test-id="company-search-input"]').first().type('menoz')
@ -93,8 +94,8 @@ describe(`Navigation to income simulator using company name (${
it('should allow auto entrepreneur to access the corresponding income simulator', function () {
cy.intercept({
method: 'GET',
hostname: 'api.recherche-entreprises.fabrique.social.gouv.fr',
url: '/api/v1/search*',
hostname: 'recherche-entreprises.api.gouv.fr',
url: '/search?q=*',
}).as('search')
cy.get('input[data-test-id="company-search-input"]').type('jeremy rialland')

View File

@ -21,6 +21,7 @@ describe('Landing page', function () {
let pendingRequests = new Set()
let responses = {}
const hostnamesToRecord = [
'recherche-entreprises.api.gouv.fr',
'api.recherche-entreprises.fabrique.social.gouv.fr',
'geo.api.gouv.fr',
]
@ -41,13 +42,13 @@ describe('Landing page', function () {
cy.get(searchInputPath).should('have.attr', 'placeholder')
cy.get(searchInputPath).invoke('attr', 'type').should('equal', 'search')
cy.get(searchInputPath).first().type('noima')
cy.get(searchInputPath).first().type('menoz')
cy.intercept(
{
method: 'GET',
hostname: 'api.recherche-entreprises.fabrique.social.gouv.fr',
url: '/api/v1/search?*',
hostname: 'recherche-entreprises.api.gouv.fr',
url: '/search?q=*',
},
(req) => {
req.responseTimeout = 10 * 1000
@ -57,7 +58,7 @@ describe('Landing page', function () {
cy.wait('@search')
cy.get(searchResultsPath).children().should('have.length', 6)
cy.get(searchResultsPath).children().should('have.length.at.least', 4)
cy.get(searchResultsPath).children().first().click()
cy.url().should('include', '/pour-mon-entreprise')

View File

@ -1,7 +1,15 @@
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self' mon-entreprise.fr; style-src 'self' 'unsafe-inline' mon-entreprise.zammad.com; connect-src 'self' *.incubateur.net raw.githubusercontent.com tm.urssaf.fr mon-entreprise.zammad.com api.recherche-entreprises.fabrique.social.gouv.fr geo.api.gouv.fr *.algolia.net *.algolianet.com polyfill.io jedonnemonavis.numerique.gouv.fr user-images.githubusercontent.com; form-action 'self' *.sibforms.com *.incubateur.net mon-entreprise.zammad.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' tm.urssaf.fr *.incubateur.net stonly.com code.jquery.com mon-entreprise.zammad.com polyfill.io; img-src 'self' data: mon-entreprise.urssaf.fr tm.urssaf.fr user-images.githubusercontent.com jedonnemonavis.numerique.gouv.fr; frame-src 'self' https://www.youtube-nocookie.com https://codesandbox.io https://place-des-entreprises.beta.gouv.fr https://reso-staging.osc-fr1.scalingo.io https://stackblitz.com"
Content-Security-Policy = """\
default-src 'self' mon-entreprise.fr; \
style-src 'self' 'unsafe-inline' mon-entreprise.zammad.com; \
connect-src 'self' *.incubateur.net raw.githubusercontent.com tm.urssaf.fr mon-entreprise.zammad.com recherche-entreprises.api.gouv.fr api.recherche-entreprises.fabrique.social.gouv.fr geo.api.gouv.fr *.algolia.net *.algolianet.com polyfill.io jedonnemonavis.numerique.gouv.fr user-images.githubusercontent.com; \
form-action 'self' *.sibforms.com *.incubateur.net mon-entreprise.zammad.com; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' tm.urssaf.fr *.incubateur.net stonly.com code.jquery.com mon-entreprise.zammad.com polyfill.io; \
img-src 'self' data: mon-entreprise.urssaf.fr tm.urssaf.fr user-images.githubusercontent.com jedonnemonavis.numerique.gouv.fr; \
frame-src 'self' https://www.youtube-nocookie.com https://codesandbox.io https://place-des-entreprises.beta.gouv.fr https://reso-staging.osc-fr1.scalingo.io https://stackblitz.com \
"""
[dev]
autoLaunch = false
@ -33,7 +41,7 @@ Content-Security-Policy = "default-src 'self' mon-entreprise.fr; style-src 'self
############
# Redirects following architectural changes
# DO NOT MOVE THIS SECTION ! ORDER MATTERS IN REDIRECTS !
# DO NOT MOVE THIS SECTION ! ORDER MATTERS IN REDIRECTS !
# :SITE_<name> is a placeholder replaced before deploy (depends on the environment)
@ -80,7 +88,7 @@ Content-Security-Policy = "default-src 'self' mon-entreprise.fr; style-src 'self
from=":SITE_FR/simulateurs/%C3%A9conomie-collaborative/*"
to=":SITE_FR/assistants/%C3%A9conomie-collaborative/:splat"
status = 301
## Older changes
# FR | coronavirus -> simulateurs/chômage-partiel
@ -163,7 +171,7 @@ Content-Security-Policy = "default-src 'self' mon-entreprise.fr; style-src 'self
####################
# Redirect following huge refacto in modele-social
# DO NOT MOVE THIS SECTION ! ORDER MATTERS IN REDIRECTS !
# DO NOT MOVE THIS SECTION ! ORDER MATTERS IN REDIRECTS !
##################""

View File

@ -0,0 +1,87 @@
import { CodeActivite } from '@/domain/CodeActivite'
import { CodeCatégorieJuridique } from '@/domain/CodeCatégorieJuridique'
import { IsoDate, parseIsoDateString } from '@/domain/Date'
import { Entreprise } from '@/domain/Entreprise'
import { EntreprisesRepository } from '@/domain/EntreprisesRepository'
import { Établissement } from '@/domain/Établissement'
import { Siren, Siret } from '@/domain/Siren'
/**
* @see https://api.gouv.fr/documentation/api-recherche-entreprises
*/
export const RechercheEntreprisesGouvFr: EntreprisesRepository = {
rechercheTexteLibre,
}
const makeSearchUrl = (q: string, limit: number) =>
`https://recherche-entreprises.api.gouv.fr/search?q=${q}&etat_administratif=A&per_page=${limit}`
async function rechercheTexteLibre(text: string, limit = 10) {
const response = await fetch(makeSearchUrl(text, limit))
if (!response.ok) {
return null
}
const json = (await response.json()) as ApiResponse
return json.results.map(EntrepriseFromApiAdapter)
}
interface ApiResponse {
results: Array<EntrepriseApi>
total_results: number
page: number
per_page: number
total_pages: number
}
interface EntrepriseApi {
siren: Siren
nom_complet: string
nom_raison_sociale: string
sigle: string
date_creation: IsoDate
nature_juridique: CodeCatégorieJuridique
activite_principale: CodeActivite
siege: EtablissementApi
matching_etablissements: Array<EtablissementApi>
nombre_etablissements: number
nombre_etablissements_ouverts: number
}
const EntrepriseFromApiAdapter = (api: EntrepriseApi): Entreprise => {
const siège = ÉtablissementFromApiAdapter(api.siege)
const établissement = api.matching_etablissements.length
? ÉtablissementFromApiAdapter(api.matching_etablissements[0])
: siège
return {
nom: api.nom_complet,
siren: api.siren,
dateDeCréation: parseIsoDateString(api.date_creation),
siège,
établissement,
activitéPrincipale: api.activite_principale,
codeCatégorieJuridique: api.nature_juridique,
}
}
interface EtablissementApi {
siret: Siret
adresse: string
commune: string
libelle_commune: string
activite_principale: CodeActivite
est_siege: boolean
nom_commercial: string
}
const ÉtablissementFromApiAdapter = (api: EtablissementApi): Établissement => ({
siret: api.siret,
adresse: {
complète: api.adresse,
codeCommune: api.commune,
},
activitéPrincipale: api.activite_principale,
})

View File

@ -44,7 +44,6 @@ const établissementAdapter = (
),
adresse: {
complète: fabriqueSocialEtablissement.address,
codePostal: fabriqueSocialEtablissement.codePostalEtablissement,
codeCommune: fabriqueSocialEtablissement.codeCommuneEtablissement,
},
})

View File

@ -51,7 +51,7 @@ export default function EntrepriseSearchDetails({
</>
)}
<br />
<Trans>Établissement recherché:</Trans>{' '}
<Trans>Établissement recherché :</Trans>{' '}
<Strong>{établissement?.adresse.complète}</Strong>
</CompanyContainer>
)

View File

@ -1,6 +1,6 @@
import { createContext } from 'react'
import { FabriqueSocialEntreprisesRepository } from '@/api/RechercheEntreprise/fabrique-social'
import { RechercheEntreprisesGouvFr } from '@/api/RechercheEntreprise/RechercheEntreprisesGouvFr'
import { EntreprisesRepository } from '@/domain/EntreprisesRepository'
interface Repositories {
@ -8,5 +8,5 @@ interface Repositories {
}
export const RepositoriesContext = createContext<Repositories>({
entreprises: FabriqueSocialEntreprisesRepository,
entreprises: RechercheEntreprisesGouvFr,
})

View File

@ -1,5 +1,4 @@
export interface Adresse {
complète?: string
codePostal: string
codeCommune: string
}

View File

@ -4,6 +4,13 @@ export type PublicodeDate =
`${number}${number}/${number}${number}/${number}${number}${number}${number}`
export const publicodesStandardDateFormat = 'dd/MM/yyyy'
/**
* @example 2024-12-31
*/
export type IsoDate =
`${number}${number}${number}${number}-${number}${number}-${number}${number}`
export const isoDateFormat = 'yyyy-MM-dd'
type DateToPublicodeDate = (d: Date) => PublicodeDate
export const toPublicodeDate = format(
@ -16,3 +23,5 @@ export const parsePublicodesDateString = parse(
new Date(),
publicodesStandardDateFormat
) as PublicodeDateToDate
export const parseIsoDateString = parse(new Date(), isoDateFormat)

View File

@ -86,7 +86,6 @@ Coût de création: Cost of creation
Dividendes nets: Net dividends
Documentation: Documentation
Documentation des simulateurs: Simulator documentation
"Domiciliée à l'adresse :": "Domiciled at address :"
Donner votre avis: Give your opinion
Décrivez votre projet ou votre problème en donnant quelques éléments de contexte. Notre partenaire Place des Entreprises identifiera, parmi lensemble des partenaires publics et parapublics, le conseiller compétent pour votre demande. Celui-ci vous contactera par téléphone sous 5 jours et vous accompagnera en fonction de votre situation.:
Describe your project or problem and provide some background information. Our
@ -285,6 +284,7 @@ Simulateurs: Simulators
Simulateurs et Assistants: Simulators and Assistants
Simulation en cours: Simulation in progress
Situation personnelle: Personal situation
"Siège :": "Headquarters:"
Soit <2><0></0></2> avant impôts: Or <2><0></0></2> before tax
Statistiques: Statistics
Statut du conjoint: Spouse's status
@ -1721,6 +1721,7 @@ warning:
À quoi servent mes cotisations ?: What are my contributions used for?
Échanger avec un conseiller: Talk to an advisor
Échec: Failure
"Établissement recherché :": "Establishment sought :"
Étape complétée: Step completed
Étape non complétée: Step not completed
Étape {{ total }} sur {{ maxValue }}: Step {{ total }} on {{ maxValue }}

View File

@ -91,7 +91,6 @@ Coût de création: Coût de création
Dividendes nets: Dividendes nets
Documentation: Documentation
Documentation des simulateurs: Documentation des simulateurs
"Domiciliée à l'adresse :": "Domiciliée à l'adresse :"
Donner votre avis: Donner votre avis
Décrivez votre projet ou votre problème en donnant quelques éléments de contexte. Notre partenaire Place des Entreprises identifiera, parmi lensemble des partenaires publics et parapublics, le conseiller compétent pour votre demande. Celui-ci vous contactera par téléphone sous 5 jours et vous accompagnera en fonction de votre situation.:
Décrivez votre projet ou votre problème en donnant quelques éléments de
@ -299,6 +298,7 @@ Simulateurs: Simulateurs
Simulateurs et Assistants: Simulateurs et Assistants
Simulation en cours: Simulation en cours
Situation personnelle: Situation personnelle
"Siège :": "Siège :"
Soit <2><0></0></2> avant impôts: Soit <2><0></0></2> avant impôts
Statistiques: Statistiques
Statut du conjoint: Statut du conjoint
@ -1827,6 +1827,7 @@ warning:
À quoi servent mes cotisations ?: À quoi servent mes cotisations ?
Échanger avec un conseiller: Échanger avec un conseiller
Échec: Échec
"Établissement recherché :": "Établissement recherché :"
Étape complétée: Étape complétée
Étape non complétée: Étape non complétée
Étape {{ total }} sur {{ maxValue }}: Étape {{ total }} sur {{ maxValue }}

View File

@ -1,4 +1,4 @@
import { FabriqueSocialEntreprise } from '@/api/fabrique-social'
import { FabriqueSocialEntreprise } from '@/api/RechercheEntreprise/fabrique-social'
export const fabriqueSocialWithSiege: FabriqueSocialEntreprise = {
activitePrincipale: 'Agences immobilières',

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { fabriqueSocialEntrepriseAdapter } from '@/api/fabrique-social'
import { fabriqueSocialEntrepriseAdapter } from '@/api/RechercheEntreprise/fabrique-social'
import {
fabriqueSocialWithoutSiege,
@ -15,16 +15,16 @@ describe('Fabrique Social', () => {
)
it('retourne le siren', () => {
expect(entreprise.siren).to.equal('849074190')
expect(entreprise.siren).toBe('849074190')
})
it("a l'établissement demandé dans 'établissement'", () => {
expect(entreprise.siège?.adresse.complète).to.equal(
expect(entreprise.siège?.adresse.complète).toBe(
'23 RUE DE MOGADOR 75009 PARIS 9'
)
})
it("a le siège dans 'siège'", () => {
expect(entreprise.établissement.adresse.complète).to.equal(
expect(entreprise.établissement.adresse.complète).toBe(
'4 RUE VOLTAIRE 44000 NANTES'
)
})
@ -35,10 +35,10 @@ describe('Fabrique Social', () => {
)
it("n'a pas de siège", () => {
expect(entreprise.siège?.adresse.complète).to.equal(undefined)
expect(entreprise.siège?.adresse.complète).toBe(undefined)
})
it('a létablissement demandé', () => {
expect(entreprise.établissement.adresse.complète).to.equal(
expect(entreprise.établissement.adresse.complète).toBe(
'4 RUE VOLTAIRE 44000 NANTES'
)
})