Explorar o código

工单详情-流程明细改造

xfe hai 4 meses
pai
achega
a2c1c203bf

+ 18 - 25
src/Hotline.Api/Controllers/OrderController.cs

@@ -593,8 +593,13 @@ public class OrderController : BaseController
             orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
         var handler = new UserInfo(orderPublish.CreatorId, orderPublish.CreatorName,
             orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
+        //上面回访人取值当前操作人,所以暂时可取值SessionContext,如需求有变需取值ordervisit中指定的回访人
+        var visitAcceptor = string.IsNullOrEmpty(orderVisit.EmployeeId)
+            ? new UserInfo()
+            : new UserInfo(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId,
+            _sessionContext.OrgName, _sessionContext.OrgIsCenter);
         await _workflowDomainService.HandlePublishTraceAsync(order.WorkflowId, orderPublish.Id, acceptor, handler,
-            orderPublish.CreationTime, HttpContext.RequestAborted);
+            orderPublish.CreationTime, visitAcceptor, orderVisit.Id, HttpContext.RequestAborted);
 
         //需求251  某些工单需自动发送短信
         //任何类型的省工单都不需要发送短信
@@ -4321,33 +4326,21 @@ public class OrderController : BaseController
     /// <summary>
     /// 查询工单办理流程明细
     /// </summary>
-    /// <param name="orderId"></param>
-    /// <returns></returns>
-    [HttpGet("{orderId}/traces")]
-    public async Task<IReadOnlyList<OrderFlowTraceDto>> GetOrderTraces(string orderId)
+    [HttpGet("traces/{workflowId}")]
+    [AllowAnonymous]
+    public async Task<IReadOnlyList<OrderFlowTraceDto>> GetOrderTraces(string workflowId)
     {
-        var order = await _orderRepository.GetAsync(orderId, HttpContext.RequestAborted);
-        if (order is null)
-            throw new UserFriendlyException("无效工单编号");
-        var traces = await _workflowStepRepository.Queryable()
-            .Where(d => d.CountersignPosition == ECountersignPosition.None && d.IsOrigin)
-            .ToListAsync(HttpContext.RequestAborted);
-
-        var traceDtos = _mapper.Map<List<OrderFlowTraceDto>>(traces);
-        var fileTraces = traces.Where(d => d.FileJson != null).ToList();
-        foreach (var fileTrace in fileTraces)
-        {
-            var fileIds = fileTrace.FileJson.Select(x => x.Id).ToList();
-            var files = await _fileRepository.Queryable().In(x => x.Id, fileIds).ToListAsync(HttpContext.RequestAborted);
-            traceDtos.First(d => d.Id == fileTrace.Id).Files = files.Any() ? _mapper.Map<List<FileDto>>(files) : new List<FileDto>();
-        }
+        var traces = await _workflowTraceRepository.Queryable()
+            .Includes(d => d.OrderPublish)
+            .Includes(d => d.OrderVisit, x => x.OrderVisitDetails)
+            .Where(d => d.WorkflowId == workflowId)
+            .OrderBy(d => d.CreationTime)
+            .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
 
-        var publishes = await _orderPublishRepository.Queryable()
-            .Where(d => d.OrderId == orderId)
-            .ToListAsync();
-        var publishDtos = _mapper.Map<List<OrderFlowTraceDto>>(publishes);
+        //todo files
+        //await _fileRepository.WorkflowTraceRecursion(workflowDto.Traces, HttpContext.RequestAborted);
 
-        throw new NotImplementedException();
+        return _mapper.Map<IReadOnlyList<OrderFlowTraceDto>>(traces);
     }
 
     #endregion

+ 2 - 2
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -174,7 +174,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         //starttrace
         var startTrace = _mapper.Map<WorkflowTrace>(startStep);
         startTrace.StepId = startStep.Id;
-        startTrace.WorkflowTraceType = EWorkflowTraceType.Normal;
+        startTrace.TraceType = EWorkflowTraceType.Normal;
         //_mapper.Map(dto, startTrace);
         await _workflowTraceRepository.AddAsync(startTrace, cancellationToken);
         workflow.Traces.Add(startTrace);
@@ -254,7 +254,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         //starttrace
         var startTrace = _mapper.Map<WorkflowTrace>(startStep);
         startTrace.StepId = startStep.Id;
