소스 검색

自贡12345 - 预案库分类管理

libin 4 달 전
부모
커밋
357feaf129

+ 133 - 0
src/Hotline.Api/Controllers/PlanController.cs

@@ -0,0 +1,133 @@
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
+using Hotline.Application.Planlibrary;
+using Hotline.Share.Dtos.Planlibrary;
+using SqlSugar;
+using Hotline.Planlibrary;
+using XF.Domain.Exceptions;
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Dtos;
+
+namespace Hotline.Api.Controllers
+{
+    /// <summary>
+    /// 预案库
+    /// </summary>
+    public class PlanController : BaseController
+    {
+        #region 注入
+
+        private readonly IMapper _mapper;
+        private readonly ISessionContext _sessionContext;
+        private readonly IPlanApplication _planApplication;
+        private readonly IRepository<PlanType> _planTypeRepository;
+
+
+        public PlanController(
+           IMapper mapper,
+           ISessionContext sessionContext,
+           IPlanApplication planApplication,
+           IRepository<PlanType> planTypeRepository)
+        {
+            _mapper = mapper;
+            _sessionContext = sessionContext;
+            _planApplication = planApplication;
+            _planTypeRepository = planTypeRepository;
+        }
+
+        #endregion
+
+        #region 预案库类型管理
+
+        /// <summary>
+        /// 获取列表层级分类
+        /// </summary>
+        /// <param name="IsEnable">是否启用</param>
+        /// <returns></returns>
+        [HttpGet("type/treelist")]
+        public async Task<List<PlanTypeDto>> GetTreeList(bool? IsEnable)
+        {
+            return await _planTypeRepository.Queryable()
+                .WhereIF(IsEnable.HasValue, x => x.IsEnable == IsEnable)
+                .Where(x => SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).Any() ||
+                SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).NotAny()
+                )
+                .Select(x => new PlanTypeDto()
+                {
+                    Id = x.Id.SelectAll()
+                }
+                )
+                .OrderBy(x => x.Sort).ToTreeAsync(it => it.children, it => it.ParentId, null, it => it.Id);
+        }
+
+        /// <summary>
+        /// 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("type/add")]
+        public async Task<string> AddType([FromBody] AddPlanTypeDto dto)
+        {
+            return await _planApplication.AddType(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("type/update")]
+        public async Task UpdateType([FromBody] UpdatePlanTypeDto dto)
+        {
+            await _planApplication.UpdateType(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 查询详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpGet("type/info/{Id}")]
+        public async Task<PlanType> GetType(string Id)
+        {
+            var types = await _planTypeRepository.Queryable()
+                .Includes(x => x.PlanTypeOrgs)   // 填充子对象
+                .Where(x => x.Id == Id)
+                .FirstAsync(HttpContext.RequestAborted);
+            if (types is null)
+                throw UserFriendlyException.SameMessage("查询失败!");
+            return types;
+        }
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpDelete("type/remove/{Id}")]
+        public async Task RemoveType(string Id)
+        {
+            await _planApplication.RemoveType(Id, HttpContext.RequestAborted);
+        }
+
+        #endregion
+
+        #region 预案库管理
+
+        ///// <summary>
+        ///// 预案库列表
+        ///// </summary>
+        ///// <param name="pagedDto"></param>
+        ///// <returns></returns>
+        //[HttpGet("list")]
+        //public async Task<PagedDto<KnowledgeDataDto>> GetKnowList([FromQuery] KnowPagedListDto pagedDto)
+        //{
+        //    return (await _planApplication.GetKnowList(pagedDto, HttpContext.RequestAborted))
+        //        .ToPaged();
+        //}
+
+        #endregion
+    }
+}

+ 60 - 0
src/Hotline.Application/Planlibrary/IPlanApplication.cs

@@ -0,0 +1,60 @@
+using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Share.Dtos.Knowledge;
+
+namespace Hotline.Application.Planlibrary
+{
+    public interface IPlanApplication
+    {
+
+        #region 预案库类型管理
+
+        /// <summary>
+        ///预案库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<string> AddType(AddPlanTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task UpdateType(UpdatePlanTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 预案库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        Task RemoveType(string Id, CancellationToken cancellationToken);
+
+        #endregion
+
+        #region 预案库管理
+
+        // 预案库分类列表
+
+        /// <summary>
+        /// 预案库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        //Task<(int, IList<KnowledgeDataDto>)> GetKnowList(KnowPagedListDto pagedDto, CancellationToken cancellationToken);
+
+        // 新增(没有关键字、根据内容生成关键词,保存为草稿)
+        // 编辑
+        // 详情(增加阅读量)
+        // 审核(新增、修改、删除、下架)
+        // 下架
+        // 删除
+        // 评分
+        // 导出
+
+        // 生成关键词
+        // 关键词列表
+
+        #endregion
+
+    }
+}

