Geburtstagskalender + Geburtstagsgruß-Modal im Kundenportal

Admin (Vertrags-Cockpit):
- Neue Section "Geburtstage" zeigt Kunden mit Geburtstag
- Fenster: -7 bis +30 Tage um heute
- Farbcodierung: heute (pink), vergangen (amber), bevorstehend (grau)
- Anzeige: Name, Kundennummer, Geburtsdatum, Alter, "Heute!" / "In X Tagen" / "Vor X Tagen"

Portal (Kundenportal):
- Modal mit Geburtstagsgruß wenn Geburtstag heute oder in den letzten 7 Tagen war
- Unterscheidet zwischen aktuellem Geburtstag und nachträglichen Glückwünschen
- Schönes Gradient-Design mit Konfetti-Emojis
- Wird pro Jahr nur einmal angezeigt (Customer.lastBirthdayGreetingYear)
- Bestätigung speichert das aktuelle Jahr

Backend:
- Neues Feld Customer.lastBirthdayGreetingYear (Int?)
- Service birthday.service.ts mit Fenster-Logik + Alter-Berechnung
- Endpoints /api/birthdays/upcoming (Admin),
  /api/birthdays/my-birthday (Portal GET + POST /acknowledge)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:51:20 +02:00
parent a47dfcd841
commit 9e55e25dc8
9 changed files with 467 additions and 1 deletions
@@ -0,0 +1,56 @@
import { Response } from 'express';
import { AuthRequest } from '../types/index.js';
import * as birthdayService from '../services/birthday.service.js';
/**
* Admin/Mitarbeiter: Kommende und vergangene Geburtstage
* Query: ?past=7&future=30 (Default)
*/
export async function getUpcomingBirthdays(req: AuthRequest, res: Response) {
try {
const past = req.query.past ? parseInt(String(req.query.past)) : 7;
const future = req.query.future ? parseInt(String(req.query.future)) : 30;
const entries = await birthdayService.getUpcomingBirthdays(past, future);
res.json({ success: true, data: entries });
} catch (error) {
console.error('Fehler beim Abrufen der Geburtstage:', error);
res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Geburtstage' });
}
}
/**
* Portal: Eigenen Geburtstags-Check soll das Modal angezeigt werden?
*/
export async function getMyBirthday(req: AuthRequest, res: Response) {
try {
const user = req.user as any;
if (!user?.isCustomerPortal || !user?.customerId) {
return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' });
}
const data = await birthdayService.checkMyBirthday(user.customerId);
res.json({ success: true, data });
} catch (error) {
console.error('Fehler beim Geburtstags-Check:', error);
res.status(500).json({ success: false, error: 'Fehler beim Abruf' });
}
}
/**
* Portal: Modal als gesehen markieren
*/
export async function acknowledgeMyBirthday(req: AuthRequest, res: Response) {
try {
const user = req.user as any;
if (!user?.isCustomerPortal || !user?.customerId) {
return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' });
}
await birthdayService.acknowledgeBirthdayGreeting(user.customerId);
res.json({ success: true });
} catch (error) {
console.error('Fehler beim Bestätigen:', error);
res.status(500).json({ success: false, error: 'Fehler beim Speichern' });
}
}