const { PrismaClient, RoomType } = require('@prisma/client'); const bcrypt = require('bcryptjs'); const prisma = new PrismaClient(); // Demo data marker - all demo entities use this prefix for easy identification const DEMO_PREFIX = '[DEMO]'; async function hashPassword(password) { return bcrypt.hash(password, 10); } async function main() { console.log('🌱 Seeding database with demo data...\n'); // ==================== ROLES ==================== console.log('šŸ“‹ Creating Roles...'); const rolesData = [ { name: 'Facility Owner', description: 'Full access to all features and settings', permissions: { admin: true, all: true }, isSystem: true }, { name: 'Manager', description: 'Operational control and reporting', permissions: { users: { view: true, manage: true }, tasks: { view: true, manage: true, assign: true }, inventory: { view: true, manage: true }, reports: { view: true, export: true }, visitors: { view: true, manage: true }, compliance: { view: true } }, isSystem: true }, { name: 'Grower', description: 'Plant care and daily operations', permissions: { tasks: { view: true, complete: true }, inventory: { view: true }, batches: { view: true, update: true }, rooms: { view: true } }, isSystem: true }, { name: 'Worker', description: 'Basic task completion and logging', permissions: { tasks: { view: true, complete: true }, timeclock: { view: true, punch: true } }, isSystem: true }, { name: 'Viewer', description: 'Read-only access to dashboards', permissions: { dashboard: { view: true }, reports: { view: true } }, isSystem: true } ]; for (const r of rolesData) { const existing = await prisma.role.findUnique({ where: { name: r.name } }); if (!existing) { await prisma.role.create({ data: r }); console.log(` āœ“ Created Role: ${r.name}`); } } const ownerRole = await prisma.role.findUnique({ where: { name: 'Facility Owner' } }); const managerRole = await prisma.role.findUnique({ where: { name: 'Manager' } }); const cultivatorRole = await prisma.role.findUnique({ where: { name: 'Grower' } }); const workerRole = await prisma.role.findUnique({ where: { name: 'Worker' } }); // ==================== USERS ==================== console.log('\nšŸ‘„ Creating Demo Users...'); const usersData = [ { email: 'admin@runfoo.run', password: 'password123', name: 'Travis (Owner)', role: 'OWNER', roleId: ownerRole?.id, rate: 100.00 }, { email: 'manager@demo.local', password: 'demo1234', name: `${DEMO_PREFIX} Sarah Chen`, role: 'MANAGER', roleId: managerRole?.id, rate: 45.00 }, { email: 'cultivator@demo.local', password: 'demo1234', name: `${DEMO_PREFIX} Mike Thompson`, role: 'GROWER', roleId: cultivatorRole?.id, rate: 28.00 }, { email: 'worker@demo.local', password: 'demo1234', name: `${DEMO_PREFIX} Alex Rivera`, role: 'STAFF', roleId: workerRole?.id, rate: 22.00 } ]; for (const u of usersData) { const existing = await prisma.user.findUnique({ where: { email: u.email } }); if (!existing) { const passwordHash = await hashPassword(u.password); await prisma.user.create({ data: { email: u.email, passwordHash, name: u.name, role: u.role, roleId: u.roleId, rate: u.rate } }); console.log(` āœ“ Created User: ${u.name} (${u.email})`); } } // ==================== ROOMS ==================== console.log('\nšŸ  Creating Demo Rooms...'); const rooms = [ { name: `${DEMO_PREFIX} Veg Room 1`, type: RoomType.VEG, sqft: 1200, capacity: 500, targetTemp: 78, targetHumidity: 65 }, { name: `${DEMO_PREFIX} Veg Room 2`, type: RoomType.VEG, sqft: 1000, capacity: 400, targetTemp: 78, targetHumidity: 65 }, { name: `${DEMO_PREFIX} Flower Room A`, type: RoomType.FLOWER, sqft: 2500, capacity: 300, targetTemp: 75, targetHumidity: 50 }, { name: `${DEMO_PREFIX} Flower Room B`, type: RoomType.FLOWER, sqft: 2500, capacity: 300, targetTemp: 75, targetHumidity: 50 }, { name: `${DEMO_PREFIX} Flower Room C`, type: RoomType.FLOWER, sqft: 2000, capacity: 250, targetTemp: 75, targetHumidity: 50 }, { name: `${DEMO_PREFIX} Dry Room`, type: RoomType.DRY, sqft: 800, capacity: 100, targetTemp: 60, targetHumidity: 55 }, { name: `${DEMO_PREFIX} Cure Room`, type: RoomType.CURE, sqft: 600, capacity: 50, targetTemp: 62, targetHumidity: 60 }, { name: `${DEMO_PREFIX} Mother Room`, type: RoomType.MOTHER, sqft: 400, capacity: 50, targetTemp: 76, targetHumidity: 60 }, ]; const createdRooms = []; for (const r of rooms) { let room = await prisma.room.findFirst({ where: { name: r.name } }); if (!room) { room = await prisma.room.create({ data: r }); console.log(` āœ“ Created Room: ${r.name}`); } createdRooms.push(room); } // ==================== BATCHES ==================== console.log('\n🌱 Creating Demo Batches...'); const batchesData = [ { name: `${DEMO_PREFIX} Gorilla Glue #4 - Batch 001`, strain: 'Gorilla Glue #4', status: 'ACTIVE', stage: 'FLOWERING', source: 'CLONE', plantCount: 450, startDate: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000), // 45 days ago roomId: createdRooms.find(r => r.name.includes('Flower Room A'))?.id }, { name: `${DEMO_PREFIX} Blue Dream - Batch 002`, strain: 'Blue Dream', status: 'ACTIVE', stage: 'VEGETATIVE', source: 'CLONE', plantCount: 300, startDate: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000), // 20 days ago roomId: createdRooms.find(r => r.name.includes('Veg Room 1'))?.id }, { name: `${DEMO_PREFIX} Wedding Cake - Batch 003`, strain: 'Wedding Cake', status: 'ACTIVE', stage: 'FLOWERING', source: 'CLONE', plantCount: 400, startDate: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000), // 10 days ago (Just flipped) roomId: createdRooms.find(r => r.name.includes('Flower Room B'))?.id }, { name: `${DEMO_PREFIX} Purple Punch - Batch 004`, strain: 'Purple Punch', status: 'ACTIVE', stage: 'DRYING', source: 'CLONE', plantCount: 30, startDate: new Date(Date.now() - 75 * 24 * 60 * 60 * 1000), // 75 days ago roomId: createdRooms.find(r => r.name.includes('Dry Room'))?.id }, { name: `${DEMO_PREFIX} Gelato - Batch 005`, strain: 'Gelato #41', status: 'ACTIVE', stage: 'CLONE_IN', source: 'CLONE', plantCount: 60, startDate: new Date(), // Today roomId: createdRooms.find(r => r.name.includes('Veg Room 2'))?.id } ]; const createdBatches = []; for (const b of batchesData) { let batch = await prisma.batch.findFirst({ where: { name: b.name } }); if (!batch) { batch = await prisma.batch.create({ data: b }); console.log(` āœ“ Created Batch: ${b.name} (${b.stage})`); } createdBatches.push(batch); } // ==================== IPM SCHEDULES ==================== console.log('\nšŸ›”ļø Creating IPM Schedules...'); for (const batch of createdBatches) { if (batch.stage === 'VEGETATIVE' || batch.stage === 'FLOWERING') { const existing = await prisma.iPMSchedule.findFirst({ where: { batchId: batch.id } }); if (!existing) { const nextTreatment = new Date(); nextTreatment.setDate(nextTreatment.getDate() + Math.floor(Math.random() * 5) - 2); // -2 to +2 days await prisma.iPMSchedule.create({ data: { batchId: batch.id, product: 'Pyganic 5.0', intervalDays: 10, nextTreatment } }); console.log(` āœ“ Created IPM Schedule for: ${batch.name}`); } } } // ==================== SUPPLIES ==================== console.log('\nšŸ“¦ Creating Demo Supplies...'); const supplies = [ { name: `${DEMO_PREFIX} Nitrile Gloves (L)`, category: 'PPE', quantity: 5, minThreshold: 10, unit: 'box', location: 'Ante Room', vendor: 'Uline' }, { name: `${DEMO_PREFIX} Nitrile Gloves (M)`, category: 'PPE', quantity: 8, minThreshold: 10, unit: 'box', location: 'Ante Room', vendor: 'Uline' }, { name: `${DEMO_PREFIX} Tyvek Suits`, category: 'PPE', quantity: 25, minThreshold: 20, unit: 'each', location: 'Ante Room', vendor: 'Uline' }, { name: `${DEMO_PREFIX} Hypochlorous Acid`, category: 'CLEANING', quantity: 12, minThreshold: 5, unit: 'gallon', location: 'Janitor Closet', vendor: 'Athena' }, { name: `${DEMO_PREFIX} Isopropyl Alcohol 99%`, category: 'CLEANING', quantity: 6, minThreshold: 4, unit: 'gallon', location: 'Trim Room', vendor: 'GrowGen' }, { name: `${DEMO_PREFIX} Rockwool Cubes 4"`, category: 'OTHER', quantity: 450, minThreshold: 200, unit: 'cube', location: 'Veg Storage', vendor: 'GrowGen' }, { name: `${DEMO_PREFIX} Coco Coir`, category: 'OTHER', quantity: 15, minThreshold: 10, unit: 'bag', location: 'Veg Storage', vendor: 'Botanicare' }, { name: `${DEMO_PREFIX} Trimmers (Fiskars)`, category: 'MAINTENANCE', quantity: 12, minThreshold: 15, unit: 'pair', location: 'Trim Room', vendor: 'Amazon' }, { name: `${DEMO_PREFIX} Pruning Shears`, category: 'MAINTENANCE', quantity: 8, minThreshold: 6, unit: 'pair', location: 'Flower Room A', vendor: 'Amazon' }, { name: `${DEMO_PREFIX} pH Test Kit`, category: 'MAINTENANCE', quantity: 3, minThreshold: 2, unit: 'kit', location: 'Nutrient Mix Station', vendor: 'Bluelab' }, ]; for (const s of supplies) { const existing = await prisma.supplyItem.findFirst({ where: { name: s.name } }); if (!existing) { await prisma.supplyItem.create({ data: s }); console.log(` āœ“ Created Supply: ${s.name} (qty: ${s.quantity})`); } } // ==================== TASKS ==================== console.log('\nāœ… Creating Demo Tasks...'); const manager = await prisma.user.findFirst({ where: { email: 'manager@demo.local' } }); const cultivator = await prisma.user.findFirst({ where: { email: 'cultivator@demo.local' } }); const tasksData = [ { title: `${DEMO_PREFIX} Morning Walkthrough - Veg Rooms`, description: 'Check all veg rooms for pest issues, water levels, and plant health', status: 'PENDING', priority: 'HIGH', dueDate: new Date() }, { title: `${DEMO_PREFIX} IPM Treatment - Flower Room A`, description: 'Apply Pyganic 5.0 root drench as per schedule', status: 'IN_PROGRESS', priority: 'HIGH', dueDate: new Date() }, { title: `${DEMO_PREFIX} Trim Batch 004 - Purple Punch`, description: 'Process dried material from Batch 004', status: 'PENDING', priority: 'MEDIUM', dueDate: new Date(Date.now() + 24 * 60 * 60 * 1000) // Tomorrow }, { title: `${DEMO_PREFIX} Inventory Audit - PPE Supplies`, description: 'Count and verify PPE stock levels, update system', status: 'PENDING', priority: 'LOW', dueDate: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000) // 3 days }, { title: `${DEMO_PREFIX} Equipment Maintenance - HVAC Check`, description: 'Quarterly HVAC filter replacement and system check', status: 'COMPLETED', priority: 'MEDIUM', dueDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000) // 2 days ago } ]; for (const t of tasksData) { const existing = await prisma.task.findFirst({ where: { title: t.title } }); if (!existing) { await prisma.task.create({ data: { ...t, assignedToId: cultivator?.id, batchId: createdBatches[0]?.id } }); console.log(` āœ“ Created Task: ${t.title} (${t.status})`); } } // ==================== ANNOUNCEMENTS ==================== console.log('\nšŸ“¢ Creating Demo Announcements...'); const announcementsData = [ { title: `${DEMO_PREFIX} Welcome to 777 Wolfpack Grow Ops`, body: 'This is a demo environment. All data shown is sample data for testing purposes. Login credentials: admin@runfoo.run / password123', priority: 'INFO', requiresAck: false, createdById: manager?.id }, { title: `${DEMO_PREFIX} Scheduled Facility Inspection - Dec 15`, body: 'State inspector visit scheduled for December 15th. Please ensure all compliance documentation is up to date.', priority: 'WARNING', requiresAck: true, createdById: manager?.id } ]; for (const a of announcementsData) { if (a.createdById) { const existing = await prisma.announcement.findFirst({ where: { title: a.title } }); if (!existing) { await prisma.announcement.create({ data: a }); console.log(` āœ“ Created Announcement: ${a.title}`); } } } console.log('\n✨ Seeding complete!\n'); console.log('Demo Login Credentials:'); console.log(' Owner: admin@runfoo.run / password123'); console.log(' Manager: manager@demo.local / demo1234'); console.log(' Cultivator: cultivator@demo.local / demo1234'); console.log(' Worker: worker@demo.local / demo1234'); } main() .catch((e) => { console.error('Seeding failed:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });