import { FastifyInstance } from "fastify" import { stafftimeentries, stafftimenetryconnects } from "../../../db/schema" import { eq, and, gte, lte, desc } from "drizzle-orm" import {stafftimeevents} from "../../../db/schema/staff_time_events"; import {compareTimeEvents, loadRelatedAdminEvents, loadValidEvents} from "../../modules/time/loadvalidevents.service"; import {deriveTimeSpans} from "../../modules/time/derivetimespans.service"; import {buildTimeEvaluationFromSpans} from "../../modules/time/buildtimeevaluation.service"; import {z} from "zod"; import {enrichSpansWithStatus} from "../../modules/time/enrichtimespanswithstatus.service"; export default async function staffTimeRoutesInternal(server: FastifyInstance) { server.post("/staff/time/event", async (req, reply) => { try { const body = req.body as {user_id:string,tenant_id:number,eventtime:string,eventtype:string} const normalizeDate = (val: any) => { if (!val) return null const d = new Date(val) return isNaN(d.getTime()) ? null : d } const dataToInsert = { tenant_id: body.tenant_id, user_id: body.user_id, actortype: "user", actoruser_id: body.user_id, eventtime: normalizeDate(body.eventtime), eventtype: body.eventtype, source: "WEB" } console.log(dataToInsert) const [created] = await server.db .insert(stafftimeevents) //@ts-ignore .values(dataToInsert) .returning() return created } catch (err: any) { console.error(err) return reply.code(400).send({ error: err.message }) } }) // GET /api/staff/time/spans server.get("/staff/time/spans", async (req, reply) => { try { // Query-Parameter: targetUserId ist optional const { targetUserId, tenantId} = req.query as { targetUserId: string, tenantId:number }; // Falls eine targetUserId übergeben wurde, nutzen wir diese, sonst die eigene ID const evaluatedUserId = targetUserId; // 💡 "Unendlicher" Zeitraum, wie gewünscht const startDate = new Date(0); // 1970 const endDate = new Date("2100-12-31"); // SCHRITT 1: Lade ALLE Events für den ZIEL-USER (evaluatedUserId) const allEventsInTimeFrame = await loadValidEvents( server, tenantId, evaluatedUserId, // <--- Hier wird jetzt die variable ID genutzt startDate, endDate ); // SCHRITT 2: Filtere faktische Events const FACTUAL_EVENT_TYPES = new Set([ "work_start", "work_end", "pause_start", "pause_end", "sick_start", "sick_end", "vacation_start", "vacation_end", "overtime_compensation_start", "overtime_compensation_end", "auto_stop" ]); const factualEvents = allEventsInTimeFrame.filter(e => FACTUAL_EVENT_TYPES.has(e.eventtype)); // SCHRITT 3: Hole administrative Events const factualEventIds = factualEvents.map(e => e.id); if (factualEventIds.length === 0) { return []; } const relatedAdminEvents = await loadRelatedAdminEvents(server, factualEventIds); // SCHRITT 4: Kombinieren und Sortieren const combinedEvents = [ ...factualEvents, ...relatedAdminEvents, ].sort(compareTimeEvents); // SCHRITT 5: Spans ableiten const derivedSpans = deriveTimeSpans(combinedEvents); // SCHRITT 6: Spans anreichern const enrichedSpans = enrichSpansWithStatus(derivedSpans, combinedEvents); return enrichedSpans; } catch (error) { console.error("Fehler beim Laden der Spans:", error); return reply.code(500).send({ error: "Interner Fehler beim Laden der Zeitspannen." }); } }); }