tractatus/src/models/SLATracking.model.js
TheFlow 40601f7d27 refactor(lint): fix code style and unused variables across src/
- Fixed unused function parameters by prefixing with underscore
- Removed unused imports and variables
- Applied eslint --fix for automatic style fixes
  - Property shorthand
  - String template literals
  - Prefer const over let where appropriate
  - Spacing and formatting

Reduces lint errors from 108+ to 78 (61 unused vars, 17 other issues)

Related to CI lint failures in previous commit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 20:15:26 +13:00

296 lines
7.5 KiB
JavaScript

/**
* 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;