Added Backend

This commit is contained in:
2026-01-06 12:07:43 +01:00
parent b013ef8f4b
commit 6f3d4c0bff
165 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
// src/services/buildTimeEvaluationFromSpans.ts
import { FastifyInstance } from "fastify";
import { and, eq, gte, lte, inArray } from "drizzle-orm";
import { authProfiles, holidays } from "../../../db/schema";
import { DerivedSpan } from "./derivetimespans.service"; // Importiert den angereicherten Span-Typ
// Definiert das erwartete Rückgabeformat
export type TimeEvaluationResult = {
user_id: string;
tenant_id: number;
from: string;
to: string;
// Sollzeit
timeSpanWorkingMinutes: number;
// Arbeitszeit Salden
sumWorkingMinutesSubmitted: number;
sumWorkingMinutesApproved: number;
// Abwesenheiten (minuten und Tage)
sumWorkingMinutesRecreationDays: number;
sumRecreationDays: number;
sumWorkingMinutesVacationDays: number;
sumVacationDays: number;
sumWorkingMinutesSickDays: number;
sumSickDays: number;
// Endsalden
saldoApproved: number; // Saldo basierend auf genehmigter Zeit
saldoSubmitted: number; // Saldo basierend auf eingereichter/genehmigter Zeit
spans: DerivedSpan[];
};
// Hilfsfunktion zur Berechnung der Minuten (nur für geschlossene Spannen)
const calcMinutes = (start: Date, end: Date | null): number => {
if (!end) return 0;
return (end.getTime() - start.getTime()) / 60000;
};
export async function buildTimeEvaluationFromSpans(
server: FastifyInstance,
user_id: string,
tenant_id: number,
startDateInput: string,
endDateInput: string,
// Der wichtigste Unterschied: Wir nehmen die angereicherten Spannen als Input
spans: DerivedSpan[]
): Promise<TimeEvaluationResult> {
const startDate = server.dayjs(startDateInput);
const endDate = server.dayjs(endDateInput);
// -------------------------------------------------------------
// 1⃣ Profil und Feiertage laden (WIE IM ALTEN SERVICE)
// -------------------------------------------------------------
const profileRows = await server.db
.select()
.from(authProfiles)
.where(
and(
eq(authProfiles.user_id, user_id),
eq(authProfiles.tenant_id, tenant_id)
)
)
.limit(1);
const profile = profileRows[0];
if (!profile) throw new Error("Profil konnte nicht geladen werden.");
const holidaysRows = await server.db
.select({
date: holidays.date,
})
.from(holidays)
.where(
and(
inArray(holidays.state_code, [profile.state_code, "DE"]),
gte(holidays.date, startDate.format("YYYY-MM-DD")),
lte(holidays.date, endDate.add(1, "day").format("YYYY-MM-DD"))
)
);
// -------------------------------------------------------------
// 2⃣ Sollzeit berechnen (WIE IM ALTEN SERVICE)
// -------------------------------------------------------------
let timeSpanWorkingMinutes = 0;
const totalDays = endDate.add(1, "day").diff(startDate, "days");
for (let i = 0; i < totalDays; i++) {
const date = startDate.add(i, "days");
const weekday = date.day();
timeSpanWorkingMinutes +=
(profile.weekly_regular_working_hours?.[weekday] || 0) * 60;
}
// -------------------------------------------------------------
// 3⃣ Arbeits- und Abwesenheitszeiten berechnen (NEUE LOGIK)
// -------------------------------------------------------------
let sumWorkingMinutesSubmitted = 0;
let sumWorkingMinutesApproved = 0;
let sumWorkingMinutesVacationDays = 0;
let sumVacationDays = 0;
let sumWorkingMinutesSickDays = 0;
let sumSickDays = 0;
// Akkumulieren der Zeiten basierend auf dem abgeleiteten Typ und Status
for (const span of spans) {
// **A. Arbeitszeiten (WORK)**
if (span.type === "work") {
const minutes = calcMinutes(span.startedAt, span.endedAt);
// Zähle zur eingereichten Summe, wenn der Status submitted oder approved ist
if (span.status === "submitted" || span.status === "approved") {
sumWorkingMinutesSubmitted += minutes;
}
// Zähle zur genehmigten Summe, wenn der Status approved ist
if (span.status === "approved") {
sumWorkingMinutesApproved += minutes;
}
}
// **B. Abwesenheiten (VACATION, SICK)**
// Wir verwenden die Logik aus dem alten Service: Berechnung der Sollzeit
// basierend auf den Tagen der Span (Voraussetzung: Spannen sind Volltages-Spannen)
if (span.type === "vacation" || span.type === "sick") {
// Behandle nur genehmigte Abwesenheiten für die Saldenberechnung
if (span.status !== "approved") {
continue;
}
const startDay = server.dayjs(span.startedAt).startOf('day');
// Wenn endedAt null ist (offene Span), nehmen wir das Ende des Zeitraums
const endDay = span.endedAt ? server.dayjs(span.endedAt).startOf('day') : endDate.startOf('day');
// Berechnung der Tage der Span
const days = endDay.diff(startDay, "day") + 1;
for (let i = 0; i < days; i++) {
const day = startDay.add(i, "day");
const weekday = day.day();
const hours = profile.weekly_regular_working_hours?.[weekday] || 0;
if (span.type === "vacation") {
sumWorkingMinutesVacationDays += hours * 60;
} else if (span.type === "sick") {
sumWorkingMinutesSickDays += hours * 60;
}
}
if (span.type === "vacation") {
sumVacationDays += days;
} else if (span.type === "sick") {
sumSickDays += days;
}
}
// PAUSE Spannen werden ignoriert, da sie in der faktischen Ableitung bereits von WORK abgezogen wurden.
}
// -------------------------------------------------------------
// 4⃣ Feiertagsausgleich (WIE IM ALTEN SERVICE)
// -------------------------------------------------------------
let sumWorkingMinutesRecreationDays = 0;
let sumRecreationDays = 0;
if (profile.recreation_days_compensation && holidaysRows?.length) {
holidaysRows.forEach(({ date }) => {
const weekday = server.dayjs(date).day();
const hours = profile.weekly_regular_working_hours?.[weekday] || 0;
sumWorkingMinutesRecreationDays += hours * 60;
sumRecreationDays++;
});
}
// -------------------------------------------------------------
// 5⃣ Salden berechnen (NEUE LOGIK)
// -------------------------------------------------------------
const totalCompensatedMinutes =
sumWorkingMinutesRecreationDays +
sumWorkingMinutesVacationDays +
sumWorkingMinutesSickDays;
// Saldo basierend auf GENEHMIGTER Arbeitszeit
const totalApprovedMinutes = sumWorkingMinutesApproved + totalCompensatedMinutes;
const saldoApproved = totalApprovedMinutes - timeSpanWorkingMinutes;
// Saldo basierend auf EINGEREICHTER und GENEHMIGTER Arbeitszeit
const totalSubmittedMinutes = sumWorkingMinutesSubmitted + totalCompensatedMinutes;
const saldoSubmitted = totalSubmittedMinutes - timeSpanWorkingMinutes;
// -------------------------------------------------------------
// 6⃣ Rückgabe
// -------------------------------------------------------------
return {
user_id,
tenant_id,
from: startDate.format("YYYY-MM-DD"),
to: endDate.format("YYYY-MM-DD"),
timeSpanWorkingMinutes,
sumWorkingMinutesSubmitted,
sumWorkingMinutesApproved,
sumWorkingMinutesRecreationDays,
sumRecreationDays,
sumWorkingMinutesVacationDays,
sumVacationDays,
sumWorkingMinutesSickDays,
sumSickDays,
saldoApproved,
saldoSubmitted,
spans,
};
}