20 changed files with 2425 additions and 294 deletions
@ -1,7 +1,196 @@ |
|||
package statistics |
|||
|
|||
import "github.com/gin-gonic/gin" |
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// OverviewResponse 总览统计响应
|
|||
type OverviewResponse struct { |
|||
// 基本统计
|
|||
TotalTasks int64 `json:"total_tasks"` |
|||
TotalUsers int64 `json:"total_users"` |
|||
TotalOrganizations int64 `json:"total_organizations"` |
|||
|
|||
// 任务状态统计
|
|||
TasksByStatus map[string]int64 `json:"tasks_by_status"` |
|||
|
|||
// 任务优先级统计
|
|||
TasksByPriority map[string]int64 `json:"tasks_by_priority"` |
|||
|
|||
// 最近7天任务创建数
|
|||
RecentTasksCount int64 `json:"recent_tasks_count"` |
|||
|
|||
// 最近7天任务完成数
|
|||
RecentCompletedTasks int64 `json:"recent_completed_tasks"` |
|||
|
|||
// 今日任务统计
|
|||
TodayTasks struct { |
|||
Created int64 `json:"created"` |
|||
Completed int64 `json:"completed"` |
|||
} `json:"today_tasks"` |
|||
|
|||
// 本周任务统计
|
|||
WeeklyTasks struct { |
|||
Created int64 `json:"created"` |
|||
Completed int64 `json:"completed"` |
|||
} `json:"weekly_tasks"` |
|||
|
|||
// 本月任务统计
|
|||
MonthlyTasks struct { |
|||
Created int64 `json:"created"` |
|||
Completed int64 `json:"completed"` |
|||
} `json:"monthly_tasks"` |
|||
} |
|||
|
|||
func (h *StatisticsHandler) GetOverview(c *gin.Context) { |
|||
// TODO: 实现统计概览接口
|
|||
// 获取组织ID参数(可选)
|
|||
organizationIDStr := c.Query("organization_id") |
|||
var organizationID uint |
|||
if organizationIDStr != "" { |
|||
if id, err := strconv.ParseUint(organizationIDStr, 10, 32); err == nil { |
|||
organizationID = uint(id) |
|||
} |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
var response OverviewResponse |
|||
|
|||
// 基础查询构建器
|
|||
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 |
|||
} |
|||
|
|||
// 总用户数
|
|||
userQuery := db.Model(&model.User{}) |
|||
if err := userQuery.Count(&response.TotalUsers).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户统计失败"}) |
|||
return |
|||
} |
|||
|
|||
// 总组织数
|
|||
if err := db.Model(&model.Organization{}).Count(&response.TotalOrganizations).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(map[string]int64) |
|||
for _, stat := range statusStats { |
|||
response.TasksByStatus[stat.Status] = stat.Count |
|||
} |
|||
|
|||
// 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(map[string]int64) |
|||
for _, stat := range priorityStats { |
|||
response.TasksByPriority[stat.Priority] = stat.Count |
|||
} |
|||
|
|||
// 4. 时间范围统计
|
|||
now := time.Now() |
|||
|
|||
// 今日开始时间
|
|||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) |
|||
|
|||
// 本周开始时间(周一)
|
|||
weekStart := todayStart.AddDate(0, 0, -int(now.Weekday())+1) |
|||
if now.Weekday() == time.Sunday { |
|||
weekStart = weekStart.AddDate(0, 0, -7) |
|||
} |
|||
|
|||
// 本月开始时间
|
|||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) |
|||
|
|||
// 最近7天开始时间
|
|||
sevenDaysAgo := now.AddDate(0, 0, -7) |
|||
|
|||
// 今日任务统计
|
|||
todayTaskQuery := baseTaskQuery.Where("created_at >= ?", todayStart) |
|||
if err := todayTaskQuery.Count(&response.TodayTasks.Created).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取今日任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
todayCompletedQuery := baseTaskQuery.Where("status = ? AND updated_at >= ?", "completed", todayStart) |
|||
if err := todayCompletedQuery.Count(&response.TodayTasks.Completed).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取今日完成任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
// 本周任务统计
|
|||
weekTaskQuery := baseTaskQuery.Where("created_at >= ?", weekStart) |
|||
if err := weekTaskQuery.Count(&response.WeeklyTasks.Created).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取本周任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
weekCompletedQuery := baseTaskQuery.Where("status = ? AND updated_at >= ?", "completed", weekStart) |
|||
if err := weekCompletedQuery.Count(&response.WeeklyTasks.Completed).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取本周完成任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
// 本月任务统计
|
|||
monthTaskQuery := baseTaskQuery.Where("created_at >= ?", monthStart) |
|||
if err := monthTaskQuery.Count(&response.MonthlyTasks.Created).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取本月任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
monthCompletedQuery := baseTaskQuery.Where("status = ? AND updated_at >= ?", "completed", monthStart) |
|||
if err := monthCompletedQuery.Count(&response.MonthlyTasks.Completed).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取本月完成任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
// 最近7天任务统计
|
|||
recentTaskQuery := baseTaskQuery.Where("created_at >= ?", sevenDaysAgo) |
|||
if err := recentTaskQuery.Count(&response.RecentTasksCount).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取最近任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
recentCompletedQuery := baseTaskQuery.Where("status = ? AND updated_at >= ?", "completed", sevenDaysAgo) |
|||
if err := recentCompletedQuery.Count(&response.RecentCompletedTasks).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取最近完成任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, response) |
|||
} |
|||
|
|||
@ -1,7 +1,339 @@ |
|||
package statistics |
|||
|
|||
import "github.com/gin-gonic/gin" |
|||
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) { |
|||
// TODO: 实现任务统计接口
|
|||
// 获取组织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) |
|||
} |
|||
|
|||
@ -1,7 +1,450 @@ |
|||
package statistics |
|||
|
|||
import "github.com/gin-gonic/gin" |
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// UserStatisticsResponse 用户统计响应
|
|||
type UserStatisticsResponse struct { |
|||
// 基本统计
|
|||
TotalUsers int64 `json:"total_users"` |
|||
ActiveUsers int64 `json:"active_users"` // 最近30天有活动的用户
|
|||
|
|||
// 用户任务统计
|
|||
UserTaskStats []UserTaskStat `json:"user_task_stats"` |
|||
|
|||
// 用户效率统计
|
|||
UserEfficiencyStats []UserEfficiencyStat `json:"user_efficiency_stats"` |
|||
|
|||
// 用户活跃度统计
|
|||
UserActivityStats []UserActivityStat `json:"user_activity_stats"` |
|||
|
|||
// 用户完成率排行
|
|||
UserCompletionRank []UserCompletionRank `json:"user_completion_rank"` |
|||
|
|||
// 用户任务分配统计
|
|||
UserAssignmentStats []UserAssignmentStat `json:"user_assignment_stats"` |
|||
|
|||
// 总体用户效率指标
|
|||
OverallEfficiency struct { |
|||
AverageCompletionRate float64 `json:"average_completion_rate"` |
|||
AverageTasksPerUser float64 `json:"average_tasks_per_user"` |
|||
AverageCompletionTime float64 `json:"average_completion_time"` |
|||
} `json:"overall_efficiency"` |
|||
} |
|||
|
|||
// UserTaskStat 用户任务统计
|
|||
type UserTaskStat struct { |
|||
UserID uint `json:"user_id"` |
|||
Username string `json:"username"` |
|||
RealName string `json:"real_name"` |
|||
CreatedTasks int64 `json:"created_tasks"` |
|||
AssignedTasks int64 `json:"assigned_tasks"` |
|||
CompletedTasks int64 `json:"completed_tasks"` |
|||
PendingTasks int64 `json:"pending_tasks"` |
|||
InProgressTasks int64 `json:"in_progress_tasks"` |
|||
} |
|||
|
|||
// UserEfficiencyStat 用户效率统计
|
|||
type UserEfficiencyStat struct { |
|||
UserID uint `json:"user_id"` |
|||
Username string `json:"username"` |
|||
RealName string `json:"real_name"` |
|||
CompletionRate float64 `json:"completion_rate"` |
|||
AverageCompletionTime float64 `json:"average_completion_time"` |
|||
TasksCompletedThisWeek int64 `json:"tasks_completed_this_week"` |
|||
TasksCompletedThisMonth int64 `json:"tasks_completed_this_month"` |
|||
} |
|||
|
|||
// UserActivityStat 用户活跃度统计
|
|||
type UserActivityStat struct { |
|||
UserID uint `json:"user_id"` |
|||
Username string `json:"username"` |
|||
RealName string `json:"real_name"` |
|||
LastActivity time.Time `json:"last_activity"` |
|||
CommentsCount int64 `json:"comments_count"` |
|||
TasksCreatedThisWeek int64 `json:"tasks_created_this_week"` |
|||
TasksCreatedThisMonth int64 `json:"tasks_created_this_month"` |
|||
} |
|||
|
|||
// UserCompletionRank 用户完成率排行
|
|||
type UserCompletionRank struct { |
|||
UserID uint `json:"user_id"` |
|||
Username string `json:"username"` |
|||
RealName string `json:"real_name"` |
|||
CompletionRate float64 `json:"completion_rate"` |
|||
CompletedTasks int64 `json:"completed_tasks"` |
|||
TotalTasks int64 `json:"total_tasks"` |
|||
Rank int `json:"rank"` |
|||
} |
|||
|
|||
// UserAssignmentStat 用户任务分配统计
|
|||
type UserAssignmentStat struct { |
|||
UserID uint `json:"user_id"` |
|||
Username string `json:"username"` |
|||
RealName string `json:"real_name"` |
|||
UrgentTasks int64 `json:"urgent_tasks"` |
|||
HighPriorityTasks int64 `json:"high_priority_tasks"` |
|||
MediumPriorityTasks int64 `json:"medium_priority_tasks"` |
|||
LowPriorityTasks int64 `json:"low_priority_tasks"` |
|||
OverdueTasks int64 `json:"overdue_tasks"` |
|||
} |
|||
|
|||
func (h *StatisticsHandler) GetUserStatistics(c *gin.Context) { |
|||
// TODO: 实现用户统计接口
|
|||
// 获取组织ID参数(可选)
|
|||
organizationIDStr := c.Query("organization_id") |
|||
var organizationID uint |
|||
if organizationIDStr != "" { |
|||
if id, err := strconv.ParseUint(organizationIDStr, 10, 32); err == nil { |
|||
organizationID = uint(id) |
|||
} |
|||
} |
|||
|
|||
// 获取用户ID参数(可选,如果提供则只查询该用户)
|
|||
userIDStr := c.Query("user_id") |
|||
var userID uint |
|||
if userIDStr != "" { |
|||
if id, err := strconv.ParseUint(userIDStr, 10, 32); err == nil { |
|||
userID = uint(id) |
|||
} |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
var response UserStatisticsResponse |
|||
|
|||
// 基础用户查询构建器
|
|||
baseUserQuery := db.Model(&model.User{}) |
|||
if organizationID > 0 { |
|||
baseUserQuery = baseUserQuery.Joins("JOIN user_organizations ON users.id = user_organizations.user_id"). |
|||
Where("user_organizations.organization_id = ?", organizationID) |
|||
} |
|||
if userID > 0 { |
|||
baseUserQuery = baseUserQuery.Where("users.id = ?", userID) |
|||
} |
|||
|
|||
// 1. 基本统计
|
|||
if err := baseUserQuery.Count(&response.TotalUsers).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户总数失败"}) |
|||
return |
|||
} |
|||
|
|||
// 活跃用户数(最近30天有任务活动)
|
|||
thirtyDaysAgo := time.Now().AddDate(0, 0, -30) |
|||
activeUserQuery := baseUserQuery.Joins("JOIN tasks ON users.id = tasks.creator_id OR users.id = tasks.assignee_id"). |
|||
Where("tasks.created_at >= ? OR tasks.updated_at >= ?", thirtyDaysAgo, thirtyDaysAgo). |
|||
Distinct("users.id") |
|||
if err := activeUserQuery.Count(&response.ActiveUsers).Error; err != nil { |
|||
response.ActiveUsers = 0 |
|||
} |
|||
|
|||
// 2. 用户任务统计
|
|||
var userTaskStats []UserTaskStat |
|||
|
|||
// 构建用户任务统计查询
|
|||
userTaskQuery := ` |
|||
SELECT |
|||
u.id as user_id, |
|||
u.username, |
|||
u.real_name, |
|||
COALESCE(created_tasks.count, 0) as created_tasks, |
|||
COALESCE(assigned_tasks.count, 0) as assigned_tasks, |
|||
COALESCE(completed_tasks.count, 0) as completed_tasks, |
|||
COALESCE(pending_tasks.count, 0) as pending_tasks, |
|||
COALESCE(in_progress_tasks.count, 0) as in_progress_tasks |
|||
FROM users u |
|||
LEFT JOIN ( |
|||
SELECT creator_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE 1=1 %s |
|||
GROUP BY creator_id |
|||
) created_tasks ON u.id = created_tasks.creator_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL %s |
|||
GROUP BY assignee_id |
|||
) assigned_tasks ON u.id = assigned_tasks.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'completed' %s |
|||
GROUP BY assignee_id |
|||
) completed_tasks ON u.id = completed_tasks.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'pending' %s |
|||
GROUP BY assignee_id |
|||
) pending_tasks ON u.id = pending_tasks.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'in_progress' %s |
|||
GROUP BY assignee_id |
|||
) in_progress_tasks ON u.id = in_progress_tasks.assignee_id |
|||
WHERE 1=1 %s |
|||
ORDER BY (COALESCE(created_tasks.count, 0) + COALESCE(assigned_tasks.count, 0)) DESC |
|||
LIMIT 20 |
|||
` |
|||
|
|||
// 构建WHERE条件
|
|||
orgCondition := "" |
|||
userCondition := "" |
|||
if organizationID > 0 { |
|||
orgCondition = "AND organization_id = " + strconv.FormatUint(uint64(organizationID), 10) |
|||
} |
|||
if userID > 0 { |
|||
userCondition = "AND u.id = " + strconv.FormatUint(uint64(userID), 10) |
|||
} |
|||
|
|||
finalQuery := fmt.Sprintf(userTaskQuery, orgCondition, orgCondition, orgCondition, orgCondition, orgCondition, userCondition) |
|||
|
|||
if err := db.Raw(finalQuery).Scan(&userTaskStats).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户任务统计失败"}) |
|||
return |
|||
} |
|||
|
|||
response.UserTaskStats = userTaskStats |
|||
|
|||
// 3. 用户效率统计
|
|||
var userEfficiencyStats []UserEfficiencyStat |
|||
|
|||
now := time.Now() |
|||
weekStart := now.AddDate(0, 0, -int(now.Weekday())+1) |
|||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) |
|||
|
|||
efficiencyQuery := ` |
|||
SELECT |
|||
u.id as user_id, |
|||
u.username, |
|||
u.real_name, |
|||
CASE |
|||
WHEN total_assigned.count > 0 THEN |
|||
(completed_assigned.count * 100.0 / total_assigned.count) |
|||
ELSE 0 |
|||
END as completion_rate, |
|||
COALESCE(avg_completion.avg_hours, 0) as average_completion_time, |
|||
COALESCE(week_completed.count, 0) as tasks_completed_this_week, |
|||
COALESCE(month_completed.count, 0) as tasks_completed_this_month |
|||
FROM users u |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL %s |
|||
GROUP BY assignee_id |
|||
) total_assigned ON u.id = total_assigned.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'completed' %s |
|||
GROUP BY assignee_id |
|||
) completed_assigned ON u.id = completed_assigned.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, AVG(TIMESTAMPDIFF(HOUR, created_at, updated_at)) as avg_hours |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'completed' %s |
|||
GROUP BY assignee_id |
|||
) avg_completion ON u.id = avg_completion.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'completed' AND updated_at >= '%s' %s |
|||
GROUP BY assignee_id |
|||
) week_completed ON u.id = week_completed.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND status = 'completed' AND updated_at >= '%s' %s |
|||
GROUP BY assignee_id |
|||
) month_completed ON u.id = month_completed.assignee_id |
|||
WHERE 1=1 %s |
|||
HAVING total_assigned.count > 0 |
|||
ORDER BY completion_rate DESC |
|||
LIMIT 20 |
|||
` |
|||
|
|||
finalEfficiencyQuery := fmt.Sprintf(efficiencyQuery, |
|||
orgCondition, orgCondition, orgCondition, |
|||
weekStart.Format("2006-01-02"), orgCondition, |
|||
monthStart.Format("2006-01-02"), orgCondition, |
|||
userCondition) |
|||
|
|||
if err := db.Raw(finalEfficiencyQuery).Scan(&userEfficiencyStats).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户效率统计失败"}) |
|||
return |
|||
} |
|||
|
|||
response.UserEfficiencyStats = userEfficiencyStats |
|||
|
|||
// 4. 用户活跃度统计
|
|||
var userActivityStats []UserActivityStat |
|||
|
|||
activityQuery := ` |
|||
SELECT |
|||
u.id as user_id, |
|||
u.username, |
|||
u.real_name, |
|||
COALESCE(MAX(GREATEST(t.created_at, t.updated_at)), u.created_at) as last_activity, |
|||
COALESCE(comments.count, 0) as comments_count, |
|||
COALESCE(week_created.count, 0) as tasks_created_this_week, |
|||
COALESCE(month_created.count, 0) as tasks_created_this_month |
|||
FROM users u |
|||
LEFT JOIN tasks t ON u.id = t.creator_id OR u.id = t.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT user_id, COUNT(*) as count |
|||
FROM task_comments |
|||
GROUP BY user_id |
|||
) comments ON u.id = comments.user_id |
|||
LEFT JOIN ( |
|||
SELECT creator_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE created_at >= '%s' %s |
|||
GROUP BY creator_id |
|||
) week_created ON u.id = week_created.creator_id |
|||
LEFT JOIN ( |
|||
SELECT creator_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE created_at >= '%s' %s |
|||
GROUP BY creator_id |
|||
) month_created ON u.id = month_created.creator_id |
|||
WHERE 1=1 %s |
|||
GROUP BY u.id, u.username, u.real_name, u.created_at, comments.count, week_created.count, month_created.count |
|||
ORDER BY last_activity DESC |
|||
LIMIT 20 |
|||
` |
|||
|
|||
finalActivityQuery := fmt.Sprintf(activityQuery, |
|||
weekStart.Format("2006-01-02"), orgCondition, |
|||
monthStart.Format("2006-01-02"), orgCondition, |
|||
userCondition) |
|||
|
|||
if err := db.Raw(finalActivityQuery).Scan(&userActivityStats).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户活跃度统计失败"}) |
|||
return |
|||
} |
|||
|
|||
response.UserActivityStats = userActivityStats |
|||
|
|||
// 5. 用户完成率排行
|
|||
var userCompletionRank []UserCompletionRank |
|||
|
|||
for i, stat := range userEfficiencyStats { |
|||
rank := UserCompletionRank{ |
|||
UserID: stat.UserID, |
|||
Username: stat.Username, |
|||
RealName: stat.RealName, |
|||
CompletionRate: stat.CompletionRate, |
|||
Rank: i + 1, |
|||
} |
|||
|
|||
// 获取用户的总任务数和完成任务数
|
|||
for _, taskStat := range userTaskStats { |
|||
if taskStat.UserID == stat.UserID { |
|||
rank.TotalTasks = taskStat.AssignedTasks |
|||
rank.CompletedTasks = taskStat.CompletedTasks |
|||
break |
|||
} |
|||
} |
|||
|
|||
userCompletionRank = append(userCompletionRank, rank) |
|||
} |
|||
|
|||
response.UserCompletionRank = userCompletionRank |
|||
|
|||
// 6. 用户任务分配统计
|
|||
var userAssignmentStats []UserAssignmentStat |
|||
|
|||
assignmentQuery := ` |
|||
SELECT |
|||
u.id as user_id, |
|||
u.username, |
|||
u.real_name, |
|||
COALESCE(urgent.count, 0) as urgent_tasks, |
|||
COALESCE(high_priority.count, 0) as high_priority_tasks, |
|||
COALESCE(medium_priority.count, 0) as medium_priority_tasks, |
|||
COALESCE(low_priority.count, 0) as low_priority_tasks, |
|||
COALESCE(overdue.count, 0) as overdue_tasks |
|||
FROM users u |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND priority = 'urgent' %s |
|||
GROUP BY assignee_id |
|||
) urgent ON u.id = urgent.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND priority = 'high' %s |
|||
GROUP BY assignee_id |
|||
) high_priority ON u.id = high_priority.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND priority = 'medium' %s |
|||
GROUP BY assignee_id |
|||
) medium_priority ON u.id = medium_priority.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND priority = 'low' %s |
|||
GROUP BY assignee_id |
|||
) low_priority ON u.id = low_priority.assignee_id |
|||
LEFT JOIN ( |
|||
SELECT assignee_id, COUNT(*) as count |
|||
FROM tasks |
|||
WHERE assignee_id IS NOT NULL AND end_time < NOW() AND status != 'completed' %s |
|||
GROUP BY assignee_id |
|||
) overdue ON u.id = overdue.assignee_id |
|||
WHERE 1=1 %s |
|||
ORDER BY (COALESCE(urgent.count, 0) + COALESCE(high_priority.count, 0)) DESC |
|||
LIMIT 20 |
|||
` |
|||
|
|||
finalAssignmentQuery := fmt.Sprintf(assignmentQuery, |
|||
orgCondition, orgCondition, orgCondition, orgCondition, orgCondition, userCondition) |
|||
|
|||
if err := db.Raw(finalAssignmentQuery).Scan(&userAssignmentStats).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户任务分配统计失败"}) |
|||
return |
|||
} |
|||
|
|||
response.UserAssignmentStats = userAssignmentStats |
|||
|
|||
// 7. 总体效率指标
|
|||
if len(userEfficiencyStats) > 0 { |
|||
var totalCompletionRate float64 |
|||
var totalTasks int64 |
|||
var totalCompletionTime float64 |
|||
var validTimeCount int64 |
|||
|
|||
for _, stat := range userEfficiencyStats { |
|||
totalCompletionRate += stat.CompletionRate |
|||
if stat.AverageCompletionTime > 0 { |
|||
totalCompletionTime += stat.AverageCompletionTime |
|||
validTimeCount++ |
|||
} |
|||
} |
|||
|
|||
for _, stat := range userTaskStats { |
|||
totalTasks += stat.AssignedTasks |
|||
} |
|||
|
|||
response.OverallEfficiency.AverageCompletionRate = totalCompletionRate / float64(len(userEfficiencyStats)) |
|||
if response.TotalUsers > 0 { |
|||
response.OverallEfficiency.AverageTasksPerUser = float64(totalTasks) / float64(response.TotalUsers) |
|||
} |
|||
if validTimeCount > 0 { |
|||
response.OverallEfficiency.AverageCompletionTime = totalCompletionTime / float64(validTimeCount) |
|||
} |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, response) |
|||
} |
|||
|
|||
@ -1,9 +1,86 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"path/filepath" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) AddTaskAttachment(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
// 获取上传的用户ID
|
|||
userIDStr := c.PostForm("user_id") |
|||
if userIDStr == "" { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户ID不能为空"}) |
|||
return |
|||
} |
|||
|
|||
userID, err := strconv.ParseUint(userIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"}) |
|||
return |
|||
} |
|||
|
|||
// 处理文件上传
|
|||
file, err := c.FormFile("file") |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 生成文件名和路径
|
|||
timestamp := time.Now().UnixNano() |
|||
fileName := file.Filename |
|||
filePath := fmt.Sprintf("uploads/%d_%s", timestamp, fileName) |
|||
|
|||
// 保存文件
|
|||
if err := c.SaveUploadedFile(file, filePath); err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"}) |
|||
return |
|||
} |
|||
|
|||
// 创建附件记录
|
|||
attachment := model.TaskAttachment{ |
|||
TaskID: uint(taskID), |
|||
FileName: fileName, |
|||
FilePath: filePath, |
|||
FileSize: file.Size, |
|||
FileType: filepath.Ext(fileName), |
|||
UploadedBy: uint(userID), |
|||
CreatedAt: time.Now(), |
|||
} |
|||
|
|||
if err := db.Create(&attachment).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建附件记录失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusCreated, attachment) |
|||
} |
|||
|
|||
@ -1,9 +1,79 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// AddTaskCommentRequest 添加任务评论请求结构
|
|||
type AddTaskCommentRequest struct { |
|||
Content string `json:"content" binding:"required"` |
|||
UserID uint `json:"user_id" binding:"required"` |
|||
} |
|||
|
|||
func (h *TaskHandler) AddTaskComment(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
var req AddTaskCommentRequest |
|||
if err := c.ShouldBindJSON(&req); err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 检查用户是否存在
|
|||
var user model.User |
|||
if err := db.First(&user, req.UserID).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败"}) |
|||
return |
|||
} |
|||
|
|||
// 创建评论
|
|||
comment := model.TaskComment{ |
|||
TaskID: uint(taskID), |
|||
UserID: req.UserID, |
|||
Content: req.Content, |
|||
CreatedAt: time.Now(), |
|||
} |
|||
|
|||
if err := db.Create(&comment).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建评论失败"}) |
|||
return |
|||
} |
|||
|
|||
// 预加载用户信息
|
|||
if err := db.Preload("User").First(&comment, comment.ID).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusCreated, comment) |
|||
} |
|||
|
|||
@ -1,9 +1,107 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// CreateTaskRequest 创建任务请求结构
|
|||
type CreateTaskRequest struct { |
|||
Title string `json:"title" binding:"required"` |
|||
Description string `json:"description"` |
|||
Type string `json:"type"` |
|||
Priority string `json:"priority"` |
|||
AssigneeID *uint `json:"assignee_id"` |
|||
StartTime *time.Time `json:"start_time"` |
|||
EndTime *time.Time `json:"end_time"` |
|||
OrganizationID uint `json:"organization_id" binding:"required"` |
|||
CreatorID uint `json:"creator_id" binding:"required"` |
|||
} |
|||
|
|||
func (h *TaskHandler) CreateTask(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
var req CreateTaskRequest |
|||
if err := c.ShouldBindJSON(&req); err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{ |
|||
"code": 400, |
|||
"message": "请求参数错误: " + err.Error(), |
|||
}) |
|||
return |
|||
} |
|||
|
|||
// 验证优先级
|
|||
if req.Priority != "" && req.Priority != "urgent" && req.Priority != "high" && req.Priority != "medium" && req.Priority != "low" { |
|||
c.JSON(http.StatusBadRequest, gin.H{ |
|||
"code": 400, |
|||
"message": "优先级值无效,必须是 urgent、high、medium 或 low", |
|||
}) |
|||
return |
|||
} |
|||
|
|||
// 如果没有指定优先级,默认为 medium
|
|||
if req.Priority == "" { |
|||
req.Priority = "medium" |
|||
} |
|||
|
|||
// 验证时间逻辑
|
|||
if req.StartTime != nil && req.EndTime != nil && req.StartTime.After(*req.EndTime) { |
|||
c.JSON(http.StatusBadRequest, gin.H{ |
|||
"code": 400, |
|||
"message": "开始时间不能晚于结束时间", |
|||
}) |
|||
return |
|||
} |
|||
|
|||
// 创建任务对象
|
|||
task := model.Task{ |
|||
Title: req.Title, |
|||
Description: req.Description, |
|||
Type: req.Type, |
|||
Priority: req.Priority, |
|||
Status: "pending", // 默认状态
|
|||
CreatorID: req.CreatorID, |
|||
OrganizationID: req.OrganizationID, |
|||
StartTime: req.StartTime, |
|||
EndTime: req.EndTime, |
|||
} |
|||
|
|||
// 如果指定了执行者
|
|||
if req.AssigneeID != nil { |
|||
task.AssigneeID = *req.AssigneeID |
|||
} |
|||
|
|||
// 保存到数据库
|
|||
db := database.GetDB() |
|||
if err := db.Create(&task).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{ |
|||
"code": 500, |
|||
"message": "创建任务失败: " + err.Error(), |
|||
}) |
|||
return |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
c.JSON(http.StatusCreated, gin.H{ |
|||
"code": 201, |
|||
"message": "任务创建成功", |
|||
"data": gin.H{ |
|||
"id": task.ID, |
|||
"title": task.Title, |
|||
"description": task.Description, |
|||
"type": task.Type, |
|||
"priority": task.Priority, |
|||
"status": task.Status, |
|||
"creator_id": task.CreatorID, |
|||
"assignee_id": task.AssigneeID, |
|||
"organization_id": task.OrganizationID, |
|||
"start_time": task.StartTime, |
|||
"end_time": task.EndTime, |
|||
"created_at": task.CreatedAt, |
|||
"updated_at": task.UpdatedAt, |
|||
}, |
|||
}) |
|||
} |
|||
|
|||
@ -1,9 +1,66 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) DeleteTask(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 开始事务
|
|||
tx := db.Begin() |
|||
|
|||
// 删除相关的评论
|
|||
if err := tx.Where("task_id = ?", taskID).Delete(&model.TaskComment{}).Error; err != nil { |
|||
tx.Rollback() |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除任务评论失败"}) |
|||
return |
|||
} |
|||
|
|||
// 删除相关的附件
|
|||
if err := tx.Where("task_id = ?", taskID).Delete(&model.TaskAttachment{}).Error; err != nil { |
|||
tx.Rollback() |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除任务附件失败"}) |
|||
return |
|||
} |
|||
|
|||
// 删除任务
|
|||
if err := tx.Delete(&existingTask).Error; err != nil { |
|||
tx.Rollback() |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 提交事务
|
|||
if err := tx.Commit().Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "提交事务失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, gin.H{"message": "任务删除成功"}) |
|||
} |
|||
|
|||
@ -1,9 +1,69 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"os" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) DeleteTaskAttachment(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
// 获取附件ID
|
|||
attachmentIDStr := c.Param("attachment_id") |
|||
attachmentID, err := strconv.ParseUint(attachmentIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的附件ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 检查附件是否存在且属于该任务
|
|||
var existingAttachment model.TaskAttachment |
|||
if err := db.Where("id = ? AND task_id = ?", uint(attachmentID), uint(taskID)). |
|||
First(&existingAttachment).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "附件不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询附件失败"}) |
|||
return |
|||
} |
|||
|
|||
// 删除物理文件
|
|||
if err := os.Remove(existingAttachment.FilePath); err != nil { |
|||
// 即使文件删除失败,也继续删除数据库记录
|
|||
// 记录日志但不返回错误
|
|||
} |
|||
|
|||
// 删除数据库记录
|
|||
if err := db.Delete(&existingAttachment).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除附件失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, gin.H{"message": "附件删除成功"}) |
|||
} |
|||
|
|||
@ -1,9 +1,62 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) DeleteTaskComment(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
// 获取评论ID
|
|||
commentIDStr := c.Param("comment_id") |
|||
commentID, err := strconv.ParseUint(commentIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的评论ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 检查评论是否存在且属于该任务
|
|||
var existingComment model.TaskComment |
|||
if err := db.Where("id = ? AND task_id = ?", uint(commentID), uint(taskID)). |
|||
First(&existingComment).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询评论失败"}) |
|||
return |
|||
} |
|||
|
|||
// 删除评论
|
|||
if err := db.Delete(&existingComment).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除评论失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, gin.H{"message": "评论删除成功"}) |
|||
} |
|||
|
|||
@ -1,9 +1,44 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) GetTask(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
var task model.Task |
|||
// 预加载相关数据:创建者、分配者、组织、评论、附件
|
|||
err = db.Preload("Creator"). |
|||
Preload("Assignee"). |
|||
Preload("Organization"). |
|||
Preload("Comments"). |
|||
Preload("Comments.User"). |
|||
Preload("Attachments"). |
|||
First(&task, uint(taskID)).Error |
|||
|
|||
if err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, task) |
|||
} |
|||
|
|||
@ -1,9 +1,45 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) GetTaskAttachments(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 获取附件列表
|
|||
var attachments []model.TaskAttachment |
|||
if err := db.Where("task_id = ?", uint(taskID)). |
|||
Order("created_at DESC"). |
|||
Find(&attachments).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取附件列表失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, attachments) |
|||
} |
|||
|
|||
@ -1,9 +1,46 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func (h *TaskHandler) GetTaskComments(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 获取评论列表
|
|||
var comments []model.TaskComment |
|||
if err := db.Where("task_id = ?", uint(taskID)). |
|||
Preload("User"). |
|||
Order("created_at DESC"). |
|||
Find(&comments).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论列表失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, comments) |
|||
} |
|||
|
|||
@ -1,9 +1,128 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// GetTasksQuery 获取任务列表查询参数
|
|||
type GetTasksQuery struct { |
|||
Page int `form:"page" binding:"min=1"` |
|||
PageSize int `form:"page_size" binding:"min=1,max=100"` |
|||
Status string `form:"status"` |
|||
Priority string `form:"priority"` |
|||
Type string `form:"type"` |
|||
AssigneeID uint `form:"assignee_id"` |
|||
CreatorID uint `form:"creator_id"` |
|||
OrganizationID uint `form:"organization_id"` |
|||
Search string `form:"search"` |
|||
SortBy string `form:"sort_by"` |
|||
SortOrder string `form:"sort_order"` |
|||
} |
|||
|
|||
// GetTasksResponse 获取任务列表响应
|
|||
type GetTasksResponse struct { |
|||
Tasks []model.Task `json:"tasks"` |
|||
Total int64 `json:"total"` |
|||
Page int `json:"page"` |
|||
PageSize int `json:"page_size"` |
|||
TotalPages int `json:"total_pages"` |
|||
} |
|||
|
|||
func (h *TaskHandler) GetTasks(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
var query GetTasksQuery |
|||
|
|||
// 设置默认值
|
|||
query.Page = 1 |
|||
query.PageSize = 10 |
|||
query.SortBy = "created_at" |
|||
query.SortOrder = "desc" |
|||
|
|||
if err := c.ShouldBindQuery(&query); err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 构建查询
|
|||
taskQuery := db.Model(&model.Task{}) |
|||
|
|||
// 预加载相关数据
|
|||
taskQuery = taskQuery.Preload("Creator").Preload("Assignee").Preload("Organization") |
|||
|
|||
// 应用过滤条件
|
|||
if query.Status != "" { |
|||
taskQuery = taskQuery.Where("status = ?", query.Status) |
|||
} |
|||
|
|||
if query.Priority != "" { |
|||
taskQuery = taskQuery.Where("priority = ?", query.Priority) |
|||
} |
|||
|
|||
if query.Type != "" { |
|||
taskQuery = taskQuery.Where("type = ?", query.Type) |
|||
} |
|||
|
|||
if query.AssigneeID > 0 { |
|||
taskQuery = taskQuery.Where("assignee_id = ?", query.AssigneeID) |
|||
} |
|||
|
|||
if query.CreatorID > 0 { |
|||
taskQuery = taskQuery.Where("creator_id = ?", query.CreatorID) |
|||
} |
|||
|
|||
if query.OrganizationID > 0 { |
|||
taskQuery = taskQuery.Where("organization_id = ?", query.OrganizationID) |
|||
} |
|||
|
|||
// 搜索功能
|
|||
if query.Search != "" { |
|||
searchTerm := "%" + query.Search + "%" |
|||
taskQuery = taskQuery.Where("title LIKE ? OR description LIKE ?", searchTerm, searchTerm) |
|||
} |
|||
|
|||
// 计算总数
|
|||
var total int64 |
|||
if err := taskQuery.Count(&total).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "计算任务总数失败"}) |
|||
return |
|||
} |
|||
|
|||
// 计算总页数
|
|||
totalPages := int((total + int64(query.PageSize) - 1) / int64(query.PageSize)) |
|||
|
|||
// 应用排序
|
|||
orderBy := query.SortBy |
|||
if query.SortOrder == "asc" { |
|||
orderBy += " ASC" |
|||
} else { |
|||
orderBy += " DESC" |
|||
} |
|||
taskQuery = taskQuery.Order(orderBy) |
|||
|
|||
// 应用分页
|
|||
offset := (query.Page - 1) * query.PageSize |
|||
taskQuery = taskQuery.Offset(offset).Limit(query.PageSize) |
|||
|
|||
// 执行查询
|
|||
var tasks []model.Task |
|||
if err := taskQuery.Find(&tasks).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取任务列表失败"}) |
|||
return |
|||
} |
|||
|
|||
response := GetTasksResponse{ |
|||
Tasks: tasks, |
|||
Total: total, |
|||
Page: query.Page, |
|||
PageSize: query.PageSize, |
|||
TotalPages: totalPages, |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, response) |
|||
} |
|||
|
|||
@ -1,9 +1,102 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// UpdateTaskRequest 更新任务请求结构
|
|||
type UpdateTaskRequest struct { |
|||
Title *string `json:"title"` |
|||
Description *string `json:"description"` |
|||
Type *string `json:"type"` |
|||
Priority *string `json:"priority"` |
|||
Status *string `json:"status"` |
|||
AssigneeID *uint `json:"assignee_id"` |
|||
StartTime *time.Time `json:"start_time"` |
|||
EndTime *time.Time `json:"end_time"` |
|||
} |
|||
|
|||
func (h *TaskHandler) UpdateTask(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
var req UpdateTaskRequest |
|||
if err := c.ShouldBindJSON(&req); err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 构建更新数据
|
|||
updateData := make(map[string]interface{}) |
|||
|
|||
if req.Title != nil { |
|||
updateData["title"] = *req.Title |
|||
} |
|||
if req.Description != nil { |
|||
updateData["description"] = *req.Description |
|||
} |
|||
if req.Type != nil { |
|||
updateData["type"] = *req.Type |
|||
} |
|||
if req.Priority != nil { |
|||
updateData["priority"] = *req.Priority |
|||
} |
|||
if req.Status != nil { |
|||
updateData["status"] = *req.Status |
|||
} |
|||
if req.AssigneeID != nil { |
|||
updateData["assignee_id"] = *req.AssigneeID |
|||
} |
|||
if req.StartTime != nil { |
|||
updateData["start_time"] = *req.StartTime |
|||
} |
|||
if req.EndTime != nil { |
|||
updateData["end_time"] = *req.EndTime |
|||
} |
|||
|
|||
// 设置更新时间
|
|||
updateData["updated_at"] = time.Now() |
|||
|
|||
// 执行更新
|
|||
if err := db.Model(&existingTask).Updates(updateData).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 重新查询更新后的任务,包含关联数据
|
|||
var updatedTask model.Task |
|||
if err := db.Preload("Creator"). |
|||
Preload("Assignee"). |
|||
Preload("Organization"). |
|||
First(&updatedTask, uint(taskID)).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取更新后的任务失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, updatedTask) |
|||
} |
|||
|
|||
@ -1,9 +1,84 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"task-track-backend/model" |
|||
"task-track-backend/pkg/database" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// UpdateTaskStatusRequest 更新任务状态请求结构
|
|||
type UpdateTaskStatusRequest struct { |
|||
Status string `json:"status" binding:"required"` |
|||
} |
|||
|
|||
func (h *TaskHandler) UpdateTaskStatus(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 获取任务ID
|
|||
taskIDStr := c.Param("id") |
|||
taskID, err := strconv.ParseUint(taskIDStr, 10, 32) |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务ID"}) |
|||
return |
|||
} |
|||
|
|||
var req UpdateTaskStatusRequest |
|||
if err := c.ShouldBindJSON(&req); err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
|||
return |
|||
} |
|||
|
|||
// 验证状态值
|
|||
validStatuses := []string{"pending", "in_progress", "completed", "cancelled"} |
|||
isValidStatus := false |
|||
for _, status := range validStatuses { |
|||
if req.Status == status { |
|||
isValidStatus = true |
|||
break |
|||
} |
|||
} |
|||
|
|||
if !isValidStatus { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的任务状态"}) |
|||
return |
|||
} |
|||
|
|||
db := database.GetDB() |
|||
|
|||
// 检查任务是否存在
|
|||
var existingTask model.Task |
|||
if err := db.First(&existingTask, uint(taskID)).Error; err != nil { |
|||
if err.Error() == "record not found" { |
|||
c.JSON(http.StatusNotFound, gin.H{"error": "任务不存在"}) |
|||
return |
|||
} |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询任务失败"}) |
|||
return |
|||
} |
|||
|
|||
// 更新任务状态
|
|||
updateData := map[string]interface{}{ |
|||
"status": req.Status, |
|||
"updated_at": time.Now(), |
|||
} |
|||
|
|||
if err := db.Model(&existingTask).Updates(updateData).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新任务状态失败"}) |
|||
return |
|||
} |
|||
|
|||
// 重新查询更新后的任务
|
|||
var updatedTask model.Task |
|||
if err := db.Preload("Creator"). |
|||
Preload("Assignee"). |
|||
Preload("Organization"). |
|||
First(&updatedTask, uint(taskID)).Error; err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取更新后的任务失败"}) |
|||
return |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, updatedTask) |
|||
} |
|||
|
|||
@ -1,9 +1,71 @@ |
|||
package task |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"path/filepath" |
|||
"time" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// UploadFileResponse 上传文件响应结构
|
|||
type UploadFileResponse struct { |
|||
FileName string `json:"file_name"` |
|||
FilePath string `json:"file_path"` |
|||
FileSize int64 `json:"file_size"` |
|||
FileType string `json:"file_type"` |
|||
} |
|||
|
|||
func (h *TaskHandler) UploadFile(c *gin.Context) { |
|||
// ... 复制原有实现 ...
|
|||
// 处理文件上传
|
|||
file, err := c.FormFile("file") |
|||
if err != nil { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"}) |
|||
return |
|||
} |
|||
|
|||
// 验证文件大小 (限制为 10MB)
|
|||
maxSize := int64(10 * 1024 * 1024) |
|||
if file.Size > maxSize { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "文件大小超过限制"}) |
|||
return |
|||
} |
|||
|
|||
// 验证文件类型
|
|||
allowedTypes := []string{".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt"} |
|||
fileExt := filepath.Ext(file.Filename) |
|||
isAllowed := false |
|||
for _, allowedType := range allowedTypes { |
|||
if fileExt == allowedType { |
|||
isAllowed = true |
|||
break |
|||
} |
|||
} |
|||
|
|||
if !isAllowed { |
|||
c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的文件类型"}) |
|||
return |
|||
} |
|||
|
|||
// 生成文件名和路径
|
|||
timestamp := time.Now().UnixNano() |
|||
fileName := file.Filename |
|||
filePath := fmt.Sprintf("uploads/%d_%s", timestamp, fileName) |
|||
|
|||
// 保存文件
|
|||
if err := c.SaveUploadedFile(file, filePath); err != nil { |
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件失败"}) |
|||
return |
|||
} |
|||
|
|||
// 返回文件信息
|
|||
response := UploadFileResponse{ |
|||
FileName: fileName, |
|||
FilePath: filePath, |
|||
FileSize: file.Size, |
|||
FileType: fileExt, |
|||
} |
|||
|
|||
c.JSON(http.StatusOK, response) |
|||
} |
|||
|
|||
Loading…
Reference in new issue