diff --git a/src/routes/staff/time.ts b/src/routes/staff/time.ts index 33f57c1..d1c3a58 100644 --- a/src/routes/staff/time.ts +++ b/src/routes/staff/time.ts @@ -40,7 +40,8 @@ export default async function staffTimeRoutes(server: FastifyInstance) { actoruser_id: userId, eventtime: normalizeDate(body.eventtime), eventtype: body.eventtype, - source: "WEB" + source: "WEB", + payload: body.payload // Payload (z.B. Description) mit speichern } console.log(dataToInsert) @@ -58,6 +59,95 @@ export default async function staffTimeRoutes(server: FastifyInstance) { } }) + // 🆕 POST /staff/time/edit (Bearbeiten durch Invalidieren + Neu erstellen) + server.post("/staff/time/edit", async (req, reply) => { + try { + const userId = req.user.user_id; + const tenantId = req.user.tenant_id; + + // Wir erwarten das komplette Paket für die Änderung + const { + originalEventIds, // Array der IDs, die "gelöscht" werden sollen (Start ID, End ID) + newStart, // ISO String + newEnd, // ISO String + newType, // z.B. 'work', 'vacation' + description, + reason // Warum wurde geändert? (Audit) + } = req.body as { + originalEventIds: string[], + newStart: string, + newEnd: string | null, + newType: string, + description?: string, + reason?: string + }; + + if (!originalEventIds || originalEventIds.length === 0) { + return reply.code(400).send({ error: "Keine Events zum Bearbeiten angegeben." }); + } + + // 1. Transaction starten (damit alles oder nichts passiert) + await server.db.transaction(async (tx) => { + + // A. INVALIDIEREN (Die alten Events "löschen") + // Wir erstellen für jedes alte Event ein 'invalidated' Event + const invalidations = originalEventIds.map(id => ({ + tenant_id: tenantId, + user_id: userId, // Gehört dem Mitarbeiter + actortype: "user", + actoruser_id: userId, // Wer hat geändert? + eventtime: new Date(), + eventtype: "invalidated", // <--- NEUER TYP: Muss in loadValidEvents gefiltert werden! + source: "WEB", + related_event_id: id, // Zeigt auf das alte Event + metadata: { + reason: reason || "Bearbeitung", + replaced_by_edit: true + } + })); + + // Batch Insert + // @ts-ignore + await tx.insert(stafftimeevents).values(invalidations); + + // B. NEU ERSTELLEN (Die korrigierten Events anlegen) + + // Start Event + // @ts-ignore + await tx.insert(stafftimeevents).values({ + tenant_id: tenantId, + user_id: userId, + actortype: "user", + actoruser_id: userId, + eventtime: new Date(newStart), + eventtype: `${newType}_start`, // z.B. work_start + source: "WEB", + payload: { description: description || "" } + }); + + // End Event (nur wenn vorhanden) + if (newEnd) { + // @ts-ignore + await tx.insert(stafftimeevents).values({ + tenant_id: tenantId, + user_id: userId, + actortype: "user", + actoruser_id: userId, + eventtime: new Date(newEnd), + eventtype: `${newType}_end`, // z.B. work_end + source: "WEB" + }); + } + }); + + return { success: true }; + + } catch (err: any) { + console.error("Fehler beim Bearbeiten:", err); + return reply.code(500).send({ error: err.message }); + } + }); + // POST /staff/time/submit server.post("/staff/time/submit", async (req, reply) => { try { @@ -209,6 +299,7 @@ export default async function staffTimeRoutes(server: FastifyInstance) { const endDate = new Date("2100-12-31"); // SCHRITT 1: Lade ALLE Events für den ZIEL-USER (evaluatedUserId) + // WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern! const allEventsInTimeFrame = await loadValidEvents( server, tenantId, @@ -283,6 +374,7 @@ export default async function staffTimeRoutes(server: FastifyInstance) { // --- 3. Ausführung der Logik für den ermittelten Benutzer --- // SCHRITT 1: Lade ALLE gültigen Events im Zeitraum + // WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern! const allEventsInTimeFrame = await loadValidEvents( server, tenantId, evaluatedUserId, startDate, endDate // Verwendung der evaluatedUserId ); @@ -335,226 +427,4 @@ export default async function staffTimeRoutes(server: FastifyInstance) { } }); - - - /*// ------------------------------------------------------------- - // ▶ Neue Zeit starten - // ------------------------------------------------------------- - server.post("/staff/time", async (req, reply) => { - try { - const userId = req.user.user_id - const tenantId = req.user.tenant_id - - const body = req.body as any - - const normalizeDate = (val: any) => { - if (!val) return null - const d = new Date(val) - return isNaN(d.getTime()) ? null : d - } - - const dataToInsert = { - tenant_id: tenantId, - user_id: body.user_id || userId, - type: body.type || "work", - description: body.description || null, - started_at: normalizeDate(body.started_at), - stopped_at: normalizeDate(body.stopped_at), - } - - const [created] = await server.db - .insert(stafftimeentries) - .values(dataToInsert) - .returning() - - return created - } catch (err: any) { - console.error(err) - return reply.code(400).send({ error: err.message }) - } - }) - - // ------------------------------------------------------------- - // ▶ Zeit stoppen - // ------------------------------------------------------------- - server.put<{ - Params: { id: string }, - Body: { stopped_at: string } - }>("/staff/time/:id/stop", async (req, reply) => { - try { - const { id } = req.params - const { stopped_at } = req.body - - // Normalize timestamp - const normalizeDate = (val: any) => { - const d = new Date(val) - return isNaN(d.getTime()) ? null : d - } - - const stopTime = normalizeDate(stopped_at) - if (!stopTime) { - return reply.code(400).send({ error: "Invalid stopped_at timestamp" }) - } - - const [updated] = await server.db - .update(stafftimeentries) - .set({ - stopped_at: stopTime, - updated_at: new Date(), - }) - .where(eq(stafftimeentries.id, id)) - .returning() - - if (!updated) { - return reply.code(404).send({ error: "Time entry not found" }) - } - - return reply.send(updated) - } catch (err: any) { - console.error("STOP ERROR:", err) - return reply.code(500).send({ error: err.message || "Internal server error" }) - } - }) - - // ------------------------------------------------------------- - // ▶ Liste aller Zeiten - // ------------------------------------------------------------- - server.get("/staff/time", async (req, reply) => { - try { - const { from, to, type, user_id } = req.query as any - const { tenant_id, user_id: currentUserId } = req.user - - let where = and(eq(stafftimeentries.tenant_id, tenant_id)) - - // Zugriffsbeschränkung - if (!req.hasPermission("staff.time.read_all")) { - where = and(where, eq(stafftimeentries.user_id, currentUserId)) - } else if (user_id) { - where = and(where, eq(stafftimeentries.user_id, user_id)) - } - - if (from) where = and(where, gte(stafftimeentries.started_at, from)) - if (to) where = and(where, lte(stafftimeentries.started_at, to)) - if (type) where = and(where, eq(stafftimeentries.type, type)) - - const rows = await server.db - .select() - .from(stafftimeentries) - .where(where) - .orderBy(desc(stafftimeentries.started_at)) - - return rows - } catch (err) { - console.error(err) - return reply.code(400).send({ error: (err as Error).message }) - } - }) - - // ------------------------------------------------------------- - // ▶ Einzelne Zeit (inkl. Connects) - // ------------------------------------------------------------- - server.get("/staff/time/:id", async (req, reply) => { - try { - const { id } = req.params as any - - const rows = await server.db - .select() - .from(stafftimeentries) - .where(eq(stafftimeentries.id, id)) - .limit(1) - - if (!rows.length) return reply.code(404).send({ error: "Not found" }) - - const entry = rows[0] - - const connects = await server.db - .select() - .from(stafftimenetryconnects) - .where(eq(stafftimenetryconnects.stafftimeentry, id)) - - return { - ...entry, - staff_time_entry_connects: connects - } - } catch (err) { - return reply.code(400).send({ error: (err as Error).message }) - } - }) - - // ------------------------------------------------------------- - // ▶ Zeit bearbeiten - // ------------------------------------------------------------- - // ▶ Zeit bearbeiten - server.put<{ - Params: { id: string }, - }>("/staff/time/:id", async (req, reply) => { - try { - const { id } = req.params - const body = req.body - - // Normalize all timestamp fields - const normalizeDate = (val: any) => { - if (!val) return null - const d = new Date(val) - return isNaN(d.getTime()) ? null : d - } - - - const updateData: any = { - // @ts-ignore - ...body, - updated_at: new Date(), - } - - // Only convert if present — avoid overriding with null unless sent - // @ts-ignore - if (body.started_at !== undefined) { - // @ts-ignore - updateData.started_at = normalizeDate(body.started_at) - } - // @ts-ignore - if (body.stopped_at !== undefined) { - // @ts-ignore - updateData.stopped_at = normalizeDate(body.stopped_at) - } - // @ts-ignore - if (body.approved_at !== undefined) { - // @ts-ignore - updateData.approved_at = normalizeDate(body.approved_at) - } - - const [updated] = await server.db - .update(stafftimeentries) - .set(updateData) - .where(eq(stafftimeentries.id, id)) - .returning() - - if (!updated) { - return reply.code(404).send({ error: "Time entry not found" }) - } - - return reply.send(updated) - } catch (err: any) { - console.error("UPDATE ERROR:", err) - return reply.code(500).send({ error: err.message || "Internal server error" }) - } - }) - - - // ------------------------------------------------------------- - // ▶ Zeit löschen - // ------------------------------------------------------------- - server.delete("/staff/time/:id", async (req, reply) => { - try { - const { id } = req.params as any - - await server.db - .delete(stafftimeentries) - .where(eq(stafftimeentries.id, id)) - - return { success: true } - } catch (err) { - return reply.code(400).send({ error: (err as Error).message }) - } - })*/ -} +} \ No newline at end of file