-        startTrace.WorkflowTraceType = EWorkflowTraceType.Normal;
+        startTrace.TraceType = EWorkflowTraceType.Normal;
         //_mapper.Map(dto, startTrace);
         await _workflowTraceRepository.AddAsync(startTrace, cancellationToken);
         workflow.Traces.Add(startTrace);

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

@@ -19,8 +19,8 @@ public class OrderMapperConfigs : IRegister
             .IgnoreIf((s, d) => s.OrderExtension == null, d => d.OrderExtension)
             .IgnoreIf((s, d) => s.Hotspot == null, d => d.Hotspot)
             //.Map(d => d.IsRed, s => string.IsNullOrEmpty(s.SignerId) || !s.ActualHandleStepAcceptTime.HasValue)
-            .Map(d => d.IsRed, s => s.WorkflowSteps!=null 
-                                    && s.WorkflowSteps.Any() 
+            .Map(d => d.IsRed, s => s.WorkflowSteps != null
+                                    && s.WorkflowSteps.Any()
                                     && s.WorkflowSteps.First().Status == EWorkflowStepStatus.WaitForAccept)
             ;
 
@@ -230,8 +230,24 @@ public class OrderMapperConfigs : IRegister
             .Map(d => d.No, s => s.CaseSerial);
 
         config.ForType<WorkflowTrace, OrderFlowTraceDto>()
-            .Map(d => d.FlowTraceType, s => EFlowTraceType.Flow)
-            .Map(d => d.Assigner, s => s.CreatorName)
-            .Map(d => d.AssignOrgName, s => s.CreatorOrgName);
+            .IgnoreIf((s, d) => s.OrderPublish == null, d => d.PublishState)
+            .Map(d => d.PublishState, s => s.OrderPublish.PublishState)
+            .IgnoreIf((s, d) => s.OrderPublish == null, d => d.ArrangeOpinion)
+            .Map(d => d.ArrangeOpinion, s => s.OrderPublish.ArrangeOpinion)
+            .IgnoreIf((s, d) => s.OrderVisit == null || s.OrderVisit.OrderVisitDetails.All(x => x.VisitTarget != EVisitTarget.Seat), d => d.VoiceEvaluate)
+            .Map(d => d.VoiceEvaluate, s => s.OrderVisit.OrderVisitDetails.First(x => x.VisitTarget == EVisitTarget.Seat).VoiceEvaluate)
+            .IgnoreIf((s, d) => s.OrderVisit == null || s.OrderVisit.OrderVisitDetails.All(x => x.VisitTarget != EVisitTarget.Seat), d => d.SeatEvaluate)
+            .Map(d => d.SeatEvaluate, s => s.OrderVisit.OrderVisitDetails.First(x => x.VisitTarget == EVisitTarget.Seat).SeatEvaluate)
+            .IgnoreIf((s, d) => s.OrderVisit == null || s.OrderVisit.OrderVisitDetails.All(x => x.VisitTarget != EVisitTarget.Seat), d => d.VisitContent)
+            .Map(d => d.VisitContent, s => s.OrderVisit.OrderVisitDetails.First(x => x.VisitTarget == EVisitTarget.Seat).VisitContent)
+            .IgnoreIf((s, d) => s.OrderVisit == null || s.OrderVisit.OrderVisitDetails.All(x => x.VisitTarget != EVisitTarget.Org), d => d.OrderFlowVisitDetails)
+            .Map(d => d.OrderFlowVisitDetails, s => s.OrderVisit.OrderVisitDetails.Where(x => x.VisitTarget == EVisitTarget.Org))
+            ;
+
+        config.ForType<OrderVisitDetail, OrderFlowVisitDetail>()
+            .Map(d => d.OrgProcessingResults, s => s.OrgProcessingResults.Value)
+            .Map(d => d.OrgNoSatisfiedReason, s => string.Join(',', s.OrgNoSatisfiedReason.Select(d => d.Value)))
+            .Map(d => d.OrgHandledAttitude, s => s.OrgHandledAttitude.Value);
+
     }
 }

+ 1 - 1
src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs

@@ -46,7 +46,7 @@ public class WorkflowMapperConfigs : IRegister
         config.ForType<WorkflowStep, WorkflowTrace>()
             .Ignore(d=>d.Workflow)
             .Ignore(d => d.ParentId)
