瀏覽代碼

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

TANG JIANG 1 年之前
父節點
當前提交
9b67396d66

+ 99 - 3
src/Hotline.Api/Controllers/KnowledgeController.cs

@@ -3,8 +3,8 @@ using Hotline.Application.Knowledge;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.KnowledgeBase;
 using Hotline.KnowledgeBase.Notifies;
-using Hotline.Orders;
 using Hotline.Permissions;
+using Hotline.Quality;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Repository.SqlSugar.Ts;
 using Hotline.Settings;
@@ -13,11 +13,14 @@ using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Knowledge;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.KnowledgeBase;
 using Hotline.Users;
 using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Utilities;
+using Polly.Caching;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
@@ -47,6 +50,7 @@ namespace Hotline.Api.Controllers
 		private readonly IRepository<KnowledgeCorrection> _knowledgeCorrectionRepository;
 		private readonly IRepository<KnowledgeCollect> _knowledgeCollectRepository;
 		private readonly ISystemDomainService _systemDomainService;
+		private readonly IRepository<KnowledgeComment> _knowledgeCommentRepository;
 
 
 		public KnowledgeController(
@@ -66,7 +70,8 @@ namespace Hotline.Api.Controllers
 		   IRepository<KnowledgeQuestions> knowledgeQuestionsRepository,
 		   IRepository<KnowledgeCorrection> knowledgeCorrectionRepository,
 		   IRepository<KnowledgeCollect> knowledgeCollectRepository,
-		   ISystemDomainService systemDomainService
+		   ISystemDomainService systemDomainService,
+		   IRepository<KnowledgeComment> knowledgeCommentRepository
 		   )
 		{
 			_knowledgeRepository = knowledgeRepository;
@@ -86,6 +91,7 @@ namespace Hotline.Api.Controllers
 			_knowledgeCorrectionRepository = knowledgeCorrectionRepository;
 			_knowledgeCollectRepository = knowledgeCollectRepository;
 			_systemDomainService = systemDomainService;
+			_knowledgeCommentRepository = knowledgeCommentRepository;
 		}
 
 		#endregion
@@ -166,6 +172,19 @@ namespace Hotline.Api.Controllers
 				throw UserFriendlyException.SameMessage("知识上架失败");
 		}
 
+		/// <summary>
+		/// 知识库-标题
+		/// </summary>
+		/// <param name="title"></param>
+		/// <returns></returns>
+		[HttpGet("title")]
+		public async Task<bool> KnowledgeTitle([FromQuery] KnowledgeTitleDto dto) {
+			var count = await _knowledgeRepository.Queryable()
+				.WhereIF(!string.IsNullOrEmpty(dto.Id),x=>x.Id != dto.Id)
+				.Where(x => x.Title == dto.Title).CountAsync();
+			return count > 0;
+		}
+
 		/// <summary>
 		/// 知识库-修改
 		/// </summary>
@@ -217,6 +236,8 @@ namespace Hotline.Api.Controllers
 			await StartFlow(delete.Id, WorkflowModuleConsts.KnowledgeDelete, EKnowledgeApplyType.Delete, startDto);
 		}
 
+		//public async Task SearchNum()
+
 		/// <summary>
 		/// 知识库-知识修改-查询详情
 		/// </summary>
@@ -259,7 +280,6 @@ namespace Hotline.Api.Controllers
 			var knowledge = await _knowledgeDomainService.KnowledgeInfo(Id, HttpContext.RequestAborted);
 			if (knowledge is null)
 				throw UserFriendlyException.SameMessage("知识查询失败!");
-
 			//转化
 			var knowledgeShowInfoDto = _mapper.Map<KnowledgeInfoDto>(knowledge);
 
@@ -1020,5 +1040,81 @@ namespace Hotline.Api.Controllers
 			}
 		}
 		#endregion
+
+		#region 知识评论
+
+		/// <summary>
+		/// 新增知识评论
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[Permission(EPermission.AddKnowledgeComment)]
+		[HttpPost("knowledge_comment")]
+		public async Task Add([FromBody] KnowledgeCommentAddDto dto)
+		{
+			var model = _mapper.Map<KnowledgeComment>(dto);
+			await _knowledgeCommentRepository.AddAsync(model, HttpContext.RequestAborted);
+			if (!string.IsNullOrEmpty(dto.ReplyId))
+			{
+				var comment = await _knowledgeCommentRepository.GetAsync(dto.ReplyId);
+				if (comment != null) 
+				{
+					comment.ReplyNum++;
+					await _knowledgeCommentRepository.UpdateAsync(comment, HttpContext.RequestAborted);
+				}
+			}
+		}
+
+		/// <summary>
+		/// 删除知识评论
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[Permission(EPermission.DeleteKnowledgeComment)]
+		[HttpDelete("knowledge_comment")]
+		public async Task Delete([FromBody] KnowledgeCommentDeleteDto dto)
+		{
+			var comment = await _knowledgeCommentRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+			if (comment is null)
+				throw UserFriendlyException.SameMessage("无效评论");
+			if (comment.CreatorId != _sessionContext.UserId)
+				throw UserFriendlyException.SameMessage("只有评论者可以删除当前评论");
+			await _knowledgeCommentRepository.RemoveAsync(x => x.Id == dto.Id);
+		}
+
+		/// <summary>
+		/// 修改知识评论
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[Permission(EPermission.UpdateKnowledgeComment)]
+		[HttpPut("knowledge_comment")]
+		public async Task Update([FromBody] KnowledgeCommentUpdateDto dto)
+		{
+			var comment = await _knowledgeCommentRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+			if (comment is null)
+				throw UserFriendlyException.SameMessage("无效评论");
+			_mapper.Map(dto, comment);
+			await _knowledgeCommentRepository.UpdateAsync(comment, HttpContext.RequestAborted);
+		}
+
+		/// <summary>
+		/// 知识评论列表
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[Permission(EPermission.KnowledgeCommentList)]
+		[HttpGet("knowledge_comment/list")]
+		public async Task<List<KnowledgeCommentDto>> List([FromQuery] KnowledgeCommentListDto dto)
+		{
+			var comments = await _knowledgeCommentRepository.Queryable()
+				.WhereIF(!string.IsNullOrEmpty(dto.KnowledgeId), x => x.KnowledgeId == dto.KnowledgeId)
+				.WhereIF(!string.IsNullOrEmpty(dto.ReplyId), x => x.ReplyId == dto.ReplyId)
+				.WhereIF(dto.All.HasValue && dto.All == false, x => x.CreatorId == _sessionContext.UserId)
+				.OrderByDescending(x => x.CreationTime)
+				.ToListAsync();
+			return new List<KnowledgeCommentDto>(_mapper.Map<IReadOnlyList<KnowledgeCommentDto>>(comments));
+		}
+		#endregion
 	}
 }

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

