diff --git a/pkg/db/fixtures/buckets.yml b/pkg/db/fixtures/buckets.yml index 93dc7c36445..ba0d2d354db 100644 --- a/pkg/db/fixtures/buckets.yml +++ b/pkg/db/fixtures/buckets.yml @@ -18,7 +18,6 @@ title: testbucket3 project_id: 1 created_by_id: 1 - is_done_bucket: 1 position: 3 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 @@ -26,7 +25,6 @@ title: testbucket4 - other project project_id: 2 created_by_id: 1 - is_done_bucket: 1 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 # The following are not or only partly owned by user 1 diff --git a/pkg/db/fixtures/projects.yml b/pkg/db/fixtures/projects.yml index 6603554e160..f25166483cb 100644 --- a/pkg/db/fixtures/projects.yml +++ b/pkg/db/fixtures/projects.yml @@ -5,6 +5,7 @@ identifier: test1 owner_id: 1 position: 3 + done_bucket_id: 3 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -14,6 +15,7 @@ identifier: test2 owner_id: 3 position: 2 + done_bucket_id: 4 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - diff --git a/pkg/migration/20230903143017.go b/pkg/migration/20230903143017.go new file mode 100644 index 00000000000..5040c53de66 --- /dev/null +++ b/pkg/migration/20230903143017.go @@ -0,0 +1,118 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type projects20230903143017 struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"` + DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id"` + DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"` +} + +func (projects20230903143017) TableName() string { + return "projects" +} + +type bucket20230903143017 struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"bucket"` + IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"` + ProjectID int64 `xorm:"bigint not null" json:"project_id" param:"project"` +} + +func (bucket20230903143017) TableName() string { + return "buckets" +} + +const dropIsDoneBucketColSqlite20230903143017 = ` +create table buckets_dg_tmp +( + id INTEGER not null + primary key autoincrement, + title TEXT not null, + project_id INTEGER not null, + "limit" INTEGER default 0, + position REAL, + created DATETIME not null, + updated DATETIME not null, + created_by_id INTEGER not null +); + +insert into buckets_dg_tmp(id, title, project_id, "limit", position, created, updated, created_by_id) +select id, + title, + project_id, + "limit", + position, + created, + updated, + created_by_id +from buckets; + +drop table buckets; + +alter table buckets_dg_tmp + rename to buckets; + +create unique index UQE_buckets_id + on buckets (id); +` + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20230903143017", + Description: "Move done bucket state to project + add default bucket setting", + Migrate: func(tx *xorm.Engine) (err error) { + err = tx.Sync2(projects20230903143017{}) + if err != nil { + return + } + + doneBuckets := []*bucket20230903143017{} + err = tx.Where("is_done_bucket = true"). + Find(&doneBuckets) + if err != nil { + return + } + + for _, bucket := range doneBuckets { + _, err = tx.Where("id = ?", bucket.ProjectID). + Cols("done_bucket_id"). + Update(&projects20230903143017{ + DoneBucketID: bucket.ID, + }) + if err != nil { + return + } + } + + if tx.Dialect().URI().DBType == schemas.SQLITE { + _, err = tx.Exec(dropIsDoneBucketColSqlite20230903143017) + return err + } + + return dropTableColum(tx, "buckets", "is_done_bucket") + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index beac99cb45b..60dc37a870a 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -38,8 +38,6 @@ type Bucket struct { // How many tasks can be at the same time on this board max Limit int64 `xorm:"default 0" json:"limit" minimum:"0" valid:"range(0|9223372036854775807)"` - // If this bucket is the "done bucket". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket. - IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"` // The number of tasks currently in this bucket Count int64 `xorm:"-" json:"count"` @@ -92,7 +90,7 @@ func getDefaultBucket(s *xorm.Session, projectID int64) (bucket *Bucket, err err func getDoneBucketForProject(s *xorm.Session, projectID int64) (bucket *Bucket, err error) { bucket = &Bucket{} exists, err := s. - Where("project_id = ? and is_done_bucket = ?", projectID, true). + Where("id = (select done_bucket_id from projects where id = ?)", projectID). Get(bucket) if err != nil { return nil, err @@ -287,29 +285,11 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /projects/{projectID}/buckets/{bucketID} [post] func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) { - doneBucket, err := getDoneBucketForProject(s, b.ProjectID) - if err != nil { - return err - } - - if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket && doneBucket.ID != b.ID { - // When the current bucket will be the new done bucket, the old one should not be the done bucket anymore - doneBucket.IsDoneBucket = false - _, err = s. - Where("id = ?", doneBucket.ID). - Cols("is_done_bucket"). - Update(doneBucket) - if err != nil { - return - } - } - _, err = s. Where("id = ?", b.ID). Cols( "title", "limit", - "is_done_bucket", "position", ). Update(b) diff --git a/pkg/models/kanban_test.go b/pkg/models/kanban_test.go index 31d6400cfe8..93eebce0588 100644 --- a/pkg/models/kanban_test.go +++ b/pkg/models/kanban_test.go @@ -217,28 +217,4 @@ func TestBucket_Update(t *testing.T) { testAndAssertBucketUpdate(t, b, s) }) - t.Run("old done bucket should be unset", func(t *testing.T) { - db.LoadAndAssertFixtures(t) - s := db.NewSession() - defer s.Close() - - b := &Bucket{ - ID: 1, - ProjectID: 1, - IsDoneBucket: true, - } - - err := b.Update(s, &user.User{ID: 1}) - assert.NoError(t, err) - db.AssertExists(t, "buckets", map[string]interface{}{ - "id": 1, - "project_id": 1, - "is_done_bucket": true, - }, false) - db.AssertExists(t, "buckets", map[string]interface{}{ - "id": 3, - "project_id": 1, - "is_done_bucket": false, - }, false) - }) } diff --git a/pkg/models/project.go b/pkg/models/project.go index d730de4a324..353221146ba 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -51,6 +51,11 @@ type Project struct { ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"` ParentProject *Project `xorm:"-" json:"-"` + // The ID of the bucket where new tasks without a bucket are added to. By default, this is the leftmost bucket in a project. + DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id"` + // If tasks are moved to the done bucket, they are marked as done. If they are marked as done individually, they are moved into the done bucket. + DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"` + // The user who created this project. Owner *user.User `xorm:"-" json:"owner" valid:"-"` diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 785e4f26439..a572c1f688b 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -676,7 +676,12 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke } } - if bucket.IsDoneBucket && originalTask != nil && !originalTask.Done { + project, err := GetProjectSimpleByID(s, task.ProjectID) + if err != nil { + return nil, err + } + + if bucket.ID == project.DoneBucketID && originalTask != nil && !originalTask.Done { task.Done = true } @@ -845,7 +850,11 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { // If the task was moved into the done bucket and the task has a repeating cycle we should not update // the bucket. - if targetBucket.IsDoneBucket && t.RepeatAfter > 0 { + project, err := GetProjectSimpleByID(s, t.ProjectID) + if err != nil { + return err + } + if targetBucket.ID == project.DoneBucketID && t.RepeatAfter > 0 { t.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later) t.BucketID = ot.BucketID }