operation.go 48 KB

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