Gutschriften/Kuendigung: DRAFT-endDate-Schutz + Belege staff-only
Pentest R138 Hygiene + neue Vorgaben: 1) endDate wird bei DRAFT-Vertraegen NICHT mehr gesetzt (Entwurf = nur Vorlage). Nur Status wurde vorher geschont, endDate zog trotzdem mit. 2) Ueberweisungsbelege (credit-note-receipts) sind jetzt reine Mitarbeiter/Admin-Downloads: neuer FileOwner-kind 'contract-staff' blockt Portal-Kunden im fileDownload-Controller. Das generierte Gutschrift-PDF (credit-notes) bleibt vertragsbasiert -> der besitzende Kunde darf seine eigene Gutschrift laden. Bereits vorher abgesichert (bestaetigt): Kunden koennen keine Gutschriften anlegen (blockPortal) und keine Belege hochladen (Portal-403 im Upload). Verifiziert: DRAFT haelt endDate; Beleg-Owner=contract-staff, PDF-Owner=contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,13 @@ export async function downloadFile(req: AuthRequest, res: Response): Promise<voi
|
|||||||
if (!(await canAccessCustomer(req, res, owner.customerId))) return;
|
if (!(await canAccessCustomer(req, res, owner.customerId))) return;
|
||||||
} else if (owner.kind === 'contract') {
|
} else if (owner.kind === 'contract') {
|
||||||
if (!(await canAccessContract(req, res, owner.contractId))) return;
|
if (!(await canAccessContract(req, res, owner.contractId))) return;
|
||||||
|
} else if (owner.kind === 'contract-staff') {
|
||||||
|
// Nur Mitarbeiter/Admin – Portal-Kunden nie (z.B. Gutschrift-
|
||||||
|
// Überweisungsbelege). Staff dürfen wie bei allen Vertrags-Files.
|
||||||
|
if (req.user?.isCustomerPortal) {
|
||||||
|
res.status(403).json({ success: false, error: 'Keine Berechtigung' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else if (owner.kind === 'admin') {
|
} else if (owner.kind === 'admin') {
|
||||||
// PDF-Vorlagen: nur Mitarbeiter mit settings:read
|
// PDF-Vorlagen: nur Mitarbeiter mit settings:read
|
||||||
const perms = req.user?.permissions || [];
|
const perms = req.user?.permissions || [];
|
||||||
|
|||||||
@@ -201,7 +201,13 @@ export async function maybeCancelOnCancellationConfirmation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vertragsende = Kündigungsdatum (Bestätigungsdatum), falls vorhanden.
|
// Vertragsende = Kündigungsdatum (Bestätigungsdatum), falls vorhanden.
|
||||||
if (c.cancellationConfirmationDate && asDay(c.endDate) !== asDay(c.cancellationConfirmationDate)) {
|
// NICHT bei DRAFT: ein Entwurf ist nur eine Vorlage und bekommt kein
|
||||||
|
// berechnetes Vertragsende (Pentest R138, Hygiene-Punkt).
|
||||||
|
if (
|
||||||
|
c.status !== 'DRAFT' &&
|
||||||
|
c.cancellationConfirmationDate &&
|
||||||
|
asDay(c.endDate) !== asDay(c.cancellationConfirmationDate)
|
||||||
|
) {
|
||||||
updateData.endDate = c.cancellationConfirmationDate;
|
updateData.endDate = c.cancellationConfirmationDate;
|
||||||
changes.endDate = { vorher: asDay(c.endDate), nachher: asDay(c.cancellationConfirmationDate) };
|
changes.endDate = { vorher: asDay(c.endDate), nachher: asDay(c.cancellationConfirmationDate) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import prisma from '../lib/prisma.js';
|
|||||||
export type FileOwner =
|
export type FileOwner =
|
||||||
| { kind: 'customer'; customerId: number }
|
| { kind: 'customer'; customerId: number }
|
||||||
| { kind: 'contract'; contractId: number }
|
| { kind: 'contract'; contractId: number }
|
||||||
|
// Wie 'contract', aber Portal-Kunden ausgeschlossen (nur Mitarbeiter/Admin).
|
||||||
|
| { kind: 'contract-staff'; contractId: number }
|
||||||
| { kind: 'admin' }
|
| { kind: 'admin' }
|
||||||
| { kind: 'gdpr-admin' };
|
| { kind: 'gdpr-admin' };
|
||||||
|
|
||||||
@@ -93,14 +95,21 @@ export async function findUploadOwner(uploadPath: string): Promise<FileOwner | n
|
|||||||
return r?.contractId ? { kind: 'contract', contractId: r.contractId } : null;
|
return r?.contractId ? { kind: 'contract', contractId: r.contractId } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'credit-note-receipts':
|
case 'credit-note-receipts': {
|
||||||
case 'credit-notes': {
|
// Überweisungsbeleg: NUR Mitarbeiter/Admin. Portal-Kunden dürfen Belege
|
||||||
// Überweisungsbeleg bzw. generiertes Gutschrift-PDF – Owner ist der
|
// nicht herunterladen (nur ihre Gutschrift selbst).
|
||||||
// Vertrag der Gutschrift.
|
|
||||||
const r = await prisma.creditNote.findFirst({
|
const r = await prisma.creditNote.findFirst({
|
||||||
where: subDir === 'credit-note-receipts'
|
where: { receiptPath: uploadPath },
|
||||||
? { receiptPath: uploadPath }
|
select: { contractId: true },
|
||||||
: { pdfPath: uploadPath },
|
});
|
||||||
|
return r ? { kind: 'contract-staff', contractId: r.contractId } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'credit-notes': {
|
||||||
|
// Generiertes Gutschrift-PDF: Owner ist der Vertrag der Gutschrift –
|
||||||
|
// der besitzende Kunde darf seine eigene Gutschrift laden.
|
||||||
|
const r = await prisma.creditNote.findFirst({
|
||||||
|
where: { pdfPath: uploadPath },
|
||||||
select: { contractId: true },
|
select: { contractId: true },
|
||||||
});
|
});
|
||||||
return r ? { kind: 'contract', contractId: r.contractId } : null;
|
return r ? { kind: 'contract', contractId: r.contractId } : null;
|
||||||
|
|||||||
Reference in New Issue
Block a user