delivery_project_event.go 33 KB

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