xf 1 tahun lalu
induk
melakukan
d31eb16f90
26 mengubah file dengan 292 tambahan dan 96 penghapusan
  1. 37 20
      src/Hotline.Api/Controllers/OrderController.cs
  2. 10 0
      src/Hotline.Api/Controllers/WorkflowController.cs
  3. 6 0
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  4. 43 0
      src/Hotline.Application/Handlers/FlowEngine/CancelHandler.cs
  5. 6 19
      src/Hotline.Application/Handlers/FlowEngine/RedoHandler.cs
  6. 3 3
      src/Hotline.Application/Handlers/Order/AddVisitNotifyHandler.cs
  7. 14 14
      src/Hotline.Repository.SqlSugar/BaseRepository.cs
  8. 12 5
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  9. 14 0
      src/Hotline.Repository.SqlSugar/Orders/OrderPublishRepository.cs
  10. 0 14
      src/Hotline.Repository.SqlSugar/Orders/OrderPublishedRepository.cs
  11. 2 2
      src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs
  12. 11 0
      src/Hotline.Share/Dtos/FlowEngine/CancelDto.cs
  13. 8 0
      src/Hotline.Share/Dtos/Order/QueryOrderDuplicateDto.cs
  14. 5 0
      src/Hotline/FlowEngine/Notifications/WorkflowNotify.cs
  15. 5 0
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  16. 31 2
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  17. 9 0
      src/Hotline/Orders/IOrderDomainService.cs
  18. 8 0
      src/Hotline/Orders/IOrderPublishRepository.cs
  19. 0 8
      src/Hotline/Orders/IOrderPublishedRepository.cs
  20. 1 1
      src/Hotline/Orders/IOrderVisitRepository.cs
  21. 25 3
      src/Hotline/Orders/Order.cs
  22. 34 0
      src/Hotline/Orders/OrderDomainService.cs
  23. 3 1
      src/Hotline/Orders/OrderPublish.cs
  24. 1 1
      src/Hotline/Orders/OrderScreen.cs
  25. 1 0
      src/Hotline/Orders/OrderUrge.cs
  26. 3 3
      src/Hotline/Orders/OrderVisit.cs

+ 37 - 20
src/Hotline.Api/Controllers/OrderController.cs

@@ -45,8 +45,8 @@ public class OrderController : BaseController
     private readonly ISessionContext _sessionContext;
     private readonly IMapper _mapper;
     private readonly IMediator _mediator;
-    private readonly IOrderPublishedRepository _orderPublishedRepository;
-    private readonly IOrderVisitedRepository _orderVisitedRepository;
+    private readonly IOrderPublishRepository _orderPublishRepository;
+    private readonly IOrderVisitRepository _orderVisitRepository;
     private readonly IOrderVisitedDetailRepository _orderVisitedDetailRepository;
     private readonly ICapPublisher _capPublisher;
     private readonly IOrderDelayRepository _orderDelayRepository;
@@ -67,8 +67,8 @@ public class OrderController : BaseController
         ISessionContext sessionContext,
         IMapper mapper,
         IMediator mediator,
-        IOrderPublishedRepository orderPublishedRepository,
-        IOrderVisitedRepository orderVisitedRepository,
+        IOrderPublishRepository orderPublishRepository,
+        IOrderVisitRepository orderVisitRepository,
         IOrderVisitedDetailRepository orderVisitedDetailRepository,
         ICapPublisher capPublisher,
         IOrderDelayRepository orderDelayRepository,
@@ -88,8 +88,8 @@ public class OrderController : BaseController
         _sessionContext = sessionContext;
         _mapper = mapper;
         _mediator = mediator;
-        _orderPublishedRepository = orderPublishedRepository;
-        _orderVisitedRepository = orderVisitedRepository;
+        _orderPublishRepository = orderPublishRepository;
+        _orderVisitRepository = orderVisitRepository;
         _orderVisitedDetailRepository = orderVisitedDetailRepository;
         _capPublisher = capPublisher;
         _orderDelayRepository = orderDelayRepository;