+ 361 - 0
src/Hotline.Application/Planlibrary/PlanApplication.cs

@@ -0,0 +1,361 @@
+using DocumentFormat.OpenXml.Office2010.Excel;
+using Hotline.Application.Knowledge;
+using Hotline.KnowledgeBase;
+using Hotline.Planlibrary;
+using Hotline.Repository.SqlSugar.Knowledge;
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Share.Enums.KnowledgeBase;
+using Hotline.Share.Enums.Planlibrary;
+using MapsterMapper;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Planlibrary
+{
+    /// <summary>
+    /// 预案库处理
+    /// </summary>
+    public class PlanApplication : IPlanApplication, IScopeDependency
+    {
+        #region 注册
+
+        private readonly IRepository<PlanList> _planListRepository;                      //预案库列表
+        private readonly IRepository<PlanRelationType> _planRelationTypeRepository;      //预案库关联类型
+        private readonly IRepository<PlanType> _planTypeRepository;                      //预案库分类管理
+        private readonly IRepository<PlanTypeOrg> _planTypeOrgRepository;                //预案库分类关联机构
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+        private readonly IRepository<Hotspot> _hotspotTypeRepository;
+
+        public PlanApplication(
+            IRepository<PlanList> planListRepository,
+            IRepository<PlanRelationType> planRelationTypeRepository,
+            IRepository<PlanType> planTypeRepository,
+            IRepository<PlanTypeOrg> planTypeOrgRepository, 
+            ISessionContext sessionContext,
+            IMapper mapper,
+            IRepository<Hotspot> hotspotTypeRepository)
+        {
+            _planListRepository = planListRepository;
+            _planRelationTypeRepository = planRelationTypeRepository;
+            _planTypeRepository = planTypeRepository;
+            _planTypeOrgRepository = planTypeOrgRepository;
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+            _hotspotTypeRepository = hotspotTypeRepository;
+        }
+
+        #endregion
+
+        #region 预案库类型管理
+
+        #region 预案库类型 - 新增
+
+        /// <summary>
+        /// 预案库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<string> AddType(AddPlanTypeDto dto, CancellationToken cancellationToken)
+        {
+            var sandard = await _planTypeRepository.GetAsync(p => p.ParentId == dto.ParentId && p.Name == dto.Name && p.IsDeleted == false, cancellationToken);
+            if (sandard is not null)
+                throw UserFriendlyException.SameMessage("当前层级已存在相同名称的分类!");
+
+            var type = _mapper.Map<PlanType>(dto);
+            type.InitId();
+            type.IsEnable = true;
+            //获取分类名称全称
+            string FullName = await GetFullName(type.ParentId);
+            //处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;
+            var id = await _planTypeRepository.AddAsync(type, cancellationToken);
+            return id;
+        }
+
+        #endregion
+
+        #region 预案库类型 - 编辑
+
+        /// <summary>
+        /// 预案库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdateType(UpdatePlanTypeDto dto, CancellationToken cancellationToken)
+        {
+            //查询原有数据
+            var type = await _planTypeRepository.GetAsync(dto.Id, cancellationToken);
+            if (type is null)
+                throw UserFriendlyException.SameMessage("编辑失败!");
+            bool result = type.Name != dto.Name || type.ParentId != dto.ParentId;
+            //是否更改分类名称或者层级
+
+            //转换
+            _mapper.Map(dto, type);
+            //如果更改了名称或者修改了层级,则修改全称,未更改不修改
+            if (result)
+            {
+                string FullName = await GetFullName(type.ParentId);//获取分类名称全称
+                type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;//处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            }
+
+            //修改数据
+            await _planTypeRepository.UpdateAsync(type, cancellationToken);
+
+            //如果修改了名称,对应修改子分类全称
+            if (result)
+                await UpdateChildNode(type.Id);
+
+            // 修改关联机构
+            await _planTypeOrgRepository.RemoveAsync(x => x.TypeId == type.Id, false, cancellationToken);
+            if (dto.TypeOrgDtos != null && dto.TypeOrgDtos.Any())
+            {
+                List<PlanTypeOrg> orgs = _mapper.Map<List<PlanTypeOrg>>(dto.TypeOrgDtos);
+                orgs.ForEach(x => x.TypeId = type.Id);
+                await _planTypeOrgRepository.AddRangeAsync(orgs, cancellationToken);
+            }
+        }
+
+        #endregion
+
+        #region 预案库类型 - 删除
+
+        /// <summary>
+        /// 预案库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemoveType(string Id, CancellationToken cancellationToken)
+        {
+            //查询数据是否存在
+            var sandard = await _planTypeRepository.GetAsync(p => p.Id == Id && p.IsDeleted == false, cancellationToken);
+            if (sandard is null)
+                throw UserFriendlyException.SameMessage("分类不存在!");
+
+            //查询是否有子级分类
+            var checkChild = await _planTypeRepository.CountAsync(p => p.ParentId == Id && p.IsDeleted == false, cancellationToken);
+            if (checkChild > 0)
+                throw UserFriendlyException.SameMessage("存在子级分类!");
+
+            //查询是否有知识分类
+            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.PlanId == Id), cancellationToken);
+            if (checkKnowledge > 0)
+                throw UserFriendlyException.SameMessage("分类存在预案!");
+
+            //删除操作
+            await _planTypeRepository.RemoveAsync(sandard, true, cancellationToken);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 预案库管理
+
+        /// <summary>
+        /// 预案库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        //public async Task<(int, IList<KnowledgeDataDto>)> GetKnowList(KnowPagedListDto pagedDto, CancellationToken cancellationToken)
+        //{
+        //    if (!_sessionContext.OrgIsCenter)
+        //    {
+        //        pagedDto.Attribution = "部门知识库";
+        //    }
+        //    var typeSpliceName = string.Empty;
+        //    var hotspotHotSpotFullName = string.Empty;
+        //    if (!string.IsNullOrEmpty(pagedDto.KnowledgeTypeId))
+        //    {
+        //        var type = await _planTypeRepository.GetAsync(x => x.Id == pagedDto.KnowledgeTypeId);
+        //        typeSpliceName = type?.SpliceName;
+        //    }
+        //    if (!string.IsNullOrEmpty(pagedDto.HotspotId))
+        //    {
+        //        var hotspot = await _hotspotTypeRepository.GetAsync(x => x.Id == pagedDto.HotspotId);
+        //        hotspotHotSpotFullName = hotspot?.HotSpotFullName;
+        //    }
+        //    var (total, temp) = await _knowledgeRepository.Queryable(false, false, false)
+        //        .Includes(x => x.User)
+        //        .Includes(x => x.SystemOrganize)
+        //        .Includes(x => x.SourceOrganize)
+        //        .Includes(x => x.HotspotType)
+        //        .Includes(x => x.Workflow)
+        //        .Includes(x => x.KnowledgeType)
+        //        .Where(x => x.IsDeleted == false)
+        //        .Where(x => x.KnowledgeType.Any(t => t.KnowledgeType.KnowledgeTypeOrgs.Any(to => to.OrgId == _sessionContext.RequiredOrgId) || t.KnowledgeType.KnowledgeTypeOrgs.Any() == false))
+        //        .Where(x => (x.Status == EKnowledgeStatus.Drafts && x.CreatorId == _sessionContext.UserId) || (x.Status != EKnowledgeStatus.Drafts))
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.Title), x => x.Title.Contains(pagedDto.Title!))
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), x => x.Title.Contains(pagedDto.Keyword!) || x.CreatorName.Contains(pagedDto.Keyword!) || x.CreatorOrgName.Contains(pagedDto.Keyword!) || x.SourceOrganize.Name.Contains(pagedDto.Keyword!))
+        //        .WhereIF(pagedDto.Status.HasValue && pagedDto.Status != EKnowledgeStatus.OffShelf && pagedDto.Status != EKnowledgeStatus.NewDrafts && pagedDto.Status != EKnowledgeStatus.All, x => x.Status == pagedDto.Status && ((x.ExpiredTime != null && x.ExpiredTime > DateTime.Now) || x.ExpiredTime == null))
+        //        .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EKnowledgeStatus.OffShelf, x => x.Status == pagedDto.Status || (x.ExpiredTime != null && x.ExpiredTime < DateTime.Now && x.Status != EKnowledgeStatus.Drafts))
+        //        .WhereIF(pagedDto.IsPublic.HasValue, x => x.IsPublic == pagedDto.IsPublic)
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.Summary), x => x.Summary != null && x.Summary.Contains(pagedDto.Summary!))
+        //        .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.StartsWith(typeSpliceName)))
+        //        .WhereIF(!string.IsNullOrEmpty(hotspotHotSpotFullName), x => x.HotspotType.HotSpotFullName.EndsWith(hotspotHotSpotFullName!))
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.CreateOrgId), x => x.CreatorOrgId != null && x.CreatorOrgId.StartsWith(pagedDto.CreateOrgId!))
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.ModuleCode), x => x.Workflow.ModuleCode == pagedDto.ModuleCode)
+        //        .WhereIF(pagedDto.Status == EKnowledgeStatus.NewDrafts, x => x.Status == EKnowledgeStatus.Drafts || x.Status == EKnowledgeStatus.Revert)
+        //        .WhereIF(pagedDto.NewDraftsStatus is EKnowledgeStatus.Drafts, x => x.Status == EKnowledgeStatus.Drafts)
+        //        .WhereIF(pagedDto.NewDraftsStatus is EKnowledgeStatus.Revert, x => x.Status == EKnowledgeStatus.Revert)
+
+        //        .WhereIF(pagedDto.CreationStartTime.HasValue, x => x.CreationTime >= pagedDto.CreationStartTime)
+        //        .WhereIF(pagedDto.CreationEndTime.HasValue, x => x.CreationTime <= pagedDto.CreationEndTime)
+
+        //          .WhereIF(pagedDto.StartOnShelfTime.HasValue, x => x.OnShelfTime >= pagedDto.StartOnShelfTime)
+        //          .WhereIF(pagedDto.EndOnShelfTime.HasValue, x => x.OnShelfTime <= pagedDto.EndOnShelfTime)
+
+        //           .WhereIF(pagedDto.StartOffShelfTime.HasValue, x => x.OffShelfTime >= pagedDto.StartOffShelfTime)
+        //          .WhereIF(pagedDto.EndOffShelfTime.HasValue, x => x.OffShelfTime <= pagedDto.EndOffShelfTime)
+
+        //          .WhereIF(pagedDto.StartUpdateTime.HasValue, x => x.LastModificationTime >= pagedDto.StartUpdateTime)
+        //          .WhereIF(pagedDto.EndUpdateTime.HasValue, x => x.LastModificationTime <= pagedDto.EndUpdateTime)
+        //        .WhereIF(!string.IsNullOrEmpty(pagedDto.Attribution), x => x.Attribution == pagedDto.Attribution)
+        //        .OrderByDescending(d => d.CreationTime)
+        //        .ToPagedListAsync(pagedDto.PageIndex, pagedDto.PageSize, cancellationToken);
+        //    //返回数据
+        //    return (total, _mapper.Map<IList<KnowledgeDataDto>>(temp));
+        //}
+
+        #endregion
+
+
+
+
+        #region 私有方法
+
+        #region 查询所有子级
+
+        /// <summary>
+        /// 查询所有子级
+        /// </summary>
+        /// <param name="treeDatas">分类数据</param>
+        /// <param name="ID">需要查询哪级下面的分类</param>
+        /// <param name="checkId">选中的数据ID</param>
+        /// <returns></returns>
+        private List<TreeListDto> GetChildren(List<PlanType> treeDatas, string ID, string? checkId)
+        {
+            List<TreeListDto> nodeList = new();
+            //根据ID查询子级
+            var children = treeDatas.Where(q => q.ParentId == ID);
+            foreach (var dr in children)
+            {
+                //组装数据
+                TreeListDto node = new()
+                {
+                    name = dr.Name,
+                    ParentID = dr.ParentId,
+                    value = dr.Id,
+                    IsEnable = dr.IsEnable
+                };
+                //是否选中
+                if (!string.IsNullOrEmpty(checkId) && checkId != Guid.Empty.ToString() && checkId == dr.Id)
+                    node.selected = true;
+                //子级数据赋值
+                node.children = GetChildren(treeDatas, node.value, checkId);
+                //添加数据
+                nodeList.Add(node);
+            }
+            return nodeList;
+        }
+
+        #endregion
+
+        #region  获取全称
+
+        /// <summary>
+        /// 获取全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<string> GetFullName(string? Id)
+        {
+            //获取全部父级名称
+            var list = await GetParentNode(Id);
+            //倒叙
+            list.Reverse();
+            //拆分
+            return string.Join("-", list.ToArray());
+        }
+
+        /// <summary>
+        /// 查询父级名称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<string>> GetParentNode(string? Id)
+        {
+            List<string> list = new();
+            //查询父级数据
+            var type = await _planTypeRepository.GetAsync(p => p.Id == Id);
+            if (type != null)
+            {
+                //添加名称
+                list.Add(type.Name);
+                list.AddRange(await GetParentNode(type.ParentId));
+            }
+            return list;
+        }
+
+        #endregion
+
+        #region 修改子级分类全称
+
+        /// <summary>
+        /// 修改子级分类全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task UpdateChildNode(string Id)
+        {
+            //查询子分类
+            var list = await GetChildNode(Id);
+            if (list is not null && list.Count > 0)
+            {
+                foreach (var item in list)
+                {
+                    //获取全称
+                    string FullName = await GetFullName(item.ParentId);
+                    item.SpliceName = string.IsNullOrEmpty(FullName) ? item.Name : FullName + "-" + item.Name;
+                    //修改全称
+                    await _planTypeRepository.UpdateAsync(item);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 查询子级节点数据
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<PlanType>> GetChildNode(string Id)
+        {
+            List<PlanType> list = new();
+            //查询数据
+            var typelist = await _planTypeRepository.QueryAsync(p => p.ParentId == Id);
+            if (typelist != null)
+            {
+                //处理数据
+                foreach (var item in typelist)
+                {
+                    list.Add(item);
+                    list.AddRange(await GetChildNode(item.Id));
+                }
+            }
+            return list;
+        }
+
+        #endregion
+
+        #endregion
+    }
+}

+ 189 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanTypeDto.cs

@@ -0,0 +1,189 @@
+using Hotline.Settings;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+    /// <summary>
+    /// 编辑
+    /// </summary>
+    public record UpdatePlanTypeDto : AddPlanTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+    }
+
+    /// <summary>
+    ///新增
+    /// </summary>
+    public record AddPlanTypeDto
+    {
+        /// <summary>
+        /// 类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string? ParentId { get; set; } 
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; } = 0;
+
+        public List<AddPlanTypeOrgDto>? TypeOrgDtos { get; set; }
+
+    }
+
+    /// <summary>
+    /// 初始化返回数据
+    /// </summary>
+    public record PlanTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 类型名称拼接(所有父级分类名称)
+        /// <example>
+        /// A类型/A.1类型/A.1.1类型
+        /// </example>
+        /// </summary>
+        public string SpliceName { get; set; }
+
+		/// <summary>
+		/// 类型名称
+		/// </summary>
+		public string Name { get; set; }
+
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        public bool IsEnable { get; set; }
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string ParentId { get; set; }
+
+        /// <summary>
+        /// 树形分类
+        /// </summary>
+        public List<Knowledge.TreeListDto> TreeLists { get; set; } = new();
+
+        /// <summary>
+        /// 子级
+        /// </summary>
+        public List<PlanTypeDto> children { get; set; }
+
+        public int PlanNum { get; set; }
+	}
+
+    public record PlanOrgDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+		/// <summary>
+		/// 组织架构名称
+		/// </summary>
+		public string Name { get; set; }
+
+	    /// <summary>
+	    /// 组织架构简称
+	    /// </summary>
+	    public string ShortName { get; set; }
+
+	    /// <summary>
+	    /// 区域Code(行政区域代码)
+	    /// </summary>
+	    public string? AreaCode { get; set; }
+
+	    /// <summary>
+	    /// 区域名称(行政区域名称)
+	    /// </summary>
+	    public string? AreaName { get; set; }
+
+	    /// <summary>
+	    /// 部门级别
+	    /// </summary>
+	    public int Level { get; set; }
+
+	    /// <summary>
+	    /// 部门类型
+	    /// </summary>
+	    public EOrgType OrgType { get; set; }
+
+	    /// <summary>
+	    /// 上级ID
+	    /// </summary>
+	    public string? ParentId { get; set; }
+
+	    /// <summary>
+	    /// 上级名称
+	    /// </summary>
+	    public string? ParentName { get; set; }
+
+	    /// <summary>
+	    /// 是否启用
+	    /// </summary>
+	    public bool IsEnable { get; set; }
+
+	    /// <summary>
+	    /// 是否为中心
+	    /// </summary>
+	    public bool IsCenter { get; set; }
+
+	    public List<PlanOrgDto> Children { get; set; }
+
+	    public string OrgTypeText => OrgType.GetDescription();
+
+
+	    /// <summary>
+	    /// 旧系统id
+	    /// </summary>
+	    public int? oldBmid { get; set; }
+
+	    public int PlanNum { get; set; }
+	}
+
+    public record PlanHotSpotDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+	    public int PlanNum { get; set; }
+
+		public string HotSpotName { get; set; }
+	    public string ParentId { get; set; }
+	    public string PYCode { get; set; }
+
+	    public string FullPYCode { get; set; }
+	    public string ProvinceCode { get; set; }
+	    public int OrderBy { get; set; }
+	    public string TrunkNum { get; set; }
+	    /// <summary>
+	    /// 生成的时候写入
+	    /// </summary>
+	    public string HotSpotFullName { get; set; }
+
+	    public List<PlanHotSpotDto> Children { get; set; }
+
+	    public bool HasChild { get; set; }
+
+	}
+}

