Files
betterhuman/backend/dist/routes/analytics.js
T

154 lines
6.1 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const prisma_1 = __importDefault(require("../lib/prisma"));
const auth_1 = require("../middleware/auth");
const router = (0, express_1.Router)();
// GET /analytics/dashboard
router.get('/dashboard', auth_1.requireAuth, async (req, res) => {
try {
const companyId = req.user.companyId;
const today = new Date();
today.setHours(0, 0, 0, 0);
const [headcount, onLeave, pendingLeave, openJobs, latestPayroll,] = await Promise.all([
prisma_1.default.employee.count({ where: { companyId, status: { in: ['ACTIVE', 'PROBATION'] } } }),
prisma_1.default.leaveRequest.count({
where: {
status: 'APPROVED',
startDate: { lte: new Date() },
endDate: { gte: today },
employee: { companyId },
},
}),
prisma_1.default.leaveRequest.count({
where: { status: 'PENDING', employee: { companyId } },
}),
prisma_1.default.job.count({ where: { companyId, status: 'OPEN' } }),
prisma_1.default.payrollRun.findFirst({
where: { companyId, status: 'PROCESSED' },
orderBy: { createdAt: 'desc' },
}),
]);
return res.json({
headcount,
onLeave,
pendingLeave,
openJobs,
monthlyPayroll: latestPayroll?.totalNet || 0,
avgEngagement: 78, // placeholder
});
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// GET /analytics/workforce
router.get('/workforce', auth_1.requireAuth, async (req, res) => {
try {
const companyId = req.user.companyId;
const employees = await prisma_1.default.employee.findMany({
where: { companyId, status: { not: 'TERMINATED' } },
include: {
department: { select: { name: true } },
},
});
// Department breakdown
const deptMap = {};
const genderMap = {};
const typeMap = {};
const tenureBuckets = { '0-1yr': 0, '1-3yr': 0, '3-5yr': 0, '5yr+': 0 };
const now = new Date();
employees.forEach(e => {
const dept = e.department?.name || 'Unassigned';
deptMap[dept] = (deptMap[dept] || 0) + 1;
const gender = e.gender || 'Not specified';
genderMap[gender] = (genderMap[gender] || 0) + 1;
typeMap[e.employmentType] = (typeMap[e.employmentType] || 0) + 1;
const tenure = (now.getTime() - e.startDate.getTime()) / (1000 * 60 * 60 * 24 * 365);
if (tenure < 1)
tenureBuckets['0-1yr']++;
else if (tenure < 3)
tenureBuckets['1-3yr']++;
else if (tenure < 5)
tenureBuckets['3-5yr']++;
else
tenureBuckets['5yr+']++;
});
return res.json({
departmentBreakdown: Object.entries(deptMap).map(([name, count]) => ({ name, count })),
genderRatio: Object.entries(genderMap).map(([name, count]) => ({ name, count })),
employmentTypes: Object.entries(typeMap).map(([name, count]) => ({ name, count })),
tenureBuckets: Object.entries(tenureBuckets).map(([name, count]) => ({ name, count })),
});
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// GET /analytics/leave
router.get('/leave', auth_1.requireAuth, async (req, res) => {
try {
const companyId = req.user.companyId;
const requests = await prisma_1.default.leaveRequest.findMany({
where: { employee: { companyId }, status: { not: 'REJECTED' } },
include: {
policy: { select: { name: true, type: true } },
employee: { select: { department: { select: { name: true } } } },
},
});
const byType = {};
const byDept = {};
requests.forEach(r => {
const type = r.policy.name;
byType[type] = (byType[type] || 0) + r.days;
const dept = r.employee.department?.name || 'Unassigned';
if (!byDept[dept])
byDept[dept] = { total: 0, approved: 0 };
byDept[dept].total += r.days;
if (r.status === 'APPROVED')
byDept[dept].approved += r.days;
});
return res.json({
byType: Object.entries(byType).map(([name, days]) => ({ name, days })),
byDepartment: Object.entries(byDept).map(([name, data]) => ({ name, ...data })),
});
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// GET /analytics/recruitment
router.get('/recruitment', auth_1.requireAuth, async (req, res) => {
try {
const companyId = req.user.companyId;
const [openJobs, totalCandidates, hiredCandidates] = await Promise.all([
prisma_1.default.job.count({ where: { companyId, status: 'OPEN' } }),
prisma_1.default.candidate.count({
where: { job: { companyId } },
}),
prisma_1.default.candidate.count({
where: { job: { companyId }, currentStage: 'HIRED' },
}),
]);
const stages = await prisma_1.default.candidate.groupBy({
by: ['currentStage'],
where: { job: { companyId } },
_count: true,
});
return res.json({
openJobs,
totalCandidates,
hiredCandidates,
conversionRate: totalCandidates > 0 ? Math.round((hiredCandidates / totalCandidates) * 100) : 0,
pipeline: stages.map(s => ({ stage: s.currentStage, count: s._count })),
});
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
exports.default = router;
//# sourceMappingURL=analytics.js.map