diff --git a/backend/time_tracking/views.py b/backend/time_tracking/views.py index 73fe21c..dba5125 100644 --- a/backend/time_tracking/views.py +++ b/backend/time_tracking/views.py @@ -250,19 +250,23 @@ def build_weekly_summary(user): user=user, start_date__range=(start_of_week, start_of_week + timedelta(days=6)), ).order_by('start_date', 'start_time')) days_data = [] + total_hours = 0 for offset in range(7): day = start_of_week + timedelta(days=offset) day_entries = [entry for entry in entries if entry.start_date == day] - completed = [entry for entry in day_entries if entry.end_time is not None] + completed = [entry for entry in day_entries if entry.duration is not None] + day_is_pointed = bool(day_entries) and len(completed) == len(day_entries) + day_duration = sum(entry.duration or 0 for entry in day_entries) if day_is_pointed else 0 + total_hours += day_duration days_data.append({ 'date': day.isoformat(), 'day': day.strftime('%A'), 'start_time': str(day_entries[0].start_time) if day_entries else None, - 'end_time': str(max(entry.end_time for entry in completed)) if completed and len(completed) == len(day_entries) else None, - 'duration': sum(entry.duration or 0 for entry in day_entries), + 'end_time': str(max(entry.end_time for entry in completed)) if day_is_pointed else None, + 'duration': day_duration, 'entries_count': len(day_entries), }) return dict(user=user.username, username=user.username, - total_hours=round(sum(entry.duration or 0 for entry in entries), 2), entries=days_data) + total_hours=round(total_hours, 2), entries=days_data) class WeeklySummaryView(APIView): @@ -329,6 +333,16 @@ class TimeStatsView(APIView): logical_trips = set() daily = {} + entries_by_day = {} + for entry in entries: + entries_by_day.setdefault(entry.start_date, []).append(entry) + + pointed_days = { + day + for day, day_entries in entries_by_day.items() + if day_entries and all(entry.duration is not None for entry in day_entries) + } + for entry in entries: duration = entry.duration minutes = round(duration * 60) if duration is not None else 0 @@ -340,12 +354,19 @@ class TimeStatsView(APIView): row['incomplete'] += 1 continue - total_minutes += minutes - row['minutes'] += minutes trip_key = f'g:{entry.group_id}' if entry.group_id else f'e:{entry.pk}' logical_trips.add(trip_key) row['trip_keys'].add(trip_key) + # Une journée ne compte dans les heures faites que lorsque tous ses + # pointages sont complets. Une sortie manquante exclut donc toute la + # journée du total jusqu'à sa régularisation. + if day not in pointed_days: + continue + + total_minutes += minutes + row['minutes'] += minutes + if day.weekday() == 6: sunday_minutes += minutes sunday_dates.add(day) diff --git a/frontend/src/pages/WeeklySummaryPage.jsx b/frontend/src/pages/WeeklySummaryPage.jsx index abf850b..eb55393 100644 --- a/frontend/src/pages/WeeklySummaryPage.jsx +++ b/frontend/src/pages/WeeklySummaryPage.jsx @@ -1204,10 +1204,31 @@ const WeeklySummaryPage = ({ userId }) => { return start !== null && end !== null && end >= start ? end - start : 0; }; - const pointedWeekMinutes = individualWeekAttendances.reduce( - (sum, entry) => sum + attendanceMinutes(entry), - 0, - ); + const pointedMinutesForDays = (entries) => { + const entriesByDay = new Map(); + + entries.forEach((entry) => { + const day = String(entry.start_date || '').slice(0, 10); + if (!day) return; + if (!entriesByDay.has(day)) entriesByDay.set(day, []); + entriesByDay.get(day).push(entry); + }); + + return [...entriesByDay.values()].reduce((total, dayEntries) => { + const dayIsFullyPointed = dayEntries.length > 0 && dayEntries.every( + (entry) => entry.start_time && entry.end_time && !entry.display_incomplete, + ); + + if (!dayIsFullyPointed) return total; + + return total + dayEntries.reduce( + (sum, entry) => sum + attendanceMinutes(entry), + 0, + ); + }, 0); + }; + + const pointedWeekMinutes = pointedMinutesForDays(individualWeekAttendances); const precisePlannedMinutes = plannedSegments.reduce((sum, segment) => { const start = getMinutes(getTimeValue(segment, 'start_time')); const end = getMinutes(getTimeValue(segment, 'end_time')); @@ -1243,10 +1264,7 @@ const WeeklySummaryPage = ({ userId }) => { const individualDayAttendances = selectedMobileDate ? getSelectedUserAttendances(getAttendancesForDay(selectedMobileDate)) : []; - const pointedDayMinutes = individualDayAttendances.reduce( - (sum, entry) => sum + attendanceMinutes(entry), - 0, - ); + const pointedDayMinutes = pointedMinutesForDays(individualDayAttendances); const precisePlannedDayMinutes = selectedMobileSegments.reduce((sum, segment) => { const start = getMinutes(getTimeValue(segment, 'start_time')); const end = getMinutes(getTimeValue(segment, 'end_time'));