Referrals: 4 neue Beziehungen + Bearbeiten-Stift pro Eintrag

Beziehungs-Dropdown erweitert um Schwiegertochter/Schwiegersohn,
Schwiegermutter/Schwiegervater, Oma/Opa, Uroma/Uropa (Whitelist
front- und backend synchron).

Bearbeiten-Stift pro Zeile (vor der Muelltonne): Beziehung und/oder
Gegen-Kunde aenderbar. Neuer PUT /:customerId/referrals/:referralId
mit Whitelist-Pruefung, Doppel-Werber-409 (Self-Ausschluss),
Portal-Block und UPDATE-Auditeintrag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 10:57:04 +02:00
co-authored by Claude Opus 4.8
parent 2d8ee7f569
commit 81cd284cc4
7 changed files with 231 additions and 15 deletions
@@ -15,6 +15,10 @@ export const REFERRAL_RELATIONSHIPS = [
'Freund/Kumpel',
'Eltern',
'Bruder/Schwester',
'Schwiegertochter/Schwiegersohn',
'Schwiegermutter/Schwiegervater',
'Oma/Opa',
'Uroma/Uropa',
'Bekannte',
'sonstige',
] as const;
@@ -108,6 +112,60 @@ export async function createReferral(params: {
return created;
}
/**
* Ändert eine bestehende Werbe-Beziehung (Beziehungsart und/oder den
* beteiligten Gegen-Kunden). recruiter/recruited werden vom Controller aus
* der Richtung + aktueller Kundenakte bestimmt.
*/
export async function updateReferral(params: {
id: number;
recruiterId: number;
recruitedId: number;
relationship: string;
}) {
const { id, recruiterId, recruitedId, relationship } = params;
if (!Number.isInteger(id)) {
throw new ReferralError('Ungültige ID');
}
if (!Number.isInteger(recruiterId) || !Number.isInteger(recruitedId)) {
throw new ReferralError('Ungültige Kunden-ID');
}
if (recruiterId === recruitedId) {
throw new ReferralError('Ein Kunde kann sich nicht selbst werben');
}
if (!REFERRAL_RELATIONSHIPS.includes(relationship as typeof REFERRAL_RELATIONSHIPS[number])) {
throw new ReferralError('Ungültige Beziehungsart');
}
const existing = await prisma.customerReferral.findUnique({ where: { id } });
if (!existing) {
throw new ReferralError('Eintrag nicht gefunden', 404);
}
// Beide Kunden müssen existieren
const count = await prisma.customer.count({ where: { id: { in: [recruiterId, recruitedId] } } });
if (count !== 2) {
throw new ReferralError('Kunde nicht gefunden', 404);
}
// recruitedId ist @unique → falls der Geworbene auf einen anderen Kunden
// geändert wird, der bereits einen Werber hat, klare Meldung statt DB-Fehler.
if (recruitedId !== existing.recruitedId) {
const clash = await prisma.customerReferral.findUnique({ where: { recruitedId } });
if (clash && clash.id !== id) {
throw new ReferralError('Dieser Kunde wurde bereits als angeworben markiert. Bitte zuerst den bestehenden Eintrag entfernen.', 409);
}
}
const updated = await prisma.customerReferral.update({
where: { id },
data: { recruiterId, recruitedId, relationship },
include: { recruiter: { select: CUSTOMER_SELECT }, recruited: { select: CUSTOMER_SELECT } },
});
return updated;
}
export async function deleteReferral(id: number): Promise<boolean> {
if (!Number.isInteger(id)) return false;
const existing = await prisma.customerReferral.findUnique({ where: { id } });