operation.go 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414
  1. package opsdev
  2. import (
  3. "context"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "dashoo.cn/common_definition/comm_def"
  8. "dashoo.cn/opms_libary/myerrors"
  9. opsdevdao "dashoo.cn/opms_parent/app/dao/opsdev"
  10. opsdevmodel "dashoo.cn/opms_parent/app/model/opsdev"
  11. "dashoo.cn/opms_parent/app/service"
  12. "github.com/gogf/gf/database/gdb"
  13. "github.com/gogf/gf/frame/g"
  14. "github.com/gogf/gf/os/gtime"
  15. "github.com/gogf/gf/util/gconv"
  16. "github.com/gogf/gf/util/gvalid"
  17. )
  18. // eventTypeToDevTaskType 运维事件类型 → 研发任务类型映射
  19. var eventTypeToDevTaskType = map[string]string{
  20. "10": "20", // 操作咨询 → 功能开发
  21. "20": "21", // 数据处理 → 数据处理
  22. "30": "35", // 系统BUG → BUG
  23. "40": "20", // 功能调整 → 功能开发
  24. "50": "20", // 二开需求 → 功能开发
  25. "90": "20", // 其他问题 → 功能开发
  26. }
  27. type OperationService struct {
  28. *service.ContextService
  29. Dao *opsdevdao.OpsOperationEventDao
  30. RecordDao *opsdevdao.OpsOperationEventRecordDao
  31. AttachmentDao *opsdevdao.OpsOperationEventAttachmentDao
  32. DeliveryProjectAttachmentDao *opsdevdao.OpsDeliveryProjectEventAttachmentDao
  33. WorkHourDao *opsdevdao.OpsOperationWorkHourDao
  34. DeliveryProjectDao *opsdevdao.OpsDeliveryProjectDao
  35. }
  36. func NewOperationService(ctx context.Context) (svc *OperationService, err error) {
  37. svc = new(OperationService)
  38. if svc.ContextService, err = svc.Init(ctx); err != nil {
  39. return nil, err
  40. }
  41. svc.Dao = opsdevdao.NewOpsOperationEventDao(svc.Tenant)
  42. svc.RecordDao = opsdevdao.NewOpsOperationEventRecordDao(svc.Tenant)
  43. svc.AttachmentDao = opsdevdao.NewOpsOperationEventAttachmentDao(svc.Tenant)
  44. svc.DeliveryProjectAttachmentDao = opsdevdao.NewOpsDeliveryProjectEventAttachmentDao(svc.Tenant)
  45. svc.WorkHourDao = opsdevdao.NewOpsOperationWorkHourDao(svc.Tenant)
  46. svc.DeliveryProjectDao = opsdevdao.NewOpsDeliveryProjectDao(svc.Tenant)
  47. return svc, nil
  48. }
  49. // GetList 运维事件列表
  50. func (s *OperationService) GetList(req *opsdevmodel.OpsOperationEventSearchReq) (total int, list []*opsdevmodel.OpsOperationEvent, err error) {
  51. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).Where(s.Dao.Columns.EventStatus+" != ?", opsdevmodel.EventStatusClosed)
  52. if req.EventNo != "" {
  53. db = db.Where(s.Dao.Columns.EventNo, req.EventNo)
  54. }
  55. if req.EventTitle != "" {
  56. db = db.Where(s.Dao.Columns.EventTitle+" like ?", "%"+req.EventTitle+"%")
  57. }
  58. if req.EventStatus != "" {
  59. db = db.Where(s.Dao.Columns.EventStatus, req.EventStatus)
  60. }
  61. if req.EventType != "" {
  62. db = db.Where(s.Dao.Columns.EventType, req.EventType)
  63. }
  64. if req.PriorityLevel != "" {
  65. db = db.Where(s.Dao.Columns.PriorityLevel, req.PriorityLevel)
  66. }
  67. if req.IsBig != "" {
  68. db = db.Where(s.Dao.Columns.IsBig, req.IsBig)
  69. }
  70. if req.IsOps != "" {
  71. db = db.Where(s.Dao.Columns.IsOps, req.IsOps)
  72. }
  73. if req.CustName != "" {
  74. db = db.Where(s.Dao.Columns.CustName+" like ?", "%"+req.CustName+"%")
  75. }
  76. if req.OpsUserName != "" {
  77. db = db.Where(s.Dao.Columns.OpsUserName+" like ?", "%"+req.OpsUserName+"%")
  78. }
  79. if req.ContractName != "" {
  80. db = db.Where(s.Dao.Columns.ContractName+" like ?", "%"+req.ContractName+"%")
  81. }
  82. if req.ProductLine != "" {
  83. db = db.Where(s.Dao.Columns.ProductLine, req.ProductLine)
  84. }
  85. if req.BeginTime != "" {
  86. db = db.Where(s.Dao.Columns.CreatedTime+" >= ?", req.BeginTime)
  87. }
  88. if req.EndTime != "" {
  89. db = db.Where(s.Dao.Columns.CreatedTime+" <= ?", req.EndTime)
  90. }
  91. total, err = db.Count()
  92. if err != nil {
  93. g.Log().Error(err)
  94. return 0, nil, myerrors.DbError("查询运维事件数量失败")
  95. }
  96. pageNum, pageSize := req.GetPage()
  97. err = db.Page(pageNum, pageSize).Order(s.Dao.Columns.CreatedTime + " desc").Scan(&list)
  98. if err != nil {
  99. g.Log().Error(err)
  100. return 0, nil, myerrors.DbError("查询运维事件列表失败")
  101. }
  102. return total, list, nil
  103. }
  104. // GetEntityById 运维事件详情
  105. func (s *OperationService) GetEntityById(req *comm_def.IdReq) (detail *opsdevmodel.OpsOperationEvent, err error) {
  106. if req.Id <= 0 {
  107. return nil, myerrors.ValidError("参数有误!")
  108. }
  109. detail = new(opsdevmodel.OpsOperationEvent)
  110. err = s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.Id).Scan(detail)
  111. if err != nil {
  112. g.Log().Error(err)
  113. return nil, myerrors.DbError("查询运维事件详情失败")
  114. }
  115. if detail.Id <= 0 {
  116. return nil, myerrors.TipsError("运维事件不存在")
  117. }
  118. // 查询事件级附件(eventRecordId = 0 或 null)
  119. var attachments []*opsdevmodel.OpsOperationEventAttachment
  120. err = s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  121. Where(s.AttachmentDao.Columns.EventId, req.Id).
  122. Where("(event_record_id = 0 OR event_record_id IS NULL)").
  123. Scan(&attachments)
  124. if err != nil {
  125. g.Log().Error(err)
  126. }
  127. detail.Attachments = attachments
  128. return detail, nil
  129. }
  130. // Create 创建运维事件
  131. func (s *OperationService) Create(req *opsdevmodel.OpsOperationEventReq) (err error) {
  132. if err = gvalid.CheckStruct(s.Ctx, req, nil); err != nil {
  133. return myerrors.ValidError(err.Error())
  134. }
  135. data := gconv.Map(req)
  136. service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  137. data["eventNo"] = s.generateEventNo()
  138. data["eventStatus"] = opsdevmodel.EventStatusProcessing
  139. if _, ok := data["opsUserId"]; !ok || data["opsUserId"] == 0 {
  140. data["opsUserId"] = s.GetCxtUserId()
  141. }
  142. if _, ok := data["opsUserName"]; !ok || data["opsUserName"] == "" {
  143. data["opsUserName"] = s.GetCxtUserName()
  144. }
  145. if v, ok := data["feedbackDate"]; ok && v == "" {
  146. data["feedbackDate"] = gtime.Now()
  147. }
  148. if v, ok := data["assignTime"]; ok && v == "" {
  149. data["assignTime"] = gtime.Now()
  150. }
  151. delete(data, "completeTime")
  152. delete(data, "attachments")
  153. result, err := s.Dao.Data(data).Insert()
  154. if err != nil {
  155. g.Log().Error(err)
  156. return myerrors.DbError("创建运维事件失败")
  157. }
  158. eventId, err := result.LastInsertId()
  159. if err != nil {
  160. g.Log().Error(err)
  161. return myerrors.DbError("获取运维事件ID失败")
  162. }
  163. // 保存事件级附件
  164. for _, att := range req.Attachments {
  165. attData := g.Map{
  166. s.AttachmentDao.Columns.EventId: eventId,
  167. s.AttachmentDao.Columns.FileName: att.FileName,
  168. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  169. s.AttachmentDao.Columns.FileType: att.FileType,
  170. }
  171. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  172. _, err := s.AttachmentDao.Data(attData).Insert()
  173. if err != nil {
  174. g.Log().Error(err)
  175. }
  176. }
  177. return nil
  178. }
  179. // AddWorkHour 添加工时记录
  180. func (s *OperationService) AddWorkHour(req *opsdevmodel.OpsOperationWorkHourAddReq) error {
  181. if req.EventId <= 0 {
  182. return myerrors.ValidError("事件ID不能为空")
  183. }
  184. event := new(opsdevmodel.OpsOperationEvent)
  185. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.EventId).Scan(event)
  186. if err != nil {
  187. g.Log().Error(err)
  188. return myerrors.DbError("查询运维事件失败")
  189. }
  190. if event.Id <= 0 {
  191. return myerrors.TipsError("运维事件不存在")
  192. }
  193. if event.EventStatus != opsdevmodel.EventStatusProcessing && event.EventStatus != opsdevmodel.EventStatusProcessingNormal {
  194. return myerrors.TipsError("只有处理中的事件才能添加工作时长")
  195. }
  196. workDate, err := gtime.StrToTime(req.WorkDate)
  197. if err != nil {
  198. return myerrors.ValidError("工作日期格式错误")
  199. }
  200. today := gtime.Now().Format("Y-m-d")
  201. if workDate.Format("Y-m-d") > today {
  202. return myerrors.ValidError("工作日期不能晚于今天")
  203. }
  204. if req.WorkHour < 0.5 {
  205. return myerrors.ValidError("工作时长不能小于0.5小时")
  206. }
  207. opsUserId := s.GetCxtUserId()
  208. opsUserName := s.GetCxtUserName()
  209. return s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  210. workHourData := g.Map{
  211. s.WorkHourDao.Columns.EventId: req.EventId,
  212. s.WorkHourDao.Columns.OpsUserId: opsUserId,
  213. s.WorkHourDao.Columns.OpsUserName: opsUserName,
  214. s.WorkHourDao.Columns.WorkDate: req.WorkDate,
  215. s.WorkHourDao.Columns.WorkHour: req.WorkHour,
  216. s.WorkHourDao.Columns.Remark: req.Remark,
  217. }
  218. service.SetCreatedInfo(workHourData, opsUserId, opsUserName)
  219. _, err := tx.Model(s.WorkHourDao.Table).Data(workHourData).Insert()
  220. if err != nil {
  221. g.Log().Error(err)
  222. return myerrors.DbError("添加工作时长失败")
  223. }
  224. _, err = tx.Exec("UPDATE ops_operation_event SET total_work_hour = total_work_hour + ? WHERE id = ?", req.WorkHour, req.EventId)
  225. if err != nil {
  226. g.Log().Error(err)
  227. return myerrors.DbError("更新事件总工作时长失败")
  228. }
  229. return nil
  230. })
  231. }
  232. // DeleteAttachment 删除附件
  233. func (s *OperationService) DeleteAttachment(req *comm_def.IdReq) error {
  234. if req.Id <= 0 {
  235. return myerrors.ValidError("参数有误!")
  236. }
  237. attachment := new(opsdevmodel.OpsOperationEventAttachment)
  238. err := s.AttachmentDao.WherePri(s.AttachmentDao.Columns.Id, req.Id).Scan(attachment)
  239. if err != nil {
  240. g.Log().Error(err)
  241. return myerrors.DbError("查询附件失败")
  242. }
  243. if attachment.Id <= 0 {
  244. return myerrors.TipsError("附件不存在")
  245. }
  246. _, err = s.AttachmentDao.WherePri(s.AttachmentDao.Columns.Id, req.Id).Data(g.Map{
  247. s.AttachmentDao.Columns.DeletedTime: gtime.Now(),
  248. s.AttachmentDao.Columns.UpdatedBy: s.GetCxtUserId(),
  249. s.AttachmentDao.Columns.UpdatedName: s.GetCxtUserName(),
  250. }).Update()
  251. if err != nil {
  252. g.Log().Error(err)
  253. return myerrors.DbError("删除附件失败")
  254. }
  255. return nil
  256. }
  257. // GetHistoryList 运维历史列表
  258. func (s *OperationService) GetHistoryList(req *opsdevmodel.OpsOperationEventHistorySearchReq) (total int, list []*opsdevmodel.OpsOperationEvent, err error) {
  259. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime)
  260. if !req.IncludeClosed {
  261. db = db.Where(s.Dao.Columns.EventStatus, opsdevmodel.EventStatusClosed)
  262. }
  263. if req.ScopeType == "my" {
  264. db = db.Where(s.Dao.Columns.OpsUserId, s.GetCxtUserId())
  265. }
  266. if req.EventTitle != "" {
  267. db = db.Where(s.Dao.Columns.EventTitle+" like ?", "%"+req.EventTitle+"%")
  268. }
  269. if req.CustName != "" {
  270. db = db.Where(s.Dao.Columns.CustName+" like ?", "%"+req.CustName+"%")
  271. }
  272. if req.FeedbackReporter != "" {
  273. db = db.Where(s.Dao.Columns.FeedbackReporter+" like ?", "%"+req.FeedbackReporter+"%")
  274. }
  275. if req.OpsUserName != "" {
  276. db = db.Where(s.Dao.Columns.OpsUserName+" like ?", "%"+req.OpsUserName+"%")
  277. }
  278. if req.BeginTime != "" {
  279. db = db.Where(s.Dao.Columns.FeedbackDate+" >= ?", req.BeginTime)
  280. }
  281. if req.EndTime != "" {
  282. db = db.Where(s.Dao.Columns.FeedbackDate+" <= ?", req.EndTime)
  283. }
  284. if req.ContractId != "" {
  285. db = db.Where(s.Dao.Columns.ContractId, req.ContractId)
  286. }
  287. total, err = db.Count()
  288. if err != nil {
  289. g.Log().Error(err)
  290. return 0, nil, myerrors.DbError("查询运维历史数量失败")
  291. }
  292. pageNum, pageSize := req.GetPage()
  293. err = db.Page(pageNum, pageSize).Order(s.Dao.Columns.CompleteTime + " desc").Scan(&list)
  294. if err != nil {
  295. g.Log().Error(err)
  296. return 0, nil, myerrors.DbError("查询运维历史列表失败")
  297. }
  298. return total, list, nil
  299. }
  300. // Export 导出运维历史
  301. func (s *OperationService) Export(ctx context.Context, req *opsdevmodel.OpsOperationEventHistoryExport) (content *opsdevmodel.OpsOperationEventHistoryExportContent, err error) {
  302. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime)
  303. if !req.IncludeClosed {
  304. db = db.Where(s.Dao.Columns.EventStatus, opsdevmodel.EventStatusClosed)
  305. }
  306. if req.ScopeType == "my" {
  307. db = db.Where(s.Dao.Columns.OpsUserId, s.GetCxtUserId())
  308. }
  309. if req.EventTitle != "" {
  310. db = db.Where(s.Dao.Columns.EventTitle+" like ?", "%"+req.EventTitle+"%")
  311. }
  312. if req.CustName != "" {
  313. db = db.Where(s.Dao.Columns.CustName+" like ?", "%"+req.CustName+"%")
  314. }
  315. if req.FeedbackReporter != "" {
  316. db = db.Where(s.Dao.Columns.FeedbackReporter+" like ?", "%"+req.FeedbackReporter+"%")
  317. }
  318. if req.OpsUserName != "" {
  319. db = db.Where(s.Dao.Columns.OpsUserName+" like ?", "%"+req.OpsUserName+"%")
  320. }
  321. if req.BeginTime != "" {
  322. db = db.Where(s.Dao.Columns.FeedbackDate+" >= ?", req.BeginTime)
  323. }
  324. if req.EndTime != "" {
  325. db = db.Where(s.Dao.Columns.FeedbackDate+" <= ?", req.EndTime)
  326. }
  327. if req.ContractId != "" {
  328. db = db.Where(s.Dao.Columns.ContractId, req.ContractId)
  329. }
  330. var list []*opsdevmodel.OpsOperationEvent
  331. err = db.Order(s.Dao.Columns.CompleteTime + " desc").Scan(&list)
  332. if err != nil {
  333. g.Log().Error(err)
  334. return nil, myerrors.DbError("查询运维历史失败")
  335. }
  336. // 获取所有事件ID
  337. var eventIds []int
  338. for _, item := range list {
  339. eventIds = append(eventIds, item.Id)
  340. }
  341. // 获取所有事件的处理记录
  342. var records []*opsdevmodel.OpsOperationEventRecord
  343. if len(eventIds) > 0 {
  344. err = s.RecordDao.WhereIn(s.RecordDao.Columns.EventId, eventIds).Order(s.RecordDao.Columns.HandleDate + " desc").Scan(&records)
  345. if err != nil {
  346. g.Log().Error(err)
  347. return nil, myerrors.DbError("查询处理记录失败")
  348. }
  349. }
  350. // 构建记录Map
  351. recordMap := make(map[int][]*opsdevmodel.OpsOperationEventRecord)
  352. for _, record := range records {
  353. recordMap[record.EventId] = append(recordMap[record.EventId], record)
  354. }
  355. // 构建状态Map
  356. statusMap := map[string]string{
  357. opsdevmodel.EventStatusPending: "待处理",
  358. opsdevmodel.EventStatusProcessing: "处理中(重点)",
  359. opsdevmodel.EventStatusProcessingNormal: "处理中(普通)",
  360. opsdevmodel.EventStatusTransfer: "转研发",
  361. opsdevmodel.EventStatusSuspended: "挂起",
  362. opsdevmodel.EventStatusClosed: "已关闭",
  363. }
  364. // 构建操作类型Map
  365. operateTypeMap := map[string]string{
  366. opsdevmodel.OperateTypeProcess: "处理",
  367. opsdevmodel.OperateTypeResume: "转处理",
  368. opsdevmodel.OperateTypeTransfer: "转研发",
  369. opsdevmodel.OperateTypeSuspend: "挂起",
  370. opsdevmodel.OperateTypeClose: "关闭",
  371. }
  372. // 构建处理结果Map
  373. handleResultMap := map[string]string{
  374. opsdevmodel.HandleResultResolved: "已解决",
  375. opsdevmodel.HandleResultPartially: "部分解决",
  376. opsdevmodel.HandleResultUnresolved: "未解决",
  377. }
  378. // 构建导出数据
  379. exportDataList := make([]map[string]interface{}, 0)
  380. for _, item := range list {
  381. // 构建处理过程
  382. processBuilder := ""
  383. if itemRecords, ok := recordMap[item.Id]; ok && len(itemRecords) > 0 {
  384. for i, record := range itemRecords {
  385. // 处理时间(仅年月)
  386. handleDate := ""
  387. if record.HandleDate != nil {
  388. handleDate = record.HandleDate.Format("Y-m-d H:i:s")
  389. }
  390. // 操作类型描述
  391. operateTypeStr := operateTypeMap[record.OperateType]
  392. if operateTypeStr == "" {
  393. operateTypeStr = record.OperateType
  394. }
  395. // 处理结果描述
  396. handleResultStr := handleResultMap[record.HandleResult]
  397. if handleResultStr == "" {
  398. handleResultStr = record.HandleResult
  399. }
  400. // 清理处理内容中的HTML标签
  401. cleanContent := cleanHtmlTags(record.HandleContent)
  402. recordStr := fmt.Sprintf("%s-%s-%s-%s-%s", handleDate, record.HandleUserName, operateTypeStr, handleResultStr, cleanContent)
  403. if i > 0 {
  404. processBuilder += "\n" + recordStr
  405. } else {
  406. processBuilder = recordStr
  407. }
  408. }
  409. }
  410. closedTime := ""
  411. if item.CompleteTime != nil {
  412. closedTime = item.CompleteTime.Format("Y-m-d")
  413. }
  414. feedbackDate := ""
  415. if item.FeedbackDate != nil {
  416. feedbackDate = item.FeedbackDate.Format("Y-m-d")
  417. }
  418. exportData := map[string]interface{}{
  419. "eventNo": item.EventNo,
  420. "eventTitle": item.EventTitle,
  421. "eventStatus": statusMap[item.EventStatus],
  422. "eventType": item.EventType,
  423. "custName": item.CustName,
  424. "opsUserName": item.OpsUserName,
  425. "feedbackDate": feedbackDate,
  426. "priorityLevel": item.PriorityLevel,
  427. "handleProcess": processBuilder,
  428. "closedTime": closedTime,
  429. }
  430. exportDataList = append(exportDataList, exportData)
  431. }
  432. // 调用公共导出方法
  433. exportContent := new(opsdevmodel.OpsOperationEventHistoryExportContent)
  434. contentBase64, err := service.CommonExportExcel(ctx, "运维历史", opsdevmodel.OpsOperationEventHistoryExportData{}, exportDataList)
  435. if err != nil {
  436. g.Log().Error(err)
  437. return nil, myerrors.DbError("导出运维历史失败")
  438. }
  439. exportContent.Content = contentBase64
  440. return exportContent, nil
  441. }
  442. func (s *OperationService) ExportNonClosed(ctx context.Context, req *opsdevmodel.OpsOperationEventExport) (content *opsdevmodel.OpsOperationEventHistoryExportContent, err error) {
  443. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).Where(s.Dao.Columns.EventStatus+" != ?", opsdevmodel.EventStatusClosed)
  444. var list []*opsdevmodel.OpsOperationEvent
  445. err = db.Order(s.Dao.Columns.FeedbackDate + " desc").Scan(&list)
  446. if err != nil {
  447. g.Log().Error(err)
  448. return nil, myerrors.DbError("查询运维事件失败")
  449. }
  450. var eventIds []int
  451. for _, item := range list {
  452. eventIds = append(eventIds, item.Id)
  453. }
  454. var records []*opsdevmodel.OpsOperationEventRecord
  455. if len(eventIds) > 0 {
  456. err = s.RecordDao.WhereIn(s.RecordDao.Columns.EventId, eventIds).Order(s.RecordDao.Columns.HandleDate + " desc").Scan(&records)
  457. if err != nil {
  458. g.Log().Error(err)
  459. return nil, myerrors.DbError("查询处理记录失败")
  460. }
  461. }
  462. recordMap := make(map[int][]*opsdevmodel.OpsOperationEventRecord)
  463. for _, record := range records {
  464. recordMap[record.EventId] = append(recordMap[record.EventId], record)
  465. }
  466. statusMap := map[string]string{
  467. opsdevmodel.EventStatusPending: "待处理",
  468. opsdevmodel.EventStatusProcessing: "处理中(重点)",
  469. opsdevmodel.EventStatusProcessingNormal: "处理中(普通)",
  470. opsdevmodel.EventStatusTransfer: "转研发",
  471. opsdevmodel.EventStatusSuspended: "挂起",
  472. opsdevmodel.EventStatusClosed: "已关闭",
  473. }
  474. operateTypeMap := map[string]string{
  475. opsdevmodel.OperateTypeProcess: "处理",
  476. opsdevmodel.OperateTypeResume: "转处理",
  477. opsdevmodel.OperateTypeTransfer: "转研发",
  478. opsdevmodel.OperateTypeSuspend: "挂起",
  479. opsdevmodel.OperateTypeClose: "关闭",
  480. }
  481. handleResultMap := map[string]string{
  482. opsdevmodel.HandleResultResolved: "已解决",
  483. opsdevmodel.HandleResultPartially: "部分解决",
  484. opsdevmodel.HandleResultUnresolved: "未解决",
  485. }
  486. exportDataList := make([]map[string]interface{}, 0)
  487. for _, item := range list {
  488. processBuilder := ""
  489. if itemRecords, ok := recordMap[item.Id]; ok && len(itemRecords) > 0 {
  490. for i, record := range itemRecords {
  491. handleDate := ""
  492. if record.HandleDate != nil {
  493. handleDate = record.HandleDate.Format("Y-m-d H:i:s")
  494. }
  495. operateTypeStr := operateTypeMap[record.OperateType]
  496. if operateTypeStr == "" {
  497. operateTypeStr = record.OperateType
  498. }
  499. handleResultStr := handleResultMap[record.HandleResult]
  500. if handleResultStr == "" {
  501. handleResultStr = record.HandleResult
  502. }
  503. cleanContent := cleanHtmlTags(record.HandleContent)
  504. recordStr := fmt.Sprintf("%s-%s-%s-%s-%s", handleDate, record.HandleUserName, operateTypeStr, handleResultStr, cleanContent)
  505. if i > 0 {
  506. processBuilder += "\n" + recordStr
  507. } else {
  508. processBuilder = recordStr
  509. }
  510. }
  511. }
  512. feedbackDate := ""
  513. if item.FeedbackDate != nil {
  514. feedbackDate = item.FeedbackDate.Format("Y-m-d")
  515. }
  516. exportData := map[string]interface{}{
  517. "eventNo": item.EventNo,
  518. "eventTitle": item.EventTitle,
  519. "eventStatus": statusMap[item.EventStatus],
  520. "eventType": item.EventType,
  521. "custName": item.CustName,
  522. "opsUserName": item.OpsUserName,
  523. "feedbackDate": feedbackDate,
  524. "priorityLevel": item.PriorityLevel,
  525. "handleProcess": processBuilder,
  526. }
  527. exportDataList = append(exportDataList, exportData)
  528. }
  529. exportContent := new(opsdevmodel.OpsOperationEventHistoryExportContent)
  530. contentBase64, err := service.CommonExportExcel(ctx, "运维事件", opsdevmodel.OpsOperationEventExportData{}, exportDataList)
  531. if err != nil {
  532. g.Log().Error(err)
  533. return nil, myerrors.DbError("导出运维事件失败")
  534. }
  535. exportContent.Content = contentBase64
  536. return exportContent, nil
  537. }
  538. func cleanHtmlTags(content string) string {
  539. if content == "" {
  540. return ""
  541. }
  542. imgPattern := regexp.MustCompile(`src=["']([^"']+)["']`)
  543. imgMatches := imgPattern.FindAllStringSubmatch(content, -1)
  544. seen := make(map[string]bool)
  545. var imgUrls []string
  546. for _, match := range imgMatches {
  547. if len(match) > 1 && !seen[match[1]] {
  548. seen[match[1]] = true
  549. imgUrls = append(imgUrls, match[1])
  550. }
  551. }
  552. htmlTagPattern := regexp.MustCompile(`<[^>]*>`)
  553. result := htmlTagPattern.ReplaceAllString(content, "")
  554. result = strings.ReplaceAll(result, "&nbsp;", " ")
  555. result = strings.ReplaceAll(result, "&amp;", "&")
  556. result = strings.ReplaceAll(result, "&lt;", "<")
  557. result = strings.ReplaceAll(result, "&gt;", ">")
  558. result = strings.ReplaceAll(result, "&quot;", "\"")
  559. result = strings.ReplaceAll(result, "&#39;", "'")
  560. result = strings.TrimSpace(result)
  561. if len(imgUrls) > 0 {
  562. result += "\n图片: "
  563. for i, url := range imgUrls {
  564. if i > 0 {
  565. result += ", "
  566. }
  567. result += url
  568. }
  569. }
  570. return result
  571. }
  572. // GetWorkHourList 获取工时登记列表
  573. func (s *OperationService) GetWorkHourList(req *opsdevmodel.OpsOperationWorkHourListReq) ([]*opsdevmodel.OpsOperationWorkHourListRsp, error) {
  574. var entities []*opsdevmodel.OpsOperationWorkHour
  575. err := s.WorkHourDao.FieldsEx(s.WorkHourDao.Columns.DeletedTime).
  576. Where(s.WorkHourDao.Columns.EventId, req.EventId).
  577. Order(s.WorkHourDao.Columns.CreatedTime + " desc").
  578. Scan(&entities)
  579. if err != nil {
  580. g.Log().Error(err)
  581. return nil, myerrors.DbError("查询工时登记列表失败")
  582. }
  583. list := make([]*opsdevmodel.OpsOperationWorkHourListRsp, 0, len(entities))
  584. for _, entity := range entities {
  585. workDate := ""
  586. if entity.WorkDate != nil {
  587. workDate = entity.WorkDate.Format("Y-m-d")
  588. }
  589. createdTime := ""
  590. if entity.CreatedTime != nil {
  591. createdTime = entity.CreatedTime.Format("Y-m-d H:i:s")
  592. }
  593. list = append(list, &opsdevmodel.OpsOperationWorkHourListRsp{
  594. Id: entity.Id,
  595. WorkDate: workDate,
  596. WorkHour: entity.WorkHour,
  597. Remark: entity.Remark,
  598. CreatedName: entity.CreatedName,
  599. CreatedTime: createdTime,
  600. })
  601. }
  602. return list, nil
  603. }
  604. // generateEventNo 生成运维事件编号
  605. func (s *OperationService) generateEventNo() string {
  606. now := gtime.Now()
  607. prefix := "OPS" + now.Format("Ymd")
  608. seqVal, err := s.Dao.DB.GetValue("SELECT next_day_reset_val('event_no_seq')")
  609. if err == nil {
  610. return prefix + fmt.Sprintf("%04d", seqVal.Int())
  611. }
  612. var maxNoResult struct {
  613. EventNo string
  614. }
  615. err = s.Dao.Where("event_no like ?", prefix+"%").
  616. Order("event_no desc").
  617. Fields("event_no").
  618. Scan(&maxNoResult)
  619. if err != nil || maxNoResult.EventNo == "" {
  620. return prefix + "0001"
  621. }
  622. maxNoStr := maxNoResult.EventNo
  623. if len(maxNoStr) >= len(prefix)+4 {
  624. seq := maxNoStr[len(prefix):]
  625. seqNum := gconv.Int(seq)
  626. seqNum++
  627. return prefix + fmt.Sprintf("%04d", seqNum)
  628. }
  629. return prefix + "0001"
  630. }
  631. // Process 处理运维事件(开始/转处理/转研发/挂起/关闭)
  632. func (s *OperationService) Process(ctx context.Context, req *opsdevmodel.OpsOperationEventProcessReq) error {
  633. event := new(opsdevmodel.OpsOperationEvent)
  634. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, int(req.Id)).Scan(event)
  635. if err != nil {
  636. g.Log().Error(err)
  637. return myerrors.DbError("查询运维事件失败")
  638. }
  639. if event.Id <= 0 {
  640. return myerrors.TipsError("运维事件不存在")
  641. }
  642. opsUserId := s.GetCxtUserId()
  643. opsUserName := s.GetCxtUserName()
  644. switch req.OperateType {
  645. case opsdevmodel.OperateTypeClose:
  646. return s.processClose(req, event, opsUserId, opsUserName)
  647. default:
  648. return s.processNormal(req, event, opsUserId, opsUserName)
  649. }
  650. }
  651. // processClose 关单处理(含工时调整)
  652. func (s *OperationService) processClose(req *opsdevmodel.OpsOperationEventProcessReq, event *opsdevmodel.OpsOperationEvent, opsUserId int, opsUserName string) error {
  653. return s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  654. handleDate := gtime.Now()
  655. recordData := g.Map{
  656. s.RecordDao.Columns.EventId: req.Id,
  657. s.RecordDao.Columns.HandleUserId: opsUserId,
  658. s.RecordDao.Columns.HandleUserName: opsUserName,
  659. s.RecordDao.Columns.HandleContent: req.HandleContent,
  660. s.RecordDao.Columns.HandleResult: req.HandleResult,
  661. s.RecordDao.Columns.HandleDate: handleDate,
  662. s.RecordDao.Columns.OperateType: req.OperateType,
  663. }
  664. service.SetCreatedInfo(recordData, opsUserId, opsUserName)
  665. _, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  666. if err != nil {
  667. g.Log().Error(err)
  668. return myerrors.DbError("创建处理记录失败")
  669. }
  670. eventUpdateData := g.Map{
  671. s.Dao.Columns.EventStatus: opsdevmodel.EventStatusClosed,
  672. s.Dao.Columns.CompleteTime: gtime.Now(),
  673. s.Dao.Columns.CompleteDesc: req.HandleContent,
  674. s.Dao.Columns.EventResult: req.HandleResult,
  675. }
  676. service.SetUpdatedInfo(eventUpdateData, opsUserId, opsUserName)
  677. if req.AdjustWorkHour > 0 {
  678. currentTotal := event.TotalWorkHour
  679. if req.AdjustWorkHour < currentTotal {
  680. return myerrors.ValidError("工时只能增加不能减少")
  681. }
  682. if req.AdjustWorkHour > currentTotal {
  683. delta := req.AdjustWorkHour - currentTotal
  684. workHourData := g.Map{
  685. s.WorkHourDao.Columns.EventId: int(req.Id),
  686. s.WorkHourDao.Columns.OpsUserId: opsUserId,
  687. s.WorkHourDao.Columns.OpsUserName: opsUserName,
  688. s.WorkHourDao.Columns.WorkDate: gtime.Now(),
  689. s.WorkHourDao.Columns.WorkHour: delta,
  690. s.WorkHourDao.Columns.Remark: "关单调整",
  691. }
  692. service.SetCreatedInfo(workHourData, opsUserId, opsUserName)
  693. _, err := s.WorkHourDao.TX(tx).Data(workHourData).Insert()
  694. if err != nil {
  695. g.Log().Error(err)
  696. return myerrors.DbError("创建工时调整记录失败")
  697. }
  698. eventUpdateData[s.Dao.Columns.TotalWorkHour] = req.AdjustWorkHour
  699. }
  700. }
  701. _, err = s.Dao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(eventUpdateData).WherePri(s.Dao.Columns.Id, req.Id).Update()
  702. if err != nil {
  703. g.Log().Error(err)
  704. return myerrors.DbError("关闭事件失败")
  705. }
  706. return nil
  707. })
  708. }
  709. // getDevTaskType 根据运维事件类型获取对应的研发任务类型,未匹配时兜底返回功能开发(20)
  710. func (s *OperationService) getDevTaskType(eventType string) string {
  711. if taskType, ok := eventTypeToDevTaskType[eventType]; ok {
  712. return taskType
  713. }
  714. return opsdevmodel.TaskTypeFeatureDev
  715. }
  716. // createDevTaskFromEvent 根据运维事件自动创建研发任务(在processNormal事务完成后调用)
  717. func (s *OperationService) createDevTaskFromEvent(event *opsdevmodel.OpsOperationEvent) {
  718. var attachments []*opsdevmodel.OpsOperationEventAttachment
  719. err := s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  720. Where(s.AttachmentDao.Columns.EventId, event.Id).
  721. Scan(&attachments)
  722. if err != nil {
  723. g.Log().Errorf("createDevTaskFromEvent: query attachments failed, eventId=%d, err=%v", event.Id, err)
  724. }
  725. taskAttachments := make([]opsdevmodel.Attachment, 0, len(attachments))
  726. for _, att := range attachments {
  727. taskAttachments = append(taskAttachments, opsdevmodel.Attachment{
  728. FileName: att.FileName,
  729. FileUrl: att.FileUrl,
  730. FileType: att.FileType,
  731. })
  732. }
  733. taskType := s.getDevTaskType(event.EventType)
  734. projectId := 0
  735. if event.ContractId > 0 {
  736. var project opsdevmodel.OpsDeliveryProject
  737. err := s.DeliveryProjectDao.FieldsEx(s.DeliveryProjectDao.Columns.DeletedTime).
  738. Where(s.DeliveryProjectDao.Columns.ContractId, event.ContractId).
  739. OrderDesc(s.DeliveryProjectDao.Columns.Id).
  740. Limit(1).
  741. Scan(&project)
  742. if err != nil {
  743. g.Log().Warningf("createDevTaskFromEvent: query project by contractId=%d failed, err=%v", event.ContractId, err)
  744. } else if project.Id > 0 {
  745. projectId = project.Id
  746. } else {
  747. g.Log().Warningf("createDevTaskFromEvent: no project found for contractId=%d", event.ContractId)
  748. }
  749. }
  750. taskReq := &opsdevmodel.OpsEventTaskAddReq{
  751. ProjectId: projectId,
  752. TaskTitle: event.EventTitle,
  753. TaskDesc: event.EventDesc,
  754. TaskType: taskType,
  755. Priority: event.PriorityLevel,
  756. OpsUserId: event.OpsUserId,
  757. OpsUserName: event.OpsUserName,
  758. EventId: event.Id,
  759. EventType: opsdevmodel.EventTypeOps,
  760. Attachments: taskAttachments,
  761. }
  762. taskSvc, err := NewOpsEventTaskService(s.Ctx)
  763. if err != nil {
  764. g.Log().Errorf("createDevTaskFromEvent: init task service failed, eventId=%d, err=%v", event.Id, err)
  765. return
  766. }
  767. if err := taskSvc.Create(taskReq); err != nil {
  768. g.Log().Errorf("createDevTaskFromEvent: create task failed, eventId=%d, eventType=%s, taskType=%s, err=%v",
  769. event.Id, event.EventType, taskType, err)
  770. }
  771. }
  772. // processNormal 非关单处理(开始/转处理/转研发/挂起)
  773. func (s *OperationService) processNormal(req *opsdevmodel.OpsOperationEventProcessReq, event *opsdevmodel.OpsOperationEvent, opsUserId int, opsUserName string) error {
  774. handleDate := gtime.Now()
  775. recordData := g.Map{
  776. s.RecordDao.Columns.EventId: req.Id,
  777. s.RecordDao.Columns.HandleUserId: opsUserId,
  778. s.RecordDao.Columns.HandleUserName: opsUserName,
  779. s.RecordDao.Columns.HandleContent: req.HandleContent,
  780. s.RecordDao.Columns.HandleResult: req.HandleResult,
  781. s.RecordDao.Columns.HandleDate: handleDate,
  782. s.RecordDao.Columns.OperateType: req.OperateType,
  783. }
  784. service.SetCreatedInfo(recordData, opsUserId, opsUserName)
  785. newStatus := ""
  786. switch req.OperateType {
  787. case opsdevmodel.OperateTypeProcess:
  788. newStatus = opsdevmodel.EventStatusProcessing
  789. case opsdevmodel.OperateTypeResume:
  790. newStatus = opsdevmodel.EventStatusProcessing
  791. case opsdevmodel.OperateTypeTransfer:
  792. newStatus = opsdevmodel.EventStatusTransfer
  793. case opsdevmodel.OperateTypeSuspend:
  794. newStatus = opsdevmodel.EventStatusSuspended
  795. }
  796. err := s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  797. _, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  798. if err != nil {
  799. g.Log().Error(err)
  800. return myerrors.DbError("创建处理记录失败")
  801. }
  802. if newStatus != "" {
  803. updateData := g.Map{
  804. s.Dao.Columns.EventStatus: newStatus,
  805. }
  806. service.SetUpdatedInfo(updateData, opsUserId, opsUserName)
  807. _, err := s.Dao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(updateData).WherePri(s.Dao.Columns.Id, req.Id).Update()
  808. if err != nil {
  809. g.Log().Error(err)
  810. return myerrors.DbError("更新事件状态失败")
  811. }
  812. }
  813. return nil
  814. })
  815. if err != nil {
  816. return err
  817. }
  818. if req.OperateType == opsdevmodel.OperateTypeTransfer {
  819. s.createDevTaskFromEvent(event)
  820. }
  821. return nil
  822. }
  823. // UpdateById 更新运维事件
  824. func (s *OperationService) UpdateById(req *opsdevmodel.UpdateOpsOperationEventReq) error {
  825. if req.Id <= 0 {
  826. return myerrors.ValidError("参数有误!")
  827. }
  828. event := new(opsdevmodel.OpsOperationEvent)
  829. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.Id).Scan(event)
  830. if err != nil {
  831. g.Log().Error(err)
  832. return myerrors.DbError("查询运维事件失败")
  833. }
  834. if event.Id <= 0 {
  835. return myerrors.TipsError("运维事件不存在")
  836. }
  837. data := g.Map{}
  838. if req.EventTitle != "" {
  839. data[s.Dao.Columns.EventTitle] = req.EventTitle
  840. }
  841. if req.EventDesc != "" {
  842. data[s.Dao.Columns.EventDesc] = req.EventDesc
  843. }
  844. if req.EventType != "" {
  845. data[s.Dao.Columns.EventType] = req.EventType
  846. }
  847. if req.EventStatus != "" {
  848. data[s.Dao.Columns.EventStatus] = req.EventStatus
  849. }
  850. if req.ContractId > 0 {
  851. data[s.Dao.Columns.ContractId] = req.ContractId
  852. }
  853. if req.ContractName != "" {
  854. data[s.Dao.Columns.ContractName] = req.ContractName
  855. }
  856. if req.CustId > 0 {
  857. data[s.Dao.Columns.CustId] = req.CustId
  858. }
  859. if req.CustName != "" {
  860. data[s.Dao.Columns.CustName] = req.CustName
  861. }
  862. if req.ProductLine != "" {
  863. data[s.Dao.Columns.ProductLine] = req.ProductLine
  864. }
  865. if req.IsBig != "" {
  866. data[s.Dao.Columns.IsBig] = req.IsBig
  867. }
  868. if req.IsOps != "" {
  869. data[s.Dao.Columns.IsOps] = req.IsOps
  870. }
  871. if req.PriorityLevel != "" {
  872. data[s.Dao.Columns.PriorityLevel] = req.PriorityLevel
  873. }
  874. if req.FeedbackSource != "" {
  875. data[s.Dao.Columns.FeedbackSource] = req.FeedbackSource
  876. }
  877. if req.FeedbackReporter != "" {
  878. data[s.Dao.Columns.FeedbackReporter] = req.FeedbackReporter
  879. }
  880. if req.FeedbackDate != "" {
  881. data[s.Dao.Columns.FeedbackDate] = req.FeedbackDate
  882. }
  883. if req.OpsUserId > 0 {
  884. data[s.Dao.Columns.OpsUserId] = req.OpsUserId
  885. }
  886. if req.OpsUserName != "" {
  887. data[s.Dao.Columns.OpsUserName] = req.OpsUserName
  888. }
  889. if req.Remark != "" {
  890. data[s.Dao.Columns.Remark] = req.Remark
  891. }
  892. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  893. _, err = s.Dao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.Dao.Columns.Id, req.Id).Update()
  894. if err != nil {
  895. g.Log().Error(err)
  896. return myerrors.DbError("更新运维事件失败")
  897. }
  898. // 同步事件级附件:删除旧附件,插入新附件
  899. if req.Attachments != nil {
  900. _, _ = s.AttachmentDao.Data(g.Map{
  901. s.AttachmentDao.Columns.DeletedTime: gtime.Now(),
  902. }).Where(s.AttachmentDao.Columns.EventId, req.Id).
  903. Where("(event_record_id = 0 OR event_record_id IS NULL)").
  904. Update()
  905. for _, att := range req.Attachments {
  906. attData := g.Map{
  907. s.AttachmentDao.Columns.EventId: req.Id,
  908. s.AttachmentDao.Columns.FileName: att.FileName,
  909. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  910. s.AttachmentDao.Columns.FileType: att.FileType,
  911. }
  912. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  913. _, err := s.AttachmentDao.Data(attData).Insert()
  914. if err != nil {
  915. g.Log().Error(err)
  916. }
  917. }
  918. }
  919. return nil
  920. }
  921. // DeleteByIds 根据ID批量删除运维事件(软删除)
  922. func (s *OperationService) DeleteByIds(req *comm_def.IdsReq) error {
  923. if len(req.Ids) == 0 {
  924. return myerrors.ValidError("参数有误!")
  925. }
  926. ids := make([]int, 0, len(req.Ids))
  927. for _, id := range req.Ids {
  928. ids = append(ids, int(id))
  929. }
  930. return s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  931. // 1. 软删除事件
  932. _, err := s.Dao.TX(tx).Data(g.Map{
  933. s.Dao.Columns.DeletedTime: gtime.Now(),
  934. }).WhereIn(s.Dao.Columns.Id, ids).Update()
  935. if err != nil {
  936. g.Log().Error(err)
  937. return myerrors.DbError("删除运维事件失败")
  938. }
  939. // 2. 软删除关联的处理记录
  940. _, err = s.RecordDao.TX(tx).Data(g.Map{
  941. s.RecordDao.Columns.DeletedTime: gtime.Now(),
  942. }).WhereIn(s.RecordDao.Columns.EventId, ids).Update()
  943. if err != nil {
  944. g.Log().Error(err)
  945. return myerrors.DbError("删除处理记录失败")
  946. }
  947. // 3. 软删除关联的附件
  948. _, err = s.AttachmentDao.TX(tx).Data(g.Map{
  949. s.AttachmentDao.Columns.DeletedTime: gtime.Now(),
  950. }).WhereIn(s.AttachmentDao.Columns.EventId, ids).Update()
  951. if err != nil {
  952. g.Log().Error(err)
  953. return myerrors.DbError("删除附件失败")
  954. }
  955. return nil
  956. })
  957. }
  958. // GetRecords 获取事件处理记录列表(分页,含附件)
  959. func (s *OperationService) GetRecords(req *opsdevmodel.OpsOperationEventRecordSearchReq) (total int, list []*opsdevmodel.OpsOperationEventRecordWithAttachments, err error) {
  960. if req.EventId <= 0 {
  961. return 0, nil, myerrors.ValidError("事件ID不能为空")
  962. }
  963. db := s.RecordDao.FieldsEx(s.RecordDao.Columns.DeletedTime).Where(s.RecordDao.Columns.EventId, req.EventId)
  964. total, err = db.Count()
  965. if err != nil {
  966. g.Log().Error(err)
  967. return 0, nil, myerrors.DbError("查询处理记录数量失败")
  968. }
  969. pageNum, pageSize := req.GetPage()
  970. var records []*opsdevmodel.OpsOperationEventRecord
  971. err = db.Page(pageNum, pageSize).Order(s.RecordDao.Columns.CreatedTime + " desc").Scan(&records)
  972. if err != nil {
  973. g.Log().Error(err)
  974. return 0, nil, myerrors.DbError("查询处理记录列表失败")
  975. }
  976. result := make([]*opsdevmodel.OpsOperationEventRecordWithAttachments, 0, len(records))
  977. for _, record := range records {
  978. recordRsp := &opsdevmodel.OpsOperationEventRecordWithAttachments{
  979. OpsOperationEventRecord: *record,
  980. Attachments: []*opsdevmodel.OpsOperationEventAttachment{},
  981. }
  982. if record.Id > 0 {
  983. var attachments []*opsdevmodel.OpsOperationEventAttachment
  984. err := s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  985. Where(s.AttachmentDao.Columns.EventRecordId, record.Id).
  986. Scan(&attachments)
  987. if err != nil {
  988. g.Log().Error(err)
  989. } else {
  990. recordRsp.Attachments = attachments
  991. }
  992. }
  993. result = append(result, recordRsp)
  994. }
  995. return total, result, nil
  996. }
  997. // UploadAttachment 上传附件
  998. func (s *OperationService) UploadAttachment(req *opsdevmodel.OpsOperationEventAttachmentReq) (*opsdevmodel.OpsOperationEventAttachment, error) {
  999. if req.EventId <= 0 {
  1000. return nil, myerrors.ValidError("事件ID不能为空")
  1001. }
  1002. event := new(opsdevmodel.OpsOperationEvent)
  1003. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.EventId).Scan(event)
  1004. if err != nil {
  1005. g.Log().Error(err)
  1006. return nil, myerrors.DbError("查询运维事件失败")
  1007. }
  1008. if event.Id <= 0 {
  1009. return nil, myerrors.TipsError("运维事件不存在")
  1010. }
  1011. data := g.Map{
  1012. s.AttachmentDao.Columns.EventId: req.EventId,
  1013. s.AttachmentDao.Columns.EventRecordId: req.EventRecordId,
  1014. s.AttachmentDao.Columns.FileName: req.FileName,
  1015. s.AttachmentDao.Columns.FileUrl: req.FileUrl,
  1016. s.AttachmentDao.Columns.FileType: req.FileType,
  1017. s.AttachmentDao.Columns.Remark: req.Remark,
  1018. }
  1019. service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  1020. result, err := s.AttachmentDao.Data(data).Insert()
  1021. if err != nil {
  1022. g.Log().Error(err)
  1023. return nil, myerrors.DbError("上传附件失败")
  1024. }
  1025. id, err := result.LastInsertId()
  1026. if err != nil {
  1027. g.Log().Error(err)
  1028. return nil, myerrors.DbError("获取附件ID失败")
  1029. }
  1030. attachment := new(opsdevmodel.OpsOperationEventAttachment)
  1031. err = s.AttachmentDao.WherePri(s.AttachmentDao.Columns.Id, id).Scan(attachment)
  1032. if err != nil {
  1033. g.Log().Error(err)
  1034. return nil, myerrors.DbError("查询附件失败")
  1035. }
  1036. return attachment, nil
  1037. }
  1038. // GetAttachments 根据事件ID获取附件列表
  1039. func (s *OperationService) GetAttachments(eventId int) ([]*opsdevmodel.OpsOperationEventAttachment, error) {
  1040. if eventId <= 0 {
  1041. return nil, myerrors.ValidError("事件ID不能为空")
  1042. }
  1043. var attachments []*opsdevmodel.OpsOperationEventAttachment
  1044. err := s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  1045. Where(s.AttachmentDao.Columns.EventId, eventId).
  1046. Order(s.AttachmentDao.Columns.CreatedTime + " desc").
  1047. Scan(&attachments)
  1048. if err != nil {
  1049. g.Log().Error(err)
  1050. return nil, myerrors.DbError("查询附件列表失败")
  1051. }
  1052. return attachments, nil
  1053. }
  1054. // GetStats 获取运维事件统计数据
  1055. func (s *OperationService) GetStats() (g.Map, error) {
  1056. stats := g.Map{}
  1057. // 按状态统计
  1058. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime)
  1059. statusList, err := db.GroupBy(s.Dao.Columns.EventStatus).Fields(s.Dao.Columns.EventStatus+", count(*) as count").All()
  1060. if err != nil {
  1061. g.Log().Error(err)
  1062. return nil, myerrors.DbError("统计事件状态失败")
  1063. }
  1064. statusCount := g.Map{}
  1065. for _, row := range statusList {
  1066. statusCount[gconv.String(row["event_status"])] = row["count"]
  1067. }
  1068. stats["statusCount"] = statusCount
  1069. // 按优先级统计
  1070. priorityList, err := db.GroupBy(s.Dao.Columns.PriorityLevel).Fields(s.Dao.Columns.PriorityLevel+", count(*) as count").All()
  1071. if err != nil {
  1072. g.Log().Error(err)
  1073. return nil, myerrors.DbError("统计事件优先级失败")
  1074. }
  1075. priorityCount := g.Map{}
  1076. for _, row := range priorityList {
  1077. priorityCount[gconv.String(row["priority_level"])] = row["count"]
  1078. }
  1079. stats["priorityCount"] = priorityCount
  1080. // 按类型统计
  1081. typeList, err := db.GroupBy(s.Dao.Columns.EventType).Fields(s.Dao.Columns.EventType+", count(*) as count").All()
  1082. if err != nil {
  1083. g.Log().Error(err)
  1084. return nil, myerrors.DbError("统计事件类型失败")
  1085. }
  1086. typeCount := g.Map{}
  1087. for _, row := range typeList {
  1088. typeCount[gconv.String(row["event_type"])] = row["count"]
  1089. }
  1090. stats["typeCount"] = typeCount
  1091. return stats, nil
  1092. }
  1093. // GetKanbanData 获取看板数据
  1094. func (s *OperationService) GetKanbanData(req *opsdevmodel.OpsOperationEventKanbanSearchReq) (g.Map, error) {
  1095. // 查询所有非关闭状态的事件
  1096. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).Where(s.Dao.Columns.EventStatus+" != ?", opsdevmodel.EventStatusClosed)
  1097. if req.KeyWords != "" {
  1098. switch req.SearchType {
  1099. case "eventNo":
  1100. db = db.Where(s.Dao.Columns.EventNo, req.KeyWords)
  1101. case "title":
  1102. db = db.Where(s.Dao.Columns.EventTitle+" like ?", "%"+req.KeyWords+"%")
  1103. case "custName":
  1104. db = db.Where(s.Dao.Columns.CustName+" like ?", "%"+req.KeyWords+"%")
  1105. case "feedbackReporter":
  1106. db = db.Where(s.Dao.Columns.FeedbackReporter+" like ?", "%"+req.KeyWords+"%")
  1107. default:
  1108. db = db.Where(fmt.Sprintf("(%s like ? or %s like ? or %s like ? or %s = ?)",
  1109. s.Dao.Columns.EventTitle, s.Dao.Columns.CustName, s.Dao.Columns.FeedbackReporter, s.Dao.Columns.EventNo),
  1110. "%"+req.KeyWords+"%", "%"+req.KeyWords+"%", "%"+req.KeyWords+"%", req.KeyWords)
  1111. }
  1112. }
  1113. if req.EventType != "" {
  1114. db = db.Where(s.Dao.Columns.EventType, req.EventType)
  1115. }
  1116. if req.PriorityLevel != "" {
  1117. db = db.Where(s.Dao.Columns.PriorityLevel, req.PriorityLevel)
  1118. }
  1119. // 待处理可查看全部,处理中/转研发/挂起仅查看当前登录人的
  1120. userId := s.GetCxtUserId()
  1121. if userId > 0 {
  1122. db = db.Where(fmt.Sprintf("(%s = ? OR %s = ?)", s.Dao.Columns.EventStatus, s.Dao.Columns.OpsUserId), opsdevmodel.EventStatusPending, userId)
  1123. }
  1124. // 排序
  1125. switch req.SortBy {
  1126. case "feedbackDateAsc":
  1127. db = db.Order(s.Dao.Columns.FeedbackDate + " asc")
  1128. case "custName":
  1129. db = db.Order(s.Dao.Columns.CustName + " asc")
  1130. default:
  1131. db = db.Order(s.Dao.Columns.FeedbackDate + " desc")
  1132. }
  1133. var list []*opsdevmodel.OpsOperationEvent
  1134. err := db.Scan(&list)
  1135. if err != nil {
  1136. g.Log().Error(err)
  1137. return nil, myerrors.DbError("查询看板数据失败")
  1138. }
  1139. // 按状态分组(处理中合并 20 + 30)
  1140. groups := map[string][]*opsdevmodel.OpsOperationEvent{
  1141. opsdevmodel.EventStatusPending: make([]*opsdevmodel.OpsOperationEvent, 0), // 待处理
  1142. opsdevmodel.EventStatusProcessing: make([]*opsdevmodel.OpsOperationEvent, 0), // 处理中
  1143. opsdevmodel.EventStatusTransfer: make([]*opsdevmodel.OpsOperationEvent, 0), // 转研发
  1144. opsdevmodel.EventStatusSuspended: make([]*opsdevmodel.OpsOperationEvent, 0), // 挂起
  1145. }
  1146. for _, item := range list {
  1147. key := item.EventStatus
  1148. if key == opsdevmodel.EventStatusProcessingNormal {
  1149. key = opsdevmodel.EventStatusProcessing
  1150. }
  1151. groups[key] = append(groups[key], item)
  1152. }
  1153. kanbanData := g.Map{}
  1154. for key, items := range groups {
  1155. kanbanData[key] = g.Map{
  1156. "list": items,
  1157. "count": len(items),
  1158. }
  1159. }
  1160. return kanbanData, nil
  1161. }
  1162. // AssignOpsUser 分配运维人员
  1163. func (s *OperationService) AssignOpsUser(req *opsdevmodel.AssignOpsUserReq) error {
  1164. if req.Id <= 0 {
  1165. return myerrors.ValidError("事件ID不能为空")
  1166. }
  1167. event := new(opsdevmodel.OpsOperationEvent)
  1168. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.Id).Scan(event)
  1169. if err != nil {
  1170. g.Log().Error(err)
  1171. return myerrors.DbError("查询运维事件失败")
  1172. }
  1173. if event.Id <= 0 {
  1174. return myerrors.TipsError("运维事件不存在")
  1175. }
  1176. data := g.Map{
  1177. s.Dao.Columns.OpsUserId: req.OpsUserId,
  1178. s.Dao.Columns.OpsUserName: req.OpsUserName,
  1179. s.Dao.Columns.AssignTime: gtime.Now(),
  1180. }
  1181. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  1182. _, err = s.Dao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.Dao.Columns.Id, req.Id).Update()
  1183. if err != nil {
  1184. g.Log().Error(err)
  1185. return myerrors.DbError("分配运维人员失败")
  1186. }
  1187. return nil
  1188. }
  1189. // AddRecord 添加处理记录(含附件,事务控制)
  1190. func (s *OperationService) AddRecord(req *opsdevmodel.AddRecordReq) error {
  1191. if req.EventId <= 0 {
  1192. return myerrors.ValidError("事件ID不能为空")
  1193. }
  1194. event := new(opsdevmodel.OpsOperationEvent)
  1195. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.EventId).Scan(event)
  1196. if err != nil {
  1197. g.Log().Error(err)
  1198. return myerrors.DbError("查询运维事件失败")
  1199. }
  1200. if event.Id <= 0 {
  1201. return myerrors.TipsError("运维事件不存在")
  1202. }
  1203. opsUserId := s.GetCxtUserId()
  1204. opsUserName := s.GetCxtUserName()
  1205. recordData := g.Map{
  1206. s.RecordDao.Columns.EventId: req.EventId,
  1207. s.RecordDao.Columns.HandleUserId: opsUserId,
  1208. s.RecordDao.Columns.HandleUserName: opsUserName,
  1209. s.RecordDao.Columns.HandleContent: req.HandleContent,
  1210. s.RecordDao.Columns.HandleResult: req.HandleResult,
  1211. s.RecordDao.Columns.OperateType: opsdevmodel.OperateTypeProcess,
  1212. s.RecordDao.Columns.HandleDate: gtime.Now(),
  1213. }
  1214. service.SetCreatedInfo(recordData, opsUserId, opsUserName)
  1215. return s.RecordDao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  1216. result, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  1217. if err != nil {
  1218. g.Log().Error(err)
  1219. return myerrors.DbError("添加处理记录失败")
  1220. }
  1221. recordId, err := result.LastInsertId()
  1222. if err != nil {
  1223. g.Log().Error(err)
  1224. return myerrors.DbError("获取记录ID失败")
  1225. }
  1226. // 如果有附件,一并保存
  1227. for _, att := range req.Attachments {
  1228. attData := g.Map{
  1229. s.AttachmentDao.Columns.EventId: req.EventId,
  1230. s.AttachmentDao.Columns.EventRecordId: recordId,
  1231. s.AttachmentDao.Columns.FileName: att.FileName,
  1232. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  1233. s.AttachmentDao.Columns.FileType: att.FileType,
  1234. }
  1235. service.SetCreatedInfo(attData, opsUserId, opsUserName)
  1236. _, err := s.AttachmentDao.TX(tx).Data(attData).Insert()
  1237. if err != nil {
  1238. g.Log().Error(err)
  1239. return myerrors.DbError("保存附件失败")
  1240. }
  1241. }
  1242. return nil
  1243. })
  1244. }