diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md index 71bf58333c..82464cd580 100644 --- a/docs/en/Modules/Index.md +++ b/docs/en/Modules/Index.md @@ -17,7 +17,7 @@ There are some **free and open source** application modules developed and mainta * **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module. * [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module. * **Feature Management**: Used to persist and manage the [features](../Features.md). -* **Identity**: Manages roles, users and their permissions, based on the Microsoft Identity library. +* **Identity**: Manages [organization units](OrganizationUnit-Management.md), roles, users and their permissions, based on the Microsoft Identity library. * **IdentityServer**: Integrates to IdentityServer4. * **Permission Management**: Used to persist permissions. * **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md). diff --git a/docs/en/Modules/OrganizationUnit-Management.md b/docs/en/Modules/OrganizationUnit-Management.md new file mode 100644 index 0000000000..5befdb9349 --- /dev/null +++ b/docs/en/Modules/OrganizationUnit-Management.md @@ -0,0 +1,125 @@ +# Organization Unit Management + +Organization units (OU) is a part of **Identity Module** and can be used to **hierarchically group users and entities**. + +### OrganizationUnit Entity + +An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are: + +- **TenantId**: Tenant's Id of this OU. Can be null for host OUs. +- **ParentId**: Parent OU's Id. Can be null if this is a root OU. +- **Code**: A hierarchical string code that is unique for a tenant. +- **DisplayName**: Shown name of the OU. + +The OrganizationUnit entity's primary key (Id) is a **Guid** type and it derives from the [**FullAuditedAggregateRoot**](../Entities##aggregateroot-class) class which provides audit information with **IsDeleted** property (OUs are not deleted from the database, they are just marked as deleted). + +#### Organization Tree + +Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree; + +- There can be more than one root (where the ParentId is null). +- Maximum depth of the tree is defined as a constant as OrganizationUnit.**MaxDepth**. The value is **16**. +- There is a limit for the first-level children count of an OU (because of the fixed OU Code unit length explained below). + +#### OU Code + +OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this: + +"**00001.00042.00005**" + +This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code: + +- It must be **unique** for a [tenant](../Multi-Tenancy.md). +- All the children of the same OU have codes that **start with the parent OU's code**. +- It's **fixed length** and based on the level of the OU in the tree, as shown in the sample. +- While the OU code is unique, it can be **changeable** if you move an OU. +- We must reference an OU by Id, not Code. + +### OrganizationUnit Manager + +The **OrganizationUnitManager** class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are: + +- Create, Update or Delete an OU +- Move an OU in the OU tree. +- Getting information about the OU tree and its items. + +#### Multi-Tenancy + +The OrganizationUnitManager is designed to work for a **single tenant** at a time. It works for the **current tenant** by default. + +### Common Use Cases + +There are some common use cases for OUs. You can find the source code of the samples [here](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample). + +#### Creating An Entity That Belongs To An Organization Unit + +The most obvious usage of OUs is to assign an entity to an OU. Sample entity: + +```csharp +public class Product : AuditedAggregateRoot, IMultiTenant +{ + public virtual Guid OrganizationUnitId { get; private set; } + public virtual Guid? TenantId { get; } + public virtual string Name { get; private set; } + public virtual float Price { get; private set; } +} +``` + +You need to create **OrganizationUnitId** property to assign this entity to an OU. Depending on requirement, this property can be nullable. You can now relate a Product to an OU and query the products of a specific OU. + +You can use **IMultiTenant** interface if you want to distinguish products of different tenants in a multi-tenant application (see the [Multi-Tenancy document](https://aspnetboilerplate.com/Pages/Documents/Multi-Tenancy#data-filters) for more info). If your application is not multi-tenant, you don't need this interface and property. + +#### Getting Entities In An Organization Unit + +To get the Products of an OU, you can implement a simple domain service; [Product Manager](https://github.com/abpframework/abp-samples/blob/master/OrganizationUnitSample/src/OrganizationUnitSample.Domain/Products/ProductManager.cs) in this case to get the filtered data: + +```csharp +public class ProductManager : IDomainService +{ + private readonly IProductRepository _productRepository; + + public ProductManager(IProductRepository productRepository) + { + _productRepository = productRepository; + } + + public List GetProductsInOu(OrganizationUnit organizationUnit) + { + return (await _productRepository.GetListAsync()).Where(q => q.OrganizationUnitId == organizationUnit.Id).ToList(); + } +} +``` + +**For better practice**, you should consider querying it on domain layer for performance and scalability. To do so, add a method to your repository interface: + +```csharp +public interface IProductRepository : IRepository +{ + public Task> GetProductsOfOrganizationUnitAsync(Guid organizationUnitId); +} +``` + +Then implement it on your ORM layer (which is EntityFrameworkCore in this [sample](https://github.com/abpframework/abp-samples/blob/master/OrganizationUnitSample/src/OrganizationUnitSample.EntityFrameworkCore/Products/ProductRepository.cs)): + +```csharp +public Task> GetProductsOfOrganizationUnitAsync(Guid organizationUnitId) +{ + return DbSet.Where(p => p.OrganizationUnitId == organizationUnitId).ToListAsync(); +} +``` + +Afterwards, you can modify your domain service like below: + +```csharp +public List GetProductsInOu(OrganizationUnit organizationUnit) +{ + return await _productRepository.GetProductsOfOrganizationUnitAsync(organizationUnit.Id); +} +``` + +### Settings + +You can find **MaxUserMembershipCount** settings under SettingManagement: + +- **MaxUserMembershipCount**: Maximum allowed membership count for a user. + Default value is **int.MaxValue** which allows a user to be a member of unlimited OUs at the same time. \ No newline at end of file