Some checks failed
Build and Push Docker Images / verify-docs-sync (push) Failing after 9s
Build and Push Docker Images / build-backend (push) Has been skipped
Build and Push Docker Images / build-frontend (push) Has been skipped
Build and Push Docker Images / build-docs (push) Has been skipped
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
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." });
|
|
}
|
|
});
|
|
|
|
|
|
}
|