@@ -439,8 +439,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             if (currentStep.IsCountersignEndStep)
             {
-                //当前待办节点为会签汇总节点时:检查是否为顶级会签发起节点(csstate==none),t:按配置往下走,f:继续往上汇总,不需要重复往下指派
-                if (currentStep.CountersignStartStepId != workflow.TopCountersignStepId)
+                //当前待办节点为会签汇总节点时:检查是否为顶级会签汇总节点,t:按配置往下走,f:继续往上汇总,不需要重复往下指派
+                if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
                 {
                     //查找当前节点对应会签开始节点的上级作为下一个cs汇总节点的汇总对象
                     var startCountersignStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);

+ 133 - 0
src/Hotline.Share/Dtos/Knowledge/KnowledgeCommentDto.cs

@@ -0,0 +1,133 @@
+using Hotline.Share.Enums.Quality;
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Knowledge
+{
+	public class KnowledgeCommentDto : KnowledgeCommentBaseDto
+	{
+		/// <summary>
+		/// 知识库ID
+		/// </summary>
+		public string KnowledgeId { get; set; }
+
+		/// <summary>
+		/// 知识库
+		/// </summary>
+		public KnowledgeDto Knowledge { get; set; }
+
+		/// <summary>
+		/// 评论内容
+		/// </summary>
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 回复ID
+		/// </summary>
+		public string? ReplyId { get; set; }
+
+		/// <summary>
+		/// 匿名
+		/// </summary>
+		public bool Cryptonym { get; set; }
+
+		/// <summary>
+		/// 回复数
+		/// </summary>
+		public int ReplyNum { get; set; } = 0;
+	}
+
+	public class KnowledgeCommentAddDto
+	{
+		/// <summary>
+		/// 知识库ID
+		/// </summary>
+		public string KnowledgeId { get; set; }
+
+		/// <summary>
+		/// 知识库
+		/// </summary>
+		public KnowledgeDto Knowledge { get; set; }
+
+		/// <summary>
+		/// 评论内容
+		/// </summary>
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 回复ID
+		/// </summary>
+		public string? ReplyId { get; set; }
+
+		/// <summary>
+		/// 匿名
+		/// </summary>
+		public bool Cryptonym { get; set; }
+
+		/// <summary>
+		/// 回复数
+		/// </summary>
+		public int ReplyNum { get; set; } = 0;
+	}
+
+	public class KnowledgeCommentDeleteDto
+	{
+		public string Id { get; set; }
+	}
+
+	public class KnowledgeCommentUpdateDto : KnowledgeCommentAddDto
+	{
+		public string Id { get; set; }
+	}
+	public class KnowledgeCommentBaseDto
+	{
+		public DateTime? LastModificationTime { get; set; }
+
+		public bool IsDeleted { get; set; }
+
+		/// <summary>
+		/// 删除时间
+		/// </summary>
+		public DateTime? DeletionTime { get; set; }
+
+
+		/// <summary>
+		/// 创建时间
+		/// </summary>
+		public DateTime CreationTime { get; set; }
+
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 组织Id
+		/// </summary>
+		public string? CreatorOrgId { get; set; }
+
+
+		public string? CreatorOrgName { get; set; }
+
+		/// <summary>
+		/// 创建人
+		/// </summary>
+		public string? CreatorId { get; set; }
+
+		public string? CreatorName { get; set; }
+	}
+
+	public record KnowledgeCommentListDto : PagedKeywordRequest
+	{
+		/// <summary>
+		/// 知识库ID
+		/// </summary>
+		public string KnowledgeId { get; set; }
+
+		/// <summary>
+		/// 回复ID
+		/// </summary>
+		public string? ReplyId { get; set; }
+
+		/// <summary>
+		/// 查询全部
+		/// </summary>
+		public bool? All { get; set; }
+	}
+}

+ 13 - 0
src/Hotline.Share/Dtos/Knowledge/KnowledgeDto.cs

@@ -231,6 +231,19 @@ namespace Hotline.Share.Dtos.Knowledge
 		public string? CreatorName { get; set; }
 	}
 