-            .Ignore(d => d.WorkflowTraceType)
+            .Ignore(d => d.TraceType)
             .Map(d => d.StepId, s => s.Id)
             .AfterMapping((s, d) => d.Id = s.Id)
             ;

+ 58 - 48
src/Hotline.Application/Orders/OrderApplication.cs

@@ -988,18 +988,18 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 expiredTimeConfig.NearlyExpiredTime, expiredTimeConfig.NearlyExpiredTimeOne, dto.Workflow.Opinion,
                 _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
                 canUpdateOrderSender);
-			//TODO发送短信即将超期
-			//_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
-			//自动延期订阅
-			var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-			if (bool.Parse(enabled))
-			{
-				await _capPublisher.PublishDelayAsync(
-                    expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), 
+            //TODO发送短信即将超期
+            //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
+            //自动延期订阅
+            var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+            if (bool.Parse(enabled))
+            {
+                await _capPublisher.PublishDelayAsync(
+                    expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1),
                     EventNames.HotlineOrderAutomaticDelay,
-					new PublishAutomaticDelayDto() { OrderId = order.Id },
+                    new PublishAutomaticDelayDto() { OrderId = order.Id },
                     cancellationToken: cancellationToken);
-			}
+            }
         }
         else if (dto.Workflow.FlowDirection is EFlowDirection.CenterToOrg)
         {
@@ -1011,15 +1011,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 expiredTimeConfig.NearlyExpiredTime, expiredTimeConfig.NearlyExpiredTimeOne, dto.Workflow.Opinion,
                 _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
                 canUpdateOrderSender);
-			//TODO发送短信即将超期
-			//_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
-			//自动延期订阅
-			var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-			if (bool.Parse(enabled))
-			{
-				_capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
-					new PublishAutomaticDelayDto() { OrderId = order.Id });
-			}
+            //TODO发送短信即将超期
+            //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
+            //自动延期订阅
+            var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+            if (bool.Parse(enabled))
+            {
+                _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
+                    new PublishAutomaticDelayDto() { OrderId = order.Id });
+            }
         }
         else if (dto.Workflow.FlowDirection is EFlowDirection.CenterToCenter)
         {
@@ -1029,15 +1029,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 order.CenterToCenter(expiredTimeConfig.TimeText, expiredTimeConfig.Count,
                     expiredTimeConfig.TimeType, expiredTimeConfig.ExpiredTime, expiredTimeConfig.NearlyExpiredTime,
                     expiredTimeConfig.NearlyExpiredTimeOne);
-				//TODO发送短信即将超期
-				//_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
-				//自动延期订阅
-				var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-				if (bool.Parse(enabled))
-				{
-					_capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
-						new PublishAutomaticDelayDto() { OrderId = order.Id });
-				}
+                //TODO发送短信即将超期
+                //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
+                //自动延期订阅
+                var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+                if (bool.Parse(enabled))
+                {
+                    _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
+                        new PublishAutomaticDelayDto() { OrderId = order.Id });
+                }
 
             }
         }
@@ -1140,6 +1140,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
         await _orderVisitedDetailRepository.UpdateRangeAsync(visit.OrderVisitDetails, cancellationToken);
         await _orderRepository.UpdateAsync(visit.Order, cancellationToken);
+
+        //handle visit trace
+        //上面取得当前操作人,需求变动前暂时可以这样写
+        var visitor = new UserInfo(
+            _sessionContextProvider.SessionContext.UserId,
+            _sessionContextProvider.SessionContext.UserName,
+            _sessionContextProvider.SessionContext.OrgId,
+            _sessionContextProvider.SessionContext.OrgName);
+        await _workflowDomainService.HandleVisitTraceAsync(visit.Id, visitor, visit.VisitTime ?? DateTime.Now, cancellationToken);
+
         var orderDto = _mapper.Map<OrderDto>(visit.Order);
         await _publisher.PublishAsync(new UpdateOrderVisitNotify(visit.Adapt<OrderVisitNotifyDto>()), cancellationToken);
         if (first != null)
