package opsdev import ( "context" "fmt" "strings" "dashoo.cn/opms_libary/myerrors" opsdevdao "dashoo.cn/opms_parent/app/dao/opsdev" opsdevmodel "dashoo.cn/opms_parent/app/model/opsdev" "dashoo.cn/opms_parent/app/service" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" ) // OpsEventTaskService 任务业务逻辑实现类 type OpsEventTaskService struct { *service.ContextService TaskDao *opsdevdao.OpsEventTaskDao RecordDao *opsdevdao.OpsEventTaskRecordDao AttachmentDao *opsdevdao.OpsEventTaskAttachmentDao ProjectDao *opsdevdao.OpsDeliveryProjectDao ReleaseDao *opsdevdao.OpsEventTaskReleaseDao WorkHourDao *opsdevdao.OpsEventTaskWorkHourDao } // NewOpsEventTaskService 初始化service func NewOpsEventTaskService(ctx context.Context) (svc *OpsEventTaskService, err error) { svc = new(OpsEventTaskService) if svc.ContextService, err = svc.Init(ctx); err != nil { return nil, err } svc.TaskDao = opsdevdao.NewOpsEventTaskDao(svc.Tenant) svc.RecordDao = opsdevdao.NewOpsEventTaskRecordDao(svc.Tenant) svc.AttachmentDao = opsdevdao.NewOpsEventTaskAttachmentDao(svc.Tenant) svc.ProjectDao = opsdevdao.NewOpsDeliveryProjectDao(svc.Tenant) svc.ReleaseDao = opsdevdao.NewOpsEventTaskReleaseDao(svc.Tenant) svc.WorkHourDao = opsdevdao.NewOpsEventTaskWorkHourDao(svc.Tenant) return svc, nil } // GetList 分页查询任务列表 func (s *OpsEventTaskService) GetList(req *opsdevmodel.OpsEventTaskSearchReq) (total int, list []*opsdevmodel.OpsEventTaskRsp, err error) { db := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime) // 项目ID筛选 if req.ProjectId > 0 { db = db.Where(s.TaskDao.Columns.ProjectId, req.ProjectId) } // 任务标题模糊查询 if req.TaskTitle != "" { db = db.Where(s.TaskDao.Columns.TaskTitle+" like ?", "%"+req.TaskTitle+"%") } // 任务类型筛选(多选) if len(req.TaskType) > 0 { db = db.Where(s.TaskDao.Columns.TaskType+" in (?)", req.TaskType) } // 任务状态筛选(多选) if len(req.TaskStatus) > 0 { db = db.Where(s.TaskDao.Columns.TaskStatus+" in (?)", req.TaskStatus) } // 优先级筛选(多选) if len(req.Priority) > 0 { db = db.Where(s.TaskDao.Columns.Priority+" in (?)", req.Priority) } // 执行人筛选(多选) if len(req.OpsUserName) > 0 { db = db.Where(s.TaskDao.Columns.OpsUserName+" in (?)", req.OpsUserName) } // 计划结束日期范围筛选 if req.PlanEndDateStart != "" { db = db.Where(s.TaskDao.Columns.PlanEndTime+" >= ?", req.PlanEndDateStart+" 00:00:00") } if req.PlanEndDateEnd != "" { db = db.Where(s.TaskDao.Columns.PlanEndTime+" <= ?", req.PlanEndDateEnd+" 23:59:59") } // 创建日期范围筛选 if req.CreatedTimeStart != "" { db = db.Where(s.TaskDao.Columns.CreatedTime+" >= ?", req.CreatedTimeStart+" 00:00:00") } if req.CreatedTimeEnd != "" { db = db.Where(s.TaskDao.Columns.CreatedTime+" <= ?", req.CreatedTimeEnd+" 23:59:59") } // 完成日期范围筛选 if req.CompleteTimeStart != "" { db = db.Where(s.TaskDao.Columns.CompleteTime+" >= ?", req.CompleteTimeStart+" 00:00:00") } if req.CompleteTimeEnd != "" { db = db.Where(s.TaskDao.Columns.CompleteTime+" <= ?", req.CompleteTimeEnd+" 23:59:59") } // 统计总数 total, err = db.Count() if err != nil { g.Log().Error(err) return 0, nil, myerrors.DbError("获取任务总数失败") } // 分页查询 pageNum, pageSize := req.GetPage() var entityList []*opsdevmodel.OpsEventTask // 处理排序 if len(req.SortFields) > 0 { orderClauses := []string{} for _, sort := range req.SortFields { // 将前端字段名转换为数据库列名 colName := s.getSortColumnName(sort.Field) if colName != "" { orderClauses = append(orderClauses, colName+" "+strings.ToUpper(sort.Order)) } } if len(orderClauses) > 0 { db = db.Order(strings.Join(orderClauses, ", ")) } else { db = db.Order(s.TaskDao.Columns.CreatedTime + " desc") } } else { db = db.Order(s.TaskDao.Columns.CreatedTime + " desc") } err = db.Page(pageNum, pageSize).Scan(&entityList) if err != nil { g.Log().Error(err) return 0, nil, myerrors.DbError("查询任务列表失败") } // 转换为响应结构体 if err = gconv.Structs(entityList, &list); err != nil { g.Log().Error(err) return 0, nil, myerrors.DbError("数据转换失败") } return } // getSortColumnName 将前端排序字段名转换为数据库列名 func (s *OpsEventTaskService) getSortColumnName(field string) string { // 前端字段名到数据库列名的映射 fieldMap := map[string]string{ "taskTitle": s.TaskDao.Columns.TaskTitle, "functionName": s.TaskDao.Columns.FunctionName, "taskType": s.TaskDao.Columns.TaskType, "taskStatus": s.TaskDao.Columns.TaskStatus, "priority": s.TaskDao.Columns.Priority, "opsUserName": s.TaskDao.Columns.OpsUserName, "planStartTime": s.TaskDao.Columns.PlanStartTime, "planEndTime": s.TaskDao.Columns.PlanEndTime, "completeTime": s.TaskDao.Columns.CompleteTime, "releaseVersion": s.TaskDao.Columns.ReleaseVersion, "createdTime": s.TaskDao.Columns.CreatedTime, } if colName, ok := fieldMap[field]; ok { return colName } return "" } // Create 新增任务,包含事务控制和过程记录 func (s *OpsEventTaskService) Create(req *opsdevmodel.OpsEventTaskAddReq) error { // 判断任务状态:如果执行人、计划开始时间、计划结束时间都填写了,则状态为处理中,否则为待处理 taskStatus := opsdevmodel.TaskStatusTodo if req.OpsUserId > 0 && req.PlanStartTime != "" && req.PlanEndTime != "" { taskStatus = opsdevmodel.TaskStatusProcessing } // 生成任务编号 taskNo := s.generateTaskNo() // 构造数据 data := g.Map{ s.TaskDao.Columns.TaskNo: taskNo, s.TaskDao.Columns.ProjectId: req.ProjectId, s.TaskDao.Columns.ProjectName: s.getProjectName(req.ProjectId), s.TaskDao.Columns.TaskTitle: req.TaskTitle, s.TaskDao.Columns.TaskDesc: req.TaskDesc, s.TaskDao.Columns.FunctionName: req.FunctionName, s.TaskDao.Columns.TaskType: req.TaskType, s.TaskDao.Columns.TaskStatus: taskStatus, s.TaskDao.Columns.Priority: req.Priority, s.TaskDao.Columns.OpsUserId: req.OpsUserId, s.TaskDao.Columns.OpsUserName: req.OpsUserName, s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour, s.TaskDao.Columns.DefectType: req.DefectType, s.TaskDao.Columns.ReleaseVersion: req.ReleaseVersion, s.TaskDao.Columns.Remark: req.Remark, s.TaskDao.Columns.EventId: req.EventId, s.TaskDao.Columns.EventType: req.EventType, s.TaskDao.Columns.TaskParentId: req.TaskParentId, } if req.PlanStartTime != "" { data[s.TaskDao.Columns.PlanStartTime] = req.PlanStartTime } if req.PlanEndTime != "" { data[s.TaskDao.Columns.PlanEndTime] = req.PlanEndTime } // 补齐审计字段 service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务控制 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 创建任务记录 result, err := s.TaskDao.TX(tx).Data(data).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增任务失败") } // 获取新创建的任务ID taskId, err := result.LastInsertId() if err != nil { g.Log().Error(err) return myerrors.DbError("获取任务ID失败") } // 2. 创建任务过程记录 recordData := g.Map{ s.RecordDao.Columns.TaskId: taskId, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "创建任务
任务标题: " + req.TaskTitle, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增任务过程记录失败") } // 获取过程记录ID recordId, err := result.LastInsertId() if err != nil { g.Log().Error(err) return myerrors.DbError("获取过程记录ID失败") } // 3. 保存附件到任务附件表,关联过程记录 if len(req.Attachments) > 0 { for _, att := range req.Attachments { attData := g.Map{ s.AttachmentDao.Columns.TaskId: taskId, s.AttachmentDao.Columns.TaskRecordId: recordId, s.AttachmentDao.Columns.FileName: att.FileName, s.AttachmentDao.Columns.FileUrl: att.FileUrl, s.AttachmentDao.Columns.FileType: att.FileType, } service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.AttachmentDao.TX(tx).Data(attData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("保存附件信息失败") } } } return nil }) } // UpdateById 根据ID更新任务 func (s *OpsEventTaskService) UpdateById(req *opsdevmodel.OpsEventTaskUpdateReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 已完成(30)或已作废(90)状态的任务不允许编辑 if !s.canEdit(entity.TaskStatus) { return myerrors.TipsError("已完成或已作废状态的任务不允许编辑") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskTitle: req.TaskTitle, s.TaskDao.Columns.TaskDesc: req.TaskDesc, s.TaskDao.Columns.FunctionName: req.FunctionName, s.TaskDao.Columns.TaskType: req.TaskType, s.TaskDao.Columns.Priority: req.Priority, s.TaskDao.Columns.OpsUserId: req.OpsUserId, s.TaskDao.Columns.OpsUserName: req.OpsUserName, s.TaskDao.Columns.PlanStartTime: req.PlanStartTime, s.TaskDao.Columns.PlanEndTime: req.PlanEndTime, s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour, s.TaskDao.Columns.DefectType: req.DefectType, s.TaskDao.Columns.ReleaseVersion: req.ReleaseVersion, s.TaskDao.Columns.Remark: req.Remark, } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 更新操作,排除不可改字段 _, err = s.TaskDao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("更新任务失败") } return nil } // DeleteByIds 根据ID批量删除 func (s *OpsEventTaskService) DeleteByIds(ids []int64) error { if len(ids) == 0 { return myerrors.TipsError("请选择需要删除的任务") } return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 删除任务 _, err := s.TaskDao.TX(tx).WhereIn(s.TaskDao.Columns.Id, ids).Delete() if err != nil { g.Log().Error(err) return myerrors.DbError("删除任务失败") } // 2. 删除关联的过程记录 _, err = s.RecordDao.TX(tx).WhereIn(s.RecordDao.Columns.TaskId, ids).Delete() if err != nil { g.Log().Error(err) return myerrors.DbError("删除任务过程记录失败") } // 3. 删除关联的附件 _, err = s.AttachmentDao.TX(tx).WhereIn(s.AttachmentDao.Columns.TaskId, ids).Delete() if err != nil { g.Log().Error(err) return myerrors.DbError("删除任务附件失败") } return nil }) } // GetById 根据ID查询单条(关联项目信息) func (s *OpsEventTaskService) GetById(id int) (*opsdevmodel.OpsEventTaskRsp, error) { var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, id).Scan(&entity) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return nil, myerrors.TipsError("任务数据不存在") } var rsp opsdevmodel.OpsEventTaskRsp if err := gconv.Struct(entity, &rsp); err != nil { g.Log().Error(err) return nil, myerrors.DbError("数据转换失败") } return &rsp, nil } // Schedule 任务排期 func (s *OpsEventTaskService) Schedule(req *opsdevmodel.OpsEventTaskScheduleReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 只有待处理状态可以排期 if !s.canSchedule(entity.TaskStatus) { return myerrors.TipsError("只有待处理状态的任务可以排期") } data := g.Map{ s.TaskDao.Columns.OpsUserId: req.OpsUserId, s.TaskDao.Columns.OpsUserName: req.OpsUserName, s.TaskDao.Columns.PlanStartTime: req.PlanStartTime, s.TaskDao.Columns.PlanEndTime: req.PlanEndTime, s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour, s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusProcessing, } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("任务排期失败") } // 2. 创建过程记录 handleContent := "任务排期
执行人: " + req.OpsUserName + "
计划开始: " + req.PlanStartTime + "
计划结束: " + req.PlanEndTime + "
预估工时: " + gconv.String(req.EstimateWorkHour) + "小时" recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: handleContent, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // Start 开始任务 func (s *OpsEventTaskService) Start(req *opsdevmodel.OpsEventTaskStartReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 只有待处理(10)或暂停(25)状态可以开始 if !s.canStart(entity.TaskStatus) { return myerrors.TipsError("只有待处理或暂停状态的任务可以开始") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusProcessing, // 处理中 } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务状态 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("开始任务失败") } // 2. 创建过程记录 recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "开始处理任务
任务标题: " + entity.TaskTitle, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // Complete 完成任务 func (s *OpsEventTaskService) Complete(req *opsdevmodel.OpsEventTaskCompleteReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 只有处理中(20)状态可以完成 if !s.canComplete(entity.TaskStatus) { return myerrors.TipsError("只有处理中的任务可以完成") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusCompleted, // 已完成 s.TaskDao.Columns.CompleteTime: gtime.Now(), s.TaskDao.Columns.ActualWorkHour: req.ActualWorkHour, s.TaskDao.Columns.Remark: req.Remark, } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 预生成下游任务编号(避免事务内查询重复) var devTaskNo, testTaskNo string if entity.TaskType == opsdevmodel.TaskTypeReqReview { devTaskNo = s.generateTaskNo() } if entity.TaskType == opsdevmodel.TaskTypeFeatureDev { testTaskNo = s.generateTaskNo() } // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务状态 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("完成任务失败") } // 2. 创建过程记录 var handleContent string if entity.TaskType == opsdevmodel.TaskTypeFeatureTest && req.TestResult != "" { testResultText := "通过" if req.TestResult == "fail" { testResultText = "不通过" } handleContent = fmt.Sprintf("测试结果:%s
测试时间:%s", testResultText, gtime.Now().Format("Y-m-d")) } else if entity.TaskType == opsdevmodel.TaskTypeSystemRelease && req.IsReleaseComplete { // 系统发版完成记录 handleContent = "发版完成
任务标题: " + entity.TaskTitle + "
发版工时: " + gconv.String(req.ActualWorkHour) + "小时" if req.Remark != "" { handleContent += "
发版说明: " + req.Remark } if len(req.DevTaskIds) > 0 { handleContent += "
关联研发任务数: " + gconv.String(len(req.DevTaskIds)) + "个" } } else { // 普通任务完成记录 handleContent = "完成任务
任务标题: " + entity.TaskTitle + "
实际工作量: " + gconv.String(req.ActualWorkHour) + "小时" } recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: handleContent, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) result, err := s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } recordId, _ := result.LastInsertId() // 3. 保存附件 if len(req.Attachments) > 0 { for _, att := range req.Attachments { attData := g.Map{ s.AttachmentDao.Columns.TaskId: req.Id, s.AttachmentDao.Columns.TaskRecordId: recordId, s.AttachmentDao.Columns.FileName: att.FileName, s.AttachmentDao.Columns.FileUrl: att.FileUrl, s.AttachmentDao.Columns.FileType: att.FileType, } service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.AttachmentDao.TX(tx).Data(attData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("保存附件信息失败") } } } // 4. 根据任务类型自动创建下游任务 // 4.1 需求评审完成时,自动创建功能开发任务 if entity.TaskType == opsdevmodel.TaskTypeReqReview { devTaskData := g.Map{ s.TaskDao.Columns.TaskNo: devTaskNo, s.TaskDao.Columns.ProjectId: entity.ProjectId, s.TaskDao.Columns.ProjectName: entity.ProjectName, s.TaskDao.Columns.EventId: entity.EventId, s.TaskDao.Columns.EventType: entity.EventType, s.TaskDao.Columns.TaskTitle: entity.TaskTitle, s.TaskDao.Columns.TaskDesc: entity.TaskDesc, s.TaskDao.Columns.FunctionName: entity.FunctionName, s.TaskDao.Columns.TaskType: opsdevmodel.TaskTypeFeatureDev, // 功能开发 s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusTodo, // 待处理 s.TaskDao.Columns.Priority: entity.Priority, s.TaskDao.Columns.TaskParentId: req.Id, } service.SetCreatedInfo(devTaskData, s.GetCxtUserId(), s.GetCxtUserName()) devResult, err := s.TaskDao.TX(tx).Data(devTaskData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("自动创建功能开发任务失败") } devTaskId, _ := devResult.LastInsertId() // 创建功能开发任务的过程记录 devRecordData := g.Map{ s.RecordDao.Columns.TaskId: devTaskId, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "创建功能开发任务
说明: 由需求评审任务自动创建", } service.SetCreatedInfo(devRecordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(devRecordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增功能开发任务过程记录失败") } } // 4.2 功能开发完成时,自动创建功能测试任务 if entity.TaskType == opsdevmodel.TaskTypeFeatureDev { testTaskData := g.Map{ s.TaskDao.Columns.TaskNo: testTaskNo, s.TaskDao.Columns.ProjectId: entity.ProjectId, s.TaskDao.Columns.ProjectName: entity.ProjectName, s.TaskDao.Columns.EventId: entity.EventId, s.TaskDao.Columns.EventType: entity.EventType, s.TaskDao.Columns.TaskTitle: entity.TaskTitle, s.TaskDao.Columns.TaskDesc: entity.TaskDesc, s.TaskDao.Columns.FunctionName: entity.FunctionName, s.TaskDao.Columns.TaskType: opsdevmodel.TaskTypeFeatureTest, // 功能测试 s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusTodo, // 待处理 s.TaskDao.Columns.Priority: entity.Priority, s.TaskDao.Columns.TaskParentId: req.Id, } service.SetCreatedInfo(testTaskData, s.GetCxtUserId(), s.GetCxtUserName()) testResult, err := s.TaskDao.TX(tx).Data(testTaskData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("自动创建功能测试任务失败") } testTaskId, _ := testResult.LastInsertId() // 创建功能测试任务的过程记录 testRecordData := g.Map{ s.RecordDao.Columns.TaskId: testTaskId, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "创建功能测试任务
说明: 由功能开发任务自动创建", } service.SetCreatedInfo(testRecordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(testRecordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增功能测试任务过程记录失败") } } // 4.3 系统发版完成时,保存关联的研发任务到ops_event_task_release表 if entity.TaskType == opsdevmodel.TaskTypeSystemRelease && req.IsReleaseComplete && len(req.DevTaskIds) > 0 { for _, devTaskId := range req.DevTaskIds { releaseData := g.Map{ s.ReleaseDao.Columns.ReleaseTaskId: req.Id, s.ReleaseDao.Columns.DevTaskId: devTaskId, s.ReleaseDao.Columns.ProjectId: entity.ProjectId, } service.SetCreatedInfo(releaseData, s.GetCxtUserId(), s.GetCxtUserName()) _, err := s.ReleaseDao.TX(tx).Data(releaseData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("保存发版关联任务失败") } } } return nil }) } // Pause 暂停任务 func (s *OpsEventTaskService) Pause(req *opsdevmodel.OpsEventTaskPauseReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 只有处理中(20)状态可以暂停 if !s.canPause(entity.TaskStatus) { return myerrors.TipsError("只有处理中的任务可以暂停") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusPaused, // 暂停 } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务状态 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("暂停任务失败") } // 2. 创建过程记录 recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "暂停任务
暂停原因: " + req.Remark, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // Block 阻塞任务 func (s *OpsEventTaskService) Block(req *opsdevmodel.OpsEventTaskBlockReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 只有处理中(20)或暂停(25)状态可以阻塞 if !s.canBlock(entity.TaskStatus) { return myerrors.TipsError("只有处理中或暂停状态的任务可以阻塞") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusBlocked, // 阻塞 } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务状态 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("阻塞任务失败") } // 2. 创建过程记录 recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "阻塞任务
阻塞原因: " + req.Remark, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // Cancel 作废任务 func (s *OpsEventTaskService) Cancel(req *opsdevmodel.OpsEventTaskCancelReq) error { // 校验数据是否存在 var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } // 已完成(30)的任务不能作废 if !s.canCancel(entity.TaskStatus) { return myerrors.TipsError("已完成的任务不能作废") } // 构造更新数据 data := g.Map{ s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusCancelled, // 作废 } // 补齐审计字段 service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务 return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 更新任务状态 _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("作废任务失败") } // 2. 创建过程记录 recordData := g.Map{ s.RecordDao.Columns.TaskId: req.Id, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: "作废任务
作废原因: " + req.Remark, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // GetRecords 获取任务过程记录列表(包含附件) func (s *OpsEventTaskService) GetRecords(req *opsdevmodel.OpsEventTaskRecordSearchReq) ([]*opsdevmodel.OpsEventTaskRecordWithAttachments, error) { var records []*opsdevmodel.OpsEventTaskRecord err := s.RecordDao.Where(s.RecordDao.Columns.TaskId, req.TaskId). Order(s.RecordDao.Columns.CreatedTime + " desc"). Scan(&records) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询过程记录失败") } result := make([]*opsdevmodel.OpsEventTaskRecordWithAttachments, 0, len(records)) for _, record := range records { recordRsp := &opsdevmodel.OpsEventTaskRecordWithAttachments{ OpsEventTaskRecord: *record, Attachments: []*opsdevmodel.OpsEventTaskAttachment{}, } // 查询该记录关联的附件 if record.Id > 0 { var attachments []*opsdevmodel.OpsEventTaskAttachment err := s.AttachmentDao.Where(s.AttachmentDao.Columns.TaskRecordId, record.Id). Scan(&attachments) if err != nil { g.Log().Error(err) } else { recordRsp.Attachments = attachments } } result = append(result, recordRsp) } return result, nil } // generateTaskNo 生成任务编号 func (s *OpsEventTaskService) generateTaskNo() string { // 格式: TSK + 年月日 + 4位序号 now := gtime.Now() prefix := "TSK" + now.Format("ymd") // 查询当天最大序号 var maxNo string err := s.TaskDao.Where(s.TaskDao.Columns.TaskNo+" like ?", prefix+"%").Order(s.TaskDao.Columns.TaskNo + " desc").Fields(s.TaskDao.Columns.TaskNo).Scan(&maxNo) if err != nil { return prefix + "0001" } if maxNo == "" { return prefix + "0001" } // 提取序号并+1 if len(maxNo) >= len(prefix)+4 { seq := maxNo[len(prefix):] var seqNum int gconv.Struct(seq, &seqNum) seqNum++ return prefix + fmt.Sprintf("%04d", seqNum) } return prefix + "0001" } // 状态校验辅助方法 func (s *OpsEventTaskService) canEdit(status string) bool { return status != opsdevmodel.TaskStatusCompleted && status != opsdevmodel.TaskStatusCancelled } func (s *OpsEventTaskService) canSchedule(status string) bool { return status == opsdevmodel.TaskStatusTodo } func (s *OpsEventTaskService) canStart(status string) bool { return status == opsdevmodel.TaskStatusTodo || status == opsdevmodel.TaskStatusPaused } func (s *OpsEventTaskService) canComplete(status string) bool { return status == opsdevmodel.TaskStatusProcessing } func (s *OpsEventTaskService) canPause(status string) bool { return status == opsdevmodel.TaskStatusProcessing } func (s *OpsEventTaskService) canBlock(status string) bool { return status == opsdevmodel.TaskStatusProcessing || status == opsdevmodel.TaskStatusPaused } func (s *OpsEventTaskService) canCancel(status string) bool { return status != opsdevmodel.TaskStatusCompleted } // getProjectName 根据项目ID获取项目名称 func (s *OpsEventTaskService) getProjectName(projectId int) string { if projectId <= 0 { return "" } var project opsdevmodel.OpsDeliveryProject err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime). WherePri(s.ProjectDao.Columns.Id, projectId). Scan(&project) if err != nil { g.Log().Error(err) return "" } return project.ProjectName } // GetAttachments 获取任务附件列表 func (s *OpsEventTaskService) GetAttachments(taskId int) ([]*opsdevmodel.OpsEventTaskAttachment, error) { var attachments []*opsdevmodel.OpsEventTaskAttachment err := s.AttachmentDao.Where(s.AttachmentDao.Columns.TaskId, taskId). Order(s.AttachmentDao.Columns.CreatedTime + " desc"). Scan(&attachments) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询附件失败") } return attachments, nil } // GetTaskReleaseList 根据发版任务ID查询关联的开发任务列表 func (s *OpsEventTaskService) GetTaskReleaseList(req *opsdevmodel.OpsEventTaskReleaseListReq) ([]*opsdevmodel.OpsEventTaskReleaseRsp, error) { // 1. 查询关联表获取关联的任务ID列表 var releaseList []*opsdevmodel.OpsEventTaskRelease err := s.ReleaseDao.Where(s.ReleaseDao.Columns.ReleaseTaskId, req.ReleaseTaskId). Scan(&releaseList) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询发布版本关联记录失败") } if len(releaseList) == 0 { return []*opsdevmodel.OpsEventTaskReleaseRsp{}, nil } // 2. 提取开发任务ID列表 devTaskIds := make([]int, 0, len(releaseList)) for _, r := range releaseList { devTaskIds = append(devTaskIds, r.DevTaskId) } // 3. 查询任务详情 var taskList []*opsdevmodel.OpsEventTask err = s.TaskDao.WhereIn(s.TaskDao.Columns.Id, devTaskIds). Scan(&taskList) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询关联任务详情失败") } // 4. 构建响应数据 result := make([]*opsdevmodel.OpsEventTaskReleaseRsp, 0, len(taskList)) for _, task := range taskList { result = append(result, &opsdevmodel.OpsEventTaskReleaseRsp{ Id: task.Id, DevTaskId: task.Id, TaskNo: task.TaskNo, TaskTitle: task.TaskTitle, TaskType: task.TaskType, TaskStatus: task.TaskStatus, OpsUserName: task.OpsUserName, ProjectId: task.ProjectId, CreatedTime: task.CreatedTime.Format("Y-m-d H:i:s"), }) } return result, nil } // AddRecord 添加任务过程记录 func (s *OpsEventTaskService) AddRecord(req *opsdevmodel.OpsEventTaskRecordAddReq) error { // 构造记录数据 recordData := g.Map{ s.RecordDao.Columns.TaskId: req.TaskId, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: req.HandleContent, } // 补齐审计字段 service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) // 使用事务控制 return s.RecordDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // 1. 创建过程记录 result, err := s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("添加过程记录失败") } // 获取新创建的记录ID recordId, err := result.LastInsertId() if err != nil { g.Log().Error(err) return myerrors.DbError("获取记录ID失败") } // 2. 如果有附件,保存附件 if len(req.Attachments) > 0 { for _, att := range req.Attachments { attData := g.Map{ s.AttachmentDao.Columns.TaskId: req.TaskId, s.AttachmentDao.Columns.TaskRecordId: int(recordId), s.AttachmentDao.Columns.FileName: att.FileName, s.AttachmentDao.Columns.FileUrl: att.FileUrl, s.AttachmentDao.Columns.FileType: att.FileType, } service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.AttachmentDao.TX(tx).Data(attData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("保存附件失败") } } } return nil }) } // AddWorkHour 添加工时登记(累加actual_work_hour并记录过程) func (s *OpsEventTaskService) AddWorkHour(req *opsdevmodel.OpsEventTaskWorkHourAddReq) error { var entity opsdevmodel.OpsEventTask err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.TaskId).Scan(&entity) if err != nil { g.Log().Error(err) return myerrors.DbError("查询任务数据失败") } if entity.Id <= 0 { return myerrors.TipsError("任务数据不存在") } if !s.canAddWorkHour(entity.TaskStatus) { return myerrors.TipsError("只有处理中的任务可以登记工时") } return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { workHourData := g.Map{ s.WorkHourDao.Columns.TaskId: req.TaskId, s.WorkHourDao.Columns.OpsUserId: s.GetCxtUserId(), s.WorkHourDao.Columns.OpsUserName: s.GetCxtUserName(), s.WorkHourDao.Columns.ActualWorkDate: req.WorkDate, s.WorkHourDao.Columns.ActualWorkHour: req.ActualHour, s.WorkHourDao.Columns.Remark: req.Remark, } service.SetCreatedInfo(workHourData, s.GetCxtUserId(), s.GetCxtUserName()) _, err := s.WorkHourDao.TX(tx).Data(workHourData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("工时登记失败") } newActualWorkHour := entity.ActualWorkHour + req.ActualHour updateData := g.Map{ s.TaskDao.Columns.ActualWorkHour: newActualWorkHour, } service.SetUpdatedInfo(updateData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(updateData).WherePri(s.TaskDao.Columns.Id, req.TaskId).Update() if err != nil { g.Log().Error(err) return myerrors.DbError("更新实际工时失败") } handleContent := fmt.Sprintf("工时登记
工作日期: %s
实际工时: %s小时
工作进展: %s", req.WorkDate, gconv.String(req.ActualHour), req.Remark) recordData := g.Map{ s.RecordDao.Columns.TaskId: req.TaskId, s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(), s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(), s.RecordDao.Columns.HandleContent: handleContent, } service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName()) _, err = s.RecordDao.TX(tx).Data(recordData).Insert() if err != nil { g.Log().Error(err) return myerrors.DbError("新增过程记录失败") } return nil }) } // GetWorkHourList 获取工时登记列表 func (s *OpsEventTaskService) GetWorkHourList(req *opsdevmodel.OpsEventTaskWorkHourListReq) ([]*opsdevmodel.OpsEventTaskWorkHourRsp, error) { var entities []*opsdevmodel.OpsEventTaskWorkHour err := s.WorkHourDao.FieldsEx(s.WorkHourDao.Columns.DeletedTime). Where(s.WorkHourDao.Columns.TaskId, req.TaskId). Order(s.WorkHourDao.Columns.CreatedTime + " desc"). Scan(&entities) if err != nil { g.Log().Error(err) return nil, myerrors.DbError("查询工时登记列表失败") } list := make([]*opsdevmodel.OpsEventTaskWorkHourRsp, 0, len(entities)) for _, entity := range entities { workDate := "" if entity.ActualWorkDate != nil { workDate = entity.ActualWorkDate.Format("Y-m-d") } createdTime := "" if entity.CreatedTime != nil { createdTime = entity.CreatedTime.Format("Y-m-d H:i:s") } list = append(list, &opsdevmodel.OpsEventTaskWorkHourRsp{ Id: entity.Id, TaskId: entity.TaskId, WorkDate: workDate, ActualHour: entity.ActualWorkHour, Remark: entity.Remark, CreatedName: entity.CreatedName, CreatedTime: createdTime, }) } return list, nil } // canAddWorkHour 检查是否可以登记工时(仅处理中状态) func (s *OpsEventTaskService) canAddWorkHour(status string) bool { return status == opsdevmodel.TaskStatusProcessing }