+ 56 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanTypeOrgDto.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+	public record AddPlanTypeOrgDto
+	{
+		/// <summary>
+		/// 机构ID
+		/// </summary>
+		public string OrgId { get; set; }
+
+		/// <summary>
+		/// 机构名称
+		/// </summary>
+		public string OrgName { get; set; }
+	}
+
+	public record PlanTypeOrgDto : AddPlanTypeOrgDto
+	{
+		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; }
+	}
+}

+ 33 - 0
src/Hotline.Share/Enums/Planlibrary/EPlanApplyType.cs

@@ -0,0 +1,33 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Planlibrary;
+
+/// <summary>
+/// 知识审批-申请类型
+/// </summary>
+public enum EPlanApplyType
+{
+    /// <summary>
+    /// 新增
+    /// </summary>
+    [Description("新增")]
+    Add = 0,
+
+    /// <summary>
+    /// 修改
+    /// </summary>
+    [Description("修改")]
+    Update = 1,
+
+    /// <summary>
+    /// 删除
+    /// </summary>
+    [Description("删除")]
+    Delete = 2,
+
+	/// <summary>
+	/// 下架
+	/// </summary>
+	[Description("下架")]
+    Offshelf = 3,
+}

+ 75 - 0
src/Hotline.Share/Enums/Planlibrary/EPlanStatus.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.Planlibrary
+{
+    /// <summary>
+    /// 预案状态
+    /// </summary>
+    public enum EPlanStatus
+    {
+        /// <summary>
+        /// 全部
+        /// </summary>
+        [Description("全部")]
+        All = -1,
+
+        /// <summary>
+        /// 待提交
+        /// </summary>
+        [Description("待提交")]
+        Drafts = 0,
+
+        /// <summary>
+        /// 审批中
+        /// </summary>
+        [Description("审批中")]
+        Auditing = 1,
+
+        /// <summary>
+        /// 失败
+        /// </summary>
+        [Description("失败")]
+        Failed = 2,
+
+        /// <summary>
+        /// 已上架
+        /// </summary>
+        [Description("已上架")]
+        OnShelf = 3,
+
+        /// <summary>
+        /// 已下架
+        /// </summary>
+        [Description("已下架")]
+        OffShelf = 4,
+
+        /// <summary>
+        /// 已驳回
+        /// </summary>
+        [Description("已驳回")]
+        Revert = 5,
+
+        /// <summary>
+        /// 已过期
+        /// </summary>
+        [Description("已过期")]
+        Overdue = 6,
+
+        /// <summary>
+        /// 下架审批中
+        /// </summary>
+        [Description("下架审批中")]
+        OffShelfAudit = 7,
+
+        /// <summary>
+        /// 草稿
+        /// </summary>
+        [Description("草稿")]
+        NewDrafts = 8,
+    }
+}