@@ -1717,7 +1727,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     {
         bool IsCenter = _sessionContextProvider.SessionContext.OrgIsCenter;
         int orgLevel = _sessionContextProvider.SessionContext.OrgLevel;
-        string orgLevelStr = ((orgLevel+1) * 3).ToString();
+        string orgLevelStr = ((orgLevel + 1) * 3).ToString();
         var list = _orderVisitDetailRepository.Queryable()
             .Where(x => x.OrderVisit.VisitTime >= dto.StartTime.Value && x.OrderVisit.VisitTime <= dto.EndTime.Value &&
                         x.VisitTarget == EVisitTarget.Org && x.OrderVisit.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(x.VisitOrgCode))
@@ -1815,7 +1825,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "6", 1, 0))), //未接通
                 })
                 .MergeTable()
-                .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id && (o.Level == orgLevel || o.Level == (orgLevel+1)))
+                .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id && (o.Level == orgLevel || o.Level == (orgLevel + 1)))
                 .Select((it, o) => new VisitAndOrgSatisfactionStatisticsDto()
                 {
                     OrgName = o.Name,
@@ -1857,7 +1867,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .WhereIF(dto.VisitType != null, (x, it) => it.OrderVisit.VisitType == dto.VisitType)
             .GroupBy((x, it) => new
             {
-                VisitOrgCode = it.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"),SqlFunc.MappingColumn<int>("9"))
+                VisitOrgCode = it.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("9"))
             })
             .Select((x, it) => new VisitAndOrgSatisfactionStatisticsDto()
             {
@@ -2593,7 +2603,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .Select(x => new OrderScreenAuditVo
             {
                 AuditName = x.HandlerName,
-                AuditNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.WorkflowTraceType == EWorkflowTraceType.Normal && x.TraceState == EWorkflowTraceState.Normal, 1, 0)),
+                AuditNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.TraceType == EWorkflowTraceType.Normal && x.TraceState == EWorkflowTraceState.Normal, 1, 0)),
                 AuditBackNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.TraceState == EWorkflowTraceState.StepRemoveByPrevious, 1, 0)),
             });
         return query;
@@ -2900,7 +2910,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         var query = _orderVisitRepository.Queryable()
             .Includes(d => d.Order)
             .Includes(d => d.Employee)
-            .Includes(d=>d.OrderVisitDetails)
+            .Includes(d => d.OrderVisitDetails)
             .WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoVisit,
                 d => d.VisitState == EVisitState.WaitForVisit ||
                      d.VisitState == EVisitState.NoSatisfiedWaitForVisit)
@@ -2951,23 +2961,23 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return query;
     }
 
-	/// <summary>
-	/// 热点类型小类统计明细
-	/// </summary>
-	/// <param name="dto"></param>
-	/// <returns></returns>
+    /// <summary>
+    /// 热点类型小类统计明细
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
 
-	public ISugarQueryable<Order> HotspotStatisticsDetail(HotspotStatisticsRep dto)
+    public ISugarQueryable<Order> HotspotStatisticsDetail(HotspotStatisticsRep dto)
     {
-	    var IsCenter = _sessionContext.OrgIsCenter;
-	    var query = _orderRepository.Queryable()
-		    .Includes(d => d.OrderVisits)
-		    .Where(d => d.CreationTime >= dto.StartTime && d.CreationTime <= dto.EndTime)
-		    .Where(d => d.HotspotId.StartsWith(dto.HotspotCode))
-		    .WhereIF(dto.TypeId == 1, d => d.IdentityType == EIdentityType.Citizen)
-		    .WhereIF(dto.TypeId == 2, d => d.IdentityType == EIdentityType.Enterprise)
-		    .WhereIF(IsCenter == false, d => d.ActualHandleOrgCode.StartsWith(_sessionContext.RequiredOrgId));
-	    return query;
+        var IsCenter = _sessionContext.OrgIsCenter;
+        var query = _orderRepository.Queryable()
+            .Includes(d => d.OrderVisits)
+            .Where(d => d.CreationTime >= dto.StartTime && d.CreationTime <= dto.EndTime)
+            .Where(d => d.HotspotId.StartsWith(dto.HotspotCode))
+            .WhereIF(dto.TypeId == 1, d => d.IdentityType == EIdentityType.Citizen)
+            .WhereIF(dto.TypeId == 2, d => d.IdentityType == EIdentityType.Enterprise)
+            .WhereIF(IsCenter == false, d => d.ActualHandleOrgCode.StartsWith(_sessionContext.RequiredOrgId));
+        return query;
     }
 
     /// <summary>

