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