2026-02-02 23:36:35 -05:00
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
|
|
|
import { postgresClient } from '@/lib/services/postgres-client';
|
|
|
|
|
|
2026-02-03 08:32:42 -05:00
|
|
|
async function getExcludedCompanyIds(): Promise<number[]> {
|
|
|
|
|
try {
|
|
|
|
|
const result = await postgresClient.query(
|
|
|
|
|
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_company_ids'`
|
|
|
|
|
);
|
|
|
|
|
const value = result.rows[0]?.setting_value || '';
|
|
|
|
|
return value ? value.split(',').map((id: string) => parseInt(id.trim())).filter((id: number) => !isNaN(id)) : [];
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching excluded company IDs:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 09:17:07 -05:00
|
|
|
async function getExcludedClassifications(): Promise<string[]> {
|
|
|
|
|
try {
|
|
|
|
|
const result = await postgresClient.query(
|
|
|
|
|
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_classifications'`
|
|
|
|
|
);
|
|
|
|
|
const value = result.rows[0]?.setting_value || '';
|
|
|
|
|
return value ? value.split(',').map((c: string) => c.trim()).filter(Boolean) : [];
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching excluded classifications:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 23:36:35 -05:00
|
|
|
export async function GET(request: NextRequest) {
|
|
|
|
|
try {
|
2026-02-03 09:17:07 -05:00
|
|
|
// Get excluded company IDs (co-managed clients) and classifications
|
2026-02-03 08:32:42 -05:00
|
|
|
const excludedCompanyIds = await getExcludedCompanyIds();
|
2026-02-03 09:17:07 -05:00
|
|
|
const excludedClassifications = await getExcludedClassifications();
|
|
|
|
|
|
|
|
|
|
// Build company exclusion filter
|
|
|
|
|
let excludeCompanyFilter = '';
|
|
|
|
|
if (excludedCompanyIds.length > 0 || excludedClassifications.length > 0) {
|
|
|
|
|
const conditions = [];
|
|
|
|
|
if (excludedCompanyIds.length > 0) {
|
|
|
|
|
conditions.push(`t.company_id NOT IN (${excludedCompanyIds.join(',')})`);
|
|
|
|
|
}
|
|
|
|
|
if (excludedClassifications.length > 0) {
|
|
|
|
|
const classificationList = excludedClassifications.map(c => `'${c.replace(/'/g, "''")}'`).join(',');
|
|
|
|
|
conditions.push(`t.company_id NOT IN (SELECT id FROM companies WHERE classification IN (${classificationList}))`);
|
|
|
|
|
}
|
|
|
|
|
excludeCompanyFilter = `AND (${conditions.join(' AND ')})`;
|
|
|
|
|
}
|
2026-02-03 08:32:42 -05:00
|
|
|
|
2026-02-03 09:17:07 -05:00
|
|
|
// Get recent ticket activity for ticker feed - exclude RMM alerts (source = 8), co-managed clients, and excluded classifications
|
2026-02-02 23:36:35 -05:00
|
|
|
const activityResult = await postgresClient.query(
|
|
|
|
|
`SELECT
|
|
|
|
|
t.ticket_number,
|
|
|
|
|
t.title,
|
|
|
|
|
t.priority,
|
|
|
|
|
t.status,
|
|
|
|
|
t.create_date,
|
|
|
|
|
t.last_activity_date,
|
|
|
|
|
c.company_name,
|
|
|
|
|
s.label as status_label
|
|
|
|
|
FROM tickets t
|
|
|
|
|
LEFT JOIN companies c ON t.company_id = c.id
|
|
|
|
|
LEFT JOIN statuses s ON t.status = s.value
|
|
|
|
|
WHERE t.completed_date IS NULL
|
feat: QuickBooks Online integration
- Add QBO OAuth2 client with token refresh (lib/services/qbo-client.ts)
- Add QBO sync service for invoices, payments, deposits, purchases, journal entries, reports (lib/services/qbo-sync-service.ts)
- Add QBO types (lib/types/qbo.ts)
- Add API routes: /api/qbo/auth, /api/qbo/sync, /api/qbo/disconnect
- Add /admin/qbo status and sync management page
- Add legal pages: /legal/eula, /legal/privacy (Intuit app assessment)
- Add QBO nav link under Admin
- Fix reports: remove invalid summarize_column_by, add accounting_method from Preferences API, add showrows=all&showcols=all
- Add CashFlow report type alongside P&L and BalanceSheet
- Add NoReportData check to skip empty report months
- Add intuit_tid capture in error messages
- Add redirect: follow for cluster routing
- Migration 051: qbo_tokens, qbo_invoices, qbo_payments, qbo_deposits, qbo_transactions, qbo_reports tables
Also includes earlier work:
- Ping flap suppression pipeline step
- Ticket digest reports with LLM analysis
- Zabbix WAN monitor and gap analysis
- Kiosk is_deleted filter fixes
- Datto RMM ping target enrichment
- Entity sync soft-delete detection
2026-03-17 07:39:55 -04:00
|
|
|
AND t.is_deleted = false
|
2026-02-03 07:02:47 -05:00
|
|
|
AND (t.source IS NULL OR t.source != 8)
|
2026-02-03 08:32:42 -05:00
|
|
|
${excludeCompanyFilter}
|
2026-02-02 23:36:35 -05:00
|
|
|
ORDER BY t.last_activity_date DESC NULLS LAST
|
|
|
|
|
LIMIT 50`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const activities = activityResult.rows.map((row: any) => ({
|
|
|
|
|
ticketNumber: row.ticket_number,
|
|
|
|
|
title: row.title,
|
|
|
|
|
priority: row.priority,
|
|
|
|
|
status: row.status,
|
|
|
|
|
statusLabel: row.status_label,
|
|
|
|
|
companyName: row.company_name,
|
|
|
|
|
createDate: row.create_date,
|
|
|
|
|
lastActivityDate: row.last_activity_date,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({ activities });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching kiosk activity:', error);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: 'Failed to fetch kiosk activity' },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|