@@ -110,21 +110,21 @@ public class OrderController : BaseController
     public async Task<PagedDto<PublishDto>> PublishOrderList([FromQuery] QueryOrderPublishDto dto)
     {
         var (total, items) = await _orderRepository.Queryable()
-            .Includes(d => d.OrderPublished)
+            .Includes(d => d.OrderPublish)
             .Includes(d => d.Acceptor)
             .Where(x => x.Status == EOrderStatus.Filed)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
             .WhereIF(dto.PubState == EPubState.Pub, d => d.Status >= EOrderStatus.Published)
             .WhereIF(dto.PubState == EPubState.NoPub, d => d.Status < EOrderStatus.Published)
             .WhereIF(!string.IsNullOrEmpty(dto.PubMan), d => d.Acceptor.Name.Contains(dto.PubMan!) || d.Acceptor.StaffNo.Contains(dto.PubMan!))
-            .WhereIF(dto.PubRange == EPublicState.Pub, d => d.OrderPublished.PublishState)
-            .WhereIF(dto.PubRange == EPublicState.NoPub, d => !d.OrderPublished.PublishState)
+            .WhereIF(dto.PubRange == EPublicState.Pub, d => d.OrderPublish.PublishState)
+            .WhereIF(dto.PubRange == EPublicState.NoPub, d => !d.OrderPublish.PublishState)
             .WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptType))
             .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId))
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
-            .WhereIF(dto.FiledTimeStart.HasValue, d => d.OrderPublished.CreationTime >= dto.CreationTimeStart)
-            .WhereIF(dto.FiledTimeEnd.HasValue, d => d.OrderPublished.CreationTime <= dto.CreationTimeEnd)
+            .WhereIF(dto.FiledTimeStart.HasValue, d => d.OrderPublish.CreationTime >= dto.CreationTimeStart)
+            .WhereIF(dto.FiledTimeEnd.HasValue, d => d.OrderPublish.CreationTime <= dto.CreationTimeEnd)
             .OrderBy(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
@@ -147,6 +147,7 @@ public class OrderController : BaseController
 
         //新增发布工单
         OrderPublish orderPublish = new OrderPublish();
+        orderPublish.OrderId = order.Id;
         orderPublish.No = order.No;
         orderPublish.PublishState = dto.PublishState;
         orderPublish.ArrangeTitle = dto.ArrangeTitle;
@@ -155,7 +156,7 @@ public class OrderController : BaseController
         orderPublish.ProPublishState = dto.ProPublishState;
         orderPublish.FeedBackPhone = dto.FeedBackPhone;
         orderPublish.NoPubReason = dto.NoPubReason;
-        string id = await _orderPublishedRepository.AddAsync(orderPublish);
+        string id = await _orderPublishRepository.AddAsync(orderPublish);
         //新增回访信息
         var visitedDetail = new List<OrderVisitDetail>();
 
@@ -226,7 +227,7 @@ public class OrderController : BaseController
     [HttpGet("visit")]
     public async Task<PagedDto<OrderVisit>> QueryOrderVisitList([FromQuery] QueryOrderVisitDto dto)
     {
-        var (total, items) = await _orderVisitedRepository.Queryable()
+        var (total, items) = await _orderVisitRepository.Queryable()
             .Includes(x => x.Order)
             .Includes(x => x.Employee)
             .WhereIF(dto.VisitState == EVisitStateQuery.NoVisit, x => x.VisitState == Share.Enums.Order.EVisitState.WaitForVisit || x.VisitState == Share.Enums.Order.EVisitState.NoSatisfiedWaitForVisit)
@@ -245,7 +246,7 @@ public class OrderController : BaseController
     [HttpGet("visit/{id}")]
     public async Task<object> VisitInfo(string id)
     {
-        var orderVisit = await _orderVisitedRepository.Queryable()
+        var orderVisit = await _orderVisitRepository.Queryable()
            .Includes(x => x.Order)
            .Includes(x => x.Employee)
            .FirstAsync(x => x.Id == id, HttpContext.RequestAborted);
@@ -255,8 +256,8 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("未知工单回访");
         }
 
-        int visitCount = await _orderVisitedRepository.CountAsync(x => x.OrderId == orderVisit.OrderId && x.VisitState == Share.Enums.Order.EVisitState.Visited, HttpContext.RequestAborted);
-        int againCount = await _orderVisitedRepository.CountAsync(x => x.OrderId == orderVisit.OrderId && x.AgainState == EAgainState.DoAgain, HttpContext.RequestAborted);
+        int visitCount = await _orderVisitRepository.CountAsync(x => x.OrderId == orderVisit.OrderId && x.VisitState == Share.Enums.Order.EVisitState.Visited, HttpContext.RequestAborted);
+        int againCount = await _orderVisitRepository.CountAsync(x => x.OrderId == orderVisit.OrderId && x.AgainState == EAgainState.DoAgain, HttpContext.RequestAborted);
         var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
         var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
         var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner);
@@ -293,8 +294,8 @@ public class OrderController : BaseController
     [HttpPost("visit")]
     public async Task Visit([FromBody] VisitDto dto)
     {
-        //var visit = await _orderVisitedRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
-        var visit = await _orderVisitedRepository.Queryable()
+        //var visit = await _orderVisitRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        var visit = await _orderVisitRepository.Queryable()
             .Includes(d => d.Order)
             .FirstAsync(d => d.Id == dto.Id, HttpContext.RequestAborted);
         if (visit is null)
@@ -310,7 +311,7 @@ public class OrderController : BaseController
         visit.AgainState = dto.IsAgain ? EAgainState.NeedAgain : EAgainState.NoAgain;
         visit.EmployeeId = _sessionContext.UserId;
         visit.NowEvaluate = first.OrgProcessingResults;
-        //await _orderVisitedRepository.UpdateAsync(visit,HttpContext.RequestAborted);
+        //await _orderVisitRepository.UpdateAsync(visit,HttpContext.RequestAborted);
 
         //update order
         visit.Order.Visited(first.OrgProcessingResults.Id, first.OrgProcessingResults.Name);
@@ -320,7 +321,7 @@ public class OrderController : BaseController
         //await _orderVisitedDetailRepository.UpdateRangeAsync(visitDetails, HttpContext.RequestAborted);
         visit.VisitDetails = _mapper.Map<List<OrderVisitDetail>>(dto.VisitDetails);
 
-        _orderVisitedRepository.UpdateNav(visit);
+        _orderVisitRepository.UpdateNav(visit);
 
         var orderDto = _mapper.Map<OrderDto>(visit.Order);
 
@@ -565,6 +566,22 @@ public class OrderController : BaseController
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 
+    /// <summary>
+    /// 查询重复工单
+    /// </summary>
+    [HttpGet("duplicate")]
+    public async Task<PagedDto<OrderDto>> Query([FromQuery] QueryOrderDuplicateDto dto)
+    {
+        if (!dto.OrderIds.Any())
+            return new PagedDto<OrderDto>(0, new List<OrderDto>());
+
+        var (total, items) = await _orderRepository.Queryable()
+            .Where(d => dto.OrderIds.Contains(d.Id))
+            .OrderByDescending(d => d.CreationTime)
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
+    }
+
     /// <summary>
     /// 查询工单详情
     /// </summary>

+ 10 - 0
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -393,6 +393,16 @@ public class WorkflowController : BaseController
         await _workflowDomainService.TerminateAsync(dto, HttpContext.RequestAborted);
     }
 
+    /// <summary>
+    /// 撤销流程
+    /// </summary>
+    [HttpPost("cancel")]
+    public async Task Cancel([FromBody] CancelDto dto)
+    {
+        //todo
+        await _workflowDomainService.CancelAsync(dto, HttpContext.RequestAborted);
+    }
+
     /// <summary>
     /// 补充
     /// </summary>

+ 6 - 0
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -143,6 +143,9 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     public async Task RecallAsync(RecallDto dto, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+        
+        await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+
         var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
         //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
         var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
@@ -155,6 +158,9 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     public async Task JumpAsync(RecallDto dto, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+
+        await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+
         var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
         //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
         var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);

+ 43 - 0
src/Hotline.Application/Handlers/FlowEngine/CancelHandler.cs

@@ -0,0 +1,43 @@
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.WfModules;
+using Hotline.Orders;
+using MapsterMapper;
+using MediatR;
+
+namespace Hotline.Application.Handlers.FlowEngine;
+
+public class CancelHandler : INotificationHandler<CancelWorkflowNotify>
+{
+    private readonly IOrderDomainService _orderDomainService;
+    private readonly IOrderRepository _orderRepository;
+    private readonly IMapper _mapper;
+
+    public CancelHandler(
+        IOrderDomainService orderDomainService,
+        IOrderRepository orderRepository,
+        IMapper mapper)
+    {
+        _orderDomainService = orderDomainService;
+        _orderRepository = orderRepository;
+        _mapper = mapper;
+    }
+
+    /// <summary>Handles a notification</summary>
+    /// <param name="notification">The notification</param>
+    /// <param name="cancellationToken">Cancellation token</param>
+    public async Task Handle(CancelWorkflowNotify notification, CancellationToken cancellationToken)
+    {
+        var workflow = notification.Workflow;
+
+        switch (workflow.ModuleCode)
+        {
+            case WorkflowModuleConsts.OrderHandle:
+                var order = await _orderDomainService.GetOrderAsync(workflow.ExternalId, cancellationToken);
+                order.CheckIfFiled();
+                order.Cancel();
+                _mapper.Map(workflow, order);
+                await _orderRepository.UpdateAsync(order, cancellationToken);
+                break;
+        }
+    }
+}

+ 6 - 19
src/Hotline.Application/Handlers/FlowEngine/RedoHandler.cs

@@ -11,6 +11,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Orders;
 using MapsterMapper;
+using XF.Domain.Exceptions;
 
 namespace Hotline.Application.Handlers.FlowEngine
 {
@@ -41,26 +42,12 @@ namespace Hotline.Application.Handlers.FlowEngine
             var workflow = notification.Workflow;
             var data = notification.Dto;
 
-            switch (workflow.ModuleCode)
-            {
-                case WorkflowModuleConsts.OrderHandle:
-                    var order = await _orderDomainService.GetOrderAsync(workflow.ExternalId, cancellationToken);
-                    _mapper.Map(workflow, order);
-                    await _orderRepository.UpdateAsync(order, cancellationToken);
+            var order = await _orderDomainService.GetOrderAsync(workflow.ExternalId, cancellationToken);
+            if (order is null)
+                throw new UserFriendlyException("无效工单编号");
 
-                    if (notification.IsOrgToCenter)
-                    {
-                        var dto = _mapper.Map<OrderDto>(order);
-                        await _capPublisher.PublishAsync(EventNames.HotlineOrderExpiredTimeUpdate, dto, cancellationToken: cancellationToken);
-                    }
-
-                    break;
-                case WorkflowModuleConsts.KnowledgeAdd:
-                case WorkflowModuleConsts.KnowledgeUpdate:
-                case WorkflowModuleConsts.KnowledgeDelete:
-                case WorkflowModuleConsts.TelRestApply:
-                    break;
-            }
+            order.Redo();
+            await _orderRepository.UpdateAsync(order, cancellationToken);
         }
     }
 }

