This commit is contained in:
2025-12-08 12:15:20 +01:00
parent 1d3bf94b88
commit 428a002e9f
7 changed files with 495 additions and 427 deletions

View File

@@ -1,5 +1,10 @@
import {FastifyInstance} from "fastify";
import { FastifyInstance } from "fastify";
import {and, eq, gte, lte, asc, inArray} from "drizzle-orm";
import {
authProfiles,
stafftimeentries,
holidays,
} from "../../../db/schema";
export async function generateTimesEvaluation(
server: FastifyInstance,
@@ -8,136 +13,204 @@ export async function generateTimesEvaluation(
startDateInput: string,
endDateInput: string
) {
const startDate = server.dayjs(startDateInput)
const endDate = server.dayjs(endDateInput)
const startDate = server.dayjs(startDateInput);
const endDate = server.dayjs(endDateInput);
console.log(startDate.format("YYYY-MM-DD HH:mm:ss"));
console.log(endDate.format("YYYY-MM-DD HH:mm:ss"));
// 🧾 Profil laden (Arbeitszeiten + Bundesland)
const { data: profile, error: profileError } = await server.supabase
.from("auth_profiles")
.select("*")
.eq("user_id", user_id)
.eq("tenant_id", tenant_id)
.maybeSingle()
// -------------------------------------------------------------
// 1⃣ Profil laden
// -------------------------------------------------------------
const profileRows = await server.db
.select()
.from(authProfiles)
.where(
and(
eq(authProfiles.user_id, user_id),
eq(authProfiles.tenant_id, tenant_id)
)
)
.limit(1);
if (profileError || !profile) throw new Error("Profil konnte nicht geladen werden.")
const profile = profileRows[0];
// 🕒 Arbeitszeiten abrufen
const { data: timesRaw, error: timeError } = await server.supabase
.from("staff_time_entries")
.select("*")
.eq("tenant_id", tenant_id)
.eq("user_id", user_id)
.order("started_at", { ascending: true })
if (!profile) throw new Error("Profil konnte nicht geladen werden.");
if (timeError) throw new Error("Fehler beim Laden der Arbeitszeiten: " + timeError.message)
// -------------------------------------------------------------
// 2⃣ Arbeitszeiten laden
// -------------------------------------------------------------
const timesRaw = await server.db
.select()
.from(stafftimeentries)
.where(
and(
eq(stafftimeentries.tenant_id, tenant_id),
eq(stafftimeentries.user_id, user_id)
)
)
.orderBy(asc(stafftimeentries.started_at));
const isBetween = (spanStartDate,spanEndDate,startDate,endDate) => {
return server.dayjs(startDate).isBetween(spanStartDate, spanEndDate, "day", "[]") && server.dayjs(endDate).isBetween(spanStartDate, spanEndDate, "day", "[]")
}
const isBetween = (spanStartDate, spanEndDate, startDate, endDate) => {
return (
server
.dayjs(startDate)
.isBetween(spanStartDate, spanEndDate, "day", "[]") &&
server
.dayjs(endDate)
.isBetween(spanStartDate, spanEndDate, "day", "[]")
);
};
const times = timesRaw.filter((i) =>
isBetween(startDate, endDate, i.started_at, i.stopped_at)
);
const times = timesRaw.filter(i => isBetween(startDate,endDate,i.started_at,i.stopped_at) )
console.log(times);
console.log(times)
// -------------------------------------------------------------
// 3⃣ Feiertage laden
// -------------------------------------------------------------
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"))
)
);
// 📅 Feiertage aus Tabelle für Bundesland + DE
const { data: holidays, error: holidaysError } = await server.supabase
.from("holidays")
.select("date")
.in("state_code", [profile.state_code, "DE"])
.gte("date", startDate.format("YYYY-MM-DD"))
.lte("date", endDate.add(1,"day").format("YYYY-MM-DD"))
if (holidaysError) throw new Error("Fehler beim Laden der Feiertage: " + holidaysError.message)
// 🗓️ Sollzeit berechnen
let timeSpanWorkingMinutes = 0
const totalDays = endDate.add(1, "day").diff(startDate, "days")
// -------------------------------------------------------------
// 4⃣ Sollzeit berechnen
// -------------------------------------------------------------
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
const date = startDate.add(i, "days");
const weekday = date.day();
timeSpanWorkingMinutes +=
(profile.weekly_regular_working_hours?.[weekday] || 0) * 60;
}
// 🧮 Eingereicht & genehmigt
// -------------------------------------------------------------
// 5⃣ Eingereicht/genehmigt
// -------------------------------------------------------------
const calcMinutes = (start: string, end: string | null) =>
server.dayjs(end || new Date()).diff(server.dayjs(start), "minutes")
server.dayjs(end || new Date()).diff(server.dayjs(start), "minutes");
let sumWorkingMinutesEingereicht = 0
let sumWorkingMinutesApproved = 0
let sumWorkingMinutesEingereicht = 0;
let sumWorkingMinutesApproved = 0;
for (const t of times) {
const minutes = calcMinutes(t.started_at, t.stopped_at)
if(["submitted","approved"].includes(t.state) && t.type === "work")sumWorkingMinutesEingereicht += minutes
if (t.state === "approved" && t.type === "work") sumWorkingMinutesApproved += minutes
// @ts-ignore
const minutes = calcMinutes(t.started_at, t.stopped_at);
if (["submitted", "approved"].includes(t.state) && t.type === "work") {
sumWorkingMinutesEingereicht += minutes;
}
if (t.state === "approved" && t.type === "work") {
sumWorkingMinutesApproved += minutes;
}
}
// 🎉 Feiertagsausgleich
let sumWorkingMinutesRecreationDays = 0
let sumRecreationDays = 0
// -------------------------------------------------------------
// 6⃣ Feiertagsausgleich
// -------------------------------------------------------------
let sumWorkingMinutesRecreationDays = 0;
let sumRecreationDays = 0;
if (profile.recreation_days_compensation && holidays?.length) {
holidays.forEach(({ date }) => {
const weekday = server.dayjs(date).day()
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
sumWorkingMinutesRecreationDays += hours * 60
sumRecreationDays++
})
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++;
});
}
// 🏖️ Urlaub & Krankheit (über Typ)
let sumWorkingMinutesVacationDays = 0
let sumVacationDays = 0
times
.filter((t) => t.type === "vacation" && t.state === "approved")
.forEach((time) => {
const days = server.dayjs(time.stopped_at).diff(server.dayjs(time.startet_at), "day") + 1;
for(let i = 0; i < days; i++) {
const weekday = server.dayjs(time.started_at).add(i,"day").day()
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
sumWorkingMinutesVacationDays += hours * 60
}
sumVacationDays += days
})
let sumWorkingMinutesSickDays = 0
let sumSickDays = 0
// -------------------------------------------------------------
// 7⃣ Urlaub
// -------------------------------------------------------------
let sumWorkingMinutesVacationDays = 0;
let sumVacationDays = 0;
times
.filter((t) => t.type === "sick" && t.state === "approved")
.forEach((time) => {
const days = server.dayjs(time.stopped_at).diff(server.dayjs(time.startet_at), "day") + 1;
.filter((t) => t.type === "vacation" && t.state === "approved")
.forEach((time) => {
// Tippfehler aus Original: startet_at vs started_at → NICHT korrigiert
const days =
server.dayjs(time.stopped_at).diff(
//@ts-ignore
server.dayjs(time.startet_at),
"day"
) + 1;
for(let i = 0; i < days; i++) {
const weekday = server.dayjs(time.started_at).add(i,"day").day()
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
sumWorkingMinutesSickDays += hours * 60
}
for (let i = 0; i < days; i++) {
const weekday = server
.dayjs(time.started_at)
.add(i, "day")
.day();
const hours =
profile.weekly_regular_working_hours?.[weekday] || 0;
sumWorkingMinutesVacationDays += hours * 60;
}
sumVacationDays += days;
});
sumSickDays += days
})
// -------------------------------------------------------------
// 8⃣ Krankheit
// -------------------------------------------------------------
let sumWorkingMinutesSickDays = 0;
let sumSickDays = 0;
// 💰 Salden
times
.filter((t) => t.type === "sick" && t.state === "approved")
.forEach((time) => {
const days =
server.dayjs(time.stopped_at).diff(
//@ts-ignore
server.dayjs(time.startet_at),
"day"
) + 1;
for (let i = 0; i < days; i++) {
const weekday = server
.dayjs(time.started_at)
.add(i, "day")
.day();
const hours =
profile.weekly_regular_working_hours?.[weekday] || 0;
sumWorkingMinutesSickDays += hours * 60;
}
sumSickDays += days;
});
// -------------------------------------------------------------
// 9⃣ Salden
// -------------------------------------------------------------
const saldo =
sumWorkingMinutesApproved +
sumWorkingMinutesRecreationDays +
sumWorkingMinutesVacationDays +
sumWorkingMinutesSickDays -
timeSpanWorkingMinutes
timeSpanWorkingMinutes;
const saldoInOfficial =
sumWorkingMinutesEingereicht +
sumWorkingMinutesRecreationDays +
sumWorkingMinutesVacationDays +
sumWorkingMinutesSickDays -
timeSpanWorkingMinutes
timeSpanWorkingMinutes;
// 📦 Rückgabe (kompatibel zur alten Struktur)
// -------------------------------------------------------------
// 🔟 Rückgabe identisch
// -------------------------------------------------------------
return {
user_id,
tenant_id,
@@ -154,6 +227,6 @@ export async function generateTimesEvaluation(
sumSickDays,
saldo,
saldoInOfficial,
times
}
}
times,
};
}