package store import ( "context" "database/sql" "github.com/wotra/wotra/internal/domain" ) // ClosedWeekStore handles persistence for closed weeks. type ClosedWeekStore struct { db *sql.DB } func NewClosedWeekStore(db *sql.DB) *ClosedWeekStore { return &ClosedWeekStore{db: db} } func (s *ClosedWeekStore) Upsert(ctx context.Context, w *domain.ClosedWeek) error { _, err := s.db.ExecContext(ctx, `INSERT INTO closed_weeks (week_key, expected_ms, worked_ms, delta_ms, closed_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(week_key) DO UPDATE SET expected_ms=excluded.expected_ms, worked_ms=excluded.worked_ms, delta_ms=excluded.delta_ms, closed_at=excluded.closed_at, updated_at=excluded.updated_at`, w.WeekKey, w.ExpectedMs, w.WorkedMs, w.DeltaMs, w.ClosedAt, w.UpdatedAt, ) return err } func (s *ClosedWeekStore) Delete(ctx context.Context, weekKey string) error { _, err := s.db.ExecContext(ctx, `DELETE FROM closed_weeks WHERE week_key=?`, weekKey) return err } func (s *ClosedWeekStore) GetByWeekKey(ctx context.Context, weekKey string) (*domain.ClosedWeek, error) { row := s.db.QueryRowContext(ctx, `SELECT week_key, expected_ms, worked_ms, delta_ms, closed_at, updated_at FROM closed_weeks WHERE week_key=?`, weekKey) var w domain.ClosedWeek err := row.Scan(&w.WeekKey, &w.ExpectedMs, &w.WorkedMs, &w.DeltaMs, &w.ClosedAt, &w.UpdatedAt) if err != nil { return nil, err } return &w, nil } func (s *ClosedWeekStore) ListByRange(ctx context.Context, fromWeekKey, toWeekKey string) ([]*domain.ClosedWeek, error) { rows, err := s.db.QueryContext(ctx, `SELECT week_key, expected_ms, worked_ms, delta_ms, closed_at, updated_at FROM closed_weeks WHERE week_key >= ? AND week_key <= ? ORDER BY week_key ASC`, fromWeekKey, toWeekKey) if err != nil { return nil, err } defer rows.Close() var result []*domain.ClosedWeek for rows.Next() { var w domain.ClosedWeek if err := rows.Scan(&w.WeekKey, &w.ExpectedMs, &w.WorkedMs, &w.DeltaMs, &w.ClosedAt, &w.UpdatedAt); err != nil { return nil, err } result = append(result, &w) } return result, rows.Err() } // SumDelta returns the sum of delta_ms and the count of rows across all closed_weeks. func (s *ClosedWeekStore) SumDelta(ctx context.Context) (totalDeltaMs int64, count int, err error) { err = s.db.QueryRowContext(ctx, `SELECT COALESCE(SUM(delta_ms), 0), COUNT(*) FROM closed_weeks`, ).Scan(&totalDeltaMs, &count) return } // SumWorkedMsForWeek sums worked_ms across closed_days for the given day keys. func SumWorkedMsForWeek(ctx context.Context, db *sql.DB, dayKeys []string) (int64, error) { if len(dayKeys) == 0 { return 0, nil } // Build IN clause placeholders := make([]byte, 0, len(dayKeys)*2) args := make([]interface{}, len(dayKeys)) for i, k := range dayKeys { if i > 0 { placeholders = append(placeholders, ',') } placeholders = append(placeholders, '?') args[i] = k } query := "SELECT COALESCE(SUM(worked_ms),0) FROM closed_days WHERE day_key IN (" + string(placeholders) + ")" var total int64 err := db.QueryRowContext(ctx, query, args...).Scan(&total) return total, err }