Added time adjustment api
This commit is contained in:
parent
7fac56b5ec
commit
dd118a2359
|
|
@ -40,7 +40,7 @@ async function va_login_callback(req, res) {
|
||||||
name: user.userDetails.firstName,
|
name: user.userDetails.firstName,
|
||||||
},
|
},
|
||||||
process.env.JWT_SECRET_ACCESS_TOKEN || "DocTrackerSecretKey",
|
process.env.JWT_SECRET_ACCESS_TOKEN || "DocTrackerSecretKey",
|
||||||
{ expiresIn: "24h" }
|
// { expiresIn: "24h" }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generate JWT refreshToken with 24h expiry
|
// Generate JWT refreshToken with 24h expiry
|
||||||
|
|
@ -51,7 +51,7 @@ async function va_login_callback(req, res) {
|
||||||
name: user.userDetails.firstName,
|
name: user.userDetails.firstName,
|
||||||
},
|
},
|
||||||
process.env.JWT_SECRET_REFRESH_TOKEN || "DocTrackerSecretKeyRefreshToken",
|
process.env.JWT_SECRET_REFRESH_TOKEN || "DocTrackerSecretKeyRefreshToken",
|
||||||
{ expiresIn: "30d" }
|
// { expiresIn: "30d" }
|
||||||
);
|
);
|
||||||
|
|
||||||
await VAModel.updateOne({ "userDetails.email": userDetails.email }, {
|
await VAModel.updateOne({ "userDetails.email": userDetails.email }, {
|
||||||
|
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
import jwt from "jsonwebtoken";
|
|
||||||
import { VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
|
|
||||||
|
|
||||||
// Adjust missing hours by creating new records
|
|
||||||
async function va_timesheet_adjustment_function(req, res) {
|
|
||||||
try {
|
|
||||||
const accessToken = req.headers.token;
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
return res.status(401).json({ success: false, message: "Missing token" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN);
|
|
||||||
if (!decoded || !decoded.email) {
|
|
||||||
return res.status(401).json({ success: false, message: "Invalid token" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const vaEmail = decoded.email;
|
|
||||||
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
|
||||||
if (!vaUser) {
|
|
||||||
return res.status(404).json({ success: false, message: "VA not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const vaId = vaUser._id;
|
|
||||||
const { projectId, date } = req.params;
|
|
||||||
const { loginTime, logoutTime, reason } = req.body;
|
|
||||||
|
|
||||||
if (!projectId || !date) {
|
|
||||||
return res.status(400).json({ success: false, message: "Project ID and date are required" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loginTime || !logoutTime) {
|
|
||||||
return res.status(400).json({ success: false, message: "Login time and logout time are required" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate project exists and VA is assigned to it
|
|
||||||
const project = await ProjectModel.findOne({
|
|
||||||
_id: projectId,
|
|
||||||
vaAssigned: vaId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!project) {
|
|
||||||
return res.status(404).json({ success: false, message: "Project not found or VA not assigned to this project" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse and validate times
|
|
||||||
const adjustmentDate = new Date(date);
|
|
||||||
const loginDateTime = new Date(loginTime);
|
|
||||||
const logoutDateTime = new Date(logoutTime);
|
|
||||||
|
|
||||||
// Ensure times are within the target date
|
|
||||||
const startOfDay = new Date(adjustmentDate);
|
|
||||||
startOfDay.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const endOfDay = new Date(adjustmentDate);
|
|
||||||
endOfDay.setHours(23, 59, 59, 999);
|
|
||||||
|
|
||||||
if (loginDateTime < startOfDay || loginDateTime > endOfDay ||
|
|
||||||
logoutDateTime < startOfDay || logoutDateTime > endOfDay) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: "Login and logout times must be within the specified date"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginDateTime >= logoutDateTime) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: "Login time must be before logout time"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing logs that might conflict with this adjustment
|
|
||||||
const existingLogs = await LoggedModel.find({
|
|
||||||
vaId,
|
|
||||||
projectId,
|
|
||||||
time: {
|
|
||||||
$gte: loginDateTime,
|
|
||||||
$lte: logoutDateTime
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingLogs.length > 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: "There are existing logs in the specified time range. Please choose a different time range."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create adjustment logs
|
|
||||||
const loginLog = new LoggedModel({
|
|
||||||
loggedType: "LOGIN",
|
|
||||||
time: loginDateTime,
|
|
||||||
vaId,
|
|
||||||
vaEmail,
|
|
||||||
projectId,
|
|
||||||
isAdjustment: true,
|
|
||||||
adjustmentReason: reason
|
|
||||||
});
|
|
||||||
|
|
||||||
const logoutLog = new LoggedModel({
|
|
||||||
loggedType: "LOGOUT",
|
|
||||||
time: logoutDateTime,
|
|
||||||
vaId,
|
|
||||||
vaEmail,
|
|
||||||
projectId,
|
|
||||||
isAdjustment: true,
|
|
||||||
adjustmentReason: reason
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save both logs in transaction
|
|
||||||
await loginLog.save();
|
|
||||||
await logoutLog.save();
|
|
||||||
|
|
||||||
// Calculate adjusted hours
|
|
||||||
const adjustedSeconds = (logoutDateTime - loginDateTime) / 1000;
|
|
||||||
const formatTime = (seconds) => {
|
|
||||||
const h = Math.floor(seconds / 3600);
|
|
||||||
const m = Math.floor((seconds % 3600) / 60);
|
|
||||||
const s = Math.floor(seconds % 60);
|
|
||||||
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
success: true,
|
|
||||||
message: "Timesheet adjustment completed successfully",
|
|
||||||
data: {
|
|
||||||
adjustment: {
|
|
||||||
date: adjustmentDate.toISOString().split('T')[0],
|
|
||||||
loginTime: loginDateTime.toISOString(),
|
|
||||||
logoutTime: logoutDateTime.toISOString(),
|
|
||||||
adjustedHours: formatTime(adjustedSeconds),
|
|
||||||
adjustedHoursDecimal: (adjustedSeconds / 3600).toFixed(2),
|
|
||||||
reason: reason
|
|
||||||
},
|
|
||||||
logs: {
|
|
||||||
loginLogId: loginLog._id,
|
|
||||||
logoutLogId: logoutLog._id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return res.status(201).json(response);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Timesheet adjustment error:", error);
|
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { va_timesheet_adjustment_function };
|
|
||||||
|
|
@ -34,7 +34,7 @@ async function va_profile_callback(req, res) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "VA profile",
|
message: "VA profile",
|
||||||
data: vaUser
|
data: vaUser.userDetails
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
import { LoggedModel, WorkTrackModel, VAModel } from "../../../models/vaModel.js";
|
import { LoggedModel, WorkTrackModel, VAModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
function formatHMS(ms) {
|
function formatHMS(ms) {
|
||||||
|
|
@ -10,6 +11,23 @@ function formatHMS(ms) {
|
||||||
|
|
||||||
async function va_projects_report_function(req, res) {
|
async function va_projects_report_function(req, res) {
|
||||||
try {
|
try {
|
||||||
|
const accessToken = req.headers.token;
|
||||||
|
if (!accessToken) {
|
||||||
|
return res.status(401).json({ success: false, message: "Missing token" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN);
|
||||||
|
if (!decoded || !decoded.email) {
|
||||||
|
return res.status(401).json({ success: false, message: "Invalid token" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaEmail = decoded.email;
|
||||||
|
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
||||||
|
if (!vaUser) {
|
||||||
|
return res.status(404).json({ success: false, message: "VA not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaId = vaUser._id;
|
||||||
const { projectId, from, to } = req.params;
|
const { projectId, from, to } = req.params;
|
||||||
|
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
|
|
@ -25,6 +43,7 @@ async function va_projects_report_function(req, res) {
|
||||||
// Get all VA login/logout logs for this project in range
|
// Get all VA login/logout logs for this project in range
|
||||||
const logs = await LoggedModel.find({
|
const logs = await LoggedModel.find({
|
||||||
projectId,
|
projectId,
|
||||||
|
vaId,
|
||||||
time: { $gte: fromDate, $lte: toDate }
|
time: { $gte: fromDate, $lte: toDate }
|
||||||
}).sort({ time: 1 }).lean();
|
}).sort({ time: 1 }).lean();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { AdjustedHoursModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// Get all adjustment requests (with filtering options)
|
||||||
|
async function get_adjustment_requests_list(req, res) {
|
||||||
|
try {
|
||||||
|
const { status, projectId, vaId } = req.params;
|
||||||
|
|
||||||
|
// Build filter object
|
||||||
|
const filter = {};
|
||||||
|
if (status) filter.status = status;
|
||||||
|
if (projectId) filter.projectId = projectId;
|
||||||
|
if (vaId) filter.vaId = vaId;
|
||||||
|
|
||||||
|
const adjustments = await AdjustedHoursModel.find(filter)
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.populate('vaId', 'userDetails.name userDetails.email')
|
||||||
|
.populate('projectId', 'name')
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
// Format response
|
||||||
|
const formattedData = adjustments.map(adj => ({
|
||||||
|
id: adj._id,
|
||||||
|
va: {
|
||||||
|
id: adj.vaId._id,
|
||||||
|
name: adj.vaId.userDetails?.name,
|
||||||
|
email: adj.vaId.userDetails?.email
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
id: adj.projectId._id,
|
||||||
|
name: adj.projectId.name
|
||||||
|
},
|
||||||
|
date: adj.date,
|
||||||
|
loginTime: adj.loginTime,
|
||||||
|
logoutTime: adj.logoutTime,
|
||||||
|
comment: adj.comment,
|
||||||
|
status: adj.status,
|
||||||
|
requestedAt: adj.createdAt,
|
||||||
|
reviewedAt: adj.reviewedAt,
|
||||||
|
rejectionReason: adj.rejectionReason,
|
||||||
|
adjustedHours: formatTime((new Date(adj.logoutTime) - new Date(adj.loginTime)) / 1000)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: formattedData,
|
||||||
|
pagination: {
|
||||||
|
currentPage: adjustments.page,
|
||||||
|
totalPages: adjustments.totalPages,
|
||||||
|
totalItems: adjustments.totalDocs,
|
||||||
|
hasNext: adjustments.hasNextPage,
|
||||||
|
hasPrev: adjustments.hasPrevPage
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Get adjustment requests error:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Internal Server Error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${h}h ${m}m ${s}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { get_adjustment_requests_list }
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { AdjustedHoursModel, LoggedModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// Approve or reject adjustment request
|
||||||
|
async function va_timesheet_manage_adjustment_request(req, res) {
|
||||||
|
try {
|
||||||
|
const { adjustmentId } = req.params;
|
||||||
|
const { action, rejectionReason } = req.body; // action: "approve" or "reject"
|
||||||
|
|
||||||
|
if (!adjustmentId || !action) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Adjustment ID and action are required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find adjustment request
|
||||||
|
const adjustment = await AdjustedHoursModel.findById(adjustmentId)
|
||||||
|
.populate('vaId', 'userDetails.email');
|
||||||
|
|
||||||
|
if (!adjustment) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "Adjustment request not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adjustment.status !== "PENDING") {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Adjustment request has already been processed"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === "approve") {
|
||||||
|
// Create new log entries in LoggedModel
|
||||||
|
const loginLog = new LoggedModel({
|
||||||
|
loggedType: "LOGIN",
|
||||||
|
time: adjustment.loginTime,
|
||||||
|
vaId: adjustment.vaId._id,
|
||||||
|
vaEmail: adjustment.vaId.userDetails.email,
|
||||||
|
projectId: adjustment.projectId,
|
||||||
|
isAdjustment: true,
|
||||||
|
adjustmentId: adjustment._id
|
||||||
|
});
|
||||||
|
|
||||||
|
const logoutLog = new LoggedModel({
|
||||||
|
loggedType: "LOGOUT",
|
||||||
|
time: adjustment.logoutTime,
|
||||||
|
vaId: adjustment.vaId._id,
|
||||||
|
vaEmail: adjustment.vaId.userDetails.email,
|
||||||
|
projectId: adjustment.projectId,
|
||||||
|
isAdjustment: true,
|
||||||
|
adjustmentId: adjustment._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await loginLog.save();
|
||||||
|
await logoutLog.save();
|
||||||
|
|
||||||
|
// Update adjustment record
|
||||||
|
adjustment.status = "APPROVED";
|
||||||
|
adjustment.reviewedAt = new Date();
|
||||||
|
adjustment.adjustedLogs = [
|
||||||
|
{ loginLogId: loginLog._id },
|
||||||
|
{ logoutLogId: logoutLog._id }
|
||||||
|
];
|
||||||
|
|
||||||
|
await adjustment.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Adjustment request approved successfully",
|
||||||
|
data: {
|
||||||
|
adjustmentId: adjustment._id,
|
||||||
|
status: "APPROVED",
|
||||||
|
loginLogId: loginLog._id,
|
||||||
|
logoutLogId: logoutLog._id,
|
||||||
|
adjustedHours: formatTime((adjustment.logoutTime - adjustment.loginTime) / 1000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (action === "reject") {
|
||||||
|
// Update adjustment record as rejected
|
||||||
|
adjustment.status = "REJECTED";
|
||||||
|
adjustment.reviewedAt = new Date();
|
||||||
|
adjustment.rejectionReason = rejectionReason || "No reason provided";
|
||||||
|
|
||||||
|
await adjustment.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Adjustment request rejected",
|
||||||
|
data: {
|
||||||
|
adjustmentId: adjustment._id,
|
||||||
|
status: "REJECTED",
|
||||||
|
rejectionReason: adjustment.rejectionReason
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Invalid action. Use 'approve' or 'reject'"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Manage adjustment error:", error);
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Internal Server Error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${h}h ${m}m ${s}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { va_timesheet_manage_adjustment_request }
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
|
// Adjust missing hours by creating new records
|
||||||
|
async function va_timesheet_adjustment_function(req, res) {
|
||||||
|
try {
|
||||||
|
const accessToken = req.headers.token;
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
return res.status(401).json({ success: false, message: "Missing token" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN);
|
||||||
|
if (!decoded || !decoded.email) {
|
||||||
|
return res.status(401).json({ success: false, message: "Invalid token" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaEmail = decoded.email;
|
||||||
|
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
||||||
|
if (!vaUser) {
|
||||||
|
return res.status(404).json({ success: false, message: "VA not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaId = vaUser._id;
|
||||||
|
const { projectId, date, loginTime, logoutTime, comment } = req.body;
|
||||||
|
|
||||||
|
if (!projectId || !date || !loginTime || !logoutTime || !comment) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Project ID, date, loginTime, logoutTime, and comment are required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate project exists and VA is assigned to it
|
||||||
|
const project = await ProjectModel.findOne({
|
||||||
|
_id: projectId,
|
||||||
|
vaAssigned: vaId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
return res.status(404).json({ success: false, message: "Project not found or VA not assigned to this project" });
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustmentDate;
|
||||||
|
try {
|
||||||
|
adjustmentDate = new Date(date);
|
||||||
|
if (isNaN(adjustmentDate.getTime())) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Invalid date format. Use YYYY-MM-DD"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Invalid date format"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const combineDateTime = (dateObj, timeStr) => {
|
||||||
|
const [hours, minutes, seconds] = timeStr.split(':').map(Number);
|
||||||
|
|
||||||
|
// Extract date components from the Date object
|
||||||
|
const year = dateObj.getFullYear();
|
||||||
|
const month = dateObj.getMonth();
|
||||||
|
const day = dateObj.getDate();
|
||||||
|
|
||||||
|
// Create UTC date directly
|
||||||
|
return new Date(Date.UTC(year, month, day, hours, minutes, seconds));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginDateTime = combineDateTime(adjustmentDate, loginTime);
|
||||||
|
const logoutDateTime = combineDateTime(adjustmentDate, logoutTime);
|
||||||
|
|
||||||
|
if (isNaN(adjustmentDate.getTime()) || isNaN(loginDateTime.getTime()) || isNaN(logoutDateTime.getTime())) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Invalid date format"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure login time is before logout time
|
||||||
|
if (loginDateTime >= logoutDateTime) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Login time must be before logout time"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure times are within the target date
|
||||||
|
const startOfDay = new Date(adjustmentDate);
|
||||||
|
startOfDay.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const endOfDay = new Date(adjustmentDate);
|
||||||
|
endOfDay.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
if (loginDateTime < startOfDay || loginDateTime > endOfDay ||
|
||||||
|
logoutDateTime < startOfDay || logoutDateTime > endOfDay) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Login and logout times must be within the specified date"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingAdjustment = await AdjustedHoursModel.findOne({
|
||||||
|
vaId,
|
||||||
|
projectId,
|
||||||
|
date: startOfDay,
|
||||||
|
status: "PENDING"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingAdjustment) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "You already have a pending adjustment request for this date and project"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjustmentRequest = new AdjustedHoursModel({
|
||||||
|
vaId,
|
||||||
|
projectId,
|
||||||
|
date: startOfDay,
|
||||||
|
loginTime: loginDateTime,
|
||||||
|
logoutTime: logoutDateTime,
|
||||||
|
comment,
|
||||||
|
status: "PENDING"
|
||||||
|
});
|
||||||
|
|
||||||
|
await adjustmentRequest.save();
|
||||||
|
|
||||||
|
const adjustedSeconds = (logoutDateTime - loginDateTime) / 1000;
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${h}h ${m}m ${s}s`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
message: "Adjustment request submitted successfully",
|
||||||
|
data: {
|
||||||
|
adjustmentId: adjustmentRequest._id,
|
||||||
|
date: adjustmentDate.toISOString().split('T')[0],
|
||||||
|
loginTime: loginDateTime.toISOString(),
|
||||||
|
logoutTime: logoutDateTime.toISOString(),
|
||||||
|
adjustedHours: formatTime(adjustedSeconds),
|
||||||
|
comment: comment,
|
||||||
|
status: "PENDING"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Timesheet adjustment error:", error);
|
||||||
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { va_timesheet_adjustment_function };
|
||||||
|
|
@ -86,7 +86,7 @@ async function va_timesheet_summary_function(req, res) {
|
||||||
if (!firstLoginTime || logTime < firstLoginTime) {
|
if (!firstLoginTime || logTime < firstLoginTime) {
|
||||||
firstLoginTime = logTime;
|
firstLoginTime = logTime;
|
||||||
}
|
}
|
||||||
} else if (log.loggedType === "LOGOUT" && lastLogin) {
|
} else if (log.loggedType === "LOGOUT") {
|
||||||
if (loginStack.length > 0) {
|
if (loginStack.length > 0) {
|
||||||
// Pop the last login from stack
|
// Pop the last login from stack
|
||||||
const lastLogin = loginStack.pop();
|
const lastLogin = loginStack.pop();
|
||||||
|
|
@ -157,7 +157,6 @@ async function va_timesheet_summary_function(req, res) {
|
||||||
const response = {
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
date: targetDate.toISOString().split('T')[0],
|
|
||||||
project: {
|
project: {
|
||||||
id: project._id,
|
id: project._id,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
|
|
@ -171,10 +170,10 @@ async function va_timesheet_summary_function(req, res) {
|
||||||
summary: {
|
summary: {
|
||||||
totalWorkedHours: formatTime(totalWorkSeconds),
|
totalWorkedHours: formatTime(totalWorkSeconds),
|
||||||
totalWorkedHoursDetailed: formatTimeDetailed(totalWorkSeconds),
|
totalWorkedHoursDetailed: formatTimeDetailed(totalWorkSeconds),
|
||||||
totalWorkedHoursDecimal: totalWorkedHours.toFixed(4),
|
totalWorkedHoursDecimal: totalWorkedHours.toFixed(2),
|
||||||
totalWorkedMinutes: (totalWorkSeconds / 60).toFixed(2),
|
totalWorkedMinutes: (totalWorkSeconds / 60).toFixed(2),
|
||||||
dedicatedHours: dedicatedHours,
|
dedicatedHours: dedicatedHours,
|
||||||
pendingHours: pendingHours.toFixed(4),
|
pendingHours: pendingHours.toFixed(2),
|
||||||
firstLoginTime: firstLoginTime ? firstLoginTime.toISOString() : null,
|
firstLoginTime: firstLoginTime ? firstLoginTime.toISOString() : null,
|
||||||
lastLogoutTime: lastLogoutTime ? lastLogoutTime.toISOString() : null
|
lastLogoutTime: lastLogoutTime ? lastLogoutTime.toISOString() : null
|
||||||
},
|
},
|
||||||
|
|
@ -358,6 +358,57 @@ passwordResetSchema.index({ email: 1, used: 1 });
|
||||||
passwordResetSchema.index({ token: 1, used: 1, expiresAt: 1 });
|
passwordResetSchema.index({ token: 1, used: 1, expiresAt: 1 });
|
||||||
passwordResetSchema.index({ userType: 1, email: 1 });
|
passwordResetSchema.index({ userType: 1, email: 1 });
|
||||||
|
|
||||||
|
|
||||||
|
const adjustedHoursSchema = new Schema({
|
||||||
|
vaId: {
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "VA",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
projectId: {
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "Project",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: Date,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
loginTime: {
|
||||||
|
type: Date,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
logoutTime: {
|
||||||
|
type: Date,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ["PENDING", "APPROVED", "REJECTED"],
|
||||||
|
default: "PENDING"
|
||||||
|
},
|
||||||
|
reviewedAt: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
rejectionReason: {
|
||||||
|
type: String // If rejected, store the reason
|
||||||
|
},
|
||||||
|
adjustedLogs: [{
|
||||||
|
loginLogId: {
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "Logged"
|
||||||
|
},
|
||||||
|
logoutLogId: {
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "Logged"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}, { timestamps: true });
|
||||||
|
|
||||||
/* ---------------------- Model Exports ---------------------- */
|
/* ---------------------- Model Exports ---------------------- */
|
||||||
const VAModel = model("VA", vaSchema);
|
const VAModel = model("VA", vaSchema);
|
||||||
const ClientModel = model("Client", clientSchema);
|
const ClientModel = model("Client", clientSchema);
|
||||||
|
|
@ -365,5 +416,6 @@ const ProjectModel = model("Project", projectSchema);
|
||||||
const LoggedModel = model("TrackLog", loggedSchema);
|
const LoggedModel = model("TrackLog", loggedSchema);
|
||||||
const WorkTrackModel = model("TrackWork", vaWorkTrackSchema);
|
const WorkTrackModel = model("TrackWork", vaWorkTrackSchema);
|
||||||
const PasswordResetModel = model("PasswordReset", passwordResetSchema);
|
const PasswordResetModel = model("PasswordReset", passwordResetSchema);
|
||||||
|
const AdjustedHoursModel = model("AdjustedHours", adjustedHoursSchema);
|
||||||
|
|
||||||
export { VAModel, ClientModel, ProjectModel, LoggedModel, WorkTrackModel, PasswordResetModel };
|
export { VAModel, ClientModel, ProjectModel, LoggedModel, WorkTrackModel, PasswordResetModel, AdjustedHoursModel };
|
||||||
|
|
@ -18,8 +18,10 @@ import { va_log_work_report_callback } from "../../controllers/va/vaLog/vaLogWor
|
||||||
import { va_projects_report_function } from "../../controllers/va/vaProjects/vaProjectReport.controller.js";
|
import { va_projects_report_function } from "../../controllers/va/vaProjects/vaProjectReport.controller.js";
|
||||||
import { va_projects_log_list_function } from "../../controllers/va/vaProjects/vaProjectLogList.controller.js";
|
import { va_projects_log_list_function } from "../../controllers/va/vaProjects/vaProjectLogList.controller.js";
|
||||||
import { va_request_password_reset } from "../../controllers/passwordReset.controllers.js";
|
import { va_request_password_reset } from "../../controllers/passwordReset.controllers.js";
|
||||||
import { va_timesheet_summary_function } from "../../controllers/va/vaLog/vaTimesheetAdjustmentSummary.controller.js";
|
import { va_timesheet_summary_function } from "../../controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustmentSummary.controller.js";
|
||||||
import { va_timesheet_adjustment_function } from "../../controllers/va/vaLog/vaTimesheetAdjustment.controller.js";
|
import { va_timesheet_adjustment_function } from "../../controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js";
|
||||||
|
import { get_adjustment_requests_list } from "../../controllers/va/vaTimesheetAdjustment/vaAllTimesheetAdjustmentReuqest.controller.js"
|
||||||
|
import { va_timesheet_manage_adjustment_request } from "../../controllers/va/vaTimesheetAdjustment/vaManageTimesheetAdjustment.controller.js"
|
||||||
|
|
||||||
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
|
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
|
||||||
|
|
||||||
|
|
@ -36,9 +38,13 @@ vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update
|
||||||
|
|
||||||
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback (NC)
|
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback (NC)
|
||||||
|
|
||||||
|
// timesheet adjustment routes
|
||||||
vaRoutes.get("/timesheet/summary/:projectId/:date", authValidator, va_timesheet_summary_function); // Get Timesheet Adjustment for VA on specific project and date
|
vaRoutes.get("/timesheet/summary/:projectId/:date", authValidator, va_timesheet_summary_function); // Get Timesheet Adjustment for VA on specific project and date
|
||||||
vaRoutes.post("/timesheet/adjustment/:projectId/:date", authValidator, va_timesheet_adjustment_function); // Get Timesheet Adjustment for VA on specific project and date
|
vaRoutes.post("/timesheet/adjustment/request", authValidator, va_timesheet_adjustment_function); // Timesheet Adjustment for VA
|
||||||
|
vaRoutes.get("/timesheet/adjustments/list/:status?/:projectId?/:vaId?", get_adjustment_requests_list); // show all adjustment list to approve and reject
|
||||||
|
vaRoutes.patch("/timesheet/adjustments/:adjustmentId", va_timesheet_manage_adjustment_request);
|
||||||
|
|
||||||
|
// timesheet adjustment routes
|
||||||
|
|
||||||
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)
|
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)
|
||||||
vaRoutes.get("/search", va_filter_search);
|
vaRoutes.get("/search", va_filter_search);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue