Parcourir la source

Merge branch 'master' of http://git.12345lm.cn/Fengwo/hotline

Dun.Jason il y a 1 an
Parent
commit
662e6d5ecd

+ 39 - 29
src/Hotline.Api/Controllers/CommonPController.cs

@@ -89,37 +89,47 @@ namespace Hotline.Api.Controllers
                 return new { CallNum = callNum, ValidCallNum = validCallNum, AnsweredNum = answeredNum, AnsweredRate = answeredRate, OrderNum = orderNum, DirectlyNum = directlyNum };
 			}
 			//部门
-			//今日待办
-            var time = DateTime.Parse(tadayTime);
-			var tasksOkNum = await _orderRepository.Queryable(false, false, false)
+			//今日待办 tasksOkNum
+			var time = DateTime.Parse(tadayTime);
+			var order = await _orderRepository.Queryable(false, false, false)
 				.Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled))
-                .Where(o => SqlFunc.JsonListObjectAny(o.Workflow.HandlerUsers, "Key", _sessionContext.RequiredUserId) && o.Workflow.ExpiredTime > time).CountAsync();
-            var tasksOkOrgNum = await _orderRepository.Queryable(false, false, false)
-	            .Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled))
-                .Where(o => SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.Workflow.ExpiredTime > time).CountAsync();
+                .Where(o => o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled) && o.Workflow.ExpiredTime > time)
+                .GroupBy(o=>o.Id)
+                .Select(o => new {
+					tasksOkNum= SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerUsers, "Key", _sessionContext.RequiredUserId) , 1, 0)),
+					tasksOkOrgNum = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) , 1, 0)),
+                    //handleNum = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerUsers, "Key", _sessionContext.RequiredUserId) && o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled) && o.Workflow.ActualHandleTime.Value.ToString("yyyy-MM-dd") == tadayTime && o.Workflow.ActualHandleTime != null, 1, 0)),
+                    //handleOrgNum = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled) && o.Workflow.ActualHandleTime.Value.ToString("yyyy-MM-dd") == tadayTime && o.Workflow.ActualHandleTime != null, 1, 0)),
+                    //exceedTasksOkNum = SqlFunc.AggregateSum(SqlFunc.IIF(o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled) && SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.ExpiredTime > DateTime.Now, 1, 0)),
+                    //exceedHandleNum = SqlFunc.AggregateSum(SqlFunc.IIF(o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled) && SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.ExpiredTime > DateTime.Now, 1, 0)),
+                }).FirstAsync();
+            var tasksOkNum = order?.tasksOkNum ?? 0;
+            var tasksOkOrgNum = order?.tasksOkOrgNum ?? 0;
             //今日已办
-			var handleNum = await _orderRepository.Queryable(false, false, false)
-				.Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled))
-                .Where(o => o.Workflow.ActualHandleTime != null && SqlFunc.JsonListObjectAny(o.Workflow.HandlerUsers, "Key", _sessionContext.RequiredUserId) && o.Workflow.ActualHandleTime.Value.ToString("yyyy-MM-dd") == tadayTime).CountAsync();
-            var handleOrgNum = await _orderRepository.Queryable(false, false, false)
-	            .Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled))
-                .Where(o => o.Workflow.ActualHandleTime != null && SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.Workflow.ActualHandleTime.Value.ToString("yyyy-MM-dd") == tadayTime).CountAsync();
-			//部门超期
-			var exceedTasksOkNum = await _orderRepository.Queryable(false, false, false)
-				.Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled))
-                .Where(o => SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.ExpiredTime > DateTime.Now).CountAsync();
-            var exceedHandleNum = await _orderRepository.Queryable(false, false, false)
-	            .Includes(o => o.Workflow, w => w.Steps)
-                .Where(o => o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled))
-                .Where(o => SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.ExpiredTime > DateTime.Now).CountAsync();
-            return new { TasksOkNum = tasksOkNum, TasksOkOrgNum = tasksOkOrgNum, HandleNum = handleNum, HandleOrgNum = handleOrgNum, ExceedTasksOkNum = exceedTasksOkNum, ExceedHandleNum = exceedHandleNum };
-
-            return new { };
+            var handleOrder = await _orderRepository.Queryable(false, false, false)
+                .Includes(o => o.Workflow, w => w.Steps)
+                .Where(o => o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled) && o.Workflow.ActualHandleTime.Value.ToString("yyyy-MM-dd") == tadayTime && o.Workflow.ActualHandleTime != null)
+                .GroupBy(o => o.Id)
+                .Select(o => new
+                {
+                    handleNum = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerUsers, "Key", _sessionContext.RequiredUserId), 1, 0)),
+                    handleOrgNum = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId), 1, 0)),
+                }).FirstAsync();
+            var handleNum = handleOrder?.handleNum ?? 0;
+            var handleOrgNum = handleOrder?.handleOrgNum ?? 0;
+            //部门超期
+            var exceedOrder = await _orderRepository.Queryable(false, false, false)
+                .Includes(o => o.Workflow, w => w.Steps)
+                .Where(o => SqlFunc.JsonListObjectAny(o.Workflow.HandlerOrgs, "Key", _sessionContext.RequiredOrgId) && o.ExpiredTime > DateTime.Now)
+                .GroupBy(o => o.Id)
+                .Select(o => new
+                {
+                    exceedTasksOkNum = SqlFunc.AggregateSum(SqlFunc.IIF(o.Workflow.Steps.Any(s => s.Status == EWorkflowStepStatus.Handled), 1, 0)),
+                    exceedHandleNum = SqlFunc.AggregateSum(SqlFunc.IIF(o.Workflow.Steps.Any(s => s.Status != EWorkflowStepStatus.Handled), 1, 0)),
+                }).FirstAsync();
+            var exceedTasksOkNum = exceedOrder?.exceedTasksOkNum ?? 0;
+            var exceedHandleNum = exceedOrder?.exceedHandleNum ?? 0;
+			return new { TasksOkNum = tasksOkNum, TasksOkOrgNum = tasksOkOrgNum, HandleNum = handleNum, HandleOrgNum = handleOrgNum, ExceedTasksOkNum = exceedTasksOkNum, ExceedHandleNum = exceedHandleNum };
         }
 	}
 }

