
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
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Epic struct {
|
|
ID uint `json:"id" gorm:"primaryKey"`
|
|
ProjectID uint `json:"project_id"`
|
|
Title string `json:"title" gorm:"not null"`
|
|
Description string `json:"description"`
|
|
Status string `json:"status" gorm:"default:'backlog'"`
|
|
Priority string `json:"priority" gorm:"default:'medium'"`
|
|
AssigneeID *uint `json:"assignee_id"`
|
|
ReporterID uint `json:"reporter_id"`
|
|
StartDate *time.Time `json:"start_date"`
|
|
DueDate *time.Time `json:"due_date"`
|
|
EstimatedHours *float64 `json:"estimated_hours"`
|
|
ActualHours float64 `json:"actual_hours" gorm:"default:0"`
|
|
ProgressPercentage int `json:"progress_percentage" gorm:"default:0"`
|
|
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
|
|
Assignee *User `json:"assignee" gorm:"foreignKey:AssigneeID"`
|
|
Reporter User `json:"reporter" gorm:"foreignKey:ReporterID"`
|
|
Stories []Story `json:"stories" gorm:"foreignKey:EpicID"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type EpicStatus string
|
|
|
|
const (
|
|
EpicStatusBacklog EpicStatus = "backlog"
|
|
EpicStatusPlanning EpicStatus = "planning"
|
|
EpicStatusInProgress EpicStatus = "in_progress"
|
|
EpicStatusTesting EpicStatus = "testing"
|
|
EpicStatusDone EpicStatus = "done"
|
|
EpicStatusCancelled EpicStatus = "cancelled"
|
|
)
|
|
|
|
func (e *Epic) BeforeCreate(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func (e *Epic) UpdateProgress() error {
|
|
var totalStories int64
|
|
var completedStories int64
|
|
|
|
err := DB.Model(&Story{}).Where("epic_id = ?", e.ID).Count(&totalStories).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if totalStories == 0 {
|
|
e.ProgressPercentage = 0
|
|
return DB.Save(e).Error
|
|
}
|
|
|
|
err = DB.Model(&Story{}).Where("epic_id = ? AND status = 'done'", e.ID).Count(&completedStories).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.ProgressPercentage = int(float64(completedStories) / float64(totalStories) * 100)
|
|
return DB.Save(e).Error
|
|
}
|
|
|
|
func (e *Epic) GetStories() ([]Story, error) {
|
|
var stories []Story
|
|
err := DB.Where("epic_id = ?", e.ID).Preload("Assignee").Find(&stories).Error
|
|
return stories, err
|
|
}
|
|
|
|
func CreateEpic(epic *Epic) error {
|
|
return DB.Create(epic).Error
|
|
}
|
|
|
|
func GetEpicByID(id uint) (*Epic, error) {
|
|
var epic Epic
|
|
err := DB.Preload("Project").Preload("Assignee").Preload("Reporter").
|
|
Preload("Stories").First(&epic, id).Error
|
|
return &epic, err
|
|
}
|
|
|
|
func GetEpicsByProject(projectID uint) ([]Epic, error) {
|
|
var epics []Epic
|
|
err := DB.Where("project_id = ?", projectID).
|
|
Preload("Assignee").Preload("Reporter").
|
|
Find(&epics).Error
|
|
return epics, err
|
|
}
|
|
|
|
func UpdateEpic(epic *Epic) error {
|
|
return DB.Save(epic).Error
|
|
}
|
|
|
|
func DeleteEpic(id uint) error {
|
|
return DB.Delete(&Epic{}, id).Error
|
|
}
|
|
|
|
func ListEpics(offset, limit int, filters map[string]interface{}) ([]Epic, int64, error) {
|
|
var epics []Epic
|
|
var total int64
|
|
|
|
query := DB.Model(&Epic{})
|
|
|
|
for key, value := range filters {
|
|
switch key {
|
|
case "project_id":
|
|
query = query.Where("project_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("Project").Preload("Assignee").Preload("Reporter").
|
|
Offset(offset).Limit(limit).Find(&epics).Error
|
|
return epics, total, err
|
|
} |