operation.go 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  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, req *opsdevmodel.OpsOperationEventProcessReq) {
  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: req.OpsUserId,
  757. OpsUserName: req.OpsUserName,
  758. PlanStartTime: req.PlanStartTime,
  759. PlanEndTime: req.PlanEndTime,
  760. EventId: event.Id,
  761. EventType: opsdevmodel.EventTypeOps,
  762. Attachments: taskAttachments,
  763. }
  764. taskSvc, err := NewOpsEventTaskService(s.Ctx)
  765. if err != nil {
  766. g.Log().Errorf("createDevTaskFromEvent: init task service failed, eventId=%d, err=%v", event.Id, err)
  767. return
  768. }
  769. if err := taskSvc.Create(taskReq); err != nil {
  770. g.Log().Errorf("createDevTaskFromEvent: create task failed, eventId=%d, eventType=%s, taskType=%s, err=%v",
  771. event.Id, event.EventType, taskType, err)
  772. }
  773. }
  774. // processNormal 非关单处理(开始/转处理/转研发/挂起)
  775. func (s *OperationService) processNormal(req *opsdevmodel.OpsOperationEventProcessReq, event *opsdevmodel.OpsOperationEvent, opsUserId int, opsUserName string) error {
  776. handleDate := gtime.Now()
  777. recordData := g.Map{
  778. s.RecordDao.Columns.EventId: req.Id,
  779. s.RecordDao.Columns.HandleUserId: opsUserId,
  780. s.RecordDao.Columns.HandleUserName: opsUserName,
  781. s.RecordDao.Columns.HandleContent: req.HandleContent,
  782. s.RecordDao.Columns.HandleResult: req.HandleResult,
  783. s.RecordDao.Columns.HandleDate: handleDate,
  784. s.RecordDao.Columns.OperateType: req.OperateType,
  785. }
  786. service.SetCreatedInfo(recordData, opsUserId, opsUserName)
  787. newStatus := ""
  788. switch req.OperateType {
  789. case opsdevmodel.OperateTypeProcess:
  790. newStatus = opsdevmodel.EventStatusProcessing
  791. case opsdevmodel.OperateTypeResume:
  792. newStatus = opsdevmodel.EventStatusProcessing
  793. case opsdevmodel.OperateTypeTransfer:
  794. newStatus = opsdevmodel.EventStatusTransfer
  795. case opsdevmodel.OperateTypeSuspend:
  796. newStatus = opsdevmodel.EventStatusSuspended
  797. }
  798. err := s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  799. _, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  800. if err != nil {
  801. g.Log().Error(err)
  802. return myerrors.DbError("创建处理记录失败")
  803. }
  804. if newStatus != "" {
  805. updateData := g.Map{
  806. s.Dao.Columns.EventStatus: newStatus,
  807. }
  808. service.SetUpdatedInfo(updateData, opsUserId, opsUserName)
  809. _, err := s.Dao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(updateData).WherePri(s.Dao.Columns.Id, req.Id).Update()
  810. if err != nil {
  811. g.Log().Error(err)
  812. return myerrors.DbError("更新事件状态失败")
  813. }
  814. }
  815. return nil
  816. })
  817. if err != nil {
  818. return err
  819. }
  820. if req.OperateType == opsdevmodel.OperateTypeTransfer {
  821. s.createDevTaskFromEvent(event, req)
  822. }
  823. return nil
  824. }
  825. // UpdateById 更新运维事件
  826. func (s *OperationService) UpdateById(req *opsdevmodel.UpdateOpsOperationEventReq) error {
  827. if req.Id <= 0 {
  828. return myerrors.ValidError("参数有误!")
  829. }
  830. event := new(opsdevmodel.OpsOperationEvent)
  831. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.Id).Scan(event)
  832. if err != nil {
  833. g.Log().Error(err)
  834. return myerrors.DbError("查询运维事件失败")
  835. }
  836. if event.Id <= 0 {
  837. return myerrors.TipsError("运维事件不存在")
  838. }
  839. data := g.Map{}
  840. if req.EventTitle != "" {
  841. data[s.Dao.Columns.EventTitle] = req.EventTitle
  842. }
  843. if req.EventDesc != "" {
  844. data[s.Dao.Columns.EventDesc] = req.EventDesc
  845. }
  846. if req.EventType != "" {
  847. data[s.Dao.Columns.EventType] = req.EventType
  848. }
  849. if req.EventStatus != "" {
  850. data[s.Dao.Columns.EventStatus] = req.EventStatus
  851. }
  852. if req.ContractId > 0 {
  853. data[s.Dao.Columns.ContractId] = req.ContractId
  854. }
  855. if req.ContractName != "" {
  856. data[s.Dao.Columns.ContractName] = req.ContractName
  857. }
  858. if req.CustId > 0 {
  859. data[s.Dao.Columns.CustId] = req.CustId
  860. }
  861. if req.CustName != "" {
  862. data[s.Dao.Columns.CustName] = req.CustName
  863. }
  864. if req.ProductLine != "" {
  865. data[s.Dao.Columns.ProductLine] = req.ProductLine
  866. }
  867. if req.IsBig != "" {
  868. data[s.Dao.Columns.IsBig] = req.IsBig
  869. }
  870. if req.IsOps != "" {
  871. data[s.Dao.Columns.IsOps] = req.IsOps
  872. }
  873. if req.PriorityLevel != "" {
  874. data[s.Dao.Columns.PriorityLevel] = req.PriorityLevel
  875. }
  876. if req.FeedbackSource != "" {
  877. data[s.Dao.Columns.FeedbackSource] = req.FeedbackSource
  878. }
  879. if req.FeedbackReporter != "" {
  880. data[s.Dao.Columns.FeedbackReporter] = req.FeedbackReporter
  881. }
  882. if req.FeedbackDate != "" {
  883. data[s.Dao.Columns.FeedbackDate] = req.FeedbackDate
  884. }
  885. if req.OpsUserId > 0 {
  886. data[s.Dao.Columns.OpsUserId] = req.OpsUserId
  887. }
  888. if req.OpsUserName != "" {
  889. data[s.Dao.Columns.OpsUserName] = req.OpsUserName
  890. }
  891. if req.Remark != "" {
  892. data[s.Dao.Columns.Remark] = req.Remark
  893. }
  894. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  895. _, err = s.Dao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.Dao.Columns.Id, req.Id).Update()
  896. if err != nil {
  897. g.Log().Error(err)
  898. return myerrors.DbError("更新运维事件失败")
  899. }
  900. // 同步事件级附件:删除旧附件,插入新附件
  901. if req.Attachments != nil {
  902. _, _ = s.AttachmentDao.Data(g.Map{
  903. s.AttachmentDao.Columns.DeletedTime: gtime.Now(),
  904. }).Where(s.AttachmentDao.Columns.EventId, req.Id).
  905. Where("(event_record_id = 0 OR event_record_id IS NULL)").
  906. Update()
  907. for _, att := range req.Attachments {
  908. attData := g.Map{
  909. s.AttachmentDao.Columns.EventId: req.Id,
  910. s.AttachmentDao.Columns.FileName: att.FileName,
  911. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  912. s.AttachmentDao.Columns.FileType: att.FileType,
  913. }
  914. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  915. _, err := s.AttachmentDao.Data(attData).Insert()
  916. if err != nil {
  917. g.Log().Error(err)
  918. }
  919. }
  920. }
  921. return nil
  922. }
  923. // DeleteByIds 根据ID批量删除运维事件(软删除)
  924. func (s *OperationService) DeleteByIds(req *comm_def.IdsReq) error {
  925. if len(req.Ids) == 0 {
  926. return myerrors.ValidError("参数有误!")
  927. }
  928. ids := make([]int, 0, len(req.Ids))
  929. for _, id := range req.Ids {
  930. ids = append(ids, int(id))
  931. }
  932. return s.Dao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  933. // 1. 软删除事件
  934. _, err := s.Dao.TX(tx).Data(g.Map{
  935. s.Dao.Columns.DeletedTime: gtime.Now(),
  936. }).WhereIn(s.Dao.Columns.Id, ids).Update()
  937. if err != nil {
  938. g.Log().Error(err)
  939. return myerrors.DbError("删除运维事件失败")
  940. }
  941. // 2. 软删除关联的处理记录
  942. _, err = s.RecordDao.TX(tx).Data(g.Map{
  943. s.RecordDao.Columns.DeletedTime: gtime.Now(),
  944. }).WhereIn(s.RecordDao.Columns.EventId, ids).Update()
  945. if err != nil {
  946. g.Log().Error(err)
  947. return myerrors.DbError("删除处理记录失败")
  948. }
  949. // 3. 软删除关联的附件
  950. _, err = s.AttachmentDao.TX(tx).Data(g.Map{
  951. s.AttachmentDao.Columns.DeletedTime: gtime.Now(),
  952. }).WhereIn(s.AttachmentDao.Columns.EventId, ids).Update()
  953. if err != nil {
  954. g.Log().Error(err)
  955. return myerrors.DbError("删除附件失败")
  956. }
  957. return nil
  958. })
  959. }
  960. // GetRecords 获取事件处理记录列表(分页,含附件)
  961. func (s *OperationService) GetRecords(req *opsdevmodel.OpsOperationEventRecordSearchReq) (total int, list []*opsdevmodel.OpsOperationEventRecordWithAttachments, err error) {
  962. if req.EventId <= 0 {
  963. return 0, nil, myerrors.ValidError("事件ID不能为空")
  964. }
  965. db := s.RecordDao.FieldsEx(s.RecordDao.Columns.DeletedTime).Where(s.RecordDao.Columns.EventId, req.EventId)
  966. total, err = db.Count()
  967. if err != nil {
  968. g.Log().Error(err)
  969. return 0, nil, myerrors.DbError("查询处理记录数量失败")
  970. }
  971. pageNum, pageSize := req.GetPage()
  972. var records []*opsdevmodel.OpsOperationEventRecord
  973. err = db.Page(pageNum, pageSize).Order(s.RecordDao.Columns.CreatedTime + " desc").Scan(&records)
  974. if err != nil {
  975. g.Log().Error(err)
  976. return 0, nil, myerrors.DbError("查询处理记录列表失败")
  977. }
  978. result := make([]*opsdevmodel.OpsOperationEventRecordWithAttachments, 0, len(records))
  979. for _, record := range records {
  980. recordRsp := &opsdevmodel.OpsOperationEventRecordWithAttachments{
  981. OpsOperationEventRecord: *record,
  982. Attachments: []*opsdevmodel.OpsOperationEventAttachment{},
  983. }
  984. if record.Id > 0 {
  985. var attachments []*opsdevmodel.OpsOperationEventAttachment
  986. err := s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  987. Where(s.AttachmentDao.Columns.EventRecordId, record.Id).
  988. Scan(&attachments)
  989. if err != nil {
  990. g.Log().Error(err)
  991. } else {
  992. recordRsp.Attachments = attachments
  993. }
  994. }
  995. result = append(result, recordRsp)
  996. }
  997. return total, result, nil
  998. }
  999. // UploadAttachment 上传附件
  1000. func (s *OperationService) UploadAttachment(req *opsdevmodel.OpsOperationEventAttachmentReq) (*opsdevmodel.OpsOperationEventAttachment, error) {
  1001. if req.EventId <= 0 {
  1002. return nil, myerrors.ValidError("事件ID不能为空")
  1003. }
  1004. event := new(opsdevmodel.OpsOperationEvent)
  1005. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.EventId).Scan(event)
  1006. if err != nil {
  1007. g.Log().Error(err)
  1008. return nil, myerrors.DbError("查询运维事件失败")
  1009. }
  1010. if event.Id <= 0 {
  1011. return nil, myerrors.TipsError("运维事件不存在")
  1012. }
  1013. data := g.Map{
  1014. s.AttachmentDao.Columns.EventId: req.EventId,
  1015. s.AttachmentDao.Columns.EventRecordId: req.EventRecordId,
  1016. s.AttachmentDao.Columns.FileName: req.FileName,
  1017. s.AttachmentDao.Columns.FileUrl: req.FileUrl,
  1018. s.AttachmentDao.Columns.FileType: req.FileType,
  1019. s.AttachmentDao.Columns.Remark: req.Remark,
  1020. }
  1021. service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  1022. result, err := s.AttachmentDao.Data(data).Insert()
  1023. if err != nil {
  1024. g.Log().Error(err)
  1025. return nil, myerrors.DbError("上传附件失败")
  1026. }
  1027. id, err := result.LastInsertId()
  1028. if err != nil {
  1029. g.Log().Error(err)
  1030. return nil, myerrors.DbError("获取附件ID失败")
  1031. }
  1032. attachment := new(opsdevmodel.OpsOperationEventAttachment)
  1033. err = s.AttachmentDao.WherePri(s.AttachmentDao.Columns.Id, id).Scan(attachment)
  1034. if err != nil {
  1035. g.Log().Error(err)
  1036. return nil, myerrors.DbError("查询附件失败")
  1037. }
  1038. return attachment, nil
  1039. }
  1040. // GetAttachments 根据事件ID获取附件列表
  1041. func (s *OperationService) GetAttachments(eventId int) ([]*opsdevmodel.OpsOperationEventAttachment, error) {
  1042. if eventId <= 0 {
  1043. return nil, myerrors.ValidError("事件ID不能为空")
  1044. }
  1045. var attachments []*opsdevmodel.OpsOperationEventAttachment
  1046. err := s.AttachmentDao.FieldsEx(s.AttachmentDao.Columns.DeletedTime).
  1047. Where(s.AttachmentDao.Columns.EventId, eventId).
  1048. Order(s.AttachmentDao.Columns.CreatedTime + " desc").
  1049. Scan(&attachments)
  1050. if err != nil {
  1051. g.Log().Error(err)
  1052. return nil, myerrors.DbError("查询附件列表失败")
  1053. }
  1054. return attachments, nil
  1055. }
  1056. // GetStats 获取运维事件统计数据
  1057. func (s *OperationService) GetStats() (g.Map, error) {
  1058. stats := g.Map{}
  1059. // 按状态统计
  1060. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime)
  1061. statusList, err := db.GroupBy(s.Dao.Columns.EventStatus).Fields(s.Dao.Columns.EventStatus+", count(*) as count").All()
  1062. if err != nil {
  1063. g.Log().Error(err)
  1064. return nil, myerrors.DbError("统计事件状态失败")
  1065. }
  1066. statusCount := g.Map{}
  1067. for _, row := range statusList {
  1068. statusCount[gconv.String(row["event_status"])] = row["count"]
  1069. }
  1070. stats["statusCount"] = statusCount
  1071. // 按优先级统计
  1072. priorityList, err := db.GroupBy(s.Dao.Columns.PriorityLevel).Fields(s.Dao.Columns.PriorityLevel+", count(*) as count").All()
  1073. if err != nil {
  1074. g.Log().Error(err)
  1075. return nil, myerrors.DbError("统计事件优先级失败")
  1076. }
  1077. priorityCount := g.Map{}
  1078. for _, row := range priorityList {
  1079. priorityCount[gconv.String(row["priority_level"])] = row["count"]
  1080. }
  1081. stats["priorityCount"] = priorityCount
  1082. // 按类型统计
  1083. typeList, err := db.GroupBy(s.Dao.Columns.EventType).Fields(s.Dao.Columns.EventType+", count(*) as count").All()
  1084. if err != nil {
  1085. g.Log().Error(err)
  1086. return nil, myerrors.DbError("统计事件类型失败")
  1087. }
  1088. typeCount := g.Map{}
  1089. for _, row := range typeList {
  1090. typeCount[gconv.String(row["event_type"])] = row["count"]
  1091. }
  1092. stats["typeCount"] = typeCount
  1093. return stats, nil
  1094. }
  1095. // GetKanbanData 获取看板数据
  1096. func (s *OperationService) GetKanbanData(req *opsdevmodel.OpsOperationEventKanbanSearchReq) (g.Map, error) {
  1097. // 查询所有非关闭状态的事件
  1098. db := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).Where(s.Dao.Columns.EventStatus+" != ?", opsdevmodel.EventStatusClosed)
  1099. if req.KeyWords != "" {
  1100. switch req.SearchType {
  1101. case "eventNo":
  1102. db = db.Where(s.Dao.Columns.EventNo, req.KeyWords)
  1103. case "title":
  1104. db = db.Where(s.Dao.Columns.EventTitle+" like ?", "%"+req.KeyWords+"%")
  1105. case "custName":
  1106. db = db.Where(s.Dao.Columns.CustName+" like ?", "%"+req.KeyWords+"%")
  1107. case "feedbackReporter":
  1108. db = db.Where(s.Dao.Columns.FeedbackReporter+" like ?", "%"+req.KeyWords+"%")
  1109. default:
  1110. db = db.Where(fmt.Sprintf("(%s like ? or %s like ? or %s like ? or %s = ?)",
  1111. s.Dao.Columns.EventTitle, s.Dao.Columns.CustName, s.Dao.Columns.FeedbackReporter, s.Dao.Columns.EventNo),
  1112. "%"+req.KeyWords+"%", "%"+req.KeyWords+"%", "%"+req.KeyWords+"%", req.KeyWords)
  1113. }
  1114. }
  1115. if req.EventType != "" {
  1116. db = db.Where(s.Dao.Columns.EventType, req.EventType)
  1117. }
  1118. if req.PriorityLevel != "" {
  1119. db = db.Where(s.Dao.Columns.PriorityLevel, req.PriorityLevel)
  1120. }
  1121. // 待处理可查看全部,处理中/转研发/挂起仅查看当前登录人的
  1122. userId := s.GetCxtUserId()
  1123. if userId > 0 {
  1124. db = db.Where(fmt.Sprintf("(%s = ? OR %s = ?)", s.Dao.Columns.EventStatus, s.Dao.Columns.OpsUserId), opsdevmodel.EventStatusPending, userId)
  1125. }
  1126. // 排序
  1127. switch req.SortBy {
  1128. case "feedbackDateAsc":
  1129. db = db.Order(s.Dao.Columns.FeedbackDate + " asc")
  1130. case "custName":
  1131. db = db.Order(s.Dao.Columns.CustName + " asc")
  1132. default:
  1133. db = db.Order(s.Dao.Columns.FeedbackDate + " desc")
  1134. }
  1135. var list []*opsdevmodel.OpsOperationEvent
  1136. err := db.Scan(&list)
  1137. if err != nil {
  1138. g.Log().Error(err)
  1139. return nil, myerrors.DbError("查询看板数据失败")
  1140. }
  1141. // 按状态分组(处理中合并 20 + 30)
  1142. groups := map[string][]*opsdevmodel.OpsOperationEvent{
  1143. opsdevmodel.EventStatusPending: make([]*opsdevmodel.OpsOperationEvent, 0), // 待处理
  1144. opsdevmodel.EventStatusProcessing: make([]*opsdevmodel.OpsOperationEvent, 0), // 处理中
  1145. opsdevmodel.EventStatusTransfer: make([]*opsdevmodel.OpsOperationEvent, 0), // 转研发
  1146. opsdevmodel.EventStatusSuspended: make([]*opsdevmodel.OpsOperationEvent, 0), // 挂起
  1147. }
  1148. for _, item := range list {
  1149. key := item.EventStatus
  1150. if key == opsdevmodel.EventStatusProcessingNormal {
  1151. key = opsdevmodel.EventStatusProcessing
  1152. }
  1153. groups[key] = append(groups[key], item)
  1154. }
  1155. kanbanData := g.Map{}
  1156. for key, items := range groups {
  1157. kanbanData[key] = g.Map{
  1158. "list": items,
  1159. "count": len(items),
  1160. }
  1161. }
  1162. return kanbanData, nil
  1163. }
  1164. // AssignOpsUser 分配运维人员
  1165. func (s *OperationService) AssignOpsUser(req *opsdevmodel.AssignOpsUserReq) error {
  1166. if req.Id <= 0 {
  1167. return myerrors.ValidError("事件ID不能为空")
  1168. }
  1169. event := new(opsdevmodel.OpsOperationEvent)
  1170. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.Id).Scan(event)
  1171. if err != nil {
  1172. g.Log().Error(err)
  1173. return myerrors.DbError("查询运维事件失败")
  1174. }
  1175. if event.Id <= 0 {
  1176. return myerrors.TipsError("运维事件不存在")
  1177. }
  1178. data := g.Map{
  1179. s.Dao.Columns.OpsUserId: req.OpsUserId,
  1180. s.Dao.Columns.OpsUserName: req.OpsUserName,
  1181. s.Dao.Columns.AssignTime: gtime.Now(),
  1182. }
  1183. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  1184. _, err = s.Dao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.Dao.Columns.Id, req.Id).Update()
  1185. if err != nil {
  1186. g.Log().Error(err)
  1187. return myerrors.DbError("分配运维人员失败")
  1188. }
  1189. return nil
  1190. }
  1191. // AddRecord 添加处理记录(含附件,事务控制)
  1192. func (s *OperationService) AddRecord(req *opsdevmodel.AddRecordReq) error {
  1193. if req.EventId <= 0 {
  1194. return myerrors.ValidError("事件ID不能为空")
  1195. }
  1196. event := new(opsdevmodel.OpsOperationEvent)
  1197. err := s.Dao.FieldsEx(s.Dao.Columns.DeletedTime).WherePri(s.Dao.Columns.Id, req.EventId).Scan(event)
  1198. if err != nil {
  1199. g.Log().Error(err)
  1200. return myerrors.DbError("查询运维事件失败")
  1201. }
  1202. if event.Id <= 0 {
  1203. return myerrors.TipsError("运维事件不存在")
  1204. }
  1205. opsUserId := s.GetCxtUserId()
  1206. opsUserName := s.GetCxtUserName()
  1207. recordData := g.Map{
  1208. s.RecordDao.Columns.EventId: req.EventId,
  1209. s.RecordDao.Columns.HandleUserId: opsUserId,
  1210. s.RecordDao.Columns.HandleUserName: opsUserName,
  1211. s.RecordDao.Columns.HandleContent: req.HandleContent,
  1212. s.RecordDao.Columns.HandleResult: req.HandleResult,
  1213. s.RecordDao.Columns.OperateType: opsdevmodel.OperateTypeProcess,
  1214. s.RecordDao.Columns.HandleDate: gtime.Now(),
  1215. }
  1216. service.SetCreatedInfo(recordData, opsUserId, opsUserName)
  1217. return s.RecordDao.Transaction(s.Ctx, func(ctx context.Context, tx *gdb.TX) error {
  1218. result, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  1219. if err != nil {
  1220. g.Log().Error(err)
  1221. return myerrors.DbError("添加处理记录失败")
  1222. }
  1223. recordId, err := result.LastInsertId()
  1224. if err != nil {
  1225. g.Log().Error(err)
  1226. return myerrors.DbError("获取记录ID失败")
  1227. }
  1228. // 如果有附件,一并保存
  1229. for _, att := range req.Attachments {
  1230. attData := g.Map{
  1231. s.AttachmentDao.Columns.EventId: req.EventId,
  1232. s.AttachmentDao.Columns.EventRecordId: recordId,
  1233. s.AttachmentDao.Columns.FileName: att.FileName,
  1234. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  1235. s.AttachmentDao.Columns.FileType: att.FileType,
  1236. }
  1237. service.SetCreatedInfo(attData, opsUserId, opsUserName)
  1238. _, err := s.AttachmentDao.TX(tx).Data(attData).Insert()
  1239. if err != nil {
  1240. g.Log().Error(err)
  1241. return myerrors.DbError("保存附件失败")
  1242. }
  1243. }
  1244. return nil
  1245. })
  1246. }