Anwesenheit ignoriert Auto-Check-in vom 6-Uhr-Schnitt
ci / quality (push) Failing after 46s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 1m30s

Der 6-Uhr-Schnitt legt Folgetage mit checkedInAt=06:00 an — das ist kein
echter Lock-in, die Anwesenheitsanzeige lief dadurch fälschlich seit
06:00 mit (z. B. "6h 6m" mittags trotz spätem Arbeitsbeginn). Neues Flag
day_entry.auto_checked_in (Backfill: bestehende 06:00:00-Check-ins);
teamToday, Heute-Balken und "Zeit heute" zeigen Anwesenheit nur noch bei
echtem Check-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Fable 5 committed 2026-06-12 12:13:57 +02:00
1 parent e0fbc386fa
commit e049e0a88c
8 files changed
+3394 -7

No files matched your search

+8 -1
View File
@@ -62,7 +62,14 @@ export async function applyWorkdayBoundary(db: Db, userId: string, now: Date): P
const [created] = await tx
.insert(dayEntry)
.values({ userId, date: nextDate, status: 'begun', checkedInAt: cutoff })
.values({
userId,
date: nextDate,
status: 'begun',
checkedInAt: cutoff,
// Kein echter Lock-in — Anwesenheits-Anzeigen ignorieren diesen Check-in.
autoCheckedIn: true,
})
.returning();
if (!created) throw new Error('Folge-Tag konnte nicht angelegt werden.');
+4 -2
View File
@@ -341,7 +341,9 @@ export const timeRouter = router({
perUser.set(r.userId, bucket);
}
// Anwesenheit (Lock-in → Feierabend) pro Person — reine Anzeige neben dem Tracking.
// 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.
const dayRows = await ctx.db
.select({
userId: dayEntry.userId,
@@ -349,7 +351,7 @@ export const timeRouter = router({
checkedOutAt: dayEntry.checkedOutAt,
})
.from(dayEntry)
.where(eq(dayEntry.date, today));
.where(and(eq(dayEntry.date, today), eq(dayEntry.autoCheckedIn, false)));
const presenceByUser = new Map(dayRows.map((d) => [d.userId, d]));
// „Gesamt" pro Person = Wall-Clock (Vereinigung der Intervalle).
@@ -31,9 +31,11 @@ export function TimelineSection() {
const todayQuery = trpc.dayEntry.today.useQuery();
const [manualOpen, setManualOpen] = useState(false);
const [editing, setEditing] = useState<string | null>(null);
// Auto-Check-in vom 6-Uhr-Schnitt ist kein echter Lock-in → keine Anwesenheit.
const todayEntry = todayQuery.data?.entry;
const presenceSeconds = usePresenceSeconds(
todayQuery.data?.entry?.checkedInAt,
todayQuery.data?.entry?.checkedOutAt,
todayEntry?.autoCheckedIn ? null : todayEntry?.checkedInAt,
todayEntry?.checkedOutAt,
);
if (listQuery.isPending) return null;
+6 -1
View File
@@ -320,11 +320,13 @@ function AddTodoInline() {
function CheckInOutBar({
checkedInAt,
checkedOutAt,
autoCheckedIn,
todos,
reflected,
}: {
checkedInAt: string | Date | null;
checkedOutAt: string | Date | null;
autoCheckedIn: boolean;
todos: Array<{ id: string; title: string; status: 'open' | 'done' | 'dropped' }>;
reflected: boolean;
}) {
@@ -355,7 +357,8 @@ function CheckInOutBar({
}).format(new Date(d))
: '—';
// Anwesenheit (Lock-in → Feierabend bzw. jetzt) — reine Anzeige, kein Tracking.
const presenceSeconds = usePresenceSeconds(checkedInAt, checkedOutAt);
// Auto-Check-in vom 6-Uhr-Schnitt ist kein echter Lock-in → keine Anwesenheit.
const presenceSeconds = usePresenceSeconds(autoCheckedIn ? null : checkedInAt, checkedOutAt);
return (
<>
@@ -555,6 +558,7 @@ function EinstempeltView({
status: 'begun' | 'full' | 'absent';
checkedInAt: string | Date | null;
checkedOutAt: string | Date | null;
autoCheckedIn: boolean;
};
}) {
// Reflexion ist „fällig", wenn nach 18:00 Berlin und noch nicht abgeschlossen.
@@ -576,6 +580,7 @@ function EinstempeltView({
<CheckInOutBar
checkedInAt={entry.checkedInAt}
checkedOutAt={entry.checkedOutAt}
autoCheckedIn={entry.autoCheckedIn}
todos={todos}
reflected={entry.status === 'full'}
/>