Files
gitpm/internal/models/story.go
huxunan 885fad6c64 Initial commit: Gitea Project Management System
Features:
- Complete project management system with Epic/Story/Task hierarchy
- Vue.js 3 + Element Plus frontend with kanban board
- Go backend with Gin framework and GORM
- OAuth2 integration with Gitea
- Docker containerization with MySQL
- RESTful API for project, task, and user management
- JWT authentication and authorization
- Responsive web interface with dashboard
2025-09-22 14:53:53 +08:00

159 lines
4.5 KiB
Go

package models
import (
"time"
"gorm.io/gorm"
)
type Story struct {
ID uint `json:"id" gorm:"primaryKey"`
EpicID *uint `json:"epic_id"`
ProjectID uint `json:"project_id"`
Title string `json:"title" gorm:"not null"`
Description string `json:"description"`
AcceptanceCriteria string `json:"acceptance_criteria"`
Status string `json:"status" gorm:"default:'backlog'"`
Priority string `json:"priority" gorm:"default:'medium'"`
StoryPoints *int `json:"story_points"`
AssigneeID *uint `json:"assignee_id"`
ReporterID uint `json:"reporter_id"`
SprintID *uint `json:"sprint_id"`
EstimatedHours *float64 `json:"estimated_hours"`
ActualHours float64 `json:"actual_hours" gorm:"default:0"`
Epic *Epic `json:"epic" gorm:"foreignKey:EpicID"`
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
Assignee *User `json:"assignee" gorm:"foreignKey:AssigneeID"`
Reporter User `json:"reporter" gorm:"foreignKey:ReporterID"`
Sprint *Sprint `json:"sprint" gorm:"foreignKey:SprintID"`
Tasks []Task `json:"tasks" gorm:"foreignKey:StoryID"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type StoryStatus string
const (
StoryStatusBacklog StoryStatus = "backlog"
StoryStatusTodo StoryStatus = "todo"
StoryStatusInProgress StoryStatus = "in_progress"
StoryStatusReview StoryStatus = "review"
StoryStatusTesting StoryStatus = "testing"
StoryStatusDone StoryStatus = "done"
StoryStatusCancelled StoryStatus = "cancelled"
)
func (s *Story) BeforeCreate(tx *gorm.DB) error {
return nil
}
func (s *Story) GetTasks() ([]Task, error) {
var tasks []Task
err := DB.Where("story_id = ?", s.ID).Preload("Assignee").Find(&tasks).Error
return tasks, err
}
func (s *Story) UpdateProgress() error {
var totalTasks int64
var completedTasks int64
err := DB.Model(&Task{}).Where("story_id = ?", s.ID).Count(&totalTasks).Error
if err != nil {
return err
}
if totalTasks == 0 {
return nil
}
err = DB.Model(&Task{}).Where("story_id = ? AND status = 'done'", s.ID).Count(&completedTasks).Error
if err != nil {
return err
}
if completedTasks == totalTasks {
s.Status = string(StoryStatusDone)
} else if completedTasks > 0 {
s.Status = string(StoryStatusInProgress)
}
return DB.Save(s).Error
}
func CreateStory(story *Story) error {
return DB.Create(story).Error
}
func GetStoryByID(id uint) (*Story, error) {
var story Story
err := DB.Preload("Epic").Preload("Project").Preload("Assignee").
Preload("Reporter").Preload("Sprint").Preload("Tasks").
First(&story, id).Error
return &story, err
}
func GetStoriesByProject(projectID uint) ([]Story, error) {
var stories []Story
err := DB.Where("project_id = ?", projectID).
Preload("Epic").Preload("Assignee").Preload("Reporter").
Find(&stories).Error
return stories, err
}
func GetStoriesByEpic(epicID uint) ([]Story, error) {
var stories []Story
err := DB.Where("epic_id = ?", epicID).
Preload("Assignee").Preload("Reporter").
Find(&stories).Error
return stories, err
}
func GetStoriesBySprint(sprintID uint) ([]Story, error) {
var stories []Story
err := DB.Where("sprint_id = ?", sprintID).
Preload("Assignee").Preload("Reporter").
Find(&stories).Error
return stories, err
}
func UpdateStory(story *Story) error {
return DB.Save(story).Error
}
func DeleteStory(id uint) error {
return DB.Delete(&Story{}, id).Error
}
func ListStories(offset, limit int, filters map[string]interface{}) ([]Story, int64, error) {
var stories []Story
var total int64
query := DB.Model(&Story{})
for key, value := range filters {
switch key {
case "project_id":
query = query.Where("project_id = ?", value)
case "epic_id":
query = query.Where("epic_id = ?", value)
case "sprint_id":
query = query.Where("sprint_id = ?", value)
case "status":
query = query.Where("status = ?", value)
case "priority":
query = query.Where("priority = ?", value)
case "assignee_id":
query = query.Where("assignee_id = ?", value)
}
}
err := query.Count(&total).Error
if err != nil {
return nil, 0, err
}
err = query.Preload("Epic").Preload("Project").Preload("Assignee").
Preload("Reporter").Preload("Sprint").
Offset(offset).Limit(limit).Find(&stories).Error
return stories, total, err
}