Skip to content

Commit fcee570

Browse files
Merge pull request #1699 from OneCommunityGlobal/saikrishna_donutChartForPlannedCostBreakdown
SaiKrishna add donut chart route and controller for planned cost breakdown
2 parents 3746b35 + ca2c291 commit fcee570

4 files changed

Lines changed: 299 additions & 0 deletions

File tree

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
const mongoose = require('mongoose');
2+
3+
const plannedCostController = (PlannedCost, Project) => {
4+
const getPlannedCostBreakdown = async (req, res) => {
5+
console.log('[Controller] GET /planned-cost-breakdown hit, projectId:', req.params.projectId);
6+
7+
try {
8+
const { projectId } = req.params;
9+
console.log('[Controller] Requested projectId:', projectId, 'Type:', typeof projectId);
10+
11+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
12+
return res.status(400).send({ error: 'Invalid project ID' });
13+
}
14+
15+
const project = await Project.findById(projectId);
16+
if (!project) return res.status(404).send({ error: 'Project not found' });
17+
console.log('[Controller] Found project:', project.projectName);
18+
19+
console.log('[Controller] Querying PlannedCost with projectId:', projectId);
20+
console.log('[Controller] PlannedCost model collection name:', PlannedCost.collection.name);
21+
22+
// Try to find planned costs with both ObjectId and string versions of projectId
23+
let plannedCosts = await PlannedCost.find({ projectId });
24+
console.log('[Controller] Found planned costs with ObjectId query:', plannedCosts);
25+
26+
// If no results, try with string version
27+
if (plannedCosts.length === 0) {
28+
console.log('[Controller] No results with ObjectId, trying string query...');
29+
plannedCosts = await PlannedCost.find({ projectId: projectId.toString() });
30+
console.log('[Controller] Found planned costs with string query:', plannedCosts);
31+
}
32+
33+
// If still no results, try with both ObjectId and string in the same query
34+
if (plannedCosts.length === 0) {
35+
console.log('[Controller] No results with string, trying OR query...');
36+
plannedCosts = await PlannedCost.find({
37+
$or: [{ projectId }, { projectId: projectId.toString() }],
38+
});
39+
console.log('[Controller] Found planned costs with OR query:', plannedCosts);
40+
}
41+
42+
// If still no results, try a more direct approach
43+
if (plannedCosts.length === 0) {
44+
console.log('[Controller] No results with any query, trying direct collection access...');
45+
const { db } = PlannedCost;
46+
const collection = db.collection('plannedCosts');
47+
plannedCosts = await collection.find({ projectId: projectId.toString() }).toArray();
48+
console.log('[Controller] Found planned costs with direct collection query:', plannedCosts);
49+
}
50+
51+
const total = plannedCosts.reduce((sum, c) => sum + c.plannedCost, 0);
52+
console.log('[Controller] Total calculated:', total);
53+
54+
const breakdown = {};
55+
plannedCosts.forEach((c) => {
56+
console.log('[Controller] Processing category:', c.category, 'cost:', c.plannedCost);
57+
console.log(
58+
'[Controller] Category type:',
59+
typeof c.category,
60+
'Cost type:',
61+
typeof c.plannedCost,
62+
);
63+
64+
// Sum up costs for the same category instead of overwriting
65+
if (breakdown[c.category]) {
66+
breakdown[c.category] += c.plannedCost;
67+
console.log(
68+
'[Controller] Added to existing category. New total for',
69+
c.category,
70+
':',
71+
breakdown[c.category],
72+
);
73+
} else {
74+
breakdown[c.category] = c.plannedCost;
75+
console.log('[Controller] New category added:', c.category, '=', c.plannedCost);
76+
}
77+
});
78+
console.log('[Controller] Final breakdown:', breakdown);
79+
80+
// Also try to query with different approaches to debug
81+
console.log('[Controller] Trying alternative query...');
82+
const allPlannedCosts = await PlannedCost.find({});
83+
console.log('[Controller] All planned costs in DB:', allPlannedCosts.length);
84+
if (allPlannedCosts.length > 0) {
85+
console.log(
86+
'[Controller] Sample planned cost document:',
87+
JSON.stringify(allPlannedCosts[0], null, 2),
88+
);
89+
}
90+
91+
res.status(200).send({ projectId, projectName: project.projectName, total, breakdown });
92+
} catch (err) {
93+
console.error(err);
94+
res.status(500).send({ error: 'Internal server error' });
95+
}
96+
};
97+
98+
const getAllPlannedCostsForProject = async (req, res) => {
99+
console.log('[Controller] GET /planned-costs hit, projectId:', req.params.projectId);
100+
101+
try {
102+
const { projectId } = req.params;
103+
104+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
105+
return res.status(400).send({ error: 'Invalid project ID' });
106+
}
107+
108+
const project = await Project.findById(projectId);
109+
if (!project) return res.status(404).send({ error: 'Project not found' });
110+
111+
// Try to find planned costs with both ObjectId and string versions of projectId
112+
let plannedCosts = await PlannedCost.find({ projectId });
113+
if (plannedCosts.length === 0) {
114+
plannedCosts = await PlannedCost.find({ projectId: projectId.toString() });
115+
}
116+
if (plannedCosts.length === 0) {
117+
plannedCosts = await PlannedCost.find({
118+
$or: [{ projectId }, { projectId: projectId.toString() }],
119+
});
120+
}
121+
122+
// Group by category and sum up costs
123+
const groupedCosts = {};
124+
plannedCosts.forEach((cost) => {
125+
if (groupedCosts[cost.category]) {
126+
groupedCosts[cost.category] += cost.plannedCost;
127+
} else {
128+
groupedCosts[cost.category] = cost.plannedCost;
129+
}
130+
});
131+
132+
res.status(200).send({
133+
projectId,
134+
projectName: project.projectName,
135+
plannedCosts,
136+
groupedCosts,
137+
});
138+
} catch (err) {
139+
console.error(err);
140+
res.status(500).send({ error: 'Internal server error' });
141+
}
142+
};
143+
144+
const createOrUpdatePlannedCost = async (req, res) => {
145+
console.log('[Controller] POST /planned-costs hit, projectId:', req.params.projectId);
146+
147+
try {
148+
const { projectId } = req.params;
149+
const { category, plannedCost } = req.body;
150+
151+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
152+
return res.status(400).send({ error: 'Invalid project ID' });
153+
}
154+
155+
if (!category || !plannedCost) {
156+
return res.status(400).send({ error: 'Category and plannedCost are required' });
157+
}
158+
159+
const validCategories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical'];
160+
if (!validCategories.includes(category)) {
161+
return res.status(400).send({ error: 'Invalid category' });
162+
}
163+
164+
const project = await Project.findById(projectId);
165+
if (!project) return res.status(404).send({ error: 'Project not found' });
166+
167+
const result = await PlannedCost.findOneAndUpdate(
168+
{ projectId, category },
169+
{ plannedCost },
170+
{ upsert: true, new: true },
171+
);
172+
173+
res.status(200).send({ message: 'Planned cost updated successfully', data: result });
174+
} catch (err) {
175+
console.error(err);
176+
res.status(500).send({ error: 'Internal server error' });
177+
}
178+
};
179+
180+
const deletePlannedCost = async (req, res) => {
181+
console.log(
182+
'[Controller] DELETE /planned-costs hit, projectId:',
183+
req.params.projectId,
184+
'category:',
185+
req.params.category,
186+
);
187+
188+
try {
189+
const { projectId, category } = req.params;
190+
191+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
192+
return res.status(400).send({ error: 'Invalid project ID' });
193+
}
194+
195+
const validCategories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical'];
196+
if (!validCategories.includes(category)) {
197+
return res.status(400).send({ error: 'Invalid category' });
198+
}
199+
200+
const result = await PlannedCost.findOneAndDelete({ projectId, category });
201+
if (!result) {
202+
return res.status(404).send({ error: 'Planned cost not found' });
203+
}
204+
205+
res.status(200).send({ message: 'Planned cost deleted successfully', data: result });
206+
} catch (err) {
207+
console.error(err);
208+
res.status(500).send({ error: 'Internal server error' });
209+
}
210+
};
211+
212+
return {
213+
getPlannedCostBreakdown,
214+
getAllPlannedCostsForProject,
215+
createOrUpdatePlannedCost,
216+
deletePlannedCost,
217+
};
218+
};
219+
220+
module.exports = plannedCostController;

