using Dlhis.Basic.Device.Dtos; using Dlhis.Basic.Device.Request; using Dlhis.Basic.Device.Response; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Basic.Enums; using Dlhis.Entity.Device.Entities; using Dlhis.Entity.Device.Enums; using Dlhis.Entity.Sys.Entities; using Dlhis.Entity.Sys.Enums; using Dlhis.Entity.Visitor.Entity; using Dlhis.Sys.Api; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.Utils.Extensions; using Mapster; namespace Dlhis.Basic.Device.Services.Implements; public class DeviceGroupService : IDeviceGroupService { private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly IDevicePermissionAnalyzeService _devicePermissionAnalyzeService; private readonly ITenantApi _tenantApi; public DeviceGroupService(IDbContext dbContext, ICurrentUser currentUser, IDevicePermissionAnalyzeService devicePermissionAnalyzeService, ITenantApi tenantApi) { _dbContext = dbContext; _currentUser = currentUser; _devicePermissionAnalyzeService = devicePermissionAnalyzeService; _tenantApi = tenantApi; } public async Task> GetTreeAsync(DeviceTypeEnumType type) { var tenants = await _tenantApi.GetMyTenantsAsync(); var tenantIds = tenants.Select(x => x.Code).ToList(); tenantIds.AddRange(tenants.Select(x => x.PCode)); var deviceGroups = await _dbContext.Queryable() .Where(x => tenantIds.Contains(x.TenantId)) .Where(x => x.Type == type) .Select() .ToListAsync(); var others = await _dbContext.Client.Queryable() .Where(x => x.Type == type) .Where(x => x.Name == "*共用区域" || x.Name == "*非共用区域" || x.Name == "*通用考勤设备组" || x.Name == "*专用考勤设备组") .Select() .ToListAsync(); deviceGroups.AddRange(others); var tops = deviceGroups.Where(x => !x.PCode.HasValue).ToList(); tops.ForEach(x => { RecursionGetNext(x, deviceGroups); }); return tops; } public async Task SaveAsync(DeviceGroupSaveRequest request) { //检查父级节点 await CheckPCodeAsync(request); if (!request.Id.HasValue) { var entity = request.Adapt(); entity.Code = Guid.NewGuid(); entity.TenantId = _currentUser.GetStringTenantId(); await _dbContext.InsertAsync(entity); } else { var entity = await _dbContext.Queryable().Where(x => x.Id == request.Id).FirstAsync(); entity.ThrowIfNull("设备分组不存在,无法更新"); entity.Editable.ThrowIfFalse("不可编辑当前分组"); if (entity.AsAppointment && request.AsAppointment == false) { (await _dbContext.Queryable() .Where(x => x.DeviceGroupCode == entity.Code) .AnyAsync() ).ThrowIfTrue("该访客组已被分配,无法取消"); } if (request.Type != entity.Type) { //检查下级分组type不一致 var existsDiffNextGroups = await _dbContext.Queryable() .Where(x => x.PCode == entity.Code) .Where(x => x.Type != request.Type) .AnyAsync(); existsDiffNextGroups.ThrowIfTrue("存在下级分组类型不一致,无法修改"); var nextDevices = await GetDevicesByGroupCodeAsync(entity.Code); nextDevices.Any(x => x.Type != request.Type).ThrowIfTrue("分组设备类型不匹配,无法修改类型"); } request.Adapt(entity); await _dbContext.UpdateAsync(entity); } } public async Task AssignDevicesAsync(DeviceAssignRequest request) { var group = await _dbContext.Queryable().Where(x => x.Code == request.GroupCode).SingleAsync(); group.ThrowIfNull("设备分组不存在,无法进行分配"); await CheckNoNextGroupAsync(group.Code); await CheckGroupTypeMatchAsync(request, group); await CheckAssignToTenantAsync(group, request.DeviceCodes); var relationships = await _dbContext.Queryable().Where(x => x.GroupCode == request.GroupCode).ToListAsync(); var r = await PermissionResetAsync(request.GroupCode, relationships.Select(x => x.DeviceCode).Distinct().ToList(), request.DeviceCodes); await _dbContext.DeleteAsync(relationships); var entities = request.DeviceCodes.Select(x => new DeviceGroupRelationshipEntity { GroupCode = request.GroupCode, DeviceCode = x, TenantId = _currentUser.GetStringTenantId(), }).ToList(); await _dbContext.InsertAsync(entities); return r; } private async Task CheckAssignToTenantAsync(DeviceGroupEntity group, List deviceCodes) { if (await _dbContext.Queryable() .Where(x => x.AssignType == AssignTypeEnumType.Tenant) .Where(x => x.UniqueType == DeviceAssignmentTypeEnumType.Group) .Where(x => x.UniqueValue == group.Code) .AnyAsync()) { (await _dbContext.Queryable() .Where(x => x.AssignType != AssignTypeEnumType.Tenant) .Where(x => x.UniqueType == DeviceAssignmentTypeEnumType.Device) .Where(x => deviceCodes.Contains(x.UniqueValue)) .AnyAsync()).ThrowIfTrue("无法分配设备到该设备组:设备组已分配到厂区并且存在设备已分配到其他类型"); } else { //当前组没有tenant级别 var currentAssignedToOthers = await _dbContext.Queryable() .Where(x => x.AssignType != AssignTypeEnumType.Tenant) .Where(x => x.UniqueType == DeviceAssignmentTypeEnumType.Group) .Where(x => x.UniqueValue == group.Code) .AnyAsync(); var groupCodes = await _dbContext.Queryable() .Where(x => deviceCodes.Contains(x.DeviceCode)) .Select(x => x.GroupCode).Distinct() .ToListAsync(); if (groupCodes.Any()) { if (await _dbContext.Queryable() .Where(x => x.AssignType == AssignTypeEnumType.Tenant) .Where(x => x.UniqueType == DeviceAssignmentTypeEnumType.Group) .Where(x => groupCodes.Contains(x.UniqueValue)) .AnyAsync() && currentAssignedToOthers) { true.ThrowIfTrue("存在设备所在的分组已分配到厂区级别,但当前分组存在其他级别的分配"); } } } } private async Task PermissionResetAsync(Guid groupCode, List beforeDeviceCodes, List presentDeviceCodes) { var codesToAdd = presentDeviceCodes.Except(beforeDeviceCodes).ToList(); var codesToDelete = beforeDeviceCodes.Except(presentDeviceCodes).ToList(); if (codesToAdd.IsEmpty() && codesToDelete.IsEmpty()) { return new DevicePermissionAssignResponse(0, 0); } var deviceGroups = await _dbContext.Queryable().ToListAsync(); var deviceGroupCodes = RecursionHelper.HandlerDeviceGroupPreviousCodes(groupCode, deviceGroups); var dps = await _dbContext.Queryable() .Where(x => x.UniqueType == DeviceAssignmentTypeEnumType.Group) .Where(x => deviceGroupCodes.Contains(x.UniqueValue)) .ToListAsync(); if (dps.IsEmpty()) { return new DevicePermissionAssignResponse(0, 0); } var gainfoNos = await GetPermissionUserNosAsync(dps); var userTenantDeviceCodes = codesToDelete.IsPresent() && dps.Exists(x => x.AssignType != AssignTypeEnumType.Tenant) ? await _devicePermissionAnalyzeService.GetUserAssignDeviceCodesWithTypeInTenantAsync(gainfoNos) : new Dictionary>(); var devicesToAdd = await _dbContext.Queryable().Where(x => codesToAdd.Contains(x.Code)).ToListAsync(); var devicesToDelete = await _dbContext.Queryable().Where(x => codesToDelete.Contains(x.Code)).ToListAsync(); var tasksToAdd = new List(); var tasksToDelete = new List(); foreach (var gainfoNo in gainfoNos) { tasksToAdd.AddRange(devicesToAdd.Select(x => new DeviceTaskEntity { Type = DeviceTaskEnumType.Add, DeviceType = x.Type, UserId = null, UserNo = gainfoNo, DeviceUniqueNo = x.UniqueNo, Status = DeviceTaskStatusEnumType.Wait, Version = 0, ResultMessage = null, Source = "设备组-设备", Priority = 0, TenantId = x.TenantId }).ToList()); if (userTenantDeviceCodes.TryGetValue(gainfoNo, out var permissions)) { foreach (var permission in permissions) { permission.AssignTypes = permission.AssignTypes.Where(p => { if (p.UniqueType == DeviceAssignmentTypeEnumType.Device) { return true; } return p.OriginalCodes.IsPresent() && p.OriginalCodes.First() != groupCode; }).ToList(); } var myDeviceCodes = permissions.SelectMany(x => x.AssignTypes.SelectMany(p => p.Codes)).ToList(); var currentDevicesToDelete = devicesToDelete.Where(p => devicesToDelete.Select(x => x.Code).Except(myDeviceCodes).Contains(p.Code)).ToList(); tasksToDelete.AddRange(currentDevicesToDelete.Select(x => new DeviceTaskEntity { Type = DeviceTaskEnumType.Delete, DeviceType = x.Type, UserId = null, UserNo = gainfoNo, DeviceUniqueNo = x.UniqueNo, Status = DeviceTaskStatusEnumType.Wait, Version = 0, ResultMessage = null, Source = "设备组-设备", Priority = 0, TenantId = x.TenantId }).ToList()); } } await _dbContext.InsertAsync(tasksToAdd); await _dbContext.InsertAsync(tasksToDelete); return new DevicePermissionAssignResponse(tasksToAdd.Count, tasksToDelete.Count); } /// /// 获取当前基地有权限的用户 /// /// private async Task> GetPermissionUserNosAsync(List permissions) { //User var userNos = permissions.Where(x => x.AssignType == AssignTypeEnumType.User) .Select(x => x.AssignValue) .Distinct() .ToList(); //Group var groupCodes = permissions.Where(x => x.AssignType == AssignTypeEnumType.Group) .Select(x => Guid.Parse(x.AssignValue)) .Distinct() .ToList(); var userNosByGroup = await _dbContext.Queryable() .Where(x => groupCodes.Contains(x.GroupCode)) .Select(x => x.UserNo) .ToListAsync(); //Department var tenantDepartmentNos = permissions.Where(x => x.AssignType == AssignTypeEnumType.Department) .Where(x => x.AssignValue.Contains(':')) .Select(x => { var arr = x.AssignValue.Split(':'); return new { Factory = arr[0], DepartmentNo = arr[1] }; }) .GroupBy(x => x.Factory) .ToDictionary(x => x.Key, x => x.Select(p => p.DepartmentNo).Distinct().ToList()); var departments = await _dbContext.Queryable().ToListAsync(); var gainfoByDeptWhere = LinqExpressionExtensions.False(); foreach ((string factory, List departmentNos) in tenantDepartmentNos) { var departmentNosToSearch = new List(); foreach (var d in departmentNos) { departmentNosToSearch.AddRange(RecursionHelper.HandlerDepartmentNextCodes(d, departments)); } departmentNosToSearch = departmentNosToSearch.Distinct().ToList(); gainfoByDeptWhere = gainfoByDeptWhere.Or(x => x.Factory == factory && departmentNosToSearch.Contains(x.DepartmentNo)); } var userNosByDepartment = await _dbContext.Queryable() .Where(gainfoByDeptWhere) .Select(x => x.No) .ToListAsync(); //Tenant var tenants = permissions.Where(x => x.AssignType == AssignTypeEnumType.Tenant).Select(x => x.AssignValue).Distinct().ToList(); var userNosByWerk = await _dbContext.Queryable() .Where(x => x.Type != GainfoUserTypeEnumType.External) .Where(x => tenants.Contains(x.Factory)) .Select(x => x.No).ToListAsync(); return userNos.Concat(userNosByGroup).Concat(userNosByDepartment).Concat(userNosByWerk).Distinct().ToList(); } public async Task> GetDevicesByGroupCodeAsync(Guid code) { var relationships = await _dbContext.Queryable() .Where(x => x.GroupCode == code) .ToListAsync(); var deviceCodes = relationships.Select(x => x.DeviceCode); return await _dbContext.Queryable() .LeftJoin((x, dt) => x.TemplateCode == dt.Code) .Where(x => deviceCodes.Contains(x.Code)) .Select((x, dt) => new DeviceUnderGroupResponse { UniqueNo = x.UniqueNo, Type = x.Type, Code = x.Code, Enable = x.Enable, Brand = dt.Brand, DeployPlace = x.DeployPlace, ConnectionInfo = x.ConnectionInfo, }) .ToListAsync(); } public async Task> GetAppointmentGroupsAsync() { return await _dbContext.Queryable() .Where(x => x.AsAppointment) .Where(x => x.Type == DeviceTypeEnumType.Door) .Select(x => new DeviceGroupResponse { Id = x.Id, Code = x.Code, Name = x.Name, }) .ToListAsync(); } /****************************************************** help methods *********************************************************/ /// /// 检查父级分组 /// /// /// private async Task CheckPCodeAsync(DeviceGroupSaveRequest request) { var pGrp = await _dbContext.Queryable().Where(x => x.Code == request.PCode).FirstAsync(); if (pGrp != null) { (!pGrp.AbleSetNext && !request.Id.HasValue).ThrowIfTrue($"{pGrp.Name}不可添加下级"); var exists = await _dbContext.Queryable().Where(x => x.GroupCode == request.PCode).AnyAsync(); exists.ThrowIfTrue("上级分组存在设备"); } (pGrp != null && pGrp.Type != request.Type).ThrowIfTrue("分组类型必须与上级类型保持一致"); } /// /// 检查分组是否存在下级 /// /// /// private async Task CheckNoNextGroupAsync(Guid currentCode) { var exists = await _dbContext.Queryable().Where(x => x.PCode == currentCode).AnyAsync(); exists.ThrowIfTrue("存在下级设备分组,无法分配设备"); } /// /// 检查分组分类匹配 /// /// /// /// private async Task CheckGroupTypeMatchAsync(DeviceAssignRequest request, DeviceGroupEntity group) { var devices = await _dbContext.Queryable().Where(x => request.DeviceCodes.Contains(x.Code)).ToListAsync(); //检查分组的分类控制 devices.Any(x => x.Type != group.Type).ThrowIfTrue($"存在设备类型不一致,无法分配到当前{group.Type.GetDescription()}分组"); //重新赋值到请求参数中 request.DeviceCodes = devices.Select(x => x.Code).ToList(); } /// /// 递归获取树 /// /// /// private static void RecursionGetNext(DeviceGroupTreeResponse current, List deviceGroups) { var next = deviceGroups.Where(x => x.PCode == current.Code).ToList(); current.Next = next; foreach (var item in next) { RecursionGetNext(item, deviceGroups); } } }