From 22513b8af3ddc653ad3f0ced8f28b73e2ffc0ec1 Mon Sep 17 00:00:00 2001 From: Xavier Jp Date: Mon, 16 Dec 2024 21:39:07 +0100 Subject: [PATCH] feat: implements rne and insee source lastModified dates (#1398) * feat: implements rne and insee source lastModified dates * feat: add sources * feat: implements rne and insee source lastModified dates * feat: add sources * refactor: data sources last modified - only for rne and idcc * chore: re merge with main --- app/(header-default)/divers/[slug]/page.tsx | 12 +- .../_components/immatriculation-section.tsx | 195 +++++++++--------- .../entreprise/[slug]/page.tsx | 7 +- .../inclusion-kind.ts | 4 +- clients/metadata-store/index.ts | 31 --- .../idcc-metadata.ts} | 4 +- clients/recherche-entreprise/last-modified.ts | 21 ++ clients/routes.ts | 2 + clients/store.ts | 36 ++++ .../conventions-collectives-section/index.tsx | 6 +- components/section/data-section/content.tsx | 1 + components/section/data-section/index.tsx | 3 +- components/title-section/index.tsx | 2 +- components/unite-legale-description/index.tsx | 6 +- models/certifications/entreprise-inclusive.ts | 2 +- models/conventions-collectives/index.ts | 2 +- models/recherche-entreprise-modified.ts | 27 +++ next-env.d.ts | 2 +- 18 files changed, 216 insertions(+), 147 deletions(-) rename clients/{metadata-store => api-inclusion}/inclusion-kind.ts (85%) delete mode 100644 clients/metadata-store/index.ts rename clients/{metadata-store/idcc.ts => recherche-entreprise/idcc-metadata.ts} (94%) create mode 100644 clients/recherche-entreprise/last-modified.ts create mode 100644 clients/store.ts create mode 100644 models/recherche-entreprise-modified.ts diff --git a/app/(header-default)/divers/[slug]/page.tsx b/app/(header-default)/divers/[slug]/page.tsx index 841512f03..607f6c109 100644 --- a/app/(header-default)/divers/[slug]/page.tsx +++ b/app/(header-default)/divers/[slug]/page.tsx @@ -2,6 +2,7 @@ import ConventionsCollectivesSection from '#components/conventions-collectives-s import Title from '#components/title-section'; import { FICHE } from '#components/title-section/tabs'; import { getAllIdccWithMetadata } from '#models/conventions-collectives'; +import { getRechercheEntrepriseSourcesLastModified } from '#models/recherche-entreprise-modified'; import { uniteLegalePageDescription, uniteLegalePageTitle, @@ -33,7 +34,11 @@ export const generateMetadata = async ( export default async function ConventionCollectivePage(props: AppRouterProps) { const session = await getSession(); const { slug, page, isBot } = await extractParamsAppRouter(props); - const uniteLegale = await cachedGetUniteLegale(slug, isBot, page); + + const [uniteLegale, sourcesLastModified] = await Promise.all([ + cachedGetUniteLegale(slug, isBot, page), + getRechercheEntrepriseSourcesLastModified(), + ]); const ccWithMetadata = await getAllIdccWithMetadata(uniteLegale.siren); @@ -44,7 +49,10 @@ export default async function ConventionCollectivePage(props: AppRouterProps) { uniteLegale={uniteLegale} session={session} /> - + ); } diff --git a/app/(header-default)/entreprise/[slug]/_components/immatriculation-section.tsx b/app/(header-default)/entreprise/[slug]/_components/immatriculation-section.tsx index 19ce14fe9..6a398cdb0 100644 --- a/app/(header-default)/entreprise/[slug]/_components/immatriculation-section.tsx +++ b/app/(header-default)/entreprise/[slug]/_components/immatriculation-section.tsx @@ -1,7 +1,7 @@ import FAQLink from '#components-ui/faq-link'; import { INPI } from '#components/administrations'; import { DataInpiLinkWithExplanations } from '#components/justificatifs/data-inpi-link'; -import { DataSection } from '#components/section/data-section'; +import { Section } from '#components/section'; import { TwoColumnTable } from '#components/table/simple'; import { EAdministration } from '#models/administrations/EAdministration'; import { IUniteLegale } from '#models/core/types'; @@ -17,114 +17,113 @@ const formatDateCloture = (DDMM: string) => { export const UniteLegaleImmatriculationSection = ({ uniteLegale, + rneLastModified, session, }: { uniteLegale: IUniteLegale; + rneLastModified: string | null; session: ISession | null; }) => { const immatriculation = uniteLegale.immatriculation; + if (!immatriculation) { + return null; + } + return ( - - {(immatriculation) => ( - <> -

- Cette structure est une entreprise immatriculée au{' '} - Registre National des Entreprises (RNE). Ce - registre liste les entreprises de France. Il est tenu par l’ - . -

- - Le capital social d’une société est constitué des - apports (en argent ou en nature) de ses - actionnaires. -
- Il peut être fixe ou variable. La modification - d’un capital fixe nécessite une modification des - statuts tandis que le capital variable peut varier - dans certaines limites sans modification des - statuts. - , - immatriculation?.capital, - ], - [ - 'Clôture de l’exercice comptable', - formatDateCloture(immatriculation?.dateCloture), - ], - ] - : []), - ...(immatriculation?.duree - ? [ - [ - 'Durée de la personne morale', - `${immatriculation?.duree} ans${ - immatriculation?.dateFin - ? `, jusqu’au ${immatriculation?.dateFin}` - : '' - }`, - ], - ] - : []), - ...(immatriculation?.dateRadiation - ? [ - [ - 'Date de radiation', - formatDate(immatriculation?.dateRadiation), - ], - ] - : []), - [ - 'Dirigeants', - - → Consulter la liste des dirigeants - , - ], - [ - - Les annonces BODACC et les observations au RNE assurent - la publicité des actes enregistrés pour une entreprise - (procédures collectives, ventes, créations, - modification, radiation et dépôt des comptes) - , - - → Consulter les annonces - , - ], - ] - : []), - ]} - /> - - - )} -
+