+ 56 - 29
src/Hotline.Api/Controllers/OrderController.cs

@@ -1319,7 +1319,11 @@ public class OrderController : BaseController
             .FirstAsync(x => x.Id == id);
         var rspModel = _mapper.Map<OrderDelayDto>(model);
         rspModel.IsCanHandle = model.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
-        rspModel.Handle = await _workflowDomainService.CheckCurrentIsStartStepAsync(rspModel.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, HttpContext.RequestAborted);
+        rspModel.Handle = false;
+		if (!string.IsNullOrEmpty(rspModel.WorkflowId))
+        {
+			rspModel.Handle = await _workflowDomainService.CheckCurrentIsStartStepAsync(rspModel.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, HttpContext.RequestAborted);
+		}
         if (rspModel.FileJson != null && rspModel.FileJson.Any())
         {
             var ids = rspModel.FileJson.Select(x => x.Id).ToList();
@@ -1595,7 +1599,11 @@ public class OrderController : BaseController
             .FirstAsync(x => x.Id == id);
         var rspModel = _mapper.Map<OrderScreenListDto>(model);
         rspModel.IsCanHandle = model.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
-        rspModel.Handle = await _workflowDomainService.CheckCurrentIsStartStepAsync(rspModel.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, HttpContext.RequestAborted);
+        rspModel.Handle = false;
+		if (!string.IsNullOrEmpty(rspModel.WorkflowId))
+        {
+			rspModel.Handle = await _workflowDomainService.CheckCurrentIsStartStepAsync(rspModel.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, HttpContext.RequestAborted);
+		}
         if (rspModel.FileJson != null && rspModel.FileJson.Any())
         {
             var ids = rspModel.FileJson.Select(x => x.Id).ToList();
@@ -2046,7 +2054,7 @@ public class OrderController : BaseController
             .WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.SourceChannelCode)) //来源渠道
             .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId)) //热点类型
             .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone.Contains(dto.TransferPhone!)) //转接号码
-            //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
+                                                                                                                  //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
             .WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.ActualHandleOrgCode)) //接办部门
             .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName.Contains(dto.NameOrNo!) || d.AcceptorStaffNo.Contains(dto.NameOrNo!)) //受理人/坐席
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
@@ -2070,8 +2078,8 @@ public class OrderController : BaseController
             .WhereIF(dto.IdentityType != null, d => d.IdentityType == dto.IdentityType) //来电主体
             .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName.Contains(dto.FromName)) //来电人姓名
             .WhereIF(dto.AreaCodes.Any(), d => dto.AreaCodes.Contains(d.AreaCode)) //区域
-            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true,x=>x.IsProvince == true)
-            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false,x=>x.IsProvince == false)
+            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.IsProvince == true)
+            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.IsProvince == false)
             .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
 
@@ -2130,13 +2138,15 @@ public class OrderController : BaseController
             return new();
 
         string? countersignId = null;
