Sfoglia il codice sorgente

merge release -> dev

xf 9 mesi fa
parent
commit
d043fdc6c1
37 ha cambiato i file con 876 aggiunte e 129 eliminazioni
  1. 1 1
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  2. 33 9
      src/Hotline.Api/Controllers/IPPbxController.cs
  3. 54 1
      src/Hotline.Api/Controllers/OrderController.cs
  4. 14 7
      src/Hotline.Api/Controllers/QualityController.cs
  5. 209 4
      src/Hotline.Api/Controllers/TestController.cs
  6. 3 0
      src/Hotline.Api/Program.cs
  7. 8 8
      src/Hotline.Api/StartupExtensions.cs
  8. 23 0
      src/Hotline.Api/StartupHelper.cs
  9. 2 1
      src/Hotline.Api/config/appsettings.Development.json
  10. 10 3
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  11. 10 2
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  12. 2 1
      src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs
  13. 11 3
      src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs
  14. 34 0
      src/Hotline.Application/Handlers/Order/OrderDelayWorkflowEndHandler.cs
  15. 1 0
      src/Hotline.Application/JudicialManagement/EnforcementApplication.cs
  16. 5 4
      src/Hotline.Application/Mappers/OrderMapperConfigs.cs
  17. 2 2
      src/Hotline.Application/Orders/OrderApplication.cs
  18. 42 0
      src/Hotline.Application/Quality/AiOrderQualityHandler.cs
  19. 78 51
      src/Hotline.Application/Quality/QualityApplication.cs
  20. 9 6
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  21. 12 1
      src/Hotline.Repository.SqlSugar/Extensions/XingTangDbExtensions.cs
  22. 9 0
      src/Hotline.Share/Dtos/Quality/AiQualityDto.cs
  23. 3 9
      src/Hotline/Ai/Quality/IAiQualityService.cs
  24. 7 0
      src/Hotline/AppDefaults.cs
  25. 4 2
      src/Hotline/Authentications/ProvinceSessionContext.cs
  26. 1 1
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  27. 39 0
      src/Hotline/EventBus/CustomMediator.cs
  28. 12 0
      src/Hotline/EventBus/EAppScope.cs
  29. 22 0
      src/Hotline/EventBus/HandlerInjectAttribute.cs
  30. 37 0
      src/Hotline/EventBus/PublishStrategy.cs
  31. 146 0
      src/Hotline/EventBus/Publisher.cs
  32. 9 0
      src/Hotline/FlowEngine/Notifications/WorkflowNotify.cs
  33. 1 1
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  34. 7 1
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  35. 11 4
      src/Hotline/Settings/SystemDicData.cs
  36. 4 6
      src/XingTang.Sdk/XingtangCall.cs
  37. 1 1
      src/XingTang.Sdk/XingtangSeatOperation.cs

+ 1 - 1
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -737,7 +737,7 @@ namespace Hotline.Api.Controllers.Bi
                     AcceptType = x.AcceptType,
                     OneHotspot = SqlFunc.Substring(x.HotspotSpliceName, 0, SqlFunc.CharIndex("-", x.HotspotSpliceName + "-")),
                     Id = x.Id
-                }).MergeTable()
+                }).MergeTable().Where(x => x.OneHotspot != "非受理范围")
                 .GroupBy(x => new { x.OneHotspot })
                 .Select(x => new AcceptTypeTop10Vo
                 {

+ 33 - 9
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -25,6 +25,8 @@ using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
+using System.Threading;
+using Hotline.EventBus;
 using Tr.Sdk;
 using Tr.Sdk.Blacklist;
 using Tr.Sdk.Tels;
@@ -33,6 +35,8 @@ using XF.Domain.Exceptions;
 using XF.Domain.Filters;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
+using Hotline.FlowEngine.Notifications;
+using Hotline.Share.Dtos.Quality;
 
 namespace Hotline.Api.Controllers
 {
@@ -60,8 +64,9 @@ namespace Hotline.Api.Controllers
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
         private readonly IRepository<TelActionRecord> _telActionRecordRepository;
         private readonly ISystemMobilAreaApplication _systemMobilAreaApplication;
+        private readonly Publisher _publisher;
 
-        public IPPbxController(ITrClient trClient, IMapper mapper, IUserDomainService userDomainService,
+		public IPPbxController(ITrClient trClient, IMapper mapper, IUserDomainService userDomainService,
             ISessionContext sessionContext, IRepository<TrCallRecord> trCallRecordRepository,
             ITrApplication trApplication, IRepository<TrCallEvaluate> trCallRecord,
             ISystemDicDataCacheManager systemDicDataCacheManager, ILogger<IPPbxController> logger,
@@ -71,7 +76,7 @@ namespace Hotline.Api.Controllers
             ITelApplication telApplication, IRepository<Quality.Quality> qualiteyRepository,
             IAiQualityService aiQualityService, IRepository<QualityTemplate> qualityTemplate, 
             ISystemSettingCacheManager systemSettingCacheManager,IRepository<TelActionRecord> telActionRecordRepository,
-            ISystemMobilAreaApplication systemMobilAreaApplication)
+            ISystemMobilAreaApplication systemMobilAreaApplication, Publisher publisher)
         {
             _trClient = trClient;
             _mapper = mapper;
@@ -95,7 +100,9 @@ namespace Hotline.Api.Controllers
             _systemSettingCacheManager = systemSettingCacheManager;
             _telActionRecordRepository = telActionRecordRepository;
             _systemMobilAreaApplication = systemMobilAreaApplication;
-        }
+            _publisher = publisher;
+
+		}
 
         #region 添添呼
 
@@ -503,12 +510,29 @@ namespace Hotline.Api.Controllers
                         {
 							var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
 							//await _aiQualityService.CreateAiOrderQualityTask(quality, model, order, setting?.SettingValue[0], HttpContext.RequestAborted);
-                            await _aiQualityService.CreateAiOrderQualityTask(
-                                quality,
-                                model.RecordingAbsolutePath,
-                                model.CPN,
-                                model.CreatedTime,
-                                order, setting?.SettingValue[0], HttpContext.RequestAborted);
+
+							try
+							{
+								 //_aiQualityService.CreateAiOrderQualityTask(
+									//quality,
+									//model.RecordingAbsolutePath,
+									//model.CPN,
+									//model.CreatedTime,
+									//order, setting?.SettingValue[0], HttpContext.RequestAborted);
+								 var handler = new AiQualityHandler()
+								 {
+									 AudioFile = model.RecordingAbsolutePath,
+									 FromNo = model.CPN,
+									 CallStartTime = model.CreatedTime,
+									 ViteRecordPrefix = setting?.SettingValue[0],
+								 };
+								await _publisher.PublishAsync(new AiOrderQualityNotify(quality, order, handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted);
+							}
+							catch (Exception e)
+							{
+								_logger.LogError($"写入智能质检异常!, \r\n{e.Message}");
+							}
+						
                         }
                     }
                 }

+ 54 - 1
src/Hotline.Api/Controllers/OrderController.cs

@@ -417,6 +417,38 @@ public class OrderController : BaseController
 
                     visitedDetail.Add(seatDetail);
                     await _orderVisitedDetailRepository.AddRangeAsync(visitedDetail, HttpContext.RequestAborted);
+
+                    if (orderVisit.VisitState == EVisitState.Visited)
+                    {
+                        //推省上
+                        await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
+                            new PublishVisitDto()
+                            {
+                                Order = _mapper.Map<OrderDto>(order),
+                                No = orderVisit.No,
+                                VisitType = orderVisit.VisitType,
+                                VisitName = orderVisit.CreatorName,
+                                VisitTime = orderVisit.VisitTime,
+                                VisitRemark = orderVisit.NowEvaluate?.Value,
+                                AreaCode = order.AreaCode!,
+                                SubjectResultSatifyCode = orderVisit.NowEvaluate?.Key,
+                                FirstSatisfactionCode = orderVisit.NowEvaluate?.Key,
+                                ClientGuid = ""
+                            }, cancellationToken: HttpContext.RequestAborted);
+                    }
+
+                    //推门户
+                    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
+                    {
+                        Id = orderVisit.Id,
+                        Order = _mapper.Map<OrderDto>(order),
+                        OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(orderVisit.OrderVisitDetails),
+                        VisitName = _sessionContext.UserName,
+                        VisitTime = orderVisit.VisitTime,
+                        VisitType = orderVisit.VisitType,
+                        VisitState = orderVisit.VisitState,
+                        PublishTime = orderVisit.PublishTime,
+                    }, cancellationToken: HttpContext.RequestAborted);
                 }
                 catch
                 {
@@ -566,6 +598,27 @@ public class OrderController : BaseController
 
         visitedDetail.Add(seatDetail);
         await _orderVisitedDetailRepository.AddRangeAsync(visitedDetail, HttpContext.RequestAborted);
+
+
+        if (orderVisit.VisitState == EVisitState.Visited)
+        {
+            //推省上
+            await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
+                new PublishVisitDto()
+                {
+                    Order = _mapper.Map<OrderDto>(order),
+                    No = orderVisit.No,
+                    VisitType = orderVisit.VisitType,
+                    VisitName = orderVisit.CreatorName,
+                    VisitTime = orderVisit.VisitTime,
+                    VisitRemark = orderVisit.NowEvaluate?.Value,
+                    AreaCode = order.AreaCode!,
+                    SubjectResultSatifyCode = orderVisit.NowEvaluate?.Key,
+                    FirstSatisfactionCode = orderVisit.NowEvaluate?.Key,
+                    ClientGuid = ""
+                }, cancellationToken: HttpContext.RequestAborted);
+        }
+
         //推送回访信息
         //推门户
         await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
