summaryrefslogtreecommitdiff
path: root/internal/models/recurrence.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/models/recurrence.go')
-rw-r--r--internal/models/recurrence.go40
1 files changed, 40 insertions, 0 deletions
diff --git a/internal/models/recurrence.go b/internal/models/recurrence.go
index f42183c..f407688 100644
--- a/internal/models/recurrence.go
+++ b/internal/models/recurrence.go
@@ -55,3 +55,43 @@ func nextWeeklyOccurrence(due time.Time, interval int, weekdays []int) time.Time
daysToNextWeekStart := 7 - dueWeekday
return due.AddDate(0, 0, daysToNextWeekStart+sorted[0]+7*(interval-1))
}
+
+// CollapseRecurringSeries reduces each recurrence series to its single
+// earliest-due instance, preserving input order otherwise.
+//
+// The scheduler deliberately materializes a series' next instance once the
+// current one comes due, whether or not it was completed, so that ignoring one
+// occurrence doesn't stall the series (see
+// GetSeriesNeedingNextIteration). The side effect is that an unfinished
+// recurring chore and its already-generated successor both sit in the flat task
+// list at once -- "Bills" appearing twice, once overdue from Sep 1 and once for
+// Oct 1. Only the earliest open one is a thing you actually owe right now.
+//
+// This is for flat "what's open" views only. Date-anchored views (timeline,
+// widget) should keep every instance, since there each one legitimately belongs
+// to its own day.
+func CollapseRecurringSeries(tasks []Task) []Task {
+ earliest := make(map[string]*Task)
+ for i := range tasks {
+ t := &tasks[i]
+ if t.RecurrenceSeriesID == "" || t.DueDate == nil {
+ continue
+ }
+ cur, ok := earliest[t.RecurrenceSeriesID]
+ if !ok || t.DueDate.Before(*cur.DueDate) {
+ earliest[t.RecurrenceSeriesID] = t
+ }
+ }
+
+ out := make([]Task, 0, len(tasks))
+ for i := range tasks {
+ t := tasks[i]
+ // An undated recurring row has no position in the series ordering, so it
+ // is never collapsed away -- only dated instances compete.
+ if keep, ok := earliest[t.RecurrenceSeriesID]; ok && t.DueDate != nil && keep.ID != t.ID {
+ continue
+ }
+ out = append(out, t)
+ }
+ return out
+}