+        var canPrevious = false;
         if (!string.IsNullOrEmpty(order.WorkflowId))
         {
-            var (workflow, unCompleteCountersignId) = await _workflowDomainService.GetWorkflowHandlePermissionAsync(
+            var result = await _workflowDomainService.GetWorkflowHandlePermissionAsync(
                 order.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
                 cancellationToken: HttpContext.RequestAborted);
-            order.Workflow = workflow;
-            countersignId = unCompleteCountersignId;
+            order.Workflow = result.workflow;
+            countersignId = result.countersignId;
+            canPrevious = result.canPrevious;
 
             await _mediator.Publish(new GetOrderDetailNotify(order.Workflow,
                 _sessionContext.RequiredUserId, _sessionContext.UserName,
@@ -2150,6 +2160,7 @@ public class OrderController : BaseController
         dto.IsCanDelay = !order.OrderDelays.Any(x => x.DelayState == EDelayState.Examining);
         var delayModel = order.OrderDelays.Where(x => x.DelayState == EDelayState.Pass).MaxBy(x => x.CreationTime);
         dto.DelayString = delayModel != null ? delayModel?.DelayNum + "个" + delayModel?.DelayUnit.GetDescription() : "";
+        dto.CanPrevious = canPrevious;
 
         if (dto.FileJson != null && dto.FileJson.Any())
         {
@@ -2161,10 +2172,6 @@ public class OrderController : BaseController
         var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == order.CallId).FirstAsync();
         if (call != null) dto.RecordingFileUrl = call.RecordingFileUrl;
 
-        if (order.Workflow != null)
-        {
-            dto.CanPrevious = !order.Workflow.IsInCountersign;
-        }
         var repeatablesMap = await _repeatableEventDetailRepository.Queryable()
             .Includes(x => x.Order)
             .Where(x => x.OrderId == id).Distinct().ToListAsync();
@@ -2392,7 +2399,6 @@ public class OrderController : BaseController
     [HttpPost("handle")]
     public async Task Handle([FromBody] NextWorkflowDto dto)
     {
-        //todo 需求待确认
         var order = await _orderRepository.Queryable()
             .FirstAsync(d => d.WorkflowId == dto.WorkflowId, HttpContext.RequestAborted);
         //if (await _orderDelayRepository.AnyAsync(x => x.OrderId == order.Id && x.DelayState == EDelayState.Examining, HttpContext.RequestAborted))
@@ -2404,6 +2410,26 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("该工单存在正在审核中的退回,不能办理");
         }
 
+        if (dto.FlowDirection.HasValue
+            && dto.External.TimeLimit.HasValue
+            && dto.External.TimeLimitUnit.HasValue)
+        {
+            var expiredTimeConfig = _timeLimitDomainService.CalcEndTime(DateTime.Now,
+                new TimeConfig(dto.External.TimeLimit.Value, dto.External.TimeLimitUnit.Value), order.AcceptTypeCode);
+            if (dto.FlowDirection is EFlowDirection.CenterToOrg)
+            {
+                order.CenterToOrg(expiredTimeConfig.TimeText, expiredTimeConfig.Count,
+                    expiredTimeConfig.TimeType, expiredTimeConfig.ExpiredTime, expiredTimeConfig.NearlyExpiredTime);
+                //写入质检
+                await _qualityApplication.AddQualityAsync(EQualitySource.Send, order.Id, HttpContext.RequestAborted);
+            }
+            else if(dto.FlowDirection is EFlowDirection.OrgToCenter)
+            {
+                order.OrgToCenter(expiredTimeConfig.TimeText, expiredTimeConfig.Count,
+                    expiredTimeConfig.TimeType, expiredTimeConfig.ExpiredTime, expiredTimeConfig.NearlyExpiredTime);
+            }
+        }
+
         await _workflowApplication.NextAsync(dto, HttpContext.RequestAborted);
     }
 
@@ -2572,7 +2598,7 @@ public class OrderController : BaseController
             .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
             .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
             .Where(x => x.Source < ESource.MLSQ || x.Source > ESource.WZSC)
-            .Where(x=>x.Status!= EOrderStatus.BackToProvince)
+            .Where(x => x.Status != EOrderStatus.BackToProvince)
             .OrderByDescending(d => d.StartTime)
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
 
@@ -2619,7 +2645,7 @@ public class OrderController : BaseController
     {
         var oneSendBack = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.OneOrgSendBack)?.SettingValue[0]);
         var twoSendBack = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.TwoOrgSendBack)?.SettingValue[0]);
-		if (oneSendBack || twoSendBack)
+        if (oneSendBack || twoSendBack)
         {
             var workflow =
                 await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true, cancellationToken: HttpContext.RequestAborted);
@@ -2633,19 +2659,19 @@ public class OrderController : BaseController
             if (prevStep == null)
                 throw UserFriendlyException.SameMessage("未查询到前一节点");
 
-            var sendBack = await _orderSendBackAuditRepository.Queryable().Where(x => x.OrderId == workflow.ExternalId && x.State == ESendBackAuditState.Apply ).AnyAsync();
+            var sendBack = await _orderSendBackAuditRepository.Queryable().Where(x => x.OrderId == workflow.ExternalId && x.State == ESendBackAuditState.Apply).AnyAsync();
             if (sendBack)
-	            throw UserFriendlyException.SameMessage("当前工单已经生成退回记录");
+                throw UserFriendlyException.SameMessage("当前工单已经生成退回记录");
 
             var order = await _orderRepository
-	            .Queryable()
-	            .Includes(d => d.Workflow)
-	            .FirstAsync(d => d.Id == workflow.ExternalId);
+                .Queryable()
+                .Includes(d => d.Workflow)
+                .FirstAsync(d => d.Id == workflow.ExternalId);
             if (order.Workflow.IsInCountersign) throw UserFriendlyException.SameMessage("工单会签中,无法进行退回!");
 
-			var applyOrg = _organizeRepository.Get(currentStep.AcceptorOrgId);
+            var applyOrg = _organizeRepository.Get(currentStep.AcceptorOrgId);
             var sendBackOrg = _organizeRepository.Get(prevStep.AcceptorOrgId);
