diff --git a/src/app.js b/src/app.js index e36bd9f..b26802f 100644 --- a/src/app.js +++ b/src/app.js @@ -22,6 +22,7 @@ import doctorRoutes from './routes/doctor.routes.js'; import receptionRoutes from './routes/reception.routes.js'; import billingRoutes from './routes/billing.routes.js'; +import leaveRoutes from './routes/leave.routes.js'; import patientRoutes from './routes/patient.js'; import deptRoutes from './routes/dept.js'; import appointmentRoutes from './routes/appointment.js'; @@ -99,6 +100,7 @@ app.use('/api/reports', reportRoutes); app.use('/api/users/doctors', doctorRoutes); app.use('/api/users/receptionist', receptionRoutes); app.use('/api/billing', billingRoutes); +app.use('/api/leaves', leaveRoutes); app.use('/api/patients', patientRoutes); app.use('/api/departments', deptRoutes); diff --git a/src/controllers/aptcontrol.js b/src/controllers/aptcontrol.js index 7aec91e..7102342 100644 --- a/src/controllers/aptcontrol.js +++ b/src/controllers/aptcontrol.js @@ -1,5 +1,6 @@ import Appointment from "../models/appointment.js"; import mongoose from "mongoose"; +import Leave from '../models/leave.model.js' const DAYS = [ "Sunday", @@ -11,6 +12,58 @@ const DAYS = [ "Saturday", ]; +const checkDoctorLeave = async (doctorId, date) => { + const appointmentDate = new Date(date) + const leave = await Leave.findOne({ + doctor: doctorId, + status: 'approved', + startDate: { $lte: appointmentDate }, + endDate: { $gte: appointmentDate } + }) + if (leave) { + return { + onLeave: true, + message: `Doctor is on approved leave on this date. Please choose a different date or doctor.` + } + } + return { onLeave: false } +} + +const checkTimeConflict = async (doctorId, date, time) => { + const startOfDay = new Date(date) + startOfDay.setHours(0, 0, 0, 0) + const endOfDay = new Date(date) + endOfDay.setHours(23, 59, 59, 999) + + const existing = await Appointment.find({ + doctor: doctorId, + date: { $gte: startOfDay, $lte: endOfDay }, + status: 'scheduled' + }).lean() + + if (existing.length === 0) return { conflict: false } + + const toMinutes = (t) => { + const [h, m] = t.split(':').map(Number) + return h * 60 + m + } + + const newTime = toMinutes(time) + + for (const apt of existing) { + const existingTime = toMinutes(apt.time) + const diff = Math.abs(newTime - existingTime) + if (diff < 20) { + return { + conflict: true, + message: `Dr. is already booked at ${apt.time}. Please choose a time at least 20 minutes apart.` + } + } + } + + return { conflict: false } +} + const validateWorkingHours = (doctorDoc, date, time) => { if (!doctorDoc.workingHours || doctorDoc.workingHours.length === 0) { return { valid: true }; // No working hours defined — skip validation @@ -65,6 +118,16 @@ const createAppointment = async (req, res) => { if (!valid) { return res.status(400).json({ message }); } + + const { conflict, message: conflictMsg } = await checkTimeConflict(doctor, date, time); + if (conflict) { + return res.status(400).json({ message: conflictMsg }); + } + } + + const { onLeave, message: leaveMsg } = await checkDoctorLeave(doctor, date); + if (onLeave) { + return res.status(400).json({ message: leaveMsg }); } const appointment = new Appointment(req.body); diff --git a/src/controllers/authcontroller.js b/src/controllers/authcontroller.js index d3c7dc7..01d4cfb 100644 --- a/src/controllers/authcontroller.js +++ b/src/controllers/authcontroller.js @@ -17,7 +17,7 @@ export const login = async (req, res) => { const user = await User.findOne({ email: { $regex: new RegExp(`^${email}$`, "i") }, role: { $regex: new RegExp(`^${role}$`, "i") }, - }).select("email role status password name"); + }).select("email role status password name phno"); if (!user) { return res.status(401).json({ success: false, message: "Invalid role or email" }); } @@ -56,6 +56,7 @@ export const login = async (req, res) => { role: user.role, status: user.status, name: user.name, + phno: user.phno || '' }, }); } catch (error) { diff --git a/src/controllers/doctor.controller.js b/src/controllers/doctor.controller.js index f59717c..d2119fd 100644 --- a/src/controllers/doctor.controller.js +++ b/src/controllers/doctor.controller.js @@ -1,4 +1,5 @@ import doctorService from '../services/doctor.service.js'; +import User from '../models/User.js'; const createDoctor = async (req, res, next) => { try { @@ -52,22 +53,50 @@ const deleteDoctor = async (req, res) => { }; const updateMyWorkingHours = async (req, res) => { - try { + try { + const doctor = await User.findById(req.user.id) + if (!doctor) return res.status(404).json({ message: 'Doctor not found' }) + + doctor.pendingWorkingHours = req.body.workingHours + doctor.pendingWorkingHoursStatus = 'pending' + await doctor.save() - if (req.user.role !== 'doctor') { - return res.status(403).json({ message: "Access denied" }); - } + res.json({ message: 'Schedule change submitted for admin approval.' }) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} - const result = await doctorService.updateWorkingHours( - req.user.id, - req.body.workingHours - ); + const approveSchedule = async (req, res) => { + try { + const doctor = await User.findById(req.params.id) + if (!doctor) return res.status(404).json({ message: 'Doctor not found' }) + if (!doctor.pendingWorkingHours) return res.status(400).json({ message: 'No pending schedule.' }) - res.status(200).json(result); + doctor.workingHours = doctor.pendingWorkingHours + doctor.pendingWorkingHours = undefined + doctor.pendingWorkingHoursStatus = 'approved' + await doctor.save() - } catch (error) { - res.status(400).json({ message: error.message }); - } + res.json({ message: 'Schedule approved.' }) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + + const rejectSchedule = async (req, res) => { + try { + const doctor = await User.findById(req.params.id) + if (!doctor) return res.status(404).json({ message: 'Doctor not found' }) + + doctor.pendingWorkingHours = undefined + doctor.pendingWorkingHoursStatus = 'rejected' + await doctor.save() + + res.json({ message: 'Schedule rejected.' }) + } catch (err) { + res.status(500).json({ message: err.message }) + } } export { @@ -76,5 +105,7 @@ export { getDoctors, updateDoctor, deleteDoctor, - updateMyWorkingHours + updateMyWorkingHours, + approveSchedule, + rejectSchedule }; \ No newline at end of file diff --git a/src/controllers/leave.controller.js b/src/controllers/leave.controller.js new file mode 100644 index 0000000..22862ab --- /dev/null +++ b/src/controllers/leave.controller.js @@ -0,0 +1,75 @@ +import Leave from '../models/leave.model.js' + +export const applyLeave = async (req, res) => { + try { + const { startDate, endDate, reason } = req.body + if (!startDate || !endDate) return res.status(400).json({ message: 'Start and end date are required.' }) + if (new Date(startDate) > new Date(endDate)) return res.status(400).json({ message: 'Start date cannot be after end date.' }) + + const leave = await Leave.create({ doctor: req.user.id, startDate, endDate, reason }) + res.status(201).json(leave) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const getMyLeaves = async (req, res) => { + try { + const leaves = await Leave.find({ doctor: req.user.id }).sort({ createdAt: -1 }) + res.json(leaves) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const getAllLeaves = async (req, res) => { + try { + const leaves = await Leave.find({ status: 'pending' }) + .populate('doctor', 'name email') + .sort({ createdAt: -1 }) + res.json(leaves) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const approveLeave = async (req, res) => { + try { + const leave = await Leave.findByIdAndUpdate(req.params.id, { status: 'approved' }, { new: true }) + if (!leave) return res.status(404).json({ message: 'Leave not found.' }) + res.json(leave) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const rejectLeave = async (req, res) => { + try { + const leave = await Leave.findByIdAndUpdate(req.params.id, { status: 'rejected' }, { new: true }) + if (!leave) return res.status(404).json({ message: 'Leave not found.' }) + res.json(leave) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const cancelLeave = async (req, res) => { + try { + const leave = await Leave.findOne({ _id: req.params.id, doctor: req.user.id }) + if (!leave) return res.status(404).json({ message: 'Leave not found.' }) + if (leave.status !== 'pending') return res.status(400).json({ message: 'Only pending leaves can be cancelled.' }) + await leave.deleteOne() + res.json({ message: 'Leave cancelled.' }) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} + +export const getDoctorApprovedLeaves = async (req, res) => { + try { + const leaves = await Leave.find({ doctor: req.params.doctorId, status: 'approved' }) + res.json(leaves) + } catch (err) { + res.status(500).json({ message: err.message }) + } +} \ No newline at end of file diff --git a/src/models/User.js b/src/models/User.js index 0ccd1b5..cad7c94 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -135,6 +135,16 @@ const userSchema = new mongoose.Schema( workingHours: { type: [workingDaySchema], }, + + pendingWorkingHours: { + type: [workingDaySchema], + default: undefined +}, +pendingWorkingHoursStatus: { + type: String, + enum: ['pending', 'approved', 'rejected'], + default: undefined +} }, { timestamps: true }, ); diff --git a/src/models/leave.model.js b/src/models/leave.model.js new file mode 100644 index 0000000..eab5cce --- /dev/null +++ b/src/models/leave.model.js @@ -0,0 +1,19 @@ +import mongoose from 'mongoose' + +const leaveSchema = new mongoose.Schema({ + doctor: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true + }, + startDate: { type: Date, required: true }, + endDate: { type: Date, required: true }, + reason: { type: String, trim: true }, + status: { + type: String, + enum: ['pending', 'approved', 'rejected'], + default: 'pending' + } +}, { timestamps: true }) + +export default mongoose.model('Leave', leaveSchema) \ No newline at end of file diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js index 5d6d924..5d0e60b 100644 --- a/src/routes/auth.routes.js +++ b/src/routes/auth.routes.js @@ -44,4 +44,31 @@ router.post('/signup', async (req, res) => { } }); +router.put('/me', protect, async (req, res) => { + try { + const { phno } = req.body; + + if (!phno || !/^\d{10}$/.test(phno.replace(/\D/g, ''))) { + return res.status(400).json({ success: false, message: 'Valid 10-digit phone number is required.' }); + } + + const cleanPhone = phno.replace(/\D/g, ''); + + const existing = await User.findOne({ phno: cleanPhone, _id: { $ne: req.user.id } }); + if (existing) { + return res.status(400).json({ success: false, message: 'This phone number is already registered.' }); + } + + const updated = await User.findByIdAndUpdate( + req.user.id, + { phno: cleanPhone }, + { new: true, runValidators: true } + ).select('-password'); + + res.json({ success: true, data: updated }); + } catch (err) { + res.status(500).json({ success: false, message: err.message }); + } +}); + export default router; \ No newline at end of file diff --git a/src/routes/doctor.routes.js b/src/routes/doctor.routes.js index 9165b85..ff4da92 100644 --- a/src/routes/doctor.routes.js +++ b/src/routes/doctor.routes.js @@ -10,7 +10,7 @@ router.get('/', protect, authorize('admin', 'doctor', 'receptionist'), doctorCon router.put('/change-password', protect, authorize('doctor'), doctorController.changePassword); -router.put('/:id', protect, authorize('admin'), doctorController.updateDoctor); +router.put('/:id', protect, authorize('admin', 'doctor'), doctorController.updateDoctor); router.put('/me/working-hours', protect, @@ -18,6 +18,9 @@ router.put('/me/working-hours', doctorController.updateMyWorkingHours ); +router.put('/:id/approve-schedule', protect, authorize(['admin']), doctorController.approveSchedule) +router.put('/:id/reject-schedule', protect, authorize(['admin']), doctorController.rejectSchedule) + router.delete('/:id', protect, authorize('admin'), doctorController.deleteDoctor); export default router; \ No newline at end of file diff --git a/src/routes/leave.routes.js b/src/routes/leave.routes.js new file mode 100644 index 0000000..2357218 --- /dev/null +++ b/src/routes/leave.routes.js @@ -0,0 +1,15 @@ +import express from 'express' +import { protect, authorize } from '../middleware/authmiddleware.js' +import * as leaveController from '../controllers/leave.controller.js' + +const router = express.Router() + +router.post('/', protect, authorize('doctor'), leaveController.applyLeave) +router.get('/my', protect, authorize('doctor'), leaveController.getMyLeaves) +router.get('/pending', protect, authorize('admin'), leaveController.getAllLeaves) +router.put('/:id/approve', protect, authorize('admin'), leaveController.approveLeave) +router.put('/:id/reject', protect, authorize('admin'), leaveController.rejectLeave) +router.delete('/:id', protect, authorize('doctor'), leaveController.cancelLeave) +router.get('/doctor/:doctorId', protect, leaveController.getDoctorApprovedLeaves) + +export default router \ No newline at end of file