Skip to content

Commit c3141a2

Browse files
committed
feat: add indexes, validations at some points and final polishing before production
1 parent 437e709 commit c3141a2

6 files changed

Lines changed: 197 additions & 19 deletions

File tree

seedadmin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function seedAdmin() {
1010

1111
const existingAdmin = await User.findOne({ role: 'admin' });
1212
if (existingAdmin) {
13-
console.log('⚠️ Admin user already exists:', existingAdmin.email);
13+
console.log('Admin user already exists:', existingAdmin.email);
1414
return process.exit(0);
1515
}
1616

src/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import paymentRoutes from './routes/payment.routes.js';
1010
import signupRoutes from './routes/signup.js';
1111
import adminRoutes from './routes/adminroutes.js';
1212

13+
import reportRoutes from './routes/report.routes.js';
1314
import dashboardRoutes from './routes/dashboard.routes.js';
15+
1416
import doctorRoutes from './routes/doctor.routes.js';
1517
import receptionRoutes from './routes/reception.routes.js';
1618
import billingRoutes from './routes/billing.routes.js';
@@ -57,6 +59,8 @@ app.use('/api/signup', signupRoutes);
5759
app.use('/api/admin', adminRoutes);
5860

5961
app.use('/api/admin/dashboard', dashboardRoutes);
62+
app.use('/api/reports', reportRoutes);
63+
6064
app.use('/api/users/doctors', doctorRoutes);
6165
app.use('/api/users/receptionist', receptionRoutes);
6266
app.use('/api/billing', billingRoutes);

src/controllers/aptcontrol.js

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,35 +64,83 @@ const getAppointmentById = async (req, res) => {
6464
}
6565
};
6666

