delivery_project_event.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. package opsdev
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "dashoo.cn/opms_libary/myerrors"
  8. eventdao "dashoo.cn/opms_parent/app/dao/opsdev"
  9. eventmodel "dashoo.cn/opms_parent/app/model/opsdev"
  10. "dashoo.cn/opms_parent/app/service"
  11. "github.com/gogf/gf/database/gdb"
  12. "github.com/gogf/gf/frame/g"
  13. "github.com/gogf/gf/os/gtime"
  14. "github.com/gogf/gf/util/gconv"
  15. )
  16. // eventTypeToAutoTaskType 交付事件类型→自动创建研发任务类型的映射
  17. var eventTypeToAutoTaskType = map[string]string{
  18. "31": eventmodel.TaskTypeReqReview, // 需求评审 → 需求评审
  19. "32": eventmodel.TaskTypeFeatureDev, // 功能调整 → 功能开发
  20. "33": eventmodel.TaskTypeFeatureDev, // 二开需求 → 功能开发
  21. "35": eventmodel.TaskTypeFeatureDev, // 系统缺陷 → 功能开发
  22. "38": eventmodel.TaskTypeSystemReleaseEvt, // 系统发版 → 系统发版(事件)
  23. "40": eventmodel.TaskTypeSystemReleaseEvt, // 硬件发货 → 系统发版(事件)
  24. "41": eventmodel.TaskTypeSystemReleaseEvt, // 硬件安装 → 系统发版(事件)
  25. }
  26. // DeliveryProjectEventService 交付项目事件业务逻辑实现类
  27. type DeliveryProjectEventService struct {
  28. *service.ContextService
  29. EventDao *eventdao.OpsDeliveryProjectEventDao
  30. RecordDao *eventdao.OpsDeliveryProjectEventRecordDao
  31. AttachmentDao *eventdao.OpsDeliveryProjectEventAttachmentDao
  32. ProjectDao *eventdao.OpsDeliveryProjectDao
  33. TaskDao *eventdao.OpsEventTaskDao
  34. TaskRecordDao *eventdao.OpsEventTaskRecordDao
  35. TaskAttachmentDao *eventdao.OpsEventTaskAttachmentDao
  36. }
  37. // NewDeliveryProjectEventService 初始化service
  38. func NewDeliveryProjectEventService(ctx context.Context) (svc *DeliveryProjectEventService, err error) {
  39. svc = new(DeliveryProjectEventService)
  40. if svc.ContextService, err = svc.Init(ctx); err != nil {
  41. return nil, err
  42. }
  43. svc.EventDao = eventdao.NewOpsDeliveryProjectEventDao(svc.Tenant)
  44. svc.RecordDao = eventdao.NewOpsDeliveryProjectEventRecordDao(svc.Tenant)
  45. svc.AttachmentDao = eventdao.NewOpsDeliveryProjectEventAttachmentDao(svc.Tenant)
  46. svc.ProjectDao = eventdao.NewOpsDeliveryProjectDao(svc.Tenant)
  47. svc.TaskDao = eventdao.NewOpsEventTaskDao(svc.Tenant)
  48. svc.TaskRecordDao = eventdao.NewOpsEventTaskRecordDao(svc.Tenant)
  49. svc.TaskAttachmentDao = eventdao.NewOpsEventTaskAttachmentDao(svc.Tenant)
  50. return svc, nil
  51. }
  52. // GetList 分页查询事件列表
  53. func (s *DeliveryProjectEventService) GetList(req *eventmodel.OpsDeliveryProjectEventSearchReq) (total int, list []*eventmodel.OpsDeliveryProjectEventRsp, err error) {
  54. db := s.EventDao.FieldsEx(s.EventDao.Columns.DeletedTime)
  55. // 项目ID筛选
  56. if req.ProjectId > 0 {
  57. db = db.Where(s.EventDao.Columns.ProjectId, req.ProjectId)
  58. }
  59. // 事件标题模糊查询
  60. if req.DeliveryEventTitle != "" {
  61. db = db.Where(s.EventDao.Columns.DeliveryEventTitle+" like ?", "%"+req.DeliveryEventTitle+"%")
  62. }
  63. // 事件描述模糊查询
  64. if req.DeliveryEventDesc != "" {
  65. db = db.Where(s.EventDao.Columns.DeliveryEventDesc+" like ?", "%"+req.DeliveryEventDesc+"%")
  66. }
  67. // 事件类型筛选(多选)
  68. if len(req.DeliveryEventType) > 0 {
  69. db = db.Where(s.EventDao.Columns.DeliveryEventType+" in (?)", req.DeliveryEventType)
  70. }
  71. // 事件状态筛选(多选)
  72. if len(req.DeliveryEventStatus) > 0 {
  73. db = db.Where(s.EventDao.Columns.DeliveryEventStatus+" in (?)", req.DeliveryEventStatus)
  74. }
  75. // 事件结果筛选(多选)
  76. if len(req.DeliveryEventResult) > 0 {
  77. db = db.Where(s.EventDao.Columns.DeliveryEventResult+" in (?)", req.DeliveryEventResult)
  78. }
  79. // 反馈来源筛选
  80. if req.FeedbackSource != "" {
  81. db = db.Where(s.EventDao.Columns.FeedbackSource, req.FeedbackSource)
  82. }
  83. // 反馈人模糊查询
  84. if req.FeedbackReporter != "" {
  85. db = db.Where(s.EventDao.Columns.FeedbackReporter+" like ?", "%"+req.FeedbackReporter+"%")
  86. }
  87. // 负责人筛选(多选精确匹配)
  88. if len(req.OpsUserName) > 0 {
  89. db = db.Where(s.EventDao.Columns.OpsUserName+" in (?)", req.OpsUserName)
  90. }
  91. // 反馈时间范围
  92. if req.FeedbackDateStart != "" {
  93. db = db.Where(s.EventDao.Columns.FeedbackDate+">= ?", req.FeedbackDateStart)
  94. }
  95. if req.FeedbackDateEnd != "" {
  96. db = db.Where(s.EventDao.Columns.FeedbackDate+"<= ?", req.FeedbackDateEnd)
  97. }
  98. // 处理时间范围
  99. if req.CompleteTimeStart != "" {
  100. db = db.Where(s.EventDao.Columns.CompleteTime+">= ?", req.CompleteTimeStart)
  101. }
  102. if req.CompleteTimeEnd != "" {
  103. db = db.Where(s.EventDao.Columns.CompleteTime+"<= ?", req.CompleteTimeEnd)
  104. }
  105. // 统计总数
  106. total, err = db.Count()
  107. if err != nil {
  108. g.Log().Error(err)
  109. return 0, nil, myerrors.DbError("获取事件总数失败")
  110. }
  111. // 分页查询
  112. pageNum, pageSize := req.GetPage()
  113. var entityList []*eventmodel.OpsDeliveryProjectEvent
  114. // 处理排序
  115. if len(req.SortFields) > 0 {
  116. orderClauses := []string{}
  117. for _, sort := range req.SortFields {
  118. // 将前端字段名转换为数据库列名
  119. colName := s.getSortColumnName(sort.Field)
  120. if colName != "" {
  121. orderClauses = append(orderClauses, colName+" "+strings.ToUpper(sort.Order))
  122. }
  123. }
  124. if len(orderClauses) > 0 {
  125. db = db.Order(strings.Join(orderClauses, ", "))
  126. } else {
  127. db = db.Order(s.EventDao.Columns.CreatedTime + " desc")
  128. }
  129. } else {
  130. db = db.Order(s.EventDao.Columns.CreatedTime + " desc")
  131. }
  132. err = db.Page(pageNum, pageSize).Scan(&entityList)
  133. if err != nil {
  134. g.Log().Error(err)
  135. return 0, nil, myerrors.DbError("查询事件列表失败")
  136. }
  137. // 转换为响应结构体
  138. if err = gconv.Structs(entityList, &list); err != nil {
  139. g.Log().Error(err)
  140. return 0, nil, myerrors.DbError("数据转换失败")
  141. }
  142. if len(list) > 0 {
  143. projectIds := make([]int, 0, len(list))
  144. for _, item := range list {
  145. if item.ProjectId > 0 {
  146. projectIds = append(projectIds, item.ProjectId)
  147. }
  148. }
  149. if len(projectIds) > 0 {
  150. var projects []*eventmodel.OpsDeliveryProject
  151. err := s.ProjectDao.Fields(
  152. s.ProjectDao.Columns.Id,
  153. s.ProjectDao.Columns.ProjectName,
  154. ).WhereIn(s.ProjectDao.Columns.Id, projectIds).Scan(&projects)
  155. if err != nil {
  156. g.Log().Error(err)
  157. } else {
  158. projectMap := make(map[int]string)
  159. for _, p := range projects {
  160. projectMap[p.Id] = p.ProjectName
  161. }
  162. for _, item := range list {
  163. if name, ok := projectMap[item.ProjectId]; ok {
  164. item.ProjectName = name
  165. }
  166. }
  167. }
  168. }
  169. }
  170. return
  171. }
  172. // getSortColumnName 将前端排序字段名转换为数据库列名
  173. func (s *DeliveryProjectEventService) getSortColumnName(field string) string {
  174. // 前端字段名到数据库列名的映射
  175. fieldMap := map[string]string{
  176. "deliveryEventType": s.EventDao.Columns.DeliveryEventType,
  177. "deliveryEventStatus": s.EventDao.Columns.DeliveryEventStatus,
  178. "deliveryEventResult": s.EventDao.Columns.DeliveryEventResult,
  179. "opsUserName": s.EventDao.Columns.OpsUserName,
  180. "feedbackReporter": s.EventDao.Columns.FeedbackReporter,
  181. "feedbackDate": s.EventDao.Columns.FeedbackDate,
  182. "completeTime": s.EventDao.Columns.CompleteTime,
  183. }
  184. if colName, ok := fieldMap[field]; ok {
  185. return colName
  186. }
  187. return ""
  188. }
  189. // Create 新增事件,包含事务控制和过程记录、附件存储
  190. func (s *DeliveryProjectEventService) Create(req *eventmodel.OpsDeliveryProjectEventAddReq) error {
  191. // 生成事件编码
  192. eventNo := s.generateEventNo()
  193. // 硬件发货(40)和硬件安装(41)默认状态为处理中(20)
  194. status := "10" // 待处理
  195. if req.DeliveryEventType == "40" || req.DeliveryEventType == "41" {
  196. status = "20"
  197. }
  198. // 构造数据,负责人默认为当前登录人,允许前端传入
  199. opsUserId := s.GetCxtUserId()
  200. opsUserName := s.GetCxtUserName()
  201. if req.OpsUserId > 0 {
  202. opsUserId = req.OpsUserId
  203. opsUserName = req.OpsUserName
  204. }
  205. data := g.Map{
  206. s.EventDao.Columns.ProjectId: req.ProjectId,
  207. s.EventDao.Columns.DeliveryEventNo: eventNo,
  208. s.EventDao.Columns.DeliveryEventTitle: req.DeliveryEventTitle,
  209. s.EventDao.Columns.DeliveryEventDesc: req.DeliveryEventDesc,
  210. s.EventDao.Columns.DeliveryEventType: req.DeliveryEventType,
  211. s.EventDao.Columns.DeliveryEventStatus: status,
  212. s.EventDao.Columns.FeedbackSource: req.FeedbackSource,
  213. s.EventDao.Columns.FeedbackReporter: req.FeedbackReporter,
  214. s.EventDao.Columns.FeedbackDate: gtime.Now(),
  215. s.EventDao.Columns.OnSite: req.OnSite,
  216. s.EventDao.Columns.OpsUserId: opsUserId,
  217. s.EventDao.Columns.OpsUserName: opsUserName,
  218. }
  219. if req.Attribute1 != "" {
  220. data[s.EventDao.Columns.Attribute1] = req.Attribute1
  221. }
  222. if req.CompleteTime != "" {
  223. data[s.EventDao.Columns.CompleteTime] = req.CompleteTime
  224. }
  225. if req.ActualWorkHour > 0 {
  226. data[s.EventDao.Columns.ActualWorkHour] = req.ActualWorkHour
  227. }
  228. // 补齐审计字段
  229. service.SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  230. // 使用事务控制
  231. return s.EventDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  232. // 1. 创建事件记录
  233. result, err := s.EventDao.TX(tx).Data(data).Insert()
  234. if err != nil {
  235. g.Log().Error(err)
  236. return myerrors.DbError("新增事件失败")
  237. }
  238. // 获取新创建的事件ID
  239. eventId, err := result.LastInsertId()
  240. if err != nil {
  241. g.Log().Error(err)
  242. return myerrors.DbError("获取事件ID失败")
  243. }
  244. // 2. 创建事件过程记录
  245. recordData := g.Map{
  246. s.RecordDao.Columns.DeliveryEventId: eventId,
  247. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  248. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  249. s.RecordDao.Columns.HandleContent: "创建事件<br/>事件标题: " + req.DeliveryEventTitle,
  250. }
  251. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  252. recordResult, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  253. if err != nil {
  254. g.Log().Error(err)
  255. return myerrors.DbError("新增事件过程记录失败")
  256. }
  257. recordId, err := recordResult.LastInsertId()
  258. if err != nil {
  259. g.Log().Error(err)
  260. return myerrors.DbError("获取过程记录ID失败")
  261. }
  262. // 3. 保存附件信息
  263. if len(req.Attachments) > 0 {
  264. for _, att := range req.Attachments {
  265. attData := g.Map{
  266. s.AttachmentDao.Columns.DeliveryEventId: eventId,
  267. s.AttachmentDao.Columns.EventId: eventId,
  268. s.AttachmentDao.Columns.EventRecordId: recordId,
  269. s.AttachmentDao.Columns.FileName: att.FileName,
  270. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  271. s.AttachmentDao.Columns.FileType: att.FileType,
  272. }
  273. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  274. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  275. if err != nil {
  276. g.Log().Error(err)
  277. return myerrors.DbError("保存附件信息失败")
  278. }
  279. }
  280. }
  281. // 4. 特定事件类型自动创建研发任务
  282. if taskType, ok := eventTypeToAutoTaskType[req.DeliveryEventType]; ok {
  283. taskNo := s.generateTaskNo()
  284. taskData := g.Map{
  285. s.TaskDao.Columns.TaskNo: taskNo,
  286. s.TaskDao.Columns.ProjectId: req.ProjectId,
  287. s.TaskDao.Columns.ProjectName: s.getProjectName(req.ProjectId),
  288. s.TaskDao.Columns.TaskTitle: req.DeliveryEventTitle,
  289. s.TaskDao.Columns.TaskDesc: req.DeliveryEventDesc,
  290. s.TaskDao.Columns.FunctionName: req.DeliveryEventTitle,
  291. s.TaskDao.Columns.TaskType: taskType,
  292. s.TaskDao.Columns.TaskStatus: eventmodel.TaskStatusTodo,
  293. s.TaskDao.Columns.Priority: "20",
  294. s.TaskDao.Columns.EventId: int(eventId),
  295. s.TaskDao.Columns.EventType: eventmodel.EventTypeDelivery,
  296. }
  297. service.SetCreatedInfo(taskData, s.GetCxtUserId(), s.GetCxtUserName())
  298. taskResult, err := s.TaskDao.TX(tx).Data(taskData).Insert()
  299. if err != nil {
  300. g.Log().Error(err)
  301. return myerrors.DbError("自动创建研发任务失败")
  302. }
  303. taskId, _ := taskResult.LastInsertId()
  304. taskRecordData := g.Map{
  305. s.TaskRecordDao.Columns.TaskId: taskId,
  306. s.TaskRecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  307. s.TaskRecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  308. s.TaskRecordDao.Columns.HandleContent: "自动创建任务<br/>说明: 由交付事件自动创建 (" + req.DeliveryEventTitle + ")",
  309. }
  310. service.SetCreatedInfo(taskRecordData, s.GetCxtUserId(), s.GetCxtUserName())
  311. taskRecordResult, err := s.TaskRecordDao.TX(tx).Data(taskRecordData).Insert()
  312. if err != nil {
  313. g.Log().Error(err)
  314. return myerrors.DbError("新增任务过程记录失败")
  315. }
  316. taskRecordId, _ := taskRecordResult.LastInsertId()
  317. // 5. 复制交付事件附件到研发任务
  318. if len(req.Attachments) > 0 {
  319. for _, att := range req.Attachments {
  320. attData := g.Map{
  321. s.TaskAttachmentDao.Columns.TaskId: taskId,
  322. s.TaskAttachmentDao.Columns.TaskRecordId: int(taskRecordId),
  323. s.TaskAttachmentDao.Columns.FileName: att.FileName,
  324. s.TaskAttachmentDao.Columns.FileUrl: att.FileUrl,
  325. s.TaskAttachmentDao.Columns.FileType: att.FileType,
  326. }
  327. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  328. _, err = s.TaskAttachmentDao.TX(tx).Data(attData).Insert()
  329. if err != nil {
  330. g.Log().Error(err)
  331. return myerrors.DbError("复制任务附件失败")
  332. }
  333. }
  334. }
  335. }
  336. return nil
  337. })
  338. }
  339. // UpdateById 根据ID更新事件
  340. func (s *DeliveryProjectEventService) UpdateById(req *eventmodel.OpsDeliveryProjectEventUpdateReq) error {
  341. // 校验数据是否存在
  342. var entity eventmodel.OpsDeliveryProjectEvent
  343. err := s.EventDao.FieldsEx(s.EventDao.Columns.DeletedTime).WherePri(s.EventDao.Columns.Id, req.Id).Scan(&entity)
  344. if err != nil {
  345. g.Log().Error(err)
  346. return myerrors.DbError("查询事件数据失败")
  347. }
  348. if entity.Id <= 0 {
  349. return myerrors.TipsError("事件数据不存在")
  350. }
  351. // 构造更新数据 - 从请求中动态构建,支持前端传来的任何有效字段
  352. data := g.Map{}
  353. // 基础字段(编辑时可能更新,但关闭/处理事件时不传这些字段)
  354. if req.DeliveryEventTitle != "" {
  355. data[s.EventDao.Columns.DeliveryEventTitle] = req.DeliveryEventTitle
  356. }
  357. if req.DeliveryEventDesc != "" {
  358. data[s.EventDao.Columns.DeliveryEventDesc] = req.DeliveryEventDesc
  359. }
  360. if req.DeliveryEventType != "" {
  361. data[s.EventDao.Columns.DeliveryEventType] = req.DeliveryEventType
  362. }
  363. if req.FeedbackSource != "" {
  364. data[s.EventDao.Columns.FeedbackSource] = req.FeedbackSource
  365. }
  366. if req.FeedbackReporter != "" {
  367. data[s.EventDao.Columns.FeedbackReporter] = req.FeedbackReporter
  368. }
  369. if req.OnSite != "" {
  370. data[s.EventDao.Columns.OnSite] = req.OnSite
  371. }
  372. // 处理状态和结果字段(关闭事件时传入)
  373. if req.DeliveryEventStatus != "" {
  374. data[s.EventDao.Columns.DeliveryEventStatus] = req.DeliveryEventStatus
  375. }
  376. if req.DeliveryEventResult != "" {
  377. data[s.EventDao.Columns.DeliveryEventResult] = req.DeliveryEventResult
  378. }
  379. // 处理完成相关字段(处理/关闭事件时传入)
  380. if req.CompleteDesc != "" {
  381. data[s.EventDao.Columns.CompleteDesc] = req.CompleteDesc
  382. }
  383. if req.CompleteTime != "" {
  384. data[s.EventDao.Columns.CompleteTime] = req.CompleteTime
  385. }
  386. if req.ActualWorkHour > 0 {
  387. data[s.EventDao.Columns.ActualWorkHour] = req.ActualWorkHour
  388. }
  389. // 处理负责人相关字段(分配时传入)
  390. if req.OpsUserId > 0 {
  391. data[s.EventDao.Columns.OpsUserId] = req.OpsUserId
  392. data[s.EventDao.Columns.OpsUserName] = req.OpsUserName
  393. }
  394. // 物流单号
  395. if req.Attribute1 != "" {
  396. data[s.EventDao.Columns.Attribute1] = req.Attribute1
  397. }
  398. // 补齐审计字段
  399. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  400. // 更新操作,排除不可改字段
  401. _, err = s.EventDao.FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.EventDao.Columns.Id, req.Id).Update()
  402. if err != nil {
  403. g.Log().Error(err)
  404. return myerrors.DbError("更新事件失败")
  405. }
  406. // 事件关闭时,根据事件类型自动推进关联项目的状态和节点(只允许前进,不允许回退)
  407. if req.DeliveryEventStatus == eventmodel.DeliveryEventStatusClosed {
  408. s.advanceProjectStatusOnEventClose(entity.ProjectId, req.DeliveryEventType)
  409. }
  410. return nil
  411. }
  412. func (s *DeliveryProjectEventService) advanceProjectStatusOnEventClose(projectId int, eventType string) {
  413. targetStatus, targetNode := s.getTargetStatusAndNode(eventType)
  414. if targetStatus == "" && targetNode == "" {
  415. return
  416. }
  417. var project eventmodel.OpsDeliveryProject
  418. err := s.ProjectDao.Fields(
  419. s.ProjectDao.Columns.Id,
  420. s.ProjectDao.Columns.ProjectStatus,
  421. s.ProjectDao.Columns.DeliveryNode,
  422. ).WherePri(s.ProjectDao.Columns.Id, projectId).Scan(&project)
  423. if err != nil || project.Id <= 0 {
  424. g.Log().Error("查询项目数据失败:", err)
  425. return
  426. }
  427. updateData := g.Map{}
  428. currentStatus := project.ProjectStatus
  429. currentNode := project.DeliveryNode
  430. if targetStatus != "" && s.isForwardTransition(currentStatus, targetStatus) {
  431. updateData[s.ProjectDao.Columns.ProjectStatus] = targetStatus
  432. }
  433. if targetNode != "" && s.isForwardTransition(currentNode, targetNode) {
  434. updateData[s.ProjectDao.Columns.DeliveryNode] = targetNode
  435. }
  436. if len(updateData) == 0 {
  437. return
  438. }
  439. service.SetUpdatedInfo(updateData, s.GetCxtUserId(), s.GetCxtUserName())
  440. _, err = s.ProjectDao.Data(updateData).WherePri(s.ProjectDao.Columns.Id, projectId).Update()
  441. if err != nil {
  442. g.Log().Error("更新项目状态失败:", err)
  443. }
  444. }
  445. func (s *DeliveryProjectEventService) getTargetStatusAndNode(eventType string) (status string, node string) {
  446. statusNodeMap := map[string][2]string{
  447. "10": {"20", "10"},
  448. "15": {"20", "15"},
  449. "20": {"", "20"},
  450. "30": {"", "30"},
  451. "31": {"", "30"},
  452. "32": {"", "30"},
  453. "33": {"", "30"},
  454. "35": {"", "30"},
  455. "37": {"", "30"},
  456. "38": {"", "30"},
  457. "39": {"", "30"},
  458. "40": {"", "30"},
  459. "41": {"", "30"},
  460. "42": {"", "30"},
  461. "50": {"", "30"},
  462. "55": {"40", "40"},
  463. "60": {"50", "60"},
  464. }
  465. if entry, ok := statusNodeMap[eventType]; ok {
  466. return entry[0], entry[1]
  467. }
  468. return "", ""
  469. }
  470. func (s *DeliveryProjectEventService) isForwardTransition(current, target string) bool {
  471. currentNum, err1 := strconv.Atoi(current)
  472. targetNum, err2 := strconv.Atoi(target)
  473. if err1 != nil || err2 != nil {
  474. return target > current
  475. }
  476. return targetNum > currentNum
  477. }
  478. // DeleteByIds 根据ID批量删除
  479. func (s *DeliveryProjectEventService) DeleteByIds(ids []int64) error {
  480. if len(ids) == 0 {
  481. return myerrors.TipsError("请选择需要删除的事件")
  482. }
  483. return s.EventDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  484. // 1. 删除事件
  485. _, err := s.EventDao.TX(tx).WhereIn(s.EventDao.Columns.Id, ids).Delete()
  486. if err != nil {
  487. g.Log().Error(err)
  488. return myerrors.DbError("删除事件失败")
  489. }
  490. // 2. 删除关联的过程记录
  491. _, err = s.RecordDao.TX(tx).WhereIn(s.RecordDao.Columns.DeliveryEventId, ids).Delete()
  492. if err != nil {
  493. g.Log().Error(err)
  494. return myerrors.DbError("删除事件过程记录失败")
  495. }
  496. // 3. 删除关联的附件
  497. _, err = s.AttachmentDao.TX(tx).WhereIn(s.AttachmentDao.Columns.DeliveryEventId, ids).Delete()
  498. if err != nil {
  499. g.Log().Error(err)
  500. return myerrors.DbError("删除事件附件失败")
  501. }
  502. return nil
  503. })
  504. }
  505. // Cancel 作废事件
  506. func (s *DeliveryProjectEventService) Cancel(req *eventmodel.OpsDeliveryProjectEventCancelReq) error {
  507. // 校验数据是否存在
  508. var entity eventmodel.OpsDeliveryProjectEvent
  509. err := s.EventDao.FieldsEx(s.EventDao.Columns.DeletedTime).WherePri(s.EventDao.Columns.Id, req.Id).Scan(&entity)
  510. if err != nil {
  511. g.Log().Error(err)
  512. return myerrors.DbError("查询事件数据失败")
  513. }
  514. if entity.Id <= 0 {
  515. return myerrors.TipsError("事件数据不存在")
  516. }
  517. // 已关闭的事件不能作废
  518. if entity.DeliveryEventStatus == eventmodel.DeliveryEventStatusClosed {
  519. return myerrors.TipsError("已关闭的事件不能作废")
  520. }
  521. // 已作废的事件不能再次作废
  522. if entity.DeliveryEventStatus == eventmodel.DeliveryEventStatusCancelled {
  523. return myerrors.TipsError("该事件已作废")
  524. }
  525. return s.EventDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  526. // 1. 更新事件状态为已作废
  527. data := g.Map{
  528. s.EventDao.Columns.DeliveryEventStatus: eventmodel.DeliveryEventStatusCancelled,
  529. s.EventDao.Columns.Remark: req.CancelReason,
  530. }
  531. service.SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
  532. _, err := s.EventDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(data).WherePri(s.EventDao.Columns.Id, req.Id).Update()
  533. if err != nil {
  534. g.Log().Error(err)
  535. return myerrors.DbError("作废事件失败")
  536. }
  537. // 2. 添加作废过程记录
  538. recordContent := "作废事件<br/>作废原因: " + req.CancelReason
  539. recordData := g.Map{
  540. s.RecordDao.Columns.DeliveryEventId: req.Id,
  541. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  542. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  543. s.RecordDao.Columns.HandleContent: recordContent,
  544. }
  545. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  546. recordResult, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  547. if err != nil {
  548. g.Log().Error(err)
  549. return myerrors.DbError("新增过程记录失败")
  550. }
  551. recordId, _ := recordResult.LastInsertId()
  552. // 3. 保存附件
  553. if len(req.Attachments) > 0 {
  554. for _, att := range req.Attachments {
  555. attData := g.Map{
  556. s.AttachmentDao.Columns.DeliveryEventId: req.Id,
  557. s.AttachmentDao.Columns.EventId: req.Id,
  558. s.AttachmentDao.Columns.EventRecordId: recordId,
  559. s.AttachmentDao.Columns.FileName: att.FileName,
  560. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  561. s.AttachmentDao.Columns.FileType: att.FileType,
  562. }
  563. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  564. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  565. if err != nil {
  566. g.Log().Error(err)
  567. return myerrors.DbError("保存附件信息失败")
  568. }
  569. }
  570. }
  571. _ = recordId // 避免未使用变量警告
  572. // 4. 查询关联的任务并作废
  573. var tasks []*eventmodel.OpsEventTask
  574. err = s.TaskDao.TX(tx).Where(s.TaskDao.Columns.EventId, req.Id).
  575. Where(s.TaskDao.Columns.EventType, eventmodel.EventTypeDelivery).
  576. Scan(&tasks)
  577. if err != nil {
  578. g.Log().Error(err)
  579. return myerrors.DbError("查询关联任务失败")
  580. }
  581. // 5. 作废关联的任务
  582. for _, task := range tasks {
  583. // 已完成的任务不能作废
  584. if task.TaskStatus == "30" {
  585. continue
  586. }
  587. // 已作废的任务不需要再次作废
  588. if task.TaskStatus == "90" {
  589. continue
  590. }
  591. // 更新任务状态为作废
  592. taskData := g.Map{
  593. s.TaskDao.Columns.TaskStatus: "90",
  594. }
  595. service.SetUpdatedInfo(taskData, s.GetCxtUserId(), s.GetCxtUserName())
  596. _, err := s.TaskDao.TX(tx).FieldsEx(service.UpdateFieldEx...).Data(taskData).
  597. WherePri(s.TaskDao.Columns.Id, task.Id).Update()
  598. if err != nil {
  599. g.Log().Error(err)
  600. return myerrors.DbError("作废关联任务失败")
  601. }
  602. // 添加任务作废过程记录
  603. taskRecordData := g.Map{
  604. s.TaskRecordDao.Columns.TaskId: task.Id,
  605. s.TaskRecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  606. s.TaskRecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  607. s.TaskRecordDao.Columns.HandleContent: "作废任务<br/>作废原因: 关联事件被作废 - " + req.CancelReason,
  608. }
  609. service.SetCreatedInfo(taskRecordData, s.GetCxtUserId(), s.GetCxtUserName())
  610. _, err = s.TaskRecordDao.TX(tx).Data(taskRecordData).Insert()
  611. if err != nil {
  612. g.Log().Error(err)
  613. return myerrors.DbError("新增任务过程记录失败")
  614. }
  615. }
  616. return nil
  617. })
  618. }
  619. // GetById 根据ID查询单条(关联项目信息)
  620. func (s *DeliveryProjectEventService) GetById(id int) (*eventmodel.OpsDeliveryProjectEventRsp, error) {
  621. var entity eventmodel.OpsDeliveryProjectEvent
  622. err := s.EventDao.FieldsEx(s.EventDao.Columns.DeletedTime).WherePri(s.EventDao.Columns.Id, id).Scan(&entity)
  623. if err != nil {
  624. g.Log().Error(err)
  625. return nil, myerrors.DbError("查询事件数据失败")
  626. }
  627. if entity.Id <= 0 {
  628. return nil, myerrors.TipsError("事件数据不存在")
  629. }
  630. var rsp eventmodel.OpsDeliveryProjectEventRsp
  631. if err := gconv.Struct(entity, &rsp); err != nil {
  632. g.Log().Error(err)
  633. return nil, myerrors.DbError("数据转换失败")
  634. }
  635. // 查询关联的项目信息
  636. if entity.ProjectId > 0 {
  637. var project eventmodel.OpsDeliveryProject
  638. err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
  639. WherePri(s.ProjectDao.Columns.Id, entity.ProjectId).
  640. Scan(&project)
  641. if err != nil {
  642. g.Log().Error(err)
  643. } else if project.Id > 0 {
  644. // 填充项目信息
  645. rsp.ProjectName = project.ProjectName
  646. rsp.ContractNo = project.ContractNo
  647. rsp.CustName = project.CustName
  648. rsp.ProductLine = project.ProductLine
  649. rsp.SalesUserName = project.SalesUserName
  650. // 填充关键节点时间
  651. if project.InternalKickoffTime != nil {
  652. rsp.InternalKickoffTime = project.InternalKickoffTime.Format("Y-m-d H:i:s")
  653. }
  654. if project.ExternalKickoffTime != nil {
  655. rsp.ExternalKickoffTime = project.ExternalKickoffTime.Format("Y-m-d H:i:s")
  656. }
  657. if project.DeliveryPlanSubmitTime != nil {
  658. rsp.DeliveryPlanSubmitTime = project.DeliveryPlanSubmitTime.Format("Y-m-d H:i:s")
  659. }
  660. if project.DeploymentTime != nil {
  661. rsp.DeploymentTime = project.DeploymentTime.Format("Y-m-d H:i:s")
  662. }
  663. if project.TrialRunTime != nil {
  664. rsp.TrialRunTime = project.TrialRunTime.Format("Y-m-d H:i:s")
  665. }
  666. if project.GoLiveTime != nil {
  667. rsp.GoLiveTime = project.GoLiveTime.Format("Y-m-d H:i:s")
  668. }
  669. }
  670. }
  671. return &rsp, nil
  672. }
  673. // GetAttachments 获取事件附件列表
  674. func (s *DeliveryProjectEventService) GetAttachments(eventId int) ([]*eventmodel.OpsDeliveryProjectEventAttachment, error) {
  675. var list []*eventmodel.OpsDeliveryProjectEventAttachment
  676. err := s.AttachmentDao.Where(s.AttachmentDao.Columns.DeliveryEventId, eventId).Scan(&list)
  677. if err != nil {
  678. g.Log().Error(err)
  679. return nil, myerrors.DbError("查询附件列表失败")
  680. }
  681. return list, nil
  682. }
  683. // GetRecords 获取事件过程记录列表(包含附件)
  684. func (s *DeliveryProjectEventService) GetRecords(req *eventmodel.OpsDeliveryProjectEventRecordSearchReq) ([]*eventmodel.OpsDeliveryProjectEventRecordWithAttachments, error) {
  685. var records []*eventmodel.OpsDeliveryProjectEventRecord
  686. err := s.RecordDao.Where(s.RecordDao.Columns.DeliveryEventId, req.DeliveryEventId).
  687. Order(s.RecordDao.Columns.CreatedTime + " desc").
  688. Scan(&records)
  689. if err != nil {
  690. g.Log().Error(err)
  691. return nil, myerrors.DbError("查询过程记录失败")
  692. }
  693. result := make([]*eventmodel.OpsDeliveryProjectEventRecordWithAttachments, 0, len(records))
  694. for _, record := range records {
  695. recordRsp := &eventmodel.OpsDeliveryProjectEventRecordWithAttachments{
  696. OpsDeliveryProjectEventRecord: *record,
  697. Attachments: []*eventmodel.OpsDeliveryProjectEventAttachment{},
  698. }
  699. // 查询该记录关联的附件
  700. if record.Id > 0 {
  701. var attachments []*eventmodel.OpsDeliveryProjectEventAttachment
  702. err := s.AttachmentDao.Where(s.AttachmentDao.Columns.EventRecordId, record.Id).
  703. Scan(&attachments)
  704. if err != nil {
  705. g.Log().Error(err)
  706. } else {
  707. recordRsp.Attachments = attachments
  708. }
  709. }
  710. result = append(result, recordRsp)
  711. }
  712. return result, nil
  713. }
  714. // AddRecord 添加事件过程记录(带附件)
  715. func (s *DeliveryProjectEventService) AddRecord(req *eventmodel.OpsDeliveryProjectEventRecordAddReqWithAttachments) error {
  716. return s.EventDao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
  717. // 1. 创建过程记录
  718. recordData := g.Map{
  719. s.RecordDao.Columns.DeliveryEventId: req.DeliveryEventId,
  720. s.RecordDao.Columns.HandleUserId: s.GetCxtUserId(),
  721. s.RecordDao.Columns.HandleUserName: s.GetCxtUserName(),
  722. s.RecordDao.Columns.HandleContent: req.HandleContent,
  723. }
  724. service.SetCreatedInfo(recordData, s.GetCxtUserId(), s.GetCxtUserName())
  725. result, err := s.RecordDao.TX(tx).Data(recordData).Insert()
  726. if err != nil {
  727. g.Log().Error(err)
  728. return myerrors.DbError("新增过程记录失败")
  729. }
  730. // 获取记录ID
  731. recordId, err := result.LastInsertId()
  732. if err != nil {
  733. g.Log().Error(err)
  734. return myerrors.DbError("获取记录ID失败")
  735. }
  736. // 2. 保存附件
  737. if len(req.Attachments) > 0 {
  738. for _, att := range req.Attachments {
  739. attData := g.Map{
  740. s.AttachmentDao.Columns.DeliveryEventId: req.DeliveryEventId,
  741. s.AttachmentDao.Columns.EventId: req.DeliveryEventId,
  742. s.AttachmentDao.Columns.EventRecordId: recordId,
  743. s.AttachmentDao.Columns.FileName: att.FileName,
  744. s.AttachmentDao.Columns.FileUrl: att.FileUrl,
  745. s.AttachmentDao.Columns.FileType: att.FileType,
  746. }
  747. service.SetCreatedInfo(attData, s.GetCxtUserId(), s.GetCxtUserName())
  748. _, err = s.AttachmentDao.TX(tx).Data(attData).Insert()
  749. if err != nil {
  750. g.Log().Error(err)
  751. return myerrors.DbError("保存附件信息失败")
  752. }
  753. }
  754. }
  755. return nil
  756. })
  757. }
  758. // generateTaskNo 生成任务编号(与OpsEventTaskService保持统一格式,使用数据库序列防并发冲突)
  759. func (s *DeliveryProjectEventService) generateTaskNo() string {
  760. now := gtime.Now()
  761. prefix := "TSK" + now.Format("Ymd")
  762. // 优先使用数据库序列(并发安全),序列异常时降级为查最大+1
  763. seqVal, err := s.TaskDao.DB.GetValue("SELECT next_day_reset_val('task_no_seq')")
  764. if err == nil {
  765. return prefix + fmt.Sprintf("%04d", seqVal.Int())
  766. }
  767. // 降级方案:查询当天最大序号+1(不保证并发安全,仅在序列不可用时兜底)
  768. var maxNoResult struct {
  769. TaskNo string
  770. }
  771. err = s.TaskDao.Where(s.TaskDao.Columns.TaskNo+" like ?", prefix+"%").
  772. Order(s.TaskDao.Columns.TaskNo + " desc").
  773. Fields(s.TaskDao.Columns.TaskNo).
  774. Scan(&maxNoResult)
  775. if err != nil || maxNoResult.TaskNo == "" {
  776. return prefix + "0001"
  777. }
  778. maxNoStr := maxNoResult.TaskNo
  779. if len(maxNoStr) >= len(prefix)+4 {
  780. seq := maxNoStr[len(prefix):]
  781. seqNum := gconv.Int(seq)
  782. seqNum++
  783. return prefix + fmt.Sprintf("%04d", seqNum)
  784. }
  785. return prefix + "0001"
  786. }
  787. // getProjectName 根据项目ID获取项目名称
  788. func (s *DeliveryProjectEventService) getProjectName(projectId int) string {
  789. if projectId <= 0 {
  790. return ""
  791. }
  792. var project eventmodel.OpsDeliveryProject
  793. err := s.ProjectDao.FieldsEx(s.ProjectDao.Columns.DeletedTime).
  794. WherePri(s.ProjectDao.Columns.Id, projectId).
  795. Scan(&project)
  796. if err != nil {
  797. g.Log().Error(err)
  798. return ""
  799. }
  800. return project.ProjectName
  801. }
  802. // generateEventNo 生成事件编码(使用数据库序列防并发冲突,与generateTaskNo保持一致)
  803. func (s *DeliveryProjectEventService) generateEventNo() string {
  804. now := gtime.Now()
  805. prefix := "EVT" + now.Format("Ymd")
  806. seqVal, err := s.EventDao.DB.GetValue("SELECT next_day_reset_val('event_no_seq')")
  807. if err == nil {
  808. return prefix + fmt.Sprintf("%04d", seqVal.Int())
  809. }
  810. var maxNoResult struct {
  811. DeliveryEventNo string
  812. }
  813. err = s.EventDao.Where(s.EventDao.Columns.DeliveryEventNo+" like ?", prefix+"%").
  814. Order(s.EventDao.Columns.DeliveryEventNo + " desc").
  815. Fields(s.EventDao.Columns.DeliveryEventNo).
  816. Scan(&maxNoResult)
  817. if err != nil || maxNoResult.DeliveryEventNo == "" {
  818. return prefix + "0001"
  819. }
  820. maxNoStr := maxNoResult.DeliveryEventNo
  821. if len(maxNoStr) >= len(prefix)+4 {
  822. seq := maxNoStr[len(prefix):]
  823. seqNum := gconv.Int(seq)
  824. seqNum++
  825. return prefix + fmt.Sprintf("%04d", seqNum)
  826. }
  827. return prefix + "0001"
  828. }