feat(backend): Automatisierungen mit drei fest hinterlegten Triggern (Modul 30)

Neues AutomationModule mit AutomationRule-Modell (ein Row je Trigger-Typ,
per Unique Constraint erzwungen). Drei fest im Code hinterlegte Trigger,
die Admins nur aktivieren/deaktivieren bzw. deren Konfiguration/Aufgaben-
text anpassen können:

- QUEENLESS_NO_EGGS: Königin nicht gesehen UND keine Stifte vorhanden
  (bei Durchsicht) → Kontrollaufgabe
- VARROA_THRESHOLD: Varroa-Schwellenwert überschritten (konfigurierbar,
  Standard 10 Milben/Tag) → Behandlungsprüfung
- FEED_STATUS_CRITICAL: Futterstatus "kritisch" (bei Durchsicht) →
  Fütterungsaufgabe

Jeder ausgelöste Trigger erzeugt eine Task UND eine Notification sowie
einen Audit-Log-Eintrag; Tasks lösen zusätzlich das bestehende
task.created-Webhook aus. InspectionsService und VarroaService rufen
die Auswertung direkt nach dem jeweiligen Erstellen auf.

Neue Endpunkte (nur Administrator): GET/PATCH /automation-rules.

Unit-Tests für AutomationService (alle drei Trigger, Deaktivierung,
Update mit Audit-Log) sowie E2E-Tests (Rollen-Check, alle drei Trigger
end-to-end über echte Inspection-/Varroa-Endpunkte, Deaktivierung
verhindert weitere Trigger). E2E-Suite zweimal hintereinander grün
verifiziert (Idempotenz der globalen Regel-Zeilen sichergestellt).
Migration auf Dev- und Test-Datenbank angewendet.
This commit is contained in:
Nick Szaunig committed 2026-08-24 17:25:42 +02:00
1 parent 42a2be31b4
commit 3f47a6ae8d
15 files changed
+733

No files matched your search

@@ -1,9 +1,11 @@
import { Module } from '@nestjs/common';
import { AutomationModule } from '../automation/automation.module';
import { VarroaController } from './varroa.controller';
import { ColonyVarroaMeasurementsController } from './colony-varroa-measurements.controller';
import { VarroaService } from './varroa.service';
@Module({
imports: [AutomationModule],
controllers: [VarroaController, ColonyVarroaMeasurementsController],
providers: [VarroaService],
exports: [VarroaService],
@@ -2,6 +2,7 @@ import { NotFoundException } from '@nestjs/common';
import { VarroaService } from './varroa.service';
import { PrismaService } from '../prisma/prisma.service';
import { AuditLogService } from '../audit/audit-log.service';
import { AutomationService } from '../automation/automation.service';
describe('VarroaService', () => {
let service: VarroaService;
@@ -29,9 +30,13 @@ describe('VarroaService', () => {
},
};
auditLog = { record: jest.fn() };
const automation = {
evaluateVarroaMeasurement: jest.fn().mockResolvedValue(undefined),
};
service = new VarroaService(
prisma as unknown as PrismaService,
auditLog as unknown as AuditLogService,
automation as unknown as AutomationService,
);
});
@@ -1,6 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AuditLogService } from '../audit/audit-log.service';
import { AutomationService } from '../automation/automation.service';
import { VarroaMeasurement } from '../../generated/prisma';
import { CreateVarroaMeasurementDto } from './dto/create-varroa-measurement.dto';
@@ -9,6 +10,7 @@ export class VarroaService {
constructor(
private readonly prisma: PrismaService,
private readonly auditLog: AuditLogService,
private readonly automation: AutomationService,
) {}
async findAll(): Promise<VarroaMeasurement[]> {
@@ -48,6 +50,7 @@ export class VarroaService {
userId: actingUserId,
afterData: measurement,
});
await this.automation.evaluateVarroaMeasurement(measurement);
return measurement;
}
}