瀏覽代碼

Merge branch 'dev' of http://110.188.24.182:10023/Fengwo/hotline into dev

tangjiang 6 月之前
父節點
當前提交
70264a9b0b

+ 216 - 267
src/Hotline.Api/Controllers/OrderController.cs

@@ -67,6 +67,7 @@ using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
 using Newtonsoft.Json;
 using XF.Domain.Extensions;
+using Order = Hotline.Orders.Order;
 using WorkflowStep = Hotline.FlowEngine.Workflows.WorkflowStep;
 
 namespace Hotline.Api.Controllers;
@@ -868,15 +869,15 @@ public class OrderController : BaseController
             .Includes(d => d.Order)
             .Includes(d => d.Employee)
             .Includes(d => d.OrderVisitDetails)
-            .WhereIF(dto.VisitState == EVisitStateQuery.NoVisit,
+            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoVisit,
                 d => d.VisitState == EVisitState.WaitForVisit ||
                      d.VisitState == EVisitState.NoSatisfiedWaitForVisit)
-            .WhereIF(dto.VisitState == EVisitStateQuery.Visited, d => d.VisitState == EVisitState.Visited)
-            .WhereIF(dto.VisitState == EVisitStateQuery.SMSUnsatisfied, m => m.VisitState == EVisitState.SMSUnsatisfied)
-            .WhereIF(dto.VisitState == EVisitStateQuery.SMSVisiting, m => m.VisitState == EVisitState.SMSVisiting)
+            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.Visited, d => d.VisitState == EVisitState.Visited)
+            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.SMSUnsatisfied, d => d.VisitState == EVisitState.SMSUnsatisfied)
+            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.SMSVisiting, d => d.VisitState == EVisitState.SMSVisiting)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Order.Title.StartsWith(dto.Keyword!))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
-            .WhereIF(dto.VisitType != null, x => x.VisitType == dto.VisitType)
+            .WhereIF(dto.VisitType != null, d => d.VisitType == dto.VisitType)
             .WhereIF(dto.FiledType != null && dto.FiledType == FiledType.CenterFiled, d => d.Order.ProcessType == EProcessType.Zhiban)
             .WhereIF(dto.FiledType != null && dto.FiledType == FiledType.OrgFiled, d => d.Order.ProcessType == EProcessType.Jiaoban)
             .WhereIF(dto.IsCountersign != null && dto.IsCountersign == true, d => d.Order.CounterSignType != null)
@@ -886,15 +887,16 @@ public class OrderController : BaseController
             .WhereIF(dto.IsProvince != null && dto.IsProvince == true, d => d.Order.IsProvince == true)
             .WhereIF(dto.IsProvince != null && dto.IsProvince == false, d => d.Order.IsProvince == false)
             .WhereIF(dto.IsEffectiveAiVisit != null, d => d.IsEffectiveAiVisit == dto.IsEffectiveAiVisit)
-            .WhereIF(dto.FromPhone.NotNullOrEmpty(), m => m.Order.FromPhone == dto.FromPhone)
-            .WhereIF(dto.Contact.NotNullOrEmpty(), m => m.Order.Contact == dto.Contact)
+            .WhereIF(dto.FromPhone.NotNullOrEmpty(), d => d.Order.FromPhone == dto.FromPhone)
+            .WhereIF(dto.Contact.NotNullOrEmpty(), d => d.Order.Contact == dto.Contact)
             .WhereIF(dto.VoiceEvaluate.Any(), d => d.OrderVisitDetails.Any(m => dto.VoiceEvaluate.Contains(m.VoiceEvaluate.Value)))
             .WhereIF(dto.SeatEvaluate.Any(), d => d.OrderVisitDetails.Any(m => dto.SeatEvaluate.Contains(m.SeatEvaluate.Value)))
             .WhereIF(dto.OrgProcessingResults.Any(),
                 d => d.OrderVisitDetails.Any(m => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(m.OrgProcessingResults, "Key"))))
             .WhereIF(dto.OrgHandledAttitude.Any(),
-                d => d.OrderVisitDetails.Any(q => dto.OrgHandledAttitude.Contains(SqlFunc.JsonField(q.OrgHandledAttitude, "Key"))))
-            .OrderByDescending(x => x.PublishTime)
+                d => d.OrderVisitDetails.Any(m => dto.OrgHandledAttitude.Contains(SqlFunc.JsonField(m.OrgHandledAttitude, "Key"))))
+            .WhereIF(dto.Channel.NotNullOrEmpty(), d => d.Order.SourceChannelCode == dto.Channel)
+            .OrderByDescending(d => d.PublishTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
     }
@@ -1957,7 +1959,7 @@ public class OrderController : BaseController
             //.LeftJoin<OrderScreen>((x, s) => x.Id == s.VisitDetailId && s.IsDeleted == false)
             .Includes(x => x.OrderScreens)
             .Where(x => x.OrderScreens.Any(s => s.Status == EScreenStatus.SendBack && s.SendBackApply == true) || x.OrderScreens.Any() == false
-                //|| x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.SendBackApply != true)) == false
+            //|| x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.SendBackApply != true)) == false
             )
             .WhereIF(dto.ScreenSendBack is 1, x => x.OrderScreens.Any(s => s.Status == EScreenStatus.SendBack && s.SendBackApply == true))
             .WhereIF(dto.ScreenSendBack is 2, x => x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.SendBackApply != true)) == false)
@@ -2039,80 +2041,50 @@ public class OrderController : BaseController
     [HttpGet("screen")]
     public async Task<PagedDto<OrderScreenListDto>> ScreenList([FromQuery] ScreenListDto dto)
     {
-        var handler = dto.TabStatus is EScreenStatus.Apply;
-        var isAdmin = _orderDomainService.IsCheckAdmin();
-        ISugarQueryable<OrderScreen> query;
+        var (total, items) = await  _orderApplication.OrderScreenList(dto)
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        return new PagedDto<OrderScreenListDto>(total, _mapper.Map<IReadOnlyList<OrderScreenListDto>>(items));
+    }
 
-        if (dto.source == 1)
-        {
-            query = _orderScreenRepository.Queryable(hasHandled: !handler, isAdmin: isAdmin);
-        }
-        else
-        {
-            query = _orderScreenRepository.Queryable(isAdmin: isAdmin)
-                .WhereIF(!isAdmin, x => x.CreatorOrgId.StartsWith(_sessionContext.RequiredOrgId));
-        }
 
