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
}
// 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)
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 req.OpsUserName != "" {
db = db.Where(s.TaskDao.Columns.OpsUserName+" like ?", "%"+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("数据转换失败")
}
// 填充项目名称
for _, item := range list {
if item.ProjectId > 0 {
var project opsdevmodel.OpsDeliveryProject
err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
WherePri(s.ProjectDao.Columns.Id, item.ProjectId).
Scan(&project)
if err != nil {
g.Log().Error(err)
} else if project.Id > 0 {
item.ProjectName = project.ProjectName
item.ContractNo = project.ContractNo
}
}
}
return
}
// getSortColumnName 将前端排序字段名转换为数据库列名
func (s *OpsEventTaskService) getSortColumnName(field string) string {
// 前端字段名到数据库列名的映射
fieldMap := map[string]string{
"taskTitle": s.TaskDao.Columns.TaskTitle,
"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,
"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.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("数据转换失败")
}
// 查询关联的项目信息
if entity.ProjectId > 0 {
var project opsdevmodel.OpsDeliveryProject
err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
WherePri(s.ProjectDao.Columns.Id, entity.ProjectId).
Scan(&project)
if err != nil {
g.Log().Error(err)
} else if project.Id > 0 {
rsp.ProjectName = project.ProjectName
rsp.ContractNo = project.ContractNo
}
}
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())
// 使用事务
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.ProjectId: entity.ProjectId,
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.ProjectId: entity.ProjectId,
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
}
// 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
})
}