diff --git a/backend/src/routes/cockpit.ts b/backend/src/routes/cockpit.ts index aa8525c..fc6cedb 100644 --- a/backend/src/routes/cockpit.ts +++ b/backend/src/routes/cockpit.ts @@ -1,56 +1,112 @@ -import { Router, Request, Response } from 'express'; +import { Router, Response } from 'express'; import { PrismaClient } from '@prisma/client'; import { PaymentService } from '../modules/banking/paymentService'; -import { requireAuth } from '../middleware/auth'; +import { AuthedRequest, requireAuth } from '../middleware/auth'; const prisma = new PrismaClient(); const paymentService = new PaymentService(prisma); -/** Router für das Vermieter-Cockpit-Dashboard (aggregierte Übersichtsdaten). */ +/** Router für das Cockpit-Dashboard (aggregierte Übersichtsdaten). */ export const cockpitRouter = Router(); +const TICKET_SELECT = { + id: true, + title: true, + category: true, + priority: true, + status: true, + createdAt: true, + room: { select: { roomNumber: true } }, + creator: { select: { id: true, fullName: true } }, +} as const; + // Erfordert Login: sensible Miet- und Kontaktdaten dürfen nicht öffentlich sein. -cockpitRouter.get('/cockpit/summary', requireAuth, async (_req: Request, res: Response) => { - const [trafficLights, openTickets, rooms] = await Promise.all([ +// +// Berechtigungsregel: Mieter dürfen NUR ihre eigenen Miet-/Vertragsdaten und +// Tickets sehen (plus Gemeinschaftsbereich-Tickets ohne Zimmerzuordnung) — +// niemals die Miet-/Kontaktdaten oder privaten Zimmer-Tickets anderer Mieter. +// Vermieter/Admin sehen weiterhin die vollständige Übersicht über die WG. +cockpitRouter.get('/cockpit/summary', requireAuth, async (req: AuthedRequest, res: Response) => { + const isLandlord = req.user!.role === 'LANDLORD' || req.user!.role === 'ADMIN'; + + if (isLandlord) { + const [trafficLights, openTickets, rooms] = await Promise.all([ + paymentService.getRentTrafficLights(), + prisma.ticket.findMany({ + where: { status: { in: ['OPEN', 'IN_PROGRESS'] } }, + orderBy: { createdAt: 'desc' }, + select: TICKET_SELECT, + }), + prisma.room.findMany({ + select: { id: true, roomNumber: true, sizeSqm: true, status: true }, + orderBy: { roomNumber: 'asc' }, + }), + ]); + + const counts = { GREEN: 0, YELLOW: 0, RED: 0 } as Record<'GREEN' | 'YELLOW' | 'RED', number>; + let totalWarmRentSum = 0; + for (const light of trafficLights) { + counts[light.status]++; + totalWarmRentSum += light.amountDue; + } + + return res.status(200).json({ + role: req.user!.role, + generatedAt: new Date().toISOString(), + rooms, + trafficLights, + summary: { + totalRooms: rooms.length, + rentGreen: counts.GREEN, + rentYellow: counts.YELLOW, + rentRed: counts.RED, + totalMonthlyRentTarget: totalWarmRentSum, + openTicketsCount: openTickets.length, + }, + openTickets, + }); + } + + // --- Mieter-Ansicht: nur eigene Daten + Gemeinschaftsbereich --- + const me = await prisma.user.findUnique({ + where: { id: req.user!.id }, + select: { roomId: true }, + }); + const ownRoomId = me?.roomId ?? null; + + const [allTrafficLights, openTickets] = await Promise.all([ paymentService.getRentTrafficLights(), prisma.ticket.findMany({ - where: { status: { in: ['OPEN', 'IN_PROGRESS'] } }, - orderBy: { createdAt: 'desc' }, - select: { - id: true, - title: true, - category: true, - priority: true, - status: true, - createdAt: true, - room: { select: { roomNumber: true } }, + where: { + status: { in: ['OPEN', 'IN_PROGRESS'] }, + OR: [ + { creatorId: req.user!.id }, + { roomId: null }, + ...(ownRoomId ? [{ roomId: ownRoomId }] : []), + ], }, - }), - prisma.room.findMany({ - select: { id: true, roomNumber: true, sizeSqm: true, status: true }, - orderBy: { roomNumber: 'asc' }, + orderBy: { createdAt: 'desc' }, + select: TICKET_SELECT, }), ]); - const counts = { GREEN: 0, YELLOW: 0, RED: 0 } as Record<'GREEN' | 'YELLOW' | 'RED', number>; - let totalWarmRentSum = 0; - for (const light of trafficLights) { - counts[light.status]++; - totalWarmRentSum += light.amountDue; + // Eigenen aktiven Vertrag laden, um die passende Ampel aus der Gesamtliste herauszufiltern + // (getRentTrafficLights liefert alle Verträge — hier wird strikt auf den eigenen reduziert). + const myContract = await prisma.contract.findFirst({ + where: { userId: req.user!.id, isActive: true }, + include: { room: true }, + }); + + let myRent = null; + if (myContract) { + myRent = allTrafficLights.find((l) => l.contractId === myContract.id) || null; } res.status(200).json({ + role: req.user!.role, generatedAt: new Date().toISOString(), - rooms, - trafficLights, - summary: { - totalRooms: rooms.length, - rentGreen: counts.GREEN, - rentYellow: counts.YELLOW, - rentRed: counts.RED, - totalMonthlyRentTarget: totalWarmRentSum, - openTicketsCount: openTickets.length, - }, + myRoom: myContract?.room ? { id: myContract.room.id, roomNumber: myContract.room.roomNumber } : null, + myRent, openTickets, }); });