Vertreter: Portal-Pflicht raus, Warn-Badge stattdessen
Suche und Add haben bisher nur Kunden mit portalEnabled=true zugelassen – das machte das Feature quasi unnutzbar, weil die meisten Kunden kein Portal aktiviert haben. Der Zugriff ist ohnehin erst dann effektiv, wenn der Vertreter ein Portal-Konto bekommt. - searchCustomersForRepresentative: portalEnabled-Filter raus, dafür Feld im select mitgeliefert. - addRepresentative: Portal-Pflicht-Check raus. - getRepresentedByList: portalEnabled im rep-Include, damit die UI auch für schon hinterlegte Vertreter das Badge zeigen kann. - CustomerDetail: gelbes "Portal inaktiv"-Badge in Suchergebnissen und in der Vertreter-Liste. Hinweistext geändert. - CustomerSummary-Type: portalEnabled? ergänzt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -900,7 +900,10 @@ export async function getCustomerRepresentatives(customerId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getRepresentedByList(customerId: number) {
|
export async function getRepresentedByList(customerId: number) {
|
||||||
// Holt alle Kunden, die den angegebenen Kunden vertreten können
|
// Holt alle Kunden, die den angegebenen Kunden vertreten können.
|
||||||
|
// `portalEnabled` mit rausgeben, damit die UI ein Badge zeigen kann,
|
||||||
|
// falls der Vertreter noch keinen Portal-Zugang hat (Vertretung ist
|
||||||
|
// dann formal eingetragen, greift aber erst mit aktivem Portal).
|
||||||
return prisma.customerRepresentative.findMany({
|
return prisma.customerRepresentative.findMany({
|
||||||
where: { customerId: customerId, isActive: true },
|
where: { customerId: customerId, isActive: true },
|
||||||
include: {
|
include: {
|
||||||
@@ -912,6 +915,7 @@ export async function getRepresentedByList(customerId: number) {
|
|||||||
lastName: true,
|
lastName: true,
|
||||||
companyName: true,
|
companyName: true,
|
||||||
type: true,
|
type: true,
|
||||||
|
portalEnabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -940,10 +944,10 @@ export async function addRepresentative(
|
|||||||
throw new Error('Ein Kunde kann sich nicht selbst vertreten');
|
throw new Error('Ein Kunde kann sich nicht selbst vertreten');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfen ob der Vertreter ein Portal-Konto hat
|
// Portal-Zwang entfernt (2026-07-03): der Vertreter darf ohne Portal
|
||||||
if (!representative.portalEnabled) {
|
// eingetragen werden; die Beziehung ist erst dann effektiv aktiv,
|
||||||
throw new Error('Der Vertreter-Kunde muss ein aktiviertes Portal-Konto haben');
|
// wenn der Vertreter einen Portal-Zugang bekommt (die Frontend-UI
|
||||||
}
|
// zeigt in dem Fall ein Warn-Badge).
|
||||||
|
|
||||||
return prisma.customerRepresentative.upsert({
|
return prisma.customerRepresentative.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -985,12 +989,15 @@ export async function removeRepresentative(customerId: number, representativeId:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function searchCustomersForRepresentative(search: string, excludeCustomerId: number) {
|
export async function searchCustomersForRepresentative(search: string, excludeCustomerId: number) {
|
||||||
// Sucht Kunden, die als Vertreter hinzugefügt werden können
|
// Sucht Kunden, die als Vertreter hinzugefügt werden können.
|
||||||
// Nur Kunden mit aktiviertem Portal
|
// 2026-07-03: Portal-Filter entfernt – der Admin soll auch Kunden ohne
|
||||||
|
// Portal-Zugang als Vertreter markieren können; das Portal wird dann
|
||||||
|
// typischerweise nachträglich aktiviert. Der Zugriff wird ohnehin erst
|
||||||
|
// aktiv, sobald das Portal für den Vertreter angeschaltet ist – die UI
|
||||||
|
// zeigt jetzt ein Warn-Badge, wenn der Vertreter noch kein Portal hat.
|
||||||
return prisma.customer.findMany({
|
return prisma.customer.findMany({
|
||||||
where: {
|
where: {
|
||||||
id: { not: excludeCustomerId },
|
id: { not: excludeCustomerId },
|
||||||
portalEnabled: true,
|
|
||||||
OR: [
|
OR: [
|
||||||
{ firstName: { contains: search } },
|
{ firstName: { contains: search } },
|
||||||
{ lastName: { contains: search } },
|
{ lastName: { contains: search } },
|
||||||
@@ -1005,6 +1012,7 @@ export async function searchCustomersForRepresentative(search: string, excludeCu
|
|||||||
lastName: true,
|
lastName: true,
|
||||||
companyName: true,
|
companyName: true,
|
||||||
type: true,
|
type: true,
|
||||||
|
portalEnabled: true,
|
||||||
},
|
},
|
||||||
take: 10,
|
take: 10,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2327,7 +2327,8 @@ function PortalTab({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
Nur Kunden mit aktiviertem Portal können als Vertreter hinzugefügt werden.
|
Vertreter ohne Portal-Zugang können bereits hinterlegt werden – der Zugriff
|
||||||
|
wird erst wirksam, sobald das Portal aktiviert ist.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Suchergebnisse */}
|
{/* Suchergebnisse */}
|
||||||
@@ -2336,8 +2337,13 @@ function PortalTab({
|
|||||||
{searchResults.map((customer) => (
|
{searchResults.map((customer) => (
|
||||||
<div key={customer.id} className="flex items-center justify-between p-3 hover:bg-gray-50">
|
<div key={customer.id} className="flex items-center justify-between p-3 hover:bg-gray-50">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">
|
<p className="font-medium flex items-center gap-2">
|
||||||
{customer.companyName || `${customer.firstName} ${customer.lastName}`}
|
{customer.companyName || `${customer.firstName} ${customer.lastName}`}
|
||||||
|
{!customer.portalEnabled && (
|
||||||
|
<Badge variant="warning" className="text-xs">
|
||||||
|
Portal inaktiv
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">{customer.customerNumber}</p>
|
<p className="text-sm text-gray-500">{customer.customerNumber}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -2362,9 +2368,14 @@ function PortalTab({
|
|||||||
{representatives.map((rep: CustomerRepresentative) => (
|
{representatives.map((rep: CustomerRepresentative) => (
|
||||||
<div key={rep.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
<div key={rep.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">
|
<p className="font-medium flex items-center gap-2">
|
||||||
{rep.representative?.companyName ||
|
{rep.representative?.companyName ||
|
||||||
`${rep.representative?.firstName} ${rep.representative?.lastName}`}
|
`${rep.representative?.firstName} ${rep.representative?.lastName}`}
|
||||||
|
{rep.representative && rep.representative.portalEnabled === false && (
|
||||||
|
<Badge variant="warning" className="text-xs">
|
||||||
|
Portal inaktiv
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500">{rep.representative?.customerNumber}</p>
|
<p className="text-sm text-gray-500">{rep.representative?.customerNumber}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface CustomerSummary {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
companyName?: string;
|
companyName?: string;
|
||||||
type: 'PRIVATE' | 'BUSINESS';
|
type: 'PRIVATE' | 'BUSINESS';
|
||||||
|
portalEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RepresentativeAuthorization {
|
export interface RepresentativeAuthorization {
|
||||||
|
|||||||
Reference in New Issue
Block a user