const { TaskEvent } = require('../models'); class TaskEventService { /** * Log a task event * @param {Object} eventData - Event data * @param {number} eventData.taskId - Task ID * @param {number} eventData.userId - User ID * @param {string} eventData.eventType - Type of event * @param {string} eventData.fieldName - Field that changed (optional) * @param {any} eventData.oldValue - Old value (optional) * @param {any} eventData.newValue - New value (optional) * @param {Object} eventData.metadata - Additional metadata (optional) */ static async logEvent({ taskId, userId, eventType, fieldName = null, oldValue = null, newValue = null, metadata = {}, }) { try { // Add source to metadata if not provided if (!metadata.source) { metadata.source = 'web'; } const event = await TaskEvent.create({ task_id: taskId, user_id: userId, event_type: eventType, field_name: fieldName, old_value: oldValue ? { [fieldName || 'value']: oldValue } : null, new_value: newValue ? { [fieldName || 'value']: newValue } : null, metadata: metadata, }); return event; } catch (error) { console.error('Error logging task event:', error); throw error; } } /** * Log task creation event */ static async logTaskCreated(taskId, userId, taskData, metadata = {}) { return await this.logEvent({ taskId, userId, eventType: 'created', newValue: taskData, metadata: { ...metadata, action: 'task_created' }, }); } /** * Log status change event */ static async logStatusChange( taskId, userId, oldStatus, newStatus, metadata = {} ) { const eventType = newStatus === 2 ? 'completed' : newStatus === 3 ? 'archived' : 'status_changed'; return await this.logEvent({ taskId, userId, eventType, fieldName: 'status', oldValue: oldStatus, newValue: newStatus, metadata: { ...metadata, action: 'status_change' }, }); } /** * Log priority change event */ static async logPriorityChange( taskId, userId, oldPriority, newPriority, metadata = {} ) { return await this.logEvent({ taskId, userId, eventType: 'priority_changed', fieldName: 'priority', oldValue: oldPriority, newValue: newPriority, metadata: { ...metadata, action: 'priority_change' }, }); } /** * Log due date change event */ static async logDueDateChange( taskId, userId, oldDueDate, newDueDate, metadata = {} ) { return await this.logEvent({ taskId, userId, eventType: 'due_date_changed', fieldName: 'due_date', oldValue: oldDueDate, newValue: newDueDate, metadata: { ...metadata, action: 'due_date_change' }, }); } /** * Log project change event */ static async logProjectChange( taskId, userId, oldProjectId, newProjectId, metadata = {} ) { return await this.logEvent({ taskId, userId, eventType: 'project_changed', fieldName: 'project_id', oldValue: oldProjectId, newValue: newProjectId, metadata: { ...metadata, action: 'project_change' }, }); } /** * Log task name change event */ static async logNameChange( taskId, userId, oldName, newName, metadata = {} ) { return await this.logEvent({ taskId, userId, eventType: 'name_changed', fieldName: 'name', oldValue: oldName, newValue: newName, metadata: { ...metadata, action: 'name_change' }, }); } /** * Log description change event */ static async logDescriptionChange( taskId, userId, oldDescription, newDescription, metadata = {} ) { return await this.logEvent({ taskId, userId, eventType: 'description_changed', fieldName: 'description', oldValue: oldDescription, newValue: newDescription, metadata: { ...metadata, action: 'description_change' }, }); } /** * Log multiple field changes at once */ static async logTaskUpdate(taskId, userId, changes, metadata = {}) { const events = []; for (const [fieldName, { oldValue, newValue }] of Object.entries( changes )) { // Skip if values are the same if (oldValue === newValue) continue; let eventType; switch (fieldName) { case 'status': eventType = newValue === 2 ? 'completed' : newValue === 3 ? 'archived' : 'status_changed'; break; default: eventType = `${fieldName}_changed`; } const event = await this.logEvent({ taskId, userId, eventType, fieldName, oldValue, newValue, metadata: { ...metadata, action: 'bulk_update' }, }); events.push(event); } return events; } /** * Get task timeline (all events for a task) */ static async getTaskTimeline(taskId) { return await TaskEvent.findAll({ where: { task_id: taskId }, order: [['created_at', 'ASC']], include: [ { model: require('../models').User, as: 'User', attributes: ['id', 'name', 'email'], }, ], }); } /** * Get task completion metrics */ static async getTaskCompletionTime(taskId) { const events = await TaskEvent.findAll({ where: { task_id: taskId, event_type: ['status_changed', 'created', 'completed'], }, order: [['created_at', 'ASC']], }); if (events.length === 0) return null; // Find when task was started (moved to in_progress or created) const startEvent = events.find( (e) => e.event_type === 'created' || (e.event_type === 'status_changed' && e.new_value?.status === 1) // in_progress ); // Find when task was completed const completedEvent = events.find( (e) => e.event_type === 'completed' || (e.event_type === 'status_changed' && e.new_value?.status === 2) // done ); if (!startEvent || !completedEvent) return null; const startTime = new Date(startEvent.created_at); const endTime = new Date(completedEvent.created_at); return { task_id: taskId, started_at: startTime, completed_at: endTime, duration_ms: endTime - startTime, duration_hours: (endTime - startTime) / (1000 * 60 * 60), duration_days: (endTime - startTime) / (1000 * 60 * 60 * 24), }; } /** * Get user productivity metrics */ static async getUserProductivityMetrics( userId, startDate = null, endDate = null ) { const whereClause = { user_id: userId }; if (startDate && endDate) { whereClause.created_at = { [require('sequelize').Op.between]: [startDate, endDate], }; } const events = await TaskEvent.findAll({ where: whereClause, order: [['created_at', 'ASC']], }); // Calculate metrics const metrics = { total_events: events.length, tasks_created: events.filter((e) => e.event_type === 'created') .length, tasks_completed: events.filter((e) => e.event_type === 'completed') .length, status_changes: events.filter( (e) => e.event_type === 'status_changed' ).length, average_completion_time: null, completion_times: [], }; // Calculate completion times for all completed tasks const completedTasks = events.filter( (e) => e.event_type === 'completed' ); const completionTimes = []; for (const completedEvent of completedTasks) { const taskCompletion = await this.getTaskCompletionTime( completedEvent.task_id ); if (taskCompletion) { completionTimes.push(taskCompletion); } } if (completionTimes.length > 0) { const totalHours = completionTimes.reduce( (sum, ct) => sum + ct.duration_hours, 0 ); metrics.average_completion_time = totalHours / completionTimes.length; metrics.completion_times = completionTimes; } return metrics; } /** * Get task activity summary for a date range */ static async getTaskActivitySummary(userId, startDate, endDate) { const events = await TaskEvent.findAll({ where: { user_id: userId, created_at: { [require('sequelize').Op.between]: [startDate, endDate], }, }, attributes: [ 'event_type', [ require('sequelize').fn( 'COUNT', require('sequelize').col('id') ), 'count', ], [ require('sequelize').fn( 'DATE', require('sequelize').col('created_at') ), 'date', ], ], group: ['event_type', 'date'], order: [['date', 'ASC']], }); return events; } /** * Get count of how many times a task has been moved to today */ static async getTaskTodayMoveCount(taskId) { const { Op } = require('sequelize'); const count = await TaskEvent.count({ where: { task_id: taskId, event_type: 'today_changed', new_value: { [Op.like]: '%"today":true%', }, }, }); return count; } } module.exports = TaskEventService;