diff --git a/pkg/migration/20240315110428.go b/pkg/migration/20240315110428.go index 332a8f93e..b2ae07ba8 100644 --- a/pkg/migration/20240315110428.go +++ b/pkg/migration/20240315110428.go @@ -33,8 +33,9 @@ func (task20240315110428) TableName() string { } type taskBuckets20240315110428 struct { - BucketID int64 `xorm:"bigint not null index"` - TaskID int64 `xorm:"bigint not null index"` + BucketID int64 `xorm:"bigint not null index"` + TaskID int64 `xorm:"bigint not null index"` + ProjectViewID int64 `xorm:"bigint not null index"` } func (taskBuckets20240315110428) TableName() string { diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 1b4a1128d..df885d17f 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -71,8 +71,13 @@ func (b *Bucket) TableName() string { } type TaskBucket struct { - BucketID int64 `xorm:"bigint not null index"` - TaskID int64 `xorm:"bigint not null index"` + BucketID int64 `xorm:"bigint not null index"` + TaskID int64 `xorm:"bigint not null index"` + ProjectViewID int64 `xorm:"bigint not null index"` +} + +func (b *TaskBucket) TableName() string { + return "task_buckets" } func getBucketByID(s *xorm.Session, id int64) (b *Bucket, err error) { diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index de9c90e37..58c51328e 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -151,6 +151,14 @@ func (p *ProjectView) TableName() string { return "project_views" } +func getViewsForProject(s *xorm.Session, projectID int64) (views []*ProjectView, err error) { + views = []*ProjectView{} + err = s. + Where("project_id = ?", projectID). + Find(&views) + return +} + // ReadAll gets all project views // @Summary Get all project views for a project // @Description Returns all project views for a sepcific project @@ -173,12 +181,9 @@ func (p *ProjectView) ReadAll(s *xorm.Session, a web.Auth, _ string, _ int, _ in return nil, 0, 0, ErrGenericForbidden{} } - projectViews := []*ProjectView{} - err = s. - Where("project_id = ?", p.ProjectID). - Find(&projectViews) + projectViews, err := getViewsForProject(s, p.ProjectID) if err != nil { - return + return nil, 0, 0, err } totalCount, err := s. diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 3710fdc1f..d4ad6ec05 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -648,41 +648,48 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) { } // Contains all the task logic to figure out what bucket to use for this task. -func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool, view *ProjectView) (targetBucket *Bucket, err error) { +func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, view *ProjectView, targetBucket *TaskBucket) (err error) { - if view == nil { - view, err = GetProjectViewByID(s, view.ID, task.ProjectID) - if err != nil { - return nil, err + var shouldChangeBucket = true + if targetBucket == nil { + targetBucket = &TaskBucket{ + BucketID: 0, + TaskID: task.ID, + ProjectViewID: view.ID, } } - var bucket *Bucket - if task.Done && originalTask != nil && - (!originalTask.Done || task.ProjectID != originalTask.ProjectID) { - task.BucketID = view.DoneBucketID + oldTaskBucket := &TaskBucket{} + _, err = s. + Where("task_id = ? AND project_view_id = ?", task.ID, view.ID). + Get(oldTaskBucket) + if err != nil { + return } - if task.BucketID == 0 && originalTask != nil && originalTask.BucketID != 0 { - task.BucketID = originalTask.BucketID + if task.Done && originalTask != nil && + (!originalTask.Done || task.ProjectID != originalTask.ProjectID) { + targetBucket.BucketID = view.DoneBucketID + } + + if targetBucket.BucketID == 0 && oldTaskBucket.BucketID != 0 { + shouldChangeBucket = false } // Either no bucket was provided or the task was moved between projects // But if the task was moved between projects, don't update the done bucket // because then we have it already updated to the done bucket. - if task.BucketID == 0 || + if targetBucket.BucketID == 0 || (originalTask != nil && task.ProjectID != 0 && originalTask.ProjectID != task.ProjectID && !task.Done) { - task.BucketID, err = getDefaultBucketID(s, view) + targetBucket.BucketID, err = getDefaultBucketID(s, view) if err != nil { return } } - if bucket == nil { - bucket, err = getBucketByID(s, task.BucketID) - if err != nil { - return - } + bucket, err := getBucketByID(s, targetBucket.BucketID) + if err != nil { + return err } // If there is a bucket set, make sure they belong to the same project as the task @@ -693,9 +700,10 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke // Check the bucket limit // Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket - if doCheckBucketLimit { - if err := checkBucketLimit(s, task, bucket); err != nil { - return nil, err + if targetBucket.BucketID != 0 && targetBucket.BucketID != oldTaskBucket.BucketID { + err = checkBucketLimit(s, task, bucket) + if err != nil { + return err } } @@ -703,7 +711,26 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke task.Done = true } - return bucket, nil + // If the task was moved into the done bucket and the task has a repeating cycle we should not update + // the bucket. + if bucket.ID == view.DoneBucketID && task.RepeatAfter > 0 { + task.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later) + shouldChangeBucket = false + } + + if shouldChangeBucket { + _, err = s. + Where("task_id = ? AND project_view_id = ?", task.ID, view.ID). + Delete(&TaskBucket{}) + if err != nil { + return + } + + targetBucket.BucketID = bucket.ID + _, err = s.Insert(targetBucket) + } + + return } func calculateDefaultPosition(entityID int64, position float64) float64 { @@ -714,6 +741,18 @@ func calculateDefaultPosition(entityID int64, position float64) float64 { return position } +func (t *Task) setTaskPosition(s *xorm.Session, position float64, view *ProjectView) (err error) { + + pos := &TaskPosition{ + TaskID: t.ID, + ProjectViewID: view.ID, + Position: calculateDefaultPosition(t.Index, position), + } + + _, err = s.Insert(pos) + return +} + func getNextTaskIndex(s *xorm.Session, projectID int64) (nextIndex int64, err error) { latestTask := &Task{} _, err = s. @@ -771,21 +810,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err t.UID = uuid.NewString() } - // Get the default bucket and move the task there - _, err = setTaskBucket(s, t, nil, true, nil) - if err != nil { - return - } - // Get the index for this task t.Index, err = getNextTaskIndex(s, t.ProjectID) if err != nil { return err } - // If no position was supplied, set a default one - t.Position = calculateDefaultPosition(t.Index, t.Position) - t.HexColor = utils.NormalizeHex(t.HexColor) _, err = s.Insert(t) @@ -793,6 +823,25 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err return err } + views, err := getViewsForProject(s, t.ProjectID) + if err != nil { + return err + } + + for _, view := range views { + + // Get the default bucket and move the task there + err = setTaskBucket(s, t, nil, view, &TaskBucket{}) + if err != nil { + return + } + + err = t.setTaskPosition(s, t.Position, view) + if err != nil { + return + } + } + t.CreatedBy = createdBy // Update the assignees @@ -864,21 +913,29 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { // Old task has the stored reminders ot.Reminders = reminders - project, err := GetProjectSimpleByID(s, t.ProjectID) + views, err := getViewsForProject(s, t.ProjectID) if err != nil { return err } - targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID, nil) - if err != nil { - return err - } + for _, view := range views { + // bucket id mitgeben, dann den view zu dem bucket suchen und updaten wenn nötig - // If the task was moved into the done bucket and the task has a repeating cycle we should not update - // the bucket. - 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 + taskBucket := &TaskBucket{ + BucketID: t.BucketID, + TaskID: t.ID, + ProjectViewID: view.ID, + } + + err = setTaskBucket(s, t, &ot, view, taskBucket) + if err != nil { + return err + } + + err = t.setTaskPosition(s, t.Position, view) + if err != nil { + return + } } // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone @@ -909,7 +966,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { "percent_done", "project_id", "bucket_id", - "position", "repeat_mode", "cover_image_attachment_id", }