This commit is contained in:
nithin varma 2026-06-16 18:33:12 +00:00 committed by GitHub
commit 14ece2f9ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 22 deletions

View File

@ -148,14 +148,20 @@ func updateTaskBucket(s *xorm.Session, a web.Auth, b *TaskBucket) (err error) {
// A repeating task doesn't stay in the done bucket; route
// it back to the view's default bucket so the user sees
// the next iteration waiting in the "To-Do" column.
b.BucketID, err = getDefaultBucketID(s, view)
if err != nil {
return err
// When no default bucket is configured, leave the task in
// its current bucket — no update needed.
if view.DefaultBucketID != 0 {
b.BucketID, err = getDefaultBucketID(s, view)
if err != nil {
return err
}
} else {
b.BucketID = oldTaskBucket.BucketID
}
// If the task is already in the default bucket, skip the
// upsert — MySQL's UPDATE returns 0 affected rows when
// the value is unchanged, which would make upsert fall
// through to INSERT and hit the unique constraint.
// If the bucket is unchanged, skip the upsert — MySQL's
// UPDATE returns 0 affected rows when the value is unchanged,
// which would make upsert fall through to INSERT and hit the
// unique constraint.
if b.BucketID == oldTaskBucket.BucketID {
updateBucket = false
}

View File

@ -226,6 +226,61 @@ func TestTaskBucket_Update(t *testing.T) {
})
})
t.Run("moving a repeating task from a non-default bucket when no default bucket is configured preserves the original bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
u := &user.User{ID: 1}
// View 4 has default_bucket_id: 1 and done_bucket_id: 3.
// Remove the default bucket to test fallback behavior.
_, err := s.ID(4).Cols("default_bucket_id").Update(&ProjectView{DefaultBucketID: 0})
require.NoError(t, err)
// Task 28 is a repeating task. Pre-position it in bucket 2
// using a raw update so we can bypass the bucket-2 limit check.
_, err = s.Where("task_id = ? AND project_view_id = ?", 28, 4).
Cols("bucket_id").
Update(&TaskBucket{BucketID: 2})
require.NoError(t, err)
tb := &TaskBucket{
TaskID: 28,
BucketID: 3, // Bucket 3 is the done bucket on view 4
ProjectViewID: 4,
ProjectID: 1,
}
err = tb.Update(s, u)
require.NoError(t, err)
err = s.Commit()
require.NoError(t, err)
// Repeating task should have been re-opened by updateDone...
assert.False(t, tb.Task.Done)
// ...and routed back to the ORIGINAL bucket (2), not the default (1)
// and not left in the done bucket (3).
assert.Equal(t, int64(2), tb.BucketID)
db.AssertExists(t, "tasks", map[string]interface{}{
"id": 28,
"done": false,
}, false)
db.AssertExists(t, "task_buckets", map[string]interface{}{
"task_id": 28,
"bucket_id": 2,
}, false)
db.AssertMissing(t, "task_buckets", map[string]interface{}{
"task_id": 28,
"bucket_id": 1,
})
db.AssertMissing(t, "task_buckets", map[string]interface{}{
"task_id": 28,
"bucket_id": 3,
})
})
t.Run("keep done timestamp when moving task between projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
u := &user.User{ID: 1}

View File

@ -1535,31 +1535,35 @@ func (t *Task) moveTaskToDoneBuckets(s *xorm.Session, a web.Auth, views []*Proje
// and is used when a repeating task is marked done: repeating tasks
// don't stay in the done bucket, so they should be routed back to
// the default ("To-Do") bucket so the next iteration is visible there.
// When no explicit default bucket is configured, the task stays in its
// current bucket — no update needed.
func (t *Task) moveTaskToDefaultBuckets(s *xorm.Session, a web.Auth, views []*ProjectView) error {
for _, view := range views {
defaultBucketID, err := getDefaultBucketID(s, view)
if err != nil {
return err
}
if view.DefaultBucketID != 0 {
defaultBucketID, err := getDefaultBucketID(s, view)
if err != nil {
return err
}
tb := &TaskBucket{
BucketID: defaultBucketID,
TaskID: t.ID,
ProjectViewID: view.ID,
ProjectID: t.ProjectID,
}
err = updateTaskBucket(s, a, tb)
if err != nil {
return err
tb := &TaskBucket{
BucketID: defaultBucketID,
TaskID: t.ID,
ProjectViewID: view.ID,
ProjectID: t.ProjectID,
}
if err = updateTaskBucket(s, a, tb); err != nil {
return err
}
}
// When no default bucket is configured, the task stays in its current
// bucket — no bucket update needed.
tp := TaskPosition{
TaskID: t.ID,
ProjectViewID: view.ID,
Position: calculateDefaultPosition(t.Index, t.Position),
}
err = updateTaskPosition(s, a, &tp)
if err != nil {
if err := updateTaskPosition(s, a, &tp); err != nil {
return err
}
}