Add Putzplan cleaning-tasks route

This commit is contained in:
Giuseppe Lombardo 2026-08-12 18:50:23 +00:00
parent e3ac1e9c4a
commit 57ee85292c

View File

@ -0,0 +1,147 @@
import { Router, Response } from 'express';
import { PrismaClient, CleaningArea, CleaningStatus } from '@prisma/client';
import { AuthedRequest, requireAuth, requireRole } from '../middleware/auth';
const prisma = new PrismaClient();
/**
* Router für den Putzplan (rotierende Reinigungsaufgaben).
*
* Zugriffsregel: ein Mieter sieht (und kann abhaken) nur seine eigenen
* zugewiesenen Putzaufgaben. Vermieter/Admin sehen und verwalten den
* kompletten Putzplan aller Mieter.
*/
export const cleaningTasksRouter = Router();
const AREAS = Object.values(CleaningArea);
const TASK_SELECT = {
id: true,
area: true,
weekOf: true,
dueDate: true,
completedAt: true,
proofImageUrl: true,
status: true,
verifiedBy: true,
assignedUser: { select: { id: true, fullName: true } },
};
// Vermieter/Admin legen eine neue Putzaufgabe an (z. B. für die kommende Rotationswoche).
cleaningTasksRouter.post(
'/cleaning-tasks',
requireAuth,
requireRole('LANDLORD', 'ADMIN'),
async (req: AuthedRequest, res: Response) => {
const { assignedUserId, area, weekOf, dueDate } = req.body || {};
if (!assignedUserId || typeof assignedUserId !== 'string') {
return res.status(400).json({ error: 'assignedUserId ist erforderlich' });
}
if (!area || !AREAS.includes(area)) {
return res.status(400).json({ error: `Ungültiger Bereich. Erlaubt: ${AREAS.join(', ')}` });
}
if (!weekOf || Number.isNaN(Date.parse(weekOf))) {
return res.status(400).json({ error: 'weekOf (Datum) ist erforderlich' });
}
if (!dueDate || Number.isNaN(Date.parse(dueDate))) {
return res.status(400).json({ error: 'dueDate (Datum) ist erforderlich' });
}
const assignedUser = await prisma.user.findUnique({ where: { id: assignedUserId } });
if (!assignedUser) {
return res.status(400).json({ error: 'Zugewiesener Nutzer nicht gefunden' });
}
const task = await prisma.cleaningTask.create({
data: {
assignedUserId,
area,
weekOf: new Date(weekOf),
dueDate: new Date(dueDate),
status: CleaningStatus.PENDING,
},
select: TASK_SELECT,
});
res.status(201).json({ task });
},
);
cleaningTasksRouter.get('/cleaning-tasks', requireAuth, async (req: AuthedRequest, res: Response) => {
const isLandlord = req.user!.role === 'LANDLORD' || req.user!.role === 'ADMIN';
const statusFilter = typeof req.query.status === 'string' ? req.query.status : undefined;
const where: Record<string, unknown> = {};
if (statusFilter && (Object.values(CleaningStatus) as string[]).includes(statusFilter)) {
where.status = statusFilter;
}
if (!isLandlord) {
where.assignedUserId = req.user!.id;
}
const tasks = await prisma.cleaningTask.findMany({
where,
orderBy: [{ weekOf: 'desc' }, { dueDate: 'asc' }],
select: TASK_SELECT,
});
res.status(200).json({ tasks });
});
// Mieter hakt die eigene Putzaufgabe ab, optional mit Fotobeweis (URL).
// Vermieter/Admin dürfen ebenfalls jede Aufgabe abschließen (z. B. Vertretung).
cleaningTasksRouter.post(
'/cleaning-tasks/:id/complete',
requireAuth,
async (req: AuthedRequest, res: Response) => {
const task = await prisma.cleaningTask.findUnique({ where: { id: req.params.id } });
if (!task) return res.status(404).json({ error: 'Putzaufgabe nicht gefunden' });
const isLandlord = req.user!.role === 'LANDLORD' || req.user!.role === 'ADMIN';
if (!isLandlord && task.assignedUserId !== req.user!.id) {
return res.status(403).json({ error: 'Du kannst nur deine eigenen Putzaufgaben abhaken' });
}
const { proofImageUrl } = req.body || {};
const updated = await prisma.cleaningTask.update({
where: { id: req.params.id },
data: {
status: CleaningStatus.COMPLETED,
completedAt: new Date(),
proofImageUrl: typeof proofImageUrl === 'string' && proofImageUrl.trim() ? proofImageUrl.trim() : task.proofImageUrl,
},
select: TASK_SELECT,
});
res.status(200).json({ task: updated });
},
);
// Vermieter/Admin bestätigt eine erledigte Aufgabe (Mitbewohner-Verifizierung) oder markiert sie als verpasst.
cleaningTasksRouter.patch(
'/cleaning-tasks/:id',
requireAuth,
requireRole('LANDLORD', 'ADMIN'),
async (req: AuthedRequest, res: Response) => {
const { status } = req.body || {};
if (!status || !(Object.values(CleaningStatus) as string[]).includes(status)) {
return res.status(400).json({ error: `Ungültiger Status. Erlaubt: ${Object.values(CleaningStatus).join(', ')}` });
}
try {
const task = await prisma.cleaningTask.update({
where: { id: req.params.id },
data: {
status,
verifiedBy: status === CleaningStatus.VERIFIED ? req.user!.id : undefined,
},
select: TASK_SELECT,
});
res.status(200).json({ task });
} catch {
res.status(404).json({ error: 'Putzaufgabe nicht gefunden' });
}
},
);