+ 163 - 0
src/Hotline/Planlibrary/PlanList.cs

@@ -0,0 +1,163 @@
+using Hotline.Settings;
+using Hotline.Settings.Hotspots;
+using Hotline.Users;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+using Hotline.Share.Enums.Planlibrary;
+using Hotline.KnowledgeBase;
+
+namespace Hotline.Planlibrary;
+
+/// <summary>
+/// 预案库列表内容
+/// </summary>
+[Description("预案库列表")]
+[SugarIndex("index_planlibrary_hotspotId", nameof(PlanList.HotspotId), OrderByType.Desc)]
+[SugarIndex("index_planlibrary_attribution", nameof(PlanList.Attribution), OrderByType.Desc)]
+[SugarIndex("index_planlibrary_creatorOrgId", nameof(PlanList.CreatorOrgId), OrderByType.Desc)]
+public class PlanList : CreationEntity
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    [SugarColumn(ColumnDescription = "标题")]
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 关键词
+    /// </summary>
+    [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true, ColumnDescription = "关键词")]
+    public List<string>? Keywords { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    [SugarColumn(ColumnDataType = "text", ColumnDescription = "内容")]
+    public string Content { get; set; }
+
+
+    /// <summary>
+    /// 预案状态
+    /// </summary>
+    [SugarColumn(ColumnDescription = "预案状态")]
+    public EPlanStatus Status { get; set; }
+
+    /// <summary>
+    /// 预案归属
+    /// </summary>
+    [SugarColumn(ColumnDescription = "预案归属")]
+    public string? Attribution { get; set; }
+
+    /// <summary>
+    /// 热点
+    /// </summary>
+    [SugarColumn(ColumnDescription = "热点")]
+    public string? HotspotId { get; set; }
+
+    /// <summary>
+    /// 热点
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(HotspotId))]//一对一 
+    public Hotspot? HotspotType { get; set; }
+
+    /// <summary>
+    /// 是否公开
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否公开")]
+    public bool? IsPublic { get; set; }
+
+    /// <summary>
+    /// 是否删除
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否删除")]
+    public bool? IsDeleted { get; set; } = false;
+
+    /// <summary>
+    /// 阅读次数
+    /// </summary>
+    [SugarColumn(ColumnDescription = "阅读次数")]
+    public int PageView { get; set; } = 0;
+
+    /// <summary>
+    /// 上架时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "上架时间")]
+    public DateTime? OnShelfTime { get; set; }
+
+    /// <summary>
+    /// 下架时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "下架时间")]
+    public DateTime? OffShelfTime { get; set; }
+
+    /// <summary>
+    /// 创建人
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(CreatorId))]
+    public User? Creator { get; set; }
+
+    /// <summary>
+    /// 创建部门
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(CreatorOrgId))]//一对一 
+    public SystemOrganize? SystemOrganize { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "更新时间")]
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 审核人ID
+    /// </summary>
+    public string? ExaminManId { get; set; }
+
+    /// <summary>
+    /// 审核人
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(ExaminManId))]
+    public User? ExaminMan { get; set; }
+
+    /// <summary>
+    /// 审核部门ID
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审核部门")]
+    public string? ExaminOrganizeId { get; set; }
+
+    /// <summary>
+    /// 审核部门
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(ExaminOrganizeId))]//一对一 
+    public SystemOrganize? ExaminOrganize { get; set; }
+
+    /// <summary>
+    /// 审核时间
+    /// </summary>
+    public DateTime? ExaminTime { get; set; }
+
+    /// <summary>
+    /// 失效时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "失效时间")]
+    public DateTime? ExpiredTime { get; set; }
+
+    /// <summary>
+    /// 评分
+    /// </summary>
+    [SugarColumn(ColumnDescription = "评分")]
+    public decimal? Score { get; set; } = decimal.Zero;
+
+    /// <summary>
+	/// 预案库类型关联ID
+	/// </summary>
+	[Navigate(NavigateType.OneToMany, nameof(PlanRelationType.PlanId))]
+    public List<PlanRelationType> PlanTypes { get; set; }
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
+    public List<string>? FileJson { get; set; }
+}

