/** * Seed R0 — idempotent (upserts) : 7 rôles, matrice COMPLÈTE (une ligne par * rôle × catégorie, invariant R0) et 7 comptes de démonstration (ADR-002). * La matrice ci-dessous est le point de DÉPART pédagogique : elle vit en base * et sera administrable (R1) — la modifier ici ne change pas une base déjà * seedée (les lignes existantes ne sont pas écrasées, voir plus bas). * Usage : pnpm seed (ou prisma db seed) */ import 'dotenv/config'; import { PrismaClient } from '@prisma/client'; import { OBJECT_CATEGORIES, ROLE_NAMES, type ObjectCategory, type RoleName, } from '@siop/shared'; import * as argon2 from 'argon2'; type Grant = Partial<{ view: boolean; viewOther: boolean; create: boolean; edit: boolean; delete: boolean; }>; const FULL: Grant = { view: true, viewOther: true, create: true, edit: true, delete: true }; const READ: Grant = { view: true, viewOther: true }; /** Matrice de départ (rôle → catégorie → droits ; absent = tout à false). */ const MATRIX: Record>> = { Administrateur: Object.fromEntries( OBJECT_CATEGORIES.map((c) => [c, FULL]), ) as Record, Gestionnaire: { WORK_ORDERS: FULL, REQUESTS: FULL, ASSETS: FULL, LOCATIONS: FULL, METERS: FULL, PARTS: FULL, PURCHASE_ORDERS: FULL, PEOPLE_TEAMS: { view: true, viewOther: true, create: true, edit: true }, ANALYTICS: READ, SETTINGS: { view: true }, }, Dispatcher: { WORK_ORDERS: { view: true, viewOther: true, create: true, edit: true }, REQUESTS: { view: true, viewOther: true, create: true, edit: true }, ASSETS: READ, LOCATIONS: READ, METERS: READ, PARTS: { view: true }, PEOPLE_TEAMS: READ, ANALYTICS: READ, }, Technicien: { WORK_ORDERS: { view: true, edit: true }, // ses OT uniquement (viewOther=false) REQUESTS: { view: true }, ASSETS: READ, LOCATIONS: READ, METERS: { view: true, viewOther: true, create: true }, // relevés PARTS: { view: true }, }, 'Technicien limité': { WORK_ORDERS: { view: true, edit: true }, // ses OT, sans consultation du parc ASSETS: { view: true }, }, Demandeur: { REQUESTS: { view: true, create: true }, // ses demandes uniquement }, 'Vue seule': { WORK_ORDERS: READ, REQUESTS: READ, ASSETS: READ, LOCATIONS: READ, METERS: READ, PARTS: READ, PURCHASE_ORDERS: READ, PEOPLE_TEAMS: READ, ANALYTICS: READ, }, }; /** 7 comptes démo — un par rôle (personas des maquettes 02-design). */ const DEMO_USERS: { email: string; displayName: string; role: RoleName }[] = [ { email: 'admin@demo.siop.ma', displayName: 'Amina Benali', role: 'Administrateur' }, { email: 'dispatcher@demo.siop.ma', displayName: 'Salma Radi', role: 'Dispatcher' }, { email: 'technicien@demo.siop.ma', displayName: 'Ahmed Meskini', role: 'Technicien' }, { email: 'technicien-limite@demo.siop.ma', displayName: 'Yassine Bouzid', role: 'Technicien limité' }, { email: 'gestionnaire@demo.siop.ma', displayName: 'Nadia Cherkaoui', role: 'Gestionnaire' }, { email: 'demandeur@demo.siop.ma', displayName: 'Karim El Fassi', role: 'Demandeur' }, { email: 'vue-seule@demo.siop.ma', displayName: 'Omar Senhaji', role: 'Vue seule' }, ]; export async function seed(prisma: PrismaClient): Promise { const roleIds = new Map(); for (const name of ROLE_NAMES) { const role = await prisma.role.upsert({ where: { name }, update: {}, create: { name }, }); roleIds.set(name, role.id); } // Matrice complète : une ligne par rôle × catégorie. Les lignes existantes // ne sont PAS écrasées (la base est la source de vérité, pas ce fichier). for (const name of ROLE_NAMES) { const roleId = roleIds.get(name)!; for (const category of OBJECT_CATEGORIES) { const g = MATRIX[name][category] ?? {}; await prisma.permission.upsert({ where: { roleId_objectCategory: { roleId, objectCategory: category } }, update: {}, create: { roleId, objectCategory: category, canView: g.view ?? false, canViewOther: g.viewOther ?? false, canCreate: g.create ?? false, canEdit: g.edit ?? false, canDelete: g.delete ?? false, }, }); } } // Mot de passe commun des comptes démo (la connexion classique reste testable). const passwordHash = await argon2.hash( process.env.SEED_DEMO_PASSWORD ?? 'Demo!2026', ); for (const u of DEMO_USERS) { await prisma.user.upsert({ where: { email: u.email }, update: {}, create: { email: u.email, displayName: u.displayName, passwordHash, roleId: roleIds.get(u.role)!, isDemo: true, }, }); } } /* c8 ignore start — wrapper CLI */ if (require.main === module) { const prisma = new PrismaClient(); seed(prisma) .then(async () => { const [roles, permissions, users] = await Promise.all([ prisma.role.count(), prisma.permission.count(), prisma.user.count({ where: { isDemo: true } }), ]); console.log( `Seed OK — ${roles} rôles, ${permissions} lignes de matrice, ${users} comptes démo.`, ); }) .catch((e) => { console.error(e); process.exitCode = 1; }) .finally(() => prisma.$disconnect()); } /* c8 ignore stop */