+ 4 - 0
src/Hotline.Share/Dtos/FlowEngine/UserInfo.cs

@@ -2,6 +2,10 @@
 
 public class UserInfo
 {
+    public UserInfo()
+    {
+        
+    }
     public UserInfo(string? userId, string? username, string? orgId, string? orgName, bool orgIsCenter = false)
     {
         UserId = userId;

+ 9 - 21
src/Hotline.Share/Dtos/Order/Detail/OrderFlowTraceDto.cs

@@ -20,7 +20,7 @@ public class OrderFlowTraceDto
     /// <summary>
     /// 交办人
     /// </summary>
-    public string Assigner { get; set; }
+    public string? AssignerName { get; set; }
 
     /// <summary>
     /// 交办部门
@@ -60,22 +60,10 @@ public class OrderFlowTraceDto
     /// </summary>
     public DateTime? StepExpiredTime { get; set; }
 
-    public ExpiredStatus ExpiredStatus
-    {
-        get
-        {
-            if (HandleTime.HasValue)
-            {
-                return HandleTime.Value < StepExpiredTime ? ExpiredStatus.Completed : ExpiredStatus.Expired;
-            }
-            else
-            {
-                return DateTime.Now < StepExpiredTime ? ExpiredStatus.Handling : ExpiredStatus.Expired;
-            }
-        }
-    }
-
-    public string ExpiredStatusText => ExpiredStatus.GetDescription();
+    /// <summary>
+    /// 节点超期状态
+    /// </summary>
+    public EExpiredStatus? ExpiredStatus { get; set; }
 
     /// <summary>
     /// 办理方式
@@ -85,9 +73,9 @@ public class OrderFlowTraceDto
     public string HandleModeText => HandleMode.HasValue ? HandleMode.GetDescription() : "";
 
     /// <summary>
-    /// 节点类型
+    /// 快照类型
     /// </summary>
-    public ETraceType FlowTraceType { get; set; }
+    public ETraceStyle TraceStyle { get; set; }
 
     #region 流程节点展开
 
@@ -144,7 +132,7 @@ public class OrderFlowTraceDto
     /// </summary>
     public string? VisitContent { get; set; }
 
-    public IReadOnlyList<FlowDetailVisitDetail> FlowDetailVisitDetails { get; set; }
+    public IReadOnlyList<OrderFlowVisitDetail> OrderFlowVisitDetails { get; set; }
 
     #endregion
 
@@ -153,7 +141,7 @@ public class OrderFlowTraceDto
 /// <summary>
 /// 流程明细回访节点展开元素
 /// </summary>
-public class FlowDetailVisitDetail
+public class OrderFlowVisitDetail
 {
     /// <summary>
     /// 部门办件结果

+ 2 - 2
src/Hotline.Share/Enums/FlowEngine/EFlowTraceType.cs

@@ -3,7 +3,7 @@ namespace Hotline.Share.Enums.FlowEngine;
 /// <summary>
 /// 流程节点类型
 /// </summary>
-public enum ETraceType
+public enum ETraceStyle
 {
     /// <summary>
     /// 流程节点
@@ -23,5 +23,5 @@ public enum ETraceType
     /// <summary>
     /// 结束(为了与老系统保持一致,额外添加在回访后的节点)
     /// </summary>
-    AppendEnd = 9,
+    TrashEnd = 9,
 }

+ 1 - 1
src/Hotline/CallCenter/Calls/CallNative.cs

@@ -14,7 +14,7 @@ namespace Hotline.CallCenter.Calls
     /// 本地通话记录
     /// </summary>
     [SugarIndex("index_call_callid", nameof(CallNative.CallNo), OrderByType.Asc)]
-    public class CallNative : CreationEntity
+    public class CallNative : CreationSoftDeleteEntity
     {
         /// <summary>
         /// 通话记录编号

+ 4 - 1
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -303,6 +303,9 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         WorkflowStep GetCsLoopStartStep(List<WorkflowStep> steps, WorkflowStep currentStep);
 
-        Task HandlePublishTraceAsync(string workflowId, string orderPublishId, UserInfo acceptor, UserInfo handler, DateTime handleTime, CancellationToken cancellation);
+        Task HandlePublishTraceAsync(string workflowId, string orderPublishId, UserInfo acceptor, UserInfo handler, DateTime handleTime, 
+            UserInfo visitAcceptor, string orderVisitId, CancellationToken cancellation);
+
+        Task HandleVisitTraceAsync(string orderVisitId, UserInfo visitor, DateTime visitTime, CancellationToken cancellation);
     }
 }

+ 1 - 6
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -164,7 +164,7 @@ public abstract class StepBasicEntity : CreationEntity
 
     public string? AssignerOrgName { get; set; }
 
-    public bool AssignerOrgIsCenter { get; set; }
+    public bool? AssignerOrgIsCenter { get; set; }
 
     #endregion
 
@@ -370,11 +370,6 @@ public abstract class StepBasicEntity : CreationEntity
     /// </summary>
     public bool IsStartCountersign { get; set; }
 
-    /// <summary>
-    /// 顶级会签发起节点(每组会签的最顶级发起节点,即:发起会签时,流程处于非会签状态)
-    /// </summary>
-    public bool IsTopStartCountersign { get; set; }
-
     #endregion
     #region 创建时赋值
 

+ 90 - 11
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -26,6 +26,7 @@ using Microsoft.Extensions.Options;
 using System.Diagnostics;
 using Hotline.Orders;
 using System.Security.AccessControl;
+using Microsoft.AspNetCore.Http.HttpResults;
 
 namespace Hotline.FlowEngine.Workflows
 {
@@ -244,7 +245,7 @@ namespace Hotline.FlowEngine.Workflows
             //starttrace
             var startTrace = _mapper.Map<WorkflowTrace>(startStep);
             startTrace.StepId = startStep.Id;
-            startTrace.WorkflowTraceType = EWorkflowTraceType.Normal;
+            startTrace.TraceType = EWorkflowTraceType.Normal;
             await _workflowTraceRepository.AddAsync(startTrace, cancellationToken);
             workflow.Traces.Add(startTrace);
             startStep.WorkflowTrace = startTrace;
@@ -1911,7 +1912,7 @@ namespace Hotline.FlowEngine.Workflows
             var steps = workflow.Traces
                 .Where(d => d.StepType is EStepType.Normal)
                 .ToList();
-            var items = steps.Where(d => d.WorkflowTraceType == EWorkflowTraceType.Normal || d.WorkflowTraceType == EWorkflowTraceType.Jump)
+            var items = steps.Where(d => d.TraceType == EWorkflowTraceType.Normal || d.TraceType == EWorkflowTraceType.Jump)
                 .Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName))
                 .DistinctBy(d => d.Key).ToList();
             return (new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items);
@@ -2093,12 +2094,14 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         public async Task HandlePublishTraceAsync(string workflowId, string orderPublishId,
-            UserInfo acceptor, UserInfo handler, DateTime handleTime, CancellationToken cancellation)
+            UserInfo acceptor, UserInfo handler, DateTime handleTime, UserInfo visitAcceptor, string orderVisitId, CancellationToken cancellation)
         {
+            if (string.IsNullOrEmpty(orderPublishId))
+                throw new UserFriendlyException($"参数异常,orderPublishId不能为空, workflowId: {workflowId}");
             //handle pubtrace
             var pubTrace = await _workflowTraceRepository.Queryable()
                 .FirstAsync(d => d.WorkflowId == workflowId
-                          && d.TraceType == ETraceType.Publish
+                          && d.TraceStyle == ETraceStyle.Publish
                           && d.Status == EWorkflowStepStatus.WaitForAccept, cancellation);
             if (pubTrace is null) throw new UserFriendlyException($"未查询到待办的发布节点, workflowId:{workflowId}");
             pubTrace.OrderPublishId = orderPublishId;
@@ -2116,7 +2119,29 @@ namespace Hotline.FlowEngine.Workflows
             await _workflowTraceRepository.UpdateAsync(pubTrace, cancellation);
 
             //create visit trace
-            CreateVisitTraceAsync()
+            await CreateVisitTraceAsync(pubTrace, visitAcceptor, orderVisitId, cancellation);
+        }
+
+        public async Task HandleVisitTraceAsync(string orderVisitId, UserInfo visitor, DateTime visitTime, CancellationToken cancellation)
+        {
+            var visitTrace = await _workflowTraceRepository.GetAsync(d => d.OrderVisitId == orderVisitId,
+                cancellationToken: cancellation);
+            if (visitTrace is null) throw new UserFriendlyException($"未查询到待办的发布节点, orderVisitId:{orderVisitId}");
+            visitTrace.AcceptorId = visitor.UserId;
+            visitTrace.AcceptorName = visitor.UserName;
+            visitTrace.AcceptorOrgId = visitor.OrgId;
+            visitTrace.AcceptorOrgName = visitor.OrgName;
+            visitTrace.HandlerId = visitor.UserId;
+            visitTrace.HandlerName = visitor.UserName;
+            visitTrace.HandlerOrgId = visitor.OrgId;
+            visitTrace.HandlerOrgName = visitor.OrgName;
+            visitTrace.HandleTime = visitTime;
+            visitTrace.Status = EWorkflowStepStatus.Handled;
+
+            await _workflowTraceRepository.UpdateAsync(visitTrace, cancellation);
+
+            //create append end trace
+            await CreateTrashEndTraceAsync(visitTrace, cancellation);
         }
 
         #region private method
