From 92745004439ef4c6433d99197742c5e59ada1814 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 27 Aug 2026 08:31:12 +0200 Subject: [PATCH] =?UTF-8?q?Verbessere=20die=20Verarbeitung=20von=20Zeitein?= =?UTF-8?q?tr=C3=A4gen=20und=20integriere=20Zeitkorrekturen=20in=20die=20L?= =?UTF-8?q?ogik=20zur=20Erstellung=20von=20Arbeitsbl=C3=B6cken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/TimeEntryService.js | 70 +++++++++++++++++++++--- deploy.sh | 5 ++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/backend/src/services/TimeEntryService.js b/backend/src/services/TimeEntryService.js index 0b9643a..d236afd 100755 --- a/backend/src/services/TimeEntryService.js +++ b/backend/src/services/TimeEntryService.js @@ -1283,18 +1283,74 @@ class TimeEntryService { const weekEnd = new Date(year, month, day + daysToMonday + (weekOffset * 7) + 6); weekEnd.setHours(23, 59, 59, 999); - // Hole alle Worklog-Einträge für diese Woche - const pairs = await worklogRepository.findPairsByUserInDateRange(uid, weekStart, weekEnd); - // Hole auch alle einzelnen Einträge für Pausen const allEntries = await worklogRepository.findByDateRange(uid, weekStart, weekEnd); // Hole Timefix-Einträge für alle Worklog-IDs - const allWorklogIds = [...new Set([ - ...pairs.map(p => p.id), - ...allEntries.map(e => e.id) - ])]; + const allWorklogIds = [...new Set(allEntries.map(e => e.id))]; const timefixMap = await worklogRepository.getTimefixesByWorklogIds(allWorklogIds); + + // Arbeitsblöcke aus den effektiven Aktionen bilden. Eine Zeitkorrektur + // kann nicht nur den Zeitpunkt, sondern auch den Aktionstyp ändern (z. B. + // „Pause beginnen“ zu „Arbeit beenden“). Die Repository-Paarbildung kennt + // nur die Original-Aktion und würde einen solchen Block als laufend lassen. + const getAction = (entry) => { + const fixes = timefixMap.get(entry.id); + if (fixes?.length) return fixes[fixes.length - 1].fix_type; + + let state = entry.state; + if (typeof state === 'string') { + try { + state = JSON.parse(state); + } catch (_) { + // Der String ist bereits die Aktion. + } + } + return state?.action || state; + }; + + const getEffectiveTime = (entry) => { + const fixes = timefixMap.get(entry.id); + const fix = fixes?.[fixes.length - 1]; + if (!fix) return entry.tstamp; + return typeof fix.fix_date_time === 'string' + ? fix.fix_date_time.replace(' ', 'T') + 'Z' + : fix.fix_date_time; + }; + + const pairs = []; + const openWorkEntries = new Map(); + [...allEntries] + .sort((a, b) => new Date(getEffectiveTime(a)) - new Date(getEffectiveTime(b))) + .forEach(entry => { + const action = getAction(entry); + if (action === 'start work') { + openWorkEntries.set(String(entry.id), entry); + } else if (action === 'stop work' && entry.relatedTo_id) { + const startEntry = openWorkEntries.get(String(entry.relatedTo_id)); + if (startEntry) { + pairs.push({ + id: startEntry.id, + end_id: entry.id, + start_time: startEntry.tstamp, + end_time: entry.tstamp, + start_state: startEntry.state, + end_state: entry.state + }); + openWorkEntries.delete(String(entry.relatedTo_id)); + } + } + }); + + openWorkEntries.forEach(startEntry => { + pairs.push({ + id: startEntry.id, + start_time: startEntry.tstamp, + end_time: null, + start_state: startEntry.state, + end_state: null + }); + }); // Hole Vacation-Einträge für diese Woche const vacations = await worklogRepository.getVacationsByUserInDateRange(uid, weekStart, weekEnd); diff --git a/deploy.sh b/deploy.sh index bd74118..904c8c9 100755 --- a/deploy.sh +++ b/deploy.sh @@ -481,6 +481,11 @@ do_update() { print_info "Kopiere Frontend..." rsync -av --exclude 'node_modules' --exclude 'dist' --exclude '.env*' --delete "$CURRENT_DIR/frontend/" "$FRONTEND_DIR/" + + # Der Workspace-Lock gilt für Backend und Frontend gemeinsam und muss bei + # jedem Update mit dem Quell-Checkout synchronisiert werden. + print_info "Kopiere Workspace-Metadaten..." + rsync -av "$CURRENT_DIR/package.json" "$CURRENT_DIR/package-lock.json" "$PROJECT_DIR/" print_info "Kopiere Konfigurations-Dateien..." rsync -av "$CURRENT_DIR"/*.{sh,conf,md,service} "$PROJECT_DIR/" 2>/dev/null || true