+    public class KnowledgeTitleDto {
+
+		/// <summary>
+		/// ID
+		/// </summary>
+		public string? Id { get; set; }
+
+		/// <summary>
+		/// 标题
+		/// </summary>
+		public string Title { get; set; }
+
+	}
 
 
 	public class AddStartFlowDto : StartWorkflowDto<AddKnowledgeDto>

+ 54 - 409
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -238,8 +238,6 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 受理(接办)
         /// </summary>
-
-        //new
         public async Task AcceptAsync(Workflow workflow,
             string userId, string? userName,
             string orgId, string? orgName,
@@ -255,33 +253,6 @@ namespace Hotline.FlowEngine.Workflows
 
             if (currentStep.Handlers.All(d => d.Key != orgId && d.Key != userId)) return;
 
-            //if (currentStep.HandlerType is EHandlerType.AssignedOrg or EHandlerType.OrgLevel or EHandlerType.OrgType
-            //   || (currentStep.InstanceMode is EInstanceMode.Dynamic && !currentStep.DynamicShouldTerminal())//动态并且非结束节点
-            //       || (currentStep.IsInCountersign() && !currentStep.IsCountersignEndStep)//会签并且非会签节点
-            //       )
-            //{
-            //    //orgId
-            //    if (currentStep.Handlers.All(d => d.Key != orgId)) return;
-            //}
-            //else
-            //{
-            //    //userId
-            //    if (currentStep.Handlers.All(d => d.Key != userId)) return;
-            //}
-
-            //if (currentStep.HandlerType is EHandlerType.AssignedUser or EHandlerType.Role
-            //&& !currentStep.IsInCountersign()
-            //&& currentStep.InstanceMode is EInstanceMode.Config
-            //)
-            //{
-            //    //userId
-            //    if (currentStep.Handlers.All(d => d.Key != userId)) return;
-            //}
-            //else
-            //{
-            //    //orgId
-            //    if (currentStep.Handlers.All(d => d.Key != orgId)) return;
-            //}
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
 
@@ -318,6 +289,7 @@ namespace Hotline.FlowEngine.Workflows
         public async Task NextAsync(Workflow workflow, WorkflowStep currentStep, NextWorkflowDto dto, StepDefine nextStepDefine,
             FlowAssignInfo flowAssignInfo, DateTime expiredTime, CancellationToken cancellationToken)
         {
+            //todo 1.汇总节点的创建不能简单看上级节点发起的是否全办完
             ValidatePermission(workflow);
             CheckWhetherRunnable(workflow.Status);
 
@@ -367,13 +339,23 @@ namespace Hotline.FlowEngine.Workflows
             //操作为回到会签汇总时,更新开始会签节点的会签办理状态
             if (currentStep.IsInCountersign() && dto.BackToCountersignEnd)
             {
-                var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.StartCountersignId == currentStep.CountersignId);
+                var targetStep = currentStep;
+                if (currentStep.IsCountersignEndStep)
+                {
+                    var csStartStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+                    if (csStartStep is null)
+                        throw new UserFriendlyException("未查询到会签开始节点");
+                    targetStep = csStartStep;
+                }
+
+                var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == targetStep.PrevStepId);
                 if (countersignStartStep is null)
-                    throw new UserFriendlyException("未查询到会签开始节点");
-                if (!countersignStartStep.IsStartCountersign)
-                    throw new UserFriendlyException("查询到会签开始节点状态异常");
+                    throw new UserFriendlyException("未查询到目标节点的前一节点");
 
-                countersignStartStep.CountersignSteps.First(d => d.StepId == currentStep.Id).Completed = true;
+                var csStep = countersignStartStep.CountersignSteps.FirstOrDefault(d => d.StepId == targetStep.Id);
+                if (csStep is null)
+                    throw new UserFriendlyException("未查询到当前待办节点");
+                csStep.Completed = true;
                 updateSteps.Add(countersignStartStep);
             }
 
@@ -468,19 +450,18 @@ namespace Hotline.FlowEngine.Workflows
                 {
                     //todo check if current is topend f: csStartStep.prev
                     //todo t: check if dto.StartCs t: csconfig f: config
-                    if (currentStep.CountersignStartStepId == workflow.TopCountersignStepId)
+                    if (currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
                     {
                         if (dto.IsStartCountersign)
                         {
                             //todo 依据会签策略创建会签下一级节点
-                            nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
-                                EWorkflowStepStatus.WaitForAccept, currentStep.GetNextStepCountersignPosition(),
-                                expiredTime, false, cancellationToken);
+                            nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
+                                expiredTime, cancellationToken);
                         }
                         else
                         {
                             //todo 创建普通节点(根据配置)
-                            nextSteps = await CreateStepsByDefineAsync(workflow, nextStepDefine, currentStep, dto, expiredTime, cancellationToken);
+                            nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, expiredTime, cancellationToken);
                         }
                     }
                     else
@@ -504,48 +485,56 @@ namespace Hotline.FlowEngine.Workflows
                     else
                     {
                         //todo 依据会签策略创建会签下一级节点
-                        nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
-                            EWorkflowStepStatus.WaitForAccept, currentStep.GetNextStepCountersignPosition(),
-                            expiredTime, false, cancellationToken);
+                        nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
+                            expiredTime, cancellationToken);
                     }
                 }
             }
