mirror of https://github.com/abpframework/abp.git
14 changed files with 515 additions and 2 deletions
@ -0,0 +1,25 @@ |
|||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
public class OrganizationUnitConsts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Maximum length of the <see cref="DisplayName"/> property.
|
||||
|
/// </summary>
|
||||
|
public const int MaxDisplayNameLength = 128; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maximum depth of an OU hierarchy.
|
||||
|
/// </summary>
|
||||
|
public const int MaxDepth = 16; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Length of a code unit between dots.
|
||||
|
/// </summary>
|
||||
|
public const int CodeUnitLength = 5; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Maximum length of the <see cref="Code"/> property.
|
||||
|
/// </summary>
|
||||
|
public const int MaxCodeLength = MaxDepth * (CodeUnitLength + 1) - 1; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
public interface IOrganizationUnitRepository : IBasicRepository<OrganizationUnit, Guid> |
||||
|
{ |
||||
|
Task<List<OrganizationUnit>> GetChildrenAsync(Guid? parentId); |
||||
|
|
||||
|
Task<List<OrganizationUnit>> GetAllChildrenWithParentCodeAsync(string code, Guid? parentId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,178 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an organization unit (OU).
|
||||
|
/// </summary>
|
||||
|
public class OrganizationUnit : FullAuditedAggregateRoot<Guid>, IMultiTenant |
||||
|
{ |
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parent <see cref="OrganizationUnit"/> Id.
|
||||
|
/// Null, if this OU is root.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid? ParentId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hierarchical Code of this organization unit.
|
||||
|
/// Example: "00001.00042.00005".
|
||||
|
/// This is a unique code for a Tenant.
|
||||
|
/// It's changeable if OU hierarch is changed.
|
||||
|
/// </summary>
|
||||
|
public virtual string Code { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Display name of this role.
|
||||
|
/// </summary>
|
||||
|
public virtual string DisplayName { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Children of this OU.
|
||||
|
/// </summary>
|
||||
|
public virtual ICollection<OrganizationUnit> Children { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrganizationUnit"/> class.
|
||||
|
/// </summary>
|
||||
|
public OrganizationUnit() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrganizationUnit"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">Tenant's Id or null for host.</param>
|
||||
|
/// <param name="displayName">Display name.</param>
|
||||
|
/// <param name="parentId">Parent's Id or null if OU is a root.</param>
|
||||
|
public OrganizationUnit(Guid? tenantId, string displayName, Guid? parentId = null) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
DisplayName = displayName; |
||||
|
ParentId = parentId; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates code for given numbers.
|
||||
|
/// Example: if numbers are 4,2 then returns "00004.00002";
|
||||
|
/// </summary>
|
||||
|
/// <param name="numbers">Numbers</param>
|
||||
|
public static string CreateCode(params int[] numbers) |
||||
|
{ |
||||
|
if (numbers.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return numbers.Select(number => number.ToString(new string('0', OrganizationUnitConsts.CodeUnitLength))).JoinAsString("."); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Appends a child code to a parent code.
|
||||
|
/// Example: if parentCode = "00001", childCode = "00042" then returns "00001.00042".
|
||||
|
/// </summary>
|
||||
|
/// <param name="parentCode">Parent code. Can be null or empty if parent is a root.</param>
|
||||
|
/// <param name="childCode">Child code.</param>
|
||||
|
public static string AppendCode(string parentCode, string childCode) |
||||
|
{ |
||||
|
if (childCode.IsNullOrEmpty()) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(childCode), "childCode can not be null or empty."); |
||||
|
} |
||||
|
|
||||
|
if (parentCode.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return childCode; |
||||
|
} |
||||
|
|
||||
|
return parentCode + "." + childCode; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets relative code to the parent.
|
||||
|
/// Example: if code = "00019.00055.00001" and parentCode = "00019" then returns "00055.00001".
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code.</param>
|
||||
|
/// <param name="parentCode">The parent code.</param>
|
||||
|
public static string GetRelativeCode(string code, string parentCode) |
||||
|
{ |
||||
|
if (code.IsNullOrEmpty()) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(code), "code can not be null or empty."); |
||||
|
} |
||||
|
|
||||
|
if (parentCode.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
if (code.Length == parentCode.Length) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return code.Substring(parentCode.Length + 1); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates next code for given code.
|
||||
|
/// Example: if code = "00019.00055.00001" returns "00019.00055.00002".
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code.</param>
|
||||
|
public static string CalculateNextCode(string code) |
||||
|
{ |
||||
|
if (code.IsNullOrEmpty()) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(code), "code can not be null or empty."); |
||||
|
} |
||||
|
|
||||
|
var parentCode = GetParentCode(code); |
||||
|
var lastUnitCode = GetLastUnitCode(code); |
||||
|
|
||||
|
return AppendCode(parentCode, CreateCode(Convert.ToInt32(lastUnitCode) + 1)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the last unit code.
|
||||
|
/// Example: if code = "00019.00055.00001" returns "00001".
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code.</param>
|
||||
|
public static string GetLastUnitCode(string code) |
||||
|
{ |
||||
|
if (code.IsNullOrEmpty()) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(code), "code can not be null or empty."); |
||||
|
} |
||||
|
|
||||
|
var splittedCode = code.Split('.'); |
||||
|
return splittedCode[splittedCode.Length - 1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets parent code.
|
||||
|
/// Example: if code = "00019.00055.00001" returns "00019.00055".
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code.</param>
|
||||
|
public static string GetParentCode(string code) |
||||
|
{ |
||||
|
if (code.IsNullOrEmpty()) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(code), "code can not be null or empty."); |
||||
|
} |
||||
|
|
||||
|
var splittedCode = code.Split('.'); |
||||
|
if (splittedCode.Length == 1) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return splittedCode.Take(splittedCode.Length - 1).JoinAsString("."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,138 @@ |
|||||
|
using Microsoft.Extensions.Localization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
using Volo.Abp.Identity.Localization; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs domain logic for Organization Units.
|
||||
|
/// </summary>
|
||||
|
public class OrganizationUnitManager : DomainService |
||||
|
{ |
||||
|
protected IOrganizationUnitRepository _organizationUnitRepository { get; private set; } |
||||
|
|
||||
|
private readonly IStringLocalizer<IdentityResource> _localizer; |
||||
|
|
||||
|
public OrganizationUnitManager( |
||||
|
IOrganizationUnitRepository organizationUnitRepository, |
||||
|
IStringLocalizer<IdentityResource> localizer) |
||||
|
{ |
||||
|
_organizationUnitRepository = organizationUnitRepository; |
||||
|
_localizer = localizer; |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task CreateAsync(OrganizationUnit organizationUnit) |
||||
|
{ |
||||
|
organizationUnit.Code = await GetNextChildCodeAsync(organizationUnit.ParentId); |
||||
|
await ValidateOrganizationUnitAsync(organizationUnit); |
||||
|
await _organizationUnitRepository.InsertAsync(organizationUnit); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task UpdateAsync(OrganizationUnit organizationUnit) |
||||
|
{ |
||||
|
await ValidateOrganizationUnitAsync(organizationUnit); |
||||
|
await _organizationUnitRepository.UpdateAsync(organizationUnit); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> GetNextChildCodeAsync(Guid? parentId) |
||||
|
{ |
||||
|
var lastChild = await GetLastChildOrNullAsync(parentId); |
||||
|
if (lastChild == null) |
||||
|
{ |
||||
|
var parentCode = parentId != null ? await GetCodeOrDefaultAsync(parentId.Value) : null; |
||||
|
return OrganizationUnit.AppendCode(parentCode, OrganizationUnit.CreateCode(1)); |
||||
|
} |
||||
|
|
||||
|
return OrganizationUnit.CalculateNextCode(lastChild.Code); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<OrganizationUnit> GetLastChildOrNullAsync(Guid? parentId) |
||||
|
{ |
||||
|
var children = await _organizationUnitRepository.GetChildrenAsync(parentId); |
||||
|
return children.OrderBy(c => c.Code).LastOrDefault(); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
var children = await FindChildrenAsync(id, true); |
||||
|
|
||||
|
foreach (var child in children) |
||||
|
{ |
||||
|
await _organizationUnitRepository.DeleteAsync(child); |
||||
|
} |
||||
|
|
||||
|
await _organizationUnitRepository.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task MoveAsync(Guid id, Guid? parentId) |
||||
|
{ |
||||
|
var organizationUnit = await _organizationUnitRepository.GetAsync(id); |
||||
|
if (organizationUnit.ParentId == parentId) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
//Should find children before Code change
|
||||
|
var children = await FindChildrenAsync(id, true); |
||||
|
|
||||
|
//Store old code of OU
|
||||
|
var oldCode = organizationUnit.Code; |
||||
|
|
||||
|
//Move OU
|
||||
|
organizationUnit.Code = await GetNextChildCodeAsync(parentId); |
||||
|
organizationUnit.ParentId = parentId; |
||||
|
|
||||
|
await ValidateOrganizationUnitAsync(organizationUnit); |
||||
|
|
||||
|
//Update Children Codes
|
||||
|
foreach (var child in children) |
||||
|
{ |
||||
|
child.Code = OrganizationUnit.AppendCode(organizationUnit.Code, OrganizationUnit.GetRelativeCode(child.Code, oldCode)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> GetCodeOrDefaultAsync(Guid id) |
||||
|
{ |
||||
|
var ou = await _organizationUnitRepository.GetAsync(id); |
||||
|
return ou?.Code; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task ValidateOrganizationUnitAsync(OrganizationUnit organizationUnit) |
||||
|
{ |
||||
|
var siblings = (await FindChildrenAsync(organizationUnit.ParentId)) |
||||
|
.Where(ou => ou.Id != organizationUnit.Id) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (siblings.Any(ou => ou.DisplayName == organizationUnit.DisplayName)) |
||||
|
{ |
||||
|
throw new UserFriendlyException(_localizer["OrganizationUnitDuplicateDisplayNameWarning", organizationUnit.DisplayName]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<List<OrganizationUnit>> FindChildrenAsync(Guid? parentId, bool recursive = false) |
||||
|
{ |
||||
|
if (!recursive) |
||||
|
{ |
||||
|
return await _organizationUnitRepository.GetChildrenAsync(parentId); |
||||
|
} |
||||
|
|
||||
|
if (!parentId.HasValue) |
||||
|
{ |
||||
|
return await _organizationUnitRepository.GetListAsync(); |
||||
|
} |
||||
|
|
||||
|
var code = await GetCodeOrDefaultAsync(parentId.Value); |
||||
|
|
||||
|
return await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(code, parentId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents membership of a User to an OU.
|
||||
|
/// </summary>
|
||||
|
public class OrganizationUnitRole : CreationAuditedEntity<Guid>, IMultiTenant, ISoftDelete |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// TenantId of this entity.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Id of the Role.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid RoleId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Id of the <see cref="OrganizationUnit"/>.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid OrganizationUnitId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies if the organization is soft deleted or not.
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsDeleted { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrganizationUnitRole"/> class.
|
||||
|
/// </summary>
|
||||
|
public OrganizationUnitRole() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrganizationUnitRole"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">TenantId</param>
|
||||
|
/// <param name="roleId">Id of the User.</param>
|
||||
|
/// <param name="organizationUnitId">Id of the <see cref="OrganizationUnit"/>.</param>
|
||||
|
public OrganizationUnitRole(Guid? tenantId, Guid roleId, Guid organizationUnitId) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
RoleId = roleId; |
||||
|
OrganizationUnitId = organizationUnitId; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.Organizations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents membership of a User to an OU.
|
||||
|
/// </summary>
|
||||
|
public class OrganizationUnitUser : CreationAuditedEntity<Guid>, IMultiTenant, ISoftDelete |
||||
|
{ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// TenantId of this entity.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid? TenantId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Id of the User.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid UserId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Id of the <see cref="OrganizationUnit"/>.
|
||||
|
/// </summary>
|
||||
|
public virtual Guid OrganizationUnitId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies if the organization is soft deleted or not.
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsDeleted { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="UserOrganizationUnit"/> class.
|
||||
|
/// </summary>
|
||||
|
public OrganizationUnitUser() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="UserOrganizationUnit"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tenantId">TenantId</param>
|
||||
|
/// <param name="userId">Id of the User.</param>
|
||||
|
/// <param name="organizationUnitId">Id of the <see cref="OrganizationUnit"/>.</param>
|
||||
|
public OrganizationUnitUser(Guid? tenantId, Guid userId, Guid organizationUnitId) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
UserId = userId; |
||||
|
OrganizationUnitId = organizationUnitId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.Identity.Organizations; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.EntityFrameworkCore |
||||
|
{ |
||||
|
public class EfCoreOrganizationUnitRepository : EfCoreRepository<IIdentityDbContext, OrganizationUnit, Guid>, IOrganizationUnitRepository |
||||
|
{ |
||||
|
public EfCoreOrganizationUnitRepository(IDbContextProvider<IIdentityDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<List<OrganizationUnit>> GetChildrenAsync(Guid? parentId) |
||||
|
{ |
||||
|
return await DbSet.Where(x => x.ParentId == parentId) |
||||
|
.ToListAsync(); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<OrganizationUnit>> GetAllChildrenWithParentCodeAsync(string code, Guid? parentId) |
||||
|
{ |
||||
|
return await DbSet.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value) |
||||
|
.ToListAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue