using Fake.DependencyInjection; using Fake.Rbac.Infrastructure; using Fake.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Oms.Application.Dtos.Rbac; namespace Oms.Application.Rbac; public class InternalUserRepository(FakeRbacDbContext dbContext) : IScopedDependency { private static readonly string InternalUserFilter = $$""" ( NOT EXISTS (SELECT 1 FROM `{{FakeTables.UserRole}}` ur0 WHERE ur0.`UserId` = u.`Id`) OR EXISTS ( SELECT 1 FROM `{{FakeTables.UserRole}}` ur INNER JOIN `{{FakeTables.Role}}` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0 WHERE ur.`UserId` = u.`Id` AND r.`Code` <> 'Customer' AND r.`Code` NOT LIKE 'CustomerSub\_%' ) ) """; public async Task GetInternalUsersAsync( string? keyword, Guid? organizationId, int page, int pageSize, CancellationToken cancellationToken = default) { var where = $"WHERE u.`IsDeleted` = 0 AND {InternalUserFilter}"; var args = new List(); if (!string.IsNullOrWhiteSpace(keyword)) { var pattern = $"%{keyword.Trim()}%"; where += $" AND (u.`Name` LIKE {{{args.Count}}} OR u.`Account` LIKE {{{args.Count + 1}}})"; args.Add(pattern); args.Add(pattern); } if (organizationId.HasValue && organizationId.Value != Guid.Empty) { where += $" AND u.`OrganizationId` = {{{args.Count}}}"; args.Add(organizationId.Value.ToString()); } var countSql = $$""" SELECT COUNT(*) AS `Value` FROM `{{FakeTables.User}}` u {where} """; var total = await dbContext.Database.SqlQueryRaw(countSql, args.ToArray()) .FirstAsync(cancellationToken); var offset = Math.Max(page - 1, 0) * pageSize; var listSql = $$""" SELECT u.`Id` AS `Id`, u.`Name` AS `Name`, u.`Account` AS `Account`, u.`Email` AS `Email`, u.`Avatar` AS `Avatar`, u.`Phone` AS `Phone`, u.`EmployeeNo` AS `EmployeeNo`, u.`WechatId` AS `WechatId`, u.`OrganizationId` AS `OrganizationId`, o.`Name` AS `OrganizationName`, u.`CreateTime` AS `CreatedAt` FROM `{{FakeTables.User}}` u LEFT JOIN `{{FakeTables.Organization}}` o ON o.`Id` = u.`OrganizationId` AND o.`IsDeleted` = 0 {where} ORDER BY u.`CreateTime` DESC LIMIT {pageSize} OFFSET {offset} """; var rows = await dbContext.Database.SqlQueryRaw(listSql, args.ToArray()) .ToListAsync(cancellationToken); if (rows.Count == 0) { return new InternalUserListResultDto { Total = total, Items = [] }; } var userIds = rows.Select(x => x.Id).Distinct().ToList(); var roleMap = await LoadRolesByUserIdsAsync(userIds, cancellationToken); var items = rows.Select(row => new InternalUserListItemDto { Id = row.Id.ToString(), Name = row.Name, Account = row.Account, Email = row.Email, Avatar = row.Avatar, Phone = row.Phone, EmployeeNo = row.EmployeeNo, WechatId = row.WechatId, OrganizationId = row.OrganizationId?.ToString(), OrganizationName = row.OrganizationName, CreatedAt = row.CreatedAt, Roles = roleMap.TryGetValue(row.Id, out var roles) ? roles : [] }).ToList(); return new InternalUserListResultDto { Total = total, Items = items }; } private async Task>> LoadRolesByUserIdsAsync( IReadOnlyList userIds, CancellationToken cancellationToken) { if (userIds.Count == 0) { return []; } var inClause = string.Join(",", userIds.Select(x => $"'{x}'")); var sql = $$""" SELECT ur.`UserId` AS `UserId`, r.`Id` AS `RoleId`, r.`Name` AS `RoleName`, r.`Code` AS `RoleCode` FROM `{{FakeTables.UserRole}}` ur INNER JOIN `{{FakeTables.Role}}` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0 WHERE ur.`UserId` IN ({inClause}) ORDER BY r.`Name` """; var roleRows = await dbContext.Database.SqlQueryRaw(sql).ToListAsync(cancellationToken); return roleRows .GroupBy(x => x.UserId) .ToDictionary( g => g.Key, g => g.Select(x => new InternalUserRoleDto { Id = x.RoleId.ToString(), Name = x.RoleName, Code = x.RoleCode }).ToList()); } private sealed class InternalUserRow { public Guid Id { get; set; } public string? Name { get; set; } public string? Account { get; set; } public string? Email { get; set; } public string? Avatar { get; set; } public string? Phone { get; set; } public string? EmployeeNo { get; set; } public string? WechatId { get; set; } public Guid? OrganizationId { get; set; } public string? OrganizationName { get; set; } public DateTime? CreatedAt { get; set; } } private sealed class UserRoleRow { public Guid UserId { get; set; } public Guid RoleId { get; set; } public string? RoleName { get; set; } public string? RoleCode { get; set; } } }