-            else if (dto.IsStartCountersign)
+            else if (dto.IsStartCountersign)//top
             {
                 //todo 依据会签策略创建会签下一级节点
-                nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
-                    EWorkflowStepStatus.WaitForAccept, currentStep.GetNextStepCountersignPosition(),
-                        expiredTime, false, cancellationToken);
+                nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
+                    expiredTime, cancellationToken);
             }
             else if (currentStep.InstanceMode is EInstanceMode.Dynamic && !currentStep.DynamicShouldTerminal())
             {
                 //todo 创建动态下一级节点
                 nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
-                    EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
+                    null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
                     expiredTime, false, cancellationToken);
             }
             else
             {
                 //todo 创建普通节点(根据配置)
-                nextSteps = await CreateStepsByDefineAsync(workflow, nextStepDefine, currentStep, dto, expiredTime, cancellationToken);
+                nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, expiredTime, cancellationToken);
             }
 
             return nextSteps;
         }
 
+        private Task<List<WorkflowStep>> CreateCountersignStepsAsync(
+            Workflow workflow,
+            StepDefine stepDefine,
+            WorkflowStep prevStep,
+            BasicWorkflowDto dto,
+            DateTime expiredTime,
+            CancellationToken cancellationToken
+        )
+        {
+            var countersignId = prevStep.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
+
+            return CreateStepsAsync(workflow, stepDefine, prevStep, dto, dto.NextHandlers, countersignId,
+                  EWorkflowStepStatus.WaitForAccept, prevStep.GetNextStepCountersignPosition(),
+                  expiredTime, false, cancellationToken);
+        }
+
         /// <summary>
         /// 根据传入节点的上一节点创建会签汇总节点(汇总传入节点的前一节点)
         /// </summary>
-        /// <param name="workflow"></param>
-        /// <param name="currentStep"></param>
-        /// <param name="dto"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        /// <exception cref="UserFriendlyException"></exception>
-        private async Task<List<WorkflowStep>> CreateCsEndStepsByPrevStepAsync(Workflow workflow, WorkflowStep currentStep, BasicWorkflowDto dto,
-            CancellationToken cancellationToken)
+        private async Task<List<WorkflowStep>> CreateCsEndStepsByPrevStepAsync(Workflow workflow, WorkflowStep step,
+            BasicWorkflowDto dto, CancellationToken cancellationToken)
         {
-            var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId);
+            var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == step.PrevStepId);
             if (prevStep is null)
                 throw new UserFriendlyException("未查询到当前节点上级节点");
             var nextSteps = new List<WorkflowStep>();
@@ -683,7 +672,7 @@ namespace Hotline.FlowEngine.Workflows
                 if (lastStep is null || lastStep.StepType is EStepType.End)
                     throw new UserFriendlyException($"流程流转数据异常,未结束流程出现endStep, flowId: {workflow.Id}", "流程流转数据异常");
 
-                var targetSteps = await CreateStepsByDefineAsync(workflow, targetStepDefine, lastStep, dto, workflow.ExpiredTime, cancellationToken);
+                var targetSteps = await CreateConfigStepsAsync(workflow, targetStepDefine, lastStep, dto, workflow.ExpiredTime, cancellationToken);
                 targetStep = targetSteps.First();
 
                 workflow.EndCountersign();
@@ -931,39 +920,6 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 办理节点
         /// </summary>
-        //private async Task HandleStepAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep currentStepBox, WorkflowStep currentStep,
-        //    ECounterSignType counterSignType, CancellationToken cancellationToken)
-        //{
-        //    if (currentStep.Status is EWorkflowStepStatus.Handled or EWorkflowStepStatus.Created)
-        //        throw UserFriendlyException.SameMessage("当前节点状态无法办理");
-
-        //    if (currentStep.Status is EWorkflowStepStatus.WaitForAccept)
-        //        await AcceptAsync(workflow,
-        //            _sessionContext.RequiredUserId,
-        //            _sessionContext.UserName,
-        //            _sessionContext.RequiredOrgId,
-        //            _sessionContext.OrgName,
-        //            cancellationToken);
-        //    if (currentStep.StepType is EStepType.End)
-        //        throw new UserFriendlyException("当前流程已流转到最终步骤");
-
-        //    //创建会签数据
-        //    if (dto.IsStartCountersign)
-        //        await StartCountersignAsync(workflow, dto, currentStepBox, currentStep, counterSignType, cancellationToken);
-
-        //    _mapper.Map(dto, currentStep);
-
-        //    //step办理状态
-        //    currentStep.Handled(
-        //        _sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-        //        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-        //        dto.NextStepCode);
-
-        //    //stepBox办理状态
-        //    currentStepBox.CheckStepBoxStatusAndUpdate();
-        //}
-        //new
         private async Task HandleStepAsync(WorkflowStep step, Workflow workflow, BasicWorkflowDto dto,
              ECounterSignType? counterSignType, CancellationToken cancellationToken)
         {
@@ -1273,7 +1229,7 @@ namespace Hotline.FlowEngine.Workflows
             //var targetStepBoxNew = await CreateStepAsync(workflow, targetStepDefine, dto, EWorkflowStepStatus.WaitForAccept,
             //     targetPrevStepBox, targetPrevStep, traceStatus, workflow.ExpiredTime, cancellationToken);
 
-            var targetStepsNew = await CreateStepsByDefineAsync(workflow, targetStepDefine, targetPrevStep, dto, workflow.ExpiredTime, cancellationToken);
+            var targetStepsNew = await CreateConfigStepsAsync(workflow, targetStepDefine, targetPrevStep, dto, workflow.ExpiredTime, cancellationToken);
 
             //更新当前办理节点信息
             workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, nextStep: targetStepsNew.First());
