243 lines
8.4 KiB
TypeScript
243 lines
8.4 KiB
TypeScript
import { TimeService } from "../time-v2/service";
|
||
import { legacySpans } from "../time-v2/compatibility";
|
||
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,
|
||
user_id: string,
|
||
tenant_id: number,
|
||
startDateInput: string,
|
||
endDateInput: string
|
||
) {
|
||
const service = new TimeService(server.db);
|
||
const actor = { tenant: tenant_id, user: user_id };
|
||
if (await service.active(actor, user_id)) {
|
||
const result = await service.evaluation(actor, user_id, startDateInput, endDateInput);
|
||
return { ...result.summary, sumWorkingMinutesEingereicht: result.summary.sumWorkingMinutesSubmitted,
|
||
saldo: result.summary.saldoApproved, saldoInOfficial: result.summary.saldoSubmitted,
|
||
times: legacySpans(result.spans).map(s => ({ ...s, started_at: s.startedAt, stopped_at: s.endedAt, state: s.status })) };
|
||
}
|
||
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"));
|
||
|
||
// -------------------------------------------------------------
|
||
// 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);
|
||
|
||
const profile = profileRows[0];
|
||
|
||
if (!profile) throw new Error("Profil konnte nicht geladen werden.");
|
||
|
||
// -------------------------------------------------------------
|
||
// 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 times = timesRaw.filter((i) =>
|
||
isBetween(startDate, endDate, i.started_at, i.stopped_at)
|
||
);
|
||
|
||
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"))
|
||
)
|
||
);
|
||
|
||
// -------------------------------------------------------------
|
||
// 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;
|
||
}
|
||
|
||
// -------------------------------------------------------------
|
||
// 5️⃣ Eingereicht/genehmigt
|
||
// -------------------------------------------------------------
|
||
const calcMinutes = (start: string, end: string | null) =>
|
||
server.dayjs(end || new Date()).diff(server.dayjs(start), "minutes");
|
||
|
||
let sumWorkingMinutesEingereicht = 0;
|
||
let sumWorkingMinutesApproved = 0;
|
||
|
||
for (const t of times) {
|
||
// @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;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------
|
||
// 6️⃣ Feiertagsausgleich
|
||
// -------------------------------------------------------------
|
||
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++;
|
||
});
|
||
}
|
||
|
||
// -------------------------------------------------------------
|
||
// 7️⃣ Urlaub
|
||
// -------------------------------------------------------------
|
||
let sumWorkingMinutesVacationDays = 0;
|
||
let sumVacationDays = 0;
|
||
|
||
times
|
||
.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;
|
||
sumWorkingMinutesVacationDays += hours * 60;
|
||
}
|
||
sumVacationDays += days;
|
||
});
|
||
|
||
// -------------------------------------------------------------
|
||
// 8️⃣ Krankheit
|
||
// -------------------------------------------------------------
|
||
let sumWorkingMinutesSickDays = 0;
|
||
let sumSickDays = 0;
|
||
|
||
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;
|
||
|
||
const saldoInOfficial =
|
||
sumWorkingMinutesEingereicht +
|
||
sumWorkingMinutesRecreationDays +
|
||
sumWorkingMinutesVacationDays +
|
||
sumWorkingMinutesSickDays -
|
||
timeSpanWorkingMinutes;
|
||
|
||
// -------------------------------------------------------------
|
||
// 🔟 Rückgabe identisch
|
||
// -------------------------------------------------------------
|
||
return {
|
||
user_id,
|
||
tenant_id,
|
||
from: startDate.format("YYYY-MM-DD"),
|
||
to: endDate.format("YYYY-MM-DD"),
|
||
timeSpanWorkingMinutes,
|
||
sumWorkingMinutesEingereicht,
|
||
sumWorkingMinutesApproved,
|
||
sumWorkingMinutesRecreationDays,
|
||
sumRecreationDays,
|
||
sumWorkingMinutesVacationDays,
|
||
sumVacationDays,
|
||
sumWorkingMinutesSickDays,
|
||
sumSickDays,
|
||
saldo,
|
||
saldoInOfficial,
|
||
times,
|
||
};
|
||
}
|