diff --git a/lib/services/sync-scheduler.ts b/lib/services/sync-scheduler.ts index cae7f18..09b9382 100644 --- a/lib/services/sync-scheduler.ts +++ b/lib/services/sync-scheduler.ts @@ -22,7 +22,7 @@ export interface ScheduleConfig { name: string; description: string; cron_expression: string; - sync_type: 'incremental' | 'full' | 'veeam-incremental' | 'veeam-full' | 'veeam-rpo-check' | 'contract-services' | 'engagement-daily' | 'zoom-daily' | 'morning-summary' | 'ticket-digest-daily' | 'ticket-digest-weekly' | 'ticket-digest-monthly' | 'device-link-reconcile' | 'integration-health'; + sync_type: 'incremental' | 'full' | 'veeam-incremental' | 'veeam-full' | 'veeam-rpo-check' | 'contract-services' | 'engagement-daily' | 'zoom-daily' | 'morning-summary' | 'ticket-digest-daily' | 'ticket-digest-weekly' | 'ticket-digest-monthly' | 'device-link-reconcile' | 'integration-health' | 'qbo' | 'appgate-sessions' | 'appgate-daily' | 'tickets-reconcile'; years_back?: number; is_enabled: boolean; last_run?: Date; @@ -291,6 +291,14 @@ class SyncScheduler { sync_type: 'integration-health', is_enabled: false, }, + { + id: 'tickets-reconcile', + name: 'Tickets Reconciliation', + description: 'STOPGAP backstop: re-fetches stale-open tickets (>7d since last sync) from Autotask and reconciles status / soft-deletes missing rows. Runs daily at 4:30 AM. Capped at 500 tickets per run.', + cron_expression: '30 4 * * *', + sync_type: 'tickets-reconcile', + is_enabled: false, + }, ]; for (const schedule of defaultSchedules) { @@ -434,6 +442,25 @@ class SyncScheduler { console.log( `[SCHEDULER] integration-health: failed=${result.summary.failed} expired=${result.summary.expired} expiringSoon=${result.summary.expiringWithin14Days} alertSent=${result.alertSent}` ); + } else if (config.sync_type === 'appgate-sessions' || config.sync_type === 'appgate-daily') { + const { isAppgateConfigured } = await import('@/lib/services/appgate-factory'); + if (!isAppgateConfigured()) { + console.log(`[SCHEDULER] Skipping ${config.sync_type} — AppGate not configured`); + } else { + const { getAppgateSyncService } = await import('@/lib/services/appgate-sync-service'); + const svc = getAppgateSyncService(); + if (config.sync_type === 'appgate-daily') { + await svc.dailySync('scheduled'); + } else { + await svc.sessionsSync('scheduled'); + } + } + } else if (config.sync_type === 'tickets-reconcile') { + const { reconcileStaleTickets } = await import('@/lib/services/ticket-reconciliation-service'); + const result = await reconcileStaleTickets(); + console.log( + `[SCHEDULER] tickets-reconcile: scanned=${result.scanned} updated=${result.updated} flippedComplete=${result.statusFlippedToComplete} softDeleted=${result.softDeleted} errors=${result.errors}` + ); } else if (config.sync_type === 'incremental') { await this.syncService.incrementalSync('scheduled'); } else { diff --git a/migrations/090_ticket_reconcile_schedule.sql b/migrations/090_ticket_reconcile_schedule.sql new file mode 100644 index 0000000..728ed80 --- /dev/null +++ b/migrations/090_ticket_reconcile_schedule.sql @@ -0,0 +1,24 @@ +-- Migration 090: Seed the tickets-reconcile sync schedule (STOPGAP). +-- +-- The sync_scheduler.createDefaultSchedules() path only seeds defaults on a +-- virgin sync_schedules table; this migration covers existing installs. +-- Idempotent via ON CONFLICT (id) DO NOTHING. + +INSERT INTO sync_schedules ( + id, + name, + description, + cron_expression, + sync_type, + years_back, + is_enabled +) VALUES ( + 'tickets-reconcile', + 'Tickets Reconciliation', + 'STOPGAP backstop: re-fetches stale-open tickets (>7d since last sync) from Autotask and reconciles status / soft-deletes missing rows. Runs daily at 4:30 AM. Capped at 500 tickets per run.', + '30 4 * * *', + 'tickets-reconcile', + NULL, + false +) +ON CONFLICT (id) DO NOTHING;