-        query = query
-            .Includes(d => d.Order)
-            .Includes(d => d.VisitDetail)
-            .Includes(d => d.Visit, v => v.Order)
-            .Includes(d => d.Workflow)
-            .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.UserId).OrderByDescending(sd => sd.AuditTime).Take(1)
-                .ToList())
-            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!))
-            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Visit.Order.No.Contains(dto.No!));
-        if (dto.TabStatus is EScreenStatus.Apply)
-        {
-            query.Where(d =>
-                (d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval ||
-                 (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
-        }
+	/// <summary>
+	/// 工单甄别列表导出
+	/// </summary>
+	/// <returns></returns>
+	[HttpPost("screen_list/_export")]
+    public async Task<FileStreamResult> ScreenListExport([FromBody] ExportExcelDto<ScreenListDto> dto)
+    {
+	    var query = _orderApplication.OrderScreenList(dto.QueryDto);
+	    List<OrderScreen> data;
+	    if (dto.IsExportAll)
+	    {
+		    data = await query.ToListAsync(HttpContext.RequestAborted);
+	    }
+	    else
+	    {
+		    var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+		    data = items;
+	    }
 
-        if (dto.TabStatus.HasValue && dto.Status == EScreenStatus.MyHandle)
-        {
-            query.Where(d => (d.Status != EScreenStatus.Apply));
-        }
+	    var dataDtos = _mapper.Map<ICollection<OrderScreenListDto>>(data);
 
-        var (total, items) = await query
-            .WhereIF(dto.DataScope is 1, x => x.CreatorId == _sessionContext.RequiredUserId)
-            .WhereIF(dto.Status.HasValue, x => x.Status == dto.Status)
-            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.Order!.AcceptTypeCode! == dto.AcceptType!)
-            .WhereIF(!string.IsNullOrEmpty(dto.HotspotSpliceName), x => x.Order!.Hotspot.HotSpotFullName!.StartsWith(dto.HotspotSpliceName!))
-            .WhereIF(!string.IsNullOrEmpty(dto.SourceChannel), x => x.Order!.SourceChannelCode! == dto.SourceChannel!)
-            .WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitDetail.VisitOrgName!.Contains(dto.VisitOrgName!))
-            .WhereIF(!string.IsNullOrEmpty(dto.CreatorOrgName), d => d.CreatorOrgName == dto.CreatorOrgName)
-            .WhereIF(!string.IsNullOrEmpty(dto.CreatorName), d => d.CreatorName == dto.CreatorName)
-            .WhereIF(dto.IsProvince.HasValue, x => x.Order!.IsProvince == dto.IsProvince)
-            .WhereIF(dto.IsSendBackApplyNum is true, x => x.SendBackApplyNum > 0)
-            .WhereIF(dto.IsSendBackApplyNum is false, x => x.SendBackApplyNum == 0)
-            .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
-            .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
-            .WhereIF(!string.IsNullOrEmpty(dto.OrderId), d => d.OrderId == dto.OrderId)
-            //甄别列表
-            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
-            .WhereIF(!string.IsNullOrEmpty(dto.CurrentHandleOrgName), x => x.Order!.CurrentHandleOrgName!.Contains(dto.CurrentHandleOrgName!))
-            .WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue,
-                x => x.Order!.CreationTime >= dto.CreationTime && x.Order!.CreationTime <= dto.EndCreationTime)
-            .WhereIF(dto.CurrentHandleTime.HasValue && dto.EndCurrentHandleTime.HasValue,
-                x => x.Order!.CurrentHandleTime >= dto.CurrentHandleTime && x.Order!.ActualHandleTime <= dto.EndCurrentHandleTime)
-            .WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue,
-                x => x.Order!.FiledTime >= dto.FiledTime && x.Order!.FiledTime <= dto.EndFiledTime)
-            .WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue,
-                x => x.Visit.VisitTime >= dto.VisitTime && x.Visit.VisitTime <= dto.EndVisitTime)
-            .WhereIF(!string.IsNullOrEmpty(dto.Contact), x => x.Order!.Contact! == dto.Contact!)
-            .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), x => x.Order!.FromPhone! == dto.FromPhone!)
-            .OrderByIF(dto is { SortRule: 0, SortField: "creationTime" }, x => x.CreationTime, OrderByType.Asc)
-            .OrderByIF(dto is { SortRule: 1, SortField: "creationTime" } || dto.SortRule is null, x => x.CreationTime, OrderByType.Desc)
-            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
-        return new PagedDto<OrderScreenListDto>(total, _mapper.Map<IReadOnlyList<OrderScreenListDto>>(items));
+	    dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+	    var dtos = dataDtos
+		    .Select(stu => _mapper.Map(stu, typeof(OrderScreenListDto), dynamicClass))
+		    .Cast<object>()
+		    .ToList();
+
+	    var stream = ExcelHelper.CreateStream(dtos);
+
+	    return ExcelStreamResult(stream, "工单甄别列表数据");
     }
 
 
-    /// <summary>
-    /// 开始工单甄别流程
-    /// </summary>
-    [Permission(EPermission.ApplyScreen)]
+	/// <summary>
+	/// 开始工单甄别流程
+	/// </summary>
+	[Permission(EPermission.ApplyScreen)]
     [HttpPost("screen/startflow")]
     [LogFilter("开始工单甄别流程")]
     public async Task StartFlow([FromBody] StartWorkflowDto<OrderScreenDto> dto)
@@ -3115,7 +3087,7 @@ public class OrderController : BaseController
                 cancellationToken: HttpContext.RequestAborted);
 
             List<OrderRemarksDto> remarks = workflow.Steps.Where(x => !string.IsNullOrEmpty(x.Remark)).Select(x => new OrderRemarksDto
-                { Remark = x.Remark, RemarkTime = x.CreationTime, RemarkUser = x.CreatorName }).ToList();
+            { Remark = x.Remark, RemarkTime = x.CreationTime, RemarkUser = x.CreatorName }).ToList();
             dto.OrderRemarks = remarks;
             if (order.Status == EOrderStatus.SendBack || order.Status == EOrderStatus.SendBackAudit || order.Status == EOrderStatus.BackToUnAccept)
             {
@@ -3170,7 +3142,7 @@ public class OrderController : BaseController
             "不同意" : orderTerminateList.Any(x => x.Status == ETerminateStatus.Approval || x.Status == ETerminateStatus.SendBack) ? "审批中" : null;
 
 
-		return _sessionContext.OrgIsCenter ? dto : dto.DataMask();
+        return _sessionContext.OrgIsCenter ? dto : dto.DataMask();
     }
 
     /// <summary>
@@ -3518,6 +3490,10 @@ public class OrderController : BaseController
         }
 
         _mapper.Map(expiredTimeConfig, order);
+        
+        if (dto.Workflow.BusinessType is EBusinessType.Department or EBusinessType.DepartmentLeader)
+            order.ProcessType = EProcessType.Jiaoban;
+        
         await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
 
         try
@@ -3539,81 +3515,8 @@ public class OrderController : BaseController
             startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderHandle;
             startDto.Title = order.Title;
             var startStep = await _workflowDomainService.StartAsync(startDto, order.Id, order.ExpiredTime, HttpContext.RequestAborted);
