#!/bin/sh set -e echo "=== BetterHuman HR Portal Starting ===" # Run database migrations echo "[start] Pushing database schema..." cd /app && npx prisma db push --schema=./prisma/schema.prisma --accept-data-loss 2>&1 || echo "[start] DB push warning (continuing)" # Seed initial data if needed echo "[start] Checking seed data..." node -e " const { PrismaClient } = require('@prisma/client'); const bcrypt = require('bcryptjs'); const prisma = new PrismaClient(); async function seed() { try { const existing = await prisma.company.findFirst(); if (existing) { console.log('[seed] Already seeded, skipping'); await prisma.\$disconnect(); process.exit(0); } console.log('[seed] Creating initial data...'); const company = await prisma.company.create({ data: { name: 'Acme Corp', domain: 'acme.com', plan: 'enterprise' } }); const hash = await bcrypt.hash('admin123', 12); const adminUser = await prisma.user.create({ data: { companyId: company.id, email: 'admin@acme.com', passwordHash: hash, role: 'HR_ADMIN' } }); // Departments const eng = await prisma.department.create({ data: { companyId: company.id, name: 'Engineering' } }); const sales = await prisma.department.create({ data: { companyId: company.id, name: 'Sales' } }); const hr = await prisma.department.create({ data: { companyId: company.id, name: 'Human Resources' } }); const product = await prisma.department.create({ data: { companyId: company.id, name: 'Product' } }); const marketing = await prisma.department.create({ data: { companyId: company.id, name: 'Marketing' } }); // Positions const swePos = await prisma.position.create({ data: { companyId: company.id, name: 'Software Engineer', level: 'L3' } }); const srSwePos = await prisma.position.create({ data: { companyId: company.id, name: 'Senior Software Engineer', level: 'L4' } }); const pmPos = await prisma.position.create({ data: { companyId: company.id, name: 'Product Manager', level: 'L4' } }); const hrPos = await prisma.position.create({ data: { companyId: company.id, name: 'HR Manager', level: 'L3' } }); const salesPos = await prisma.position.create({ data: { companyId: company.id, name: 'Sales Executive', level: 'L2' } }); const vpPos = await prisma.position.create({ data: { companyId: company.id, name: 'VP Engineering', level: 'L6' } }); // Locations const blr = await prisma.location.create({ data: { companyId: company.id, name: 'Bengaluru HQ', country: 'India', city: 'Bengaluru' } }); const mum = await prisma.location.create({ data: { companyId: company.id, name: 'Mumbai Office', country: 'India', city: 'Mumbai' } }); const del = await prisma.location.create({ data: { companyId: company.id, name: 'Delhi Office', country: 'India', city: 'New Delhi' } }); // Admin employee const adminEmp = await prisma.employee.create({ data: { userId: adminUser.id, companyId: company.id, employeeCode: 'EMP001', firstName: 'Admin', lastName: 'User', workEmail: 'admin@acme.com', departmentId: hr.id, positionId: hrPos.id, locationId: blr.id, salary: 150000, status: 'ACTIVE', employmentType: 'FULL_TIME', gender: 'Male' } }); // Sample employees const empData = [ { code: 'EMP002', first: 'Arjun', last: 'Sharma', dept: eng.id, pos: vpPos.id, loc: blr.id, salary: 250000, gender: 'Male', email: 'arjun@acme.com' }, { code: 'EMP003', first: 'Priya', last: 'Nair', dept: eng.id, pos: srSwePos.id, loc: blr.id, salary: 180000, gender: 'Female', email: 'priya@acme.com' }, { code: 'EMP004', first: 'Rahul', last: 'Gupta', dept: product.id, pos: pmPos.id, loc: blr.id, salary: 160000, gender: 'Male', email: 'rahul@acme.com' }, { code: 'EMP005', first: 'Divya', last: 'Krishnan', dept: sales.id, pos: salesPos.id, loc: mum.id, salary: 90000, gender: 'Female', email: 'divya@acme.com' }, { code: 'EMP006', first: 'Rohan', last: 'Mehta', dept: eng.id, pos: swePos.id, loc: blr.id, salary: 120000, gender: 'Male', email: 'rohan@acme.com' }, { code: 'EMP007', first: 'Ananya', last: 'Singh', dept: marketing.id, pos: swePos.id, loc: del.id, salary: 100000, gender: 'Female', email: 'ananya@acme.com' }, { code: 'EMP008', first: 'Vikram', last: 'Patel', dept: eng.id, pos: swePos.id, loc: blr.id, salary: 130000, gender: 'Male', email: 'vikram@acme.com', type: 'PROBATION' }, ]; const createdEmployees = []; for (const emp of empData) { const eHash = await bcrypt.hash('emp123', 12); const eUser = await prisma.user.create({ data: { companyId: company.id, email: emp.email, passwordHash: eHash, role: 'EMPLOYEE' } }); const e = await prisma.employee.create({ data: { userId: eUser.id, companyId: company.id, employeeCode: emp.code, firstName: emp.first, lastName: emp.last, workEmail: emp.email, departmentId: emp.dept, positionId: emp.pos, locationId: emp.loc, salary: emp.salary, gender: emp.gender, status: emp.type || 'ACTIVE', employmentType: 'FULL_TIME', managerId: emp.code !== 'EMP002' ? adminEmp.id : null, startDate: new Date(Date.now() - Math.random() * 2 * 365 * 24 * 60 * 60 * 1000) } }); createdEmployees.push(e); } // Leave policies const annualPolicy = await prisma.leavePolicy.create({ data: { companyId: company.id, name: 'Annual Leave', type: 'ANNUAL', accrualDays: 1.5, maxBalance: 24, carryForward: true } }); const sickPolicy = await prisma.leavePolicy.create({ data: { companyId: company.id, name: 'Sick Leave', type: 'SICK', accrualDays: 1, maxBalance: 12 } }); const casualPolicy = await prisma.leavePolicy.create({ data: { companyId: company.id, name: 'Casual Leave', type: 'CASUAL', accrualDays: 0.5, maxBalance: 6 } }); const allEmps = [adminEmp, ...createdEmployees]; const year = new Date().getFullYear(); for (const emp of allEmps) { await prisma.leaveBalance.createMany({ data: [ { employeeId: emp.id, policyId: annualPolicy.id, year, allocated: 18, used: Math.floor(Math.random() * 5), balance: 18 - Math.floor(Math.random() * 5) }, { employeeId: emp.id, policyId: sickPolicy.id, year, allocated: 12, used: Math.floor(Math.random() * 3), balance: 12 - Math.floor(Math.random() * 3) }, { employeeId: emp.id, policyId: casualPolicy.id, year, allocated: 6, used: Math.floor(Math.random() * 2), balance: 6 - Math.floor(Math.random() * 2) }, ], skipDuplicates: true, }); } // Sample leave request await prisma.leaveRequest.create({ data: { employeeId: createdEmployees[0].id, policyId: annualPolicy.id, startDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), endDate: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000), days: 3, status: 'PENDING', reason: 'Family vacation' } }); // Sample jobs await prisma.job.createMany({ data: [ { companyId: company.id, title: 'Senior Frontend Engineer', departmentId: eng.id, locationId: blr.id, status: 'OPEN', type: 'FULL_TIME', description: 'React + TypeScript expert needed' }, { companyId: company.id, title: 'Product Manager - Growth', departmentId: product.id, locationId: blr.id, status: 'OPEN', type: 'FULL_TIME' }, { companyId: company.id, title: 'Sales Manager', departmentId: sales.id, locationId: mum.id, status: 'OPEN', type: 'FULL_TIME' }, ] }); // Sample announcement await prisma.announcement.create({ data: { companyId: company.id, title: 'Welcome to BetterHuman HR Portal!', content: 'We are excited to launch our new HR management system. Track your leave, attendance, payroll and more from one place.', isPinned: true, authorId: adminEmp.id, } }); // Sample recognition await prisma.recognition.create({ data: { companyId: company.id, giverId: adminEmp.id, receiverId: createdEmployees[0].id, message: 'Outstanding work on the Q2 product launch! Your dedication and attention to detail made all the difference.', badge: 'HERO', isPublic: true, } }); // Sample goal await prisma.goal.create({ data: { employeeId: adminEmp.id, title: 'Complete Q3 HR Digital Transformation', description: 'Implement new HR portal and onboard all employees', type: 'OKR', status: 'IN_PROGRESS', progress: 75, dueDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) } }); console.log('[seed] Successfully seeded: admin@acme.com / admin123'); await prisma.\$disconnect(); process.exit(0); } catch (err) { console.error('[seed] Error:', err.message); await prisma.\$disconnect(); process.exit(0); // Don't fail startup on seed error } } seed(); " 2>/dev/null || echo "[start] Seed warning (continuing)" echo "[start] Starting Node.js backend on port ${PORT:-80}..." exec node dist/index.js