@@ -4755,7 +4808,7 @@ public class OrderController : BaseController
                 d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
             //.WhereIF(!string.IsNullOrEmpty(dto.Content), d => d.Content.Contains(dto.Content!))
             .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType)//受理类型
-                                                                                                    //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
+                                                                                                //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
             .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)//来源渠道
             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))
             .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone.Contains(dto.TransferPhone!))

+ 14 - 7
src/Hotline.Api/Controllers/QualityController.cs

@@ -755,13 +755,20 @@ namespace Hotline.Api.Controllers
                 }
 
                 var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
-                await _aiQualityService.CreateAiOrderQualityTask(
-                    quality,
-                    audioFile,
-                    fromNo,
-                    callStartTime,
-                    order, setting?.SettingValue[0], HttpContext.RequestAborted);
-                await _qualitey.UpdateAsync(quality, HttpContext.RequestAborted);
+                try
+                {
+					 _aiQualityService.CreateAiOrderQualityTask(
+						quality,
+						audioFile,
+						fromNo,
+						callStartTime,
+						order, setting?.SettingValue[0], HttpContext.RequestAborted);
+				}
+                catch (Exception e)
+                {
+	                _logger.LogError($"写入智能质检异常!, \r\n{e.Message}");
+                }
+				await _qualitey.UpdateAsync(quality, HttpContext.RequestAborted);
             }
         }
 

+ 209 - 4
src/Hotline.Api/Controllers/TestController.cs

@@ -8,9 +8,11 @@ using Fw.Utility.Client;
 using Google.Protobuf.WellKnownTypes;
 using Hotline.Ai.Visit;
 using Hotline.Application.ExportExcel;
+using Hotline.Application.FlowEngine;
 using Hotline.Application.JudicialManagement;
 using Hotline.Application.Quality;
 using Hotline.Application.StatisticalReport;
+using Hotline.Authentications;
 using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Configs;
@@ -133,6 +135,7 @@ public class TestController : BaseController
     private readonly IRepository<EnforcementOrders> _enforcementOrdersRepository;
     private readonly IRepository<JudicialManagementOrders> _judicialManagementOrdersRepository;
     private readonly IRepository<EnforcementOrdersHandler> _enforcementOrdersHandlerRepository;
+    private readonly IWorkflowApplication _workflowApplication;
 
     private readonly IRepository<ContingencyManagementHotspot> _contingencyManagementHotspotRepository;
     private readonly IRepository<Hotspot> _hotspotRepository;
@@ -966,12 +969,113 @@ IRepository<Hotspot> hotspotRepository
     [HttpGet("t5")]
     public async Task Test5()
     {
-        //var orderCount = await _orderRepository.Queryable()
-        //    .Where(d => d.Status >= EOrderStatus.Filed &&
-        //              d.CurrentStepId == null)
+        var query = _orderRepository.Queryable()
+            .Includes(d => d.Workflow, x => x.Steps)
+            .Where(d => d.Status >= EOrderStatus.Filed &&
+                        d.CurrentStepId == null)
+            .OrderBy(d => d.Id);
+
+        var size = 1000;
+        var count = await query.CountAsync(HttpContext.RequestAborted);
+        _logger.LogWarning($"共计 {count} 条");
+        var batchs = (int)Math.Ceiling(Convert.ToDouble(count) / size);
+        for (int i = 0; i < batchs; i++)
+        {
+            _logger.LogWarning($"第 {i} 批次开始");
+            var orders = await query
+                .Skip(i * size)
+                .Take(size)
+                .ToListAsync(HttpContext.RequestAborted);
+            foreach (var order in orders)
+            {
+                if (order.No.Trim().Length != 14)
+                {
+                    order.CurrentStepId = order.ActualHandleStepId;
+                    order.CurrentStepCode = order.ActualHandleStepCode;
+                    order.CurrentStepName = order.ActualHandleStepName;
+                    order.CurrentStepCreateTime = order.ActualHandleStepCreateTime;
+                    order.CurrentStepAcceptTime = order.ActualHandleStepAcceptTime;
+                    order.CurrentHandleTime = order.ActualHandleTime;
+                    order.CurrentHandlerId = order.ActualHandlerId;
+                    order.CurrentHandlerName = order.ActualHandlerName;
+                    order.CurrentHandleOrgName = order.ActualHandleOrgName;
+                    order.CurrentHandleOrgId = order.ActualHandleOrgCode;
+                    order.CurrentHandleOrgAreaCode = order.ActualHandleOrgAreaCode;
+                    order.CurrentHandleOrgAreaName = order.ActualHandleOrgAreaName;
+                }
+                else
+                {
+                    if (order.CounterSignType != null)
+                    {
+                        order.Workflow.CurrentStepId = order.Workflow.ActualHandleStepId;
+                        order.Workflow.CurrentStepCode = order.Workflow.ActualHandleStepCode;
+                        order.Workflow.CurrentStepName = order.Workflow.ActualHandleStepName;
+                        order.Workflow.CurrentStepCreateTime = order.Workflow.ActualHandleStepCreateTime;
+                        order.Workflow.CurrentStepAcceptTime = order.Workflow.ActualHandleStepAcceptTime;
+                        order.Workflow.CurrentHandleTime = order.Workflow.ActualHandleTime;
+                        order.Workflow.CurrentHandlerId = order.Workflow.ActualHandlerId;
+                        order.Workflow.CurrentHandlerName = order.Workflow.ActualHandlerName;
+                        order.Workflow.CurrentHandleOrgName = order.Workflow.ActualHandleOrgName;
+                        order.Workflow.CurrentHandleOrgId = order.Workflow.ActualHandleOrgCode;
+                        order.Workflow.CurrentHandleOrgAreaCode = order.Workflow.ActualHandleOrgAreaCode;
+                        order.Workflow.CurrentHandleOrgAreaName = order.Workflow.ActualHandleOrgAreaName;
+
+                        order.CurrentStepId = order.Workflow.ActualHandleStepId;
+                        order.CurrentStepCode = order.Workflow.ActualHandleStepCode;
+                        order.CurrentStepName = order.Workflow.ActualHandleStepName;
+                        order.CurrentStepCreateTime = order.Workflow.ActualHandleStepCreateTime;
+                        order.CurrentStepAcceptTime = order.Workflow.ActualHandleStepAcceptTime;
+                        order.CurrentHandleTime = order.Workflow.ActualHandleTime;
+                        order.CurrentHandlerId = order.Workflow.ActualHandlerId;
+                        order.CurrentHandlerName = order.Workflow.ActualHandlerName;
+                        order.CurrentHandleOrgName = order.Workflow.ActualHandleOrgName;
+                        order.CurrentHandleOrgId = order.Workflow.ActualHandleOrgCode;
+                        order.CurrentHandleOrgAreaCode = order.Workflow.ActualHandleOrgAreaCode;
+                        order.CurrentHandleOrgAreaName = order.Workflow.ActualHandleOrgAreaName;
+                    }
+                    else
+                    {
+                        var endStep = order.Workflow.Steps.FirstOrDefault(d => d.StepType == EStepType.End);
+                        if (endStep is null) continue;
+                        var preStep = order.Workflow.Steps.FirstOrDefault(d => d.Id == endStep.PrevStepId);
+                        if (preStep is null) continue;
+
+                        order.Workflow.CurrentStepId = preStep.Id;
+                        order.Workflow.CurrentStepCode = preStep.Code;
+                        order.Workflow.CurrentStepName = preStep.Name;
+                        order.Workflow.CurrentStepCreateTime = preStep.CreationTime;
+                        order.Workflow.CurrentStepAcceptTime = preStep.AcceptTime;
+                        order.Workflow.CurrentHandleTime = preStep.HandleTime;
+                        order.Workflow.CurrentHandlerId = preStep.HandlerId;
+                        order.Workflow.CurrentHandlerName = preStep.HandlerName;
+                        order.Workflow.CurrentHandleOrgName = preStep.HandlerOrgName;
+                        order.Workflow.CurrentHandleOrgId = preStep.HandlerOrgId;
+                        order.Workflow.CurrentHandleOrgAreaCode = preStep.HandlerOrgAreaCode;
+                        order.Workflow.CurrentHandleOrgAreaName = preStep.HandlerOrgAreaName;
+
+                        order.CurrentStepId = order.Workflow.ActualHandleStepId;
+                        order.CurrentStepCode = order.Workflow.ActualHandleStepCode;
+                        order.CurrentStepName = order.Workflow.ActualHandleStepName;
+                        order.CurrentStepCreateTime = order.Workflow.ActualHandleStepCreateTime;
+                        order.CurrentStepAcceptTime = order.Workflow.ActualHandleStepAcceptTime;
+                        order.CurrentHandleTime = order.Workflow.ActualHandleTime;
+                        order.CurrentHandlerId = order.Workflow.ActualHandlerId;
+                        order.CurrentHandlerName = order.Workflow.ActualHandlerName;
+                        order.CurrentHandleOrgName = order.Workflow.ActualHandleOrgName;
+                        order.CurrentHandleOrgId = order.Workflow.ActualHandleOrgCode;
+                        order.CurrentHandleOrgAreaCode = order.Workflow.ActualHandleOrgAreaCode;
+                        order.CurrentHandleOrgAreaName = order.Workflow.ActualHandleOrgAreaName;
+                    }
+                }
+            }
+
+            _logger.LogWarning($"更新数据:{orders.Count} 条");
+            await _orderRepository.UpdateNav(orders)
+                .Include(d => d.Workflow)
+                .ExecuteCommandAsync();
+        }
 
 
-        //_logger.LogWarning($"更新数据:{updateSteps.Count} 条");
         //await _workflowTraceRepository.UpdateRangeAsync(updateSteps, HttpContext.RequestAborted);
     }
 
