Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"cron": "^1.8.2",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.1",
"dayjs": "^1.11.20",
"dotenv": "^5.0.1",
"dropbox": "^10.34.0",
"express": "^4.22.1",
Expand Down
237 changes: 237 additions & 0 deletions src/controllers/bmdashboard/costBreakdownController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/* eslint-disable import/no-unresolved, import/no-extraneous-dependencies, import/no-cycle, import/order, import/no-self-import */
const moment = require('moment');

const controller = function (CostBreakdown) {
// Get cost breakdown for a specific project with optional date filtering
const getCostBreakdown = async (req, res) => {
try {
const { projectId } = req.params;
const { fromDate, toDate } = req.query;

const sanitizedProjectId = Number(projectId);
if (Number.isNaN(sanitizedProjectId)) {
return res.status(400).json({ message: 'Invalid project ID' });
}

// Find the cost breakdown for the project
const costBreakdown = await CostBreakdown.findOne({
$or: [{ projectId: String(sanitizedProjectId) }, { projectId: sanitizedProjectId }],
});

if (!costBreakdown) {
return res.status(404).json({
message: 'Cost breakdown not found for this project',
projectId: sanitizedProjectId,
});
}

let filteredCosts = costBreakdown.costs;

// Apply date filtering if provided
if (fromDate || toDate) {
filteredCosts = costBreakdown.costs.filter((cost) => {
const costDate = moment(cost.month, 'MMM YYYY');

if (fromDate && toDate) {
const from = moment(fromDate);
const to = moment(toDate);
return costDate.isBetween(from, to, 'month', '[]');
}
if (fromDate) {
const from = moment(fromDate);
return costDate.isSameOrAfter(from, 'month');
}
if (toDate) {
const to = moment(toDate);
return costDate.isSameOrBefore(to, 'month');
}

return true;
});
}

// Format the response
const responseData = {
projectId: Number(projectId),
actual: filteredCosts.map((cost) => ({
month: cost.month,
plumbing: cost.plumbing || 0,
electrical: cost.electrical || 0,
structural: cost.structural || 0,
mechanical: cost.mechanical || 0,
})),
};

res.status(200).json(responseData);
} catch (error) {
console.error('Error in getCostBreakdown:', error);
res.status(500).json({
message: 'Error fetching cost breakdown. Please try again.',
error: error.message,
});
}
};

// Create a new cost breakdown entry for a project
const createCostBreakdown = async (req, res) => {
try {
const { projectId, costs } = req.body;

// Validate required fields
if (!projectId || !costs || !Array.isArray(costs)) {
return res.status(400).json({
message: 'Project ID and costs array are required',
});
}

// Check if cost breakdown already exists for this project
const existingBreakdown = await CostBreakdown.findOne({
projectId: Number(projectId),
});

if (existingBreakdown) {
return res.status(409).json({
message: 'Cost breakdown already exists for this project',
});
}

// Create new cost breakdown
const newCostBreakdown = new CostBreakdown({
projectId: Number(projectId),
costs,
});

const savedCostBreakdown = await newCostBreakdown.save();
res.status(201).json(savedCostBreakdown);
} catch (error) {
console.error('Error in createCostBreakdown:', error);
res.status(400).json({
message: error.message,
});
}
};

// Add a new cost entry to an existing project
const addCostEntry = async (req, res) => {
try {
const { projectId } = req.params;
const { month, plumbing, electrical, structural, mechanical } = req.body;

const costBreakdown = await CostBreakdown.findOne({
projectId: Number(projectId),
});

if (!costBreakdown) {
return res.status(404).json({
message: 'Cost breakdown not found for this project',
});
}

// Add new cost entry
costBreakdown.costs.push({
month,
plumbing: plumbing || 0,
electrical: electrical || 0,
structural: structural || 0,
mechanical: mechanical || 0,
});

const updatedCostBreakdown = await costBreakdown.save();
res.status(200).json(updatedCostBreakdown);
} catch (error) {
console.error('Error in addCostEntry:', error);
res.status(400).json({
message: error.message,
});
}
};

// Update a specific cost entry
const updateCostEntry = async (req, res) => {
try {
const { projectId, costId } = req.params;
const { month, plumbing, electrical, structural, mechanical } = req.body;

const costBreakdown = await CostBreakdown.findOne({
projectId: Number(projectId),
});

if (!costBreakdown) {
return res.status(404).json({
message: 'Cost breakdown not found for this project',
});
}

const costEntry = costBreakdown.costs.id(costId);
if (!costEntry) {
return res.status(404).json({
message: 'Cost entry not found',
});
}

// Update fields
if (month !== undefined) costEntry.month = month;
if (plumbing !== undefined) costEntry.plumbing = plumbing;
if (electrical !== undefined) costEntry.electrical = electrical;
if (structural !== undefined) costEntry.structural = structural;
if (mechanical !== undefined) costEntry.mechanical = mechanical;

const updatedCostBreakdown = await costBreakdown.save();
res.status(200).json(updatedCostBreakdown);
} catch (error) {
console.error('Error in updateCostEntry:', error);
res.status(400).json({
message: error.message,
});
}
};

// Get all cost breakdowns (for admin purposes)
const getAllCostBreakdowns = async (req, res) => {
try {
const costBreakdowns = await CostBreakdown.find();
res.status(200).json(costBreakdowns);
} catch (error) {
console.error('Error in getAllCostBreakdowns:', error);
res.status(500).json({
message: error.message,
});
}
};

// Delete a cost breakdown for a project
const deleteCostBreakdown = async (req, res) => {
try {
const { projectId } = req.params;
const deletedCostBreakdown = await CostBreakdown.findOneAndDelete({
projectId: Number(projectId),
});

if (!deletedCostBreakdown) {
return res.status(404).json({
message: 'Cost breakdown not found for this project',
});
}

res.status(200).json({
message: 'Cost breakdown deleted successfully',
});
} catch (error) {
console.error('Error in deleteCostBreakdown:', error);
res.status(500).json({
message: error.message,
});
}
};

return {
getCostBreakdown,
createCostBreakdown,
addCostEntry,
updateCostEntry,
getAllCostBreakdowns,
deleteCostBreakdown,
};
};

