fix #29 Corrected User ID
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 33s
Build and Push Docker Images / build-frontend (push) Successful in 16s

This commit is contained in:
2026-01-08 12:26:13 +01:00
parent 2cb0d9b607
commit 76b363fdaf

View File

@@ -62,17 +62,17 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
// 🆕 POST /staff/time/edit (Bearbeiten durch Invalidieren + Neu erstellen) // 🆕 POST /staff/time/edit (Bearbeiten durch Invalidieren + Neu erstellen)
server.post("/staff/time/edit", async (req, reply) => { server.post("/staff/time/edit", async (req, reply) => {
try { try {
const userId = req.user.user_id; // 1. Der "Actor" ist der, der gerade eingeloggt ist (z.B. Manager)
const actorId = req.user.user_id;
const tenantId = req.user.tenant_id; const tenantId = req.user.tenant_id;
// Wir erwarten das komplette Paket für die Änderung
const { const {
originalEventIds, // Array der IDs, die "gelöscht" werden sollen (Start ID, End ID) originalEventIds,
newStart, // ISO String newStart,
newEnd, // ISO String newEnd,
newType, // z.B. 'work', 'vacation' newType,
description, description,
reason // Warum wurde geändert? (Audit) reason
} = req.body as { } = req.body as {
originalEventIds: string[], originalEventIds: string[],
newStart: string, newStart: string,
@@ -86,41 +86,66 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
return reply.code(400).send({ error: "Keine Events zum Bearbeiten angegeben." }); return reply.code(400).send({ error: "Keine Events zum Bearbeiten angegeben." });
} }
// 1. Transaction starten (damit alles oder nichts passiert) // -----------------------------------------------------------
// SCHRITT A: Den eigentlichen Besitzer (Mitarbeiter) ermitteln
// -----------------------------------------------------------
// Wir holen uns das erste Event aus der Liste, um zu sehen, wem es gehört.
const existingEvents = await server.db
.select({
user_id: stafftimeevents.user_id,
tenant_id: stafftimeevents.tenant_id
})
.from(stafftimeevents)
.where(and(
eq(stafftimeevents.id, originalEventIds[0]),
eq(stafftimeevents.tenant_id, tenantId) // Sicherheitscheck: Nur im eigenen Tenant
))
.limit(1);
if (existingEvents.length === 0) {
return reply.code(404).send({ error: "Ursprüngliches Event nicht gefunden oder Zugriff verweigert." });
}
// Das ist der Mitarbeiter, dem die Zeit gehört
const targetUserId = existingEvents[0].user_id;
// -----------------------------------------------------------
// SCHRITT B: Transaktion durchführen
// -----------------------------------------------------------
await server.db.transaction(async (tx) => { await server.db.transaction(async (tx) => {
// A. INVALIDIEREN (Die alten Events "löschen") // 1. INVALIDIEREN
// Wir erstellen für jedes alte Event ein 'invalidated' Event // Wir nutzen 'targetUserId' als Besitzer des Events, aber 'actorId' als Auslöser
const invalidations = originalEventIds.map(id => ({ const invalidations = originalEventIds.map(id => ({
tenant_id: tenantId, tenant_id: tenantId,
user_id: userId, // Gehört dem Mitarbeiter user_id: targetUserId, // <--- WICHTIG: Gehört dem Mitarbeiter
actortype: "user", actortype: "user",
actoruser_id: userId, // Wer hat geändert? actoruser_id: actorId, // <--- WICHTIG: Geändert durch Manager/Self
eventtime: new Date(), eventtime: new Date(),
eventtype: "invalidated", // <--- NEUER TYP: Muss in loadValidEvents gefiltert werden! eventtype: "invalidated",
source: "WEB", source: "WEB",
related_event_id: id, // Zeigt auf das alte Event related_event_id: id,
metadata: { metadata: {
reason: reason || "Bearbeitung", reason: reason || "Bearbeitung",
replaced_by_edit: true replaced_by_edit: true
} }
})); }));
// Batch Insert
// @ts-ignore // @ts-ignore
await tx.insert(stafftimeevents).values(invalidations); await tx.insert(stafftimeevents).values(invalidations);
// B. NEU ERSTELLEN (Die korrigierten Events anlegen) // 2. NEU ERSTELLEN
// Start Event // Start Event
// @ts-ignore // @ts-ignore
await tx.insert(stafftimeevents).values({ await tx.insert(stafftimeevents).values({
tenant_id: tenantId, tenant_id: tenantId,
user_id: userId, user_id: targetUserId, // <--- Gehört dem Mitarbeiter
actortype: "user", actortype: "user",
actoruser_id: userId, actoruser_id: actorId, // <--- Erstellt durch Manager/Self
eventtime: new Date(newStart), eventtime: new Date(newStart),
eventtype: `${newType}_start`, // z.B. work_start eventtype: `${newType}_start`,
source: "WEB", source: "WEB",
payload: { description: description || "" } payload: { description: description || "" }
}); });
@@ -130,11 +155,11 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
// @ts-ignore // @ts-ignore
await tx.insert(stafftimeevents).values({ await tx.insert(stafftimeevents).values({
tenant_id: tenantId, tenant_id: tenantId,
user_id: userId, user_id: targetUserId, // <--- Gehört dem Mitarbeiter
actortype: "user", actortype: "user",
actoruser_id: userId, actoruser_id: actorId, // <--- Erstellt durch Manager/Self
eventtime: new Date(newEnd), eventtime: new Date(newEnd),
eventtype: `${newType}_end`, // z.B. work_end eventtype: `${newType}_end`,
source: "WEB" source: "WEB"
}); });
} }