-            
-            switch (dto.Data.OrderAssignMode)
-            {
-                case EOrderAssignMode.AdjoinLevel:
-                    var nextDto = _mapper.Map<NextWorkflowDto>(dto.Workflow);
-                    // 平均派单
-                    var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-                    if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
-                    {
-                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                        nextDto.NextHandlers = new List<FlowStepHandler> { handler };
-                    }
-                    await _workflowDomainService.NextAsync(nextDto,order.ExpiredTime,HttpContext.RequestAborted);
-                    break;
-                case EOrderAssignMode.CrossLevel:
-                    if (!dto.Data.SecondaryHandlers.Any())
-                        throw new UserFriendlyException("跨级指派参数异常");
-                    var orgIds = dto.Data.SecondaryHandlers
-                        .Where(d=>!string.IsNullOrEmpty(d.OrgId))
-                        .SelectMany(d => d.OrgId!.GetHigherOrgIds(true))
-                        .ToList();
-                    orgIds.Add(dto.Workflow.NextHandlers.First().OrgId);
-                    var orgs = await _organizeRepository.Queryable()
-                        .Where(d=> orgIds.Contains(d.Id))
-                        .ToListAsync(HttpContext.RequestAborted);
-                    var maxLevel = orgs.MaxBy(d => d.Level).Level;
-
-                    var unhandleSteps = new List<WorkflowStep>
-                    {
-                        startStep
-                    };
-                    
-                    for (int i = 1; i < maxLevel; i++)
-                    {
-                        var tempSteps = new List<WorkflowStep>();
-                        foreach (var unhandleStep in unhandleSteps)
-                        {
-                            var handleOrgs = orgs.Where(d => d.Level == i).ToList();
-                            if (i != 1)
-                                handleOrgs = handleOrgs.Where(d => d.Id.StartsWith(unhandleStep.HandlerOrgId)).ToList();
-                            var isStartCountersign = handleOrgs.Count > 1;
-                            var handlers = handleOrgs.Select(d => new FlowStepHandler
-                            {
-                                OrgId = d.Id,
-                                OrgName = d.Name,
-                                Key = d.Id,
-                                Value = d.Name
-                            }).ToList();
-                            var nextflowDto = new NextWorkflowDto
-                            {
-                                WorkflowId = unhandleStep.WorkflowId,
-                                StepId = unhandleStep.Id,
-                                NextStepCode = unhandleStep.Code,
-                                NextStepName = unhandleStep.Name,
-                                FlowDirection = EFlowDirection.CenterToOrg,
-                                HandlerType = unhandleStep.HandlerType,
-                                StepType = unhandleStep.StepType,
-                                NextHandlers = handlers,
-                                IsStartCountersign = isStartCountersign,
-                                BusinessType = unhandleStep.BusinessType,
-                                Opinion = "跨级派单,自动办理"
-                            };
-
-                            var nextSteps = await _workflowDomainService.NextAsync(nextflowDto, order.ExpiredTime, HttpContext.RequestAborted);
-                            tempSteps.AddRange(nextSteps);
-                        }
-                        unhandleSteps = tempSteps;
-                    }
-                    
-                    break;
-                case EOrderAssignMode.MainAndSecondary:
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException();
-            }
+
+            await HandleOrderAsync(order, startStep, dto.Data, dto.Workflow, HttpContext.RequestAborted);
         }
         catch (Exception e)
         {
@@ -3621,8 +3524,12 @@ public class OrderController : BaseController
             throw new UserFriendlyException($"工单开启流程失败!, {e.Message}, {e.StackTrace}", "工单开启流程失败");
         }
 
-        //开启流程处理事件,处理市州互转
-        await _publisher.PublishAsync(new OrderStartWorkflowNotify(order.Id), PublishStrategy.ParallelWhenAll, HttpContext.RequestAborted);
+        if (_appOptions.Value.IsYiBin && dto.Data.Transpond.HasValue && dto.Data.Transpond.Value)
+        {
+            //开启流程处理事件,处理市州互转
+            await _publisher.PublishAsync(new OrderStartWorkflowNotify(order.Id), PublishStrategy.ParallelWhenAll,
+                HttpContext.RequestAborted);
+        }
     }
 
     /// <summary>
@@ -3649,11 +3556,11 @@ public class OrderController : BaseController
     {
         var order = await _orderApplication.SaveOrderWorkflowInfo(dto, HttpContext.RequestAborted);
 
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowDto.WorkflowId, withSteps: true, withTraces: true,
+            cancellationToken: HttpContext.RequestAborted);
         //1.是否是判断节点  2.是否存在历史派单节点  3.存在获取上个派单节点  4.不存在走平均派单
