Créer un salarié demandait cinq champs. Le contrat, la déclaration et le registre en réclament une vingtaine, et rien ne permettait de les saisir : le sexe, le nom de naissance, le pays et le département de naissance, la situation de famille, les personnes à charge, le téléphone fixe, le complément d'adresse et l'heure d'embauche n'existaient pas en base. La migration les ajoute, toutes facultatives : un dossier incomplet doit pouvoir exister — c'est au registre de signaler ce qui lui manque, pas à la base de refuser l'embauche. Seul l'envoi des plannings par SMS fait exception : c'est un consentement, donc faux par défaut, la charge de la preuve pesant sur l'employeur. Le pays et le département de naissance sont des colonnes à part et non une commune saisie librement : la déclaration sociale les demande séparément, et les rétro-extraire échouerait au premier « Bar-le-Duc (Meuse) ». Le formulaire devient un panneau latéral : une trentaine de champs posés au centre masquaient l'annuaire, et on embauche en regardant qui est déjà là. Le matricule y est proposé à la suite du dernier, attribué dans la transaction pour que deux embauches simultanées ne tombent pas sur le même rang. Le nom de naissance vaut le nom de famille quand il n'en diffère pas, plutôt que d'imposer une ressaisie à l'immense majorité des dossiers. Le responsable hiérarchique et la politique RTT se posent enfin à l'embauche : les deux modèles existaient sans qu'aucun écran ne les alimente. Le registre unique du personnel gagne sa colonne « sexe » et se tient désormais au nom de naissance — le nom d'usage peut changer sans que la personne change. La migration s'applique au redéploiement : l'entrypoint du conteneur passe `prisma migrate deploy` avant de démarrer le serveur. Elle n'ajoute que des colonnes nullables et deux types énumérés, sans réécriture de table. Écrite et validée contre le schéma, non exécutée ici : aucune base n'était ouverte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
4.2 KiB
TypeScript
90 lines
4.2 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
||
|
||
/**
|
||
* Registre de paramétrage et création d'un salarié sans accès applicatif.
|
||
*
|
||
* Ces parcours n'éprouvent pas l'authentification : ils réutilisent la session
|
||
* commune plutôt que d'ouvrir la leur. La direction porte un second facteur, et
|
||
* un code TOTP ne sert qu'une fois — deux connexions parallèles sur ce compte
|
||
* échoueraient, à raison.
|
||
*/
|
||
|
||
test('la direction lit et alimente le registre de paramétrage', async ({
|
||
page,
|
||
}) => {
|
||
await page.goto('/reglages/registre');
|
||
|
||
await expect(
|
||
page.getByRole('heading', { name: 'Registre de paramétrage juridique' }),
|
||
).toBeVisible();
|
||
|
||
const parameter = `Durée quotidienne maximale ${Date.now()}`;
|
||
// Ciblage par attribut `name` : les libellés portent un texte d'aide, et
|
||
// celui de « Source » contient lui-même le mot « valeur », ce qui rend la
|
||
// correspondance par libellé ambiguë.
|
||
const form = page.locator('form').filter({ hasText: 'Consigner' });
|
||
await form.locator('input[name="key"]').fill(parameter);
|
||
await form.locator('input[name="value"]').fill('10 h');
|
||
await form
|
||
.locator('input[name="source"]')
|
||
.fill('IDCC 1517 — texte consolidé Legifrance');
|
||
await form.locator('input[name="population"]').fill('Tous les salariés');
|
||
await page.getByRole('button', { name: 'Consigner' }).click();
|
||
|
||
const row = page.getByRole('row', { name: new RegExp(parameter) });
|
||
await expect(row).toBeVisible();
|
||
|
||
// Consigné n'est pas approuvé : la matrice exige un approbateur nommé.
|
||
await row.getByRole('button', { name: 'Approuver' }).click();
|
||
await expect(row.getByText(/Approuvé le/)).toBeVisible();
|
||
});
|
||
|
||
test('un salarié sans compte applicatif est créable', async ({ page }) => {
|
||
await page.goto('/equipe');
|
||
|
||
await expect(page.getByRole('heading', { name: 'Équipe' })).toBeVisible();
|
||
// L'effectif vient de la base, pas du module de démonstration : un annuaire
|
||
// fabriqué à l'écran ne se filtrerait pas par matricule.
|
||
await page
|
||
.getByPlaceholder('Rechercher par prénom, nom ou matricule.')
|
||
.fill('E0001');
|
||
await expect(page.getByText(/^1 sur \d+ salariés$/)).toBeVisible();
|
||
await page.goto('/equipe');
|
||
|
||
// Nom unique par exécution : la base de test n'est pas remise à zéro entre
|
||
// deux passages, et un nom fixe finirait par désigner plusieurs salariés.
|
||
const matricule = `E9${Date.now() % 100000}`;
|
||
const nom = `Sanscompte${matricule}`;
|
||
// Le formulaire d’embauche n’est plus posé au bas de la liste : il s’ouvre
|
||
// en modale, à la demande.
|
||
await page.getByRole('button', { name: 'Ajouter un collaborateur' }).click();
|
||
const form = page.locator('form').filter({ hasText: 'Informations salarié' });
|
||
await form.locator('input[name="firstName"]').fill('Sans');
|
||
await form.locator('input[name="lastName"]').fill(nom);
|
||
// Le matricule est proposé par l'application : ce test en impose un, parce
|
||
// qu'il vérifie ensuite qu'on retrouve ce salarié par ce numéro.
|
||
await form.getByRole('button', { name: 'Saisir un matricule' }).click();
|
||
await form.locator('input[name="employeeNumber"]').fill(matricule);
|
||
// Le formulaire propose d’ouvrir un contrat d’emblée : ce parcours n’en veut
|
||
// pas, et un salarié sans contrat doit rester créable.
|
||
await form.getByLabel('Ouvrir un contrat maintenant').uncheck();
|
||
await page.getByRole('button', { name: 'Enregistrer' }).click();
|
||
await expect(page.getByText('Salarié ajouté.')).toBeVisible();
|
||
|
||
// Rechargement explicite : ce qui est vérifié ici est la persistance et la
|
||
// présence dans l'annuaire, pas le moment exact où la revalidation atteint
|
||
// le rendu courant.
|
||
await page.reload();
|
||
|
||
// Un salarié sans adresse doit exister : la plupart des équipes de vente ne
|
||
// se connectent jamais à l'outil.
|
||
// Sans compte applicatif, l'annuaire affiche le matricule sous le nom : c'est
|
||
// la seule adresse à laquelle on désigne ce salarié.
|
||
await expect(page.getByText(`Matricule ${matricule}`)).toBeVisible();
|
||
// Le nom vit sur le dossier, pas sur le compte : un salarié sans accès
|
||
// applicatif doit tout de même figurer nommément au registre du personnel.
|
||
await expect(
|
||
page.getByRole('link', { name: new RegExp(`Sans ${nom}`) }),
|
||
).toBeVisible();
|
||
});
|