-            if ((twoSendBack && 2.Equals(applyOrg.Level) && 1.Equals(sendBackOrg.Level) && !sendBackOrg.IsCenter) 
+            if ((twoSendBack && 2.Equals(applyOrg.Level) && 1.Equals(sendBackOrg.Level) && !sendBackOrg.IsCenter)
                 || (oneSendBack && 1.Equals(applyOrg.Level) && sendBackOrg.IsCenter && !applyOrg.IsCenter))
             {
                 var audit = new OrderSendBackAudit
@@ -2728,7 +2754,7 @@ public class OrderController : BaseController
             .WhereIF(dto.AuditState == 1, d => d.State == ESendBackAuditState.Apply)
             .WhereIF(dto.AuditState == 2 && !dto.State.HasValue, d => d.State > ESendBackAuditState.Apply)
             .WhereIF(dto.AuditState == 2 && dto.State.HasValue, d => d.State == dto.State)
-			.Where(x=>x.SendBackOrgId == _sessionContext.OrgId)
+            .Where(x => x.SendBackOrgId == _sessionContext.OrgId)
             .OrderByDescending(x => x.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
@@ -2781,7 +2807,7 @@ public class OrderController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpPost("send_back/batch")]
-    public async Task<object> BatchApplyUrge([FromBody]BatchOrderSendBackAddDto dto)
+    public async Task<object> BatchApplyUrge([FromBody] BatchOrderSendBackAddDto dto)
     {
         int count = dto.OrderIds.Count;
         int successCount = 0;
@@ -2806,10 +2832,11 @@ public class OrderController : BaseController
                 errorCount++;
                 continue;
             }
-            var model = new OrderSendBack() { 
-                 Content = dto.Content,
-                 OrderId = item,
-                 Destination = ESendBackDestination.Province
+            var model = new OrderSendBack()
+            {
+                Content = dto.Content,
+                OrderId = item,
+                Destination = ESendBackDestination.Province
             };
             await _orderSendBackRepository.AddAsync(model, HttpContext.RequestAborted);
             if (!string.IsNullOrEmpty(model.Id))
@@ -2892,7 +2919,7 @@ public class OrderController : BaseController
             var sendBackDto = _mapper.Map<OrderSendBackDto>(sendBack);
             var OrderDto = _mapper.Map<OrderDto>(order);
             //await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFlowPrevious,
-              //      new PublishOrderSendBackDto() { Order = OrderDto, SendBack = sendBackDto, ClientGuid = "" });
+            //      new PublishOrderSendBackDto() { Order = OrderDto, SendBack = sendBackDto, ClientGuid = "" });
             //try
             //{
             //    await _provinceService.GetCaseBackApply(

+ 83 - 38
src/Hotline.Api/Controllers/SchedulingController.cs

@@ -38,7 +38,7 @@ namespace Hotline.Api.Controllers
 		/// <param name="dtos"></param>
 		/// <returns></returns>
 		[HttpPost("user")]
-		public async Task Add([FromBody] List<SchedulingUserDto> dtos)
+		public async Task Add([FromBody] List<UserAddDto> dtos)
 		{
 			List<SchedulingUser> user = new List<SchedulingUser>();
 			foreach (var dto in dtos)
@@ -139,7 +139,7 @@ namespace Hotline.Api.Controllers
 		/// <param name="dtos"></param>
 		/// <returns></returns>
 		[HttpPost("shift")]
-		public async Task Add([FromBody] SchedulingShiftDto dto)
+		public async Task Add([FromBody] ShiftAddDto dto)
 		{
 			var model = _mapper.Map<SchedulingShift>(dto);
 			await _schedulingShiftRepository.AddAsync(model, HttpContext.RequestAborted);
@@ -167,11 +167,11 @@ namespace Hotline.Api.Controllers
 		[HttpPut("shift")]
 		public async Task Update([FromBody] ShiftUpdateDto dto)
 		{
-			var user = await _schedulingShiftRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
-			if (user is null)
+			var shift = await _schedulingShiftRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+			if (shift is null)
 				throw UserFriendlyException.SameMessage("无效班次信息");
-			_mapper.Map(dto, user);
-			await _schedulingShiftRepository.UpdateAsync(user, HttpContext.RequestAborted);
+			_mapper.Map(dto, shift);
+			await _schedulingShiftRepository.UpdateAsync(shift, HttpContext.RequestAborted);
 		}
 
 
@@ -205,30 +205,75 @@ namespace Hotline.Api.Controllers
 
 		#region 排班管理
 		/// <summary>
-		/// 新增排班人员
+		/// 新增排班管理
 		/// </summary>
 		/// <param name="dtos"></param>
 		/// <returns></returns>
 		[HttpPost]
-		public async Task Add([FromBody] List<SchedulingDto> dtos)
+		public async Task Add([FromBody] AddDto dtos)
 		{
 			List<Scheduling> schedulings = new List<Scheduling>();
-			foreach (var dto in dtos)
+			
+			if (string.IsNullOrEmpty(dtos.ShiftId))
+				throw UserFriendlyException.SameMessage("请传入排班班次信息");
+			var shift = await _schedulingShiftRepository.GetAsync(dtos.ShiftId, HttpContext.RequestAborted);
+			if (shift  == null)
+				throw UserFriendlyException.SameMessage("传入排班班次信息错误");
+			foreach (var Id in dtos.UserIds)
 			{
-				if (string.IsNullOrEmpty(dto.SchedulingUserId))
+				if (string.IsNullOrEmpty(Id))
 					throw UserFriendlyException.SameMessage("请传入排班用户信息");
-				if (string.IsNullOrEmpty(dto.ShiftId))
-					throw UserFriendlyException.SameMessage("请传入排班班次信息");
+				var user = await _schedulingUserRepository.GetAsync(Id, HttpContext.RequestAborted);
+				if (user == null)
+					throw UserFriendlyException.SameMessage("传入排班用户信息错误");
 
-				var scheduling = _mapper.Map<Scheduling>(dto);
-				scheduling.SendOrderNum = 0;
-				schedulings.Add(scheduling);
+				if (dtos.SchedulingStartTime.HasValue  && dtos.SchedulingEndTime.HasValue)
+				{
+					for (int i = 0; dtos.SchedulingStartTime.Value.AddDays(i) <= dtos.SchedulingEndTime.Value; i++)
+					{
+						var schedulingTime = dtos.SchedulingStartTime.Value.AddDays(i);
+						var oldScheduling = await _schedulingRepository.Queryable().Where(x => x.SchedulingUserId == user.UserId && x.ShiftId == dtos.ShiftId && x.SchedulingTime == schedulingTime).AnyAsync();
+						if (!oldScheduling)
+						{
+							var scheduling = new Scheduling
+							{
+								SchedulingUserId = user.UserId,
+								SchedulingUserName = user.UserName,
+								ShiftId = dtos.ShiftId,
+								ShiftName = dtos.ShiftName,
+								SchedulingTime = schedulingTime,
+								WorkingTime = shift.WorkingTime,
+								OffDutyTime = shift.OffDutyTime,
+								SendOrderNum = 0
+							};
+							schedulings.Add(scheduling);
+						}
+					}
+                }
+				else {
+					var oldScheduling = await _schedulingRepository.Queryable().Where(x => x.SchedulingUserId == user.UserId && x.ShiftId == dtos.ShiftId && x.SchedulingTime == dtos.SchedulingTime).AnyAsync();
+					if (!oldScheduling)
+					{
+						var scheduling = new Scheduling
+						{
+							SchedulingUserId = user.UserId,
+							SchedulingUserName = user.UserName,
+							ShiftId = dtos.ShiftId,
+							ShiftName = dtos.ShiftName,
+							SchedulingTime = dtos.SchedulingTime,
+							WorkingTime = shift.WorkingTime,
+							OffDutyTime = shift.OffDutyTime,
+							SendOrderNum = 0
+						};
+						schedulings.Add(scheduling);
+					}
+				}
 			}
 			await _schedulingRepository.AddRangeAsync(schedulings, HttpContext.RequestAborted);
 		}
 
 		/// <summary>
-		/// 删除排班人员
+		/// 删除排班管理
 		/// </summary>
 		/// <param name="dto"></param>
 		/// <returns></returns>
@@ -242,7 +287,7 @@ namespace Hotline.Api.Controllers
 		}
 
 		/// <summary>
-		/// 更新排班人员
+		/// 更新排班管理
 		/// </summary>
 		/// <param name="dto"></param>
 		/// <returns></returns>
@@ -256,29 +301,29 @@ namespace Hotline.Api.Controllers
 			await _schedulingRepository.UpdateAsync(scheduling, HttpContext.RequestAborted);
 		}
 
-		/// <summary>
-		/// 批量更新排班人员
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpPut("batch")]
-		public async Task Update([FromBody] List<UpdateDto> dto)
-		{
-			List<Scheduling> schedulings = new List<Scheduling>();
-			foreach (var item in dto)
-			{
-				var scheduling = await _schedulingRepository.GetAsync(item.Id, HttpContext.RequestAborted);
-				if (scheduling is null)
-					throw UserFriendlyException.SameMessage("无效排班信息");
-				_mapper.Map(dto, scheduling);
-				schedulings.Add(scheduling);
-			}
-			await _schedulingRepository.UpdateRangeAsync(schedulings, HttpContext.RequestAborted);
-		}
+		///// <summary>
+		///// 批量更新排班管理
+		///// </summary>
+		///// <param name="dto"></param>
+		///// <returns></returns>
+		//[HttpPut("batch")]
+		//public async Task Update([FromBody] List<UpdateDto> dto)
+		//{
+		//	List<Scheduling> schedulings = new List<Scheduling>();
+		//	foreach (var item in dto)
+		//	{
+		//		var scheduling = await _schedulingRepository.GetAsync(item.Id, HttpContext.RequestAborted);
+		//		if (scheduling is null)
+		//			throw UserFriendlyException.SameMessage("无效排班信息");
+		//		_mapper.Map(dto, scheduling);
+		//		schedulings.Add(scheduling);
+		//	}
+		//	await _schedulingRepository.UpdateRangeAsync(schedulings, HttpContext.RequestAborted);
+		//}
 
 
 		/// <summary>
-		/// 获取排班人员列表
+		/// 获取排班管理列表
 		/// </summary>
 		/// <param name="dto"></param>
 		/// <returns></returns>
@@ -297,7 +342,7 @@ namespace Hotline.Api.Controllers
 		}
 
 		/// <summary>
-		/// 获取排班人员实体
+		/// 获取排班管理实体
 		/// </summary>
 		/// <param name="id"></param>
 		/// <returns></returns>

+ 1 - 1
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -555,7 +555,7 @@ public class WorkflowController : BaseController
             .Includes(x=>x.Members)
             .LeftJoin<Workflow>((c, w) => c.WorkflowId == w.Id)
             .InnerJoin<Order>((c, w, o) => w.ExternalId == o.Id)
-            .WhereIF(!_sessionContext.OrgIsCenter,x=>x.Members.Any(x=>x.Key== _sessionContext.OrgId))
+            .WhereIF(!_sessionContext.OrgIsCenter, (c, w, o) => c.Members.Any(m=>m.Key== _sessionContext.OrgId))
             .WhereIF(dto.IsProvince.HasValue, (c, w, o) => o.IsProvince == dto.IsProvince.Value)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
                 (c, w, o) => o.No.Contains(dto.Keyword) || o.Title.Contains(dto.Keyword));

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

