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

@@ -70,7 +70,7 @@ describe('AutomationService', () => {
);
});
it('findAll seeds the three default rules before returning them', async () => {
it('findAll seeds all default rules before returning them', async () => {
prisma.automationRule.findMany.mockResolvedValue([
queenlessRule,
varroaRule,
@@ -79,7 +79,7 @@ describe('AutomationService', () => {
const result = await service.findAll();
expect(prisma.automationRule.upsert).toHaveBeenCalledTimes(3);
expect(prisma.automationRule.upsert).toHaveBeenCalledTimes(6);
expect(result).toHaveLength(3);
});
@@ -188,6 +188,106 @@ describe('AutomationService', () => {
});
});
describe('evaluateSwarmControlCheck', () => {
const swarmRule = {
id: 'rule-4',
triggerType: 'SWARM_MOOD_HIGH',
active: true,
config: {},
actionTemplate: 'Schwarmkontrolle dringend',
};
it('creates a task when swarm mood is HIGH', async () => {
prisma.automationRule.findUnique.mockResolvedValue(swarmRule);
await service.evaluateSwarmControlCheck({
id: 'check-1',
colonyId: 'colony-1',
swarmMood: 'HIGH',
} as never);
expect(prisma.task.create).toHaveBeenCalledTimes(1);
});
it('does not trigger for a low swarm mood', async () => {
prisma.automationRule.findUnique.mockResolvedValue(swarmRule);
await service.evaluateSwarmControlCheck({
id: 'check-2',
colonyId: 'colony-1',
swarmMood: 'LOW',
} as never);
expect(prisma.task.create).not.toHaveBeenCalled();
});
});
describe('evaluateColonyHealth', () => {
const healthRule = {
id: 'rule-5',
triggerType: 'COLONY_HEALTH_CRITICAL',
active: true,
config: {},
actionTemplate: 'Kontrolle erforderlich',
};
it('creates a task on the transition into CRITICAL', async () => {
prisma.automationRule.findUnique.mockResolvedValue(healthRule);
await service.evaluateColonyHealth(
{ id: 'colony-1', healthStatus: 'AVERAGE' } as never,
{ id: 'colony-1', healthStatus: 'CRITICAL' } as never,
);
expect(prisma.task.create).toHaveBeenCalledTimes(1);
});
it('does not re-trigger when already CRITICAL before the update', async () => {
prisma.automationRule.findUnique.mockResolvedValue(healthRule);
await service.evaluateColonyHealth(
{ id: 'colony-1', healthStatus: 'CRITICAL' } as never,
{ id: 'colony-1', healthStatus: 'CRITICAL' } as never,
);
expect(prisma.task.create).not.toHaveBeenCalled();
});
});
describe('evaluateStockItem', () => {
const stockRule = {
id: 'rule-6',
triggerType: 'STOCK_ITEM_LOW',
active: true,
config: {},
actionTemplate: 'Nachbestellung prüfen',
};
it('creates a task when quantity drops below minThreshold', async () => {
prisma.automationRule.findUnique.mockResolvedValue(stockRule);
await service.evaluateStockItem({
id: 'item-1',
quantity: 2,
minThreshold: 5,
} as never);
expect(prisma.task.create).toHaveBeenCalledTimes(1);
});
it('does not trigger when minThreshold is not set', async () => {
prisma.automationRule.findUnique.mockResolvedValue(stockRule);
await service.evaluateStockItem({
id: 'item-2',
quantity: 2,
minThreshold: null,
} as never);
expect(prisma.task.create).not.toHaveBeenCalled();
});
});
describe('update', () => {
it('records an audit log entry with before/after data', async () => {
prisma.automationRule.findUnique.mockResolvedValue(varroaRule);