+ 42 - 0
src/Hotline/Planlibrary/PlanRelationType.cs

@@ -0,0 +1,42 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Planlibrary;
+
+/// <summary>
+/// 预案库关联类型
+/// </summary>
+[Description("预案库关联类型")]
+[SugarIndex("index_planRelationType_planId", nameof(PlanRelationType.PlanId), OrderByType.Desc)]
+[SugarIndex("index_planRelationType_typeId", nameof(PlanRelationType.PlanTypeId), OrderByType.Desc)]
+[SugarIndex("index_planRelationType_spliceName", nameof(PlanRelationType.PlanTypeSpliceName), OrderByType.Desc)]
+public class PlanRelationType : FullStateEntity
+{
+    /// <summary>
+    /// 预案库ID
+    /// </summary>
+    public string PlanId { get; set; }
+
+    /// <summary>
+    /// 预案库类型ID
+    /// </summary>
+    public string PlanTypeId { get; set; }
+
+    /// <summary>
+    /// 预案库类型名称
+    /// </summary>
+    public string PlanTypeName { get; set; }
+
+    /// <summary>
+    /// 预案库类型名称
+    /// </summary>
+    public string PlanTypeSpliceName { get; set; }
+
+    /// <summary>
+    /// 预案库类型
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(PlanTypeId))]
+    public PlanType PlanType { get; set; }
+}
+