@@ -1311,60 +1267,6 @@ namespace Hotline.FlowEngine.Workflows
                 throw UserFriendlyException.SameMessage("无办理权限");
         }
 
-        //private async Task<(WorkflowStep startStepBox, WorkflowStep startStep, WorkflowStep firstStepBox)> CreateStartAndFirstStepAsync(
-        //    Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
-        //{
-        //    if (workflow.Steps.Any())
-        //        throw UserFriendlyException.SameMessage("无法重复创建开始节点");
-
-        //    var startStepDefinition = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
-        //    if (startStepDefinition == null)
-        //        throw new UserFriendlyException($"模板未配置开始节点, defineCode: {workflow.WorkflowDefinition.Code}", "模板未配置开始节点");
-
-        //    var startStepBox = CreateStepBox(workflow.Id, startStepDefinition, string.Empty);
-        //    await _workflowStepRepository.AddAsync(startStepBox, cancellationToken);
-
-
-        //    //start节点的办理人分类默认为用户,即为当前发起流程的操作员
-        //    var handler = new Kv { Key = _sessionContext.RequiredUserId, Value = _sessionContext.UserName };
-
-        //    //开始节点的下一个节点(工单业务:话务员节点)
-        //    var firstStepCode = workflow.WorkflowDefinition.FindStartStepDefine().NextSteps.First().Code;
-        //    var startStep = await CreateStartSubStepAsync(handler, firstStepCode, startStepBox, dto, cancellationToken);
-
-        //    //开始节点trace
-        //    await CreateTraceAsync(workflow, startStep, cancellationToken: cancellationToken);
-
-        //    //创建firstStep
-        //    var firsStepDefine = workflow.WorkflowDefinition.FindStepDefine(firstStepCode);
-        //    var firstStepBox = await CreateStepAsync(workflow, firsStepDefine, dto, EWorkflowStepStatus.WaitForHandle,
-        //        startStepBox, startStep, EWorkflowTraceStatus.Normal, workflow.ExpiredTime, cancellationToken);
-
-        //    return (startStepBox, startStep, firstStepBox);
-        //}
-
-        //private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateEndStepAsync(
-        //    Workflow workflow,
-        //    StepDefine endStepDefine,
-        //    WorkflowStep prevStepBox,
-        //    WorkflowStep prevStep,
-        //    CancellationToken cancellationToken)
-        //{
-        //    if (workflow.Steps.Any(d => d.StepType == EStepType.End))
-        //        throw UserFriendlyException.SameMessage("无法重复创建结束节点");
-
-        //    var stepBox = CreateStepBox(workflow.Id, endStepDefine, prevStepBox.Id);
-        //    await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
-
-        //    var handler = new Kv { Key = _sessionContext.RequiredUserId, Value = _sessionContext.UserName };
-        //    var step = await CreateEndSubStepAsync(handler, stepBox, prevStep, cancellationToken);
-
-        //    //end trace
-        //    await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken);
-
-        //    return (stepBox, step);
-        //}
-
         private async Task<WorkflowStep> CreateEndStepAsync(
             Workflow workflow,
             StepDefine endStepDefine,
@@ -1394,52 +1296,7 @@ namespace Hotline.FlowEngine.Workflows
             return step;
         }
 
