/** * SLATracking Model * Tracks Service Level Agreement compliance for response times */ const { ObjectId } = require('mongodb'); const { getCollection } = require('../utils/db.util'); class SLATracking { /** * Create SLA tracking entry */ static async create(data) { const collection = await getCollection('sla_tracking'); const sla = { // What this SLA is tracking item_type: data.item_type, // contact, media, case item_id: new ObjectId(data.item_id), // SLA targets (hours) sla_target: data.sla_target || 24, // Default 24 hours sla_priority: data.sla_priority || 'normal', // low, normal, high, urgent // Timestamps received_at: data.received_at || new Date(), due_at: data.due_at || this.calculateDueDate(data.sla_target || 24), first_response_at: data.first_response_at || null, resolved_at: data.resolved_at || null, // Status status: data.status || 'pending', // pending, responded, resolved, breached breach: data.breach || false, // Response time metrics (hours) response_time: data.response_time || null, resolution_time: data.resolution_time || null, // Escalation tracking escalated: data.escalated || false, escalated_at: data.escalated_at || null, escalated_to: data.escalated_to ? new ObjectId(data.escalated_to) : null, // Metadata project: data.project || 'tractatus', assigned_to: data.assigned_to ? new ObjectId(data.assigned_to) : null, created_at: new Date(), updated_at: new Date() }; const result = await collection.insertOne(sla); return { ...sla, _id: result.insertedId }; } /** * Calculate due date based on SLA hours */ static calculateDueDate(slaHours, fromDate = new Date()) { return new Date(fromDate.getTime() + (slaHours * 60 * 60 * 1000)); } /** * Find SLA by item */ static async findByItem(itemType, itemId) { const collection = await getCollection('sla_tracking'); return await collection.findOne({ item_type: itemType, item_id: new ObjectId(itemId) }); } /** * Record first response */ static async recordResponse(itemType, itemId) { const collection = await getCollection('sla_tracking'); const sla = await this.findByItem(itemType, itemId); if (!sla || sla.first_response_at) { return; // Already recorded } const responseTime = (new Date() - new Date(sla.received_at)) / (1000 * 60 * 60); // hours const breach = responseTime > sla.sla_target; await collection.updateOne( { _id: sla._id }, { $set: { first_response_at: new Date(), response_time: responseTime, status: 'responded', breach, updated_at: new Date() } } ); return await collection.findOne({ _id: sla._id }); } /** * Record resolution */ static async recordResolution(itemType, itemId) { const collection = await getCollection('sla_tracking'); const sla = await this.findByItem(itemType, itemId); if (!sla || sla.resolved_at) { return; // Already recorded } const resolutionTime = (new Date() - new Date(sla.received_at)) / (1000 * 60 * 60); // hours await collection.updateOne( { _id: sla._id }, { $set: { resolved_at: new Date(), resolution_time: resolutionTime, status: 'resolved', updated_at: new Date() } } ); return await collection.findOne({ _id: sla._id }); } /** * Escalate SLA */ static async escalate(itemType, itemId, escalatedTo) { const collection = await getCollection('sla_tracking'); const sla = await this.findByItem(itemType, itemId); if (!sla) return; await collection.updateOne( { _id: sla._id }, { $set: { escalated: true, escalated_at: new Date(), escalated_to: new ObjectId(escalatedTo), updated_at: new Date() } } ); return await collection.findOne({ _id: sla._id }); } /** * Get items approaching SLA breach (within 2 hours) */ static async getApproachingBreach(options = {}) { const collection = await getCollection('sla_tracking'); const { project = null } = options; const now = new Date(); const twoHoursFromNow = new Date(now.getTime() + (2 * 60 * 60 * 1000)); const query = { status: 'pending', breach: false, due_at: { $gte: now, $lte: twoHoursFromNow } }; if (project) query.project = project; return await collection .find(query) .sort({ due_at: 1 }) .toArray(); } /** * Get breached SLAs */ static async getBreached(options = {}) { const collection = await getCollection('sla_tracking'); const { limit = 50, project = null } = options; const query = { breach: true }; if (project) query.project = project; return await collection .find(query) .sort({ due_at: 1 }) .limit(limit) .toArray(); } /** * Get pending items (not yet responded) */ static async getPending(options = {}) { const collection = await getCollection('sla_tracking'); const { limit = 50, project = null } = options; const query = { status: 'pending' }; if (project) query.project = project; return await collection .find(query) .sort({ due_at: 1 }) .limit(limit) .toArray(); } /** * Check and update breached SLAs */ static async checkBreaches() { const collection = await getCollection('sla_tracking'); const now = new Date(); // Find all pending items past their due date const breached = await collection.find({ status: 'pending', breach: false, due_at: { $lt: now } }).toArray(); if (breached.length > 0) { // Mark as breached await collection.updateMany( { status: 'pending', breach: false, due_at: { $lt: now } }, { $set: { breach: true, status: 'breached', updated_at: new Date() } } ); } return breached.length; } /** * Get SLA statistics */ static async getStats(filters = {}) { const collection = await getCollection('sla_tracking'); const query = {}; if (filters.project) query.project = filters.project; const [total, pending, responded, resolved, breached, avgResponseTime] = await Promise.all([ collection.countDocuments(query), collection.countDocuments({ ...query, status: 'pending' }), collection.countDocuments({ ...query, status: 'responded' }), collection.countDocuments({ ...query, status: 'resolved' }), collection.countDocuments({ ...query, breach: true }), collection.aggregate([ { $match: { ...query, response_time: { $exists: true, $ne: null } } }, { $group: { _id: null, avg: { $avg: '$response_time' } } } ]).toArray() ]); const complianceRate = total > 0 ? ((total - breached) / total) * 100 : 100; return { total, pending, responded, resolved, breached, compliance_rate: Math.round(complianceRate * 100) / 100, avg_response_time: avgResponseTime.length > 0 ? Math.round(avgResponseTime[0].avg * 100) / 100 : null }; } /** * Delete SLA record */ static async delete(id) { const collection = await getCollection('sla_tracking'); return await collection.deleteOne({ _id: new ObjectId(id) }); } } module.exports = SLATracking;