Füge Logik zur Berechnung wirksamer Arbeits- und Pausenblöcke hinzu und optimiere die Verarbeitung von Zeiteinträgen
All checks were successful
Deploy production / deploy (push) Successful in 53s
All checks were successful
Deploy production / deploy (push) Successful in 53s
This commit is contained in:
@@ -21,6 +21,63 @@ class TimeEntryService {
|
||||
this.defaultUserId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die wirksame Ereignisfolge. `relatedTo_id` beschreibt die
|
||||
* ursprüngliche Buchungskette und ist nach einer Action-Korrektur (z. B.
|
||||
* Pause -> Arbeitsende) nicht mehr zuverlässig. Alle Auswertungen, die
|
||||
* einen Zustand ableiten, müssen deshalb diese Folge verwenden.
|
||||
* @private
|
||||
*/
|
||||
async _getEffectiveEntries(userId) {
|
||||
const entries = await worklogRepository.findByUser(userId);
|
||||
const fixes = await worklogRepository.getTimefixesByWorklogIds(entries.map(entry => entry.id));
|
||||
return entries.map(entry => {
|
||||
const fix = fixes.get(entry.id)?.at(-1);
|
||||
let action = entry.state;
|
||||
if (typeof action === 'string') {
|
||||
try { action = JSON.parse(action); } catch (_) { /* action is already a string */ }
|
||||
}
|
||||
action = fix?.fix_type || action?.action || action;
|
||||
const time = fix?.fix_date_time
|
||||
? (typeof fix.fix_date_time === 'string' ? fix.fix_date_time.replace(' ', 'T') + 'Z' : fix.fix_date_time)
|
||||
: entry.tstamp;
|
||||
return { ...entry, effectiveAction: action, effectiveTime: time, fix };
|
||||
}).sort((a, b) => new Date(a.effectiveTime) - new Date(b.effectiveTime) || a.id - b.id);
|
||||
}
|
||||
|
||||
/** Bildet Arbeits- und Pausenblöcke ausschließlich aus der wirksamen Folge. */
|
||||
_buildEffectiveWorkBlocks(entries) {
|
||||
const blocks = [];
|
||||
let work = null;
|
||||
let pause = null;
|
||||
for (const entry of entries) {
|
||||
switch (entry.effectiveAction) {
|
||||
case 'start work':
|
||||
// Ein neuer Arbeitsbeginn schließt keinen bestehenden Block künstlich.
|
||||
if (!work) work = { start: entry, pauses: [] };
|
||||
break;
|
||||
case 'start pause':
|
||||
if (work && !pause) pause = entry;
|
||||
break;
|
||||
case 'stop pause':
|
||||
if (work && pause) {
|
||||
work.pauses.push({ start: pause, end: entry });
|
||||
pause = null;
|
||||
}
|
||||
break;
|
||||
case 'stop work':
|
||||
if (work) {
|
||||
blocks.push({ ...work, end: entry, runningPause: null });
|
||||
work = null;
|
||||
pause = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (work) blocks.push({ ...work, end: null, runningPause: pause });
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Zeiteinträge abrufen
|
||||
* @param {number} userId - Benutzer-ID (optional)
|
||||
@@ -228,116 +285,25 @@ class TimeEntryService {
|
||||
const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
||||
|
||||
if (runningEntry) {
|
||||
// Berechne ALLE Arbeitszeiten des heutigen Tages
|
||||
// Hole alle Einträge von heute (wird auch für Pausen verwendet)
|
||||
allEntries = await worklogRepository.findByDateRange(uid, todayStart, todayEnd);
|
||||
|
||||
// Hole Timefixes für alle Einträge von heute
|
||||
const allEntryIds = allEntries.map(e => e.id);
|
||||
const timefixMap = await worklogRepository.getTimefixesByWorklogIds(allEntryIds);
|
||||
|
||||
// Finde alle Start-Work-Paare von heute
|
||||
const workBlocks = [];
|
||||
const startWorks = {};
|
||||
|
||||
allEntries.forEach(entry => {
|
||||
let state = entry.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
if (action === 'start work') {
|
||||
// Prüfe auf Timefix-Korrektur
|
||||
const startFix = timefixMap.get(entry.id)?.find(f => f.fix_type === 'start work');
|
||||
const startTime = startFix?.fix_date_time || entry.tstamp;
|
||||
|
||||
startWorks[entry.id] = {
|
||||
id: entry.id,
|
||||
startTime: startTime,
|
||||
endTime: null,
|
||||
pauses: []
|
||||
};
|
||||
} else if (action === 'stop work' && entry.relatedTo_id && startWorks[entry.relatedTo_id]) {
|
||||
// Prüfe auf Timefix-Korrektur
|
||||
const endFix = timefixMap.get(entry.id)?.find(f => f.fix_type === 'stop work');
|
||||
const endTime = endFix?.fix_date_time || entry.tstamp;
|
||||
|
||||
startWorks[entry.relatedTo_id].endTime = endTime;
|
||||
}
|
||||
});
|
||||
|
||||
// Sammle Pausen für jeden Block
|
||||
allEntries.forEach(entry => {
|
||||
let state = entry.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
if (action === 'start pause' && entry.relatedTo_id && startWorks[entry.relatedTo_id]) {
|
||||
// Prüfe auf Timefix-Korrektur
|
||||
const pauseStartFix = timefixMap.get(entry.id)?.find(f => f.fix_type === 'start pause');
|
||||
const pauseStartTime = pauseStartFix?.fix_date_time || entry.tstamp;
|
||||
|
||||
startWorks[entry.relatedTo_id].pauses.push({
|
||||
id: entry.id,
|
||||
startTime: pauseStartTime,
|
||||
endTime: null
|
||||
});
|
||||
} else if (action === 'stop pause' && entry.relatedTo_id) {
|
||||
// Finde das zugehörige start pause
|
||||
Object.values(startWorks).forEach(block => {
|
||||
const pause = block.pauses.find(p => p.id === entry.relatedTo_id);
|
||||
if (pause) {
|
||||
// Prüfe auf Timefix-Korrektur
|
||||
const pauseEndFix = timefixMap.get(entry.id)?.find(f => f.fix_type === 'stop pause');
|
||||
pause.endTime = pauseEndFix?.fix_date_time || entry.tstamp;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Berechne Gesamtarbeitszeit aller Blöcke
|
||||
// Arbeitszeit wird aus der korrigierten Ereignisfolge gebildet. Damit
|
||||
// darf eine nachträglich umtypisierte Buchung keine Phantompause mehr
|
||||
// erzeugen.
|
||||
const blocks = this._buildEffectiveWorkBlocks(await this._getEffectiveEntries(uid));
|
||||
let totalWorkedMs = 0;
|
||||
|
||||
Object.values(startWorks).forEach(block => {
|
||||
const blockStart = new Date(block.startTime).getTime();
|
||||
const blockEnd = block.endTime ? new Date(block.endTime).getTime() : now.getTime();
|
||||
|
||||
let blockWorkedMs = blockEnd - blockStart;
|
||||
|
||||
// Ziehe abgeschlossene Pausen ab
|
||||
block.pauses.forEach(pause => {
|
||||
if (pause.endTime) {
|
||||
const pauseDuration = new Date(pause.endTime).getTime() - new Date(pause.startTime).getTime();
|
||||
blockWorkedMs -= pauseDuration;
|
||||
} else {
|
||||
// Laufende Pause
|
||||
const pauseDuration = now.getTime() - new Date(pause.startTime).getTime();
|
||||
blockWorkedMs -= pauseDuration;
|
||||
}
|
||||
});
|
||||
|
||||
totalWorkedMs += blockWorkedMs;
|
||||
});
|
||||
|
||||
// Formatiere als HH:MM:SS
|
||||
const totalSeconds = Math.floor(totalWorkedMs / 1000);
|
||||
for (const block of blocks) {
|
||||
const start = new Date(block.start.effectiveTime);
|
||||
if (start < todayStart || start > todayEnd) continue;
|
||||
const endMs = block.end ? new Date(block.end.effectiveTime).getTime() : now.getTime();
|
||||
let duration = endMs - start.getTime();
|
||||
duration -= block.pauses.reduce((sum, pause) => sum +
|
||||
new Date(pause.end.effectiveTime).getTime() - new Date(pause.start.effectiveTime).getTime(), 0);
|
||||
if (block.runningPause) duration -= now.getTime() - new Date(block.runningPause.effectiveTime).getTime();
|
||||
totalWorkedMs += duration;
|
||||
}
|
||||
const totalSeconds = Math.max(0, Math.floor(totalWorkedMs / 1000));
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
currentlyWorked = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
@@ -1318,39 +1284,22 @@ class TimeEntryService {
|
||||
: 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
|
||||
});
|
||||
});
|
||||
const effectiveEntries = [...allEntries]
|
||||
.map(entry => ({ ...entry, 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,
|
||||
end_id: block.end?.id || null,
|
||||
start_time: block.start.tstamp,
|
||||
end_time: block.end?.tstamp || null,
|
||||
start_state: block.start.state,
|
||||
end_state: block.end?.state || null,
|
||||
// Die nachfolgenden Darstellungsfelder verwenden diese bereits
|
||||
// normalisierten Pausen, statt die ursprünglichen Referenzen erneut zu
|
||||
// interpretieren.
|
||||
effectivePauses: block.pauses,
|
||||
runningPause: block.runningPause
|
||||
}));
|
||||
|
||||
// Hole Vacation-Einträge für diese Woche
|
||||
const vacations = await worklogRepository.getVacationsByUserInDateRange(uid, weekStart, weekEnd);
|
||||
@@ -1515,42 +1464,7 @@ class TimeEntryService {
|
||||
|
||||
// Berechne Pausen NUR für diesen spezifischen Arbeitsblock
|
||||
// Finde alle Einträge, die zu diesem pair.id gehören (über relatedTo_id)
|
||||
const blockEntries = allEntries.filter(e => {
|
||||
// Parse state if it's a JSON string
|
||||
let state = e.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (err) {
|
||||
// state bleibt als String
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
// Einträge gehören zum Block wenn:
|
||||
// 1. Sie start/stop pause sind UND ihr relatedTo_id auf pair.id zeigt (direkt oder indirekt)
|
||||
// 2. Oder sie stop work sind mit relatedTo_id = pair.id
|
||||
|
||||
if (action === 'start pause' && e.relatedTo_id === pair.id) {
|
||||
return true; // Pause gehört zu diesem Arbeitsblock
|
||||
}
|
||||
|
||||
if (action === 'stop pause') {
|
||||
// Finde das zugehörige start pause
|
||||
const startPause = allEntries.find(sp => {
|
||||
const spState = typeof sp.state === 'string' ? JSON.parse(sp.state) : sp.state;
|
||||
const spAction = spState?.action || spState;
|
||||
return spAction === 'start pause' && sp.id === e.relatedTo_id;
|
||||
});
|
||||
|
||||
if (startPause && startPause.relatedTo_id === pair.id) {
|
||||
return true; // Stop pause gehört zu einem start pause, das zu diesem Block gehört
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
const blockEntries = pair.effectivePauses.flatMap(pause => [pause.start, pause.end]);
|
||||
|
||||
// Finde Pausen-Paare NUR für diesen Block
|
||||
const pausePairs = [];
|
||||
@@ -1709,41 +1623,10 @@ class TimeEntryService {
|
||||
}
|
||||
|
||||
// Prüfe auf Pausen NUR für diesen laufenden Arbeitsblock
|
||||
const blockEntries = allEntries.filter(e => {
|
||||
// Parse state if it's a JSON string
|
||||
let state = e.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (err) {
|
||||
// state bleibt als String
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
// Einträge gehören zum Block wenn:
|
||||
// 1. Sie start/stop pause sind UND ihr relatedTo_id auf pair.id zeigt (direkt oder indirekt)
|
||||
|
||||
if (action === 'start pause' && e.relatedTo_id === pair.id) {
|
||||
return true; // Pause gehört zu diesem Arbeitsblock
|
||||
}
|
||||
|
||||
if (action === 'stop pause') {
|
||||
// Finde das zugehörige start pause
|
||||
const startPause = allEntries.find(sp => {
|
||||
const spState = typeof sp.state === 'string' ? JSON.parse(sp.state) : sp.state;
|
||||
const spAction = spState?.action || spState;
|
||||
return spAction === 'start pause' && sp.id === e.relatedTo_id;
|
||||
});
|
||||
|
||||
if (startPause && startPause.relatedTo_id === pair.id) {
|
||||
return true; // Stop pause gehört zu einem start pause, das zu diesem Block gehört
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
const blockEntries = [
|
||||
...pair.effectivePauses.flatMap(pause => [pause.start, pause.end]),
|
||||
...(pair.runningPause ? [pair.runningPause] : [])
|
||||
];
|
||||
|
||||
// Finde laufende Pause
|
||||
const pauseStarts = {};
|
||||
@@ -2087,36 +1970,8 @@ class TimeEntryService {
|
||||
*/
|
||||
async getCurrentState(userId) {
|
||||
const uid = userId || this.defaultUserId;
|
||||
|
||||
// Hole letzten Worklog-Eintrag (inkl. Timefix-Korrekturen)
|
||||
const lastEntry = await worklogRepository.findLatestByUser(uid);
|
||||
|
||||
if (!lastEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse state
|
||||
let state = lastEntry.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (e) {
|
||||
// state bleibt als String
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
// Prüfe auf Timefix-Korrektur für diesen Eintrag
|
||||
const timefixMap = await worklogRepository.getTimefixesByWorklogIds([lastEntry.id]);
|
||||
const timefix = timefixMap.get(lastEntry.id)?.[0];
|
||||
|
||||
if (timefix) {
|
||||
// Verwende korrigierten Action-Typ
|
||||
return timefix.fix_type;
|
||||
}
|
||||
|
||||
return action;
|
||||
const entries = await this._getEffectiveEntries(uid);
|
||||
return entries.at(-1)?.effectiveAction || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2196,19 +2051,18 @@ class TimeEntryService {
|
||||
}
|
||||
|
||||
// stop work und stop pause referenzieren den entsprechenden Start
|
||||
if (newAction === 'stop work') {
|
||||
// Finde letzten 'start work' ohne 'stop work'
|
||||
return await this._findLastUnpairedStart(userId, 'start work');
|
||||
const blocks = this._buildEffectiveWorkBlocks(await this._getEffectiveEntries(userId));
|
||||
const activeBlock = blocks.at(-1);
|
||||
if (!activeBlock || activeBlock.end) return null;
|
||||
|
||||
if (newAction === 'stop work' || newAction === 'start pause') {
|
||||
// Auch neu erzeugte Folgebuchungen müssen auf den wirksamen Start
|
||||
// zeigen, wenn dieser ursprünglich eine andere Action hatte.
|
||||
return activeBlock.start.id;
|
||||
}
|
||||
|
||||
if (newAction === 'stop pause') {
|
||||
// Finde letzten 'start pause' ohne 'stop pause'
|
||||
return await this._findLastUnpairedStart(userId, 'start pause');
|
||||
}
|
||||
|
||||
if (newAction === 'start pause') {
|
||||
// start pause referenziert den laufenden 'start work'
|
||||
return await this._findLastUnpairedStart(userId, 'start work');
|
||||
return activeBlock.runningPause?.id || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -2283,115 +2137,18 @@ class TimeEntryService {
|
||||
*/
|
||||
async getRunningEntry(userId = null) {
|
||||
const uid = userId || this.defaultUserId;
|
||||
|
||||
// Hole letzten Eintrag
|
||||
const lastEntry = await worklogRepository.findLatestByUser(uid);
|
||||
|
||||
if (!lastEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse state
|
||||
let state = lastEntry.state;
|
||||
if (typeof state === 'string') {
|
||||
try {
|
||||
state = JSON.parse(state);
|
||||
} catch (e) {
|
||||
// state bleibt als String
|
||||
}
|
||||
}
|
||||
|
||||
const action = state?.action || state;
|
||||
|
||||
// Wenn letzter Eintrag "stop work" oder null ist, läuft nichts
|
||||
if (!action || action === 'stop work') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finde den zugehörigen "start work"
|
||||
const startWorkId = await this._findLastUnpairedStart(uid, 'start work');
|
||||
|
||||
if (!startWorkId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Hole alle Einträge seit dem Start
|
||||
const allEntries = await worklogRepository.findByUser(uid);
|
||||
const startWorkEntry = allEntries.find(e => e.id === startWorkId);
|
||||
|
||||
if (!startWorkEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sammle alle abgeschlossenen Pausen
|
||||
const pauseDurations = [];
|
||||
let currentPauseStart = null;
|
||||
|
||||
// Finde alle Pausen-Paare
|
||||
const relevantEntries = allEntries.filter(e => {
|
||||
const eDate = new Date(e.tstamp);
|
||||
const startDate = new Date(startWorkEntry.tstamp);
|
||||
return eDate >= startDate;
|
||||
});
|
||||
|
||||
|
||||
const pauseStarts = {};
|
||||
|
||||
// Hole Timefixes für alle relevanten Einträge (inklusive Pausen)
|
||||
const allEntryIds = relevantEntries.map(e => e.id);
|
||||
const pauseTimefixMap = await worklogRepository.getTimefixesByWorklogIds(allEntryIds);
|
||||
|
||||
relevantEntries.forEach(entry => {
|
||||
let eState = entry.state;
|
||||
if (typeof eState === 'string') {
|
||||
try {
|
||||
eState = JSON.parse(eState);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const eAction = eState?.action || eState;
|
||||
|
||||
if (eAction === 'start pause') {
|
||||
pauseStarts[entry.id] = entry;
|
||||
} else if (eAction === 'stop pause' && entry.relatedTo_id) {
|
||||
const startPause = pauseStarts[entry.relatedTo_id];
|
||||
if (startPause) {
|
||||
// Prüfe auf Timefix-Korrekturen für Pausen
|
||||
const pauseStartFix = pauseTimefixMap.get(startPause.id)?.find(f => f.fix_type === 'start pause');
|
||||
const pauseEndFix = pauseTimefixMap.get(entry.id)?.find(f => f.fix_type === 'stop pause');
|
||||
|
||||
// Verwende korrigierte Zeiten falls vorhanden
|
||||
const pStartTime = pauseStartFix?.fix_date_time || startPause.tstamp;
|
||||
const pEndTime = pauseEndFix?.fix_date_time || entry.tstamp;
|
||||
|
||||
const duration = new Date(pEndTime).getTime() - new Date(pStartTime).getTime();
|
||||
pauseDurations.push(duration);
|
||||
delete pauseStarts[entry.relatedTo_id];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wenn noch ein pauseStart übrig ist, ist das die laufende Pause
|
||||
const runningPauseIds = Object.keys(pauseStarts);
|
||||
if (runningPauseIds.length > 0) {
|
||||
const pauseId = parseInt(runningPauseIds[0]);
|
||||
const pauseEntry = pauseStarts[pauseId];
|
||||
|
||||
// Prüfe auf Timefix-Korrektur für laufende Pause
|
||||
const currentPauseTimefix = pauseTimefixMap.get(pauseId)?.find(f => f.fix_type === 'start pause');
|
||||
currentPauseStart = currentPauseTimefix?.fix_date_time || pauseEntry.tstamp;
|
||||
}
|
||||
const blocks = this._buildEffectiveWorkBlocks(await this._getEffectiveEntries(uid));
|
||||
const runningBlock = blocks.at(-1);
|
||||
if (!runningBlock || runningBlock.end) return null;
|
||||
const startWorkEntry = runningBlock.start;
|
||||
const startWorkId = startWorkEntry.id;
|
||||
const pauseDurations = runningBlock.pauses.map(pause =>
|
||||
new Date(pause.end.effectiveTime).getTime() - new Date(pause.start.effectiveTime).getTime()
|
||||
);
|
||||
const currentPauseStart = runningBlock.runningPause?.effectiveTime || null;
|
||||
|
||||
// Prüfe auf Timefix-Korrektur für die Start-Zeit (timefixMap bereits geholt in pauseTimefixMap)
|
||||
const startTimefix = pauseTimefixMap.get(startWorkId)?.find(f => f.fix_type === 'start work');
|
||||
|
||||
let displayStartTime = startWorkEntry.tstamp;
|
||||
if (startTimefix && startTimefix.fix_date_time) {
|
||||
// Verwende korrigierte Zeit
|
||||
displayStartTime = startTimefix.fix_date_time;
|
||||
}
|
||||
const displayStartTime = startWorkEntry.effectiveTime;
|
||||
|
||||
// Stelle sicher, dass startTime ein String ist (kein Date-Objekt)
|
||||
// Konvertiere UTC zu lokaler Zeit für Frontend
|
||||
@@ -2419,8 +2176,8 @@ class TimeEntryService {
|
||||
id: startWorkId,
|
||||
startTime: startTimeStr, // Explizit als String
|
||||
endTime: null,
|
||||
description: (state?.description || ''),
|
||||
project: (state?.project || 'Allgemein'),
|
||||
description: '',
|
||||
project: 'Allgemein',
|
||||
duration: null,
|
||||
isRunning: true,
|
||||
userId: uid,
|
||||
|
||||
Reference in New Issue
Block a user