@@ -984,4 +1088,105 @@ IRepository<Hotspot> hotspotRepository
         return $"{publicKey} \r\n {privateKey}";
     }
 
+    /// <summary>
+    /// 处理工单流程错误数据   话务部直接归档件  没有归档节点
+    /// </summary>
+    /// <param name="StartTime"></param>
+    /// <param name="EndTime"></param>
+    /// <returns></returns>
+    [HttpGet("oders_workflow_step")]
+    [AllowAnonymous]
+    public async Task AddJudicialManagementOrders()
+    {
+
+        var steps = await _workflowStepRepository.Queryable()
+            .LeftJoin<Order>((ws, d) => ws.ExternalId == d.Id)
+            .Where((ws, d) => d.Status == EOrderStatus.Handling && ws.NextStepCode == "end" && ws.Status == EWorkflowStepStatus.Handled && ws.StepType == EStepType.Start)
+            .ToListAsync(HttpContext.RequestAborted);
+
+        _logger.LogInformation($"取到数据 {steps.Count} 条");
+        foreach (var item in steps)
+        {
+            ///组装数据
+            //Workflow workflow
+            Workflow workflow = await _workflowDomainService.GetWorkflowAsync(item.WorkflowId, withDefine: true, withSteps: true,
+                cancellationToken: HttpContext.RequestAborted);
+            //WorkflowStep startStep
+            var startStep = workflow.Steps.FirstOrDefault(x => x.StepType == EStepType.Start);
+            if (startStep is null) continue;
+            //BasicWorkflowDto dto
+            BasicWorkflowDto dto = new BasicWorkflowDto
+            {
+                NextStepCode = "end",
+                NextStepName = "结束",
+                BackToCountersignEnd = false,
+                FlowDirection = null,
+                HandlerType = 0,
+                StepType = 0,
+                NextHandlers = new List<FlowStepHandler>(),
+                IsSms = false,
+                NextMainHandler = "",
+                IsStartCountersign = false,
+                External = new External
+                {
+                    TimeLimit = null,
+                    TimeLimitUnit = null,
+                    IsPoliceReturn = false,
+                    IsResolved = false,
+                },
+                BusinessType = EBusinessType.File,
+                ReviewResult = 0,
+                Remark = null,
+                Opinion = startStep.Opinion,
+                Files = new List<Share.Dtos.File.FileDto>()
+            };
+            //ISessionContext current
+            ISessionContext current = new ProvinceSessionContext
+            {
+                UserId = startStep.HandlerId,
+                UserName = startStep.HandlerName,
+                OrgId = startStep.HandlerOrgId,
+                OrgName = startStep.HandlerOrgName,
+                OrgAreaCode = startStep.HandlerOrgAreaCode,
+                OrgAreaName = startStep.HandlerOrgAreaName,
+                OrgLevel = 1
+            };
+
+            //StepDefine firstStepDefine
+            StepDefine firstStepDefine = workflow.WorkflowDefinition.FindStepDefine(startStep.NextStepCode);
+            //FlowAssignInfo flowAssignInfo
+            ////如果发起会签需检查是否支持发起会签
+            //var startStepDefine = workflow.WorkflowDefinition.FindStartStepDefine();
+
+            //下一节点是否为动态节点
+            var isNextDynamic = false;
+
+            FlowAssignInfo flowAssignInfo =
+                await _workflowApplication.GetNextStepFlowAssignInfoAsync(workflow, startStep, dto, firstStepDefine, isNextDynamic, HttpContext.RequestAborted);
+            //DateTime? expiredTime
+            DateTime? expiredTime = startStep.StepExpiredTime;
+
+            ///
+            await _mediator.Publish(
+                new StartWorkflowNotify(workflow, dto, flowAssignInfo, startStep.WorkflowTrace), HttpContext.RequestAborted);
+
+            //firstStep是否为end,t: 实际办理节点为startStep, 并且handlerId赋值 f: 实际办理节点为firstStep, handlerId未赋值
+            workflow.UpdateActualStepWhenHandle(startStep,
+                current.RequiredUserId, current.UserName,
+                current.RequiredOrgId, current.OrgName,
+                current.OrgAreaCode, current.OrgAreaName,
+                current.OrgLevel);
+
+            workflow.UpdateCurrentStepWhenHandle(startStep,
+                current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
+
+            var endTrace = await _workflowDomainService.EndAsync(workflow, dto, firstStepDefine,
+                startStep, current, expiredTime, HttpContext.RequestAborted);
+
+
+        }
+
+        _logger.LogInformation($"处理完成");
+    }
+
 }