@@ -2833,7 +2858,7 @@ namespace Hotline.FlowEngine.Workflows
 
 
             var trace = _mapper.Map<WorkflowTrace>(step);
-            trace.WorkflowTraceType = traceType;
+            trace.TraceType = traceType;
             trace.SendHandleTimes = sendHandleTimes;
 
             if (workflow.IsInCountersign && step.IsInCountersign())
@@ -3173,7 +3198,7 @@ namespace Hotline.FlowEngine.Workflows
         private async Task<WorkflowTrace> CreatePublishTraceAsync(WorkflowTrace endTrace, CancellationToken cancellation)
         {
             var pubTrace = _mapper.Map<WorkflowTrace>(endTrace);
-            pubTrace.TraceType = ETraceType.Publish;
+            pubTrace.TraceStyle = ETraceStyle.Publish;
             pubTrace.Name = "中心发布";
             pubTrace.Status = EWorkflowStepStatus.WaitForAccept;
             pubTrace.Code = "publish";
@@ -3202,13 +3227,17 @@ namespace Hotline.FlowEngine.Workflows
             return pubTrace;
         }
 
-        private async Task<WorkflowTrace> CreateVisitTraceAsync(WorkflowTrace pubTrace, CancellationToken cancellation)
+        private async Task<WorkflowTrace> CreateVisitTraceAsync(WorkflowTrace pubTrace, UserInfo acceptor, string orderVisitId, CancellationToken cancellation)
         {
+            if (string.IsNullOrEmpty(orderVisitId))
+                throw new UserFriendlyException($"参数异常,orderVisitId不能为空, pubTraceId: {pubTrace.Id}");
             var visitTrace = new WorkflowTrace
             {
-                TraceType = ETraceType.Visit,
+                OrderVisitId = orderVisitId,
+                TraceStyle = ETraceStyle.Visit,
                 Name = "中心回访",
                 Status = EWorkflowStepStatus.WaitForAccept,
+                Code = "visit",
                 CreationTime = pubTrace.HandleTime ?? DateTime.Now,
                 PrevStepId = pubTrace.Id,
                 PrevStepCode = pubTrace.Code,
@@ -3217,8 +3246,58 @@ namespace Hotline.FlowEngine.Workflows
                 AssignerName = pubTrace.HandlerName,
                 AssignerOrgId = pubTrace.HandlerOrgId,
                 AssignerOrgName = pubTrace.HandlerOrgName,
-                AssignerOrgIsCenter = pubTrace.HandlerOrgIsCenter ?? false
-            }
+                AssignerOrgIsCenter = pubTrace.HandlerOrgIsCenter ?? false,
+
+                AcceptorId = acceptor.UserId,
+                AcceptorName = acceptor.UserName,
+                AcceptorOrgId = acceptor.OrgId,
+                AcceptorOrgName = acceptor.OrgName,
+
+                StepExpiredTime = null,
+            };
+
+            await _workflowTraceRepository.AddAsync(visitTrace, cancellation);
+
+            return visitTrace;
+        }
+
+        private async Task<WorkflowTrace> CreateTrashEndTraceAsync(WorkflowTrace visitTrace, CancellationToken cancellation)
+        {
+            var now = DateTime.Now;
+            var TrashEndTrace = _mapper.Map<WorkflowTrace>(visitTrace);
+            TrashEndTrace.TraceStyle = ETraceStyle.TrashEnd;
+            TrashEndTrace.Name = "结束";
+            TrashEndTrace.Status = EWorkflowStepStatus.Handled;
+            TrashEndTrace.Code = "trashend";
+            TrashEndTrace.CreationTime = visitTrace.HandleTime ?? now;
+
+            TrashEndTrace.PrevStepId = visitTrace.Id;
+            TrashEndTrace.PrevStepCode = visitTrace.Code;
+            TrashEndTrace.PrevStepName = visitTrace.Name;
+
+            TrashEndTrace.AssignerId = visitTrace.HandlerId;
+            TrashEndTrace.AssignerName = visitTrace.HandlerName;
+            TrashEndTrace.AssignerOrgId = visitTrace.HandlerOrgId;
+            TrashEndTrace.AssignerOrgName = visitTrace.HandlerOrgName;
+            TrashEndTrace.AssignerOrgIsCenter = visitTrace.HandlerOrgIsCenter ?? false;
+
+            TrashEndTrace.AcceptorId = visitTrace.HandlerId;
+            TrashEndTrace.AcceptorName = visitTrace.HandlerName;
+            TrashEndTrace.AcceptorOrgId = visitTrace.HandlerOrgId;
+            TrashEndTrace.AcceptorOrgName = visitTrace.HandlerOrgName;
+            TrashEndTrace.AcceptTime = visitTrace.HandleTime ?? now;
+
+            TrashEndTrace.HandlerId = visitTrace.HandlerId;
+            TrashEndTrace.HandlerName = visitTrace.HandlerName;
+            TrashEndTrace.HandlerOrgId = visitTrace.HandlerOrgId;
+            TrashEndTrace.HandlerOrgName = visitTrace.HandlerOrgName;
+            TrashEndTrace.HandleTime = visitTrace.HandleTime ?? now;
+
+            TrashEndTrace.StepExpiredTime = null;
+
+            await _workflowTraceRepository.AddAsync(visitTrace, cancellation);
+
+            return visitTrace;
         }
 
         /// <summary>

