OrderDomainService.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. using DotNetCore.CAP;
  2. using Hotline.Caching.Interfaces;
  3. using Hotline.Share.Dtos.Order;
  4. using Hotline.Share.Enums.Order;
  5. using MapsterMapper;
  6. using Microsoft.Extensions.Logging;
  7. using XF.Domain.Authentications;
  8. using XF.Domain.Cache;
  9. using XF.Domain.Dependency;
  10. using XF.Domain.Exceptions;
  11. using XF.Domain.Repository;
  12. using Hotline.CallCenter.Calls;
  13. using Hotline.File;
  14. using Hotline.FlowEngine.Workflows;
  15. using Hotline.Schedulings;
  16. using Hotline.Users;
  17. using Hotline.Share.Dtos;
  18. using Hotline.Settings.Hotspots;
  19. using Hotline.Share.Dtos.FlowEngine;
  20. namespace Hotline.Orders;
  21. public class OrderDomainService : IOrderDomainService, IScopeDependency
  22. {
  23. private readonly IOrderRepository _orderRepository;
  24. private readonly IRepository<OrderRedo> _orderRedoRepository;
  25. private readonly IRepository<OrderPublish> _orderPublishRepository;
  26. private readonly IRepository<OrderVisit> _orderVisitRepository;
  27. private readonly IRepository<OrderExtension> _orderExtensionRepository;
  28. private readonly IRepository<OrderComplement> _orderComplementRepository;
  29. private readonly ITypedCache<CacheOrderNO> _cacheOrderNo;
  30. private readonly ISessionContext _sessionContext;
  31. private readonly ICapPublisher _capPublisher;
  32. private readonly IMapper _mapper;
  33. private readonly ILogger<OrderDomainService> _logger;
  34. private readonly IFileRepository _fileRepository;
  35. private readonly IRepository<Scheduling> _schedulingRepository;
  36. private readonly IRepository<User> _userRepository;
  37. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  38. private readonly IWorkflowDomainService _workflowDomainService;
  39. private readonly IRepository<Hotspot> _hotspotRepository;
  40. public OrderDomainService(
  41. IOrderRepository orderRepository,
  42. IRepository<OrderRedo> orderRedoRepository,
  43. IRepository<OrderPublish> orderPublishRepository,
  44. IRepository<OrderVisit> orderVisitRepository,
  45. IRepository<OrderExtension> orderExtensionRepository,
  46. IRepository<OrderComplement> orderComplementRepository,
  47. ITypedCache<CacheOrderNO> cacheOrderNo,
  48. ISessionContext sessionContext,
  49. ICapPublisher capPublisher,
  50. IMapper mapper,
  51. ILogger<OrderDomainService> logger,
  52. IFileRepository fileRepository,
  53. IRepository<WexCallRecord> wexCallRecordRepository,
  54. IRepository<User> userRepository,
  55. ISystemSettingCacheManager systemSettingCacheManager,
  56. IRepository<Scheduling> schedulingRepository,
  57. IWorkflowDomainService workflowDomainService,
  58. IRepository<Hotspot> hotspotRepository)
  59. {
  60. _orderRepository = orderRepository;
  61. _orderRedoRepository = orderRedoRepository;
  62. _orderPublishRepository = orderPublishRepository;
  63. _orderVisitRepository = orderVisitRepository;
  64. _orderExtensionRepository = orderExtensionRepository;
  65. _orderComplementRepository = orderComplementRepository;
  66. _cacheOrderNo = cacheOrderNo;
  67. _sessionContext = sessionContext;
  68. _capPublisher = capPublisher;
  69. _mapper = mapper;
  70. _logger = logger;
  71. _fileRepository = fileRepository;
  72. _userRepository = userRepository;
  73. _schedulingRepository = schedulingRepository;
  74. _systemSettingCacheManager = systemSettingCacheManager;
  75. _workflowDomainService = workflowDomainService;
  76. _hotspotRepository = hotspotRepository;
  77. }
  78. public async Task<Order> GetOrderAsync(string? orderId, bool withHotspot = false, bool withAcceptor = false,
  79. bool withExtension = false, CancellationToken cancellationToken = default)
  80. {
  81. if (string.IsNullOrEmpty(orderId))
  82. throw UserFriendlyException.SameMessage("无效工单编号");
  83. var query = _orderRepository.Queryable();
  84. if (withHotspot)
  85. query = query.Includes(d => d.Hotspot);
  86. if (withAcceptor)
  87. query = query.Includes(d => d.Acceptor);
  88. if (withExtension)
  89. query = query.Includes(d => d.OrderExtension);
  90. var order = await query.FirstAsync(d => d.Id == orderId, cancellationToken);
  91. if (order == null)
  92. throw new UserFriendlyException($"无效工单编号, orderId: {orderId}", "无效工单编号");
  93. return order;
  94. }
  95. public async Task<string> AddAsync(Order order, bool autoAccept = false, CancellationToken cancellationToken = default)
  96. {
  97. if (autoAccept)
  98. order.AutoAccept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.StaffNo);
  99. order.Init();
  100. order.No = GenerateNewOrderNo();
  101. order.Password = Random.Shared.Next(100000, 1000000).ToString();
  102. order.ProvinceNo ??= GenerateNewProvinceNo(order.No, order.SourceChannelCode);
  103. return await _orderRepository.AddOrderNavAsync(order, cancellationToken);
  104. }
  105. /// <summary>
  106. /// 撤回或跳转前处理数据及校验
  107. /// <remarks>
  108. ///工单撤回时需校验工单当前是否存在待发布记录、待回访记录,若存在需删除对应记录(跳转同理)
  109. ///工单撤回时需校验工单是否存在甄别中记录,若存在不允许撤回当前工单(跳转同理)
  110. /// </remarks>
  111. /// </summary>
  112. public async Task ReadyToRecallAsync(string orderId, CancellationToken cancellationToken)
  113. {
  114. var order = await _orderRepository.Queryable()
  115. .Includes(d => d.OrderPublish)
  116. .Includes(d => d.OrderVisits.Where(d => d.VisitState == EVisitState.WaitForVisit))
  117. .Includes(d => d.OrderScreens)
  118. .FirstAsync(d => d.Id == orderId, cancellationToken);
  119. if (order.OrderScreens.Any())
  120. throw UserFriendlyException.SameMessage("已甄别工单无法撤回或跳转");
  121. //todo 需求调整:P72, 4,5,6,7,8,9,10
  122. if (order.OrderPublish is not null)
  123. await _orderPublishRepository.RemoveAsync(order.OrderPublish, cancellationToken: cancellationToken);
  124. if (order.OrderVisits.Any())
  125. await _orderVisitRepository.RemoveNav(order.OrderVisits)
  126. .Include(d => d.OrderVisitDetails)
  127. .ExecuteCommandAsync();
  128. }
  129. public Task<string> AddOrderRedoAsync(OrderRedo orderRedo, CancellationToken cancellationToken) =>
  130. _orderRedoRepository.AddAsync(orderRedo, cancellationToken);
  131. public Task RemoveOrderRedoAsync(string id, CancellationToken cancellationToken) =>
  132. _orderRedoRepository.RemoveAsync(id, cancellationToken: cancellationToken);
  133. public Task UpdateOrderRedoAsync(OrderRedo orderRedo, CancellationToken cancellationToken) =>
  134. _orderRedoRepository.UpdateAsync(orderRedo, cancellationToken);
  135. #region 工单扩展信息
  136. public async Task<OrderExtension?> GetOrderExtensionsAsync(string provinceNo, CancellationToken cancellationToken) =>
  137. await _orderExtensionRepository.GetAsync(d => d.ProvinceNo == provinceNo, cancellationToken);
  138. public Task UpdateExtensionAsync(OrderExtension orderExtension, CancellationToken cancellationToken) =>
  139. _orderExtensionRepository.UpdateAsync(orderExtension, cancellationToken);
  140. /// <summary>
  141. /// 新增工单扩展信息
  142. /// </summary>
  143. public async Task AddExtensionAsync(OrderExtension extension, CancellationToken cancellationToken) =>
  144. await _orderExtensionRepository.AddAsync(extension, cancellationToken);
  145. /// <summary>
  146. /// 新增工单补充信息
  147. /// </summary>
  148. public async Task<string> AddOrderComplementAsync(AddOrderComplementDto dto, CancellationToken cancellationToken)
  149. {
  150. var complement = _mapper.Map<OrderComplement>(dto);
  151. complement.InitId();
  152. if (dto.Files.Any()) complement.FileJson = await _fileRepository.AddFileAsync(dto.Files, complement.Id, "", cancellationToken);
  153. return await _orderComplementRepository.AddAsync(complement, cancellationToken);
  154. }
  155. #endregion
  156. #region 平均派单
  157. /// <summary>
  158. /// 平均派单
  159. /// </summary>
  160. /// <returns></returns>
  161. public async Task<FlowStepHandler> AverageOrder(CancellationToken cancellationToken)
  162. {
  163. var time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  164. var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  165. .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.AtWork == true)
  166. .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
  167. if (scheduling is null)
  168. //return new List<Kv> { new(OrderDefaults.SourceChannel.SendPoolId, "待派单池") };
  169. return new FlowStepHandler
  170. {
  171. Key = OrderDefaults.SourceChannel.SendPoolId,
  172. Value = "待派单池",
  173. UserId = OrderDefaults.SourceChannel.SendPoolId,
  174. Username = "待派单池",
  175. };
  176. //var user = await _userRepository.GetAsync(x => x.Id == scheduling.SchedulingUser.UserId, cancellationToken);
  177. //if (user is null)
  178. // throw new UserFriendlyException("无效用户编号");
  179. scheduling.SendOrderNum++;
  180. await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
  181. //return new List<Kv> { new(user.Id, user.Name) };
  182. var user = scheduling.SchedulingUser;
  183. return new FlowStepHandler
  184. {
  185. Key = user.UserId,
  186. Value = user.UserName,
  187. UserId = user.UserId,
  188. Username = user.UserName,
  189. OrgId = user.OrgId,
  190. OrgName = user.OrgIdName
  191. };
  192. }
  193. /// <summary>
  194. /// 登录平均派单
  195. /// </summary>
  196. /// <param name="userId"></param>
  197. /// <returns></returns>
  198. public async Task LogAverageOrder(string userId, CancellationToken cancellationToken)
  199. {
  200. //1.获取默认派单员所属的工单
  201. //2.获取今天上班的人员
  202. //3.给当前这个用户平均派单
  203. var steps = await _workflowDomainService.GetUnhandleStepIdsFromSendPoolAsync(OrderDefaults.SourceChannel.SendPoolId, cancellationToken);
  204. var stepsList = steps.ToList();
  205. var user = await _userRepository.Queryable()
  206. .Includes(d => d.Organization)
  207. .FirstAsync(d => d.Id == userId, cancellationToken);
  208. DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  209. var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  210. .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay).CountAsync(cancellationToken);
  211. if (schedulings > 0)
  212. {
  213. List<string> stepIds = new List<string>();
  214. var sendNum = stepsList.Count() / schedulings;
  215. for (int i = 0; i < sendNum; i++)
  216. {
  217. stepIds.Add(stepsList[0]);
  218. stepsList.Remove(stepsList[0]);
  219. }
  220. List<(string, string, string, string, IReadOnlyList<string> stepIds)> handlers = new();
  221. ; handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(user.Id, user.Name, user.OrgId, user.Organization.Name, stepIds));
  222. var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
  223. var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
  224. .ToListAsync(cancellationToken);
  225. foreach (var order in orders)
  226. {
  227. _mapper.Map(order.Workflow, order);
  228. }
  229. var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  230. .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.SchedulingUser.UserId == userId).FirstAsync(cancellationToken);
  231. scheduling.SendOrderNum += sendNum;
  232. await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
  233. await _orderRepository.UpdateRangeAsync(orders, cancellationToken);
  234. }
  235. }
  236. /// <summary>
  237. /// 触发平均派单
  238. /// </summary>
  239. /// <returns></returns>
  240. public async Task TriggerAverageOrder(CancellationToken cancellationToken)
  241. {
  242. //1.从排班里面获取今天上班的人
  243. //2.获取默认派单员剩下的工单
  244. //3.平均分配剩下的工单给今天上班的人
  245. DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  246. var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  247. .Where(x => x.SchedulingTime!.Value == time && x.WorkingTime!.Value <= DateTime.Now.TimeOfDay && x.OffDutyTime!.Value >= DateTime.Now.TimeOfDay && x.AtWork == true)
  248. .OrderBy(x => x.SendOrderNum).ToListAsync(cancellationToken);
  249. if (schedulings.Any())
  250. {
  251. var steps = await _workflowDomainService.GetUnhandleStepIdsFromSendPoolAsync(OrderDefaults.SourceChannel.SendPoolId, cancellationToken);
  252. var stepsList = steps.ToList();
  253. var sendNum = steps.Count() / schedulings.Count();
  254. List<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers = new();
  255. if (sendNum > 0)
  256. {
  257. foreach (var scheduling in schedulings)
  258. {
  259. List<string> stepIds = new List<string>();
  260. for (int i = 0; i < sendNum; i++)
  261. {
  262. stepIds.Add(stepsList[0]);
  263. stepsList.Remove(stepsList[0]);
  264. }
  265. handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(scheduling.SchedulingUser.UserId, scheduling.SchedulingUser.UserName, scheduling.SchedulingUser.OrgId, scheduling.SchedulingUser.OrgIdName, stepIds));
  266. }
  267. }
  268. sendNum = steps.Count() % schedulings.Count();
  269. if (sendNum > 0)
  270. {
  271. List<string> stepIds = new List<string>();
  272. for (int i = 0; i < sendNum; i++)
  273. {
  274. stepIds.Add(stepsList[0]);
  275. stepsList.Remove(stepsList[0]);
  276. }
  277. handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(schedulings[0].SchedulingUser.UserId, schedulings[0].SchedulingUser.UserName, schedulings[0].SchedulingUser.OrgId, schedulings[0].SchedulingUser.OrgIdName, stepIds));
  278. }
  279. if (handlers.Any())
  280. {
  281. var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
  282. var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
  283. .ToListAsync(cancellationToken);
  284. foreach (var order in orders)
  285. {
  286. _mapper.Map(order.Workflow, order);
  287. }
  288. await _orderRepository.UpdateRangeAsync(orders, cancellationToken);
  289. }
  290. }
  291. }
  292. #endregion
  293. #region 工单校验- 交通类工单
  294. /// <summary>
  295. /// 工单校验 - 交通类工单
  296. /// </summary>
  297. /// <returns></returns>
  298. public async Task<OrderValidation> OrderValidation(AddOrderDto dto, CancellationToken cancellationToken)
  299. {
  300. var valid = new OrderValidation { Validation = false, Result = "" };
  301. var hotspot = await _hotspotRepository.GetAsync(dto.HotspotId, cancellationToken);
  302. if (hotspot.TrunkNum.Equals(OrderDefaults.SourceChannel.TrafficTrunkNum))
  303. {
  304. switch (dto.AcceptTypeCode)
  305. {
  306. //投诉举报
  307. case "30":
  308. case "35":
  309. valid.Validation = dto.Title.Contains("意见") || dto.Title.Contains("建议") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
  310. valid.Validation = dto.Content.Contains("意见") || dto.Content.Contains("建议") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
  311. if (dto.Content.Length < 25)
  312. {
  313. valid.Validation = true;
  314. valid.Result = "保存失败,受理内容字数不足!";
  315. }
  316. break;
  317. // 意见
  318. case "1":
  319. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
  320. valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
  321. if (dto.Content.Length < 5)
  322. {
  323. valid.Validation = true;
  324. valid.Result = "保存失败,受理内容字数不足!";
  325. }
  326. break;
  327. //建议求助
  328. case "15":
  329. case "20":
  330. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
  331. valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
  332. if (dto.Content.Length < 25)
  333. {
  334. valid.Validation = true;
  335. valid.Result = "保存失败,受理内容字数不足!";
  336. }
  337. break;
  338. // 咨询
  339. case "10":
  340. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("意见") || dto.Title.Contains("建议");
  341. valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("意见") || dto.Content.Contains("建议");
  342. if (dto.Content.Length < 5)
  343. {
  344. valid.Validation = true;
  345. valid.Result = "保存失败,受理内容字数不足!";
  346. }
  347. break;
  348. // 表扬
  349. case "25":
  350. if (dto.Content.Length < 25)
  351. {
  352. valid.Validation = true;
  353. valid.Result = "保存失败,受理内容字数不足!";
  354. }
  355. break;
  356. default:
  357. if (dto.Content.Length < 5)
  358. {
  359. valid.Validation = true;
  360. valid.Result = "保存失败,受理内容字数不足!";
  361. }
  362. break;
  363. }
  364. }
  365. if (valid.Validation && string.IsNullOrEmpty(valid.Result))
  366. valid.Result = "标题或受理内容出现限制词,请检查!";
  367. return valid;
  368. }
  369. #endregion
  370. #region SchedulingSendOrder
  371. #endregion
  372. #region private
  373. private async Task<Order> GetOrderByFlowIdAsync(string workflowId, CancellationToken cancellationToken)
  374. {
  375. if (string.IsNullOrEmpty(workflowId))
  376. throw UserFriendlyException.SameMessage("无效流程编号");
  377. var order = await _orderRepository.Queryable()
  378. .Includes(d => d.Hotspot)
  379. .FirstAsync(d => d.WorkflowId == workflowId);
  380. if (order == null)
  381. throw new UserFriendlyException($"无效流程编号, workflowId: {workflowId}", "无效流程编号");
  382. return order;
  383. }
  384. private CacheOrderNO GetCurrentOrderNo()
  385. {
  386. var today = DateTime.Today;
  387. var cacheKey = today.ToString("yyyyMMdd");
  388. var cacheOrderNo = _cacheOrderNo.GetOrSet(cacheKey, f =>
  389. {
  390. var todayOrderCount = _orderRepository.Queryable(includeDeleted: true)
  391. .CountAsync(d => d.CreationTime.Date == today.Date)
  392. .GetAwaiter()
  393. .GetResult();
  394. return new CacheOrderNO { TotalCount = todayOrderCount };
  395. }, TimeSpan.FromDays(1));
  396. return cacheOrderNo;
  397. }
  398. private string GenerateNewOrderNo()
  399. {
  400. var today = DateTime.Today;
  401. var cacheKey = $"{today:yyyyMMdd}";
  402. var cacheOrderNo = _cacheOrderNo.GetOrSet(cacheKey, f =>
  403. {
  404. var todayOrderCount = _orderRepository.Queryable(true)
  405. .CountAsync(d => d.CreationTime.Date == today.Date)
  406. .GetAwaiter()
  407. .GetResult();
  408. return new CacheOrderNO { TotalCount = todayOrderCount };
  409. }, TimeSpan.FromDays(1));
  410. cacheOrderNo!.TotalCount += 1;
  411. var no = GenerateOrderNo(today, cacheOrderNo.TotalCount);
  412. _cacheOrderNo.Set(cacheKey, cacheOrderNo);
  413. return no;
  414. }
  415. private string GenerateNewProvinceNo(string no, string sourceChannelCode)
  416. {
  417. //诉求渠道+成员单位标识+行政区划代码+年月日+流水号
  418. //成员单位标识 99
  419. //宜宾市 511500 市级
  420. var today = DateTime.Today;
  421. //var count = no.Substring(no.Length - 5);
  422. //todo 双系统并行暂时执行此方案
  423. var count = no.Substring(no.Length - 4);
  424. count = (Convert.ToInt32(count) + 50000).ToString();
  425. var provinceCodes = new[] { "RGDH", "WX", "WB", "AP", "WZ", "YJ", "SCZWFWW", "XCX", "QT" };
  426. var prefix = provinceCodes.Any(d => d.Equals(sourceChannelCode))
  427. ? sourceChannelCode
  428. : "QT";
  429. return $"{prefix}99511500{today:yyMMdd}{count}";
  430. }
  431. private string GenerateOrderNo(DateTime today, int count)
  432. {
  433. return $"{today:yyyyMMdd}{count:000000}";
  434. }
  435. #endregion
  436. }
  437. public class CacheOrderNO
  438. {
  439. public int TotalCount { get; set; }
  440. }
  441. public class OrderValidation
  442. {
  443. public bool Validation { get; set; }
  444. public string Result { get; set; }
  445. }