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
}
}