-        if (dto.WorkflowDto.BusinessType == EBusinessType.Send)
+        if (dto.Data.OrderAssignMode is EOrderAssignMode.AdjoinLevel && dto.WorkflowDto.BusinessType == EBusinessType.Send)
         {
-            var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowDto.WorkflowId, withSteps:true, withTraces: true,
-                cancellationToken: HttpContext.RequestAborted);
             var sendOrderTraces = workflow.Traces.Where(x => x.BusinessType == EBusinessType.Send);
             if (sendOrderTraces.Any())
             {
@@ -3671,94 +3578,121 @@ public class OrderController : BaseController
             }
             else
             {
-                // // 平均派单
-                // var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-                // if (averageSendOrder)
-                // {
-                //     var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                //     dto.WorkflowDto.NextHandlers = new List<FlowStepHandler> { handler };
-                // }
-
-                var startStep = workflow.Steps.First(d => d.Id == dto.WorkflowDto.StepId);
-                
-                switch (dto.Data.OrderAssignMode)
-            {
-                case EOrderAssignMode.AdjoinLevel:
-                    var nextDto = _mapper.Map<NextWorkflowDto>(dto.WorkflowDto);
-                    // 平均派单
-                    var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-                    if (dto.WorkflowDto.BusinessType == EBusinessType.Send && averageSendOrder)
-                    {
-                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                        nextDto.NextHandlers = new List<FlowStepHandler> { handler };
-                    }
-                    await _workflowDomainService.NextAsync(nextDto,order.ExpiredTime,HttpContext.RequestAborted);
-                    break;
-                case EOrderAssignMode.CrossLevel:
-                    if (!dto.Data.SecondaryHandlers.Any())
-                        throw new UserFriendlyException("跨级指派参数异常");
-                    var orgIds = dto.Data.SecondaryHandlers
-                        .Where(d=>!string.IsNullOrEmpty(d.OrgId))
-                        .SelectMany(d => d.OrgId!.GetHigherOrgIds(true))
-                        .ToList();
-                    orgIds.Add(dto.WorkflowDto.NextHandlers.First().OrgId);
-                    var orgs = await _organizeRepository.Queryable()
-                        .Where(d=> orgIds.Contains(d.Id))
-                        .ToListAsync(HttpContext.RequestAborted);
-                    var maxLevel = orgs.MaxBy(d => d.Level).Level;
-
-                    var unhandleSteps = new List<WorkflowStep>
-                    {
-                        startStep
-                    };
-                    
-                    for (int i = 1; i < maxLevel; i++)
+                // 平均派单
+                var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+                if (averageSendOrder)
+                {
+                    var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                    dto.WorkflowDto.NextHandlers = new List<FlowStepHandler> { handler };
+                }
+            }
+        }
+
+        //await _workflowApplication.NextAsync(dto.WorkflowDto, order.ExpiredTime, HttpContext.RequestAborted);
+        var startStep = workflow.Steps.First(d => d.Id == dto.WorkflowDto.StepId);
+        await HandleOrderAsync(order, startStep, dto.Data, dto.WorkflowDto, HttpContext.RequestAborted);
+
+        if (_appOptions.Value.IsZiGong && dto.Data.Transpond.HasValue && dto.Data.Transpond.Value)
+        {
+            var count = await _transpondCityRawDataRepository.Queryable()
+                .CountAsync(m => m.OrderCode == order.No && m.CityName == order.TranspondCityName
+                                                         && m.Direction == ETranspondDirection.Out, HttpContext.RequestAborted);
+            //处理市州互转
+            if (count == 0)
+                await _publisher.PublishAsync(new OrderStartWorkflowNotify(order.Id), PublishStrategy.ParallelWhenAll, HttpContext.RequestAborted);
+        }
+    }
+
+    private async Task HandleOrderAsync(Order order, WorkflowStep startStep, OrderHandleFlowDto orderHandleFlowDto, BasicWorkflowDto workflowDto, CancellationToken cancellationToken)
+    {
+        switch (orderHandleFlowDto.OrderAssignMode)
+        {
+            case EOrderAssignMode.AdjoinLevel:
+                var nextDto = _mapper.Map<NextWorkflowDto>(workflowDto);
+                nextDto.WorkflowId = startStep.WorkflowId;
+                nextDto.StepId = startStep.Id;
+
+                // 平均派单
+                var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder)
+                    .SettingValue[0]);
+                if (workflowDto.BusinessType == EBusinessType.Send && averageSendOrder)
+                {
+                    var handler = await _orderDomainService.AverageOrder(cancellationToken);
+                    nextDto.NextHandlers = new List<FlowStepHandler> { handler };
+                }
+
+                await _workflowDomainService.NextAsync(nextDto, order.ExpiredTime, cancellationToken);
+                break;
+            case EOrderAssignMode.CrossLevel:
+                if (!orderHandleFlowDto.CrossOrgIds.Any())
+                    throw new UserFriendlyException("跨级指派参数异常");
+
+                var orgIds = orderHandleFlowDto.CrossOrgIds;
+                orgIds.Add(workflowDto.NextHandlers.First().OrgId);
+                var orgs = await _organizeRepository.Queryable()
+                    .Where(d => orgIds.Contains(d.Id))
+                    .ToListAsync(HttpContext.RequestAborted);
+                var maxLevel = orgs.MaxBy(d => d.Level).Level;
+
+                //办理开始节点或派单节点至一级部门
+                var levelOneOrgs = orgs.Where(d => d.Level == 1).ToList();
+                var unhandleSteps = await HandleWorkflowStepAsync(startStep, levelOneOrgs, order.ExpiredTime, cancellationToken);
+
+                //依次办理路过节点
+                for (int i = 1; i < maxLevel; i++)
+                {
+                    var tempSteps = new List<WorkflowStep>();
+                    foreach (var unhandleStep in unhandleSteps)
                     {
-                        var tempSteps = new List<WorkflowStep>();
-                        foreach (var unhandleStep in unhandleSteps)
-                        {
-                            var handleOrgs = orgs.Where(d => d.Level == i).ToList();
-                            if (i != 1)
-                                handleOrgs = handleOrgs.Where(d => d.Id.StartsWith(unhandleStep.HandlerOrgId)).ToList();
-                            var isStartCountersign = handleOrgs.Count > 1;
-                            var handlers = handleOrgs.Select(d => new FlowStepHandler
-                            {
-                                OrgId = d.Id,
-                                OrgName = d.Name,
-                                Key = d.Id,
-                                Value = d.Name
-                            }).ToList();
-                            var nextflowDto = new NextWorkflowDto
-                            {
-                                WorkflowId = unhandleStep.WorkflowId,
-                                StepId = unhandleStep.Id,
-                                NextStepCode = unhandleStep.Code,
-                                NextStepName = unhandleStep.Name,
-                                FlowDirection = EFlowDirection.CenterToOrg,
-                                HandlerType = unhandleStep.HandlerType,
-                                StepType = unhandleStep.StepType,
-                                NextHandlers = handlers,
-                                IsStartCountersign = isStartCountersign,
-                                BusinessType = unhandleStep.BusinessType,
-                                Opinion = "跨级派单,自动办理"
-                            };
-
-                            var nextSteps = await _workflowDomainService.NextAsync(nextflowDto, order.ExpiredTime, HttpContext.RequestAborted);
-                            tempSteps.AddRange(nextSteps);
-                        }
-                        unhandleSteps = tempSteps;
+                        var hasLowerLevel = orgs.Any(d => d.Id.StartsWith(unhandleStep.HandlerOrgId));
+                        if (!hasLowerLevel) continue;
+
+                        var currentLevel = unhandleStep.HandlerOrgId.CalcOrgLevel();
+                        var lowerLevel = currentLevel++;
+                        var handleOrgs = orgs.Where(d => d.Level == lowerLevel).ToList();
+                        if (i != 1)
+                            handleOrgs = handleOrgs.Where(d => d.Id.StartsWith(unhandleStep.HandlerOrgId)).ToList();
+                        var nextSteps = await HandleWorkflowStepAsync(unhandleStep, handleOrgs, order.ExpiredTime, cancellationToken);
+                        tempSteps.AddRange(nextSteps);
                     }
-                    
-                    break;
-                case EOrderAssignMode.MainAndSecondary:
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException();
-            }
-            }
+
+                    unhandleSteps = tempSteps;
+                }
+
+                break;
+            case EOrderAssignMode.MainAndSecondary:
+                break;
+            default:
+                throw new ArgumentOutOfRangeException();
         }
+    }
+
+    private async Task<List<WorkflowStep>> HandleWorkflowStepAsync(WorkflowStep unhandleStep, List<SystemOrganize> handleOrgs, DateTime? expiredTime, CancellationToken cancellationToken)
+    {
+        var isStartCountersign = handleOrgs.Count > 1;
+        var handlers = handleOrgs.Select(d => new FlowStepHandler
+        {
+            OrgId = d.Id,
+            OrgName = d.Name,
+            Key = d.Id,
+            Value = d.Name
+        }).ToList();
+        var nextflowDto = new NextWorkflowDto
+        {
+            WorkflowId = unhandleStep.WorkflowId,
+            StepId = unhandleStep.Id,
+            NextStepCode = unhandleStep.Code,
+            NextStepName = unhandleStep.Name,
+            FlowDirection = EFlowDirection.CenterToOrg,
+            HandlerType = unhandleStep.HandlerType,
+            StepType = unhandleStep.StepType,
+            NextHandlers = handlers,
+            IsStartCountersign = isStartCountersign,
+            BusinessType = unhandleStep.BusinessType,
+            Opinion = "跨级派单,自动办理"
+        };
 
-        await _workflowApplication.NextAsync(dto.WorkflowDto, order.ExpiredTime, HttpContext.RequestAborted);
+        return await _workflowDomainService.NextAsync(nextflowDto, expiredTime, cancellationToken);
     }
 
     /// <summary>
@@ -3888,8 +3822,9 @@ public class OrderController : BaseController
             PushTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.PushType),
             OrderStatusOptions = EnumExts.GetDescriptions<EOrderStatus>(),
             CurrentStepOptions = definition?.Steps.Select(x => new Kv(x.Code, x.Name)),
