diff --git a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeant-content.tsx b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-content.tsx
similarity index 59%
rename from app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeant-content.tsx
rename to app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-content.tsx
index 1b8cb21c4..bb3261e27 100644
--- a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeant-content.tsx
+++ b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-content.tsx
@@ -1,23 +1,71 @@
+import FAQLink from '#components-ui/faq-link';
import { SeePersonPageLink } from '#components-ui/see-personn-page-link';
import { FullTable } from '#components/table/full';
import { IUniteLegale } from '#models/core/types';
-import { IDirigeants, IEtatCivil, IPersonneMorale } from '#models/rne/types';
+import {
+ IDirigeantsWithMetadata,
+ IEtatCivil,
+ IPersonneMorale,
+} from '#models/rne/types';
import { formatDateLong, formatDatePartial, formatIntFr } from '#utils/helpers';
import { isPersonneMorale } from '../is-personne-morale';
type IDirigeantContentProps = {
- dirigeants: IDirigeants;
+ dirigeants: IDirigeantsWithMetadata;
uniteLegale: IUniteLegale;
};
-export function DirigeantContent({
+const dataSourceTooltip = ({
+ dataType,
+ isInIg,
+ isInInpi,
+}: {
+ dataType: string;
+ isInIg?: boolean;
+ isInInpi?: boolean;
+}) => {
+ if (!isInIg && !isInInpi) {
+ return <>>;
+ }
+
+ return (
+ <>
+ {!isInIg && (
+ <>
+ {' '}
+ >}>
+ Ce {dataType} n‘apparait pas dans les données d‘Infogreffe.
+
+ >
+ )}
+ {!isInInpi && (
+ <>
+ {' '}
+ >}>
+ Ce {dataType} n‘apparait pas dans les données de l‘INPI.
+
+ >
+ )}
+ >
+ );
+};
+
+export default function DirigeantsContent({
dirigeants,
uniteLegale,
}: IDirigeantContentProps) {
const formatDirigeant = (dirigeant: IEtatCivil | IPersonneMorale) => {
if (isPersonneMorale(dirigeant)) {
const infos = [
- dirigeant.role,
+ dirigeant.roles?.map((role) => (
+ <>
+ {role.label}
+ {dataSourceTooltip({
+ ...role,
+ dataType: 'rôle',
+ })}
+ >
+ )) || <>{dirigeant.role}>,
<>
{dirigeant.denomination}
{dirigeant.siren ? (
@@ -32,6 +80,10 @@ export function DirigeantContent({
)}
{dirigeant.natureJuridique}
+ {dataSourceTooltip({
+ ...dirigeant,
+ dataType: 'dirigeant',
+ })}
>,
];
@@ -50,7 +102,15 @@ export function DirigeantContent({
}${(dirigeant.nom || '').toUpperCase()}`;
return [
- dirigeant.role,
+ dirigeant.roles?.map((role) => (
+ <>
+ {role.label}
+ {dataSourceTooltip({
+ ...role,
+ dataType: 'rôle',
+ })}
+ >
+ )) || <>{dirigeant.role}>,
<>
{nomComplet}
{dirigeant.dateNaissance || dirigeant.dateNaissancePartial
@@ -62,6 +122,10 @@ export function DirigeantContent({
dirigeant.lieuNaissance ? `, à ${dirigeant.lieuNaissance}` : ''
}`
: ''}
+ {dataSourceTooltip({
+ ...dirigeant,
+ dataType: 'dirigeant',
+ })}
>,
...(dirigeant.dateNaissancePartial
? [
diff --git a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rne-dirigeants.tsx b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-section.tsx
similarity index 86%
rename from app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rne-dirigeants.tsx
rename to app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-section.tsx
index 9e7b274ee..b0edcb4fd 100644
--- a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rne-dirigeants.tsx
+++ b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/dirigeants-section.tsx
@@ -7,35 +7,30 @@ import { UniteLegalePageLink } from '#components/unite-legale-page-link';
import { EAdministration } from '#models/administrations/EAdministration';
import { IUniteLegale } from '#models/core/types';
import { IDirigeantsFetching } from '.';
-import { DirigeantContent } from './dirigeant-content';
+import DirigeantsContent from './dirigeants-content';
type IProps = {
dirigeants: IDirigeantsFetching;
uniteLegale: IUniteLegale;
isProtected: boolean;
- warning: JSX.Element;
};
/**
* Dirigeants section
*/
-function DirigeantsSection({
+export default function DirigeantsSection({
uniteLegale,
dirigeants,
isProtected,
- warning,
}: IProps) {
- const sources = [EAdministration.INPI];
-
- if (isProtected) {
- sources.push(EAdministration.INFOGREFFE);
- }
-
return (
{dirigeants.metadata?.isFallback && }
- {warning ? warning : null}
{isProtected ? (
- Ces informations proviennent d’
+ Ces informations proviennent en partie d’
-
@@ -99,5 +93,3 @@ function DirigeantsSection({
);
}
-
-export default DirigeantsSection;
diff --git a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/index.tsx b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/index.tsx
index 38780d115..c3345a281 100644
--- a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/index.tsx
+++ b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/index.tsx
@@ -4,43 +4,21 @@ import { HorizontalSeparator } from '#components-ui/horizontal-separator';
import BreakPageForPrint from '#components-ui/print-break-page';
import { IAPINotRespondingError } from '#models/api-not-responding';
import { IUniteLegale } from '#models/core/types';
-import {
- IDataFetchingState,
- isDataLoading,
- isDataSuccess,
- isUnauthorized,
-} from '#models/data-fetching';
-import { IDirigeants } from '#models/rne/types';
+import { IDataFetchingState } from '#models/data-fetching';
+import { IDirigeantsWithMetadata } from '#models/rne/types';
+import { ApplicationRights, hasRights } from '#models/user/rights';
import { ISession } from '#models/user/session';
import { APIRoutesPaths } from 'app/api/data-fetching/routes-paths';
import { useAPIRouteData } from 'hooks/fetch/use-API-route-data';
import BeneficiairesSection from './beneficiaires';
-import RCSRNEComparison from './rcs-rne-comparison';
-import DirigeantsSection from './rne-dirigeants';
+import DirigeantsSection from './dirigeants-section';
import DirigeantSummary from './summary';
export type IDirigeantsFetching =
- | IDirigeants
+ | IDirigeantsWithMetadata
| IAPINotRespondingError
| IDataFetchingState;
-function mergeDirigeants(
- dirigeantsRNE: IDirigeantsFetching,
- dirigeantsRCS: IDirigeantsFetching
-) {
- if (isUnauthorized(dirigeantsRCS)) {
- return { dirigeants: dirigeantsRNE, isProtected: false };
- } else {
- if (isDataLoading(dirigeantsRCS) || isDataLoading(dirigeantsRNE)) {
- return { dirigeants: IDataFetchingState.LOADING, isProtected: false };
- }
- if (isDataSuccess(dirigeantsRCS)) {
- return { dirigeants: dirigeantsRCS, isProtected: true };
- }
- }
- return { dirigeants: dirigeantsRNE, isProtected: false };
-}
-
export function DirigeantInformation({
uniteLegale,
session,
@@ -48,23 +26,15 @@ export function DirigeantInformation({
uniteLegale: IUniteLegale;
session: ISession | null;
}) {
- const dirigeantsRNE = useAPIRouteData(
- APIRoutesPaths.RneDirigeants,
- uniteLegale.siren,
- session
- );
-
- const mandatairesRCS = useAPIRouteData(
- APIRoutesPaths.EspaceAgentRcsMandataires,
+ const isProtected = hasRights(session, ApplicationRights.mandatairesRCS);
+ const dirigeants = useAPIRouteData(
+ isProtected
+ ? APIRoutesPaths.EspaceAgentDirigeantsProtected
+ : APIRoutesPaths.RneDirigeants,
uniteLegale.siren,
session
);
- const { dirigeants, isProtected } = mergeDirigeants(
- dirigeantsRNE,
- mandatairesRCS
- );
-
return (
<>
@@ -72,13 +42,6 @@ export function DirigeantInformation({
uniteLegale={uniteLegale}
dirigeants={dirigeants}
isProtected={isProtected}
- warning={
-
- }
/>
diff --git a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rcs-rne-comparison.tsx b/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rcs-rne-comparison.tsx
deleted file mode 100644
index ee305cc79..000000000
--- a/app/(header-default)/dirigeants/[slug]/_component/sections/entreprise/rcs-rne-comparison.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import routes from '#clients/routes';
-import { Warning } from '#components-ui/alerts';
-import { INPI } from '#components/administrations';
-import { IUniteLegale } from '#models/core/types';
-import { isDataSuccess } from '#models/data-fetching';
-import { IDirigeantsFetching } from '.';
-
-function RCSRNEComparison({
- dirigeantsRNE,
- dirigeantsRCS,
- uniteLegale,
-}: {
- dirigeantsRNE: IDirigeantsFetching;
- dirigeantsRCS: IDirigeantsFetching;
- uniteLegale: IUniteLegale;
-}) {
- if (!isDataSuccess(dirigeantsRNE) || !isDataSuccess(dirigeantsRCS)) {
- return null;
- } else if (dirigeantsRNE.data.length === dirigeantsRCS.data.length) {
- return null;
- }
-
- return (
-
- Les données d’Infogreffe sont issues du RNE mais il y a une différence
- entre le nombre de dirigeant(s) retourné(s) par l’
- ({dirigeantsRNE.data.length}) et par Infogreffe (
- {dirigeantsRCS.data.length}
- ). Pour comparer, vous pouvez consulter la page de cette entreprise sur{' '}
-
- data.inpi.fr
-
- .
-
- );
-}
-
-export default RCSRNEComparison;
diff --git a/app/api/data-fetching/routes-handlers.ts b/app/api/data-fetching/routes-handlers.ts
index 688735792..c5da3bffc 100644
--- a/app/api/data-fetching/routes-handlers.ts
+++ b/app/api/data-fetching/routes-handlers.ts
@@ -8,7 +8,7 @@ import { getOpqibi } from '#models/espace-agent/certificats/opqibi';
import { getQualibat } from '#models/espace-agent/certificats/qualibat';
import { getQualifelec } from '#models/espace-agent/certificats/qualifelec';
import { getConformiteEntreprise } from '#models/espace-agent/conformite';
-import { getMandatairesRCS } from '#models/espace-agent/mandataires-rcs';
+import { getDirigeantsProtected } from '#models/espace-agent/dirigeants-protected';
import { getDocumentsRNEProtected } from '#models/espace-agent/rne-protected/documents';
import { getDirigeantsRNE } from '#models/rne/dirigeants';
import { getRNEObservations } from '#models/rne/observations';
@@ -26,7 +26,7 @@ export const APIRoutesHandlers = {
[APIRoutesPaths.EspaceAgentCnetp]: getCnetp,
[APIRoutesPaths.EspaceAgentQualibat]: getQualibat,
[APIRoutesPaths.EspaceAgentQualifelec]: getQualifelec,
- [APIRoutesPaths.EspaceAgentRcsMandataires]: getMandatairesRCS,
+ [APIRoutesPaths.EspaceAgentDirigeantsProtected]: getDirigeantsProtected,
[APIRoutesPaths.EspaceAgentBeneficiaires]: getBeneficiairesController,
[APIRoutesPaths.EspaceAgentRneDocuments]: getDocumentsRNEProtected,
[APIRoutesPaths.EspaceAgentAssociationProtected]: getAssociationProtected,
diff --git a/app/api/data-fetching/routes-paths.ts b/app/api/data-fetching/routes-paths.ts
index 3d91fb456..70c860f29 100644
--- a/app/api/data-fetching/routes-paths.ts
+++ b/app/api/data-fetching/routes-paths.ts
@@ -6,7 +6,7 @@ export enum APIRoutesPaths {
EspaceAgentCnetp = 'espace-agent/cnetp',
EspaceAgentQualibat = 'espace-agent/qualibat',
EspaceAgentQualifelec = 'espace-agent/qualifelec',
- EspaceAgentRcsMandataires = 'espace-agent/rcs-mandataires',
+ EspaceAgentDirigeantsProtected = 'espace-agent/dirigeants-protected',
EspaceAgentBeneficiaires = 'espace-agent/beneficiaires',
EspaceAgentRneDocuments = 'espace-agent/rne/documents',
EspaceAgentAssociationProtected = 'espace-agent/association-protected',
diff --git a/app/api/data-fetching/routes-scopes.ts b/app/api/data-fetching/routes-scopes.ts
index 3481915da..fdb5b76cb 100644
--- a/app/api/data-fetching/routes-scopes.ts
+++ b/app/api/data-fetching/routes-scopes.ts
@@ -9,9 +9,10 @@ export const APIRoutesScopes: Record = {
[APIRoutesPaths.EspaceAgentQualibat]: ApplicationRights.protectedCertificats,
[APIRoutesPaths.EspaceAgentQualifelec]:
ApplicationRights.protectedCertificats,
- [APIRoutesPaths.EspaceAgentRcsMandataires]: ApplicationRights.mandatairesRCS,
[APIRoutesPaths.EspaceAgentBeneficiaires]: ApplicationRights.beneficiaires,
[APIRoutesPaths.EspaceAgentRneDocuments]: ApplicationRights.documentsRne,
+ [APIRoutesPaths.EspaceAgentDirigeantsProtected]:
+ ApplicationRights.mandatairesRCS,
[APIRoutesPaths.EspaceAgentAssociationProtected]:
ApplicationRights.associationProtected,
[APIRoutesPaths.RneDirigeants]: ApplicationRights.opendata,
diff --git a/clients/api-entreprise/mandataires-rcs/index.ts b/clients/api-entreprise/mandataires-rcs/index.ts
index fe1c5b811..ba9417b0b 100644
--- a/clients/api-entreprise/mandataires-rcs/index.ts
+++ b/clients/api-entreprise/mandataires-rcs/index.ts
@@ -45,26 +45,24 @@ export const clientApiEntrepriseMandatairesRCS = async (siren: Siren) => {
const mapToDomainObject = (
response: IAPIEntrepriseMandatairesRCS
): IDirigeants => {
- return {
- data: response.data.map(({ data: dirigeant }) => {
- if (dirigeant.type === 'personne_physique') {
- return {
- sexe: null,
- nom: dirigeant.nom,
- prenom: dirigeant.prenom,
- prenoms: dirigeant.prenom,
- role: dirigeant.fonction,
- lieuNaissance: dirigeant.lieu_naissance,
- dateNaissance: dirigeant.date_naissance,
- dateNaissancePartial: dirigeant.date_naissance?.slice(0, 7),
- } as IEtatCivil;
- }
+ return response.data.map(({ data: dirigeant }) => {
+ if (dirigeant.type === 'personne_physique') {
return {
- siren: dirigeant.numero_identification,
- denomination: dirigeant.raison_sociale,
- natureJuridique: null,
+ sexe: null,
+ nom: dirigeant.nom,
+ prenom: dirigeant.prenom,
+ prenoms: dirigeant.prenom,
role: dirigeant.fonction,
- } as IPersonneMorale;
- }),
- };
+ lieuNaissance: dirigeant.lieu_naissance,
+ dateNaissance: dirigeant.date_naissance,
+ dateNaissancePartial: dirigeant.date_naissance?.slice(0, 7),
+ } as IEtatCivil;
+ }
+ return {
+ siren: dirigeant.numero_identification,
+ denomination: dirigeant.raison_sociale,
+ natureJuridique: null,
+ role: dirigeant.fonction,
+ } as IPersonneMorale;
+ });
};
diff --git a/clients/api-proxy/rne/index.ts b/clients/api-proxy/rne/index.ts
index 35ac5609d..ff287af2a 100644
--- a/clients/api-proxy/rne/index.ts
+++ b/clients/api-proxy/rne/index.ts
@@ -20,8 +20,8 @@ type IRNEProxyResponse = {
capital: string;
libelleNatureJuridique: string;
};
- observations: IObservations['data'];
- dirigeants: IDirigeants['data'];
+ observations: IObservations;
+ dirigeants: IDirigeants;
};
/**
diff --git a/clients/recherche-entreprise/dirigeants.ts b/clients/recherche-entreprise/dirigeants.ts
index d49ab18cb..c668cfe51 100644
--- a/clients/recherche-entreprise/dirigeants.ts
+++ b/clients/recherche-entreprise/dirigeants.ts
@@ -4,7 +4,7 @@ import clientSearchRechercheEntreprise from '.';
export const clientDirigeantsRechercheEntreprise = async (
siren: Siren
-): Promise => {
+): Promise => {
const { results } = await clientSearchRechercheEntreprise({
searchTerms: siren,
pageResultatsRecherche: 1,
diff --git a/components/search-results/results-list.tsx b/components/search-results/results-list.tsx
index b3e4ec992..a54704096 100644
--- a/components/search-results/results-list.tsx
+++ b/components/search-results/results-list.tsx
@@ -16,7 +16,7 @@ type IProps = {
};
const DirigeantsOrElusList: React.FC<{
- dirigeantsOrElus: IDirigeants['data'];
+ dirigeantsOrElus: IDirigeants;
}> = ({ dirigeantsOrElus }) => {
const displayMax = 5;
const firstFive = dirigeantsOrElus.slice(0, displayMax);
diff --git a/models/espace-agent/dirigeants-protected.ts b/models/espace-agent/dirigeants-protected.ts
new file mode 100644
index 000000000..9dbddd70f
--- /dev/null
+++ b/models/espace-agent/dirigeants-protected.ts
@@ -0,0 +1,40 @@
+import { EAdministration } from '#models/administrations/EAdministration';
+import {
+ APINotRespondingFactory,
+ IAPINotRespondingError,
+} from '#models/api-not-responding';
+import { hasAnyError, isDataSuccess } from '#models/data-fetching';
+import { getDirigeantsRNE } from '#models/rne/dirigeants';
+import { IDirigeantsWithMetadata } from '#models/rne/types';
+import { verifySiren } from '#utils/helpers';
+import { getMandatairesRCS } from './mandataires-rcs';
+import { mergeDirigeants } from './utils';
+
+export const getDirigeantsProtected = async (
+ maybeSiren: string
+): Promise => {
+ const siren = verifySiren(maybeSiren);
+
+ const [dirigeantsRCS, dirigeantsRNE] = await Promise.all([
+ getMandatairesRCS(siren),
+ getDirigeantsRNE(siren),
+ ]);
+
+ const dirigeantMerged = mergeDirigeants(
+ isDataSuccess(dirigeantsRCS) ? dirigeantsRCS : [],
+ isDataSuccess(dirigeantsRNE) ? dirigeantsRNE.data : []
+ );
+
+ if (hasAnyError(dirigeantsRCS) && hasAnyError(dirigeantsRNE)) {
+ return APINotRespondingFactory(EAdministration.INPI, 404);
+ }
+
+ return {
+ data: dirigeantMerged,
+ metadata: {
+ isFallback: isDataSuccess(dirigeantsRNE)
+ ? dirigeantsRNE?.metadata?.isFallback || false
+ : false,
+ },
+ };
+};
diff --git a/models/espace-agent/mandataires-rcs.ts b/models/espace-agent/mandataires-rcs.ts
index 662ab5c5f..86a37d9fd 100644
--- a/models/espace-agent/mandataires-rcs.ts
+++ b/models/espace-agent/mandataires-rcs.ts
@@ -14,14 +14,14 @@ export const getMandatairesRCS = async (
const siren = verifySiren(maybeSiren);
try {
const mandatairesRCS = await clientApiEntrepriseMandatairesRCS(siren);
- if (mandatairesRCS.data.length === 0) {
+ if (mandatairesRCS.length === 0) {
return APINotRespondingFactory(EAdministration.INFOGREFFE, 404);
}
return mandatairesRCS;
} catch (error) {
return handleApiEntrepriseError(error, {
siren,
- apiResource: 'MadatairesRCS',
+ apiResource: 'MandatairesRCS',
});
}
};
diff --git a/models/espace-agent/mergeDirigeants.test.ts b/models/espace-agent/mergeDirigeants.test.ts
new file mode 100644
index 000000000..df2e7b2f6
--- /dev/null
+++ b/models/espace-agent/mergeDirigeants.test.ts
@@ -0,0 +1,275 @@
+import { IDirigeants } from '#models/rne/types';
+import { mergeDirigeants } from './utils';
+
+describe('mergeDirigeants', () => {
+ it('same dirigeant, two roles, one source', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'PRESIDENT',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'DIRECTEUR GENERAL',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(1);
+ expect(merged[0]).toEqual(
+ expect.objectContaining({ isInIg: true, isInInpi: false })
+ );
+ expect(merged[0].roles).toEqual(
+ expect.arrayContaining([
+ { label: 'PRESIDENT', isInIg: true, isInInpi: false },
+ { label: 'DIRECTEUR GENERAL', isInIg: true, isInInpi: false },
+ ])
+ );
+ });
+
+ it('same dirigeant, two roles, two sources', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'PRESIDENT',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'DIRECTEUR GENERAL',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(1);
+ expect(merged[0]).toEqual(
+ expect.objectContaining({ isInIg: true, isInInpi: true })
+ );
+ expect(merged[0].roles).toEqual(
+ expect.arrayContaining([
+ { label: 'PRESIDENT', isInIg: true, isInInpi: false },
+ { label: 'DIRECTEUR GENERAL', isInIg: false, isInInpi: true },
+ ])
+ );
+ });
+
+ it('same dirigeant, same role, one source', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'Président',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'PRESIDENT',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(1);
+ expect(merged[0]).toEqual(
+ expect.objectContaining({ isInIg: true, isInInpi: false })
+ );
+ expect(merged[0].roles).toEqual(
+ expect.arrayContaining([
+ { label: 'PRESIDENT', isInIg: true, isInInpi: false },
+ ])
+ );
+ });
+
+ it('same dirigeant, same role, two sources', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'Président',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'PRESIDENT',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(1);
+ expect(merged[0]).toEqual(
+ expect.objectContaining({ isInIg: true, isInInpi: true })
+ );
+ expect(merged[0].roles).toEqual(
+ expect.arrayContaining([
+ { label: 'PRESIDENT', isInIg: true, isInInpi: true },
+ ])
+ );
+ });
+
+ it('two dirigeants (one company and one person), two sources', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ siren: '123456789',
+ denomination: 'Company A',
+ natureJuridique: 'SARL',
+ role: 'DIRECTEUR GENERAL',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'PRESIDENT',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(2);
+ expect(merged).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ siren: '123456789',
+ roles: [
+ { label: 'DIRECTEUR GENERAL', isInIg: true, isInInpi: false },
+ ],
+ }),
+ expect.objectContaining({
+ nom: 'Doe',
+ prenom: 'John',
+ roles: [{ label: 'PRESIDENT', isInIg: false, isInInpi: true }],
+ }),
+ ])
+ );
+ });
+
+ it('two dirigeants (two persons), two sources', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'F',
+ nom: 'Smith',
+ prenom: 'Jane',
+ prenoms: 'Jane',
+ role: 'CTO',
+ lieuNaissance: 'Lyon',
+ dateNaissance: '1990-05-15',
+ },
+ ];
+
+ const dirigeantsRNE: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Doe',
+ prenom: 'John',
+ prenoms: 'John',
+ role: 'CEO',
+ lieuNaissance: 'Paris',
+ dateNaissance: '1980-01-01',
+ },
+ ];
+
+ const merged = mergeDirigeants(dirigeantsRCS, dirigeantsRNE);
+
+ expect(merged).toHaveLength(2);
+ expect(merged).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ nom: 'Smith',
+ prenom: 'Jane',
+ roles: [{ label: 'CTO', isInIg: true, isInInpi: false }],
+ }),
+ expect.objectContaining({
+ nom: 'Doe',
+ prenom: 'John',
+ roles: [{ label: 'CEO', isInIg: false, isInInpi: true }],
+ }),
+ ])
+ );
+ });
+
+ it('should not add duplicate roles for the same dirigeant', () => {
+ const dirigeantsRCS: IDirigeants = [
+ {
+ sexe: 'M',
+ nom: 'Brown',
+ prenom: 'Mike',
+ prenoms: 'Mike',
+ role: 'DG',
+ lieuNaissance: 'Marseille',
+ dateNaissance: '1975-07-20',
+ },
+ {
+ sexe: 'M',
+ nom: 'Brown',
+ prenom: 'Mike',
+ prenoms: 'Mike',
+ role: 'DG',
+ lieuNaissance: 'Marseille',
+ dateNaissance: '1975-07-20',
+ },
+ ];
+
+ const merged = mergeDirigeants(dirigeantsRCS, []);
+
+ expect(merged).toHaveLength(1);
+ expect(merged[0].roles).toHaveLength(1);
+ expect(merged[0].roles![0]).toEqual({
+ label: 'DG',
+ isInIg: true,
+ isInInpi: false,
+ });
+ });
+});
diff --git a/models/espace-agent/utils.ts b/models/espace-agent/utils.ts
index 23e156b93..56ab648fd 100644
--- a/models/espace-agent/utils.ts
+++ b/models/espace-agent/utils.ts
@@ -2,6 +2,13 @@ import { HttpNotFound } from '#clients/exceptions';
import { EAdministration } from '#models/administrations/EAdministration';
import { APINotRespondingFactory } from '#models/api-not-responding';
import { FetchRessourceException, IExceptionContext } from '#models/exceptions';
+import {
+ IDirigeants,
+ IEtatCivil,
+ IPersonneMorale,
+ IRole,
+} from '#models/rne/types';
+import { removeSpecialChars } from '#utils/helpers';
import logErrorInSentry from '#utils/sentry';
export function handleApiEntrepriseError(
@@ -22,3 +29,67 @@ export function handleApiEntrepriseError(
);
return APINotRespondingFactory(EAdministration.DINUM, e.status || 500);
}
+
+export const mergeDirigeants = (
+ dirigeantsRCS: IDirigeants,
+ dirigeantsRNE: IDirigeants
+): IDirigeants => {
+ const mergedDirigeants: Record = {};
+ const mergedRoles: Record> = {};
+
+ const createUniqueKey = (dirigeant: IEtatCivil | IPersonneMorale): string => {
+ if ('siren' in dirigeant) {
+ return `pm-${dirigeant.siren}`;
+ } else {
+ const cleanedPrenom = removeSpecialChars(dirigeant.prenom).toUpperCase();
+ const cleanedNom = removeSpecialChars(dirigeant.nom).toUpperCase();
+ const partialDate =
+ dirigeant.dateNaissancePartial ||
+ dirigeant.dateNaissance?.slice(0, 7) ||
+ '';
+ return `pf-${cleanedPrenom}-${cleanedNom}-${partialDate}`;
+ }
+ };
+
+ const dirigeants = [
+ ...dirigeantsRCS.map((d) => ({ ...d, isInIg: true, isInInpi: false })),
+ ...dirigeantsRNE.map((d) => ({ ...d, isInIg: false, isInInpi: true })),
+ ];
+ for (const dirigeant of dirigeants) {
+ const { isInInpi, isInIg, role } = dirigeant;
+ const currentDirigeantKey = createUniqueKey(dirigeant);
+
+ const foundDirigeant = mergedDirigeants[currentDirigeantKey];
+ if (!foundDirigeant) {
+ mergedDirigeants[currentDirigeantKey] = {
+ ...dirigeant,
+ isInInpi,
+ isInIg,
+ };
+ mergedRoles[currentDirigeantKey] = {};
+ } else if (isInInpi) {
+ foundDirigeant.isInInpi = true;
+ } else if (isInIg) {
+ foundDirigeant.isInIg = true;
+ }
+
+ const cleanedRole = removeSpecialChars(role).toUpperCase();
+ const foundCleanedRole = mergedRoles[currentDirigeantKey][cleanedRole];
+ if (!foundCleanedRole) {
+ mergedRoles[currentDirigeantKey][cleanedRole] = {
+ label: cleanedRole,
+ isInInpi,
+ isInIg,
+ };
+ } else if (isInInpi) {
+ foundCleanedRole.isInInpi = true;
+ } else if (isInIg) {
+ foundCleanedRole.isInIg = true;
+ }
+ }
+
+ return Object.values(mergedDirigeants).map((dirigeant) => ({
+ ...dirigeant,
+ roles: Object.values(mergedRoles[createUniqueKey(dirigeant)]),
+ }));
+};
diff --git a/models/rne/dirigeants.tsx b/models/rne/dirigeants.tsx
index 814838b6f..58ad397fd 100644
--- a/models/rne/dirigeants.tsx
+++ b/models/rne/dirigeants.tsx
@@ -7,7 +7,7 @@ import {
IAPINotRespondingError,
} from '#models/api-not-responding';
import { verifySiren } from '#utils/helpers';
-import { IDirigeants } from './types';
+import { IDirigeantsWithMetadata } from './types';
/*
* Request dirigeants from INPI's RNE
@@ -15,7 +15,7 @@ import { IDirigeants } from './types';
*/
export const getDirigeantsRNE = async (
maybeSiren: string
-): Promise => {
+): Promise => {
const siren = verifySiren(maybeSiren);
try {
diff --git a/models/rne/observations.ts b/models/rne/observations.ts
index 0a885711f..34668e86d 100644
--- a/models/rne/observations.ts
+++ b/models/rne/observations.ts
@@ -9,7 +9,7 @@ import {
IAPINotRespondingError,
} from '#models/api-not-responding';
import { verifySiren } from '#utils/helpers';
-import { IObservations } from './types';
+import { IObservationsWithMetadata } from './types';
/*
* Request observations from INPI's RNE
@@ -17,7 +17,7 @@ import { IObservations } from './types';
*/
export const getRNEObservations = async (
maybeSiren: string
-): Promise => {
+): Promise => {
const siren = verifySiren(maybeSiren);
try {
diff --git a/models/rne/types.ts b/models/rne/types.ts
index 71d6abd16..7d3c91d35 100644
--- a/models/rne/types.ts
+++ b/models/rne/types.ts
@@ -23,10 +23,13 @@ export interface IEtatCivil {
prenom: string;
prenoms: string;
role: string;
+ roles?: IRole[];
lieuNaissance: string;
dateNaissancePartial?: string;
dateNaissance?: string;
nationalite?: string;
+ isInInpi?: boolean;
+ isInIg?: boolean;
}
export interface IPersonneMorale {
@@ -34,22 +37,35 @@ export interface IPersonneMorale {
denomination: string;
natureJuridique: string | null;
role: string;
+ roles?: IRole[];
+ isInInpi?: boolean;
+ isInIg?: boolean;
}
-export interface IObservations {
- data: {
- numObservation: string;
- dateAjout: string;
- description: string;
- }[];
+export type IObservations = {
+ numObservation: string;
+ dateAjout: string;
+ description: string;
+}[];
+
+export interface IObservationsWithMetadata {
+ data: IObservations;
metadata: {
isFallback: boolean;
};
}
-export interface IDirigeants {
- data: (IEtatCivil | IPersonneMorale)[];
+export type IDirigeants = (IEtatCivil | IPersonneMorale)[];
+
+export interface IDirigeantsWithMetadata {
+ data: IDirigeants;
metadata?: {
isFallback: boolean;
};
}
+
+export interface IRole {
+ label: string;
+ isInInpi?: boolean;
+ isInIg?: boolean;
+}
diff --git a/models/search/index.ts b/models/search/index.ts
index 206a98b15..d0786339e 100644
--- a/models/search/index.ts
+++ b/models/search/index.ts
@@ -23,7 +23,7 @@ export interface ISearchResult extends IUniteLegale {
nombreEtablissementsOuverts: number;
chemin: string;
matchingEtablissements: IEtablissement[];
- dirigeants: IDirigeants['data'];
+ dirigeants: IDirigeants;
}
export interface ISearchResults {