diff --git a/internal/service/week_service.go b/internal/service/week_service.go index 86a0d06..94b3869 100644 --- a/internal/service/week_service.go +++ b/internal/service/week_service.go @@ -88,13 +88,18 @@ func (s *WeekService) CloseWeek(ctx context.Context, weekKey string) (*domain.Cl // Compute expected ms for the week (from settings frozen at week start) expectedMs := int64(set.HoursPerWeek * 3_600_000) - // Verify all workdays are closed; collect worked ms + // Verify all workdays up to and including today are closed; collect worked ms. + // Future workdays in the week (not yet reached) are skipped. + todayKey := time.Now().In(s.tz).Format("2006-01-02") var totalWorkedMs int64 for _, dk := range dayKeys { t, _ := time.ParseInLocation("2006-01-02", dk, s.tz) if !set.IsWorkday(int(t.Weekday())) { continue // weekend or non-workday — skip } + if dk > todayKey { + continue // future workday — skip + } cd, err := s.closedDays.GetByDayKey(ctx, dk) if err != nil { if errors.Is(err, sql.ErrNoRows) { diff --git a/internal/service/week_service_test.go b/internal/service/week_service_test.go index e073f23..9ae4ce8 100644 --- a/internal/service/week_service_test.go +++ b/internal/service/week_service_test.go @@ -54,7 +54,6 @@ func TestCloseWeekBasic(t *testing.T) { ctx := context.Background() entrySvc, daySvc, weekSvc, _ := newFullServices(t) - // Find the ISO week for "this week" using a known Mon-Fri // Use a fixed Monday to make the test deterministic monday := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC) // 2024-W03, Monday weekKey := "2024-W03" @@ -92,7 +91,7 @@ func TestCloseWeekMissingDayFails(t *testing.T) { ctx := context.Background() _, daySvc, weekSvc, _ := newFullServices(t) - // Only close Mon-Thu, leave Friday open + // Only close Mon-Thu, leave Friday open — all are in the past monday := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC) for i := 0; i < 4; i++ { dk := monday.AddDate(0, 0, i).Format("2006-01-02") @@ -101,7 +100,7 @@ func TestCloseWeekMissingDayFails(t *testing.T) { _, err := weekSvc.CloseWeek(ctx, "2024-W03") if err == nil { - t.Fatal("expected error closing week with unclosed day") + t.Fatal("expected error closing week with unclosed past day") } } @@ -142,3 +141,33 @@ func TestReopenWeek(t *testing.T) { t.Fatalf("CloseWeek after reopen: %v", err) } } + +func TestCloseWeekMidWeek(t *testing.T) { + // Regression: closing the current week mid-week should succeed when all + // past workdays are closed and future workdays are left open. + ctx := context.Background() + _, daySvc, weekSvc, _ := newFullServices(t) + + tz, _ := time.LoadLocation("UTC") + now := time.Now().In(tz) + isoYear, isoWeek := now.ISOWeek() + weekKey := fmt.Sprintf("%d-W%02d", isoYear, isoWeek) + + // Close every workday from Monday up to and including today. + monday := now.AddDate(0, 0, -int(now.Weekday()-time.Monday)) + today := now.Format("2006-01-02") + for d := monday; d.Format("2006-01-02") <= today; d = d.AddDate(0, 0, 1) { + wd := d.Weekday() + if wd == time.Saturday || wd == time.Sunday { + continue + } + dk := d.Format("2006-01-02") + if _, err := daySvc.MarkDay(ctx, dk, domain.DayKindHoliday); err != nil { + t.Fatalf("MarkDay %s: %v", dk, err) + } + } + + if _, err := weekSvc.CloseWeek(ctx, weekKey); err != nil { + t.Fatalf("CloseWeek mid-week: %v", err) + } +}