-            IdentityTypeOptions = EnumExts.GetDescriptions<EIdentityType>()
-        };
+            IdentityTypeOptions = EnumExts.GetDescriptions<EIdentityType>(),
+            OrderTags = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.OrderTag)
+		};
         return rsp;
     }
 
@@ -4898,10 +4833,13 @@ public class OrderController : BaseController
             //	ETimeType.WorkDay,
             //	dto.TimeLimit.Value, order.AcceptTypeCode);
             await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
-                {
-                    ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime,
-                    NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne, ProcessType = processType, Status = EOrderStatus.Special
-                })
+            {
+                ExpiredTime = expiredTime.ExpiredTime,
+                NearlyExpiredTime = expiredTime.NearlyExpiredTime,
+                NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne,
+                ProcessType = processType,
+                Status = EOrderStatus.Special
+            })
                 .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
             var orderDto = _mapper.Map<OrderDto>(order);
             await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto,
@@ -5057,10 +4995,11 @@ public class OrderController : BaseController
 
                 endTime = expiredTime.EndTime;
                 await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
-                    {
-                        ExpiredTime = expiredTime.EndTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime,
-                        NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne
-                    })
+                {
+                    ExpiredTime = expiredTime.EndTime,
+                    NearlyExpiredTime = expiredTime.NearlyExpiredTime,
+                    NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne
+                })
                     .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                 var orderDto = _mapper.Map<OrderDto>(order);
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto,
@@ -5165,10 +5104,13 @@ public class OrderController : BaseController
                 ? EProcessType.Zhiban
                 : EProcessType.Jiaoban;
             await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
-                {
-                    ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime,
-                    NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne, ProcessType = processType, Status = EOrderStatus.Special
-                })
+            {
+                ExpiredTime = expiredTime.ExpiredTime,
+                NearlyExpiredTime = expiredTime.NearlyExpiredTime,
+                NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne,
+                ProcessType = processType,
+                Status = EOrderStatus.Special
+            })
                 .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
             var orderDto = _mapper.Map<OrderDto>(order);
             await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto,
@@ -5285,10 +5227,13 @@ public class OrderController : BaseController
                     ? EProcessType.Zhiban
                     : EProcessType.Jiaoban;
                 await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
-                    {
-                        ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime,
-                        NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne, ProcessType = processType, Status = EOrderStatus.Special
-                    })
+                {
+                    ExpiredTime = expiredTime.ExpiredTime,
+                    NearlyExpiredTime = expiredTime.NearlyExpiredTime,
+                    NearlyExpiredTimeOne = expiredTime.NearlyExpiredTimeOne,
+                    ProcessType = processType,
+                    Status = EOrderStatus.Special
+                })
                     .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                 var orderDto = _mapper.Map<OrderDto>(order);
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto,
@@ -5441,7 +5386,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!))
@@ -5532,8 +5477,8 @@ public class OrderController : BaseController
             SpecialReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.SpecialReason),
             InstaShotSpecialReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.InstaShotSpecialReason),
             Step = step,
-			IsTerminate = await _orderTerminateRepository.Queryable().Where(d=>d.OrderId == order.Id && d.Status == ETerminateStatus.End).AnyAsync(),
-			BaseTypeId = baseTypeId
+            IsTerminate = await _orderTerminateRepository.Queryable().Where(d => d.OrderId == order.Id && d.Status == ETerminateStatus.End).AnyAsync(),
+            BaseTypeId = baseTypeId
         };
         return rsp;
     }
@@ -5562,7 +5507,7 @@ public class OrderController : BaseController
                 : _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.SpecialReason),
             ReTransactErrorType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.ReTransactErrorType),
             IsTerminate = await _orderTerminateRepository.Queryable().Where(d => d.OrderId == order.Id && d.Status == ETerminateStatus.End).AnyAsync(),
-			Step = step,
+            Step = step,
             Orgs = orgs,
         };
         return rsp;
@@ -6568,7 +6513,11 @@ public class OrderController : BaseController
 
             return new
             {
-                Count = count, ErrorCount = errorCount, AddCount = addCount, ModifyCount = modifyCount, ErrorMessage = errorMessage.ToString()
+                Count = count,
+                ErrorCount = errorCount,
+                AddCount = addCount,
+                ModifyCount = modifyCount,
+                ErrorMessage = errorMessage.ToString()
             };
         }
     }
@@ -7169,7 +7118,7 @@ public class OrderController : BaseController
         {
             await _orderRepository.Updateable()
                 .SetColumns(o => new Orders.Order()
-                    { SignerId = dto.Handler.UserId, SignerName = dto.Handler.Username, Status = EOrderStatus.HandOverToUnAccept })
+                { SignerId = dto.Handler.UserId, SignerName = dto.Handler.Username, Status = EOrderStatus.HandOverToUnAccept })
                 .Where(o => o.Id == dto.OrderId).ExecuteCommandAsync(HttpContext.RequestAborted);
         }
         else

+ 8 - 0
src/Hotline.Api/Controllers/OrgController.cs

@@ -198,6 +198,14 @@ namespace Hotline.Api.Controllers
             }
         }
 
+        [HttpGet("cascade")]
+        public async Task<IReadOnlyList<SystemOrganize>> GetOrgsCascade([FromQuery]string? id)
+        {
+            return await _systemOrganizeRepository.Queryable()
+                .OrderBy(d => d.Id)
+                .ToTreeAsync(it => it.Children, it => it.ParentId, id);
+        }
+
 
         /// <summary>
         /// 新增页面基础数据

+ 66 - 2
src/Hotline.Api/Controllers/PushMessageController.cs

@@ -1,4 +1,5 @@
-using DotNetCore.CAP;
+using Amazon.Runtime.Internal.Transform;
+using DotNetCore.CAP;
 using Hotline.Permissions;
 using Hotline.Push;
 using Hotline.Push.FWMessage;
@@ -8,10 +9,15 @@ using Hotline.Share.Dtos.Push;
 using Hotline.Share.Dtos.Push.FWMessage;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Mq;
+using Hotline.Share.Tools;
+using Hotline.Users;
+using J2N.Text;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.IdentityModel.Tokens;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using System.Text;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
@@ -29,6 +35,7 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<BatchSmsTask> _batchSmsTaskRepository;
         private readonly IRepository<BatchSmsTaskDetail> _batchSmsTaskDetailRepository;
         private readonly ICapPublisher _capPublisher;
+        private readonly IRepository<User> _userRepository;
 
         /// <summary>
         /// 
@@ -43,7 +50,8 @@ namespace Hotline.Api.Controllers
             IMessageCodeDomainService messageCodeDomainService,
             IRepository<BatchSmsTask> batchSmsTaskRepository,
             IRepository<BatchSmsTaskDetail> batchSmsTaskDetailRepository,