+ 3 - 0
src/Hotline.Api/Program.cs

@@ -1,4 +1,7 @@
+using EnumsNET;
 using Hotline.Api;
+using Hotline.EventBus;
+using Novacode;
 using Serilog;
 
 Log.Logger = new LoggerConfiguration()

+ 8 - 8
src/Hotline.Api/StartupExtensions.cs

@@ -27,6 +27,8 @@ using Hotline.Application.CallCenter.Calls;
 using Hotline.Application.CallCenter;
 using Hotline.CallCenter.Calls;
 using Swashbuckle.AspNetCore.SwaggerUI;
+using Hotline.EventBus;
+using MediatR.Pipeline;
 
 namespace Hotline.Api;
 
@@ -94,10 +96,8 @@ internal static class StartupExtensions
         services.RegisterMapper();
 
         //mediatr
-        services.AddMediatR(d =>
-        {
-            d.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly);
-        });
+        //todo 
+        services.RegisterMediatR("YiBin");
 
         //callcenter
         var callCenterConfiguration = configuration
@@ -106,14 +106,14 @@ internal static class StartupExtensions
         services.AddNewRock(callCenterConfiguration.NewRock);
         switch (callCenterConfiguration.CallCenterType)
         {
-            case "XunShi":
+            case AppDefaults.CallCenterType.XunShi:
                 break;
-            case "WerErXin":
+            case AppDefaults.CallCenterType.WeiErXin:
                 services
                     .AddWex(callCenterConfiguration.Wex)
                     .AddWexDb(configuration);
                 break;
-            case "TianRun":
+            case AppDefaults.CallCenterType.TianRun:
                 services
                     .AddScoped<ICallApplication, TianRunCallApplication>()
                     .AddScoped<ITrApplication, TrApplication>()
@@ -123,7 +123,7 @@ internal static class StartupExtensions
                         callCenterConfiguration.TianRun.Username,
                         callCenterConfiguration.TianRun.Password);
                 break;
-            case "XingTang":
+            case AppDefaults.CallCenterType.XingTang:
                 services.AddXingTangDb(callCenterConfiguration.XingTang)
                     .AddScoped<ICallApplication, XingTangCallApplication>()
                     .AddScoped<CallIdManager>()

+ 23 - 0
src/Hotline.Api/StartupHelper.cs

@@ -1,14 +1,17 @@
 using System.IdentityModel.Tokens.Jwt;
 using System.Reflection;
 using System.Text;
+using Hotline.Application;
 using Hotline.Application.Jobs;
 using Hotline.Application.Orders;
 using Hotline.CallCenter.Configs;
+using Hotline.EventBus;
 using Hotline.Identity;
 using Hotline.Repository.SqlSugar;
 using Hotline.Repository.SqlSugar.Ts;
 using Mapster;
 using MapsterMapper;
+using MediatR.Pipeline;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
 using Microsoft.OpenApi.Models;
@@ -301,5 +304,25 @@ namespace Hotline.Api
 
             return services;
         }
+
+        public static IServiceCollection RegisterMediatR(this IServiceCollection services, string appScope)
+        {
+            services.AddMediatR(cfg =>
+            {
+                cfg.TypeEvaluator = d =>
+                {
+                    var attr = d.GetCustomAttribute(typeof(HandlerInjectAttribute)) as HandlerInjectAttribute;
+                    if (attr is null) return true;
+                    return attr.Enable &&
+                           attr.AppScopes.ToString()
+                               .Split(',', StringSplitOptions.RemoveEmptyEntries)
+                               .Any(x => string.Compare(x, appScope, StringComparison.Ordinal) == 0);
+                };
+                cfg.RegisterServicesFromAssemblyContaining<Program>();
+            });
+            services.AddSingleton<Publisher>();
+
+            return services;
+        }
     }
 }

+ 2 - 1
src/Hotline.Api/config/appsettings.Development.json

@@ -24,7 +24,8 @@
       "Ip": "222.213.23.229"
     },
     "XingTang": {
-      "DbConnectionString": "server=123.56.10.71;Database=callcenter_db;Uid=root;Pwd=Lhw1981!(*!"
+      //"DbConnectionString": "server=123.56.10.71;Database=callcenter_db;Uid=root;Pwd=Lhw1981!(*!"
+      "DbConnectionString": "server=110.188.24.182;Database=callcenter_xingtang;Uid=dev;Pwd=fengwo11!!"
     }
   },
   "ConnectionStrings": {

+ 10 - 3
src/Hotline.Application/FlowEngine/IWorkflowApplication.cs

@@ -1,4 +1,5 @@
-using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine;
+using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
@@ -22,9 +23,15 @@ namespace Hotline.Application.FlowEngine
             CancellationToken cancellationToken = default);
 
         /// <summary>
-        /// 流转至下一节点(节点办理)
+        /// 查询下一节点办理对象类型(user or org)及实际办理对象
         /// </summary>
-        Task<Workflow> NextAsync(NextWorkflowDto dto, ISessionContext current, DateTime? expiredTime = null,
+		Task<FlowAssignInfo> GetNextStepFlowAssignInfoAsync(Workflow workflow, WorkflowStep currentStep,
+            BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, CancellationToken cancellationToken);
+
+		/// <summary>
+		/// 流转至下一节点(节点办理)
+		/// </summary>
+		Task<Workflow> NextAsync(NextWorkflowDto dto, ISessionContext current, DateTime? expiredTime = null,
             CancellationToken cancellationToken = default);
 
         /// <summary>

+ 10 - 2
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -239,8 +239,16 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                 //这里需要判断是否是警情退回
                 orderFlowDto.IsNonPoliceReturn = notification.Dto.External == null ? false : notification.Dto.External.IsPoliceReturn;
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFiled, orderFlowDto, cancellationToken: cancellationToken);
-                //写入质检  针对受理之后直接结束的工单
-                await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
+
+                //try
+                //{
+                //    //写入质检  针对受理之后直接结束的工单
+                //    await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
+                //}
+                //catch (Exception e)
+                //{
+                //    _logger.LogError($"写入质检异常!orderId: {order.Id}, \r\n{e.Message}");
+                //}
 
                 //司法行政监督管理-工单处理
                 await _enforcementApplication.AddEnforcementOrderAsync(order, cancellationToken);

+ 2 - 1
src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs

@@ -148,7 +148,8 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
 
                 //    expiredTimeChanged = true;
                 //}
-                await _orderRepository.UpdateAsync(order, cancellationToken);
+                await _orderRepository.Updateable(order).ExecuteCommandAsync(cancellationToken);
+                //await _orderRepository.UpdateAsync(order, cancellationToken);
 
                 //司法行政监督管理-推诿工单
                 if (notification.Trace.StepType != EStepType.Summary && notification.Trace.StepType != EStepType.End && !notification.Trace.IsCountersignEndStep)

+ 11 - 3
src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs

@@ -196,12 +196,20 @@ namespace Hotline.Application.Handlers.FlowEngine
                             }
                         }
                     }
-                    catch
+                    catch(Exception e)
                     {
+                        _logger.LogError($"发送短信失败! orderId: {order.Id},\r\n{e.Message}");
                     }
 
-                    //写入质检
-                    await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
+                    try
+                    {
+                        //写入质检
+                        await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
+                    }
+                    catch (Exception e)
+                    {
+                        _logger.LogError($"写入质检异常!orderId: {order.Id}, \r\n{e.Message}");
+                    }
                     break;
                 case WorkflowModuleConsts.KnowledgeAdd:
                 case WorkflowModuleConsts.KnowledgeUpdate:

+ 34 - 0
src/Hotline.Application/Handlers/Order/OrderDelayWorkflowEndHandler.cs

