Anwesenheit: Fallback-Anker erster Zeiteintrag bei Auto-Tagen
Auto-Tage vom 6-Uhr-Schnitt haben keinen echten Lock-in — statt die Anwesenheit ganz auszublenden, ankert sie jetzt am ersten Zeiteintrag des Tages. teamToday/listToday liefern presenceStartedAt/presenceEndedAt (echter Check-in hat Vorrang); Team-Karte, Heute-Balken und "Zeit heute" nutzen die neuen Felder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e049e0a88c
commit
94faf5e41f
4 files changed
+56
-17
No files matched your search
@@ -180,11 +180,36 @@ export const timeRouter = router({
|
||||
0,
|
||||
);
|
||||
|
||||
// Anwesenheits-Anker wie in teamToday: echter Lock-in, sonst (Auto-Tag vom
|
||||
// 6-Uhr-Schnitt oder kein Eintrag) der erste Zeiteintrag des Tages.
|
||||
const ownDay = (
|
||||
await ctx.db
|
||||
.select({
|
||||
checkedInAt: dayEntry.checkedInAt,
|
||||
checkedOutAt: dayEntry.checkedOutAt,
|
||||
autoCheckedIn: dayEntry.autoCheckedIn,
|
||||
})
|
||||
.from(dayEntry)
|
||||
.where(and(eq(dayEntry.userId, ctx.user.id), eq(dayEntry.date, today)))
|
||||
.limit(1)
|
||||
)[0];
|
||||
const firstStartMs = rows.length ? Math.min(...rows.map((r) => clampStart(r))) : null;
|
||||
const anchorMs =
|
||||
ownDay && !ownDay.autoCheckedIn && ownDay.checkedInAt
|
||||
? ownDay.checkedInAt.getTime()
|
||||
: firstStartMs;
|
||||
|
||||
const entries = rows.map((r) => ({
|
||||
...r,
|
||||
todoTitle: r.todoId ? (titles.get(r.todoId) ?? null) : null,
|
||||
}));
|
||||
return { entries, totalSeconds, trackedSeconds };
|
||||
return {
|
||||
entries,
|
||||
totalSeconds,
|
||||
trackedSeconds,
|
||||
presenceStartedAt: anchorMs !== null ? new Date(anchorMs).toISOString() : null,
|
||||
presenceEndedAt: ownDay?.checkedOutAt ? ownDay.checkedOutAt.toISOString() : null,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -342,29 +367,34 @@ export const timeRouter = router({
|
||||
}
|
||||
|
||||
// Anwesenheit (Lock-in → Feierabend) pro Person — reine Anzeige neben dem
|
||||
// Tracking. Vom 6-Uhr-Schnitt auto-angelegte Tage zählen nicht: deren
|
||||
// Check-in (06:00) ist kein echter Lock-in.
|
||||
// Tracking. Anker = echter Lock-in; bei vom 6-Uhr-Schnitt auto-angelegten
|
||||
// Tagen (Check-in 06:00 ist kein echter Lock-in) stattdessen der erste
|
||||
// Zeiteintrag des Tages.
|
||||
const dayRows = await ctx.db
|
||||
.select({
|
||||
userId: dayEntry.userId,
|
||||
checkedInAt: dayEntry.checkedInAt,
|
||||
checkedOutAt: dayEntry.checkedOutAt,
|
||||
autoCheckedIn: dayEntry.autoCheckedIn,
|
||||
})
|
||||
.from(dayEntry)
|
||||
.where(and(eq(dayEntry.date, today), eq(dayEntry.autoCheckedIn, false)));
|
||||
.where(eq(dayEntry.date, today));
|
||||
const presenceByUser = new Map(dayRows.map((d) => [d.userId, d]));
|
||||
|
||||
// „Gesamt" pro Person = Wall-Clock (Vereinigung der Intervalle).
|
||||
return Array.from(perUser.values()).map((b) => {
|
||||
const p = presenceByUser.get(b.userId);
|
||||
const firstStartMs = b.intervals.length ? Math.min(...b.intervals.map((i) => i[0])) : null;
|
||||
const anchorMs =
|
||||
p && !p.autoCheckedIn && p.checkedInAt ? p.checkedInAt.getTime() : firstStartMs;
|
||||
return {
|
||||
userId: b.userId,
|
||||
totalSeconds: unionSeconds(b.intervals),
|
||||
trackedSeconds: b.trackedSeconds,
|
||||
byCategory: b.byCategory,
|
||||
active: b.active,
|
||||
checkedInAt: p?.checkedInAt ? p.checkedInAt.toISOString() : null,
|
||||
checkedOutAt: p?.checkedOutAt ? p.checkedOutAt.toISOString() : null,
|
||||
presenceStartedAt: anchorMs !== null ? new Date(anchorMs).toISOString() : null,
|
||||
presenceEndedAt: p?.checkedOutAt ? p.checkedOutAt.toISOString() : null,
|
||||
};
|
||||
});
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user