diff --git a/projects/solidify-frontend/src/lib/core/helpers/form-validation.helper.ts b/projects/solidify-frontend/src/lib/core/helpers/form-validation.helper.ts
index a6a8b6439b795eb00ba2f1411efae4d2e44d0dbc..4663e1da8ebb12600ab0a1cce9431411c7fa1459 100644
--- a/projects/solidify-frontend/src/lib/core/helpers/form-validation.helper.ts
+++ b/projects/solidify-frontend/src/lib/core/helpers/form-validation.helper.ts
@@ -33,6 +33,7 @@ import {
 } from "rxjs";
 import {tap} from "rxjs/operators";
 import {
+  isInstanceOf,
   isNullOrUndefined,
   isNullOrUndefinedOrWhiteString,
   isTrue,
@@ -99,6 +100,35 @@ export class FormValidationHelper {
     return Array.from(MappingObjectUtil.values(errors)).join(". ");
   }
 
+  static getListAbstractControlFromFormGroup(formGroup: FormGroup): AbstractControl[] {
+    const listFormControl = [];
+    Object.keys(formGroup.controls).forEach(key => {
+      listFormControl.push(formGroup.controls[key]);
+    });
+    return listFormControl;
+  }
+
+  static getListFormControlFromFormGroup(formGroup: FormGroup): FormControl[] {
+    return this.getListAbstractControlFromFormGroup(formGroup).filter(c => isInstanceOf(c, FormControl));
+  }
+
+  static getListFormArrayFromFormGroup(formGroup: FormGroup): FormArray[] {
+    return this.getListAbstractControlFromFormGroup(formGroup).filter(c => isInstanceOf(c, FormArray));
+  }
+
+  static getListFormGroupFromFormGroup(formGroup: FormGroup): FormGroup[] {
+    return this.getListAbstractControlFromFormGroup(formGroup).filter(c => isInstanceOf(c, FormGroup));
+  }
+
+  static getControlName(c: AbstractControl): string | undefined {
+    const parent = c?.parent;
+    if (isNullOrUndefined(parent) || isInstanceOf(parent, FormArray)) {
+      return undefined;
+    }
+    const parentControls = parent.controls;
+    return Object.keys(parentControls).find(name => c === parentControls[name]) || undefined;
+  }
+
   static hasRequiredFieldByName(form: FormGroup, abstractControlName: string): boolean {
     const formControl = this.getFormControl(form, abstractControlName);
     return this.hasRequiredField(formControl);
diff --git a/projects/solidify-frontend/src/lib/core/models/dto/base-relation-resource.model.ts b/projects/solidify-frontend/src/lib/core/models/dto/base-relation-resource.model.ts
index 6341eaf75d6d8dbaf4f7cffc5f40b93c4c3d943b..2ad5763ed176febb41876f41f03ff257009d1e0d 100644
--- a/projects/solidify-frontend/src/lib/core/models/dto/base-relation-resource.model.ts
+++ b/projects/solidify-frontend/src/lib/core/models/dto/base-relation-resource.model.ts
@@ -21,9 +21,12 @@
  * ----------------------------------------------------------------------------------------------%%
  */
 
+import {ChangeInfo} from "./change-info.model";
 
 export type BaseRelationResourceType = BaseRelationResource;
 
 export interface BaseRelationResource {
-
+  compositeKey?: string;
+  creation?: ChangeInfo;
+  lastUpdate?: ChangeInfo;
 }
diff --git a/projects/solidify-frontend/src/lib/core/models/dto/base-resource-with-relation.model.ts b/projects/solidify-frontend/src/lib/core/models/dto/base-resource-with-relation.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e93b293d855f255c7bea1e39bd3ec58b719d9f69
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/models/dto/base-resource-with-relation.model.ts
@@ -0,0 +1,38 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - base-resource-with-relation.model.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {BaseRelationResourceType} from "./base-relation-resource.model";
+import {BaseResource} from "./base-resource.model";
+import {ChangeInfo} from "./change-info.model";
+
+export type BaseResourceWithRelationType<URelation extends BaseRelationResourceType> = BaseResourceWithRelation<URelation>;
+
+export interface BaseResourceWithRelation<URelation extends BaseRelationResourceType> extends BaseResource {
+  resId?: string;
+  joinResource?: URelation;
+}
+
+export type BaseResourceWithRelationExtended<URelation extends BaseRelationResourceType> = {
+  creation?: ChangeInfo;
+  lastUpdate?: ChangeInfo;
+} & BaseResourceWithRelation<URelation>;
diff --git a/projects/solidify-frontend/src/lib/core/models/public_api.ts b/projects/solidify-frontend/src/lib/core/models/public_api.ts
index b237af15c409b6c5c0536d7e81afdee4dace9cf9..74bbfb6b0869e3ddf77d886d36fd628634e9ff3a 100644
--- a/projects/solidify-frontend/src/lib/core/models/public_api.ts
+++ b/projects/solidify-frontend/src/lib/core/models/public_api.ts
@@ -44,6 +44,7 @@ export * from "./dto/base-resource-logo.model";
 export * from "./dto/base-resource-thumbnail.model";
 export * from "./dto/base-resource-with-labels.model";
 export * from "./dto/base-resource-with-logo.model";
+export * from "./dto/base-resource-with-relation.model";
 export * from "./dto/change-info.model";
 export * from "./dto/citation.model";
 export * from "./dto/collection-typed.model";
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-action.helper.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-action.helper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f0bd5ed985c024ace0180deaa3b7fbad308e2a2
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-action.helper.ts
@@ -0,0 +1,114 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers-action.helper.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {BaseRelationResourceType} from "../../../models/dto/base-relation-resource.model";
+import {BaseResourceWithRelationType} from "../../../models/dto/base-resource-with-relation.model";
+import {BaseResourceType} from "../../../models/dto/base-resource.model";
+import {MultiRelation2TiersNameSpace} from "./multi-relation-2-tiers-namespace.model";
+import {MultiRelation2TiersAction} from "./multi-relation-2-tiers.action";
+
+export class MultiRelation2TiersActionHelper {
+  static getAll(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetAll>): MultiRelation2TiersAction.GetAll {
+    return new relation2TiersNameSpace.GetAll(...args);
+  }
+
+  static getAllSuccess<TResource extends BaseResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetAllSuccess>): MultiRelation2TiersAction.GetAllSuccess<TResource> {
+    return new relation2TiersNameSpace.GetAllSuccess(...args);
+  }
+
+  static getAllFail(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetAllFail>): MultiRelation2TiersAction.GetAllFail {
+    return new relation2TiersNameSpace.GetAllFail(...args);
+  }
+
+  static getById(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetById>): MultiRelation2TiersAction.GetById {
+    return new relation2TiersNameSpace.GetById(...args);
+  }
+
+  static getByIdSuccess<TResource extends BaseResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetByIdSuccess>): MultiRelation2TiersAction.GetByIdSuccess<TResource> {
+    return new relation2TiersNameSpace.GetByIdSuccess(...args);
+  }
+
+  static getByIdFail(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.GetByIdFail>): MultiRelation2TiersAction.GetByIdFail {
+    return new relation2TiersNameSpace.GetByIdFail(...args);
+  }
+
+  static create<URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.Create>): MultiRelation2TiersAction.Create<URelation> {
+    return new relation2TiersNameSpace.Create(...args);
+  }
+
+  static createSuccess<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.CreateSuccess>): MultiRelation2TiersAction.CreateSuccess<TResource, URelation> {
+    return new relation2TiersNameSpace.CreateSuccess(...args);
+  }
+
+  static createFail<URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.CreateFail>): MultiRelation2TiersAction.CreateFail<URelation> {
+    return new relation2TiersNameSpace.CreateFail(...args);
+  }
+
+  static deleteList(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.DeleteList>): MultiRelation2TiersAction.DeleteList {
+    return new relation2TiersNameSpace.DeleteList(...args);
+  }
+
+  static deleteListSuccess(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.DeleteListSuccess>): MultiRelation2TiersAction.DeleteListSuccess {
+    return new relation2TiersNameSpace.DeleteListSuccess(...args);
+  }
+
+  static deleteListFail(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.DeleteListFail>): MultiRelation2TiersAction.DeleteListFail {
+    return new relation2TiersNameSpace.DeleteListFail(...args);
+  }
+
+  static delete(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.Delete>): MultiRelation2TiersAction.Delete {
+    return new relation2TiersNameSpace.Delete(...args);
+  }
+
+  static deleteSuccess(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.DeleteSuccess>): MultiRelation2TiersAction.DeleteSuccess {
+    return new relation2TiersNameSpace.DeleteSuccess(...args);
+  }
+
+  static deleteFail(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.DeleteFail>): MultiRelation2TiersAction.DeleteFail {
+    return new relation2TiersNameSpace.DeleteFail(...args);
+  }
+
+  static update<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.Update>): MultiRelation2TiersAction.Update<TResource, URelation> {
+    return new relation2TiersNameSpace.Update(...args);
+  }
+
+  static updateSuccess<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.UpdateSuccess>): MultiRelation2TiersAction.UpdateSuccess<TResource, URelation> {
+    return new relation2TiersNameSpace.UpdateSuccess(...args);
+  }
+
+  static updateFail<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.UpdateFail>): MultiRelation2TiersAction.UpdateFail<TResource, URelation> {
+    return new relation2TiersNameSpace.UpdateFail(...args);
+  }
+
+  static updateRelation<TRelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.UpdateRelation>): MultiRelation2TiersAction.UpdateRelation<TRelation> {
+    return new relation2TiersNameSpace.UpdateRelation(...args);
+  }
+
+  static updateRelationSuccess<TRelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.UpdateRelationSuccess>): MultiRelation2TiersAction.UpdateRelationSuccess<TRelation> {
+    return new relation2TiersNameSpace.UpdateRelationSuccess(...args);
+  }
+
+  static updateRelationFail<TRelation extends BaseRelationResourceType>(relation2TiersNameSpace: MultiRelation2TiersNameSpace, ...args: ConstructorParameters<typeof MultiRelation2TiersAction.UpdateRelationFail>): MultiRelation2TiersAction.UpdateRelationFail<TRelation> {
+    return new relation2TiersNameSpace.UpdateRelationFail(...args);
+  }
+}
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-namespace.model.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-namespace.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c94693c7e0198d89141c09dc15f0fd6e4a739d75
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-namespace.model.ts
@@ -0,0 +1,49 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers-namespace.model.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {BaseResourceStoreNameSpace} from "../../../models/stores/base-resource-store-namespace.model";
+import {StoreActionClass} from "../../../models/stores/state-action.model";
+
+export interface MultiRelation2TiersNameSpace extends BaseResourceStoreNameSpace {
+  GetAll: StoreActionClass;
+  GetAllSuccess: StoreActionClass;
+  GetAllFail: StoreActionClass;
+  GetById: StoreActionClass;
+  GetByIdSuccess: StoreActionClass;
+  GetByIdFail: StoreActionClass;
+  Create: StoreActionClass;
+  CreateSuccess: StoreActionClass;
+  CreateFail: StoreActionClass;
+  DeleteList: StoreActionClass;
+  DeleteListSuccess: StoreActionClass;
+  DeleteListFail: StoreActionClass;
+  Delete: StoreActionClass;
+  DeleteSuccess: StoreActionClass;
+  DeleteFail: StoreActionClass;
+  Update: StoreActionClass;
+  UpdateSuccess: StoreActionClass;
+  UpdateFail: StoreActionClass;
+  UpdateRelation: StoreActionClass;
+  UpdateRelationSuccess: StoreActionClass;
+  UpdateRelationFail: StoreActionClass;
+}
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-options.model.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-options.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f47daf275199ba91d0163b05172b6ec6299ea51
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-options.model.ts
@@ -0,0 +1,32 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers-options.model.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {ApiResourceNamePartialEnum} from "../../../enums/partial/api-resource-name-partial.enum";
+import {ExtendEnum} from "../../../types/extend-enum.type";
+import {BaseResourceOptions} from "../base/base-resource-options.model";
+import {MultiRelation2TiersNameSpace} from "./multi-relation-2-tiers-namespace.model";
+
+export interface MultiRelation2TiersOptions extends BaseResourceOptions {
+  nameSpace: MultiRelation2TiersNameSpace;
+  resourceName: ExtendEnum<ApiResourceNamePartialEnum>;
+}
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-state.model.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-state.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eea9af7478fd11cea60974e9dd58ec185c2366a7
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-state.model.ts
@@ -0,0 +1,30 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers-state.model.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {BaseResourceType} from "../../../models/dto/base-resource.model";
+import {BaseResourceStateModel} from "../../../models/stores/base-resource-state.model";
+
+export interface MultiRelation2TiersStateModel<TResource extends BaseResourceType> extends BaseResourceStateModel {
+  selected: TResource[] | undefined;
+  current: TResource | undefined;
+}
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.action.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.action.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2cd74486fc299ca84690b763089fcf436ce549da
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.action.ts
@@ -0,0 +1,178 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers.action.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {BaseRelationResourceType} from "../../../models/dto/base-relation-resource.model";
+import {BaseResourceWithRelationType} from "../../../models/dto/base-resource-with-relation.model";
+import {BaseResourceType} from "../../../models/dto/base-resource.model";
+import {CollectionTyped} from "../../../models/dto/collection-typed.model";
+import {QueryParameters} from "../../../models/query-parameters/query-parameters.model";
+import {
+  BaseAction,
+  BaseSubActionFail,
+  BaseSubActionSuccess,
+} from "../../../models/stores/base.action";
+import {MultiRelation2TiersNameSpace} from "./multi-relation-2-tiers-namespace.model";
+
+export namespace MultiRelation2TiersAction {
+  export class GetAll extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get All";
+
+    constructor(public parentId: string, public queryParameters?: QueryParameters, public keepCurrentContext: boolean = false) {
+      super();
+    }
+  }
+
+  export class GetAllSuccess<TResource extends BaseResourceType> extends BaseSubActionSuccess<GetAll> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get All Success";
+
+    constructor(public parentAction: GetAll, public list: CollectionTyped<TResource>) {
+      super(parentAction);
+    }
+  }
+
+  export class GetAllFail extends BaseSubActionFail<GetAll> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get All Fail";
+  }
+
+  export class GetById extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get By Id";
+
+    constructor(public parentId: string, public resId: string, public keepCurrentContext: boolean = false) {
+      super();
+    }
+  }
+
+  export class GetByIdSuccess<TResource extends BaseResourceType> extends BaseSubActionSuccess<GetById> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get By Id Success";
+
+    constructor(public parentAction: GetById, public model: TResource) {
+      super(parentAction);
+    }
+  }
+
+  export class GetByIdFail extends BaseSubActionFail<GetById> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Get By Id Fail";
+  }
+
+  export class Create<URelation extends BaseRelationResourceType> extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Create";
+
+    constructor(public parentId: string, public resId: string, public relation: URelation) {
+      super();
+    }
+  }
+
+  export class CreateSuccess<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType> extends BaseSubActionSuccess<Create<URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Create Success";
+
+    constructor(public parentAction: Create<URelation>, public result: TResource) {
+      super(parentAction);
+    }
+  }
+
+  export class CreateFail<URelation extends BaseRelationResourceType> extends BaseSubActionFail<Create<URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Create Fail";
+  }
+
+  export class DeleteList extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete List";
+
+    constructor(public parentId: string, public listResId: string[]) {
+      super();
+    }
+  }
+
+  export class DeleteListSuccess extends BaseSubActionSuccess<DeleteList> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete ListSuccess";
+  }
+
+  export class DeleteListFail extends BaseSubActionFail<DeleteList> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete ListFail";
+  }
+
+  export class Delete extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete";
+
+    constructor(public parentId: string, public resId: string, public compositeKey: string) {
+      super();
+    }
+  }
+
+  export class DeleteSuccess extends BaseSubActionSuccess<Delete> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete Success";
+  }
+
+  export class DeleteFail extends BaseSubActionFail<Delete> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Delete Fail";
+  }
+
+  export class Update<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType> extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update";
+
+    constructor(public parentId: string, public newRelation: TResource[]) {
+      super();
+    }
+  }
+
+  export class UpdateSuccess<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType> extends BaseSubActionSuccess<Update<TResource, URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update Success";
+
+    constructor(public parentAction: Update<TResource, URelation>, public parentId: string) {
+      super(parentAction);
+    }
+  }
+
+  export class UpdateFail<TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType> extends BaseSubActionFail<Update<TResource, URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update Fail";
+
+    constructor(public parentAction: Update<TResource, URelation>, public parentId: string) {
+      super(parentAction);
+    }
+  }
+
+  export class UpdateRelation<URelation extends BaseRelationResourceType> extends BaseAction {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update Relation";
+
+    constructor(public parentId: string, public resId: string, public relation: URelation) {
+      super();
+    }
+  }
+
+  export class UpdateRelationSuccess<URelation extends BaseRelationResourceType> extends BaseSubActionSuccess<UpdateRelation<URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update Relation Success";
+
+    constructor(public parentAction: UpdateRelation<URelation>) {
+      super(parentAction);
+    }
+  }
+
+  export class UpdateRelationFail<URelation extends BaseRelationResourceType> extends BaseSubActionFail<UpdateRelation<URelation>> {
+    static readonly type: string = "[{0}] Multi Relation 2 Tiers : Update Relation Fail";
+
+    constructor(public parentAction: UpdateRelation<URelation>) {
+      super(parentAction);
+    }
+  }
+}
+
+export const multiRelation2TiersActionNameSpace: MultiRelation2TiersNameSpace = MultiRelation2TiersAction;
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.state.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.state.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d01a6de4ea21571c22519dfe2110d25d82dcd714
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.state.ts
@@ -0,0 +1,364 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - multi-relation-2-tiers.state.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+import {Inject} from "@angular/core";
+import {
+  Actions,
+  Store,
+} from "@ngxs/store";
+import {
+  Observable,
+  of,
+} from "rxjs";
+import {
+  catchError,
+  map,
+  tap,
+} from "rxjs/operators";
+import {SOLIDIFY_CONSTANTS} from "../../../constants";
+import {RegisterDefaultAction} from "../../../decorators/store.decorator";
+import {ApiResourceNamePartialEnum} from "../../../enums/partial/api-resource-name-partial.enum";
+import {DefaultSolidifyEnvironment} from "../../../environments/environment.solidify-defaults";
+import {SolidifyStateError} from "../../../errors/solidify-state.error";
+import {ApiService} from "../../../http/api.service";
+import {ENVIRONMENT} from "../../../injection-tokens/environment.injection-token";
+import {BaseRelationResourceType} from "../../../models/dto/base-relation-resource.model";
+import {BaseResourceWithRelationType} from "../../../models/dto/base-resource-with-relation.model";
+import {BaseResourceType} from "../../../models/dto/base-resource.model";
+import {CollectionTyped} from "../../../models/dto/collection-typed.model";
+import {SolidifyHttpErrorResponseModel} from "../../../models/errors/solidify-http-error-response.model";
+import {QueryParameters} from "../../../models/query-parameters/query-parameters.model";
+import {NotifierService} from "../../../models/services/notifier-service.model";
+import {BaseResourceStateModel} from "../../../models/stores/base-resource-state.model";
+import {ActionSubActionCompletionsWrapper} from "../../../models/stores/base.action";
+import {SolidifyStateContext} from "../../../models/stores/state-context.model";
+import {
+  isNonEmptyArray,
+  isNullOrUndefined,
+  isNullOrUndefinedOrWhiteString,
+} from "../../../tools/is/is.tool";
+import {ExtendEnum} from "../../../types/extend-enum.type";
+import {ArrayUtil} from "../../../utils/array.util";
+import {ObjectUtil} from "../../../utils/object.util";
+import {ofSolidifyActionCompleted} from "../../../utils/stores/store.tool";
+import {StoreUtil} from "../../../utils/stores/store.util";
+import {
+  BaseResourceState,
+  defaultBaseResourceStateInitValue,
+} from "../base/base-resource.state";
+import {MultiRelation2TiersActionHelper} from "./multi-relation-2-tiers-action.helper";
+import {MultiRelation2TiersNameSpace} from "./multi-relation-2-tiers-namespace.model";
+import {MultiRelation2TiersOptions} from "./multi-relation-2-tiers-options.model";
+import {MultiRelation2TiersStateModel} from "./multi-relation-2-tiers-state.model";
+import {MultiRelation2TiersAction} from "./multi-relation-2-tiers.action";
+
+export const defaultMultiRelation2TiersStateInitValue: <TResourceType extends BaseResourceType> () => MultiRelation2TiersStateModel<TResourceType> = () =>
+  ({
+    ...defaultBaseResourceStateInitValue(),
+    selected: undefined,
+    current: undefined,
+  });
+
+// @dynamic
+export abstract class MultiRelation2TiersState<TStateModel extends BaseResourceStateModel, TResource extends BaseResourceWithRelationType<URelation>, URelation extends BaseRelationResourceType> extends BaseResourceState<TStateModel> {
+  protected readonly _resourceName: ExtendEnum<ApiResourceNamePartialEnum>;
+  protected declare readonly _nameSpace: MultiRelation2TiersNameSpace;
+  protected declare readonly _optionsState: MultiRelation2TiersOptions;
+
+  protected constructor(protected _apiService: ApiService,
+                        protected _store: Store,
+                        protected _notificationService: NotifierService,
+                        protected _actions$: Actions,
+                        @Inject(ENVIRONMENT) protected readonly _environment: DefaultSolidifyEnvironment,
+                        protected _options: MultiRelation2TiersOptions) {
+    super(_apiService, _store, _notificationService, _actions$, _environment, _options, MultiRelation2TiersState);
+    this._resourceName = this._optionsState.resourceName;
+  }
+
+  protected static _getDefaultOptions(): MultiRelation2TiersOptions | any {
+    let defaultOptions: MultiRelation2TiersOptions | any = {} as MultiRelation2TiersOptions;
+    defaultOptions = Object.assign(BaseResourceState._getDefaultOptions(), defaultOptions);
+    return defaultOptions;
+  }
+
+  private _evaluateSubResourceUrl(parentId: string): string {
+    return this._urlResource + SOLIDIFY_CONSTANTS.URL_SEPARATOR + parentId + SOLIDIFY_CONSTANTS.URL_SEPARATOR + this._resourceName;
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetAll)
+  getAll<U>(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetAll): Observable<CollectionTyped<U>> {
+    let reset = {};
+    if (!action.keepCurrentContext) {
+      reset = {
+        total: 0,
+        selected: undefined,
+      };
+    }
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter + 1,
+      queryParameters: StoreUtil.getQueryParametersToApply(action.queryParameters, ctx),
+      ...reset,
+    });
+    return this._getAll<U>(ctx, action)
+      .pipe(
+        tap((collection: CollectionTyped<U>) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.getAllSuccess<U>(this._nameSpace, action, collection));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.getAllFail(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  protected override _callGetAllEndpoint<U>(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetAll, queryParameters: QueryParameters): Observable<CollectionTyped<U>> {
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.getCollection<U>(url, queryParameters);
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetAllSuccess)
+  getAllSuccess(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetAllSuccess<TResource>): void {
+    ctx.patchState({
+      selected: action.list._data,
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+    });
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetAllFail)
+  getAllFail(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetAllFail): void {
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+    });
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetById)
+  getById<U>(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetById): Observable<U> {
+    let reset = {};
+    if (!action.keepCurrentContext) {
+      reset = {
+        current: undefined,
+      };
+    }
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter + 1,
+      ...reset,
+    });
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.getById<U>(url, action.resId)
+      .pipe(
+        tap((model: U) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.getByIdSuccess<U>(this._nameSpace, action, model));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.getByIdFail(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetByIdSuccess)
+  getByIdSuccess(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetByIdSuccess<TResource>): void {
+    ctx.patchState({
+      current: action.model,
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+    });
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.GetByIdFail)
+  getByIdFail(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.GetByIdFail): void {
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+    });
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.Create)
+  create(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.Create<URelation>): Observable<TResource> {
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.post<URelation, TResource>(`${url}/${action.resId}`, action.relation)
+      .pipe(
+        tap(result => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.createSuccess<TResource, URelation>(this._nameSpace, action, result));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.createFail(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.DeleteList)
+  deleteList(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.DeleteList): Observable<string[]> {
+    if (action.listResId.length === 0) {
+      ctx.dispatch(MultiRelation2TiersActionHelper.deleteListSuccess(this._nameSpace, action));
+      return;
+    }
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.delete<string[]>(url, action.listResId)
+      .pipe(
+        tap(() => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.deleteListSuccess(this._nameSpace, action));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.deleteListFail(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.Delete)
+  delete(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.Delete): Observable<number> {
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.delete<number>(url + SOLIDIFY_CONSTANTS.URL_SEPARATOR + action.resId)
+      .pipe(
+        tap(() => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.deleteSuccess(this._nameSpace, action));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.deleteFail(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.UpdateRelation)
+  updateRelation(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.UpdateRelation<URelation>): Observable<URelation> {
+    const url = this._evaluateSubResourceUrl(action.parentId);
+    return this._apiService.patchById<URelation>(url, action.resId, action.relation)
+      .pipe(
+        tap(() => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.updateRelationSuccess<URelation>(this._nameSpace, action));
+        }),
+        catchError((error: SolidifyHttpErrorResponseModel) => {
+          ctx.dispatch(MultiRelation2TiersActionHelper.updateRelationFail<URelation>(this._nameSpace, action));
+          throw new SolidifyStateError(this, error);
+        }),
+      );
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.Update)
+  update(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.Update<TResource, URelation>): void {
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter + 1,
+    });
+
+    this.subscribe(this._internalUpdate(ctx, action).pipe(tap(success => {
+      if (success) {
+        ctx.dispatch(MultiRelation2TiersActionHelper.updateSuccess(this._nameSpace, action, action.parentId));
+      } else {
+        ctx.dispatch(MultiRelation2TiersActionHelper.updateFail(this._nameSpace, action, action.parentId));
+      }
+    })));
+  }
+
+  protected _internalUpdate(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.Update<TResource, URelation>): Observable<boolean> {
+    const listOld = isNullOrUndefined(ctx.getState().selected) ? [] : ctx.getState().selected;
+    const listNew = action.newRelation;
+
+    const diff = ArrayUtil.diff(listOld, listNew, (a: TResource, b: TResource) => {
+      if (isNullOrUndefinedOrWhiteString(a?.joinResource?.compositeKey) && isNullOrUndefinedOrWhiteString(b?.joinResource?.compositeKey)) {
+        return false; // Allow to manage new resource
+      }
+      return a?.joinResource?.compositeKey === b?.joinResource?.compositeKey;
+    });
+
+    const listActions: ActionSubActionCompletionsWrapper[] = [];
+
+    const listActionToAdd: ActionSubActionCompletionsWrapper<MultiRelation2TiersAction.Create<URelation>>[] = listNew
+      .filter(resource => isNullOrUndefinedOrWhiteString(resource?.joinResource?.compositeKey))
+      .map(resource => ({
+        action: MultiRelation2TiersActionHelper.create(this._nameSpace, action.parentId, resource.resId, resource.joinResource),
+        subActionCompletions: [
+          this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.CreateSuccess)),
+          this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.CreateFail)),
+        ],
+      }));
+
+    const listActionToDelete: ActionSubActionCompletionsWrapper<MultiRelation2TiersAction.Delete>[] = diff.missingInArrayB.map(resource => ({
+      action: MultiRelation2TiersActionHelper.delete(this._nameSpace, action.parentId, resource.resId, resource.joinResource.compositeKey),
+      subActionCompletions: [
+        this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.DeleteSuccess)),
+        this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.DeleteFail)),
+      ],
+    }));
+
+    let listActionToUpdate = [];
+    if (isNonEmptyArray(diff.common)) {
+      // Check if difference present on join resource parameters
+      const listIdInCommon = diff.common.map(c => c.joinResource.compositeKey);
+      const oldInCommon = listOld.filter(item => listIdInCommon.includes(item.joinResource.compositeKey));
+      const newInCommon = listNew.filter(item => listIdInCommon.includes(item.joinResource.compositeKey));
+      const diffCommon = ArrayUtil.diff(oldInCommon, newInCommon, (a: TResource, b: TResource) => {
+        if (a?.joinResource?.compositeKey !== b?.joinResource?.compositeKey) {
+          return false;
+        }
+        const oldJoinResource = ObjectUtil.clone(a?.joinResource);
+        delete oldJoinResource.compositeKey;
+        delete oldJoinResource.creation;
+        delete oldJoinResource.lastUpdate;
+        const newJoinResource = ObjectUtil.clone(b?.joinResource);
+        delete newJoinResource.compositeKey;
+        delete newJoinResource.creation;
+        delete newJoinResource.lastUpdate;
+        return JSON.stringify(oldJoinResource) === JSON.stringify(newJoinResource);
+      });
+
+      listActionToUpdate = diffCommon.presentOnlyInArrayB.map(resource => ({
+        action: MultiRelation2TiersActionHelper.updateRelation<URelation>(this._nameSpace, action.parentId, resource.resId, resource.joinResource),
+        subActionCompletions: [
+          this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.UpdateRelationSuccess)),
+          this._actions$.pipe(ofSolidifyActionCompleted(this._nameSpace.UpdateRelationFail)),
+        ],
+      }));
+    }
+
+    listActions.push(...listActionToAdd);
+    listActions.push(...listActionToUpdate);
+    listActions.push(...listActionToDelete);
+
+    if (isNonEmptyArray(listActions)) {
+      return StoreUtil.dispatchSequentialActionAndWaitForSubActionsCompletion(
+        ctx,
+        listActions,
+      ).pipe(
+        map(result => result.success),
+      );
+    }
+    return of(true);
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.UpdateSuccess)
+  updateSuccess(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.UpdateSuccess<TResource, URelation>): void {
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+      selected: undefined,
+    });
+  }
+
+  @RegisterDefaultAction((multiRelation2TiersNameSpace: MultiRelation2TiersNameSpace) => multiRelation2TiersNameSpace.UpdateFail)
+  updateFail(ctx: SolidifyStateContext<MultiRelation2TiersStateModel<TResource>>, action: MultiRelation2TiersAction.UpdateFail<TResource, URelation>): void {
+    ctx.patchState({
+      isLoadingCounter: ctx.getState().isLoadingCounter - 1,
+    });
+    ctx.dispatch(MultiRelation2TiersActionHelper.getAll(this._nameSpace, action.parentId));
+  }
+}
diff --git a/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/public_api.ts b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/public_api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cabad77e5e0a34c91a898645ff10f68b2e08ada0
--- /dev/null
+++ b/projects/solidify-frontend/src/lib/core/stores/abstract/multi-relation-2-tiers/public_api.ts
@@ -0,0 +1,32 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify Frontend - public_api.ts
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+
+
+export * from "./multi-relation-2-tiers.action";
+export * from "./multi-relation-2-tiers.state";
+export * from "./multi-relation-2-tiers-action.helper";
+export * from "./multi-relation-2-tiers-namespace.model";
+export * from "./multi-relation-2-tiers-options.model";
+export * from "./multi-relation-2-tiers-state.model";
+
+
diff --git a/projects/solidify-frontend/src/lib/core/stores/public_api.ts b/projects/solidify-frontend/src/lib/core/stores/public_api.ts
index 97353baeac518966327fb208b6955fd46ea57c45..2d38c1c9b471345b5e0fec10b0c9c9fcbefe3663 100644
--- a/projects/solidify-frontend/src/lib/core/stores/public_api.ts
+++ b/projects/solidify-frontend/src/lib/core/stores/public_api.ts
@@ -27,6 +27,7 @@ export * from "./abstract/association-no-sql-read-only/public_api";
 export * from "./abstract/association-remote/public_api";
 export * from "./abstract/association/public_api";
 export * from "./abstract/composition/public_api";
