fix: closing the week early should count properly

This commit is contained in:
2026-05-13 20:47:39 +00:00
parent c617012401
commit 8cd58fbd2c
2 changed files with 12 additions and 8 deletions

View File

@@ -197,9 +197,11 @@ func (s *WeekService) CloseWeek(ctx context.Context, weekKey string) (*domain.Cl
// Compute expected ms for the week (from settings frozen at week start) // Compute expected ms for the week (from settings frozen at week start)
expectedMs := int64(set.HoursPerWeek * 3_600_000) expectedMs := int64(set.HoursPerWeek * 3_600_000)
// Verify all past workdays that have entries are closed; collect worked ms. // Collect worked ms across all workdays in the week.
// Past workdays with no entries at all are skipped (they contribute 0h). // A workday is included if it has a closed_days record — whether past or future
// Future workdays in the week are always skipped. // (future days can be pre-marked as holiday/vacation before closing the week early).
// Past workdays with open entries require an explicit close before the week can close.
// Past workdays with no record and no entries contribute 0h.
todayKey := s.clock.Now().In(s.tz).Format("2006-01-02") todayKey := s.clock.Now().In(s.tz).Format("2006-01-02")
var totalWorkedMs int64 var totalWorkedMs int64
for _, dk := range dayKeys { for _, dk := range dayKeys {
@@ -207,18 +209,20 @@ func (s *WeekService) CloseWeek(ctx context.Context, weekKey string) (*domain.Cl
if !set.IsWorkday(int(t.Weekday())) { if !set.IsWorkday(int(t.Weekday())) {
continue // weekend or non-workday — skip continue // weekend or non-workday — skip
} }
if dk > todayKey {
continue // future workday — skip
}
cd, err := s.closedDays.GetByDayKey(ctx, dk) cd, err := s.closedDays.GetByDayKey(ctx, dk)
if err != nil && !errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err return nil, err
} }
if cd != nil { if cd != nil {
// Already closed (past or pre-marked future) — count it.
totalWorkedMs += cd.WorkedMs totalWorkedMs += cd.WorkedMs
continue continue
} }
// No closed_days record — check whether the day has any entries. if dk > todayKey {
// Future workday with no closed record — entries are impossible, skip.
continue
}
// Past/today workday with no closed record — check whether entries exist.
dayEntries, err := s.entries.ListByDayKey(ctx, dk) dayEntries, err := s.entries.ListByDayKey(ctx, dk)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -246,7 +246,7 @@ func TestCloseWeekMidWeek(t *testing.T) {
t.Errorf("expected_ms: want %s, got %s", wantExpected, time.Duration(cw.ExpectedMs)*time.Millisecond) t.Errorf("expected_ms: want %s, got %s", wantExpected, time.Duration(cw.ExpectedMs)*time.Millisecond)
} }
const wantDelta = wantWorked - wantExpected // 11.5h const wantDelta = wantWorked - wantExpected // 11.5h
if cw.DeltaMs != int64(wantDelta) { if cw.DeltaMs != int64(wantDelta/time.Millisecond) {
t.Errorf("delta_ms: want %s, got %s", wantDelta, time.Duration(cw.DeltaMs)*time.Millisecond) t.Errorf("delta_ms: want %s, got %s", wantDelta, time.Duration(cw.DeltaMs)*time.Millisecond)
} }
} }