@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -2
  3. package/dist/data/certification/categories-juridiques-sources.d.ts +8 -0
  4. package/dist/data/certification/categories-juridiques-sources.d.ts.map +1 -0
  5. package/dist/data/certification/categories-juridiques-sources.js +743 -0
  6. package/dist/data/certification/index.d.ts +1 -0
  7. package/dist/data/certification/index.d.ts.map +1 -1
  8. package/dist/data/certification/index.js +1 -0
  9. package/dist/errors/index.d.ts +0 -9
  10. package/dist/errors/index.d.ts.map +1 -1
  11. package/dist/errors/index.js +0 -18
  12. package/dist/managers/certification/certification-score.d.ts +11 -1
  13. package/dist/managers/certification/certification-score.d.ts.map +1 -1
  14. package/dist/managers/certification/certification-score.js +17 -8
  15. package/dist/managers/certification/index.d.ts +1 -1
  16. package/dist/managers/certification/index.d.ts.map +1 -1
  17. package/dist/managers/certification/index.js +1 -1
  18. package/dist/managers/certification/process-certification-dirigeant.d.ts +80 -0
  19. package/dist/managers/certification/process-certification-dirigeant.d.ts.map +1 -0
  20. package/dist/managers/certification/process-certification-dirigeant.js +121 -0
  21. package/dist/managers/organization/adapters/api_entreprise.d.ts +2 -1
  22. package/dist/managers/organization/adapters/api_entreprise.d.ts.map +1 -1
  23. package/dist/managers/organization/adapters/api_entreprise.js +3 -1
  24. package/dist/managers/organization/force-join-organization.d.ts +1 -1
  25. package/dist/managers/organization/force-join-organization.d.ts.map +1 -1
  26. package/dist/managers/organization/force-join-organization.js +3 -4
  27. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts.map +1 -1
  28. package/dist/managers/user/assign-user-verification-type-to-domain.js +6 -6
  29. package/dist/repositories/organization/find-by-user-id.d.ts +1 -1
  30. package/dist/repositories/organization/get-users-by-organization.d.ts +1 -1
  31. package/dist/repositories/organization/link-user-to-organization.d.ts +1 -1
  32. package/dist/repositories/organization/upsert.d.ts.map +1 -1
  33. package/dist/repositories/organization/upsert.js +8 -3
  34. package/dist/repositories/user/update-user-organization-link.d.ts +1 -1
  35. package/dist/services/organization/index.d.ts +1 -0
  36. package/dist/services/organization/index.d.ts.map +1 -1
  37. package/dist/services/organization/index.js +1 -0
  38. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts +3 -3
  39. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts.map +1 -1
  40. package/dist/services/organization/is-entreprise-unipersonnelle.js +4 -4
  41. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts +3 -0
  42. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts.map +1 -0
  43. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.js +2 -0
  44. package/dist/services/organization/is-public-service.d.ts.map +1 -1
  45. package/dist/services/organization/is-public-service.js +16 -6
  46. package/dist/services/organization/is-syndicat-communal.d.ts.map +1 -1
  47. package/dist/services/organization/is-syndicat-communal.js +1 -0
  48. package/dist/types/franceconnect.d.ts.map +1 -1
  49. package/dist/types/franceconnect.js +2 -2
  50. package/dist/types/{dirigeant.d.ts → identity-vector.d.ts} +2 -1
  51. package/dist/types/identity-vector.d.ts.map +1 -0
  52. package/dist/types/{dirigeant.js → identity-vector.js} +8 -0
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/types/index.js +1 -1
  56. package/dist/types/organization-info.d.ts +2 -1
  57. package/dist/types/organization-info.d.ts.map +1 -1
  58. package/dist/types/organization-info.js +1 -0
  59. package/dist/types/organization.d.ts +1 -0
  60. package/dist/types/organization.d.ts.map +1 -1
  61. package/dist/types/user-organization-link.d.ts +31 -24
  62. package/dist/types/user-organization-link.d.ts.map +1 -1
  63. package/dist/types/user-organization-link.js +15 -8
  64. package/package.json +2 -2
  65. package/src/data/certification/categories-juridiques-sources.ts +811 -0
  66. package/src/data/certification/index.ts +1 -0
  67. package/src/errors/index.ts +0 -21
  68. package/src/managers/certification/certification-score.test.ts +69 -33
  69. package/src/managers/certification/certification-score.ts +20 -9
  70. package/src/managers/certification/index.ts +1 -1
  71. package/src/managers/certification/{is-organization-dirigeant.test.ts → process-certification-dirigeant.test.ts} +62 -64
  72. package/src/managers/certification/{is-organization-dirigeant.ts → process-certification-dirigeant.ts} +115 -74
  73. package/src/managers/organization/adapters/api_entreprise.test.ts.snapshot +4 -0
  74. package/src/managers/organization/adapters/api_entreprise.ts +3 -0
  75. package/src/managers/organization/force-join-organization.ts +3 -6
  76. package/src/managers/organization/get-organization-info.test.ts.snapshot +3 -0
  77. package/src/managers/organization/mark-domain-as-verified.test.ts +1 -1
  78. package/src/managers/user/assign-user-verification-type-to-domain.ts +6 -6
  79. package/src/repositories/organization/find-by-id.test.ts +1 -0
  80. package/src/repositories/organization/find-by-user-id.test.ts.snapshot +3 -0
  81. package/src/repositories/organization/get-by-id.test.ts +1 -0
  82. package/src/repositories/organization/link-user-to-organization.test.ts +3 -2
  83. package/src/repositories/organization/link-user-to-organization.test.ts.snapshot +2 -2
  84. package/src/repositories/organization/upsert.ts +8 -1
  85. package/src/repositories/organization/upset.test.ts +2 -0
  86. package/src/services/organization/index.ts +1 -0
  87. package/src/services/organization/is-entreprise-unipersonnelle.ts +5 -5
  88. package/src/services/organization/is-organization-covered-by-certification-dirigeant.test.ts +43 -0
  89. package/src/services/organization/is-organization-covered-by-certification-dirigeant.ts +9 -0
  90. package/src/services/organization/is-public-service.test.ts +1 -1
  91. package/src/services/organization/is-public-service.ts +20 -7
  92. package/src/services/organization/is-syndicat-communal.ts +1 -0
  93. package/src/types/franceconnect.ts +6 -7
  94. package/src/types/{dirigeant.ts → identity-vector.ts} +9 -0
  95. package/src/types/index.ts +1 -1
  96. package/src/types/organization-info.ts +1 -0
  97. package/src/types/organization.ts +1 -0
  98. package/src/types/user-organization-link.ts +17 -11
  99. package/tsconfig.lib.tsbuildinfo +1 -1
  100. package/dist/managers/certification/is-organization-dirigeant.d.ts +0 -45
  101. package/dist/managers/certification/is-organization-dirigeant.d.ts.map +0 -1
  102. package/dist/managers/certification/is-organization-dirigeant.js +0 -97
  103. package/dist/types/dirigeant.d.ts.map +0 -1
@@ -1,8 +1,12 @@
1
1
  //
2
2
 
3
- import { InvalidCertificationError, NotFoundError } from "#src/errors";
4
- import type { GetFranceConnectUserInfoHandler } from "#src/repositories/user";
5
- import type { IdentityVector, Organization } from "#src/types";
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 IsOrganizationExecutiveFactoryFactoryConfig = {
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
- }: IsOrganizationExecutiveFactoryFactoryConfig,
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: SourceDirigeant.enum["registre-national-entreprises.inpi.fr/api"],
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
- SourceDirigeant.enum[
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
- export function isOrganizationDirigeantFactory(
70
- config: IsOrganizationExecutiveFactoryFactoryConfig,
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, FranceConnectApiRepository } = config;
135
+ const { InseeApiRepository } = config;
73
136
 
74
- return async function isOrganizationDirigeant(
137
+ return async function processCertificationDirigeant(
75
138
  organization: Organization,
76
- user_id: number,
139
+ franceconnect_userinfo: FranceConnectUserInfo,
77
140
  ) {
78
- const siren = organization.siret.substring(0, 9);
79
- const franceconnectUserInfo =
80
- await FranceConnectApiRepository.getFranceConnectUserInfo(user_id);
81
- if (!franceconnectUserInfo) {
82
- throw new NotFoundError("FranceConnect UserInfo not found");
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 identity = FranceConnect.toIdentityVector(franceconnectUserInfo);
154
+ const siren = organization.siret.substring(0, 9);
155
+ const identity = FranceConnect.toIdentityVector(franceconnect_userinfo);
86
156
 
87
- const prefered_source =
157
+ const preferredDataSource =
88
158
  organization.cached_libelle_categorie_juridique ===
89
159
  "Entrepreneur individuel"
90
- ? SourceDirigeant.enum["api.insee.fr/api-sirene/private"]
91
- : SourceDirigeant.enum["registre-national-entreprises.inpi.fr/api"];
92
-
93
- const { dirigeants, source } = await match(prefered_source)
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: SourceDirigeant.enum["api.insee.fr/api-sirene/private"],
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
- throw new InvalidCertificationError("No candidates found");
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
- ...result.closest,
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 { BaseUserOrganizationLink } from "#src/types";
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
- () => UserOrganizationLinkVerificationTypeSchema.enum.domain,
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,
@@ -40,7 +40,7 @@ describe("markDomainAsVerified", () => {
40
40
  {
41
41
  id: 42,
42
42
  email: "lion.eljonson@darkangels.world",
43
- verification_type: null,
43
+ verification_type: "domain_not_verified_yet",
44
44
  } as User & BaseUserOrganizationLink,
45
45
  ]),
46
46
  ),
@@ -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
- null,
32
- "no_verification_means_available",
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: "domain",
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: null,
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: "organization_dirigeant",
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": null,
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": null
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
- * These fonctions return approximate results. As the data tranche effectifs is
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 fonction.
9
+ * this function.
10
10
  */
11
11
  export function isEntrepriseUnipersonnelle({
12
12
  cached_libelle_categorie_juridique,
13
- cached_tranche_effectifs,
13
+ cached_tranche_effectifs_unite_legale,
14
14
  }: Pick<
15
15
  Organization,
16
- "cached_libelle_categorie_juridique" | "cached_tranche_effectifs"
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
- cached_tranche_effectifs,
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 || "");