@@ -0,0 +1,34 @@
+using Hotline.FlowEngine.Notifications;
+using MediatR;
+using Hotline.EventBus;
+using Microsoft.Extensions.Logging;
+
+namespace Hotline.Application.Handlers.Order
+{
+    [HandlerInject(AppScopes = EAppScope.LuZhou | EAppScope.YiBin)]
+    public class OrderDelayWorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
+    {
+        private readonly ILogger<OrderDelayWorkflowEndHandler> _logger;
+
+        public OrderDelayWorkflowEndHandler(ILogger<OrderDelayWorkflowEndHandler> logger)
+        {
+            _logger = logger;
+        }
+
+        /// <summary>Handles a notification</summary>
+        /// <param name="notification">The notification</param>
+        /// <param name="cancellationToken">Cancellation token</param>
+        public async Task Handle(EndWorkflowNotify notification, CancellationToken cancellationToken)
+        {
+            try
+            {
+                throw new NotImplementedException();
+            }
+            catch (Exception e)
+            {
+                _logger.LogError(e.Message);
+                throw;
+            }
+        }
+    }
+}

+ 1 - 0
src/Hotline.Application/JudicialManagement/EnforcementApplication.cs

@@ -274,6 +274,7 @@ namespace Hotline.Application.JudicialManagement
                      .WhereIF(dto.IsTheClueTrueText == "3", d => d.IsTheClueTrue == false)//线索不属实
                      .WhereIF(dto.IsTheClueTrueText == "4", d => d.IsPassTheBuckOrder == true)//推诿工单
                      .WhereIF(dto.IsTheClueTrueText == "5", d => d.IsEnforcementOrder == true)//行政执法类工单
+
                      .WhereIF(!string.IsNullOrEmpty(dto.EventTypeId), d => dto.EventTypeId == d.EventTypeId)//事项分类
                      .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title.Contains(dto.Title!)) //标题
                      .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No.Contains(dto.No)) //工单编码

+ 5 - 4
src/Hotline.Application/Mappers/OrderMapperConfigs.cs

@@ -106,10 +106,11 @@ public class OrderMapperConfigs : IRegister
             .Ignore(d => d.AcceptorId)
             .Ignore(d => d.AcceptorName)
             .Ignore(d => d.AcceptorStaffNo)
-			//.AfterMapping((s, d) =>
-   //         {
-   //             d.UpdateHandlingStatus(s.IsInCountersign);
-   //         })
+            //.AfterMapping((s, d) =>
+            //{
+            //    //d.UpdateHandlingStatus(s.IsInCountersign);
+            //    d.CurrentStepAcceptTime = s.CurrentStepAcceptTime;
+            //})
             ;
 
         config.ForType<Order, PublishDto>()

+ 2 - 2
src/Hotline.Application/Orders/OrderApplication.cs

@@ -496,9 +496,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!)) //标题
             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.ProvinceNo == dto.ProvinceNo) //省本地编号
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) //工单编码
-            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptType == dto.AcceptType)//受理类型
+            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType)//受理类型
                                                                                                 //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
-            .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannel == dto.Channel)
+            .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)
             //.WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.SourceChannelCode)) //来源渠道
             //.WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId)) //热点类型
             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))

+ 42 - 0
src/Hotline.Application/Quality/AiOrderQualityHandler.cs

@@ -0,0 +1,42 @@
+using Hotline.Ai.Quality;
+using Hotline.Application.Handlers.FlowEngine;
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.WorkflowModules;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Quality
+{
+	public class AiOrderQualityHandler : INotificationHandler<AiOrderQualityNotify>
+	{
+		private readonly IAiQualityService _aiQualityService;
+		private readonly ILogger<AiOrderQualityHandler> _logger;
+		public AiOrderQualityHandler(IAiQualityService aiQualityService, ILogger<AiOrderQualityHandler> logger) { _aiQualityService = aiQualityService; _logger = logger; }
+
+		/// <summary>Handles a notification</summary>
+		/// <param name="notification">The notification</param>
+		/// <param name="cancellationToken">Cancellation token</param>
+		public async Task Handle(AiOrderQualityNotify notification, CancellationToken cancellationToken)
+		{
+			try
+			{
+				await _aiQualityService.CreateAiOrderQualityTask(
+					notification.Quality,
+					notification.QualityHandler.AudioFile,
+					notification.QualityHandler.FromNo,
+					notification.QualityHandler.CallStartTime,
+					notification.Order, notification.QualityHandler.ViteRecordPrefix, cancellationToken);
+			}
+			catch (Exception e)
+			{
+				_logger.LogError($"推送质检信息错误! QualityId: {notification.Quality.Id},\r\n{e.Message}");
+				throw;
+			}
+		}
+	}
+}

+ 78 - 51
src/Hotline.Application/Quality/QualityApplication.cs

@@ -3,6 +3,8 @@ using Hotline.Application.CallCenter;
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Configs;
+using Hotline.EventBus;
+using Hotline.FlowEngine.Notifications;
 using Hotline.Orders;
 using Hotline.Quality;
 using Hotline.Settings;
@@ -10,7 +12,12 @@ using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.Quality;
 using MapsterMapper;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
+using StackExchange.Redis;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Unicode;
 using XF.Domain.Authentications;
 using XF.Domain.Constants;
 using XF.Domain.Dependency;
@@ -30,8 +37,10 @@ namespace Hotline.Application.Quality
         //private readonly IRepository<TrCallRecord> _trCallRecordRepository;
         private readonly IRepository<QualityTemplate> _qualityTemplate;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly ILogger<Hotline.Quality.Quality> _logger;
         private readonly ICallApplication _callApplication;
         private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
+        private readonly Publisher _publisher;
 
         public QualityApplication(
             ISessionContext sessionContext,
@@ -41,10 +50,12 @@ namespace Hotline.Application.Quality
             IAiQualityService aiQualityService,
             //IRepository<TrCallRecord> trCallRecordRepository,
             IRepository<QualityTemplate> qualityTemplate,
-			IOrderRepository orderRepository,
+            IOrderRepository orderRepository,
             ISystemSettingCacheManager systemSettingCacheManager,
             ICallApplication callApplication,
-            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions)
+            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
+            ILogger<Hotline.Quality.Quality> logger,
+            Publisher publisher)
         {
             _sessionContext = sessionContext;
             _mapper = mapper;
@@ -53,11 +64,14 @@ namespace Hotline.Application.Quality
             _aiQualityService = aiQualityService;
             //_trCallRecordRepository = trCallRecordRepository;
             _qualityTemplate = qualityTemplate;
-			_orderRepository = orderRepository;
+            _orderRepository = orderRepository;
             _systemSettingCacheManager = systemSettingCacheManager;
             _callApplication = callApplication;
             _callcenterOptions = callcenterOptions;
-        }
+            _logger = logger;
+            _publisher = publisher;
+
+		}
 
         public async Task AddQualityAsync(EQualitySource Source, string OrderId, string VisitId, CancellationToken cancellationToken)
         {
@@ -73,7 +87,7 @@ namespace Hotline.Application.Quality
             else
             {
                 if (Source == EQualitySource.Visit) await _qualityRepository.Updateable().SetColumns(x => x.VisitId == VisitId).Where(x => x.OrderId == OrderId && x.Source == Source).ExecuteCommandAsync();
-			}
+            }
         }
 
         public async Task AddQualityAsync(EQualitySource Source, string OrderId, CancellationToken cancellationToken)