+ 4 - 4
src/Hotline/FlowEngine/Workflows/WorkflowTrace.cs

@@ -26,7 +26,7 @@ public class WorkflowTrace : StepBasicEntity
     /// <summary>
     /// 流转记录状态
     /// </summary>
-    public EWorkflowTraceType? WorkflowTraceType { get; set; }
+    public EWorkflowTraceType? TraceType { get; set; }
 
     /// <summary>
     /// 派单组办理次数
@@ -41,11 +41,11 @@ public class WorkflowTrace : StepBasicEntity
     public EWorkflowTraceState TraceState { get; set; }
 
     /// <summary>
-    /// 流程类型
+    /// 快照类型
     /// </summary>
     [SugarColumn(DefaultValue = "0")]
-    public ETraceType TraceType { get; set; } = ETraceType.Flow;
-    
+    public ETraceStyle TraceStyle { get; set; } = ETraceStyle.Flow;
+
     public string? OrderPublishId { get; set; }
     
     [Navigate(NavigateType.OneToOne, nameof(OrderPublishId))]

+ 6 - 1
src/Hotline/Orders/OrderDomainService.cs

@@ -285,8 +285,13 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
         var handler = new UserInfo(orderPublish.CreatorId, orderPublish.CreatorName,
             orderPublish.CreatorOrgId, orderPublish.CreatorOrgName);
+        //上面回访人取值当前操作人,所以暂时可取值SessionContext,如需求有变需取值ordervisit中指定的回访人
+        var visitAcceptor = string.IsNullOrEmpty(orderVisit.EmployeeId)
+            ? new UserInfo()
+            : new UserInfo(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId,
+                _sessionContext.OrgName, _sessionContext.OrgIsCenter);
         await _workflowDomainService.HandlePublishTraceAsync(order.WorkflowId, orderPublish.Id, acceptor, handler,
-            orderPublish.CreationTime, cancellationToken);
+            orderPublish.CreationTime, visitAcceptor, orderVisit.Id, cancellationToken);
 
         //需求251  某些工单需自动发送短信
         //任何类型的省工单都不需要发送短信