ops_event_task.go 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. package opsdev
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "dashoo.cn/opms_libary/myerrors"
  7. opsdevdao "dashoo.cn/opms_parent/app/dao/opsdev"
  8. opsdevmodel "dashoo.cn/opms_parent/app/model/opsdev"
  9. "dashoo.cn/opms_parent/app/service"
  10. "github.com/gogf/gf/database/gdb"
  11. "github.com/gogf/gf/frame/g"
  12. "github.com/gogf/gf/os/gtime"
  13. "github.com/gogf/gf/util/gconv"
  14. )
  15. // OpsEventTaskService 任务业务逻辑实现类
  16. type OpsEventTaskService struct {
  17. *service.ContextService
  18. TaskDao *opsdevdao.OpsEventTaskDao
  19. RecordDao *opsdevdao.OpsEventTaskRecordDao
  20. AttachmentDao *opsdevdao.OpsEventTaskAttachmentDao
  21. ProjectDao *opsdevdao.OpsDeliveryProjectDao
  22. ReleaseDao *opsdevdao.OpsEventTaskReleaseDao
  23. WorkHourDao *opsdevdao.OpsEventTaskWorkHourDao
  24. }
  25. // NewOpsEventTaskService 初始化service
  26. func NewOpsEventTaskService(ctx context.Context) (svc *OpsEventTaskService, err error) {
  27. svc = new(OpsEventTaskService)
  28. if svc.ContextService, err = svc.Init(ctx); err != nil {
  29. return nil, err
  30. }
  31. svc.TaskDao = opsdevdao.NewOpsEventTaskDao(svc.Tenant)
  32. svc.RecordDao = opsdevdao.NewOpsEventTaskRecordDao(svc.Tenant)
  33. svc.AttachmentDao = opsdevdao.NewOpsEventTaskAttachmentDao(svc.Tenant)
  34. svc.ProjectDao = opsdevdao.NewOpsDeliveryProjectDao(svc.Tenant)
  35. svc.ReleaseDao = opsdevdao.NewOpsEventTaskReleaseDao(svc.Tenant)
  36. svc.WorkHourDao = opsdevdao.NewOpsEventTaskWorkHourDao(svc.Tenant)
  37. return svc, nil
  38. }
  39. // GetList 分页查询任务列表
  40. func (s *OpsEventTaskService) GetList(req *opsdevmodel.OpsEventTaskSearchReq) (total int, list []*opsdevmodel.OpsEventTaskRsp, err error) {
  41. db := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime)
  42. // 项目ID筛选
  43. if req.ProjectId > 0 {
  44. db = db.Where(s.TaskDao.Columns.ProjectId, req.ProjectId)
  45. }
  46. // 任务标题模糊查询
  47. if req.TaskTitle != "" {
  48. db = db.Where(s.TaskDao.Columns.TaskTitle+" like ?", "%"+req.TaskTitle+"%")
  49. }
  50. // 任务类型筛选(多选)
  51. if len(req.TaskType) > 0 {
  52. db = db.Where(s.TaskDao.Columns.TaskType+" in (?)", req.TaskType)
  53. }
  54. // 任务状态筛选(多选)
  55. if len(req.TaskStatus) > 0 {
  56. db = db.Where(s.TaskDao.Columns.TaskStatus+" in (?)", req.TaskStatus)
  57. }
  58. // 优先级筛选(多选)
  59. if len(req.Priority) > 0 {
  60. db = db.Where(s.TaskDao.Columns.Priority+" in (?)", req.Priority)
  61. }
  62. // 执行人筛选(多选)
  63. if len(req.OpsUserName) > 0 {
  64. db = db.Where(s.TaskDao.Columns.OpsUserName+" in (?)", req.OpsUserName)
  65. }
  66. // 计划结束日期范围筛选
  67. if req.PlanEndDateStart != "" {
  68. db = db.Where(s.TaskDao.Columns.PlanEndTime+" >= ?", req.PlanEndDateStart+" 00:00:00")
  69. }
  70. if req.PlanEndDateEnd != "" {
  71. db = db.Where(s.TaskDao.Columns.PlanEndTime+" <= ?", req.PlanEndDateEnd+" 23:59:59")
  72. }
  73. // 创建日期范围筛选
  74. if req.CreatedTimeStart != "" {
  75. db = db.Where(s.TaskDao.Columns.CreatedTime+" >= ?", req.CreatedTimeStart+" 00:00:00")
  76. }
  77. if req.CreatedTimeEnd != "" {
  78. db = db.Where(s.TaskDao.Columns.CreatedTime+" <= ?", req.CreatedTimeEnd+" 23:59:59")
  79. }
  80. // 完成日期范围筛选
  81. if req.CompleteTimeStart != "" {
  82. db = db.Where(s.TaskDao.Columns.CompleteTime+" >= ?", req.CompleteTimeStart+" 00:00:00")
  83. }
  84. if req.CompleteTimeEnd != "" {
  85. db = db.Where(s.TaskDao.Columns.CompleteTime+" <= ?", req.CompleteTimeEnd+" 23:59:59")
  86. }
  87. // 统计总数
  88. total, err = db.Count()
  89. if err != nil {
  90. g.Log().Error(err)
  91. return 0, nil, myerrors.DbError("获取任务总数失败")
  92. }
  93. // 分页查询
  94. pageNum, pageSize := req.GetPage()
  95. var entityList []*opsdevmodel.OpsEventTask
  96. // 处理排序
  97. if len(req.SortFields) > 0 {
  98. orderClauses := []string{}
  99. for _, sort := range req.SortFields {
  100. // 将前端字段名转换为数据库列名
  101. colName := s.getSortColumnName(sort.Field)
  102. if colName != "" {
  103. orderClauses = append(orderClauses, colName+" "+strings.ToUpper(sort.Order))
  104. }
  105. }
  106. if len(orderClauses) > 0 {
  107. db = db.Order(strings.Join(orderClauses, ", "))
  108. } else {
  109. db = db.Order(s.TaskDao.Columns.CreatedTime + " desc")
  110. }
  111. } else {
  112. db = db.Order(s.TaskDao.Columns.CreatedTime + " desc")
  113. }
  114. err = db.Page(pageNum, pageSize).Scan(&entityList)
  115. if err != nil {
  116. g.Log().Error(err)
  117. return 0, nil, myerrors.DbError("查询任务列表失败")
  118. }
  119. // 转换为响应结构体
  120. if err = gconv.Structs(entityList, &list); err != nil {
  121. g.Log().Error(err)
  122. return 0, nil, myerrors.DbError("数据转换失败")
  123. }
  124. return
  125. }
  126. // getSortColumnName 将前端排序字段名转换为数据库列名
  127. func (s *OpsEventTaskService) getSortColumnName(field string) string {
  128. // 前端字段名到数据库列名的映射
  129. fieldMap := map[string]string{
  130. "taskTitle": s.TaskDao.Columns.TaskTitle,
  131. "functionName": s.TaskDao.Columns.FunctionName,
  132. "taskType": s.TaskDao.Columns.TaskType,
  133. "taskStatus": s.TaskDao.Columns.TaskStatus,
  134. "priority": s.TaskDao.Columns.Priority,
  135. "opsUserName": s.TaskDao.Columns.OpsUserName,
  136. "planStartTime": s.TaskDao.Columns.PlanStartTime,
  137. "planEndTime": s.TaskDao.Columns.PlanEndTime,
  138. "completeTime": s.TaskDao.Columns.CompleteTime,
  139. "releaseVersion": s.TaskDao.Columns.ReleaseVersion,
  140. "createdTime": s.TaskDao.Columns.CreatedTime,
  141. }
  142. if colName, ok := fieldMap[field]; ok {
  143. return colName
  144. }
  145. return ""
  146. }
  147. // Create 新增任务,包含事务控制和过程记录
  148. func (s *OpsEventTaskService) Create(req *opsdevmodel.OpsEventTaskAddReq) error {
  149. // 判断任务状态:如果执行人、计划开始时间、计划结束时间都填写了,则状态为处理中,否则为待处理
  150. taskStatus := opsdevmodel.TaskStatusTodo
  151. if req.OpsUserId > 0 && req.PlanStartTime != "" && req.PlanEndTime != "" {
  152. taskStatus = opsdevmodel.TaskStatusProcessing
  153. }
  154. // 生成任务编号
  155. taskNo := s.generateTaskNo()
  156. // 构造数据
  157. data := g.Map{
  158. s.TaskDao.Columns.TaskNo: taskNo,
  159. s.TaskDao.Columns.ProjectId: req.ProjectId,
  160. s.TaskDao.Columns.ProjectName: s.getProjectName(req.ProjectId),
  161. s.TaskDao.Columns.TaskTitle: req.TaskTitle,
  162. s.TaskDao.Columns.TaskDesc: req.TaskDesc,
  163. s.TaskDao.Columns.FunctionName: req.FunctionName,
  164. s.TaskDao.Columns.TaskType: req.TaskType,
  165. s.TaskDao.Columns.TaskStatus: taskStatus,
  166. s.TaskDao.Columns.Priority: req.Priority,
  167. s.TaskDao.Columns.OpsUserId: req.OpsUserId,
  168. s.TaskDao.Columns.OpsUserName: req.OpsUserName,
  169. s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour,
  170. s.TaskDao.Columns.DefectType: req.DefectType,
  171. s.TaskDao.Columns.ReleaseVersion: req.ReleaseVersion,
  172. s.TaskDao.Columns.Remark: req.Remark,
  173. s.TaskDao.Columns.EventId: req.EventId,
  174. s.TaskDao.Columns.EventType: req.EventType,
  175. s.TaskDao.Columns.TaskParentId: req.TaskParentId,
  176. }
  177. if req.PlanStartTime != "" {
  178. data[s.TaskDao.Columns.PlanStartTime] = req.PlanStartTime
  179. }
  180. if req.PlanEndTime != "" {
  181. data[s.TaskDao.Columns.PlanEndTime] = req.PlanEndTime
  182. }
  183. // 补齐审计字段
  184. service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  185. // 使用事务控制
  186. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  187. // 1. 创建任务记录
  188. result, err := s.TaskDao.TX(tx).Data(data).Insert()
  189. if err != nil {
  190. g.Log().Error(err)
  191. return myerrors.DbError("新增任务失败")
  192. }
  193. // 获取新创建的任务ID
  194. taskId, err := result.LastInsertId()
  195. if err != nil {
  196. g.Log().Error(err)
  197. return myerrors.DbError("获取任务ID失败")
  198. }
  199. // 2. 创建任务过程记录
  200. recordData := g.Map{
  201. s.RecordDao.Columns.TaskId: taskId,
  202. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  203. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  204. s.RecordDao.Columns.HandleContent: "创建任务<br/>任务标题: " + req.TaskTitle,
  205. }
  206. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  207. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  208. if err != nil {
  209. g.Log().Error(err)
  210. return myerrors.DbError("新增任务过程记录失败")
  211. }
  212. // 获取过程记录ID
  213. recordId, err := result.LastInsertId()
  214. if err != nil {
  215. g.Log().Error(err)
  216. return myerrors.DbError("获取过程记录ID失败")
  217. }
  218. // 3. 保存附件到任务附件表,关联过程记录
  219. if len(req.Attachments) > 0 {
  220. for _, att := range req.Attachments {
  221. attData := g.Map{
  222. s.AttachmentDao.Columns.TaskId: taskId,
  223. s.AttachmentDao.Columns.TaskRecordId: recordId,
  224. s.AttachmentDao.Columns.FileName: att.FileName,
  225. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  226. s.AttachmentDao.Columns.FileType: att.FileType,
  227. }
  228. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  229. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  230. if err != nil {
  231. g.Log().Error(err)
  232. return myerrors.DbError("保存附件信息失败")
  233. }
  234. }
  235. }
  236. return nil
  237. })
  238. }
  239. // UpdateById 根据ID更新任务
  240. func (s *OpsEventTaskService) UpdateById(req *opsdevmodel.OpsEventTaskUpdateReq) error {
  241. // 校验数据是否存在
  242. var entity opsdevmodel.OpsEventTask
  243. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  244. if err != nil {
  245. g.Log().Error(err)
  246. return myerrors.DbError("查询任务数据失败")
  247. }
  248. if entity.Id <= 0 {
  249. return myerrors.TipsError("任务数据不存在")
  250. }
  251. // 已完成(30)或已作废(90)状态的任务不允许编辑
  252. if !s.canEdit(entity.TaskStatus) {
  253. return myerrors.TipsError("已完成或已作废状态的任务不允许编辑")
  254. }
  255. // 构造更新数据
  256. data := g.Map{
  257. s.TaskDao.Columns.TaskTitle: req.TaskTitle,
  258. s.TaskDao.Columns.TaskDesc: req.TaskDesc,
  259. s.TaskDao.Columns.FunctionName: req.FunctionName,
  260. s.TaskDao.Columns.TaskType: req.TaskType,
  261. s.TaskDao.Columns.Priority: req.Priority,
  262. s.TaskDao.Columns.OpsUserId: req.OpsUserId,
  263. s.TaskDao.Columns.OpsUserName: req.OpsUserName,
  264. s.TaskDao.Columns.PlanStartTime: req.PlanStartTime,
  265. s.TaskDao.Columns.PlanEndTime: req.PlanEndTime,
  266. s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour,
  267. s.TaskDao.Columns.DefectType: req.DefectType,
  268. s.TaskDao.Columns.ReleaseVersion: req.ReleaseVersion,
  269. s.TaskDao.Columns.Remark: req.Remark,
  270. }
  271. // 补齐审计字段
  272. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  273. // 更新操作,排除不可改字段
  274. _, err = s.TaskDao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  275. if err != nil {
  276. g.Log().Error(err)
  277. return myerrors.DbError("更新任务失败")
  278. }
  279. return nil
  280. }
  281. // DeleteByIds 根据ID批量删除
  282. func (s *OpsEventTaskService) DeleteByIds(ids []int64) error {
  283. if len(ids) == 0 {
  284. return myerrors.TipsError("请选择需要删除的任务")
  285. }
  286. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  287. // 1. 删除任务
  288. _, err := s.TaskDao.TX(tx).WhereIn(s.TaskDao.Columns.Id, ids).Delete()
  289. if err != nil {
  290. g.Log().Error(err)
  291. return myerrors.DbError("删除任务失败")
  292. }
  293. // 2. 删除关联的过程记录
  294. _, err = s.RecordDao.TX(tx).WhereIn(s.RecordDao.Columns.TaskId, ids).Delete()
  295. if err != nil {
  296. g.Log().Error(err)
  297. return myerrors.DbError("删除任务过程记录失败")
  298. }
  299. // 3. 删除关联的附件
  300. _, err = s.AttachmentDao.TX(tx).WhereIn(s.AttachmentDao.Columns.TaskId, ids).Delete()
  301. if err != nil {
  302. g.Log().Error(err)
  303. return myerrors.DbError("删除任务附件失败")
  304. }
  305. return nil
  306. })
  307. }
  308. // GetById 根据ID查询单条(关联项目信息)
  309. func (s *OpsEventTaskService) GetById(id int) (*opsdevmodel.OpsEventTaskRsp, error) {
  310. var entity opsdevmodel.OpsEventTask
  311. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, id).Scan(&entity)
  312. if err != nil {
  313. g.Log().Error(err)
  314. return nil, myerrors.DbError("查询任务数据失败")
  315. }
  316. if entity.Id <= 0 {
  317. return nil, myerrors.TipsError("任务数据不存在")
  318. }
  319. var rsp opsdevmodel.OpsEventTaskRsp
  320. if err := gconv.Struct(entity, &rsp); err != nil {
  321. g.Log().Error(err)
  322. return nil, myerrors.DbError("数据转换失败")
  323. }
  324. return &rsp, nil
  325. }
  326. // Schedule 任务排期
  327. func (s *OpsEventTaskService) Schedule(req *opsdevmodel.OpsEventTaskScheduleReq) error {
  328. // 校验数据是否存在
  329. var entity opsdevmodel.OpsEventTask
  330. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  331. if err != nil {
  332. g.Log().Error(err)
  333. return myerrors.DbError("查询任务数据失败")
  334. }
  335. if entity.Id <= 0 {
  336. return myerrors.TipsError("任务数据不存在")
  337. }
  338. // 只有待处理状态可以排期
  339. if !s.canSchedule(entity.TaskStatus) {
  340. return myerrors.TipsError("只有待处理状态的任务可以排期")
  341. }
  342. data := g.Map{
  343. s.TaskDao.Columns.OpsUserId: req.OpsUserId,
  344. s.TaskDao.Columns.OpsUserName: req.OpsUserName,
  345. s.TaskDao.Columns.PlanStartTime: req.PlanStartTime,
  346. s.TaskDao.Columns.PlanEndTime: req.PlanEndTime,
  347. s.TaskDao.Columns.EstimateWorkHour: req.EstimateWorkHour,
  348. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusProcessing,
  349. }
  350. // 补齐审计字段
  351. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  352. // 使用事务
  353. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  354. // 1. 更新任务
  355. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  356. if err != nil {
  357. g.Log().Error(err)
  358. return myerrors.DbError("任务排期失败")
  359. }
  360. // 2. 创建过程记录
  361. handleContent := "任务排期<br/>执行人: " + req.OpsUserName +
  362. "<br/>计划开始: " + req.PlanStartTime +
  363. "<br/>计划结束: " + req.PlanEndTime +
  364. "<br/>预估工时: " + gconv.String(req.EstimateWorkHour) + "小时"
  365. recordData := g.Map{
  366. s.RecordDao.Columns.TaskId: req.Id,
  367. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  368. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  369. s.RecordDao.Columns.HandleContent: handleContent,
  370. }
  371. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  372. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  373. if err != nil {
  374. g.Log().Error(err)
  375. return myerrors.DbError("新增过程记录失败")
  376. }
  377. return nil
  378. })
  379. }
  380. // Start 开始任务
  381. func (s *OpsEventTaskService) Start(req *opsdevmodel.OpsEventTaskStartReq) error {
  382. // 校验数据是否存在
  383. var entity opsdevmodel.OpsEventTask
  384. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  385. if err != nil {
  386. g.Log().Error(err)
  387. return myerrors.DbError("查询任务数据失败")
  388. }
  389. if entity.Id <= 0 {
  390. return myerrors.TipsError("任务数据不存在")
  391. }
  392. // 只有待处理(10)或暂停(25)状态可以开始
  393. if !s.canStart(entity.TaskStatus) {
  394. return myerrors.TipsError("只有待处理或暂停状态的任务可以开始")
  395. }
  396. // 构造更新数据
  397. data := g.Map{
  398. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusProcessing, // 处理中
  399. }
  400. // 补齐审计字段
  401. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  402. // 使用事务
  403. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  404. // 1. 更新任务状态
  405. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  406. if err != nil {
  407. g.Log().Error(err)
  408. return myerrors.DbError("开始任务失败")
  409. }
  410. // 2. 创建过程记录
  411. recordData := g.Map{
  412. s.RecordDao.Columns.TaskId: req.Id,
  413. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  414. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  415. s.RecordDao.Columns.HandleContent: "开始处理任务<br/>任务标题: " + entity.TaskTitle,
  416. }
  417. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  418. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  419. if err != nil {
  420. g.Log().Error(err)
  421. return myerrors.DbError("新增过程记录失败")
  422. }
  423. return nil
  424. })
  425. }
  426. // Complete 完成任务
  427. func (s *OpsEventTaskService) Complete(req *opsdevmodel.OpsEventTaskCompleteReq) error {
  428. // 校验数据是否存在
  429. var entity opsdevmodel.OpsEventTask
  430. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  431. if err != nil {
  432. g.Log().Error(err)
  433. return myerrors.DbError("查询任务数据失败")
  434. }
  435. if entity.Id <= 0 {
  436. return myerrors.TipsError("任务数据不存在")
  437. }
  438. // 只有处理中(20)状态可以完成
  439. if !s.canComplete(entity.TaskStatus) {
  440. return myerrors.TipsError("只有处理中的任务可以完成")
  441. }
  442. // 构造更新数据
  443. data := g.Map{
  444. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusCompleted, // 已完成
  445. s.TaskDao.Columns.CompleteTime: gtime.Now(),
  446. s.TaskDao.Columns.ActualWorkHour: req.ActualWorkHour,
  447. s.TaskDao.Columns.Remark: req.Remark,
  448. }
  449. // 补齐审计字段
  450. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  451. // 预生成下游任务编号(避免事务内查询重复)
  452. var devTaskNo, testTaskNo string
  453. if entity.TaskType == opsdevmodel.TaskTypeReqReview {
  454. devTaskNo = s.generateTaskNo()
  455. }
  456. if entity.TaskType == opsdevmodel.TaskTypeFeatureDev {
  457. testTaskNo = s.generateTaskNo()
  458. }
  459. // 使用事务
  460. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  461. // 1. 更新任务状态
  462. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  463. if err != nil {
  464. g.Log().Error(err)
  465. return myerrors.DbError("完成任务失败")
  466. }
  467. // 2. 创建过程记录
  468. var handleContent string
  469. if entity.TaskType == opsdevmodel.TaskTypeFeatureTest && req.TestResult != "" {
  470. testResultText := "通过"
  471. if req.TestResult == "fail" {
  472. testResultText = "不通过"
  473. }
  474. handleContent = fmt.Sprintf("测试结果:%s <br/>测试时间:%s", testResultText, gtime.Now().Format("Y-m-d"))
  475. } else if entity.TaskType == opsdevmodel.TaskTypeSystemRelease && req.IsReleaseComplete {
  476. // 系统发版完成记录
  477. handleContent = "发版完成<br/>任务标题: " + entity.TaskTitle + "<br/>发版工时: " + gconv.String(req.ActualWorkHour) + "小时"
  478. if req.Remark != "" {
  479. handleContent += "<br/>发版说明: " + req.Remark
  480. }
  481. if len(req.DevTaskIds) > 0 {
  482. handleContent += "<br/>关联研发任务数: " + gconv.String(len(req.DevTaskIds)) + "个"
  483. }
  484. } else {
  485. // 普通任务完成记录
  486. handleContent = "完成任务<br/>任务标题: " + entity.TaskTitle + "<br/>实际工作量: " + gconv.String(req.ActualWorkHour) + "小时"
  487. }
  488. recordData := g.Map{
  489. s.RecordDao.Columns.TaskId: req.Id,
  490. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  491. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  492. s.RecordDao.Columns.HandleContent: handleContent,
  493. }
  494. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  495. result, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  496. if err != nil {
  497. g.Log().Error(err)
  498. return myerrors.DbError("新增过程记录失败")
  499. }
  500. recordId, _ := result.LastInsertId()
  501. // 3. 保存附件
  502. if len(req.Attachments) > 0 {
  503. for _, att := range req.Attachments {
  504. attData := g.Map{
  505. s.AttachmentDao.Columns.TaskId: req.Id,
  506. s.AttachmentDao.Columns.TaskRecordId: recordId,
  507. s.AttachmentDao.Columns.FileName: att.FileName,
  508. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  509. s.AttachmentDao.Columns.FileType: att.FileType,
  510. }
  511. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  512. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  513. if err != nil {
  514. g.Log().Error(err)
  515. return myerrors.DbError("保存附件信息失败")
  516. }
  517. }
  518. }
  519. // 4. 根据任务类型自动创建下游任务
  520. // 4.1 需求评审完成时,自动创建功能开发任务
  521. if entity.TaskType == opsdevmodel.TaskTypeReqReview {
  522. devTaskData := g.Map{
  523. s.TaskDao.Columns.TaskNo: devTaskNo,
  524. s.TaskDao.Columns.ProjectId: entity.ProjectId,
  525. s.TaskDao.Columns.ProjectName: entity.ProjectName,
  526. s.TaskDao.Columns.EventId: entity.EventId,
  527. s.TaskDao.Columns.EventType: entity.EventType,
  528. s.TaskDao.Columns.TaskTitle: entity.TaskTitle,
  529. s.TaskDao.Columns.TaskDesc: entity.TaskDesc,
  530. s.TaskDao.Columns.FunctionName: entity.FunctionName,
  531. s.TaskDao.Columns.TaskType: opsdevmodel.TaskTypeFeatureDev, // 功能开发
  532. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusTodo, // 待处理
  533. s.TaskDao.Columns.Priority: entity.Priority,
  534. s.TaskDao.Columns.TaskParentId: req.Id,
  535. }
  536. service.SetCreatedInfo(devTaskData, s.GetCxtUserId(), s.GetCxtUserName())
  537. devResult, err := s.TaskDao.TX(tx).Data(devTaskData).Insert()
  538. if err != nil {
  539. g.Log().Error(err)
  540. return myerrors.DbError("自动创建功能开发任务失败")
  541. }
  542. devTaskId, _ := devResult.LastInsertId()
  543. // 创建功能开发任务的过程记录
  544. devRecordData := g.Map{
  545. s.RecordDao.Columns.TaskId: devTaskId,
  546. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  547. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  548. s.RecordDao.Columns.HandleContent: "创建功能开发任务<br/>说明: 由需求评审任务自动创建",
  549. }
  550. service.SetCreatedInfo(devRecordData, s.GetCxtUserId(), s.GetCxtUserName())
  551. _, err = s.RecordDao.TX(tx).Data(devRecordData).Insert()
  552. if err != nil {
  553. g.Log().Error(err)
  554. return myerrors.DbError("新增功能开发任务过程记录失败")
  555. }
  556. }
  557. // 4.2 功能开发完成时,自动创建功能测试任务
  558. if entity.TaskType == opsdevmodel.TaskTypeFeatureDev {
  559. testTaskData := g.Map{
  560. s.TaskDao.Columns.TaskNo: testTaskNo,
  561. s.TaskDao.Columns.ProjectId: entity.ProjectId,
  562. s.TaskDao.Columns.ProjectName: entity.ProjectName,
  563. s.TaskDao.Columns.EventId: entity.EventId,
  564. s.TaskDao.Columns.EventType: entity.EventType,
  565. s.TaskDao.Columns.TaskTitle: entity.TaskTitle,
  566. s.TaskDao.Columns.TaskDesc: entity.TaskDesc,
  567. s.TaskDao.Columns.FunctionName: entity.FunctionName,
  568. s.TaskDao.Columns.TaskType: opsdevmodel.TaskTypeFeatureTest, // 功能测试
  569. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusTodo, // 待处理
  570. s.TaskDao.Columns.Priority: entity.Priority,
  571. s.TaskDao.Columns.TaskParentId: req.Id,
  572. }
  573. service.SetCreatedInfo(testTaskData, s.GetCxtUserId(), s.GetCxtUserName())
  574. testResult, err := s.TaskDao.TX(tx).Data(testTaskData).Insert()
  575. if err != nil {
  576. g.Log().Error(err)
  577. return myerrors.DbError("自动创建功能测试任务失败")
  578. }
  579. testTaskId, _ := testResult.LastInsertId()
  580. // 创建功能测试任务的过程记录
  581. testRecordData := g.Map{
  582. s.RecordDao.Columns.TaskId: testTaskId,
  583. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  584. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  585. s.RecordDao.Columns.HandleContent: "创建功能测试任务<br/>说明: 由功能开发任务自动创建",
  586. }
  587. service.SetCreatedInfo(testRecordData, s.GetCxtUserId(), s.GetCxtUserName())
  588. _, err = s.RecordDao.TX(tx).Data(testRecordData).Insert()
  589. if err != nil {
  590. g.Log().Error(err)
  591. return myerrors.DbError("新增功能测试任务过程记录失败")
  592. }
  593. }
  594. // 4.3 系统发版完成时,保存关联的研发任务到ops_event_task_release表
  595. if entity.TaskType == opsdevmodel.TaskTypeSystemRelease && req.IsReleaseComplete && len(req.DevTaskIds) > 0 {
  596. for _, devTaskId := range req.DevTaskIds {
  597. releaseData := g.Map{
  598. s.ReleaseDao.Columns.ReleaseTaskId: req.Id,
  599. s.ReleaseDao.Columns.DevTaskId: devTaskId,
  600. s.ReleaseDao.Columns.ProjectId: entity.ProjectId,
  601. }
  602. service.SetCreatedInfo(releaseData, s.GetCxtUserId(), s.GetCxtUserName())
  603. _, err := s.ReleaseDao.TX(tx).Data(releaseData).Insert()
  604. if err != nil {
  605. g.Log().Error(err)
  606. return myerrors.DbError("保存发版关联任务失败")
  607. }
  608. }
  609. }
  610. return nil
  611. })
  612. }
  613. // Pause 暂停任务
  614. func (s *OpsEventTaskService) Pause(req *opsdevmodel.OpsEventTaskPauseReq) error {
  615. // 校验数据是否存在
  616. var entity opsdevmodel.OpsEventTask
  617. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  618. if err != nil {
  619. g.Log().Error(err)
  620. return myerrors.DbError("查询任务数据失败")
  621. }
  622. if entity.Id <= 0 {
  623. return myerrors.TipsError("任务数据不存在")
  624. }
  625. // 只有处理中(20)状态可以暂停
  626. if !s.canPause(entity.TaskStatus) {
  627. return myerrors.TipsError("只有处理中的任务可以暂停")
  628. }
  629. // 构造更新数据
  630. data := g.Map{
  631. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusPaused, // 暂停
  632. }
  633. // 补齐审计字段
  634. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  635. // 使用事务
  636. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  637. // 1. 更新任务状态
  638. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  639. if err != nil {
  640. g.Log().Error(err)
  641. return myerrors.DbError("暂停任务失败")
  642. }
  643. // 2. 创建过程记录
  644. recordData := g.Map{
  645. s.RecordDao.Columns.TaskId: req.Id,
  646. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  647. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  648. s.RecordDao.Columns.HandleContent: "暂停任务<br/>暂停原因: " + req.Remark,
  649. }
  650. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  651. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  652. if err != nil {
  653. g.Log().Error(err)
  654. return myerrors.DbError("新增过程记录失败")
  655. }
  656. return nil
  657. })
  658. }
  659. // Block 阻塞任务
  660. func (s *OpsEventTaskService) Block(req *opsdevmodel.OpsEventTaskBlockReq) error {
  661. // 校验数据是否存在
  662. var entity opsdevmodel.OpsEventTask
  663. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  664. if err != nil {
  665. g.Log().Error(err)
  666. return myerrors.DbError("查询任务数据失败")
  667. }
  668. if entity.Id <= 0 {
  669. return myerrors.TipsError("任务数据不存在")
  670. }
  671. // 只有处理中(20)或暂停(25)状态可以阻塞
  672. if !s.canBlock(entity.TaskStatus) {
  673. return myerrors.TipsError("只有处理中或暂停状态的任务可以阻塞")
  674. }
  675. // 构造更新数据
  676. data := g.Map{
  677. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusBlocked, // 阻塞
  678. }
  679. // 补齐审计字段
  680. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  681. // 使用事务
  682. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  683. // 1. 更新任务状态
  684. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  685. if err != nil {
  686. g.Log().Error(err)
  687. return myerrors.DbError("阻塞任务失败")
  688. }
  689. // 2. 创建过程记录
  690. recordData := g.Map{
  691. s.RecordDao.Columns.TaskId: req.Id,
  692. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  693. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  694. s.RecordDao.Columns.HandleContent: "阻塞任务<br/>阻塞原因: " + req.Remark,
  695. }
  696. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  697. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  698. if err != nil {
  699. g.Log().Error(err)
  700. return myerrors.DbError("新增过程记录失败")
  701. }
  702. return nil
  703. })
  704. }
  705. // Cancel 作废任务
  706. func (s *OpsEventTaskService) Cancel(req *opsdevmodel.OpsEventTaskCancelReq) error {
  707. // 校验数据是否存在
  708. var entity opsdevmodel.OpsEventTask
  709. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.Id).Scan(&entity)
  710. if err != nil {
  711. g.Log().Error(err)
  712. return myerrors.DbError("查询任务数据失败")
  713. }
  714. if entity.Id <= 0 {
  715. return myerrors.TipsError("任务数据不存在")
  716. }
  717. // 已完成(30)的任务不能作废
  718. if !s.canCancel(entity.TaskStatus) {
  719. return myerrors.TipsError("已完成的任务不能作废")
  720. }
  721. // 构造更新数据
  722. data := g.Map{
  723. s.TaskDao.Columns.TaskStatus: opsdevmodel.TaskStatusCancelled, // 作废
  724. }
  725. // 补齐审计字段
  726. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  727. // 使用事务
  728. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  729. // 1. 更新任务状态
  730. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.TaskDao.Columns.Id, req.Id).Update()
  731. if err != nil {
  732. g.Log().Error(err)
  733. return myerrors.DbError("作废任务失败")
  734. }
  735. // 2. 创建过程记录
  736. recordData := g.Map{
  737. s.RecordDao.Columns.TaskId: req.Id,
  738. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  739. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  740. s.RecordDao.Columns.HandleContent: "作废任务<br/>作废原因: " + req.Remark,
  741. }
  742. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  743. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  744. if err != nil {
  745. g.Log().Error(err)
  746. return myerrors.DbError("新增过程记录失败")
  747. }
  748. return nil
  749. })
  750. }
  751. // GetRecords 获取任务过程记录列表(包含附件)
  752. func (s *OpsEventTaskService) GetRecords(req *opsdevmodel.OpsEventTaskRecordSearchReq) ([]*opsdevmodel.OpsEventTaskRecordWithAttachments, error) {
  753. var records []*opsdevmodel.OpsEventTaskRecord
  754. err := s.RecordDao.Where(s.RecordDao.Columns.TaskId, req.TaskId).
  755. Order(s.RecordDao.Columns.CreatedTime + " desc").
  756. Scan(&records)
  757. if err != nil {
  758. g.Log().Error(err)
  759. return nil, myerrors.DbError("查询过程记录失败")
  760. }
  761. result := make([]*opsdevmodel.OpsEventTaskRecordWithAttachments, 0, len(records))
  762. for _, record := range records {
  763. recordRsp := &opsdevmodel.OpsEventTaskRecordWithAttachments{
  764. OpsEventTaskRecord: *record,
  765. Attachments: []*opsdevmodel.OpsEventTaskAttachment{},
  766. }
  767. // 查询该记录关联的附件
  768. if record.Id > 0 {
  769. var attachments []*opsdevmodel.OpsEventTaskAttachment
  770. err := s.AttachmentDao.Where(s.AttachmentDao.Columns.TaskRecordId, record.Id).
  771. Scan(&attachments)
  772. if err != nil {
  773. g.Log().Error(err)
  774. } else {
  775. recordRsp.Attachments = attachments
  776. }
  777. }
  778. result = append(result, recordRsp)
  779. }
  780. return result, nil
  781. }
  782. // generateTaskNo 生成任务编号
  783. func (s *OpsEventTaskService) generateTaskNo() string {
  784. // 格式: TSK + 年月日 + 4位序号
  785. now := gtime.Now()
  786. prefix := "TSK" + now.Format("ymd")
  787. // 查询当天最大序号
  788. var maxNo string
  789. err := s.TaskDao.Where(s.TaskDao.Columns.TaskNo+" like ?", prefix+"%").Order(s.TaskDao.Columns.TaskNo + " desc").Fields(s.TaskDao.Columns.TaskNo).Scan(&maxNo)
  790. if err != nil {
  791. return prefix + "0001"
  792. }
  793. if maxNo == "" {
  794. return prefix + "0001"
  795. }
  796. // 提取序号并+1
  797. if len(maxNo) >= len(prefix)+4 {
  798. seq := maxNo[len(prefix):]
  799. var seqNum int
  800. gconv.Struct(seq, &seqNum)
  801. seqNum++
  802. return prefix + fmt.Sprintf("%04d", seqNum)
  803. }
  804. return prefix + "0001"
  805. }
  806. // 状态校验辅助方法
  807. func (s *OpsEventTaskService) canEdit(status string) bool {
  808. return status != opsdevmodel.TaskStatusCompleted && status != opsdevmodel.TaskStatusCancelled
  809. }
  810. func (s *OpsEventTaskService) canSchedule(status string) bool {
  811. return status == opsdevmodel.TaskStatusTodo
  812. }
  813. func (s *OpsEventTaskService) canStart(status string) bool {
  814. return status == opsdevmodel.TaskStatusTodo || status == opsdevmodel.TaskStatusPaused
  815. }
  816. func (s *OpsEventTaskService) canComplete(status string) bool {
  817. return status == opsdevmodel.TaskStatusProcessing
  818. }
  819. func (s *OpsEventTaskService) canPause(status string) bool {
  820. return status == opsdevmodel.TaskStatusProcessing
  821. }
  822. func (s *OpsEventTaskService) canBlock(status string) bool {
  823. return status == opsdevmodel.TaskStatusProcessing || status == opsdevmodel.TaskStatusPaused
  824. }
  825. func (s *OpsEventTaskService) canCancel(status string) bool {
  826. return status != opsdevmodel.TaskStatusCompleted
  827. }
  828. // getProjectName 根据项目ID获取项目名称
  829. func (s *OpsEventTaskService) getProjectName(projectId int) string {
  830. if projectId <= 0 {
  831. return ""
  832. }
  833. var project opsdevmodel.OpsDeliveryProject
  834. err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
  835. WherePri(s.ProjectDao.Columns.Id, projectId).
  836. Scan(&project)
  837. if err != nil {
  838. g.Log().Error(err)
  839. return ""
  840. }
  841. return project.ProjectName
  842. }
  843. // GetAttachments 获取任务附件列表
  844. func (s *OpsEventTaskService) GetAttachments(taskId int) ([]*opsdevmodel.OpsEventTaskAttachment, error) {
  845. var attachments []*opsdevmodel.OpsEventTaskAttachment
  846. err := s.AttachmentDao.Where(s.AttachmentDao.Columns.TaskId, taskId).
  847. Order(s.AttachmentDao.Columns.CreatedTime + " desc").
  848. Scan(&attachments)
  849. if err != nil {
  850. g.Log().Error(err)
  851. return nil, myerrors.DbError("查询附件失败")
  852. }
  853. return attachments, nil
  854. }
  855. // GetTaskReleaseList 根据发版任务ID查询关联的开发任务列表
  856. func (s *OpsEventTaskService) GetTaskReleaseList(req *opsdevmodel.OpsEventTaskReleaseListReq) ([]*opsdevmodel.OpsEventTaskReleaseRsp, error) {
  857. // 1. 查询关联表获取关联的任务ID列表
  858. var releaseList []*opsdevmodel.OpsEventTaskRelease
  859. err := s.ReleaseDao.Where(s.ReleaseDao.Columns.ReleaseTaskId, req.ReleaseTaskId).
  860. Scan(&releaseList)
  861. if err != nil {
  862. g.Log().Error(err)
  863. return nil, myerrors.DbError("查询发布版本关联记录失败")
  864. }
  865. if len(releaseList) == 0 {
  866. return []*opsdevmodel.OpsEventTaskReleaseRsp{}, nil
  867. }
  868. // 2. 提取开发任务ID列表
  869. devTaskIds := make([]int, 0, len(releaseList))
  870. for _, r := range releaseList {
  871. devTaskIds = append(devTaskIds, r.DevTaskId)
  872. }
  873. // 3. 查询任务详情
  874. var taskList []*opsdevmodel.OpsEventTask
  875. err = s.TaskDao.WhereIn(s.TaskDao.Columns.Id, devTaskIds).
  876. Scan(&taskList)
  877. if err != nil {
  878. g.Log().Error(err)
  879. return nil, myerrors.DbError("查询关联任务详情失败")
  880. }
  881. // 4. 构建响应数据
  882. result := make([]*opsdevmodel.OpsEventTaskReleaseRsp, 0, len(taskList))
  883. for _, task := range taskList {
  884. result = append(result, &opsdevmodel.OpsEventTaskReleaseRsp{
  885. Id: task.Id,
  886. DevTaskId: task.Id,
  887. TaskNo: task.TaskNo,
  888. TaskTitle: task.TaskTitle,
  889. TaskType: task.TaskType,
  890. TaskStatus: task.TaskStatus,
  891. OpsUserName: task.OpsUserName,
  892. ProjectId: task.ProjectId,
  893. CreatedTime: task.CreatedTime.Format("Y-m-d H:i:s"),
  894. })
  895. }
  896. return result, nil
  897. }
  898. // AddRecord 添加任务过程记录
  899. func (s *OpsEventTaskService) AddRecord(req *opsdevmodel.OpsEventTaskRecordAddReq) error {
  900. // 构造记录数据
  901. recordData := g.Map{
  902. s.RecordDao.Columns.TaskId: req.TaskId,
  903. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  904. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  905. s.RecordDao.Columns.HandleContent: req.HandleContent,
  906. }
  907. // 补齐审计字段
  908. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  909. // 使用事务控制
  910. return s.RecordDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  911. // 1. 创建过程记录
  912. result, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  913. if err != nil {
  914. g.Log().Error(err)
  915. return myerrors.DbError("添加过程记录失败")
  916. }
  917. // 获取新创建的记录ID
  918. recordId, err := result.LastInsertId()
  919. if err != nil {
  920. g.Log().Error(err)
  921. return myerrors.DbError("获取记录ID失败")
  922. }
  923. // 2. 如果有附件,保存附件
  924. if len(req.Attachments) > 0 {
  925. for _, att := range req.Attachments {
  926. attData := g.Map{
  927. s.AttachmentDao.Columns.TaskId: req.TaskId,
  928. s.AttachmentDao.Columns.TaskRecordId: int(recordId),
  929. s.AttachmentDao.Columns.FileName: att.FileName,
  930. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  931. s.AttachmentDao.Columns.FileType: att.FileType,
  932. }
  933. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  934. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  935. if err != nil {
  936. g.Log().Error(err)
  937. return myerrors.DbError("保存附件失败")
  938. }
  939. }
  940. }
  941. return nil
  942. })
  943. }
  944. // AddWorkHour 添加工时登记(累加actual_work_hour并记录过程)
  945. func (s *OpsEventTaskService) AddWorkHour(req *opsdevmodel.OpsEventTaskWorkHourAddReq) error {
  946. var entity opsdevmodel.OpsEventTask
  947. err := s.TaskDao.FieldsEx(s.TaskDao.Columns.DeletedTime).WherePri(s.TaskDao.Columns.Id, req.TaskId).Scan(&entity)
  948. if err != nil {
  949. g.Log().Error(err)
  950. return myerrors.DbError("查询任务数据失败")
  951. }
  952. if entity.Id <= 0 {
  953. return myerrors.TipsError("任务数据不存在")
  954. }
  955. if !s.canAddWorkHour(entity.TaskStatus) {
  956. return myerrors.TipsError("只有处理中的任务可以登记工时")
  957. }
  958. return s.TaskDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  959. workHourData := g.Map{
  960. s.WorkHourDao.Columns.TaskId: req.TaskId,
  961. s.WorkHourDao.Columns.OpsUserId: s.GetCxtUserId(),
  962. s.WorkHourDao.Columns.OpsUserName: s.GetCxtUserName(),
  963. s.WorkHourDao.Columns.ActualWorkDate: req.WorkDate,
  964. s.WorkHourDao.Columns.ActualWorkHour: req.ActualHour,
  965. s.WorkHourDao.Columns.Remark: req.Remark,
  966. }
  967. service.SetCreatedInfo(workHourData, s.GetCxtUserId(), s.GetCxtUserName())
  968. _, err := s.WorkHourDao.TX(tx).Data(workHourData).Insert()
  969. if err != nil {
  970. g.Log().Error(err)
  971. return myerrors.DbError("工时登记失败")
  972. }
  973. newActualWorkHour := entity.ActualWorkHour + req.ActualHour
  974. updateData := g.Map{
  975. s.TaskDao.Columns.ActualWorkHour: newActualWorkHour,
  976. }
  977. service.SetUpdatedInfo(updateData, s.GetCxtUserId(), s.GetCxtUserName())
  978. _, err = s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(updateData).WherePri(s.TaskDao.Columns.Id, req.TaskId).Update()
  979. if err != nil {
  980. g.Log().Error(err)
  981. return myerrors.DbError("更新实际工时失败")
  982. }
  983. handleContent := fmt.Sprintf("工时登记<br/>工作日期: %s<br/>实际工时: %s小时<br/>工作进展: %s",
  984. req.WorkDate, gconv.String(req.ActualHour), req.Remark)
  985. recordData := g.Map{
  986. s.RecordDao.Columns.TaskId: req.TaskId,
  987. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  988. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  989. s.RecordDao.Columns.HandleContent: handleContent,
  990. }
  991. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  992. _, err = s.RecordDao.TX(tx).Data(recordData).Insert()
  993. if err != nil {
  994. g.Log().Error(err)
  995. return myerrors.DbError("新增过程记录失败")
  996. }
  997. return nil
  998. })
  999. }
  1000. // GetWorkHourList 获取工时登记列表
  1001. func (s *OpsEventTaskService) GetWorkHourList(req *opsdevmodel.OpsEventTaskWorkHourListReq) ([]*opsdevmodel.OpsEventTaskWorkHourRsp, error) {
  1002. var entities []*opsdevmodel.OpsEventTaskWorkHour
  1003. err := s.WorkHourDao.FieldsEx(s.WorkHourDao.Columns.DeletedTime).
  1004. Where(s.WorkHourDao.Columns.TaskId, req.TaskId).
  1005. Order(s.WorkHourDao.Columns.CreatedTime + " desc").
  1006. Scan(&entities)
  1007. if err != nil {
  1008. g.Log().Error(err)
  1009. return nil, myerrors.DbError("查询工时登记列表失败")
  1010. }
  1011. list := make([]*opsdevmodel.OpsEventTaskWorkHourRsp, 0, len(entities))
  1012. for _, entity := range entities {
  1013. workDate := ""
  1014. if entity.ActualWorkDate != nil {
  1015. workDate = entity.ActualWorkDate.Format("Y-m-d")
  1016. }
  1017. createdTime := ""
  1018. if entity.CreatedTime != nil {
  1019. createdTime = entity.CreatedTime.Format("Y-m-d H:i:s")
  1020. }
  1021. list = append(list, &opsdevmodel.OpsEventTaskWorkHourRsp{
  1022. Id: entity.Id,
  1023. TaskId: entity.TaskId,
  1024. WorkDate: workDate,
  1025. ActualHour: entity.ActualWorkHour,
  1026. Remark: entity.Remark,
  1027. CreatedName: entity.CreatedName,
  1028. CreatedTime: createdTime,
  1029. })
  1030. }
  1031. return list, nil
  1032. }
  1033. // canAddWorkHour 检查是否可以登记工时(仅处理中状态)
  1034. func (s *OpsEventTaskService) canAddWorkHour(status string) bool {
  1035. return status == opsdevmodel.TaskStatusProcessing
  1036. }