@@ -261,7 +261,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     public async Task PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
-            cancellationToken: cancellationToken);
+            withCountersigns: true, cancellationToken: cancellationToken);
         User user = await _userRepository.Queryable().Includes(x => x.Organization)
             .Where(x => x.Id == _sessionContext.RequiredUserId).FirstAsync();
         await _workflowDomainService.PreviousAsync(workflow, dto, user, cancellationToken);
@@ -273,7 +273,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     public async Task OrderPreviousAsync(PreviousWorkflowDto dto, string userId, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
-            cancellationToken: cancellationToken);
+           withCountersigns: true, cancellationToken: cancellationToken);
         User user = await _userRepository.Queryable().Includes(x => x.Organization).Where(x => x.Id == userId).FirstAsync();
         await _workflowDomainService.PreviousAsync(workflow, dto, user, cancellationToken);
     }

+ 13 - 3
src/Hotline.Share/Dtos/Schedulings/SchedulingDto.cs

@@ -42,12 +42,12 @@ namespace Hotline.Share.Dtos.Schedulings
 		/// <summary>
 		/// 上班时间
 		/// </summary>
-		public DateTime? WorkingTime { get; set; }
+		public TimeSpan? WorkingTime { get; set; }
 
 		/// <summary>
 		/// 下班时间
 		/// </summary>
