// /components/employeeTimings/employeeTimingModel.js 'use strict'; const mongoose = require('mongoose'); const employeeTimingSchema = new mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true }, date: { type: Date, required: true, default: Date.now, index: true }, entryTime: { type: String, trim: true, default: '' }, exitTime: { type: String, trim: true, default: '' }, note: { type: String, trim: true, default: '' }, createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, updatedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }); /** * Status virtual property: * - 'complete': both entryTime and exitTime are provided * - 'missing_exit': entryTime provided, but exitTime missing (Warning) * - 'missing_entry': exitTime provided, but entryTime missing (Warning) * - 'incomplete': both entryTime and exitTime are missing (Warning) */ employeeTimingSchema.virtual('status').get(function () { const hasEntry = Boolean(this.entryTime && String(this.entryTime).trim()); const hasExit = Boolean(this.exitTime && String(this.exitTime).trim()); if (hasEntry && hasExit) return 'complete'; if (hasEntry && !hasExit) return 'missing_exit'; if (!hasEntry && hasExit) return 'missing_entry'; return 'incomplete'; }); /** * Warning flag virtual property: * True if record is incomplete (missing entry, missing exit, or both) */ employeeTimingSchema.virtual('hasWarning').get(function () { const hasEntry = Boolean(this.entryTime && String(this.entryTime).trim()); const hasExit = Boolean(this.exitTime && String(this.exitTime).trim()); return !(hasEntry && hasExit); }); /** * Warning message helper */ employeeTimingSchema.virtual('warningMessage').get(function () { const hasEntry = Boolean(this.entryTime && String(this.entryTime).trim()); const hasExit = Boolean(this.exitTime && String(this.exitTime).trim()); if (hasEntry && !hasExit) return 'ورود ثبت شده ولی خروج ثبت نشده است'; if (!hasEntry && hasExit) return 'خروج ثبت شده ولی ورود ثبت نشده است'; if (!hasEntry && !hasExit) return 'زمان ورود و خروج هیچ‌کدام ثبت نشده است'; return null; }); /** * Duration in minutes calculation (if both entry and exit times are valid HH:mm) */ employeeTimingSchema.virtual('durationMinutes').get(function () { if (!this.entryTime || !this.exitTime) return null; const [inH, inM] = String(this.entryTime).split(':').map(Number); const [outH, outM] = String(this.exitTime).split(':').map(Number); if (Number.isNaN(inH) || Number.isNaN(inM) || Number.isNaN(outH) || Number.isNaN(outM)) { return null; } const startMinutes = inH * 60 + inM; let endMinutes = outH * 60 + outM; if (endMinutes < startMinutes) { // Crosses midnight endMinutes += 24 * 60; } return endMinutes - startMinutes; }); /** * Formatted duration in Persian/hours */ employeeTimingSchema.virtual('durationFormatted').get(function () { const minutes = this.durationMinutes; if (minutes === null || minutes === undefined) return '—'; const h = Math.floor(minutes / 60); const m = minutes % 60; if (h === 0) return `${m} دقیقه`; if (m === 0) return `${h} ساعت`; return `${h} ساعت و ${m} دقیقه`; }); module.exports = mongoose.model('EmployeeTiming', employeeTimingSchema);