-        /// <summary>
-        /// 创建节点(不含开始、结束节点)
-        /// </summary>
-        //private async Task<WorkflowStep> CreateStepAsync(
-        //Workflow workflow,
-        //StepDefine stepBoxDefine,
-        //BasicWorkflowDto dto,
-        //EWorkflowStepStatus status,
-        //WorkflowStep prevStepBox,
-        //WorkflowStep prevStep,
-        //EWorkflowTraceStatus traceStatus,
-        //DateTime expiredTime,
-        //CancellationToken cancellationToken)
-        //{
-        //    if (stepBoxDefine.StepType is EStepType.Start or EStepType.End)
-        //        throw new UserFriendlyException("该方法不支持创建开始或结束节点");
-        //    var stepBox = workflow.Steps.FirstOrDefault(d => d.Code == stepBoxDefine.Code);
-        //    if (stepBox == null)
-        //    {
-        //        stepBox = CreateStepBox(workflow.Id, stepBoxDefine, prevStepBox.Id);
-        //        await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
-        //        workflow.Steps.Add(stepBox);
-        //    }
-        //    else if (stepBox.Status != EWorkflowStepStatus.Created)
-        //    {
-        //        stepBox.Status = EWorkflowStepStatus.Created;
-        //        await _workflowStepRepository.UpdateAsync(stepBox, cancellationToken);
-        //    }
-
-        //    //下一节点为汇总节点时,同一会签只需要创建一次汇总节点
-        //    if (stepBoxDefine.StepType is EStepType.Summary && prevStep.CountersignPosition == ECountersignPosition.Inner)
-        //    {
-        //        var step = stepBox.Steps.FirstOrDefault(d =>
-        //            d.IsInCountersign && d.CountersignId == prevStep.CountersignId);
-        //        if (step != null)
-        //            return stepBox;
-        //    }
-
-        //    await CreateSubStepsAsync(workflow, stepBoxDefine, dto, stepBox, status, prevStep, traceStatus, expiredTime, cancellationToken);
-
-
-        //    return stepBox;
-        //}
-
-        //new
-        public async Task<List<WorkflowStep>> CreateStepsByDefineAsync(
+        public async Task<List<WorkflowStep>> CreateConfigStepsAsync(
             Workflow workflow,
             StepDefine stepDefine,
             WorkflowStep prevStep,
@@ -1459,21 +1316,18 @@ namespace Hotline.FlowEngine.Workflows
                 handlers = dto.NextHandlers;
             }
 
-            //var countersignId = prevStep.HasStartedCountersign() ? prevStep.StartCountersignId : prevStep.CountersignId;
-
-            return await CreateStepsAsync(workflow, stepDefine, prevStep, dto, handlers,
+            return await CreateStepsAsync(workflow, stepDefine, prevStep, dto, handlers, null,
                 EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
                 expiredTime, true, cancellationToken);
         }
 
-        //new
         private async Task<List<WorkflowStep>> CreateStepsAsync(
             Workflow workflow,
             StepDefine stepDefine,
             WorkflowStep prevStep,
             BasicWorkflowDto dto,
             List<Kv> handlers,
-            //string? countersignId,
+            string? countersignId,
             EWorkflowStepStatus stepStatus,
             ECountersignPosition csPosition,
             DateTime expiredTime,
@@ -1481,7 +1335,9 @@ namespace Hotline.FlowEngine.Workflows
             CancellationToken cancellationToken
             )
         {
-            var countersignId = prevStep.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
+            //var countersignId = prevStep.IsStartCountersign
+            //    ? prevStep.StartCountersignId
+            //    : prevStep.IsInCountersign() ? prevStep.Id : null;
 
             List<WorkflowStep> steps = new();
             if (dto.IsStartCountersign)
@@ -1515,114 +1371,9 @@ namespace Hotline.FlowEngine.Workflows
             return steps;
         }
 
-        //private async Task<WorkflowStep> CreateStartSubStepAsync(
-        //    Kv handler,
-        //    string nextStepCode,
-        //    WorkflowStep stepBox,
-        //    BasicWorkflowDto dto,
-        //    CancellationToken cancellationToken)
-        //{
-        //    //开始节点既不发起会签,也不处于会签中
-        //    var subStep = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, null,
-        //        null, null, EWorkflowStepStatus.Handled, ECountersignPosition.None, DateTime.Today,
-        //        _mapper.Map<StepExtension>(dto.Extension));
-        //    subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        _sessionContext.RequiredOrgId, _sessionContext.OrgName);
-
-        //    //step办理状态
-        //    subStep.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-        //        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-        //        nextStepCode);
-
-        //    subStep.Opinion = "流程开启";
-        //    stepBox.Steps.Add(subStep);
-        //    await _workflowStepRepository.AddAsync(subStep, cancellationToken);
-        //    return subStep;
-        //}
-
-        //private async Task<WorkflowStep> CreateEndSubStepAsync(
-        //    Kv handler,
-        //    WorkflowStep currentStepBox,
-        //    WorkflowStep prevStep,
-        //    CancellationToken cancellationToken)
-        //{
-        //    var subStep = CreateSubStep(currentStepBox, new List<Kv> { handler }, null, null, prevStep.Id,
-        //        null, EWorkflowStepStatus.Handled, ECountersignPosition.None, DateTime.Today, new());
-        //    subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId,
-        //        _sessionContext.OrgName);
-        //    subStep.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-        //        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-        //        string.Empty);
-
-        //    currentStepBox.Steps.Add(subStep);
-        //    await _workflowStepRepository.AddAsync(subStep, cancellationToken);
-        //    return subStep;
-        //}
-
-        //private async Task CreateSubStepsAsync(
-        //    Workflow workflow,
-        //    StepDefine stepBoxDefine,
-        //    BasicWorkflowDto dto,
-        //    WorkflowStep stepBox,
-        //    EWorkflowStepStatus stepStatus,
-        //    WorkflowStep prevStep,
-        //    EWorkflowTraceStatus traceStatus,
-        //    DateTime expiredTime,
-        //    CancellationToken cancellationToken = default)
-        //{
-        //    var countersignStatus = stepBoxDefine.StepType is EStepType.Summary
-        //        ? prevStep.IsInCountersign()
-        //            ? ECountersignPosition.Inner
-        //            : ECountersignPosition.None
-        //        : prevStep.GetNextStepCountersignPosition();
-
-        //    var countersignId = dto.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
-
-        //    List<WorkflowStep> subSteps;
-        //    var stepExtension = _mapper.Map<StepExtension>(dto.Extension);
-        //    if (stepBoxDefine.HandlerType is EHandlerType.AssignedUser or EHandlerType.AssignedOrg)
-        //    {
-        //        subSteps = CreateSubSteps(dto.IsStartCountersign, stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler,
-        //            prevStep?.Id, countersignId, stepStatus, countersignStatus, expiredTime, stepExtension);
-        //    }
-        //    else
-        //    {
-        //        if (stepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
-        //            throw new UserFriendlyException("未指定节点处理者");
-        //        subSteps = CreateSubSteps(dto.IsStartCountersign, stepBox, dto.NextHandlers, dto.NextStepCode, dto.NextMainHandler,
-        //            prevStep?.Id, countersignId, stepStatus, countersignStatus, expiredTime, stepExtension);
-        //    }
-        //    stepBox.Steps.AddRange(subSteps);
-        //    await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
-
-        //    //create traces
-        //    foreach (var step in subSteps)
-        //    {
-        //        await CreateTraceAsync(workflow, step, traceStatus, cancellationToken);
-        //    }
-        //}
-
-
         /// <summary>
         /// 查询未完成节点
         /// </summary>
-        /// <param name="stepBoxes"></param>
-        /// <param name="orgCode"></param>
-        /// <param name="userId"></param>
-        /// <returns></returns>
-        //private (WorkflowStep, WorkflowStep) GetUnCompleteStep(List<WorkflowStep> stepBoxes, string orgCode, string userId)
-        //{
-        //    var (stepBox, step) = GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Handled);
-        //    if (step == null)
-        //        throw new UserFriendlyException(
-        //            $"未找到对应节点, workflowId: {stepBoxes.FirstOrDefault()?.WorkflowId} orgCode:{orgCode}, userId: {userId}",
-        //            "未找到对应节点");
-        //    return (stepBox, step);
-        //}
-
-        //new
         private WorkflowStep GetUnHandleStep(List<WorkflowStep> steps, string orgCode, string userId)
         {
             var step = GetStep(steps, orgCode, userId, d => d != EWorkflowStepStatus.Handled);
@@ -1633,116 +1384,10 @@ namespace Hotline.FlowEngine.Workflows
             return step;
         }
 
-        //private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List<WorkflowStep> stepBoxes, string orgCode, string userId) =>
-        //    GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Handled);
-
-        //private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string orgCode, string userId, Func<EWorkflowStepStatus, bool> predicate)
-        //{
-        //    if (!stepBoxes.Any()) throw new UserFriendlyException("该流程中暂无节点");
-        //    foreach (var stepBox in stepBoxes)
-        //    {
-        //        foreach (var step in stepBox.Steps)
-        //        {
-        //            if (predicate(step.Status) && (step.Handlers.Any(d => d.Id == orgCode) || step.Handlers.Any(d => d.Id == userId)))
-        //                return (stepBox, step);
-        //        }
-        //    }
-
-        //    return new();
-        //}
-
-        //new
         private WorkflowStep? GetStep(List<WorkflowStep> steps, string orgCode, string userId, Func<EWorkflowStepStatus, bool> predicate) =>
             steps.FirstOrDefault(d =>
                 predicate(d.Status) && d.Handlers.Any(x => x.Key == orgCode || x.Key == userId));
 
-        //private WorkflowStep CreateStepBox(string workflowId, StepDefine stepDefine, string prevStepBoxId)
-        //{
-        //    var stepBox = _mapper.Map<WorkflowStep>(stepDefine);
-        //    stepBox.WorkflowId = workflowId;
-        //    stepBox.PrevStepId = prevStepBoxId;
-        //    stepBox.NextStepCode = string.Empty;
-        //    stepBox.Opinion = string.Empty;
-        //    stepBox.CountersignStartStepCode = stepDefine.CountersignStartStepCode;
-        //    stepBox.CountersignEndStepCode = stepDefine.CountersignEndStepCode;
-        //    return stepBox;
-        //}
-
-        //private List<WorkflowStep> CreateSubSteps(
-        //    bool isPrevStartCountersign,
-        //    WorkflowStep stepBox,
-        //    List<Kv> handlers,
-        //    string nextStepCode,
-        //    string? nextMainHandler,
-        //    string? prevStepId,
-        //    string? countersignId,
-        //    EWorkflowStepStatus stepStatus,
-        //    ECountersignPosition countersignPosition,
-        //    DateTime expiredTime,
-        //    StepExtension extension)
-        //{
-        //    if (countersignPosition is ECountersignPosition.None && !string.IsNullOrEmpty(countersignId))
-        //        throw UserFriendlyException.SameMessage("非法参数");
-        //    if (countersignPosition is not ECountersignPosition.None && string.IsNullOrEmpty(countersignId))
-        //        throw UserFriendlyException.SameMessage("非法参数");
-
-        //    //依据是否发起会签创建step,发起会签表示一个handler创建一个step,未发起会签默认作为或签处理,只创建一个step
-        //    var steps = new List<WorkflowStep>();
-        //    if (isPrevStartCountersign)
-        //    {
-        //        foreach (var handler in handlers)
-        //        {
-        //            var step = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, nextMainHandler,
-        //                prevStepId, countersignId, stepStatus, countersignPosition, expiredTime, extension);
-
-        //            steps.Add(step);
-        //        }
-        //    }
-        //    else
-        //    {
-        //        var step = CreateSubStep(stepBox, handlers, nextStepCode, nextMainHandler,
-        //            prevStepId, countersignId, stepStatus, countersignPosition, expiredTime, extension);
-
-        //        steps.Add(step);
-        //    }
-
-        //    return steps;
-        //}
-
-        //private WorkflowStep CreateSubStep(
-        //    WorkflowStep stepBox,
-        //    List<Kv> handlers,
-        //    string nextStepCode,
-        //    string? nextMainHandler,
-        //    string? prevStepId,
-        //    string? prevStepCode,
-        //    string? countersignId,
-        //    EWorkflowStepStatus stepStatus,
-        //    ECountersignPosition countersignPosition,
-        //    DateTime expiredTime)
-        //{
-        //    if (!handlers.Any())
-        //        throw new UserFriendlyException("非法参数");
-        //    var step = _mapper.Map<WorkflowStep>(stepBox);
-        //    var handlerIds = handlers.Select(d => d.Key).ToList();
-        //    var isMain = handlers.Count == 1 || (handlers.Count > 1 || handlerIds.First() == nextMainHandler);
-
-        //    step.ParentId = stepBox.Id;
-        //    step.Handlers = handlers;
-        //    step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode;
-        //    step.IsMain = isMain;
-        //    step.PrevStepId = prevStepId;
-        //    step.PrevStepCode = prevStepCode;
-        //    step.CountersignId = countersignId;
-        //    step.Status = stepStatus;
-        //    step.CountersignPosition = countersignPosition;
-        //    step.StepExpiredTime = expiredTime;
-        //    step.TimeLimit = GetTimeLimit("");//todo 过期时间
-
-        //    return step;
-        //}
-
-        //new
         private WorkflowStep CreateStep(
             StepDefine stepDefine,
             WorkflowStep prevStep,

+ 11 - 1
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -228,7 +228,7 @@ public class WorkflowStep : StepBasicEntity
     {
         //if (!HasStartedCountersign())
         //    throw new UserFriendlyException("该节点未发起会签");
-        //outer的情况也属于特殊会签
+        //outer属于特殊会签
         return CountersignSteps.All(d => d.Completed);
     }
 
@@ -238,6 +238,16 @@ public class WorkflowStep : StepBasicEntity
     /// <returns></returns>
     public bool DynamicShouldTerminal() => TerminalDynamicMark == PrevChosenStepCode;
 
+    /// <summary>
+    /// 是否是顶级会签汇总节点
+    /// </summary>
+    public bool IsTopCountersignEndStep(string? topCountersignStepId)
+    {
+        if (string.IsNullOrEmpty(topCountersignStepId))
+            throw new UserFriendlyException($"无效顶级会签节点编号,流程可能未处于会签中, wfId: {WorkflowId}");
+        return IsCountersignEndStep && CountersignStartStepId == topCountersignStepId;
+    }
+
     #endregion
 }
 

+ 52 - 0
src/Hotline/KnowledgeBase/KnowledgeComment.cs

@@ -0,0 +1,52 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.KnowledgeBase
+{
+	[Description("知识库评论")]
+	public class KnowledgeComment : FullStateEntity
+	{
+		/// <summary>
+		/// 知识库ID
+		/// </summary>
+		[SugarColumn(ColumnDescription = "知识库ID")]
+		public string KnowledgeId { get; set; }
+
+		/// <summary>
+		/// 知识库
+		/// </summary>
+		[Navigate(NavigateType.OneToOne, nameof(KnowledgeId))]
+		public Knowledge Knowledge { get; set; }
+
+		/// <summary>
+		/// 评论内容
+		/// </summary>
+		[SugarColumn(ColumnDescription = "评论内容", ColumnDataType = "varchar(2000)")]
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 回复ID
+		/// </summary>
+		[SugarColumn(ColumnDescription = "回复ID")]
+		public string? ReplyId { get; set; }
+
+		/// <summary>
+		/// 匿名
+		/// </summary>
+		[SugarColumn(ColumnDescription = "匿名")]
+		public bool Cryptonym { get; set; }= false;
+
+		/// <summary>
+		/// 回复数
+		/// </summary>
+		[SugarColumn(ColumnDescription = "回复数")]
+		public int ReplyNum { get; set; } = 0;
+
+	}
+}

+ 26 - 0
src/Hotline/Permissions/EPermission.cs

@@ -952,6 +952,32 @@ namespace Hotline.Permissions
 		AddKnowledgeScore = 400902,
 		#endregion
 
+		#region 知识评论
+		/// <summary>
+		/// 知识评论列表
+		/// </summary>
+		[Display(GroupName = "KnowledgeComment", Name = "知识评论列表", Description = "知识评论列表")]
+		KnowledgeCommentList = 401000,
+
+		/// <summary>
+		/// 新增知识评论
+		/// </summary>
+		[Display(GroupName = "KnowledgeComment", Name = "新增知识评论", Description = "新增知识评论")]
+		AddKnowledgeComment = 401001,
+
+		/// <summary>
+		/// 删除知识评论
+		/// </summary>
+		[Display(GroupName = "KnowledgeComment", Name = "删除知识评论", Description = "删除知识评论")]
+		DeleteKnowledgeComment = 401002,
+
+		/// <summary>
+		/// 修改知识评论
+		/// </summary>
+		[Display(GroupName = "KnowledgeComment", Name = "修改知识评论", Description = "修改知识评论")]
+		UpdateKnowledgeComment = 401003,
+		#endregion
+
 		#endregion
 
 		#region 业务管理(500)

+ 3 - 0
src/XF.Domain.Repository/Entity.cs

@@ -2,6 +2,7 @@
 using XF.Domain.Entities;
 using XF.Domain.Events;
 using XF.Domain.Extensions;
+using XF.Utility.SequentialId;
 
 namespace XF.Domain.Repository;
 
@@ -288,4 +289,6 @@ public abstract class PositionWorkflowEntity : PositionEntity, IWorkflow
                 throw new ArgumentOutOfRangeException(nameof(type), type, null);
         }
     }
+
+    public void InitId() => Id = SequentialStringGenerator.Create();
 }