+ 3 - 3
src/Hotline.Application/Handlers/Order/AddVisitNotifyHandler.cs

@@ -9,11 +9,11 @@ namespace Hotline.Application.Handlers.Order
     {
 
 
-        private readonly IOrderVisitedRepository _orderVisitedRepository;
+        private readonly IOrderVisitRepository _orderVisitRepository;
         private readonly IOrderVisitedDetailRepository _orderVisitedDetailRepository;
-        public AddVisitNotifyHandler(IOrderVisitedRepository orderVisitedRepository,IOrderVisitedDetailRepository orderVisitedDetailRepository)
+        public AddVisitNotifyHandler(IOrderVisitRepository orderVisitRepository,IOrderVisitedDetailRepository orderVisitedDetailRepository)
         {
-            _orderVisitedRepository = orderVisitedRepository;
+            _orderVisitRepository = orderVisitRepository;
             _orderVisitedDetailRepository = orderVisitedDetailRepository;
         }
 

+ 14 - 14
src/Hotline.Repository.SqlSugar/BaseRepository.cs

@@ -37,7 +37,7 @@ namespace Hotline.Repository.SqlSugar
         public async Task AddRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
         {
             entities.ForEach(d => d.InitDatePermission(_dataPermissionFilterBuilder.DataPermissionManager));
-            await Db.Insertable(entities).ExecuteCommandAsync();
+            await Db.Insertable(entities).ExecuteCommandAsync(cancellationToken);
         }
 
         public async Task RemoveAsync(TEntity entity, bool? soft = false, CancellationToken cancellationToken = default)
@@ -48,7 +48,7 @@ namespace Hotline.Repository.SqlSugar
             }
             else
             {
-                await Db.Deleteable(entity).ExecuteCommandAsync();
+                await Db.Deleteable(entity).ExecuteCommandAsync(cancellationToken);
             }
         }
 
