import type { PrismaClient } from "@prisma/client"; import { hashPassword } from "@/server/password"; import { PERMISSIONS, ROLE_DEFS } from "@/server/rbac"; import { MODULES } from "@/lib/modules"; export interface ProvisionOpts { name: string; slug: string; short?: string; sector?: string; admin: { email: string; name: string; password: string }; actorId?: string | null; } /** * Legt einen Mandanten idempotent an bzw. aktualisiert ihn: globaler Permission-Katalog, * Standard-Rollen (ROLE_DEFS) inkl. Rechte, erster Mandantenadministrator (Identity + * Mitgliedschaft), Mandanten-Einstellungen, alle Module aktiv, Audit-Eintrag. * * Andockpunkt Fachmodule: mandantenspezifische Stammdaten-Defaults (z. B. Auftragsarten, * Checklisten-Vorlagen) werden künftig hier nach Schritt 6 angelegt. */ export async function provisionTenant(prisma: PrismaClient, opts: ProvisionOpts) { // 1. Globaler Permission-Katalog for (const key of PERMISSIONS) { await prisma.permission.upsert({ where: { key }, update: {}, create: { key } }); } // 2. Mandant const tenant = await prisma.tenant.upsert({ where: { slug: opts.slug }, update: { name: opts.name, short: opts.short ?? null, sector: opts.sector ?? null }, create: { name: opts.name, slug: opts.slug, short: opts.short ?? null, sector: opts.sector ?? null }, }); // 3. Rollen + Permissions for (const [key, def] of Object.entries(ROLE_DEFS)) { const role = await prisma.role.upsert({ where: { tenantId_key: { tenantId: tenant.id, key } }, update: { name: def.name }, create: { tenantId: tenant.id, key, name: def.name }, }); const perms = await prisma.permission.findMany({ where: { key: { in: [...def.permissions] } } }); for (const p of perms) { await prisma.rolePermission.upsert({ where: { roleId_permissionId: { roleId: role.id, permissionId: p.id } }, update: {}, create: { roleId: role.id, permissionId: p.id }, }); } } // 4. Erster Mandantenadministrator: globale Identity (Anmeldung) + Mitgliedschaft. // Idempotent: eine bestehende Identity/ihr Passwort wird NICHT überschrieben. const passwordHash = await hashPassword(opts.admin.password); const identity = await prisma.identity.upsert({ where: { email: opts.admin.email }, update: {}, create: { email: opts.admin.email, passwordHash }, }); const admin = await prisma.user.upsert({ where: { tenantId_email: { tenantId: tenant.id, email: opts.admin.email } }, update: { name: opts.admin.name, identityId: identity.id }, create: { tenantId: tenant.id, identityId: identity.id, email: opts.admin.email, name: opts.admin.name }, }); const adminRole = await prisma.role.findUnique({ where: { tenantId_key: { tenantId: tenant.id, key: "tenant-admin" } } }); if (adminRole) { await prisma.userRole.upsert({ where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } }, update: {}, create: { userId: admin.id, roleId: adminRole.id }, }); } // 5. Mandanten-Einstellungen (Unternehmensdaten) await prisma.tenantSettings.upsert({ where: { tenantId: tenant.id }, update: {}, create: { tenantId: tenant.id, orgName: opts.name, orgShort: opts.short ?? null, sector: opts.sector ?? null, }, }); // 6. Alle Module aktivieren for (const m of MODULES) { await prisma.tenantModule.upsert({ where: { tenantId_moduleKey: { tenantId: tenant.id, moduleKey: m.key } }, update: {}, create: { tenantId: tenant.id, moduleKey: m.key, enabled: true }, }); } await prisma.auditLog.create({ data: { tenantId: tenant.id, scope: "platform", actorId: opts.actorId ?? null, action: "provision", entity: "tenant", entityId: tenant.id, after: { name: opts.name } }, }); return tenant; }