import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { config as loadEnv } from 'dotenv'; // Load repo-root .env so DATABASE_URL is visible when this script runs from // any CWD (pnpm invokes it from packages/db). const here = path.dirname(fileURLToPath(import.meta.url)); loadEnv({ path: path.resolve(here, '../../../.env') }); const { PrismaClient, UserRole } = await import('@prisma/client'); const { hashSecret } = await import('../src/crypto.js'); const prisma = new PrismaClient(); const DEMO_TENANT_NAME = 'Demo Factory'; const DEMO_ADMIN_EMAIL = 'admin@demo.local'; const DEMO_ADMIN_PASSWORD = 'admin1234'; const OPERATORS = [ { email: 'op1@demo.local', pin: '1111' }, { email: 'op2@demo.local', pin: '2222' }, { email: 'op3@demo.local', pin: '3333' }, ]; const WORKSTATIONS = [ { code: 'CTR04', name: 'Controlo 04', area: 'Montagem' }, { code: 'QVN_RTL_2', name: 'Retificação Visual 2', area: 'Qualidade' }, { code: 'MTG_01', name: 'Montagem 01', area: 'Montagem' }, ]; async function main() { // Idempotent: if a prior run created the demo tenant, wipe it and recreate. // Cascade deletes on the relations handle the children. const existing = await prisma.tenant.findFirst({ where: { name: DEMO_TENANT_NAME } }); if (existing) { await prisma.tenant.delete({ where: { id: existing.id } }); } const tenant = await prisma.tenant.create({ data: { name: DEMO_TENANT_NAME }, }); await prisma.user.create({ data: { tenantId: tenant.id, email: DEMO_ADMIN_EMAIL, role: UserRole.ADMIN, passwordHash: await hashSecret(DEMO_ADMIN_PASSWORD), }, }); for (const op of OPERATORS) { await prisma.user.create({ data: { tenantId: tenant.id, email: op.email, role: UserRole.OPERATOR, passwordHash: await hashSecret(op.pin), }, }); } await prisma.workstation.createMany({ data: WORKSTATIONS.map((ws) => ({ tenantId: tenant.id, ...ws })), }); // Seed sample maintenance requests so the shift report isn't empty. const [wsList, adminUser, op1User] = await Promise.all([ prisma.workstation.findMany({ where: { tenantId: tenant.id } }), prisma.user.findFirst({ where: { tenantId: tenant.id, email: DEMO_ADMIN_EMAIL } }), prisma.user.findFirst({ where: { tenantId: tenant.id, email: 'op1@demo.local' } }), ]); if (wsList.length && adminUser && op1User) { const now = new Date(); const ago = (minutes: number) => new Date(now.getTime() - minutes * 60_000); // 6 sample requests spread across "today" const samples = [ // RESOLVED — created 4h ago, claimed 7 min later, resolved 40 min after that { tenantId: tenant.id, workstationId: wsList[0]!.id, reportedByUserId: op1User.id, description: 'Sensor de pressão com leitura incorreta.', status: 'RESOLVED' as const, createdAt: ago(240), claimedAt: ago(233), claimedByUserId: adminUser.id, resolvedAt: ago(193), resolvedByUserId: adminUser.id, resolutionNote: 'Sensor substituído e calibrado.', clientRequestId: '00000000-0000-0000-0000-000000000001', }, // RESOLVED — created 3h ago, claimed 12 min later, resolved 55 min after { tenantId: tenant.id, workstationId: wsList[1 % wsList.length]!.id, reportedByUserId: op1User.id, description: 'Correia de transporte partida na zona B.', status: 'RESOLVED' as const, createdAt: ago(180), claimedAt: ago(168), claimedByUserId: adminUser.id, resolvedAt: ago(113), resolvedByUserId: adminUser.id, resolutionNote: 'Correia substituída.', clientRequestId: '00000000-0000-0000-0000-000000000002', }, // RESOLVED — created 2h ago, claimed 5 min later, resolved 20 min after { tenantId: tenant.id, workstationId: wsList[2 % wsList.length]!.id, reportedByUserId: op1User.id, description: 'Falha no painel de controlo, sem resposta ao toque.', status: 'RESOLVED' as const, createdAt: ago(120), claimedAt: ago(115), claimedByUserId: adminUser.id, resolvedAt: ago(95), resolvedByUserId: adminUser.id, resolutionNote: 'Reinício do painel resolveu a falha.', clientRequestId: '00000000-0000-0000-0000-000000000003', }, // CLAIMED — created 90 min ago, claimed 15 min later, not yet resolved { tenantId: tenant.id, workstationId: wsList[0]!.id, reportedByUserId: op1User.id, description: 'Vibração excessiva no eixo principal.', status: 'CLAIMED' as const, createdAt: ago(90), claimedAt: ago(75), claimedByUserId: adminUser.id, clientRequestId: '00000000-0000-0000-0000-000000000004', }, // OPEN — created 45 min ago { tenantId: tenant.id, workstationId: wsList[1 % wsList.length]!.id, reportedByUserId: op1User.id, description: 'Fuga de óleo hidráulico visível no piso.', status: 'OPEN' as const, createdAt: ago(45), clientRequestId: '00000000-0000-0000-0000-000000000005', }, // OPEN — created 10 min ago { tenantId: tenant.id, workstationId: wsList[2 % wsList.length]!.id, reportedByUserId: op1User.id, description: 'Alarme sonoro ativo sem causa identificada.', status: 'OPEN' as const, createdAt: ago(10), clientRequestId: '00000000-0000-0000-0000-000000000006', }, ]; for (const s of samples) { await prisma.maintenanceRequest.create({ data: s }); } console.warn(` pedidos de exemplo: ${samples.length} criados`); } console.warn( `Seed complete — tenant=${tenant.id} (${tenant.name})`, ); console.warn( ` admin: ${DEMO_ADMIN_EMAIL} / ${DEMO_ADMIN_PASSWORD}`, ); console.warn( ` operadores: ${OPERATORS.map((o) => `${o.email}=${o.pin}`).join(' | ')}`, ); } main() .catch((err) => { console.error('Seed failed:', err); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });