package models import ( "time" "gorm.io/gorm" ) type Sprint struct { ID uint `json:"id" gorm:"primaryKey"` ProjectID uint `json:"project_id"` Name string `json:"name" gorm:"not null"` Description string `json:"description"` Status string `json:"status" gorm:"default:'planning'"` StartDate *time.Time `json:"start_date"` EndDate *time.Time `json:"end_date"` Goal string `json:"goal"` Project Project `json:"project" gorm:"foreignKey:ProjectID"` Stories []Story `json:"stories" gorm:"foreignKey:SprintID"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type SprintStatus string const ( SprintStatusPlanning SprintStatus = "planning" SprintStatusActive SprintStatus = "active" SprintStatusCompleted SprintStatus = "completed" ) func (s *Sprint) BeforeCreate(tx *gorm.DB) error { return nil } func (s *Sprint) GetVelocity() (int, error) { var totalStoryPoints int err := DB.Model(&Story{}). Where("sprint_id = ? AND status = 'done'", s.ID). Select("COALESCE(SUM(story_points), 0)"). Scan(&totalStoryPoints).Error return totalStoryPoints, err } func (s *Sprint) GetBurndownData() ([]map[string]interface{}, error) { var stories []Story err := DB.Where("sprint_id = ?", s.ID).Find(&stories).Error if err != nil { return nil, err } var burndownData []map[string]interface{} // Calculate total story points totalPoints := 0 for _, story := range stories { if story.StoryPoints != nil { totalPoints += *story.StoryPoints } } // This is a simplified version - in a real implementation, // you'd track daily progress burndownData = append(burndownData, map[string]interface{}{ "date": s.StartDate, "remaining": totalPoints, "ideal": totalPoints, }) if s.EndDate != nil { burndownData = append(burndownData, map[string]interface{}{ "date": s.EndDate, "remaining": 0, "ideal": 0, }) } return burndownData, nil } func CreateSprint(sprint *Sprint) error { return DB.Create(sprint).Error } func GetSprintByID(id uint) (*Sprint, error) { var sprint Sprint err := DB.Preload("Project").Preload("Stories.Assignee"). First(&sprint, id).Error return &sprint, err } func GetSprintsByProject(projectID uint) ([]Sprint, error) { var sprints []Sprint err := DB.Where("project_id = ?", projectID). Preload("Stories"). Find(&sprints).Error return sprints, err } func GetActiveSprintByProject(projectID uint) (*Sprint, error) { var sprint Sprint err := DB.Where("project_id = ? AND status = 'active'", projectID). Preload("Stories.Assignee"). First(&sprint).Error return &sprint, err } func UpdateSprint(sprint *Sprint) error { return DB.Save(sprint).Error } func DeleteSprint(id uint) error { return DB.Delete(&Sprint{}, id).Error } func ListSprints(offset, limit int, filters map[string]interface{}) ([]Sprint, int64, error) { var sprints []Sprint var total int64 query := DB.Model(&Sprint{}) for key, value := range filters { switch key { case "project_id": query = query.Where("project_id = ?", value) case "status": query = query.Where("status = ?", value) } } err := query.Count(&total).Error if err != nil { return nil, 0, err } err = query.Preload("Project"). Offset(offset).Limit(limit).Find(&sprints).Error return sprints, total, err }