-            ICapPublisher capPublisher)
+            ICapPublisher capPublisher,
+            IRepository<User> userRepository)
         {
             _messageRepository = messageRepository;
             _mapper = mapper;
@@ -52,6 +60,7 @@ namespace Hotline.Api.Controllers
             _batchSmsTaskRepository = batchSmsTaskRepository;
             _batchSmsTaskDetailRepository = batchSmsTaskDetailRepository;
             _capPublisher = capPublisher;
+            _userRepository = userRepository;
         }
 
         #endregion
@@ -114,6 +123,61 @@ namespace Hotline.Api.Controllers
             await _pushDomainService.PushMsgAsync(dto, HttpContext.RequestAborted);
         }
 
+        /// <summary>
+        /// 发送短信
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("send")]
+        public async Task<string> SendMessage([FromBody] MessageInDto dto)
+        {
+            var telNumbers = dto.TelNumbers.Split(",");
+            var count = 0;
+            var errorSB = new StringBuilder("失败原因: ");
+            var errorCount = 0;
+            for (int i = 0;i < telNumbers.Length;i++)
+            {
+                var telNumber = telNumbers[i];
+                if (telNumber.IsPhoneNumber() == false)
+                {
+                    errorCount++;
+                    errorSB.Append($" {telNumber}不是合法的手机号码.");
+                    continue;
+                }
+
+                var inDto = new MessageDto
+                {
+                    TelNumber = telNumber,
+                    Content = dto.Content,
+                    PushBusiness = EPushBusiness.ManualSms,
+                    Name = "",
+                };
+                try
+                {
+                    await _pushDomainService.PushMsgAsync(inDto, HttpContext.RequestAborted);
+                }
+                catch (Exception e)
+                {
+                    errorSB.Append(e.Message);
+                    errorCount++;
+                    continue;
+                }
+                count++;
+            }
+            return $"成功发送: {count}, 失败: {errorCount}. " + (errorCount != 0 ? "" : errorSB.ToString());
+        }
+
+        /// <summary>
+        /// 手动发送短信页面基础数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("send/basedata")]
+        public async Task<dynamic> SendMessageBaseDataAsync()
+        {
+            var users = await _userRepository.Queryable()
+                .Select(m => new {m.Id,  m.PhoneNo, m.Name }).ToListAsync();
+            return new Dictionary<string, dynamic>() { { "contacts", users } };
+        }
 
         #endregion
 

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

@@ -91,13 +91,13 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
   },
   "Cache": {
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo123!$!$",
-    "Database": 3 //release:3, dev:5
+    "Database": 5 //release:3, dev:5
   },
   "Swagger": true,
   "Cors": {

+ 2 - 2
src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs

@@ -78,7 +78,8 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
                         withExtension: true, cancellationToken: cancellationToken);
                     //order.UpdateHandlingStatus(workflow.IsInCountersign);
                     _mapper.Map(workflow, order);
-                    if (notification.TargetStep.StepType is EStepType.Start)
+                    order.FileEmpty();
+					if (notification.TargetStep.StepType is EStepType.Start)
                     {
                         //if (!bool.TryParse(
                         //        _systemSettingCacheManager.GetSetting(SettingConstants.IsRecallToSeatDesignated)?.SettingValue[0],
@@ -86,7 +87,6 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
                         //    isRecallToSeatDesignated = false;
                         //var isRecallToSeatDesignated = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsRecallToSeatDesignated).SettingValue[0]);
                         order.Status = EOrderStatus.SpecialToUnAccept;
-                        order.FileEmpty();
                         //if (isRecallToSeatDesignated)
                         //{
                         //    if (data.HandlerType is EHandlerType.Role or EHandlerType.AssignedUser)

+ 7 - 0
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -313,5 +313,12 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         ISugarQueryable<OrderTerminate> OrderTerminateList(OrderTerminateListDto dto);
 
+        /// <summary>
+        /// 甄别列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<OrderScreen> OrderScreenList(ScreenListDto dto);
+
 	}
 }

+ 82 - 22
src/Hotline.Application/Orders/OrderApplication.cs

@@ -80,8 +80,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepository<OrderVisitDetail> _orderVisitedDetailRepository;
     private readonly IOrderDomainService _orderDomainService;
     private readonly IWorkflowDomainService _workflowDomainService;
-
-    private readonly IOrderRepository _orderRepository;
+    private readonly ISessionContext _sessionContext;
+	private readonly IOrderRepository _orderRepository;
 
     //private readonly ITimeLimitDomainService _timeLimitDomainService;
     private readonly IMapper _mapper;
@@ -102,7 +102,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
     private readonly IRepository<SystemDicData> _systemDicDataRepository;
     private readonly IRepository<OrderPublish> _orderPublishRepository;
-    private readonly IRepository<OrderScreen> _orderScreenRepository;
+    private readonly IOrderScreenRepository _orderScreenRepository;
     private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
     private readonly ICalcExpireTime _expireTime;
     private readonly IRepository<OrderObserve> _orderObserveRepository;
@@ -132,7 +132,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<SystemDicData> systemDicDataRepository,
         IRepository<WorkflowTrace> workflowTraceRepository,
         IRepository<OrderPublish> orderPublishRepository,
-        IRepository<OrderScreen> orderScreenRepository,
+        IOrderScreenRepository orderScreenRepository,
         IRepository<OrderSendBackAudit> orderSendBackAuditRepository,
         ICalcExpireTime expireTime,
         IMediator mediator,
@@ -179,6 +179,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _transpondCityRawDataRepository = transpondCityRawDataRepository;
         _orderObserveRepository = orderObserveRepository;
         _orderTerminateRepository = orderTerminateRepository;
+        _sessionContext = sessionContext;
 	}
 
     /// <summary>
@@ -789,16 +790,6 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _mapper.Map(dto.Data, order);
         await _orderRepository.UpdateAsync(order, cancellationToken);
 
-        if (_appOptions.Value.IsZiGong && dto.Data.Transpond.HasValue && dto.Data.Transpond.Value)
-        {
-            var count = await _transpondCityRawDataRepository.Queryable()
-                .CountAsync(m => m.OrderCode == order.No && m.CityName == order.TranspondCityName
-                                                    && m.Direction == ETranspondDirection.Out, cancellationToken);
-            //处理市州互转
-            if (count == 0)
-                await _publisher.PublishAsync(new OrderStartWorkflowNotify(order.Id), PublishStrategy.ParallelWhenAll, cancellationToken);
-        }
-
         return order;
     }
 
@@ -1045,6 +1036,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 d => d.Title.Contains(dto.ContentRetrieval) || d.Content.Contains(dto.ContentRetrieval))
             .WhereIF(dto.FiledType is FiledType.CenterFiled, d => d.ProcessType == EProcessType.Zhiban)
             .WhereIF(dto.FiledType is FiledType.OrgFiled, d => d.ProcessType == EProcessType.Jiaoban)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrderTagCode),d=>d.OrderTagCode == dto.OrderTagCode)
             .OrderByDescending(d => d.CreationTime);
     }
 
@@ -2265,15 +2257,83 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return query;
     }
 
