Kunde: persönliche Du/Sie-Präferenz pro Mitarbeiter
Neue Tabelle UserCustomerSalutation (PK userId+customerId,
preference 'DU'|'SIE'). Fehlender Eintrag → Fallback auf
Customer.useInformalAddress (Kunden-Default).
Backend:
- customerService: get/set/clearSalutationPreference mit Fallback-
Logik. Response enthält immer effektive Präferenz + `source`
('user' | 'customer-default'), damit die UI den Standard-Text
anzeigen kann.
- customerController: 3 Endpunkte GET/PUT/DELETE
/:customerId/salutation-preference. userId aus dem JWT, canAccessCustomer
greift.
Frontend:
- customerApi: 3 neue Methoden.
- CustomerDetail: neues Feld "Anrede für mich" mit Du/Sie-Toggle
und Zurücksetzen-Link. Wird nur Mitarbeitern angezeigt, nicht
Portal-Usern.
- Bestehendes Feld "Anrede per" bekommt Hinweis "Standard für alle
Mitarbeiter", damit die Semantik der beiden Felder klar ist.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
-- Per-User-Präferenz "Du"/"Sie" pro Kunde. Fehlender Eintrag fällt auf
|
||||||
|
-- den Kunden-Default `Customer.useInformalAddress` zurück. Composite-PK
|
||||||
|
-- (userId, customerId) verhindert doppelte Präferenzen pro Paar; Index
|
||||||
|
-- auf customerId hilft dem Cleanup, wenn der Kunde gelöscht wird.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `UserCustomerSalutation` (
|
||||||
|
`userId` INT NOT NULL,
|
||||||
|
`customerId` INT NOT NULL,
|
||||||
|
`preference` VARCHAR(191) NOT NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
PRIMARY KEY (`userId`, `customerId`),
|
||||||
|
INDEX `UserCustomerSalutation_customerId_idx` (`customerId`),
|
||||||
|
CONSTRAINT `UserCustomerSalutation_userId_fkey`
|
||||||
|
FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `UserCustomerSalutation_customerId_fkey`
|
||||||
|
FOREIGN KEY (`customerId`) REFERENCES `Customer`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
@@ -90,6 +90,9 @@ model User {
|
|||||||
customerId Int? @unique
|
customerId Int? @unique
|
||||||
customer Customer? @relation(fields: [customerId], references: [id])
|
customer Customer? @relation(fields: [customerId], references: [id])
|
||||||
roles UserRole[]
|
roles UserRole[]
|
||||||
|
// Persönliche Anrede-Präferenz pro Kunde ("Du"/"Sie") – User-Wunsch
|
||||||
|
// 2026-07-04. Fehlender Eintrag = Kunden-Default (Customer.useInformalAddress).
|
||||||
|
customerSalutations UserCustomerSalutation[]
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
@@ -207,10 +210,35 @@ model Customer {
|
|||||||
// DSGVO: Einwilligungen
|
// DSGVO: Einwilligungen
|
||||||
consents CustomerConsent[]
|
consents CustomerConsent[]
|
||||||
|
|
||||||
|
// Persönliche Anrede-Präferenzen aller Mitarbeiter zu diesem Kunden.
|
||||||
|
userSalutations UserCustomerSalutation[]
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== USER-CUSTOMER SALUTATION ====================
|
||||||
|
// Per-User-Präferenz, ob der eingeloggte Mitarbeiter mit dem jeweiligen
|
||||||
|
// Kunden per Du oder Sie ist. Fehlender Eintrag → fällt auf den
|
||||||
|
// Kunden-Default (Customer.useInformalAddress) zurück.
|
||||||
|
// User-Wunsch 2026-07-04: Stefan kann per Du mit einem Kunden sein,
|
||||||
|
// während andere Mitarbeiter denselben Kunden weiter siezen.
|
||||||
|
|
||||||
|
model UserCustomerSalutation {
|
||||||
|
userId Int
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
customerId Int
|
||||||
|
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
||||||
|
// 'DU' oder 'SIE'. String statt Enum, damit man ohne Migration einen
|
||||||
|
// dritten Wert nachschieben könnte (z.B. 'MIXED' o.ä.).
|
||||||
|
preference String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@id([userId, customerId])
|
||||||
|
@@index([customerId])
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== CUSTOMER REPRESENTATIVES ====================
|
// ==================== CUSTOMER REPRESENTATIVES ====================
|
||||||
// Vertretungsbeziehung: Ein Kunde kann die Verträge eines anderen Kunden einsehen
|
// Vertretungsbeziehung: Ein Kunde kann die Verträge eines anderen Kunden einsehen
|
||||||
// z.B. Sohn (representativeId) kann Verträge der Mutter (customerId) sehen
|
// z.B. Sohn (representativeId) kann Verträge der Mutter (customerId) sehen
|
||||||
|
|||||||
@@ -1274,6 +1274,88 @@ export async function removeRepresentative(req: AuthRequest, res: Response): Pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== SALUTATION PREFERENCE ====================
|
||||||
|
// Per-User "Du"/"Sie" pro Kunde – User-Wunsch 2026-07-04.
|
||||||
|
|
||||||
|
export async function getSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const customerId = parseInt(req.params.customerId);
|
||||||
|
if (!Number.isFinite(customerId) || customerId < 1) {
|
||||||
|
res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||||
|
const userId = req.user?.userId;
|
||||||
|
if (!userId) {
|
||||||
|
res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await customerService.getSalutationPreference(userId, customerId);
|
||||||
|
res.json({ success: true, data: result } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Laden der Anrede-Präferenz',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const customerId = parseInt(req.params.customerId);
|
||||||
|
if (!Number.isFinite(customerId) || customerId < 1) {
|
||||||
|
res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||||
|
const userId = req.user?.userId;
|
||||||
|
if (!userId) {
|
||||||
|
res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const preference = req.body?.preference;
|
||||||
|
if (preference !== 'DU' && preference !== 'SIE') {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'preference muss "DU" oder "SIE" sein',
|
||||||
|
} as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await customerService.setSalutationPreference(userId, customerId, preference);
|
||||||
|
const result = await customerService.getSalutationPreference(userId, customerId);
|
||||||
|
res.json({ success: true, data: result } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Speichern der Anrede-Präferenz',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clearSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const customerId = parseInt(req.params.customerId);
|
||||||
|
if (!Number.isFinite(customerId) || customerId < 1) {
|
||||||
|
res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||||
|
const userId = req.user?.userId;
|
||||||
|
if (!userId) {
|
||||||
|
res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await customerService.clearSalutationPreference(userId, customerId);
|
||||||
|
const result = await customerService.getSalutationPreference(userId, customerId);
|
||||||
|
res.json({ success: true, data: result } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen der Anrede-Präferenz',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchForRepresentative(req: AuthRequest, res: Response): Promise<void> {
|
export async function searchForRepresentative(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// KRITISCH (Pentest Runde 6): ohne canAccessCustomer kann ein Portal-User
|
// KRITISCH (Pentest Runde 6): ohne canAccessCustomer kann ein Portal-User
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ router.get('/:customerId/portal/password', authenticate, requirePermission('cust
|
|||||||
router.post('/:customerId/portal/password/generate', authenticate, requirePermission('customers:update'), customerController.generatePortalPassword);
|
router.post('/:customerId/portal/password/generate', authenticate, requirePermission('customers:update'), customerController.generatePortalPassword);
|
||||||
router.post('/:customerId/portal/send-credentials', authenticate, requirePermission('customers:update'), customerController.sendPortalCredentials);
|
router.post('/:customerId/portal/send-credentials', authenticate, requirePermission('customers:update'), customerController.sendPortalCredentials);
|
||||||
|
|
||||||
|
// Salutation-Präferenz pro User (Du/Sie) – 2026-07-04. Nutzt
|
||||||
|
// requireCustomerAccess damit auch Portal-User (falls je nötig) nicht
|
||||||
|
// automatisch scheitern; der Endpoint speichert userId aus dem Token.
|
||||||
|
router.get('/:customerId/salutation-preference', authenticate, requireCustomerAccess, customerController.getSalutationPreference);
|
||||||
|
router.put('/:customerId/salutation-preference', authenticate, requireCustomerAccess, customerController.setSalutationPreference);
|
||||||
|
router.delete('/:customerId/salutation-preference', authenticate, requireCustomerAccess, customerController.clearSalutationPreference);
|
||||||
|
|
||||||
// Representatives (Vertreter)
|
// Representatives (Vertreter)
|
||||||
router.get('/:customerId/representatives', authenticate, requirePermission('customers:read'), customerController.getRepresentatives);
|
router.get('/:customerId/representatives', authenticate, requirePermission('customers:read'), customerController.getRepresentatives);
|
||||||
router.post('/:customerId/representatives', authenticate, requirePermission('customers:update'), customerController.addRepresentative);
|
router.post('/:customerId/representatives', authenticate, requirePermission('customers:update'), customerController.addRepresentative);
|
||||||
|
|||||||
@@ -1017,3 +1017,67 @@ export async function searchCustomersForRepresentative(search: string, excludeCu
|
|||||||
take: 10,
|
take: 10,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== SALUTATION PREFERENCE ====================
|
||||||
|
// Per-User "Du"/"Sie" pro Kunde. Fehlender Eintrag ⇒ Kunden-Default
|
||||||
|
// (Customer.useInformalAddress). Nur Mitarbeiter-User, keine Portal-User.
|
||||||
|
|
||||||
|
export type SalutationPreference = 'DU' | 'SIE';
|
||||||
|
|
||||||
|
function isValidPreference(v: unknown): v is SalutationPreference {
|
||||||
|
return v === 'DU' || v === 'SIE';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die effektive Anrede für einen User/Kunde: eigene Präferenz
|
||||||
|
* bevorzugt, sonst Kunden-Default. `source` sagt der UI, ob's ein eigener
|
||||||
|
* Override ist oder der Fallback greift.
|
||||||
|
*/
|
||||||
|
export async function getSalutationPreference(
|
||||||
|
userId: number,
|
||||||
|
customerId: number,
|
||||||
|
): Promise<{
|
||||||
|
preference: SalutationPreference;
|
||||||
|
source: 'user' | 'customer-default';
|
||||||
|
}> {
|
||||||
|
const own = await prisma.userCustomerSalutation.findUnique({
|
||||||
|
where: { userId_customerId: { userId, customerId } },
|
||||||
|
});
|
||||||
|
if (own && isValidPreference(own.preference)) {
|
||||||
|
return { preference: own.preference, source: 'user' };
|
||||||
|
}
|
||||||
|
const customer = await prisma.customer.findUnique({
|
||||||
|
where: { id: customerId },
|
||||||
|
select: { useInformalAddress: true },
|
||||||
|
});
|
||||||
|
if (!customer) {
|
||||||
|
throw new Error('Kunde nicht gefunden');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
preference: customer.useInformalAddress ? 'DU' : 'SIE',
|
||||||
|
source: 'customer-default',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setSalutationPreference(
|
||||||
|
userId: number,
|
||||||
|
customerId: number,
|
||||||
|
preference: SalutationPreference,
|
||||||
|
) {
|
||||||
|
return prisma.userCustomerSalutation.upsert({
|
||||||
|
where: { userId_customerId: { userId, customerId } },
|
||||||
|
create: { userId, customerId, preference },
|
||||||
|
update: { preference },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht den eigenen Override, sodass die UI wieder auf den Kunden-
|
||||||
|
* Default zurückfällt. Kein 404, wenn kein Eintrag da war – das ist
|
||||||
|
* idempotent gedacht ("stelle sicher, dass kein Override existiert").
|
||||||
|
*/
|
||||||
|
export async function clearSalutationPreference(userId: number, customerId: number) {
|
||||||
|
await prisma.userCustomerSalutation.deleteMany({
|
||||||
|
where: { userId, customerId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -277,12 +277,19 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-gray-500">Anrede per</dt>
|
<dt className="text-sm text-gray-500">Anrede per</dt>
|
||||||
<dd>
|
<dd className="flex items-center gap-2 flex-wrap">
|
||||||
<Badge variant={c.useInformalAddress ? 'info' : 'default'}>
|
<Badge variant={c.useInformalAddress ? 'info' : 'default'}>
|
||||||
{c.useInformalAddress ? 'Du (informell)' : 'Sie (formell)'}
|
{c.useInformalAddress ? 'Du (informell)' : 'Sie (formell)'}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<span className="text-xs text-gray-400">Standard für alle Mitarbeiter</span>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
{!isCustomerPortal && (
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-gray-500">Anrede für mich</dt>
|
||||||
|
<dd><SalutationPreferenceToggle customerId={customerId} customerDefaultInformal={!!c.useInformalAddress} /></dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-gray-500">Vorname</dt>
|
<dt className="text-sm text-gray-500">Vorname</dt>
|
||||||
<dd className="flex items-center gap-1">
|
<dd className="flex items-center gap-1">
|
||||||
@@ -5058,3 +5065,86 @@ function AuthorizationsSection({ customerId, customerEmail }: { customerId: numb
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SalutationPreferenceToggle – persönliche Du/Sie-Präferenz des Mitarbeiters
|
||||||
|
// ============================================================================
|
||||||
|
// User-Wunsch 2026-07-04: Stefan darf per Du sein mit einem Kunden, ohne dass
|
||||||
|
// das für andere Mitarbeiter gilt. Wenn kein Eintrag existiert, greift der
|
||||||
|
// Kunden-Default (`useInformalAddress`) – der Toggle zeigt das als "Standard".
|
||||||
|
function SalutationPreferenceToggle({
|
||||||
|
customerId,
|
||||||
|
customerDefaultInformal,
|
||||||
|
}: {
|
||||||
|
customerId: number;
|
||||||
|
customerDefaultInformal: boolean;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['salutation-preference', customerId],
|
||||||
|
queryFn: () => customerApi.getSalutationPreference(customerId),
|
||||||
|
});
|
||||||
|
const setMutation = useMutation({
|
||||||
|
mutationFn: (preference: 'DU' | 'SIE') =>
|
||||||
|
customerApi.setSalutationPreference(customerId, preference),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['salutation-preference', customerId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const clearMutation = useMutation({
|
||||||
|
mutationFn: () => customerApi.clearSalutationPreference(customerId),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['salutation-preference', customerId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMutating = setMutation.isPending || clearMutation.isPending;
|
||||||
|
const preference = data?.data?.preference;
|
||||||
|
const source = data?.data?.source;
|
||||||
|
const defaultLabel = customerDefaultInformal ? 'Du' : 'Sie';
|
||||||
|
|
||||||
|
const btnClass = (active: boolean) =>
|
||||||
|
`px-3 py-1 text-sm rounded-md transition-colors ${
|
||||||
|
active
|
||||||
|
? 'bg-blue-100 text-blue-800 border border-blue-300'
|
||||||
|
: 'bg-gray-100 text-gray-600 border border-gray-200 hover:bg-gray-200'
|
||||||
|
} ${isMutating ? 'opacity-60 cursor-not-allowed' : ''}`;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <span className="text-xs text-gray-400">lädt…</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => !isMutating && setMutation.mutate('DU')}
|
||||||
|
className={btnClass(preference === 'DU')}
|
||||||
|
disabled={isMutating}
|
||||||
|
>
|
||||||
|
Du
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => !isMutating && setMutation.mutate('SIE')}
|
||||||
|
className={btnClass(preference === 'SIE')}
|
||||||
|
disabled={isMutating}
|
||||||
|
>
|
||||||
|
Sie
|
||||||
|
</button>
|
||||||
|
{source === 'user' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => !isMutating && clearMutation.mutate()}
|
||||||
|
className="text-xs text-blue-600 hover:underline"
|
||||||
|
disabled={isMutating}
|
||||||
|
title={`Auf Kunden-Default (${defaultLabel}) zurücksetzen`}
|
||||||
|
>
|
||||||
|
Zurücksetzen
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-gray-400">Standard: {defaultLabel}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -205,6 +205,27 @@ export const customerApi = {
|
|||||||
const res = await api.delete<ApiResponse<void>>(`/customers/${customerId}/representatives/${representativeId}`);
|
const res = await api.delete<ApiResponse<void>>(`/customers/${customerId}/representatives/${representativeId}`);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
// Persönliche Anrede-Präferenz (Du/Sie) des eingeloggten Mitarbeiters
|
||||||
|
// für einen bestimmten Kunden. Fehlender Override → Kunden-Default.
|
||||||
|
getSalutationPreference: async (customerId: number) => {
|
||||||
|
const res = await api.get<ApiResponse<{ preference: 'DU' | 'SIE'; source: 'user' | 'customer-default' }>>(
|
||||||
|
`/customers/${customerId}/salutation-preference`,
|
||||||
|
);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
setSalutationPreference: async (customerId: number, preference: 'DU' | 'SIE') => {
|
||||||
|
const res = await api.put<ApiResponse<{ preference: 'DU' | 'SIE'; source: 'user' | 'customer-default' }>>(
|
||||||
|
`/customers/${customerId}/salutation-preference`,
|
||||||
|
{ preference },
|
||||||
|
);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
clearSalutationPreference: async (customerId: number) => {
|
||||||
|
const res = await api.delete<ApiResponse<{ preference: 'DU' | 'SIE'; source: 'user' | 'customer-default' }>>(
|
||||||
|
`/customers/${customerId}/salutation-preference`,
|
||||||
|
);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
searchForRepresentative: async (customerId: number, search: string) => {
|
searchForRepresentative: async (customerId: number, search: string) => {
|
||||||
const res = await api.get<ApiResponse<CustomerSummary[]>>(`/customers/${customerId}/representatives/search`, { params: { search } });
|
const res = await api.get<ApiResponse<CustomerSummary[]>>(`/customers/${customerId}/representatives/search`, { params: { search } });
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
Reference in New Issue
Block a user