67-
const updateAppointment = async (req, res) => {
67+
const updateAppointment = async (req, res, next) => {
6868
try {
69-
const { patient, dept, doctor } = req.body;
69+
const { patient, dept, doctor, status } = req.body;
70+
71+
const appointment = await Appointment.findById(req.params.id);
72+
73+
if (!appointment) {
74+
return res.status(404).json({
75+
success: false,
76+
message: "Appointment not found"
77+
});
78+
}
79+
80+
if (
81+
req.user.role === "doctor" &&
82+
appointment.doctor.toString() !== req.user.id
83+
) {
84+
return res.status(403).json({
85+
success: false,
86+
message: "Not authorized to modify this appointment",
87+
});
88+
}
89+
90+
if (req.user.role === "doctor" && (doctor || dept)) {
91+
return res.status(403).json({
92+
success: false,
93+
message: "Doctor cannot reassign appointment",
94+
});
95+
}
7096

7197
if (patient || dept || doctor) {
7298
const checks = [];
7399
if (patient) checks.push(mongoose.model('Patient').findById(patient));
74100
if (dept) checks.push(mongoose.model('Department').findById(dept));
75-
if (doctor) checks.push(mongoose.model('User').findOne({ _id: doctor, role: 'doctor' }));
101+
if (doctor) checks.push(
102+
mongoose.model('User').findOne({ _id: doctor, role: 'doctor' })
103+
);
104+
105+
const results = await Promise.all(checks);
76106

77-
const [patientDoc, deptDoc, doctorDoc] = await Promise.all(checks);
107+
if (patient && !results[0])
108+
return res.status(400).json({ success: false, message: 'Invalid patient ID' });
78109

79-
if (patient && !patientDoc) return res.status(400).json({ message: 'Invalid patient ID' });
80-
if (dept && !deptDoc) return res.status(400).json({ message: 'Invalid department ID' });
81-
if (doctor && !doctorDoc) return res.status(400).json({ message: 'Invalid doctor ID or user is not a doctor' });
82110
}
83111

84-
const updatedAppointment = await Appointment.findByIdAndUpdate(
85-
req.params.id,
86-
req.body,
87-
{ new: true, runValidators: true }
88-
);
112+
if (status) {
113+
const validTransitions = {
114+
scheduled: ["completed", "cancelled"],
115+
completed: [],
116+
cancelled: []
117+
};
89118

90-
if (!updatedAppointment) return res.status(404).json({ message: 'Appointment not found' });
119+
const allowed = validTransitions[appointment.status] || [];
120+
121+
if (!allowed.includes(status)) {
122+
return res.status(400).json({
123+
success: false,
124+
message: "Invalid status transition"
125+
});
126+
}
127+
128+
appointment.status = status;
129+
}
130+
131+
if (patient) appointment.patient = patient;
132+
if (dept) appointment.dept = dept;
133+
if (doctor) appointment.doctor = doctor;
134+
135+
await appointment.save();
136+
137+
res.status(200).json({
138+
success: true,
139+
data: appointment
140+
});
91141

92-
res.json(updatedAppointment);
93142
} catch (err) {
94-
console.error('Error updating appointment:', err);
95-
res.status(400).json({ message: err.message });
143+
next(err);
96144
}
97145
};
98146

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import Billing from "../models/billing.js";
2+
import Appointment from "../models/appointment.js";
3+
4+
export const getRevenueReport = async (req, res, next) => {
5+
try {
6+
const { startDate, endDate } = req.query;
7+
8+
const match = { status: "paid" };
9+
10+
if (startDate && endDate) {
11+
match.createdAt = {
12+
$gte: new Date(startDate),
13+
$lte: new Date(endDate)
14+
};
15+
}
16+
17+
const result = await Billing.aggregate([
18+
{ $match: match },
19+
{
20+
$group: {
21+
_id: null,
22+
totalRevenue: { $sum: "$amount" },
23+
totalTransactions: { $sum: 1 }
24+
}
25+
}
26+
]);
27+
28+
res.status(200).json({
29+
success: true,
30+
data: result[0] || { totalRevenue: 0, totalTransactions: 0 }
31+
});
32+
33+
} catch (error) {
34+
next(error);
35+
}
36+
};
37+
38+
export const getAppointmentReport = async (req, res, next) => {
39+
try {
40+
const { status, doctorId, startDate, endDate } = req.query;
41+
42+
const filter = {};
43+
44+
if (status) filter.status = status;
45+
if (doctorId) filter.doctor = doctorId;
46+
47+
if (startDate && endDate) {
48+
filter.createdAt = {
49+
$gte: new Date(startDate),
50+
$lte: new Date(endDate)
51+
};
52+
}
53+
54+
const totalAppointments = await Appointment.countDocuments(filter);
55+
56+
res.status(200).json({
57+
success: true,
58+
data: {
59+
totalAppointments
60+
}
61+
});
62+
63+
} catch (error) {
64+
next(error);
65+
}
66+
};
67+
68+
export const getDoctorSummary = async (req, res, next) => {
69+
try {
70+
const doctorId = req.user.id;
71+
72+
const totalAppointments = await Appointment.countDocuments({
73+
doctor: doctorId
74+
});
75+
76+
const completedAppointments = await Appointment.countDocuments({
77+
doctor: doctorId,
78+
status: "completed"
79+
});
80+
81+
res.status(200).json({
82+
success: true,
83+
data: {
84+
totalAppointments,
85+
completedAppointments
86+
}
87+
});
88+
89+
} catch (error) {
90+
next(error);
91+
}
92+
};

src/models/appointment.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const appointmentSchema = new mongoose.Schema(
2222
},
2323
status: {
2424
type: String,
25-
enum: ['Scheduled', 'Completed', 'Cancelled'],
26-
default: 'Scheduled',
25+
enum: ['scheduled', 'completed', 'cancelled'],
26+
default: 'scheduled',
2727
index: true
2828
},
2929
date: {
@@ -52,5 +52,7 @@ const appointmentSchema = new mongoose.Schema(
5252

5353
appointmentSchema.index({ doctor: 1, date: 1 });
5454
appointmentSchema.index({ doctor: 1, date: 1, time: 1 }, { unique: true });
55+
appointmentSchema.index({ patient: 1});
56+
appointmentSchema.index({ status: 1});
5557

5658
export default mongoose.model('Appointment', appointmentSchema);

src/routes/report.routes.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import express from "express";
2+
import { protect, authorize } from "../middleware/authmiddleware.js";
3+
import {
4+
getRevenueReport,
5+
getAppointmentReport,
6+
getDoctorSummary
7+
} from "../controllers/report.controller.js";
8+
9+
const router = express.Router();
10+
11+
router.get(
12+
"/revenue",
13+
protect,
14+
authorize(["admin", "billing"]),
15+
getRevenueReport
16+
);
17+
18+
router.get(
19+
"/appointments",
20+
protect,
21+
authorize(["admin"]),
22+
getAppointmentReport
23+
);
24+
25+
router.get(
26+
"/my-summary",
27+
protect,
28+
authorize(["doctor"]),
29+
getDoctorSummary
30+
);
31+
32+
export default router;

0 commit comments

Comments
 (0)