src/models/plannedCost.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const mongoose = require('mongoose');
2+
3+
const { Schema } = mongoose;
4+
5+
const plannedCostSchema = new Schema({
6+
projectId: {
7+
type: Schema.Types.ObjectId,
8+
ref: 'project',
9+
required: true,
10+
},
11+
category: {
12+
type: String,
13+
enum: ['Plumbing', 'Electrical', 'Structural', 'Mechanical'],
14+
required: true,
15+
},
16+
plannedCost: {
17+
type: Number,
18+
required: true,
19+
min: 0,
20+
},
21+
createdDatetime: {
22+
type: Date,
23+
default: Date.now,
24+
},
25+
modifiedDatetime: {
26+
type: Date,
27+
default: Date.now,
28+
},
29+
});
30+
31+
// Compound index to ensure unique combination of projectId and category
32+
plannedCostSchema.index({ projectId: 1, category: 1 }, { unique: true });
33+
34+
// Update modifiedDatetime on save
35+
plannedCostSchema.pre('save', function (next) {
36+
this.modifiedDatetime = new Date();
37+
next();
38+
});
39+
40+
module.exports = mongoose.model('plannedCost', plannedCostSchema, 'plannedCosts');

src/routes/plannedCostRouter.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const express = require('express');
2+
3+
const routes = function (Cost, Project) {
4+
const controller = require('../controllers/plannedCostController')(Cost, Project);
5+
const CostRouter = express.Router();
6+
7+
// Include /projects prefix in the router paths
8+
CostRouter.route('/projects/:projectId/planned-cost-breakdown').get((req, res, next) => {
9+
console.log('[Router] Route hit for planned-cost-breakdown, projectId:', req.params.projectId);
10+
next();
11+
}, controller.getPlannedCostBreakdown);
12+
13+
CostRouter
14+
.route('/projects/:projectId/planned-costs')
15+
.get(controller.getAllPlannedCostsForProject)
16+
.post(controller.createOrUpdatePlannedCost);
17+
18+
CostRouter
19+
.route('/projects/:projectId/planned-costs/:category')
20+
.delete(controller.deletePlannedCost);
21+
22+
return CostRouter;
23+
};
24+
25+
module.exports = routes;