module.exports = controller;
26 changes: 26 additions & 0 deletions src/controllers/projectStatus.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const dayjs = require('dayjs');
const { getProjectStatusSummary } = require('../services/projectStatus.service');

exports.fetchProjectStatus = async (req, res) => {
try {
const { startDate, endDate } = req.query;

// Validate dates
if (startDate && !dayjs(startDate, 'YYYY-MM-DD', true).isValid()) {
return res.status(400).json({ message: 'Invalid startDate (YYYY-MM-DD)' });
}
if (endDate && !dayjs(endDate, 'YYYY-MM-DD', true).isValid()) {
return res.status(400).json({ message: 'Invalid endDate (YYYY-MM-DD)' });
}
if (startDate && endDate && dayjs(startDate).isAfter(dayjs(endDate))) {
return res.status(400).json({ message: 'startDate cannot be after endDate' });
}

const data = await getProjectStatusSummary({ startDate, endDate });
return res.json(data);
} catch (err) {
// eslint-disable-next-line no-console
console.error('fetchProjectStatus error:', err);
return res.status(500).json({ message: 'Internal server error' });
}
};
55 changes: 35 additions & 20 deletions src/controllers/projectStatusController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const NodeCache = require('node-cache');
const Project = require('../models/bmdashboard/project');
const Project = require('../models/project');

const cache = new NodeCache({ stdTTL: 300 });

Expand All @@ -15,38 +15,53 @@ exports.getProjectStatusSummary = async (req, res) => {

const filter = {};
if (startDate || endDate) {
filter.start_date = {};
if (startDate) filter.start_date.$gte = new Date(startDate);
if (endDate) filter.start_date.$lte = new Date(endDate);
filter.createdDatetime = {};
if (startDate) filter.createdDatetime.$gte = new Date(startDate);
if (endDate) filter.createdDatetime.$lte = new Date(endDate);
}

const data = await Project.aggregate([
{ $match: filter },
{
$group: {
_id: '$status',
count: { $sum: 1 },
_id: null,
total: { $sum: 1 },
active: {
$sum: {
$cond: [
{ $and: [{ $eq: ['$isActive', true] }, { $ne: ['$isArchived', true] }] },
1,
0,
],
},
},
completed: {
$sum: { $cond: [{ $eq: ['$isArchived', true] }, 1, 0] },
},
inactive: {
$sum: {
$cond: [
{ $and: [{ $eq: ['$isActive', false] }, { $ne: ['$isArchived', true] }] },
1,
0,
],
},
},
},
},
]);

let total = 0;
const summary = { active: 0, completed: 0, delayed: 0 };

data.forEach((item) => {
summary[item._id] = item.count;
total += item.count;
});
const result = data[0] || { total: 0, active: 0, completed: 0, inactive: 0 };

const response = {
totalProjects: total,
activeProjects: summary.active,
completedProjects: summary.completed,
delayedProjects: summary.delayed,
totalProjects: result.total,
activeProjects: result.active,
completedProjects: result.completed,
delayedProjects: result.inactive,
percentages: {
active: total ? ((summary.active / total) * 100).toFixed(1) : 0,
completed: total ? ((summary.completed / total) * 100).toFixed(1) : 0,
delayed: total ? ((summary.delayed / total) * 100).toFixed(1) : 0,
active: result.total ? ((result.active / result.total) * 100).toFixed(1) : 0,
completed: result.total ? ((result.completed / result.total) * 100).toFixed(1) : 0,
delayed: result.total ? ((result.inactive / result.total) * 100).toFixed(1) : 0,
},
};

Expand Down
Loading
Loading