// ============================================================ // Scheduler: Activity Due Soon - alle 15 Minuten // ============================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { CrmPrismaService } from '../prisma/crm-prisma.service'; import { RedisService } from '../redis/redis.service'; import { CrmEventPublisher } from './crm-event-publisher.service'; @Injectable() export class ActivityDueSoonScheduler { private readonly logger = new Logger(ActivityDueSoonScheduler.name); private static readonly LOCK_KEY = 'lock:activity-due-soon'; private static readonly LOCK_TTL = 600; // 10 Minuten constructor( private readonly prisma: CrmPrismaService, private readonly redis: RedisService, private readonly eventPublisher: CrmEventPublisher, ) {} @Cron('0 */15 * * * *') async checkDueSoon(): Promise { // Distributed Lock um parallele Ausfuehrung zu verhindern const lockAcquired = await this.redis.setNx( ActivityDueSoonScheduler.LOCK_KEY, process.pid.toString(), ActivityDueSoonScheduler.LOCK_TTL, ); if (!lockAcquired) { this.logger.debug('Activity Due Soon Check: Lock bereits vergeben, ueberspringe.'); return; } try { const now = new Date(); const in24h = new Date(now.getTime() + 24 * 60 * 60 * 1000); // Activities die in den naechsten 24h faellig sind und nicht completed const dueActivities = await this.prisma.activity.findMany({ where: { scheduledAt: { gte: now, lte: in24h, }, completedAt: null, }, select: { id: true, tenantId: true, scheduledAt: true, contactId: true, companyId: true, }, take: 100, // Batch-Limit }); if (dueActivities.length === 0) { this.logger.debug('Keine faelligen Activities gefunden.'); return; } this.logger.log( `${dueActivities.length} faellige Activities gefunden, publiziere Events...`, ); for (const activity of dueActivities) { await this.eventPublisher.activityDueSoon( activity.tenantId, activity.id, { scheduledAt: activity.scheduledAt?.toISOString() ?? '', contactId: activity.contactId ?? undefined, }, ); } this.logger.log(`${dueActivities.length} activity.due_soon Events publiziert.`); } catch (err) { this.logger.error(`Fehler im Activity Due Soon Scheduler: ${String(err)}`); } } }