@@ -60,7 +60,7 @@ namespace Hotline.Repository.SqlSugar
             }
             else
             {
-                await Db.Deleteable<TEntity>().In(id).ExecuteCommandAsync();
+                await Db.Deleteable<TEntity>().In(id).ExecuteCommandAsync(cancellationToken);
             }
         }
 
@@ -72,13 +72,13 @@ namespace Hotline.Repository.SqlSugar
             }
             else
             {
-                await Db.Deleteable<TEntity>().Where(predicate).ExecuteCommandAsync();
+                await Db.Deleteable<TEntity>().Where(predicate).ExecuteCommandAsync(cancellationToken);
             }
         }
 
         public async Task RemoveRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
         {
-            await Db.Deleteable<TEntity>(entities).ExecuteCommandAsync();
+            await Db.Deleteable<TEntity>(entities).ExecuteCommandAsync(cancellationToken);
         }
 
         public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
@@ -86,19 +86,19 @@ namespace Hotline.Repository.SqlSugar
             await Db.Updateable(entity)
                 .IgnoreColumns(ignoreAllNullColumns: true)
                 .IgnoreColumns(d => d.CreationTime)
-                .ExecuteCommandAsync();
+                .ExecuteCommandAsync(cancellationToken);
         }
 
         public async Task UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
         {
             await Db.Updateable(entities)
                 .IgnoreColumns(d => d.CreationTime)
-                .ExecuteCommandAsync();
+                .ExecuteCommandAsync(cancellationToken);
         }
 
         public async Task<TEntity?> GetAsync(string id, CancellationToken cancellationToken = default)
         {
-            return await Db.Queryable<TEntity>().FirstAsync(d => d.Id == id);
+            return await Db.Queryable<TEntity>().FirstAsync(d => d.Id == id, cancellationToken);
         }
 
         public TEntity Get(string id)
