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