@proconnect-gouv/proconnect.identite 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +0 -2
- package/dist/data/certification/categories-juridiques-sources.d.ts +8 -0
- package/dist/data/certification/categories-juridiques-sources.d.ts.map +1 -0
- package/dist/data/certification/categories-juridiques-sources.js +743 -0
- package/dist/data/certification/index.d.ts +1 -0
- package/dist/data/certification/index.d.ts.map +1 -1
- package/dist/data/certification/index.js +1 -0
- package/dist/errors/index.d.ts +0 -9
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +0 -18
- package/dist/managers/certification/certification-score.d.ts +11 -1
- package/dist/managers/certification/certification-score.d.ts.map +1 -1
- package/dist/managers/certification/certification-score.js +17 -8
- package/dist/managers/certification/index.d.ts +1 -1
- package/dist/managers/certification/index.d.ts.map +1 -1
- package/dist/managers/certification/index.js +1 -1
- package/dist/managers/certification/process-certification-dirigeant.d.ts +80 -0
- package/dist/managers/certification/process-certification-dirigeant.d.ts.map +1 -0
- package/dist/managers/certification/process-certification-dirigeant.js +121 -0
- package/dist/managers/organization/adapters/api_entreprise.d.ts +2 -1
- package/dist/managers/organization/adapters/api_entreprise.d.ts.map +1 -1
- package/dist/managers/organization/adapters/api_entreprise.js +3 -1
- package/dist/managers/organization/force-join-organization.d.ts +1 -1
- package/dist/managers/organization/force-join-organization.d.ts.map +1 -1
- package/dist/managers/organization/force-join-organization.js +3 -4
- package/dist/managers/user/assign-user-verification-type-to-domain.d.ts.map +1 -1
- package/dist/managers/user/assign-user-verification-type-to-domain.js +6 -6
- package/dist/repositories/organization/find-by-user-id.d.ts +1 -1
- package/dist/repositories/organization/get-users-by-organization.d.ts +1 -1
- package/dist/repositories/organization/link-user-to-organization.d.ts +1 -1
- package/dist/repositories/organization/upsert.d.ts.map +1 -1
- package/dist/repositories/organization/upsert.js +8 -3
- package/dist/repositories/user/update-user-organization-link.d.ts +1 -1
- package/dist/services/organization/index.d.ts +1 -0
- package/dist/services/organization/index.d.ts.map +1 -1
- package/dist/services/organization/index.js +1 -0
- package/dist/services/organization/is-entreprise-unipersonnelle.d.ts +3 -3
- package/dist/services/organization/is-entreprise-unipersonnelle.d.ts.map +1 -1
- package/dist/services/organization/is-entreprise-unipersonnelle.js +4 -4
- package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts +3 -0
- package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts.map +1 -0
- package/dist/services/organization/is-organization-covered-by-certification-dirigeant.js +2 -0
- package/dist/services/organization/is-public-service.d.ts.map +1 -1
- package/dist/services/organization/is-public-service.js +16 -6
- package/dist/services/organization/is-syndicat-communal.d.ts.map +1 -1
- package/dist/services/organization/is-syndicat-communal.js +1 -0
- package/dist/types/franceconnect.d.ts.map +1 -1
- package/dist/types/franceconnect.js +2 -2
- package/dist/types/{dirigeant.d.ts → identity-vector.d.ts} +2 -1
- package/dist/types/identity-vector.d.ts.map +1 -0
- package/dist/types/{dirigeant.js → identity-vector.js} +8 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/organization-info.d.ts +2 -1
- package/dist/types/organization-info.d.ts.map +1 -1
- package/dist/types/organization-info.js +1 -0
- package/dist/types/organization.d.ts +1 -0
- package/dist/types/organization.d.ts.map +1 -1
- package/dist/types/user-organization-link.d.ts +31 -24
- package/dist/types/user-organization-link.d.ts.map +1 -1
- package/dist/types/user-organization-link.js +15 -8
- package/package.json +2 -2
- package/src/data/certification/categories-juridiques-sources.ts +811 -0
- package/src/data/certification/index.ts +1 -0
- package/src/errors/index.ts +0 -21
- package/src/managers/certification/certification-score.test.ts +69 -33
- package/src/managers/certification/certification-score.ts +20 -9
- package/src/managers/certification/index.ts +1 -1
- package/src/managers/certification/{is-organization-dirigeant.test.ts → process-certification-dirigeant.test.ts} +62 -64
- package/src/managers/certification/{is-organization-dirigeant.ts → process-certification-dirigeant.ts} +115 -74
- package/src/managers/organization/adapters/api_entreprise.test.ts.snapshot +4 -0
- package/src/managers/organization/adapters/api_entreprise.ts +3 -0
- package/src/managers/organization/force-join-organization.ts +3 -6
- package/src/managers/organization/get-organization-info.test.ts.snapshot +3 -0
- package/src/managers/organization/mark-domain-as-verified.test.ts +1 -1
- package/src/managers/user/assign-user-verification-type-to-domain.ts +6 -6
- package/src/repositories/organization/find-by-id.test.ts +1 -0
- package/src/repositories/organization/find-by-user-id.test.ts.snapshot +3 -0
- package/src/repositories/organization/get-by-id.test.ts +1 -0
- package/src/repositories/organization/link-user-to-organization.test.ts +3 -2
- package/src/repositories/organization/link-user-to-organization.test.ts.snapshot +2 -2
- package/src/repositories/organization/upsert.ts +8 -1
- package/src/repositories/organization/upset.test.ts +2 -0
- package/src/services/organization/index.ts +1 -0
- package/src/services/organization/is-entreprise-unipersonnelle.ts +5 -5
- package/src/services/organization/is-organization-covered-by-certification-dirigeant.test.ts +43 -0
- package/src/services/organization/is-organization-covered-by-certification-dirigeant.ts +9 -0
- package/src/services/organization/is-public-service.test.ts +1 -1
- package/src/services/organization/is-public-service.ts +20 -7
- package/src/services/organization/is-syndicat-communal.ts +1 -0
- package/src/types/franceconnect.ts +6 -7
- package/src/types/{dirigeant.ts → identity-vector.ts} +9 -0
- package/src/types/index.ts +1 -1
- package/src/types/organization-info.ts +1 -0
- package/src/types/organization.ts +1 -0
- package/src/types/user-organization-link.ts +17 -11
- package/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/managers/certification/is-organization-dirigeant.d.ts +0 -45
- package/dist/managers/certification/is-organization-dirigeant.d.ts.map +0 -1
- package/dist/managers/certification/is-organization-dirigeant.js +0 -97
- package/dist/types/dirigeant.d.ts.map +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
//
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
3
|
+
import { isOrganizationCoveredByCertificationDirigeant } from "#src/services/organization";
|
|
4
|
+
import {
|
|
5
|
+
NullIdentityVector,
|
|
6
|
+
type FranceConnectUserInfo,
|
|
7
|
+
type IdentityVector,
|
|
8
|
+
type Organization,
|
|
9
|
+
} from "#src/types";
|
|
6
10
|
import type { ApiEntrepriseInfogreffeRepository } from "@proconnect-gouv/proconnect.api_entreprise/api";
|
|
7
11
|
import type { FindUniteLegaleBySirenHandler } from "@proconnect-gouv/proconnect.insee/api";
|
|
8
12
|
import type { FindPouvoirsBySirenHandler } from "@proconnect-gouv/proconnect.registre_national_entreprises/api";
|
|
@@ -16,28 +20,50 @@ import { certificationScore } from "./certification-score.js";
|
|
|
16
20
|
|
|
17
21
|
//
|
|
18
22
|
|
|
19
|
-
type
|
|
23
|
+
type ProcessCertificationDirigeantConfig = {
|
|
20
24
|
ApiEntrepriseInfogreffeRepository: Pick<
|
|
21
25
|
ApiEntrepriseInfogreffeRepository,
|
|
22
26
|
"findMandatairesSociauxBySiren"
|
|
23
27
|
>;
|
|
24
28
|
EQUALITY_THRESHOLD?: number;
|
|
25
|
-
FranceConnectApiRepository: {
|
|
26
|
-
getFranceConnectUserInfo: GetFranceConnectUserInfoHandler;
|
|
27
|
-
};
|
|
28
29
|
InseeApiRepository: { findBySiren: FindUniteLegaleBySirenHandler };
|
|
29
30
|
RegistreNationalEntreprisesApiRepository: {
|
|
30
31
|
findPouvoirsBySiren: FindPouvoirsBySirenHandler;
|
|
31
32
|
};
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
export const CertificationDirigeantDataSource = z.enum([
|
|
36
|
+
"api.insee.fr/api-sirene/private",
|
|
37
|
+
"entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux",
|
|
38
|
+
"registre-national-entreprises.inpi.fr/api",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export type CertificationDirigeantDataSource = z.infer<
|
|
42
|
+
typeof CertificationDirigeantDataSource
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
const CERTIFICATION_DIRIGEANT_DATA_SOURCE_LABELS: {
|
|
46
|
+
[source in CertificationDirigeantDataSource]: string;
|
|
47
|
+
} = {
|
|
48
|
+
"api.insee.fr/api-sirene/private": "Répertoire SIRENE de l'INSEE",
|
|
49
|
+
"entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux":
|
|
50
|
+
"Registre du commerce et des sociétés (RCS)",
|
|
51
|
+
"registre-national-entreprises.inpi.fr/api":
|
|
52
|
+
"Registre National des Entreprises",
|
|
53
|
+
};
|
|
54
|
+
export function getCertificationDirigeantDataSourceLabels(
|
|
55
|
+
dataSource: CertificationDirigeantDataSource,
|
|
56
|
+
) {
|
|
57
|
+
return CERTIFICATION_DIRIGEANT_DATA_SOURCE_LABELS[dataSource];
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
//
|
|
35
61
|
|
|
36
62
|
async function getMandatairesSociaux(
|
|
37
63
|
{
|
|
38
64
|
RegistreNationalEntreprisesApiRepository,
|
|
39
65
|
ApiEntrepriseInfogreffeRepository,
|
|
40
|
-
}:
|
|
66
|
+
}: ProcessCertificationDirigeantConfig,
|
|
41
67
|
siren: string,
|
|
42
68
|
) {
|
|
43
69
|
try {
|
|
@@ -47,9 +73,13 @@ async function getMandatairesSociaux(
|
|
|
47
73
|
|
|
48
74
|
return {
|
|
49
75
|
dirigeants,
|
|
50
|
-
source:
|
|
76
|
+
source:
|
|
77
|
+
CertificationDirigeantDataSource.enum[
|
|
78
|
+
"registre-national-entreprises.inpi.fr/api"
|
|
79
|
+
],
|
|
51
80
|
};
|
|
52
|
-
} catch {
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(error);
|
|
53
83
|
const mandataires =
|
|
54
84
|
await ApiEntrepriseInfogreffeRepository.findMandatairesSociauxBySiren(
|
|
55
85
|
siren,
|
|
@@ -59,43 +89,90 @@ async function getMandatairesSociaux(
|
|
|
59
89
|
return {
|
|
60
90
|
dirigeants,
|
|
61
91
|
source:
|
|
62
|
-
|
|
92
|
+
CertificationDirigeantDataSource.enum[
|
|
63
93
|
"entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux"
|
|
64
94
|
],
|
|
65
95
|
};
|
|
66
96
|
}
|
|
67
97
|
}
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
function match_identity_to_dirigeant(
|
|
100
|
+
identity: IdentityVector,
|
|
101
|
+
dirigeants: IdentityVector[],
|
|
102
|
+
) {
|
|
103
|
+
if (dirigeants.length === 0) return { kind: "no_candidates" as const };
|
|
104
|
+
|
|
105
|
+
const [closest] = dirigeants
|
|
106
|
+
.map((dirigeant) => ({
|
|
107
|
+
dirigeant,
|
|
108
|
+
matches: certificationScore(identity, dirigeant),
|
|
109
|
+
}))
|
|
110
|
+
.toSorted((a, b) => b.matches.size - a.matches.size); // Sort by score descending (higher is better)
|
|
111
|
+
|
|
112
|
+
// According to the specification, only score of 5 (perfect match) is certified
|
|
113
|
+
return match(closest.matches.size)
|
|
114
|
+
.with(5, () => ({
|
|
115
|
+
kind: "exact_match" as const,
|
|
116
|
+
closest,
|
|
117
|
+
}))
|
|
118
|
+
.with(4, () => ({
|
|
119
|
+
kind: "close_match" as const,
|
|
120
|
+
closest,
|
|
121
|
+
}))
|
|
122
|
+
.with(3, () => ({
|
|
123
|
+
kind: "close_match" as const,
|
|
124
|
+
closest,
|
|
125
|
+
}))
|
|
126
|
+
.otherwise(() => ({
|
|
127
|
+
kind: "below_threshold" as const,
|
|
128
|
+
closest,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function processCertificationDirigeantFactory(
|
|
133
|
+
config: ProcessCertificationDirigeantConfig,
|
|
71
134
|
) {
|
|
72
|
-
const { InseeApiRepository
|
|
135
|
+
const { InseeApiRepository } = config;
|
|
73
136
|
|
|
74
|
-
return async function
|
|
137
|
+
return async function processCertificationDirigeant(
|
|
75
138
|
organization: Organization,
|
|
76
|
-
|
|
139
|
+
franceconnect_userinfo: FranceConnectUserInfo,
|
|
77
140
|
) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
141
|
+
if (!isOrganizationCoveredByCertificationDirigeant(organization)) {
|
|
142
|
+
return {
|
|
143
|
+
details: {
|
|
144
|
+
dirigeant: null,
|
|
145
|
+
matches: new Set(),
|
|
146
|
+
identity: NullIdentityVector,
|
|
147
|
+
source: null,
|
|
148
|
+
},
|
|
149
|
+
cause: "organisation_not_covered" as const,
|
|
150
|
+
ok: false,
|
|
151
|
+
};
|
|
83
152
|
}
|
|
84
153
|
|
|
85
|
-
const
|
|
154
|
+
const siren = organization.siret.substring(0, 9);
|
|
155
|
+
const identity = FranceConnect.toIdentityVector(franceconnect_userinfo);
|
|
86
156
|
|
|
87
|
-
const
|
|
157
|
+
const preferredDataSource =
|
|
88
158
|
organization.cached_libelle_categorie_juridique ===
|
|
89
159
|
"Entrepreneur individuel"
|
|
90
|
-
?
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
160
|
+
? CertificationDirigeantDataSource.enum[
|
|
161
|
+
"api.insee.fr/api-sirene/private"
|
|
162
|
+
]
|
|
163
|
+
: CertificationDirigeantDataSource.enum[
|
|
164
|
+
"registre-national-entreprises.inpi.fr/api"
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const { dirigeants, source } = await match(preferredDataSource)
|
|
94
168
|
.with("api.insee.fr/api-sirene/private", async () => ({
|
|
95
169
|
dirigeants: await InseeApiRepository.findBySiren(siren)
|
|
96
170
|
.then(INSEE.toIdentityVector)
|
|
97
171
|
.then((vector) => [vector]),
|
|
98
|
-
source:
|
|
172
|
+
source:
|
|
173
|
+
CertificationDirigeantDataSource.enum[
|
|
174
|
+
"api.insee.fr/api-sirene/private"
|
|
175
|
+
],
|
|
99
176
|
}))
|
|
100
177
|
.with("registre-national-entreprises.inpi.fr/api", () =>
|
|
101
178
|
getMandatairesSociaux(config, siren),
|
|
@@ -104,11 +181,18 @@ export function isOrganizationDirigeantFactory(
|
|
|
104
181
|
|
|
105
182
|
const result = match_identity_to_dirigeant(identity, dirigeants);
|
|
106
183
|
|
|
107
|
-
if (result.kind === "no_candidates")
|
|
108
|
-
|
|
184
|
+
if (result.kind === "no_candidates") {
|
|
185
|
+
return {
|
|
186
|
+
details: { dirigeant: undefined, matches: new Set(), identity, source },
|
|
187
|
+
cause: result.kind,
|
|
188
|
+
ok: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
109
192
|
return {
|
|
110
193
|
details: {
|
|
111
|
-
|
|
194
|
+
dirigeant: result.closest.dirigeant,
|
|
195
|
+
matches: result.closest.matches,
|
|
112
196
|
identity,
|
|
113
197
|
source,
|
|
114
198
|
},
|
|
@@ -117,46 +201,3 @@ export function isOrganizationDirigeantFactory(
|
|
|
117
201
|
};
|
|
118
202
|
};
|
|
119
203
|
}
|
|
120
|
-
|
|
121
|
-
export type IsOrganizationDirigeantHandler = ReturnType<
|
|
122
|
-
typeof isOrganizationDirigeantFactory
|
|
123
|
-
>;
|
|
124
|
-
|
|
125
|
-
const SourceDirigeant = z.enum([
|
|
126
|
-
"api.insee.fr/api-sirene/private",
|
|
127
|
-
"entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux",
|
|
128
|
-
"registre-national-entreprises.inpi.fr/api",
|
|
129
|
-
]);
|
|
130
|
-
|
|
131
|
-
function match_identity_to_dirigeant(
|
|
132
|
-
identity: IdentityVector,
|
|
133
|
-
dirigeants: IdentityVector[],
|
|
134
|
-
) {
|
|
135
|
-
if (dirigeants.length === 0) return { kind: "no_candidates" as const };
|
|
136
|
-
|
|
137
|
-
const [closest] = dirigeants
|
|
138
|
-
.map((dirigeant) => ({
|
|
139
|
-
dirigeant,
|
|
140
|
-
score: certificationScore(identity, dirigeant),
|
|
141
|
-
}))
|
|
142
|
-
.toSorted((a, b) => b.score - a.score); // Sort by score descending (higher is better)
|
|
143
|
-
|
|
144
|
-
// According to the specification, only score of 5 (perfect match) is certified
|
|
145
|
-
return match(closest.score)
|
|
146
|
-
.with(5, () => ({
|
|
147
|
-
kind: "exact_match" as const,
|
|
148
|
-
closest,
|
|
149
|
-
}))
|
|
150
|
-
.with(4, () => ({
|
|
151
|
-
kind: "close_match" as const,
|
|
152
|
-
closest,
|
|
153
|
-
}))
|
|
154
|
-
.with(3, () => ({
|
|
155
|
-
kind: "close_match" as const,
|
|
156
|
-
closest,
|
|
157
|
-
}))
|
|
158
|
-
.otherwise(() => ({
|
|
159
|
-
kind: "below_threshold" as const,
|
|
160
|
-
closest,
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
@@ -14,6 +14,7 @@ exports[`toOrganizationInfo > AppleEuropeInc 1`] = `
|
|
|
14
14
|
"libelleCategorieJuridique": "Société commerciale étrangère immatriculée au RCS",
|
|
15
15
|
"libelleTrancheEffectif": "",
|
|
16
16
|
"nomComplet": "Apple europe inc.",
|
|
17
|
+
"siegeSocial": false,
|
|
17
18
|
"siret": "32122785200019",
|
|
18
19
|
"statutDiffusion": "diffusible",
|
|
19
20
|
"trancheEffectifs": null,
|
|
@@ -37,6 +38,7 @@ exports[`toOrganizationInfo > Commune de clamart - Mairie 1`] = `
|
|
|
37
38
|
"libelleCategorieJuridique": "Commune et commune nouvelle",
|
|
38
39
|
"libelleTrancheEffectif": "1 000 à 1 999 salariés, en 2022",
|
|
39
40
|
"nomComplet": "Commune de clamart",
|
|
41
|
+
"siegeSocial": true,
|
|
40
42
|
"siret": "21920023500014",
|
|
41
43
|
"statutDiffusion": "diffusible",
|
|
42
44
|
"trancheEffectifs": "42",
|
|
@@ -60,6 +62,7 @@ exports[`toOrganizationInfo > Papillon 1`] = `
|
|
|
60
62
|
"libelleCategorieJuridique": "SAS, société par actions simplifiée",
|
|
61
63
|
"libelleTrancheEffectif": "3 à 5 salariés, en 2022",
|
|
62
64
|
"nomComplet": "Papillon",
|
|
65
|
+
"siegeSocial": true,
|
|
63
66
|
"siret": "39234600300198",
|
|
64
67
|
"statutDiffusion": "diffusible",
|
|
65
68
|
"trancheEffectifs": "02",
|
|
@@ -83,6 +86,7 @@ exports[`toOrganizationInfo > RogalDornEntrepreneur 1`] = `
|
|
|
83
86
|
"libelleCategorieJuridique": "Entrepreneur individuel",
|
|
84
87
|
"libelleTrancheEffectif": "",
|
|
85
88
|
"nomComplet": "Nom inconnu",
|
|
89
|
+
"siegeSocial": true,
|
|
86
90
|
"siret": "94957325700019",
|
|
87
91
|
"statutDiffusion": "partiellement_diffusible",
|
|
88
92
|
"trancheEffectifs": null,
|
|
@@ -71,6 +71,7 @@ export function toOrganizationInfo(
|
|
|
71
71
|
siretData.tranche_effectif_salarie.date_reference,
|
|
72
72
|
),
|
|
73
73
|
nomComplet,
|
|
74
|
+
siegeSocial: siretData.siege_social,
|
|
74
75
|
siret: siretData.siret,
|
|
75
76
|
statutDiffusion: siretData.status_diffusion,
|
|
76
77
|
trancheEffectifs,
|
|
@@ -94,6 +95,7 @@ export function toPartialOrganization(organization_info: OrganizationInfo) {
|
|
|
94
95
|
libelleCategorieJuridique: cached_libelle_categorie_juridique,
|
|
95
96
|
libelleTrancheEffectif: cached_libelle_tranche_effectif,
|
|
96
97
|
nomComplet: cached_nom_complet,
|
|
98
|
+
siegeSocial: cached_siege_social,
|
|
97
99
|
siret,
|
|
98
100
|
statutDiffusion: cached_statut_diffusion,
|
|
99
101
|
trancheEffectifs: cached_tranche_effectifs,
|
|
@@ -114,6 +116,7 @@ export function toPartialOrganization(organization_info: OrganizationInfo) {
|
|
|
114
116
|
cached_libelle_tranche_effectif,
|
|
115
117
|
cached_libelle,
|
|
116
118
|
cached_nom_complet,
|
|
119
|
+
cached_siege_social,
|
|
117
120
|
cached_statut_diffusion,
|
|
118
121
|
cached_tranche_effectifs_unite_legale,
|
|
119
122
|
cached_tranche_effectifs,
|
|
@@ -6,10 +6,9 @@ import type {
|
|
|
6
6
|
LinkUserToOrganizationHandler,
|
|
7
7
|
} from "#src/repositories/organization";
|
|
8
8
|
import type { GetByIdHandler as GetUserByIdHandler } from "#src/repositories/user";
|
|
9
|
-
import type
|
|
9
|
+
import { type BaseUserOrganizationLink, LinkTypes } from "#src/types";
|
|
10
10
|
import { getEmailDomain } from "@proconnect-gouv/proconnect.core/services/email";
|
|
11
11
|
import { match } from "ts-pattern";
|
|
12
|
-
import { UserOrganizationLinkVerificationTypeSchema } from "../../types/index.js";
|
|
13
12
|
|
|
14
13
|
//
|
|
15
14
|
|
|
@@ -61,15 +60,13 @@ export function forceJoinOrganizationFactory({
|
|
|
61
60
|
"trackdechets_postal_mail",
|
|
62
61
|
"external",
|
|
63
62
|
"official_contact",
|
|
64
|
-
() =>
|
|
63
|
+
() => LinkTypes.enum.domain,
|
|
65
64
|
)
|
|
66
65
|
.with(
|
|
67
66
|
null,
|
|
68
67
|
"blacklisted",
|
|
69
68
|
"refused",
|
|
70
|
-
() =>
|
|
71
|
-
UserOrganizationLinkVerificationTypeSchema.enum
|
|
72
|
-
.no_validation_means_available,
|
|
69
|
+
() => LinkTypes.enum.no_validation_means_available,
|
|
73
70
|
)
|
|
74
71
|
.exhaustive();
|
|
75
72
|
},
|
|
@@ -14,6 +14,7 @@ exports[`getOrganizationInfo > should return valid payload for diffusible siren
|
|
|
14
14
|
"libelleCategorieJuridique": "Communauté de communes",
|
|
15
15
|
"libelleTrancheEffectif": "100 à 199 salariés, en 2022",
|
|
16
16
|
"nomComplet": "Cc du vexin normand",
|
|
17
|
+
"siegeSocial": true,
|
|
17
18
|
"siret": "20007184300060",
|
|
18
19
|
"statutDiffusion": "diffusible",
|
|
19
20
|
"trancheEffectifs": "22",
|
|
@@ -37,6 +38,7 @@ exports[`getOrganizationInfo > should return valid payload for diffusible siret
|
|
|
37
38
|
"libelleCategorieJuridique": "Communauté de communes",
|
|
38
39
|
"libelleTrancheEffectif": "100 à 199 salariés, en 2022",
|
|
39
40
|
"nomComplet": "Cc du vexin normand",
|
|
41
|
+
"siegeSocial": true,
|
|
40
42
|
"siret": "20007184300060",
|
|
41
43
|
"statutDiffusion": "diffusible",
|
|
42
44
|
"trancheEffectifs": "22",
|
|
@@ -60,6 +62,7 @@ exports[`getOrganizationInfo > should show partial data for partially non diffus
|
|
|
60
62
|
"libelleCategorieJuridique": "Entrepreneur individuel",
|
|
61
63
|
"libelleTrancheEffectif": "",
|
|
62
64
|
"nomComplet": "Nom inconnu",
|
|
65
|
+
"siegeSocial": true,
|
|
63
66
|
"siret": "94957325700019",
|
|
64
67
|
"statutDiffusion": "partiellement_diffusible",
|
|
65
68
|
"trancheEffectifs": null,
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { GetUsersByOrganizationHandler } from "#src/repositories/organization";
|
|
4
4
|
import type { UpdateUserOrganizationLinkHandler } from "#src/repositories/user";
|
|
5
|
+
import { LinkTypes, UnverifiedLinkTypes } from "#src/types";
|
|
5
6
|
import { getEmailDomain } from "@proconnect-gouv/proconnect.core/services/email";
|
|
7
|
+
import { match } from "ts-pattern";
|
|
6
8
|
|
|
7
9
|
//
|
|
8
10
|
|
|
@@ -27,14 +29,12 @@ export function assignUserVerificationTypeToDomainFactory({
|
|
|
27
29
|
const userDomain = getEmailDomain(email);
|
|
28
30
|
if (
|
|
29
31
|
userDomain === domain &&
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"no_verification_means_for_entreprise_unipersonnelle",
|
|
34
|
-
].includes(link_verification_type)
|
|
32
|
+
match(link_verification_type)
|
|
33
|
+
.with(...UnverifiedLinkTypes, () => true)
|
|
34
|
+
.otherwise(() => false)
|
|
35
35
|
) {
|
|
36
36
|
return updateUserOrganizationLink(organization_id, id, {
|
|
37
|
-
verification_type:
|
|
37
|
+
verification_type: LinkTypes.enum.domain,
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -38,6 +38,7 @@ suite("findByIdFactory", () => {
|
|
|
38
38
|
cached_libelle_tranche_effectif: null,
|
|
39
39
|
cached_libelle: "Necron",
|
|
40
40
|
cached_nom_complet: "Necrontyr",
|
|
41
|
+
cached_siege_social: null,
|
|
41
42
|
cached_statut_diffusion: null,
|
|
42
43
|
cached_tranche_effectifs_unite_legale: null,
|
|
43
44
|
cached_tranche_effectifs: null,
|
|
@@ -23,6 +23,7 @@ exports[`findByUserIdFactory > should return multiple organizations when user ha
|
|
|
23
23
|
"cached_libelle_categorie_juridique": null,
|
|
24
24
|
"organization_info_fetched_at": null,
|
|
25
25
|
"cached_code_officiel_geographique": null,
|
|
26
|
+
"cached_siege_social": null,
|
|
26
27
|
"is_external": false,
|
|
27
28
|
"verification_type": "necron",
|
|
28
29
|
"verified_at": null,
|
|
@@ -54,6 +55,7 @@ exports[`findByUserIdFactory > should return multiple organizations when user ha
|
|
|
54
55
|
"cached_libelle_categorie_juridique": null,
|
|
55
56
|
"organization_info_fetched_at": null,
|
|
56
57
|
"cached_code_officiel_geographique": null,
|
|
58
|
+
"cached_siege_social": null,
|
|
57
59
|
"is_external": true,
|
|
58
60
|
"verification_type": "alliance",
|
|
59
61
|
"verified_at": null,
|
|
@@ -90,6 +92,7 @@ exports[`findByUserIdFactory > should return single organization when user has o
|
|
|
90
92
|
"cached_libelle_categorie_juridique": null,
|
|
91
93
|
"organization_info_fetched_at": null,
|
|
92
94
|
"cached_code_officiel_geographique": null,
|
|
95
|
+
"cached_siege_social": null,
|
|
93
96
|
"is_external": false,
|
|
94
97
|
"verification_type": "imperial",
|
|
95
98
|
"verified_at": null,
|
|
@@ -40,6 +40,7 @@ suite("getByIdFactory", () => {
|
|
|
40
40
|
cached_libelle_tranche_effectif: null,
|
|
41
41
|
cached_libelle: "Necron",
|
|
42
42
|
cached_nom_complet: "Necrontyr",
|
|
43
|
+
cached_siege_social: null,
|
|
43
44
|
cached_statut_diffusion: null,
|
|
44
45
|
cached_tranche_effectifs_unite_legale: null,
|
|
45
46
|
cached_tranche_effectifs: null,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LinkTypes } from "#src/types";
|
|
1
2
|
import { emptyDatabase, migrate, pg } from "#testing";
|
|
2
3
|
import { before, beforeEach, mock, suite, test } from "node:test";
|
|
3
4
|
import { linkUserToOrganizationFactory } from "./link-user-to-organization.js";
|
|
@@ -32,7 +33,7 @@ suite("linkUserToOrganizationFactory", () => {
|
|
|
32
33
|
const userOrganizationLink = await linkUserToOrganization({
|
|
33
34
|
organization_id: 1,
|
|
34
35
|
user_id: 1,
|
|
35
|
-
verification_type:
|
|
36
|
+
verification_type: LinkTypes.enum.domain_not_verified_yet,
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
t.assert.snapshot(userOrganizationLink);
|
|
@@ -57,7 +58,7 @@ suite("linkUserToOrganizationFactory", () => {
|
|
|
57
58
|
const userOrganizationLink = await linkUserToOrganization({
|
|
58
59
|
organization_id: 1,
|
|
59
60
|
user_id: 1,
|
|
60
|
-
verification_type:
|
|
61
|
+
verification_type: LinkTypes.enum.organization_dirigeant,
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
t.assert.snapshot(userOrganizationLink);
|
|
@@ -5,12 +5,12 @@ exports[`linkUserToOrganizationFactory > should link user to organization 1`] =
|
|
|
5
5
|
"is_external": false,
|
|
6
6
|
"created_at": "4444-04-04T00:00:00.000Z",
|
|
7
7
|
"updated_at": "4444-04-04T00:00:00.000Z",
|
|
8
|
-
"verification_type":
|
|
8
|
+
"verification_type": "domain_not_verified_yet",
|
|
9
9
|
"has_been_greeted": false,
|
|
10
10
|
"needs_official_contact_email_verification": false,
|
|
11
11
|
"official_contact_email_verification_token": null,
|
|
12
12
|
"official_contact_email_verification_sent_at": null,
|
|
13
|
-
"verified_at":
|
|
13
|
+
"verified_at": "4444-04-04T00:00:00.000Z"
|
|
14
14
|
}
|
|
15
15
|
`;
|
|
16
16
|
|
|
@@ -35,6 +35,7 @@ export function upsertFactory({ pg }: DatabaseContext) {
|
|
|
35
35
|
cached_libelle_activite_principale,
|
|
36
36
|
cached_categorie_juridique,
|
|
37
37
|
cached_libelle_categorie_juridique,
|
|
38
|
+
cached_siege_social,
|
|
38
39
|
} = toPartialOrganization(organizationInfo);
|
|
39
40
|
|
|
40
41
|
const { rows }: QueryResult<Organization> = await pg.query(
|
|
@@ -59,12 +60,13 @@ export function upsertFactory({ pg }: DatabaseContext) {
|
|
|
59
60
|
cached_libelle_activite_principale,
|
|
60
61
|
cached_categorie_juridique,
|
|
61
62
|
cached_libelle_categorie_juridique,
|
|
63
|
+
cached_siege_social,
|
|
62
64
|
organization_info_fetched_at,
|
|
63
65
|
updated_at,
|
|
64
66
|
created_at
|
|
65
67
|
)
|
|
66
68
|
VALUES
|
|
67
|
-
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
|
|
69
|
+
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)
|
|
68
70
|
ON CONFLICT (siret)
|
|
69
71
|
DO UPDATE
|
|
70
72
|
SET (
|
|
@@ -86,6 +88,7 @@ export function upsertFactory({ pg }: DatabaseContext) {
|
|
|
86
88
|
cached_libelle_activite_principale,
|
|
87
89
|
cached_categorie_juridique,
|
|
88
90
|
cached_libelle_categorie_juridique,
|
|
91
|
+
cached_siege_social,
|
|
89
92
|
organization_info_fetched_at,
|
|
90
93
|
updated_at
|
|
91
94
|
) = (
|
|
@@ -107,6 +110,7 @@ export function upsertFactory({ pg }: DatabaseContext) {
|
|
|
107
110
|
EXCLUDED.cached_libelle_activite_principale,
|
|
108
111
|
EXCLUDED.cached_categorie_juridique,
|
|
109
112
|
EXCLUDED.cached_libelle_categorie_juridique,
|
|
113
|
+
EXCLUDED.cached_siege_social,
|
|
110
114
|
EXCLUDED.organization_info_fetched_at,
|
|
111
115
|
EXCLUDED.updated_at
|
|
112
116
|
)
|
|
@@ -131,6 +135,7 @@ export function upsertFactory({ pg }: DatabaseContext) {
|
|
|
131
135
|
cached_libelle_activite_principale,
|
|
132
136
|
cached_categorie_juridique,
|
|
133
137
|
cached_libelle_categorie_juridique,
|
|
138
|
+
cached_siege_social,
|
|
134
139
|
new Date(),
|
|
135
140
|
new Date(),
|
|
136
141
|
new Date(),
|
|
@@ -161,6 +166,7 @@ function toPartialOrganization(organization_info: OrganizationInfo) {
|
|
|
161
166
|
libelleCategorieJuridique: cached_libelle_categorie_juridique,
|
|
162
167
|
libelleTrancheEffectif: cached_libelle_tranche_effectif,
|
|
163
168
|
nomComplet: cached_nom_complet,
|
|
169
|
+
siegeSocial: cached_siege_social,
|
|
164
170
|
siret,
|
|
165
171
|
statutDiffusion: cached_statut_diffusion,
|
|
166
172
|
trancheEffectifs: cached_tranche_effectifs,
|
|
@@ -181,6 +187,7 @@ function toPartialOrganization(organization_info: OrganizationInfo) {
|
|
|
181
187
|
cached_libelle_tranche_effectif,
|
|
182
188
|
cached_libelle,
|
|
183
189
|
cached_nom_complet,
|
|
190
|
+
cached_siege_social,
|
|
184
191
|
cached_statut_diffusion,
|
|
185
192
|
cached_tranche_effectifs_unite_legale,
|
|
186
193
|
cached_tranche_effectifs,
|
|
@@ -40,6 +40,7 @@ suite("upset", () => {
|
|
|
40
40
|
cached_libelle_categorie_juridique: null,
|
|
41
41
|
cached_libelle_tranche_effectif: null,
|
|
42
42
|
cached_nom_complet: "Tau Empire",
|
|
43
|
+
cached_siege_social: null,
|
|
43
44
|
cached_statut_diffusion: null,
|
|
44
45
|
cached_tranche_effectifs: null,
|
|
45
46
|
cached_tranche_effectifs_unite_legale: null,
|
|
@@ -82,6 +83,7 @@ suite("upset", () => {
|
|
|
82
83
|
cached_libelle_categorie_juridique: null,
|
|
83
84
|
cached_libelle_tranche_effectif: null,
|
|
84
85
|
cached_nom_complet: "Necrontyr",
|
|
86
|
+
cached_siege_social: null,
|
|
85
87
|
cached_statut_diffusion: null,
|
|
86
88
|
cached_tranche_effectifs: null,
|
|
87
89
|
cached_tranche_effectifs_unite_legale: null,
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
export * from "./is-domain-allowed-for-organization.js";
|
|
4
4
|
export * from "./is-entreprise-unipersonnelle.js";
|
|
5
|
+
export * from "./is-organization-covered-by-certification-dirigeant.js";
|
|
5
6
|
export * from "./is-public-service.js";
|
|
6
7
|
export * from "./is-syndicat-communal.js";
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
import type { Organization } from "#src/types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* This function returns approximate results. As the data tranche effectifs is
|
|
7
7
|
* two years old. Consequently, an organization that growths quickly within the
|
|
8
8
|
* first two years of his existence can be miss-identified as unipersonnelle by
|
|
9
|
-
* this
|
|
9
|
+
* this function.
|
|
10
10
|
*/
|
|
11
11
|
export function isEntrepriseUnipersonnelle({
|
|
12
12
|
cached_libelle_categorie_juridique,
|
|
13
|
-
|
|
13
|
+
cached_tranche_effectifs_unite_legale,
|
|
14
14
|
}: Pick<
|
|
15
15
|
Organization,
|
|
16
|
-
"cached_libelle_categorie_juridique" | "
|
|
16
|
+
"cached_libelle_categorie_juridique" | "cached_tranche_effectifs_unite_legale"
|
|
17
17
|
>): boolean {
|
|
18
18
|
// check that the organization has the right catégorie juridique
|
|
19
19
|
const cat_jur_ok = [
|
|
@@ -24,7 +24,7 @@ export function isEntrepriseUnipersonnelle({
|
|
|
24
24
|
|
|
25
25
|
// check that the organization has the right tranche effectifs
|
|
26
26
|
const tra_eff_ok = [null, "NN", "00", "01"].includes(
|
|
27
|
-
|
|
27
|
+
cached_tranche_effectifs_unite_legale,
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
return cat_jur_ok && tra_eff_ok;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//
|
|
2
|
+
|
|
3
|
+
import { isOrganizationCoveredByCertificationDirigeant } from "#src/services/organization";
|
|
4
|
+
import type { Organization } from "#src/types";
|
|
5
|
+
import {
|
|
6
|
+
bpifrance_org_info,
|
|
7
|
+
dinum_org_info,
|
|
8
|
+
entreprise_unipersonnelle_org_info,
|
|
9
|
+
} from "#testing/seed/organizations";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { describe, it } from "node:test";
|
|
12
|
+
|
|
13
|
+
describe("isOrganizationCoveredByCertificationDirigeant", () => {
|
|
14
|
+
it("should return false for bad call", () => {
|
|
15
|
+
assert.equal(
|
|
16
|
+
isOrganizationCoveredByCertificationDirigeant({} as Organization),
|
|
17
|
+
false,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should return true for unipersonnelle organization", () => {
|
|
22
|
+
assert.equal(
|
|
23
|
+
isOrganizationCoveredByCertificationDirigeant(
|
|
24
|
+
entreprise_unipersonnelle_org_info,
|
|
25
|
+
),
|
|
26
|
+
true,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should return true for BPI France", () => {
|
|
31
|
+
assert.equal(
|
|
32
|
+
isOrganizationCoveredByCertificationDirigeant(bpifrance_org_info),
|
|
33
|
+
true,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should return false for administration", () => {
|
|
38
|
+
assert.equal(
|
|
39
|
+
isOrganizationCoveredByCertificationDirigeant(dinum_org_info),
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CATEGORIES_JURIDIQUES } from "#src/data/certification";
|
|
2
|
+
import type { Organization } from "#src/types";
|
|
3
|
+
|
|
4
|
+
export const isOrganizationCoveredByCertificationDirigeant = ({
|
|
5
|
+
cached_categorie_juridique,
|
|
6
|
+
}: Organization) =>
|
|
7
|
+
CATEGORIES_JURIDIQUES.map(
|
|
8
|
+
({ categorie_juridique }) => categorie_juridique,
|
|
9
|
+
).includes(cached_categorie_juridique || "");
|