@@ -114,15 +114,15 @@ namespace Hotline.Repository.SqlSugar
 
         public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
         {
-            return await Db.Queryable<TEntity>().FirstAsync(predicate);
+            return await Db.Queryable<TEntity>().FirstAsync(predicate, cancellationToken);
         }
 
         public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, bool isDesc, Expression<Func<TEntity, object>> orderBy, CancellationToken cancellationToken = default)
         {
             if (isDesc)
-                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Desc).FirstAsync(predicate);
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Desc).FirstAsync(predicate, cancellationToken);
             else
-                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Asc).FirstAsync(predicate);
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Asc).FirstAsync(predicate, cancellationToken);
         }
 
 
@@ -143,10 +143,10 @@ namespace Hotline.Repository.SqlSugar
         public Task<bool> AnyAsync(CancellationToken cancellationToken = default) => Db.Queryable<TEntity>().AnyAsync();
 
         public Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
-             Db.Queryable<TEntity>().AnyAsync(predicate);
+             Db.Queryable<TEntity>().AnyAsync(predicate, cancellationToken);
 
         public Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
-             Db.Queryable<TEntity>().CountAsync(predicate);
+             Db.Queryable<TEntity>().CountAsync(predicate, cancellationToken);
 
         public ISugarQueryable<TEntity> Queryable(bool permissionVerify = false, bool includeDeleted = false)
         {
@@ -328,7 +328,7 @@ namespace Hotline.Repository.SqlSugar
             await Db.Updateable(entity)
                 .IgnoreColumns(ignoreAllNullColumns: ignoreNullColumns)
                 .IgnoreColumns(d => d.CreationTime)
-                .ExecuteCommandAsync();
+                .ExecuteCommandAsync(cancellationToken);
         }
     }
 }

+ 12 - 5
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -3,6 +3,7 @@ using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
 using System.Linq.Dynamic.Core;
 using System.Linq.Expressions;
+using System.Reflection;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
 using Hotline.SeedData.Codes;
@@ -62,11 +63,11 @@ namespace Hotline.Repository.SqlSugar.Extensions
                         //{
                         //    column.IsIgnore = true;
                         //}