@@ -92,7 +106,7 @@ namespace Hotline.Application.Quality
         {
             var quality = _mapper.Map<Hotline.Quality.Quality>(model);
             quality.InitId();
-			
+
             if (model.QualityDetails.Any())
             {
                 foreach (var item in model.QualityDetails)
@@ -102,54 +116,67 @@ namespace Hotline.Application.Quality
                 List<QualityDetail> details = _mapper.Map<List<QualityDetail>>(model.QualityDetails);
                 await _qualiteyDetail.AddRangeAsync(details, cancellationToken);
             }
-            //受理智能质检
-            if (model.Source == EQualitySource.Accepted)
+            var order = await _orderRepository.GetAsync(model.OrderId);
+			//受理智能质检
+			if (model.Source == EQualitySource.Accepted)
             {
-	            var teAny = await _qualityTemplate.Queryable()
-		            .LeftJoin<QualityTemplateDetail>((x, d) => x.Id == d.TemplateId)
-		            .LeftJoin<QualityItem>((x, d, i) => d.ItemId == i.Id)
-		            .Where((x, d, i) => i.IsIntelligent == 1).AnyAsync();
-	            if (teAny)
-	            {
-		            var order = await _orderRepository.GetAsync(model.OrderId);
-		            if (order != null && !string.IsNullOrEmpty(order.CallId))
-		            {
-			            quality.AiQuality = true;
-			            quality.Mode = "智能质检";
-			            //var call = await _trCallRecordRepository.GetAsync(x => x.CallAccept == order.CallId); //由CallAccept改成OtherAccept
-			            //var call = await _trCallRecordRepository.GetAsync(x => x.OtherAccept == order.CallId);
-
-                        var audioFile = string.Empty;
-                        var fromNo = string.Empty;
-                        DateTime? callStartTime = null;
-                        if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
-                        {
-                            var call = await _callApplication.GetTianrunCallAsync(order.CallId, cancellationToken);
-                            audioFile = call.RecordingAbsolutePath;
-                            fromNo = call.CPN;
-                            callStartTime = call.CreatedTime;
-                        }
-                        else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
-                        {
-                            var call = await _callApplication.GetCallAsync(order.CallId, cancellationToken);
-                            audioFile = call.AudioFile;
-                            fromNo = call.FromNo;
-                            callStartTime = call.BeginIvrTime;
-                        }
-
-                        var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
-						//await _aiQualityService.CreateAiOrderQualityTask(quality, call, order, setting?.SettingValue[0], cancellationToken);
-                        await _aiQualityService.CreateAiOrderQualityTask(
-                            quality,
-                            audioFile,
-                            fromNo,
-                            callStartTime,
-                            order, setting?.SettingValue[0], cancellationToken);
+                var teAny = await _qualityTemplate.Queryable()
+                    .LeftJoin<QualityTemplateDetail>((x, d) => x.Id == d.TemplateId)
+                    .LeftJoin<QualityItem>((x, d, i) => d.ItemId == i.Id)
+                    .Where((x, d, i) => i.IsIntelligent == 1).AnyAsync();
+                if (teAny)
+                {
+                    if (order != null && !string.IsNullOrEmpty(order.CallId))
+                    {
+                        quality.AiQuality = true;
+                        quality.Mode = "智能质检";
                     }
-	            }
+                }
             }
-			await _qualityRepository.AddAsync(quality, cancellationToken);
-		}
+            await _qualityRepository.AddAsync(quality, cancellationToken);
+            ///调用智能质检 捷通
+			if (quality.AiQuality is true)
+            {
+                try
+                {
+                    var audioFile = string.Empty;
+                    var fromNo = string.Empty;
+                    DateTime? callStartTime = null;
+                    if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
+                    {
+                        var call = await _callApplication.GetTianrunCallAsync(order.CallId, cancellationToken);
+                        audioFile = call.RecordingAbsolutePath;
+                        fromNo = call.CPN;
+                        callStartTime = call.CreatedTime;
+                    }
+                    else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
+                    {
+                        var call = await _callApplication.GetCallAsync(order.CallId, cancellationToken);
+                        audioFile = call.AudioFile;
+                        fromNo = call.FromNo;
+                        callStartTime = call.BeginIvrTime;
+                    }
+
+                    var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
+                    var handler = new AiQualityHandler() 
+                    {
+                        AudioFile =audioFile,
+                        FromNo = fromNo,
+                        CallStartTime = callStartTime,
+                        ViteRecordPrefix = setting?.SettingValue[0],
+					};
+                   await  _publisher.PublishAsync(new AiOrderQualityNotify(quality, order, handler),PublishStrategy.ParallelNoWait,cancellationToken);
+                }
+                catch (Exception e)
+                {
+                    _logger.LogError($"写入智能质检异常!, \r\n{e.Message}");
+                }
+            }
+
+            _logger.LogInformation($"质检对象:{System.Text.Json.JsonSerializer.Serialize(quality,
+                new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) })}");
+            await _qualityRepository.AddAsync(quality, cancellationToken);
+        }
 
         public async Task UpdateQualityAsync(UpdateQualityDto model, CancellationToken cancellationToken)
         {

+ 9 - 6
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -207,16 +207,19 @@ namespace Hotline.Repository.SqlSugar.Extensions
             /***写AOP等方法***/
             db.Aop.OnLogExecuting = (sql, pars) =>
             {
-                // Log.Information("Sql: {0}", sql);
-                // Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
+                //获取原生SQL推荐 5.1.4.63  性能OK
+                //Log.Information(UtilMethods.GetNativeSql(sql, pars));
+
+                //Log.Information("Sql: {0}", sql);
+                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
             };
             db.Aop.OnError = (exp) =>//SQL报错
             {
-                //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
-                Log.Error("SqlError: {0}", exp.Sql);
-
+                //获取原生SQL推荐 5.1.4.63  性能OK
+                Log.Error(UtilMethods.GetNativeSql(exp.Sql, (SugarParameter[])exp.Parametres));
+                Log.Error(exp.InnerException.Message);
                 //5.0.8.2 获取无参数化 SQL  对性能有影响,特别大的SQL参数多的,调试使用
-                //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
+                //UtilMethods.GetSqlString(DbType.SqlServer, exp.sql, exp.parameters)
             };
             //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
             //{

+ 12 - 1
src/Hotline.Repository.SqlSugar/Extensions/XingTangDbExtensions.cs

@@ -11,9 +11,20 @@ public static class XingTangDbExtensions
     {
         SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
         {
-            DbType = DbType.MySql,
+            DbType = DbType.PostgreSQL,
             ConnectionString = xingTangConfiguration.DbConnectionString,
             IsAutoCloseConnection = true,
+            //ConfigureExternalServices = new ConfigureExternalServices()
+            //{
+            //    EntityService = (x, p) => //处理列名
+            //    {
+            //        p.DbColumnName = string.IsNullOrEmpty(p.DbColumnName) ? p.DbColumnName : p.DbColumnName.ToLower(); 
+            //        //UtilMethods.ToUnderLine(p.DbColumnName);//ToUnderLine驼峰转下划线方法
+            //    },
+            //    EntityNameService = (x, p) => //处理表名
+            //    {
+            //    }
+            //}
         },
              db => { }
          );

+ 9 - 0
src/Hotline.Share/Dtos/Quality/AiQualityDto.cs

@@ -325,4 +325,13 @@ namespace Hotline.Share.Dtos.Quality
 		/// </summary>
 		public string text { get; set; }
 	}
+
+	public class AiQualityHandler {
+	
+		public string AudioFile { get; set; }
+		public string FromNo { get; set; }
+		public DateTime? CallStartTime { get; set; }
+		public string ViteRecordPrefix { get; set; }
+
+	}
 }

+ 3 - 9
src/Hotline/Ai/Quality/IAiQualityService.cs

@@ -12,14 +12,8 @@ using RestSharp;
 namespace Hotline.Ai.Quality
 {
     public interface IAiQualityService
-	{
-        Task CreateAiOrderQualityTask(Hotline.Quality.Quality model, 
-            //TrCallRecord? call,
-            string audioFile,
-            string fromNo,
-            DateTime? callStartTime,
-            Order order, 
-            string viteRecordPrefix, 
-            CancellationToken cancellationToken);
+	{  
+        //TrCallRecord? call,
+		Task CreateAiOrderQualityTask(Hotline.Quality.Quality model,string audioFile,string fromNo,DateTime? callStartTime,Order order, string viteRecordPrefix, CancellationToken cancellationToken);
 	}
 }

+ 7 - 0
src/Hotline/AppDefaults.cs

@@ -27,5 +27,12 @@ namespace Hotline
             public const string TianRun = "TianRun";
             public const string XingTang = "XingTang";
         }