-		public DateTime? OffDutyTime { get; set; }
+		public TimeSpan? OffDutyTime { get; set; }
 
 		/// <summary>
 		/// 排班时间
@@ -56,7 +56,7 @@ namespace Hotline.Share.Dtos.Schedulings
 	}
 	public class AddDto
 	{
-		List<SchedulingUserDto> UserDtos { get; set; }
+		public List<string> UserIds { get; set; }
 
 		/// <summary>
 		/// 班次名称
@@ -72,6 +72,16 @@ namespace Hotline.Share.Dtos.Schedulings
 		/// 排班时间
 		/// </summary>
 		public DateTime? SchedulingTime { get; set; }
+
+		/// <summary>
+		/// 排班开始时间
+		/// </summary>
+		public DateTime? SchedulingStartTime { get; set; }
+
+		/// <summary>
+		/// 排班结束时间
+		/// </summary>
+		public DateTime? SchedulingEndTime { get; set; }
 	}
 	public class UpdateDto : AddDto
 	{

+ 4 - 4
src/Hotline.Share/Dtos/Schedulings/SchedulingShiftDto.cs

@@ -17,12 +17,12 @@ namespace Hotline.Share.Dtos.Schedulings
 		/// <summary>
 		/// 上班时间
 		/// </summary>
-		public DateTime? WorkingTime { get; set; }
+		public TimeSpan? WorkingTime { get; set; }
 
 		/// <summary>
 		/// 下班时间
 		/// </summary>
-		public DateTime? OffDutyTime { get; set; }
+		public TimeSpan? OffDutyTime { get; set; }
 	}
 
 	public class ShiftAddDto
@@ -35,12 +35,12 @@ namespace Hotline.Share.Dtos.Schedulings
 		/// <summary>
 		/// 上班时间
 		/// </summary>
-		public DateTime? WorkingTime { get; set; }
+		public TimeSpan? WorkingTime { get; set; }
 
 		/// <summary>
 		/// 下班时间
 		/// </summary>