-                        if (property.PropertyType.IsGenericType &&
-                            property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
-                        {
-                            column.IsNullable = true;
-                        }
+                        //if (property.PropertyType.IsGenericType &&
+                        //    property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                        //{
+                        //    column.IsNullable = true;
+                        //}
 
                         if (column.PropertyName.ToLower() == "id" ||
                             attributes.Any(it => it is KeyAttribute)) //是id的设为主键
@@ -79,6 +80,12 @@ namespace Hotline.Repository.SqlSugar.Extensions
                         //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
 
                         //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
+
+                        //统一设置 nullable等于isnullable=true
+                        if(!column.IsPrimarykey && new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
+                        {
+                            column.IsNullable = true;
+                        }
                     },
                     EntityNameService = (type, entity) =>
                     {

+ 14 - 0
src/Hotline.Repository.SqlSugar/Orders/OrderPublishRepository.cs

@@ -0,0 +1,14 @@
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Orders
+{
+    public class OrderPublishRepository : BaseRepository<OrderPublish>, IOrderPublishRepository, IScopeDependency
+    {
+        public OrderPublishRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+        {
+        }
+    }
+}

+ 0 - 14
src/Hotline.Repository.SqlSugar/Orders/OrderPublishedRepository.cs

@@ -1,14 +0,0 @@
-using Hotline.Orders;
-using Hotline.Repository.SqlSugar.DataPermissions;
-using SqlSugar;
-using XF.Domain.Dependency;
-
-namespace Hotline.Repository.SqlSugar.Orders
-{
-    public class OrderPublishedRepository : BaseRepository<OrderPublish>, IOrderPublishedRepository, IScopeDependency
-    {
-        public OrderPublishedRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
-        {
-        }
-    }
-}

+ 2 - 2
src/Hotline.Repository.SqlSugar/Orders/OrderVisitedRepository.cs → src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs

@@ -10,9 +10,9 @@ using XF.Domain.Dependency;
 
 namespace Hotline.Repository.SqlSugar.Orders
 {
-    public class OrderVisitedRepository : BaseRepository<OrderVisit>, IOrderVisitedRepository, IScopeDependency
+    public class OrderVisitRepository : BaseRepository<OrderVisit>, IOrderVisitRepository, IScopeDependency
     {
-        public OrderVisitedRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+        public OrderVisitRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
         {
         }
     }

+ 11 - 0
src/Hotline.Share/Dtos/FlowEngine/CancelDto.cs

@@ -0,0 +1,11 @@
+namespace Hotline.Share.Dtos.FlowEngine;
+
+public class CancelDto : EndWorkflowDto
+{
+    public string WorkflowId { get; set; }
+
+    /// <summary>
+    /// 是否短信通知
+    /// </summary>
+    public bool AcceptSms { get; set; }
+}

+ 8 - 0
src/Hotline.Share/Dtos/Order/QueryOrderDuplicateDto.cs

@@ -0,0 +1,8 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Order;
+
+public record QueryOrderDuplicateDto : PagedRequest
+{
+    public List<string> OrderIds { get; set; }
+}

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

@@ -40,6 +40,11 @@ public record EndWorkflowNotify(Workflow Workflow, WorkflowTrace Trace) : INotif
 
 public record TerminalWorkflowNotify(Workflow Workflow) : INotification;
 
+/// <summary>
+/// 撤销
+/// </summary>
+public record CancelWorkflowNotify(Workflow Workflow) : INotification;
+
 /// <summary>
 /// 工单最终办理通知(工单的实际办理部门办理完成时发出)
 /// </summary>

+ 5 - 0
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -101,5 +101,10 @@ namespace Hotline.FlowEngine.Workflows
         /// 依据配置过滤下一节点
         /// </summary>
         List<StepDefine> NextStepDefineFilter(EPathPolicy pathPolicy, List<StepDefine> nextStepDefines);
+
+        /// <summary>
+        /// 撤销流程
+        /// </summary>
+        Task CancelAsync(CancelDto dto, CancellationToken cancellationToken);
     }
 }

+ 31 - 2
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -5,7 +5,6 @@ using Hotline.Settings;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
-using Hotline.Share.Enums.Order;
 using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Logging;
@@ -412,6 +411,13 @@ namespace Hotline.FlowEngine.Workflows
 
             #endregion
 
+            #region 处理额外参数(短信通知、办理时限、省延期)
+
+            //需统一处理的放在这里,与业务关联的放在业务中去处理,如:省延期
+            //todo
+
+            #endregion
+
             await _mediator.Publish(new NextStepNotify(workflow, dto, trace,
                 isCenterToOrg, isStartCountersign, isCountersignOver,
                 _sessionContext.RequiredOrgCode, flowAssignInfo), cancellationToken);
@@ -521,6 +527,7 @@ namespace Hotline.FlowEngine.Workflows
                     EWorkflowTraceStatus.Jump, workflow.ExpiredTime, cancellationToken);
 
                 workflow.EndCountersign();
+                workflow.ResetOption();
 
                 //更新当前办理节点信息
                 workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, nextStep: targetStepBox.Steps.First());
@@ -585,6 +592,10 @@ namespace Hotline.FlowEngine.Workflows
             workflow.Redo();
             workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
 
+            //todo calc expiredTime
+            //dto.Extension.TimeLimitCount
+
+
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
             await _mediator.Publish(new RedoNotify(workflow, dto, isOrgToCenter), cancellationToken);
@@ -629,7 +640,7 @@ namespace Hotline.FlowEngine.Workflows
             //workflow.Terminate(dto.Opinion);
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-            await _mediator.Publish(new TerminalWorkflowNotify(workflow));
+            await _mediator.Publish(new TerminalWorkflowNotify(workflow), cancellationToken);
         }
 
         /// <summary>
@@ -719,6 +730,23 @@ namespace Hotline.FlowEngine.Workflows
             return nextStepDefines;
         }
 
+        /// <summary>
+        /// 撤销流程
+        /// </summary>
+        public async Task CancelAsync(CancelDto dto, CancellationToken cancellationToken)
+        {
+            var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true, cancellationToken: cancellationToken);
+
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
+
+            var endStepDefine = workflow.Definition.FindEndStepDefine();
+
+            var basicDto = _mapper.Map<BasicWorkflowDto>(dto);
+            var endTrace = await WorkflowEnd(workflow, basicDto, endStepDefine, currentStepBox, currentStep, cancellationToken);
+
+            await _mediator.Publish(new CancelWorkflowNotify(workflow), cancellationToken);
+        }
+
         #region private method
 
         /// <summary>
