From bce9b6633280a266e5299f62fd2b9ba7e6eec8df Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:13:38 +0530 Subject: [PATCH 1/9] adds model required for labs --- server/models/Lab/Lab.js | 116 ++++++++++++++++++++++++++++ server/models/Lab/LabAdmin.js | 32 ++++++++ server/models/Lab/LabAppointment.js | 95 +++++++++++++++++++++++ server/models/Lab/LabReport.js | 63 +++++++++++++++ server/models/Lab/LabStaff.js | 40 ++++++++++ 5 files changed, 346 insertions(+) create mode 100644 server/models/Lab/Lab.js create mode 100644 server/models/Lab/LabAdmin.js create mode 100644 server/models/Lab/LabAppointment.js create mode 100644 server/models/Lab/LabReport.js create mode 100644 server/models/Lab/LabStaff.js diff --git a/server/models/Lab/Lab.js b/server/models/Lab/Lab.js new file mode 100644 index 0000000..06758e3 --- /dev/null +++ b/server/models/Lab/Lab.js @@ -0,0 +1,116 @@ +// models/Lab/Lab.js +import mongoose from 'mongoose'; + +const labSchema = new mongoose.Schema( + { + name: { type: String, required: true }, + description: { type: String }, + address: { + formattedAddress: String, + street: String, + city: String, + state: String, + zipCode: String, + country: String, + coordinates: { + lat: Number, + lng: Number, + }, + }, + contact: { + phone: { type: String, required: true }, + email: { type: String }, + website: String, + }, + logo: { type: String }, + photos: [String], + + tests: [ + { + testName: { type: String, required: true }, + testCode: String, + category: { + type: String, + enum: [ + 'blood_test', + 'urine_test', + 'stool_test', + 'saliva_test', + 'swab_test', + 'imaging', // X-Ray, CT, MRI, Ultrasound + 'endoscopy', + 'biopsy', + 'ecg', + 'pulmonary_function', + 'other', + ], + required: true, + }, + description: String, + price: { type: Number, required: true }, + preparationInstructions: String, + sampleType: { + type: String, + enum: ['blood', 'urine', 'stool', 'saliva', 'swab', 'tissue', 'none', 'other'], + }, + + // Key field: whether this specific test supports home collection + homeCollectionAvailable: { + type: Boolean, + default: false, + }, + + // Additional fee for home collection of this specific test (if different from general lab fee) + homeCollectionFee: { + type: Number, + default: 0, + }, + + reportDeliveryTime: String, // e.g., "24 hours", "2-3 days" + + // For imaging tests - special requirements + requiresEquipment: { + type: Boolean, + default: false, + }, + equipmentName: String, // e.g., "X-Ray Machine", "CT Scanner" + + isActive: { type: Boolean, default: true }, + }, + ], + + // Lab staff members + staff: [{ type: mongoose.Schema.Types.ObjectId, ref: 'LabStaff' }], + + // Lab admin who manages this lab + labAdminId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabAdmin', + required: true, + }, + + isVerified: { type: Boolean, default: false }, + isActive: { type: Boolean, default: true }, + + ratings: { + average: { type: Number, default: 0, min: 0, max: 5 }, + count: { type: Number, default: 0 }, + }, + + openingHours: { + monday: { open: String, close: String, isOpen: Boolean }, + tuesday: { open: String, close: String, isOpen: Boolean }, + wednesday: { open: String, close: String, isOpen: Boolean }, + thursday: { open: String, close: String, isOpen: Boolean }, + friday: { open: String, close: String, isOpen: Boolean }, + saturday: { open: String, close: String, isOpen: Boolean }, + sunday: { open: String, close: String, isOpen: Boolean }, + }, + + // General lab settings for home collection + generalHomeCollectionFee: { type: Number, default: 0 }, + }, + { timestamps: true } +); + +export default mongoose.model('Lab', labSchema); diff --git a/server/models/Lab/LabAdmin.js b/server/models/Lab/LabAdmin.js new file mode 100644 index 0000000..0361cdd --- /dev/null +++ b/server/models/Lab/LabAdmin.js @@ -0,0 +1,32 @@ +// models/Users/LabAdmin.js +import mongoose from 'mongoose'; + +const labAdminSchema = new mongoose.Schema( + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + unique: true, + }, + firstName: { type: String, required: true }, + lastName: { type: String, required: true }, + phoneNumber: { type: String, required: true }, + + labId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Lab', + sparse: true, + }, + + permissions: { + manageStaff: { type: Boolean, default: true }, + manageTests: { type: Boolean, default: true }, + manageAppointments: { type: Boolean, default: true }, + manageReports: { type: Boolean, default: true }, + }, + }, + { timestamps: true } +); + +export default mongoose.model('LabAdmin', labAdminSchema); diff --git a/server/models/Lab/LabAppointment.js b/server/models/Lab/LabAppointment.js new file mode 100644 index 0000000..7d83001 --- /dev/null +++ b/server/models/Lab/LabAppointment.js @@ -0,0 +1,95 @@ +// models/Appointment/LabAppointment.js +import mongoose from 'mongoose'; + +const labAppointmentSchema = new mongoose.Schema( + { + patientId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Patient', + required: true, + }, + + labId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Lab', + required: true, + }, + + // Doctor who suggested this lab (optional) + suggestedByDoctorId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Doctor', + }, + + tests: [ + { + testId: { type: mongoose.Schema.Types.ObjectId }, + testName: { type: String, required: true }, + testCode: String, + category: String, + price: { type: Number, required: true }, + homeCollectionAvailable: { type: Boolean, required: true }, + requiresEquipment: { type: Boolean, default: false }, + }, + ], + + collectionType: { + type: String, + enum: ['home_collection', 'visit_lab'], + required: true, + }, + + // Validation: Check if all tests support home collection + canBeHomeCollected: { + type: Boolean, + required: true, + }, + + // For home collection + assignedStaffId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabStaff', + }, + + collectionAddress: { + street: String, + city: String, + state: String, + zipCode: String, + country: String, + coordinates: { + lat: Number, + lng: Number, + }, + }, + + appointmentDate: { type: Date, required: true }, + appointmentTime: { type: String, required: true }, + + status: { + type: String, + enum: ['pending', 'confirmed', 'sample_collected', 'processing', 'completed', 'cancelled'], + default: 'pending', + }, + + // Report details + reportId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabReport', + }, + + totalAmount: { type: Number, required: true }, + homeCollectionFee: { type: Number, default: 0 }, + + paymentStatus: { + type: String, + enum: ['pending', 'paid', 'refunded'], + default: 'pending', + }, + + notes: String, + }, + { timestamps: true } +); + +export default mongoose.model('LabAppointment', labAppointmentSchema); diff --git a/server/models/Lab/LabReport.js b/server/models/Lab/LabReport.js new file mode 100644 index 0000000..b9dd0c7 --- /dev/null +++ b/server/models/Lab/LabReport.js @@ -0,0 +1,63 @@ +// models/Report/LabReport.js +import mongoose from 'mongoose'; + +const labReportSchema = new mongoose.Schema( + { + appointmentId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabAppointment', + required: true, + }, + + patientId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Patient', + required: true, + }, + + labId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Lab', + required: true, + }, + + // Doctor who should receive this report + doctorId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Doctor', + }, + + testResults: [ + { + testName: String, + testCode: String, + result: String, + normalRange: String, + unit: String, + isAbnormal: { type: Boolean, default: false }, + }, + ], + + reportFile: { + url: { type: String, required: true }, + fileName: String, + uploadedAt: { type: Date, default: Date.now }, + }, + + // Doctor's remarks on the report + doctorRemarks: { + remarks: String, + addedAt: Date, + addedBy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Doctor', + }, + }, + + reportDate: { type: Date, default: Date.now }, + isSharedWithDoctor: { type: Boolean, default: false }, + }, + { timestamps: true } +); + +export default mongoose.model('LabReport', labReportSchema); diff --git a/server/models/Lab/LabStaff.js b/server/models/Lab/LabStaff.js new file mode 100644 index 0000000..c8ce95b --- /dev/null +++ b/server/models/Lab/LabStaff.js @@ -0,0 +1,40 @@ +// models/Users/LabStaff.js +import mongoose from 'mongoose'; + +const labStaffSchema = new mongoose.Schema( + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + unique: true, + }, + firstName: { type: String, required: true }, + lastName: { type: String, required: true }, + phoneNumber: { type: String, required: true }, + + labId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Lab', + required: true, + }, + + role: { + type: String, + enum: ['technician', 'phlebotomist', 'assistant', 'sample_collector'], + default: 'assistant', + }, + + // For home sample collection assignments + isAvailableForHomeCollection: { type: Boolean, default: true }, + currentAssignments: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabAppointment', + }, + ], + }, + { timestamps: true } +); + +export default mongoose.model('LabStaff', labStaffSchema); From 55900c014ac44653beef4355af851af091c8d66f Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:22:09 +0530 Subject: [PATCH 2/9] changes the exisiting code for new labs --- server/Controllers/doctorController.js | 31 ++++++++++++++++++++++++++ server/models/Users/Doctor.js | 1 + server/models/Users/Patient.js | 1 + server/models/Users/User.js | 8 +++++-- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/server/Controllers/doctorController.js b/server/Controllers/doctorController.js index 6ed04ea..ae9374c 100644 --- a/server/Controllers/doctorController.js +++ b/server/Controllers/doctorController.js @@ -409,3 +409,34 @@ export const getDoctorSchedule = async (req, res) => { }); } }; +// Suggest lab to patient +export const suggestLabToPatient = async (req, res) => { + try { + const { profileId } = req.user; + const { patientId, labId, tests, notes } = req.body; + + if (!mongoose.Types.ObjectId.isValid(patientId) || !mongoose.Types.ObjectId.isValid(labId)) { + return res.status(400).json({ message: 'Invalid ID format' }); + } + + const lab = await Lab.findById(labId); + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + // Create a suggestion record or notification + // This can be sent via notification service or email + + res.json({ + message: 'Lab suggested to patient successfully', + labDetails: { + name: lab.name, + tests, + notes, + }, + }); + } catch (error) { + console.error('Error suggesting lab:', error); + res.status(500).json({ message: 'Failed to suggest lab' }); + } +}; diff --git a/server/models/Users/Doctor.js b/server/models/Users/Doctor.js index 7cdb199..6ae4434 100644 --- a/server/models/Users/Doctor.js +++ b/server/models/Users/Doctor.js @@ -34,6 +34,7 @@ const doctorSchema = new mongoose.Schema({ count: { type: Number, default: 0 }, totalStars: { type: Number, default: 0 }, }, + suggestedLabAppointments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'LabAppointment' }], }); export default mongoose.model('Doctor', doctorSchema); diff --git a/server/models/Users/Patient.js b/server/models/Users/Patient.js index a10f5ee..bb33f84 100644 --- a/server/models/Users/Patient.js +++ b/server/models/Users/Patient.js @@ -28,6 +28,7 @@ const patientSchema = new mongoose.Schema({ profilePicture: { type: String }, healthRecords: [{ type: mongoose.Schema.Types.ObjectId, ref: 'HealthRecord' }], appointments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Appointment' }], + labAppointments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'LabAppointment' }], }); export default mongoose.model('Patient', patientSchema); diff --git a/server/models/Users/User.js b/server/models/Users/User.js index 911b642..9f03c37 100644 --- a/server/models/Users/User.js +++ b/server/models/Users/User.js @@ -3,8 +3,12 @@ import mongoose from 'mongoose'; const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true }, - role: { type: String, enum: ['patient', 'doctor', 'admin'], required: true }, - refreshToken: { type: String, default: null }, // Added missing refreshToken field + role: { + type: String, + enum: ['patient', 'doctor', 'admin', 'lab_admin', 'lab_staff'], + required: true, + }, + refreshToken: { type: String, default: null }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }, }); From 3ed010686ad471447e9d19cf83275446108047a9 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:22:54 +0530 Subject: [PATCH 3/9] adds lab admin and lab staff in auth middleware --- server/Middleware/authMiddleware.js | 8 +++ server/Middleware/authmiddleware demo 2.txt | 57 --------------------- server/Middleware/authmiddlewaredemo.txt | 40 --------------- 3 files changed, 8 insertions(+), 97 deletions(-) delete mode 100644 server/Middleware/authmiddleware demo 2.txt delete mode 100644 server/Middleware/authmiddlewaredemo.txt diff --git a/server/Middleware/authMiddleware.js b/server/Middleware/authMiddleware.js index 88161f8..71cf458 100644 --- a/server/Middleware/authMiddleware.js +++ b/server/Middleware/authMiddleware.js @@ -3,6 +3,8 @@ import Doctor from '../models/Users/Doctor.js'; import Patient from '../models/Users/Patient.js'; import User from '../models/Users/User.js'; import { verifyAccessToken } from '../services/tokenService.js'; +import LabAdmin from '../models/Lab/LabAdmin.js'; +import LabStaff from '../models/Lab/LabStaff.js'; export const authenticate = async (req, res, next) => { try { const authHeader = req.headers['authorization']; @@ -31,6 +33,12 @@ export const authenticate = async (req, res, next) => { case 'patient': profile = await Patient.findOne({ userId: user._id }); break; + case 'lab_admin': + profile = await LabAdmin.findOne({ userId: user._id }); + break; + case 'lab_staff': + profile = await LabStaff.findOne({ userId: user._id }); + break; } req.user = { diff --git a/server/Middleware/authmiddleware demo 2.txt b/server/Middleware/authmiddleware demo 2.txt deleted file mode 100644 index d605ff9..0000000 --- a/server/Middleware/authmiddleware demo 2.txt +++ /dev/null @@ -1,57 +0,0 @@ -import { verifyAccessToken } from '../services/tokenService.js'; -import User from '../models/Users/User.js'; -import Patient from '../models/Users/Patient.js'; -import Admin from '../models/Users/Admin.js'; -import Doctor from '../models/Users/Doctor.js'; - -export const authenticate = async (req, res, next) => { - try { - const authHeader = req.headers['authorization']; - const token = authHeader?.split(' ')[1]; - - if (!token) { - return res.status(401).json({ message: 'Authentication required' }); - } - - const decoded = verifyAccessToken(token); - const user = await User.findById(decoded.userId).select('-password -refreshToken'); - - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } - - // Get profile based on role - let profile = null; - switch (user.role) { - case 'admin': - profile = await Admin.findOne({ userId: user._id }); - break; - case 'doctor': - profile = await Doctor.findOne({ userId: user._id }); - break; - case 'patient': - profile = await Patient.findOne({ userId: user._id }); - break; - } - - req.user = { - ...user.toObject(), - userId: user._id, - profileId: profile ? profile._id : null, - role: user.role - }; - - next(); - } catch (error) { - res.status(403).json({ message: 'Invalid or expired token' }); - } -}; - -export const authorize = (...roles) => { - return (req, res, next) => { - if (!roles.includes(req.user.role)) { - return res.status(403).json({ message: 'Unauthorized access' }); - } - next(); - }; -}; diff --git a/server/Middleware/authmiddlewaredemo.txt b/server/Middleware/authmiddlewaredemo.txt deleted file mode 100644 index 8a0ea38..0000000 --- a/server/Middleware/authmiddlewaredemo.txt +++ /dev/null @@ -1,40 +0,0 @@ -import { verifyAccessToken } from '../services/tokenService.js'; -import User from '../models/Users/User.js' - -export const authenticate = async (req, res, next) => { - try { - const authHeader = req.headers['authorization']; - const token = authHeader?.split(' ')[1]; - - if (!token) { - return res.status(401).json({ message: 'Authentication required' }); - } - - const decoded = verifyAccessToken(token); - const user = await User.findById(decoded.userId).select('-password -refreshToken'); - - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } - - // Attach both the user document and the userId separately - req.user = { - ...user.toObject(), // Convert mongoose document to plain object - userId: user._id // Explicitly add userId - }; - - next(); - } catch (error) { - res.status(403).json({ message: 'Invalid or expired token' }); - } -}; - -export const authorize = (...roles) => { - return (req, res, next) => { - if (!roles.includes(req.user.role)) { - return res.status(403).json({ message: 'Unauthorized access' }); - } - next(); - }; -}; -MONGO_URI=mongodb+srv://Aditya:mjrayd9Gu2sGs5R4@quickclinic.dlmj1ip.mongodb.net/?retryWrites=true&w=majority&appName=QuickClinic From d5f0ae094a965b25da6c40a003a06d383bda473e Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:28:21 +0530 Subject: [PATCH 4/9] adds lab controllers --- server/Controllers/labAdminController.js | 331 ++++++++++++++ .../Controllers/labAppointmentController.js | 415 ++++++++++++++++++ server/Controllers/labController.js | 79 ++++ server/Controllers/labReportController.js | 174 ++++++++ 4 files changed, 999 insertions(+) create mode 100644 server/Controllers/labAdminController.js create mode 100644 server/Controllers/labAppointmentController.js create mode 100644 server/Controllers/labController.js create mode 100644 server/Controllers/labReportController.js diff --git a/server/Controllers/labAdminController.js b/server/Controllers/labAdminController.js new file mode 100644 index 0000000..945ad7e --- /dev/null +++ b/server/Controllers/labAdminController.js @@ -0,0 +1,331 @@ +// controllers/labAdminController.js +import mongoose from 'mongoose'; +import Lab from '../models/Lab/Lab.js'; +import LabAdmin from '../models/Users/LabAdmin.js'; +import LabStaff from '../models/Users/LabStaff.js'; +import User from '../models/Users/User.js'; +import { uploadToCloudinary } from '../services/uploadService.js'; + +// Create lab admin profile +export const createLabAdminProfile = async (req, res) => { + try { + const { userId } = req.user; + const { firstName, lastName, phoneNumber } = req.body; + + if (!mongoose.Types.ObjectId.isValid(userId)) { + return res.status(400).json({ message: 'Invalid user ID format' }); + } + + const existingAdmin = await LabAdmin.findOne({ userId }); + if (existingAdmin) { + return res.status(409).json({ message: 'Lab admin profile already exists' }); + } + + if (!firstName || !lastName || !phoneNumber) { + return res + .status(400) + .json({ message: 'First name, last name, and phone number are required' }); + } + + const labAdmin = new LabAdmin({ + userId, + firstName, + lastName, + phoneNumber, + }); + + await labAdmin.save(); + + res.status(201).json({ + message: 'Lab admin profile created successfully', + labAdmin: labAdmin.toObject({ getters: true, versionKey: false }), + }); + } catch (error) { + console.error('Error creating lab admin profile:', error); + res.status(500).json({ + message: 'Failed to create lab admin profile', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; + +// Create lab +export const createLab = async (req, res) => { + try { + const { userId, profileId } = req.user; + const labData = req.body; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin) { + return res.status(404).json({ message: 'Lab admin profile not found' }); + } + + if (labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin can only manage one lab' }); + } + + if (!labData.name || !labData.contact?.phone) { + return res.status(400).json({ message: 'Lab name and contact phone are required' }); + } + + // Handle file uploads + if (req.files?.logo) { + const result = await uploadToCloudinary(req.files.logo[0].path); + labData.logo = result.url; + } + + if (req.files?.photos) { + const photoUrls = await Promise.all( + req.files.photos.map(async (file) => { + const result = await uploadToCloudinary(file.path); + return result.url; + }) + ); + labData.photos = photoUrls; + } + + const lab = new Lab({ + ...labData, + labAdminId: profileId, + }); + + await lab.save(); + + // Update lab admin with labId + await LabAdmin.findByIdAndUpdate(profileId, { labId: lab._id }); + + res.status(201).json({ + message: 'Lab created successfully', + lab: lab.toObject({ getters: true, versionKey: false }), + }); + } catch (error) { + console.error('Error creating lab:', error); + res.status(500).json({ + message: 'Failed to create lab', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; + +// Add staff member to lab +export const addStaffMember = async (req, res) => { + try { + const { profileId } = req.user; + const { email, password, firstName, lastName, phoneNumber, role } = req.body; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + // Create user account for staff + const existingUser = await User.findOne({ email }); + if (existingUser) { + return res.status(409).json({ message: 'User with this email already exists' }); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const user = new User({ + email, + password: hashedPassword, + role: 'lab_staff', + }); + await user.save(); + + // Create staff profile + const staff = new LabStaff({ + userId: user._id, + firstName, + lastName, + phoneNumber, + labId: labAdmin.labId, + role: role || 'assistant', + }); + await staff.save(); + + // Add staff to lab + await Lab.findByIdAndUpdate(labAdmin.labId, { $push: { staff: staff._id } }); + + res.status(201).json({ + message: 'Staff member added successfully', + staff: staff.toObject({ getters: true, versionKey: false }), + }); + } catch (error) { + console.error('Error adding staff member:', error); + res.status(500).json({ + message: 'Failed to add staff member', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; + +// Get all staff members +export const getLabStaff = async (req, res) => { + try { + const { profileId } = req.user; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + const staff = await LabStaff.find({ labId: labAdmin.labId }).populate('userId', 'email').lean(); + + res.json({ + count: staff.length, + staff, + }); + } catch (error) { + console.error('Error fetching staff:', error); + res.status(500).json({ message: 'Failed to fetch staff' }); + } +}; + +// Update test +export const updateTest = async (req, res) => { + try { + const { profileId } = req.user; + const { testId } = req.params; + const updates = req.body; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + const lab = await Lab.findOneAndUpdate( + { _id: labAdmin.labId, 'tests._id': testId }, + { $set: { 'tests.$': { _id: testId, ...updates } } }, + { new: true, runValidators: true } + ); + + if (!lab) { + return res.status(404).json({ message: 'Test not found' }); + } + + res.json({ + message: 'Test updated successfully', + lab, + }); + } catch (error) { + console.error('Error updating test:', error); + res.status(500).json({ message: 'Failed to update test' }); + } +}; + +// Get lab info +export const getLabInfo = async (req, res) => { + try { + const { profileId } = req.user; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + const lab = await Lab.findById(labAdmin.labId) + .populate('staff', 'firstName lastName role phoneNumber') + .lean(); + + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + res.json({ lab }); + } catch (error) { + console.error('Error fetching lab info:', error); + res.status(500).json({ message: 'Failed to fetch lab info' }); + } +}; + +export const addTest = async (req, res) => { + try { + const { profileId } = req.user; + const { + testName, + testCode, + category, + description, + price, + preparationInstructions, + sampleType, + homeCollectionAvailable, + homeCollectionFee, + reportDeliveryTime, + requiresEquipment, + equipmentName, + } = req.body; + + const LabAdmin = (await import('../models/Users/LabAdmin.js')).default; + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + if (!testName || !category || !price) { + return res.status(400).json({ + message: 'Test name, category, and price are required', + }); + } + + // Auto-determine home collection based on category + let canBeHomeCollected = + homeCollectionAvailable !== undefined ? homeCollectionAvailable : false; + + let needsEquipment = requiresEquipment !== undefined ? requiresEquipment : false; + + // Automatic settings based on category + const homeCollectionCategories = [ + 'blood_test', + 'urine_test', + 'stool_test', + 'saliva_test', + 'swab_test', + ]; + + const equipmentCategories = ['imaging', 'endoscopy', 'ecg', 'pulmonary_function']; + + if (homeCollectionCategories.includes(category)) { + canBeHomeCollected = true; + } + + if (equipmentCategories.includes(category)) { + needsEquipment = true; + canBeHomeCollected = false; // Equipment tests cannot be home collected + } + + const testData = { + testName, + testCode, + category, + description, + price, + preparationInstructions, + sampleType, + homeCollectionAvailable: canBeHomeCollected, + homeCollectionFee: homeCollectionFee || 0, + reportDeliveryTime, + requiresEquipment: needsEquipment, + equipmentName: needsEquipment ? equipmentName : undefined, + isActive: true, + }; + + const Lab = (await import('../models/Lab/Lab.js')).default; + const lab = await Lab.findByIdAndUpdate( + labAdmin.labId, + { $push: { tests: testData } }, + { new: true, runValidators: true } + ); + + res.status(201).json({ + message: 'Test added successfully', + test: lab.tests[lab.tests.length - 1], + }); + } catch (error) { + console.error('Error adding test:', error); + res.status(500).json({ + message: 'Failed to add test', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; diff --git a/server/Controllers/labAppointmentController.js b/server/Controllers/labAppointmentController.js new file mode 100644 index 0000000..b89c1a7 --- /dev/null +++ b/server/Controllers/labAppointmentController.js @@ -0,0 +1,415 @@ +// controllers/labAppointmentController.js +import mongoose from 'mongoose'; +import LabAppointment from '../models/Appointment/LabAppointment.js'; +import Lab from '../models/Lab/Lab.js'; +import Patient from '../models/Users/Patient.js'; +import LabStaff from '../models/Users/LabStaff.js'; +import LabReport from '../models/Report/LabReport.js'; + +// Book lab appointment (by patient) +export const bookLabAppointment = async (req, res) => { + try { + const { profileId } = req.user; + const { + labId, + tests, // Array of test IDs + collectionType, + appointmentDate, + appointmentTime, + collectionAddress, + suggestedByDoctorId, + } = req.body; + + if ( + !labId || + !tests || + tests.length === 0 || + !collectionType || + !appointmentDate || + !appointmentTime + ) { + return res.status(400).json({ message: 'Required fields missing' }); + } + + const lab = await Lab.findById(labId); + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + // Fetch full test details from lab + const selectedTests = []; + let allTestsSupportHomeCollection = true; + let anyTestRequiresEquipment = false; + + for (const testId of tests) { + const test = lab.tests.id(testId); + if (!test) { + return res.status(404).json({ + message: `Test with ID ${testId} not found in this lab`, + }); + } + + if (!test.isActive) { + return res.status(400).json({ + message: `Test "${test.testName}" is currently not available`, + }); + } + + // Check if test supports home collection + if (!test.homeCollectionAvailable) { + allTestsSupportHomeCollection = false; + } + + // Check if test requires equipment (like X-ray, CT scan) + if (test.requiresEquipment) { + anyTestRequiresEquipment = true; + } + + selectedTests.push({ + testId: test._id, + testName: test.testName, + testCode: test.testCode, + category: test.category, + price: test.price, + homeCollectionAvailable: test.homeCollectionAvailable, + requiresEquipment: test.requiresEquipment, + }); + } + + // Validation: If user selected home collection, check if all tests support it + if (collectionType === 'home_collection') { + if (!allTestsSupportHomeCollection) { + const nonHomeCollectionTests = selectedTests + .filter((t) => !t.homeCollectionAvailable) + .map((t) => t.testName); + + return res.status(400).json({ + message: 'Home collection not available for some selected tests', + nonHomeCollectionTests, + suggestion: 'These tests require you to visit the lab', + }); + } + + if (anyTestRequiresEquipment) { + const equipmentTests = selectedTests + .filter((t) => t.requiresEquipment) + .map((t) => t.testName); + + return res.status(400).json({ + message: 'Some tests require special equipment and must be done at the lab', + equipmentRequiredTests: equipmentTests, + suggestion: 'Please visit the lab for these tests', + }); + } + + if (!collectionAddress) { + return res.status(400).json({ + message: 'Collection address is required for home collection', + }); + } + } + + // Calculate total amount + let totalAmount = selectedTests.reduce((sum, test) => sum + test.price, 0); + let homeCollectionFee = 0; + + if (collectionType === 'home_collection') { + // Use test-specific fees or general lab fee + homeCollectionFee = selectedTests.reduce((sum, test) => { + const testFromLab = lab.tests.id(test.testId); + return sum + (testFromLab.homeCollectionFee || 0); + }, 0); + + // If no test-specific fees, use general lab fee + if (homeCollectionFee === 0) { + homeCollectionFee = lab.generalHomeCollectionFee; + } + + totalAmount += homeCollectionFee; + } + + const appointment = new LabAppointment({ + patientId: profileId, + labId, + tests: selectedTests, + collectionType, + canBeHomeCollected: allTestsSupportHomeCollection, + appointmentDate: new Date(appointmentDate), + appointmentTime, + collectionAddress: collectionType === 'home_collection' ? collectionAddress : undefined, + suggestedByDoctorId: suggestedByDoctorId || undefined, + totalAmount, + homeCollectionFee: collectionType === 'home_collection' ? homeCollectionFee : 0, + status: 'pending', + }); + + await appointment.save(); + + // Add to patient's lab appointments + await Patient.findByIdAndUpdate(profileId, { $push: { labAppointments: appointment._id } }); + + res.status(201).json({ + message: 'Lab appointment booked successfully', + appointment: appointment.toObject({ getters: true }), + }); + } catch (error) { + console.error('Error booking lab appointment:', error); + res.status(500).json({ + message: 'Failed to book lab appointment', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; + +// Check if tests support home collection (helper endpoint) +export const checkHomeCollectionAvailability = async (req, res) => { + try { + const { labId, testIds } = req.body; + + if (!labId || !testIds || testIds.length === 0) { + return res.status(400).json({ message: 'Lab ID and test IDs are required' }); + } + + const lab = await Lab.findById(labId); + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + const testDetails = []; + let allSupport = true; + let anyRequiresEquipment = false; + let totalHomeCollectionFee = 0; + + for (const testId of testIds) { + const test = lab.tests.id(testId); + if (test) { + testDetails.push({ + testId: test._id, + testName: test.testName, + category: test.category, + homeCollectionAvailable: test.homeCollectionAvailable, + requiresEquipment: test.requiresEquipment, + homeCollectionFee: test.homeCollectionFee, + }); + + if (!test.homeCollectionAvailable) { + allSupport = false; + } + + if (test.requiresEquipment) { + anyRequiresEquipment = true; + } + + totalHomeCollectionFee += test.homeCollectionFee; + } + } + + // If no test-specific fees, use general lab fee + if (totalHomeCollectionFee === 0 && allSupport) { + totalHomeCollectionFee = lab.generalHomeCollectionFee; + } + + res.json({ + homeCollectionAvailable: allSupport && !anyRequiresEquipment, + allTestsSupportHomeCollection: allSupport, + anyTestRequiresEquipment, + homeCollectionFee: totalHomeCollectionFee, + testDetails, + message: !allSupport + ? 'Some tests do not support home collection' + : anyRequiresEquipment + ? 'Some tests require special equipment and must be done at the lab' + : 'All tests support home collection', + }); + } catch (error) { + console.error('Error checking home collection availability:', error); + res.status(500).json({ message: 'Failed to check availability' }); + } +}; + +// Get patient lab appointments +export const getPatientLabAppointments = async (req, res) => { + try { + const { profileId } = req.user; + const { status } = req.query; + + const query = { patientId: profileId }; + if (status) { + query.status = status; + } + + const appointments = await LabAppointment.find(query) + .populate('labId', 'name address contact') + .populate('assignedStaffId', 'firstName lastName phoneNumber') + .populate('suggestedByDoctorId', 'firstName lastName specialization') + .populate('reportId') + .sort({ appointmentDate: -1 }) + .lean(); + + res.json({ + count: appointments.length, + appointments, + }); + } catch (error) { + console.error('Error fetching patient lab appointments:', error); + res.status(500).json({ message: 'Failed to fetch appointments' }); + } +}; + +// Get all lab appointments (for lab admin/staff) +export const getLabAppointments = async (req, res) => { + try { + const { profileId, role } = req.user; + const { status, date, collectionType } = req.query; + + let labId; + + if (role === 'lab_admin') { + const LabAdmin = (await import('../models/Users/LabAdmin.js')).default; + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + labId = labAdmin.labId; + } else if (role === 'lab_staff') { + const LabStaff = (await import('../models/Users/LabStaff.js')).default; + const staff = await LabStaff.findById(profileId); + if (!staff) { + return res.status(404).json({ message: 'Staff not found' }); + } + labId = staff.labId; + } + + const query = { labId }; + if (status) { + query.status = status; + } + if (collectionType) { + query.collectionType = collectionType; + } + if (date) { + const dateObj = new Date(date); + query.appointmentDate = { + $gte: new Date(dateObj.setHours(0, 0, 0, 0)), + $lte: new Date(dateObj.setHours(23, 59, 59, 999)), + }; + } + + const appointments = await LabAppointment.find(query) + .populate('patientId', 'firstName lastName phoneNumber address') + .populate('assignedStaffId', 'firstName lastName phoneNumber') + .populate('suggestedByDoctorId', 'firstName lastName') + .sort({ appointmentDate: 1 }) + .lean(); + + // Separate home collection and visit lab appointments + const homeCollectionAppointments = appointments.filter( + (a) => a.collectionType === 'home_collection' + ); + const visitLabAppointments = appointments.filter((a) => a.collectionType === 'visit_lab'); + + res.json({ + count: appointments.length, + appointments, + summary: { + homeCollection: homeCollectionAppointments.length, + visitLab: visitLabAppointments.length, + }, + }); + } catch (error) { + console.error('Error fetching lab appointments:', error); + res.status(500).json({ message: 'Failed to fetch appointments' }); + } +}; + +// Assign staff for home collection +export const assignStaffForCollection = async (req, res) => { + try { + const { appointmentId } = req.params; + const { staffId } = req.body; + + if (!mongoose.Types.ObjectId.isValid(staffId)) { + return res.status(400).json({ message: 'Invalid staff ID' }); + } + + const appointment = await LabAppointment.findById(appointmentId); + if (!appointment) { + return res.status(404).json({ message: 'Appointment not found' }); + } + + if (appointment.collectionType !== 'home_collection') { + return res.status(400).json({ + message: 'This appointment is not for home collection', + collectionType: appointment.collectionType, + }); + } + + const LabStaff = (await import('../models/Users/LabStaff.js')).default; + const staff = await LabStaff.findById(staffId); + if (!staff) { + return res.status(404).json({ message: 'Staff not found' }); + } + + if (!staff.isAvailableForHomeCollection) { + return res.status(400).json({ + message: 'Staff member is not available for home collection', + }); + } + + appointment.assignedStaffId = staffId; + appointment.status = 'confirmed'; + await appointment.save(); + + // Add to staff current assignments + await LabStaff.findByIdAndUpdate(staffId, { $push: { currentAssignments: appointmentId } }); + + res.json({ + message: 'Staff assigned successfully', + appointment: await appointment.populate('assignedStaffId', 'firstName lastName phoneNumber'), + }); + } catch (error) { + console.error('Error assigning staff:', error); + res.status(500).json({ message: 'Failed to assign staff' }); + } +}; + +// Update appointment status +export const updateLabAppointmentStatus = async (req, res) => { + try { + const { appointmentId } = req.params; + const { status } = req.body; + + const validStatuses = [ + 'pending', + 'confirmed', + 'sample_collected', + 'processing', + 'completed', + 'cancelled', + ]; + if (!validStatuses.includes(status)) { + return res.status(400).json({ message: 'Invalid status' }); + } + + const appointment = await LabAppointment.findByIdAndUpdate( + appointmentId, + { status }, + { new: true, runValidators: true } + ) + .populate('patientId', 'firstName lastName') + .populate('labId', 'name') + .lean(); + + if (!appointment) { + return res.status(404).json({ message: 'Appointment not found' }); + } + + res.json({ + message: 'Appointment status updated successfully', + appointment, + }); + } catch (error) { + console.error('Error updating appointment status:', error); + res.status(500).json({ message: 'Failed to update status' }); + } +}; diff --git a/server/Controllers/labController.js b/server/Controllers/labController.js new file mode 100644 index 0000000..17b8bd8 --- /dev/null +++ b/server/Controllers/labController.js @@ -0,0 +1,79 @@ +// controllers/labController.js (for patient/doctor to search labs) +import Lab from '../models/Lab/Lab.js'; + +// Search labs +export const searchLabs = async (req, res) => { + try { + const { name, city, testName } = req.query; + + const query = { isActive: true }; + + if (name) { + query.name = { $regex: name, $options: 'i' }; + } + + if (city) { + query['address.city'] = { $regex: city, $options: 'i' }; + } + + if (testName) { + query['tests.testName'] = { $regex: testName, $options: 'i' }; + } + + const labs = await Lab.find(query) + .select( + 'name description address contact logo ratings tests homeCollectionAvailable homeCollectionFee' + ) + .lean(); + + res.json({ + count: labs.length, + labs, + }); + } catch (error) { + console.error('Error searching labs:', error); + res.status(500).json({ message: 'Failed to search labs' }); + } +}; + +// Get lab details +export const getLabDetails = async (req, res) => { + try { + const { labId } = req.params; + + const lab = await Lab.findById(labId).select('-staff -labAdminId').lean(); + + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + res.json({ lab }); + } catch (error) { + console.error('Error fetching lab details:', error); + res.status(500).json({ message: 'Failed to fetch lab details' }); + } +}; + +// Get lab tests +export const getLabTests = async (req, res) => { + try { + const { labId } = req.params; + + const lab = await Lab.findById(labId).select('tests name').lean(); + + if (!lab) { + return res.status(404).json({ message: 'Lab not found' }); + } + + const activeTests = lab.tests.filter((test) => test.isActive); + + res.json({ + labName: lab.name, + count: activeTests.length, + tests: activeTests, + }); + } catch (error) { + console.error('Error fetching lab tests:', error); + res.status(500).json({ message: 'Failed to fetch tests' }); + } +}; diff --git a/server/Controllers/labReportController.js b/server/Controllers/labReportController.js new file mode 100644 index 0000000..f681f64 --- /dev/null +++ b/server/Controllers/labReportController.js @@ -0,0 +1,174 @@ +// controllers/labReportController.js +import mongoose from 'mongoose'; +import LabReport from '../models/Report/LabReport.js'; +import LabAppointment from '../models/Appointment/LabAppointment.js'; +import { uploadToCloudinary } from '../services/uploadService.js'; + +// Upload lab report (by lab admin/staff) +export const uploadLabReport = async (req, res) => { + try { + const { appointmentId } = req.params; + const { testResults } = req.body; + + if (!req.file) { + return res.status(400).json({ message: 'Report file is required' }); + } + + const appointment = await LabAppointment.findById(appointmentId); + if (!appointment) { + return res.status(404).json({ message: 'Appointment not found' }); + } + + // Upload report file + const uploadResult = await uploadToCloudinary(req.file.path); + + const report = new LabReport({ + appointmentId, + patientId: appointment.patientId, + labId: appointment.labId, + doctorId: appointment.suggestedByDoctorId || undefined, + testResults: testResults ? JSON.parse(testResults) : [], + reportFile: { + url: uploadResult.url, + fileName: req.file.originalname, + }, + isSharedWithDoctor: !!appointment.suggestedByDoctorId, + }); + + await report.save(); + + // Update appointment + appointment.reportId = report._id; + appointment.status = 'completed'; + await appointment.save(); + + res.status(201).json({ + message: 'Lab report uploaded successfully', + report: report.toObject({ getters: true }), + }); + } catch (error) { + console.error('Error uploading lab report:', error); + res.status(500).json({ message: 'Failed to upload report' }); + } +}; + +// Get patient's lab reports +export const getPatientLabReports = async (req, res) => { + try { + const { profileId } = req.user; + + const reports = await LabReport.find({ patientId: profileId }) + .populate('labId', 'name address') + .populate('doctorId', 'firstName lastName specialization') + .populate('appointmentId', 'tests appointmentDate') + .sort({ reportDate: -1 }) + .lean(); + + res.json({ + count: reports.length, + reports, + }); + } catch (error) { + console.error('Error fetching patient reports:', error); + res.status(500).json({ message: 'Failed to fetch reports' }); + } +}; + +// Get doctor's patient lab reports (reports shared with doctor) +export const getDoctorPatientReports = async (req, res) => { + try { + const { profileId } = req.user; + + const reports = await LabReport.find({ + doctorId: profileId, + isSharedWithDoctor: true, + }) + .populate('patientId', 'firstName lastName dateOfBirth gender') + .populate('labId', 'name') + .populate('appointmentId', 'tests appointmentDate') + .sort({ reportDate: -1 }) + .lean(); + + res.json({ + count: reports.length, + reports, + }); + } catch (error) { + console.error('Error fetching doctor reports:', error); + res.status(500).json({ message: 'Failed to fetch reports' }); + } +}; + +// Add doctor remarks to report +export const addDoctorRemarks = async (req, res) => { + try { + const { reportId } = req.params; + const { profileId } = req.user; + const { remarks } = req.body; + + if (!remarks) { + return res.status(400).json({ message: 'Remarks are required' }); + } + + const report = await LabReport.findById(reportId); + if (!report) { + return res.status(404).json({ message: 'Report not found' }); + } + + if (report.doctorId && report.doctorId.toString() !== profileId.toString()) { + return res.status(403).json({ message: 'Unauthorized to add remarks to this report' }); + } + + report.doctorRemarks = { + remarks, + addedAt: new Date(), + addedBy: profileId, + }; + + await report.save(); + + res.json({ + message: 'Remarks added successfully', + report, + }); + } catch (error) { + console.error('Error adding remarks:', error); + res.status(500).json({ message: 'Failed to add remarks' }); + } +}; + +// Get specific report details +export const getReportDetails = async (req, res) => { + try { + const { reportId } = req.params; + const { profileId, role } = req.user; + + const report = await LabReport.findById(reportId) + .populate('patientId', 'firstName lastName dateOfBirth gender') + .populate('labId', 'name address contact') + .populate('doctorId', 'firstName lastName specialization') + .populate('appointmentId') + .lean(); + + if (!report) { + return res.status(404).json({ message: 'Report not found' }); + } + + // Authorization check + if (role === 'patient' && report.patientId._id.toString() !== profileId.toString()) { + return res.status(403).json({ message: 'Unauthorized' }); + } + + if ( + role === 'doctor' && + (!report.doctorId || report.doctorId._id.toString() !== profileId.toString()) + ) { + return res.status(403).json({ message: 'Unauthorized' }); + } + + res.json({ report }); + } catch (error) { + console.error('Error fetching report details:', error); + res.status(500).json({ message: 'Failed to fetch report' }); + } +}; From c64c0d026e3147375c3e1f060270116bdf3778d3 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:32:11 +0530 Subject: [PATCH 5/9] adds lab routes --- server/Routes/labAdminRoutes.js | 34 +++++++++++++++++++++++++++ server/Routes/labAppointmentRoutes.js | 30 +++++++++++++++++++++++ server/Routes/labReportRoutes.js | 27 +++++++++++++++++++++ server/Routes/labRoutes.js | 12 ++++++++++ 4 files changed, 103 insertions(+) create mode 100644 server/Routes/labAdminRoutes.js create mode 100644 server/Routes/labAppointmentRoutes.js create mode 100644 server/Routes/labReportRoutes.js create mode 100644 server/Routes/labRoutes.js diff --git a/server/Routes/labAdminRoutes.js b/server/Routes/labAdminRoutes.js new file mode 100644 index 0000000..3712ed7 --- /dev/null +++ b/server/Routes/labAdminRoutes.js @@ -0,0 +1,34 @@ +// routes/labAdminRoutes.js +import express from 'express'; +import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { + createLabAdminProfile, + createLab, + addStaffMember, + getLabStaff, + addTest, + updateTest, + getLabInfo, +} from '../Controllers/labAdminController.js'; +import upload from '../middleware/upload.js'; + +const router = express.Router(); + +router.post('/profile', authenticate, authorize('lab_admin'), createLabAdminProfile); +router.post( + '/lab', + authenticate, + authorize('lab_admin'), + upload.fields([ + { name: 'logo', maxCount: 1 }, + { name: 'photos', maxCount: 10 }, + ]), + createLab +); +router.post('/staff', authenticate, authorize('lab_admin'), addStaffMember); +router.get('/staff', authenticate, authorize('lab_admin'), getLabStaff); +router.post('/tests', authenticate, authorize('lab_admin'), addTest); +router.put('/tests/:testId', authenticate, authorize('lab_admin'), updateTest); +router.get('/lab/info', authenticate, authorize('lab_admin'), getLabInfo); + +export default router; diff --git a/server/Routes/labAppointmentRoutes.js b/server/Routes/labAppointmentRoutes.js new file mode 100644 index 0000000..6ae9113 --- /dev/null +++ b/server/Routes/labAppointmentRoutes.js @@ -0,0 +1,30 @@ +// routes/labAppointmentRoutes.js +import express from 'express'; +import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { + bookLabAppointment, + getPatientLabAppointments, + getLabAppointments, + assignStaffForCollection, + updateLabAppointmentStatus, +} from '../Controllers/labAppointmentController.js'; + +const router = express.Router(); + +router.post('/book', authenticate, authorize('patient'), bookLabAppointment); +router.get('/patient', authenticate, authorize('patient'), getPatientLabAppointments); +router.get('/lab', authenticate, authorize('lab_admin', 'lab_staff'), getLabAppointments); +router.put( + '/:appointmentId/assign-staff', + authenticate, + authorize('lab_admin'), + assignStaffForCollection +); +router.put( + '/:appointmentId/status', + authenticate, + authorize('lab_admin', 'lab_staff'), + updateLabAppointmentStatus +); + +export default router; diff --git a/server/Routes/labReportRoutes.js b/server/Routes/labReportRoutes.js new file mode 100644 index 0000000..5c92599 --- /dev/null +++ b/server/Routes/labReportRoutes.js @@ -0,0 +1,27 @@ +// routes/labReportRoutes.js +import express from 'express'; +import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { + uploadLabReport, + getPatientLabReports, + getDoctorPatientReports, + addDoctorRemarks, + getReportDetails, +} from '../Controllers/labReportController.js'; +import upload from '../middleware/upload.js'; + +const router = express.Router(); + +router.post( + '/upload/:appointmentId', + authenticate, + authorize('lab_admin', 'lab_staff'), + upload.single('reportFile'), + uploadLabReport +); +router.get('/patient', authenticate, authorize('patient'), getPatientLabReports); +router.get('/doctor', authenticate, authorize('doctor'), getDoctorPatientReports); +router.put('/:reportId/remarks', authenticate, authorize('doctor'), addDoctorRemarks); +router.get('/:reportId', authenticate, getReportDetails); + +export default router; diff --git a/server/Routes/labRoutes.js b/server/Routes/labRoutes.js new file mode 100644 index 0000000..5cc23c4 --- /dev/null +++ b/server/Routes/labRoutes.js @@ -0,0 +1,12 @@ +// routes/labRoutes.js +import express from 'express'; +import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { searchLabs, getLabDetails, getLabTests } from '../Controllers/labController.js'; + +const router = express.Router(); + +router.get('/search', authenticate, searchLabs); +router.get('/:labId', authenticate, getLabDetails); +router.get('/:labId/tests', authenticate, getLabTests); + +export default router; From a7f55b5064e62604d4dc03e24d11540d6e2c5378 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 10:56:18 +0530 Subject: [PATCH 6/9] adds routes in index and fixes imports in labadmincontroller --- server/Controllers/authController.js | 2 +- server/Controllers/labAdminController.js | 4 ++-- server/Routes/labAdminRoutes.js | 2 +- server/index.js | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/server/Controllers/authController.js b/server/Controllers/authController.js index 33f1c63..43812e1 100644 --- a/server/Controllers/authController.js +++ b/server/Controllers/authController.js @@ -17,7 +17,7 @@ export const register = async (req, res) => { } // Validate role if provided - if (role && !['patient', 'doctor', 'admin'].includes(role)) { + if (role && !['patient', 'doctor', 'admin', 'lab_admin', 'lab_staff'].includes(role)) { return res.status(400).json({ message: 'Invalid role specified' }); } diff --git a/server/Controllers/labAdminController.js b/server/Controllers/labAdminController.js index 945ad7e..7886507 100644 --- a/server/Controllers/labAdminController.js +++ b/server/Controllers/labAdminController.js @@ -1,8 +1,8 @@ // controllers/labAdminController.js import mongoose from 'mongoose'; import Lab from '../models/Lab/Lab.js'; -import LabAdmin from '../models/Users/LabAdmin.js'; -import LabStaff from '../models/Users/LabStaff.js'; +import LabAdmin from '../models/Lab/LabAdmin.js'; +import LabStaff from '../models/Lab/LabStaff.js'; import User from '../models/Users/User.js'; import { uploadToCloudinary } from '../services/uploadService.js'; diff --git a/server/Routes/labAdminRoutes.js b/server/Routes/labAdminRoutes.js index 3712ed7..dc52e6a 100644 --- a/server/Routes/labAdminRoutes.js +++ b/server/Routes/labAdminRoutes.js @@ -10,7 +10,7 @@ import { updateTest, getLabInfo, } from '../Controllers/labAdminController.js'; -import upload from '../middleware/upload.js'; +import upload from '../Middleware/upload.js'; const router = express.Router(); diff --git a/server/index.js b/server/index.js index cfbae11..0d2f84d 100644 --- a/server/index.js +++ b/server/index.js @@ -19,6 +19,8 @@ import appointmentScheduler from './services/appointmentScheduler.js'; import pushNotificationServiceRoutes from './Routes/pushNotificationRoutes.js'; //QuickMed import medicineRoutes from './Routes/QuickMed/medicineRoutes.js'; +//QuickLab +import labAdminRoutes from './Routes/labAdminRoutes.js'; const app = express(); @@ -45,6 +47,8 @@ app.use('/api/public', publicRoutes); app.use('/api/ratings', ratingRoutes); app.use('/api/notifications', notificationRoutes); app.use('/api/push-notifications', pushNotificationServiceRoutes); +//QuickLabs Route +app.use('/api/lab-admin', labAdminRoutes); //QuickMed Routes app.use('/api/medicines', medicineRoutes); From d9c07bfea3d7c7261b7254fb1456d7dc15543398 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 11:23:44 +0530 Subject: [PATCH 7/9] adds routes in index and fixes imports in labadmincontroller --- server/Controllers/labAdminController.js | 1 - server/Controllers/labAppointmentController.js | 6 +++--- server/Controllers/labReportController.js | 4 ++-- server/index.js | 3 +++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/Controllers/labAdminController.js b/server/Controllers/labAdminController.js index 7886507..c58ed0c 100644 --- a/server/Controllers/labAdminController.js +++ b/server/Controllers/labAdminController.js @@ -256,7 +256,6 @@ export const addTest = async (req, res) => { equipmentName, } = req.body; - const LabAdmin = (await import('../models/Users/LabAdmin.js')).default; const labAdmin = await LabAdmin.findById(profileId); if (!labAdmin || !labAdmin.labId) { return res.status(400).json({ message: 'Lab admin not associated with any lab' }); diff --git a/server/Controllers/labAppointmentController.js b/server/Controllers/labAppointmentController.js index b89c1a7..875507b 100644 --- a/server/Controllers/labAppointmentController.js +++ b/server/Controllers/labAppointmentController.js @@ -1,10 +1,10 @@ // controllers/labAppointmentController.js import mongoose from 'mongoose'; -import LabAppointment from '../models/Appointment/LabAppointment.js'; +import LabAppointment from '../models/Lab/LabAppointment.js'; import Lab from '../models/Lab/Lab.js'; import Patient from '../models/Users/Patient.js'; -import LabStaff from '../models/Users/LabStaff.js'; -import LabReport from '../models/Report/LabReport.js'; +import LabStaff from '../models/Lab/LabStaff.js'; +import LabReport from '../models/Lab/LabReport.js'; // Book lab appointment (by patient) export const bookLabAppointment = async (req, res) => { diff --git a/server/Controllers/labReportController.js b/server/Controllers/labReportController.js index f681f64..34a182e 100644 --- a/server/Controllers/labReportController.js +++ b/server/Controllers/labReportController.js @@ -1,7 +1,7 @@ // controllers/labReportController.js import mongoose from 'mongoose'; -import LabReport from '../models/Report/LabReport.js'; -import LabAppointment from '../models/Appointment/LabAppointment.js'; +import LabReport from '../models/Lab/LabReport.js'; +import LabAppointment from '../models/Lab/LabAppointment.js'; import { uploadToCloudinary } from '../services/uploadService.js'; // Upload lab report (by lab admin/staff) diff --git a/server/index.js b/server/index.js index 0d2f84d..2bdca19 100644 --- a/server/index.js +++ b/server/index.js @@ -21,6 +21,9 @@ import pushNotificationServiceRoutes from './Routes/pushNotificationRoutes.js'; import medicineRoutes from './Routes/QuickMed/medicineRoutes.js'; //QuickLab import labAdminRoutes from './Routes/labAdminRoutes.js'; +import labAppointmentRoutes from './Routes/labAppointmentRoutes.js'; +import labReportRoutes from './Routes/labReportRoutes.js'; +import labRoutes from './Routes/labRoutes.js'; const app = express(); From a68100652a7dcb9525c07d06e5acab2c6d157f22 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Thu, 11 Dec 2025 11:35:55 +0530 Subject: [PATCH 8/9] adds routes in index.js and gives lab staff independece --- server/Controllers/labAdminController.js | 153 +++++++++--- server/Controllers/labStaffController.js | 306 +++++++++++++++++++++++ server/Routes/labAdminRoutes.js | 14 +- server/Routes/labStaffRoutes.js | 57 +++++ server/index.js | 6 +- server/models/Lab/LabStaff.js | 24 +- 6 files changed, 523 insertions(+), 37 deletions(-) create mode 100644 server/Controllers/labStaffController.js create mode 100644 server/Routes/labStaffRoutes.js diff --git a/server/Controllers/labAdminController.js b/server/Controllers/labAdminController.js index c58ed0c..4a9902d 100644 --- a/server/Controllers/labAdminController.js +++ b/server/Controllers/labAdminController.js @@ -107,59 +107,148 @@ export const createLab = async (req, res) => { } }; -// Add staff member to lab -export const addStaffMember = async (req, res) => { +export const searchStaff = async (req, res) => { + try { + const { email, staffId } = req.query; + + if (!email && !staffId) { + return res.status(400).json({ message: 'Email or staff ID is required' }); + } + + let staff; + + if (staffId) { + if (!mongoose.Types.ObjectId.isValid(staffId)) { + return res.status(400).json({ message: 'Invalid staff ID format' }); + } + staff = await LabStaff.findById(staffId).populate('userId', 'email').lean(); + } else if (email) { + const user = await User.findOne({ email, role: 'lab_staff' }); + if (user) { + staff = await LabStaff.findOne({ userId: user._id }).populate('userId', 'email').lean(); + } + } + + if (!staff) { + return res.status(404).json({ message: 'Staff not found' }); + } + + // Check if staff is already assigned to a lab + if (staff.isAssignedToLab && staff.labId) { + return res.status(400).json({ + message: 'Staff is already assigned to another lab', + staff: { + id: staff._id, + firstName: staff.firstName, + lastName: staff.lastName, + email: staff.userId.email, + }, + }); + } + + res.json({ + message: 'Staff found', + staff: { + id: staff._id, + firstName: staff.firstName, + lastName: staff.lastName, + email: staff.userId.email, + phoneNumber: staff.phoneNumber, + role: staff.role, + qualifications: staff.qualifications, + experience: staff.experience, + isProfileComplete: staff.isProfileComplete, + }, + }); + } catch (error) { + console.error('Error searching staff:', error); + res.status(500).json({ message: 'Failed to search staff' }); + } +}; + +// Add existing staff to lab by staffId +export const addStaffToLab = async (req, res) => { try { const { profileId } = req.user; - const { email, password, firstName, lastName, phoneNumber, role } = req.body; + const { staffId } = req.body; + + if (!mongoose.Types.ObjectId.isValid(staffId)) { + return res.status(400).json({ message: 'Invalid staff ID format' }); + } const labAdmin = await LabAdmin.findById(profileId); if (!labAdmin || !labAdmin.labId) { return res.status(400).json({ message: 'Lab admin not associated with any lab' }); } - // Create user account for staff - const existingUser = await User.findOne({ email }); - if (existingUser) { - return res.status(409).json({ message: 'User with this email already exists' }); + const staff = await LabStaff.findById(staffId); + if (!staff) { + return res.status(404).json({ message: 'Staff not found' }); } - const hashedPassword = await bcrypt.hash(password, 10); - const user = new User({ - email, - password: hashedPassword, - role: 'lab_staff', - }); - await user.save(); + // Check if staff already assigned to a lab + if (staff.isAssignedToLab && staff.labId) { + return res.status(400).json({ + message: 'Staff is already assigned to another lab', + }); + } - // Create staff profile - const staff = new LabStaff({ - userId: user._id, - firstName, - lastName, - phoneNumber, - labId: labAdmin.labId, - role: role || 'assistant', - }); + // Assign staff to lab + staff.labId = labAdmin.labId; + staff.isAssignedToLab = true; await staff.save(); - // Add staff to lab - await Lab.findByIdAndUpdate(labAdmin.labId, { $push: { staff: staff._id } }); + // Add staff to lab's staff array + await Lab.findByIdAndUpdate(labAdmin.labId, { $push: { staff: staffId } }); - res.status(201).json({ - message: 'Staff member added successfully', + res.json({ + message: 'Staff added to lab successfully', staff: staff.toObject({ getters: true, versionKey: false }), }); } catch (error) { - console.error('Error adding staff member:', error); - res.status(500).json({ - message: 'Failed to add staff member', - error: process.env.NODE_ENV === 'development' ? error.message : undefined, + console.error('Error adding staff to lab:', error); + res.status(500).json({ message: 'Failed to add staff to lab' }); + } +}; + +// Remove staff from lab +export const removeStaffFromLab = async (req, res) => { + try { + const { profileId } = req.user; + const { staffId } = req.params; + + const labAdmin = await LabAdmin.findById(profileId); + if (!labAdmin || !labAdmin.labId) { + return res.status(400).json({ message: 'Lab admin not associated with any lab' }); + } + + const staff = await LabStaff.findById(staffId); + if (!staff) { + return res.status(404).json({ message: 'Staff not found' }); + } + + if (staff.labId.toString() !== labAdmin.labId.toString()) { + return res.status(403).json({ message: 'Staff does not belong to your lab' }); + } + + // Remove lab association + staff.labId = null; + staff.isAssignedToLab = false; + await staff.save(); + + // Remove from lab's staff array + await Lab.findByIdAndUpdate(labAdmin.labId, { $pull: { staff: staffId } }); + + res.json({ + message: 'Staff removed from lab successfully', }); + } catch (error) { + console.error('Error removing staff from lab:', error); + res.status(500).json({ message: 'Failed to remove staff from lab' }); } }; -// Get all staff members +// Get all staff members (existing method - keep as is) export const getLabStaff = async (req, res) => { try { const { profileId } = req.user; diff --git a/server/Controllers/labStaffController.js b/server/Controllers/labStaffController.js new file mode 100644 index 0000000..154ee25 --- /dev/null +++ b/server/Controllers/labStaffController.js @@ -0,0 +1,306 @@ +// controllers/labStaffController.js +import mongoose from 'mongoose'; +import LabStaff from '../models/Lab/LabStaff.js'; +import LabAppointment from '../models/Lab/LabAppointment.js'; +import Lab from '../models/Lab/Lab.js'; +import { uploadToCloudinary } from '../services/uploadService.js'; + +// Create staff profile (after user registration) +export const createStaffProfile = async (req, res) => { + try { + const { userId } = req.user; + const { firstName, lastName, phoneNumber, role, qualifications, experience } = req.body; + + if (!mongoose.Types.ObjectId.isValid(userId)) { + return res.status(400).json({ message: 'Invalid user ID format' }); + } + + const existingStaff = await LabStaff.findOne({ userId }); + if (existingStaff) { + return res.status(409).json({ message: 'Staff profile already exists' }); + } + + if (!firstName || !lastName || !phoneNumber) { + return res.status(400).json({ + message: 'First name, last name, and phone number are required', + }); + } + + // Handle profile picture upload + let profilePicture; + if (req.file) { + const result = await uploadToCloudinary(req.file.path); + profilePicture = result.url; + } + + const staff = new LabStaff({ + userId, + firstName, + lastName, + phoneNumber, + role: role || 'assistant', + qualifications: qualifications || [], + experience: experience || 0, + profilePicture, + isProfileComplete: true, + }); + + await staff.save(); + + res.status(201).json({ + message: 'Staff profile created successfully', + staff: staff.toObject({ getters: true, versionKey: false }), + staffId: staff._id, // Important: staff needs to share this with admin + }); + } catch (error) { + console.error('Error creating staff profile:', error); + res.status(500).json({ + message: 'Failed to create staff profile', + error: process.env.NODE_ENV === 'development' ? error.message : undefined, + }); + } +}; + +// Update staff profile +export const updateStaffProfile = async (req, res) => { + try { + const { profileId } = req.user; + const updates = req.body; + + if (!updates || (Object.keys(updates).length === 0 && !req.file)) { + return res.status(400).json({ message: 'No updates provided' }); + } + + // Handle profile picture update + if (req.file) { + const result = await uploadToCloudinary(req.file.path); + updates.profilePicture = result.url; + } + + const staff = await LabStaff.findByIdAndUpdate(profileId, updates, { + new: true, + runValidators: true, + }); + + if (!staff) { + return res.status(404).json({ message: 'Staff profile not found' }); + } + + res.json({ + message: 'Staff profile updated successfully', + staff: staff.toObject({ getters: true }), + }); + } catch (error) { + console.error('Error updating staff profile:', error); + res.status(500).json({ message: 'Failed to update staff profile' }); + } +}; + +// Get staff own profile +export const getStaffProfile = async (req, res) => { + try { + const { profileId } = req.user; + + const staff = await LabStaff.findById(profileId) + .populate('userId', 'email') + .populate('labId', 'name address contact') + .lean(); + + if (!staff) { + return res.status(404).json({ message: 'Staff profile not found' }); + } + + res.json({ + staff, + }); + } catch (error) { + console.error('Error fetching staff profile:', error); + res.status(500).json({ message: 'Failed to fetch staff profile' }); + } +}; + +// Check if staff profile exists +export const checkStaffProfileExists = async (req, res) => { + try { + const { userId } = req.user; + + const staff = await LabStaff.findOne({ userId }); + + if (staff) { + return res.json({ + exists: true, + isProfileComplete: staff.isProfileComplete, + isAssignedToLab: staff.isAssignedToLab, + staffId: staff._id, + labId: staff.labId, + }); + } + + res.json({ + exists: false, + isProfileComplete: false, + isAssignedToLab: false, + }); + } catch (error) { + console.error('Error checking staff profile:', error); + res.status(500).json({ message: 'Failed to check staff profile' }); + } +}; + +// Get staff assigned tasks/appointments +export const getMyAssignments = async (req, res) => { + try { + const { profileId } = req.user; + const { status, type } = req.query; // type: current or completed + + const staff = await LabStaff.findById(profileId); + if (!staff) { + return res.status(404).json({ message: 'Staff profile not found' }); + } + + let query = { assignedStaffId: profileId }; + + if (status) { + query.status = status; + } + + // Filter by current or completed + if (type === 'completed') { + query.status = 'completed'; + } else if (type === 'current') { + query.status = { $in: ['confirmed', 'sample_collected', 'processing'] }; + } + + const assignments = await LabAppointment.find(query) + .populate('patientId', 'firstName lastName phoneNumber address') + .populate('labId', 'name address') + .sort({ appointmentDate: 1, appointmentTime: 1 }) + .lean(); + + res.json({ + count: assignments.length, + assignments, + }); + } catch (error) { + console.error('Error fetching staff assignments:', error); + res.status(500).json({ message: 'Failed to fetch assignments' }); + } +}; + +// Get specific assignment details +export const getAssignmentDetails = async (req, res) => { + try { + const { profileId } = req.user; + const { appointmentId } = req.params; + + const staff = await LabStaff.findById(profileId); + if (!staff) { + return res.status(404).json({ message: 'Staff profile not found' }); + } + + const assignment = await LabAppointment.findById(appointmentId) + .populate('patientId', 'firstName lastName phoneNumber address emergencyContact') + .populate('labId', 'name address contact') + .lean(); + + if (!assignment) { + return res.status(404).json({ message: 'Assignment not found' }); + } + + // Check if this assignment belongs to this staff + if (assignment.assignedStaffId?.toString() !== profileId.toString()) { + return res.status(403).json({ message: 'Unauthorized to view this assignment' }); + } + + res.json({ + assignment, + }); + } catch (error) { + console.error('Error fetching assignment details:', error); + res.status(500).json({ message: 'Failed to fetch assignment details' }); + } +}; + +// Update assignment status (staff can update their own assignments) +export const updateMyAssignmentStatus = async (req, res) => { + try { + const { profileId } = req.user; + const { appointmentId } = req.params; + const { status, notes } = req.body; + + const validStatuses = ['confirmed', 'sample_collected', 'processing']; + if (!validStatuses.includes(status)) { + return res.status(400).json({ + message: + 'Invalid status. Staff can only update to: confirmed, sample_collected, processing', + }); + } + + const assignment = await LabAppointment.findById(appointmentId); + if (!assignment) { + return res.status(404).json({ message: 'Assignment not found' }); + } + + // Check if this assignment belongs to this staff + if (assignment.assignedStaffId?.toString() !== profileId.toString()) { + return res.status(403).json({ message: 'Unauthorized to update this assignment' }); + } + + assignment.status = status; + if (notes) { + assignment.notes = notes; + } + await assignment.save(); + + // If completed, move to completed assignments + if (status === 'completed') { + await LabStaff.findByIdAndUpdate(profileId, { + $pull: { currentAssignments: appointmentId }, + $push: { completedAssignments: appointmentId }, + }); + } + + res.json({ + message: 'Assignment status updated successfully', + assignment: await assignment.populate([ + { path: 'patientId', select: 'firstName lastName' }, + { path: 'labId', select: 'name' }, + ]), + }); + } catch (error) { + console.error('Error updating assignment status:', error); + res.status(500).json({ message: 'Failed to update assignment status' }); + } +}; + +// Mark assignment as completed +export const completeAssignment = async (req, res) => { + try { + const { profileId } = req.user; + const { appointmentId } = req.params; + const { notes } = req.body; + + const assignment = await LabAppointment.findById(appointmentId); + if (!assignment) { + return res.status(404).json({ message: 'Assignment not found' }); + } + + if (assignment.assignedStaffId?.toString() !== profileId.toString()) { + return res.status(403).json({ message: 'Unauthorized' }); + } + + assignment.status = 'sample_collected'; + if (notes) { + assignment.notes = notes; + } + await assignment.save(); + + res.json({ + message: 'Sample collection marked as completed', + assignment, + }); + } catch (error) { + console.error('Error completing assignment:', error); + res.status(500).json({ message: 'Failed to complete assignment' }); + } +}; diff --git a/server/Routes/labAdminRoutes.js b/server/Routes/labAdminRoutes.js index dc52e6a..5901f78 100644 --- a/server/Routes/labAdminRoutes.js +++ b/server/Routes/labAdminRoutes.js @@ -4,7 +4,9 @@ import { authenticate, authorize } from '../Middleware/authMiddleware.js'; import { createLabAdminProfile, createLab, - addStaffMember, + searchStaff, + addStaffToLab, + removeStaffFromLab, getLabStaff, addTest, updateTest, @@ -25,10 +27,18 @@ router.post( ]), createLab ); -router.post('/staff', authenticate, authorize('lab_admin'), addStaffMember); + +// Staff management +router.get('/staff/search', authenticate, authorize('lab_admin'), searchStaff); +router.post('/staff/add', authenticate, authorize('lab_admin'), addStaffToLab); +router.delete('/staff/:staffId', authenticate, authorize('lab_admin'), removeStaffFromLab); router.get('/staff', authenticate, authorize('lab_admin'), getLabStaff); + +// Test management router.post('/tests', authenticate, authorize('lab_admin'), addTest); router.put('/tests/:testId', authenticate, authorize('lab_admin'), updateTest); + +// Lab info router.get('/lab/info', authenticate, authorize('lab_admin'), getLabInfo); export default router; diff --git a/server/Routes/labStaffRoutes.js b/server/Routes/labStaffRoutes.js new file mode 100644 index 0000000..cd8c439 --- /dev/null +++ b/server/Routes/labStaffRoutes.js @@ -0,0 +1,57 @@ +// routes/labStaffRoutes.js +import express from 'express'; +import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { + createStaffProfile, + updateStaffProfile, + getStaffProfile, + checkStaffProfileExists, + getMyAssignments, + getAssignmentDetails, + updateMyAssignmentStatus, + completeAssignment, +} from '../Controllers/labStaffController.js'; +import upload from '../Middleware/upload.js'; + +const router = express.Router(); + +// Profile management +router.post( + '/profile', + authenticate, + authorize('lab_staff'), + upload.single('profilePicture'), + createStaffProfile +); +router.put( + '/profile', + authenticate, + authorize('lab_staff'), + upload.single('profilePicture'), + updateStaffProfile +); +router.get('/profile', authenticate, authorize('lab_staff'), getStaffProfile); +router.get('/profile/check', authenticate, authorize('lab_staff'), checkStaffProfileExists); + +// Assignments/Tasks +router.get('/assignments', authenticate, authorize('lab_staff'), getMyAssignments); +router.get( + '/assignments/:appointmentId', + authenticate, + authorize('lab_staff'), + getAssignmentDetails +); +router.put( + '/assignments/:appointmentId/status', + authenticate, + authorize('lab_staff'), + updateMyAssignmentStatus +); +router.post( + '/assignments/:appointmentId/complete', + authenticate, + authorize('lab_staff'), + completeAssignment +); + +export default router; diff --git a/server/index.js b/server/index.js index 2bdca19..d2ec06d 100644 --- a/server/index.js +++ b/server/index.js @@ -24,7 +24,7 @@ import labAdminRoutes from './Routes/labAdminRoutes.js'; import labAppointmentRoutes from './Routes/labAppointmentRoutes.js'; import labReportRoutes from './Routes/labReportRoutes.js'; import labRoutes from './Routes/labRoutes.js'; - +import LabStaffRoutes from './Routes/labStaffRoutes.js'; const app = express(); const PORT = process.env.PORT; @@ -52,6 +52,10 @@ app.use('/api/notifications', notificationRoutes); app.use('/api/push-notifications', pushNotificationServiceRoutes); //QuickLabs Route app.use('/api/lab-admin', labAdminRoutes); +app.use('/api/lab-appointment', labAppointmentRoutes); +app.use('/api/lab-report', labReportRoutes); +app.use('/api/lab-staff', LabStaffRoutes); +app.use('/api/lab', labRoutes); //QuickMed Routes app.use('/api/medicines', medicineRoutes); diff --git a/server/models/Lab/LabStaff.js b/server/models/Lab/LabStaff.js index c8ce95b..eecf11c 100644 --- a/server/models/Lab/LabStaff.js +++ b/server/models/Lab/LabStaff.js @@ -1,4 +1,4 @@ -// models/Users/LabStaff.js +// models/Lab/LabStaff.js import mongoose from 'mongoose'; const labStaffSchema = new mongoose.Schema( @@ -13,10 +13,11 @@ const labStaffSchema = new mongoose.Schema( lastName: { type: String, required: true }, phoneNumber: { type: String, required: true }, + // Lab association (initially null until admin adds them) labId: { type: mongoose.Schema.Types.ObjectId, ref: 'Lab', - required: true, + default: null, }, role: { @@ -25,14 +26,33 @@ const labStaffSchema = new mongoose.Schema( default: 'assistant', }, + // Profile completion status + isProfileComplete: { type: Boolean, default: false }, + + // Whether staff is associated with a lab + isAssignedToLab: { type: Boolean, default: false }, + // For home sample collection assignments isAvailableForHomeCollection: { type: Boolean, default: true }, + currentAssignments: [ { type: mongoose.Schema.Types.ObjectId, ref: 'LabAppointment', }, ], + + completedAssignments: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'LabAppointment', + }, + ], + + // Professional details + qualifications: [String], + experience: { type: Number }, // years + profilePicture: { type: String }, }, { timestamps: true } ); From 13de8bef8d7bfd62fa1074860ee3d48b119f08e1 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Fri, 12 Dec 2025 10:04:19 +0530 Subject: [PATCH 9/9] placing quicklabs route and controller in quicklab dedicated directory for easy managing --- .../Controllers/{ => QuickLab}/labAdminController.js | 10 +++++----- .../{ => QuickLab}/labAppointmentController.js | 10 +++++----- server/Controllers/{ => QuickLab}/labController.js | 2 +- .../Controllers/{ => QuickLab}/labReportController.js | 6 +++--- .../Controllers/{ => QuickLab}/labStaffController.js | 8 ++++---- server/Routes/{ => QuickLab}/labAdminRoutes.js | 6 +++--- server/Routes/{ => QuickLab}/labAppointmentRoutes.js | 4 ++-- server/Routes/{ => QuickLab}/labReportRoutes.js | 6 +++--- server/Routes/{ => QuickLab}/labRoutes.js | 8 ++++++-- server/Routes/{ => QuickLab}/labStaffRoutes.js | 6 +++--- server/index.js | 10 +++++----- 11 files changed, 40 insertions(+), 36 deletions(-) rename server/Controllers/{ => QuickLab}/labAdminController.js (97%) rename server/Controllers/{ => QuickLab}/labAppointmentController.js (97%) rename server/Controllers/{ => QuickLab}/labController.js (97%) rename server/Controllers/{ => QuickLab}/labReportController.js (96%) rename server/Controllers/{ => QuickLab}/labStaffController.js (97%) rename server/Routes/{ => QuickLab}/labAdminRoutes.js (85%) rename server/Routes/{ => QuickLab}/labAppointmentRoutes.js (84%) rename server/Routes/{ => QuickLab}/labReportRoutes.js (78%) rename server/Routes/{ => QuickLab}/labRoutes.js (59%) rename server/Routes/{ => QuickLab}/labStaffRoutes.js (86%) diff --git a/server/Controllers/labAdminController.js b/server/Controllers/QuickLab/labAdminController.js similarity index 97% rename from server/Controllers/labAdminController.js rename to server/Controllers/QuickLab/labAdminController.js index 4a9902d..cc9e962 100644 --- a/server/Controllers/labAdminController.js +++ b/server/Controllers/QuickLab/labAdminController.js @@ -1,10 +1,10 @@ // controllers/labAdminController.js import mongoose from 'mongoose'; -import Lab from '../models/Lab/Lab.js'; -import LabAdmin from '../models/Lab/LabAdmin.js'; -import LabStaff from '../models/Lab/LabStaff.js'; -import User from '../models/Users/User.js'; -import { uploadToCloudinary } from '../services/uploadService.js'; +import Lab from '../../models/Lab/Lab.js'; +import LabAdmin from '../../models/Lab/LabAdmin.js'; +import LabStaff from '../../models/Lab/LabStaff.js'; +import User from '../../models/Users/User.js'; +import { uploadToCloudinary } from '../../services/uploadService.js'; // Create lab admin profile export const createLabAdminProfile = async (req, res) => { diff --git a/server/Controllers/labAppointmentController.js b/server/Controllers/QuickLab/labAppointmentController.js similarity index 97% rename from server/Controllers/labAppointmentController.js rename to server/Controllers/QuickLab/labAppointmentController.js index 875507b..64a3b2e 100644 --- a/server/Controllers/labAppointmentController.js +++ b/server/Controllers/QuickLab/labAppointmentController.js @@ -1,10 +1,10 @@ // controllers/labAppointmentController.js import mongoose from 'mongoose'; -import LabAppointment from '../models/Lab/LabAppointment.js'; -import Lab from '../models/Lab/Lab.js'; -import Patient from '../models/Users/Patient.js'; -import LabStaff from '../models/Lab/LabStaff.js'; -import LabReport from '../models/Lab/LabReport.js'; +import LabAppointment from '../../models/Lab/LabAppointment.js'; +import Lab from '../../models/Lab/Lab.js'; +import Patient from '../../models/Users/Patient.js'; +import LabStaff from '../../models/Lab/LabStaff.js'; +import LabReport from '../../models/Lab/LabReport.js'; // Book lab appointment (by patient) export const bookLabAppointment = async (req, res) => { diff --git a/server/Controllers/labController.js b/server/Controllers/QuickLab/labController.js similarity index 97% rename from server/Controllers/labController.js rename to server/Controllers/QuickLab/labController.js index 17b8bd8..e53bbe2 100644 --- a/server/Controllers/labController.js +++ b/server/Controllers/QuickLab/labController.js @@ -1,5 +1,5 @@ // controllers/labController.js (for patient/doctor to search labs) -import Lab from '../models/Lab/Lab.js'; +import Lab from '../../models/Lab/Lab.js'; // Search labs export const searchLabs = async (req, res) => { diff --git a/server/Controllers/labReportController.js b/server/Controllers/QuickLab/labReportController.js similarity index 96% rename from server/Controllers/labReportController.js rename to server/Controllers/QuickLab/labReportController.js index 34a182e..3eb9415 100644 --- a/server/Controllers/labReportController.js +++ b/server/Controllers/QuickLab/labReportController.js @@ -1,8 +1,8 @@ // controllers/labReportController.js import mongoose from 'mongoose'; -import LabReport from '../models/Lab/LabReport.js'; -import LabAppointment from '../models/Lab/LabAppointment.js'; -import { uploadToCloudinary } from '../services/uploadService.js'; +import LabReport from '../../models/Lab/LabReport.js'; +import LabAppointment from '../../models/Lab/LabAppointment.js'; +import { uploadToCloudinary } from '../../services/uploadService.js'; // Upload lab report (by lab admin/staff) export const uploadLabReport = async (req, res) => { diff --git a/server/Controllers/labStaffController.js b/server/Controllers/QuickLab/labStaffController.js similarity index 97% rename from server/Controllers/labStaffController.js rename to server/Controllers/QuickLab/labStaffController.js index 154ee25..61648b3 100644 --- a/server/Controllers/labStaffController.js +++ b/server/Controllers/QuickLab/labStaffController.js @@ -1,9 +1,9 @@ // controllers/labStaffController.js import mongoose from 'mongoose'; -import LabStaff from '../models/Lab/LabStaff.js'; -import LabAppointment from '../models/Lab/LabAppointment.js'; -import Lab from '../models/Lab/Lab.js'; -import { uploadToCloudinary } from '../services/uploadService.js'; +import LabStaff from '../../models/Lab/LabStaff.js'; +import LabAppointment from '../../models/Lab/LabAppointment.js'; +import Lab from '../../models/Lab/Lab.js'; +import { uploadToCloudinary } from '../../services/uploadService.js'; // Create staff profile (after user registration) export const createStaffProfile = async (req, res) => { diff --git a/server/Routes/labAdminRoutes.js b/server/Routes/QuickLab/labAdminRoutes.js similarity index 85% rename from server/Routes/labAdminRoutes.js rename to server/Routes/QuickLab/labAdminRoutes.js index 5901f78..1c99cb0 100644 --- a/server/Routes/labAdminRoutes.js +++ b/server/Routes/QuickLab/labAdminRoutes.js @@ -1,6 +1,6 @@ // routes/labAdminRoutes.js import express from 'express'; -import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { authenticate, authorize } from '../../Middleware/authMiddleware.js'; import { createLabAdminProfile, createLab, @@ -11,8 +11,8 @@ import { addTest, updateTest, getLabInfo, -} from '../Controllers/labAdminController.js'; -import upload from '../Middleware/upload.js'; +} from '../../Controllers/QuickLab/labAdminController.js'; +import upload from '../../Middleware/upload.js'; const router = express.Router(); diff --git a/server/Routes/labAppointmentRoutes.js b/server/Routes/QuickLab/labAppointmentRoutes.js similarity index 84% rename from server/Routes/labAppointmentRoutes.js rename to server/Routes/QuickLab/labAppointmentRoutes.js index 6ae9113..aae59ce 100644 --- a/server/Routes/labAppointmentRoutes.js +++ b/server/Routes/QuickLab/labAppointmentRoutes.js @@ -1,13 +1,13 @@ // routes/labAppointmentRoutes.js import express from 'express'; -import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { authenticate, authorize } from '../../Middleware/authMiddleware.js'; import { bookLabAppointment, getPatientLabAppointments, getLabAppointments, assignStaffForCollection, updateLabAppointmentStatus, -} from '../Controllers/labAppointmentController.js'; +} from '../../Controllers/QuickLab/labAppointmentController.js'; const router = express.Router(); diff --git a/server/Routes/labReportRoutes.js b/server/Routes/QuickLab/labReportRoutes.js similarity index 78% rename from server/Routes/labReportRoutes.js rename to server/Routes/QuickLab/labReportRoutes.js index 5c92599..df575cd 100644 --- a/server/Routes/labReportRoutes.js +++ b/server/Routes/QuickLab/labReportRoutes.js @@ -1,14 +1,14 @@ // routes/labReportRoutes.js import express from 'express'; -import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { authenticate, authorize } from '../../Middleware/authMiddleware.js'; import { uploadLabReport, getPatientLabReports, getDoctorPatientReports, addDoctorRemarks, getReportDetails, -} from '../Controllers/labReportController.js'; -import upload from '../middleware/upload.js'; +} from '../../Controllers/QuickLab/labReportController.js'; +import upload from '../../Middleware/upload.js'; const router = express.Router(); diff --git a/server/Routes/labRoutes.js b/server/Routes/QuickLab/labRoutes.js similarity index 59% rename from server/Routes/labRoutes.js rename to server/Routes/QuickLab/labRoutes.js index 5cc23c4..f18dec7 100644 --- a/server/Routes/labRoutes.js +++ b/server/Routes/QuickLab/labRoutes.js @@ -1,7 +1,11 @@ // routes/labRoutes.js import express from 'express'; -import { authenticate, authorize } from '../Middleware/authMiddleware.js'; -import { searchLabs, getLabDetails, getLabTests } from '../Controllers/labController.js'; +import { authenticate, authorize } from '../../Middleware/authMiddleware.js'; +import { + searchLabs, + getLabDetails, + getLabTests, +} from '../../Controllers/QuickLab/labController.js'; const router = express.Router(); diff --git a/server/Routes/labStaffRoutes.js b/server/Routes/QuickLab/labStaffRoutes.js similarity index 86% rename from server/Routes/labStaffRoutes.js rename to server/Routes/QuickLab/labStaffRoutes.js index cd8c439..d830771 100644 --- a/server/Routes/labStaffRoutes.js +++ b/server/Routes/QuickLab/labStaffRoutes.js @@ -1,6 +1,6 @@ // routes/labStaffRoutes.js import express from 'express'; -import { authenticate, authorize } from '../Middleware/authMiddleware.js'; +import { authenticate, authorize } from '../../Middleware/authMiddleware.js'; import { createStaffProfile, updateStaffProfile, @@ -10,8 +10,8 @@ import { getAssignmentDetails, updateMyAssignmentStatus, completeAssignment, -} from '../Controllers/labStaffController.js'; -import upload from '../Middleware/upload.js'; +} from '../../Controllers/QuickLab/labStaffController.js'; +import upload from '../../Middleware/upload.js'; const router = express.Router(); diff --git a/server/index.js b/server/index.js index d2ec06d..92009c6 100644 --- a/server/index.js +++ b/server/index.js @@ -20,11 +20,11 @@ import pushNotificationServiceRoutes from './Routes/pushNotificationRoutes.js'; //QuickMed import medicineRoutes from './Routes/QuickMed/medicineRoutes.js'; //QuickLab -import labAdminRoutes from './Routes/labAdminRoutes.js'; -import labAppointmentRoutes from './Routes/labAppointmentRoutes.js'; -import labReportRoutes from './Routes/labReportRoutes.js'; -import labRoutes from './Routes/labRoutes.js'; -import LabStaffRoutes from './Routes/labStaffRoutes.js'; +import labAdminRoutes from './Routes/QuickLab/labAdminRoutes.js'; +import labAppointmentRoutes from './Routes/QuickLab/labAppointmentRoutes.js'; +import labReportRoutes from './Routes/QuickLab/labReportRoutes.js'; +import labRoutes from './Routes/QuickLab/labRoutes.js'; +import LabStaffRoutes from './Routes/QuickLab/labStaffRoutes.js'; const app = express(); const PORT = process.env.PORT;