OrderDomainService.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  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.SeedData;
  17. using Hotline.Users;
  18. using Hotline.Share.Dtos;
  19. using Hotline.Settings.Hotspots;
  20. using Hotline.Share.Dtos.FlowEngine;
  21. using Microsoft.AspNetCore.Http;
  22. using Hotline.Settings;
  23. using SqlSugar;
  24. using Hotline.Push.Notifies;
  25. using Hotline.Share.Enums.Push;
  26. using MediatR;
  27. using Hotline.Authentications;
  28. using Hotline.ContingencyManagement.Notifies;
  29. using Hotline.EventBus;
  30. using Hotline.Share.Enums.FlowEngine;
  31. using Mapster;
  32. using Microsoft.AspNetCore.Builder.Extensions;
  33. using Hotline.Configurations;
  34. using Microsoft.Extensions.Options;
  35. using Hotline.Share.Tools;
  36. using Hotline.Orders.Notifications;
  37. using Hotline.Share.Mq;
  38. using System.Security.Cryptography;
  39. using Hotline.FlowEngine.Definitions;
  40. using Hotline.Share.Dtos.FlowEngine.Workflow;
  41. using Hotline.Share.Enums.Settings;
  42. using Hotline.Settings.TimeLimitDomain;
  43. using Hotline.Snapshot.IRepository;
  44. namespace Hotline.Orders;
  45. public class OrderDomainService : IOrderDomainService, IScopeDependency
  46. {
  47. private readonly IOrderRepository _orderRepository;
  48. private readonly IRepository<OrderRedo> _orderRedoRepository;
  49. private readonly IRepository<OrderPublish> _orderPublishRepository;
  50. private readonly IRepository<OrderVisit> _orderVisitRepository;
  51. private readonly IRepository<OrderExtension> _orderExtensionRepository;
  52. private readonly IRepository<OrderComplement> _orderComplementRepository;
  53. private readonly ITypedCache<CacheOrderNO> _cacheOrderNo;
  54. private readonly ISessionContext _sessionContext;
  55. private readonly ICapPublisher _capPublisher;
  56. private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
  57. private readonly IMapper _mapper;
  58. private readonly ILogger<OrderDomainService> _logger;
  59. private readonly IFileRepository _fileRepository;
  60. private readonly IRepository<Scheduling> _schedulingRepository;
  61. private readonly Publisher _publisher;
  62. private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
  63. private readonly IRepository<User> _userRepository;
  64. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  65. private readonly IWorkflowDomainService _workflowDomainService;
  66. private readonly IRepository<Hotspot> _hotspotRepository;
  67. private readonly IMediator _mediator;
  68. private readonly IRepository<WorkflowStep> _workflowStepRepository;
  69. private readonly ISystemLogRepository _systemLogRepository;
  70. private readonly IOrderSnapshotRepository _orderSnapshotRepository;
  71. private readonly ICalcExpireTime _expireTime;
  72. private readonly ICallDomainService _callDomainService;
  73. private readonly IOrderVisitDomainService _orderVisitDomainService;
  74. public OrderDomainService(
  75. IOrderRepository orderRepository,
  76. IRepository<OrderRedo> orderRedoRepository,
  77. IRepository<OrderPublish> orderPublishRepository,
  78. IRepository<OrderVisit> orderVisitRepository,
  79. IRepository<OrderExtension> orderExtensionRepository,
  80. IRepository<OrderComplement> orderComplementRepository,
  81. ITypedCache<CacheOrderNO> cacheOrderNo,
  82. ISessionContext sessionContext,
  83. ICapPublisher capPublisher,
  84. IMapper mapper,
  85. ILogger<OrderDomainService> logger,
  86. IFileRepository fileRepository,
  87. IRepository<WexCallRecord> wexCallRecordRepository,
  88. IRepository<User> userRepository,
  89. ISystemSettingCacheManager systemSettingCacheManager,
  90. IRepository<Scheduling> schedulingRepository,
  91. IWorkflowDomainService workflowDomainService,
  92. IRepository<Hotspot> hotspotRepository,
  93. IMediator mediator,
  94. Publisher publisher,
  95. IRepository<OrderVisitDetail> orderVisitDetailRepository,
  96. IRepository<WorkflowStep> workflowStepRepository,
  97. ISystemLogRepository systemLogRepository,
  98. IOptionsSnapshot<AppConfiguration> appOptions,
  99. IOrderSnapshotRepository orderSnapshotRepository,
  100. ICalcExpireTime expireTime,
  101. ICallDomainService callDomainService,
  102. IOrderVisitDomainService orderVisitDomainService)
  103. {
  104. _orderRepository = orderRepository;
  105. _orderRedoRepository = orderRedoRepository;
  106. _orderPublishRepository = orderPublishRepository;
  107. _orderVisitRepository = orderVisitRepository;
  108. _orderExtensionRepository = orderExtensionRepository;
  109. _orderComplementRepository = orderComplementRepository;
  110. _cacheOrderNo = cacheOrderNo;
  111. _sessionContext = sessionContext;
  112. _capPublisher = capPublisher;
  113. _mapper = mapper;
  114. _logger = logger;
  115. _fileRepository = fileRepository;
  116. _userRepository = userRepository;
  117. _schedulingRepository = schedulingRepository;
  118. _systemSettingCacheManager = systemSettingCacheManager;
  119. _workflowDomainService = workflowDomainService;
  120. _hotspotRepository = hotspotRepository;
  121. _mediator = mediator;
  122. _publisher = publisher;
  123. _orderVisitDetailRepository = orderVisitDetailRepository;
  124. _workflowStepRepository = workflowStepRepository;
  125. _systemLogRepository = systemLogRepository;
  126. _appOptions = appOptions;
  127. _orderSnapshotRepository = orderSnapshotRepository;
  128. _expireTime = expireTime;
  129. _callDomainService = callDomainService;
  130. _orderVisitDomainService = orderVisitDomainService;
  131. }
  132. /// <summary>
  133. /// 归档后自动发布
  134. /// </summary>
  135. /// <param name="order"></param>
  136. /// <param name="cancellationToken"></param>
  137. /// <returns></returns>
  138. public async Task OrderAutomaticPublishAsync(Order order, CancellationToken cancellationToken)
  139. {
  140. var name = "中心直办件归档后默认自动发布";
  141. var msg = $"OrderId: {order.Id}, No: {order.No}";
  142. try
  143. {
  144. if (_systemSettingCacheManager.AutomaticPublishOrder == false)
  145. {
  146. return;
  147. }
  148. var isSnapshot = await _orderSnapshotRepository.AnyAsync(m => m.Id == order.Id, cancellationToken);
  149. if (isSnapshot == true)
  150. {
  151. _systemLogRepository.Add(name, msg, "随手拍工单不允许自动发布");
  152. return;
  153. }
  154. var checkResult = order.AutoPublishCheck();
  155. if (checkResult.NotNullOrEmpty())
  156. {
  157. _systemLogRepository.Add(name, msg, checkResult);
  158. return;
  159. }
  160. if (order.FileOrgIsCenter == null || order.FileOrgIsCenter == false)
  161. {
  162. _systemLogRepository.Add(name, msg, "非中心直办件");
  163. return;
  164. }
  165. await OrderPublishAsync(order, cancellationToken);
  166. }
  167. catch (Exception e)
  168. {
  169. _systemLogRepository.Add(name, msg, $"系统异常: {e.Message}");
  170. }
  171. }
  172. public async Task OrderPublishAsync(Order order, CancellationToken cancellationToken)
  173. {
  174. OrderPublish orderPublish = new()
  175. {
  176. OrderId = order.Id,
  177. No = order.No,
  178. PublishState = false, //当前写死为false
  179. ArrangeTitle = order.Title,
  180. ArrangeContent = order.Content,
  181. ArrangeOpinion = order.FileOpinion,
  182. ProPublishState = false,
  183. FeedBackPhone = order.Contact,
  184. CreatorName = _sessionContext.UserName
  185. };
  186. var id = await _orderPublishRepository.AddAsync(orderPublish, cancellationToken);
  187. order.Publish(orderPublish.PublishState);
  188. await _orderRepository.UpdateAsync(order, cancellationToken);
  189. //推省上
  190. var publishPublishOrder = orderPublish.Adapt<PublishPublishOrderDto>();
  191. publishPublishOrder.Order = order.Adapt<OrderDto>();
  192. //查询实际办理附件
  193. if (!string.IsNullOrEmpty(order.ActualHandleStepId))
  194. {
  195. var actualHandleStep = await _workflowStepRepository.GetAsync(order.ActualHandleStepId, cancellationToken);
  196. publishPublishOrder.FileJsons = actualHandleStep?.FileJson;
  197. }
  198. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderPublishOrder, publishPublishOrder, cancellationToken: cancellationToken);
  199. //推应急管理局
  200. //是否开启
  201. var isOpenContingencyManagement =
  202. _systemSettingCacheManager.GetSetting(SettingConstants.IsOpenContingencyManagement)?.SettingValue[0];
  203. if (isOpenContingencyManagement == "true")
  204. await _publisher.PublishAsync(new ContingencyManagementNotify(order, order.Title, order.Content, order.ActualOpinion),
  205. PublishStrategy.ParallelWhenAll, cancellationToken);
  206. // 取消发布的工单数量
  207. var orderPublishDeletedCount = await _orderPublishRepository.Queryable(includeDeleted: true)
  208. .Where(m => m.OrderId == order.Id && m.IsDeleted == true)
  209. .CountAsync(cancellationToken);
  210. var orderVisitVisitedCount = await _orderVisitRepository.Queryable()
  211. .Where(m => m.OrderId == order.Id && m.VisitState == EVisitState.Visited)
  212. .CountAsync(cancellationToken);
  213. // 若取消发布的工单,已经被回访过了,没有经过重新办理,再次发布后,自动跳过回访环节,展示取消发布前的回访结果
  214. if (orderPublishDeletedCount != 0 && orderVisitVisitedCount != 0)
  215. {
  216. return;
  217. }
  218. var orderVisit = new OrderVisit
  219. {
  220. No = order.No,
  221. OrderId = order.Id,
  222. VisitState = EVisitState.WaitForVisit,
  223. PublishTime = DateTime.Now,
  224. IsCanHandle = true,
  225. EmployeeId = _sessionContext.RequiredUserId
  226. };
  227. if (!order.IsProvince)
  228. {
  229. orderVisit.EmployeeId = _sessionContext.RequiredUserId;
  230. }
  231. var isReturn = false;
  232. if (_appOptions.Value.IsZiGong)
  233. {
  234. orderVisit.EmployeeId = string.Empty;
  235. //自贡任务 348 语音评价不满意的工单需回访
  236. var callNative = await GetReplyVoiceOrDefaultByOrderIdAsync(orderVisit.OrderId);
  237. if (callNative == EVoiceEvaluate.NoSatisfied || callNative == EVoiceEvaluate.VeryNoSatisfied)
  238. isReturn = true;
  239. }
  240. if (isReturn == false)
  241. {
  242. if (order is { FileOrgIsCenter: true, CounterSignType: null } && !order.IsProvince)
  243. {
  244. orderVisit.VisitState = EVisitState.Visited;
  245. orderVisit.VisitTime = DateTime.Now;
  246. orderVisit.VisitType = EVisitType.OtherVisit;
  247. orderVisit.NowEvaluate = new Kv() { Key = "4", Value = "满意" };
  248. if (_appOptions.Value.IsZiGong)
  249. {
  250. // 根据禅道 自贡需求 Id_361, 第一条, 3小条需求;
  251. // 直办件归档后自动回访量需统计在“胡玲”的默认回访量中;
  252. orderVisit.EmployeeId = _systemSettingCacheManager.DefaultVisitEmployeeId;
  253. }
  254. }
  255. if (order.CounterSignType != ECounterSignType.Center)
  256. {
  257. orderVisit.IsCanAiVisit = true;
  258. }
  259. }
  260. string visitId = await _orderVisitRepository.AddAsync(orderVisit);
  261. //新增回访信息
  262. var visitedDetail = new List<OrderVisitDetail>();
  263. //坐席回访明细
  264. var seatDetail = new OrderVisitDetail
  265. {
  266. VisitId = visitId,
  267. VisitTarget = EVisitTarget.Seat
  268. };
  269. //部门回访明细
  270. var orgDetail = new OrderVisitDetail
  271. {
  272. VisitId = visitId,
  273. VisitOrgCode = order.ActualHandleOrgCode,
  274. VisitOrgName = order.ActualHandleOrgName,
  275. VisitTarget = EVisitTarget.Org
  276. };
  277. if (isReturn == false)
  278. {
  279. if (order is { FileOrgIsCenter: true, CounterSignType: null, IsProvince: false })
  280. {
  281. var satisfy = new Kv() { Key = "4", Value = "满意" };
  282. orgDetail.OrgProcessingResults = satisfy;
  283. //orgDetail.OrgHandledAttitude = satisfy;
  284. }
  285. if (order is { FileOrgIsCenter: true, CounterSignType: null })
  286. {
  287. seatDetail.VoiceEvaluate = EVoiceEvaluate.Satisfied;
  288. seatDetail.SeatEvaluate = ESeatEvaluate.Satisfied;
  289. order.Visited("4", "满意");
  290. order.Status = EOrderStatus.Visited;
  291. await _orderRepository.UpdateAsync(order, cancellationToken);
  292. }
  293. }
  294. visitedDetail.Add(orgDetail);
  295. visitedDetail.Add(seatDetail);
  296. await _orderVisitDetailRepository.AddRangeAsync(visitedDetail, cancellationToken);
  297. await _publisher.PublishAsync(new ContingencyManagementNotify(order, order.Title, order.Content, order.ActualOpinion),
  298. PublishStrategy.ParallelWhenAll, cancellationToken);
  299. //handle publish trace
  300. var acceptor = new UserInfo(orderPublish.CreatorId, orderPublish.CreatorName,
  301. orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
  302. var handler = new UserInfo(orderPublish.CreatorId, orderPublish.CreatorName,
  303. orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
  304. //上面回访人取值当前操作人,所以暂时可取值SessionContext,如需求有变需取值ordervisit中指定的回访人
  305. var visitAcceptor = string.IsNullOrEmpty(orderVisit.EmployeeId)
  306. ? new UserInfo()
  307. : new UserInfo(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId,
  308. _sessionContext.OrgName, _sessionContext.OrgIsCenter);
  309. await _workflowDomainService.HandlePublishTraceAsync(order.WorkflowId, orderPublish.Id, acceptor, handler,
  310. orderPublish.CreationTime, visitAcceptor, orderVisit.Id, cancellationToken);
  311. //需求251 某些工单需自动发送短信
  312. //任何类型的省工单都不需要发送短信
  313. var isOpenSendEndSms = _systemSettingCacheManager.GetSetting(SettingConstants.IsOpenSendEndSms)?.SettingValue[0];
  314. if (isOpenSendEndSms == "true")
  315. {
  316. try
  317. {
  318. if (_appOptions.Value.IsZiGong)
  319. {
  320. if (order.Source == ESource.ProvinceStraight)
  321. {
  322. //发送查询短信
  323. var messageDto = new Share.Dtos.Push.MessageDto
  324. {
  325. PushBusiness = EPushBusiness.SearchSms,
  326. ExternalId = visitId,
  327. OrderId = order.Id,
  328. PushPlatform = EPushPlatform.Sms,
  329. Remark = order.Title,
  330. Name = order.FromName,
  331. TemplateCode = "1021",
  332. Params = new List<string>() { order.No, order.Password },
  333. TelNumber = order.Contact,
  334. };
  335. await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
  336. }
  337. }
  338. else
  339. {
  340. if (order.Source != ESource.ProvinceStraight && order.FileOrgIsCenter.Value == false)
  341. {
  342. var isOpenSendVisitSms = _systemSettingCacheManager.GetSetting(SettingConstants.IsOpenSendVisitSms)?.SettingValue[0];
  343. var code = "";
  344. //受理类型为“投诉、举报”
  345. if ((order.AcceptTypeCode == "30" || order.AcceptTypeCode == "35") && orderVisit.VisitState != EVisitState.Visited && isOpenSendVisitSms == "true")
  346. {
  347. code = "1017";
  348. orderVisit.VisitState = EVisitState.SMSVisiting;
  349. await _orderVisitRepository.UpdateAsync(orderVisit);
  350. }
  351. else
  352. code = "1018";
  353. //发送查询短信
  354. var messageDto = new Share.Dtos.Push.MessageDto
  355. {
  356. PushBusiness = EPushBusiness.SearchSms,
  357. ExternalId = visitId,
  358. OrderId = order.Id,
  359. PushPlatform = EPushPlatform.Sms,
  360. Remark = order.Title,
  361. Name = order.FromName,
  362. TemplateCode = code,
  363. Params = new List<string>() { order.No, order.Password },
  364. TelNumber = order.Contact,
  365. };
  366. await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
  367. // 发送短信后推送一个 48小时的延迟消息队列. 当消息队列收到消息时, 判断用户是否回复了, 如果未回复短信就 默认满意
  368. var delaySecond = _systemSettingCacheManager.DefaultVisitSmsDelaySecond;
  369. await _capPublisher.PublishDelayAsync(
  370. TimeSpan.FromSeconds(delaySecond),
  371. EventNames.UpdateVisitDelaySms,
  372. messageDto,
  373. cancellationToken: cancellationToken);
  374. }
  375. }
  376. }
  377. catch (Exception)
  378. {
  379. }
  380. }
  381. if (order.IsProvince == false && orderVisit.VisitState == EVisitState.Visited)
  382. {
  383. //推省上
  384. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
  385. new PublishVisitDto()
  386. {
  387. Order = order.Adapt<OrderDto>(),
  388. No = orderVisit.No,
  389. VisitType = orderVisit.VisitType,
  390. VisitName = orderVisit.CreatorName,
  391. VisitTime = orderVisit.VisitTime,
  392. VisitRemark = orderVisit.NowEvaluate?.Value,
  393. AreaCode = order.AreaCode!,
  394. SubjectResultSatifyCode = orderVisit.NowEvaluate?.Key,
  395. FirstSatisfactionCode = orderVisit.NowEvaluate?.Key,
  396. ClientGuid = ""
  397. }, cancellationToken: cancellationToken);
  398. }
  399. //推门户
  400. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
  401. {
  402. Id = orderVisit.Id,
  403. Order = order.Adapt<OrderDto>(),
  404. OrderVisitDetails = orderVisit.OrderVisitDetails.Adapt<List<VisitDetailDto>>(),
  405. VisitName = _sessionContext.UserName,
  406. VisitTime = orderVisit.VisitTime,
  407. VisitType = orderVisit.VisitType,
  408. VisitState = orderVisit.VisitState,
  409. PublishTime = orderVisit.PublishTime,
  410. }, cancellationToken: cancellationToken);
  411. }
  412. /// <summary>
  413. /// 重办和退回工单时如果有取消发布的工单, 清除回访待办和回访列表中的数据
  414. /// </summary>
  415. /// <param name="orderId"></param>
  416. /// <param name="cancellationToken"></param>
  417. /// <returns></returns>
  418. public async Task VisitNoneByCancelPublishAsync(string orderId, CancellationToken cancellationToken)
  419. {
  420. var cancelPublishOrderEnabled = _systemSettingCacheManager.CancelPublishOrderEnabled;
  421. // 取消发布的工单数量
  422. var orderPublishDeletedCount = await _orderPublishRepository.Queryable(includeDeleted: true)
  423. .Where(m => m.OrderId == orderId && m.IsDeleted == true)
  424. .CountAsync(cancellationToken);
  425. if (orderPublishDeletedCount == 0 || cancelPublishOrderEnabled == false) return;
  426. var visit = await _orderVisitRepository.GetAsync(x => x.OrderId == orderId && x.VisitState != EVisitState.None, cancellationToken);
  427. if (visit != null)
  428. {
  429. visit.VisitState = EVisitState.None;
  430. await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
  431. }
  432. }
  433. public async Task<Order> GetOrderAsync(string? orderId, bool withHotspot = false, bool withAcceptor = false,
  434. bool withExtension = false, CancellationToken cancellationToken = default)
  435. {
  436. if (string.IsNullOrEmpty(orderId))
  437. throw UserFriendlyException.SameMessage("无效工单编号");
  438. var query = _orderRepository.Queryable().Includes(d => d.OrderPushTypes);
  439. if (withHotspot)
  440. query = query.Includes(d => d.Hotspot);
  441. if (withAcceptor)
  442. query = query.Includes(d => d.Acceptor);
  443. if (withExtension)
  444. query = query.Includes(d => d.OrderExtension);
  445. var order = await query.FirstAsync(d => d.Id == orderId, cancellationToken);
  446. if (order == null)
  447. throw new UserFriendlyException($"无效工单编号, orderId: {orderId}", "无效工单编号");
  448. return order;
  449. }
  450. public async Task<string> AddAsync(Order order, bool autoAccept = false, CancellationToken cancellationToken = default)
  451. {
  452. if (autoAccept)
  453. {
  454. order.AutoAccept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.StaffNo);
  455. order.Sign(_sessionContext.RequiredUserId, _sessionContext.UserName);
  456. }
  457. order.Init();
  458. order.No = GenerateNewOrderNo();
  459. if (order.Source == ESource.Police110)
  460. {
  461. order.Title = @"(非警情工单-" + order.No.Substring(order.No.Length - 6) + ")";
  462. }
  463. order.Password = Random.Shared.Next(100000, 1000000).ToString();
  464. order.ProvinceNo = string.IsNullOrEmpty(order.ProvinceNo) ? GenerateNewProvinceNo(order.No, order.SourceChannelCode) : order.ProvinceNo;
  465. return await _orderRepository.AddOrderNavAsync(order, cancellationToken);
  466. }
  467. /// <summary>
  468. /// 撤回或跳转前处理数据及校验
  469. /// <remarks>
  470. ///工单撤回时需校验工单当前是否存在待发布记录、待回访记录,若存在需删除对应记录(跳转同理)
  471. ///工单撤回时需校验工单是否存在甄别中记录,若存在不允许撤回当前工单(跳转同理)
  472. /// </remarks>
  473. /// </summary>
  474. public async Task ReadyToRecallAsync(string orderId, CancellationToken cancellationToken)
  475. {
  476. var order = await _orderRepository.Queryable()
  477. .Includes(d => d.OrderPublish)
  478. .Includes(d => d.OrderVisits.Where(d => d.VisitState == EVisitState.WaitForVisit))
  479. .Includes(d => d.OrderScreens)
  480. .FirstAsync(d => d.Id == orderId, cancellationToken);
  481. if (order.OrderScreens.Any())
  482. throw UserFriendlyException.SameMessage("已甄别工单无法撤回或跳转");
  483. //todo 需求调整:P72, 4,5,6,7,8,9,10
  484. if (order.OrderPublish is not null)
  485. await _orderPublishRepository.RemoveAsync(order.OrderPublish, cancellationToken: cancellationToken);
  486. if (order.OrderVisits.Any())
  487. await _orderVisitRepository.RemoveNav(order.OrderVisits)
  488. .Include(d => d.OrderVisitDetails)
  489. .ExecuteCommandAsync();
  490. }
  491. public Task<string> AddOrderRedoAsync(OrderRedo orderRedo, CancellationToken cancellationToken) =>
  492. _orderRedoRepository.AddAsync(orderRedo, cancellationToken);
  493. public Task RemoveOrderRedoAsync(string id, CancellationToken cancellationToken) =>
  494. _orderRedoRepository.RemoveAsync(id, cancellationToken: cancellationToken);
  495. public Task UpdateOrderRedoAsync(OrderRedo orderRedo, CancellationToken cancellationToken) =>
  496. _orderRedoRepository.UpdateAsync(orderRedo, cancellationToken);
  497. #region 工单扩展信息
  498. public async Task<OrderExtension?> GetOrderExtensionsAsync(string provinceNo, CancellationToken cancellationToken) =>
  499. await _orderExtensionRepository.GetAsync(d => d.ProvinceNo == provinceNo, cancellationToken);
  500. public Task UpdateExtensionAsync(OrderExtension orderExtension, CancellationToken cancellationToken) =>
  501. _orderExtensionRepository.UpdateAsync(orderExtension, cancellationToken);
  502. /// <summary>
  503. /// 新增工单扩展信息
  504. /// </summary>
  505. public async Task AddExtensionAsync(OrderExtension extension, CancellationToken cancellationToken) =>
  506. await _orderExtensionRepository.AddAsync(extension, cancellationToken);
  507. /// <summary>
  508. /// 新增工单补充信息
  509. /// </summary>
  510. public async Task<string> AddOrderComplementAsync(AddOrderComplementDto dto, CancellationToken cancellationToken)
  511. {
  512. var complement = _mapper.Map<OrderComplement>(dto);
  513. complement.InitId();
  514. if (dto.Files.Any()) complement.FileJson = await _fileRepository.AddFileAsync(dto.Files, complement.Id, "", cancellationToken);
  515. return await _orderComplementRepository.AddAsync(complement, cancellationToken);
  516. }
  517. #endregion
  518. /// <summary>
  519. /// 验证是否是管理员
  520. /// </summary>
  521. /// <returns></returns>
  522. public bool IsCheckAdmin()
  523. {
  524. var systemAdministrator = _systemSettingCacheManager.GetSetting(SettingConstants.SystemAdministrator)?.SettingValue[0];
  525. if (!string.IsNullOrEmpty(systemAdministrator) && (_sessionContext.Roles.Contains(systemAdministrator) || _sessionContext.Roles.Contains(RoleSeedData.AdminRole)))
  526. return true;
  527. else
  528. return false;
  529. }
  530. #region 平均派单
  531. /// <summary>
  532. /// 平均派单
  533. /// </summary>
  534. /// <returns></returns>
  535. public async Task<StepAssignInfo> AverageOrder(CancellationToken cancellationToken)
  536. {
  537. var time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  538. var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  539. .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.AtWork == true)
  540. .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
  541. var centerOrgName = _systemSettingCacheManager.GetSetting(SettingConstants.CenterOrgName)?.SettingValue[0];
  542. var roleId = _systemSettingCacheManager.GetSetting(SettingConstants.RolePaiDan)?.SettingValue[0];
  543. if (scheduling is null)
  544. return new StepAssignInfo
  545. {
  546. Key = AppDefaults.SendPoolId,
  547. Value = "待派单池",
  548. UserId = AppDefaults.SendPoolId,
  549. Username = "待派单池",
  550. OrgId = OrgSeedData.CenterId,
  551. OrgName = centerOrgName,
  552. RoleId = roleId,
  553. RoleName = "派单员",
  554. FlowAssignType = EFlowAssignType.User,
  555. };
  556. scheduling.SendOrderNum++;
  557. await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
  558. var user = scheduling.SchedulingUser;
  559. return new StepAssignInfo
  560. {
  561. Key = user.UserId,
  562. Value = user.UserName,
  563. UserId = user.UserId,
  564. Username = user.UserName,
  565. OrgId = user.OrgId,
  566. OrgName = user.OrgIdName,
  567. RoleId = roleId,
  568. RoleName = "派单员",
  569. FlowAssignType = EFlowAssignType.User,
  570. };
  571. }
  572. /// <summary>
  573. /// 登录平均派单
  574. /// </summary>
  575. /// <param name="userId"></param>
  576. /// <returns></returns>
  577. public async Task LogAverageOrder(string userId, Scheduling scheduling, CancellationToken cancellationToken)
  578. {
  579. //1.获取默认派单员所属的工单
  580. //2.获取今天上班的人员
  581. //3.给当前这个用户平均派单
  582. var steps = await _workflowDomainService.GetStepsBelongsToAsync(AppDefaults.SendPoolId,
  583. cancellationToken);
  584. var roleId = _systemSettingCacheManager.GetSetting(SettingConstants.RolePaiDan)?.SettingValue[0];
  585. var user = await _userRepository.Queryable()
  586. .Includes(d => d.Organization)
  587. .FirstAsync(d => d.Id == userId, cancellationToken);
  588. var time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  589. var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  590. .Where(x => x.SchedulingTime == time).CountAsync(cancellationToken);
  591. if (schedulings > 0)
  592. {
  593. var sendNum = steps.Count / schedulings;
  594. scheduling.SendOrderNum += sendNum;
  595. if (!scheduling.LoginSendOrderNum.HasValue)
  596. {
  597. scheduling.LoginSendOrderNum = scheduling.LoginSendOrderNum.HasValue && scheduling.LoginSendOrderNum > sendNum ? scheduling.LoginSendOrderNum : sendNum;
  598. await _schedulingRepository.Updateable()
  599. .SetColumns(s => new Scheduling() { LoginSendOrderNum = scheduling.LoginSendOrderNum })
  600. .Where(s => s.SchedulingTime == scheduling.SchedulingTime).ExecuteCommandAsync(cancellationToken);
  601. }
  602. sendNum = scheduling.LoginSendOrderNum.Value;
  603. await _schedulingRepository.Updateable()
  604. .SetColumns(s => new Scheduling() { SendOrderNum = scheduling.SendOrderNum, AtWork = scheduling.AtWork })
  605. .Where(s => s.Id == scheduling.Id).ExecuteCommandAsync(cancellationToken);
  606. if (sendNum <= 0) return;
  607. var sendSteps = steps.Take(sendNum).ToList();
  608. await _orderRepository.Updateable().SetColumns(o => new Order()
  609. {
  610. CenterToOrgHandlerId = user.OrgId,
  611. CenterToOrgHandlerName = user.Name
  612. }).Where(o => sendSteps.Any(s => s.ExternalId == o.Id)).ExecuteCommandAsync(cancellationToken);
  613. await _workflowDomainService.ChangeHandlerBatchAsync(new List<(string userId, string username, string orgId, string orgName, string? roleId, string? roleName, ICollection<WorkflowStep> steps)>
  614. {
  615. new(user.Id, user.Name, user.OrgId, user.Organization.Name,roleId,"派单员", sendSteps)
  616. }, cancellationToken);
  617. }
  618. }
  619. /// <summary>
  620. /// 触发平均派单
  621. /// </summary>
  622. /// <returns></returns>
  623. public async Task TriggerAverageOrder(CancellationToken cancellationToken)
  624. {
  625. //1.从排班里面获取今天上班的人
  626. //2.获取默认派单员剩下的工单
  627. //3.平均分配剩下的工单给今天上班的人
  628. DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  629. var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
  630. .Where(x => x.SchedulingTime!.Value == time && x.WorkingTime!.Value <= DateTime.Now.TimeOfDay && x.OffDutyTime!.Value >= DateTime.Now.TimeOfDay && x.AtWork == true)
  631. .OrderBy(x => x.SendOrderNum).ToListAsync(cancellationToken);
  632. var roleId = _systemSettingCacheManager.GetSetting(SettingConstants.RolePaiDan)?.SettingValue[0];
  633. if (schedulings.Any())
  634. {
  635. var steps = await _workflowDomainService.GetStepsBelongsToAsync(AppDefaults.SendPoolId,
  636. cancellationToken);
  637. if (steps.Any())
  638. {
  639. List<(string userId, string username, string orgId, string orgName, string? roleId, string? roleName, ICollection<WorkflowStep> steps)> handlers = new();
  640. var avg = steps.Count / schedulings.Count;
  641. var remaining = steps.Count % schedulings.Count;
  642. var skip = 0;
  643. for (var i = 0; i < schedulings.Count; i++)
  644. {
  645. var scheduling = schedulings[i];
  646. var size = avg + (i < remaining ? 1 : 0);
  647. if (size > 0)
  648. {
  649. var stSteps = steps.Skip(skip).Take(size).ToList();
  650. handlers.Add(new(
  651. scheduling.SchedulingUser.UserId,
  652. scheduling.SchedulingUser.UserName,
  653. scheduling.SchedulingUser.OrgId,
  654. scheduling.SchedulingUser.OrgIdName,
  655. roleId, "派单员",
  656. stSteps));
  657. skip += size;
  658. scheduling.SendOrderNum += size;
  659. await _schedulingRepository.Updateable()
  660. .SetColumns(s => new Scheduling() { SendOrderNum = scheduling.SendOrderNum })
  661. .Where(s => s.Id == scheduling.Id).ExecuteCommandAsync(cancellationToken);
  662. await _orderRepository.Updateable().SetColumns(o => new Order()
  663. {
  664. CenterToOrgHandlerId = scheduling.SchedulingUser.UserId,
  665. CenterToOrgHandlerName = scheduling.SchedulingUser.UserName
  666. })
  667. .Where(o => stSteps.Any(s => s.ExternalId == o.Id)).ExecuteCommandAsync(cancellationToken);
  668. }
  669. }
  670. if (handlers.Any())
  671. await _workflowDomainService.ChangeHandlerBatchAsync(handlers, cancellationToken);
  672. }
  673. }
  674. }
  675. #endregion
  676. #region 工单校验- 交通类工单
  677. /// <summary>
  678. /// 工单校验 - 交通类工单
  679. /// </summary>
  680. /// <returns></returns>
  681. public async Task<OrderValidation> OrderValidation(AddOrderDto dto, CancellationToken cancellationToken)
  682. {
  683. var valid = new OrderValidation { Validation = false, Result = "" };
  684. var hotspot = await _hotspotRepository.GetAsync(dto.HotspotId, cancellationToken);
  685. if (hotspot != null && hotspot.TrunkNum.Equals(AppDefaults.TrafficTrunkNum))
  686. {
  687. switch (dto.AcceptTypeCode)
  688. {
  689. //投诉举报
  690. case "30":
  691. case "35":
  692. valid.Validation = dto.Title.Contains("意见") || dto.Title.Contains("建议") || dto.Title.Contains("咨询")
  693. || dto.Content.Contains("意见") || dto.Content.Contains("建议") || dto.Content.Contains("咨询");
  694. if (dto.Content.Length < 25)
  695. {
  696. valid.Validation = true;
  697. valid.Result = "保存失败,受理内容字数不足,需要至少25个字!";
  698. }
  699. break;
  700. // 意见
  701. case "1":
  702. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("咨询")
  703. || dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("咨询");
  704. if (dto.Content.Length < 5)
  705. {
  706. valid.Validation = true;
  707. valid.Result = "保存失败,受理内容字数不足,需要至少5个字!";
  708. }
  709. break;
  710. //建议求助
  711. case "15":
  712. case "20":
  713. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("咨询")
  714. || dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("咨询");
  715. if (dto.Content.Length < 25)
  716. {
  717. valid.Validation = true;
  718. valid.Result = "保存失败,受理内容字数不足,需要至少25个字!";
  719. }
  720. break;
  721. // 咨询
  722. case "10":
  723. valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("意见")
  724. || dto.Title.Contains("建议") || dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("意见") || dto.Content.Contains("建议");
  725. if (dto.Content.Length < 5)
  726. {
  727. valid.Validation = true;
  728. valid.Result = "保存失败,受理内容字数不足,需要至少5个字!";
  729. }
  730. break;
  731. // 表扬
  732. case "25":
  733. if (dto.Content.Length < 25)
  734. {
  735. valid.Validation = true;
  736. valid.Result = "保存失败,受理内容字数不足,需要至少25个字!";
  737. }
  738. break;
  739. default:
  740. if (dto.Content.Length < 5)
  741. {
  742. valid.Validation = true;
  743. valid.Result = "保存失败,受理内容字数不足,需要至少5个字!";
  744. }
  745. break;
  746. }
  747. }
  748. if (valid.Validation && string.IsNullOrEmpty(valid.Result))
  749. valid.Result = "标题或受理内容出现限制词,请检查!";
  750. return valid;
  751. }
  752. #endregion
  753. #region 即将超期和超期短信
  754. /// <summary>
  755. /// 即将超期和超期短信
  756. /// </summary>
  757. /// <returns></returns>
  758. public async Task SendOverTimeSms(CancellationToken cancellationToken)
  759. {
  760. var now = DateTime.Now;
  761. var isSend = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsSendOverTimeSms).SettingValue[0]);
  762. if (!isSend)
  763. {
  764. return;
  765. }
  766. //查询即将超期和超期工单
  767. var orderList = await _orderRepository.Queryable()
  768. .Where(x => x.Status < EOrderStatus.Filed && !string.IsNullOrEmpty(x.CurrentHandleOrgId))
  769. .GroupBy(x => x.CurrentHandleOrgId)
  770. .Select(x => new OverTimeOrderDto
  771. {
  772. OrgId = x.CurrentHandleOrgId,
  773. NearlyOrderCount = SqlFunc.AggregateSum(SqlFunc.IIF(now >= x.NearlyExpiredTime && now < x.ExpiredTime, 1, 0)),
  774. ExpiredTimeOrderCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.ExpiredTime < now, 1, 0))
  775. })
  776. .ToListAsync(cancellationToken);
  777. foreach (var item in orderList)
  778. {
  779. if (item.NearlyOrderCount == 0 && item.ExpiredTimeOrderCount == 0)
  780. {
  781. continue;
  782. }
  783. var acceptSmsRoleIds = _systemSettingCacheManager.GetSetting(SettingConstants.AcceptSmsRoleIds)?.SettingValue;
  784. //查询部门所有账号
  785. var userlist = await _userRepository.Queryable().Where(x =>
  786. x.OrgId == item.OrgId && !string.IsNullOrEmpty(x.PhoneNo) &&
  787. x.Roles.Any(d => acceptSmsRoleIds.Contains(d.Id))).ToListAsync();
  788. //发送短信
  789. foreach (var user in userlist)
  790. {
  791. var messageDto = new Share.Dtos.Push.MessageDto
  792. {
  793. PushBusiness = EPushBusiness.OrderExpire,
  794. PushPlatform = EPushPlatform.Sms,
  795. Name = user.Name,
  796. TemplateCode = "1009",
  797. Params = new List<string>() { item.NearlyOrderCount.ToString(), item.ExpiredTimeOrderCount.ToString() },
  798. TelNumber = user.PhoneNo,
  799. };
  800. await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
  801. }
  802. }
  803. }
  804. #endregion
  805. #region 计算甄别申请截至日期
  806. public async Task<DateTime> GetScreenByEndTime()
  807. {
  808. var endTime = DateTime.Now;
  809. var beginTime = DateTime.Now;
  810. var timeValue = 1;
  811. if (_appOptions.Value.IsZiGong)
  812. {
  813. timeValue = 2;
  814. }
  815. else if (_appOptions.Value.IsLuZhou)
  816. {
  817. DateTime firstDayOfMonth = new DateTime(beginTime.Year, beginTime.Month, 1);
  818. DateTime lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);
  819. beginTime = new DateTime(lastDayOfMonth.Year, lastDayOfMonth.Month, lastDayOfMonth.Day, 23, 59, 59);
  820. }
  821. else
  822. {
  823. var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ScreenApplyEndTime);
  824. if (int.Parse(setting?.SettingValue[0]) > 0)
  825. {
  826. timeValue = int.Parse(setting?.SettingValue[0]);
  827. }
  828. }
  829. endTime = (await _expireTime.CalcEndTime(beginTime, beginTime, ETimeType.WorkDay, timeValue, 0, 0)).EndTime;
  830. return endTime;
  831. }
  832. #endregion
  833. #region private
  834. private async Task<Order> GetOrderByFlowIdAsync(string workflowId, CancellationToken cancellationToken)
  835. {
  836. if (string.IsNullOrEmpty(workflowId))
  837. throw UserFriendlyException.SameMessage("无效流程编号");
  838. var order = await _orderRepository.Queryable()
  839. .Includes(d => d.Hotspot)
  840. .FirstAsync(d => d.WorkflowId == workflowId);
  841. if (order == null)
  842. throw new UserFriendlyException($"无效流程编号, workflowId: {workflowId}", "无效流程编号");
  843. return order;
  844. }
  845. private CacheOrderNO GetCurrentOrderNo()
  846. {
  847. var today = DateTime.Today;
  848. var cacheKey = today.ToString("yyyyMMdd");
  849. var cacheOrderNo = _cacheOrderNo.GetOrSet(cacheKey, f =>
  850. {
  851. var todayOrderCount = _orderRepository.Queryable(includeDeleted: true)
  852. .CountAsync(d => d.CreationTime.Date == today.Date)
  853. .GetAwaiter()
  854. .GetResult();
  855. return new CacheOrderNO { TotalCount = todayOrderCount };
  856. }, TimeSpan.FromDays(1));
  857. return cacheOrderNo;
  858. }
  859. public string GenerateNewOrderNo()
  860. {
  861. var today = DateTime.Today;
  862. var cacheKey = $"{today:yyyyMMdd}";
  863. var cacheOrderNo = _cacheOrderNo.GetOrSet(cacheKey, f =>
  864. {
  865. var todayOrderCount = _orderRepository.Queryable(true)
  866. .CountAsync(d => d.CreationTime.Date == today.Date)
  867. .GetAwaiter()
  868. .GetResult();
  869. return new CacheOrderNO { TotalCount = todayOrderCount };
  870. }, TimeSpan.FromDays(1));
  871. cacheOrderNo!.TotalCount += 1;
  872. var no = GenerateOrderNo(today, cacheOrderNo.TotalCount);
  873. _cacheOrderNo.Set(cacheKey, cacheOrderNo);
  874. return no;
  875. }
  876. private string GenerateNewProvinceNo(string no, string sourceChannelCode)
  877. {
  878. //诉求渠道+成员单位标识+行政区划代码+年月日+流水号
  879. //成员单位标识 99
  880. //宜宾市 511500 市级
  881. var today = DateTime.Today;
  882. //var count = no.Substring(no.Length - 5);
  883. var setting = _systemSettingCacheManager.GetSetting(SettingConstants.VersionsAreaCode);
  884. var versionsAreaCode = setting?.SettingValue[0];
  885. //todo 双系统并行暂时执行此方案
  886. var count = no.Substring(no.Length - 4);
  887. count = (Convert.ToInt32(count) + 50000).ToString();
  888. var provinceCodes = new[] { "RGDH", "WX", "WB", "AP", "WZ", "YJ", "SCZWFWW", "XCX", "QT" };
  889. var prefix = provinceCodes.Any(d => d.Equals(sourceChannelCode))
  890. ? sourceChannelCode
  891. : "QT";
  892. return $"{prefix}99{versionsAreaCode}{today:yyMMdd}{count}";
  893. }
  894. private string GenerateOrderNo(DateTime today, int count)
  895. {
  896. return $"{today:yyyyMMdd}{count:000000}";
  897. }
  898. /// <summary>
  899. /// 根据 OrderId 返回用户电话评价枚举
  900. /// 默认返回 默认满意
  901. /// </summary>
  902. /// <param name="orderId"></param>
  903. /// <returns></returns>
  904. private async Task<EVoiceEvaluate> GetReplyVoiceOrDefaultByOrderIdAsync(string orderId)
  905. {
  906. var callNative = await _callDomainService.GetByOrderIdAsync(orderId);
  907. if (callNative is null || callNative.ReplyTxt.IsNullOrEmpty())
  908. return EVoiceEvaluate.DefaultSatisfied;
  909. try
  910. {
  911. var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(callNative.ReplyTxt!.Trim());
  912. return smsReply!.VoiceEvaluate!.Value;
  913. }
  914. catch (UserFriendlyException)
  915. {
  916. return EVoiceEvaluate.DefaultSatisfied;
  917. }
  918. }
  919. #endregion
  920. }
  921. public class CacheOrderNO
  922. {
  923. public int TotalCount { get; set; }
  924. }
  925. public class OrderValidation
  926. {
  927. public bool Validation { get; set; }
  928. public string Result { get; set; }
  929. }