@@ -1096,6 +1124,7 @@ namespace Hotline.FlowEngine.Workflows
             }
 
             workflow.EndCountersign();
+            workflow.ResetOption();
 
             //recreate targetStep
             var targetStepBoxNew = await CreateStepAsync(false, workflow, targetStepDefine, dto, EWorkflowStepStatus.Assigned,

+ 9 - 0
src/Hotline/Orders/IOrderDomainService.cs

@@ -23,6 +23,15 @@ namespace Hotline.Orders
         /// </summary>
         Task FileAsync(string? orderId, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 撤回或跳转前处理数据及校验
+        /// <remarks>
+        ///工单撤回时需校验工单当前是否存在待发布记录、待回访记录,若存在需删除对应记录(跳转同理)
+        ///工单撤回时需校验工单是否存在甄别中记录,若存在不允许撤回当前工单(跳转同理)
+        /// </remarks>
+        /// </summary>
+        Task ReadyToRecallAsync(string orderId, CancellationToken cancellationToken);
+
         #endregion
 
         #region OrderRedo

+ 8 - 0
src/Hotline/Orders/IOrderPublishRepository.cs

@@ -0,0 +1,8 @@
+using XF.Domain.Repository;
+
+namespace Hotline.Orders
+{
+    public interface IOrderPublishRepository: IRepository<OrderPublish>
+    {
+    }
+}

+ 0 - 8
src/Hotline/Orders/IOrderPublishedRepository.cs

@@ -1,8 +0,0 @@
-using XF.Domain.Repository;
-
-namespace Hotline.Orders
-{
-    public interface IOrderPublishedRepository: IRepository<OrderPublish>
-    {
-    }
-}

+ 1 - 1
src/Hotline/Orders/IOrderVisitedRepository.cs → src/Hotline/Orders/IOrderVisitRepository.cs

@@ -2,7 +2,7 @@
 
 namespace Hotline.Orders
 {
-    public interface IOrderVisitedRepository: IRepository<OrderVisit>
+    public interface IOrderVisitRepository: IRepository<OrderVisit>
     {
     }
 }

+ 25 - 3
src/Hotline/Orders/Order.cs

@@ -265,6 +265,11 @@ namespace Hotline.Orders
         [SugarColumn(IsNullable = true)]
         public string? No110 { get; set; }
 
+        /// <summary>
+        /// 是否已撤销
+        /// </summary>
+        [SugarColumn(DefaultValue = "f")]
+        public bool IsCancel { get; set; }
 
         #endregion
 
@@ -460,6 +465,7 @@ namespace Hotline.Orders
 
         [SugarColumn(IsNullable = true, DefaultValue = "")]
         public string? FirstVisitResultCode { get; set; }
+
     }
 
     public partial class Order
@@ -498,16 +504,32 @@ namespace Hotline.Orders
         /// 已发布工单
         /// </summary>
         [Navigate(NavigateType.OneToOne, nameof(Id))]
-        public OrderPublish? OrderPublished { get; set; }
+        public OrderPublish? OrderPublish { get; set; }
 
         /// <summary>
         /// 已回访工单
         /// </summary>
-        [Navigate(NavigateType.OneToOne, nameof(Id))]
-        public OrderVisit? OrderVisited { get; set; }
+        [Navigate(NavigateType.OneToMany, nameof(OrderVisit.OrderId))]
+        public List<OrderVisit> OrderVisits { get; set; }
+
+
+        [Navigate(NavigateType.OneToMany, nameof(OrderScreen.OrderId))]
+        public List<OrderScreen> OrderScreens { get; set; }
 
         #region Method
 
+        public void Cancel()
+        {
+            IsCancel = true;
+            
+        }
+
+        public void Redo()
+        {
+            Status = EOrderStatus.Handling;
+            ExpiredStatus = EExpiredStatus.Normal;
+        }
+
         public void CheckIfFiled()
         {
             if (Status is EOrderStatus.Filed)

+ 34 - 0
src/Hotline/Orders/OrderDomainService.cs

@@ -7,6 +7,7 @@ using Hotline.Share.Mq;
 using MapsterMapper;
 using Microsoft.Extensions.Logging;
 using System.Threading;
+using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
@@ -21,6 +22,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     private const string OrderNoPrefix = "OrderNo:";
     private readonly IOrderRepository _orderRepository;
     private readonly IOrderRedoRepository _orderRedoRepository;
+    private readonly IOrderPublishRepository _orderPublishRepository;
+    private readonly IOrderVisitRepository _orderVisitRepository;
     private readonly ITypedCache<CacheOrderNO> _cacheOrderNo;
     private readonly ISessionContext _sessionContext;
     private readonly ICapPublisher _capPublisher;
@@ -30,6 +33,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     public OrderDomainService(
         IOrderRepository orderRepository,
         IOrderRedoRepository orderRedoRepository,
+        IOrderPublishRepository orderPublishRepository,
+        IOrderVisitRepository orderVisitRepository,
         ITypedCache<CacheOrderNO> cacheOrderNo,
         ISessionContext sessionContext,
         ICapPublisher capPublisher,
@@ -38,6 +43,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     {
         _orderRepository = orderRepository;
         _orderRedoRepository = orderRedoRepository;
+        _orderPublishRepository = orderPublishRepository;
+        _orderVisitRepository = orderVisitRepository;
         _cacheOrderNo = cacheOrderNo;
         _sessionContext = sessionContext;
         _capPublisher = capPublisher;
@@ -89,6 +96,33 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         await _orderRepository.UpdateAsync(order, cancellationToken);
     }
 
+    /// <summary>
+    /// 撤回或跳转前处理数据及校验
+    /// <remarks>
+    ///工单撤回时需校验工单当前是否存在待发布记录、待回访记录,若存在需删除对应记录(跳转同理)
+    ///工单撤回时需校验工单是否存在甄别中记录,若存在不允许撤回当前工单(跳转同理)
+    /// </remarks>
+    /// </summary>
+    public async Task ReadyToRecallAsync(string orderId, CancellationToken cancellationToken)
+    {
+        var order = await _orderRepository.Queryable()
+            .Includes(d => d.OrderPublish)
+            .Includes(d => d.OrderVisits.Where(d => d.VisitState == EVisitState.WaitForVisit))
+            .Includes(d => d.OrderScreens)
+            .FirstAsync(d => d.Id == orderId, cancellationToken);
+
+        if (order.OrderScreens.Any())
+            throw UserFriendlyException.SameMessage("已甄别工单无法撤回或跳转");
+
+        if (order.OrderPublish is not null)
+            await _orderPublishRepository.RemoveAsync(order.OrderPublish, cancellationToken: cancellationToken);
+
+        if (order.OrderVisits.Any())
+            await _orderVisitRepository.RemoveNav(order.OrderVisits)
+                .Include(d => d.VisitDetails)
+                .ExecuteCommandAsync();
+    }
+
     public Task<string> AddOrderRedoAsync(OrderRedo orderRedo, CancellationToken cancellationToken) =>
         _orderRedoRepository.AddAsync(orderRedo, cancellationToken);
 

+ 3 - 1
src/Hotline/Orders/OrderPublish.cs

@@ -9,6 +9,8 @@ namespace Hotline.Orders;
 /// </summary>
 public class OrderPublish : CreationEntity
 {
+    public string OrderId { get; set; }
+
     /// <summary>
     /// 工单编码(冗余)
     /// </summary>
@@ -38,7 +40,7 @@ public class OrderPublish : CreationEntity
     /// <summary>
     /// 已发布工单
     /// </summary>
-    [Navigate(NavigateType.OneToOne, nameof(Id))]
+    [Navigate(NavigateType.OneToOne, nameof(OrderId))]
     public Order Order { get; set; }
 
     #region 省工单使用字段

+ 1 - 1
src/Hotline/Orders/OrderScreenRecord.cs → src/Hotline/Orders/OrderScreen.cs

@@ -3,7 +3,7 @@ using XF.Domain.Repository;
 
 namespace Hotline.Orders
 {
-    public class OrderScreenRecord: CreationEntity
+    public class OrderScreen : CreationEntity
     {
         /// <summary>
         /// 工单编号

+ 1 - 0
src/Hotline/Orders/OrderUrge.cs

@@ -12,4 +12,5 @@ public class OrderUrge : CreationEntity
 
     [SugarColumn(IsNullable = true, ColumnDataType = "tsvector")]
     public NpgsqlTsVector? TsVector { get; set; }
+    
 }

+ 3 - 3
src/Hotline/Orders/OrderVisit.cs

@@ -78,13 +78,13 @@ public class OrderVisit : CreationEntity
     /// <summary>
     /// 回访明细
     /// </summary>
-    [SugarColumn(IsIgnore = true)]
-    public List<OrderVisitDetail>? VisitDetails { get; set; }
+    [Navigate(NavigateType.OneToMany, nameof(OrderVisitDetail.VisitId))]
+    public List<OrderVisitDetail> VisitDetails { get; set; }
 
     /// <summary>
     /// 当前评价结果
     /// </summary>
-    [SugarColumn(ColumnDataType = "json",IsJson = true,IsNullable = true)]
+    [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
     public IdName? NowEvaluate { get; set; }
 
 }