feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
Backend Image / build-and-push (push) Successful in 2m28s
Frontend Image / build-and-push (push) Successful in 2m10s

- 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:
Nick Szaunig committed 2026-08-31 18:56:08 +02:00
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,
},
});
}
}