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:
@@ -106,6 +106,61 @@ export async function createReferral(req: AuthRequest, res: Response): Promise<v
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateReferral(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (blockPortal(req, res)) return;
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const referralId = parseInt(req.params.referralId);
|
||||
if (Number.isNaN(customerId) || Number.isNaN(referralId)) {
|
||||
res.status(400).json({ success: false, error: 'Ungültige ID' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
const { direction, otherCustomerId, relationship } = req.body ?? {};
|
||||
const otherId = parseInt(otherCustomerId);
|
||||
if (Number.isNaN(otherId)) {
|
||||
res.status(400).json({ success: false, error: 'Kein Kunde ausgewählt' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
let recruiterId: number;
|
||||
let recruitedId: number;
|
||||
if (direction === 'recruitedBy') {
|
||||
recruiterId = otherId;
|
||||
recruitedId = customerId;
|
||||
} else if (direction === 'recruited') {
|
||||
recruiterId = customerId;
|
||||
recruitedId = otherId;
|
||||
} else {
|
||||
res.status(400).json({ success: false, error: 'Ungültige Richtung' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = await referralService.updateReferral({
|
||||
id: referralId,
|
||||
recruiterId,
|
||||
recruitedId,
|
||||
relationship: (relationship ?? '').toString(),
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'CustomerReferral',
|
||||
resourceId: updated.id.toString(),
|
||||
label: `Werbe-Beziehung geändert: Kunde #${recruiterId} hat Kunde #${recruitedId} geworben (${updated.relationship})`,
|
||||
customerId: recruitedId,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: updated } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = (error as referralService.ReferralError)?.status ?? 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Ändern der Werbe-Beziehung',
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteReferral(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (blockPortal(req, res)) return;
|
||||
|
||||
@@ -60,6 +60,7 @@ router.get('/:customerId/representatives/search', authenticate, requirePermissio
|
||||
router.get('/:customerId/referrals/search', authenticate, requirePermission('customers:read'), customerReferralController.searchCustomers);
|
||||
router.get('/:customerId/referrals', authenticate, requirePermission('customers:read'), customerReferralController.getReferrals);
|
||||
router.post('/:customerId/referrals', authenticate, requirePermission('customers:update'), customerReferralController.createReferral);
|
||||
router.put('/:customerId/referrals/:referralId', authenticate, requirePermission('customers:update'), customerReferralController.updateReferral);
|
||||
router.delete('/:customerId/referrals/:referralId', authenticate, requirePermission('customers:update'), customerReferralController.deleteReferral);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
Reference in New Issue
Block a user