+
+        public class AppScope
+        {
+            public const string YiBin = "YiBin";
+            public const string ZiGong = "ZiGong";
+            public const string LuZhou = "LuZhou";
+        }
     }
 }

+ 4 - 2
src/Hotline/Authentications/ProvinceSessionContext.cs

@@ -13,17 +13,19 @@ namespace Hotline.Authentications
             OrgLevel = 1;
         }
 
+        //TODO 取消写入
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
         /// </summary>
         /// <exception cref="AuthenticationException"></exception>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
-        public string? UserName { get; }
+        //TODO 取消写入
+		public string? UserName { get; set; }
         public string? Phone { get; }
 
         /// <summary>

+ 1 - 1
src/Hotline/Caching/Services/SysDicDataCacheManager.cs

@@ -21,7 +21,7 @@ namespace Hotline.Caching.Services
         {
             var sysDicDataList = _cacheSysDicData.GetOrSet(code, k =>
             {
-                return _sysDicDataRepository.Queryable().Where(x => x.DicTypeCode == code).OrderBy(x=>x.Sort).ToTreeAsync(x => x.Children, it => it.ParentId, "").GetAwaiter().GetResult();
+                return _sysDicDataRepository.Queryable().Where(x => x.DicTypeCode == code && x.IsShow == true).OrderBy(x=>x.Sort).ToTreeAsync(x => x.Children, it => it.ParentId, "").GetAwaiter().GetResult();
             });
             return sysDicDataList;
         }

+ 39 - 0
src/Hotline/EventBus/CustomMediator.cs

@@ -0,0 +1,39 @@
+using MediatR;
+using MediatR.Pipeline;
+using Microsoft.Extensions.Logging;
+
+namespace Hotline.EventBus;
+
+public class CustomMediator : Mediator
+{
+    private readonly Func<IEnumerable<NotificationHandlerExecutor>, INotification, CancellationToken, Task> _publish;
+
+    public CustomMediator(IServiceProvider serviceFactory, Func<IEnumerable<NotificationHandlerExecutor>, INotification, CancellationToken, Task> publish) : base(serviceFactory) 
+        => _publish = publish;
+
+    protected override Task PublishCore(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken) 
+        => _publish(handlerExecutors, notification, cancellationToken);
+}
+
+public class ExceptionLoggingHandler<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException>
+    where TRequest : IRequest<TResponse>
+    where TException : Exception
+{
+    private readonly ILogger<ExceptionLoggingHandler<TRequest, TResponse, TException>> _logger;
+
+    public ExceptionLoggingHandler(ILogger<ExceptionLoggingHandler<TRequest, TResponse, TException>> logger)
+    {
+        _logger = logger;
+    }
+
+    public Task Handle(TRequest request, TException exception, RequestExceptionHandlerState<TResponse> state, CancellationToken cancellationToken)
+    {
+        _logger.LogError(exception, "Something went wrong while handling request of type {@requestType}", typeof(TRequest));
+
+        // TODO: when we want to show the user somethig went wrong, we need to expand this with something like
+        // a ResponseBase where we wrap the actual response and return an indication whether the call was successful or not.
+        state.SetHandled(default!);
+
+        return Task.CompletedTask;
+    }
+}

+ 12 - 0
src/Hotline/EventBus/EAppScope.cs

@@ -0,0 +1,12 @@
+namespace Hotline.EventBus;
+
+/// <summary>
+/// 适用范围(prop名称与appsettings.json保持一致)
+/// </summary>
+[Flags]
+public enum EAppScope
+{
+    YiBin = 1 << 0,
+    ZiGong = 1 << 1,
+    LuZhou = 1 << 2,
+}

+ 22 - 0
src/Hotline/EventBus/HandlerInjectAttribute.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.EventBus
+{
+    [AttributeUsage(AttributeTargets.Class)]
+    public class HandlerInjectAttribute : Attribute
+    {
+        /// <summary>
+        /// 适用范围
+        /// </summary>
+        public EAppScope AppScopes { get; set; }
+
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        public bool Enable { get; set; } = true;
+    }
+}

+ 37 - 0
src/Hotline/EventBus/PublishStrategy.cs

@@ -0,0 +1,37 @@
+namespace Hotline.EventBus;
+
+/// <summary>
+/// Strategy to use when publishing notifications
+/// </summary>
+public enum PublishStrategy
+{
+    /// <summary>
+    /// Run each notification handler after one another. Returns when all handlers are finished. In case of any exception(s), they will be captured in an AggregateException.
+    /// </summary>
+    SyncContinueOnException = 0,
+
+    /// <summary>
+    /// Run each notification handler after one another. Returns when all handlers are finished or an exception has been thrown. In case of an exception, any handlers after that will not be run.
+    /// </summary>
+    SyncStopOnException = 1,
+
+    /// <summary>
+    /// Run all notification handlers asynchronously. Returns when all handlers are finished. In case of any exception(s), they will be captured in an AggregateException.
+    /// </summary>
+    Async = 2,
+
+    /// <summary>
+    /// Run each notification handler on its own thread using Task.Run(). Returns immediately and does not wait for any handlers to finish. Note that you cannot capture any exceptions, even if you await the call to Publish.
+    /// </summary>
+    ParallelNoWait = 3,
+
+    /// <summary>
+    /// Run each notification handler on its own thread using Task.Run(). Returns when all threads (handlers) are finished. In case of any exception(s), they are captured in an AggregateException by Task.WhenAll.
+    /// </summary>
+    ParallelWhenAll = 4,
+
+    /// <summary>
+    /// Run each notification handler on its own thread using Task.Run(). Returns when any thread (handler) is finished. Note that you cannot capture any exceptions (See msdn documentation of Task.WhenAny)
+    /// </summary>
+    ParallelWhenAny = 5,
+}

+ 146 - 0
src/Hotline/EventBus/Publisher.cs

@@ -0,0 +1,146 @@
+using MediatR;
+
+namespace Hotline.EventBus;
+
+public class Publisher
+{
+    private readonly IServiceProvider _serviceFactory;
+
+    public Publisher(IServiceProvider serviceFactory)
+    {
+        _serviceFactory = serviceFactory;
+
+        PublishStrategies[PublishStrategy.Async] = new CustomMediator(_serviceFactory, AsyncContinueOnException);
+        PublishStrategies[PublishStrategy.ParallelNoWait] = new CustomMediator(_serviceFactory, ParallelNoWait);
+        PublishStrategies[PublishStrategy.ParallelWhenAll] = new CustomMediator(_serviceFactory, ParallelWhenAll);
+        PublishStrategies[PublishStrategy.ParallelWhenAny] = new CustomMediator(_serviceFactory, ParallelWhenAny);
+        PublishStrategies[PublishStrategy.SyncContinueOnException] = new CustomMediator(_serviceFactory, SyncContinueOnException);
+        PublishStrategies[PublishStrategy.SyncStopOnException] = new CustomMediator(_serviceFactory, SyncStopOnException);
+    }
+
+    public IDictionary<PublishStrategy, IMediator> PublishStrategies = new Dictionary<PublishStrategy, IMediator>();
+    public PublishStrategy DefaultStrategy { get; set; } = PublishStrategy.SyncContinueOnException;
+
+    public Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken)
+    {
+        return PublishAsync(notification, DefaultStrategy, cancellationToken);
+    }
+
+    public Task PublishAsync<TNotification>(TNotification notification, PublishStrategy strategy, CancellationToken cancellationToken)
+    {
+        if (!PublishStrategies.TryGetValue(strategy, out var mediator))
+        {
+            throw new ArgumentException($"Unknown strategy: {strategy}");
+        }
+        
+        return mediator.Publish(notification, cancellationToken);
+    }
+
+    #region private
+
+    private Task ParallelWhenAll(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        var tasks = new List<Task>();
+
+        foreach (var handler in handlers)
+        {
+            tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken)));
+        }
+
+        return Task.WhenAll(tasks);
+    }
+
+    private Task ParallelWhenAny(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        var tasks = new List<Task>();
+
+        foreach (var handler in handlers)
+        {
+            tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken)));
+        }
+
+        return Task.WhenAny(tasks);
+    }
+
+    private Task ParallelNoWait(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        foreach (var handler in handlers)
+        {
+            Task.Run(() => handler.HandlerCallback(notification, cancellationToken));
+        }
+
+        return Task.CompletedTask;
+    }
+
+    private async Task AsyncContinueOnException(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        var tasks = new List<Task>();
+        var exceptions = new List<Exception>();
+
+        foreach (var handler in handlers)
+        {
+            try
+            {
+                tasks.Add(handler.HandlerCallback(notification, cancellationToken));
+            }
+            catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException))
+            {
+                exceptions.Add(ex);
+            }
+        }
+
+        try
+        {
+            await Task.WhenAll(tasks).ConfigureAwait(false);
+        }
+        catch (AggregateException ex)
+        {
+            exceptions.AddRange(ex.Flatten().InnerExceptions);
+        }
+        catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException))
+        {
+            exceptions.Add(ex);
+        }
+
+        if (exceptions.Any())
+        {
+            throw new AggregateException(exceptions);
+        }
+    }
+
+    private async Task SyncStopOnException(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        foreach (var handler in handlers)
+        {
+            await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false);
+        }
+    }
+
+    private async Task SyncContinueOnException(IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken cancellationToken)
+    {
+        var exceptions = new List<Exception>();
+
+        foreach (var handler in handlers)
+        {
+            try
+            {
+                await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false);
+            }
+            catch (AggregateException ex)
+            {
+                exceptions.AddRange(ex.Flatten().InnerExceptions);
+            }
+            catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException))
+            {
+                exceptions.Add(ex);
+            }
+        }
+
+        if (exceptions.Any())
+        {
+            throw new AggregateException(exceptions);
+        }
+    }
+
+    #endregion
+}

