Hauptmenue: Gutschriften/Lieferscheine-Gesamtuebersicht (portal-scoped)

Neuer Menuepunkt 'Gutschriften' -> Seite /credit-notes mit Tabelle
aller Belege (Beleg-Nr, Art, Kunde, Vertrag, Betrag, Datum, PDF),
Suche + Pagination.

Neuer Endpoint GET /credit-notes (NICHT staff-only wie die uebrigen
Credit-Note-Endpoints): Staff sieht alle Belege aller Kunden, Portal-
Kunden nur eigene + vertretene (Vollmacht via hasAuthorization).
customerIds kommt aus dem JWT, nicht aus Query/Body -> nicht
manipulierbar. Fuer Portal wird receiptPath aus der Response entfernt
(Belege bleiben staff-only). Route requirePermission contracts:read.

Verifiziert: Staff -> alle Belege; Portal-scoped -> nur eigene, korrekt
zugeordnet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 10:23:53 +02:00
co-authored by Claude Opus 4.8
parent bbe1aed230
commit 48e65be91c
9 changed files with 281 additions and 2 deletions
@@ -6,6 +6,7 @@ import * as creditNoteService from '../services/creditNote.service.js';
import { effectiveNumber } from '../services/creditNote.service.js';
import * as numberRangeService from '../services/creditNoteNumberRange.service.js';
import * as deliveryRangeService from '../services/deliveryNoteNumberRange.service.js';
import * as authorizationService from '../services/authorization.service.js';
import { generateCreditNotePdf } from '../services/creditNotePdf.service.js';
// Gutschriften sind ein reiner Mitarbeiter-/Admin-Bereich (interne
@@ -42,6 +43,43 @@ function handleError(res: Response, error: unknown, fallback: string) {
} as ApiResponse);
}
// ---- Gesamtübersicht (Hauptmenü) ----
// Anders als die übrigen Credit-Note-Endpunkte NICHT staff-only: Portal-Kunden
// dürfen ihre eigenen (+ vertretene) Belege sehen. Scoping über customerIds
// aus dem JWT eine im Body/Query mitgeschickte customerId hat keinen Effekt.
export async function listAll(req: AuthRequest, res: Response): Promise<void> {
try {
// Portal-User: nur eigene + vertretene Kunden MIT Vollmacht.
let customerIds: number[] | undefined;
if (req.user?.isCustomerPortal && req.user.customerId) {
customerIds = [req.user.customerId];
const representedIds: number[] = (req.user as any).representedCustomerIds || [];
for (const repCustId of representedIds) {
if (await authorizationService.hasAuthorization(repCustId, req.user.customerId)) {
customerIds.push(repCustId);
}
}
}
const page = parseInt((req.query.page as string) || '1') || 1;
const limit = Math.min(parseInt((req.query.limit as string) || '50') || 50, 200);
const search = typeof req.query.search === 'string' ? req.query.search : undefined;
const result = await creditNoteService.getAllCreditNotes({ customerIds, page, limit, search });
// Portal-Kunden dürfen keine Überweisungsbelege laden → receiptPath aus der
// Response entfernen (Beleg-Download bleibt ohnehin staff-only).
const isPortal = !!req.user?.isCustomerPortal;
const items = isPortal
? result.items.map((cn) => ({ ...cn, receiptPath: null }))
: result.items;
res.json({ success: true, data: { items, pagination: result.pagination } } as ApiResponse);
} catch (error) {
handleError(res, error, 'Fehler beim Laden der Belegübersicht');
}
}
// ---- Gutschriften pro Vertrag ----
export async function listByContract(req: AuthRequest, res: Response): Promise<void> {