+export * from "./abstract/multi-relation-2-tiers/public_api";
 export * from "./abstract/relation-2-tiers/public_api";
 export * from "./abstract/relation-3-tiers/public_api";
 export * from "./abstract/resource/public_api";
diff --git a/projects/solidify-frontend/src/lib/core/utils/array.util.ts b/projects/solidify-frontend/src/lib/core/utils/array.util.ts
index 000f510a41c709e156eb1586e3c280b8abd241ae..c166d75453b877b853e75260f0758a045d58812d 100644
--- a/projects/solidify-frontend/src/lib/core/utils/array.util.ts
+++ b/projects/solidify-frontend/src/lib/core/utils/array.util.ts
@@ -54,7 +54,7 @@ export class ArrayUtil {
    * @param arrayA
    * @param arrayB
    */
-  static diff<T>(arrayA: T[], arrayB: T[], comparator: (a: T, b, T) => boolean = undefined): ArrayDiffResult<T> {
+  static diff<T>(arrayA: T[], arrayB: T[], comparator: (a: T, b: T) => boolean = undefined): ArrayDiffResult<T> {
     /* eslint-disable no-console */
     if (isNullOrUndefined(arrayA)) {
       console.warn("'arrayA' param should not be null");
diff --git a/projects/solidify-frontend/src/lib/core/utils/stores/memoized.util.ts b/projects/solidify-frontend/src/lib/core/utils/stores/memoized.util.ts
index 4919c47332aec217dcd21d10d5b20ba2e58b4c13..89551a50efbf5fb28c65df580b167890ddeaf792 100644
--- a/projects/solidify-frontend/src/lib/core/utils/stores/memoized.util.ts
+++ b/projects/solidify-frontend/src/lib/core/utils/stores/memoized.util.ts
@@ -47,6 +47,8 @@ import {AssociationState} from "../../stores/abstract/association/association.st
 import {BasicState} from "../../stores/abstract/base/basic.state";
 import {CompositionStateModel} from "../../stores/abstract/composition/composition-state.model";
 import {CompositionState} from "../../stores/abstract/composition/composition.state";
+import {MultiRelation2TiersStateModel} from "../../stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers-state.model";
+import {MultiRelation2TiersState} from "../../stores/abstract/multi-relation-2-tiers/multi-relation-2-tiers.state";
 import {Relation2TiersStateModel} from "../../stores/abstract/relation-2-tiers/relation-2-tiers-state.model";
 import {Relation2TiersState} from "../../stores/abstract/relation-2-tiers/relation-2-tiers.state";
 import {Relation3TiersStateModel} from "../../stores/abstract/relation-3-tiers/relation-3-tiers-state.model";
@@ -117,6 +119,7 @@ export class MemoizedUtil {
   static selected<TStateModel extends AssociationStateModel<TResource>
     | AssociationNoSqlReadOnlyStateModel<TResource>
     | AssociationRemoteStateModel<TResource>
+    | MultiRelation2TiersStateModel<TResource>
     | Relation2TiersStateModel<TResource>
     | Relation3TiersStateModel<TResource>
     , TResource extends BaseResourceType, TRelation extends BaseRelationResourceType>(
@@ -124,6 +127,7 @@ export class MemoizedUtil {
     ctor: Type<AssociationState<TStateModel, TResource>>
       | Type<AssociationNoSqlReadOnlyState<TStateModel, TResource>>
       | Type<AssociationRemoteState<TStateModel, TResource>>
+      | Type<MultiRelation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation3TiersState<TStateModel, TResource, TRelation>>,
   ): Observable<TResource[]> {
@@ -133,6 +137,7 @@ export class MemoizedUtil {
   static selectedSignal<TStateModel extends AssociationStateModel<TResource>
     | AssociationNoSqlReadOnlyStateModel<TResource>
     | AssociationRemoteStateModel<TResource>
+    | MultiRelation2TiersStateModel<TResource>
     | Relation2TiersStateModel<TResource>
     | Relation3TiersStateModel<TResource>
     , TResource extends BaseResourceType, TRelation extends BaseRelationResourceType>(
@@ -140,6 +145,7 @@ export class MemoizedUtil {
     ctor: Type<AssociationState<TStateModel, TResource>>
       | Type<AssociationNoSqlReadOnlyState<TStateModel, TResource>>
       | Type<AssociationRemoteState<TStateModel, TResource>>
+      | Type<MultiRelation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation3TiersState<TStateModel, TResource, TRelation>>,
   ): Signal<TResource[]> {
@@ -149,6 +155,7 @@ export class MemoizedUtil {
   static selectedSnapshot<TStateModel extends AssociationStateModel<TResource>
     | AssociationNoSqlReadOnlyStateModel<TResource>
     | AssociationRemoteStateModel<TResource>
+    | MultiRelation2TiersStateModel<TResource>
     | Relation2TiersStateModel<TResource>
     | Relation3TiersStateModel<TResource>
     , TResource extends BaseResourceType, TRelation extends BaseRelationResourceType>(
@@ -156,6 +163,7 @@ export class MemoizedUtil {
     ctor: Type<AssociationState<TStateModel, TResource>>
       | Type<AssociationNoSqlReadOnlyState<TStateModel, TResource>>
       | Type<AssociationRemoteState<TStateModel, TResource>>
+      | Type<MultiRelation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation2TiersState<TStateModel, TResource, TRelation>>
       | Type<Relation3TiersState<TStateModel, TResource, TRelation>>,
   ): TResource[] {
@@ -291,6 +299,7 @@ export type MemoizedUtilState<TResource extends BaseResourceType> = AssociationS
   | AssociationNoSqlReadOnlyStateModel<TResource>
   | AssociationRemoteStateModel<TResource>
   | CompositionStateModel<TResource>
+  | MultiRelation2TiersStateModel<TResource>
   | Relation2TiersStateModel<TResource>
   | Relation3TiersStateModel<TResource>
   | ResourceStateModel<TResource>
@@ -303,6 +312,7 @@ export type MemoizedUtilStateType<TStateModel extends MemoizedUtilState<TResourc
   | Type<AssociationNoSqlReadOnlyState<TStateModel, TResource>>
   | Type<AssociationRemoteState<TStateModel, TResource>>
   | Type<CompositionState<TStateModel, TResource>>
+  | Type<MultiRelation2TiersState<TStateModel, TResource, TRelation>>
   | Type<Relation2TiersState<TStateModel, TResource, TRelation>>
   | Type<Relation3TiersState<TStateModel, TResource, TRelation>>
   | Type<ResourceState<TStateModel, TResource>>;
diff --git a/projects/solidify-frontend/src/lib/core/utils/string.util.ts b/projects/solidify-frontend/src/lib/core/utils/string.util.ts
index 65094ac018e635bb58d7fe5506873e340f8da1ee..0a0c2e2e4ce5e921ef9ddd58218377739231cd2a 100644
--- a/projects/solidify-frontend/src/lib/core/utils/string.util.ts
+++ b/projects/solidify-frontend/src/lib/core/utils/string.util.ts
@@ -34,14 +34,14 @@ export class StringUtil {
 
   static format(str: string, ...val: string[]): string {
     for (let i = 0; i < val.length; i++) {
-      str = StringUtil.replaceAll(str, `{${i}}`, val[i]);
+      str = this.replaceAll(str, `{${i}}`, val[i]);
     }
     return str;
   }
 
   static formatKeyValue(str: string, ...val: KeyValue[]): string {
     val.forEach(v => {
-      str = StringUtil.replaceAll(str, `{${v.key}}`, v.value);
+      str = this.replaceAll(str, `{${v.key}}`, v.value);
     });
     return str;
   }
@@ -58,12 +58,12 @@ export class StringUtil {
   }
 
   static convertSpinalCaseToPascalCase(origin: string): string {
-    origin = origin.replace("-", " ");
+    origin = this.replaceAll(origin, "-", " ");
     return ChangeCase.pascalCase(origin);
   }
 
   static convertSpinalCaseToCamelCase(origin: string): string {
-    origin = origin.replace("-", " ");
+    origin = this.replaceAll(origin, "-", " ");
     return ChangeCase.camelCase(origin);
   }
 
@@ -80,7 +80,7 @@ export class StringUtil {
    * @param origin
    */
   static convertToSpinalCase(origin: string): string {
-    origin = origin.replace(" ", "-");
+    origin = this.replaceAll(origin, " ", "-");
     return ChangeCase.kebabCase(origin);
   }
 
@@ -89,7 +89,7 @@ export class StringUtil {
    * @param origin
    */
   static convertToSnakeCase(origin: string): string {
-    origin = origin.replace(" ", "_");
+    origin = this.replaceAll(origin, " ", "_");
     return ChangeCase.snakeCase(origin);
   }
 
@@ -101,6 +101,16 @@ export class StringUtil {
     return ChangeCase.pascalCase(origin);
   }
 
+  /**
+   * Two words
+   * @param value
+   */
+  static convertToSentence(value: string): string {
+    value = StringUtil.convertToSnakeCase(value);
+    value = StringUtil.capitalize(value);
+    return this.replaceAll(value, "_", " ");
+  }
+
   static replaceAll(str: string, oldChar: string, newChar: string): string {
     const strSplitter = str.split(oldChar);
     return strSplitter.join(newChar);
@@ -110,7 +120,7 @@ export class StringUtil {
     if (isNullOrUndefined(value)) {
       return value;
     }
-    value = StringUtil.replaceAll(value, "’", "'");
+    value = this.replaceAll(value, "’", "'");
     return value
       .normalize("NFD")  // Unicode normalization (decomposition of accented characters)
       .replace(/[\u0300-\u036f]/g, "")  // Remove diacritical marks
@@ -121,15 +131,15 @@ export class StringUtil {
     if (isNullOrUndefined(value)) {
       return value;
     }
-    value = StringUtil.replaceAll(value, "\\", replacementChar);
-    value = StringUtil.replaceAll(value, "/", replacementChar);
-    value = StringUtil.replaceAll(value, ":", replacementChar);
-    value = StringUtil.replaceAll(value, "*", replacementChar);
-    value = StringUtil.replaceAll(value, "?", replacementChar);
-    value = StringUtil.replaceAll(value, "\"", replacementChar);
-    value = StringUtil.replaceAll(value, "<", replacementChar);
-    value = StringUtil.replaceAll(value, ">", replacementChar);
-    value = StringUtil.replaceAll(value, "|", replacementChar);
+    value = this.replaceAll(value, "\\", replacementChar);
+    value = this.replaceAll(value, "/", replacementChar);
+    value = this.replaceAll(value, ":", replacementChar);
+    value = this.replaceAll(value, "*", replacementChar);
+    value = this.replaceAll(value, "?", replacementChar);
+    value = this.replaceAll(value, "\"", replacementChar);
+    value = this.replaceAll(value, "<", replacementChar);
+    value = this.replaceAll(value, ">", replacementChar);
+    value = this.replaceAll(value, "|", replacementChar);
     return value;
   }
 }