/** * ScheduledTask Model * * Multi-project calendar and reminder system for governance, framework reviews, * and project-specific tasks. Integrated with session-init.js for startup reminders. * * Copyright 2025 Tractatus Project * Licensed under Apache License 2.0 */ const mongoose = require('mongoose'); const scheduledTaskSchema = new mongoose.Schema({ title: { type: String, required: true, trim: true, maxlength: 200 }, description: { type: String, required: true, maxlength: 2000 }, dueDate: { type: Date, required: true, index: true }, priority: { type: String, enum: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'], default: 'MEDIUM', index: true }, category: { type: String, enum: ['framework', 'governance', 'security', 'project', 'research', 'deployment', 'other'], default: 'other', index: true }, // Optional: Link to specific project (null for cross-project or framework tasks) projectId: { type: mongoose.Schema.Types.ObjectId, ref: 'Project', default: null, index: true }, projectName: { type: String, default: null // e.g., "tractatus", "family-history", "sydigital" }, recurrence: { type: String, enum: ['once', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'], default: 'once' }, // For recurring tasks: when was this instance generated from? parentTaskId: { type: mongoose.Schema.Types.ObjectId, ref: 'ScheduledTask', default: null }, status: { type: String, enum: ['pending', 'in_progress', 'completed', 'dismissed', 'overdue'], default: 'pending', index: true }, // Reference to documentation or related files documentRef: { type: String, default: null // e.g., "docs/AUTONOMOUS_FRAMEWORK_WORK_2025-10-23.md" }, // Links to external resources or admin pages links: [{ label: String, url: String }], createdBy: { type: String, enum: ['system', 'user', 'session-init', 'migration'], default: 'system' }, assignedTo: { type: String, // User email or "PM" or "Framework" default: 'PM' // Default to human PM }, completedDate: { type: Date, default: null }, dismissedDate: { type: Date, default: null }, dismissedReason: { type: String, default: null }, // Flexible metadata for filtering and grouping metadata: { type: Map, of: mongoose.Schema.Types.Mixed, default: {} }, // Tags for flexible categorization tags: [{ type: String, lowercase: true, trim: true }], // Session-init display configuration showInSessionInit: { type: Boolean, default: true }, // How many days before due date to show in session-init reminderDaysBefore: { type: Number, default: 7 } }, { timestamps: true }); // Indexes for common queries scheduledTaskSchema.index({ dueDate: 1, status: 1 }); scheduledTaskSchema.index({ category: 1, status: 1 }); scheduledTaskSchema.index({ projectId: 1, status: 1 }); scheduledTaskSchema.index({ status: 1, priority: -1, dueDate: 1 }); // Virtual: days until due scheduledTaskSchema.virtual('daysUntilDue').get(function() { const now = new Date(); const due = new Date(this.dueDate); const diffTime = due - now; const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return diffDays; }); // Virtual: is overdue scheduledTaskSchema.virtual('isOverdue').get(function() { return this.status === 'pending' && new Date(this.dueDate) < new Date(); }); // Method: Complete task scheduledTaskSchema.methods.complete = function() { this.status = 'completed'; this.completedDate = new Date(); return this.save(); }; // Method: Dismiss task scheduledTaskSchema.methods.dismiss = function(reason) { this.status = 'dismissed'; this.dismissedDate = new Date(); this.dismissedReason = reason || 'Dismissed by user'; return this.save(); }; // Static: Get tasks due soon (for session-init) scheduledTaskSchema.statics.getDueSoon = function(daysAhead = 7) { const now = new Date(); const futureDate = new Date(now.getTime() + (daysAhead * 24 * 60 * 60 * 1000)); return this.find({ dueDate: { $lte: futureDate }, status: 'pending', showInSessionInit: true }) .sort({ priority: -1, dueDate: 1 }) .limit(10); }; // Static: Get overdue tasks scheduledTaskSchema.statics.getOverdue = function() { return this.find({ dueDate: { $lt: new Date() }, status: 'pending' }) .sort({ priority: -1, dueDate: 1 }); }; // Static: Get tasks by category scheduledTaskSchema.statics.getByCategory = function(category, includeCompleted = false) { const query = { category }; if (!includeCompleted) { query.status = { $in: ['pending', 'in_progress'] }; } return this.find(query).sort({ dueDate: 1 }); }; // Static: Get tasks by project scheduledTaskSchema.statics.getByProject = function(projectId, includeCompleted = false) { const query = { projectId }; if (!includeCompleted) { query.status = { $in: ['pending', 'in_progress'] }; } return this.find(query).sort({ dueDate: 1 }); }; // Pre-save hook: Update overdue status scheduledTaskSchema.pre('save', function(next) { if (this.status === 'pending' && this.dueDate < new Date()) { this.status = 'overdue'; } next(); }); // Enable virtuals in JSON scheduledTaskSchema.set('toJSON', { virtuals: true }); scheduledTaskSchema.set('toObject', { virtuals: true }); const ScheduledTask = mongoose.model('ScheduledTask', scheduledTaskSchema); module.exports = ScheduledTask;