diff --git a/app-backend/src/controllers/shift.controller.js b/app-backend/src/controllers/shift.controller.js index 41260d4f..7c98c50f 100644 --- a/app-backend/src/controllers/shift.controller.js +++ b/app-backend/src/controllers/shift.controller.js @@ -1,5 +1,6 @@ import mongoose from 'mongoose'; import Shift from '../models/Shift.js'; +import ShiftAttendance from '../models/ShiftAttendance.js'; import { ACTIONS } from "../middleware/logger.js"; @@ -408,7 +409,7 @@ export const completeShift = async (req, res) => { const { id } = req.params; if (!mongoose.isValidObjectId(id)) return res.status(400).json({ message: 'Invalid id' }); - const shift = await Shift.findById(id); + const shift = await Shift.findById(id).populate('attendance'); if (!shift) return res.status(404).json({ message: 'Shift not found' }); const isOwner = String(shift.createdBy) === String(req.user._id); @@ -418,6 +419,9 @@ export const completeShift = async (req, res) => { if (!shift.assignedGuard) return res.status(400).json({ message: 'No guard assigned' }); if (shift.status === 'completed') return res.status(400).json({ message: 'Already completed' }); + if (!shift.hasCheckedIn) return res.status(400).json({ message: 'Guard has not checked in' }); + if (!shift.hasCheckedOut) return res.status(400).json({ message: 'Guard has not checked out' }); + shift.status = 'completed'; await shift.save(); await req.audit.log(req.user._id, ACTIONS.SHIFT_COMPLETED, { diff --git a/app-backend/src/models/Shift.js b/app-backend/src/models/Shift.js index f5c3d8c3..bcb3246a 100644 --- a/app-backend/src/models/Shift.js +++ b/app-backend/src/models/Shift.js @@ -157,6 +157,24 @@ shiftSchema this.acceptedBy = v; }); +// Virtual for attendance record +shiftSchema + .virtual('attendance', { + ref: 'ShiftAttendance', + localField: '_id', + foreignField: 'shiftId', + justOne: true, +}); + +// Virtuals for check-in/out status +shiftSchema.virtual('hasCheckedIn').get(function () { + return this.attendance?.checkInTime != null; +}); + +shiftSchema.virtual('hasCheckedOut').get(function () { + return this.attendance?.checkOutTime != null; +}); + // Ensure virtuals in responses shiftSchema.set('toJSON', { virtuals: true }); shiftSchema.set('toObject', { virtuals: true }); diff --git a/app-backend/src/routes/index.js b/app-backend/src/routes/index.js index 33e7b464..77e01cd7 100644 --- a/app-backend/src/routes/index.js +++ b/app-backend/src/routes/index.js @@ -9,6 +9,8 @@ import adminRoutes from './admin.routes.js'; import availabilityRoutes from './availability.routes.js'; import rbacRoutes from './rbac.routes.js'; import branchRoutes from './branch.routes.js' +import shiftAttendanceRoutes from './shiftattendance.routes.js'; + import payrollRoutes from './payroll.routes.js'; import documentRoutes from './document.routes.js'; const router = express.Router(); @@ -22,6 +24,7 @@ router.use('/availability', availabilityRoutes); router.use('/users', userRoutes); router.use('/rbac', rbacRoutes); router.use('/branch', branchRoutes); +router.use('/attendance', shiftAttendanceRoutes); router.use('/payroll', payrollRoutes); export default router; \ No newline at end of file