feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
- Profilseite mit MFA-Einrichtung, Passwort/E-Mail/Name aendern, Konto deaktivieren - Bearbeiten-Buttons fuer Bienenstaende/Voelker/Koeniginnen/Aufgaben-Datensaetze - Restliche englische Enum-Werte durch deutsche Labels ersetzt (SelectValue-Fix) - Sidebar-Eintraege fuer Ernten/Schleuderungen ergaenzt - Automatisierungen von 3 auf 6 Regeln erweitert (Schwarmstimmung, kritischer Gesundheitsstatus, Lagerbestand) - Responsive Grid-Layout fuer Volk-/Koeniginnen-/Honigchargen-/Bienenstand-Detailseiten - Etikettenvorlagen mit Platzhaltern inkl. QR-Code (verweist auf Herkunftsseite) und PDF-Druck - Entra-ID-SSO (Authorization Code + PKCE, kein Auto-Provisioning, Secrets nur ueber Environment-Variablen)
This commit is contained in:
1 parent
0a52b29c57
commit
8432313fa9
83 files changed
+2684
-289
No files matched your search
@@ -6,8 +6,12 @@ import { WebhooksService } from '../webhooks/webhooks.service';
|
||||
import {
|
||||
AutomationRule,
|
||||
AutomationTriggerType,
|
||||
Colony,
|
||||
Inspection,
|
||||
Prisma,
|
||||
StockItem,
|
||||
SwarmControlCheck,
|
||||
TaskRelatedType,
|
||||
VarroaMeasurement,
|
||||
} from '../../generated/prisma';
|
||||
import { UpdateAutomationRuleDto } from './dto/update-automation-rule.dto';
|
||||
@@ -31,6 +35,19 @@ const DEFAULT_RULES: Record<AutomationTriggerType, RuleDefaults> = {
|
||||
config: {},
|
||||
actionTemplate: 'Fütterung erforderlich: Futterstatus kritisch.',
|
||||
},
|
||||
SWARM_MOOD_HIGH: {
|
||||
config: {},
|
||||
actionTemplate:
|
||||
'Schwarmkontrolle dringend: hohe Schwarmstimmung festgestellt.',
|
||||
},
|
||||
COLONY_HEALTH_CRITICAL: {
|
||||
config: {},
|
||||
actionTemplate: 'Kontrolle erforderlich: Gesundheitsstatus kritisch.',
|
||||
},
|
||||
STOCK_ITEM_LOW: {
|
||||
config: {},
|
||||
actionTemplate: 'Nachbestellung prüfen: Lagerartikel unter Mindestbestand.',
|
||||
},
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -118,6 +135,7 @@ export class AutomationService {
|
||||
) {
|
||||
await this.triggerAction(
|
||||
queenlessRule,
|
||||
'COLONY',
|
||||
inspection.colonyId,
|
||||
inspection.id,
|
||||
);
|
||||
@@ -125,7 +143,12 @@ export class AutomationService {
|
||||
|
||||
const feedRule = ruleByType.get('FEED_STATUS_CRITICAL');
|
||||
if (feedRule && inspection.feedStatus?.toLowerCase() === 'kritisch') {
|
||||
await this.triggerAction(feedRule, inspection.colonyId, inspection.id);
|
||||
await this.triggerAction(
|
||||
feedRule,
|
||||
'COLONY',
|
||||
inspection.colonyId,
|
||||
inspection.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,13 +166,59 @@ export class AutomationService {
|
||||
const threshold = config?.thresholdMitesPerDay ?? 10;
|
||||
const value = measurement.calculatedValue ?? measurement.miteCount;
|
||||
if (value !== null && value >= threshold) {
|
||||
await this.triggerAction(rule, measurement.colonyId);
|
||||
await this.triggerAction(rule, 'COLONY', measurement.colonyId);
|
||||
}
|
||||
}
|
||||
|
||||
async evaluateSwarmControlCheck(check: SwarmControlCheck): Promise<void> {
|
||||
await this.ensureDefaults();
|
||||
const rule = await this.prisma.automationRule.findUnique({
|
||||
where: { triggerType: 'SWARM_MOOD_HIGH' },
|
||||
});
|
||||
if (!rule || !rule.active) {
|
||||
return;
|
||||
}
|
||||
if (check.swarmMood === 'HIGH') {
|
||||
await this.triggerAction(rule, 'COLONY', check.colonyId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fires only on the transition into CRITICAL, not on every edit while already critical. */
|
||||
async evaluateColonyHealth(before: Colony, after: Colony): Promise<void> {
|
||||
await this.ensureDefaults();
|
||||
if (
|
||||
after.healthStatus !== 'CRITICAL' ||
|
||||
before.healthStatus === 'CRITICAL'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const rule = await this.prisma.automationRule.findUnique({
|
||||
where: { triggerType: 'COLONY_HEALTH_CRITICAL' },
|
||||
});
|
||||
if (!rule || !rule.active) {
|
||||
return;
|
||||
}
|
||||
await this.triggerAction(rule, 'COLONY', after.id);
|
||||
}
|
||||
|
||||
async evaluateStockItem(item: StockItem): Promise<void> {
|
||||
await this.ensureDefaults();
|
||||
if (item.minThreshold === null || item.quantity >= item.minThreshold) {
|
||||
return;
|
||||
}
|
||||
const rule = await this.prisma.automationRule.findUnique({
|
||||
where: { triggerType: 'STOCK_ITEM_LOW' },
|
||||
});
|
||||
if (!rule || !rule.active) {
|
||||
return;
|
||||
}
|
||||
await this.triggerAction(rule, 'STORAGE', item.id);
|
||||
}
|
||||
|
||||
private async triggerAction(
|
||||
rule: AutomationRule,
|
||||
colonyId: string,
|
||||
relatedType: TaskRelatedType,
|
||||
relatedId: string,
|
||||
sourceInspectionId?: string,
|
||||
): Promise<void> {
|
||||
const title = rule.actionTemplate ?? 'Automatisierte Aufgabe';
|
||||
@@ -157,8 +226,8 @@ export class AutomationService {
|
||||
data: {
|
||||
title,
|
||||
priority: 'HIGH',
|
||||
relatedType: 'COLONY',
|
||||
relatedId: colonyId,
|
||||
relatedType,
|
||||
relatedId,
|
||||
sourceInspectionId,
|
||||
},
|
||||
});
|
||||
@@ -166,14 +235,19 @@ export class AutomationService {
|
||||
await this.notifications.record({
|
||||
priority: 'WARNUNG',
|
||||
message: title,
|
||||
relatedType: 'COLONY',
|
||||
relatedId: colonyId,
|
||||
relatedType,
|
||||
relatedId,
|
||||
});
|
||||
await this.auditLog.record({
|
||||
entityType: 'AutomationRule',
|
||||
entityId: rule.id,
|
||||
action: 'CREATE',
|
||||
afterData: { taskId: task.id, colonyId, triggerType: rule.triggerType },
|
||||
afterData: {
|
||||
taskId: task.id,
|
||||
relatedType,
|
||||
relatedId,
|
||||
triggerType: rule.triggerType,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user