const Shift = require('../models/shift.model');
const Rate = require('../models/rate.model');
const escape = require('escape-html');
const { checkRatelogHasPeriod, checkSamePeriod } = require('../util/date.util');
/** @module payroll_controller */
/**
* This function generates all payrolls from a site.
*
* @function
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Object[]} - Site payroll report
*/
exports.getSitePayrolls = async (req, res) => {
const query = { site: req.user.site };
generateReport(res, query);
};
/**
* This function generates all payrolls for a user.
*
* @function
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Object[]} - User payroll report
*/
exports.getUserPayrolls = async (req, res) => {
const query = {
$or: [{ teacher: req.user._id, sub: null }, { sub: req.user._id }],
};
generateReport(res, query);
};
/**
* This function generates a payroll for a given month (site-wide).
*
* @function
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Object[]} - Site payroll report for a given month
*/
exports.getSitePayroll = async (req, res) => {
const date = new Date(escape(req.params.date));
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
const query = {
site: req.user.site,
startTime: { $gte: firstDay, $lt: lastDay },
};
generateReport(res, query);
};
/**
* This function generates a payroll for a given month.
*
* @function
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Object} - User payroll report for a given month
*/
exports.getUserPayroll = async (req, res) => {
const date = new Date(escape(req.params.date));
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
const query = {
$or: [{ teacher: req.user._id, sub: null }, { sub: req.user._id }],
startTime: { $gte: firstDay, $lt: lastDay },
};
generateReport(res, query);
};
/**
* This function generates a payroll report based on a given query.
*
* @function
* @async
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Object} - Payrolls array
*/
const generateReport = async (res, query) => {
try {
let payrolls = [];
// Generate shifts based on query
const shifts = await Shift.find(query)
.lean()
.populate('teacher', 'email hourlyRate taxRate')
.select('teacher startTime endTime')
.sort({ startTime: 1 });
// Cleaning for individual shift objects to to be pushed into respective timeframes.
for (const shift of shifts) {
let period = `${new Date(shift.startTime).getFullYear()}-${
new Date(shift.startTime).getMonth() + 1
}`;
// Check if timeframe exists in payrolls.
let timeframe = payrolls.filter((s) => s.period === period)[0];
let hours =
new Date(shift.endTime).getHours() -
new Date(shift.startTime).getHours();
shift.hours = hours;
const rate = await Rate.findOne({ user: shift.teacher });
if (!checkRatelogHasPeriod(rate.ratelog, shift.startTime)) {
rate.ratelog.push({
date: shift.startTime,
hourlyRate: shift.teacher.hourlyRate,
taxRate: shift.teacher.taxRate,
});
}
await rate.save();
for (const log of rate.ratelog) {
if (checkSamePeriod(log.date, shift.startTime)) {
shift.pay = hours * log.hourlyRate;
shift.deductions = shift.pay * (log.taxRate / 100);
shift.netPay = shift.pay - shift.deductions;
break;
}
}
// If timeframe exists, push shift into timeframe, otherwise create timeframe and push shift.
if (timeframe) {
timeframe.shifts.push(shift);
} else {
payrolls.push({
period,
shifts: [shift],
});
}
}
// Add up totals for each timeframe.
for (const payroll of payrolls) {
payroll.hours = 0;
payroll.pay = 0;
payroll.deductions = 0;
payroll.netPay = 0;
for (const shift of payroll.shifts) {
payroll.hours += shift.hours;
payroll.pay += shift.pay;
payroll.deductions += shift.deductions;
payroll.netPay += shift.netPay;
}
delete payroll.shifts;
}
return res.status(200).json(payrolls);
} catch (err) {
return res.status(400).json(err.message);
}
};