-		public DateTime? OffDutyTime { get; set; }
+		public TimeSpan? OffDutyTime { get; set; }
 	}
 	public class ShiftUpdateDto : ShiftAddDto
 	{

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

@@ -35,7 +35,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 查询工作流包含当前用户办理权限(是否可办理)
         /// </summary>
-        Task<(Workflow, string?)> GetWorkflowHandlePermissionAsync(string workflowId, string userId, string orgCode,
+        Task<(Workflow workflow, string? countersignId, bool canPrevious)> GetWorkflowHandlePermissionAsync(string workflowId, string userId, string orgId,
             CancellationToken cancellationToken = default);
 
         /// <summary>

+ 10 - 0
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -1,6 +1,7 @@
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Enums.Order;
 using SqlSugar;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
@@ -44,6 +45,11 @@ public abstract class StepBasicEntity : CreationEntity
     /// </summary>
     public bool IsActualHandled { get; set; }
 
+    /// <summary>
+    /// 节点超期状态
+    /// </summary>
+    public EExpiredStatus? ExpiredStatus { get; set; }
+
     #region 接办
 
     /// <summary>
@@ -286,6 +292,10 @@ public abstract class StepBasicEntity : CreationEntity
 
         if (!string.IsNullOrEmpty(opinion))
             Opinion = opinion;
+
+        ExpiredStatus = HandleTime > StepExpiredTime
+            ? EExpiredStatus.Expired
+            : EExpiredStatus.Normal;
     }
 
     #endregion

+ 86 - 29
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -250,17 +250,29 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 查询工作流包含当前用户结束会签权限(是否可结束)
         /// </summary>
-        public async Task<(Workflow, string?)> GetWorkflowHandlePermissionAsync(
-            string workflowId, string userId, string orgCode, CancellationToken cancellationToken = default)
+        public async Task<(Workflow, string?, bool)> GetWorkflowHandlePermissionAsync(
+            string workflowId, string userId, string orgId, CancellationToken cancellationToken = default)
         {
             var workflow = await GetWorkflowAsync(workflowId, withSteps: true, withCountersigns: true,
                 cancellationToken: cancellationToken);
+
+            var canPrevious = false;
+            if (workflow.CanHandle(userId, orgId))
+            {
+                var currentStep = FindCurrentStepWaitForHandle(workflow, userId, orgId);
+                if (currentStep.Status is not EWorkflowStepStatus.Handled)
+                {
+                    canPrevious = !(currentStep.IsInCountersign() &&
+                                    !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId));
+                }
+            }
+
             var unCompletedCountersign = workflow.Countersigns.FirstOrDefault(d => !d.IsCompleted() && d.StarterId == userId);
-            if (unCompletedCountersign is null) return (workflow, null);
+            if (unCompletedCountersign is null) return (workflow, null, canPrevious);
 
             var existCountersignEndStep = workflow.Steps.Exists(d =>
                 d.IsCountersignEndStep && d.CountersignStartStepId == unCompletedCountersign.StartStepId);
-            return (workflow, existCountersignEndStep ? null : unCompletedCountersign.Id);
+            return (workflow, existCountersignEndStep ? null : unCompletedCountersign.Id, canPrevious);
         }
 
         /// <summary>
@@ -455,9 +467,16 @@ namespace Hotline.FlowEngine.Workflows
             ////if (isCenterToOrg)
             ////    workflow.CenterToOrg(CalculateExpiredTime(workflow.WorkflowDefinition.Code));//todo 过期时间
 
+            //todo 如果是工单办理类型 并且参数传入期满时间有值, 将值赋予workflow
+            //if (workflow.FlowType is EFlowType.Handle && )
+            //{
+            //    workflow.NearlyExpiredTime =
+            //workflow.ExpiredTime =
+            //}
+
             //创建下一/N个节点(会签汇总节点:会签未全部办理时不创建,最后一个会签办理节点创建会签汇总节点)
             var nextSteps = await CreateNextStepsAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic,
-                flowAssignInfo, cancellationToken);
+                    flowAssignInfo, cancellationToken);
 
             //赋值当前节点的下级办理节点
             if (dto.IsStartCountersign
@@ -550,15 +569,18 @@ namespace Hotline.FlowEngine.Workflows
             if (workflow.FlowType is EFlowType.Review && currentStep.StepType is EStepType.Start && currentStep.IsOrigin)
                 throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
 
+            //当退回操作遇到会签时,删除所有会签节点直达topCsStep
+            var shouldCountersignEnd = false;
             //find prevStep, update handler
-            WorkflowStep? prevStep;
+            WorkflowStep? prevStep, countersignStartStep = null;
             if (isCurrentTopCountersignEndStep)
             {
-                //todo end countersign
                 //prev is topstart's prev
-                var countersignStartStep =
-                    workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+                countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+                if (countersignStartStep is null)
+                    throw new UserFriendlyException("未查询到对应会签开始节点");
                 prevStep = workflow.Steps.FirstOrDefault(d => d.Id == countersignStartStep.PrevStepId);
+                shouldCountersignEnd = true;
             }
             else
             {
@@ -567,25 +589,48 @@ namespace Hotline.FlowEngine.Workflows
             if (prevStep == null)
                 throw UserFriendlyException.SameMessage("未查询到前一节点");
 
-            if(prevStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            if (prevStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            {
+                countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == prevStep.CountersignStartStepId);
+                if (countersignStartStep is null)
+                    throw new UserFriendlyException("未查询到对应会签开始节点");
                 prevStep = workflow.Steps.FirstOrDefault(d => d.Id == prevStep.CountersignStartStepId);
+                shouldCountersignEnd = true;
+            }
             if (prevStep == null)
                 throw UserFriendlyException.SameMessage("未查询到会签发起节点");
 
+            // add prev current to remove list
             var removeSteps = new List<WorkflowStep> { currentStep, prevStep };
-            //todo remove steps after prevStep
-
 
-            //1. remove steps between cs end to start except startStep 2. prev == start
-            if (prevStep.IsCountersignEndStep)
+            if (shouldCountersignEnd)
             {
-                var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == prevStep.CountersignStartStepId);
-                if (countersignStartStep is null)
-                    throw new UserFriendlyException("未查询到对应会签开始节点");
-                SearchCsSteps(countersignStartStep, prevStep, workflow.Steps, ref removeSteps);
-                removeSteps.Add(prevStep);
+                //add cs steps to remove list
+                SearchCountersignSteps(countersignStartStep!, workflow.Steps, ref removeSteps);
+
+                //end cs
+                var currentCountersign =
+                    workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId);
+                if (currentCountersign is null)
+                    throw new UserFriendlyException(
+                        $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}",
+                        "无效会签编号");
+
+                //结束step会签信息
+                countersignStartStep.CountersignEnd();
+                await _workflowStepRepository.UpdateAsync(countersignStartStep, cancellationToken);
+                //updateSteps.Add(countersignStartStep);
+
+                //结束会签
+                currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
+                    _sessionContext.RequiredUserId, _sessionContext.UserName,
+                    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
 
-                prevStep = countersignStartStep;
+                //update workflow cs status
+                if (workflow.CheckIfCountersignOver())
+                    workflow.EndCountersign();
             }
 
             //update trace