+    #region 甄别
+    public ISugarQueryable<OrderScreen> OrderScreenList(ScreenListDto dto) {
+	    var handler = dto.TabStatus is EScreenStatus.Apply;
+	    var isAdmin = _orderDomainService.IsCheckAdmin();
+		ISugarQueryable<OrderScreen> query;
+
+		if (dto.source == 1)
+		{
+			query = _orderScreenRepository.Queryable(hasHandled: !handler, isAdmin: isAdmin);
+		}
+		else
+		{
+			query = _orderScreenRepository.Queryable(isAdmin: isAdmin)
+				.WhereIF(!isAdmin, x => x.CreatorOrgId.StartsWith(_sessionContext.RequiredOrgId));
+		}
+
+		query = query
+			.Includes(d => d.Order)
+			.Includes(d => d.VisitDetail)
+			.Includes(d => d.Visit, v => v.Order)
+			.Includes(d => d.Workflow)
+			.Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.UserId).OrderByDescending(sd => sd.AuditTime).Take(1)
+				.ToList())
+			.WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!))
+			.WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Visit.Order.No.Contains(dto.No!));
+		if (dto.TabStatus is EScreenStatus.Apply)
+		{
+			query.Where(d =>
+				(d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval ||
+				 (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
+		}
+
+		if (dto.TabStatus.HasValue && dto.Status == EScreenStatus.MyHandle)
+		{
+			query.Where(d => (d.Status != EScreenStatus.Apply));
+		}
+        return query
+            .WhereIF(dto.DataScope is 1, x => x.CreatorId == _sessionContext.RequiredUserId)
+            .WhereIF(dto.Status.HasValue, x => x.Status == dto.Status)
+            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.Order!.AcceptTypeCode! == dto.AcceptType!)
+            .WhereIF(!string.IsNullOrEmpty(dto.HotspotSpliceName), x => x.Order!.Hotspot.HotSpotFullName!.StartsWith(dto.HotspotSpliceName!))
+            .WhereIF(!string.IsNullOrEmpty(dto.SourceChannel), x => x.Order!.SourceChannelCode! == dto.SourceChannel!)
+            .WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitDetail.VisitOrgName!.Contains(dto.VisitOrgName!))
+            .WhereIF(!string.IsNullOrEmpty(dto.CreatorOrgName), d => d.CreatorOrgName == dto.CreatorOrgName)
+            .WhereIF(!string.IsNullOrEmpty(dto.CreatorName), d => d.CreatorName == dto.CreatorName)
+            .WhereIF(dto.IsProvince.HasValue, x => x.Order!.IsProvince == dto.IsProvince)
+            .WhereIF(dto.IsSendBackApplyNum is true, x => x.SendBackApplyNum > 0)
+            .WhereIF(dto.IsSendBackApplyNum is false, x => x.SendBackApplyNum == 0)
+            .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
+            .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrderId), d => d.OrderId == dto.OrderId)
+            //甄别列表
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
+            .WhereIF(!string.IsNullOrEmpty(dto.CurrentHandleOrgName), x => x.Order!.CurrentHandleOrgName!.Contains(dto.CurrentHandleOrgName!))
+            .WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue,
+                x => x.Order!.CreationTime >= dto.CreationTime && x.Order!.CreationTime <= dto.EndCreationTime)
+            .WhereIF(dto.CurrentHandleTime.HasValue && dto.EndCurrentHandleTime.HasValue,
+                x => x.Order!.CurrentHandleTime >= dto.CurrentHandleTime && x.Order!.ActualHandleTime <= dto.EndCurrentHandleTime)
+            .WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue,
+                x => x.Order!.FiledTime >= dto.FiledTime && x.Order!.FiledTime <= dto.EndFiledTime)
+            .WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue,
+                x => x.Visit.VisitTime >= dto.VisitTime && x.Visit.VisitTime <= dto.EndVisitTime)
+            .WhereIF(!string.IsNullOrEmpty(dto.Contact), x => x.Order!.Contact! == dto.Contact!)
+            .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), x => x.Order!.FromPhone! == dto.FromPhone!)
+            .OrderByIF(dto is { SortRule: 0, SortField: "creationTime" }, x => x.CreationTime, OrderByType.Asc)
+            .OrderByIF(dto is { SortRule: 1, SortField: "creationTime" } || dto.SortRule is null, x => x.CreationTime, OrderByType.Desc);
+	}
+	#endregion
 
-    #region private
-    /// <summary>
-    /// 接受外部工单(除省平台)
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <param name="cancellationToken"></param>
-    /// <returns></returns>
-    private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files, CancellationToken cancellationToken)
+	#region private
+	/// <summary>
+	/// 接受外部工单(除省平台)
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <param name="cancellationToken"></param>
+	/// <returns></returns>
+	private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files, CancellationToken cancellationToken)
     {
         if (string.IsNullOrEmpty(dto.ExternalId))
             throw new UserFriendlyException("工单外部编号不能为空");

+ 28 - 8
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -79,12 +79,19 @@ namespace Hotline.Share.Dtos.Order
 
         public string? TagNames { get; set; }
 
-        #region 流程信息
-
         /// <summary>
-        /// 工单开始时间(受理/接办时间=流程开启时间)
+        /// 工单标签(自贡)
         /// </summary>
-        public DateTime? StartTime { get; set; }
+        public string? OrderTag { get; set; }
+
+        public string? OrderTagCode { get; set; }
+
+		#region 流程信息
+
+		/// <summary>
+		/// 工单开始时间(受理/接办时间=流程开启时间)
+		/// </summary>
+		public DateTime? StartTime { get; set; }
 
         /// <summary>
         /// 交办时间(中心交部门办理时间)
@@ -119,10 +126,23 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public double AllDuration { get; set; }
 
-        /// <summary>
-        /// 办结时长(秒) 归档时间-受理时间(工单创建时间)
-        /// </summary>
-        public double? CreationTimeHandleDuration { get; set; }
+
+        public string AllDurationHour => GetAllDurationHour();
+
+
+		public string GetAllDurationHour() {
+
+			if (Status >= EOrderStatus.Filed)
+			{
+                return  Math.Round(AllDuration / 60, 2).ToString() + "小时";
+			}
+            return "-";
+        }
+
+		/// <summary>
+		/// 办结时长(秒) 归档时间-受理时间(工单创建时间)
+		/// </summary>
+		public double? CreationTimeHandleDuration { get; set; }
 
         /// <summary>
         /// 办结工作日时长(秒)归档时间-受理时间(工单创建时间)

+ 2 - 2
src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs

@@ -17,9 +17,9 @@ namespace Hotline.Share.Dtos.Order
         public EOrderAssignMode OrderAssignMode { get; set; }
 
         /// <summary>
-        /// 跨级转派得下级办理对象/主协办得协办对象
+        /// 跨级转派得下级办理部门/主协办得协办部门
         /// </summary>
-        public List<FlowStepHandler> SecondaryHandlers { get; set; }
+        public List<string> CrossOrgIds { get; set; }
 
         /// <summary>
         /// 抄送对象

+ 8 - 2
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -19,7 +19,7 @@ namespace Hotline.Share.Dtos.Order
     }
     public record QueryOrderVisitDto : PagedKeywordRequest
     {
-        public EVisitStateQuery VisitState { get; set; }
+        public EVisitStateQuery VisitStateQuery { get; set; }
 
         /// <summary>
         /// 归档方式
@@ -90,7 +90,13 @@ namespace Hotline.Share.Dtos.Order
         /// 部门办件态度
         /// </summary>
         public List<string> OrgHandledAttitude { get; set; } = new();
-    }
+
+        /// <summary>
+        /// 来源渠道
+        /// </summary>
+        public string Channel { get; set; }
+
+	}
 
     public record QueryOrderPublishStatisticsAllDto : PagedRequest
     {

+ 6 - 1
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -184,7 +184,12 @@ namespace Hotline.Share.Dtos.Order
         /// 省交办编号
         /// </summary>
         public string? ReceiveProvinceNo { get; set; }
-    }
+
+        /// <summary>
+        /// 工单标签Code
+        /// </summary>
+        public string? OrderTagCode { get; set; }
+	}
 
 
 	public enum FiledType

