184 lines
8.9 KiB
Bash
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 ${PORT:-80}..."
|
|
exec node dist/index.js
|