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

160 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)();
function calculateIndiaPayroll(grossSalary, pfApplicable, esiApplicable) {
const basic = grossSalary * 0.40;
const hra = grossSalary * 0.20;
const transport = 1600;
const pfBasic = Math.min(basic, 15000);
const pfEmployee = pfApplicable ? pfBasic * 0.12 : 0;
const pfEmployer = pfApplicable ? pfBasic * 0.12 : 0;
const esiEmployee = (esiApplicable && grossSalary <= 21000) ? grossSalary * 0.0075 : 0;
const esiEmployer = (esiApplicable && grossSalary <= 21000) ? grossSalary * 0.0325 : 0;
const professionalTax = grossSalary > 15000 ? 200 : 0;
const annualGross = grossSalary * 12;
const tds = annualGross > 750000 ? (annualGross - 750000) * 0.20 / 12 : 0;
const totalDeductions = pfEmployee + esiEmployee + tds + professionalTax;
const netPay = grossSalary - totalDeductions;
return {
grossPay: grossSalary,
netPay: parseFloat(netPay.toFixed(2)),
basicSalary: parseFloat(basic.toFixed(2)),
hra: parseFloat(hra.toFixed(2)),
transport,
pfEmployee: parseFloat(pfEmployee.toFixed(2)),
pfEmployer: parseFloat(pfEmployer.toFixed(2)),
esiEmployee: parseFloat(esiEmployee.toFixed(2)),
esiEmployer: parseFloat(esiEmployer.toFixed(2)),
tds: parseFloat(tds.toFixed(2)),
professionalTax,
totalDeductions: parseFloat(totalDeductions.toFixed(2)),
};
}
// GET /payroll/runs
router.get('/runs', auth_1.requireAuth, async (req, res) => {
try {
const runs = await prisma_1.default.payrollRun.findMany({
where: { companyId: req.user.companyId },
include: { _count: { select: { payslips: true } } },
orderBy: { createdAt: 'desc' },
});
return res.json(runs);
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// POST /payroll/runs
router.post('/runs', auth_1.requireAuth, async (req, res) => {
try {
const { name, periodStart, periodEnd } = req.body;
const run = await prisma_1.default.payrollRun.create({
data: {
companyId: req.user.companyId,
name,
periodStart: new Date(periodStart),
periodEnd: new Date(periodEnd),
status: 'DRAFT',
},
});
return res.status(201).json(run);
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// POST /payroll/runs/:id/process
router.post('/runs/:id/process', auth_1.requireAuth, async (req, res) => {
try {
const run = await prisma_1.default.payrollRun.findUnique({ where: { id: req.params.id } });
if (!run)
return res.status(404).json({ error: 'Payroll run not found' });
const employees = await prisma_1.default.employee.findMany({
where: {
companyId: req.user.companyId,
status: { in: ['ACTIVE', 'PROBATION'] },
},
});
// Delete existing payslips for this run
await prisma_1.default.payslip.deleteMany({ where: { payrollRunId: run.id } });
let totalGross = 0;
let totalNet = 0;
let totalDeductions = 0;
const payslipsData = employees.map(emp => {
const calc = calculateIndiaPayroll(emp.salary, emp.pfApplicable, emp.esiApplicable);
totalGross += calc.grossPay;
totalNet += calc.netPay;
totalDeductions += calc.totalDeductions;
return {
payrollRunId: run.id,
employeeId: emp.id,
...calc,
};
});
await prisma_1.default.payslip.createMany({ data: payslipsData });
await prisma_1.default.payrollRun.update({
where: { id: run.id },
data: {
status: 'PROCESSED',
totalGross: parseFloat(totalGross.toFixed(2)),
totalNet: parseFloat(totalNet.toFixed(2)),
totalDeductions: parseFloat(totalDeductions.toFixed(2)),
},
});
return res.json({ message: `Processed ${employees.length} payslips`, totalGross, totalNet });
}
catch (err) {
console.error(err);
return res.status(500).json({ error: 'Internal server error' });
}
});
// GET /payroll/runs/:id
router.get('/runs/:id', auth_1.requireAuth, async (req, res) => {
try {
const run = await prisma_1.default.payrollRun.findUnique({
where: { id: req.params.id },
include: {
payslips: {
include: {
employee: {
select: { firstName: true, lastName: true, employeeCode: true, department: { select: { name: true } } },
},
},
},
},
});
if (!run)
return res.status(404).json({ error: 'Not found' });
return res.json(run);
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
// GET /payroll/payslips (employee view)
router.get('/payslips', auth_1.requireAuth, async (req, res) => {
try {
const employeeId = req.user.employeeId;
if (!employeeId)
return res.json([]);
const payslips = await prisma_1.default.payslip.findMany({
where: { employeeId },
include: {
payrollRun: { select: { name: true, periodStart: true, periodEnd: true, status: true } },
},
orderBy: { generatedAt: 'desc' },
});
return res.json(payslips);
}
catch (err) {
return res.status(500).json({ error: 'Internal server error' });
}
});
exports.default = router;
//# sourceMappingURL=payroll.js.map