ProcessOverviewRepository.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. using AutoMapper;
  2. using Newtonsoft.Json;
  3. using OASystem.Domain;
  4. using OASystem.Domain.Dtos.Groups;
  5. using OASystem.Domain.Entities.Groups;
  6. using System.Reflection;
  7. namespace OASystem.Infrastructure.Repositories.Groups
  8. {
  9. /// <summary>
  10. /// 团组流程总览表仓储
  11. /// </summary>
  12. public class ProcessOverviewRepository : BaseRepository<Grp_ProcessOverview, Grp_ProcessOverview>
  13. {
  14. private readonly IMapper _mapper;
  15. private readonly DelegationInfoRepository _groupRep;
  16. public ProcessOverviewRepository(SqlSugarClient sqlSugar, IMapper mapper, DelegationInfoRepository groupRep) : base(sqlSugar)
  17. {
  18. _mapper = mapper;
  19. _groupRep = groupRep;
  20. }
  21. /// <summary>
  22. /// 团组流程初始化
  23. /// </summary>
  24. /// <param name="request">创建流程请求参数</param>
  25. /// <returns>创建的流程信息</returns>
  26. public async Task<Result> ProcessInitAsync(int groupId, int currUserId)
  27. {
  28. //团组验证
  29. var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(g => g.Id == groupId);
  30. if (groupInfo == null)
  31. {
  32. return new Result { Code = 400, Msg = "团组不存在" };
  33. }
  34. // 检查是否已存在流程
  35. var existingProcesses = await _sqlSugar.Queryable<Grp_ProcessOverview>().Where(p => p.GroupId == groupId).ToListAsync();
  36. if (existingProcesses.Any())
  37. {
  38. return new Result { Code = 400, Msg = "该团组的流程已存在" };
  39. }
  40. //处理签证国家
  41. var visaCountries = _groupRep.GroupSplitCountry(groupInfo.VisitCountry);
  42. // 定义默认的流程节点
  43. var processs = Grp_ProcessOverview.ProcessInit(groupId, currUserId, visaCountries);
  44. _sqlSugar.BeginTran();
  45. foreach (var item in processs)
  46. {
  47. var processId = await _sqlSugar.Insertable(item).ExecuteReturnIdentityAsync();
  48. if (processId < 1)
  49. {
  50. _sqlSugar.RollbackTran();
  51. return new Result { Code = 400, Msg = "团组流程进度总览表添加失败!" };
  52. }
  53. // 记录流程日志
  54. await LogProcessOpAsync(null, item, "Create", currUserId);
  55. var nodes = item.Nodes.Select((nodeDto, index) => new Grp_ProcessNode
  56. {
  57. ProcessId = processId,
  58. NodeName = nodeDto.NodeName,
  59. NodeOrder = nodeDto.NodeOrder,
  60. OverallStatus = nodeDto.OverallStatus,
  61. //Country = nodeDto.Country,
  62. IsCurrent = nodeDto.IsCurrent,
  63. Remark = nodeDto.Remark
  64. }).ToList();
  65. var nodeIds = await _sqlSugar.Insertable(nodes).ExecuteCommandAsync();
  66. if (nodeIds < 1)
  67. {
  68. _sqlSugar.RollbackTran();
  69. return new Result { Code = 400, Msg = "团组流程进度流程节点添加失败!" };
  70. }
  71. //记录节点日志
  72. foreach (var node in nodes)
  73. {
  74. await LogNodeOpAsync(null, node, "Create", currUserId);
  75. }
  76. }
  77. _sqlSugar.CommitTran();
  78. return new Result { Code = 200, Msg = "添加成功!" }; ;
  79. }
  80. /// <summary>
  81. /// 获取团组的所有流程及流程详情
  82. /// </summary>
  83. /// <param name="request">创建流程请求参数</param>
  84. /// <returns>创建的流程信息</returns>
  85. public async Task<Result> ProcessesDetailsAsync(int groupId)
  86. {
  87. //团组验证
  88. var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(g => g.Id == groupId);
  89. if (groupInfo == null)
  90. {
  91. return new Result { Code = 400, Msg = "团组不存在" };
  92. }
  93. // 检查是否已存在流程
  94. var existingProcesses = await _sqlSugar.Queryable<Grp_ProcessOverview>().Where(p => p.GroupId == groupId).ToListAsync();
  95. if (!existingProcesses.Any())
  96. {
  97. //新建团组流程
  98. var res = await ProcessInitAsync(groupId, 4);
  99. if (res.Code != 200)
  100. {
  101. return res;
  102. }
  103. }
  104. var users = await _sqlSugar.Queryable<Sys_Users>().ToListAsync();
  105. var processData = await _sqlSugar.Queryable<Grp_ProcessOverview>()
  106. .Where(p => p.GroupId == groupId && p.IsDel == 0)
  107. .Mapper(p => p.Nodes, p => p.Nodes.First().ProcessId)
  108. .ToListAsync();
  109. var processes = processData.Select(p => new
  110. {
  111. p.Id,
  112. p.GroupId,
  113. p.ProcessType,
  114. ProcessName = p.ProcessType.GetEnumDescription(),
  115. //p.OverallStatus,
  116. //StatusText = p.OverallStatus.GetDescription(),
  117. Nodes = p.Nodes.Select(n =>
  118. {
  119. //单独处理签证板块
  120. var visaSubNodes = new List<VisaProcessNode>();
  121. string remark = string.Empty;
  122. if (p.ProcessType == GroupProcessType.Visa)
  123. {
  124. visaSubNodes = JsonConvert.DeserializeObject<List<VisaProcessNode>>(n.Remark);
  125. }
  126. return new
  127. {
  128. n.Id,
  129. n.ProcessId,
  130. n.NodeOrder,
  131. n.NodeName,
  132. n.OverallStatus,
  133. StatusText = n.OverallStatus.GetEnumDescription(),
  134. Operator = users.FirstOrDefault(u => u.Id == n.Operator)?.CnName ?? "-",
  135. OpeateTime = n.OperationTime.HasValue ? n.OperationTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : "-",
  136. //节点类型为签证时使用
  137. visaSubNodes
  138. };
  139. }).OrderBy(n => n.NodeOrder).ToList()
  140. }).ToList();
  141. return new Result { Code = 200, Data = processes, Msg = "查询成功!" };
  142. }
  143. /// <summary>
  144. /// 更新节点状态
  145. /// </summary>
  146. /// <param name="nodeId">节点ID</param>
  147. /// <param name="currUserId">当前用户ID</param>
  148. /// <param name="processStatus">流程状态,默认为已完成</param>
  149. /// <returns>操作结果</returns>
  150. public async Task<Result> UpdateNodeStatusAsync(int nodeId, int currUserId, ProcessStatus processStatus = ProcessStatus.Completed)
  151. {
  152. try
  153. {
  154. // 使用事务确保数据一致性
  155. var result = await _sqlSugar.Ado.UseTranAsync(async () =>
  156. {
  157. // 1. 获取并验证节点
  158. var node = await _sqlSugar.Queryable<Grp_ProcessNode>()
  159. .FirstAsync(n => n.Id == nodeId && n.IsDel == 0);
  160. if (node == null)
  161. {
  162. throw new BusinessException("当前节点不存在或已被删除。");
  163. }
  164. // 新增验证:当前节点已完成,不可操作
  165. ValidateNodeOperation(node, processStatus);
  166. //存储更新前的值
  167. var before = new Grp_ProcessNode() {
  168. Id = node.Id,
  169. ProcessId = node.ProcessId,
  170. NodeName = node.NodeName,
  171. NodeOrder = node.NodeOrder,
  172. OverallStatus = node.OverallStatus,
  173. Operator = node.Operator,
  174. OperationTime = node.OperationTime,
  175. IsCurrent = node.IsCurrent,
  176. };
  177. // 2. 更新节点状态
  178. node.OverallStatus = processStatus;
  179. node.Operator = currUserId;
  180. node.OperationTime = DateTime.Now;
  181. var updateCount = await _sqlSugar.Updateable(node)
  182. .UpdateColumns(n => new
  183. {
  184. n.OverallStatus,
  185. n.Operator,
  186. n.OperationTime
  187. })
  188. .ExecuteCommandAsync();
  189. if (updateCount == 0)
  190. {
  191. throw new BusinessException("节点状态更新失败。");
  192. }
  193. //记录节点日志
  194. await LogNodeOpAsync(before, node, "Update", currUserId);
  195. // 3. 如果是完成当前节点,处理流程流转
  196. if (processStatus == ProcessStatus.Completed && node.IsCurrent)
  197. {
  198. await ProcessCurrentNodeCompletionAsync(node, currUserId);
  199. }
  200. return new Result { Code = StatusCodes.Status200OK, Msg = "操作成功。" };
  201. });
  202. return result.IsSuccess ? result.Data : new Result
  203. {
  204. Code = StatusCodes.Status500InternalServerError,
  205. Msg = result.ErrorMessage
  206. };
  207. }
  208. catch (BusinessException ex)
  209. {
  210. // 业务异常
  211. return new Result { Code = StatusCodes.Status400BadRequest, Msg = ex.Message };
  212. }
  213. catch (Exception ex)
  214. {
  215. // 系统异常
  216. return new Result { Code = StatusCodes.Status500InternalServerError, Msg = "系统错误,请稍后重试" };
  217. }
  218. }
  219. /// <summary>
  220. /// 验证节点操作权限
  221. /// </summary>
  222. /// <param name="node">流程节点</param>
  223. /// <param name="targetStatus">目标状态</param>
  224. private static void ValidateNodeOperation(Grp_ProcessNode node, ProcessStatus targetStatus)
  225. {
  226. // 验证节点是否已完成
  227. if (node.OverallStatus == ProcessStatus.Completed)
  228. {
  229. throw new BusinessException("当前节点已完成,不可重复操作。");
  230. }
  231. // 验证状态流转是否合法(可选)
  232. if (targetStatus != ProcessStatus.Completed)
  233. {
  234. throw new BusinessException("未开始或者进行中的节点只能重新完成,不可进行其他操作。");
  235. }
  236. // 验证是否尝试将已完成节点改为其他状态
  237. if (node.OverallStatus == ProcessStatus.Completed && targetStatus != ProcessStatus.Completed)
  238. {
  239. throw new BusinessException("已完成节点不可修改状态。");
  240. }
  241. }
  242. /// <summary>
  243. /// 处理当前节点完成后的流程流转
  244. /// </summary>
  245. private async Task ProcessCurrentNodeCompletionAsync(Grp_ProcessNode currentNode, int currUserId)
  246. {
  247. // 1. 获取流程信息
  248. var process = await _sqlSugar.Queryable<Grp_ProcessOverview>()
  249. .FirstAsync(p => p.Id == currentNode.ProcessId && p.IsDel == 0);
  250. if (process == null)
  251. {
  252. throw new BusinessException("关联的流程不存在。");
  253. }
  254. var processBefore = new Grp_ProcessOverview()
  255. {
  256. Id = process.Id,
  257. GroupId = process.GroupId,
  258. ProcessOrder = process.ProcessOrder,
  259. ProcessType = process.ProcessType,
  260. OverallStatus = process.OverallStatus,
  261. StartTime = process.StartTime,
  262. EndTime = process.EndTime,
  263. UpdatedUserId = process.UpdatedUserId,
  264. UpdatedTime = process.UpdatedTime
  265. };
  266. // 2. 取消当前节点的当前状态
  267. var before = new Grp_ProcessNode()
  268. {
  269. Id = currentNode.Id,
  270. ProcessId = currentNode.ProcessId,
  271. NodeName = currentNode.NodeName,
  272. NodeOrder = currentNode.NodeOrder,
  273. OverallStatus = currentNode.OverallStatus,
  274. Operator = currentNode.Operator,
  275. OperationTime = currentNode.OperationTime,
  276. IsCurrent = currentNode.IsCurrent,
  277. };
  278. currentNode.IsCurrent = false;
  279. await _sqlSugar.Updateable(currentNode)
  280. .UpdateColumns(n => new { n.IsCurrent })
  281. .ExecuteCommandAsync();
  282. // 2.1 记录节点日志 取消当前节点状态
  283. await LogNodeOpAsync(before, currentNode, "Update", currUserId);
  284. // 3. 查找并激活下一个节点
  285. var nextNode = await _sqlSugar.Queryable<Grp_ProcessNode>()
  286. .Where(n => n.ProcessId == currentNode.ProcessId
  287. && n.NodeOrder == currentNode.NodeOrder + 1
  288. && n.IsDel == 0)
  289. .FirstAsync();
  290. if (nextNode != null)
  291. {
  292. var nextNodeBefore = new Grp_ProcessNode()
  293. {
  294. Id = nextNode.Id,
  295. ProcessId = nextNode.ProcessId,
  296. NodeName = nextNode.NodeName,
  297. NodeOrder = nextNode.NodeOrder,
  298. OverallStatus = nextNode.OverallStatus,
  299. Operator = nextNode.Operator,
  300. OperationTime = nextNode.OperationTime,
  301. IsCurrent = nextNode.IsCurrent,
  302. };
  303. // 激活下一个节点
  304. nextNode.IsCurrent = true;
  305. nextNode.OverallStatus = ProcessStatus.InProgress;
  306. //nextNode.Operator = currUserId;
  307. //nextNode.OperationTime = DateTime.Now;
  308. var updateCount = await _sqlSugar.Updateable(nextNode)
  309. .UpdateColumns(n => new
  310. {
  311. n.IsCurrent,
  312. n.OverallStatus,
  313. n.Operator,
  314. n.OperationTime
  315. })
  316. .ExecuteCommandAsync();
  317. if (updateCount == 0)
  318. {
  319. throw new BusinessException("激活下一节点失败");
  320. }
  321. // 1.1 记录节点日志 激活下一节点当前节点状态
  322. await LogNodeOpAsync(nextNodeBefore, nextNode, "Start", currUserId);
  323. // 更新流程状态为进行中
  324. process.OverallStatus = ProcessStatus.InProgress;
  325. }
  326. else
  327. {
  328. // 下一节点不存在,整个流程完成
  329. process.OverallStatus = ProcessStatus.Completed;
  330. process.EndTime = DateTime.Now;
  331. }
  332. // 4. 更新流程信息
  333. process.UpdatedUserId = currUserId;
  334. process.UpdatedTime = DateTime.Now;
  335. var processUpdateCount = await _sqlSugar.Updateable(process)
  336. .UpdateColumns(p => new
  337. {
  338. p.OverallStatus,
  339. p.EndTime,
  340. p.UpdatedUserId,
  341. p.UpdatedTime
  342. })
  343. .ExecuteCommandAsync();
  344. if (processUpdateCount == 0)
  345. {
  346. throw new BusinessException("流程状态更新失败。");
  347. }
  348. //记录流程日志
  349. await LogProcessOpAsync(processBefore, process, "Update", currUserId);
  350. }
  351. /// <summary>
  352. /// 更新签证节点信息及状态
  353. /// </summary>
  354. /// <param name="dto">签证节点更新数据传输对象</param>
  355. /// <returns>操作结果</returns>
  356. public async Task<Result> UpdateVisaNodeDetailsAsync(GroupProcessUpdateVisaNodeDetailsDto dto)
  357. {
  358. // 1. 获取并验证节点和流程
  359. var node = await _sqlSugar.Queryable<Grp_ProcessNode>()
  360. .FirstAsync(n => n.Id == dto.NodeId && n.IsDel == 0)
  361. ?? throw new BusinessException("当前节点不存在或已被删除。");
  362. var process = await _sqlSugar.Queryable<Grp_ProcessOverview>()
  363. .FirstAsync(p => p.Id == node.ProcessId && p.IsDel == 0)
  364. ?? throw new BusinessException("当前流程不存在或已被删除。");
  365. if (process.ProcessType != GroupProcessType.Visa)
  366. {
  367. throw new BusinessException("当前流程节点不为签证流程,不可编辑。");
  368. }
  369. // 2. 检查签证子节点 字段信息是否全部填写
  370. var allSubNodesCompleted = dto.VisaSubNodes?.All(subNode => EntityExtensions.IsCompleted(subNode)) ?? false;
  371. // 2.1 存储更新前流程及节点信息
  372. var nodeBefore = new Grp_ProcessNode()
  373. {
  374. Id = node.Id,
  375. ProcessId = node.ProcessId,
  376. NodeName = node.NodeName,
  377. NodeOrder = node.NodeOrder,
  378. OverallStatus = node.OverallStatus,
  379. Operator = node.Operator,
  380. OperationTime = node.OperationTime,
  381. IsCurrent = node.IsCurrent,
  382. };
  383. var processBefore = new Grp_ProcessOverview()
  384. {
  385. Id = process.Id,
  386. GroupId = process.GroupId,
  387. ProcessOrder = process.ProcessOrder,
  388. ProcessType = process.ProcessType,
  389. OverallStatus = process.OverallStatus,
  390. StartTime = process.StartTime,
  391. EndTime = process.EndTime,
  392. UpdatedUserId = process.UpdatedUserId,
  393. UpdatedTime = process.UpdatedTime
  394. };
  395. // 3. 更新节点信息
  396. node.Remark = JsonConvert.SerializeObject(dto.VisaSubNodes);
  397. node.Operator = dto.CurrUserId;
  398. node.OperationTime = DateTime.Now;
  399. if (allSubNodesCompleted)
  400. {
  401. node.OverallStatus = ProcessStatus.Completed;
  402. process.OverallStatus = ProcessStatus.Completed;
  403. process.EndTime = DateTime.Now;
  404. process.UpdatedUserId = dto.CurrUserId;
  405. process.UpdatedTime = DateTime.Now;
  406. // 更新流程状态
  407. await _sqlSugar.Updateable(process)
  408. .UpdateColumns(p => new
  409. {
  410. p.OverallStatus,
  411. p.EndTime,
  412. p.UpdatedUserId,
  413. p.UpdatedTime
  414. })
  415. .ExecuteCommandAsync();
  416. //记录流程日志
  417. await LogProcessOpAsync(processBefore, process, "Update", dto.CurrUserId);
  418. }
  419. // 4. 保存节点更新
  420. await _sqlSugar.Updateable(node)
  421. .UpdateColumns(n => new
  422. {
  423. n.Remark,
  424. n.Operator,
  425. n.OperationTime,
  426. n.OverallStatus
  427. })
  428. .ExecuteCommandAsync();
  429. //记录节点日志
  430. await LogNodeOpAsync(nodeBefore, node, "Update", dto.CurrUserId);
  431. return new Result { Code = 200, Msg = "节点信息更新成功。" };
  432. }
  433. #region 操作日志
  434. /// <summary>
  435. /// 记录流程操作日志
  436. /// </summary>
  437. /// <param name="before">操作前</param>
  438. /// <param name="after">操作后</param>
  439. /// <param name="opType">操作类型(Create - 创建、Update - 更新、Complete - 完成)</param>
  440. /// <param name="operId">操作人ID</param>
  441. /// <returns>异步任务</returns>
  442. public async Task LogProcessOpAsync(Grp_ProcessOverview before, Grp_ProcessOverview after,string opType, int operId)
  443. {
  444. var chgDetails = GetProcessChgDetails(before, after);
  445. var log = new Grp_ProcessLog
  446. {
  447. ProcessId = after?.Id ?? before?.Id,
  448. GroupId = after?.GroupId ?? before?.GroupId ?? 0,
  449. OpType = opType,
  450. OpDesc = GenerateProcessOpDesc(opType, before, after, chgDetails),
  451. BeforeData = before != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
  452. AfterData = after != null ? JsonConvert.SerializeObject(after, GetJsonSettings()) : null,
  453. ChgFields = string.Join(",", chgDetails.Select(x => x.FieldName)),
  454. CreateUserId = operId
  455. };
  456. await _sqlSugar.Insertable(log).ExecuteCommandAsync();
  457. }
  458. /// <summary>
  459. /// 记录节点操作日志
  460. /// </summary>
  461. /// <param name="before">操作前</param>
  462. /// <param name="after">操作后</param>
  463. /// <param name="opType">操作类型(Create - 创建、Update - 更新、Start - 启动、Complete - 完成)</param>
  464. /// <param name="operId">操作人ID</param>
  465. /// <returns>异步任务</returns>
  466. public async Task LogNodeOpAsync(Grp_ProcessNode before, Grp_ProcessNode after,string opType, int operId)
  467. {
  468. var chgDetails = GetNodeChgDetails(before, after);
  469. var log = new Grp_ProcessLog
  470. {
  471. NodeId = after?.Id ?? before?.Id,
  472. ProcessId = after?.ProcessId ?? before?.ProcessId,
  473. GroupId = 0, // 通过流程ID关联获取
  474. OpType = opType,
  475. OpDesc = GenerateNodeOpDesc(opType, before, after, chgDetails),
  476. BeforeData = before != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
  477. AfterData = after != null ? JsonConvert.SerializeObject(before, GetJsonSettings()) : null,
  478. ChgFields = string.Join(",", chgDetails.Select(x => x.FieldName)),
  479. CreateUserId = operId
  480. };
  481. await _sqlSugar.Insertable(log).ExecuteCommandAsync();
  482. }
  483. /// <summary>
  484. /// 获取流程变更详情
  485. /// </summary>
  486. /// <param name="before">变更前</param>
  487. /// <param name="after">变更后</param>
  488. /// <returns>变更详情</returns>
  489. private List<FieldChgDetail> GetProcessChgDetails(Grp_ProcessOverview before, Grp_ProcessOverview after)
  490. {
  491. var chgDetails = new List<FieldChgDetail>();
  492. if (before == null || after == null) return chgDetails;
  493. var props = typeof(Grp_ProcessOverview).GetProperties(BindingFlags.Public | BindingFlags.Instance)
  494. .Where(p => p.CanRead && p.CanWrite && !IsExclField(p.Name));
  495. foreach (var prop in props)
  496. {
  497. var beforeVal = prop.GetValue(before);
  498. var afterVal = prop.GetValue(after);
  499. if (!Equals(beforeVal, afterVal))
  500. {
  501. chgDetails.Add(new FieldChgDetail
  502. {
  503. FieldName = prop.Name,
  504. BeforeValue = FormatVal(beforeVal),
  505. AfterValue = FormatVal(afterVal)
  506. });
  507. }
  508. }
  509. return chgDetails;
  510. }
  511. /// <summary>
  512. /// 获取节点变更详情
  513. /// </summary>
  514. /// <param name="before">变更前</param>
  515. /// <param name="after">变更后</param>
  516. /// <returns>变更详情</returns>
  517. private List<FieldChgDetail> GetNodeChgDetails(Grp_ProcessNode before, Grp_ProcessNode after)
  518. {
  519. var chgDetails = new List<FieldChgDetail>();
  520. if (before == null || after == null) return chgDetails;
  521. var props = typeof(Grp_ProcessNode).GetProperties(BindingFlags.Public | BindingFlags.Instance)
  522. .Where(p => p.CanRead && p.CanWrite && !IsExclField(p.Name));
  523. foreach (var prop in props)
  524. {
  525. var beforeVal = prop.GetValue(before);
  526. var afterVal = prop.GetValue(after);
  527. if (!Equals(beforeVal, afterVal))
  528. {
  529. chgDetails.Add(new FieldChgDetail
  530. {
  531. FieldName = prop.Name,
  532. BeforeValue = FormatVal(beforeVal),
  533. AfterValue = FormatVal(afterVal)
  534. });
  535. }
  536. }
  537. return chgDetails;
  538. }
  539. /// <summary>
  540. /// 生成流程操作描述
  541. /// </summary>
  542. /// <param name="opType">操作类型</param>
  543. /// <param name="before">操作前</param>
  544. /// <param name="after">操作后</param>
  545. /// <param name="chgDetails">变更详情</param>
  546. /// <returns>操作描述</returns>
  547. private string GenerateProcessOpDesc(string opType, Grp_ProcessOverview before,
  548. Grp_ProcessOverview after, List<FieldChgDetail> chgDetails)
  549. {
  550. var processType = after?.ProcessType ?? before?.ProcessType;
  551. var processName = GetProcessTypeName(processType);
  552. if (!chgDetails.Any())
  553. {
  554. return opType switch
  555. {
  556. "Create" => $"创建流程:{processName}",
  557. "Update" => $"更新流程:{processName} - 无变更",
  558. //"Start" => $"启动流程:{processName}",
  559. "Complete" => $"完成流程:{processName}",
  560. //"Delete" => $"删除流程:{processName}",
  561. _ => $"{opType}:{processName}"
  562. };
  563. }
  564. var chgDesc = string.Join("; ", chgDetails.Select(x =>
  565. $"{GetFieldDisplayName(x.FieldName)} ({x.BeforeValue} -> {x.AfterValue})"));
  566. return $"{GetOpTypeDisplay(opType)}:{processName} - {chgDesc}";
  567. }
  568. /// <summary>
  569. /// 获取JSON序列化设置
  570. /// </summary>
  571. /// <returns>JSON设置</returns>
  572. private static JsonSerializerSettings GetJsonSettings()
  573. {
  574. return new JsonSerializerSettings
  575. {
  576. ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
  577. NullValueHandling = NullValueHandling.Ignore,
  578. DateFormatString = "yyyy-MM-dd HH:mm:ss",
  579. Formatting = Formatting.None
  580. };
  581. }
  582. /// <summary>
  583. /// 生成节点操作描述
  584. /// </summary>
  585. /// <param name="opType">操作类型</param>
  586. /// <param name="before">操作前</param>
  587. /// <param name="after">操作后</param>
  588. /// <param name="chgDetails">变更详情</param>
  589. /// <returns>操作描述</returns>
  590. private string GenerateNodeOpDesc(string opType, Grp_ProcessNode before,
  591. Grp_ProcessNode after, List<FieldChgDetail> chgDetails)
  592. {
  593. var nodeName = after?.NodeName ?? before?.NodeName;
  594. if (!chgDetails.Any())
  595. {
  596. return opType switch
  597. {
  598. "Create" => $"创建节点:{nodeName}",
  599. "Update" => $"更新节点:{nodeName} - 无变更",
  600. "Start" => $"启动节点:{nodeName}",
  601. "Complete" => $"完成节点:{nodeName}",
  602. //"Delete" => $"删除节点:{nodeName}",
  603. _ => $"{opType}:{nodeName}"
  604. };
  605. }
  606. var chgDesc = string.Join("; ", chgDetails.Select(x =>
  607. $"{GetFieldDisplayName(x.FieldName)} ({x.BeforeValue} -> {x.AfterValue})"));
  608. return $"{GetOpTypeDisplay(opType)}:{nodeName} - {chgDesc}";
  609. }
  610. /// <summary>
  611. /// 获取流程类型名称
  612. /// </summary>
  613. /// <param name="processType">流程类型</param>
  614. /// <returns>流程名称</returns>
  615. private static string GetProcessTypeName(GroupProcessType? processType)
  616. {
  617. return processType switch
  618. {
  619. GroupProcessType.Invitation => "商邀报批",
  620. GroupProcessType.Visa => "签证",
  621. GroupProcessType.AirTicket => "机票",
  622. GroupProcessType.Hotel => "酒店",
  623. GroupProcessType.LocalGuide => "地接",
  624. GroupProcessType.FeeSettle => "费用结算",
  625. _ => "未知流程"
  626. };
  627. }
  628. /// <summary>
  629. /// 获取操作类型显示
  630. /// </summary>
  631. /// <param name="opType">操作类型</param>
  632. /// <returns>显示名称</returns>
  633. private static string GetOpTypeDisplay(string opType)
  634. {
  635. return opType switch
  636. {
  637. "Create" => "创建",
  638. "Update" => "更新",
  639. "Start" => "启动",
  640. "Complete" => "完成",
  641. "Delete" => "删除",
  642. "StatusChg" => "状态变更",
  643. _ => opType
  644. };
  645. }
  646. /// <summary>
  647. /// 获取字段显示名称
  648. /// </summary>
  649. /// <param name="fieldName">字段名</param>
  650. /// <returns>显示名称</returns>
  651. private string GetFieldDisplayName(string fieldName)
  652. {
  653. return fieldName switch
  654. {
  655. "OverallStatus" => "状态",
  656. "ProcessOrder" => "流程顺序",
  657. "StartTime" => "开始时间",
  658. "EndTime" => "结束时间",
  659. "NodeOrder" => "节点顺序",
  660. "NodeName" => "节点名称",
  661. "IsCurrent" => "当前节点",
  662. "Operator" => "操作人",
  663. "OperationTime" => "操作时间",
  664. _ => fieldName
  665. };
  666. }
  667. /// <summary>
  668. /// 格式化值显示
  669. /// </summary>
  670. /// <param name="value">值</param>
  671. /// <returns>格式化值</returns>
  672. private string FormatVal(object value)
  673. {
  674. if (value == null) return "空";
  675. if (value is ProcessStatus status)
  676. {
  677. return status switch
  678. {
  679. ProcessStatus.UnStarted => "未开始",
  680. ProcessStatus.InProgress => "进行中",
  681. ProcessStatus.Completed => "已完成",
  682. _ => status.ToString()
  683. };
  684. }
  685. if (value is bool boolVal) return boolVal ? "是" : "否";
  686. if (value is DateTime dateVal) return dateVal.ToString("yyyy-MM-dd HH:mm");
  687. var strVal = value.ToString();
  688. return string.IsNullOrEmpty(strVal) ? "空" : strVal;
  689. }
  690. /// <summary>
  691. /// 检查是否排除字段
  692. /// </summary>
  693. /// <param name="fieldName">字段名</param>
  694. /// <returns>是否排除</returns>
  695. private bool IsExclField(string fieldName)
  696. {
  697. var exclFields = new List<string>
  698. {
  699. "Id", "CreateTime", "CreateUserId", "UpdatedTime", "UpdatedUserId",
  700. "Nodes", "Process" // 导航属性
  701. };
  702. return exclFields.Contains(fieldName);
  703. }
  704. /// <summary>
  705. /// 获取流程日志
  706. /// </summary>
  707. /// <param name="processId">流程ID</param>
  708. /// <returns>日志列表</returns>
  709. public async Task<List<Grp_ProcessLog>> GetProcessLogsAsync(int processId)
  710. {
  711. return await _sqlSugar.Queryable<Grp_ProcessLog>()
  712. .Where(x => x.ProcessId == processId)
  713. .OrderByDescending(x => x.CreateTime)
  714. .ToListAsync();
  715. }
  716. /// <summary>
  717. /// 获取团组流程日志
  718. /// </summary>
  719. /// <param name="groupId">团组ID</param>
  720. /// <returns>日志列表</returns>
  721. public async Task<List<Grp_ProcessLog>> GetGroupLogsAsync(int groupId)
  722. {
  723. return await _sqlSugar.Queryable<Grp_ProcessLog>()
  724. .Where(x => x.GroupId == groupId)
  725. .OrderByDescending(x => x.CreateTime)
  726. .ToListAsync();
  727. }
  728. #endregion
  729. }
  730. }