ca-grow-ops-manager/backend/prisma/seed.js
fullsizemalt 3dad07de03
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
Test / backend-test (push) Failing after 0s
Test / frontend-test (push) Failing after 0s
feat(seed): Add Nutrient Management protocols and Supplies
- Added Front Row Ag 3-2-2 Mixing Task to seed.js
- Added Front Row Ag nutrients and Phoszyme to demo supplies
- Created spec 013 (Facility Monitoring)
- Updated spec 006 (Cultivation) with nutrient protocols
2025-12-11 12:40:03 -08:00

404 lines
17 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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' },
// Front Row Ag Nutrients
{ name: `${DEMO_PREFIX} Front Row Ag - Part A`, category: 'NUTRIENTS', quantity: 40, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' },
{ name: `${DEMO_PREFIX} Front Row Ag - Part B`, category: 'NUTRIENTS', quantity: 30, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' },
{ name: `${DEMO_PREFIX} Front Row Ag - Bloom`, category: 'NUTRIENTS', quantity: 30, minThreshold: 10, unit: 'bag (25lb)', location: 'Nutrient Storage', vendor: 'Front Row Ag' },
{ name: `${DEMO_PREFIX} Front Row Ag - Phoszyme`, category: 'NUTRIENTS', quantity: 5, minThreshold: 2, unit: 'lb', location: 'Nutrient Storage', vendor: 'Front Row Ag' },
];
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})`);
}
}
// ==================== TREATMENT PROTOCOLS (NUTRIENTS) ====================
console.log('\n🧪 Creating Treatment Protocols...');
// Front Row Ag 3-2-2 Mixing Task
const nutrientMixTask = {
title: `${DEMO_PREFIX} Mix Nutrient Stock - Week 4 Flower`,
description: `Follow 3-2-2 Protocol (50gal Drum):
1. Part A: 3 Bags (75lbs) -> Mix 15m
2. Part B: 2 Bags (50lbs) + 6lbs Phoszyme -> Mix 15m
3. Bloom: 2 Bags (50lbs) -> Mix 15m
Validate pH 5.8-6.0 before plumbing to Dosatron.`,
status: 'PENDING',
priority: 'HIGH',
dueDate: new Date(Date.now() + 4 * 60 * 60 * 1000) // Due in 4 hours
};
const existingMix = await prisma.task.findFirst({ where: { title: nutrientMixTask.title } });
if (!existingMix) {
await prisma.task.create({
data: {
...nutrientMixTask,
assignedToId: cultivator?.id,
batchId: createdBatches.find(b => b.name.includes('Gorilla Glue'))?.id
}
});
console.log(` ✓ Created Task: ${nutrientMixTask.title}`);
}
// ==================== 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();
});