From ef3dd5c0276f0a7763698719c38d4909dcee96fb Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Tue, 11 Aug 2026 15:00:07 +0200 Subject: [PATCH] =?UTF-8?q?Fermer=20les=20listes=20de=20pays=20et=20de=20d?= =?UTF-8?q?=C3=A9partements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nationalité, pays de naissance, département de naissance et pays de l'adresse étaient des champs libres. « France », « FRANCE », « Française », « FR » et « france » désignent la même chose et se retrouvaient toutes en base. Le registre unique du personnel les imprime telles quelles, et un contrôle y lit cinq pays. Le code ISO 3166-1 est désormais stocké, jamais le libellé : il ne change pas d'orthographe, tient dans deux caractères, et c'est lui que reprennent les déclarations sociales. Le libellé n'est plus qu'un affichage, et le registre imprime le gentilé résolu depuis le code. La liste des pays couvre ce qu'un commerce de détail français rencontre — Europe, pourtour méditerranéen, Afrique francophone, principaux pays d'origine relevés par l'INSEE. Elle n'est pas universelle et ne le prétend pas : un pays absent s'ajoute en une ligne, plutôt que de rouvrir la saisie libre pour tout le monde. La France est en tête du sélecteur, le reste par ordre alphabétique ; un classement par fréquence supposée aurait été deviné, et se lirait comme un jugement. Les départements portent leur code officiel, Corse comprise — 2A et 2B, jamais 20, qui n'existe plus depuis 1976 mais survit dans les saisies manuelles. Les trois départements du régime local d'Alsace-Moselle sont identifiés à part : deux jours fériés de plus et un régime de maintien de salaire distinct, ce qui change la planification d'un établissement à quinze kilomètres d'un autre. Un dossier saisi avant la liste porte encore un libellé en clair. Le sélecteur n'y trouve pas d'option, affiche « non renseigné », et la première correction du dossier écrit le code. La valeur n'est ni perdue en silence ni opposée à qui vient corriger un champ voisin. Co-Authored-By: Claude Opus 5 --- .../(app)/equipe/[id]/PersonalInfoPanel.tsx | 40 +++- src/domain/legal/countries.ts | 184 ++++++++++++++++++ src/domain/legal/departments.ts | 147 ++++++++++++++ src/server/employees/profile-actions.ts | 17 +- src/server/employees/rup.ts | 10 +- 5 files changed, 384 insertions(+), 14 deletions(-) create mode 100644 src/domain/legal/countries.ts create mode 100644 src/domain/legal/departments.ts diff --git a/src/app/(app)/equipe/[id]/PersonalInfoPanel.tsx b/src/app/(app)/equipe/[id]/PersonalInfoPanel.tsx index 5d94764..2d86f53 100644 --- a/src/app/(app)/equipe/[id]/PersonalInfoPanel.tsx +++ b/src/app/(app)/equipe/[id]/PersonalInfoPanel.tsx @@ -11,6 +11,13 @@ import { genderLabel, maritalStatusLabel, } from '@/domain/hr/civil-status'; +import { + COUNTRY_OPTIONS, + NATIONALITY_OPTIONS, + countryLabel, + nationalityLabel, +} from '@/domain/legal/countries'; +import { DEPARTMENTS, departmentLabel } from '@/domain/legal/departments'; import { updateProfileAction, updateSensitiveAction, @@ -153,22 +160,31 @@ export function PersonalInfoPanel({ defaultValue={profile.birthName} /> - + @@ -267,10 +288,13 @@ export function PersonalInfoPanel({ - + - - + + - + diff --git a/src/domain/legal/countries.ts b/src/domain/legal/countries.ts new file mode 100644 index 0000000..b3475a2 --- /dev/null +++ b/src/domain/legal/countries.ts @@ -0,0 +1,184 @@ +/** + * Pays et nationalités — pour le dossier salarié et le registre du personnel. + * + * Liste fermée plutôt que champ libre. « France », « FRANCE », « Française », + * « FR » et « france » désignaient la même chose et se retrouvaient toutes en + * base ; le registre unique du personnel les imprime telles quelles, et un + * contrôle y lit cinq pays. + * + * Le **code ISO 3166-1 alpha-2** est stocké, jamais le libellé : il ne change + * pas d'orthographe, tient dans deux caractères, et c'est lui que reprennent + * les déclarations sociales. Le libellé reste un affichage. + * + * La liste couvre les nationalités rencontrées dans un commerce de détail + * français : l'Europe, le pourtour méditerranéen, l'Afrique francophone, et les + * principaux pays d'origine relevés par l'INSEE. Elle n'est pas universelle et + * ne prétend pas l'être — un pays absent s'ajoute ici, en une ligne, plutôt que + * de rouvrir la saisie libre pour tout le monde. + */ + +export interface Country { + /** ISO 3166-1 alpha-2. */ + code: string; + name: string; + /** Gentilé au féminin singulier, forme retenue au registre. */ + nationality: string; +} + +export const COUNTRIES: readonly Country[] = [ + { code: 'FR', name: 'France', nationality: 'Française' }, + { code: 'DE', name: 'Allemagne', nationality: 'Allemande' }, + { code: 'AD', name: 'Andorre', nationality: 'Andorrane' }, + { code: 'AO', name: 'Angola', nationality: 'Angolaise' }, + { code: 'DZ', name: 'Algérie', nationality: 'Algérienne' }, + { code: 'AR', name: 'Argentine', nationality: 'Argentine' }, + { code: 'AM', name: 'Arménie', nationality: 'Arménienne' }, + { code: 'AU', name: 'Australie', nationality: 'Australienne' }, + { code: 'AT', name: 'Autriche', nationality: 'Autrichienne' }, + { code: 'BE', name: 'Belgique', nationality: 'Belge' }, + { code: 'BJ', name: 'Bénin', nationality: 'Béninoise' }, + { code: 'BY', name: 'Biélorussie', nationality: 'Biélorusse' }, + { code: 'BO', name: 'Bolivie', nationality: 'Bolivienne' }, + { code: 'BA', name: 'Bosnie-Herzégovine', nationality: 'Bosnienne' }, + { code: 'BR', name: 'Brésil', nationality: 'Brésilienne' }, + { code: 'BG', name: 'Bulgarie', nationality: 'Bulgare' }, + { code: 'BF', name: 'Burkina Faso', nationality: 'Burkinabè' }, + { code: 'KH', name: 'Cambodge', nationality: 'Cambodgienne' }, + { code: 'CM', name: 'Cameroun', nationality: 'Camerounaise' }, + { code: 'CA', name: 'Canada', nationality: 'Canadienne' }, + { code: 'CV', name: 'Cap-Vert', nationality: 'Cap-verdienne' }, + { code: 'CL', name: 'Chili', nationality: 'Chilienne' }, + { code: 'CN', name: 'Chine', nationality: 'Chinoise' }, + { code: 'CY', name: 'Chypre', nationality: 'Chypriote' }, + { code: 'CO', name: 'Colombie', nationality: 'Colombienne' }, + { code: 'KM', name: 'Comores', nationality: 'Comorienne' }, + { code: 'CG', name: 'Congo', nationality: 'Congolaise' }, + { code: 'CD', name: 'Congo (RDC)', nationality: 'Congolaise (RDC)' }, + { code: 'KR', name: 'Corée du Sud', nationality: 'Sud-coréenne' }, + { code: 'CI', name: "Côte d'Ivoire", nationality: 'Ivoirienne' }, + { code: 'HR', name: 'Croatie', nationality: 'Croate' }, + { code: 'DK', name: 'Danemark', nationality: 'Danoise' }, + { code: 'DJ', name: 'Djibouti', nationality: 'Djiboutienne' }, + { code: 'EG', name: 'Égypte', nationality: 'Égyptienne' }, + { code: 'AE', name: 'Émirats arabes unis', nationality: 'Émirienne' }, + { code: 'EC', name: 'Équateur', nationality: 'Équatorienne' }, + { code: 'ES', name: 'Espagne', nationality: 'Espagnole' }, + { code: 'EE', name: 'Estonie', nationality: 'Estonienne' }, + { code: 'US', name: 'États-Unis', nationality: 'Américaine' }, + { code: 'ET', name: 'Éthiopie', nationality: 'Éthiopienne' }, + { code: 'FI', name: 'Finlande', nationality: 'Finlandaise' }, + { code: 'GA', name: 'Gabon', nationality: 'Gabonaise' }, + { code: 'GE', name: 'Géorgie', nationality: 'Géorgienne' }, + { code: 'GH', name: 'Ghana', nationality: 'Ghanéenne' }, + { code: 'GR', name: 'Grèce', nationality: 'Grecque' }, + { code: 'GN', name: 'Guinée', nationality: 'Guinéenne' }, + { code: 'HT', name: 'Haïti', nationality: 'Haïtienne' }, + { code: 'HU', name: 'Hongrie', nationality: 'Hongroise' }, + { code: 'IN', name: 'Inde', nationality: 'Indienne' }, + { code: 'ID', name: 'Indonésie', nationality: 'Indonésienne' }, + { code: 'IQ', name: 'Irak', nationality: 'Irakienne' }, + { code: 'IR', name: 'Iran', nationality: 'Iranienne' }, + { code: 'IE', name: 'Irlande', nationality: 'Irlandaise' }, + { code: 'IS', name: 'Islande', nationality: 'Islandaise' }, + { code: 'IL', name: 'Israël', nationality: 'Israélienne' }, + { code: 'IT', name: 'Italie', nationality: 'Italienne' }, + { code: 'JP', name: 'Japon', nationality: 'Japonaise' }, + { code: 'JO', name: 'Jordanie', nationality: 'Jordanienne' }, + { code: 'KZ', name: 'Kazakhstan', nationality: 'Kazakhe' }, + { code: 'KE', name: 'Kenya', nationality: 'Kényane' }, + { code: 'XK', name: 'Kosovo', nationality: 'Kosovare' }, + { code: 'LV', name: 'Lettonie', nationality: 'Lettone' }, + { code: 'LB', name: 'Liban', nationality: 'Libanaise' }, + { code: 'LY', name: 'Libye', nationality: 'Libyenne' }, + { code: 'LT', name: 'Lituanie', nationality: 'Lituanienne' }, + { code: 'LU', name: 'Luxembourg', nationality: 'Luxembourgeoise' }, + { code: 'MK', name: 'Macédoine du Nord', nationality: 'Macédonienne' }, + { code: 'MG', name: 'Madagascar', nationality: 'Malgache' }, + { code: 'MY', name: 'Malaisie', nationality: 'Malaisienne' }, + { code: 'ML', name: 'Mali', nationality: 'Malienne' }, + { code: 'MT', name: 'Malte', nationality: 'Maltaise' }, + { code: 'MA', name: 'Maroc', nationality: 'Marocaine' }, + { code: 'MU', name: 'Maurice', nationality: 'Mauricienne' }, + { code: 'MR', name: 'Mauritanie', nationality: 'Mauritanienne' }, + { code: 'MX', name: 'Mexique', nationality: 'Mexicaine' }, + { code: 'MD', name: 'Moldavie', nationality: 'Moldave' }, + { code: 'MC', name: 'Monaco', nationality: 'Monégasque' }, + { code: 'ME', name: 'Monténégro', nationality: 'Monténégrine' }, + { code: 'MZ', name: 'Mozambique', nationality: 'Mozambicaine' }, + { code: 'NE', name: 'Niger', nationality: 'Nigérienne' }, + { code: 'NG', name: 'Nigeria', nationality: 'Nigériane' }, + { code: 'NO', name: 'Norvège', nationality: 'Norvégienne' }, + { code: 'NZ', name: 'Nouvelle-Zélande', nationality: 'Néo-zélandaise' }, + { code: 'PK', name: 'Pakistan', nationality: 'Pakistanaise' }, + { code: 'NL', name: 'Pays-Bas', nationality: 'Néerlandaise' }, + { code: 'PE', name: 'Pérou', nationality: 'Péruvienne' }, + { code: 'PH', name: 'Philippines', nationality: 'Philippine' }, + { code: 'PL', name: 'Pologne', nationality: 'Polonaise' }, + { code: 'PT', name: 'Portugal', nationality: 'Portugaise' }, + { code: 'RO', name: 'Roumanie', nationality: 'Roumaine' }, + { code: 'GB', name: 'Royaume-Uni', nationality: 'Britannique' }, + { code: 'RU', name: 'Russie', nationality: 'Russe' }, + { code: 'RW', name: 'Rwanda', nationality: 'Rwandaise' }, + { code: 'SN', name: 'Sénégal', nationality: 'Sénégalaise' }, + { code: 'RS', name: 'Serbie', nationality: 'Serbe' }, + { code: 'SG', name: 'Singapour', nationality: 'Singapourienne' }, + { code: 'SK', name: 'Slovaquie', nationality: 'Slovaque' }, + { code: 'SI', name: 'Slovénie', nationality: 'Slovène' }, + { code: 'SD', name: 'Soudan', nationality: 'Soudanaise' }, + { code: 'LK', name: 'Sri Lanka', nationality: 'Sri-lankaise' }, + { code: 'SE', name: 'Suède', nationality: 'Suédoise' }, + { code: 'CH', name: 'Suisse', nationality: 'Suisse' }, + { code: 'SY', name: 'Syrie', nationality: 'Syrienne' }, + { code: 'TW', name: 'Taïwan', nationality: 'Taïwanaise' }, + { code: 'TD', name: 'Tchad', nationality: 'Tchadienne' }, + { code: 'CZ', name: 'Tchéquie', nationality: 'Tchèque' }, + { code: 'TH', name: 'Thaïlande', nationality: 'Thaïlandaise' }, + { code: 'TG', name: 'Togo', nationality: 'Togolaise' }, + { code: 'TN', name: 'Tunisie', nationality: 'Tunisienne' }, + { code: 'TR', name: 'Turquie', nationality: 'Turque' }, + { code: 'UA', name: 'Ukraine', nationality: 'Ukrainienne' }, + { code: 'UY', name: 'Uruguay', nationality: 'Uruguayenne' }, + { code: 'VE', name: 'Venezuela', nationality: 'Vénézuélienne' }, + { code: 'VN', name: 'Viêt Nam', nationality: 'Vietnamienne' }, +]; + +const BY_CODE = new Map(COUNTRIES.map((country) => [country.code, country])); + +/** + * Options du sélecteur de pays : la France en tête, le reste par ordre + * alphabétique. + * + * Placer le pays le plus fréquent en premier évite un défilement à chaque + * saisie. L'ordre alphabétique ensuite, parce qu'un classement par fréquence + * supposée serait deviné, et se lirait comme un jugement. + */ +export const COUNTRY_OPTIONS: ReadonlyArray = [ + ['FR', 'France'], + ...COUNTRIES.filter((country) => country.code !== 'FR') + .map((country) => [country.code, country.name] as const) + .sort((a, b) => a[1].localeCompare(b[1], 'fr')), +]; + +/** Mêmes pays, mais nommés par leur gentilé. */ +export const NATIONALITY_OPTIONS: ReadonlyArray = [ + ['FR', 'Française'], + ...COUNTRIES.filter((country) => country.code !== 'FR') + .map((country) => [country.code, country.nationality] as const) + .sort((a, b) => a[1].localeCompare(b[1], 'fr')), +]; + +export function countryLabel(code: string | null): string | null { + if (!code) return null; + // Les dossiers saisis avant la liste portent un libellé en clair : le rendre + // tel quel vaut mieux qu'afficher un vide sur une donnée qui existe. + return BY_CODE.get(code)?.name ?? code; +} + +export function nationalityLabel(code: string | null): string | null { + if (!code) return null; + return BY_CODE.get(code)?.nationality ?? code; +} + +export function isCountryCode(value: string): boolean { + return BY_CODE.has(value); +} diff --git a/src/domain/legal/departments.ts b/src/domain/legal/departments.ts new file mode 100644 index 0000000..a292dbf --- /dev/null +++ b/src/domain/legal/departments.ts @@ -0,0 +1,147 @@ +/** + * Départements français — pour le lieu de naissance et le rattachement d'un + * établissement. + * + * Liste fermée plutôt que champ libre : « 54 », « Meurthe-et-Moselle », + * « Meurthe et Moselle » et « meurthe-et-moselle » désignent le même + * département et se retrouvaient tous les quatre en base. Le registre unique du + * personnel les imprime tels quels, et un contrôle y lit quatre départements. + * + * Le code est stocké, pas le libellé : il tient dans deux caractères, ne change + * pas d'orthographe, et c'est lui que reprennent les déclarations sociales. + * + * La Corse porte `2A` et `2B` — jamais `20`, qui n'existe plus depuis 1976 mais + * survit dans beaucoup de saisies manuelles. + */ +export const DEPARTMENTS: ReadonlyArray = [ + ['01', 'Ain'], + ['02', 'Aisne'], + ['03', 'Allier'], + ['04', 'Alpes-de-Haute-Provence'], + ['05', 'Hautes-Alpes'], + ['06', 'Alpes-Maritimes'], + ['07', 'Ardèche'], + ['08', 'Ardennes'], + ['09', 'Ariège'], + ['10', 'Aube'], + ['11', 'Aude'], + ['12', 'Aveyron'], + ['13', 'Bouches-du-Rhône'], + ['14', 'Calvados'], + ['15', 'Cantal'], + ['16', 'Charente'], + ['17', 'Charente-Maritime'], + ['18', 'Cher'], + ['19', 'Corrèze'], + ['2A', 'Corse-du-Sud'], + ['2B', 'Haute-Corse'], + ['21', "Côte-d'Or"], + ['22', "Côtes-d'Armor"], + ['23', 'Creuse'], + ['24', 'Dordogne'], + ['25', 'Doubs'], + ['26', 'Drôme'], + ['27', 'Eure'], + ['28', 'Eure-et-Loir'], + ['29', 'Finistère'], + ['30', 'Gard'], + ['31', 'Haute-Garonne'], + ['32', 'Gers'], + ['33', 'Gironde'], + ['34', 'Hérault'], + ['35', 'Ille-et-Vilaine'], + ['36', 'Indre'], + ['37', 'Indre-et-Loire'], + ['38', 'Isère'], + ['39', 'Jura'], + ['40', 'Landes'], + ['41', 'Loir-et-Cher'], + ['42', 'Loire'], + ['43', 'Haute-Loire'], + ['44', 'Loire-Atlantique'], + ['45', 'Loiret'], + ['46', 'Lot'], + ['47', 'Lot-et-Garonne'], + ['48', 'Lozère'], + ['49', 'Maine-et-Loire'], + ['50', 'Manche'], + ['51', 'Marne'], + ['52', 'Haute-Marne'], + ['53', 'Mayenne'], + ['54', 'Meurthe-et-Moselle'], + ['55', 'Meuse'], + ['56', 'Morbihan'], + ['57', 'Moselle'], + ['58', 'Nièvre'], + ['59', 'Nord'], + ['60', 'Oise'], + ['61', 'Orne'], + ['62', 'Pas-de-Calais'], + ['63', 'Puy-de-Dôme'], + ['64', 'Pyrénées-Atlantiques'], + ['65', 'Hautes-Pyrénées'], + ['66', 'Pyrénées-Orientales'], + ['67', 'Bas-Rhin'], + ['68', 'Haut-Rhin'], + ['69', 'Rhône'], + ['70', 'Haute-Saône'], + ['71', 'Saône-et-Loire'], + ['72', 'Sarthe'], + ['73', 'Savoie'], + ['74', 'Haute-Savoie'], + ['75', 'Paris'], + ['76', 'Seine-Maritime'], + ['77', 'Seine-et-Marne'], + ['78', 'Yvelines'], + ['79', 'Deux-Sèvres'], + ['80', 'Somme'], + ['81', 'Tarn'], + ['82', 'Tarn-et-Garonne'], + ['83', 'Var'], + ['84', 'Vaucluse'], + ['85', 'Vendée'], + ['86', 'Vienne'], + ['87', 'Haute-Vienne'], + ['88', 'Vosges'], + ['89', 'Yonne'], + ['90', 'Territoire de Belfort'], + ['91', 'Essonne'], + ['92', 'Hauts-de-Seine'], + ['93', 'Seine-Saint-Denis'], + ['94', 'Val-de-Marne'], + ['95', "Val-d'Oise"], + ['971', 'Guadeloupe'], + ['972', 'Martinique'], + ['973', 'Guyane'], + ['974', 'La Réunion'], + ['976', 'Mayotte'], +]; + +const LABELS = new Map(DEPARTMENTS); + +export const DEPARTMENT_CODES: readonly string[] = DEPARTMENTS.map( + ([code]) => code, +); + +export function departmentLabel(code: string | null): string | null { + if (!code) return null; + const label = LABELS.get(code); + return label ? `${code} — ${label}` : code; +} + +export function isDepartmentCode(value: string): boolean { + return LABELS.has(value); +} + +/** + * Départements du régime local d'Alsace-Moselle. + * + * Deux jours fériés de plus — Vendredi saint et 26 décembre — et un régime de + * maintien de salaire distinct. Un établissement de Moselle ne se planifie donc + * pas comme un établissement de Meurthe-et-Moselle, à quinze kilomètres de là. + */ +export const ALSACE_MOSELLE = ['57', '67', '68'] as const; + +export function isAlsaceMoselle(code: string | null): boolean { + return code !== null && (ALSACE_MOSELLE as readonly string[]).includes(code); +} diff --git a/src/server/employees/profile-actions.ts b/src/server/employees/profile-actions.ts index 05632c1..64f72d4 100644 --- a/src/server/employees/profile-actions.ts +++ b/src/server/employees/profile-actions.ts @@ -4,6 +4,8 @@ import { revalidatePath } from 'next/cache'; import { z } from 'zod'; import { AuthorizationError, can } from '@/domain/access/authorize'; +import { COUNTRIES } from '@/domain/legal/countries'; +import { DEPARTMENT_CODES } from '@/domain/legal/departments'; import { recordAudit } from '@/server/audit'; import { mutate } from '@/server/context'; import { encryptOptional } from '@/server/crypto'; @@ -44,6 +46,8 @@ const optionalEnum = (values: T) => ) .transform((value) => (value === '' ? null : value)); +const COUNTRY_CODES = COUNTRIES.map((country) => country.code); + const profileInput = z.object({ membershipId: z.string().min(1), gender: optionalEnum(['FEMALE', 'MALE', 'UNSPECIFIED'] as const), @@ -52,9 +56,14 @@ const profileInput = z.object({ lastName: z.string().trim().min(1, 'Nom requis').max(80), birthDate: optionalText(10), birthPlace: optionalText(120), - birthCountry: optionalText(80), - birthDepartment: optionalText(80), - nationality: optionalText(80), + // Codes de liste fermée, pas des libellés. Un dossier saisi avant la liste + // porte encore « France » en clair : le sélecteur ne trouve pas d'option + // correspondante, affiche « non renseigné », et la première correction du + // dossier remplace la valeur par son code. Elle n'est ni perdue en silence + // ni opposée à qui vient corriger un champ voisin. + birthCountry: optionalEnum(COUNTRY_CODES), + birthDepartment: optionalEnum(DEPARTMENT_CODES), + nationality: optionalEnum(COUNTRY_CODES), maritalStatus: optionalEnum([ 'SINGLE', 'MARRIED', @@ -87,7 +96,7 @@ const profileInput = z.object({ addressLine2: optionalText(180), postalCode: optionalText(12), city: optionalText(120), - country: optionalText(80), + country: optionalEnum(COUNTRY_CODES), emergencyContactName: optionalText(120), emergencyContactPhone: optionalText(30), }); diff --git a/src/server/employees/rup.ts b/src/server/employees/rup.ts index 3e7b62d..71f401a 100644 --- a/src/server/employees/rup.ts +++ b/src/server/employees/rup.ts @@ -2,6 +2,8 @@ import 'server-only'; import { cache } from 'react'; +import { nationalityLabel } from '@/domain/legal/countries'; + import { genderShort } from '@/domain/hr/civil-status'; import type { RupPerson } from '@/domain/legal/rup'; import { peopleWithGaps } from '@/domain/legal/rup'; @@ -82,7 +84,9 @@ export const getRegisterData = cache(async function getRegisterData( '', firstName: contract.membership.profile?.firstName ?? '', sex: genderShort(contract.membership.profile?.gender ?? null), - nationality: contract.membership.profile?.nationality ?? null, + nationality: nationalityLabel( + contract.membership.profile?.nationality ?? null, + ), birthDate: contract.membership.profile?.birthDate ?? null, jobTitle: contract.jobTitleId ? (jobTitleNames.get(contract.jobTitleId) ?? null) @@ -152,7 +156,9 @@ export const listRegisterLocations = cache( '', firstName: contract.membership.profile?.firstName ?? '', sex: genderShort(contract.membership.profile?.gender ?? null), - nationality: contract.membership.profile?.nationality ?? null, + nationality: nationalityLabel( + contract.membership.profile?.nationality ?? null, + ), birthDate: contract.membership.profile?.birthDate ?? null, jobTitle: contract.jobTitleId ? 'x' : null, qualification: contract.classification,