You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
9.9 KiB
339 lines
9.9 KiB
package statistics
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"task-track-backend/model"
|
|
"task-track-backend/pkg/database"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// TaskStatisticsResponse 任务统计响应
|
|
type TaskStatisticsResponse struct {
|
|
// 基本统计
|
|
TotalTasks int64 `json:"total_tasks"`
|
|
PendingTasks int64 `json:"pending_tasks"`
|
|
InProgressTasks int64 `json:"in_progress_tasks"`
|
|
CompletedTasks int64 `json:"completed_tasks"`
|
|
CancelledTasks int64 `json:"cancelled_tasks"`
|
|
|
|
// 按状态统计
|
|
TasksByStatus []TaskStatusStat `json:"tasks_by_status"`
|
|
|
|
// 按优先级统计
|
|
TasksByPriority []TaskPriorityStat `json:"tasks_by_priority"`
|
|
|
|
// 按类型统计
|
|
TasksByType []TaskTypeStat `json:"tasks_by_type"`
|
|
|
|
// 按创建者统计
|
|
TasksByCreator []TaskCreatorStat `json:"tasks_by_creator"`
|
|
|
|
// 按分配者统计
|
|
TasksByAssignee []TaskAssigneeStat `json:"tasks_by_assignee"`
|
|
|
|
// 时间趋势统计(最近30天)
|
|
DailyTrend []DailyTaskStat `json:"daily_trend"`
|
|
|
|
// 完成率统计
|
|
CompletionRate float64 `json:"completion_rate"`
|
|
|
|
// 平均完成时间(小时)
|
|
AverageCompletionTime float64 `json:"average_completion_time"`
|
|
|
|
// 逾期任务统计
|
|
OverdueTasks int64 `json:"overdue_tasks"`
|
|
|
|
// 即将到期任务统计(7天内)
|
|
UpcomingTasks int64 `json:"upcoming_tasks"`
|
|
}
|
|
|
|
// TaskStatusStat 任务状态统计
|
|
type TaskStatusStat struct {
|
|
Status string `json:"status"`
|
|
Count int64 `json:"count"`
|
|
Percentage float64 `json:"percentage"`
|
|
}
|
|
|
|
// TaskPriorityStat 任务优先级统计
|
|
type TaskPriorityStat struct {
|
|
Priority string `json:"priority"`
|
|
Count int64 `json:"count"`
|
|
Percentage float64 `json:"percentage"`
|
|
}
|
|
|
|
// TaskTypeStat 任务类型统计
|
|
type TaskTypeStat struct {
|
|
Type string `json:"type"`
|
|
Count int64 `json:"count"`
|
|
Percentage float64 `json:"percentage"`
|
|
}
|
|
|
|
// TaskCreatorStat 任务创建者统计
|
|
type TaskCreatorStat struct {
|
|
CreatorID uint `json:"creator_id"`
|
|
CreatorName string `json:"creator_name"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
// TaskAssigneeStat 任务分配者统计
|
|
type TaskAssigneeStat struct {
|
|
AssigneeID uint `json:"assignee_id"`
|
|
AssigneeName string `json:"assignee_name"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
// DailyTaskStat 每日任务统计
|
|
type DailyTaskStat struct {
|
|
Date string `json:"date"`
|
|
Created int64 `json:"created"`
|
|
Completed int64 `json:"completed"`
|
|
}
|
|
|
|
func (h *StatisticsHandler) GetTaskStatistics(c *gin.Context) {
|
|
// 获取组织ID参数(可选)
|
|
organizationIDStr := c.Query("organization_id")
|
|
var organizationID uint
|
|
if organizationIDStr != "" {
|
|
if id, err := strconv.ParseUint(organizationIDStr, 10, 32); err == nil {
|
|
organizationID = uint(id)
|
|
}
|
|
}
|
|
|
|
// 获取时间范围参数
|
|
daysStr := c.DefaultQuery("days", "30")
|
|
days, err := strconv.Atoi(daysStr)
|
|
if err != nil {
|
|
days = 30
|
|
}
|
|
|
|
db := database.GetDB()
|
|
var response TaskStatisticsResponse
|
|
|
|
// 基础查询构建器
|
|
baseTaskQuery := db.Model(&model.Task{})
|
|
if organizationID > 0 {
|
|
baseTaskQuery = baseTaskQuery.Where("organization_id = ?", organizationID)
|
|
}
|
|
|
|
// 1. 基本统计
|
|
if err := baseTaskQuery.Count(&response.TotalTasks).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务总数失败"})
|
|
return
|
|
}
|
|
|
|
// 各状态任务数
|
|
if err := baseTaskQuery.Where("status = ?", "pending").Count(&response.PendingTasks).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取待处理任务数失败"})
|
|
return
|
|
}
|
|
|
|
if err := baseTaskQuery.Where("status = ?", "in_progress").Count(&response.InProgressTasks).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取进行中任务数失败"})
|
|
return
|
|
}
|
|
|
|
if err := baseTaskQuery.Where("status = ?", "completed").Count(&response.CompletedTasks).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取已完成任务数失败"})
|
|
return
|
|
}
|
|
|
|
if err := baseTaskQuery.Where("status = ?", "cancelled").Count(&response.CancelledTasks).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取已取消任务数失败"})
|
|
return
|
|
}
|
|
|
|
// 2. 按状态统计
|
|
var statusStats []struct {
|
|
Status string `json:"status"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
statusQuery := baseTaskQuery.Select("status, count(*) as count").Group("status")
|
|
if err := statusQuery.Find(&statusStats).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务状态统计失败"})
|
|
return
|
|
}
|
|
|
|
response.TasksByStatus = make([]TaskStatusStat, len(statusStats))
|
|
for i, stat := range statusStats {
|
|
percentage := float64(0)
|
|
if response.TotalTasks > 0 {
|
|
percentage = float64(stat.Count) / float64(response.TotalTasks) * 100
|
|
}
|
|
response.TasksByStatus[i] = TaskStatusStat{
|
|
Status: stat.Status,
|
|
Count: stat.Count,
|
|
Percentage: percentage,
|
|
}
|
|
}
|
|
|
|
// 3. 按优先级统计
|
|
var priorityStats []struct {
|
|
Priority string `json:"priority"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
priorityQuery := baseTaskQuery.Select("priority, count(*) as count").Group("priority")
|
|
if err := priorityQuery.Find(&priorityStats).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务优先级统计失败"})
|
|
return
|
|
}
|
|
|
|
response.TasksByPriority = make([]TaskPriorityStat, len(priorityStats))
|
|
for i, stat := range priorityStats {
|
|
percentage := float64(0)
|
|
if response.TotalTasks > 0 {
|
|
percentage = float64(stat.Count) / float64(response.TotalTasks) * 100
|
|
}
|
|
response.TasksByPriority[i] = TaskPriorityStat{
|
|
Priority: stat.Priority,
|
|
Count: stat.Count,
|
|
Percentage: percentage,
|
|
}
|
|
}
|
|
|
|
// 4. 按类型统计
|
|
var typeStats []struct {
|
|
Type string `json:"type"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
typeQuery := baseTaskQuery.Select("type, count(*) as count").Group("type")
|
|
if err := typeQuery.Find(&typeStats).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务类型统计失败"})
|
|
return
|
|
}
|
|
|
|
response.TasksByType = make([]TaskTypeStat, len(typeStats))
|
|
for i, stat := range typeStats {
|
|
percentage := float64(0)
|
|
if response.TotalTasks > 0 {
|
|
percentage = float64(stat.Count) / float64(response.TotalTasks) * 100
|
|
}
|
|
response.TasksByType[i] = TaskTypeStat{
|
|
Type: stat.Type,
|
|
Count: stat.Count,
|
|
Percentage: percentage,
|
|
}
|
|
}
|
|
|
|
// 5. 按创建者统计
|
|
var creatorStats []struct {
|
|
CreatorID uint `json:"creator_id"`
|
|
CreatorName string `json:"creator_name"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
creatorQuery := baseTaskQuery.Select("creator_id, users.real_name as creator_name, count(*) as count").
|
|
Joins("LEFT JOIN users ON tasks.creator_id = users.id").
|
|
Group("creator_id, users.real_name").
|
|
Order("count desc").
|
|
Limit(10)
|
|
if err := creatorQuery.Find(&creatorStats).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取创建者统计失败"})
|
|
return
|
|
}
|
|
|
|
response.TasksByCreator = make([]TaskCreatorStat, len(creatorStats))
|
|
for i, stat := range creatorStats {
|
|
response.TasksByCreator[i] = TaskCreatorStat{
|
|
CreatorID: stat.CreatorID,
|
|
CreatorName: stat.CreatorName,
|
|
Count: stat.Count,
|
|
}
|
|
}
|
|
|
|
// 6. 按分配者统计
|
|
var assigneeStats []struct {
|
|
AssigneeID uint `json:"assignee_id"`
|
|
AssigneeName string `json:"assignee_name"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
assigneeQuery := baseTaskQuery.Select("assignee_id, users.real_name as assignee_name, count(*) as count").
|
|
Joins("LEFT JOIN users ON tasks.assignee_id = users.id").
|
|
Where("assignee_id IS NOT NULL").
|
|
Group("assignee_id, users.real_name").
|
|
Order("count desc").
|
|
Limit(10)
|
|
if err := assigneeQuery.Find(&assigneeStats).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取分配者统计失败"})
|
|
return
|
|
}
|
|
|
|
response.TasksByAssignee = make([]TaskAssigneeStat, len(assigneeStats))
|
|
for i, stat := range assigneeStats {
|
|
response.TasksByAssignee[i] = TaskAssigneeStat{
|
|
AssigneeID: stat.AssigneeID,
|
|
AssigneeName: stat.AssigneeName,
|
|
Count: stat.Count,
|
|
}
|
|
}
|
|
|
|
// 7. 时间趋势统计
|
|
now := time.Now()
|
|
startDate := now.AddDate(0, 0, -days)
|
|
|
|
response.DailyTrend = make([]DailyTaskStat, 0)
|
|
|
|
for i := 0; i < days; i++ {
|
|
date := startDate.AddDate(0, 0, i)
|
|
dateStr := date.Format("2006-01-02")
|
|
|
|
// 当天创建的任务数
|
|
var createdCount int64
|
|
createdQuery := baseTaskQuery.Where("DATE(created_at) = ?", dateStr)
|
|
if err := createdQuery.Count(&createdCount).Error; err != nil {
|
|
createdCount = 0
|
|
}
|
|
|
|
// 当天完成的任务数
|
|
var completedCount int64
|
|
completedQuery := baseTaskQuery.Where("status = ? AND DATE(updated_at) = ?", "completed", dateStr)
|
|
if err := completedQuery.Count(&completedCount).Error; err != nil {
|
|
completedCount = 0
|
|
}
|
|
|
|
response.DailyTrend = append(response.DailyTrend, DailyTaskStat{
|
|
Date: dateStr,
|
|
Created: createdCount,
|
|
Completed: completedCount,
|
|
})
|
|
}
|
|
|
|
// 8. 完成率
|
|
if response.TotalTasks > 0 {
|
|
response.CompletionRate = float64(response.CompletedTasks) / float64(response.TotalTasks) * 100
|
|
}
|
|
|
|
// 9. 平均完成时间
|
|
var avgCompletionTime struct {
|
|
AverageHours float64 `json:"average_hours"`
|
|
}
|
|
|
|
avgQuery := baseTaskQuery.Select("AVG(TIMESTAMPDIFF(HOUR, created_at, updated_at)) as average_hours").
|
|
Where("status = ?", "completed")
|
|
if err := avgQuery.Scan(&avgCompletionTime).Error; err == nil {
|
|
response.AverageCompletionTime = avgCompletionTime.AverageHours
|
|
}
|
|
|
|
// 10. 逾期任务统计
|
|
now = time.Now()
|
|
overdueQuery := baseTaskQuery.Where("end_time < ? AND status != ?", now, "completed")
|
|
if err := overdueQuery.Count(&response.OverdueTasks).Error; err != nil {
|
|
response.OverdueTasks = 0
|
|
}
|
|
|
|
// 11. 即将到期任务统计
|
|
sevenDaysLater := now.AddDate(0, 0, 7)
|
|
upcomingQuery := baseTaskQuery.Where("end_time BETWEEN ? AND ? AND status != ?", now, sevenDaysLater, "completed")
|
|
if err := upcomingQuery.Count(&response.UpcomingTasks).Error; err != nil {
|
|
response.UpcomingTasks = 0
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|