using Model; using Model.Dto.System; using Model.Entity.System; using Model.Mapper; using ORM; using Service.Interface; using SqlSugar; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Service.Implement { /// /// 组织架构管理 服务实现 /// public class OrgService : IOrgService { private readonly IAuditRecorder _audit; public OrgService(IAuditRecorder audit) { _audit = audit; } public async Task>> GetTreeAsync() { try { var db = SqlSugarContext.DbContext; var all = await db.Queryable() .Where(x => x.IsDel == 0) .OrderBy(x => x.Sort).OrderBy(x => x.Code) .ToListAsync(); // 聚合每个组织下的用户角色 + 数据范围(ScopeSummary/用户数展示用) var users = await db.Queryable() .Where(x => x.IsDel == 0) .Select(x => new { x.Id, x.OrgId }) .ToListAsync(); var userIds = users.Select(u => u.Id).ToList(); var roleLinks = userIds.Count > 0 ? await db.Queryable().Where(x => userIds.Contains(x.UserId)).ToListAsync() : new List(); var roleIds = roleLinks.Select(l => l.RoleId).Distinct().ToList(); var roles = roleIds.Count > 0 ? await db.Queryable().Where(x => roleIds.Contains(x.Id) && x.IsDel == 0).ToListAsync() : new List(); var roleMap = roles.ToDictionary(r => r.Id, r => r); // userId -> 角色数据范围标签列表(如「超级管理员·全部」) var userScopeTags = new Dictionary>(); foreach (var link in roleLinks) { if (!roleMap.TryGetValue(link.RoleId, out var role)) continue; var tag = $"{role.Name}·{ScopeLabel(role.DataScope)}"; if (!userScopeTags.TryGetValue(link.UserId, out var list)) userScopeTags[link.UserId] = list = new List(); if (!list.Contains(tag)) list.Add(tag); } // orgId -> 直接挂靠用户Id列表 var orgUsers = users.GroupBy(u => u.OrgId).ToDictionary(g => g.Key, g => g.Select(u => u.Id).ToList()); var tree = BuildTreeWithStats(all, 0, orgUsers, userScopeTags); return Result>.Success(tree); } catch (Exception ex) { return Result>.Error("查询组织树失败", ex); } } /// /// 建树并聚合统计:用户数(含下级)+ 数据权限摘要(子树内用户角色的数据范围并集) /// private static List BuildTreeWithStats(List all, long parentId, Dictionary> orgUsers, Dictionary> userScopeTags) { var nodes = new List(); foreach (var entity in all.Where(x => x.ParentId == parentId).OrderBy(x => x.Sort).ThenBy(x => x.Code)) { var node = entity.ToTreeDto(); node.Children = BuildTreeWithStats(all, entity.Id, orgUsers, userScopeTags); // 本节点直接用户 orgUsers.TryGetValue(entity.Id, out var directUserIds); node.UserCount = directUserIds?.Count ?? 0; // 汇总子树:用户总数 + 数据权限标签并集 var scopes = new List(); if (directUserIds != null) { foreach (var uid in directUserIds) if (userScopeTags.TryGetValue(uid, out var tags)) foreach (var t in tags) if (!scopes.Contains(t)) scopes.Add(t); } var total = node.UserCount; foreach (var child in node.Children) { total += child.TotalUserCount; foreach (var t in child.ScopeSummary) if (!scopes.Contains(t)) scopes.Add(t); } node.TotalUserCount = total; node.ScopeSummary = scopes; nodes.Add(node); } return nodes; } /// 数据范围标签:1=全部, 2=本组织及下级 private static string ScopeLabel(byte dataScope) => dataScope == 1 ? "全部" : "本组织及下级"; public async Task> AddAsync(OrgDto dto) { if (dto == null || string.IsNullOrWhiteSpace(dto.Name) || string.IsNullOrWhiteSpace(dto.Code)) return Result.Error("组织名称和编码不能为空"); try { var exists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Code == dto.Code && x.IsDel == 0).AnyAsync(); if (exists) return Result.Error($"组织编码「{dto.Code}」已存在"); // 父节点校验 + 层级固定校验(根节点必须是公司) var parentId = ParseParent(dto.ParentId); if (parentId > 0) { var parent = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == parentId && x.IsDel == 0).FirstAsync(); if (parent == null) return Result.Error("父级组织不存在"); var hierarchyError = ValidateHierarchy(parent.OrgType, (byte)dto.OrgType); if (hierarchyError != null) return Result.Error(hierarchyError.Msg); } else if (dto.OrgType != 1) { return Result.Error("根节点只能是公司"); } // 班次仅班组有效 var shiftType = dto.OrgType == 4 ? (byte)Math.Max(1, dto.ShiftType) : (byte)0; var entity = dto.ToEntity(); entity.Id = 0; entity.ParentId = parentId; entity.ShiftType = shiftType; entity.CreateTime = DateTime.Now; var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); entity.Id = id; await _audit.RecordAsync("Create", "Org", id, 0, newValue: $"[{OrgTypeName(entity.OrgType)}] {entity.Code}({entity.Name})", description: $"新增组织 {entity.Name}"); return Result.Success(entity.ToDto()); } catch (Exception ex) { return Result.Error("新增组织失败", ex); } } public async Task> UpdateAsync(OrgDto dto) { if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0) return Result.Error("组织Id无效"); try { var entity = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == id && x.IsDel == 0).FirstAsync(); if (entity == null) return Result.Error("组织不存在或已被删除"); var codeExists = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync(); if (codeExists) return Result.Error($"组织编码「{dto.Code}」已存在"); var parentId = ParseParent(dto.ParentId); if (parentId == id) return Result.Error("父级不能是自己"); // 层级固定:组织类型创建后不可变更 if (dto.OrgType != entity.OrgType) return Result.Error($"组织层级固定,类型不可变更(当前为「{OrgTypeName(entity.OrgType)}」)"); // 换父级时不能挂到自己的子孙下面(否则成环),且层级必须匹配 if (parentId != entity.ParentId && parentId > 0) { var all = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0).ToListAsync(); var subtreeIds = CollectSubtreeIds(all, id); if (subtreeIds.Contains(parentId)) return Result.Error("不能把组织挂到自己的下级下面"); var newParent = all.FirstOrDefault(x => x.Id == parentId); if (newParent == null) return Result.Error("目标父级组织不存在"); var hierarchyError = ValidateHierarchy(newParent.OrgType, entity.OrgType); if (hierarchyError != null) return Result.Error(hierarchyError.Msg); } var oldEntity = entity; var newEntity = dto.ToEntity(); newEntity.Id = id; newEntity.ParentId = parentId; newEntity.OrgType = entity.OrgType; // 类型不可变 // 班次仅班组有效 newEntity.ShiftType = entity.OrgType == 4 ? (byte)Math.Max(1, dto.ShiftType) : (byte)0; await SqlSugarContext.DbContext.Updateable(newEntity) .IgnoreColumns(x => new { x.CreateTime, x.IsDel }) .ExecuteCommandAsync(); await _audit.RecordAsync("Update", "Org", id, 0, oldValue: $"[{OrgTypeName(oldEntity.OrgType)}] {oldEntity.Name}", newValue: $"[{OrgTypeName(newEntity.OrgType)}] {newEntity.Name}", description: $"修改组织 {newEntity.Name}"); return Result.Success(newEntity.ToDto()); } catch (Exception ex) { return Result.Error("修改组织失败", ex); } } public async Task DeleteAsync(long id) { if (id <= 0) return Result.Error("组织Id无效"); try { var entity = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == id && x.IsDel == 0).FirstAsync(); if (entity == null) return Result.Error("组织不存在或已被删除"); var hasChildren = await SqlSugarContext.DbContext.Queryable() .Where(x => x.ParentId == id && x.IsDel == 0).AnyAsync(); if (hasChildren) return Result.Error($"组织「{entity.Name}」下存在子组织,请先删除子组织"); var hasUsers = await SqlSugarContext.DbContext.Queryable() .Where(x => x.OrgId == id && x.IsDel == 0).AnyAsync(); if (hasUsers) return Result.Error($"组织「{entity.Name}」下仍有用户,请先移出用户"); await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.IsDel == 1) .Where(x => x.Id == id).ExecuteCommandAsync(); await _audit.RecordAsync("Delete", "Org", id, 0, oldValue: $"[{OrgTypeName(entity.OrgType)}] {entity.Code}({entity.Name})", description: $"删除组织 {entity.Name}"); return Result.Success(); } catch (Exception ex) { return Result.Error("删除组织失败", ex); } } public async Task>> GetOptionsAsync() { try { var list = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0) .OrderBy(x => x.Sort).OrderBy(x => x.Code) .ToListAsync(); return Result>.Success(list.ToOptionDtoList()); } catch (Exception ex) { return Result>.Error("查询组织选项失败", ex); } } public async Task> GetSubtreeIdsAsync(long orgId) { if (orgId <= 0) return new List(); var all = await SqlSugarContext.DbContext.Queryable() .Where(x => x.IsDel == 0).ToListAsync(); return CollectSubtreeIds(all, orgId); } #region 私有工具 /// 收集指定组织及其全部子孙 Id(含自身) public static List CollectSubtreeIds(List all, long orgId) { var result = new List(); var stack = new Stack(); stack.Push(orgId); while (stack.Count > 0) { var current = stack.Pop(); result.Add(current); foreach (var child in all.Where(x => x.ParentId == current)) stack.Push(child.Id); } return result; } private static long ParseParent(string? parentId) => long.TryParse(parentId, out var v) ? v : 0; /// 层级固定校验:公司(1)→实验室(2)→部门(3)→班组(4),子级类型必须=父级类型+1 private static Result? ValidateHierarchy(byte parentType, byte childType) { var validChild = parentType switch { 1 => (byte)2, // 公司下只能建实验室 2 => (byte)3, // 实验室下只能建部门 3 => (byte)4, // 部门下只能建班组 _ => (byte)0 // 班组不能再有下级 }; if (childType != validChild) return Result.Error($"组织层级固定:公司→实验室→部门→班组,该节点下只能建「{OrgTypeName(validChild)}」"); return null; } private static string OrgTypeName(byte orgType) => orgType switch { 1 => "公司", 2 => "实验室", 3 => "部门", 4 => "班组", _ => "组织" }; #endregion } }