feat: sync calendar entries to a Microsoft 365 group calendar
Adds a one-way push (ApiCore -> M365) of Aufgaben/Durchsichten/ Behandlungen/Fütterungen/Ernten/Königinnen-Zuordnungen into the calendar of a Microsoft 365 group, via the Graph API. - CalendarSyncModule (app-only client-credentials Graph auth, separate from the interactive SSO app registration; new env vars M365_CALENDAR_TENANT_ID/_CLIENT_ID/_CLIENT_SECRET). - Outbox + minute-interval cron worker (CalendarSyncOutboxEntry) instead of a synchronous push, since Graph calls are too slow/flaky to block a domain create request on; failed syncs retry up to 5 times. - Per-source-type toggle plus a group-ID field, configurable in Verwaltung > Automatisierung > Kalender-Sync (Verwaltung > Backup, an env var still wins on those fields where one applies to related config). - Hooked into Tasks/Inspections/Treatments/Feedings/Harvests/Queens at their existing create (and Task update/complete) points; entries render as all-day Graph events to avoid timezone ambiguity. - Documents the design decision in ARCHITECTURE.md (Annahme 9), since this is a net-new capability the lastenheft doesn't scope. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
28ae0a2723
commit
e7b157ec35
35 files changed
+1760
-7
No files matched your search
@@ -3,6 +3,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditLogService } from '../audit/audit-log.service';
|
||||
import { WebhooksService } from '../webhooks/webhooks.service';
|
||||
import { TaskTemplatesService } from '../task-templates/task-templates.service';
|
||||
import { CalendarSyncService } from '../calendar-sync/calendar-sync.service';
|
||||
import { Task, TaskPriority, TaskStatus } from '../../generated/prisma';
|
||||
import { CreateTaskDto } from './dto/create-task.dto';
|
||||
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||
@@ -19,6 +20,7 @@ export class TasksService {
|
||||
private readonly auditLog: AuditLogService,
|
||||
private readonly webhooks: WebhooksService,
|
||||
private readonly taskTemplates: TaskTemplatesService,
|
||||
private readonly calendarSync: CalendarSyncService,
|
||||
) {}
|
||||
|
||||
async findAll(filter: TaskFilter): Promise<Task[]> {
|
||||
@@ -53,6 +55,9 @@ export class TasksService {
|
||||
afterData: task,
|
||||
});
|
||||
await this.webhooks.dispatch('task.created', task);
|
||||
if (task.dueDate) {
|
||||
await this.calendarSync.enqueue('TASK', task.id);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -77,6 +82,11 @@ export class TasksService {
|
||||
beforeData: before,
|
||||
afterData: task,
|
||||
});
|
||||
if (task.dueDate) {
|
||||
await this.calendarSync.enqueue('TASK', task.id);
|
||||
} else {
|
||||
await this.calendarSync.cancel('TASK', task.id);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -95,6 +105,7 @@ export class TasksService {
|
||||
afterData: task,
|
||||
});
|
||||
await this.webhooks.dispatch('task.completed', task);
|
||||
await this.calendarSync.cancel('TASK', task.id);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user