+ 53 - 0
src/Hotline/Planlibrary/PlanType.cs

@@ -0,0 +1,53 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Planlibrary;
+
+/// <summary>
+/// 预案分类
+/// </summary>
+[Description("预案分类")]
+public class PlanType : FullStateEntity
+{
+    /// <summary>
+    /// 类型名称
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 类型名称拼接(所有父级分类名称)
+    /// <example>
+    /// A类型/A.1类型/A.1.1类型
+    /// </example>
+    /// </summary>
+    public string SpliceName { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool IsEnable { get; set; }
+
+    /// <summary>
+    /// 排序
+    /// </summary>
+    public int Sort { get; set; }
+
+    /// <summary>
+    /// 父级ID
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ParentId { get; set; }
+
+
+    /// <summary>
+    /// 旧系统类型id
+    /// </summary>
+    public int? OldTypeId { get; set; }
+
+    /// <summary>
+    /// 类型关联机构
+    /// </summary>
+    [Navigate(NavigateType.OneToMany, nameof(PlanTypeOrg.TypeId))]
+    public List<PlanTypeOrg> PlanTypeOrgs { get; set; }
+}

+ 30 - 0
src/Hotline/Planlibrary/PlanTypeOrg.cs

@@ -0,0 +1,30 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Planlibrary;
+
+/// <summary>
+/// 预案分类关联机构
+/// </summary>
+[Description("知识分类关联机构")]
+[SugarIndex("index_planTypeOrg_typeId", nameof(PlanTypeOrg.TypeId), OrderByType.Desc)]
+[SugarIndex("index_planTypeOrg_orgId", nameof(PlanTypeOrg.OrgId), OrderByType.Desc)]
+public class PlanTypeOrg : FullStateEntity
+{
+    /// <summary>
+    /// 预案分类ID
+    /// </summary>
+    public string TypeId { get; set; }
+
+    /// <summary>
+    /// 机构ID
+    /// </summary>
+    public string OrgId { get; set; }
+
+    /// <summary>
+    /// 机构名称
+    /// </summary>
+    public string OrgName { get; set; }
+}
+

+ 5 - 5
src/Hotline/Statistics/StatisticsBaseInfo.cs

@@ -26,7 +26,7 @@ namespace Hotline.Statistics
         /// 受理类型
         /// </summary>
         [SugarColumn(ColumnDescription = "受理类型")]
-        public string PurName { get; set; }
+        public string? PurName { get; set; }
 
         /// <summary>
         /// 受理时间
@@ -44,13 +44,13 @@ namespace Hotline.Statistics
         /// 部门标识
         /// </summary>
         [SugarColumn(ColumnDescription = "部门标识")]
-        public int DepartID { get; set; }
+        public int DepartID { get; set; } = 0;
 
         /// <summary>
         /// 部门名称
         /// </summary>
         [SugarColumn(ColumnDescription = "部门名称")]
-        public string DepartName { get; set; }
+        public string? DepartName { get; set; }
 
         /// <summary>
         /// 部门分类
@@ -68,7 +68,7 @@ namespace Hotline.Statistics
         /// 回访状态
         /// </summary>
         [SugarColumn(ColumnDescription = "回访状态")]
-        public string AppTypeName { get; set; }
+        public string? AppTypeName { get; set; }
 
         /// <summary>
         /// 按时办结 1按时 0超期 2未完成
@@ -80,7 +80,7 @@ namespace Hotline.Statistics
         /// 办理时长(工作分钟)
         /// </summary>
         [SugarColumn(ColumnDescription = "办理时长")]
-        public int CallHandToEndWork { get; set; }
+        public int CallHandToEndWork { get; set; } = 0;
 
         /// <summary>
         /// 统计日期