E-Mail-Ansicht: Postfach-Filter in Trash/Sent durchreichen

Bug: Im Vertrags-Tab (Gesendet/Gelöscht) und im Kunden-Haupt-
Postfach (Gelöscht) wurden Mails aus ALLEN Postfächern angezeigt,
unabhängig vom ausgewählten Postfach. Im Vertrag fehlte zusätzlich
der Vertrags-Filter im Papierkorb.

Backend:
- getEmailsForContract akzeptiert accountId → stressfreiEmailId
- getTrashEmails (controller + service) nimmt {accountId, contractId}
- getFolderCountsForContract bekommt optional stressfreiEmailId und
  zusätzlich trash/trashUnread im Result

Frontend:
- API-Client (getForContract/getTrash/getContractFolderCounts) nimmt
  Filter entgegen
- ContractEmailsSection reicht selectedAccountId in alle drei Queries
  + queryKey durch. Trash-Badge kommt jetzt aus contract-scoped
  Counts statt account-globalem stressfreiEmailApi
- EmailClientTab reicht selectedAccountId in die Trash-Query durch

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:06:24 +02:00
parent f02824fe7d
commit 993f2d10f0
6 changed files with 171 additions and 87 deletions
@@ -81,17 +81,22 @@ export async function getEmailsForCustomer(req: AuthRequest, res: Response): Pro
}
}
// E-Mails für einen Vertrag abrufen
// E-Mails für einen Vertrag abrufen.
// `accountId` (optional) schränkt zusätzlich auf ein bestimmtes Postfach
// ein ohne, sieht man im Vertrags-Tab Mails aus ALLEN Postfächern des
// Kunden, die dem Vertrag zugeordnet sind (User-Bug 2026-06-21).
export async function getEmailsForContract(req: AuthRequest, res: Response): Promise<void> {
try {
const contractId = parseInt(req.params.contractId);
if (!(await canAccessContract(req, res, contractId))) return;
const folder = req.query.folder as string | undefined; // INBOX oder SENT
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
const emails = await cachedEmailService.getCachedEmails({
contractId,
stressfreiEmailId,
folder,
limit,
offset,
@@ -238,13 +243,14 @@ export async function getFolderCounts(req: AuthRequest, res: Response): Promise<
}
}
// E-Mail-Anzahl pro Ordner für einen Vertrag
// E-Mail-Anzahl pro Ordner für einen Vertrag (optional pro Postfach)
export async function getContractFolderCounts(req: AuthRequest, res: Response): Promise<void> {
try {
const contractId = parseInt(req.params.contractId);
if (!(await canAccessContract(req, res, contractId))) return;
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
const counts = await cachedEmailService.getFolderCountsForContract(contractId);
const counts = await cachedEmailService.getFolderCountsForContract(contractId, stressfreiEmailId);
res.json({ success: true, data: counts } as ApiResponse);
} catch (error) {
@@ -882,13 +888,21 @@ export async function deleteEmail(req: AuthRequest, res: Response): Promise<void
// ==================== TRASH OPERATIONS ====================
// Papierkorb-E-Mails für einen Kunden abrufen
// Papierkorb-E-Mails für einen Kunden abrufen.
// Optional `accountId` (Postfach-Filter) und `contractId` (Vertrags-Filter)
// beide aus User-Bug 2026-06-21. Wenn beide leer sind, Verhalten wie
// vorher: alle gelöschten E-Mails des Kunden.
export async function getTrashEmails(req: AuthRequest, res: Response): Promise<void> {
try {
const customerId = parseInt(req.params.customerId);
if (!(await canAccessCustomer(req, res, customerId))) return;
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
const contractId = req.query.contractId ? parseInt(req.query.contractId as string) : undefined;
const emails = await cachedEmailService.getTrashEmails(customerId);
const emails = await cachedEmailService.getTrashEmails(customerId, {
stressfreiEmailId,
contractId,
});
res.json({ success: true, data: emails } as ApiResponse);
} catch (error) {
@@ -900,13 +914,18 @@ export async function getTrashEmails(req: AuthRequest, res: Response): Promise<v
}
}
// Papierkorb-Anzahl für einen Kunden
// Papierkorb-Anzahl für einen Kunden (gleiche Filter wie getTrashEmails)
export async function getTrashCount(req: AuthRequest, res: Response): Promise<void> {
try {
const customerId = parseInt(req.params.customerId);
if (!(await canAccessCustomer(req, res, customerId))) return;
const stressfreiEmailId = req.query.accountId ? parseInt(req.query.accountId as string) : undefined;
const contractId = req.query.contractId ? parseInt(req.query.contractId as string) : undefined;
const count = await cachedEmailService.getTrashCount(customerId);
const count = await cachedEmailService.getTrashCount(customerId, {
stressfreiEmailId,
contractId,
});
res.json({ success: true, data: { count } } as ApiResponse);
} catch (error) {
+58 -38
View File
@@ -603,33 +603,35 @@ export async function getFolderCountsForAccount(stressfreiEmailId: number): Prom
return { inbox, inboxUnread, sent, sentUnread, trash, trashUnread };
}
// E-Mail-Anzahl pro Ordner für einen Vertrag (zugeordnete E-Mails)
export async function getFolderCountsForContract(contractId: number): Promise<{
// E-Mail-Anzahl pro Ordner für einen Vertrag (zugeordnete E-Mails).
// Optional auf ein bestimmtes Postfach einschränken. Bug 2026-06-21:
// vorher zählten die Badges Mails aus ALLEN Postfächern, während die
// Liste (nach Fix) nur die des ausgewählten Postfachs zeigt Badge
// und Liste liefen auseinander. Trash mit reingenommen, weil der
// Contract-Trash-Badge sonst wieder auf account-globalen Zähler
// zurückfallen müsste.
export async function getFolderCountsForContract(
contractId: number,
stressfreiEmailId?: number,
): Promise<{
inbox: number;
inboxUnread: number;
sent: number;
sentUnread: number;
trash: number;
trashUnread: number;
}> {
const [inbox, inboxUnread, sent, sentUnread] = await Promise.all([
// INBOX total
prisma.cachedEmail.count({
where: { contractId, folder: EmailFolder.INBOX, isDeleted: false },
}),
// INBOX unread
prisma.cachedEmail.count({
where: { contractId, folder: EmailFolder.INBOX, isDeleted: false, isRead: false },
}),
// SENT total
prisma.cachedEmail.count({
where: { contractId, folder: EmailFolder.SENT, isDeleted: false },
}),
// SENT unread
prisma.cachedEmail.count({
where: { contractId, folder: EmailFolder.SENT, isDeleted: false, isRead: false },
}),
const baseWhere: Prisma.CachedEmailWhereInput = { contractId };
if (stressfreiEmailId) baseWhere.stressfreiEmailId = stressfreiEmailId;
const [inbox, inboxUnread, sent, sentUnread, trash, trashUnread] = await Promise.all([
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.INBOX, isDeleted: false } }),
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.INBOX, isDeleted: false, isRead: false } }),
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.SENT, isDeleted: false } }),
prisma.cachedEmail.count({ where: { ...baseWhere, folder: EmailFolder.SENT, isDeleted: false, isRead: false } }),
prisma.cachedEmail.count({ where: { ...baseWhere, isDeleted: true } }),
prisma.cachedEmail.count({ where: { ...baseWhere, isDeleted: true, isRead: false } }),
]);
return { inbox, inboxUnread, sent, sentUnread };
return { inbox, inboxUnread, sent, sentUnread, trash, trashUnread };
}
// Alle StressfreiEmails eines Kunden mit Mailbox
@@ -904,14 +906,26 @@ export async function permanentDeleteEmail(id: number): Promise<TrashOperationRe
}
// Papierkorb-E-Mails für einen Kunden abrufen
export async function getTrashEmails(customerId: number): Promise<CachedEmailWithRelations[]> {
// Optional: nach Postfach (stressfreiEmailId) und/oder Vertrag (contractId)
// einschränken. Vorher zeigte der Papierkorb immer ALLE gelöschten E-Mails
// des Kunden, unabhängig von welchem Postfach man gerade angemeldet ist
// User-Bug 2026-06-21.
export async function getTrashEmails(
customerId: number,
options?: { stressfreiEmailId?: number; contractId?: number },
): Promise<CachedEmailWithRelations[]> {
const where: Prisma.CachedEmailWhereInput = {
isDeleted: true,
stressfreiEmail: { customerId },
};
if (options?.stressfreiEmailId) {
where.stressfreiEmailId = options.stressfreiEmailId;
}
if (options?.contractId) {
where.contractId = options.contractId;
}
return prisma.cachedEmail.findMany({
where: {
isDeleted: true,
stressfreiEmail: {
customerId,
},
},
where,
include: {
stressfreiEmail: {
select: {
@@ -931,16 +945,22 @@ export async function getTrashEmails(customerId: number): Promise<CachedEmailWit
}) as Promise<CachedEmailWithRelations[]>;
}
// Papierkorb-E-Mails zählen
export async function getTrashCount(customerId: number): Promise<number> {
return prisma.cachedEmail.count({
where: {
isDeleted: true,
stressfreiEmail: {
customerId,
},
},
});
// Papierkorb-E-Mails zählen (gleiche Filter wie getTrashEmails)
export async function getTrashCount(
customerId: number,
options?: { stressfreiEmailId?: number; contractId?: number },
): Promise<number> {
const where: Prisma.CachedEmailWhereInput = {
isDeleted: true,
stressfreiEmail: { customerId },
};
if (options?.stressfreiEmailId) {
where.stressfreiEmailId = options.stressfreiEmailId;
}
if (options?.contractId) {
where.contractId = options.contractId;
}
return prisma.cachedEmail.count({ where });
}
// Legacy: E-Mail löschen (jetzt deprecated, nutze moveEmailToTrash)