From e84f2f38fe931aa810906436d4c2f753cdfa77b7 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 28 Aug 2026 07:35:42 +0200 Subject: [PATCH] =?UTF-8?q?Verbessere=20die=20Berechnung=20der=20Arbeitsta?= =?UTF-8?q?ge,=20indem=20die=20effektive=20Ereignisfolge=20f=C3=BCr=20Acti?= =?UTF-8?q?on-Korrekturen=20verwendet=20wird=20und=20optimiere=20die=20Ver?= =?UTF-8?q?arbeitung=20von=20Zeiteintr=C3=A4gen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/TimeEntryService.js | 37 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/backend/src/services/TimeEntryService.js b/backend/src/services/TimeEntryService.js index f33a626..890d7ea 100755 --- a/backend/src/services/TimeEntryService.js +++ b/backend/src/services/TimeEntryService.js @@ -1052,10 +1052,27 @@ class TimeEntryService { ORDER BY DATE(COALESCE(w1_fix.fix_date_time, w1.tstamp)) `; - const workDays = await sequelize.query(query, { - replacements: [userId, endDateStr], - type: sequelize.QueryTypes.SELECT - }); + // Die SQL-Abfrage oben kann nur originale `state`-/`relatedTo`-Paare + // erkennen. Bei Action-Korrekturen fehlen dadurch ganze Arbeitstage in + // der Gesamtstatistik. Verwende dieselbe effektive Ereignisfolge wie + // Timer und Wochenansicht. + const netMinutesByDate = new Map(); + this._buildEffectiveWorkBlocks(await this._getEffectiveEntries(userId)) + .filter(block => block.end) + .forEach(block => { + const start = new Date(block.start.effectiveTime); + const date = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')}`; + const day = start.getDay(); + if (date > endDateStr || day === 0 || day === 6) return; + const pauses = block.pauses.reduce((sum, pause) => sum + + new Date(pause.end.effectiveTime).getTime() - new Date(pause.start.effectiveTime).getTime(), 0); + const minutes = Math.round((new Date(block.end.effectiveTime).getTime() - start.getTime() - pauses) / 60000); + netMinutesByDate.set(date, (netMinutesByDate.get(date) || 0) + minutes); + }); + const workDays = [...netMinutesByDate.entries()].map(([work_date, minutes]) => ({ + work_date, + net_hours: minutes / 60 + })); // Hole User-Bundesland const { State: StateModel } = database.getModels(); @@ -1285,7 +1302,17 @@ class TimeEntryService { }; const effectiveEntries = [...allEntries] - .map(entry => ({ ...entry, effectiveAction: getAction(entry), effectiveTime: getEffectiveTime(entry) })) + // findByDateRange liefert Sequelize-Instanzen. Ein Spread würde deren + // Modellattribute (id, tstamp, state) nicht übernehmen und damit alle + // Blöcke aus der Wochenansicht verschwinden lassen. + .map(entry => ({ + id: entry.id, + tstamp: entry.tstamp, + state: entry.state, + relatedTo_id: entry.relatedTo_id, + effectiveAction: getAction(entry), + effectiveTime: getEffectiveTime(entry) + })) .sort((a, b) => new Date(a.effectiveTime) - new Date(b.effectiveTime) || a.id - b.id); const pairs = this._buildEffectiveWorkBlocks(effectiveEntries).map(block => ({ id: block.start.id,