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
143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
// src/services/loadValidEvents.ts
|
|
|
|
import { stafftimeevents } from "../../../db/schema";
|
|
import {sql, and, eq, gte, lte, inArray, asc} from "drizzle-orm";
|
|
import { FastifyInstance } from "fastify";
|
|
|
|
export type TimeType = "work_start" | "work_end" | "pause_start" | "pause_end" | "sick_start" | "sick_end" | "vacation_start" | "vacation_end" | "ovetime_compensation_start" | "overtime_compensation_end";
|
|
|
|
|
|
// Die Definition des TimeEvent Typs, der zurückgegeben wird (muss mit dem tatsächlichen Typ übereinstimmen)
|
|
export type TimeEvent = {
|
|
id: string;
|
|
eventtype: string;
|
|
eventtime: Date;
|
|
actoruser_id?: string;
|
|
related_event_id: string | null;
|
|
payload?: Record<string, any> | null;
|
|
created_at?: Date | null;
|
|
};
|
|
|
|
const EVENT_TYPE_ORDER: Record<string, number> = {
|
|
auto_stop: 10,
|
|
work_end: 10,
|
|
pause_end: 10,
|
|
vacation_end: 10,
|
|
sick_end: 10,
|
|
overtime_compensation_end: 10,
|
|
work_start: 20,
|
|
pause_start: 20,
|
|
vacation_start: 20,
|
|
sick_start: 20,
|
|
overtime_compensation_start: 20,
|
|
submitted: 30,
|
|
approved: 30,
|
|
rejected: 30,
|
|
invalidated: 40,
|
|
};
|
|
|
|
export function compareTimeEvents(a: TimeEvent, b: TimeEvent) {
|
|
const eventTimeDiff = a.eventtime.getTime() - b.eventtime.getTime();
|
|
if (eventTimeDiff !== 0) return eventTimeDiff;
|
|
|
|
const typeOrderDiff = (EVENT_TYPE_ORDER[a.eventtype] ?? 999) - (EVENT_TYPE_ORDER[b.eventtype] ?? 999);
|
|
if (typeOrderDiff !== 0) return typeOrderDiff;
|
|
|
|
const createdAtDiff = (a.created_at?.getTime() ?? 0) - (b.created_at?.getTime() ?? 0);
|
|
if (createdAtDiff !== 0) return createdAtDiff;
|
|
|
|
return a.id.localeCompare(b.id);
|
|
}
|
|
|
|
export async function loadValidEvents(
|
|
server: FastifyInstance,
|
|
tenantId: number,
|
|
userId: string,
|
|
from: Date,
|
|
to: Date
|
|
): Promise<TimeEvent[]> {
|
|
// Definieren Sie einen Alias für die stafftimeevents Tabelle in der äußeren Abfrage
|
|
const baseEvents = stafftimeevents;
|
|
|
|
// Die Subquery, um alle IDs zu finden, die ungültig gemacht wurden
|
|
// Wir nennen die innere Tabelle 'invalidatingEvents'
|
|
const invalidatingEvents = server.db
|
|
.select({
|
|
invalidatedId: baseEvents.invalidates_event_id
|
|
})
|
|
.from(baseEvents)
|
|
.as('invalidating_events');
|
|
|
|
// Die Hauptabfrage
|
|
const result = await server.db
|
|
.select()
|
|
.from(baseEvents)
|
|
.where(
|
|
and(
|
|
// 1. Tenant und User filtern
|
|
eq(baseEvents.tenant_id, tenantId),
|
|
eq(baseEvents.user_id, userId),
|
|
|
|
// 2. Zeitbereich filtern (Typensicher)
|
|
gte(baseEvents.eventtime, from),
|
|
lte(baseEvents.eventtime, to),
|
|
|
|
// 3. WICHTIG: Korrekturen ausschließen (NOT EXISTS)
|
|
// Schließe jedes Event aus, dessen ID in der Liste der invalidates_event_id erscheint.
|
|
sql`
|
|
not exists (
|
|
select 1
|
|
from ${stafftimeevents} i
|
|
where i.invalidates_event_id = ${baseEvents.id}
|
|
)
|
|
`
|
|
)
|
|
)
|
|
.orderBy(
|
|
asc(baseEvents.eventtime),
|
|
asc(baseEvents.created_at),
|
|
asc(baseEvents.id)
|
|
);
|
|
|
|
// Mapping auf den sauberen TimeEvent Typ
|
|
return result.map(e => ({
|
|
id: e.id,
|
|
eventtype: e.eventtype,
|
|
eventtime: e.eventtime,
|
|
actoruser_id: e.actoruser_id,
|
|
related_event_id: e.related_event_id,
|
|
payload: e.payload,
|
|
created_at: e.created_at,
|
|
})) as TimeEvent[];
|
|
}
|
|
|
|
export async function loadRelatedAdminEvents(server, eventIds) {
|
|
if (eventIds.length === 0) return [];
|
|
|
|
// Lädt alle administrativen Events, die sich auf die faktischen Event-IDs beziehen
|
|
const adminEvents = await server.db
|
|
.select()
|
|
.from(stafftimeevents)
|
|
.where(
|
|
and(
|
|
inArray(stafftimeevents.related_event_id, eventIds),
|
|
// Wir müssen hier die Entkräftung prüfen, um z.B. einen abgelehnten submitted-Event auszuschließen
|
|
sql`
|
|
not exists (
|
|
select 1
|
|
from ${stafftimeevents} i
|
|
where i.invalidates_event_id = ${stafftimeevents}.id
|
|
)
|
|
`,
|
|
)
|
|
)
|
|
// Muss nach Zeit sortiert sein, um den Status korrekt zu bestimmen!
|
|
.orderBy(
|
|
asc(stafftimeevents.eventtime),
|
|
asc(stafftimeevents.created_at),
|
|
asc(stafftimeevents.id)
|
|
);
|
|
|
|
return adminEvents as TimeEvent[];
|
|
}
|