Skip to content

Commit d4dcb54

Browse files
Merge pull request #1920 from OneCommunityGlobal/shashank-madan-create-intermediate-tasks-backend
Shashank Madan - Phase 4 – Teacher-Created Intermediate Tasks (Backend)
2 parents 5ef572b + 811f5ca commit d4dcb54

6 files changed

Lines changed: 339 additions & 6 deletions

File tree

create-tasks-for-current-user.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ async function createTasksForCurrentUser() {
1818

1919
console.log('✅ Connected to MongoDB');
2020

21-
const currentUserId = '68645a7c48658d005501e2f9';
21+
// add your user id here to create tasks for that user
22+
const currentUserId = '';
2223

2324
// Get existing data
2425
const subjects = await Subject.find({});

src/controllers/educationTaskController.js

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const EducationTask = require('../models/educationTask');
22
const LessonPlan = require('../models/lessonPlan');
33
const UserProfile = require('../models/userProfile');
44
const Atom = require('../models/atom');
5+
const IntermediateTask = require('../models/intermediateTask');
56

67
const educationTaskController = function () {
78
// Get all education tasks
@@ -293,24 +294,91 @@ const educationTaskController = function () {
293294
}
294295
};
295296

297+
// Helper function to check and update parent task progress
298+
const checkAndUpdateParentTaskProgress = async (parentTaskId) => {
299+
try {
300+
// Get all intermediate tasks for this parent
301+
const intermediateTasks = await IntermediateTask.find({ parent_task_id: parentTaskId });
302+
303+
// If there are no intermediate tasks, return
304+
if (intermediateTasks.length === 0) {
305+
return;
306+
}
307+
308+
// Check if all intermediate tasks are completed
309+
const allCompleted = intermediateTasks.every((task) => task.status === 'completed');
310+
311+
if (allCompleted) {
312+
// Get the parent task
313+
const parentTask = await EducationTask.findById(parentTaskId);
314+
315+
// Only update if parent task is not already completed or graded
316+
if (parentTask && parentTask.status !== 'completed' && parentTask.status !== 'graded') {
317+
await EducationTask.findByIdAndUpdate(
318+
parentTaskId,
319+
{
320+
status: 'completed',
321+
completedAt: new Date(),
322+
},
323+
{ new: true },
324+
);
325+
}
326+
}
327+
} catch (error) {
328+
console.error('Error updating parent task progress:', error);
329+
}
330+
};
331+
296332
// Mark task as complete
297333
const markTaskAsComplete = async (req, res) => {
298334
try {
299-
const { taskId, studentId } = req.body;
335+
const { taskId, studentId, taskType } = req.body;
300336
const requestorId = req.body.requestor?.requestorId;
301337

302338
if (!taskId) {
303339
return res.status(400).json({ error: 'Task ID is required' });
304340
}
305341

306-
if (!studentId) {
307-
return res.status(400).json({ error: 'Student ID is required' });
308-
}
309-
310342
if (!requestorId) {
311343
return res.status(401).json({ error: 'Authentication required' });
312344
}
313345

346+
// Handle intermediate tasks
347+
if (taskType === 'intermediate') {
348+
const intermediateTask = await IntermediateTask.findById(taskId).populate('parent_task_id');
349+
350+
if (!intermediateTask) {
351+
return res.status(404).json({ error: 'Intermediate task not found' });
352+
}
353+
354+
// Check if task is already completed
355+
if (intermediateTask.status === 'completed') {
356+
return res.status(400).json({ error: 'Task is already completed' });
357+
}
358+
359+
// Update intermediate task status to completed (only update status field)
360+
const updatedTask = await IntermediateTask.findByIdAndUpdate(
361+
taskId,
362+
{
363+
$set: { status: 'completed' },
364+
},
365+
{ new: true, runValidators: true },
366+
).populate('parent_task_id', 'type status dueAt studentId lessonPlanId');
367+
368+
// Check if all intermediate tasks for the parent are completed
369+
await checkAndUpdateParentTaskProgress(intermediateTask.parent_task_id);
370+
371+
return res.status(200).json({
372+
message: 'Intermediate task marked as complete successfully',
373+
task: updatedTask,
374+
});
375+
}
376+
377+
// Handle education tasks (original logic)
378+
if (!studentId) {
379+
return res.status(400).json({ error: 'Student ID is required' });
380+
}
381+
314382
// Find the task and verify it belongs to the student
315383
const task = await EducationTask.findOne({
316384
_id: taskId,
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
const IntermediateTask = require('../models/intermediateTask');
2+
const EducationTask = require('../models/educationTask');
3+
4+
const intermediateTaskController = function () {
5+
// Create new intermediate task
6+
const createIntermediateTask = async (req, res) => {
7+
try {
8+
const { parentTaskId, title, description, expectedHours, loggedHours, status, dueDate } =
9+
req.body;
10+
11+
// Validate required fields
12+
if (!parentTaskId || !title) {
13+
return res.status(400).json({ error: 'parent_task_id and title are required' });
14+
}
15+
16+
// Validate parent task exists
17+
const parentTask = await EducationTask.findById(parentTaskId);
18+
if (!parentTask) {
19+
return res.status(404).json({ error: 'Parent education task not found' });
20+
}
21+
22+
// Validate status if provided
23+
if (status) {
24+
const validStatuses = ['pending', 'in_progress', 'completed'];
25+
if (!validStatuses.includes(status)) {
26+
return res.status(400).json({
27+
error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,
28+
});
29+
}
30+
}
31+
32+
const intermediateTask = new IntermediateTask({
33+
parent_task_id: parentTaskId,
34+
title,
35+
description,
36+
expected_hours: expectedHours || 0,
37+
logged_hours: loggedHours || 0,
38+
status: status || 'pending',
39+
due_date: dueDate,
40+
});
41+
42+
const savedTask = await intermediateTask.save();
43+
const populatedTask = await IntermediateTask.findById(savedTask._id).populate(
44+
'parent_task_id',
45+
'type status dueAt studentId lessonPlanId',
46+
);
47+
48+
res.status(201).json(populatedTask);
49+
} catch (error) {
50+
res.status(500).json({ error: error.message });
51+
}
52+
};
53+
54+
// Get intermediate task by ID
55+
const getIntermediateTaskById = async (req, res) => {
56+
try {
57+
const { id } = req.params;
58+
59+
const intermediateTask = await IntermediateTask.findById(id).populate(
60+
'parent_task_id',
61+
'type status dueAt studentId lessonPlanId',
62+
);
63+
64+
if (!intermediateTask) {
65+
return res.status(404).json({ error: 'Intermediate task not found' });
66+
}
67+
68+
res.status(200).json(intermediateTask);
69+
} catch (error) {
70+
res.status(500).json({ error: error.message });
71+
}
72+
};
73+
74+
// Get intermediate tasks for a parent task
75+
const getIntermediateTasksByParent = async (req, res) => {
76+
try {
77+
const { taskId } = req.params;
78+
79+
// Validate parent task exists
80+
const parentTask = await EducationTask.findById(taskId);
81+
if (!parentTask) {
82+
return res.status(404).json({ error: 'Parent education task not found' });
83+
}
84+
85+
const intermediateTasks = await IntermediateTask.find({ parent_task_id: taskId })
86+
.populate('parent_task_id', 'type status dueAt studentId lessonPlanId')
87+
.sort({ createdAt: 1 });
88+
89+
res.status(200).json(intermediateTasks);
90+
} catch (error) {
91+
res.status(500).json({ error: error.message });
92+
}
93+
};
94+
95+
// Helper function to check and update parent task progress
96+
const checkAndUpdateParentTaskProgress = async (parentTaskId) => {
97+
try {
98+
// Get all intermediate tasks for this parent
99+
const intermediateTasks = await IntermediateTask.find({ parent_task_id: parentTaskId });
100+
101+
// If there are no intermediate tasks, return
102+
if (intermediateTasks.length === 0) {
103+
return;
104+
}
105+
106+
// Check if all intermediate tasks are completed
107+
const allCompleted = intermediateTasks.every((task) => task.status === 'completed');
108+
109+
if (allCompleted) {
110+
// Get the parent task
111+
const parentTask = await EducationTask.findById(parentTaskId);
112+
113+
// Only update if parent task is not already completed or graded
114+
if (parentTask && parentTask.status !== 'completed' && parentTask.status !== 'graded') {
115+
await EducationTask.findByIdAndUpdate(
116+
parentTaskId,
117+
{
118+
status: 'completed',
119+
completedAt: new Date(),
120+
},
121+
{ new: true },
122+
);
123+
}
124+
}
125+
} catch (error) {
126+
console.error('Error updating parent task progress:', error);
127+
}
128+
};
129+
130+
// Update intermediate task
131+
const updateIntermediateTask = async (req, res) => {
132+
try {
133+
const { id } = req.params;
134+
const { title, description, expectedHours, loggedHours, status, dueDate } = req.body;
135+
136+
// Find the intermediate task
137+
const intermediateTask = await IntermediateTask.findById(id);
138+
if (!intermediateTask) {
139+
return res.status(404).json({ error: 'Intermediate task not found' });
140+
}
141+
142+
// Validate status if provided
143+
if (status) {
144+
const validStatuses = ['pending', 'in_progress', 'completed'];
145+
if (!validStatuses.includes(status)) {
146+
return res.status(400).json({
147+
error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,
148+
});
149+
}
150+
}
151+
152+
// Build update object with only provided fields
153+
const updateFields = {};
154+
if (title !== undefined) updateFields.title = title;
155+
if (description !== undefined) updateFields.description = description;
156+
if (expectedHours !== undefined) updateFields.expected_hours = expectedHours;
157+
if (loggedHours !== undefined) updateFields.logged_hours = loggedHours;
158+
if (status !== undefined) updateFields.status = status;
159+
if (dueDate !== undefined) updateFields.due_date = dueDate;
160+
161+
// Update the task
162+
const updatedTask = await IntermediateTask.findByIdAndUpdate(
163+
id,
164+
{ $set: updateFields },
165+
{ new: true, runValidators: true },
166+
).populate('parent_task_id', 'type status dueAt studentId lessonPlanId');
167+
168+
// Check if all intermediate tasks for the parent are completed
169+
if (status === 'completed') {
170+
await checkAndUpdateParentTaskProgress(intermediateTask.parent_task_id);
171+
}
172+
173+
res.status(200).json(updatedTask);
174+
} catch (error) {
175+
res.status(500).json({ error: error.message });
176+
}
177+
};
178+
179+
// Delete intermediate task
180+
const deleteIntermediateTask = async (req, res) => {
181+
try {
182+
const { id } = req.params;
183+
184+
const intermediateTask = await IntermediateTask.findByIdAndDelete(id);
185+
if (!intermediateTask) {
186+
return res.status(404).json({ error: 'Intermediate task not found' });
187+
}
188+
189+
res.status(200).json({ message: 'Intermediate task deleted successfully' });
190+
} catch (error) {
191+
res.status(500).json({ error: error.message });
192+
}
193+
};
194+
195+
return {
196+
createIntermediateTask,
197+
getIntermediateTaskById,
198+
getIntermediateTasksByParent,
199+
updateIntermediateTask,
200+
deleteIntermediateTask,
201+
};
202+
};
203+
204+
module.exports = intermediateTaskController;

src/models/intermediateTask.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const mongoose = require('mongoose');
2+
3+
const intermediateTaskSchema = new mongoose.Schema(
4+
{
5+
parent_task_id: {
6+
type: mongoose.Schema.Types.ObjectId,
7+
ref: 'EducationTask',
8+
required: true,
9+
},
10+
title: {
11+
type: String,
12+
required: true,
13+
trim: true,
14+
},
15+
description: {
16+
type: String,
17+
trim: true,
18+
},
19+
expected_hours: {
20+
type: Number,
21+
default: 0,
22+
},
23+
logged_hours: {
24+
type: Number,
25+
default: 0,
26+
},
27+
status: {
28+
type: String,
29+
required: true,
30+
enum: ['pending', 'in_progress', 'completed'],
31+
default: 'pending',
32+
},
33+
due_date: {
34+
type: Date,
35+
},
36+
},
37+
{
38+
timestamps: true,
39+
},
40+
);
41+
42+
module.exports = mongoose.model('IntermediateTask', intermediateTaskSchema);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const express = require('express');
2+
3+
const router = express.Router();
4+
const intermediateTaskController = require('../controllers/intermediateTaskController');
5+
6+
// Initialize controller
7+
const controller = intermediateTaskController();
8+
9+
// Routes
10+
router.post('/intermediate-tasks', controller.createIntermediateTask);
11+
router.get('/intermediate-tasks/:id', controller.getIntermediateTaskById);
12+
router.get('/tasks/:taskId/intermediate', controller.getIntermediateTasksByParent);
13+
router.put('/intermediate-tasks/:id', controller.updateIntermediateTask);
14+
router.delete('/intermediate-tasks/:id', controller.deleteIntermediateTask);
15+
16+
module.exports = router;

src/startup/routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ const projectCostRouter = require('../routes/bmdashboard/projectCostRouter')(pro
304304

305305
const tagRouter = require('../routes/tagRouter')(tag);
306306
const educationTaskRouter = require('../routes/educationTaskRouter');
307+
const intermediateTaskRouter = require('../routes/intermediateTaskRouter');
307308
const savedFilterRouter = require('../routes/savedFilterRouter')(savedFilter);
308309
// lbdashboard
309310
const bidTermsRouter = require('../routes/lbdashboard/bidTermsRouter');
@@ -396,6 +397,7 @@ module.exports = function (app) {
396397
app.use('/api/help-categories', helpCategoryRouter);
397398
app.use('/api', tagRouter);
398399
app.use('/api/education-tasks', educationTaskRouter);
400+
app.use('/api/educator', intermediateTaskRouter);
399401
app.use('/api/analytics', pledgeAnalyticsRoutes);
400402
app.use('/api', registrationRouter);
401403

0 commit comments

Comments
 (0)