Files
betterhuman/start.sh
T

184 lines
8.9 KiB
Bash

#!/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 3000..."
exec node dist/index.js