src/startup/routes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ const templateRouter = require('../routes/templateRouter');
325325
const emailTemplateRouter = require('../routes/emailTemplateRouter');
326326

327327
const projectMaterialRouter = require('../routes/projectMaterialroutes');
328+
console.log('Loading plannedCost model...');
329+
const plannedCost = require('../models/plannedCost');
330+
console.log('PlannedCost model loaded:', plannedCost ? 'success' : 'failed');
331+
332+
console.log('Loading plannedCostRouter...');
333+
const plannedCostRouter = require('../routes/plannedCostRouter');
334+
console.log('PlannedCostRouter loaded:', plannedCostRouter ? 'success' : 'failed');
328335

329336
const projectCostRouter = require('../routes/bmdashboard/projectCostRouter')(projectCost);
330337

@@ -486,6 +493,10 @@ module.exports = function (app) {
486493
app.use('/api', toolUtilizationRouter);
487494
// lb dashboard
488495

496+
497+
app.use('/api', toolAvailabilityRouter);
498+
app.use('/api', projectCostTrackingRouter);
499+
489500
app.use('/api/bm', bmIssueRouter);
490501
app.use('/api/bm', bmDashboardRouter);
491502
app.use('/api/bm', bmActualVsPlannedCostRouter);
@@ -518,6 +529,9 @@ module.exports = function (app) {
518529

519530
app.use('/api/lb', biddingRouter);
520531
app.use('/api', registrationRouter);
532+
app.use('/api', projectMaterialRouter);
533+
app.use('/api', plannedCostRouter(plannedCost, project));
534+
521535

522536
// summary dashboard
523537
app.use('/api/suppliers', supplierPerformanceRouter);

0 commit comments

Comments
 (0)