+ 71 - 59
src/Hotline.Share/Dtos/Push/MessageDto.cs

@@ -1,63 +1,75 @@
 using Hotline.Share.Enums.Push;
 
-namespace Hotline.Share.Dtos.Push
+namespace Hotline.Share.Dtos.Push;
+
+public class MessageDto
+{
+    /// <summary>
+    /// 消息推送业务
+    /// </summary>
+    public EPushBusiness PushBusiness { get; set; }
+
+    /// <summary>
+    /// 接收姓名
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 接收手机号码
+    /// </summary>
+    public string TelNumber { get; set; }
+
+    /// <summary>
+    /// 外部业务唯一编号
+    /// </summary>
+    public string? ExternalId { get; set; }
+
+    /// <summary>
+    /// 关联工单编号
+    /// </summary>
+    public string? OrderId { get; set; }
+
+    /// <summary>
+    /// 推送平台
+    /// </summary>
+    public EPushPlatform? PushPlatform { get; set; } = EPushPlatform.Sms;
+
+    /// <summary>
+    /// 模板
+    /// </summary>
+    public string? TemplateCode { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    public string? Content { get; set; }
+
+    /// <summary>
+    /// 备注
+    /// </summary>
+    public string? Remark { get; set; }
+
+    /// <summary>
+    /// 参数
+    /// </summary>
+    public List<string>? Params { get; set; }
+    public bool IsSmsReply { get; set; }
+    public string? SmsReplyContent { get; set; }
+    /// <summary>
+    /// 消息处理类型  0:推送状态修改,1:发送状态,2:短信回复
+    /// </summary>
+    public string Type { get; set; }
+}
+
+public class MessageInDto
 {
-    public class MessageDto
-    {
-        /// <summary>
-        /// 消息推送业务
-        /// </summary>
-        public EPushBusiness PushBusiness { get; set; }
-
-        /// <summary>
-        /// 接收姓名
-        /// </summary>
-        public string Name { get; set; }
-
-        /// <summary>
-        /// 接收手机号码
-        /// </summary>
-        public string TelNumber { get; set; }
-
-        /// <summary>
-        /// 外部业务唯一编号
-        /// </summary>
-        public string? ExternalId { get; set; }
-
-        /// <summary>
-        /// 关联工单编号
-        /// </summary>
-        public string? OrderId { get; set; }
-
-        /// <summary>
-        /// 推送平台
-        /// </summary>
-        public EPushPlatform? PushPlatform { get; set; } = EPushPlatform.Sms;
-
-        /// <summary>
-        /// 模板
-        /// </summary>
-        public string? TemplateCode { get; set; }
-
-        /// <summary>
-        /// 内容
-        /// </summary>
-        public string? Content { get; set; }
-
-        /// <summary>
-        /// 备注
-        /// </summary>
-        public string? Remark { get; set; }
-
-        /// <summary>
-        /// 参数
-        /// </summary>
-        public List<string>? Params { get; set; }
-        public bool IsSmsReply { get; set; }
-        public string? SmsReplyContent { get; set; }
-        /// <summary>
-        /// 消息处理类型  0:推送状态修改,1:发送状态,2:短信回复
-        /// </summary>
-        public string Type { get; set; }
-    }
+    /// <summary>
+    /// 接收手机号码(多个逗号分隔)
+    /// </summary>
+    public string TelNumbers { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    public string? Content { get; set; }
 }

+ 7 - 1
src/Hotline.Share/Enums/Push/EPushBusiness.cs

@@ -67,10 +67,16 @@ public enum EPushBusiness
     [Description("撤销短信")]
     OrderRevocationSms = 9,
 
-
     /// <summary>
     /// 自动延期
     /// </summary>
     [Description("自动延期")]
     AutomaticDelay = 10,
+
+    /// <summary>
+    /// 手动发送短信
+    /// </summary>
+    [Description("手动发送短信")]
+    ManualSms = 11,
+
 }

+ 14 - 0
src/Hotline.Share/Tools/StringExtensions.cs

@@ -39,4 +39,18 @@ public static class StringExtensions
                     .Select(s => s.Trim())
                     .ToList();
     }
+
+    /// <summary>
+    /// 是否手机号码
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    public static bool IsPhoneNumber(this string value)
+    {
+        if (string.IsNullOrWhiteSpace(value))
+            return false;
+
+        var phoneNumberPattern = @"^1[3-9]\d{9}$";
+        return Regex.IsMatch(value, phoneNumberPattern);
+    }
 }

+ 8 - 0
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -224,6 +224,7 @@ namespace Hotline.FlowEngine.Workflows
             workflow.Traces.Add(startTrace);
             startStep.WorkflowTrace = startTrace;
 
+            //todo refactor:新增&更新整合
             //更新受理人信息
             workflow.UpdateAcceptor(
                 _sessionContext.RequiredUserId,
@@ -232,6 +233,13 @@ namespace Hotline.FlowEngine.Workflows
                 _sessionContext.RequiredOrgId,
                 _sessionContext.OrgName);
 
+            //发起会签时记录顶层会签节点(必须在update currentStep之后)
+            var counterSignType = GetCounterSignType(dto.IsStartCountersign);
+            if (dto.IsStartCountersign && !workflow.IsInCountersign)
+                workflow.StartCountersign(startStep.Id, counterSignType);
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
             return startStep;
         }
 

+ 4 - 3
src/Hotline/Settings/TimeLimitDomain/ExpireTimeSupplier/HourSupplier.cs

@@ -11,10 +11,11 @@ public class HourSupplier : IExpireTimeSupplier, IScopeDependency
 {
     public async Task<TimeResult> CalcEndTimeAsync(DateTime beginTime, TimeConfig timeConfig)
     {
-        return new TimeResult { 
-            EndTime = 
+        return new TimeResult
+        {
+            EndTime =
             beginTime.AddHours(timeConfig.Count),
-            RuleStr = timeConfig.Count + "个小时", 
+            RuleStr = timeConfig.Count + "个小时",
             NearlyExpiredTime = beginTime.AddHours(timeConfig.Count * ((double)timeConfig.Percentage / 100)),
             NearlyExpiredTimeOne = beginTime.AddHours(timeConfig.Count * ((double)timeConfig.PercentageOne / 100))
         };