+ Cette structure est une entreprise immatriculée au{' '} + Registre National des Entreprises (RNE). Ce registre + liste les entreprises de France. Il est tenu par l’ + . +

+ + Le capital social d’une société est constitué des + apports (en argent ou en nature) de ses actionnaires. +
+ Il peut être fixe ou variable. La modification d’un + capital fixe nécessite une modification des statuts + tandis que le capital variable peut varier dans + certaines limites sans modification des statuts. + , + immatriculation?.capital, + ], + [ + 'Clôture de l’exercice comptable', + formatDateCloture(immatriculation?.dateCloture), + ], + ] + : []), + ...(immatriculation?.duree + ? [ + [ + 'Durée de la personne morale', + `${immatriculation?.duree} ans${ + immatriculation?.dateFin + ? `, jusqu’au ${immatriculation?.dateFin}` + : '' + }`, + ], + ] + : []), + ...(immatriculation?.dateRadiation + ? [ + [ + 'Date de radiation', + formatDate(immatriculation?.dateRadiation), + ], + ] + : []), + [ + 'Dirigeants', + + → Consulter la liste des dirigeants + , + ], + [ + + Les annonces BODACC et les observations au RNE assurent la + publicité des actes enregistrés pour une entreprise + (procédures collectives, ventes, créations, modification, + radiation et dépôt des comptes) + , + + → Consulter les annonces + , + ], + ] + : []), + ]} + /> + + ); }; diff --git a/app/(header-default)/entreprise/[slug]/page.tsx b/app/(header-default)/entreprise/[slug]/page.tsx index 475dede23..0d211b69e 100644 --- a/app/(header-default)/entreprise/[slug]/page.tsx +++ b/app/(header-default)/entreprise/[slug]/page.tsx @@ -16,6 +16,7 @@ import { isCollectiviteTerritoriale, isServicePublic, } from '#models/core/types'; +import { getRechercheEntrepriseSourcesLastModified } from '#models/recherche-entreprise-modified'; import { ApplicationRights, hasRights } from '#models/user/rights'; import { shouldNotIndex, @@ -54,7 +55,10 @@ export default async function UniteLegalePage(props: AppRouterProps) { props ); const session = await getSession(); - const uniteLegale = await cachedGetUniteLegale(slug, isBot, page); + const [uniteLegale, sourcesLastModified] = await Promise.all([ + cachedGetUniteLegale(slug, isBot, page), + getRechercheEntrepriseSourcesLastModified(), + ]); return ( <> @@ -84,6 +88,7 @@ export default async function UniteLegalePage(props: AppRouterProps) { {uniteLegale.dateMiseAJourInpi && ( )} diff --git a/clients/metadata-store/inclusion-kind.ts b/clients/api-inclusion/inclusion-kind.ts similarity index 85% rename from clients/metadata-store/inclusion-kind.ts rename to clients/api-inclusion/inclusion-kind.ts index 4a434db83..68060356e 100644 --- a/clients/metadata-store/inclusion-kind.ts +++ b/clients/api-inclusion/inclusion-kind.ts @@ -1,5 +1,5 @@ import routes from '#clients/routes'; -import { MetadataStore } from '.'; +import { DataStore } from '../store'; type InclusionMetadata = { id: string; name: string; parent: string }; @@ -10,7 +10,7 @@ function mapToDomainObject(response: { results: InclusionMetadata[] }) { }, {} as { [kind: string]: InclusionMetadata }); } -const store = new MetadataStore( +const store = new DataStore( routes.certifications.entrepriseInclusive.api.metadata, 'inclusion-metadata', mapToDomainObject diff --git a/clients/metadata-store/index.ts b/clients/metadata-store/index.ts deleted file mode 100644 index 0d935468c..000000000 --- a/clients/metadata-store/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HttpServerError } from '#clients/exceptions'; -import { httpGet } from '#utils/network'; - -export class MetadataStore { - private metadata: { [key: string]: T } | null; - - constructor( - private metadataUrl: string, - private storeName: string, - private mapToDomainObject: (e: any) => { [key: string]: T } - ) { - this.metadata = null; - } - - get = async (key: string) => { - if (!this.metadata) { - const response = await httpGet(this.metadataUrl); - this.metadata = this.mapToDomainObject(response); - } - - if (Object.values(this.metadata).length === 0) { - throw new HttpServerError(`Empty metadata list : ${this.storeName}`); - } - - if (key in this.metadata) { - return this.metadata[key]; - } else { - return null; - } - }; -} diff --git a/clients/metadata-store/idcc.ts b/clients/recherche-entreprise/idcc-metadata.ts similarity index 94% rename from clients/metadata-store/idcc.ts rename to clients/recherche-entreprise/idcc-metadata.ts index 551ac06af..21ba0874a 100644 --- a/clients/metadata-store/idcc.ts +++ b/clients/recherche-entreprise/idcc-metadata.ts @@ -1,6 +1,6 @@ import routes from '#clients/routes'; import { ICCWithMetadata } from '#models/conventions-collectives'; -import { MetadataStore } from '.'; +import { DataStore } from '../store'; type IIdccMetadata = { [idcc: string]: { @@ -33,7 +33,7 @@ function mapToDomainObject(response: IIdccMetadata) { }, {} as { [idcc: string]: ICCWithMetadata }); } -const store = new MetadataStore( +const store = new DataStore( routes.rechercheEntreprise.idcc.metadata, 'idcc-metadata', mapToDomainObject diff --git a/clients/recherche-entreprise/last-modified.ts b/clients/recherche-entreprise/last-modified.ts new file mode 100644 index 000000000..a8055736e --- /dev/null +++ b/clients/recherche-entreprise/last-modified.ts @@ -0,0 +1,21 @@ +import routes from '#clients/routes'; +import { DataStore } from '../store'; + +const store = new DataStore( + routes.rechercheEntreprise.lastModified, + 'recherche-entreprise-last-modified', + (response) => response +); + +/** + * Returns the dates of last modification of the data sources used in recherche entreprise + * + * For instance : + * IDCC was published on 29/11, we indexed it on 12/12, then last modified date is 29/11 + */ +export const clientRechercheEntrepriseLastModified = async () => { + return { + rne: (await store.get('rne')) ?? null, + idcc: (await store.get('convention_collective')) ?? null, + }; +}; diff --git a/clients/routes.ts b/clients/routes.ts index 354b20b1f..4a4aa970f 100644 --- a/clients/routes.ts +++ b/clients/routes.ts @@ -210,6 +210,8 @@ const routes = { metadata: 'https://recherche-entreprises.api.gouv.fr/idcc/metadata', siren: 'https://recherche-entreprises.api.gouv.fr/idcc', }, + lastModified: + 'https://recherche-entreprises.api.gouv.fr/sources/last_modified', }, tooling: { grist: 'https://grist.numerique.gouv.fr/api/docs/', diff --git a/clients/store.ts b/clients/store.ts new file mode 100644 index 000000000..d0f00e9bb --- /dev/null +++ b/clients/store.ts @@ -0,0 +1,36 @@ +import { HttpServerError } from '#clients/exceptions'; +import { httpGet } from '#utils/network'; + +/** + * Basic client that store an API response in memory + * + * Call once and cache response. + */ +export class DataStore { + private data: { [key: string]: T } | null; + + constructor( + private dataUrl: string, + private storeName: string, + private mapToDomainObject: (e: any) => { [key: string]: T } + ) { + this.data = null; + } + + get = async (key: string) => { + if (!this.data) { + const response = await httpGet(this.dataUrl); + this.data = this.mapToDomainObject(response); + } + + if (Object.values(this.data).length === 0) { + throw new HttpServerError(`Empty data list : ${this.storeName}`); + } + + if (key in this.data) { + return this.data[key]; + } else { + return null; + } + }; +} diff --git a/components/conventions-collectives-section/index.tsx b/components/conventions-collectives-section/index.tsx index 5091388ea..27417c406 100644 --- a/components/conventions-collectives-section/index.tsx +++ b/components/conventions-collectives-section/index.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import routes from '#clients/routes'; import ButtonLink from '#components-ui/button'; import FAQLink from '#components-ui/faq-link'; @@ -11,6 +10,7 @@ import { EAdministration } from '#models/administrations/EAdministration'; import { IAPINotRespondingError } from '#models/api-not-responding'; import { ICCWithMetadata } from '#models/conventions-collectives'; import { capitalize, formatSiret } from '#utils/helpers'; +import React from 'react'; function CCUnknown({ ccWithMetadata }: { ccWithMetadata: ICCWithMetadata[] }) { const unknown = ccWithMetadata.filter((e) => e.unknown); @@ -43,7 +43,8 @@ function CCUnknown({ ccWithMetadata }: { ccWithMetadata: ICCWithMetadata[] }) { const ConventionsCollectivesSection: React.FC<{ ccWithMetadata: ICCWithMetadata[] | IAPINotRespondingError; -}> = ({ ccWithMetadata }) => { + ccLastModified: string | null; +}> = ({ ccWithMetadata, ccLastModified }) => { return ( } data={ccWithMetadata} + lastModified={ccLastModified} > {(ccWithMetadata) => { const plural = ccWithMetadata.length > 0 ? 's' : ''; diff --git a/components/section/data-section/content.tsx b/components/section/data-section/content.tsx index d52d28eed..f4f670339 100644 --- a/components/section/data-section/content.tsx +++ b/components/section/data-section/content.tsx @@ -34,6 +34,7 @@ export function DataSectionContent< const administrationMetaData = administrationsMetaData[data.administration] || {}; + return ( <> >({ if (notFoundInfo === null && isAPI404(data)) { return null; } + //@ts-ignore - const lastModified = data?.lastModified || null; + const lastModified = props?.lastModified ?? data?.lastModified ?? null; return (
diff --git a/components/title-section/index.tsx b/components/title-section/index.tsx index 868f03883..3b9fd6004 100644 --- a/components/title-section/index.tsx +++ b/components/title-section/index.tsx @@ -69,7 +69,7 @@ const Title: React.FC = ({ {estNonDiffusibleStrict(uniteLegale) ? (

Les informations concernant cette entreprise ne sont pas publiques.

) : ( - + )} = ({ uniteLegale, session }) => { +}> = ({ uniteLegale }) => { const ageCreation = uniteLegale.dateCreation ? formatAge(uniteLegale.dateCreation) : null; diff --git a/models/certifications/entreprise-inclusive.ts b/models/certifications/entreprise-inclusive.ts index 9e1aed53d..bdcb194ac 100644 --- a/models/certifications/entreprise-inclusive.ts +++ b/models/certifications/entreprise-inclusive.ts @@ -1,6 +1,6 @@ import { clientAPIInclusion } from '#clients/api-inclusion'; +import { clientInclusionKindMetadata } from '#clients/api-inclusion/inclusion-kind'; import { HttpNotFound } from '#clients/exceptions'; -import { clientInclusionKindMetadata } from '#clients/metadata-store/inclusion-kind'; import { EAdministration } from '#models/administrations/EAdministration'; import { APINotRespondingFactory, diff --git a/models/conventions-collectives/index.ts b/models/conventions-collectives/index.ts index dff903c57..235fd28f2 100644 --- a/models/conventions-collectives/index.ts +++ b/models/conventions-collectives/index.ts @@ -1,5 +1,5 @@ -import { clientIdccMetadata } from '#clients/metadata-store/idcc'; import { clientIdccRechercheEntreprise } from '#clients/recherche-entreprise/idcc'; +import { clientIdccMetadata } from '#clients/recherche-entreprise/idcc-metadata'; import { Siren } from '#utils/helpers'; import logErrorInSentry from '#utils/sentry'; import { EAdministration } from '../administrations/EAdministration'; diff --git a/models/recherche-entreprise-modified.ts b/models/recherche-entreprise-modified.ts new file mode 100644 index 000000000..0481e7c89 --- /dev/null +++ b/models/recherche-entreprise-modified.ts @@ -0,0 +1,27 @@ +import { clientRechercheEntrepriseLastModified } from '#clients/recherche-entreprise/last-modified'; +import logErrorInSentry from '#utils/sentry'; +import { FetchRechercheEntrepriseException } from './core/types'; + +export interface IRechercheEntrepriseSourcesLastModified { + rne: string | null; + idcc: string | null; +} + +/** + * Return the last modified dates for a selections of sources used directly in the site : + * IDCC and RNE are directly use from recherche entreprise + */ +export const getRechercheEntrepriseSourcesLastModified = + async (): Promise => { + try { + return await clientRechercheEntrepriseLastModified(); + } catch (e: any) { + logErrorInSentry( + new FetchRechercheEntrepriseException({ + cause: e, + message: 'Could not fetch sources’s last modified dates', + }) + ); + return { rne: null, idcc: null }; + } + }; diff --git a/next-env.d.ts b/next-env.d.ts index 3cd7048ed..725dd6f24 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -3,4 +3,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.