154 lines
6.1 KiB
JavaScript
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
|