+ 9 - 0
src/Hotline/FlowEngine/Notifications/WorkflowNotify.cs

@@ -1,6 +1,8 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
 using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.FlowEngine;
 using MediatR;
 
@@ -44,3 +46,10 @@ public record TerminalWorkflowNotify(Workflow Workflow) : INotification;
 /// 撤销
 /// </summary>
 public record CancelWorkflowNotify(Workflow Workflow) : INotification;
+
+
+/// <summary>
+/// 智能质检
+/// </summary>
+/// <param name="Workflow"></param>
+public record AiOrderQualityNotify(Quality.Quality Quality , Order Order , AiQualityHandler QualityHandler) : INotification;

+ 1 - 1
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -561,7 +561,7 @@ public partial class Workflow
         string? handleOrgAreaName,
         int handlerOrgLevel)
     {
-        CurrentStepAcceptTime = step.AcceptTime;
+        //CurrentStepAcceptTime = step.AcceptTime;
         CurrentHandleTime = step.HandleTime;
         CurrentHandleOrgAreaCode = handleOrgAreaCode;
         CurrentHandleOrgAreaName = handleOrgAreaName;

+ 7 - 1
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -15,6 +15,7 @@ using MediatR;
 using Microsoft.Extensions.Logging;
 using SqlSugar;
 using System.Diagnostics;
+using Hotline.EventBus;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Entities;
@@ -36,6 +37,7 @@ namespace Hotline.FlowEngine.Workflows
         private readonly ISessionContext _sessionContext;
         private readonly IMapper _mapper;
         private readonly IMediator _mediator;
+        private readonly Publisher _publisher;
         private readonly ILogger<WorkflowDomainService> _logger;
         private readonly IFileRepository _fileRepository;
         private readonly IRepository<User> _userRepository;
@@ -50,6 +52,7 @@ namespace Hotline.FlowEngine.Workflows
             ISessionContext sessionContext,
             IMapper mapper,
             IMediator mediator,
+            Publisher publisher,
             ILogger<WorkflowDomainService> logger,
             IFileRepository fileRepository)
         {
@@ -62,6 +65,7 @@ namespace Hotline.FlowEngine.Workflows
             _sessionContext = sessionContext;
             _mapper = mapper;
             _mediator = mediator;
+            _publisher = publisher;
             _logger = logger;
             _fileRepository = fileRepository;
         }
@@ -178,7 +182,9 @@ namespace Hotline.FlowEngine.Workflows
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
             //publish
-            await _mediator.Publish(new StartWorkflowNotify(workflow, dto, flowAssignInfo, trace), cancellationToken);
+            //await _mediator.Publish(new StartWorkflowNotify(workflow, dto, flowAssignInfo, trace), cancellationToken);
+            await _publisher.PublishAsync(new StartWorkflowNotify(workflow, dto, flowAssignInfo, trace),
+                PublishStrategy.ParallelNoWait, cancellationToken);
         }
 
         public async Task<Workflow> GetWorkflowAsync(string workflowId,

+ 11 - 4
src/Hotline/Settings/SystemDicData.cs

@@ -5,8 +5,8 @@ using XF.Domain.Repository;
 namespace Hotline.Settings
 {
     [Description("字典表")]
-    public class SystemDicData: CreationEntity
-    {
+    public class SystemDicData : FullStateEntity
+	{
         /// <summary>
         /// 字典类型Id
         /// </summary>
@@ -32,10 +32,17 @@ namespace Hotline.Settings
         /// </summary>
         [SugarColumn(DefaultValue = "0")]
         public int Sort { get; set; }
+
         /// <summary>
-        /// 上级ID
+        /// 是否显示
         /// </summary>
-        [SugarColumn(IsNullable = true)]
+        [SugarColumn(DefaultValue = "t")]
+		public bool IsShow { get; set; }
+
+		/// <summary>
+		/// 上级ID
+		/// </summary>
+		[SugarColumn(IsNullable = true)]
         public string? ParentId { get; set; }
 
         [SugarColumn(IsIgnore = true)]

+ 4 - 6
src/XingTang.Sdk/XingtangCall.cs

@@ -5,13 +5,12 @@ namespace XingTang.Sdk;
 [SugarTable("call_cti_trafficlist")]
 public class XingtangCall
 {
-    [SugarColumn(ColumnName = "ID", IsPrimaryKey = true)]
+    [SugarColumn(IsPrimaryKey = true)]
     public int Id { get; set; }
 
     /// <summary>
     /// 客户id
     /// </summary>
-    [SugarColumn(ColumnName = "CustomerID")]
     public string CustomerId { get; set; }
 
     public string CallGuid { get; set; }
@@ -89,13 +88,13 @@ public class XingtangCall
     /// <summary>
     /// 呼出通道号
     /// </summary>
-    [SugarColumn(ColumnName = "CallOut_Channel")]
+    [SugarColumn(ColumnName = "callout_channel")]
     public int CallOutChannel { get; set; }
 
     /// <summary>
     /// 呼入通道号
     /// </summary>
-    [SugarColumn(ColumnName = "CallIn_Channel")]
+    [SugarColumn(ColumnName = "callin_channel")]
     public int CallInChannel { get; set; }
 
     /// <summary>
@@ -134,10 +133,9 @@ public class XingtangCall
     public int? CallForword { get; set; }
     public DateTime? RingStartTime { get; set; }
 
-    [SugarColumn(ColumnName = "Trans_Called")]
+    [SugarColumn(ColumnName = "trans_called")]
     public string? TransCalled { get; set; }
 
-    [SugarColumn(ColumnName = "UserListID")]
     public int? UserListId { get; set; }
 
     public int? Score { get; set; }

+ 1 - 1
src/XingTang.Sdk/XingtangSeatOperation.cs

@@ -13,7 +13,7 @@ namespace XingTang.Sdk
     [SugarTable("call_cti_seatingoperation")]
     public class XingtangSeatOperation
     {
-        [SugarColumn(ColumnName = "ID", IsPrimaryKey = true)]
+        [SugarColumn(IsPrimaryKey = true)]
         public int Id { get; set; }
 
         /// <summary>