All mobile dashboard and ticket list queries now INNER JOIN companies on user_defined_fields->>'MSP Service Model' = 'Wulf Managed', scoping all stats (open total, by priority, by queue, SLA, recent activity) and ticket list to managed clients only. 349 open tickets in scope.
91 lines
4.1 KiB
TypeScript
91 lines
4.1 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { postgresClient } from '@/lib/services/postgres-client';
|
|
|
|
export async function GET() {
|
|
const [byStatus, byQueue, byPriority, recentActivity, sla] = await Promise.all([
|
|
postgresClient.query(`
|
|
SELECT t.status, COUNT(*) as count
|
|
FROM tickets t
|
|
INNER JOIN companies c ON c.id = t.company_id AND c.user_defined_fields->>'MSP Service Model' = 'Wulf Managed'
|
|
WHERE t.status != 5 AND t.is_deleted = false
|
|
GROUP BY t.status ORDER BY count DESC
|
|
`),
|
|
postgresClient.query(`
|
|
SELECT t.queue_id, q.label as queue_label, COUNT(*) as count
|
|
FROM tickets t
|
|
INNER JOIN companies c ON c.id = t.company_id AND c.user_defined_fields->>'MSP Service Model' = 'Wulf Managed'
|
|
LEFT JOIN queues q ON q.value = t.queue_id
|
|
WHERE t.status != 5 AND t.is_deleted = false
|
|
GROUP BY t.queue_id, q.label ORDER BY count DESC LIMIT 8
|
|
`),
|
|
postgresClient.query(`
|
|
SELECT t.priority, p.label, COUNT(*) as count
|
|
FROM tickets t
|
|
INNER JOIN companies c ON c.id = t.company_id AND c.user_defined_fields->>'MSP Service Model' = 'Wulf Managed'
|
|
LEFT JOIN priorities p ON p.value = t.priority
|
|
WHERE t.status != 5 AND t.is_deleted = false
|
|
GROUP BY t.priority, p.label ORDER BY count DESC
|
|
`),
|
|
postgresClient.query(`
|
|
SELECT t.id, t.ticket_number, t.title, t.status, t.priority,
|
|
t.last_activity_date, t.company_id,
|
|
c.company_name, q.label as queue_label,
|
|
r.first_name || ' ' || r.last_name as assigned_to
|
|
FROM tickets t
|
|
INNER JOIN companies c ON c.id = t.company_id AND c.user_defined_fields->>'MSP Service Model' = 'Wulf Managed'
|
|
LEFT JOIN queues q ON q.value = t.queue_id
|
|
LEFT JOIN resources r ON r.id = t.assigned_resource_id
|
|
WHERE t.status != 5 AND t.is_deleted = false AND t.last_activity_date IS NOT NULL
|
|
ORDER BY t.last_activity_date DESC LIMIT 10
|
|
`),
|
|
postgresClient.query(`
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE t.first_response_date_time IS NOT NULL
|
|
AND EXTRACT(EPOCH FROM (t.first_response_date_time - t.create_date))/3600 <= 1) as resp_met,
|
|
COUNT(*) FILTER (WHERE t.first_response_date_time IS NOT NULL) as resp_total,
|
|
COUNT(*) FILTER (WHERE t.resolved_date_time IS NOT NULL
|
|
AND EXTRACT(EPOCH FROM (t.resolved_date_time - t.create_date))/3600 <= 24) as res_met,
|
|
COUNT(*) FILTER (WHERE t.resolved_date_time IS NOT NULL) as res_total
|
|
FROM tickets t
|
|
INNER JOIN companies c ON c.id = t.company_id AND c.user_defined_fields->>'MSP Service Model' = 'Wulf Managed'
|
|
WHERE t.create_date >= NOW() - INTERVAL '30 days' AND t.is_deleted = false
|
|
`),
|
|
]);
|
|
|
|
const statusLabels: Record<number, string> = {
|
|
1: 'New', 5: 'Complete', 7: 'In Progress', 8: 'In Progress',
|
|
9: 'Scheduled', 12: 'On Hold', 14: 'Waiting Customer',
|
|
19: 'Waiting Materials', 21: 'Dispatched', 25: 'In Review',
|
|
27: 'Pending Decision', 30: 'On Hold', 45: 'Escalated', 47: 'Waiting Customer',
|
|
56: 'Waiting Vendor', 58: 'Waiting Parts', 59: 'Pending Approval',
|
|
60: 'In Deployment', 66: 'Closed', 68: 'Resolved', 70: 'Customer Follow-Up', 71: 'Archived',
|
|
};
|
|
|
|
const slaRow = sla.rows[0];
|
|
|
|
return NextResponse.json({
|
|
open_total: byStatus.rows.reduce((s, r) => s + parseInt(r.count), 0),
|
|
by_status: byStatus.rows.map(r => ({
|
|
status: parseInt(r.status),
|
|
label: statusLabels[r.status] ?? `Status ${r.status}`,
|
|
count: parseInt(r.count),
|
|
})),
|
|
by_queue: byQueue.rows.map(r => ({
|
|
queue_id: r.queue_id,
|
|
label: r.queue_label ?? `Queue ${r.queue_id}`,
|
|
count: parseInt(r.count),
|
|
})),
|
|
by_priority: byPriority.rows.map(r => ({
|
|
priority: parseInt(r.priority),
|
|
label: r.label ?? `P${r.priority}`,
|
|
count: parseInt(r.count),
|
|
})),
|
|
recent: recentActivity.rows,
|
|
sla: {
|
|
response_met: parseInt(slaRow.resp_met ?? 0),
|
|
response_total: parseInt(slaRow.resp_total ?? 0),
|
|
resolution_met: parseInt(slaRow.res_met ?? 0),
|
|
resolution_total: parseInt(slaRow.res_total ?? 0),
|
|
},
|
|
});
|
|
}
|