mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r6): R6.6 — Demandeur restreint à son site
Trouvé en recette : GET /assets/options n'avait aucun filtre — tout Demandeur voyait le parc complet dans le sélecteur d'équipement de "Nouvelle demande", sur le web ET le mobile (même endpoint partagé). Risque réel : signaler accidentellement une panne sur un ascenseur qu'on ne gère pas. Corrigé à la racine, sur les deux plateformes à la fois : - Relation many-to-many User↔Location (assignedSites/assignedUsers, migration r6_demandeur_sites, même style que Team.members) — vide = aucune restriction, comportement historique inchangé pour tous les rôles sauf un Demandeur affecté à un site. - AssetsService.allowedLocationIds(user) : sites + zones filles autorisés, ou null si aucune restriction — réutilisée par options() ET par RequestsService.create (défense en profondeur : un assetId soumis directement hors périmètre est rejeté, 400). - UsersService : assertTopLevelSites (un Demandeur est affecté à un site, jamais une zone) ; invite()/update() branchent locationIds (remplace l'affectation, comme teamIds). - Web (personnes.tsx) : ModaleInvitation affiche les sites à cocher pour un rôle Demandeur ; colonne "Sites" éditable via une modale dédiée. - Mobile (formulaire-demande.tsx) : bouton "Scanner l'étiquette" en raccourci — résout uniquement contre les options déjà chargées (déjà filtrées), jamais de repli sur le parc complet qui annulerait la restriction. Aucun changement à useAssetOptions() : le filtrage serveur profite automatiquement au formulaire mobile. - Seed : Karim Doukkali (démo) rattaché à Tour Atlas. Bug trouvé en vérification avant tout commit : create() comparait allowed.includes(dto.assetId), mais allowed est une liste d'ids de sites/zones, pas d'ids d'appareils — aurait rejeté à tort tout signalement d'un Demandeur affecté, y compris dans son propre périmètre. Corrigé (comparaison sur asset.locationId) ; méthode renommée allowedAssetIds → allowedLocationIds pour que le nom dise ce qu'elle retourne. exploitation.e2e-spec.ts mis à jour (A1/C1 → A2/B1, dans le site de Karim — sinon rejetés par la nouvelle règle, comportement voulu). 79/80 tests API verts, le seul échec (documents-analytics, monthCost) est le flake calendaire déjà identifié cette session, sans rapport. Typecheck/tests/lint verts sur les 4 paquets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ export const UserAdminSchema = z.object({
|
||||
isDemo: z.boolean(),
|
||||
/** Taux horaire COURANT (MAD/h) — chaque saisie de main-d'œuvre fige le sien. */
|
||||
hourlyRate: z.number().nullable(),
|
||||
/** R6.6 — sites autorisés en signalement (Demandeur) ; vide = aucune restriction. */
|
||||
assignedSites: z.array(z.object({ id: z.uuid(), name: z.string() })),
|
||||
});
|
||||
export type UserAdmin = z.infer<typeof UserAdminSchema>;
|
||||
|
||||
@@ -34,6 +36,7 @@ export const UserUpdateSchema = z.object({
|
||||
teamIds: z.array(z.uuid()).optional(), // remplace l'affectation
|
||||
isActive: z.boolean().optional(),
|
||||
hourlyRate: z.number().positive().nullable().optional(), // R3 — taux courant
|
||||
locationIds: z.array(z.uuid()).optional(), // R6.6 — remplace l'affectation (sites racine)
|
||||
});
|
||||
export type UserUpdate = z.infer<typeof UserUpdateSchema>;
|
||||
|
||||
@@ -45,6 +48,7 @@ export const InvitationCreateSchema = z.object({
|
||||
roleId: z.uuid(),
|
||||
teamIds: z.array(z.uuid()).optional(),
|
||||
phone: z.string().max(40).optional(),
|
||||
locationIds: z.array(z.uuid()).optional(), // R6.6 — sites racine (Demandeur)
|
||||
});
|
||||
export type InvitationCreate = z.infer<typeof InvitationCreateSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user