domain logic for organization unit

pull/2563/head
Mehmet Perk 6 years ago
parent d63b1a44ae
commit 286d7909b7

@ -63,6 +63,7 @@
"Identity.PasswordConfirmationFailed": "Password does not match the confirm password.",
"Identity.StaticRoleRenamingErrorMessage": "Static roles can not be renamed.",
"Identity.StaticRoleDeletionErrorMessage": "Static roles can not be deleted.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "There is already an organization unit with name {0}. Two units with same name can not be created in same level.",
"Volo.Abp.Identity:010001": "You can not delete your own account!",
"Permission:IdentityManagement": "Identity management",
"Permission:RoleManagement": "Role management",

@ -63,6 +63,7 @@
"Identity.PasswordConfirmationFailed": "A senha não confere com a confirmação de senha.",
"Identity.StaticRoleRenamingErrorMessage": "Perfis estáticos não podem ser renomeados.",
"Identity.StaticRoleDeletionErrorMessage": "Perfis estáticos não podem ser excluídos.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "Já existe uma unidade organizacional com o nome {0}. Duas unidades com o mesmo nome não pode ser criada no mesmo nível.",
"Volo.Abp.Identity:010001": "Você não pode deletar sua própria conta!",
"Permission:IdentityManagement": "Acessos",
"Permission:RoleManagement": "Perfis",

@ -63,6 +63,7 @@
"Identity.UserNameNotFound": "{0} kullanıcısı bulunamadı.",
"Identity.UserNotInRole": "Kullanıcı '{0}' rolünde değil.",
"Identity.PasswordConfirmationFailed": "Yeni şifre ile onay şifresi uyuşmuyor.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "{0} isminde bir birim zaten var. Aynı seviyede aynı isimli iki birim olamaz.",
"Volo.Abp.Identity:010001": "Kendi hesabınızı silemezsiniz!",
"Permission:IdentityManagement": "Kimlik yönetimi",
"Permission:RoleManagement": "Rol yönetimi",

@ -63,6 +63,7 @@
"Identity.PasswordConfirmationFailed": "密码或确认密码不一致.",
"Identity.StaticRoleRenamingErrorMessage": "无法重命名静态角色.",
"Identity.StaticRoleDeletionErrorMessage": "无法删除静态角色.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.",
"Volo.Abp.Identity:010001": "您无法删除自己的帐户!",
"Permission:IdentityManagement": "身份标识管理",
"Permission:RoleManagement": "角色管理",

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

@ -14,8 +14,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj" />
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.EntityFrameworkCore\Volo.Abp.Users.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj" />
</ItemGroup>
</Project>

@ -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();
}
}
}

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity.Organizations;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
@ -12,5 +13,11 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
DbSet<IdentityRole> Roles { get; set; }
DbSet<IdentityClaimType> ClaimTypes { get; set; }
DbSet<OrganizationUnit> OrganizationUnits { get; set; }
DbSet<OrganizationUnitRole> OrganizationUnitRoles { get; set; }
DbSet<OrganizationUnitUser> OrganizationUnitUsers { get; set; }
}
}
}

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity.Organizations;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
@ -16,6 +17,12 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
public DbSet<IdentityClaimType> ClaimTypes { get; set; }
public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
public DbSet<OrganizationUnitRole> OrganizationUnitRoles { get; set; }
public DbSet<OrganizationUnitUser> OrganizationUnitUsers { get; set; }
public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
: base(options)
{

Loading…
Cancel
Save