@@ -600,16 +645,9 @@ namespace Hotline.FlowEngine.Workflows
             if (workflow.Status is EWorkflowStatus.Completed)
                 workflow.SetStatusRunnable();
 
-            //更新当前办理节点信息
-            //workflow.UpdateWorkflowCurrentStepInfo(false,
-            //    operater.Id, operater.Name,
-            //    operater.OrgId, operater.Organization.Name,
-            //    operater.Organization.AreaCode, operater.Organization.AreaName,
-            //    nextStep: newPrevStep);
-            //workflow.UpdateActualStepWhenAssign(newPrevStep, prevStep.Handlers.First(), prevStep.HandlerType);
+            //更新实际办理节点信息
             workflow.UpdateActualStepWhenAssign(newPrevStep, prevStep.HandlerOrgName, prevStep.HandlerOrgId);
 
-
             //更新流程可办理对象
             workflow.UpdatePreviousHandlers(operater.Id, operater.OrgId, prevStep);
 
@@ -622,7 +660,26 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         /// <summary>
-        /// 查找当前节点至结束节点之前的所有节点(不含头尾)
+        /// 查找当前会签内所有节点(含start,end)
+        /// </summary>
+        private void SearchCountersignSteps(WorkflowStep startStep, List<WorkflowStep> steps, ref List<WorkflowStep> csSteps)
+        {
+            if (startStep.IsStartCountersign)
+            {
+                var countersignSteps = steps.Where(d => d.CountersignId == startStep.StartCountersignId).ToList();
+                if (countersignSteps.Any())
+                {
+                    foreach (var countersignStep in countersignSteps)
+                    {
+                        SearchCountersignSteps(countersignStep, steps, ref csSteps);
+                    }
+                }
+            }
+            csSteps.Add(startStep);
+        }
+
+        /// <summary>
+        /// 查找当前节点至结束节点间的所有节点(不含头尾)
         /// </summary>
         private void SearchCsSteps(WorkflowStep step, WorkflowStep csEndStep,
             List<WorkflowStep> steps, ref List<WorkflowStep> removeSteps)

+ 5 - 5
src/Hotline/Schedulings/Scheduling.cs

@@ -46,19 +46,19 @@ namespace Hotline.Schedulings
 		/// <summary>
 		/// 上班时间
 		/// </summary>
-		[SugarColumn(ColumnDescription = "上班时间")]
-		public DateTime? WorkingTime { get; set; }
+		[SugarColumn(ColumnDataType = "time", ColumnDescription = "上班时间")]
+		public TimeSpan? WorkingTime { get; set; }
 
 		/// <summary>
 		/// 下班时间
 		/// </summary>
-		[SugarColumn(ColumnDescription = "下班时间")]
-		public DateTime? OffDutyTime { get; set; }
+		[SugarColumn(ColumnDataType = "time", ColumnDescription = "下班时间")]
+		public TimeSpan? OffDutyTime { get; set; }
 
 		/// <summary>
 		/// 排班时间
 		/// </summary>
-		[SugarColumn(ColumnDescription = "排班时间")]
+		[SugarColumn( ColumnDescription = "排班时间")]
 		public DateTime? SchedulingTime { get; set; }
 
 		/// <summary>

+ 4 - 4
src/Hotline/Schedulings/SchedulingShift.cs

@@ -21,13 +21,13 @@ namespace Hotline.Schedulings
 		/// <summary>
 		/// 上班时间
 		/// </summary>
-		[SugarColumn(ColumnDescription = "上班时间")]
-		public DateTime? WorkingTime { get; set; }
+		[SugarColumn(ColumnDataType = "time", ColumnDescription = "上班时间")]
+		public TimeSpan? WorkingTime { get; set; }
 
 		/// <summary>
 		/// 下班时间
 		/// </summary>
-		[SugarColumn(ColumnDescription = "下班时间")]
-		public DateTime? OffDutyTime { get; set; }
+		[SugarColumn(ColumnDataType = "time", ColumnDescription = "下班时间")]
+		public TimeSpan? OffDutyTime { get; set; }
 	}
 }