diff --git a/alfa-client/Jenkinsfile.e2e b/alfa-client/Jenkinsfile.e2e
index da6f80f30f5cb7e07babc7fd53e3195f86d3afc4..ebde17972347d5b54e42ab4607728a852ac91b0e 100644
--- a/alfa-client/Jenkinsfile.e2e
+++ b/alfa-client/Jenkinsfile.e2e
@@ -462,7 +462,7 @@ Void initEnvAdminDefaultVersions() {
   env.ADMINISTRATION_HELM_CHART_VERSION = getHelmChartVersion(values)
   env.ADMINISTRATION_HELM_REPO_URL = getHelmRepoUrl()
 
-  values = getApplicationValues('admin-client')
+  values = getApplicationValues('administration-client')
   env.ADMIN_CLIENT_IMAGE_TAG = getImageTag(values)
   env.ADMIN_CLIENT_HELM_CHART_VERSION = getHelmChartVersion(values)
   env.ADMIN_CLIENT_HELM_REPO_URL = getHelmRepoUrl()
@@ -565,8 +565,8 @@ Void generateAdminNamespaceYaml() {
     envValues.administration.put("helm", ['version': env.ADMINISTRATION_HELM_CHART_VERSION, 'repoUrl': env.ADMINISTRATION_HELM_REPO_URL])
     envValues.administration.put("ozgcloud", ['feature': ['organisationsEinheiten': "true"], 'organisationEinheit': ['zufiSearchUri': generateZufiSearchUri(bezeichner)]])
 
-    envValues.admin_client.put("image", ['tag': env.ADMIN_CLIENT_IMAGE_TAG])
-    envValues.admin_client.put("helm", ['version': env.ADMIN_CLIENT_HELM_CHART_VERSION, 'repoUrl': env.ADMIN_CLIENT_HELM_REPO_URL])
+    envValues.administration_client.put("image", ['tag': env.ADMIN_CLIENT_IMAGE_TAG])
+    envValues.administration_client.put("helm", ['version': env.ADMIN_CLIENT_HELM_CHART_VERSION, 'repoUrl': env.ADMIN_CLIENT_HELM_REPO_URL])
   }
   return writeYamlToGitOps(bezeichner, envValues);
 }
@@ -657,7 +657,7 @@ Void waitForAlfaRollout(ozgCloudBezeichner) {
 Void waitForAdminRollout(String bezeichner) {
   waitForAlfaRollout([bezeichner])
   waitForHealthyApplication(bezeichner, 'administration')
-  waitForHealthyApplication(bezeichner, 'admin-client')
+  waitForHealthyApplication(bezeichner, 'administration-client')
 }
 
 Void waitForAlfaRollout(String bezeichner) {
@@ -798,7 +798,7 @@ String generateCypressConfig(String bezeichner, String appName, String appVarian
 
 String generateUrlBezeichner(String bezeichner, String appName) {
   if (appName == 'admin-e2e') {
-    return "${bezeichner}-admin";
+    return "${bezeichner}-administration";
   }
   return bezeichner;
 }
diff --git a/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping-form.e2e.component.ts
similarity index 53%
rename from alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts
rename to alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping-form.e2e.component.ts
index 8936e02edbe69c765364304bc497d2be61a970da..e140ef04867eeb97212c840b44e44535696422e4 100644
--- a/alfa-client/apps/admin-e2e/src/components/statistik/statistik-fields-form.e2e.component.ts
+++ b/alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping-form.e2e.component.ts
@@ -1,67 +1,58 @@
-import { enterWith } from '../../support/cypress.util';
+export class AggregationMappingFormE2EComponent {
+  private readonly root: string = 'aggregation-mapping-form';
 
-export class StatistikFieldsFormE2EComponent {
+  private readonly nameInput: string = 'aggregation-mapping-name-text-input';
   private readonly formEngineInput: string = 'form-engine-name-text-input';
   private readonly formIdInput: string = 'form-id-text-input';
-  private readonly formDataFieldInput: string = 'mapping-field-';
+  private readonly dataFieldInputPrefix: string = 'aggregation-mapping-field-mapping-form-';
+  private readonly sourceMappingFieldInputPrefix: string = 'source-mapping-field-';
+  private readonly targetMappingFieldInputPrefix: string = 'target-mapping-field-';
   private readonly addDataFieldButton: string = 'add-mapping-button';
   private readonly deleteDataFieldButtonPrefix: string = 'remove-mapping-button-';
   private readonly saveButton: string = 'save-button';
   private readonly cancelButton: string = 'cancel-button';
 
-  public getFormEngineInput(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.formEngineInput);
+  public getRoot(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.root);
+  }
+
+  public getNameInput(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.nameInput);
   }
 
-  public enterFormEngine(text: string): void {
-    enterWith(this.getFormEngineInput(), text);
+  public getFormEngineInput(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.formEngineInput);
   }
 
   public getFormIdInput(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.formIdInput);
   }
 
-  public enterFormId(text: string): void {
-    enterWith(this.getFormIdInput(), text);
+  public getDataFieldInput(index: number): Cypress.Chainable<Element> {
+    return cy.getTestElement(`${this.dataFieldInputPrefix}${index}`);
   }
 
   public getAddFieldButton(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.addDataFieldButton);
   }
 
-  public addField(): void {
-    this.getAddFieldButton().click();
-  }
-
-  public getDataFieldInput(index: number): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.formDataFieldInput + index + '-text-input');
+  public getSourceMappingFieldInput(index: number): Cypress.Chainable<Element> {
+    return cy.getTestElement(`${this.sourceMappingFieldInputPrefix}${index}-text-input`);
   }
 
-  public enterDataFieldPath(text: string, index: number): void {
-    enterWith(this.getDataFieldInput(index), text);
+  public getTargetMappingFieldInput(index: number): Cypress.Chainable<Element> {
+    return cy.getTestElement(`${this.targetMappingFieldInputPrefix}${index}-text-input`);
   }
 
   public getDataFieldDeleteButton(index: number): Cypress.Chainable<Element> {
     return cy.getTestElement(this.deleteDataFieldButtonPrefix + index);
   }
 
-  public deleteDataField(index: number): void {
-    this.getDataFieldDeleteButton(index).click();
-  }
-
   public getSaveButton(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.saveButton);
   }
 
-  public save(): void {
-    this.getSaveButton().click();
-  }
-
   public getCancelButton(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.cancelButton);
   }
-
-  public cancel(): void {
-    this.getCancelButton().click();
-  }
 }
diff --git a/alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping.e2e.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69df7bcedef6c8927a65f0e24873afc3d6c54bbb
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/components/aggregation-mapping/aggregation-mapping.e2e.component.ts
@@ -0,0 +1,47 @@
+import { getTestElement } from '../../support/cypress-helper';
+import { convertToDataTestId } from '../../support/tech-util';
+
+export class AggregationMappingE2EComponent {
+  private readonly headerText: string = 'aggregation-mapping-header-text';
+  private readonly weitereFelderAuswertenButton = 'weitere-felder-auswerten-button';
+
+  public getHeaderText(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.headerText);
+  }
+
+  public getWeitereFelderAuswertenButton(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.weitereFelderAuswertenButton);
+  }
+
+  public getListItem(name: string): AggregationMappingListItemE2EComponent {
+    return new AggregationMappingListItemE2EComponent(name);
+  }
+}
+
+export class AggregationMappingListItemE2EComponent {
+  private root: string;
+
+  private readonly listItemName: string = 'list-item-name';
+  private readonly listItemFormEngineName: string = 'list-item-form-engine-name';
+  private readonly listItemFormId: string = 'list-item-form-id';
+
+  constructor(root: string) {
+    this.root = convertToDataTestId(root);
+  }
+
+  public getRoot(): Cypress.Chainable<Element> {
+    return getTestElement(this.root);
+  }
+
+  public getName(): Cypress.Chainable<Element> {
+    return this.getRoot().findTestElementWithClass(this.listItemName);
+  }
+
+  public getFormEngineName(): Cypress.Chainable<Element> {
+    return this.getRoot().findTestElementWithClass(this.listItemFormEngineName);
+  }
+
+  public getFormId(): Cypress.Chainable<Element> {
+    return this.getRoot().findTestElementWithClass(this.listItemFormId);
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts
index 673ea2c47571873cc69795547f6dc5359609e99a..c80f8d4b5056e16a6de0d772cfd6df4e8e02b45e 100644
--- a/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts
+++ b/alfa-client/apps/admin-e2e/src/components/benutzer/benutzer.e2e.component.ts
@@ -23,6 +23,7 @@
  */
 import 'cypress-real-events';
 import { convertToDataTestId } from '../../support/tech-util';
+import { TextEditorE2EComponent } from '../ods/text-editor-e2-e.component';
 
 //TODO BenutzerListPage erstellen welche den Button und die Liste enthaelt.
 export class BenutzerListE2EComponent {
@@ -95,11 +96,6 @@ export class BenutzerListItemE2EComponent {
 export class BenutzerE2EComponent {
   private readonly headline: string = 'benutzer-form-headline';
 
-  private readonly userVorname: string = 'Vorname-text-input';
-  private readonly userNachname: string = 'Nachname-text-input';
-  private readonly userBenutzername: string = 'Benutzername-text-input';
-  private readonly userMail: string = 'E-Mail-text-input';
-
   private readonly adminCheckboxLabel: string = 'Admin';
   private readonly loeschenCheckboxLabel: string = 'Löschen';
   private readonly userCheckboxLabel: string = 'User';
@@ -111,6 +107,13 @@ export class BenutzerE2EComponent {
   private readonly saveButton: string = 'save-button';
   private readonly deleteButton: string = 'delete-button';
 
+  private readonly rollenValidationError: string = 'rollen-error';
+
+  private readonly vornameTextFeld: TextEditorE2EComponent = new TextEditorE2EComponent('firstName');
+  private readonly nachnameTextFeld: TextEditorE2EComponent = new TextEditorE2EComponent('lastName');
+  private readonly benutzernameTextFeld: TextEditorE2EComponent = new TextEditorE2EComponent('username');
+  private readonly emailTextFeld: TextEditorE2EComponent = new TextEditorE2EComponent('email');
+
   public getHeadline(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.headline);
   }
@@ -120,19 +123,19 @@ export class BenutzerE2EComponent {
   }
 
   public getVornameInput(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.userVorname);
+    return this.vornameTextFeld.getInput();
   }
 
   public getNachnameInput(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.userNachname);
+    return this.nachnameTextFeld.getInput();
   }
 
   public getBenutzernameInput(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.userBenutzername);
+    return this.benutzernameTextFeld.getInput();
   }
 
   public getMailInput(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.userMail);
+    return this.emailTextFeld.getInput();
   }
 
   public getAdminCheckbox(): BenutzerCheckboxE2EComponent {
@@ -166,6 +169,26 @@ export class BenutzerE2EComponent {
   public getDeleteButton(): Cypress.Chainable<Element> {
     return cy.getTestElement(this.deleteButton);
   }
+
+  public getVornameTextFeld(): TextEditorE2EComponent {
+    return this.vornameTextFeld;
+  }
+
+  public getNachnameTextFeld(): TextEditorE2EComponent {
+    return this.nachnameTextFeld;
+  }
+
+  public getBenutzernameTextFeld(): TextEditorE2EComponent {
+    return this.benutzernameTextFeld;
+  }
+
+  public getEmailTextFeld(): TextEditorE2EComponent {
+    return this.emailTextFeld;
+  }
+
+  public getRollenValidationError(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.rollenValidationError);
+  }
 }
 
 export class BenutzerDeleteDialogE2EComponent {
diff --git a/alfa-client/apps/admin-e2e/src/components/ods/text-editor-e2-e.component.ts b/alfa-client/apps/admin-e2e/src/components/ods/text-editor-e2-e.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0605779543307d829c2fc65dc3380643cc519bba
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/components/ods/text-editor-e2-e.component.ts
@@ -0,0 +1,19 @@
+export class TextEditorE2EComponent {
+  private readonly root: string;
+  private readonly input: string;
+  private readonly validationError: string;
+
+  constructor(root: string) {
+    this.root = `${root}-text-editor`;
+    this.input = `${root}-text-input`;
+    this.validationError = `${root}-text-editor-error`;
+  }
+
+  public getInput(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.input);
+  }
+
+  public getErrorMessage(): Cypress.Chainable<Element> {
+    return cy.getTestElement(this.validationError);
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts b/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts
deleted file mode 100644
index b7467232f5d596a2984bc109e84180810c5e8dc3..0000000000000000000000000000000000000000
--- a/alfa-client/apps/admin-e2e/src/components/statistik/statistik.e2e.component.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export class StatistikE2EComponent {
-  private readonly locatorHeaderText: string = 'statistik-header-text';
-  private readonly locatorWeitereFelderAuswertenButton = 'weitere-felder-auswerten-button';
-
-  public getHeaderText(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.locatorHeaderText);
-  }
-
-  public getWeitereFelderAuswertenButton(): Cypress.Chainable<Element> {
-    return cy.getTestElement(this.locatorWeitereFelderAuswertenButton);
-  }
-}
diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/aggregation-mapping/aggregation-mapping.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/aggregation-mapping/aggregation-mapping.cy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ffabc87021d990125ddb5402cac4244d4dc2fa6b
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/aggregation-mapping/aggregation-mapping.cy.ts
@@ -0,0 +1,86 @@
+import { AggregationMapping, FieldMapping } from '@admin-client/reporting-shared';
+import { E2EAggregationMappingVerifier } from 'apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.verifier';
+import { AggregationMappingFormE2EComponent } from '../../../components/aggregation-mapping/aggregation-mapping-form.e2e.component';
+import { AggregationMappingE2EComponent } from '../../../components/aggregation-mapping/aggregation-mapping.e2e.component';
+import { E2EAggregationMappingHelper } from '../../../helper/aggregation-mapping/aggregation-mapping.helper';
+import { dropCollections } from '../../../support/cypress-helper';
+import { exist, notExist } from '../../../support/cypress.util';
+import { loginAsDaria } from '../../../support/user-util';
+
+describe('Aggregation Mapping hinzufügen', () => {
+  const page: AggregationMappingE2EComponent = new AggregationMappingE2EComponent();
+  const form: AggregationMappingFormE2EComponent = new AggregationMappingFormE2EComponent();
+
+  const helper: E2EAggregationMappingHelper = new E2EAggregationMappingHelper();
+  const verifier: E2EAggregationMappingVerifier = new E2EAggregationMappingVerifier();
+
+  const fieldMapping0: FieldMapping = {
+    sourcePath: '/path/to/source/a',
+    targetPath: '/path/to/target/a',
+  };
+
+  const fieldMapping1: FieldMapping = {
+    sourcePath: '/path/to/source/b',
+    targetPath: '/path/to/target/b',
+  };
+
+  const aggregationMapping: AggregationMapping = {
+    name: 'Aggregation Mapping',
+    formIdentifier: {
+      formEngineName: 'formEngineName',
+      formId: 'formId',
+    },
+    mappings: [fieldMapping0, fieldMapping1],
+  };
+
+  before(() => {
+    loginAsDaria();
+  });
+
+  after(() => {
+    dropCollections();
+  });
+
+  it('should show "Weitere Felder auswerten" button', () => {
+    helper.openStatistik();
+
+    exist(page.getWeitereFelderAuswertenButton());
+  });
+
+  it('should show form after button click', () => {
+    page.getWeitereFelderAuswertenButton().click();
+
+    exist(form.getRoot());
+  });
+
+  it('should navigate to aggregation mapping on cancel', () => {
+    form.getCancelButton().click();
+
+    exist(page.getWeitereFelderAuswertenButton());
+  });
+
+  it('should add data field in form', () => {
+    page.getWeitereFelderAuswertenButton().click();
+    form.getAddFieldButton().click();
+
+    exist(form.getDataFieldInput(1));
+  });
+
+  it('should fill out form with two data fields', () => {
+    helper.fillFormular(aggregationMapping);
+
+    verifier.verifyForm(aggregationMapping);
+  });
+
+  it('should delete data fields', () => {
+    form.getDataFieldDeleteButton(0).click();
+
+    notExist(form.getDataFieldInput(1));
+  });
+
+  it('should show aggregation mapping in list after save', () => {
+    form.getSaveButton().click();
+
+    verifier.verifyAggregationMappingInList(aggregationMapping);
+  });
+});
diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts
index 6869eafd381b1d9df0dde4b213466d4c8db8b4db..e659bf8dde0d751c2f2af41860eb90944aadf1fb 100644
--- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts
+++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/benutzer_rollen/benutzer-anlegen.cy.ts
@@ -122,4 +122,96 @@ describe('Benutzer anlegen', () => {
       benutzerHelper.deleteBenutzer(newAdminUser.username);
     });
   });
+
+  describe('when filling formular', () => {
+    it('should show validation errors on empty formular', () => {
+      benutzerHelper.openNewBenutzerPage();
+      benutzerHelper.saveBenutzer();
+
+      benutzerVerifier.verifyEmptyVornameError();
+      benutzerVerifier.verifyEmptyNameError();
+      benutzerVerifier.verifyUsernameLengthError();
+      benutzerVerifier.verifyInvalidEmailError();
+      benutzerVerifier.verifyEmptyRollenError();
+    });
+
+    it('should hide Vorname validation error on filled', () => {
+      benutzerHelper.editVorname('Max');
+      benutzerVerifier.verifyVornameHasNoError();
+      benutzerVerifier.verifyEmptyNameError();
+      benutzerVerifier.verifyUsernameLengthError();
+      benutzerVerifier.verifyInvalidEmailError();
+      benutzerVerifier.verifyEmptyRollenError();
+    });
+
+    it('should hide Nachname validation error on filled', () => {
+      benutzerHelper.editNachname('Mustermann');
+      benutzerVerifier.verifyVornameHasNoError();
+      benutzerVerifier.verifyLastNameHasNoError();
+      benutzerVerifier.verifyUsernameLengthError();
+      benutzerVerifier.verifyInvalidEmailError();
+      benutzerVerifier.verifyEmptyRollenError();
+    });
+
+    it('should hide Benutzername validation error on filled', () => {
+      benutzerHelper.editBenutzername('Max');
+      benutzerVerifier.verifyVornameHasNoError();
+      benutzerVerifier.verifyLastNameHasNoError();
+      benutzerVerifier.verifyUsernameHasNoError();
+      benutzerVerifier.verifyInvalidEmailError();
+      benutzerVerifier.verifyEmptyRollenError();
+    });
+
+    it('should hide Email validation error on filled', () => {
+      benutzerHelper.editEmail('max@max.local');
+      benutzerVerifier.verifyVornameHasNoError();
+      benutzerVerifier.verifyLastNameHasNoError();
+      benutzerVerifier.verifyUsernameHasNoError();
+      benutzerVerifier.verifyEmailHasNoError();
+      benutzerVerifier.verifyEmptyRollenError();
+    });
+
+    it('should hide Rollen validation error on filled', () => {
+      benutzerHelper.addUserRole();
+      benutzerVerifier.verifyVornameHasNoError();
+      benutzerVerifier.verifyLastNameHasNoError();
+      benutzerVerifier.verifyUsernameHasNoError();
+      benutzerVerifier.verifyEmailHasNoError();
+      benutzerVerifier.verifyRollenHasNoError();
+    });
+  });
+
+  describe('after formular submission', () => {
+    it('should have user created', () => {
+      benutzerHelper.openNewBenutzerPage();
+
+      benutzerHelper.addBenutzer(newAdminUser);
+      benutzerHelper.saveBenutzer();
+
+      benutzerVerifier.verifyUserInList(newAdminUser);
+    });
+
+    it('should show validation error on existing email', () => {
+      benutzerHelper.openNewBenutzerPage();
+      benutzerHelper.addBenutzer(newAdminUser);
+      benutzerHelper.saveBenutzer();
+
+      benutzerVerifier.verifyEmailExistsError();
+    });
+
+    it('should show validation error on existing username', () => {
+      benutzerHelper.openNewBenutzerPage();
+      benutzerHelper.addBenutzer({ ...newAdminUser, email: faker.internet.email() + '.local' });
+      benutzerHelper.saveBenutzer();
+
+      benutzerVerifier.verifyUsernameExistsError();
+    });
+
+    it('should remove benutzer', () => {
+      benutzerHelper.openBenutzerPage(newAdminUser.username);
+      benutzerHelper.deleteBenutzer(newAdminUser.username);
+
+      benutzerVerifier.verifyUserNotInList(newAdminUser.username);
+    });
+  });
 });
diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts
deleted file mode 100644
index d877ae4b900417af37328322176ce8070c6d0844..0000000000000000000000000000000000000000
--- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/statistik/statistik-fields.cy.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { StatistikE2EComponent } from 'apps/admin-e2e/src/components/statistik/statistik.e2e.component';
-import { StatistikFieldsFormE2EComponent } from '../../../components/statistik/statistik-fields-form.e2e.component';
-import { exist, haveText, haveValue } from '../../../support/cypress.util';
-import { loginAsDaria } from '../../../support/user-util';
-
-describe('Felder in Statistik hinzufügen', () => {
-  const component: StatistikE2EComponent = new StatistikE2EComponent();
-  const fieldsFormComponent: StatistikFieldsFormE2EComponent = new StatistikFieldsFormE2EComponent();
-
-  const dataText1: string = 'Eingabe A';
-  const dataText2: string = 'Eingabe B';
-
-  before(() => {
-    loginAsDaria();
-  });
-
-  it('should show "Weitere Felder auswerten" button', () => {
-    exist(component.getWeitereFelderAuswertenButton());
-  });
-
-  it('should have all form elements after button click', () => {
-    component.getWeitereFelderAuswertenButton().click();
-
-    exist(fieldsFormComponent.getFormEngineInput());
-    exist(fieldsFormComponent.getFormIdInput());
-    exist(fieldsFormComponent.getDataFieldInput(0));
-    exist(fieldsFormComponent.getDataFieldDeleteButton(0));
-    exist(fieldsFormComponent.getAddFieldButton());
-    exist(fieldsFormComponent.getSaveButton());
-    exist(fieldsFormComponent.getCancelButton());
-  });
-
-  it('should add data field', () => {
-    fieldsFormComponent.addField();
-
-    exist(fieldsFormComponent.getDataFieldInput(1));
-  });
-
-  it('should enter text in both data fields', () => {
-    fieldsFormComponent.enterDataFieldPath(dataText1, 0);
-    fieldsFormComponent.enterDataFieldPath(dataText2, 1);
-
-    haveValue(fieldsFormComponent.getDataFieldInput(0), dataText1);
-    haveValue(fieldsFormComponent.getDataFieldInput(1), dataText2);
-  });
-
-  it('should delete data fields', () => {
-    fieldsFormComponent.deleteDataField(0);
-    haveValue(fieldsFormComponent.getDataFieldInput(0), dataText2);
-  });
-
-  it('should navigate to statistik on cancel', () => {
-    fieldsFormComponent.cancel();
-
-    exist(component.getWeitereFelderAuswertenButton());
-  });
-});
diff --git a/alfa-client/apps/admin-e2e/src/fixtures/argocd/by-admin-dev.yaml b/alfa-client/apps/admin-e2e/src/fixtures/argocd/by-admin-dev.yaml
index 1a25b8d740968a62ad08eb5ca4947866650f04ac..f7781946a0ca5c123c2dea8a408caab17f871793 100644
--- a/alfa-client/apps/admin-e2e/src/fixtures/argocd/by-admin-dev.yaml
+++ b/alfa-client/apps/admin-e2e/src/fixtures/argocd/by-admin-dev.yaml
@@ -87,7 +87,7 @@ alfa_client:
   ingress:
     use_staging_cert: true
 
-admin_client:
+administration_client:
   enabled: true
   ingress:
     use_staging_cert: true
diff --git a/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.executor.ts b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.executor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e9b392696e1839765a8d8c6668722db0bffd262
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.executor.ts
@@ -0,0 +1,22 @@
+import { AggregationMapping, FieldMapping } from '@admin-client/reporting-shared';
+import { AggregationMappingFormE2EComponent } from '../../components/aggregation-mapping/aggregation-mapping-form.e2e.component';
+import { enterWith } from '../../support/cypress.util';
+
+export class E2EAggregationMappingExecutor {
+  private formComponent: AggregationMappingFormE2EComponent = new AggregationMappingFormE2EComponent();
+
+  public fillFormular(aggregationMapping: AggregationMapping): void {
+    enterWith(this.formComponent.getNameInput(), aggregationMapping.name);
+    enterWith(this.formComponent.getFormEngineInput(), aggregationMapping.formIdentifier.formEngineName);
+    enterWith(this.formComponent.getFormIdInput(), aggregationMapping.formIdentifier.formId);
+
+    aggregationMapping.mappings.forEach((fieldMapping, index) => {
+      this.enterFieldMapping(fieldMapping, index);
+    });
+  }
+
+  private enterFieldMapping(fieldMapping: FieldMapping, index: number): void {
+    enterWith(this.formComponent.getSourceMappingFieldInput(index), fieldMapping.sourcePath);
+    enterWith(this.formComponent.getTargetMappingFieldInput(index), fieldMapping.targetPath);
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.helper.ts b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.helper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b39a391240e6a139c1f9df4e673e4e3ccda6e3e8
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.helper.ts
@@ -0,0 +1,16 @@
+import { AggregationMapping } from '@admin-client/reporting-shared';
+import { E2EAggregationMappingExecutor } from './aggregation-mapping.executor';
+import { E2EAggregationMapperNavigator } from './aggregation-mapping.navigator';
+
+export class E2EAggregationMappingHelper {
+  private readonly navigator: E2EAggregationMapperNavigator = new E2EAggregationMapperNavigator();
+  private readonly executor: E2EAggregationMappingExecutor = new E2EAggregationMappingExecutor();
+
+  public openStatistik(): void {
+    this.navigator.openStatistik();
+  }
+
+  public fillFormular(aggregationMapping: AggregationMapping): void {
+    this.executor.fillFormular(aggregationMapping);
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.navigator.ts b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.navigator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f82cc547926244caca1d78afeb30be3a843b665e
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.navigator.ts
@@ -0,0 +1,18 @@
+import { AggregationMappingE2EComponent } from '../../components/aggregation-mapping/aggregation-mapping.e2e.component';
+import { MainPage } from '../../page-objects/main.po';
+import { exist } from '../../support/cypress.util';
+import { E2EAppHelper } from '../app.helper';
+
+export class E2EAggregationMapperNavigator {
+  private readonly appHelper: E2EAppHelper = new E2EAppHelper();
+
+  private readonly mainPage: MainPage = new MainPage();
+
+  private readonly aggregationMappingPage: AggregationMappingE2EComponent = new AggregationMappingE2EComponent();
+
+  public openStatistik(): void {
+    this.appHelper.navigateToDomain();
+    this.mainPage.getStatistikNavigationItem().click();
+    exist(this.aggregationMappingPage.getHeaderText());
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.verifier.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df93defea446b90102747c7af9ddd41edb254e59
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/helper/aggregation-mapping/aggregation-mapping.verifier.ts
@@ -0,0 +1,34 @@
+import { AggregationMapping, FieldMapping } from '@admin-client/reporting-shared';
+import { AggregationMappingFormE2EComponent } from '../../components/aggregation-mapping/aggregation-mapping-form.e2e.component';
+import {
+  AggregationMappingE2EComponent,
+  AggregationMappingListItemE2EComponent,
+} from '../../components/aggregation-mapping/aggregation-mapping.e2e.component';
+import { haveText, haveValue } from '../../support/cypress.util';
+
+export class E2EAggregationMappingVerifier {
+  private component: AggregationMappingE2EComponent = new AggregationMappingE2EComponent();
+  private formComponent: AggregationMappingFormE2EComponent = new AggregationMappingFormE2EComponent();
+
+  public verifyFieldMapping(fieldMapping: FieldMapping, index: number): void {
+    haveValue(this.formComponent.getSourceMappingFieldInput(index), fieldMapping.sourcePath);
+    haveValue(this.formComponent.getTargetMappingFieldInput(index), fieldMapping.targetPath);
+  }
+
+  public verifyForm(aggregationMapping: AggregationMapping): void {
+    haveValue(this.formComponent.getNameInput(), aggregationMapping.name);
+    haveValue(this.formComponent.getFormEngineInput(), aggregationMapping.formIdentifier.formEngineName);
+    haveValue(this.formComponent.getFormIdInput(), aggregationMapping.formIdentifier.formId);
+
+    aggregationMapping.mappings.forEach((fieldMapping, index) => {
+      this.verifyFieldMapping(fieldMapping, index);
+    });
+  }
+
+  public verifyAggregationMappingInList(aggregationMapping: AggregationMapping): void {
+    const listItem: AggregationMappingListItemE2EComponent = this.component.getListItem(aggregationMapping.name);
+    haveText(listItem.getName(), aggregationMapping.name);
+    haveText(listItem.getFormEngineName(), aggregationMapping.formIdentifier.formEngineName);
+    haveText(listItem.getFormId(), aggregationMapping.formIdentifier.formId);
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/helper/app.helper.ts b/alfa-client/apps/admin-e2e/src/helper/app.helper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee3170e548ffc5b7cad695a0f28d0b1e7e993a11
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/helper/app.helper.ts
@@ -0,0 +1,9 @@
+import { MainPage } from '../page-objects/main.po';
+
+export class E2EAppHelper {
+  private readonly mainPage: MainPage = new MainPage();
+
+  public navigateToDomain(): void {
+    this.mainPage.getHeader().getLogo().click();
+  }
+}
diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts
index ec1bbb05e560d3c45de062711da3da9ed8cf39c1..d6a839a2c755d0e43ab6608f1a8dcb60b57855b0 100644
--- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts
+++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.executor.ts
@@ -34,6 +34,26 @@ export class E2EBenutzerExecutor {
     this.modifyOrganisationsEinheiten(user.organisationseinheiten);
   }
 
+  public modifyVorname(vorname: string): void {
+    this.benutzerPage.getVornameInput().type(vorname);
+  }
+
+  public modifyNachname(nachname: string): void {
+    this.benutzerPage.getNachnameInput().type(nachname);
+  }
+
+  public modifyBenutzername(benutzername: string): void {
+    this.benutzerPage.getBenutzernameInput().type(benutzername);
+  }
+
+  public modifyEmail(email: string): void {
+    this.benutzerPage.getMailInput().type(email);
+  }
+
+  public checkUserRole(): void {
+    this.benutzerPage.getUserCheckbox().getRoot().click();
+  }
+
   public modifyOrganisationsEinheiten(organisationseinheiten: OrganisationsEinheitE2E[]): void {
     organisationseinheiten.forEach((name: string) => this.benutzerPage.getOrganisationsEinheitCheckbox(name).click());
   }
diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts
index 0c0ad0c26492851788cb31352522ac0244f4217f..daf08ce5a6fb0869a60531b9a0179e44d4ce4b72 100644
--- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts
+++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.helper.ts
@@ -1,6 +1,7 @@
 import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';
 import { OrganisationsEinheitE2E } from '../../model/organisations-einheit';
 import { AdminUserE2E } from '../../model/util';
+import { waitForSpinnerToDisappear } from '../../page-objects/main.po';
 import { E2EBenutzerExecutor } from './benutzer.executor';
 import { E2EBenutzerNavigator } from './benutzer.navigator';
 
@@ -34,6 +35,26 @@ export class E2EBenutzerHelper {
     this.modifyBenutzer(user);
   }
 
+  public editVorname(vorname: string): void {
+    this.executor.modifyVorname(vorname);
+  }
+
+  public editNachname(nachname: string): void {
+    this.executor.modifyNachname(nachname);
+  }
+
+  public editBenutzername(username: string): void {
+    this.executor.modifyBenutzername(username);
+  }
+
+  public editEmail(email: string): void {
+    this.executor.modifyEmail(email);
+  }
+
+  public addUserRole(): void {
+    this.executor.checkUserRole();
+  }
+
   private modifyBenutzer(user: AdminUserE2E): void {
     this.executor.modifyBenutzer(user);
   }
@@ -53,11 +74,13 @@ export class E2EBenutzerHelper {
 
   public saveBenutzer(): void {
     this.executor.saveBenutzer();
+    waitForSpinnerToDisappear();
   }
 
   public deleteBenutzer(userName: string): void {
     this.openBenutzerPage(userName);
     this.executor.deleteBenutzer();
+    waitForSpinnerToDisappear();
   }
 
   public openBenutzerPage(userName: string): void {
diff --git a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts
index 277f735a6038200c0c21c6efaa8b06d71d6d9e9c..22f42fb09f6d560491cc9230cb705a3729059dec 100644
--- a/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts
+++ b/alfa-client/apps/admin-e2e/src/helper/benutzer/benutzer.verifier.ts
@@ -1,10 +1,12 @@
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
 import {
   BenutzerE2EComponent,
   BenutzerListE2EComponent,
   BenutzerListItemE2EComponent,
 } from '../../components/benutzer/benutzer.e2e.component';
+import { E2EBenutzerValidationMessages } from '../../model/benutzer.messages';
 import { AdminUserE2E } from '../../model/util';
-import { contains, exist, notBeEnabled, notExist } from '../../support/cypress.util';
+import { contains, exist, haveText, notBeEnabled, notContains, notExist } from '../../support/cypress.util';
 import { AlfaRollen } from '../../support/user-util';
 
 export class E2EBenutzerVerifier {
@@ -67,4 +69,62 @@ export class E2EBenutzerVerifier {
   private getBenutzerItem(userName: string): BenutzerListItemE2EComponent {
     return this.benutzerListPage.getItem(userName);
   }
+
+  public verifyEmptyVornameError(): void {
+    exist(this.benutzerPage.getVornameTextFeld().getErrorMessage());
+    haveText(this.benutzerPage.getVornameTextFeld().getErrorMessage(), E2EBenutzerValidationMessages.VORNAME_EMPTY_ERROR);
+  }
+
+  public verifyEmptyNameError(): void {
+    exist(this.benutzerPage.getNachnameTextFeld().getErrorMessage());
+    haveText(this.benutzerPage.getNachnameTextFeld().getErrorMessage(), E2EBenutzerValidationMessages.NACHNAME_EMPTY_ERROR);
+  }
+
+  public verifyUsernameLengthError(): void {
+    exist(this.benutzerPage.getBenutzernameTextFeld().getErrorMessage());
+    haveText(
+      this.benutzerPage.getBenutzernameTextFeld().getErrorMessage(),
+      E2EBenutzerValidationMessages.BENUTZERNAME_SIZE_ERROR,
+    );
+  }
+
+  public verifyUsernameExistsError(): void {
+    exist(this.benutzerPage.getBenutzernameTextFeld().getErrorMessage());
+    haveText(this.benutzerPage.getBenutzernameTextFeld().getErrorMessage(), E2EBenutzerValidationMessages.BENUTZER_NAME_EXISTS);
+  }
+
+  public verifyInvalidEmailError(): void {
+    exist(this.benutzerPage.getEmailTextFeld().getErrorMessage());
+    haveText(this.benutzerPage.getEmailTextFeld().getErrorMessage(), E2EBenutzerValidationMessages.EMAIL_INVALID_ERROR);
+  }
+
+  public verifyEmailExistsError(): void {
+    exist(this.benutzerPage.getEmailTextFeld().getErrorMessage());
+    haveText(this.benutzerPage.getEmailTextFeld().getErrorMessage(), E2EBenutzerValidationMessages.EMAIL_EXISTS);
+  }
+
+  public verifyEmptyRollenError(): void {
+    exist(this.benutzerPage.getRollenValidationError());
+    contains(this.benutzerPage.getRollenValidationError(), E2EBenutzerValidationMessages.ROLLEN_EMPTY_ERROR);
+  }
+
+  public verifyVornameHasNoError(): void {
+    haveText(this.benutzerPage.getVornameTextFeld().getErrorMessage(), EMPTY_STRING);
+  }
+
+  public verifyLastNameHasNoError(): void {
+    haveText(this.benutzerPage.getNachnameTextFeld().getErrorMessage(), EMPTY_STRING);
+  }
+
+  public verifyUsernameHasNoError(): void {
+    haveText(this.benutzerPage.getBenutzernameTextFeld().getErrorMessage(), EMPTY_STRING);
+  }
+
+  public verifyEmailHasNoError(): void {
+    haveText(this.benutzerPage.getEmailTextFeld().getErrorMessage(), EMPTY_STRING);
+  }
+
+  public verifyRollenHasNoError(): void {
+    notContains(this.benutzerPage.getRollenValidationError(), E2EBenutzerValidationMessages.ROLLEN_EMPTY_ERROR);
+  }
 }
diff --git a/alfa-client/apps/admin-e2e/src/model/benutzer.messages.ts b/alfa-client/apps/admin-e2e/src/model/benutzer.messages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db6aa9f09f5f2115a1de2e6295b564ea845c4aa8
--- /dev/null
+++ b/alfa-client/apps/admin-e2e/src/model/benutzer.messages.ts
@@ -0,0 +1,9 @@
+export enum E2EBenutzerValidationMessages {
+  VORNAME_EMPTY_ERROR = 'Bitte Vorname ausfüllen',
+  NACHNAME_EMPTY_ERROR = 'Bitte Nachname ausfüllen',
+  BENUTZERNAME_SIZE_ERROR = 'Benutzername muss mindestens 3 und darf höchstens 255 Zeichen enthalten',
+  EMAIL_INVALID_ERROR = 'Bitte E-Mail korrekt ausfüllen',
+  ROLLEN_EMPTY_ERROR = 'Bitte Rollen ausfüllen',
+  BENUTZER_NAME_EXISTS = 'Benutzername bereits verwendet',
+  EMAIL_EXISTS = 'Email-Adresse bereits verwendet',
+}
diff --git a/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts b/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts
index 75ecbdaefa5d692ca637af325afb58ec05f80abe..b07542fc24c5b83a7d9b623842d7a89ee4e9d9ce 100644
--- a/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts
+++ b/alfa-client/apps/admin-e2e/src/page-objects/main.po.ts
@@ -31,7 +31,7 @@ export class MainPage {
   private readonly benutzerNavigationItem: string = 'link-path-benutzer';
   private readonly organisationEinheitNavigationItem: string = 'link-path-organisationseinheiten';
   private readonly postfachNavigationItem: string = 'link-path-postfach';
-  private readonly statistikNavigationItem: string = 'link-path-statistik';
+  private readonly statistikNavigationItem: string = 'link-path-auswertungen';
 
   public getBuildInfo(): BuildInfoE2EComponent {
     return this.buildInfo;
diff --git a/alfa-client/apps/admin-e2e/src/support/cypress-helper.ts b/alfa-client/apps/admin-e2e/src/support/cypress-helper.ts
index a48d664eb799ccad45bf162f74c804d524fbf8d4..5d457ea558240cbd2d5e415ffc8136f8be4452ad 100644
--- a/alfa-client/apps/admin-e2e/src/support/cypress-helper.ts
+++ b/alfa-client/apps/admin-e2e/src/support/cypress-helper.ts
@@ -21,6 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { HttpMethod } from '@alfa-client/tech-shared';
 import { Interception, RouteHandler, RouteMatcher } from 'cypress/types/net-stubbing';
 import { OrganisationsEinheitE2E } from './organisationseinheit';
 
@@ -31,6 +32,7 @@ enum CypressTasks {
 
 enum MongoCollections {
   ORGANISATIONS_EINHEIT = 'organisationsEinheit',
+  AGGREGATION_MAPPING = 'aggregationMapping',
 }
 
 const DOWNLOAD_FOLDER: string = 'cypress/downloads';
@@ -47,7 +49,7 @@ export function intercept(method: string, url: string): Cypress.Chainable<null>
   return cy.intercept(method, url);
 }
 
-export function interceptWithResponse(method, url: RouteMatcher, response: RouteHandler): Cypress.Chainable<null> {
+export function interceptWithResponse(method: HttpMethod, url: RouteMatcher, response: RouteHandler): Cypress.Chainable<null> {
   return cy.intercept(method, url, response);
 }
 
@@ -109,5 +111,5 @@ export function initOrganisationsEinheitenData(data: OrganisationsEinheitE2E[]):
 }
 
 export function dropCollections() {
-  cy.task(CypressTasks.DROP_COLLECTIONS, [MongoCollections.ORGANISATIONS_EINHEIT]);
+  cy.task(CypressTasks.DROP_COLLECTIONS, [MongoCollections.ORGANISATIONS_EINHEIT, MongoCollections.AGGREGATION_MAPPING]);
 }
diff --git a/alfa-client/apps/admin/Jenkinsfile b/alfa-client/apps/admin/Jenkinsfile
index 8ae0ccfcdb74541493558353c758eb9896742caa..2253bfcdd61fcf724892c3f0efef1b21970e4f80 100644
--- a/alfa-client/apps/admin/Jenkinsfile
+++ b/alfa-client/apps/admin/Jenkinsfile
@@ -50,7 +50,7 @@ pipeline {
             steps {
                 script {
                     FAILED_STAGE = env.STAGE_NAME
-                    VERSION = getAdminPackageJsonVersion()
+                    VERSION = getPackageJsonVersion()
 
                     if(isReleaseBranch()){
                         if ( !isReleaseVersion([VERSION]) ) {
@@ -65,7 +65,7 @@ pipeline {
             }
         }
 
-        stage('Build admin client and its docker image') {
+        stage('Build administration client and its docker image') {
             steps {
                 script {
 	                FAILED_STAGE=env.STAGE_NAME
@@ -75,9 +75,9 @@ pipeline {
                             sh 'pnpm install --frozen-lockfile'
 
 	                        if (isReleaseBranch()) {
-	                        	sh 'pnpm run ci-prodBuild-admin'
+	                        	sh 'pnpm run ci-prodBuild-administration'
 	                        } else {
-	                        	sh 'pnpm run ci-build-admin'
+	                        	sh 'pnpm run ci-build-administration'
 	                        }
     	                    if (isMainBranch()) {
                                 withSonarQubeEnv('sonarqube-ozg-sh'){
@@ -148,7 +148,7 @@ pipeline {
 
         stage('Trigger Test rollout') {
             when {
-                branch 'release-admin'
+                branch 'release-administration'
             }
             steps {
                 script {
@@ -176,7 +176,7 @@ pipeline {
 
 
 Boolean isReleaseBranch() {
-    return env.BRANCH_NAME == 'release-admin'
+    return env.BRANCH_NAME == 'release-administration'
 }
 
 def validateBranchName(branchName) {
@@ -224,24 +224,24 @@ Void setNewTestVersion() {
 
 Void setNewGitopsVersion(String environment) {
     dir("gitops") {
-        def envFile = "${environment}/application/values/admin-client-values.yaml"
+        def envFile = "${environment}/application/values/administration-client-values.yaml"
 
         def envVersions = readYaml file: envFile
 
-        envVersions.admin_client.image.tag = IMAGE_TAG
-        envVersions.admin_client.helm.version = HELM_CHART_VERSION
+        envVersions.administration_client.image.tag = IMAGE_TAG
+        envVersions.administration_client.helm.version = HELM_CHART_VERSION
 
         writeYaml file: envFile, data: envVersions, overwrite: true
 
         if (hasValuesFileChanged(environment)) {
             sh "git add ${envFile}"
-            sh "git commit -m 'jenkins rollout ${environment} admin_client version ${IMAGE_TAG}'"
+            sh "git commit -m 'jenkins rollout ${environment} administration_client version ${IMAGE_TAG}'"
         }
     }
 }
 
 Boolean hasValuesFileChanged(String environment) {
-    return sh (script: "git status | grep '${environment}/application/values/admin-client-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
+    return sh (script: "git status | grep '${environment}/application/values/administration-client-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
 }
 
 
@@ -263,11 +263,11 @@ Void tagAndPushDockerImage(String newTag){
     withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) {
         sh 'docker login docker.ozg-sh.de -u ${USER} -p ${PASSWORD}'
 
-        sh "docker tag docker.ozg-sh.de/admin-client:build-latest docker.ozg-sh.de/admin-client:${newTag}"
-        sh "docker push docker.ozg-sh.de/admin-client:${newTag}"
+        sh "docker tag docker.ozg-sh.de/administration-client:build-latest docker.ozg-sh.de/administration-client:${newTag}"
+        sh "docker push docker.ozg-sh.de/administration-client:${newTag}"
     }
 }
-String getAdminPackageJsonVersion() {
+String getPackageJsonVersion() {
     def packageJSON = readJSON file: 'alfa-client/apps/admin/package.json'
     def packageJSONVersion = packageJSON.version
     echo packageJSONVersion
@@ -277,10 +277,10 @@ String getAdminPackageJsonVersion() {
 Void deployHelmChart(String helmChartVersion) {
     withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]){
         if (isReleaseBranch()) {
-            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps -F file=@admin-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
+            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps -F file=@administration-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
         }
         else {
-            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot -F file=@admin-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
+            result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot -F file=@administration-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
         }
 
         if (result != '') {
@@ -296,9 +296,9 @@ Boolean isMainBranch() {
 Void sendFailureMessage() {
     def room = ''
     def data = """{"msgtype":"m.text", \
-                    "body":"Admin-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: ${env.BUILD_NUMBER} Link: ${JENKINS_URL}", \
+                    "body":"Administration-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: ${env.BUILD_NUMBER} Link: ${JENKINS_URL}", \
                     "format": "org.matrix.custom.html", \
-                    "formatted_body":"Admin-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: <a href='${JENKINS_URL}'>${env.BUILD_NUMBER}</a>"}"""
+                    "formatted_body":"Administration-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: <a href='${JENKINS_URL}'>${env.BUILD_NUMBER}</a>"}"""
 
     if (isMainBranch()) {
         room = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de"
diff --git a/alfa-client/apps/admin/package.json b/alfa-client/apps/admin/package.json
index d124f972122f91564b8d04044ab8541beb8b4ae6..65a6ef5178180d831904cfd25ca58ae832164385 100644
--- a/alfa-client/apps/admin/package.json
+++ b/alfa-client/apps/admin/package.json
@@ -1,4 +1,4 @@
 {
   "name": "admin",
-  "version": "1.7.0-SNAPSHOT"
+  "version": "1.8.0-SNAPSHOT"
 }
diff --git a/alfa-client/apps/admin/project.json b/alfa-client/apps/admin/project.json
index 9876b834e6f340fb8d71da344f75a75a9386f41b..8bffa965bb8057ebe41383397a5bf311660a5f24 100644
--- a/alfa-client/apps/admin/project.json
+++ b/alfa-client/apps/admin/project.json
@@ -15,10 +15,7 @@
         "main": "apps/admin/src/main.ts",
         "polyfills": ["zone.js"],
         "tsConfig": "apps/admin/tsconfig.app.json",
-        "allowedCommonJsDependencies": [
-          "sanitize-filename-ts",
-          "jsrsasign"
-        ],
+        "allowedCommonJsDependencies": ["sanitize-filename-ts", "jsrsasign"],
         "assets": [
           "apps/admin/src/assets",
           {
@@ -112,7 +109,7 @@
         "engine": "docker",
         "push": false,
         "metadata": {
-          "images": ["docker.ozg-sh.de/admin-client"],
+          "images": ["docker.ozg-sh.de/administration-client"],
           "load": true,
           "tags": ["build-latest"]
         }
diff --git a/alfa-client/apps/admin/src/app/app.component.spec.ts b/alfa-client/apps/admin/src/app/app.component.spec.ts
index baa8c7fcc532a0e859fe596782a94bf7b2257803..fb01cab1df3733fa40d66591f3562cf452661d65 100644
--- a/alfa-client/apps/admin/src/app/app.component.spec.ts
+++ b/alfa-client/apps/admin/src/app/app.component.spec.ts
@@ -27,11 +27,24 @@ import { KeycloakTokenService } from '@admin/keycloak-shared';
 import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared';
 import { BuildInfoComponent } from '@alfa-client/common';
 import { createEmptyStateResource, createStateResource, HasLinkPipe } from '@alfa-client/tech-shared';
-import { existsAsHtmlElement, getElementComponentFromFixtureByCss, Mock, mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils';
+import {
+  existsAsHtmlElement,
+  getElementComponentFromFixtureByCss,
+  Mock,
+  mock,
+  notExistsAsHtmlElement,
+} from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
 import { AuthenticationService } from '@authentication';
-import { AdminLogoIconComponent, MailboxIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system';
+import {
+  AdminLogoIconComponent,
+  MailboxIconComponent,
+  NavbarComponent,
+  NavItemComponent,
+  OrgaUnitIconComponent,
+  UsersIconComponent,
+} from '@ods/system';
 import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration';
 import { MenuContainerComponent } from 'libs/admin/configuration/src/lib/menu-container/menu-container.component';
 import { createApiRootResource } from 'libs/api-root-shared/test/api-root';
@@ -297,7 +310,7 @@ describe('AppComponent', () => {
       it('should navigate to statistik if aggregation mapping link exists', () => {
         component._navigateByConfiguration(createConfigurationResource([ConfigurationLinkRel.AGGREGATION_MAPPINGS]));
 
-        expect(router.navigate).toHaveBeenCalledWith(['/statistik']);
+        expect(router.navigate).toHaveBeenCalledWith(['/auswertungen']);
       });
 
       it('should navigate to unavailable page if no link exists', () => {
diff --git a/alfa-client/apps/admin/src/app/app.component.ts b/alfa-client/apps/admin/src/app/app.component.ts
index 909b6cd198bd3ccb91dd61e922abb6a1ed79210c..989a4ad4b3fd75d2767750eb535a8db27a9d6c29 100644
--- a/alfa-client/apps/admin/src/app/app.component.ts
+++ b/alfa-client/apps/admin/src/app/app.component.ts
@@ -123,7 +123,7 @@ export class AppComponent implements OnInit {
     if (hasLink(configurationResource, ConfigurationLinkRel.SETTING)) {
       this.navigate(ROUTES.POSTFACH);
     } else if (hasLink(configurationResource, ConfigurationLinkRel.AGGREGATION_MAPPINGS)) {
-      this.navigate(ROUTES.STATISTIK);
+      this.navigate(ROUTES.AGGREGATION_MAPPING);
     } else {
       this.navigate(ROUTES.UNAVAILABLE);
     }
diff --git a/alfa-client/apps/admin/src/app/app.routes.ts b/alfa-client/apps/admin/src/app/app.routes.ts
index c5974993c237ec15254220a64157b63ae0f46136..dcdc28a4fdd107ad204ac6caf3b94775462e953e 100644
--- a/alfa-client/apps/admin/src/app/app.routes.ts
+++ b/alfa-client/apps/admin/src/app/app.routes.ts
@@ -26,10 +26,10 @@ import { ROUTES } from '@admin-client/shared';
 import { UserFormComponent } from '@admin-client/user';
 import { ApiRootLinkRel } from '@alfa-client/api-root-shared';
 import { Route } from '@angular/router';
+import { AggregationMappingFormPageComponent } from '../pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component';
+import { AggregationMappingListPageComponent } from '../pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component';
 import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component';
 import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component';
-import { StatistikFieldsFormPageComponent } from '../pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component';
-import { StatistikPageComponent } from '../pages/statistik/statistik-page/statistik-page.component';
 import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component';
 import { UserFormPageComponent } from '../pages/user/user-form-page/user-form-page.component';
 import { UserListPageComponent } from '../pages/user/user-list-page/user-list-page.component';
@@ -83,17 +83,25 @@ export const appRoutes: Route[] = [
     title: 'Unavailable',
   },
   {
-    path: ROUTES.STATISTIK,
-    component: StatistikPageComponent,
+    path: ROUTES.AGGREGATION_MAPPING,
+    component: AggregationMappingListPageComponent,
     title: 'Admin | Statistik',
     runGuardsAndResolvers: 'always',
     canActivate: [configurationGuard],
     data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS },
   },
   {
-    path: ROUTES.STATISTIK_NEU,
-    component: StatistikFieldsFormPageComponent,
-    title: 'Admin | Statistik weitere Felder auswerten',
+    path: ROUTES.AGGREGATION_MAPPING_NEU,
+    component: AggregationMappingFormPageComponent,
+    title: 'Admin | Auswertung anlegen',
+    runGuardsAndResolvers: 'always',
+    canActivate: [configurationGuard],
+    data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS },
+  },
+  {
+    path: ROUTES.AGGREGATION_MAPPING_ID,
+    component: AggregationMappingFormPageComponent,
+    title: 'Admin | Auswertung bearbeiten',
     runGuardsAndResolvers: 'always',
     canActivate: [configurationGuard],
     data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS },
diff --git a/alfa-client/apps/admin/src/main/helm/Chart.yaml b/alfa-client/apps/admin/src/main/helm/Chart.yaml
index 7f1c7ec2c239956bf3b275ede276c5520603a2b1..98e2c2f72c110125d1ecf1e333e1b696841e9834 100644
--- a/alfa-client/apps/admin/src/main/helm/Chart.yaml
+++ b/alfa-client/apps/admin/src/main/helm/Chart.yaml
@@ -24,7 +24,7 @@
 
 apiVersion: v1
 appVersion: '1.0'
-description: A Helm chart for Admin Client
-name: admin-client
+description: A Helm chart for Administration Client
+name: administration-client
 version: 0.0.0-MANAGED-BY-JENKINS
 icon: https://simpleicons.org/icons/helm.svg
diff --git a/alfa-client/apps/admin/src/main/helm/templates/_helpers.tpl b/alfa-client/apps/admin/src/main/helm/templates/_helpers.tpl
index 3e25f32b0a413172ebebbcd61487f45237d1e420..fd312ca2b186f6b8f711f5af52270e35a6c8905a 100644
--- a/alfa-client/apps/admin/src/main/helm/templates/_helpers.tpl
+++ b/alfa-client/apps/admin/src/main/helm/templates/_helpers.tpl
@@ -51,7 +51,7 @@
 
 {{/* Default Labels: Helm recommended best-practice labels https://helm.sh/docs/chart_best_practices/labels/ */}}
 {{- define "app.defaultLabels" }}
-app.kubernetes.io/instance: admin-client
+app.kubernetes.io/instance: administration-client
 app.kubernetes.io/managed-by: {{ include "app.managedBy" . }}
 app.kubernetes.io/name: {{ .Release.Name }}
 app.kubernetes.io/namespace: {{ include "app.namespace" . }}
@@ -80,12 +80,12 @@ app.kubernetes.io/namespace: {{ include "app.namespace" . }}
 
 
 {{- define "app.serviceAccountName" -}}
-{{ printf "%s" ( (.Values.serviceAccount).name | default "admin-client-service-account" ) }}
+{{ printf "%s" ( (.Values.serviceAccount).name | default "administration-client-service-account" ) }}
 {{- end -}}
 
 
 {{- define "app.baseDomain" -}}
-{{- printf "%s-%s.%s" (include "app.ozgcloudBezeichner" . ) (.Values.ozgcloud).adminDomainSuffix (include "app.baseUrl" . ) }}
+{{- printf "%s-%s.%s" (include "app.ozgcloudBezeichner" . ) (.Values.ozgcloud).administrationDomainSuffix (include "app.baseUrl" . ) }}
 {{- end -}}
 
 {{- define "app.ozgcloudBezeichner" -}}
diff --git a/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml b/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml
index 313812a726ff3f48bdab917e1b7ecfe8812cc3c0..d42402bb33022a66ed4f19427f3f9072b0330e2f 100644
--- a/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml
+++ b/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml
@@ -45,7 +45,7 @@ spec:
     metadata:
       labels:
         {{- include "app.defaultLabels" . | indent 8 }}
-        component: admin-client
+        component: administration-client
     spec:
       {{- if (.Values.serviceAccount).create }}
       serviceAccountName: {{ include "app.serviceAccountName" . }}
@@ -69,7 +69,7 @@ spec:
 
         image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ coalesce (.Values.image).tag "latest" }}"
         imagePullPolicy: Always
-        name: admin-client
+        name: administration-client
 
         startupProbe:
           httpGet:
diff --git a/alfa-client/apps/admin/src/main/helm/templates/ingress.yaml b/alfa-client/apps/admin/src/main/helm/templates/ingress.yaml
index eb9b7523e636cd573782b12cd4851ef087afa782..3a98e2e4a60e9dab74d960f6b51860dbc22d7fe3 100644
--- a/alfa-client/apps/admin/src/main/helm/templates/ingress.yaml
+++ b/alfa-client/apps/admin/src/main/helm/templates/ingress.yaml
@@ -49,14 +49,14 @@ spec:
             backend:
               service:
                 name: administration
-                port: 
+                port:
                   number: 8080
           - path: /
             pathType: Prefix
             backend:
               service:
-                name: admin-client
-                port: 
+                name: administration-client
+                port:
                   number: 8080
 
       host: {{ include "app.baseDomain" . }}
diff --git a/alfa-client/apps/admin/src/main/helm/templates/network_policy.yaml b/alfa-client/apps/admin/src/main/helm/templates/network_policy.yaml
index 27b2d9d14a038a1dc5c96d66ca4c464ea4a93679..9a5db61e93b95cb856adc1a337e863d1cf28fc83 100644
--- a/alfa-client/apps/admin/src/main/helm/templates/network_policy.yaml
+++ b/alfa-client/apps/admin/src/main/helm/templates/network_policy.yaml
@@ -26,7 +26,7 @@
 apiVersion: networking.k8s.io/v1
 kind: NetworkPolicy
 metadata:
-  name: network-policy-admin-client
+  name: network-policy-administration-client
   namespace: {{ .Release.Namespace }}
 spec:
   podSelector:
@@ -46,7 +46,7 @@ spec:
 {{- end }}
   egress:
   - to:
-    - namespaceSelector: 
+    - namespaceSelector:
         matchLabels:
           kubernetes.io/metadata.name: administration
   - to:
diff --git a/alfa-client/apps/admin/src/main/helm/templates/service.yaml b/alfa-client/apps/admin/src/main/helm/templates/service.yaml
index 53c0aa703d4563cc7322a8f672fa78c6bda526eb..4c3ba099a334cb01d4bcd07516d8426124e6d5a6 100644
--- a/alfa-client/apps/admin/src/main/helm/templates/service.yaml
+++ b/alfa-client/apps/admin/src/main/helm/templates/service.yaml
@@ -29,7 +29,7 @@ metadata:
   namespace: {{ include "app.namespace" . }}
   labels:
     {{- include "app.defaultLabels" . | indent 4 }}
-    component: admin-client-service
+    component: administration-client-service
 spec:
   type: ClusterIP
   ports:
@@ -40,4 +40,4 @@ spec:
 
   selector:
     {{- include "app.matchLabels" . | indent 4 }}
-    component: admin-client
\ No newline at end of file
+    component: administration-client
\ No newline at end of file
diff --git a/alfa-client/apps/admin/src/main/helm/values.yaml b/alfa-client/apps/admin/src/main/helm/values.yaml
index c1b1df2cdae5978d1c6dbe828a4a36f973f70ede..3bd9e23beaf72777caffab31fcfa85bfc25bf327 100644
--- a/alfa-client/apps/admin/src/main/helm/values.yaml
+++ b/alfa-client/apps/admin/src/main/helm/values.yaml
@@ -24,9 +24,9 @@
 
 image:
   repo: docker.ozg-sh.de
-  name: admin-client
+  name: administration-client
   tag: 0.1.0 # [default: latest]
 replicaCount: 1
 
 ozgcloud:
-  adminDomainSuffix: admin
+  administrationDomainSuffix: administration
diff --git a/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.html b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..d51d31da0551989004ab7559e67800e689e0bb1f
--- /dev/null
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.html
@@ -0,0 +1 @@
+<admin-aggregation-mapping-form-container></admin-aggregation-mapping-form-container>
\ No newline at end of file
diff --git a/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.spec.ts b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8d31cfc83f60711e8639ea6e8f3f281eaf8ee5b
--- /dev/null
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.spec.ts
@@ -0,0 +1,32 @@
+import { AggregationMappingFormContainerComponent } from '@admin-client/aggregation-mapping';
+import { expectComponentExistsInTemplate } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MockComponent } from 'ng-mocks';
+import { AggregationMappingFormPageComponent } from './aggregation-mapping-form-page.component';
+
+describe('AggregationMappingFormPageComponent', () => {
+  let component: AggregationMappingFormPageComponent;
+  let fixture: ComponentFixture<AggregationMappingFormPageComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [AggregationMappingFormPageComponent, MockComponent(AggregationMappingFormContainerComponent)],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingFormPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('template', () => {
+    describe('aggregation mapping form', () => {
+      it('should exists', () => {
+        expectComponentExistsInTemplate(fixture, AggregationMappingFormContainerComponent);
+      });
+    });
+  });
+});
diff --git a/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.ts b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a85688c8b243b0bd400b30d7db4b088514227554
--- /dev/null
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-form-page/aggregation-mapping-form-page.component.ts
@@ -0,0 +1,10 @@
+import { AggregationMappingFormContainerComponent } from '@admin-client/aggregation-mapping';
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'admin-aggregation-mapping-form-page',
+  standalone: true,
+  imports: [AggregationMappingFormContainerComponent],
+  templateUrl: './aggregation-mapping-form-page.component.html',
+})
+export class AggregationMappingFormPageComponent {}
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.html b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.html
similarity index 91%
rename from alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.html
rename to alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.html
index 5213f512591359ed5e619f95d17e27b36dc4d3a7..ac183ce76a05631f1880ccf6d982135071806adf 100644
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.html
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.html
@@ -23,4 +23,4 @@
     unter der Lizenz sind dem Lizenztext zu entnehmen.
 
 -->
-<admin-statistik-container data-test-id="statistik-container" />
+<admin-aggregation-mapping-list-container data-test-id="aggregation-mapping-container" />
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.spec.ts b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.spec.ts
similarity index 70%
rename from alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.spec.ts
rename to alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.spec.ts
index 2166339888793c63b712e4b4d67cd286002ac6b0..b036d9be9e55aea5415fdc7d172b401789b9d3c0 100644
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.spec.ts
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.spec.ts
@@ -21,24 +21,23 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { StatistikContainerComponent } from '@admin-client/statistik';
+import { AggregationMappingListContainerComponent } from '@admin-client/aggregation-mapping';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { MockComponent } from 'ng-mocks';
-import { StatistikPageComponent } from './statistik-page.component';
+import { AggregationMappingListPageComponent } from './aggregation-mapping-list-page.component';
 
-describe('StatistikPageComponent', () => {
-  let component: StatistikPageComponent;
-  let fixture: ComponentFixture<StatistikPageComponent>;
+describe('AggregationMappingListPageComponent', () => {
+  let component: AggregationMappingListPageComponent;
+  let fixture: ComponentFixture<AggregationMappingListPageComponent>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [],
-      declarations: [StatistikPageComponent, MockComponent(StatistikContainerComponent)],
+      imports: [AggregationMappingListPageComponent, MockComponent(AggregationMappingListContainerComponent)],
     }).compileComponents();
   });
 
   beforeEach(() => {
-    fixture = TestBed.createComponent(StatistikPageComponent);
+    fixture = TestBed.createComponent(AggregationMappingListPageComponent);
     component = fixture.componentInstance;
 
     fixture.detectChanges();
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.ts b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.ts
similarity index 76%
rename from alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.ts
rename to alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.ts
index 53fd00fc7c8677f8b78216c013f6f191bd24cd76..47858d2978fa3ec8ac8231539f4b1a9fba3c5c85 100644
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-page/statistik-page.component.ts
+++ b/alfa-client/apps/admin/src/pages/aggregation-mapping/aggregation-mapping-list-page/aggregation-mapping-list-page.component.ts
@@ -21,13 +21,13 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { StatistikContainerComponent } from '@admin-client/statistik';
+import { AggregationMappingListContainerComponent } from '@admin-client/aggregation-mapping';
 import { Component } from '@angular/core';
 
 @Component({
-  selector: 'statistik-page',
+  selector: 'admin-aggregation-mapping-list-page',
   standalone: true,
-  imports: [StatistikContainerComponent],
-  templateUrl: './statistik-page.component.html',
+  imports: [AggregationMappingListContainerComponent],
+  templateUrl: './aggregation-mapping-list-page.component.html',
 })
-export class StatistikPageComponent {}
+export class AggregationMappingListPageComponent {}
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.html b/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.html
deleted file mode 100644
index a2e1b29bb93d508d58ca98beaf365e1999e301ab..0000000000000000000000000000000000000000
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.html
+++ /dev/null
@@ -1 +0,0 @@
-<admin-statistik-fields-form data-test-id="evaluate-fields-form"></admin-statistik-fields-form>
\ No newline at end of file
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.spec.ts b/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.spec.ts
deleted file mode 100644
index 938cc1b0061d2194105629ca70c1e5f8619d881c..0000000000000000000000000000000000000000
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.spec.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { AdminStatistikFieldsFormComponent } from '@admin-client/statistik';
-import { existsAsHtmlElement } from '@alfa-client/test-utils';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MockComponent } from 'ng-mocks';
-import { getDataTestIdOf } from '../../../../../../libs/tech-shared/test/data-test';
-import { StatistikFieldsFormPageComponent } from './statistik-fields-form-page.component';
-
-describe('StatistikFieldsFormPageComponent', () => {
-  let component: StatistikFieldsFormPageComponent;
-  let fixture: ComponentFixture<StatistikFieldsFormPageComponent>;
-
-  const evaluateFieldsForm: string = getDataTestIdOf('evaluate-fields-form');
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [StatistikFieldsFormPageComponent, MockComponent(AdminStatistikFieldsFormComponent)],
-    }).compileComponents();
-
-    fixture = TestBed.createComponent(StatistikFieldsFormPageComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  describe('template', () => {
-    describe('weiter felder auswerten form', () => {
-      it('should exists', () => {
-        fixture.detectChanges();
-
-        existsAsHtmlElement(fixture, evaluateFieldsForm);
-      });
-    });
-  });
-});
diff --git a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.ts b/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.ts
deleted file mode 100644
index c4122b72d148865b4219d5446a3c2060574b23e7..0000000000000000000000000000000000000000
--- a/alfa-client/apps/admin/src/pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { AdminStatistikFieldsFormComponent } from '@admin-client/statistik';
-import { CommonModule } from '@angular/common';
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'statistik-fields-form-page',
-  standalone: true,
-  imports: [CommonModule, AdminStatistikFieldsFormComponent],
-  templateUrl: './statistik-fields-form-page.component.html',
-})
-export class StatistikFieldsFormPageComponent {}
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_63_char_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_63_char_test.yaml
index aefd7f81b2c70e7b750b09f5545507345b5bf4ab..db1cc9f92904d10299725421e4c7206b7d2e5798 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_63_char_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_63_char_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deyploment less than 63 chars
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 
 templates:
@@ -49,7 +49,7 @@ tests:
       version: 1.0-test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890
     asserts:
       - failedTemplate:
-          errorMessage: .Chart.Name-.Chart.Version admin-client-1.0-test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890 ist zu lang (max. 63 Zeichen)
+          errorMessage: .Chart.Name-.Chart.Version administration-client-1.0-test1234567890123123456789012345678901234567890123456789012345678901234567890123456789012345678904567890 ist zu lang (max. 63 Zeichen)
   - it: should not fail on .Chart.Name-.Chart.Version length less than 63 characters
     asserts:
-      - notFailedTemplate: {}
\ No newline at end of file
+      - notFailedTemplate: {}
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_container_basic_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_container_basic_test.yaml
index fd783faf0597f003256bfc3b6cdb3a4b48fda537..301b06eb9bd252846c9cc2e908902380ecf6739d 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_container_basic_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_container_basic_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment container basics
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -38,20 +38,10 @@ tests:
     asserts:
       - equal:
           path: spec.template.spec.containers[0].image
-          value: docker.ozg-sh.de/admin-client:0.1.0
+          value: docker.ozg-sh.de/administration-client:0.1.0
       - equal:
           path: spec.template.spec.containers[0].name
-          value: admin-client
-      - equal: 
+          value: administration-client
+      - equal:
           path: spec.template.spec.containers[0].imagePullPolicy
           value: Always
-  #- it: should have correct values for container ports
-   # asserts:
-    #  - contains: 
-     #     path: spec.template.spec.containers[0].ports
-      #    content:  
-       #     containerPort: 8081
-        #    name: metrics
-         #   protocol: TCP
-            
-            
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_container_other_values_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_container_other_values_test.yaml
index e73cabb2634d16f2c3c8cd104ecc0ade2af46d78..32d7a2be8b0340082991856a15d75408a3e86360 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_container_other_values_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_container_other_values_test.yaml
@@ -22,8 +22,7 @@
 # unter der Lizenz sind dem Lizenztext zu entnehmen.
 #
 
-   
-   #
+#
 # Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
 # Ministerpräsidenten des Landes Schleswig-Holstein
 # Staatskanzlei
@@ -49,7 +48,7 @@
 
 suite: test deployment container other values
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -57,7 +56,7 @@ set:
   ozgcloud:
     environment: dev
   imagePullSecret: test-image-secret
- 
+
 tests:
   - it: should have correct values for container terminationMessagePolicy, terminationMessagePath, stdin, tty
     asserts:
@@ -67,9 +66,9 @@ tests:
       - equal:
           path: spec.template.spec.containers[0].terminationMessagePath
           value: /dev/termination-log
-      - equal: 
+      - equal:
           path: spec.template.spec.containers[0].stdin
           value: true
-      - equal: 
+      - equal:
           path: spec.template.spec.containers[0].tty
-          value: true
\ No newline at end of file
+          value: true
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_container_security_context_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_container_security_context_test.yaml
index 6acb97512889899878f48eb3f510d1011ca7e0f4..6e42a6a93048f8577650fe57186df2a2db592b0f 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_container_security_context_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_container_security_context_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment container security context
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -65,4 +65,4 @@ tests:
     asserts:
       - equal:
           path: spec.template.spec.containers[0].securityContext.runAsGroup
-          value: 1000
\ No newline at end of file
+          value: 1000
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_defaults_labels_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_defaults_labels_test.yaml
index 8a98ecee5e35f378e0bc537dd6fca032198083a4..e616ba28a7f21bc9cb1cdbed81e5b065dc9a290d 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_defaults_labels_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_defaults_labels_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment default labels
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -39,33 +39,32 @@ tests:
       - equal:
           path: metadata.labels
           value:
-            app.kubernetes.io/instance: admin-client
+            app.kubernetes.io/instance: administration-client
             app.kubernetes.io/managed-by: Helm
-            app.kubernetes.io/name: admin-client
+            app.kubernetes.io/name: administration-client
             app.kubernetes.io/namespace: sh-helm-test
             app.kubernetes.io/part-of: ozgcloud
             app.kubernetes.io/version: 0.0.0-MANAGED-BY-JENKINS
-            helm.sh/chart: admin-client-0.0.0-MANAGED-BY-JENKINS      
-  
+            helm.sh/chart: administration-client-0.0.0-MANAGED-BY-JENKINS
+
   - it: should set spec.selector.matchLabels
     asserts:
       - equal:
           path: spec.selector.matchLabels
           value:
-            app.kubernetes.io/name: admin-client
+            app.kubernetes.io/name: administration-client
             app.kubernetes.io/namespace: sh-helm-test
 
-
   - it: should have correct deyploment spec.template.metadata.labels
     asserts:
       - equal:
           path: spec.template.metadata.labels
-          value: 
-            app.kubernetes.io/instance: admin-client
+          value:
+            app.kubernetes.io/instance: administration-client
             app.kubernetes.io/managed-by: Helm
-            app.kubernetes.io/name: admin-client
+            app.kubernetes.io/name: administration-client
             app.kubernetes.io/namespace: sh-helm-test
             app.kubernetes.io/part-of: ozgcloud
             app.kubernetes.io/version: 0.0.0-MANAGED-BY-JENKINS
-            component: admin-client
-            helm.sh/chart: admin-client-0.0.0-MANAGED-BY-JENKINS
\ No newline at end of file
+            component: administration-client
+            helm.sh/chart: administration-client-0.0.0-MANAGED-BY-JENKINS
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_defaults_topologySpreadConstraints_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_defaults_topologySpreadConstraints_test.yaml
index 7f1200536637e1584514d7bc504615702694dc01..dcef5143625d5da53e1333f52826c9ec15348efa 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_defaults_topologySpreadConstraints_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_defaults_topologySpreadConstraints_test.yaml
@@ -24,11 +24,11 @@
 
 suite: test deployment topology spread constrains
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
-set:  
+set:
   ozgcloud:
     environment: test
   imagePullSecret: test-image-secret
@@ -49,4 +49,4 @@ tests:
           value: ScheduleAnyway
       - equal:
           path: spec.template.spec.topologySpreadConstraints[0].labelSelector.matchLabels["app.kubernetes.io/name"]
-          value: admin-client
\ No newline at end of file
+          value: administration-client
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_general_value_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_general_value_test.yaml
index 85d1b98c99cefb1080d4db168395c6d764b9a050..114d9e309062cf45213faaeedc9170dc54da3665 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_general_value_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_general_value_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment general values
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -33,25 +33,23 @@ set:
     environment: dev
   imagePullSecret: test-image-secret
 
-
 tests:
   - it: should have correct apiVersion
     asserts:
       - isKind:
           of: Deployment
       - isAPIVersion:
-           of: "apps/v1"
-      
-  - it: should have correct deployment metadata 
-    asserts: 
+          of: 'apps/v1'
+
+  - it: should have correct deployment metadata
+    asserts:
       - equal:
           path: metadata.name
-          value: admin-client
-      - equal: 
+          value: administration-client
+      - equal:
           path: metadata.namespace
           value: sh-helm-test
 
-
   - it: should have correct deyployment general spec values
     asserts:
       - equal:
@@ -65,12 +63,10 @@ tests:
           value: 10
   - it: should have correct deployment spec strategy values
     asserts:
-      - equal: 
+      - equal:
           path: spec.strategy
-          value: 
+          value:
             rollingUpdate:
               maxSurge: 1
               maxUnavailable: 0
             type: RollingUpdate
-          
-
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_host_aliases_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_host_aliases_test.yaml
index 4fe65ff025aa4892f639bd6452bc5e9cea316e09..fda8d70bf7892b11f9f383fae197207b86c499e2 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_host_aliases_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_host_aliases_test.yaml
@@ -24,11 +24,11 @@
 
 suite: deployment host aliases
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
-set:  
+set:
   ozgcloud:
     environment: test
   imagePullSecret: test-image-secret
@@ -40,15 +40,15 @@ tests:
   - it: should set spec.template.spec.hostAliases
     set:
       hostAliases:
-        - ip: "127.0.0.1"
+        - ip: '127.0.0.1'
           hostname:
-          - "eins"
-          - "zwei"
+            - 'eins'
+            - 'zwei'
     asserts:
       - contains:
           path: spec.template.spec.hostAliases
           content:
-            ip: "127.0.0.1"
+            ip: '127.0.0.1'
             hostname:
-            - "eins"
-            - "zwei"
+              - 'eins'
+              - 'zwei'
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_imagepull_secret_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_imagepull_secret_test.yaml
index 7129ef48705458bca19a741136907126fbe09fa1..1671e8a154517a59520eba65fa4f73ea50d7f9bf 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_imagepull_secret_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_imagepull_secret_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment image pull secret
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -38,4 +38,4 @@ tests:
     asserts:
       - equal:
           path: spec.template.spec.imagePullSecrets[0].name
-          value: test-image-secret
\ No newline at end of file
+          value: test-image-secret
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_ozgcloud_base_values_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_ozgcloud_base_values_test.yaml
index 9195d806882c3e6bc7c841b34169e8de7a2673e8..892f2a185541661a5aaed10337c9eee95c2744fc 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_ozgcloud_base_values_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_ozgcloud_base_values_test.yaml
@@ -24,11 +24,11 @@
 
 suite: test ozgcloud base values
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
-set:  
+set:
   imagePullSecret: test-image-secret
 
 tests:
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_resources_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_resources_test.yaml
index 487a6aa04c55c1636d661a7262635e573ab80276..ed640c855a3eb9a0a4c85087cf0275f72bf7b17e 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_resources_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_resources_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test deployment container resources
 release:
-  name: admin-client
+  name: administration-client
 templates:
   - templates/deployment.yaml
 set:
@@ -59,4 +59,3 @@ tests:
     asserts:
       - isEmpty:
           path: spec.template.spec.containers[0].resources
-
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml
index 98246ba981cba50008edd137c04040383faa5ecc..e8544c9249516ef141e947003588e565bc138929 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml
@@ -24,7 +24,7 @@
 
 suite: deployment service account
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
@@ -41,7 +41,7 @@ tests:
     asserts:
       - equal:
           path: spec.template.spec.serviceAccountName
-          value: admin-client-service-account
+          value: administration-client-service-account
   - it: should use service account with name
     set:
       serviceAccount:
@@ -54,4 +54,4 @@ tests:
   - it: should use default service account
     asserts:
       - isNull:
-          path: spec.template.spec.serviceAccountName
\ No newline at end of file
+          path: spec.template.spec.serviceAccountName
diff --git a/alfa-client/apps/admin/src/test/helm/deployment_springProfile_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_springProfile_test.yaml
index c42ea85d177c3daf475aa403f636efc4f8594707..483f9ef354b4d5bedeaf9fbbede3b1da763afc92 100644
--- a/alfa-client/apps/admin/src/test/helm/deployment_springProfile_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/deployment_springProfile_test.yaml
@@ -24,11 +24,11 @@
 
 suite: test deployment spring profiles
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/deployment.yaml
-set:  
+set:
   ozgcloud:
     environment: test
   imagePullSecret: test-image-secret
@@ -53,4 +53,4 @@ tests:
           path: spec.template.spec.containers[0].env
           content:
             name: spring_profiles_active
-            value: oc, test
\ No newline at end of file
+            value: oc, test
diff --git a/alfa-client/apps/admin/src/test/helm/ingress-tests.yaml b/alfa-client/apps/admin/src/test/helm/ingress-tests.yaml
index 55974c06a961fefc8b43b985118eaf0eff5cc92a..ffcbcd43dc757d2bc0bc454dc88bcb556afb10f9 100644
--- a/alfa-client/apps/admin/src/test/helm/ingress-tests.yaml
+++ b/alfa-client/apps/admin/src/test/helm/ingress-tests.yaml
@@ -24,11 +24,11 @@
 
 suite: test ingress.yaml
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/ingress.yaml
-set:  
+set:
   ozgcloud:
     bezeichner: helm
   baseUrl: test.by.ozg-cloud.de
@@ -42,7 +42,7 @@ tests:
     asserts:
       - equal:
           path: spec.tls[0].secretName
-          value: helm-admin-client-tls
+          value: helm-administration-client-tls
 
   - it: should not create ingress tls/ingressClass
     set:
@@ -52,7 +52,7 @@ tests:
           path: spec.ingressClassName
       - isNull:
           path: spec.tls[0].secretName
-  
+
   - it: should use default letsencrypt-prod cluster-issuer
     asserts:
       - equal:
@@ -79,35 +79,34 @@ tests:
     asserts:
       - equal:
           path: spec.tls[0].hosts[0]
-          value: helm-admin.test.by.ozg-cloud.de
+          value: helm-administration.test.by.ozg-cloud.de
 
   - it: should create rules correctly
     asserts:
       - equal:
           path: spec.rules[0].http.paths[0]
-          value: 
-              path: /api
-              pathType: Prefix
-              backend:
-                service:
-                  name: administration
-                  port: 
-                    number: 8080
+          value:
+            path: /api
+            pathType: Prefix
+            backend:
+              service:
+                name: administration
+                port:
+                  number: 8080
 
       - equal:
           path: spec.rules[0].http.paths[1]
-          value: 
-              path: /
-              pathType: Prefix
-              backend:
-                service:
-                  name: admin-client
-                  port: 
-                    number: 8080
-      
+          value:
+            path: /
+            pathType: Prefix
+            backend:
+              service:
+                name: administration-client
+                port:
+                  number: 8080
 
   - it: should set hostname
     asserts:
       - equal:
           path: spec.rules[0].host
-          value: helm-admin.test.by.ozg-cloud.de
+          value: helm-administration.test.by.ozg-cloud.de
diff --git a/alfa-client/apps/admin/src/test/helm/network_policy_test.yaml b/alfa-client/apps/admin/src/test/helm/network_policy_test.yaml
index 2032db88a659c5b7fcf67aa199d6dba893bf5397..b9b5bce398183921d186fd26e480b1b0d6090110 100644
--- a/alfa-client/apps/admin/src/test/helm/network_policy_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/network_policy_test.yaml
@@ -22,9 +22,9 @@
 # unter der Lizenz sind dem Lizenztext zu entnehmen.
 #
 
-suite: network policy admin-client test
+suite: network policy administration-client test
 release:
-  name: admin-client
+  name: administration-client
   namespace: by-helm-test
 templates:
   - templates/network_policy.yaml
@@ -52,7 +52,7 @@ tests:
       - equal:
           path: metadata
           value:
-            name: network-policy-admin-client
+            name: network-policy-administration-client
             namespace: by-helm-test
 
   - it: should add egress rule to administration service
@@ -63,8 +63,8 @@ tests:
       - contains:
           path: spec.egress
           content:
-              to:
-              - namespaceSelector: 
+            to:
+              - namespaceSelector:
                   matchLabels:
                     kubernetes.io/metadata.name: administration
 
@@ -76,87 +76,86 @@ tests:
       - contains:
           path: spec.egress
           content:
-              to:
+            to:
               - namespaceSelector:
                   matchLabels:
                     kubernetes.io/metadata.name: test-dns-namespace
-              ports:
-                - port: 53
-                  protocol: UDP
-                - port: 53
-                  protocol: TCP
-                - port: 5353
-                  protocol: UDP
-                - port: 5353
-                  protocol: TCP
+            ports:
+              - port: 53
+                protocol: UDP
+              - port: 53
+                protocol: TCP
+              - port: 5353
+                protocol: UDP
+              - port: 5353
+                protocol: TCP
 
   - it: should add additionalIngressConfig local
     set:
       networkPolicy:
         dnsServerNamespace: test-namespace-dns
         additionalIngressConfigLocal:
-        - from:
-          - podSelector: 
-              matchLabels:
-                component: client2
+          - from:
+              - podSelector:
+                  matchLabels:
+                    component: client2
     asserts:
       - contains:
           path: spec.ingress
           content:
             from:
-            - podSelector: 
-                matchLabels:
-                  component: client2
+              - podSelector:
+                  matchLabels:
+                    component: client2
   - it: should add additionalIngressConfig global
     set:
       networkPolicy:
         dnsServerNamespace: test-namespace-dns
         additionalIngressConfigGlobal:
-        - from:
-          - podSelector: 
-              matchLabels:
-                component: client2
+          - from:
+              - podSelector:
+                  matchLabels:
+                    component: client2
     asserts:
       - contains:
           path: spec.ingress
           content:
             from:
-            - podSelector: 
-                matchLabels:
-                  component: client2
+              - podSelector:
+                  matchLabels:
+                    component: client2
 
   - it: should add additionalEgressConfig local
     set:
       networkPolicy:
         dnsServerNamespace: test-dns-namespace
         additionalEgressConfigLocal:
-        - to:
-          - ipBlock:
-              cidr: 1.2.3.4/32
+          - to:
+              - ipBlock:
+                  cidr: 1.2.3.4/32
     asserts:
-    - contains:
-        path: spec.egress
-        content:
-          to:
-          - ipBlock:
-              cidr: 1.2.3.4/32
+      - contains:
+          path: spec.egress
+          content:
+            to:
+              - ipBlock:
+                  cidr: 1.2.3.4/32
 
   - it: should add additionalEgressConfig global
     set:
       networkPolicy:
         dnsServerNamespace: test-dns-namespace
         additionalEgressConfigGlobal:
-        - to:
-          - ipBlock:
-              cidr: 1.2.3.4/32
+          - to:
+              - ipBlock:
+                  cidr: 1.2.3.4/32
     asserts:
-    - contains:
-        path: spec.egress
-        content:
-          to:
-          - ipBlock:
-              cidr: 1.2.3.4/32
-
+      - contains:
+          path: spec.egress
+          content:
+            to:
+              - ipBlock:
+                  cidr: 1.2.3.4/32
 
   - it: test network policy disabled
     set:
@@ -188,4 +187,4 @@ tests:
         dnsServerNamespace: test-dns-server-namespace
     asserts:
       - hasDocuments:
-          count: 1
\ No newline at end of file
+          count: 1
diff --git a/alfa-client/apps/admin/src/test/helm/service_account_test.yaml b/alfa-client/apps/admin/src/test/helm/service_account_test.yaml
index e80dde85e375a7ba052a403807c460969c9fdc4e..ed45acb09d1975f2cde3e28d2ffa53ca44603e8e 100644
--- a/alfa-client/apps/admin/src/test/helm/service_account_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/service_account_test.yaml
@@ -24,7 +24,7 @@
 
 suite: test service account
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/service_account.yaml
@@ -40,7 +40,7 @@ tests:
           of: v1
       - equal:
           path: metadata.name
-          value: admin-client-service-account
+          value: administration-client-service-account
       - equal:
           path: metadata.namespace
           value: sh-helm-test
@@ -61,4 +61,4 @@ tests:
   - it: should not create service account
     asserts:
       - hasDocuments:
-        count: 0
\ No newline at end of file
+        count: 0
diff --git a/alfa-client/apps/admin/src/test/helm/service_test.yaml b/alfa-client/apps/admin/src/test/helm/service_test.yaml
index 6496fb494b71c6d91a23ee3979b611ebd06a9fda..02bff8a2fc9e152c2db8f5ec57eb91e6a592ff88 100644
--- a/alfa-client/apps/admin/src/test/helm/service_test.yaml
+++ b/alfa-client/apps/admin/src/test/helm/service_test.yaml
@@ -24,20 +24,20 @@
 
 suite: test service
 release:
-  name: admin-client
+  name: administration-client
   namespace: sh-helm-test
 templates:
   - templates/service.yaml
 tests:
-  - it: should have the label component with correct value 
+  - it: should have the label component with correct value
     asserts:
       - isKind:
           of: Service
       - isAPIVersion:
-           of: v1
+          of: v1
       - equal:
           path: metadata.labels.component
-          value: admin-client-service
+          value: administration-client-service
   - it: should be of type ClusterIP
     asserts:
       - equal:
@@ -48,19 +48,19 @@ tests:
     asserts:
       - equal:
           path: spec.selector.component
-          value: admin-client
+          value: administration-client
 
   - it: selector should contain helm recommended labels name and namespace
     asserts:
       - equal:
           path: spec.selector
           value:
-            app.kubernetes.io/name: admin-client
+            app.kubernetes.io/name: administration-client
             app.kubernetes.io/namespace: sh-helm-test
-            component: admin-client
+            component: administration-client
 
   - it: check component label for service
     asserts:
       - equal:
           path: metadata.labels["component"]
-          value: admin-client-service
\ No newline at end of file
+          value: administration-client-service
diff --git a/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component.ts b/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component.ts
index 7f5d897628057b0c929c61fd9596ad7de3dad49a..34702bb369f5ae2c4bb777ea001a14562c8d5b9c 100644
--- a/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component.ts
+++ b/alfa-client/apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component.ts
@@ -5,9 +5,9 @@ export class ForwardingDialogE2EComponent {
   private readonly forwardingButton: string = 'forwarding-dialog-forwarding-button';
   private readonly searchText: string = 'instant_search-text-input';
   private readonly searchEntry: string = 'item-button';
-  private readonly forwardingItem: string = 'forwarding-item';
+  private readonly selectedSearchItem: string = 'selected-search-item';
   private readonly changeButton: string = 'forwarding-item-change-button';
-  private readonly zufiSearch: string = 'zufi-search';
+  private readonly organisationsEinheitSearch: string = 'organisations-einheit-search';
 
   public getRoot() {
     return cy.getTestElement(this.root);
@@ -37,15 +37,15 @@ export class ForwardingDialogE2EComponent {
     cy.getTestElement(this.searchEntry).eq(index).click();
   }
 
-  public getForwardingItem() {
-    return cy.getTestElement(this.forwardingItem);
+  public getSelectedSearchItem() {
+    return cy.getTestElement(this.selectedSearchItem);
   }
 
   public getChangeButton() {
     return cy.getTestElement(this.changeButton);
   }
 
-  public getZufiSearch() {
-    return cy.getTestElement(this.zufiSearch);
+  public getOrganisationsEinheitSearch() {
+    return cy.getTestElement(this.organisationsEinheitSearch);
   }
 }
diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-forwarding.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-forwarding.cy.ts
index d91019905c1bc53a44fb0a28e0fb882a416933f3..8634b339b87e025f62705edaefa760c86c16575b 100644
--- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-forwarding.cy.ts
+++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-detailansicht/vorgang-forwarding.cy.ts
@@ -2,6 +2,7 @@ import { registerLocaleData } from '@angular/common';
 import localeDe from '@angular/common/locales/de';
 import localeDeExtra from '@angular/common/locales/extra/de';
 import { VorgangFormularButtonsE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-formular-buttons.e2e.components';
+import { ForwardingDialogE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component';
 import { VorgangListE2EComponent } from '../../../components/vorgang/vorgang-list.e2e.component';
 import { E2EVorgangNavigator } from '../../../helper/vorgang/vorgang.navigator';
 import { E2EVorgangVerifier } from '../../../helper/vorgang/vorgang.verifier';
@@ -9,10 +10,9 @@ import { VorgangE2E } from '../../../model/vorgang';
 import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po';
 import { VorgangPage } from '../../../page-objects/vorgang.po';
 import { dropCollections } from '../../../support/cypress-helper';
-import { beDisabled, contains, exist, notBeDisabled, notExist } from '../../../support/cypress.util';
+import { beAriaDisabled, contains, exist, notBeAriaDisabled, notExist } from '../../../support/cypress.util';
 import { loginAsPeter, loginAsSabine } from '../../../support/user-util';
 import { createVorgang, initVorgaenge } from '../../../support/vorgang-util';
-import { ForwardingDialogE2EComponent } from 'apps/alfa-e2e/src/components/vorgang/vorgang-forwarding-dialog.e2e.component';
 
 registerLocaleData(localeDe, 'de', localeDeExtra);
 
@@ -59,12 +59,12 @@ describe('Vorgang weiterleiten', () => {
       exist(forwardingDialog.getRoot());
     });
 
-    it('should have zufi search', () => {
-      exist(forwardingDialog.getZufiSearch());
+    it('should have organisations einheit search', () => {
+      exist(forwardingDialog.getOrganisationsEinheitSearch());
     });
 
     it('should have disabled forwarding button', () => {
-      beDisabled(forwardingDialog.getForwardingButton());
+      beAriaDisabled(forwardingDialog.getForwardingButton());
     });
 
     it('should close dialog on escape', () => {
@@ -87,30 +87,30 @@ describe('Vorgang weiterleiten', () => {
       notExist(forwardingDialog.getRoot());
     });
 
-    it('should show forwarding item on search select', () => {
+    it('should show selected search item on search select', () => {
       vorgangFormularButtons.getForwardButton().click();
       forwardingDialog.search(organisationsEinheitName);
       forwardingDialog.clickSearchEntry(0);
 
-      exist(forwardingDialog.getForwardingItem());
-      contains(forwardingDialog.getForwardingItem(), organisationsEinheitName);
-      contains(forwardingDialog.getForwardingItem(), organisationsEinheitAddress);
+      exist(forwardingDialog.getSelectedSearchItem());
+      contains(forwardingDialog.getSelectedSearchItem(), organisationsEinheitName);
+      contains(forwardingDialog.getSelectedSearchItem(), organisationsEinheitAddress);
     });
 
     it('should not show zufi search on search select', () => {
-      notExist(forwardingDialog.getZufiSearch());
+      notExist(forwardingDialog.getOrganisationsEinheitSearch());
     });
 
     it('should not disable forwarding button on search select', () => {
-      notBeDisabled(forwardingDialog.getForwardingButton());
+      notBeAriaDisabled(forwardingDialog.getForwardingButton());
     });
 
-    it('should clear forwarding item on change button click', () => {
+    it('should clear selected search item on change button click', () => {
       forwardingDialog.getChangeButton().click();
 
-      notExist(forwardingDialog.getForwardingItem());
-      exist(forwardingDialog.getZufiSearch());
-      beDisabled(forwardingDialog.getForwardingButton());
+      notExist(forwardingDialog.getSelectedSearchItem());
+      exist(forwardingDialog.getOrganisationsEinheitSearch());
+      beAriaDisabled(forwardingDialog.getForwardingButton());
     });
 
     it('should not display Weiterleiten button in status Angenommen', () => {
diff --git a/alfa-client/apps/alfa-e2e/src/helper/forwarding/forwarding.executor.ts b/alfa-client/apps/alfa-e2e/src/helper/forwarding/forwarding.executor.ts
index 22295bb180d00c9d23b787fde72a001bea29cab2..659d24215d7275c70ae2ee1b710f196dd0b81a46 100644
--- a/alfa-client/apps/alfa-e2e/src/helper/forwarding/forwarding.executor.ts
+++ b/alfa-client/apps/alfa-e2e/src/helper/forwarding/forwarding.executor.ts
@@ -9,7 +9,7 @@ export class E2EForwardingExecutor {
     this.forwardingDialog.getSearchText().type(organisationsEinheit);
     waitForSpinnerToDisappear();
     this.forwardingDialog.clickSearchEntry(0);
-    exist(this.forwardingDialog.getForwardingItem());
+    exist(this.forwardingDialog.getSelectedSearchItem());
     this.forwardingDialog.getForwardingButton().click();
     waitForSpinnerToDisappear();
   }
diff --git a/alfa-client/apps/alfa-e2e/src/support/cypress.util.ts b/alfa-client/apps/alfa-e2e/src/support/cypress.util.ts
index f62a66e73e504ddd9952afd438ecdd7cf242866a..768db5ed90ce7662d1e2358b58aff85d86655f6b 100644
--- a/alfa-client/apps/alfa-e2e/src/support/cypress.util.ts
+++ b/alfa-client/apps/alfa-e2e/src/support/cypress.util.ts
@@ -97,12 +97,12 @@ export function notBeChecked(element: any): void {
   element.should('not.be.checked');
 }
 
-export function beDisabled(element: any): void {
-  element.should('be.disabled');
+export function beAriaDisabled(element: any): void {
+  element.should('have.attr', 'aria-disabled', 'true');
 }
 
-export function notBeDisabled(element: any): void {
-  element.should('not.be.disabled');
+export function notBeAriaDisabled(element: any): void {
+  element.should('have.attr', 'aria-disabled', 'false');
 }
 
 //TODO: "first()" rausnehmen -> im html eine entprechende data-test-id ansprechen?! | trennen in "get" und "verify"
diff --git a/alfa-client/apps/alfa/package.json b/alfa-client/apps/alfa/package.json
index db8d098a8244e0cfde3ebcd7f8e148c07ffd9316..d2b0c691be9b12310cb1f527368dbf97b1ac764e 100644
--- a/alfa-client/apps/alfa/package.json
+++ b/alfa-client/apps/alfa/package.json
@@ -1,4 +1,4 @@
 {
   "name": "alfa",
-  "version": "2.22.0-SNAPSHOT"
+  "version": "2.23.0-SNAPSHOT"
 }
diff --git a/alfa-client/apps/info/package.json b/alfa-client/apps/info/package.json
index f7b9ea61038da3ea7801ac1121b57c21816c49a6..b0116c864ac85254663de4319855171ec1183e33 100644
--- a/alfa-client/apps/info/package.json
+++ b/alfa-client/apps/info/package.json
@@ -1,4 +1,4 @@
 {
   "name": "info",
-  "version": "1.7.0-SNAPSHOT"
+  "version": "1.8.0-SNAPSHOT"
 }
diff --git a/alfa-client/libs/admin/statistik/.eslintrc.json b/alfa-client/libs/admin/aggregation-mapping/.eslintrc.json
similarity index 100%
rename from alfa-client/libs/admin/statistik/.eslintrc.json
rename to alfa-client/libs/admin/aggregation-mapping/.eslintrc.json
diff --git a/alfa-client/libs/admin/aggregation-mapping/README.md b/alfa-client/libs/admin/aggregation-mapping/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e7c28147dedbfc615cb130aa675974839ef5485
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/README.md
@@ -0,0 +1 @@
+# Aggregation Mapping
\ No newline at end of file
diff --git a/alfa-client/libs/admin/statistik/jest.config.ts b/alfa-client/libs/admin/aggregation-mapping/jest.config.ts
similarity index 83%
rename from alfa-client/libs/admin/statistik/jest.config.ts
rename to alfa-client/libs/admin/aggregation-mapping/jest.config.ts
index fc41bd8816868cdd6f860b9908d0f06dbc9defc9..8ae2a4a3db388eb314ca1fbd2a5f4472c536e795 100644
--- a/alfa-client/libs/admin/statistik/jest.config.ts
+++ b/alfa-client/libs/admin/aggregation-mapping/jest.config.ts
@@ -1,8 +1,8 @@
 export default {
-  displayName: 'admin-statistik',
+  displayName: 'admin-aggregation-mapping',
   preset: '../../../jest.preset.js',
   setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
-  coverageDirectory: '../../../coverage/libs/admin/statistik',
+  coverageDirectory: '../../../coverage/libs/admin/aggregation-mapping',
   transform: {
     '^.+\\.(ts|mjs|js|html)$': [
       'jest-preset-angular',
diff --git a/alfa-client/libs/admin/statistik/project.json b/alfa-client/libs/admin/aggregation-mapping/project.json
similarity index 62%
rename from alfa-client/libs/admin/statistik/project.json
rename to alfa-client/libs/admin/aggregation-mapping/project.json
index a5c36fc013da6fcc3504172dad9627628b42cfd9..ab4704d303d7ff8c812f90168092a7f4de11d91e 100644
--- a/alfa-client/libs/admin/statistik/project.json
+++ b/alfa-client/libs/admin/aggregation-mapping/project.json
@@ -1,7 +1,7 @@
 {
-  "name": "admin-statistik",
+  "name": "admin-aggregation-mapping",
   "$schema": "../../../node_modules/nx/schemas/project-schema.json",
-  "sourceRoot": "libs/admin/statistik/src",
+  "sourceRoot": "libs/admin/aggregation-mapping/src",
   "prefix": "admin",
   "projectType": "library",
   "tags": [],
@@ -10,8 +10,8 @@
       "executor": "@nx/jest:jest",
       "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
       "options": {
-        "tsConfig": "libs/admin/statistik/tsconfig.lib.json",
-        "jestConfig": "libs/admin/statistik/jest.config.ts"
+        "tsConfig": "libs/admin/aggregation-mapping/tsconfig.lib.json",
+        "jestConfig": "libs/admin/aggregation-mapping/jest.config.ts"
       }
     },
     "lint": {
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/index.ts b/alfa-client/libs/admin/aggregation-mapping/src/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa299f4458131fceb97d7979da73dbd1c23d3446
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/index.ts
@@ -0,0 +1,2 @@
+export * from './lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component';
+export * from './lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component';
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..4cccfbe689b149cd7712935505a4bb3c95b67fec
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.html
@@ -0,0 +1,3 @@
+<ods-spinner [stateResource]="listStateResource$ | async">
+  <admin-aggregation-mapping-form data-test-id="aggregation-mapping-form" />
+</ods-spinner>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d192a6d428de34ad1b7abefa0b79d0a2ce1158ef
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.spec.ts
@@ -0,0 +1,51 @@
+import { AggregationMappingFormContainerComponent } from '@admin-client/aggregation-mapping';
+import { AggregationMappingService } from '@admin-client/reporting-shared';
+import { Mock, mock } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
+import { SpinnerComponent } from '@ods/component';
+import { MockComponent } from 'ng-mocks';
+import { AggregationMappingFormComponent } from './aggregation-mapping-form/aggregation-mapping-form.component';
+
+describe('AggregationMappingFormContainerComponent', () => {
+  let component: AggregationMappingFormContainerComponent;
+  let fixture: ComponentFixture<AggregationMappingFormContainerComponent>;
+
+  let aggregationMappingService: Mock<AggregationMappingService>;
+
+  beforeEach(async () => {
+    aggregationMappingService = mock(AggregationMappingService);
+
+    await TestBed.configureTestingModule({
+      imports: [
+        AggregationMappingFormContainerComponent,
+        MockComponent(SpinnerComponent),
+        MockComponent(AggregationMappingFormComponent),
+      ],
+      providers: [
+        {
+          provide: AggregationMappingService,
+          useValue: aggregationMappingService,
+        },
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingFormContainerComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('component', () => {
+    describe('on destroy', () => {
+      it('should reload list', () => {
+        component.ngOnDestroy();
+
+        expect(aggregationMappingService.refreshList).toHaveBeenCalled();
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f20feab09a9b98b0e1860ce01517220110d44b8c
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form-container.component.ts
@@ -0,0 +1,24 @@
+import { AggregationMappingListResource, AggregationMappingService } from '@admin-client/reporting-shared';
+import { StateResource } from '@alfa-client/tech-shared';
+import { AsyncPipe } from '@angular/common';
+import { Component, inject, OnDestroy } from '@angular/core';
+import { SpinnerComponent } from '@ods/component';
+import { Observable } from 'rxjs';
+import { AggregationMappingFormComponent } from './aggregation-mapping-form/aggregation-mapping-form.component';
+
+@Component({
+  selector: 'admin-aggregation-mapping-form-container',
+  standalone: true,
+  imports: [SpinnerComponent, AsyncPipe, AggregationMappingFormComponent],
+  templateUrl: './aggregation-mapping-form-container.component.html',
+})
+export class AggregationMappingFormContainerComponent implements OnDestroy {
+  public readonly aggregationMappingService = inject(AggregationMappingService);
+
+  public readonly listStateResource$: Observable<StateResource<AggregationMappingListResource>> =
+    this.aggregationMappingService.getList();
+
+  ngOnDestroy(): void {
+    this.aggregationMappingService.refreshList();
+  }
+}
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..a70383f4bcb5c99d3c73a19cc00d3ade15c5b9e5
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.html
@@ -0,0 +1,40 @@
+<form [formGroup]="formService.form">
+  <ng-container [formArrayName]="AggregationMappingFormService.FIELD_MAPPINGS">
+    <ng-container [formGroupName]="index">
+      <div class="mt-4 flex w-full flex-col rounded-md bg-background-150 p-4">
+        <div class="flex flex-row items-center justify-between">
+          <p class="mb-2 block text-lg font-medium text-text">Datenfeld</p>
+          <ods-button
+            class="self-end"
+            variant="ghost"
+            size="fit"
+            destructive="true"
+            (clickEmitter)="formService.removeMapping(index)"
+            [dataTestId]="'remove-mapping-button-' + index"
+            [attr.data-test-id]="'remove-mapping-' + index"
+          >
+            <ods-delete-icon icon />
+          </ods-button>
+        </div>
+        <div class="flex flex-col gap-4">
+          <ods-text-editor
+            [formControlName]="AggregationMappingFormService.FIELD_MAPPING_SOURCE_PATH"
+            label="Pfad"
+            placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
+            isRequired="true"
+            [dataTestId]="'source-mapping-field-' + index"
+            [attr.data-test-id]="'source-mapping-field-' + index"
+          ></ods-text-editor>
+          <ods-text-editor
+            [formControlName]="AggregationMappingFormService.FIELD_MAPPING_TARGET_PATH"
+            label="Zielfeld"
+            isRequired="true"
+            placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
+            [dataTestId]="'target-mapping-field-' + index"
+            [attr.data-test-id]="'target-mapping-field-' + index"
+          ></ods-text-editor>
+        </div>
+      </div>
+    </ng-container>
+  </ng-container>
+</form>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5f466dd455aa9a62f69769e5e51c6fa4a22a7a5
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.spec.ts
@@ -0,0 +1,99 @@
+import { ADMIN_FORMSERVICE } from '@admin-client/shared';
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
+import { existsAsHtmlElement, mock, Mock, MockEvent, mockGetValue, triggerEvent } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
+import { expect } from '@jest/globals';
+import { TextEditorComponent } from '@ods/component';
+import { ButtonComponent, DeleteIconComponent } from '@ods/system';
+import { MockComponent } from 'ng-mocks';
+import { getDataTestIdOf } from '../../../../../../../../tech-shared/test/data-test';
+import { AggregationMappingFormservice } from '../../aggregation-mapping.formservice';
+import { AggregationMappingFieldFormComponent } from './aggregation-mapping-field-form.component';
+
+describe('AggregationMappingFieldFormComponent', () => {
+  let component: AggregationMappingFieldFormComponent;
+  let fixture: ComponentFixture<AggregationMappingFieldFormComponent>;
+
+  const formBuilder: FormBuilder = new FormBuilder();
+  const fieldIndex: number = 0;
+  const sourcePathEditorTestId: string = getDataTestIdOf('source-mapping-field-0');
+  const targetPathEditorTestId: string = getDataTestIdOf('target-mapping-field-0');
+  const removeMappingButtonTestId: string = getDataTestIdOf('remove-mapping-0');
+
+  let formService: Mock<AggregationMappingFormservice>;
+
+  beforeEach(async () => {
+    const form: FormGroup = formBuilder.group({
+      [AggregationMappingFormservice.FIELD_MAPPINGS]: formBuilder.array([
+        new FormGroup({
+          [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: new FormControl(EMPTY_STRING),
+          [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: new FormControl(EMPTY_STRING),
+        }),
+      ]),
+    });
+
+    formService = <any>{
+      ...mock(AggregationMappingFormservice),
+      form,
+      addMapping: jest.fn(),
+      removeMapping: jest.fn(),
+    };
+
+    mockGetValue(
+      formService,
+      AggregationMappingFormservice.FIELD_MAPPINGS,
+      form.controls[AggregationMappingFormservice.FIELD_MAPPINGS],
+    );
+
+    await TestBed.configureTestingModule({
+      imports: [
+        AggregationMappingFieldFormComponent,
+        MockComponent(TextEditorComponent),
+        MockComponent(ButtonComponent),
+        MockComponent(DeleteIconComponent),
+      ],
+      providers: [
+        {
+          provide: ADMIN_FORMSERVICE,
+          useValue: formService,
+        },
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingFieldFormComponent);
+    component = fixture.componentInstance;
+    component.index = fieldIndex;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('template', () => {
+    describe('remove mapping button', () => {
+      it('should exists', () => {
+        existsAsHtmlElement(fixture, removeMappingButtonTestId);
+      });
+
+      it('should remove mapping on click', () => {
+        triggerEvent({ fixture, elementSelector: removeMappingButtonTestId, name: MockEvent.CLICK, data: fieldIndex });
+
+        expect(formService.removeMapping).toHaveBeenCalledWith(fieldIndex);
+      });
+    });
+
+    describe('source path text editor', () => {
+      it('should exists', () => {
+        existsAsHtmlElement(fixture, sourcePathEditorTestId);
+      });
+    });
+
+    describe('target path text editor', () => {
+      it('should should exists', () => {
+        existsAsHtmlElement(fixture, targetPathEditorTestId);
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..346d5f27049b36d31cdb49547ffc49607f94496a
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-form/aggregation-mapping-field-form.component.ts
@@ -0,0 +1,20 @@
+import { ADMIN_FORMSERVICE } from '@admin-client/shared';
+import { Component, inject, Input } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { TextEditorComponent } from '@ods/component';
+import { ButtonComponent, DeleteIconComponent } from '@ods/system';
+import { AggregationMappingFormservice } from '../../aggregation-mapping.formservice';
+
+@Component({
+  selector: 'admin-aggregation-mapping-field-form',
+  standalone: true,
+  templateUrl: './aggregation-mapping-field-form.component.html',
+  imports: [ButtonComponent, DeleteIconComponent, ReactiveFormsModule, TextEditorComponent],
+})
+export class AggregationMappingFieldFormComponent {
+  @Input({ required: true }) index: number;
+
+  public readonly formService = <AggregationMappingFormservice>inject(ADMIN_FORMSERVICE);
+
+  public readonly AggregationMappingFormService = AggregationMappingFormservice;
+}
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..965537eeb053cbb0003784d3e2adaebdbae084cb
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.html
@@ -0,0 +1,8 @@
+<form [formGroup]="formService.form">
+    <div *ngFor="let ignore of mappingsFormArray; let i = index">
+      <admin-aggregation-mapping-field-form
+        [index]="i"
+        [attr.data-test-id]="'aggregation-mapping-field-mapping-form-' + i"
+      ></admin-aggregation-mapping-field-form>
+    </div>
+</form>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..724a1e08cc31da67b155908df64530bda5b3cc4d
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.spec.ts
@@ -0,0 +1,84 @@
+import { ADMIN_FORMSERVICE } from '@admin-client/shared';
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
+import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock, mockGetValue } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { expect } from '@jest/globals';
+import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
+import { MockComponent } from 'ng-mocks';
+import { AggregationMappingFormservice } from '../aggregation-mapping.formservice';
+import { AggregationMappingFieldFormComponent } from './aggregation-mapping-field-form/aggregation-mapping-field-form.component';
+import { AggregationMappingFieldListFormComponent } from './aggregation-mapping-field-list-form.component';
+
+describe('AggregationMappingFieldListFormComponent', () => {
+  let component: AggregationMappingFieldListFormComponent;
+  let fixture: ComponentFixture<AggregationMappingFieldListFormComponent>;
+
+  const mappingForm: string = getDataTestIdOf('aggregation-mapping-field-mapping-form-0');
+
+  const formBuilder: FormBuilder = new FormBuilder();
+
+  let formService: Mock<AggregationMappingFormservice>;
+
+  beforeEach(async () => {
+    const form: FormGroup = formBuilder.group({
+      [AggregationMappingFormservice.FIELD_MAPPINGS]: formBuilder.array([
+        new FormGroup({
+          [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: new FormControl(EMPTY_STRING),
+          [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: new FormControl(EMPTY_STRING),
+        }),
+      ]),
+    });
+
+    formService = <any>{
+      ...mock(AggregationMappingFormservice),
+      form,
+      addMapping: jest.fn(),
+      removeMapping: jest.fn(),
+    };
+
+    mockGetValue(
+      formService,
+      AggregationMappingFormservice.FIELD_MAPPINGS,
+      form.controls[AggregationMappingFormservice.FIELD_MAPPINGS],
+    );
+
+    await TestBed.configureTestingModule({
+      imports: [
+        ReactiveFormsModule,
+        AggregationMappingFieldListFormComponent,
+        MockComponent(AggregationMappingFieldFormComponent),
+      ],
+      providers: [
+        {
+          provide: ADMIN_FORMSERVICE,
+          useValue: formService,
+        },
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingFieldListFormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('template', () => {
+    describe('mapping input', () => {
+      it('should exists', () => {
+        fixture.detectChanges();
+
+        existsAsHtmlElement(fixture, mappingForm);
+      });
+
+      it('should have inputs', () => {
+        const mappingComponent: AggregationMappingFieldFormComponent = getElementComponentFromFixtureByCss(fixture, mappingForm);
+
+        expect(mappingComponent.index).toEqual(0);
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b751ad91466cdf1b42f2f275bcaa9416882ddb1
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component.ts
@@ -0,0 +1,20 @@
+import { ADMIN_FORMSERVICE } from '@admin-client/shared';
+import { CommonModule } from '@angular/common';
+import { Component, inject } from '@angular/core';
+import { AbstractControl, ReactiveFormsModule } from '@angular/forms';
+import { AggregationMappingFormservice } from '../aggregation-mapping.formservice';
+import { AggregationMappingFieldFormComponent } from './aggregation-mapping-field-form/aggregation-mapping-field-form.component';
+
+@Component({
+  selector: 'admin-aggregation-mapping-field-list-form',
+  templateUrl: './aggregation-mapping-field-list-form.component.html',
+  standalone: true,
+  imports: [CommonModule, ReactiveFormsModule, AggregationMappingFieldFormComponent],
+})
+export class AggregationMappingFieldListFormComponent {
+  public readonly formService = <AggregationMappingFormservice>inject(ADMIN_FORMSERVICE);
+
+  public readonly mappingsFormArray: AbstractControl[] = this.formService.mappings.controls;
+
+  public readonly AggregationMappingFormService = AggregationMappingFormservice;
+}
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..346b7322ddeef11cd3c74b98dc1577243bf7714e
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.html
@@ -0,0 +1,53 @@
+<h2 class="heading-2" data-test-id="aggregation-mapping-fields-form-header-text">Felder zur Auswertung hinzufügen</h2>
+
+<ods-spinner [stateResource]="aggregationMappingStateResource$ | async">
+  <div class="flex max-w-4xl flex-col gap-4">
+    <form class="form flex-col" [formGroup]="formService.form" class="flex flex-col gap-4">
+      <div class="flex flex-col gap-4 lg:flex-row">
+        <ods-text-editor
+          class="basis-1/2 lg:pr-2"
+          [formControlName]="AggregationMappingFormService.FIELD_NAME"
+          label="Name"
+          placeholder=""
+          isRequired="true"
+          data-test-id="aggregation-mapping-name-text-editor"
+          dataTestId="aggregation-mapping-name"
+        ></ods-text-editor>
+      </div>
+      <div [formGroupName]="AggregationMappingFormService.FIELD_FORM_IDENTIFIER" class="flex flex-col gap-4 lg:flex-row">
+        <ods-text-editor
+          class="flex-1"
+          [formControlName]="AggregationMappingFormService.FIELD_FORM_ENGINE_NAME"
+          label="Formengine"
+          placeholder="Tragen Sie hier die Formengine des Formulars ein."
+          isRequired="true"
+          data-test-id="form-engine-name-text-editor"
+          dataTestId="form-engine-name"
+        ></ods-text-editor>
+        <ods-text-editor
+          class="flex-1"
+          [formControlName]="AggregationMappingFormService.FIELD_FORM_ID"
+          label="FormID"
+          placeholder="Tragen Sie hier die FormID des Formulars ein."
+          isRequired="true"
+          data-test-id="form-id-text-editor"
+          dataTestId="form-id"
+        ></ods-text-editor>
+      </div>
+      <admin-aggregation-mapping-field-list-form />
+    </form>
+    <ods-button
+      text="Datenfeld hinzufügen"
+      dataTestId="add-mapping-button"
+      data-test-id="add-mapping"
+      (clickEmitter)="formService.addMapping()"
+    >
+      <ods-plus-icon icon class="fill-whitetext" />
+    </ods-button>
+
+    <div class="mt-4 flex gap-4">
+      <admin-save-button [successLinkPath]="Routes.AGGREGATION_MAPPING" />
+      <admin-cancel-button [linkPath]="Routes.AGGREGATION_MAPPING" />
+    </div>
+  </div>
+</ods-spinner>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2abba71105c82b854633725a837836ec7f278f0a
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.spec.ts
@@ -0,0 +1,154 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ADMIN_FORMSERVICE, AdminCancelButtonComponent, AdminSaveButtonComponent, ROUTES } from '@admin-client/shared';
+import { createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
+import {
+  dispatchEventFromFixture,
+  existsAsHtmlElement,
+  getElementFromFixtureByType,
+  mock,
+  Mock,
+  MockEvent,
+} from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { expect } from '@jest/globals';
+import { TextEditorComponent } from '@ods/component';
+import { ButtonComponent, PlusIconComponent } from '@ods/system';
+import { MockComponent } from 'ng-mocks';
+import { of } from 'rxjs';
+import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test';
+import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles';
+import { createAggregationMappingResource } from '../../../../../reporting-shared/test/aggregation-mapping';
+import { AggregationMappingFieldListFormComponent } from './aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component';
+import { AggregationMappingFormComponent } from './aggregation-mapping-form.component';
+import { AggregationMappingFormservice } from './aggregation-mapping.formservice';
+
+describe('AggregationMappingFormComponent', () => {
+  let component: AggregationMappingFormComponent;
+  let fixture: ComponentFixture<AggregationMappingFormComponent>;
+
+  const formEngineNameInputTestId: string = getDataTestIdOf('form-engine-name-text-editor');
+  const formIdInputTestId: string = getDataTestIdOf('form-id-text-editor');
+  const addMappingButton: string = getDataTestIdOf('add-mapping');
+
+  const aggregationMappingStateResource: StateResource<AggregationMappingResource> = createStateResource(
+    createAggregationMappingResource(),
+  );
+
+  const formBuilder: FormBuilder = new FormBuilder();
+
+  let formService: Mock<AggregationMappingFormservice>;
+
+  beforeEach(async () => {
+    const form: FormGroup = formBuilder.group({
+      [AggregationMappingFormservice.FIELD_NAME]: new FormControl(EMPTY_STRING),
+      [AggregationMappingFormservice.FIELD_FORM_IDENTIFIER]: formBuilder.group({
+        [AggregationMappingFormservice.FIELD_FORM_ENGINE_NAME]: new FormControl(EMPTY_STRING),
+        [AggregationMappingFormservice.FIELD_FORM_ID]: new FormControl(EMPTY_STRING),
+      }),
+    });
+
+    formService = <any>{ ...mock(AggregationMappingFormservice), form };
+    formService.get = jest.fn().mockReturnValue(of(aggregationMappingStateResource));
+
+    await TestBed.configureTestingModule({
+      imports: [
+        ReactiveFormsModule,
+        AggregationMappingFormComponent,
+        MockComponent(TextEditorComponent),
+        MockComponent(ButtonComponent),
+        MockComponent(PlusIconComponent),
+        MockComponent(AdminSaveButtonComponent),
+        MockComponent(AdminCancelButtonComponent),
+        MockComponent(AggregationMappingFieldListFormComponent),
+      ],
+    })
+      .overrideComponent(AggregationMappingFormComponent, {
+        set: {
+          providers: [
+            {
+              provide: AggregationMappingFormservice,
+              useValue: formService,
+            },
+            {
+              provide: ADMIN_FORMSERVICE,
+              useValue: formService,
+            },
+          ],
+        },
+      })
+      .compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingFormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('component', () => {
+    it('should get aggregation mapping from form service', () => {
+      expect(formService.get).toHaveBeenCalled();
+    });
+
+    it('should initial values', () => {
+      expect(component.aggregationMappingStateResource$).toBeObservable(singleColdCompleted(aggregationMappingStateResource));
+    });
+  });
+
+  describe('template', () => {
+    describe('form engine input', () => {
+      it('should exists', () => {
+        fixture.detectChanges();
+
+        existsAsHtmlElement(fixture, formEngineNameInputTestId);
+      });
+    });
+
+    describe('form id input', () => {
+      it('should exists', () => {
+        fixture.detectChanges();
+
+        existsAsHtmlElement(fixture, formIdInputTestId);
+      });
+    });
+
+    describe('add mapping button', () => {
+      it('should exists', () => {
+        fixture.detectChanges();
+
+        existsAsHtmlElement(fixture, addMappingButton);
+      });
+
+      describe('output', () => {
+        describe('clickEmitter', () => {
+          it('should call formService', () => {
+            fixture.detectChanges();
+
+            dispatchEventFromFixture(fixture, addMappingButton, MockEvent.CLICK);
+
+            expect(formService.addMapping).toHaveBeenCalled();
+          });
+        });
+      });
+    });
+
+    describe('save button', () => {
+      it('should have link path', () => {
+        const comp: AdminSaveButtonComponent = getElementFromFixtureByType(fixture, AdminSaveButtonComponent);
+
+        expect(comp.successLinkPath).toEqual(ROUTES.AGGREGATION_MAPPING);
+      });
+    });
+
+    describe('cancel button', () => {
+      it('should have link path', () => {
+        const comp: AdminCancelButtonComponent = getElementFromFixtureByType(fixture, AdminCancelButtonComponent);
+
+        expect(comp.linkPath).toEqual(ROUTES.AGGREGATION_MAPPING);
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4865e5521255cca5a9d421d8fc7a42e4764d433a
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping-form.component.ts
@@ -0,0 +1,38 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ADMIN_FORMSERVICE, AdminCancelButtonComponent, AdminSaveButtonComponent, ROUTES } from '@admin-client/shared';
+import { StateResource } from '@alfa-client/tech-shared';
+import { AsyncPipe } from '@angular/common';
+import { Component, inject } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { SpinnerComponent, TextEditorComponent } from '@ods/component';
+import { ButtonComponent, PlusIconComponent } from '@ods/system';
+import { Observable } from 'rxjs';
+import { AggregationMappingFieldListFormComponent } from './aggregation-mapping-field-list-form/aggregation-mapping-field-list-form.component';
+import { AggregationMappingFormservice } from './aggregation-mapping.formservice';
+
+@Component({
+  selector: 'admin-aggregation-mapping-form',
+  templateUrl: './aggregation-mapping-form.component.html',
+  standalone: true,
+  imports: [
+    ButtonComponent,
+    PlusIconComponent,
+    ReactiveFormsModule,
+    AdminSaveButtonComponent,
+    AdminCancelButtonComponent,
+    AggregationMappingFieldListFormComponent,
+    SpinnerComponent,
+    AsyncPipe,
+    TextEditorComponent,
+  ],
+  providers: [{ provide: ADMIN_FORMSERVICE, useClass: AggregationMappingFormservice }],
+})
+export class AggregationMappingFormComponent {
+  public readonly formService = <AggregationMappingFormservice>inject(ADMIN_FORMSERVICE);
+
+  public readonly aggregationMappingStateResource$: Observable<StateResource<AggregationMappingResource>> =
+    this.formService.get();
+
+  public readonly AggregationMappingFormService = AggregationMappingFormservice;
+  public readonly Routes = ROUTES;
+}
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8aae7f66f74abf27206092e6df5e05413295bc9d
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.spec.ts
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+import { AggregationMappingResource, AggregationMappingService } from '@admin-client/reporting-shared';
+import { createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
+import { Mock, mock } from '@alfa-client/test-utils';
+import { TestBed } from '@angular/core/testing';
+import { FormArray, FormControl, FormGroup } from '@angular/forms';
+import { expect } from '@jest/globals';
+import { createAggregationMapping, createAggregationMappingResource } from 'libs/admin/reporting-shared/test/aggregation-mapping';
+import { omit } from 'lodash-es';
+import { of } from 'rxjs';
+import { singleColdCompleted } from '../../../../../../tech-shared/test/marbles';
+import { AggregationMappingFormservice } from './aggregation-mapping.formservice';
+
+describe('AggregationMappingFormService', () => {
+  let formService: AggregationMappingFormservice;
+
+  let service: Mock<AggregationMappingService>;
+
+  beforeEach(() => {
+    service = mock(AggregationMappingService);
+
+    TestBed.configureTestingModule({
+      providers: [AggregationMappingFormservice, { provide: AggregationMappingService, useValue: service }],
+    });
+
+    formService = TestBed.inject(AggregationMappingFormservice);
+  });
+
+  it('should create', () => {
+    expect(formService).toBeTruthy();
+  });
+
+  describe('on do submit', () => {
+    const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+
+    beforeEach(() => {
+      service.create.mockReturnValue(of(stateResource));
+      service.save.mockReturnValue(of(stateResource));
+    });
+
+    it('should create', () => {
+      formService.isPatch = jest.fn().mockReturnValue(false);
+      formService.form = <any>createAggregationMapping();
+
+      formService.submit();
+
+      expect(service.create).toHaveBeenCalledWith(formService.form.value);
+    });
+
+    it('should save', () => {
+      formService.isPatch = jest.fn().mockReturnValue(true);
+      formService.form = <any>createAggregationMapping();
+
+      formService.submit();
+
+      expect(service.save).toHaveBeenCalledWith(formService.form.value);
+    });
+  });
+
+  describe('add mapping', () => {
+    it('should add mapping control', () => {
+      formService.addMapping();
+
+      const mappingFormArray: FormArray = <FormArray>formService.form.controls[AggregationMappingFormservice.FIELD_MAPPINGS];
+      expect(mappingFormArray).toHaveLength(2);
+      expect(mappingFormArray.controls[0].value).toEqual({
+        [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: EMPTY_STRING,
+        [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: EMPTY_STRING,
+      });
+    });
+  });
+
+  describe('remove mapping', () => {
+    it('should remove mapping control', () => {
+      (<FormArray>formService.form.controls[AggregationMappingFormservice.FIELD_MAPPINGS]).push(
+        new FormGroup({
+          [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: new FormControl('controlToRemove'),
+          [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: new FormControl('controlToRemove'),
+        }),
+      );
+
+      formService.removeMapping(1);
+
+      const mappingFormArray: FormArray = <FormArray>formService.form.controls[AggregationMappingFormservice.FIELD_MAPPINGS];
+      expect(mappingFormArray).toHaveLength(1);
+      expect(mappingFormArray.controls[0].value).toEqual({
+        [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: EMPTY_STRING,
+        [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: EMPTY_STRING,
+      });
+    });
+  });
+
+  describe('get mappings', () => {
+    it('should return mappings as array', () => {
+      const mappings: FormArray = formService.mappings;
+
+      expect(mappings).toHaveLength(1);
+      expect(mappings.controls[0].value).toEqual({
+        [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: EMPTY_STRING,
+        [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: EMPTY_STRING,
+      });
+    });
+  });
+
+  describe('get', () => {
+    const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+
+    beforeEach(() => {
+      service.get.mockReturnValue(of(stateResource));
+      formService._patchForm = jest.fn();
+    });
+
+    it('should get by current url', () => {
+      formService.get();
+
+      expect(service.get).toHaveBeenCalled();
+    });
+
+    it('should patch form', () => {
+      formService.get().subscribe();
+
+      expect(formService._patchForm).toHaveBeenCalledWith(stateResource.resource);
+    });
+
+    it('should emit state resource', () => {
+      expect(formService.get()).toBeObservable(singleColdCompleted(stateResource));
+    });
+  });
+
+  describe('patch form', () => {
+    const aggregationMappingResource: AggregationMappingResource = createAggregationMappingResource();
+
+    it('should patch', () => {
+      formService._patchForm(aggregationMappingResource);
+
+      expect(formService.form.value).toEqual(omit(aggregationMappingResource, '_links'));
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..09c620e51c53f37b5db731b7488d9538c1bdee06
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-form-container/aggregation-mapping-form/aggregation-mapping.formservice.ts
@@ -0,0 +1,75 @@
+import { AggregationMappingResource, AggregationMappingService, FieldMapping } from '@admin-client/reporting-shared';
+import { AbstractFormService, EMPTY_STRING, isLoaded, StateResource } from '@alfa-client/tech-shared';
+import { inject, Injectable } from '@angular/core';
+import { FormArray, FormControl, FormGroup, UntypedFormGroup } from '@angular/forms';
+import { filter, Observable, tap } from 'rxjs';
+
+@Injectable()
+export class AggregationMappingFormservice extends AbstractFormService<AggregationMappingResource> {
+  public static readonly FIELD_NAME: string = 'name';
+  public static readonly FIELD_FORM_IDENTIFIER: string = 'formIdentifier';
+  public static readonly FIELD_FORM_ENGINE_NAME: string = 'formEngineName';
+  public static readonly FIELD_FORM_ID: string = 'formId';
+  public static readonly FIELD_MAPPINGS: string = 'mappings';
+  public static readonly FIELD_MAPPING_SOURCE_PATH = 'sourcePath';
+  public static readonly FIELD_MAPPING_TARGET_PATH = 'targetPath';
+
+  private readonly aggregationMappingService = inject(AggregationMappingService);
+
+  protected initForm(): UntypedFormGroup {
+    return this.formBuilder.group({
+      [AggregationMappingFormservice.FIELD_NAME]: new FormControl(EMPTY_STRING),
+      [AggregationMappingFormservice.FIELD_FORM_IDENTIFIER]: this.formBuilder.group({
+        [AggregationMappingFormservice.FIELD_FORM_ENGINE_NAME]: new FormControl(EMPTY_STRING),
+        [AggregationMappingFormservice.FIELD_FORM_ID]: new FormControl(EMPTY_STRING),
+      }),
+      [AggregationMappingFormservice.FIELD_MAPPINGS]: new FormArray([this.createArrayControl()]),
+    });
+  }
+
+  protected doSubmit(): Observable<StateResource<AggregationMappingResource>> {
+    if (this.isPatch()) {
+      return this.aggregationMappingService.save(this.getFormValue());
+    }
+    return this.aggregationMappingService.create(this.getFormValue());
+  }
+
+  protected getPathPrefix(): string {
+    return EMPTY_STRING;
+  }
+
+  public addMapping(): void {
+    this.mappings.push(this.createArrayControl());
+  }
+
+  private createArrayControl(sourcePath: string = EMPTY_STRING, targetPath: string = EMPTY_STRING): FormGroup {
+    return new FormGroup({
+      [AggregationMappingFormservice.FIELD_MAPPING_SOURCE_PATH]: new FormControl(sourcePath),
+      [AggregationMappingFormservice.FIELD_MAPPING_TARGET_PATH]: new FormControl(targetPath),
+    });
+  }
+
+  public removeMapping(index: number): void {
+    this.mappings.removeAt(index);
+  }
+
+  public get mappings(): FormArray {
+    return this.form.controls[AggregationMappingFormservice.FIELD_MAPPINGS] as FormArray;
+  }
+
+  public get(): Observable<StateResource<AggregationMappingResource>> {
+    return this.aggregationMappingService.get().pipe(
+      filter(isLoaded),
+      tap((stateResource: StateResource<AggregationMappingResource>) => this._patchForm(stateResource.resource)),
+    );
+  }
+
+  _patchForm(value: AggregationMappingResource): void {
+    this.patch(value);
+    const mappingsFormArray: FormArray = this.mappings;
+    mappingsFormArray.clear();
+    value.mappings.forEach((mapping: FieldMapping) =>
+      mappingsFormArray.push(this.createArrayControl(mapping.sourcePath, mapping.targetPath)),
+    );
+  }
+}
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.html
similarity index 73%
rename from alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html
rename to alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.html
index 1fd0441a39d71372d0c65fd84d0ce5386ac52fd2..a95bf3f3a42150b41b020b139387585105eeebbe 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.html
@@ -23,13 +23,12 @@
     unter der Lizenz sind dem Lizenztext zu entnehmen.
 
 -->
-<h1 class="heading-1" data-test-id="statistik-header-text">Statistik</h1>
-<div class="mt-4 w-fit">
-  <ods-routing-button
-    [linkPath]="ROUTES.STATISTIK_NEU"
-    text="Weitere Felder auswerten"
-    dataTestId="weitere-felder-auswerten-button"
-  ></ods-routing-button>
-</div>
+<h1 class="heading-1" data-test-id="aggregation-mapping-header-text">Statistik</h1>
+<ods-routing-button
+  class="my-4 w-fit"
+  [linkPath]="ROUTES.AGGREGATION_MAPPING_NEU"
+  text="Weitere Felder auswerten"
+  dataTestId="weitere-felder-auswerten-button"
+/>
 
-<ng-container *ngIf="listStateResource$ | async"></ng-container>
+<admin-aggregation-mapping-list [aggregationMappingListStateResource]="listStateResource$ | async" />
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.spec.ts
similarity index 55%
rename from alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts
rename to alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.spec.ts
index 3abca44e4a1aa07b3bceb119a0caf010014c02ed..7548de0fe94f05ec6678cd5dec6156e98bdd3286 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.spec.ts
@@ -23,17 +23,20 @@
  */
 import { AggregationMappingListResource, AggregationMappingService } from '@admin-client/reporting-shared';
 import { createStateResource, StateResource } from '@alfa-client/tech-shared';
-import { mock, Mock } from '@alfa-client/test-utils';
+import { expectComponentExistsInTemplate, getElementFromFixtureByType, mock, Mock } from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
 import { RoutingButtonComponent } from '@ods/component';
 import { singleCold } from 'libs/tech-shared/test/marbles';
 import { MockComponent } from 'ng-mocks';
+import { of } from 'rxjs';
 import { createAggregationMappingListResource } from '../../../../reporting-shared/test/aggregation-mapping';
-import { StatistikContainerComponent } from './statistik-container.component';
+import { AggregationMappingListContainerComponent } from './aggregation-mapping-list-container.component';
+import { AggregationMappingListComponent } from './aggregation-mapping-list/aggregation-mapping-list.component';
 
-describe('StatistikContainerComponent', () => {
-  let component: StatistikContainerComponent;
-  let fixture: ComponentFixture<StatistikContainerComponent>;
+describe('AggregationMappingListContainerComponent', () => {
+  let component: AggregationMappingListContainerComponent;
+  let fixture: ComponentFixture<AggregationMappingListContainerComponent>;
 
   let aggregationMappingService: Mock<AggregationMappingService>;
 
@@ -41,22 +44,20 @@ describe('StatistikContainerComponent', () => {
     aggregationMappingService = mock(AggregationMappingService);
 
     await TestBed.configureTestingModule({
-      imports: [StatistikContainerComponent],
-      declarations: [MockComponent(RoutingButtonComponent)],
-    })
-      .overrideComponent(StatistikContainerComponent, {
-        set: {
-          providers: [
-            {
-              provide: AggregationMappingService,
-              useValue: aggregationMappingService,
-            },
-          ],
+      imports: [
+        AggregationMappingListContainerComponent,
+        MockComponent(RoutingButtonComponent),
+        MockComponent(AggregationMappingListComponent),
+      ],
+      providers: [
+        {
+          provide: AggregationMappingService,
+          useValue: aggregationMappingService,
         },
-      })
-      .compileComponents();
+      ],
+    }).compileComponents();
 
-    fixture = TestBed.createComponent(StatistikContainerComponent);
+    fixture = TestBed.createComponent(AggregationMappingListContainerComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });
@@ -86,4 +87,32 @@ describe('StatistikContainerComponent', () => {
       expect(component.listStateResource$).toBeObservable(singleCold(stateResource));
     });
   });
+
+  describe('on destroy', () => {
+    it('should refresh aggregation mapping list', () => {
+      component.ngOnDestroy();
+
+      expect(aggregationMappingService.refreshList).toHaveBeenCalled();
+    });
+  });
+
+  describe('template', () => {
+    describe('aggregation mapping list', () => {
+      it('should exists', () => {
+        expectComponentExistsInTemplate(fixture, AggregationMappingListComponent);
+      });
+
+      it('should have inputs', () => {
+        const stateResource: StateResource<AggregationMappingListResource> = createStateResource(
+          createAggregationMappingListResource(),
+        );
+        component.listStateResource$ = of(stateResource);
+        fixture.detectChanges();
+
+        const comp: AggregationMappingListComponent = getElementFromFixtureByType(fixture, AggregationMappingListComponent);
+
+        expect(comp.aggregationMappingListStateResource).toEqual(stateResource);
+      });
+    });
+  });
 });
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.ts
similarity index 74%
rename from alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts
rename to alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.ts
index 90c3515896348b1c1d6ee77ba4f247428e79c4bf..622a61d5efc8a1466aa8af3c4e39e99f4a133369 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list-container.component.ts
@@ -25,18 +25,18 @@ import { AggregationMappingListResource, AggregationMappingService } from '@admi
 import { ROUTES } from '@admin-client/shared';
 import { StateResource } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
-import { Component, inject, OnInit } from '@angular/core';
+import { Component, inject, OnDestroy, OnInit } from '@angular/core';
 import { RoutingButtonComponent } from '@ods/component';
 import { Observable } from 'rxjs';
+import { AggregationMappingListComponent } from './aggregation-mapping-list/aggregation-mapping-list.component';
 
 @Component({
-  selector: 'admin-statistik-container',
-  templateUrl: './statistik-container.component.html',
+  selector: 'admin-aggregation-mapping-list-container',
+  templateUrl: './aggregation-mapping-list-container.component.html',
   standalone: true,
-  imports: [CommonModule, RoutingButtonComponent],
-  providers: [AggregationMappingService],
+  imports: [CommonModule, RoutingButtonComponent, AggregationMappingListComponent],
 })
-export class StatistikContainerComponent implements OnInit {
+export class AggregationMappingListContainerComponent implements OnInit, OnDestroy {
   private service = inject(AggregationMappingService);
 
   public listStateResource$: Observable<StateResource<AggregationMappingListResource>>;
@@ -46,4 +46,8 @@ export class StatistikContainerComponent implements OnInit {
   ngOnInit(): void {
     this.listStateResource$ = this.service.getList();
   }
+
+  ngOnDestroy(): void {
+    this.service.refreshList();
+  }
 }
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..9c4788d829ced3e228a1f73861c33dcd63f647df
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html
@@ -0,0 +1,15 @@
+<ods-list-item [path]="aggregationMapping | toResourceUri" [attr.data-test-id]="aggregationMapping.name | convertForDataTest">
+  <dl class="flex w-full">
+    <div class="flex-1">
+      <dt class="sr-only">Name</dt>
+      <dd class="font-semibold" data-test-class="list-item-name">{{ aggregationMapping.name }}</dd>
+    </div>
+    <div class="flex-wrap flex-1">
+      <dt class="sr-only">Formengine</dt>
+      <dd data-test-class="list-item-form-engine-name">{{ aggregationMapping.formIdentifier.formEngineName }}</dd>
+
+      <dt class="sr-only">Form ID</dt>
+      <dd data-test-class="list-item-form-id">{{ aggregationMapping.formIdentifier.formId }}</dd>
+    </div>
+  </dl>
+</ods-list-item>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a6061356da4568345c2ae526967310605b7d096
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts
@@ -0,0 +1,42 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ToResourceUriPipe } from '@alfa-client/tech-shared';
+import { getElementFromFixtureByType } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
+import { ListItemComponent } from '@ods/system';
+import { createAggregationMappingResource } from 'libs/admin/reporting-shared/test/aggregation-mapping';
+import { MockComponent } from 'ng-mocks';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item.component';
+
+describe('AggregationMappingListItemComponent', () => {
+  let component: AggregationMappingListItemComponent;
+  let fixture: ComponentFixture<AggregationMappingListItemComponent>;
+
+  const toResourceUriPipe: ToResourceUriPipe = new ToResourceUriPipe();
+  const aggregationMappingResource: AggregationMappingResource = createAggregationMappingResource();
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [AggregationMappingListItemComponent, ToResourceUriPipe, MockComponent(ListItemComponent)],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingListItemComponent);
+    component = fixture.componentInstance;
+    component.aggregationMapping = aggregationMappingResource;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('template', () => {
+    describe('list item', () => {
+      it('should have inputs', () => {
+        const comp: ListItemComponent = getElementFromFixtureByType(fixture, ListItemComponent);
+
+        expect(comp.path).toEqual(toResourceUriPipe.transform(aggregationMappingResource));
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6880096a779fa36e88b62dba199d952c7d34d9c7
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts
@@ -0,0 +1,15 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ConvertForDataTestPipe, ToResourceUriPipe } from '@alfa-client/tech-shared';
+import { Component, Input } from '@angular/core';
+import { ListItemComponent } from '@ods/system';
+
+@Component({
+  selector: 'admin-aggregation-mapping-list-item',
+  standalone: true,
+  templateUrl: './aggregation-mapping-list-item.component.html',
+  imports: [ConvertForDataTestPipe, ListItemComponent, ToResourceUriPipe],
+  styles: [':host {@apply block}'],
+})
+export class AggregationMappingListItemComponent {
+  @Input({ required: true }) aggregationMapping: AggregationMappingResource;
+}
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.html b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..fb306dc34f8a492b6540186720ce02b350c89e50
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.html
@@ -0,0 +1,7 @@
+<ods-spinner [stateResource]="aggregationMappingListStateResource">
+  <ods-list data-test-id="aggregation-mapping-list">
+    @for (aggregationMapping of (aggregationMappingListStateResource.resource | toEmbeddedResources: AggregationMappingListLinkRel.LIST); track $index) {
+      <admin-aggregation-mapping-list-item [aggregationMapping]="aggregationMapping" />
+    }
+  </ods-list>
+</ods-spinner>
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37718ad397856c004d3b8eba65080a19bfceb4cb
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts
@@ -0,0 +1,64 @@
+import { AggregationMappingListLinkRel, AggregationMappingListResource } from '@admin-client/reporting-shared';
+import { createStateResource, getEmbeddedResources, StateResource } from '@alfa-client/tech-shared';
+import { expectComponentExistsInTemplate, getElementFromFixtureByType } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
+import { SpinnerComponent } from '@ods/component';
+import { ListComponent } from '@ods/system';
+import { MockComponent } from 'ng-mocks';
+import { createAggregationMappingListResource } from '../../../../../reporting-shared/test/aggregation-mapping';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item/aggregation-mapping-list-item.component';
+import { AggregationMappingListComponent } from './aggregation-mapping-list.component';
+
+describe('AggregationMappingListComponent', () => {
+  let component: AggregationMappingListComponent;
+  let fixture: ComponentFixture<AggregationMappingListComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        AggregationMappingListComponent,
+        MockComponent(SpinnerComponent),
+        MockComponent(ListComponent),
+        MockComponent(AggregationMappingListItemComponent),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingListComponent);
+    component = fixture.componentInstance;
+    component.aggregationMappingListStateResource = createStateResource(createAggregationMappingListResource());
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('template', () => {
+    const aggregationMappingListStateResource: StateResource<AggregationMappingListResource> = createStateResource(
+      createAggregationMappingListResource(),
+    );
+
+    beforeEach(() => {
+      component.aggregationMappingListStateResource = aggregationMappingListStateResource;
+      fixture.detectChanges();
+    });
+
+    it('should have list', () => {
+      expectComponentExistsInTemplate(fixture, ListComponent);
+    });
+
+    it('should have spinner with state resource', () => {
+      const comp: SpinnerComponent = getElementFromFixtureByType(fixture, SpinnerComponent);
+
+      expect(comp.stateResource).toEqual(aggregationMappingListStateResource);
+    });
+
+    it('should have list item with mapping', () => {
+      const comp: AggregationMappingListItemComponent = getElementFromFixtureByType(fixture, AggregationMappingListItemComponent);
+      expect(comp.aggregationMapping).toEqual(
+        getEmbeddedResources(aggregationMappingListStateResource, AggregationMappingListLinkRel.LIST)[0],
+      );
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.ts b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac2c343c47d7ea879951589de6206c3b35d6ab77
--- /dev/null
+++ b/alfa-client/libs/admin/aggregation-mapping/src/lib/aggregation-mapping-list-container/aggregation-mapping-list/aggregation-mapping-list.component.ts
@@ -0,0 +1,19 @@
+import { AggregationMappingListResource } from '@admin-client/reporting-shared';
+import { StateResource, ToEmbeddedResourcesPipe } from '@alfa-client/tech-shared';
+import { Component, Input } from '@angular/core';
+import { SpinnerComponent } from '@ods/component';
+import { ListComponent } from '@ods/system';
+import { AggregationMappingListLinkRel } from 'libs/admin/reporting-shared/src/lib/aggregation-mapping.linkrel';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item/aggregation-mapping-list-item.component';
+
+@Component({
+  selector: 'admin-aggregation-mapping-list',
+  standalone: true,
+  templateUrl: './aggregation-mapping-list.component.html',
+  imports: [SpinnerComponent, ListComponent, AggregationMappingListItemComponent, ToEmbeddedResourcesPipe],
+})
+export class AggregationMappingListComponent {
+  @Input({ required: true }) aggregationMappingListStateResource: StateResource<AggregationMappingListResource>;
+
+  public readonly AggregationMappingListLinkRel = AggregationMappingListLinkRel;
+}
diff --git a/alfa-client/libs/admin/statistik/src/test-setup.ts b/alfa-client/libs/admin/aggregation-mapping/src/test-setup.ts
similarity index 100%
rename from alfa-client/libs/admin/statistik/src/test-setup.ts
rename to alfa-client/libs/admin/aggregation-mapping/src/test-setup.ts
diff --git a/alfa-client/libs/admin/statistik/tsconfig.json b/alfa-client/libs/admin/aggregation-mapping/tsconfig.json
similarity index 100%
rename from alfa-client/libs/admin/statistik/tsconfig.json
rename to alfa-client/libs/admin/aggregation-mapping/tsconfig.json
diff --git a/alfa-client/libs/admin/statistik/tsconfig.lib.json b/alfa-client/libs/admin/aggregation-mapping/tsconfig.lib.json
similarity index 100%
rename from alfa-client/libs/admin/statistik/tsconfig.lib.json
rename to alfa-client/libs/admin/aggregation-mapping/tsconfig.lib.json
diff --git a/alfa-client/libs/admin/statistik/tsconfig.spec.json b/alfa-client/libs/admin/aggregation-mapping/tsconfig.spec.json
similarity index 100%
rename from alfa-client/libs/admin/statistik/tsconfig.spec.json
rename to alfa-client/libs/admin/aggregation-mapping/tsconfig.spec.json
diff --git a/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.html b/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.html
index 0c1102a4c22c1603d4cc93f6baaaf5fcb2f94d40..c319836e14be9bee1d1314df5f293f09173a12b0 100644
--- a/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.html
+++ b/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.html
@@ -4,7 +4,7 @@
   </ods-nav-item>
 }
 @if (configurationStateResource.resource | hasLink: configurationLinkRel.AGGREGATION_MAPPINGS) {
-  <ods-nav-item data-test-id="statistik-navigation" caption="Statistik" path="/statistik">
+  <ods-nav-item data-test-id="statistik-navigation" caption="Statistik" [path]="'/' + ROUTES.AGGREGATION_MAPPING">
     <ods-statistic-icon icon />
   </ods-nav-item>
 }
diff --git a/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.ts b/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.ts
index 0c9cddefff0b13b58d8dd915f44352b78bd60f2b..c0845f3f13cf83a993f7cbe48f8021d42a2097e2 100644
--- a/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.ts
+++ b/alfa-client/libs/admin/configuration/src/lib/menu-container/menu/menu.component.ts
@@ -1,4 +1,5 @@
 import { ConfigurationLinkRel, ConfigurationResource } from '@admin-client/configuration-shared';
+import { ROUTES } from '@admin-client/shared';
 import { HasLinkPipe, StateResource } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
@@ -14,4 +15,5 @@ export class MenuComponent {
   @Input() configurationStateResource: StateResource<ConfigurationResource>;
 
   public readonly configurationLinkRel = ConfigurationLinkRel;
+  public readonly ROUTES = ROUTES;
 }
diff --git a/alfa-client/libs/admin/keycloak-shared/src/index.ts b/alfa-client/libs/admin/keycloak-shared/src/index.ts
index 4197ca2f1a4afbeacb65191a3fbe565c72987da5..06df4b1a3e6c7b8508c6d3a1e6ae4e55844020da 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/index.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/index.ts
@@ -1,3 +1,4 @@
+export * from './lib/keycloak-error.model';
 export * from './lib/keycloak-formservice';
 export * from './lib/keycloak-token.service';
 export * from './lib/keycloak.resource.service';
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.model.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87bdce2f2d937a86c9358ddd751b5b1c36c59cd4
--- /dev/null
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.model.ts
@@ -0,0 +1,31 @@
+export const KEYCLOAK_VALIDATION_ERROR_STATUS_CODES: number[] = [400, 409];
+
+export enum KeycloakFieldName {
+  USERNAME = 'username',
+  EMAIL = 'email',
+}
+
+export enum KeycloakErrorMessage {
+  USERNAME_IS_MISSING = 'User name is missing',
+  EMAIL_EXISTS = 'User exists with same email',
+  USERNAME_EXISTS = 'User exists with same username',
+  INVALID_LENGTH = 'error-invalid-length',
+  INVALID_EMAIL = 'error-invalid-email',
+}
+
+export interface ErrorRepresentation {
+  errorMessage?: KeycloakErrorMessage;
+  field?: KeycloakFieldName;
+  params?: any[];
+}
+
+export interface KeycloakHttpErrorResponse {
+  response: {
+    status: number;
+  };
+  responseData: KeycloakErrorResponseData;
+}
+
+export interface KeycloakErrorResponseData extends ErrorRepresentation {
+  errors?: ErrorRepresentation[];
+}
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..29c1aea25abe2703d3f1feecba2be25d470e8faf
--- /dev/null
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.spec.ts
@@ -0,0 +1,253 @@
+import { describe, expect, it } from '@jest/globals';
+import {
+  ErrorRepresentation,
+  KeycloakErrorMessage,
+  KeycloakErrorResponseData,
+  KeycloakFieldName,
+  KeycloakHttpErrorResponse,
+} from './keycloak-error.model';
+import {
+  canHandleKeycloakError,
+  extractErrorRepresentations,
+  isFieldErrorMessage,
+  isSingleErrorMessage,
+} from './keycloak-error.util';
+
+describe('keycloak error model', () => {
+  describe('extract error representations', () => {
+    it('should throw error', () => {
+      expect(() => extractErrorRepresentations({} as any)).toThrowError();
+    });
+
+    it('should return errors', () => {
+      const keycloakError: KeycloakHttpErrorResponse = {
+        response: {
+          status: 400,
+        },
+        responseData: {
+          errors: [
+            {
+              field: KeycloakFieldName.USERNAME,
+              errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+              params: ['username', 3, 255],
+            },
+            {
+              field: KeycloakFieldName.EMAIL,
+              errorMessage: KeycloakErrorMessage.INVALID_EMAIL,
+              params: ['email', 'f'],
+            },
+          ],
+        },
+      };
+
+      expect(extractErrorRepresentations(keycloakError)).toStrictEqual([
+        {
+          errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+          params: ['username', 3, 255],
+          field: KeycloakFieldName.USERNAME,
+        } as ErrorRepresentation,
+
+        {
+          errorMessage: KeycloakErrorMessage.INVALID_EMAIL,
+          params: ['email', 'f'],
+          field: KeycloakFieldName.EMAIL,
+        } as ErrorRepresentation,
+      ]);
+    });
+
+    it('should error response data', () => {
+      const keycloakError: KeycloakHttpErrorResponse = {
+        response: {
+          status: 400,
+        },
+        responseData: {
+          field: KeycloakFieldName.USERNAME,
+          errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+          params: ['username', 3, 255],
+        },
+      };
+
+      expect(extractErrorRepresentations(keycloakError)).toStrictEqual([
+        {
+          errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+          params: ['username', 3, 255],
+          field: KeycloakFieldName.USERNAME,
+        } as ErrorRepresentation,
+      ]);
+    });
+  });
+
+  describe('can handle keycloak error', () => {
+    const errorWithMessage: any = {
+      response: {
+        status: 400,
+      },
+      responseData: {
+        errorMessage: 'User name is missing',
+      },
+    };
+
+    const errorWithObject: any = {
+      response: {
+        status: 400,
+      },
+      responseData: {
+        field: 'username',
+        errorMessage: 'error-invalid-length',
+        params: ['username', 3, 255],
+      },
+    };
+
+    const errorWithArray: any = {
+      response: {
+        status: 400,
+      },
+      responseData: {
+        errors: [
+          {
+            field: 'username',
+            errorMessage: 'error-invalid-length',
+            params: ['username', 3, 255],
+          },
+          {
+            field: 'email',
+            errorMessage: 'error-invalid-email',
+            params: ['email', 'f'],
+          },
+        ],
+      },
+    };
+
+    const errorWithoutErrorMessage: any = {
+      response: {},
+      responseData: {},
+    };
+
+    const errorWithoutResponseData: any = {
+      response: {},
+      responseData: {},
+    };
+
+    const error500: any = {
+      response: {
+        status: 500,
+      },
+      responseData: {
+        errorMessage: 'internal error',
+      },
+    };
+
+    it('should return true for text error message', () => {
+      expect(canHandleKeycloakError(errorWithMessage)).toBe(true);
+    });
+
+    it('should return true for error object', () => {
+      expect(canHandleKeycloakError(errorWithObject)).toBe(true);
+    });
+
+    it('should return true for error array', () => {
+      expect(canHandleKeycloakError(errorWithArray)).toBe(true);
+    });
+
+    it('should return false for error without error message', () => {
+      expect(canHandleKeycloakError(errorWithoutErrorMessage)).toBe(false);
+    });
+
+    it('should return false for error without response data', () => {
+      expect(canHandleKeycloakError(errorWithoutResponseData)).toBe(false);
+    });
+
+    it('should return false for 500', () => {
+      expect(canHandleKeycloakError(error500)).toBe(false);
+    });
+
+    describe('evaluate validation errors', () => {});
+  });
+
+  describe('isSingleErrorMessage', () => {
+    it('should return false if contains field', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        field: KeycloakFieldName.USERNAME,
+      };
+
+      expect(isSingleErrorMessage(error)).toBe(false);
+    });
+
+    it('should return false if contains params', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        params: [],
+      };
+
+      expect(isSingleErrorMessage(error)).toBe(false);
+    });
+
+    it('should return false if contains errors', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        errors: [],
+      };
+
+      expect(isSingleErrorMessage(error)).toBe(false);
+    });
+
+    it('should return true', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+      };
+
+      expect(isSingleErrorMessage(error)).toBe(true);
+    });
+  });
+
+  describe('isFieldErrorMessage', () => {
+    it('should return false if error message missing', () => {
+      const error: KeycloakErrorResponseData = {
+        params: [],
+        field: KeycloakFieldName.USERNAME,
+      };
+
+      expect(isFieldErrorMessage(error)).toBe(false);
+    });
+
+    it('should return false if field missing', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        params: [],
+      };
+
+      expect(isFieldErrorMessage(error)).toBe(false);
+    });
+
+    it('should return false if params missing', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        field: KeycloakFieldName.USERNAME,
+      };
+
+      expect(isFieldErrorMessage(error)).toBe(false);
+    });
+
+    it('should return false if contains errors', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        field: KeycloakFieldName.USERNAME,
+        params: [],
+        errors: [],
+      };
+
+      expect(isFieldErrorMessage(error)).toBe(false);
+    });
+
+    it('should return true', () => {
+      const error: KeycloakErrorResponseData = {
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+        field: KeycloakFieldName.USERNAME,
+        params: [],
+      };
+
+      expect(isFieldErrorMessage(error)).toBe(true);
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f8cf30cada65b7045f7625bf301fa826bec902b
--- /dev/null
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-error.util.ts
@@ -0,0 +1,40 @@
+import {
+  ErrorRepresentation,
+  KEYCLOAK_VALIDATION_ERROR_STATUS_CODES,
+  KeycloakErrorResponseData,
+  KeycloakHttpErrorResponse,
+} from '@admin/keycloak-shared';
+import { isNotNil } from '@alfa-client/tech-shared';
+import { isNil } from 'lodash-es';
+
+export function extractErrorRepresentations(error: KeycloakHttpErrorResponse): ErrorRepresentation[] {
+  if (!canHandleKeycloakError(error)) {
+    throw new Error(`Cannot handle keycloak error: ${error}`);
+  }
+  const errorResponseData: KeycloakErrorResponseData = error.responseData;
+  return error.responseData.errors ?? [errorResponseData];
+}
+
+export function canHandleKeycloakError(err: KeycloakHttpErrorResponse): boolean {
+  if (!KEYCLOAK_VALIDATION_ERROR_STATUS_CODES.includes(err?.response?.status)) return false;
+  if (isNil(err?.responseData)) return false;
+
+  if (containsErrorMessage(err)) return true;
+  return containsErrorArray(err) && isNotNil(err.responseData.errors[0]?.errorMessage);
+}
+
+function containsErrorMessage(err: any): boolean {
+  return isNotNil(err?.responseData?.errorMessage);
+}
+
+function containsErrorArray(err: any): boolean {
+  return isNotNil(err?.responseData?.errors) && err.responseData.errors.length > 0;
+}
+
+export function isSingleErrorMessage(err: KeycloakErrorResponseData): boolean {
+  return isNotNil(err.errorMessage) && isNil(err.field) && isNil(err.params) && isNil(err.errors);
+}
+
+export function isFieldErrorMessage(err: KeycloakErrorResponseData): boolean {
+  return isNotNil(err.errorMessage) && isNotNil(err.field) && isNotNil(err.params) && isNil(err.errors);
+}
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts
index 6fe0a332ba918b071d6bddfb712e7b4e177a4891..038187decc6f18d2bac42ab33d94cdf3a751912a 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.spec.ts
@@ -21,20 +21,43 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { createEmptyStateResource, createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
+import { createEmptyStateResource, createStateResource, EMPTY_STRING, InvalidParam, setInvalidParamValidationError, StateResource, } from '@alfa-client/tech-shared';
 import { Injectable } from '@angular/core';
 import { TestBed } from '@angular/core/testing';
-import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, UntypedFormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, UrlSegment } from '@angular/router';
 import { faker } from '@faker-js/faker/.';
 import { createDummy, Dummy } from 'libs/tech-shared/test/dummy';
-import { singleCold, singleHot } from 'libs/tech-shared/test/marbles';
+import { singleCold, singleColdCompleted, singleHot } from 'libs/tech-shared/test/marbles';
 import { createSpy, mock, Mock } from 'libs/test-utils/src/lib/mocking';
-import { Observable, of } from 'rxjs';
+import { Observable, of, throwError } from 'rxjs';
 import { createUrlSegment } from '../../../../navigation-shared/test/navigation-test-factory';
 import { KeycloakFormService, PatchConfig } from './keycloak-formservice';
 
+import { expect } from '@jest/globals';
+import { ValidationMessageCode } from '../../../../tech-shared/src/lib/validation/tech.validation.messages';
+import { createInvalidParam } from '../../../../tech-shared/test/error';
+import { createErrorRepresentation, createKeycloakHttpErrorResponse } from '../test/keycloak';
 import * as FormUtil from './form.util';
+import { ErrorRepresentation, KeycloakErrorMessage, KeycloakFieldName, KeycloakHttpErrorResponse } from './keycloak-error.model';
+import { extractErrorRepresentations, isFieldErrorMessage, isSingleErrorMessage } from './keycloak-error.util';
+
+jest.mock('./keycloak-error.util', () => ({
+  ...jest.requireActual('./keycloak-error.util'),
+  extractErrorRepresentations: jest.fn(),
+  isSingleErrorMessage: jest.fn(),
+  isFieldErrorMessage: jest.fn(),
+}));
+
+jest.mock('@alfa-client/tech-shared', () => ({
+  ...jest.requireActual('@alfa-client/tech-shared'),
+  setInvalidParamValidationError: jest.fn(),
+}));
+
+const setInvalidParamValidationErrorMock: jest.Mock = setInvalidParamValidationError as jest.Mock;
+const extractErrorRepresentationsMock: jest.Mock = extractErrorRepresentations as jest.Mock;
+const isSingleErrorMessageMock: jest.Mock = isSingleErrorMessage as jest.Mock;
+const isFieldErrorMessageMock: jest.Mock = isFieldErrorMessage as jest.Mock;
 
 describe('KeycloakFormService', () => {
   let service: KeycloakFormService<Dummy>;
@@ -53,6 +76,10 @@ describe('KeycloakFormService', () => {
 
     initFormSpy = createSpy(TestKeycloakFormService, '_initForm').mockReturnValue(formGroup);
     evaluateRouteSpy = createSpy(TestKeycloakFormService, '_evaluateRoute');
+    extractErrorRepresentationsMock.mockClear();
+    isSingleErrorMessageMock.mockClear();
+    isFieldErrorMessageMock.mockClear();
+    setInvalidParamValidationErrorMock.mockClear();
 
     TestBed.configureTestingModule({
       providers: [
@@ -186,10 +213,29 @@ describe('KeycloakFormService', () => {
 
     beforeEach(() => {
       service._doSubmit = jest.fn().mockReturnValue(singleHot(dummyStateResource));
+      service._processInvalidForm = jest.fn().mockReturnValue(of(createEmptyStateResource()));
+      service._processResponseValidationErrors = jest.fn().mockReturnValue(of(createEmptyStateResource()));
+      service.form.setErrors(null);
+    });
+
+    describe('on client validation error', () => {
+      it('should process invalid form', () => {
+        service.form.setErrors({ dummy: 'dummy error' });
+
+        service.submit().subscribe();
+
+        expect(service._processInvalidForm).toHaveBeenCalled();
+      });
+
+      it('should return invalid form processing result on invalid form', () => {
+        service.form.setErrors({ dummy: 'dummy error' });
+
+        expect(service.submit()).toBeObservable(singleColdCompleted(createEmptyStateResource()));
+      });
     });
 
     it('should call do submit', () => {
-      service.submit();
+      service.submit().subscribe();
 
       expect(service._doSubmit).toHaveBeenCalled();
     });
@@ -199,6 +245,424 @@ describe('KeycloakFormService', () => {
 
       expect(submitResponse).toBeObservable(singleCold(dummyStateResource));
     });
+
+    describe('on server validation error', () => {
+      const keycloakHttpError: KeycloakHttpErrorResponse = createKeycloakHttpErrorResponse();
+
+      beforeEach(() => {
+        service._doSubmit = jest.fn().mockReturnValue(throwError(() => keycloakHttpError));
+      });
+
+      it('should process response validation errors', () => {
+        service.submit().subscribe();
+
+        expect(service._processResponseValidationErrors).toHaveBeenCalledWith(keycloakHttpError);
+      });
+
+      it('should return processing response validation errors result', () => {
+        expect(service.submit()).toBeObservable(singleColdCompleted(createEmptyStateResource()));
+      });
+    });
+  });
+
+  describe('process invalid form', () => {
+    beforeEach(() => {
+      service._showValidationErrorForAllInvalidControls = jest.fn();
+    });
+
+    it('should show validation errors on all invalid controls', () => {
+      service._processInvalidForm();
+
+      expect(service._showValidationErrorForAllInvalidControls).toHaveBeenCalledWith(service.form);
+    });
+
+    it('should return emit state resource', () => {
+      expect(service._processInvalidForm()).toBeObservable(singleColdCompleted(createEmptyStateResource()));
+    });
+  });
+
+  describe('process response validation errors', () => {
+    const keycloakHttpError: KeycloakHttpErrorResponse = createKeycloakHttpErrorResponse();
+
+    beforeEach(() => {
+      service._handleUnknownKeycloakError = jest.fn();
+      service._setValidationErrorsOnControls = jest.fn();
+      service._mapKeycloakErrorRepresentationsToInvalidParams = jest.fn();
+    });
+
+    it('should set validation errors on controls', () => {
+      const invalidParams: InvalidParam[] = [createInvalidParam()];
+      service._mapKeycloakErrorRepresentationsToInvalidParams = jest.fn().mockReturnValue(invalidParams);
+
+      service._processResponseValidationErrors(keycloakHttpError);
+
+      expect(service._setValidationErrorsOnControls).toHaveBeenCalledWith(invalidParams);
+    });
+
+    it('should map keycloak error representations to invalid params', () => {
+      const errorRepresentation: ErrorRepresentation = createErrorRepresentation();
+      extractErrorRepresentationsMock.mockReturnValue([errorRepresentation]);
+
+      service._processResponseValidationErrors(keycloakHttpError);
+
+      expect(service._mapKeycloakErrorRepresentationsToInvalidParams).toHaveBeenCalledWith([errorRepresentation]);
+    });
+
+    it('should extract error representations', () => {
+      service._processResponseValidationErrors(keycloakHttpError);
+
+      expect(extractErrorRepresentationsMock).toHaveBeenCalledWith(keycloakHttpError);
+    });
+
+    it('should emit empty state resource', () => {
+      expect(service._processResponseValidationErrors(keycloakHttpError)).toBeObservable(
+        singleColdCompleted(createEmptyStateResource()),
+      );
+    });
+
+    it('should handle unknown keycloak error', () => {
+      const error: Error = new Error('Fehler');
+      extractErrorRepresentationsMock.mockImplementation(() => {
+        throw error;
+      });
+
+      service._processResponseValidationErrors(keycloakHttpError);
+
+      expect(service._handleUnknownKeycloakError).toHaveBeenCalledWith(keycloakHttpError);
+    });
+
+    it('should emit empty state resource exception', () => {
+      const error: Error = new Error('Fehler');
+      extractErrorRepresentationsMock.mockImplementation(() => {
+        throw error;
+      });
+
+      expect(service._processResponseValidationErrors(keycloakHttpError)).toBeObservable(
+        singleColdCompleted(createEmptyStateResource()),
+      );
+    });
+  });
+
+  describe('show validation errors on all invalid controls', () => {
+    it('should update value and validity on invalid control', () => {
+      const control: AbstractControl = new FormControl();
+      control.setErrors({ dummy: 'error' });
+      const spy: jest.SpyInstance = jest.spyOn(control, 'updateValueAndValidity');
+
+      service._showValidationErrorForAllInvalidControls(control);
+
+      expect(spy).toHaveBeenCalled();
+    });
+
+    it('should update value and validity form invalid control from group', () => {
+      const control: AbstractControl = new FormControl();
+      control.setErrors({ dummy: 'error' });
+      const spy: jest.SpyInstance = jest.spyOn(service, '_showValidationErrorForAllInvalidControls');
+      const group: UntypedFormGroup = new FormGroup({
+        someControl: control,
+      });
+
+      service._showValidationErrorForAllInvalidControls(group);
+
+      expect(spy).toHaveBeenCalledWith(control);
+    });
+
+    it('should update value and validity on invalid control from group', () => {
+      const control: AbstractControl = new FormControl();
+      control.setErrors({ dummy: 'error' });
+      const spy: jest.SpyInstance = jest.spyOn(control, 'updateValueAndValidity');
+      const group: UntypedFormGroup = new FormGroup({
+        someControl: control,
+      });
+
+      service._showValidationErrorForAllInvalidControls(group);
+
+      expect(spy).toHaveBeenCalled();
+    });
+
+    it('should update value and validity form invalid control from array', () => {
+      const control: AbstractControl = new FormControl();
+      control.setErrors({ dummy: 'error' });
+      const spy: jest.SpyInstance = jest.spyOn(service, '_showValidationErrorForAllInvalidControls');
+      const array: FormArray = new FormArray([control]);
+
+      service._showValidationErrorForAllInvalidControls(array);
+
+      expect(spy).toHaveBeenCalledWith(control);
+    });
+
+    it('should update value and validity on invalid control from group', () => {
+      const control: AbstractControl = new FormControl();
+      control.setErrors({ dummy: 'error' });
+      const spy: jest.SpyInstance = jest.spyOn(control, 'updateValueAndValidity');
+      const array: FormArray = new FormArray([control]);
+
+      service._showValidationErrorForAllInvalidControls(array);
+
+      expect(spy).toHaveBeenCalled();
+    });
+  });
+
+  describe('set validation errors on controls', () => {
+    it('should set invalid param validation error', () => {
+      const invalidParam: InvalidParam = createInvalidParam();
+
+      service._setValidationErrorsOnControls([invalidParam]);
+
+      expect(setInvalidParamValidationErrorMock).toHaveBeenCalledWith(service.form, invalidParam);
+    });
+  });
+
+  describe('map keycloak error representations to invalid params', () => {
+    it('should map', () => {
+      service._mapKeycloakErrorRepresentationToInvalidParam = jest.fn();
+      const errorRepresentation: ErrorRepresentation = createErrorRepresentation();
+
+      service._mapKeycloakErrorRepresentationsToInvalidParams([errorRepresentation]);
+
+      expect(service._mapKeycloakErrorRepresentationToInvalidParam).toHaveBeenCalledWith(errorRepresentation);
+    });
+  });
+
+  describe('map keycloak error representation to invalid param', () => {
+    const controlName: string = faker.word.noun();
+    const invalidParam: InvalidParam = createInvalidParam();
+
+    beforeEach(() => {
+      service._evaluateControlName = jest.fn().mockReturnValue(controlName);
+      service._mapInvalidEmail = jest.fn().mockReturnValue(invalidParam);
+      service._mapInvalidLength = jest.fn().mockReturnValue(invalidParam);
+      service._mapEmailExists = jest.fn().mockReturnValue(invalidParam);
+      service._mapUsernameExists = jest.fn().mockReturnValue(invalidParam);
+      service._mapUserNameIsMissing = jest.fn().mockReturnValue(invalidParam);
+    });
+
+    it('should map invalid email', () => {
+      service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.INVALID_EMAIL,
+      });
+
+      expect(service._mapInvalidEmail).toHaveBeenCalledWith(controlName);
+    });
+
+    it('should return invalid param for invalid email', () => {
+      const param: InvalidParam = service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.INVALID_EMAIL,
+      });
+
+      expect(param).toEqual(invalidParam);
+    });
+
+    it('should map invalid length', () => {
+      const errorRepresentation: ErrorRepresentation = {
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+      };
+
+      service._mapKeycloakErrorRepresentationToInvalidParam(errorRepresentation);
+
+      expect(service._mapInvalidLength).toHaveBeenCalledWith(controlName, errorRepresentation);
+    });
+
+    it('should return invalid param for invalid length', () => {
+      const param: InvalidParam = service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.INVALID_LENGTH,
+      });
+
+      expect(param).toEqual(invalidParam);
+    });
+
+    it('should map email exists', () => {
+      service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.EMAIL_EXISTS,
+      });
+
+      expect(service._mapEmailExists).toHaveBeenCalledWith(controlName);
+    });
+
+    it('should return invalid param for email exists', () => {
+      const param: InvalidParam = service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.EMAIL_EXISTS,
+      });
+
+      expect(param).toEqual(invalidParam);
+    });
+
+    it('should map username exists', () => {
+      service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.USERNAME_EXISTS,
+      });
+
+      expect(service._mapUsernameExists).toHaveBeenCalledWith(controlName);
+    });
+
+    it('should return invalid param for username exists', () => {
+      const param: InvalidParam = service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.USERNAME_EXISTS,
+      });
+
+      expect(param).toEqual(invalidParam);
+    });
+
+    it('should map username is missing', () => {
+      service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+      });
+
+      expect(service._mapUserNameIsMissing).toHaveBeenCalledWith(controlName);
+    });
+
+    it('should return invalid param for username is missing', () => {
+      const param: InvalidParam = service._mapKeycloakErrorRepresentationToInvalidParam({
+        ...createErrorRepresentation(),
+        errorMessage: KeycloakErrorMessage.USERNAME_IS_MISSING,
+      });
+
+      expect(param).toEqual(invalidParam);
+    });
+
+    it('should throw error', () => {
+      expect(() =>
+        service._mapKeycloakErrorRepresentationToInvalidParam({
+          ...createErrorRepresentation(),
+          errorMessage: 'unknown' as KeycloakErrorMessage,
+        }),
+      ).toThrowError();
+    });
+  });
+
+  describe('map invalid email', () => {
+    it('should return invalid param', () => {
+      expect(service._mapInvalidEmail('control')).toEqual({
+        name: 'control',
+        reason: ValidationMessageCode.FIELD_INVALID,
+        constraintParameters: [],
+        value: null,
+      } as InvalidParam);
+    });
+  });
+
+  describe('map invalid length', () => {
+    it('should return invalid param', () => {
+      expect(service._mapInvalidLength('control', { params: ['email', 1, 2] })).toEqual({
+        name: 'control',
+        reason: ValidationMessageCode.FIELD_SIZE,
+        constraintParameters: [
+          { name: 'min', value: '1' },
+          { name: 'max', value: '2' },
+        ],
+        value: null,
+      } as InvalidParam);
+    });
+  });
+
+  describe('map email exists', () => {
+    it('should return invalid param', () => {
+      expect(service._mapEmailExists('control')).toEqual({
+        name: 'control',
+        reason: ValidationMessageCode.FIELD_VALUE_ALREADY_EXISTS,
+        constraintParameters: [{ name: 'value', value: 'Email-Adresse' }],
+        value: null,
+      } as InvalidParam);
+    });
+  });
+
+  describe('map username exists', () => {
+    it('should return invalid param', () => {
+      expect(service._mapUsernameExists('control')).toEqual({
+        name: 'control',
+        reason: ValidationMessageCode.FIELD_VALUE_ALREADY_EXISTS,
+        constraintParameters: [{ name: 'value', value: 'Benutzername' }],
+        value: null,
+      } as InvalidParam);
+    });
+  });
+
+  describe('map username is missing', () => {
+    it('should return invalid param', () => {
+      expect(service._mapUserNameIsMissing('control')).toEqual({
+        name: 'control',
+        reason: ValidationMessageCode.FIELD_EMPTY,
+        constraintParameters: [],
+        value: null,
+      } as InvalidParam);
+    });
+  });
+
+  describe('evaluate control name', () => {
+    const errorRepresentation: ErrorRepresentation = createErrorRepresentation();
+
+    beforeEach(() => {
+      service._mapKeycloakErrorMessageToControlName = jest.fn();
+      service._mapKeycloakFieldNameToControlName = jest.fn();
+    });
+
+    describe('on single error message', () => {
+      beforeEach(() => {
+        isSingleErrorMessageMock.mockReturnValue(true);
+        isFieldErrorMessageMock.mockReturnValue(false);
+      });
+
+      it('should check if single error message', () => {
+        service._evaluateControlName(errorRepresentation);
+
+        expect(isSingleErrorMessageMock).toHaveBeenCalledWith(errorRepresentation);
+      });
+
+      it('should map to control name', () => {
+        service._evaluateControlName(errorRepresentation);
+
+        expect(service._mapKeycloakErrorMessageToControlName).toHaveBeenCalledWith(errorRepresentation.errorMessage);
+      });
+
+      it('should return control name', () => {
+        const controlName: string = faker.word.noun();
+        service._mapKeycloakErrorMessageToControlName = jest.fn().mockReturnValue(controlName);
+
+        expect(service._evaluateControlName(errorRepresentation)).toEqual(controlName);
+      });
+    });
+
+    describe('on field error message', () => {
+      beforeEach(() => {
+        isSingleErrorMessageMock.mockReturnValue(false);
+        isFieldErrorMessageMock.mockReturnValue(true);
+      });
+
+      it('should check if field error message', () => {
+        service._evaluateControlName(errorRepresentation);
+
+        expect(isFieldErrorMessageMock).toHaveBeenCalledWith(errorRepresentation);
+      });
+
+      it('should map to control name', () => {
+        service._evaluateControlName(errorRepresentation);
+
+        expect(service._mapKeycloakFieldNameToControlName).toHaveBeenCalledWith(errorRepresentation.field);
+      });
+
+      it('should return control name', () => {
+        const controlName: string = faker.word.noun();
+        service._mapKeycloakFieldNameToControlName = jest.fn().mockReturnValue(controlName);
+
+        expect(service._evaluateControlName(errorRepresentation)).toEqual(controlName);
+      });
+    });
+
+    it('should throw error', () => {
+      isSingleErrorMessageMock.mockReturnValue(false);
+      isFieldErrorMessageMock.mockReturnValue(false);
+
+      expect(() => service._evaluateControlName(errorRepresentation)).toThrowError();
+    });
   });
 
   describe('patch', () => {
@@ -306,4 +770,16 @@ export class TestKeycloakFormService extends KeycloakFormService<Dummy> {
   _doDelete(): Observable<StateResource<Dummy>> {
     return TestKeycloakFormService.DELETE_OBSERVABLE();
   }
+
+  _mapKeycloakFieldNameToControlName(keycloakFieldName: KeycloakFieldName): string {
+    throw new Error('Method not implemented.');
+  }
+
+  _mapKeycloakErrorMessageToControlName(keycloakErrorMessage: KeycloakErrorMessage): string {
+    throw new Error('Method not implemented.');
+  }
+
+  _handleUnknownKeycloakError(error: KeycloakHttpErrorResponse): void {
+    throw new Error('Method not implemented.');
+  }
 }
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
index 49a2e6c84e3fdbf69b22ca324ee0100a34ae6763..e1856df17c3952c3fde88e15d205b6838b9083ff 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak-formservice.ts
@@ -21,13 +21,15 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { createEmptyStateResource, isLoaded, StateResource } from '@alfa-client/tech-shared';
+import { createEmptyStateResource, InvalidParam, isLoaded, setInvalidParamValidationError, StateResource, } from '@alfa-client/tech-shared';
 import { inject, Injectable } from '@angular/core';
-import { FormBuilder, FormGroup } from '@angular/forms';
+import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms';
 import { ActivatedRoute, UrlSegment } from '@angular/router';
-import { first, Observable, of, tap } from 'rxjs';
-
+import { catchError, first, Observable, of, tap } from 'rxjs';
+import { ValidationMessageCode } from '../../../../tech-shared/src/lib/validation/tech.validation.messages';
 import * as FormUtil from './form.util';
+import { ErrorRepresentation, KeycloakErrorMessage, KeycloakFieldName, KeycloakHttpErrorResponse } from './keycloak-error.model';
+import { extractErrorRepresentations, isFieldErrorMessage, isSingleErrorMessage } from './keycloak-error.util';
 
 @Injectable()
 export abstract class KeycloakFormService<T> {
@@ -70,9 +72,127 @@ export abstract class KeycloakFormService<T> {
   }
 
   public submit(): Observable<StateResource<T>> {
-    return this._doSubmit();
+    if (this.form.invalid) {
+      return this._processInvalidForm();
+    }
+    return this._doSubmit().pipe(
+      catchError((keycloakError: KeycloakHttpErrorResponse) => this._processResponseValidationErrors(keycloakError)),
+    );
+  }
+
+  _processInvalidForm(): Observable<StateResource<T>> {
+    this._showValidationErrorForAllInvalidControls(this.form);
+    return of(createEmptyStateResource<T>());
+  }
+
+  _processResponseValidationErrors(keycloakError: KeycloakHttpErrorResponse): Observable<StateResource<T>> {
+    try {
+      this._setValidationErrorsOnControls(
+        this._mapKeycloakErrorRepresentationsToInvalidParams(extractErrorRepresentations(keycloakError)),
+      );
+    } catch (error: any) {
+      console.error(error);
+      this._handleUnknownKeycloakError(keycloakError);
+    }
+    return of(createEmptyStateResource<T>());
+  }
+
+  _showValidationErrorForAllInvalidControls(control: AbstractControl): void {
+    if (control.invalid) control.updateValueAndValidity();
+    if (control instanceof FormGroup || control instanceof FormArray) {
+      Object.values(control.controls).forEach((control) => this._showValidationErrorForAllInvalidControls(control));
+    }
   }
 
+  _setValidationErrorsOnControls(invalidParams: InvalidParam[]): void {
+    invalidParams.forEach((invalidParam: InvalidParam) => {
+      setInvalidParamValidationError(this.form, invalidParam);
+    });
+  }
+
+  _mapKeycloakErrorRepresentationsToInvalidParams(errorRepresentations: ErrorRepresentation[]): InvalidParam[] {
+    return errorRepresentations.map((errorRepresentation: ErrorRepresentation) =>
+      this._mapKeycloakErrorRepresentationToInvalidParam(errorRepresentation),
+    );
+  }
+
+  _mapKeycloakErrorRepresentationToInvalidParam(errorRepresentation: ErrorRepresentation): InvalidParam {
+    const controlName: string = this._evaluateControlName(errorRepresentation);
+    switch (errorRepresentation.errorMessage) {
+      case KeycloakErrorMessage.INVALID_EMAIL:
+        return this._mapInvalidEmail(controlName);
+      case KeycloakErrorMessage.INVALID_LENGTH:
+        return this._mapInvalidLength(controlName, errorRepresentation);
+      case KeycloakErrorMessage.EMAIL_EXISTS:
+        return this._mapEmailExists(controlName);
+      case KeycloakErrorMessage.USERNAME_EXISTS:
+        return this._mapUsernameExists(controlName);
+      case KeycloakErrorMessage.USERNAME_IS_MISSING:
+        return this._mapUserNameIsMissing(controlName);
+      default:
+        throw new Error(`Unknown keycloak error message ${errorRepresentation.errorMessage}`);
+    }
+  }
+
+  _mapInvalidEmail(controlName: string): InvalidParam {
+    return { name: controlName, reason: ValidationMessageCode.FIELD_INVALID, constraintParameters: [], value: null };
+  }
+
+  _mapInvalidLength(controlName: string, errorRepresentation: ErrorRepresentation): InvalidParam {
+    return {
+      name: controlName,
+      reason: ValidationMessageCode.FIELD_SIZE,
+      constraintParameters: [
+        { name: 'min', value: `${errorRepresentation.params[1]}` },
+        { name: 'max', value: `${errorRepresentation.params[2]}` },
+      ],
+      value: null,
+    };
+  }
+
+  _mapEmailExists(controlName: string): InvalidParam {
+    return {
+      name: controlName,
+      reason: ValidationMessageCode.FIELD_VALUE_ALREADY_EXISTS,
+      constraintParameters: [{ name: 'value', value: 'Email-Adresse' }],
+      value: null,
+    };
+  }
+
+  _mapUsernameExists(controlName: string): InvalidParam {
+    return {
+      name: controlName,
+      reason: ValidationMessageCode.FIELD_VALUE_ALREADY_EXISTS,
+      constraintParameters: [{ name: 'value', value: 'Benutzername' }],
+      value: null,
+    };
+  }
+
+  _mapUserNameIsMissing(controlName: string): InvalidParam {
+    return {
+      name: controlName,
+      reason: ValidationMessageCode.FIELD_EMPTY,
+      constraintParameters: [],
+      value: null,
+    };
+  }
+
+  _evaluateControlName(errorRepresentation: ErrorRepresentation): string {
+    if (isSingleErrorMessage(errorRepresentation)) {
+      return this._mapKeycloakErrorMessageToControlName(errorRepresentation.errorMessage);
+    }
+    if (isFieldErrorMessage(errorRepresentation)) {
+      return this._mapKeycloakFieldNameToControlName(errorRepresentation.field as KeycloakFieldName);
+    }
+    throw new Error(`Cannot evaluate control name for error ${errorRepresentation}`);
+  }
+
+  abstract _mapKeycloakFieldNameToControlName(keycloakFieldName: KeycloakFieldName): string;
+
+  abstract _mapKeycloakErrorMessageToControlName(keycloakErrorMessage: KeycloakErrorMessage): string;
+
+  abstract _handleUnknownKeycloakError(error: KeycloakHttpErrorResponse): void;
+
   abstract _doSubmit(): Observable<StateResource<T>>;
 
   _patch(valueToPatch: T): void {
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts
index 3928e72cc9b20e768c905eedb69dc48c4ecfba3b..99426f1a908aa57d206ebc0d9880d1d20b08bcd8 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.spec.ts
@@ -21,13 +21,13 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { fakeAsync, TestBed, tick } from '@angular/core/testing';
 import { faker } from '@faker-js/faker';
 import { cold } from 'jest-marbles';
-import { StateResource, createEmptyStateResource, createStateResource } from 'libs/tech-shared/src/lib/resource/resource.util';
-import { Dummy, createDummy } from 'libs/tech-shared/test/dummy';
+import { createEmptyStateResource, createStateResource, StateResource } from 'libs/tech-shared/src/lib/resource/resource.util';
+import { createDummy, Dummy } from 'libs/tech-shared/test/dummy';
 import { singleCold } from 'libs/tech-shared/test/marbles';
-import { Observable, of } from 'rxjs';
+import { Observable, of, throwError } from 'rxjs';
 import { KeycloakResourceService } from './keycloak.resource.service';
 
 import * as ResourceUtil from 'libs/tech-shared/src/lib/resource/resource.util';
@@ -211,6 +211,13 @@ describe('KeycloakResourceService', () => {
       expect(service.refresh).toHaveBeenCalled();
     });
 
+    it('should rethrow error', () => {
+      const error = new Error('dummy');
+      service.refresh = jest.fn();
+
+      expect(service._refreshAfterEmit(throwError(() => error))).toBeObservable(cold('#', null, error));
+    });
+
     it('should throw error refresh on error', () => {
       service.refresh = jest.fn();
 
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts
index 2b6255aa9bba1e4ac1424de5a9b56282cb683349..9e8f1f7828f030c702b617414ff8f46ad0de23dc 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/keycloak.resource.service.ts
@@ -78,11 +78,14 @@ export abstract class KeycloakResourceService<T> {
     return action.pipe(
       first(),
       tap((): void => this.refresh()),
-      catchError((err: Error) => this.handleError(err)),
+      catchError((err) => {
+        this.refresh();
+        return this._handleError(err);
+      }),
     );
   }
 
-  handleError(err: Error): Observable<never> {
+  _handleError(err: Error): Observable<never> {
     this.refresh();
     return throwError(() => err);
   }
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts
index c0ef6b387b57946efc4336573809b55582b7912e..8508ad1aad9980fca4502a94f84a2591c77bf814 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.spec.ts
@@ -36,7 +36,6 @@ import { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/rol
 import { Users } from '@keycloak/keycloak-admin-client/lib/resources/users';
 import { cold } from 'jest-marbles';
 import { omit, times } from 'lodash-es';
-import { throwError } from 'rxjs';
 import { createUser } from '../../../user-shared/test/user';
 import { UserFormService } from '../../../user/src/lib/user-form/user.formservice';
 import { KeycloakDefaults } from './keycloak.model';
@@ -92,16 +91,6 @@ describe('UserRepository', () => {
         done();
       });
     });
-
-    it('should call handleError', fakeAsync(() => {
-      repository._handleCreateError = jest.fn();
-      kcAdminClient.users['create'] = jest.fn().mockReturnValue(throwError(() => new Error('error')));
-
-      repository.createInKeycloak(user).subscribe({ error: () => {} });
-      tick();
-
-      expect(repository._handleCreateError).toHaveBeenCalled();
-    }));
   });
 
   describe('saveInKeycloak', () => {
@@ -133,16 +122,6 @@ describe('UserRepository', () => {
         done();
       });
     });
-
-    it('should call handleError', fakeAsync(() => {
-      repository._handleSaveError = jest.fn();
-      kcAdminClient.users['update'] = jest.fn().mockReturnValue(throwError(() => new Error('error')));
-
-      repository.saveInKeycloak(user).subscribe({ error: () => {} });
-      tick();
-
-      expect(repository._handleSaveError).toHaveBeenCalled();
-    }));
   });
 
   describe('updateUserGroups', () => {
diff --git a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts
index c510b595f5b6157f20093328d23fa31bcc11e242..da9b890518ef470364c4a30953894c255a328e4b 100644
--- a/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts
+++ b/alfa-client/libs/admin/keycloak-shared/src/lib/user.repository.ts
@@ -31,11 +31,12 @@ import MappingsRepresentation from '@keycloak/keycloak-admin-client/lib/defs/map
 import { RequiredActionAlias } from '@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation';
 import { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation';
 import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';
-import * as _ from 'lodash-es';
 import { isNil, omit } from 'lodash-es';
-import { catchError, concatMap, forkJoin, from, map, mergeMap, Observable, tap, throwError } from 'rxjs';
+import { concatMap, forkJoin, from, map, mergeMap, Observable, tap, throwError } from 'rxjs';
 import { KeycloakDefaults } from './keycloak.model';
 
+import * as _ from 'lodash-es';
+
 @Injectable({
   providedIn: 'root',
 })
@@ -53,7 +54,6 @@ export class UserRepository {
       }),
       tap((response: { id: string }): void => this._sendActivationMail(response.id)),
       map((): User => user),
-      catchError((err: Error): Observable<never> => this._handleCreateError(err)),
     );
   }
 
@@ -65,7 +65,6 @@ export class UserRepository {
         await this._updateUserGroups(user.id, user.groupIds);
       }),
       map((): User => user),
-      catchError((err: Error): Observable<never> => this._handleSaveError(err)),
     );
   }
 
diff --git a/alfa-client/libs/admin/keycloak-shared/src/test/keycloak.ts b/alfa-client/libs/admin/keycloak-shared/src/test/keycloak.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32fdb0792d197579367961070e189301a786d222
--- /dev/null
+++ b/alfa-client/libs/admin/keycloak-shared/src/test/keycloak.ts
@@ -0,0 +1,23 @@
+import {
+  ErrorRepresentation,
+  KeycloakErrorMessage,
+  KeycloakFieldName,
+  KeycloakHttpErrorResponse,
+} from '../lib/keycloak-error.model';
+
+export function createErrorRepresentation(): ErrorRepresentation {
+  return {
+    field: KeycloakFieldName.USERNAME,
+    errorMessage: KeycloakErrorMessage.USERNAME_EXISTS,
+    params: [],
+  };
+}
+
+export function createKeycloakHttpErrorResponse(): KeycloakHttpErrorResponse {
+  return {
+    response: {
+      status: 400,
+    },
+    responseData: createErrorRepresentation(),
+  };
+}
diff --git a/alfa-client/libs/admin/reporting-shared/src/index.ts b/alfa-client/libs/admin/reporting-shared/src/index.ts
index 44ab78233d97deaf497e327e28f71c431ae6960e..17fd61a17a54ff36c1cdc0799dbc16036c853571 100644
--- a/alfa-client/libs/admin/reporting-shared/src/index.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/index.ts
@@ -1,4 +1,6 @@
+export * from './lib/aggregation-mapping-list-resource.service';
 export * from './lib/aggregation-mapping-resource.service';
+export * from './lib/aggregation-mapping.linkrel';
 export * from './lib/aggregation-mapping.model';
 export * from './lib/aggregation-mapping.provider';
 export * from './lib/aggregation-mapping.service';
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-list-resource.service.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-list-resource.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1378b74475c88940614acdf93cd68218ee9927ea
--- /dev/null
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-list-resource.service.ts
@@ -0,0 +1,26 @@
+import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared';
+import { ListResourceServiceConfig, ResourceListService, ResourceRepository } from '@alfa-client/tech-shared';
+import { AggregationMappingListLinkRel } from './aggregation-mapping.linkrel';
+import { AggregationMappingListResource, AggregationMappingResource } from './aggregation-mapping.model';
+
+export class AggregationMappingListResourceService extends ResourceListService<
+  ConfigurationResource,
+  AggregationMappingListResource,
+  AggregationMappingResource
+> {}
+
+export function createAggregationMappingListResourceService(
+  repository: ResourceRepository,
+  configurationService: ConfigurationService,
+) {
+  return new ResourceListService(buildConfig(configurationService), repository);
+}
+
+function buildConfig(configurationService: ConfigurationService): ListResourceServiceConfig<ConfigurationResource> {
+  return {
+    baseResource: configurationService.get(),
+    listLinkRel: ConfigurationLinkRel.AGGREGATION_MAPPINGS,
+    listResourceListLinkRel: AggregationMappingListLinkRel.LIST,
+    createLinkRel: AggregationMappingListLinkRel.SELF,
+  };
+}
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.spec.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9131bbba356d5498790a473a7429705a8f3b92e
--- /dev/null
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.spec.ts
@@ -0,0 +1,211 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ROUTES } from '@admin-client/shared';
+import { NavigationService, RouteData } from '@alfa-client/navigation-shared';
+import {
+  createEmptyStateResource,
+  createStateResource,
+  decodeUrlFromEmbedding,
+  ResourceRepository,
+  ResourceServiceConfig,
+  StateResource,
+} from '@alfa-client/tech-shared';
+import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
+import { UrlSegment } from '@angular/router';
+import { faker } from '@faker-js/faker';
+import { afterAll, expect } from '@jest/globals';
+import { ResourceUri } from '@ngxp/rest';
+import { Observable, of } from 'rxjs';
+import { createRouteData, createUrlSegment } from '../../../../navigation-shared/test/navigation-test-factory';
+import { singleColdCompleted } from '../../../../tech-shared/test/marbles';
+import { createAggregationMappingResource } from '../../test/aggregation-mapping';
+import * as self from './aggregation-mapping-resource.service';
+
+jest.mock('@alfa-client/tech-shared', () => ({
+  ...jest.requireActual('@alfa-client/tech-shared'),
+  decodeUrlFromEmbedding: jest.fn(),
+}));
+
+const decodeUrlFromEmbeddingMock: jest.Mock = decodeUrlFromEmbedding as jest.Mock;
+
+describe('AggregationMappingResourceService', () => {
+  let repository: Mock<ResourceRepository>;
+  let navigationService: Mock<NavigationService>;
+
+  beforeEach(() => {
+    repository = mock(ResourceRepository);
+    navigationService = mock(NavigationService);
+  });
+
+  describe('build config', () => {
+    const getResourceByNavigationRouteSpy: jest.SpyInstance = jest
+      .spyOn(self, '_getResourceByNavigationRoute')
+      .mockImplementation();
+
+    afterAll(() => {
+      getResourceByNavigationRouteSpy.mockRestore();
+    });
+
+    it('should get resource by navigation route', () => {
+      self._buildResourceServiceConfig(useFromMock(repository), useFromMock(navigationService));
+
+      expect(getResourceByNavigationRouteSpy).toHaveBeenCalled();
+    });
+
+    it('should have aggregation mapping static resource', () => {
+      const staticResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+      getResourceByNavigationRouteSpy.mockReturnValue(of(staticResource));
+
+      const config: ResourceServiceConfig<AggregationMappingResource> = self._buildResourceServiceConfig(
+        useFromMock(repository),
+        useFromMock(navigationService),
+      );
+
+      expect(config.resource).toBeObservable(singleColdCompleted(staticResource));
+    });
+  });
+
+  describe('get resource by navigation route', () => {
+    const routeData: RouteData = createRouteData();
+    const _isAggregationMappingEditUrl: jest.SpyInstance = jest.spyOn(self, '_isAggregationMappingEditUrl').mockImplementation();
+    const _getAggregationMappingResourceByRoute: jest.SpyInstance = jest
+      .spyOn(self, '_getAggregationMappingResourceByRoute')
+      .mockImplementation();
+
+    beforeEach(() => {
+      navigationService.getCurrentRouteData.mockReturnValue(of(routeData));
+    });
+
+    afterAll(() => {
+      _isAggregationMappingEditUrl.mockRestore();
+      _getAggregationMappingResourceByRoute.mockRestore();
+    });
+
+    it('should get current route data', () => {
+      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();
+
+      expect(navigationService.getCurrentRouteData).toHaveBeenCalled();
+    });
+
+    it('should check if url contains resource uri', () => {
+      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();
+
+      expect(_isAggregationMappingEditUrl).toHaveBeenCalled();
+    });
+
+    it('should get aggregation mapping resource by route', () => {
+      self._getResourceByNavigationRoute(useFromMock(repository), useFromMock(navigationService)).subscribe();
+
+      expect(_getAggregationMappingResourceByRoute).toHaveBeenCalled();
+    });
+
+    it('should return aggregation mapping by route', () => {
+      const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+      _getAggregationMappingResourceByRoute.mockReturnValue(of(stateResource));
+      _isAggregationMappingEditUrl.mockReturnValue(true);
+
+      const resourceByRoute$: Observable<StateResource<AggregationMappingResource>> = self._getResourceByNavigationRoute(
+        useFromMock(repository),
+        useFromMock(navigationService),
+      );
+
+      expect(resourceByRoute$).toBeObservable(singleColdCompleted(stateResource));
+    });
+
+    it('should return empty state resource', () => {
+      const stateResource: StateResource<AggregationMappingResource> = createEmptyStateResource();
+      _isAggregationMappingEditUrl.mockReturnValue(false);
+
+      const resourceByRoute$: Observable<StateResource<AggregationMappingResource>> = self._getResourceByNavigationRoute(
+        useFromMock(repository),
+        useFromMock(navigationService),
+      );
+
+      expect(resourceByRoute$).toBeObservable(singleColdCompleted(stateResource));
+    });
+  });
+
+  describe('is aggregation mapping edit url', () => {
+    it.each([0, 1, 3])('should return false of wrong number of segments = %d', (numberOfSegments: number) => {
+      const routeData: RouteData = {
+        ...createRouteData(),
+        urlSegments: Array(numberOfSegments).fill(createUrlSegment()),
+      };
+
+      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
+    });
+
+    it('should return false if first path is wrong', () => {
+      const routeData: RouteData = {
+        ...createRouteData(),
+        urlSegments: Array(2).fill(createUrlSegment()),
+      };
+
+      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
+    });
+
+    it('should return false if second path is wrong', () => {
+      const firstUrlSegment: UrlSegment = createUrlSegment();
+      firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
+      const secondUrlSegment: UrlSegment = createUrlSegment();
+      secondUrlSegment.path = 'neu';
+      const routeData: RouteData = {
+        ...createRouteData(),
+        urlSegments: [firstUrlSegment, secondUrlSegment],
+      };
+
+      expect(self._isAggregationMappingEditUrl(routeData)).toBe(false);
+    });
+
+    it('should return true', () => {
+      const firstUrlSegment: UrlSegment = createUrlSegment();
+      firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
+      const secondUrlSegment: UrlSegment = createUrlSegment();
+      secondUrlSegment.path = faker.internet.url();
+      const routeData: RouteData = {
+        ...createRouteData(),
+        urlSegments: [firstUrlSegment, secondUrlSegment],
+      };
+
+      expect(self._isAggregationMappingEditUrl(routeData)).toBe(true);
+    });
+  });
+
+  describe('get aggregation mapping resource by route', () => {
+    const firstUrlSegment: UrlSegment = createUrlSegment();
+    firstUrlSegment.path = ROUTES.AGGREGATION_MAPPING;
+    const secondUrlSegment: UrlSegment = createUrlSegment();
+    secondUrlSegment.path = faker.internet.url();
+    const routeData: RouteData = {
+      ...createRouteData(),
+      urlSegments: [firstUrlSegment, secondUrlSegment],
+    };
+    const uri: ResourceUri = faker.internet.url();
+    const resource: AggregationMappingResource = createAggregationMappingResource();
+
+    beforeEach(() => {
+      decodeUrlFromEmbeddingMock.mockReturnValue(uri);
+      repository.getResource.mockReturnValue(of(resource));
+    });
+
+    it('should decode url', () => {
+      self._getAggregationMappingResourceByRoute(useFromMock(repository), routeData);
+
+      expect(decodeUrlFromEmbeddingMock).toHaveBeenCalledWith(secondUrlSegment.path);
+    });
+
+    it('should get resource', () => {
+      self._getAggregationMappingResourceByRoute(useFromMock(repository), routeData);
+
+      expect(repository.getResource).toHaveBeenCalledWith(uri);
+    });
+
+    it('should return state resource', () => {
+      const stateResource$: Observable<StateResource<AggregationMappingResource>> = self._getAggregationMappingResourceByRoute(
+        useFromMock(repository),
+        routeData,
+      );
+
+      expect(stateResource$).toBeObservable(singleColdCompleted(createStateResource(resource)));
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.ts
index 8f96d053a24b8d10c4878d99dc75ce7fae2506a9..0bfa0689a8c24df11542605a2f33dfbe4a4401eb 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping-resource.service.ts
@@ -1,26 +1,70 @@
-import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared';
-import { ApiListResourceService, ListResourceServiceConfig, ResourceRepository } from '@alfa-client/tech-shared';
-import { AggregationMappingListLinkRel } from './aggregation-mapping.linkrel';
-import { AggregationMappingListResource, AggregationMappingResource } from './aggregation-mapping.model';
+import { ROUTES } from '@admin-client/shared';
+import { NavigationService, RouteData } from '@alfa-client/navigation-shared';
+import {
+  ApiResourceService,
+  createEmptyStateResource,
+  createStateResource,
+  decodeUrlFromEmbedding,
+  ResourceRepository,
+  ResourceServiceConfig,
+  StateResource,
+} from '@alfa-client/tech-shared';
+import { UrlSegment } from '@angular/router';
+import { iif, map, Observable, of, switchMap } from 'rxjs';
+import * as self from './aggregation-mapping-resource.service';
+import { AggregationMappingLinkRel } from './aggregation-mapping.linkrel';
+import { AggregationMappingResource } from './aggregation-mapping.model';
 
-export class AggregationMappingListResourceService extends ApiListResourceService<
-  ConfigurationResource,
-  AggregationMappingListResource,
+export class AggregationMappingResourceService extends ApiResourceService<
+  AggregationMappingResource,
   AggregationMappingResource
 > {}
 
 export function createAggregationMappingResourceService(
   repository: ResourceRepository,
-  configurationService: ConfigurationService,
-) {
-  return new ApiListResourceService(buildConfig(configurationService), repository);
+  navigationService: NavigationService,
+): AggregationMappingResourceService {
+  return new AggregationMappingResourceService(_buildResourceServiceConfig(repository, navigationService), repository);
 }
 
-function buildConfig(configurationService: ConfigurationService): ListResourceServiceConfig<ConfigurationResource> {
+export function _buildResourceServiceConfig(
+  repository: ResourceRepository,
+  navigationService: NavigationService,
+): ResourceServiceConfig<AggregationMappingResource> {
   return {
-    baseResource: configurationService.get(),
-    listResourceListLinkRel: AggregationMappingListLinkRel.LIST,
-    getLinkRel: ConfigurationLinkRel.SETTING,
-    create: { linkRel: AggregationMappingListLinkRel.SELF },
+    resource: self._getResourceByNavigationRoute(repository, navigationService),
+    getLinkRel: AggregationMappingLinkRel.SELF,
+    edit: { linkRel: AggregationMappingLinkRel.SELF },
+    delete: { linkRel: AggregationMappingLinkRel.SELF },
   };
 }
+
+export function _getResourceByNavigationRoute(
+  repository: ResourceRepository,
+  navigationService: NavigationService,
+): Observable<StateResource<AggregationMappingResource>> {
+  return navigationService
+    .getCurrentRouteData()
+    .pipe(
+      switchMap((route: RouteData) =>
+        iif(
+          () => self._isAggregationMappingEditUrl(route),
+          self._getAggregationMappingResourceByRoute(repository, route),
+          of(createEmptyStateResource<AggregationMappingResource>()),
+        ),
+      ),
+    );
+}
+
+export function _isAggregationMappingEditUrl(route: RouteData): boolean {
+  const urlSegments: UrlSegment[] = route.urlSegments;
+  return urlSegments.length === 2 && urlSegments[0].path === ROUTES.AGGREGATION_MAPPING && urlSegments[1].path !== 'neu';
+}
+
+export function _getAggregationMappingResourceByRoute(
+  repository: ResourceRepository,
+  route: RouteData,
+): Observable<StateResource<AggregationMappingResource>> {
+  const uri: string = decodeUrlFromEmbedding(route.urlSegments[1].path);
+  return repository.getResource(uri).pipe(map((resource: AggregationMappingResource) => createStateResource(resource)));
+}
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.linkrel.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.linkrel.ts
index a4a02bfd7087b105a9d0d2dd816aa2b180a4db9c..6aea64156f045b33fd70c20aa2427aaebb4ece32 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.linkrel.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.linkrel.ts
@@ -2,3 +2,7 @@ export enum AggregationMappingListLinkRel {
   LIST = 'aggregationMappings',
   SELF = 'self',
 }
+
+export enum AggregationMappingLinkRel {
+  SELF = 'self',
+}
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.model.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.model.ts
index 0db6cb4e397768b10781b3c34c9379fc866cec2b..5d1e7e3feec8053e7e77eedc54d709c46da15deb 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.model.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.model.ts
@@ -2,6 +2,7 @@ import { ListResource } from '@alfa-client/tech-shared';
 import { Resource } from '@ngxp/rest';
 
 export interface AggregationMapping {
+  name: string;
   formIdentifier: FormIdentifier;
   mappings: FieldMapping[];
 }
@@ -17,4 +18,5 @@ export interface FieldMapping {
 }
 
 export interface AggregationMappingResource extends AggregationMapping, Resource {}
+
 export interface AggregationMappingListResource extends ListResource {}
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.provider.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.provider.ts
index 888d7154e81dbfe53fb7476e4de8835463de6d25..76d294de177ea685a3e9cbd849e5a032f5423101 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.provider.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.provider.ts
@@ -1,8 +1,13 @@
+import { NavigationService } from '@alfa-client/navigation-shared';
 import { ResourceRepository } from '@alfa-client/tech-shared';
 import { Provider } from '@angular/core';
 import { ConfigurationService } from 'libs/admin/configuration-shared/src/lib/configuration.service';
 import {
   AggregationMappingListResourceService,
+  createAggregationMappingListResourceService,
+} from './aggregation-mapping-list-resource.service';
+import {
+  AggregationMappingResourceService,
   createAggregationMappingResourceService,
 } from './aggregation-mapping-resource.service';
 import { AggregationMappingService } from './aggregation-mapping.service';
@@ -10,8 +15,13 @@ import { AggregationMappingService } from './aggregation-mapping.service';
 export const AggregationMappingProvider: Provider[] = [
   {
     provide: AggregationMappingListResourceService,
-    useFactory: createAggregationMappingResourceService,
+    useFactory: createAggregationMappingListResourceService,
     deps: [ResourceRepository, ConfigurationService],
   },
+  {
+    provide: AggregationMappingResourceService,
+    useFactory: createAggregationMappingResourceService,
+    deps: [ResourceRepository, NavigationService],
+  },
   AggregationMappingService,
 ];
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.spec.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.spec.ts
index ecf4387ceee6a3dbdd6775f8ef4fa3a306144002..bca88ee563cf743b74f44776c11ab3e848f73241 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.spec.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.spec.ts
@@ -1,26 +1,34 @@
-import { StateResource, createStateResource } from '@alfa-client/tech-shared';
+import { createStateResource, StateResource } from '@alfa-client/tech-shared';
 import { Mock, mock } from '@alfa-client/test-utils';
 import { TestBed } from '@angular/core/testing';
-import { singleCold } from 'libs/tech-shared/test/marbles';
-import { Observable } from 'rxjs';
+import { expect } from '@jest/globals';
+import { singleCold, singleColdCompleted } from 'libs/tech-shared/test/marbles';
+import { Observable, of } from 'rxjs';
 import {
   createAggregationMapping,
   createAggregationMappingListResource,
   createAggregationMappingResource,
 } from '../../test/aggregation-mapping';
-import { AggregationMappingListResourceService } from './aggregation-mapping-resource.service';
+import { AggregationMappingListResourceService } from './aggregation-mapping-list-resource.service';
+import { AggregationMappingResourceService } from './aggregation-mapping-resource.service';
 import { AggregationMapping, AggregationMappingListResource, AggregationMappingResource } from './aggregation-mapping.model';
 import { AggregationMappingService } from './aggregation-mapping.service';
 
 describe('AggregationMappingService', () => {
   let service: AggregationMappingService;
   let listResourceService: Mock<AggregationMappingListResourceService>;
+  let resourceService: Mock<AggregationMappingResourceService>;
 
   beforeEach(() => {
     listResourceService = mock(AggregationMappingListResourceService);
+    resourceService = mock(AggregationMappingResourceService);
 
     TestBed.configureTestingModule({
-      providers: [AggregationMappingService, { provide: AggregationMappingListResourceService, useValue: listResourceService }],
+      providers: [
+        AggregationMappingService,
+        { provide: AggregationMappingListResourceService, useValue: listResourceService },
+        { provide: AggregationMappingResourceService, useValue: resourceService },
+      ],
     });
 
     service = TestBed.inject(AggregationMappingService);
@@ -76,4 +84,54 @@ describe('AggregationMappingService', () => {
       expect(loadedAggregationMappingResource).toBeObservable(singleCold(aggregationMappingStateResource));
     });
   });
+
+  describe('refresh list', () => {
+    it('should call list resource service', () => {
+      listResourceService.refresh = jest.fn();
+
+      service.refreshList();
+
+      expect(listResourceService.refresh).toHaveBeenCalled();
+    });
+  });
+
+  describe('get', () => {
+    const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+
+    beforeEach(() => {
+      resourceService.get = jest.fn().mockReturnValue(of(stateResource));
+    });
+
+    it('should call resource service', () => {
+      service.get();
+
+      expect(resourceService.get).toHaveBeenCalled();
+    });
+
+    it('should emit resource', () => {
+      const stateResource$: Observable<StateResource<AggregationMappingResource>> = service.get();
+
+      expect(stateResource$).toBeObservable(singleColdCompleted(stateResource));
+    });
+  });
+
+  describe('save', () => {
+    const aggregationMapping: AggregationMapping = createAggregationMapping();
+    const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
+    beforeEach(() => {
+      resourceService.save = jest.fn().mockReturnValue(of(stateResource));
+    });
+
+    it('should call resource service', () => {
+      service.save(aggregationMapping);
+
+      expect(resourceService.save).toHaveBeenCalledWith(aggregationMapping);
+    });
+
+    it('should emit saved state resource', () => {
+      const saved$: Observable<StateResource<AggregationMappingResource>> = service.save(aggregationMapping);
+
+      expect(saved$).toBeObservable(singleColdCompleted(stateResource));
+    });
+  });
 });
diff --git a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.ts b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.ts
index 20a8ce6c1ccda133323bac99a671d887e77afb8e..96c72825e5794c7affdb1019d5febd4624b885a7 100644
--- a/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.ts
+++ b/alfa-client/libs/admin/reporting-shared/src/lib/aggregation-mapping.service.ts
@@ -1,12 +1,14 @@
 import { StateResource } from '@alfa-client/tech-shared';
-import { Injectable, inject } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { Observable } from 'rxjs';
-import { AggregationMappingListResourceService } from './aggregation-mapping-resource.service';
+import { AggregationMappingListResourceService } from './aggregation-mapping-list-resource.service';
+import { AggregationMappingResourceService } from './aggregation-mapping-resource.service';
 import { AggregationMapping, AggregationMappingListResource, AggregationMappingResource } from './aggregation-mapping.model';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class AggregationMappingService {
-  readonly listService = inject(AggregationMappingListResourceService);
+  private readonly listService = inject(AggregationMappingListResourceService);
+  private readonly resourceService = inject(AggregationMappingResourceService);
 
   public getList(): Observable<StateResource<AggregationMappingListResource>> {
     return this.listService.getList();
@@ -15,4 +17,16 @@ export class AggregationMappingService {
   public create(toCreate: AggregationMapping): Observable<StateResource<AggregationMappingResource>> {
     return this.listService.create(toCreate);
   }
+
+  public refreshList(): void {
+    this.listService.refresh();
+  }
+
+  public get(): Observable<StateResource<AggregationMappingResource>> {
+    return this.resourceService.get();
+  }
+
+  public save(aggregationMapping: AggregationMapping): Observable<StateResource<AggregationMappingResource>> {
+    return this.resourceService.save(aggregationMapping);
+  }
 }
diff --git a/alfa-client/libs/admin/reporting-shared/test/aggregation-mapping.ts b/alfa-client/libs/admin/reporting-shared/test/aggregation-mapping.ts
index acbcc9fa75722396e4a410ea77e9157a2a8ce8fc..3506ea96a8c9387cf6258756bc880e63c44fe535 100644
--- a/alfa-client/libs/admin/reporting-shared/test/aggregation-mapping.ts
+++ b/alfa-client/libs/admin/reporting-shared/test/aggregation-mapping.ts
@@ -1,12 +1,13 @@
+import { AggregationMappingListLinkRel } from '@admin-client/reporting-shared';
 import { faker } from '@faker-js/faker';
 import { times } from 'lodash-es';
 import { LinkRelationName } from '../../../tech-shared/src';
 import { toResource } from '../../../tech-shared/test/resource';
 import { AggregationMapping, AggregationMappingListResource, AggregationMappingResource } from '../src';
-import { AggregationMappingListLinkRel } from '../src/lib/aggregation-mapping.linkrel';
 
 export function createAggregationMapping(): AggregationMapping {
   return {
+    name: faker.word.noun(),
     formIdentifier: {
       formEngineName: faker.lorem.word(),
       formId: faker.string.uuid(),
diff --git a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts
index baee873bfd7c0db07a9295de75844d638edaead5..d6ff6dfa2d7082c4e314dcd7a7d05bb919aeb8fa 100644
--- a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts
+++ b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.spec.ts
@@ -1,20 +1,32 @@
 import { ADMIN_FORMSERVICE } from '@admin-client/shared';
-import { AbstractFormService, createStateResource, StateResource } from '@alfa-client/tech-shared';
-import { dispatchEventFromFixture, getMockComponent, Mock, MockEvent } from '@alfa-client/test-utils';
+import { NavigationService } from '@alfa-client/navigation-shared';
+import { AbstractFormService, createStateResource, isLoaded, StateResource } from '@alfa-client/tech-shared';
+import { dispatchEventFromFixture, getMockComponent, mock, Mock, MockEvent } from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { faker } from '@faker-js/faker/.';
+import { expect } from '@jest/globals';
 import { Resource } from '@ngxp/rest';
 import { ButtonWithSpinnerComponent } from '@ods/component';
 import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
-import { singleCold } from 'libs/tech-shared/test/marbles';
+import { singleColdCompleted } from 'libs/tech-shared/test/marbles';
 import { createDummyResource } from 'libs/tech-shared/test/resource';
 import { MockComponent } from 'ng-mocks';
 import { of } from 'rxjs';
 import { AdminSaveButtonComponent } from './admin-save-button.component';
 
+jest.mock('@alfa-client/tech-shared', () => {
+  return {
+    ...jest.requireActual('@alfa-client/tech-shared'),
+    isLoaded: jest.fn(),
+  };
+});
+const isLoadedMock: jest.Mock = isLoaded as jest.Mock;
+
 describe('AdminSaveButtonComponent', () => {
   let component: AdminSaveButtonComponent;
   let fixture: ComponentFixture<AdminSaveButtonComponent>;
 
+  let navigationService: Mock<NavigationService>;
   let formService: Mock<AbstractFormService<Resource>>;
 
   const saveButton: string = getDataTestIdOf('save');
@@ -22,7 +34,8 @@ describe('AdminSaveButtonComponent', () => {
   const stateResource: StateResource<Resource> = createStateResource(createDummyResource());
 
   beforeEach(async () => {
-    formService = <any>{ submit: jest.fn().mockReturnValue(singleCold(stateResource)) };
+    formService = <any>{ submit: jest.fn().mockReturnValue(of(stateResource)) };
+    navigationService = mock(NavigationService);
 
     await TestBed.configureTestingModule({
       imports: [AdminSaveButtonComponent],
@@ -32,6 +45,10 @@ describe('AdminSaveButtonComponent', () => {
           provide: ADMIN_FORMSERVICE,
           useValue: formService,
         },
+        {
+          provide: NavigationService,
+          useValue: navigationService,
+        },
       ],
     }).compileComponents();
 
@@ -44,17 +61,50 @@ describe('AdminSaveButtonComponent', () => {
     expect(component).toBeTruthy();
   });
 
-  describe('on submit', () => {
-    it('should call formService', () => {
-      dispatchEventFromFixture(fixture, saveButton, MockEvent.CLICK);
+  describe('component', () => {
+    describe('on successful form submission', () => {
+      it('should navigate to success link path', () => {
+        const successLinkPath: string = faker.internet.url();
+        component.successLinkPath = successLinkPath;
+        isLoadedMock.mockReturnValue(true);
+
+        component._navigateOnSuccessfulSubmission(createStateResource(createDummyResource()));
+
+        expect(navigationService.navigate).toHaveBeenCalledWith(successLinkPath);
+      });
+
+      it('should NOT navigate', () => {
+        isLoadedMock.mockReturnValue(false);
 
-      expect(formService.submit).toHaveBeenCalled();
+        component._navigateOnSuccessfulSubmission(createStateResource(createDummyResource()));
+
+        expect(navigationService.navigate).not.toHaveBeenCalled();
+      });
     });
 
-    it('should assign state resource', () => {
-      dispatchEventFromFixture(fixture, saveButton, MockEvent.CLICK);
+    describe('on submit', () => {
+      beforeEach(() => {
+        component._navigateOnSuccessfulSubmission = jest.fn();
+      });
+
+      it('should call formService', () => {
+        dispatchEventFromFixture(fixture, saveButton, MockEvent.CLICK);
+
+        expect(formService.submit).toHaveBeenCalled();
+      });
+
+      it('should assign state resource', () => {
+        dispatchEventFromFixture(fixture, saveButton, MockEvent.CLICK);
+
+        expect(component.stateResource$).toBeObservable(singleColdCompleted(stateResource));
+      });
+
+      it('should navigate on successful submit', () => {
+        dispatchEventFromFixture(fixture, saveButton, MockEvent.CLICK);
+        component.stateResource$.subscribe();
 
-      expect(component.stateResource$).toBeObservable(singleCold(stateResource));
+        expect(component._navigateOnSuccessfulSubmission).toHaveBeenCalledWith(stateResource);
+      });
     });
   });
 
diff --git a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.ts b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.ts
index 60abd08c7f311ecabe6d56ecc47fbfcd16412877..598d056ea60aeb0edc17497fd923982ed4533474 100644
--- a/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.ts
+++ b/alfa-client/libs/admin/shared/src/lib/admin-save-button/admin-save-button.component.ts
@@ -1,10 +1,11 @@
 import { ADMIN_FORMSERVICE } from '@admin-client/shared';
-import { AbstractFormService, createEmptyStateResource, StateResource } from '@alfa-client/tech-shared';
+import { NavigationService } from '@alfa-client/navigation-shared';
+import { AbstractFormService, createEmptyStateResource, isLoaded, StateResource } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
-import { Component, inject } from '@angular/core';
+import { Component, inject, Input } from '@angular/core';
 import { Resource } from '@ngxp/rest';
 import { ButtonWithSpinnerComponent } from '@ods/component';
-import { Observable, of } from 'rxjs';
+import { Observable, of, tap } from 'rxjs';
 
 @Component({
   selector: 'admin-save-button',
@@ -13,11 +14,24 @@ import { Observable, of } from 'rxjs';
   templateUrl: './admin-save-button.component.html',
 })
 export class AdminSaveButtonComponent {
-  private formService: AbstractFormService<Resource> = inject(ADMIN_FORMSERVICE);
+  @Input() successLinkPath: string;
+
+  private readonly formService: AbstractFormService<Resource> = inject(ADMIN_FORMSERVICE);
+  private readonly navigationService: NavigationService = inject(NavigationService);
 
   public stateResource$: Observable<StateResource<Resource>> = of(createEmptyStateResource<Resource>());
 
   public submit(): void {
-    this.stateResource$ = this.formService.submit();
+    this.stateResource$ = this.formService.submit().pipe(
+      tap((stateResource: StateResource<Resource>) => {
+        this._navigateOnSuccessfulSubmission(stateResource);
+      }),
+    );
+  }
+
+  _navigateOnSuccessfulSubmission(stateResource: StateResource<Resource>) {
+    if (isLoaded(stateResource)) {
+      this.navigationService.navigate(this.successLinkPath);
+    }
   }
 }
diff --git a/alfa-client/libs/admin/shared/src/lib/routes.ts b/alfa-client/libs/admin/shared/src/lib/routes.ts
index 090a9f3a7d82d6c3489bfa5c542b31592c177e06..9f1496843e54d2c7ff25e132f0b68b4edee6469d 100644
--- a/alfa-client/libs/admin/shared/src/lib/routes.ts
+++ b/alfa-client/libs/admin/shared/src/lib/routes.ts
@@ -28,6 +28,7 @@ export enum ROUTES {
   BENUTZER_ID = 'benutzer/:userid',
   ORGANISATIONSEINHEITEN = 'organisationseinheiten',
   UNAVAILABLE = 'unavailable',
-  STATISTIK = 'statistik',
-  STATISTIK_NEU = 'statistik/neu',
+  AGGREGATION_MAPPING = 'auswertungen',
+  AGGREGATION_MAPPING_NEU = 'auswertungen/neu',
+  AGGREGATION_MAPPING_ID = 'auswertungen/:aggregationMappingId',
 }
diff --git a/alfa-client/libs/admin/statistik/README.md b/alfa-client/libs/admin/statistik/README.md
deleted file mode 100644
index ad651ba5c6b2577aae0af1c1ac56ca839db65022..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# statistik
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test statistik` to execute the unit tests.
diff --git a/alfa-client/libs/admin/statistik/src/index.ts b/alfa-client/libs/admin/statistik/src/index.ts
deleted file mode 100644
index 157271feb3bf8625864f12faec9623307c3b7690..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './lib/statistik-container/statistik-container.component';
-export * from './lib/statistik-fields-form/admin-statistik-fields-form.component';
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html
deleted file mode 100644
index ce24e7184c9f832dec08b205237303ce3cd25af7..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<h2 class="heading-2" data-test-id="statistik-fields-form-header-text">Felder zur Auswertung hinzufügen</h2>
-
-<div class="flex max-w-4xl flex-col gap-4">
-  <form class="form flex-col" [formGroup]="formService.form" class="flex flex-col gap-4">
-    <div [formGroupName]="StatistikFieldsFormService.FIELD_FORM_IDENTIFIER" class="flex flex-col gap-4">
-      <ods-text-editor
-        [formControlName]="StatistikFieldsFormService.FIELD_FORM_ENGINE_NAME"
-        label="Formengine"
-        placeholder="Tragen Sie hier die Formengine des Formulars ein."
-        data-test-id="form-engine-name"
-        dataTestId="form-engine-name"
-      ></ods-text-editor>
-      <ods-text-editor
-        [formControlName]="StatistikFieldsFormService.FIELD_FORM_ID"
-        label="FormID"
-        placeholder="Tragen Sie hier die FormID des Formulars ein."
-        data-test-id="form-id"
-        dataTestId="form-id"
-      ></ods-text-editor>
-    </div>
-    <statistik-fields-form-mapping />
-  </form>
-  <ods-button
-    text="Datenfeld hinzufügen"
-    dataTestId="add-mapping-button"
-    data-test-id="add-mapping"
-    (clickEmitter)="formService.addMapping()"
-  >
-    <ods-plus-icon icon class="fill-whitetext" />
-  </ods-button>
-
-  <div class="mt-4 flex gap-4">
-    <admin-save-button />
-    <admin-cancel-button [linkPath]="Routes.STATISTIK" />
-  </div>
-</div>
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts
deleted file mode 100644
index e124d07cf71b81b1c0ee692d87cd25ed8e8b59cd..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.spec.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { ADMIN_FORMSERVICE } from '@admin-client/shared';
-import { EMPTY_STRING } from '@alfa-client/tech-shared';
-import { dispatchEventFromFixture, existsAsHtmlElement, mock, Mock, MockEvent } from '@alfa-client/test-utils';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
-import { TextEditorComponent } from '@ods/component';
-import { ButtonComponent, PlusIconComponent } from '@ods/system';
-import { MockComponent } from 'ng-mocks';
-import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test';
-import { AdminCancelButtonComponent } from '../../../../shared/src/lib/admin-cancel-button/admin-cancel-button.component';
-import { AdminSaveButtonComponent } from '../../../../shared/src/lib/admin-save-button/admin-save-button.component';
-import { AdminStatistikFieldsFormComponent } from './admin-statistik-fields-form.component';
-import { AdminStatistikFieldsFormMappingComponent } from './statistik-fields-form-mapping/statistik-fields-form-mapping.component';
-import { StatistikFieldsFormService } from './statistik-fields.formservice';
-
-describe('AdminStatistikFieldsFormComponent', () => {
-  let component: AdminStatistikFieldsFormComponent;
-  let fixture: ComponentFixture<AdminStatistikFieldsFormComponent>;
-
-  const formEngineNameInputTestId: string = getDataTestIdOf('form-engine-name');
-  const formIdInputTestId: string = getDataTestIdOf('form-id');
-  const addMappingButton: string = getDataTestIdOf('add-mapping');
-
-  const formBuilder: FormBuilder = new FormBuilder();
-
-  let formService: Mock<StatistikFieldsFormService>;
-
-  beforeEach(async () => {
-    const form: FormGroup = formBuilder.group({
-      [StatistikFieldsFormService.FIELD_FORM_IDENTIFIER]: formBuilder.group({
-        [StatistikFieldsFormService.FIELD_FORM_ENGINE_NAME]: new FormControl(EMPTY_STRING),
-        [StatistikFieldsFormService.FIELD_FORM_ID]: new FormControl(EMPTY_STRING),
-      }),
-    });
-
-    formService = <any>{ ...mock(StatistikFieldsFormService), form };
-
-    await TestBed.configureTestingModule({
-      declarations: [
-        AdminStatistikFieldsFormComponent,
-        MockComponent(TextEditorComponent),
-        MockComponent(ButtonComponent),
-        MockComponent(PlusIconComponent),
-        MockComponent(AdminSaveButtonComponent),
-        MockComponent(AdminCancelButtonComponent),
-        MockComponent(AdminStatistikFieldsFormMappingComponent),
-      ],
-      imports: [ReactiveFormsModule],
-    })
-      .overrideComponent(AdminStatistikFieldsFormComponent, {
-        set: {
-          providers: [
-            {
-              provide: StatistikFieldsFormService,
-              useValue: formService,
-            },
-            {
-              provide: ADMIN_FORMSERVICE,
-              useValue: formService,
-            },
-          ],
-        },
-      })
-      .compileComponents();
-
-    fixture = TestBed.createComponent(AdminStatistikFieldsFormComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  describe('template', () => {
-    describe('form engine input', () => {
-      it('should exists', () => {
-        fixture.detectChanges();
-
-        existsAsHtmlElement(fixture, formEngineNameInputTestId);
-      });
-    });
-
-    describe('form id input', () => {
-      it('should exists', () => {
-        fixture.detectChanges();
-
-        existsAsHtmlElement(fixture, formIdInputTestId);
-      });
-    });
-
-    describe('add mapping button', () => {
-      it('should exists', () => {
-        fixture.detectChanges();
-
-        existsAsHtmlElement(fixture, addMappingButton);
-      });
-
-      describe('output', () => {
-        describe('clickEmitter', () => {
-          it('should call formService', () => {
-            fixture.detectChanges();
-
-            dispatchEventFromFixture(fixture, addMappingButton, MockEvent.CLICK);
-
-            expect(formService.addMapping).toHaveBeenCalled();
-          });
-        });
-      });
-    });
-  });
-});
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.ts
deleted file mode 100644
index 0298196202e623a3d080468c792f3c1c800a8750..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ADMIN_FORMSERVICE, AdminCancelButtonComponent, AdminSaveButtonComponent, ROUTES } from '@admin-client/shared';
-import { Component, inject } from '@angular/core';
-import { ReactiveFormsModule } from '@angular/forms';
-import { TextEditorComponent } from '@ods/component';
-import { ButtonComponent, DeleteIconComponent, PlusIconComponent } from '@ods/system';
-import { AdminStatistikFieldsFormMappingComponent } from './statistik-fields-form-mapping/statistik-fields-form-mapping.component';
-import { StatistikFieldsFormService } from './statistik-fields.formservice';
-
-@Component({
-  selector: 'admin-statistik-fields-form',
-  templateUrl: './admin-statistik-fields-form.component.html',
-  standalone: true,
-  imports: [
-    ButtonComponent,
-    PlusIconComponent,
-    ReactiveFormsModule,
-    TextEditorComponent,
-    DeleteIconComponent,
-    AdminSaveButtonComponent,
-    AdminCancelButtonComponent,
-    AdminStatistikFieldsFormMappingComponent,
-  ],
-  providers: [{ provide: ADMIN_FORMSERVICE, useClass: StatistikFieldsFormService }],
-})
-export class AdminStatistikFieldsFormComponent {
-  public readonly formService = <StatistikFieldsFormService>inject(ADMIN_FORMSERVICE);
-
-  public readonly StatistikFieldsFormService = StatistikFieldsFormService;
-  public readonly Routes = ROUTES;
-}
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html
deleted file mode 100644
index 569fc993f66170c76ad2cad6c28679b36e86e210..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<form [formGroup]="formService.form">
-  <div class="flex flex-col gap-4" [formArrayName]="StatistikFieldsFormService.FIELD_MAPPINGS">
-    <div
-      *ngFor="let mappingControl of formService.mappings.controls; let i = index"
-      [formGroupName]="i"
-      class="flex w-full gap-2"
-    >
-      <ods-text-editor
-        class="flex-1"
-        formControlName="sourcePath"
-        label="Pfad des Datenfeldes"
-        placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
-        [dataTestId]="'mapping-field-' + i"
-        [attr.data-test-id]="'mapping-field-' + i"
-      ></ods-text-editor>
-      <ods-button
-        class="self-end"
-        variant="ghost"
-        size="fit"
-        destructive="true"
-        (clickEmitter)="formService.removeMapping(i)"
-        [dataTestId]="'remove-mapping-button-' + i"
-        [attr.data-test-id]="'remove-mapping-' + i"
-      >
-        <ods-delete-icon icon />
-      </ods-button>
-    </div>
-  </div>
-</form>
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts
deleted file mode 100644
index d06746e48f9660298e3c4e2d305a1404cd0e8605..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.spec.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { ADMIN_FORMSERVICE } from '@admin-client/shared';
-import { EMPTY_STRING } from '@alfa-client/tech-shared';
-import { dispatchEventFromFixture, existsAsHtmlElement, mock, Mock, MockEvent, mockGetValue } from '@alfa-client/test-utils';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
-import { TextEditorComponent } from '@ods/component';
-import { ButtonComponent, DeleteIconComponent } from '@ods/system';
-import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
-import { MockComponent } from 'ng-mocks';
-import { StatistikFieldsFormService } from '../statistik-fields.formservice';
-import { AdminStatistikFieldsFormMappingComponent } from './statistik-fields-form-mapping.component';
-
-describe('AdminStatistikFieldsFormMappingComponent', () => {
-  let component: AdminStatistikFieldsFormMappingComponent;
-  let fixture: ComponentFixture<AdminStatistikFieldsFormMappingComponent>;
-
-  const mappingField: string = getDataTestIdOf('mapping-field-0');
-  const removeMappingButton: string = getDataTestIdOf('remove-mapping-0');
-
-  const formBuilder: FormBuilder = new FormBuilder();
-
-  let formService: Mock<StatistikFieldsFormService>;
-
-  beforeEach(async () => {
-    const form: FormGroup = formBuilder.group({
-      [StatistikFieldsFormService.FIELD_MAPPINGS]: formBuilder.array([
-        new FormGroup({ sourcePath: new FormControl(EMPTY_STRING) }),
-      ]),
-    });
-
-    formService = <any>{
-      ...mock(StatistikFieldsFormService),
-      form,
-      addMapping: jest.fn(),
-      removeMapping: jest.fn(),
-    };
-
-    mockGetValue(
-      formService,
-      StatistikFieldsFormService.FIELD_MAPPINGS,
-      form.controls[StatistikFieldsFormService.FIELD_MAPPINGS],
-    );
-
-    await TestBed.configureTestingModule({
-      declarations: [
-        AdminStatistikFieldsFormMappingComponent,
-        MockComponent(TextEditorComponent),
-        MockComponent(ButtonComponent),
-        MockComponent(DeleteIconComponent),
-      ],
-      imports: [ReactiveFormsModule],
-      providers: [
-        {
-          provide: StatistikFieldsFormService,
-          useValue: formService,
-        },
-        {
-          provide: ADMIN_FORMSERVICE,
-          useValue: formService,
-        },
-      ],
-    }).compileComponents();
-
-    fixture = TestBed.createComponent(AdminStatistikFieldsFormMappingComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  describe('template', () => {
-    describe('mapping input', () => {
-      it('should exists', () => {
-        fixture.detectChanges();
-
-        existsAsHtmlElement(fixture, mappingField);
-      });
-    });
-
-    describe('remove mapping button', () => {
-      it('should call formservice', () => {
-        dispatchEventFromFixture(fixture, removeMappingButton, MockEvent.CLICK);
-
-        expect(formService.removeMapping).toHaveBeenCalledWith(0);
-      });
-    });
-  });
-});
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.ts
deleted file mode 100644
index 48bc3eaddd2a2fe51b983939dd8920b02e7f8dae..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields-form-mapping/statistik-fields-form-mapping.component.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { ADMIN_FORMSERVICE, AdminCancelButtonComponent, AdminSaveButtonComponent, ROUTES } from '@admin-client/shared';
-import { CommonModule } from '@angular/common';
-import { Component, inject } from '@angular/core';
-import { ReactiveFormsModule } from '@angular/forms';
-import { TextEditorComponent } from '@ods/component';
-import { ButtonComponent, DeleteIconComponent, PlusIconComponent } from '@ods/system';
-import { StatistikFieldsFormService } from '../statistik-fields.formservice';
-
-@Component({
-  selector: 'statistik-fields-form-mapping',
-  templateUrl: './statistik-fields-form-mapping.component.html',
-  standalone: true,
-  imports: [
-    CommonModule,
-    ButtonComponent,
-    PlusIconComponent,
-    ReactiveFormsModule,
-    TextEditorComponent,
-    DeleteIconComponent,
-    AdminSaveButtonComponent,
-    AdminCancelButtonComponent,
-  ],
-})
-export class AdminStatistikFieldsFormMappingComponent {
-  public readonly formService = <StatistikFieldsFormService>inject(ADMIN_FORMSERVICE);
-
-  public readonly StatistikFieldsFormService = StatistikFieldsFormService;
-  public readonly Routes = ROUTES;
-}
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.spec.ts
deleted file mode 100644
index 58a2273cb692762c4f930e583c8c49f210099517..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.spec.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-import { AggregationMappingResource, AggregationMappingService } from '@admin-client/reporting-shared';
-import { EMPTY_STRING, StateResource, createStateResource } from '@alfa-client/tech-shared';
-import { Mock, mock } from '@alfa-client/test-utils';
-import { TestBed } from '@angular/core/testing';
-import { FormArray, FormControl, FormGroup } from '@angular/forms';
-import { createAggregationMapping, createAggregationMappingResource } from 'libs/admin/reporting-shared/test/aggregation-mapping';
-import { of } from 'rxjs';
-import { StatistikFieldsFormService } from './statistik-fields.formservice';
-
-describe('StatistikFieldsFormService', () => {
-  let formService: StatistikFieldsFormService;
-
-  let service: Mock<AggregationMappingService>;
-
-  beforeEach(() => {
-    service = mock(AggregationMappingService);
-
-    TestBed.configureTestingModule({
-      providers: [StatistikFieldsFormService, { provide: AggregationMappingService, useValue: service }],
-    });
-
-    formService = TestBed.inject(StatistikFieldsFormService);
-  });
-
-  it('should create', () => {
-    expect(formService).toBeTruthy();
-  });
-
-  describe('on do submit', () => {
-    const stateResource: StateResource<AggregationMappingResource> = createStateResource(createAggregationMappingResource());
-
-    beforeEach(() => {
-      service.create.mockReturnValue(of(stateResource));
-    });
-
-    it('should call service', () => {
-      formService.form = <any>createAggregationMapping();
-
-      formService.submit();
-
-      expect(service.create).toHaveBeenCalledWith(formService.form.value);
-    });
-  });
-
-  describe('add mapping', () => {
-    it('should add mapping control', () => {
-      formService.addMapping();
-
-      const mappingFormArray: FormArray = <FormArray>formService.form.controls[StatistikFieldsFormService.FIELD_MAPPINGS];
-      expect(mappingFormArray).toHaveLength(2);
-      expect(mappingFormArray.controls[0].value).toEqual({ sourcePath: EMPTY_STRING });
-    });
-  });
-
-  describe('remove mapping', () => {
-    it('should remove mapping control', () => {
-      (<FormArray>formService.form.controls[StatistikFieldsFormService.FIELD_MAPPINGS]).push(
-        new FormGroup({ sourcePath: new FormControl('controlToRemove') }),
-      );
-
-      formService.removeMapping(1);
-
-      const mappingFormArray: FormArray = <FormArray>formService.form.controls[StatistikFieldsFormService.FIELD_MAPPINGS];
-      expect(mappingFormArray).toHaveLength(1);
-      expect(mappingFormArray.controls[0].value).toEqual({ sourcePath: EMPTY_STRING });
-    });
-  });
-
-  describe('get mappings', () => {
-    it('should return mappings as array', () => {
-      const mappings: FormArray = formService.mappings;
-
-      expect(mappings).toHaveLength(1);
-      expect(mappings.controls[0].value).toEqual({ sourcePath: EMPTY_STRING });
-    });
-  });
-});
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.ts
deleted file mode 100644
index 1b746e12f42b44cc1261ce6de543380ea61d9ec4..0000000000000000000000000000000000000000
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-fields-form/statistik-fields.formservice.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { AggregationMappingResource, AggregationMappingService } from '@admin-client/reporting-shared';
-import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
-import { inject, Injectable } from '@angular/core';
-import { FormArray, FormControl, FormGroup, UntypedFormGroup } from '@angular/forms';
-import { Observable } from 'rxjs';
-
-@Injectable()
-export class StatistikFieldsFormService extends AbstractFormService<AggregationMappingResource> {
-  private service = inject(AggregationMappingService);
-
-  public static readonly FIELD_FORM_IDENTIFIER: string = 'formIdentifier';
-  public static readonly FIELD_FORM_ENGINE_NAME: string = 'formEngineName';
-  public static readonly FIELD_FORM_ID: string = 'formId';
-
-  public static readonly FIELD_MAPPINGS: string = 'mappings';
-
-  protected initForm(): UntypedFormGroup {
-    return this.formBuilder.group({
-      [StatistikFieldsFormService.FIELD_FORM_IDENTIFIER]: this.formBuilder.group({
-        [StatistikFieldsFormService.FIELD_FORM_ENGINE_NAME]: new FormControl(EMPTY_STRING),
-        [StatistikFieldsFormService.FIELD_FORM_ID]: new FormControl(EMPTY_STRING),
-      }),
-      [StatistikFieldsFormService.FIELD_MAPPINGS]: new FormArray([this.createArrayControl()]),
-    });
-  }
-
-  protected doSubmit(): Observable<StateResource<AggregationMappingResource>> {
-    return this.service.create(this.getFormValue());
-  }
-
-  protected getPathPrefix(): string {
-    return 'settingBody';
-  }
-
-  public addMapping(): void {
-    this.mappings.push(this.createArrayControl());
-  }
-
-  private createArrayControl(): FormGroup {
-    return new FormGroup({ sourcePath: new FormControl(EMPTY_STRING) });
-  }
-
-  public removeMapping(index: number): void {
-    this.mappings.removeAt(index);
-  }
-
-  public get mappings(): FormArray {
-    return this.form.controls[StatistikFieldsFormService.FIELD_MAPPINGS] as FormArray;
-  }
-}
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-data/user-form-data.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-data/user-form-data.component.html
index 830c7960ac97bb94267cdb6737d3a83001e44951..fb1574bf9b8090e385af452584610fb4d03f457f 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-data/user-form-data.component.html
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-data/user-form-data.component.html
@@ -1,17 +1,18 @@
 <div [formGroup]="formGroupParent">
   <div class="mb-4 grid gap-4 xl:grid-cols-2">
-    <ods-text-editor [formControlName]="UserFormService.FIRST_NAME" [isRequired]="true" label="Vorname" />
-    <ods-text-editor [formControlName]="UserFormService.LAST_NAME" [isRequired]="true" label="Nachname" />
+    <ods-text-editor [formControlName]="UserFormService.FIRST_NAME" [isRequired]="true" label="Vorname" dataTestId="firstName" />
+    <ods-text-editor [formControlName]="UserFormService.LAST_NAME" [isRequired]="true" label="Nachname" dataTestId="lastName" />
     @if (isPatch) {
-      <admin-user-form-data-name [userName]="userName" data-test-id="user-name-info" />
+      <admin-user-form-data-name [userName]="userName" data-test-id="user-name-info" dataTestId="username" />
     } @else {
       <ods-text-editor
         [formControlName]="UserFormService.USERNAME"
         [isRequired]="true"
         label="Benutzername"
         data-test-id="user-name-editor"
+        dataTestId="username"
       />
     }
-    <ods-text-editor [formControlName]="UserFormService.EMAIL" [isRequired]="true" label="E-Mail" />
+    <ods-text-editor [formControlName]="UserFormService.EMAIL" [isRequired]="true" label="E-Mail" dataTestId="email" />
   </div>
 </div>
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-organisations-einheit-list/user-form-organisations-einheit-list.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-organisations-einheit-list/user-form-organisations-einheit-list.component.html
index 5d3fea2f35377a4c9a2a62d99d356fe38bbbf7d7..2c1d3520d18708d5b7861627130f35cec586c927 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-organisations-einheit-list/user-form-organisations-einheit-list.component.html
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-organisations-einheit-list/user-form-organisations-einheit-list.component.html
@@ -1,11 +1,14 @@
-<div class="mb-12 block">
-  <h2 class="heading-2 mt-4">Organisationseinheiten</h2>
-  <div [formGroup]="formGroupParent">
-    <div [formGroupName]="UserFormService.GROUPS" class="flex flex-col gap-2">
-      <p class="font-medium">Dem Benutzer sind folgende Organisationseinheiten zugewiesen</p>
-      @for (controlName of formGroupOrganisationsEinheiten.controls | keyvalue; track controlName.key) {
-        <ods-checkbox-editor [formControlName]="controlName.key" [label]="controlName.key" [inputId]="controlName.key" />
-      }
-    </div>
+<div
+  [formGroup]="formGroupParent"
+  role="group"
+  aria-labelledby="organisationseinheiten-heading organisationseinheiten-desc"
+  class="mb-12"
+>
+  <h2 id="organisationseinheiten-heading" class="heading-2 mt-4">Organisationseinheiten</h2>
+  <p id="organisationseinheiten-desc" class="mb-2 font-medium">Dem Benutzer sind folgende Organisationseinheiten zugewiesen</p>
+  <div [formGroupName]="UserFormService.GROUPS" class="flex flex-col gap-2">
+    @for (controlName of formGroupOrganisationsEinheiten.controls | keyvalue; track controlName.key) {
+      <ods-checkbox-editor [formControlName]="controlName.key" [label]="controlName.key" [inputId]="controlName.key" />
+    }
   </div>
 </div>
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
index 1dda4319a297a47b72c1d1c8bc6e1401a796a7ee..d3d39a71ceb69e639c38e1ac0219f27eb1c5e6f5 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html
@@ -1,6 +1,12 @@
-<h2 class="heading-2 mt-4">Rollen für OZG-Cloud</h2>
-<div [formGroup]="formGroupParent">
-  <div [formGroupName]="UserFormService.CLIENT_ROLES" class="mb-8 flex flex-col gap-4 md:flex-row">
+<h2 class="heading-2 mt-4" id="rollen-ozg-heading">Rollen für OZG-Cloud<i aria-hidden="true">*</i></h2>
+<div
+  [formGroup]="formGroupParent"
+  role="group"
+  aria-required="true"
+  aria-labelledby="rollen-ozg-heading"
+  [attr.aria-invalid]="!isValid"
+>
+  <div [formGroupName]="UserFormService.CLIENT_ROLES" class="flex flex-col gap-4 md:flex-row">
     <div [formGroupName]="UserFormService.ADMINISTRATION_GROUP" class="flex flex-1 flex-col gap-2">
       <h3 class="text-md block font-medium text-text">Administration</h3>
       <div class="flex items-center gap-2">
@@ -54,4 +60,10 @@
       </div>
     </div>
   </div>
+  <ods-validation-error
+    [id]="validationErrorId"
+    [invalidParams]="invalidParams$ | async"
+    label="Rollen"
+    data-test-id="rollen-error"
+  ></ods-validation-error>
 </div>
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts
index b27337b2786891b5738a6d09bae607cecd492cdb..5f21edb7a5722e98bd9a0069b3dcb3f9ce8ad10a 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts
@@ -1,14 +1,24 @@
+import { InvalidParam } from '@alfa-client/tech-shared';
+import { existsAsHtmlElement, getElementComponentFromFixtureByCss } from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ReactiveFormsModule } from '@angular/forms';
+import { AbstractControl, FormControl, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
+import { expect } from '@jest/globals';
 import { InfoIconComponent, TooltipDirective } from '@ods/system';
 import { MockComponent, MockDirective } from 'ng-mocks';
+import { of } from 'rxjs';
+import { ValidationErrorComponent } from '../../../../../../design-component/src/lib/form/validation-error/validation-error.component';
+import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test';
+import { createInvalidParam } from '../../../../../../tech-shared/test/error';
 import { createUserFormGroup } from '../../../../test/form';
+import { UserFormService } from '../user.formservice';
 import { UserFormRolesComponent } from './user-form-roles.component';
 
 describe('UserFormRolesComponent', () => {
   let component: UserFormRolesComponent;
   let fixture: ComponentFixture<UserFormRolesComponent>;
 
+  const validationErrorTestId: string = getDataTestIdOf('rollen-error');
+
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [UserFormRolesComponent, ReactiveFormsModule, MockComponent(InfoIconComponent), MockDirective(TooltipDirective)],
@@ -23,4 +33,84 @@ describe('UserFormRolesComponent', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
   });
+
+  describe('component', () => {
+    describe('ngOnInit', () => {
+      it('should emit invalid params on invalid control', (done) => {
+        const invalidParam: InvalidParam = createInvalidParam();
+        const error: any = { dummy: invalidParam };
+        const control: AbstractControl = new FormControl();
+        component.formGroupParent = new UntypedFormGroup({ [UserFormService.CLIENT_ROLES]: control });
+
+        component.ngOnInit();
+        component.invalidParams$.subscribe((result) => {
+          expect(result).toEqual([invalidParam]);
+          done();
+        });
+        control.setErrors(error);
+      });
+
+      it('should emit empty array on valid control', (done) => {
+        const control: AbstractControl = new FormControl();
+        component.formGroupParent = new UntypedFormGroup({ [UserFormService.CLIENT_ROLES]: control });
+
+        component.ngOnInit();
+        component.invalidParams$.subscribe((result) => {
+          expect(result).toEqual([]);
+          done();
+        });
+
+        control.setErrors(null);
+      });
+
+      it('should mark as invalid', (done) => {
+        const invalidParam: InvalidParam = createInvalidParam();
+        const error: any = { dummy: invalidParam };
+        const control: AbstractControl = new FormControl();
+        component.formGroupParent = new UntypedFormGroup({ [UserFormService.CLIENT_ROLES]: control });
+
+        component.ngOnInit();
+        component.invalidParams$.subscribe(() => {
+          expect(component.isValid).toBe(false);
+          done();
+        });
+
+        control.setErrors(error);
+      });
+
+      it('should mark as valid', (done) => {
+        const control: AbstractControl = new FormControl();
+        component.formGroupParent = new UntypedFormGroup({ [UserFormService.CLIENT_ROLES]: control });
+
+        component.ngOnInit();
+        component.invalidParams$.subscribe(() => {
+          expect(component.isValid).toBe(true);
+          done();
+        });
+
+        control.setErrors(null);
+      });
+    });
+  });
+
+  describe('template', () => {
+    describe('validation error', () => {
+      it('should exists', () => {
+        existsAsHtmlElement(fixture, validationErrorTestId);
+      });
+
+      it('should have inputs', () => {
+        const invalidParam: InvalidParam = createInvalidParam();
+        component.invalidParams$ = of([invalidParam]);
+
+        fixture.detectChanges();
+        const validationErrorComponent: ValidationErrorComponent = getElementComponentFromFixtureByCss(
+          fixture,
+          validationErrorTestId,
+        );
+
+        expect(validationErrorComponent.invalidParams).toEqual([invalidParam]);
+      });
+    });
+  });
 });
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
index c7d46e948f2ddc5b4c50fd5880ddf7ff2221030c..7a8c901dab035b8a8364b9ce60c1453a7374711c 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts
@@ -1,17 +1,40 @@
-import { Component, Input } from '@angular/core';
-import { ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
-import { CheckboxEditorComponent } from '@ods/component';
+import { generateValidationErrorId, InvalidParam } from '@alfa-client/tech-shared';
+import { AsyncPipe } from '@angular/common';
+import { Component, Input, OnInit } from '@angular/core';
+import { AbstractControl, FormControlStatus, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
+import { CheckboxEditorComponent, ValidationErrorComponent } from '@ods/component';
 import { InfoIconComponent, TooltipDirective } from '@ods/system';
+import { isEmpty } from 'lodash-es';
+import { map, Observable, of, tap } from 'rxjs';
 import { UserFormService } from '../user.formservice';
 
 @Component({
   selector: 'admin-user-form-roles',
   standalone: true,
-  imports: [CheckboxEditorComponent, ReactiveFormsModule, TooltipDirective, InfoIconComponent],
+  imports: [
+    CheckboxEditorComponent,
+    ReactiveFormsModule,
+    TooltipDirective,
+    InfoIconComponent,
+    ValidationErrorComponent,
+    AsyncPipe,
+  ],
   templateUrl: './user-form-roles.component.html',
 })
-export class UserFormRolesComponent {
+export class UserFormRolesComponent implements OnInit {
   @Input() formGroupParent: UntypedFormGroup;
 
+  public invalidParams$: Observable<InvalidParam[]> = of([]);
+
   public readonly UserFormService = UserFormService;
+  public readonly validationErrorId: string = generateValidationErrorId();
+  public isValid: boolean = true;
+
+  ngOnInit(): void {
+    const control: AbstractControl = this.formGroupParent.controls[UserFormService.CLIENT_ROLES];
+    this.invalidParams$ = control.statusChanges.pipe(
+      map((status: FormControlStatus) => (status === 'INVALID' ? Object.values(control.errors) : [])),
+      tap((invalidParams: InvalidParam[]) => (this.isValid = isEmpty(invalidParams))),
+    );
+  }
 }
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts
index 99e28aabc946516a2076a89eb850c1df7b5594cd..caabe30992d06bce0dffefa1c988f2e405a8793a 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.spec.ts
@@ -24,9 +24,14 @@
 import { AdminOrganisationsEinheit, AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared';
 import { ROUTES } from '@admin-client/shared';
 import { User, UserService } from '@admin-client/user-shared';
-import { PatchConfig } from '@admin/keycloak-shared';
+import { KeycloakErrorMessage, KeycloakFieldName, PatchConfig } from '@admin/keycloak-shared';
 import { NavigationService } from '@alfa-client/navigation-shared';
-import { createEmptyStateResource, createLoadingStateResource, createStateResource, StateResource, } from '@alfa-client/tech-shared';
+import {
+  createEmptyStateResource,
+  createLoadingStateResource,
+  createStateResource,
+  StateResource,
+} from '@alfa-client/tech-shared';
 import { Mock, mock, mockWindowError } from '@alfa-client/test-utils';
 import { SnackBarService } from '@alfa-client/ui';
 import { fakeAsync, TestBed, tick } from '@angular/core/testing';
@@ -34,24 +39,24 @@ import { AbstractControl, FormControl, FormGroup, UntypedFormBuilder, UntypedFor
 import { ActivatedRoute, UrlSegment } from '@angular/router';
 import { faker } from '@faker-js/faker/locale/de';
 import { expect } from '@jest/globals';
-import { cold } from 'jest-marbles';
 import { createUser } from 'libs/admin/user-shared/test/user';
-import { Observable, of, throwError } from 'rxjs';
+import { Observable, of } from 'rxjs';
 import { createUrlSegment } from '../../../../../navigation-shared/test/navigation-test-factory';
 import { singleCold, singleColdCompleted, singleHot } from '../../../../../tech-shared/test/marbles';
+import { createKeycloakHttpErrorResponse } from '../../../../keycloak-shared/src/test/keycloak';
 import { createAdminOrganisationsEinheit } from '../../../../organisations-einheit-shared/src/test/organisations-einheit';
 import { UserFormService } from './user.formservice';
 import SpyInstance = jest.SpyInstance;
 
 describe('UserFormService', () => {
-  let formService: UserFormService;
+  let service: UserFormService;
 
   let roleGroup: UntypedFormGroup;
   let alfaGroup: UntypedFormGroup;
   let organisationsEinheitenGroup: UntypedFormGroup;
   let administrationGroup: UntypedFormGroup;
 
-  let service: Mock<UserService>;
+  let userService: Mock<UserService>;
   let adminOrganisationsEinheitService: Mock<AdminOrganisationsEinheitService>;
   let navigationService: Mock<NavigationService>;
   let snackBarService: Mock<SnackBarService>;
@@ -63,7 +68,7 @@ describe('UserFormService', () => {
   ]);
 
   beforeEach(() => {
-    service = {
+    userService = {
       ...mock(UserService),
       refresh: jest.fn(),
       create: jest.fn(),
@@ -83,7 +88,7 @@ describe('UserFormService', () => {
       providers: [
         UserFormService,
         UntypedFormBuilder,
-        { provide: UserService, useValue: service },
+        { provide: UserService, useValue: userService },
         { provide: AdminOrganisationsEinheitService, useValue: adminOrganisationsEinheitService },
         { provide: NavigationService, useValue: navigationService },
         { provide: SnackBarService, useValue: snackBarService },
@@ -91,15 +96,15 @@ describe('UserFormService', () => {
       ],
     });
 
-    formService = TestBed.inject(UserFormService);
-    organisationsEinheitenGroup = <UntypedFormGroup>formService.form.get(UserFormService.GROUPS);
-    roleGroup = <UntypedFormGroup>formService.form.get(UserFormService.CLIENT_ROLES);
+    service = TestBed.inject(UserFormService);
+    organisationsEinheitenGroup = <UntypedFormGroup>service.form.get(UserFormService.GROUPS);
+    roleGroup = <UntypedFormGroup>service.form.get(UserFormService.CLIENT_ROLES);
     alfaGroup = <UntypedFormGroup>roleGroup.get(UserFormService.ALFA_GROUP);
     administrationGroup = <UntypedFormGroup>roleGroup.get(UserFormService.ADMINISTRATION_GROUP);
   });
 
   it('should create', () => {
-    expect(formService).toBeTruthy();
+    expect(service).toBeTruthy();
   });
 
   describe('build patch config', () => {
@@ -108,7 +113,7 @@ describe('UserFormService', () => {
         const benutzerUrlSegment: UrlSegment = <UrlSegment>{ ...createUrlSegment(), path: 'benutzer' };
         const idUrlSegment: UrlSegment = <UrlSegment>{ ...createUrlSegment(), path: 'dummyId' };
 
-        const patchConfig: PatchConfig = formService._buildPatchConfig([benutzerUrlSegment, idUrlSegment]);
+        const patchConfig: PatchConfig = service._buildPatchConfig([benutzerUrlSegment, idUrlSegment]);
 
         expect(patchConfig).toEqual({ id: 'dummyId', doPatch: true });
       });
@@ -118,7 +123,7 @@ describe('UserFormService', () => {
       const benutzerUrlSegment: UrlSegment = <UrlSegment>{ ...createUrlSegment(), path: 'benutzer' };
       const idUrlSegment: UrlSegment = <UrlSegment>{ ...createUrlSegment(), path: 'neu' };
 
-      const patchConfig: PatchConfig = formService._buildPatchConfig([benutzerUrlSegment, idUrlSegment]);
+      const patchConfig: PatchConfig = service._buildPatchConfig([benutzerUrlSegment, idUrlSegment]);
 
       expect(patchConfig).toEqual({ doPatch: false });
     });
@@ -130,17 +135,17 @@ describe('UserFormService', () => {
     const loadedUser: StateResource<User> = createStateResource(createUser());
 
     beforeEach(() => {
-      service.getUserById.mockReturnValue(singleHot(loadedUser));
+      userService.getUserById.mockReturnValue(singleHot(loadedUser));
     });
 
     it('should call service to get user by id', () => {
-      formService._load(id);
+      service._load(id);
 
-      expect(service.getUserById).toHaveBeenCalledWith(id);
+      expect(userService.getUserById).toHaveBeenCalledWith(id);
     });
 
     it('should return loaded user', () => {
-      const response: Observable<StateResource<User>> = formService._load(id);
+      const response: Observable<StateResource<User>> = service._load(id);
 
       expect(response).toBeObservable(singleCold(loadedUser));
     });
@@ -148,65 +153,63 @@ describe('UserFormService', () => {
 
   describe('listenToAlfaGroupChanges', () => {
     it('should call handleAlfaGroupChange on initial change', () => {
-      formService._handleAlfaGroupChange = jest.fn();
+      service._handleAlfaGroupChange = jest.fn();
 
-      formService.listenToAlfaGroupChanges();
+      service.listenToAlfaGroupChanges();
 
-      expect(formService._handleAlfaGroupChange).toHaveBeenCalled();
+      expect(service._handleAlfaGroupChange).toHaveBeenCalled();
     });
 
     it('should call handleAlfaGroupChange when value of form element changes', fakeAsync(() => {
-      formService._handleAlfaGroupChange = jest.fn();
+      service._handleAlfaGroupChange = jest.fn();
 
       alfaGroup.get(UserFormService.LOESCHEN).setValue(true);
 
       tick();
 
-      expect(formService._handleAlfaGroupChange).toHaveBeenCalled();
+      expect(service._handleAlfaGroupChange).toHaveBeenCalled();
     }));
   });
 
   describe('initOrganisationsEinheiten', () => {
     beforeEach(() => {
-      formService._addOrganisationsEinheitenToForm = jest.fn();
+      service._addOrganisationsEinheitenToForm = jest.fn();
     });
 
     it('should call adminOrganisationsEinheitService getAll', () => {
-      formService._initOrganisationsEinheiten();
+      service._initOrganisationsEinheiten();
 
       expect(adminOrganisationsEinheitService.getAll).toHaveBeenCalled();
     });
 
     it('should return resource', () => {
-      const result: Observable<AdminOrganisationsEinheit[]> = formService._initOrganisationsEinheiten();
+      const result: Observable<AdminOrganisationsEinheit[]> = service._initOrganisationsEinheiten();
 
       expect(result).toBeObservable(singleColdCompleted(adminOrganisationsEinheitList.resource));
     });
 
     it('should call addOrganisationsEinheitenToForm ', fakeAsync(() => {
-      formService._initOrganisationsEinheiten().subscribe();
+      service._initOrganisationsEinheiten().subscribe();
       tick();
 
-      expect(formService._addOrganisationsEinheitenToForm).toHaveBeenCalled();
+      expect(service._addOrganisationsEinheitenToForm).toHaveBeenCalled();
     }));
 
     it('should save ids in map', () => {
-      formService._initOrganisationsEinheiten().subscribe();
+      service._initOrganisationsEinheiten().subscribe();
 
-      expect(formService._organisationsEinheitToGroupIdMap.get(adminOrganisationsEinheit.name)).toEqual(
-        adminOrganisationsEinheit.id,
-      );
+      expect(service._organisationsEinheitToGroupIdMap.get(adminOrganisationsEinheit.name)).toEqual(adminOrganisationsEinheit.id);
     });
 
     it('should set initOrganisationsEinheiten$', () => {
-      expect(formService['_initOrganisationsEinheiten$']).toBeDefined();
+      expect(service['_initOrganisationsEinheiten$']).toBeDefined();
     });
 
     it('should not throw any exception on loading state resource', () => {
       adminOrganisationsEinheitService.getAll.mockReturnValue(of(createLoadingStateResource()));
       const errorMock: any = mockWindowError();
 
-      formService._initOrganisationsEinheiten();
+      service._initOrganisationsEinheiten();
 
       expect(errorMock).not.toHaveBeenCalled();
     });
@@ -214,53 +217,37 @@ describe('UserFormService', () => {
 
   describe('addOrganisationsEinheitenToForm', () => {
     it('should add organisationsEinheiten to form', () => {
-      const organisationsEinheitenGroup: UntypedFormGroup = <UntypedFormGroup>formService.form.get(UserFormService.GROUPS);
+      const organisationsEinheitenGroup: UntypedFormGroup = <UntypedFormGroup>service.form.get(UserFormService.GROUPS);
 
-      formService._addOrganisationsEinheitenToForm(adminOrganisationsEinheitList.resource, organisationsEinheitenGroup);
+      service._addOrganisationsEinheitenToForm(adminOrganisationsEinheitList.resource, organisationsEinheitenGroup);
 
       expect(organisationsEinheitenGroup.value).toEqual({ [adminOrganisationsEinheit.name]: false });
     });
   });
 
-  describe('roleValidator', () => {
-    it('should return error if no role is selected', () => {
-      const result = formService.roleValidator()(roleGroup);
-
-      expect(result).toEqual({ atLeastOneRoleSelected: true });
-    });
-
-    it('should return null if at least one role is selected', () => {
-      alfaGroup.get(UserFormService.LOESCHEN).setValue(true);
-
-      const result = formService.roleValidator()(roleGroup);
-
-      expect(result).toBeNull();
-    });
-  });
-
   describe('handleAlfaGroupChange', () => {
     it('should call disableUncheckedCheckboxes if any checkbox is checked', () => {
-      formService._isAnyChecked = jest.fn().mockReturnValue(true);
-      formService._disableUncheckedCheckboxes = jest.fn();
+      service._isAnyChecked = jest.fn().mockReturnValue(true);
+      service._disableUncheckedCheckboxes = jest.fn();
 
-      formService._handleAlfaGroupChange(alfaGroup);
+      service._handleAlfaGroupChange(alfaGroup);
 
-      expect(formService._disableUncheckedCheckboxes).toHaveBeenCalled();
+      expect(service._disableUncheckedCheckboxes).toHaveBeenCalled();
     });
 
     it('should call enableAllCheckboxes if not any checkbox is checked', () => {
-      formService._isAnyChecked = jest.fn().mockReturnValue(false);
-      formService._enableAllCheckboxes = jest.fn();
+      service._isAnyChecked = jest.fn().mockReturnValue(false);
+      service._enableAllCheckboxes = jest.fn();
 
-      formService._handleAlfaGroupChange(alfaGroup);
+      service._handleAlfaGroupChange(alfaGroup);
 
-      expect(formService._enableAllCheckboxes).toHaveBeenCalled();
+      expect(service._enableAllCheckboxes).toHaveBeenCalled();
     });
   });
 
   describe('isAnyChecked', () => {
     it('should return false if no checkbox is checked', () => {
-      const result = formService._isAnyChecked(alfaGroup);
+      const result = service._isAnyChecked(alfaGroup);
 
       expect(result).toBe(false);
     });
@@ -268,7 +255,7 @@ describe('UserFormService', () => {
     it('should return true if any checkbox is checked', () => {
       alfaGroup.get(UserFormService.LOESCHEN).setValue(true);
 
-      const result = formService._isAnyChecked(alfaGroup);
+      const result = service._isAnyChecked(alfaGroup);
 
       expect(result).toBe(true);
     });
@@ -279,7 +266,7 @@ describe('UserFormService', () => {
       const control: AbstractControl = alfaGroup.get(UserFormService.LOESCHEN);
       control.setValue(false);
 
-      formService._disableUncheckedCheckboxes(alfaGroup);
+      service._disableUncheckedCheckboxes(alfaGroup);
 
       expect(control.disabled).toBe(true);
     });
@@ -288,7 +275,7 @@ describe('UserFormService', () => {
       const control: AbstractControl = alfaGroup.get(UserFormService.LOESCHEN);
       control.setValue(true);
 
-      formService._disableUncheckedCheckboxes(alfaGroup);
+      service._disableUncheckedCheckboxes(alfaGroup);
 
       expect(control.disabled).toBe(false);
     });
@@ -299,7 +286,7 @@ describe('UserFormService', () => {
       const control: AbstractControl = alfaGroup.get(UserFormService.LOESCHEN);
       control.setValue(false);
 
-      formService._disableUncheckedCheckboxes(alfaGroup);
+      service._disableUncheckedCheckboxes(alfaGroup);
 
       expect(control.disabled).toBe(true);
     });
@@ -310,7 +297,7 @@ describe('UserFormService', () => {
       const control: AbstractControl = alfaGroup.get(UserFormService.LOESCHEN);
       const enableSpy = jest.spyOn(control, 'enable');
 
-      formService._enableAllCheckboxes(alfaGroup);
+      service._enableAllCheckboxes(alfaGroup);
 
       expect(enableSpy).toHaveBeenCalled();
     });
@@ -320,63 +307,44 @@ describe('UserFormService', () => {
     const user: User = createUser();
 
     beforeEach(() => {
-      formService.isInvalid = jest.fn().mockReturnValue(false);
-      service.create.mockReturnValue(of(createStateResource(user)));
-    });
-
-    it('should return empty stateResource if form is invalid', () => {
-      formService.isInvalid = jest.fn().mockReturnValue(true);
-
-      const result: Observable<StateResource<User>> = formService._doSubmit();
-
-      expect(result).toBeObservable(singleColdCompleted(createEmptyStateResource<User>()));
+      service.isInvalid = jest.fn().mockReturnValue(false);
+      userService.create.mockReturnValue(of(createStateResource(user)));
     });
 
     it('should call createUser', () => {
-      formService._createUser = jest.fn();
+      service._createUser = jest.fn();
 
-      formService.submit();
+      service._doSubmit().subscribe();
 
-      expect(formService._createUser).toHaveBeenCalled();
+      expect(service._createUser).toHaveBeenCalled();
     });
 
     it('should call _createOrSave', () => {
-      formService._createUser = jest.fn().mockReturnValue(user);
-      formService._createOrSave = jest.fn().mockReturnValue(of(user));
+      service._createUser = jest.fn().mockReturnValue(user);
+      service._createOrSave = jest.fn().mockReturnValue(of(user));
 
-      formService.submit();
+      service._doSubmit().subscribe();
 
-      expect(formService._createOrSave).toHaveBeenCalledWith(user);
+      expect(service._createOrSave).toHaveBeenCalledWith(user);
     });
 
     it('should call handleOnCreateUserSuccess if not loading', fakeAsync(() => {
-      const handleOnCreateUserSuccessSpy: SpyInstance = jest.spyOn(formService, 'handleOnCreateUserSuccess');
+      const handleOnCreateUserSuccessSpy: SpyInstance = jest.spyOn(service, 'handleOnCreateUserSuccess');
 
-      formService.submit().subscribe();
-      tick();
+      service._doSubmit().subscribe();
 
       expect(handleOnCreateUserSuccessSpy).toHaveBeenCalled();
     }));
 
     it('should not call handleOnCreateUserSuccess if loading', fakeAsync(() => {
-      service.create.mockReturnValue(of(createEmptyStateResource(true)));
-      const handleOnCreateUserSuccessSpy: SpyInstance = jest.spyOn(formService, 'handleOnCreateUserSuccess');
+      userService.create.mockReturnValue(of(createEmptyStateResource(true)));
+      const handleOnCreateUserSuccessSpy: SpyInstance = jest.spyOn(service, 'handleOnCreateUserSuccess');
 
-      formService.submit().subscribe();
+      service.submit().subscribe();
       tick();
 
       expect(handleOnCreateUserSuccessSpy).not.toHaveBeenCalled();
     }));
-
-    it('should call handleSubmitError on error', fakeAsync(() => {
-      service.create.mockReturnValue(throwError(() => new Error()));
-      const handleSubmitErrorSpy: SpyInstance = jest.spyOn(formService, 'handleSubmitError');
-
-      formService.submit().subscribe();
-      tick();
-
-      expect(handleSubmitErrorSpy).toHaveBeenCalled();
-    }));
   });
 
   describe('createOrSave', () => {
@@ -387,57 +355,50 @@ describe('UserFormService', () => {
     const patchConfig: PatchConfig = { id, doPatch: true };
 
     it('should call save if patched', () => {
-      formService.isPatch = jest.fn().mockReturnValue(true);
-      formService._patchConfig = patchConfig;
+      service.isPatch = jest.fn().mockReturnValue(true);
+      service._patchConfig = patchConfig;
       user.groups = [adminOrganisationsEinheit.name];
-      formService._organisationsEinheitToGroupIdMap.set(adminOrganisationsEinheit.name, adminOrganisationsEinheit.id);
+      service._organisationsEinheitToGroupIdMap.set(adminOrganisationsEinheit.name, adminOrganisationsEinheit.id);
 
-      formService._createOrSave(user);
+      service._createOrSave(user);
 
-      expect(service.save).toHaveBeenCalledWith({ ...user, id, groupIds: [adminOrganisationsEinheit.id] });
+      expect(userService.save).toHaveBeenCalledWith({ ...user, id, groupIds: [adminOrganisationsEinheit.id] });
     });
 
     it('should call create if not patched', () => {
-      formService.isPatch = jest.fn().mockReturnValue(false);
+      service.isPatch = jest.fn().mockReturnValue(false);
 
-      formService._createOrSave(user);
+      service._createOrSave(user);
 
-      expect(service.create).toHaveBeenCalledWith(user);
+      expect(userService.create).toHaveBeenCalledWith(user);
     });
   });
 
   describe('handleOnCreateUserSuccess', () => {
     it('should show success message for create', () => {
-      formService.handleOnCreateUserSuccess();
+      service.handleOnCreateUserSuccess();
 
       expect(snackBarService.showInfo).toHaveBeenCalledWith('Der Benutzer wurde angelegt.');
     });
 
     it('should show success message for patch', () => {
-      formService.isPatch = jest.fn().mockReturnValue(true);
+      service.isPatch = jest.fn().mockReturnValue(true);
 
-      formService.handleOnCreateUserSuccess();
+      service.handleOnCreateUserSuccess();
 
       expect(snackBarService.showInfo).toHaveBeenCalledWith('Der Benutzer wurde gespeichert.');
     });
 
     it('should navigate back to user list', () => {
-      formService.handleOnCreateUserSuccess();
+      service.handleOnCreateUserSuccess();
 
       expect(navigationService.navigate).toHaveBeenCalledWith(ROUTES.BENUTZER);
     });
   });
 
-  describe('handleSubmitError', () => {
-    it('should return empty stateResource', () => {
-      const result = formService.handleSubmitError();
-
-      expect(result).toBeObservable(cold('(a|)', { a: createEmptyStateResource<User>() }));
-    });
-
+  describe('handle unknown keycloak error', () => {
     it('should show error message', fakeAsync(() => {
-      formService.handleSubmitError().subscribe();
-      tick();
+      service._handleUnknownKeycloakError(createKeycloakHttpErrorResponse());
 
       expect(snackBarService.showError).toHaveBeenCalledWith('Der Benutzer konnte nicht angelegt werden.');
     }));
@@ -445,7 +406,7 @@ describe('UserFormService', () => {
 
   describe('getRoles', () => {
     it('should return no roles when none are active', () => {
-      const result: string[] = formService._getRoles(UserFormService.ALFA_GROUP);
+      const result: string[] = service._getRoles(UserFormService.ALFA_GROUP);
 
       expect(result).toEqual([]);
     });
@@ -453,7 +414,7 @@ describe('UserFormService', () => {
     it('should return poststelle role when active', () => {
       alfaGroup.get(UserFormService.POSTSTELLE).setValue(true);
 
-      const result: string[] = formService._getRoles(UserFormService.ALFA_GROUP);
+      const result: string[] = service._getRoles(UserFormService.ALFA_GROUP);
 
       expect(result).toEqual([UserFormService.POSTSTELLE]);
     });
@@ -461,7 +422,7 @@ describe('UserFormService', () => {
 
   describe('getActiveOrganisationsEinheiten', () => {
     it('should return no groups when none are active', () => {
-      const result: string[] = formService._getActiveOrganisationsEinheiten();
+      const result: string[] = service._getActiveOrganisationsEinheiten();
 
       expect(result).toEqual([]);
     });
@@ -470,7 +431,7 @@ describe('UserFormService', () => {
       const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit();
       organisationsEinheitenGroup.addControl(organisationsEinheit.name, new FormControl(true));
 
-      const result: string[] = formService._getActiveOrganisationsEinheiten();
+      const result: string[] = service._getActiveOrganisationsEinheiten();
 
       expect(result).toEqual([organisationsEinheit.name]);
     });
@@ -478,29 +439,65 @@ describe('UserFormService', () => {
 
   describe('ngOnDestroy', () => {
     it('should unsubscribe from initOrganisationsEinheiten$', () => {
-      formService._initOrganisationsEinheiten$.unsubscribe = jest.fn();
+      service._initOrganisationsEinheiten$.unsubscribe = jest.fn();
 
-      formService.ngOnDestroy();
+      service.ngOnDestroy();
 
-      expect(formService._initOrganisationsEinheiten$.unsubscribe).toHaveBeenCalled();
+      expect(service._initOrganisationsEinheiten$.unsubscribe).toHaveBeenCalled();
     });
 
     it('should unsubscribe from initOrganisationsEinheiten$', () => {
-      formService._alfaGroupChanges.unsubscribe = jest.fn();
+      service._alfaGroupChanges.unsubscribe = jest.fn();
 
-      formService.ngOnDestroy();
+      service.ngOnDestroy();
 
-      expect(formService._alfaGroupChanges.unsubscribe).toHaveBeenCalled();
+      expect(service._alfaGroupChanges.unsubscribe).toHaveBeenCalled();
     });
   });
 
   describe('get userName', () => {
     it('should return form control value of userName', () => {
-      formService.form = new FormGroup({ [UserFormService.USERNAME]: new FormControl('userNameDummy') });
+      service.form = new FormGroup({ [UserFormService.USERNAME]: new FormControl('userNameDummy') });
 
-      const userName: string = formService.getUserName();
+      const userName: string = service.getUserName();
 
       expect(userName).toBe('userNameDummy');
     });
   });
+
+  describe('map keycloak error message to control name', () => {
+    it('should map username is missing error message to username', () => {
+      expect(service._mapKeycloakErrorMessageToControlName(KeycloakErrorMessage.USERNAME_IS_MISSING)).toEqual(
+        UserFormService.USERNAME,
+      );
+    });
+
+    it('should user name already exists error message to username', () => {
+      expect(service._mapKeycloakErrorMessageToControlName(KeycloakErrorMessage.USERNAME_EXISTS)).toEqual(
+        UserFormService.USERNAME,
+      );
+    });
+
+    it('should map email exists error message to email', () => {
+      expect(service._mapKeycloakErrorMessageToControlName(KeycloakErrorMessage.EMAIL_EXISTS)).toEqual(UserFormService.EMAIL);
+    });
+
+    it('should throw error', () => {
+      expect(() => service._mapKeycloakErrorMessageToControlName('unknown' as KeycloakErrorMessage)).toThrowError();
+    });
+  });
+
+  describe('map keycloak field name to control name', () => {
+    it('should map username to username', () => {
+      expect(service._mapKeycloakFieldNameToControlName(KeycloakFieldName.USERNAME)).toEqual(UserFormService.USERNAME);
+    });
+
+    it('should map email to email', () => {
+      expect(service._mapKeycloakFieldNameToControlName(KeycloakFieldName.EMAIL)).toEqual(UserFormService.EMAIL);
+    });
+
+    it('should throw error', () => {
+      expect(() => service._mapKeycloakFieldNameToControlName('unknown' as KeycloakFieldName)).toThrowError();
+    });
+  });
 });
diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
index 666d4d192605be0c6e14cca6008225b86570e1d5..b0cb7867c9cd8330c274fd9ee944d16f9a5507bb 100644
--- a/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
+++ b/alfa-client/libs/admin/user/src/lib/user-form/user.formservice.ts
@@ -24,14 +24,29 @@
 import { AdminOrganisationsEinheit, AdminOrganisationsEinheitService } from '@admin-client/organisations-einheit-shared';
 import { ROUTES } from '@admin-client/shared';
 import { AdminRoles, User, UserService } from '@admin-client/user-shared';
-import { KeycloakFormService, PatchConfig } from '@admin/keycloak-shared';
+import {
+  KeycloakErrorMessage,
+  KeycloakFieldName,
+  KeycloakFormService,
+  KeycloakHttpErrorResponse,
+  PatchConfig,
+} from '@admin/keycloak-shared';
 import { NavigationService } from '@alfa-client/navigation-shared';
-import { createEmptyStateResource, EMPTY_STRING, isLoaded, mapToResource, StateResource } from '@alfa-client/tech-shared';
+import {
+  checkBoxGroupsEmptyValidator,
+  EMPTY_STRING,
+  fieldEmptyValidator,
+  fieldInvalidValidator,
+  fieldLengthValidator,
+  isLoaded,
+  mapToResource,
+  StateResource,
+} from '@alfa-client/tech-shared';
 import { SnackBarService } from '@alfa-client/ui';
 import { Injectable, OnDestroy } from '@angular/core';
-import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup, ValidationErrors, ValidatorFn, Validators, } from '@angular/forms';
+import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 import { UrlSegment } from '@angular/router';
-import { catchError, filter, Observable, of, Subscription, tap } from 'rxjs';
+import { filter, Observable, Subscription, tap } from 'rxjs';
 
 @Injectable()
 export class UserFormService extends KeycloakFormService<User> implements OnDestroy {
@@ -86,10 +101,12 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
 
   _initForm(): UntypedFormGroup {
     return this.formBuilder.group({
-      [UserFormService.FIRST_NAME]: new FormControl(EMPTY_STRING, Validators.required),
-      [UserFormService.LAST_NAME]: new FormControl(EMPTY_STRING, Validators.required),
-      [UserFormService.USERNAME]: new FormControl(EMPTY_STRING, Validators.required),
-      [UserFormService.EMAIL]: new FormControl(EMPTY_STRING, [Validators.required]),
+      [UserFormService.FIRST_NAME]: new FormControl(EMPTY_STRING, fieldEmptyValidator(UserFormService.FIRST_NAME)),
+      [UserFormService.LAST_NAME]: new FormControl(EMPTY_STRING, fieldEmptyValidator(UserFormService.LAST_NAME)),
+      [UserFormService.USERNAME]: new FormControl(EMPTY_STRING, [fieldLengthValidator(UserFormService.USERNAME, 3, 255)]),
+      [UserFormService.EMAIL]: new FormControl(EMPTY_STRING, [
+        fieldInvalidValidator(UserFormService.EMAIL, /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/),
+      ]),
       [UserFormService.CLIENT_ROLES]: this.formBuilder.group(
         {
           [UserFormService.ADMINISTRATION_GROUP]: this.formBuilder.group({
@@ -102,33 +119,17 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
             [UserFormService.POSTSTELLE]: new FormControl(false),
           }),
         },
-        { validators: this.roleValidator() },
+        {
+          validators: checkBoxGroupsEmptyValidator(UserFormService.CLIENT_ROLES, [
+            UserFormService.ADMINISTRATION_GROUP,
+            UserFormService.ALFA_GROUP,
+          ]),
+        },
       ),
       [UserFormService.GROUPS]: this.formBuilder.group({}),
     });
   }
 
-  roleValidator(): ValidatorFn {
-    return (control: AbstractControl<UntypedFormGroup>): ValidationErrors | null => {
-      const rolesGroups: UntypedFormGroup = control.value;
-
-      if (this.anyRoleSelected(rolesGroups)) {
-        return null;
-      }
-
-      return { atLeastOneRoleSelected: true };
-    };
-  }
-
-  private anyRoleSelected(rolesGroups: UntypedFormGroup): boolean {
-    for (const subGroup of Object.values(rolesGroups)) {
-      if (Object.values(subGroup).includes(true)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   _initOrganisationsEinheiten(): Observable<AdminOrganisationsEinheit[]> {
     const organisationsEinheitenGroup: UntypedFormGroup = this.getOrganisationsEinheitenGroup();
     return this.adminOrganisationsEinheitService.getAll().pipe(
@@ -190,16 +191,11 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
   }
 
   _doSubmit(): Observable<StateResource<User>> {
-    if (this.isInvalid()) {
-      return of(createEmptyStateResource<User>());
-    }
-
     const user: User = this._createUser();
     return this._createOrSave(user).pipe(
       tap((state: StateResource<User>): void => {
         if (!state.loading) this.handleOnCreateUserSuccess();
       }),
-      catchError((): Observable<StateResource<User>> => this.handleSubmitError()),
     );
   }
 
@@ -220,11 +216,6 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
     this.navigationService.navigate(ROUTES.BENUTZER);
   }
 
-  handleSubmitError(): Observable<StateResource<User>> {
-    this.snackBarService.showError('Der Benutzer konnte nicht angelegt werden.');
-    return of(createEmptyStateResource<User>());
-  }
-
   _createUser(): User {
     return {
       ...this._getFormValue(),
@@ -265,4 +256,33 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
   public getUserName(): string {
     return this.form.get(UserFormService.USERNAME).value;
   }
+
+  _mapKeycloakErrorMessageToControlName(keycloakErrorMessage: KeycloakErrorMessage): string {
+    switch (keycloakErrorMessage) {
+      case KeycloakErrorMessage.USERNAME_IS_MISSING:
+      case KeycloakErrorMessage.USERNAME_EXISTS:
+        return UserFormService.USERNAME;
+      case KeycloakErrorMessage.EMAIL_EXISTS:
+        return UserFormService.EMAIL;
+      default:
+        throw new Error(`Cannot map keycloak error message ${keycloakErrorMessage} to control name`);
+    }
+  }
+
+  _mapKeycloakFieldNameToControlName(keycloakFieldName: KeycloakFieldName): string {
+    switch (keycloakFieldName) {
+      case KeycloakFieldName.USERNAME:
+        return UserFormService.USERNAME;
+      case KeycloakFieldName.EMAIL:
+        return UserFormService.EMAIL;
+      default:
+        throw new Error(`Cannot map keycloak field name ${keycloakFieldName} to control name`);
+    }
+  }
+
+  _handleUnknownKeycloakError(error: KeycloakHttpErrorResponse): void {
+    this.snackBarService.showError(
+      this.isPatch() ? 'Der Benutzer konnte nicht gespeichert werden.' : 'Der Benutzer konnte nicht angelegt werden.',
+    );
+  }
 }
diff --git a/alfa-client/libs/design-component/src/index.ts b/alfa-client/libs/design-component/src/index.ts
index 9eec827c3a019115cc42c751521816fdde5c87a7..27e3b6381b8dc83a99533d4440b7fac8845e9f48 100644
--- a/alfa-client/libs/design-component/src/index.ts
+++ b/alfa-client/libs/design-component/src/index.ts
@@ -31,6 +31,7 @@ export * from './lib/form/formcontrol-editor.abstract.component';
 export * from './lib/form/single-file-upload-editor/single-file-upload-editor.component';
 export * from './lib/form/text-editor/text-editor.component';
 export * from './lib/form/textarea-editor/textarea-editor.component';
+export * from './lib/form/validation-error/validation-error.component';
 export * from './lib/open-dialog-button/open-dialog-button.component';
 export * from './lib/routing-button/routing-button.component';
 export * from './lib/spinner/spinner.component';
diff --git a/alfa-client/libs/design-component/src/lib/button-with-spinner/button-with-spinner.component.ts b/alfa-client/libs/design-component/src/lib/button-with-spinner/button-with-spinner.component.ts
index 1a97d12351e15c734c2108bf255e4f6836b31d08..b92ebcbbb4afa26d585285c50c807934c3ea735f 100644
--- a/alfa-client/libs/design-component/src/lib/button-with-spinner/button-with-spinner.component.ts
+++ b/alfa-client/libs/design-component/src/lib/button-with-spinner/button-with-spinner.component.ts
@@ -49,7 +49,7 @@ type ButtonVariants = VariantProps<typeof buttonVariants>;
       [dataTestId]="dataTestId"
       [isLoading]="isLoading"
       [disabled]="disabled"
-      (click)="clickEmitter.emit()"
+      (clickEmitter)="clickEmitter.emit()"
     >
       <ng-content icon select="[icon]" />
     </ods-button>`,
diff --git a/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts
index df1f5fa4b1732a9b60879f6353ec9f006832cb5f..03ac3acbe39b7907df7af242da47d4d3f70a5fa3 100644
--- a/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts
+++ b/alfa-client/libs/design-component/src/lib/cancel-dialog-button/cancel-dialog-button.component.ts
@@ -27,7 +27,11 @@ import { ButtonComponent, CloseIconComponent, TooltipDirective } from '@ods/syst
         text="Abbrechen"
         dataTestId="cancel-dialog-button"
         data-test-id="cancel-dialog-button-container"
-      />
+      >
+        <ng-container icon>
+          <ods-close-icon class="fill-primary" />
+        </ng-container>
+      </ods-button>
     }`,
 })
 export class CancelDialogButtonComponent {
diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
index 2533dc1ed4b63eec0da6aac3d7969a8b34172740..1cc8b07470d8086854cee7b310342c8697b4eed5 100644
--- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.html
@@ -29,9 +29,11 @@
   [label]="label"
   [disabled]="control.disabled"
   [hasError]="hasError"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
index dfab859671455c3354bd14297d069ffde5474cde..bbb9f693fc54a9760f0bb33b006848b12aba6554 100644
--- a/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/checkbox-editor/checkbox-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 import { CheckboxComponent } from '@ods/system';
@@ -38,6 +38,8 @@ export class CheckboxEditorComponent extends FormControlEditorAbstractComponent
   @Input() inputId: string;
   @Input() label: string;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get hasError(): boolean {
     return this.invalidParams.length > 0;
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
index c4c009107436b4b7a8837c0fa9c31c4add1008d6..3c116fb0b27c3f1b3b24807e7a0e26affb514b00 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
@@ -34,9 +34,11 @@
   [required]="isRequired"
   [focus]="focus"
   [showLabel]="showLabel"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="dataTestId + '-text-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
index 45f7c42c8f3f609567e89d3075411ac652eb8882..19ae4d4d435edb97ec43d2512eda5f6ab371c7aa 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
@@ -44,6 +44,8 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent {
   @Input() showLabel: boolean = true;
   @Input() dataTestId: string;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get variant(): string {
     return this.invalidParams.length > 0 ? 'error' : 'default';
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
index 39ac3109643ea6771e5199ba3e9bba49cef69c37..753a2c385a768d890c272847d220b89f8de34c9e 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
@@ -34,9 +34,11 @@
   [focus]="focus"
   [isResizable]="isResizable"
   [showLabel]="showLabel"
+  [ariaDescribedBy]="validationErrorId"
 >
   <ods-validation-error
     error
+    [id]="validationErrorId"
     [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-textarea-editor-error'"
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
index c7032ef03b58627369f4857d0b2bda4546d092b5..9203da053f1a13a071244c009c6516c213de5434 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
@@ -44,6 +44,8 @@ export class TextareaEditorComponent extends FormControlEditorAbstractComponent
   @Input() isResizable: boolean = true;
   @Input() showLabel: boolean = true;
 
+  public readonly validationErrorId: string = generateValidationErrorId();
+
   get variant(): string {
     return this.invalidParams.length > 0 ? 'error' : 'default';
   }
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
index 224dbe8cc5d73df87e45d77700b0ffca1efdb03e..cb566bafe0c5a5a804ae3e10491c0ca19faf4c40 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
@@ -24,5 +24,5 @@
 
 -->
 <ng-container *ngFor="let invalidParam of invalidParams"
-  ><ods-error-message [text]="message(invalidParam)"></ods-error-message
+  ><ods-error-message [id]="id" [text]="message(invalidParam)"></ods-error-message
 ></ng-container>
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
index 8ae6e949f8ed75b49c309609d0ff9419acb46582..fca8d2a2ecfbec56310a3ec835f03459edb4ec27 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared';
+import { getMessageForInvalidParam, InvalidParam } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ErrorMessageComponent } from '@ods/system';
@@ -33,6 +33,7 @@ import { ErrorMessageComponent } from '@ods/system';
   templateUrl: './validation-error.component.html',
 })
 export class ValidationErrorComponent {
+  @Input({ required: true }) id: string;
   @Input() label: string;
   @Input() invalidParams: InvalidParam[];
 
diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts
index d05d44db8ce46777152a6f48145ac2000dd5d0a3..c17fdb29f53e0948d6f7b3a61d0d3725c18e2ffc 100644
--- a/alfa-client/libs/design-system/src/index.ts
+++ b/alfa-client/libs/design-system/src/index.ts
@@ -28,6 +28,7 @@ export * from './lib/bescheid-status-text/bescheid-status-text.component';
 export * from './lib/bescheid-wrapper/bescheid-wrapper.component';
 export * from './lib/button-card/button-card.component';
 export * from './lib/button/button.component';
+export * from './lib/dialog-container/dialog-container.component';
 export * from './lib/dropdown-menu/dropdown-menu-button-item/dropdown-menu-button-item.component';
 export * from './lib/dropdown-menu/dropdown-menu-item/dropdown-menu-item.component';
 export * from './lib/dropdown-menu/dropdown-menu-link-item/dropdown-menu-link-item.component';
@@ -41,6 +42,7 @@ export * from './lib/form/file-upload-button/file-upload-button.component';
 export * from './lib/form/radio-button-card/radio-button-card.component';
 export * from './lib/form/text-input/text-input.component';
 export * from './lib/form/textarea/textarea.component';
+export * from './lib/forwarding-item/forwarding-item-info/forwarding-item-info.component';
 export * from './lib/forwarding-item/forwarding-item.component';
 export * from './lib/icons/accessibility-icon/accessibility-icon.component';
 export * from './lib/icons/account-circle-icon/account-circle-icon.component';
diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.spec.ts b/alfa-client/libs/design-system/src/lib/button/button.component.spec.ts
index ecd327e0aa7ec13701a789698d80c163ea5d3dfa..fc5322e9e5371ac6f5a6dcc897224fce9b0a4c5e 100644
--- a/alfa-client/libs/design-system/src/lib/button/button.component.spec.ts
+++ b/alfa-client/libs/design-system/src/lib/button/button.component.spec.ts
@@ -41,4 +41,26 @@ describe('ButtonComponent', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
   });
+
+  describe('component', () => {
+    beforeEach(() => {
+      component.clickEmitter.emit = jest.fn();
+    });
+
+    describe('onClick', () => {
+      it('should emit click', () => {
+        component.onClick();
+
+        expect(component.clickEmitter.emit).toHaveBeenCalled();
+      });
+
+      it('should NOT emit click if button is disabled', () => {
+        component.disabled = true;
+
+        component.onClick();
+
+        expect(component.clickEmitter.emit).not.toHaveBeenCalled();
+      });
+    });
+  });
 });
diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.ts b/alfa-client/libs/design-system/src/lib/button/button.component.ts
index 47e76172adb5e6f9600be54291ee79a841defb78..3f88a32abc1bdb41fc4c12cabc4543f7c2def3c1 100644
--- a/alfa-client/libs/design-system/src/lib/button/button.component.ts
+++ b/alfa-client/libs/design-system/src/lib/button/button.component.ts
@@ -31,18 +31,18 @@ import { SpinnerIconComponent } from '../icons/spinner-icon/spinner-icon.compone
 export const buttonVariants = cva(
   [
     'flex items-center gap-3 rounded-lg text-sm font-medium box-border',
-    'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
+    'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 outline-focus',
   ],
   {
     variants: {
       variant: {
-        primary: 'bg-primary text-whitetext shadow-md hover:enabled:bg-primary-hover focus-visible:bg-primary-hover',
+        primary: 'bg-primary text-whitetext shadow-md hover:bg-primary-hover focus-visible:bg-primary-hover',
         outline:
-          'border border-primary bg-background-50 text-primary shadow-md hover:enabled:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200',
+          'border border-primary bg-background-50 text-primary shadow-md hover:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200',
         outline_error:
-          'border border-error bg-background-50 text-error shadow-md hover:enabled:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200',
+          'border border-error bg-background-50 text-error shadow-md hover:bg-ghost-hover focus-visible:bg-ghost-hover focus-visible:border-background-200',
         ghost:
-          'border border-transparent hover:enabled:bg-ghost-hover text-primary focus-visible:border-background-200 focus-visible:bg-ghost-hover font-semibold [&]:focus-visible:outline-offset-1',
+          'border border-transparent hover:bg-ghost-hover text-primary focus-visible:border-background-200 focus-visible:bg-ghost-hover font-semibold [&]:focus-visible:outline-offset-1',
       },
       size: {
         medium: 'h-9 py-2 px-4 min-w-32',
@@ -50,11 +50,11 @@ export const buttonVariants = cva(
       },
       disabled: {
         false: null,
-        true: ['opacity-70', 'cursor-not-allowed'],
+        true: '[&]:outline-disabled-button cursor-not-allowed',
       },
       destructive: {
-        false: 'outline-focus',
-        true: 'outline-destructive',
+        false: null,
+        true: '[&]:outline-destructive',
       },
     },
     defaultVariants: {
@@ -66,19 +66,34 @@ export const buttonVariants = cva(
       {
         variant: 'primary',
         destructive: true,
-        class:
-          '[&]:hover:enabled:bg-destructive-primary-hover [&]:bg-destructive [&]:outline-destructive [&]:focus-visible:bg-destructive-primary-hover',
+        class: '[&]:hover:bg-destructive-primary-hover [&]:bg-destructive [&]:focus-visible:bg-destructive-primary-hover',
       },
       {
         variant: 'outline',
         destructive: true,
         class:
-          '[&]:border-destructive [&]:text-destructive [&]:hover:enabled:bg-destructive-hover [&]:focus-visible:bg-destructive-hover',
+          '[&]:border-destructive [&]:text-destructive [&]:hover:bg-destructive-hover [&]:focus-visible:bg-destructive-hover',
       },
       {
         variant: 'ghost',
         destructive: true,
-        class: '[&]:text-destructive [&]:hover:enabled:bg-destructive-hover [&]:focus-visible:bg-destructive-hover',
+        class: '[&]:text-destructive [&]:hover:bg-destructive-hover [&]:focus-visible:bg-destructive-hover',
+      },
+      {
+        variant: 'primary',
+        disabled: true,
+        class: '[&]:bg-disabled-button [&]:hover:bg-disabled-button/90 [&]:focus-visible:bg-disabled-button/90',
+      },
+      {
+        variant: 'outline',
+        disabled: true,
+        class:
+          '[&]:text-disabled-button [&]:border-disabled-button [&]:hover:bg-disabled-button/10 [&]:focus-visible:bg-disabled-button/10',
+      },
+      {
+        variant: 'ghost',
+        disabled: true,
+        class: '[&]:text-disabled-button [&]:hover:bg-disabled-button/10 [&]:focus-visible:bg-disabled-button/10',
       },
     ],
   },
@@ -89,27 +104,31 @@ export type ButtonVariants = VariantProps<typeof buttonVariants>;
   selector: 'ods-button',
   standalone: true,
   imports: [CommonModule, SpinnerIconComponent],
-  template: ` <button
+  template: `<button
     type="button"
-    [ngClass]="buttonVariants({ size, variant, disabled, destructive })"
-    [disabled]="isDisabled"
+    [ngClass]="buttonVariants({ size, variant, disabled: isDisabled, destructive })"
     [attr.aria-disabled]="isDisabled"
     [attr.aria-label]="text"
     [attr.data-test-id]="dataTestId"
     [attr.data-test-class]="dataTestClass"
-    (click)="clickEmitter.emit()"
+    (click)="onClick()"
   >
-    <ng-content *ngIf="!isLoading" select="[icon]"></ng-content>
-    <ods-spinner-icon *ngIf="isLoading" [size]="spinnerSize" data-test-class="spinner"></ods-spinner-icon>
-    <div *ngIf="text" class="flex-grow">{{ text }}</div>
+    @if (isLoading) {
+      <ods-spinner-icon [class]="isDisabled && 'fill-disabled-button'" [size]="spinnerSize" data-test-class="spinner" />
+    } @else {
+      <ng-content select="[icon]" />
+    }
+    @if (text) {
+      <p class="flex-grow">{{ text }}</p>
+    }
   </button>`,
 })
 export class ButtonComponent {
   @Input() text: string = '';
   @Input() dataTestId: string = '';
   @Input() dataTestClass: string = '';
-  @Input() disabled: boolean = false;
-  @Input() isLoading: boolean = false;
+  @Input({ transform: booleanAttribute }) disabled: boolean = false;
+  @Input({ transform: booleanAttribute }) isLoading: boolean = false;
   @Input({ transform: booleanAttribute }) destructive: boolean = false;
   @Input() variant: ButtonVariants['variant'];
   @Input() size: ButtonVariants['size'];
@@ -121,5 +140,11 @@ export class ButtonComponent {
     return this.disabled || this.isLoading;
   }
 
+  public onClick(): void {
+    if (!this.isDisabled) {
+      this.clickEmitter.emit();
+    }
+  }
+
   readonly buttonVariants = buttonVariants;
 }
diff --git a/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.spec.ts b/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d1f894406fa1f6c9468c1d7ba356a56947a05c25
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DialogContainerComponent } from './dialog-container.component';
+
+describe('DialogContainerComponent', () => {
+  let component: DialogContainerComponent;
+  let fixture: ComponentFixture<DialogContainerComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DialogContainerComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(DialogContainerComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.ts b/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3df7faf24a83fb4ed3965b741004cdc53aaeabb6
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/dialog-container/dialog-container.component.ts
@@ -0,0 +1,15 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'ods-dialog-container',
+  standalone: true,
+  imports: [],
+  template: `
+    <div class="static flex w-[calc(100vw-2rem)] justify-center">
+      <div class="flex max-w-4xl grow flex-col rounded-lg bg-background-50 p-6 shadow-md">
+        <ng-content />
+      </div>
+    </div>
+  `,
+})
+export class DialogContainerComponent {}
diff --git a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
index ffadf796c9031418fda5463896a1452351f2e109..3b3750bd3174bfaeecf2821d4dc29d9e5c6a4d4e 100644
--- a/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/checkbox/checkbox.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { ConvertForDataTestPipe, EMPTY_STRING } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { FormControl, ReactiveFormsModule } from '@angular/forms';
@@ -45,6 +45,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
           [attr.id]="inputId"
           [attr.disabled]="disabled ? true : null"
           [attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor'"
+          [attr.aria-describedby]="ariaDescribedBy"
         />
         <label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label>
         <svg
@@ -69,4 +70,5 @@ export class CheckboxComponent {
   @Input() label: string;
   @Input() disabled: boolean = false;
   @Input() hasError: boolean = false;
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 }
diff --git a/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts b/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
index 66ed08e7f973dad14e092c899de2880ff7d8f515..7e8cc5ae0ed07c1d6de152f018842bb9f05ab11c 100644
--- a/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/error-message/error-message.component.ts
@@ -21,6 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamation-icon.component';
@@ -31,13 +32,14 @@ import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamati
   imports: [CommonModule, ExclamationIconComponent],
   styles: [':host {@apply flex text-error my-2 text-sm items-center font-medium}'],
   template: `<ods-exclamation-icon class="mr-1"></ods-exclamation-icon>
-    <div class="flex-grow break-all">
+    <div class="flex-grow break-words" [id]="id">
       {{ text }}
       <br *ngIf="subText" aria-hidden="true" />
       {{ subText }}
     </div> `,
 })
 export class ErrorMessageComponent {
+  @Input() id: string = EMPTY_STRING;
   @Input({ required: true }) text!: string;
   @Input() subText: string = '';
 }
diff --git a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
index cd01ba0190c68b4f9fec34f3e94496e6d86f73aa..c84948d1e2db9eba1d7cd0071f193f1010ad825e 100644
--- a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts
@@ -55,29 +55,31 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
   template: `
     <div class="relative">
       <label *ngIf="showLabel" [for]="id" class="text-md mb-2 block font-medium text-text">
-        {{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container>
+        {{ inputLabel }}
+        <ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container>
       </label>
-      <div class="mt-2">
-        <div *ngIf="withPrefix" class="pointer-events-none absolute bottom-2 left-2 flex size-6 items-center justify-center">
-          <ng-content select="[prefix]" />
-        </div>
-        <input
-          type="text"
-          [id]="id"
-          [formControl]="fieldControl"
-          [ngClass]="[textInputVariants({ variant }), withPrefix ? 'pl-10' : '', withSuffix ? 'pr-10' : '']"
-          [placeholder]="placeholder"
-          [autocomplete]="autocomplete"
-          [attr.aria-required]="required"
-          [attr.aria-invalid]="variant === 'error'"
-          [attr.data-test-id]="_dataTestId + '-text-input'"
-          (click)="clickEmitter.emit()"
-          #inputElement
-        />
-        <div *ngIf="withSuffix" class="absolute bottom-2 right-2 flex size-6 items-center justify-center">
-          <ng-content select="[suffix]" />
-        </div>
+
+      <div *ngIf="withPrefix" class="pointer-events-none absolute bottom-2 left-2 flex size-6 items-center justify-center">
+        <ng-content select="[prefix]"/>
+      </div>
+      <input
+        type="text"
+        [id]="id"
+        [formControl]="fieldControl"
+        [ngClass]="[textInputVariants({ variant }), withPrefix ? 'pl-10' : '', withSuffix ? 'pr-10' : '']"
+        [placeholder]="placeholder"
+        [autocomplete]="autocomplete"
+        [attr.aria-required]="required"
+        [attr.aria-invalid]="variant === 'error'"
+        [attr.data-test-id]="_dataTestId + '-text-input'"
+        [attr.aria-describedby]="ariaDescribedBy"
+        (click)="clickEmitter.emit()"
+        #inputElement
+      />
+      <div *ngIf="withSuffix" class="absolute bottom-2 right-2 flex size-6 items-center justify-center">
+        <ng-content select="[suffix]"/>
       </div>
+
       <ng-content select="[error]"></ng-content>
     </div>
   `,
@@ -102,6 +104,7 @@ export class TextInputComponent implements AfterViewInit {
   @Input() set dataTestId(value: string) {
     if (isNotUndefined(value)) this._dataTestId = value;
   }
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 
   @Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
 
diff --git a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
index f3eb84af9d638567a3bcbdc644b400abf344643e..d3c04b6c82bb491b5f25652bd89e6af37595dd85 100644
--- a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
+++ b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts
@@ -57,6 +57,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
         {{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container>
       </label>
       <textarea
+        #textAreaElement
         [id]="id"
         [formControl]="fieldControl"
         [rows]="rows"
@@ -66,7 +67,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
         [attr.aria-required]="required"
         [attr.aria-invalid]="variant === 'error'"
         [attr.data-test-id]="(inputLabel | convertForDataTest) + '-textarea'"
-        #textAreaElement
+        [attr.aria-describedby]="ariaDescribedBy"
       ></textarea>
       <ng-content select="[error]"></ng-content>
     </div>
@@ -94,6 +95,7 @@ export class TextareaComponent {
       this.textAreaElement.nativeElement.focus();
     }
   }
+  @Input() ariaDescribedBy: string = EMPTY_STRING;
 
   inputLabel: string;
   id: string;
diff --git a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.spec.ts b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa29de43383f328ce1179743c2fb6bbd0b435954
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ForwardingItemInfoComponent } from './forwarding-item-info.component';
+
+describe('ForwardingItemInfoComponent', () => {
+  let component: ForwardingItemInfoComponent;
+  let fixture: ComponentFixture<ForwardingItemInfoComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ForwardingItemInfoComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ForwardingItemInfoComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.ts b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..abace4dec64981bfc6fd2e5cd8e3c66abeca881e
--- /dev/null
+++ b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item-info/forwarding-item-info.component.ts
@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'ods-forwarding-item-info',
+  standalone: true,
+  template: `<p class="font-medium">{{ label }}</p>
+    <p>{{ address }}</p>`,
+})
+export class ForwardingItemInfoComponent {
+  @Input({ required: true }) label!: string;
+  @Input({ required: true }) address!: string;
+}
diff --git a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.component.ts b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.component.ts
index 7f8ebfd800c662910c5ec0905935c3d12487b7f3..e1322cfcdef6610df2a458541bcd3436fc12d7d2 100644
--- a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.component.ts
+++ b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.component.ts
@@ -21,7 +21,6 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ForwardVorgangIconComponent } from '../icons/forward-vorgang-icon/forward-vorgang-icon.component';
 
@@ -33,25 +32,18 @@ export enum ForwardingDirection {
 @Component({
   selector: 'ods-forwarding-item',
   standalone: true,
-  imports: [CommonModule, ForwardVorgangIconComponent],
-  template: `<div
-    class="flex flex-col items-start justify-between gap-2 rounded-lg border border-grayborder p-3 md:flex-row md:items-center md:gap-0"
-  >
-    <div class="flex gap-3">
-      <ods-forward-vorgang-icon class="fill-text" />
-      <p class="text-gray-500">{{ direction }}:</p>
-      <div>
-        <p class="font-medium">{{ label }}</p>
-        <p>{{ address }}</p>
+  imports: [ForwardVorgangIconComponent],
+  template: ` <div class="h-full rounded-lg border border-grayborder">
+    <div class="flex flex-col gap-3 p-3 align-top sm:flex-row">
+      <div class="flex gap-3">
+        <ods-forward-vorgang-icon class="fill-text" />
+        <div class="font-medium text-gray-500">{{ direction }}:</div>
       </div>
-    </div>
-    <div class="text-end empty:hidden">
-      <ng-content />
+      <div class="grow"><ng-content /></div>
+      <div class="flex items-center"><ng-content select="[end-content]" /></div>
     </div>
   </div>`,
 })
 export class ForwardingItemComponent {
-  @Input({ required: true }) label!: string;
-  @Input({ required: true }) address!: string;
   @Input() direction: ForwardingDirection = ForwardingDirection.TO;
 }
diff --git a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.stories.ts b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.stories.ts
index 53d2116e9f314c9e2194cd8a9ff7c72e39dd1b12..d70b202b1147e34a030258198844ac19afc7b1b6 100644
--- a/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.stories.ts
+++ b/alfa-client/libs/design-system/src/lib/forwarding-item/forwarding-item.stories.ts
@@ -24,11 +24,9 @@
 import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
 
 import { ButtonComponent } from '../button/button.component';
+import { ForwardingItemInfoComponent } from './forwarding-item-info/forwarding-item-info.component';
 import { ForwardingDirection, ForwardingItemComponent } from './forwarding-item.component';
 
-const label: string = 'Bund für Umwelt und Naturschutz Kreisgruppe Kiel';
-const address: string = 'Kaiserstraße 25, 12443 Kiel';
-
 const meta: Meta<ForwardingItemComponent> = {
   title: 'Forwarding item',
   component: ForwardingItemComponent,
@@ -41,7 +39,7 @@ const meta: Meta<ForwardingItemComponent> = {
   },
   decorators: [
     moduleMetadata({
-      imports: [ForwardingItemComponent, ButtonComponent],
+      imports: [ForwardingItemComponent, ForwardingItemInfoComponent, ButtonComponent],
     }),
   ],
   excludeStories: /.*Data$/,
@@ -53,40 +51,39 @@ type Story = StoryObj<ForwardingItemComponent>;
 
 export const Default: Story = {
   args: {
-    label: label,
-    address: address,
     direction: ForwardingDirection.TO,
   },
   argTypes: {
     direction: { control: 'select', options: [ForwardingDirection.TO, ForwardingDirection.FROM] },
   },
+  render: () => ({
+    template: `<ods-forwarding-item>
+        <ods-forwarding-item-info label="Bund für Umwelt und Naturschutz Kreisgruppe Kiel" address="Kaiserstraße 25, 12443 Kiel" />
+      </ods-forwarding-item>`,
+  }),
 };
 
 export const WithButton: Story = {
-  args: {
-    label: label,
-    address: address,
-  },
-
-  render: (args: ForwardingItemComponent) => ({
-    props: args,
-    template: `<ods-forwarding-item ${argsToTemplate(args)}>
-        <ods-button variant="outline" text="Stelle ändern" />
+  render: () => ({
+    template: `<ods-forwarding-item>
+        <ods-forwarding-item-info label="Bund für Umwelt und Naturschutz Kreisgruppe Kiel" address="Kaiserstraße 25, 12443 Kiel" />
+        <ods-button variant="outline" text="Stelle ändern" end-content />
       </ods-forwarding-item>`,
   }),
 };
 
 export const WithCreationInfo: Story = {
   args: {
-    label: label,
-    address: address,
     direction: ForwardingDirection.FROM,
   },
   render: (args: ForwardingItemComponent) => ({
     props: args,
     template: `<ods-forwarding-item ${argsToTemplate(args)}>
-        <p>20. Dez. 09:35</p>
-        <p class="text-sm">Karin Wanowski-Müller</p>
+        <ods-forwarding-item-info label="Bund für Umwelt und Naturschutz Kreisgruppe Kiel" address="Kaiserstraße 25, 12443 Kiel" />
+        <div end-content>
+          <p>20. Dez. 09:35</p>
+          <p class="text-sm">Karin Wanowski-Müller</p>
+        </div>
       </ods-forwarding-item>`,
   }),
 };
diff --git a/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts
index d361292b08e026c2f05e245276dae53b82fa2805..20ca2f1c2affe64e1a2aab8b81576c2c40a68e3e 100644
--- a/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts
+++ b/alfa-client/libs/design-system/src/lib/icons/spinner-icon/spinner-icon.component.ts
@@ -24,6 +24,7 @@
 import { NgClass } from '@angular/common';
 import { Component, Input } from '@angular/core';
 
+import { twMerge } from 'tailwind-merge';
 import { IconVariants, iconVariants } from '../iconVariants';
 
 @Component({
@@ -33,8 +34,7 @@ import { IconVariants, iconVariants } from '../iconVariants';
   template: `
     <svg
       xmlns="http://www.w3.org/2000/svg"
-      [ngClass]="iconVariants({ size })"
-      class="animate-spin fill-primary text-gray-200 dark:text-gray-600"
+      [ngClass]="twMerge('animate-spin fill-primary text-gray-200 dark:text-gray-600', iconVariants({ size }), class)"
       aria-hidden="true"
       viewBox="0 0 100 100"
       fill="none"
@@ -53,6 +53,8 @@ import { IconVariants, iconVariants } from '../iconVariants';
 })
 export class SpinnerIconComponent {
   @Input() size: IconVariants['size'] = 'full';
+  @Input() class: string;
 
-  iconVariants = iconVariants;
+  readonly iconVariants = iconVariants;
+  readonly twMerge = twMerge;
 }
diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts
index 48a8a502165e5795ba32bd93485def62486d4de9..73cf86fa263ec18bad66efdb06b5ad28e6b48041 100644
--- a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts
+++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts
@@ -71,7 +71,7 @@ import { InstantSearchQuery, InstantSearchResult } from './instant-search.model'
     <ods-aria-live-region [text]="ariaLiveText" />
     <ods-search-result-layer
       *ngIf="results.length && areResultsVisible"
-      containerClass="absolute z-50 mt-3 max-h-[calc(50vh)] w-full overflow-y-auto"
+      containerClass="absolute z-50 mt-3 max-h-[calc(37vh)] w-full overflow-y-auto"
       id="results"
     >
       <ods-search-result-header *ngIf="headerText" [text]="headerText" [count]="results.length" header />
diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts
index 3f5364877b5c08806ff6990851a7863abdcd74a5..3d765154891b5f39c3168a8a07e39c973eee840b 100644
--- a/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts
+++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts
@@ -32,7 +32,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@
     *ngIf="title"
     [ngClass]="[
       'flex w-full justify-between border-2 border-transparent px-6 py-3',
-      'hover:border-focus focus:border-focus focus:outline-none',
+      'hover:bg-background-150 focus:border-focus focus:outline-none',
     ]"
     role="listitem"
     tabindex="-1"
diff --git a/alfa-client/libs/design-system/src/lib/list/list.component.ts b/alfa-client/libs/design-system/src/lib/list/list.component.ts
index 95c4bd3e7251e650b4a34670ebee420ee7f57f5f..3621cbb31fc7848a1b986a72ed3ad3521cc7b385 100644
--- a/alfa-client/libs/design-system/src/lib/list/list.component.ts
+++ b/alfa-client/libs/design-system/src/lib/list/list.component.ts
@@ -23,12 +23,11 @@
  */
 import { CommonModule } from '@angular/common';
 import { Component } from '@angular/core';
-import { ListItemComponent } from './list-item/list-item.component';
 
 @Component({
   selector: 'ods-list',
   standalone: true,
-  imports: [CommonModule, ListItemComponent],
+  imports: [CommonModule],
   template: `
     <ul class="divide-y divide-gray-300 rounded-md bg-background-50 text-text shadow-sm ring-1 ring-gray-300 empty:hidden">
       <ng-content />
diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css b/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css
index a3c8f7b01b5e1b8342761bf3190841ef059b5cc8..333257d60e54034a148f8f864ae42132d1a7643e 100644
--- a/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css
+++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css
@@ -18,6 +18,7 @@
 
   --color-disabled: 206 14% 95%;
   --color-disabled-dark: 208 12% 65%;
+  --color-disabled-button: 0 0% 42%;
 
   --color-destructive: 360, 71%, 49%, 1;
   --color-destructive-hover: 360, 71%, 49%, 0.07;
@@ -59,6 +60,7 @@
 
   --color-disabled: 206 14% 15%;
   --color-disabled-dark: 208 12% 33%;
+  --color-disabled-button: 0 0% 68%;
 
   --color-destructive: 360, 71%, 49%, 1;
   --color-destructive-hover: 360, 71%, 49%, 0.2;
diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
index cf2abfcb2f32821bcb410f4d9bcd817f2aa2efb0..9c5ba7a4e53f673e8ac6c1b27e08f63936647d7f 100644
--- a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
+++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js
@@ -146,6 +146,7 @@ module.exports = {
         focus: 'hsl(var(--color-focus))',
         disabled: {
           dark: 'hsl(var(--color-disabled-dark) / <alpha-value>)',
+          button: 'hsl(var(--color-disabled-button) / <alpha-value>)',
           DEFAULT: 'hsl(var(--color-disabled) / <alpha-value>)',
         },
         destructive: 'hsla(var(--color-destructive))',
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.html
index 5bf44a14c8978d8fea1e07d51b0ccc1279935ca4..6995cd3356290db9eb8f6011c21e013009b29734 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.html
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.html
@@ -1,11 +1,12 @@
 <ods-button-with-spinner
   [stateResource]="stateResource"
   [disabled]="disabled"
+  [tooltip]="tooltip"
   (clickEmitter)="clickEmitter.emit()"
-  text="Weiterleiten"
-  variant="outline"
+  text="Jetzt weiterleiten"
+  tooltipPosition="above"
   dataTestId="forwarding-dialog-forwarding-button"
   data-test-id="forwarding-button-container"
 >
-  <ods-forward-vorgang-icon icon class="fill-primary" />
+  <ods-forward-vorgang-icon icon class="fill-whitetext" />
 </ods-button-with-spinner>
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.spec.ts
index 9e9a9d2118e1a9758f7ba58715f822822ead4cbf..661105c1ad208f595ae5b3f7f1158e0529d9fe2f 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.spec.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.spec.ts
@@ -1,3 +1,4 @@
+import { EMPTY_STRING } from '@alfa-client/tech-shared';
 import { dispatchEventFromFixture, MockEvent } from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ButtonWithSpinnerComponent } from '@ods/component';
@@ -27,13 +28,31 @@ describe('ForwardingButtonComponent', () => {
     expect(component).toBeTruthy();
   });
 
-  describe('on button click', () => {
-    it('should emit', () => {
-      component.clickEmitter.emit = jest.fn();
+  describe('component', () => {
+    describe('set disabled', () => {
+      it('should set tooltip text', () => {
+        component.disabled = true;
 
-      dispatchEventFromFixture(fixture, button, MockEvent.CLICK);
+        expect(component.tooltip).not.toBe(EMPTY_STRING);
+      });
 
-      expect(component.clickEmitter.emit).toHaveBeenCalled();
+      it('should set empty tooltip', () => {
+        component.disabled = false;
+
+        expect(component.tooltip).toBe(EMPTY_STRING);
+      });
+    });
+  });
+
+  describe('template', () => {
+    describe('on button click', () => {
+      it('should emit', () => {
+        component.clickEmitter.emit = jest.fn();
+
+        dispatchEventFromFixture(fixture, button, MockEvent.CLICK);
+
+        expect(component.clickEmitter.emit).toHaveBeenCalled();
+      });
     });
   });
 });
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.ts
index 767fe5e4ce40e7589725973d37e9a84c12fd0564..e881014e936c13d57632ed0c9d059093bcb1d76a 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-button/forwarding-button.component.ts
@@ -1,18 +1,28 @@
 import { CommandResource } from '@alfa-client/command-shared';
-import { StateResource } from '@alfa-client/tech-shared';
+import { EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
 import { Component, EventEmitter, Input, Output } from '@angular/core';
 import { ButtonWithSpinnerComponent } from '@ods/component';
-import { ForwardVorgangIconComponent } from '@ods/system';
+import { ForwardVorgangIconComponent, TooltipDirective } from '@ods/system';
 
 @Component({
   selector: 'alfa-forwarding-button',
   standalone: true,
-  imports: [ButtonWithSpinnerComponent, ForwardVorgangIconComponent],
+  imports: [ButtonWithSpinnerComponent, ForwardVorgangIconComponent, TooltipDirective],
   templateUrl: './forwarding-button.component.html',
 })
 export class ForwardingButtonComponent {
-  @Input() disabled: boolean;
+  @Input() set disabled(value: boolean) {
+    this._disabled = value;
+    this.tooltip = value ? 'Bitte ein Amt oder Stelle auswählen' : EMPTY_STRING;
+  }
   @Input() stateResource: StateResource<CommandResource>;
 
   @Output() clickEmitter: EventEmitter<void> = new EventEmitter();
+
+  public tooltip: string;
+  private _disabled: boolean;
+
+  get disabled() {
+    return this._disabled;
+  }
 }
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.html
index 5aaf99a340455262073812254cf8c7d74adf578e..54327bb8b611751959f462fc5415b16e0fcde6d7 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.html
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.html
@@ -1,22 +1,25 @@
-<div class="flex w-[620px] max-w-full flex-col gap-4 bg-background-100 p-8">
-  <div class="flex items-center justify-between">
-    <h1 class="text-xl font-semibold text-primary">Vorgang weiterleiten</h1>
-    <ods-cancel-dialog-button showAsIconButton="true" />
-  </div>
-
-  @if (!selectedSearchResult) {
-    <alfa-search-zustaendige-stelle-form-container cdkFocusInitial focusOnSearchField="true" data-test-id="zufi-search" />
-  } @else {
-    <alfa-forwarding-item-in-dialog [organisationsEinheitResource]="selectedSearchResult" data-test-id="forwarding-item" />
-  }
+<ods-dialog-container>
+  <div class="flex grow flex-col gap-6">
+    <div class="flex items-center justify-between">
+      <h1 class="text-lg font-medium text-primary">Vorgang weiterleiten</h1>
+      <ods-cancel-dialog-button showAsIconButton="true" />
+    </div>
+    <div class="h-[calc(37vh+4.5rem)] grow">
+      @if (!selectedSearchResult) {
+        <alfa-forwarding-search-organisations-einheit cdkFocusInitial data-test-id="organisations-einheit-search" />
+      } @else {
+        <alfa-selected-search-item [organisationsEinheitResource]="selectedSearchResult" data-test-id="selected-search-item" />
+      }
+    </div>
 
-  <div class="flex gap-4">
-    <alfa-forwarding-button
-      [stateResource]="forwardCommandStateResource"
-      [disabled]="!selectedSearchResult"
-      (clickEmitter)="onForwarding()"
-      data-test-id="foward-dialog-forward-button"
-    />
-    <ods-cancel-dialog-button />
+    <div class="flex gap-4">
+      <alfa-forwarding-button
+        [stateResource]="forwardCommandStateResource"
+        [disabled]="!selectedSearchResult"
+        (clickEmitter)="onForwarding()"
+        data-test-id="foward-dialog-forward-button"
+      />
+      <ods-cancel-dialog-button />
+    </div>
   </div>
-</div>
+</ods-dialog-container>
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.spec.ts
index 7e6114236be2ad7d2dddfcdfd309853b32568cdd..be35437daced36c2d43a532852f0d78cbc4fa8b9 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.spec.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.spec.ts
@@ -5,35 +5,36 @@ import {
   MockEvent,
   notExistsAsHtmlElement,
 } from '@alfa-client/test-utils';
-import { ZustaendigeStelleModule } from '@alfa-client/zustaendige-stelle';
 import { OrganisationsEinheitResource } from '@alfa-client/zustaendige-stelle-shared';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { getUrl } from '@ngxp/rest';
 import { CancelDialogButtonComponent } from '@ods/component';
-import { MockComponent, MockModule } from 'ng-mocks';
+import { MockComponent } from 'ng-mocks';
 import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test';
 import { createOrganisationsEinheitResource } from '../../../../../zustaendige-stelle-shared/test/organisations-einheit';
 import { ForwardingButtonComponent } from './forwarding-button/forwarding-button.component';
 import { ForwardingDialogComponent } from './forwarding-dialog.component';
-import { ForwardingItemInDialogComponent } from './forwarding-item/forwarding-item.component';
+import { SelectedSearchItemComponent } from './selected-search-item/selected-search-item.component';
+import { ForwardingSearchOrganisationsEinheitComponent } from './search-organisations-einheit/search-organisations-einheit.component';
 
 describe('ForwardingDialogComponent', () => {
   let component: ForwardingDialogComponent;
   let fixture: ComponentFixture<ForwardingDialogComponent>;
 
-  const zufiSearch: string = getDataTestIdOf('zufi-search');
-  const forwardingItem: string = getDataTestIdOf('forwarding-item');
+  const organisationsEinheitSearch: string = getDataTestIdOf('organisations-einheit-search');
+  const selectedSearchItem: string = getDataTestIdOf('selected-search-item');
   const forwardButton: string = getDataTestIdOf('foward-dialog-forward-button');
 
   const organisationsEinheitResource: OrganisationsEinheitResource = createOrganisationsEinheitResource();
+
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [ForwardingDialogComponent],
       declarations: [
         MockComponent(CancelDialogButtonComponent),
         MockComponent(ForwardingButtonComponent),
-        MockComponent(ForwardingItemInDialogComponent),
-        MockModule(ZustaendigeStelleModule),
+        MockComponent(SelectedSearchItemComponent),
+        MockComponent(ForwardingSearchOrganisationsEinheitComponent),
       ],
     }).compileComponents();
 
@@ -53,7 +54,7 @@ describe('ForwardingDialogComponent', () => {
 
         fixture.detectChanges();
 
-        existsAsHtmlElement(fixture, zufiSearch);
+        existsAsHtmlElement(fixture, organisationsEinheitSearch);
       });
 
       it('should NOT render if selectedSearchResult is NOT null', () => {
@@ -61,7 +62,7 @@ describe('ForwardingDialogComponent', () => {
 
         fixture.detectChanges();
 
-        notExistsAsHtmlElement(fixture, zufiSearch);
+        notExistsAsHtmlElement(fixture, organisationsEinheitSearch);
       });
     });
   });
@@ -78,7 +79,7 @@ describe('ForwardingDialogComponent', () => {
 
       fixture.detectChanges();
 
-      existsAsHtmlElement(fixture, forwardingItem);
+      existsAsHtmlElement(fixture, selectedSearchItem);
     });
 
     it('should NOT render if selectedSearchResult is null', () => {
@@ -86,7 +87,7 @@ describe('ForwardingDialogComponent', () => {
 
       fixture.detectChanges();
 
-      notExistsAsHtmlElement(fixture, forwardingItem);
+      notExistsAsHtmlElement(fixture, selectedSearchItem);
     });
   });
 
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.ts
index 178d84465273bc421ee5bd9a49dca84d213cb299..9ff338f0a8f472af342aa6ccd809b3dcbcfca407 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-dialog.component.ts
@@ -7,8 +7,10 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 import { getUrl, ResourceUri } from '@ngxp/rest';
 import { CancelDialogButtonComponent } from '@ods/component';
+import { DialogContainerComponent } from '@ods/system';
 import { ForwardingButtonComponent } from './forwarding-button/forwarding-button.component';
-import { ForwardingItemInDialogComponent } from './forwarding-item/forwarding-item.component';
+import { SelectedSearchItemComponent } from './selected-search-item/selected-search-item.component';
+import { ForwardingSearchOrganisationsEinheitComponent } from './search-organisations-einheit/search-organisations-einheit.component';
 
 @Component({
   selector: 'alfa-forwarding-dialog',
@@ -19,7 +21,9 @@ import { ForwardingItemInDialogComponent } from './forwarding-item/forwarding-it
     ReactiveFormsModule,
     ZustaendigeStelleModule,
     ForwardingButtonComponent,
-    ForwardingItemInDialogComponent,
+    ForwardingSearchOrganisationsEinheitComponent,
+    SelectedSearchItemComponent,
+    DialogContainerComponent,
   ],
   templateUrl: './forwarding-dialog.component.html',
 })
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.html
deleted file mode 100644
index bd41e43d6a1c75eb03bca208ca264c7553aaa5a8..0000000000000000000000000000000000000000
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<ods-forwarding-item [label]="organisationsEinheitResource.name" [address]="organisationsEinheitResource.anschrift | anschriftToString" >
-  <alfa-forwarding-item-change-button-container />
-</ods-forwarding-item>
\ No newline at end of file
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.spec.ts
deleted file mode 100644
index b8ffb55d8ab7ff80771ddd8cdd2d1e6611dbe459..0000000000000000000000000000000000000000
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.spec.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { getMockComponent } from '@alfa-client/test-utils';
-import { Anschrift, OrganisationsEinheitResource } from '@alfa-client/zustaendige-stelle-shared';
-import { ForwardingItemComponent } from '@ods/system';
-import { MockComponent } from 'ng-mocks';
-import { createOrganisationsEinheitResource } from '../../../../../../zustaendige-stelle-shared/test/organisations-einheit';
-import { ForwardingItemChangeButtonContainerComponent } from './forwarding-item-change-button-container/forwarding-item-change-button-container.component';
-import { ForwardingItemInDialogComponent } from './forwarding-item.component';
-
-describe('ForwardingDialogForwardingItemComponent', () => {
-  let component: ForwardingItemInDialogComponent;
-  let fixture: ComponentFixture<ForwardingItemInDialogComponent>;
-
-  const organisationsEinheitResource: OrganisationsEinheitResource = createOrganisationsEinheitResource();
-  const anschrift: Anschrift = organisationsEinheitResource.anschrift;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [ForwardingItemInDialogComponent],
-      declarations: [MockComponent(ForwardingItemChangeButtonContainerComponent), MockComponent(ForwardingItemComponent)],
-    }).compileComponents();
-
-    fixture = TestBed.createComponent(ForwardingItemInDialogComponent);
-    component = fixture.componentInstance;
-    component.organisationsEinheitResource = organisationsEinheitResource;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-
-  describe('forwarding item', () => {
-    it('should exist with input', () => {
-      const forwardingItem: ForwardingItemComponent = getMockComponent(fixture, ForwardingItemComponent);
-
-      expect(forwardingItem.label).toBe(organisationsEinheitResource.name);
-      expect(forwardingItem.address).toBe(`${anschrift.strasse} ${anschrift.hausnummer}, ${anschrift.plz} ${anschrift.ort}`);
-    });
-  });
-});
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.ts
deleted file mode 100644
index 9828f682d8860e851cc15cde31fef24150120f4a..0000000000000000000000000000000000000000
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item.component.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { AnschriftToStringPipe, OrganisationsEinheitResource } from '@alfa-client/zustaendige-stelle-shared';
-import { Component, Input } from '@angular/core';
-import { ForwardingItemComponent } from '@ods/system';
-import { ForwardingItemChangeButtonContainerComponent } from './forwarding-item-change-button-container/forwarding-item-change-button-container.component';
-
-@Component({
-  selector: 'alfa-forwarding-item-in-dialog',
-  standalone: true,
-  imports: [ForwardingItemChangeButtonContainerComponent, AnschriftToStringPipe, ForwardingItemComponent],
-  templateUrl: './forwarding-item.component.html',
-})
-export class ForwardingItemInDialogComponent {
-  @Input() organisationsEinheitResource: OrganisationsEinheitResource;
-}
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..410a1ed8605cd28872a594b69559885e6e707d5d
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.html
@@ -0,0 +1,3 @@
+<ods-forwarding-item>
+  <alfa-search-zustaendige-stelle-form-container focusOnSearchField="true"/>
+</ods-forwarding-item>
\ No newline at end of file
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84b088454368fd60b3f91e74363ce1aadae01ee0
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.spec.ts
@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ZustaendigeStelleModule } from '@alfa-client/zustaendige-stelle';
+import { ForwardingItemComponent } from '@ods/system';
+import { MockComponent, MockModule } from 'ng-mocks';
+import { ForwardingSearchOrganisationsEinheitComponent } from './search-organisations-einheit.component';
+
+describe('ForwardingSearchOrganisationsEinheitComponent', () => {
+  let component: ForwardingSearchOrganisationsEinheitComponent;
+  let fixture: ComponentFixture<ForwardingSearchOrganisationsEinheitComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ForwardingSearchOrganisationsEinheitComponent],
+      declarations: [MockComponent(ForwardingItemComponent), MockModule(ZustaendigeStelleModule)],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ForwardingSearchOrganisationsEinheitComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc974a66d4a87b9d32b81acf1e8eff03d5f40430
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/search-organisations-einheit/search-organisations-einheit.component.ts
@@ -0,0 +1,11 @@
+import { ZustaendigeStelleModule } from '@alfa-client/zustaendige-stelle';
+import { Component } from '@angular/core';
+import { ForwardingItemComponent } from '@ods/system';
+
+@Component({
+  selector: 'alfa-forwarding-search-organisations-einheit',
+  standalone: true,
+  imports: [ForwardingItemComponent, ZustaendigeStelleModule],
+  templateUrl: './search-organisations-einheit.component.html',
+})
+export class ForwardingSearchOrganisationsEinheitComponent {}
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.html
similarity index 100%
rename from alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.html
rename to alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.html
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.spec.ts
similarity index 79%
rename from alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.spec.ts
rename to alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.spec.ts
index 49e50eaa845262e74914cd7f4717c66e549ca924..86ce46346d2fa0592dc93cc2c83c75f3f4512d83 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.spec.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.spec.ts
@@ -5,11 +5,11 @@ import { OrganisationsEinheitService, ZUSTAENDIGE_STELLE_SERVICE } from '@alfa-c
 import { ButtonComponent } from '@ods/system';
 import { MockComponent } from 'ng-mocks';
 import { getDataTestIdOf } from '../../../../../../../tech-shared/test/data-test';
-import { ForwardingItemChangeButtonContainerComponent } from './forwarding-item-change-button-container.component';
+import { ChangeButtonContainerComponent } from './change-button-container.component';
 
 describe('ForwardingItemChangeButtonContainerComponent', () => {
-  let component: ForwardingItemChangeButtonContainerComponent;
-  let fixture: ComponentFixture<ForwardingItemChangeButtonContainerComponent>;
+  let component: ChangeButtonContainerComponent;
+  let fixture: ComponentFixture<ChangeButtonContainerComponent>;
 
   const buttonContainer: string = getDataTestIdOf('forwarding-item-change-button-container');
 
@@ -19,12 +19,12 @@ describe('ForwardingItemChangeButtonContainerComponent', () => {
     service = mock(OrganisationsEinheitService);
 
     TestBed.configureTestingModule({
-      imports: [ForwardingItemChangeButtonContainerComponent],
+      imports: [ChangeButtonContainerComponent],
       declarations: [MockComponent(ButtonComponent)],
       providers: [{ provide: ZUSTAENDIGE_STELLE_SERVICE, useValue: service }],
     }).compileComponents();
 
-    fixture = TestBed.createComponent(ForwardingItemChangeButtonContainerComponent);
+    fixture = TestBed.createComponent(ChangeButtonContainerComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.ts
similarity index 71%
rename from alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.ts
rename to alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.ts
index 5a0ca9ccbe7730e0eb8d54ef013b17d12719b0ec..549bd4342b5c274724a0a55a6fdde303d1209d69 100644
--- a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/forwarding-item/forwarding-item-change-button-container/forwarding-item-change-button-container.component.ts
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/change-button-container/change-button-container.component.ts
@@ -3,12 +3,12 @@ import { Component, inject } from '@angular/core';
 import { ButtonComponent } from '@ods/system';
 
 @Component({
-  selector: 'alfa-forwarding-item-change-button-container',
+  selector: 'alfa-change-button-container',
   standalone: true,
   imports: [ButtonComponent],
-  templateUrl: './forwarding-item-change-button-container.component.html',
+  templateUrl: './change-button-container.component.html',
 })
-export class ForwardingItemChangeButtonContainerComponent {
+export class ChangeButtonContainerComponent {
   private readonly organisationsEinheitService = inject(ZUSTAENDIGE_STELLE_SERVICE) as OrganisationsEinheitService;
 
   public onClick(): void {
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.html b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..66f8b9cac15d60112010465b2632f3e607b0dfa7
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.html
@@ -0,0 +1,7 @@
+<ods-forwarding-item class="block">
+  <ods-forwarding-item-info
+    [label]="organisationsEinheitResource.name"
+    [address]="organisationsEinheitResource.anschrift | anschriftToString"
+  />
+  <alfa-change-button-container end-content />
+</ods-forwarding-item>
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.spec.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c95fd052a78482a1a5aa3a15a60e3e3cd0b35ec3
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.spec.ts
@@ -0,0 +1,46 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { getMockComponent } from '@alfa-client/test-utils';
+import { Anschrift, OrganisationsEinheitResource } from '@alfa-client/zustaendige-stelle-shared';
+import { ForwardingItemComponent, ForwardingItemInfoComponent } from '@ods/system';
+import { MockComponent } from 'ng-mocks';
+import { createOrganisationsEinheitResource } from '../../../../../../zustaendige-stelle-shared/test/organisations-einheit';
+import { ChangeButtonContainerComponent } from './change-button-container/change-button-container.component';
+import { SelectedSearchItemComponent } from './selected-search-item.component';
+
+describe('ForwardingSearchOrganisationsEinheitComponent', () => {
+  let component: SelectedSearchItemComponent;
+  let fixture: ComponentFixture<SelectedSearchItemComponent>;
+
+  const organisationsEinheitResource: OrganisationsEinheitResource = createOrganisationsEinheitResource();
+  const anschrift: Anschrift = organisationsEinheitResource.anschrift;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [SelectedSearchItemComponent],
+      declarations: [
+        MockComponent(ChangeButtonContainerComponent),
+        MockComponent(ForwardingItemComponent),
+        MockComponent(ForwardingItemInfoComponent),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(SelectedSearchItemComponent);
+    component = fixture.componentInstance;
+    component.organisationsEinheitResource = organisationsEinheitResource;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('forwarding item info', () => {
+    it('should exist with input', () => {
+      const forwardingItemInfo: ForwardingItemInfoComponent = getMockComponent(fixture, ForwardingItemInfoComponent);
+
+      expect(forwardingItemInfo.label).toBe(organisationsEinheitResource.name);
+      expect(forwardingItemInfo.address).toBe(`${anschrift.strasse} ${anschrift.hausnummer}, ${anschrift.plz} ${anschrift.ort}`);
+    });
+  });
+});
diff --git a/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.ts b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c40737820507149807e1fe17be6de8a6033b6e6d
--- /dev/null
+++ b/alfa-client/libs/forwarding/src/lib/forwarding-dialog-container/forwarding-dialog/selected-search-item/selected-search-item.component.ts
@@ -0,0 +1,14 @@
+import { AnschriftToStringPipe, OrganisationsEinheitResource } from '@alfa-client/zustaendige-stelle-shared';
+import { Component, Input } from '@angular/core';
+import { ForwardingItemComponent, ForwardingItemInfoComponent } from '@ods/system';
+import { ChangeButtonContainerComponent } from './change-button-container/change-button-container.component';
+
+@Component({
+  selector: 'alfa-selected-search-item',
+  standalone: true,
+  imports: [ChangeButtonContainerComponent, AnschriftToStringPipe, ForwardingItemComponent, ForwardingItemInfoComponent],
+  templateUrl: './selected-search-item.component.html',
+})
+export class SelectedSearchItemComponent {
+  @Input() organisationsEinheitResource: OrganisationsEinheitResource;
+}
diff --git a/alfa-client/libs/tech-shared/src/index.ts b/alfa-client/libs/tech-shared/src/index.ts
index dcfa58a7cb36883ea69342990b5e6f1880dabd86..bc0367096e77bd152634a643e3b2026a3b3d9bfe 100644
--- a/alfa-client/libs/tech-shared/src/index.ts
+++ b/alfa-client/libs/tech-shared/src/index.ts
@@ -66,3 +66,4 @@ export * from './lib/service/formservice.abstract';
 export * from './lib/tech.model';
 export * from './lib/tech.util';
 export * from './lib/validation/tech.validation.util';
+export * from './lib/validation/tech.validators';
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
index accb4de66726417404f156a36d8b732731199c50..2045e5afa6740ac78ea86e2a9057d3ea9066122a 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
@@ -32,21 +32,20 @@ export enum ValidationMessageCode {
   FIELD_INVALID = 'validation_field_invalid',
   FIELD_DATE_FORMAT_INVALID = 'validation_field_date_format_invalid',
   FIELD_ASSIGN_BEARBEITER_NOT_EXIST = 'fe_only_validation_bearbeiter_not_exist',
+  FIELD_VALUE_ALREADY_EXISTS = 'validation_field_value_already_exists',
 }
 
 export const VALIDATION_MESSAGES: { [code: string]: string } = {
   [ValidationMessageCode.FIELD_EMPTY]: 'Bitte {field} ausfüllen',
   [ValidationMessageCode.FIELD_MAX_SIZE]: '{field} darf höchstens {max} Zeichen enthalten',
   [ValidationMessageCode.FIELD_MIN_SIZE]: '{field} muss aus mindestens {min} Zeichen bestehen',
-  [ValidationMessageCode.FIELD_SIZE]:
-    '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten',
+  [ValidationMessageCode.FIELD_SIZE]: '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten',
   [ValidationMessageCode.FIELD_DATE_PAST]: 'Das Datum für {field} muss in der Zukunft liegen',
   [ValidationMessageCode.FIELD_INVALID]: 'Bitte {field} korrekt ausfüllen',
-  [ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED]:
-    'Anhänge größer {max}{unit} können nicht hinzugefügt werden.',
+  [ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED]: 'Anhänge größer {max}{unit} können nicht hinzugefügt werden.',
 
   fe_only_validation_bearbeiter_not_exist: 'Der Bearbeiter existiert nicht',
   [ValidationMessageCode.FIELD_DATE_FORMAT_INVALID]: 'Geben Sie ein gültiges Datum ein',
-  [ValidationMessageCode.FIELD_FILE_CONTENT_TYPE_INVALID]:
-    'Erlaubte Dateiendungen: pdf, jpg, png, jpeg',
+  [ValidationMessageCode.FIELD_FILE_CONTENT_TYPE_INVALID]: 'Erlaubte Dateiendungen: pdf, jpg, png, jpeg',
+  [ValidationMessageCode.FIELD_VALUE_ALREADY_EXISTS]: '{value} bereits verwendet',
 };
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
index 32ce38f3c400ca02773522f97c30b6e7fab1b246..b4a1c442684dc9101a616d957e0897d8003df9d0 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
@@ -26,7 +26,16 @@ import { faker } from '@faker-js/faker';
 import { createInvalidParam, createIssue, createProblemDetail } from '../../../test/error';
 import { InvalidParam, Issue } from '../tech.model';
 import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
-import { getControlForInvalidParam, getControlForIssue, getFieldPath, getMessageForInvalidParam, getMessageForIssue, getMessageReason, setInvalidParamValidationError, setIssueValidationError } from './tech.validation.util';
+import {
+  getControlForInvalidParam,
+  getControlForIssue,
+  getFieldPath,
+  getMessageForInvalidParam,
+  getMessageForIssue,
+  getMessageReason,
+  setInvalidParamValidationError,
+  setIssueValidationError,
+} from './tech.validation.util';
 
 describe('ValidationUtils', () => {
   const baseField1Control: FormControl = new UntypedFormControl();
@@ -44,7 +53,7 @@ describe('ValidationUtils', () => {
   describe('set issue validation error', () => {
     describe('get control for issue', () => {
       it('should return base field control', () => {
-        const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
+        const issue: Issue = { ...createIssue(), field: 'baseField1' };
 
         const control: AbstractControl = getControlForIssue(form, issue);
 
@@ -69,7 +78,7 @@ describe('ValidationUtils', () => {
     });
 
     describe('in base field', () => {
-      const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
+      const issue: Issue = { ...createIssue(), field: 'baseField1' };
 
       it('should set error in control', () => {
         setIssueValidationError(form, issue);
@@ -144,7 +153,7 @@ describe('ValidationUtils', () => {
       it('should return base field control', () => {
         const invalidParam: InvalidParam = {
           ...createInvalidParam(),
-          name: 'class.resource.baseField1',
+          name: 'baseField1',
         };
 
         const control: AbstractControl = getControlForInvalidParam(form, invalidParam);
@@ -155,7 +164,7 @@ describe('ValidationUtils', () => {
       it('should return sub group field', () => {
         const invalidParam: InvalidParam = {
           ...createInvalidParam(),
-          name: 'class.resource.subGroup.subGroupField1',
+          name: 'resource.subGroup.subGroupField1',
         };
 
         const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');
@@ -166,7 +175,7 @@ describe('ValidationUtils', () => {
       it('should ignore path prefix', () => {
         const invalidParam: InvalidParam = {
           ...createInvalidParam(),
-          name: 'class.resource.baseField1',
+          name: 'resource.baseField1',
         };
 
         const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');
@@ -178,7 +187,7 @@ describe('ValidationUtils', () => {
     describe('in base field', () => {
       const invalidParam: InvalidParam = {
         ...createInvalidParam(),
-        name: 'class.resource.baseField1',
+        name: 'baseField1',
       };
 
       it('should set error in control', () => {
@@ -209,7 +218,7 @@ describe('ValidationUtils', () => {
     describe('in subGroup Field', () => {
       const invalidParam: InvalidParam = {
         ...createInvalidParam(),
-        name: 'class.resource.subGroup.subGroupField1',
+        name: 'resource.subGroup.subGroupField1',
       };
 
       it('should set error in control', () => {
@@ -243,12 +252,9 @@ describe('ValidationUtils', () => {
     });
 
     it('should return field from full path when resource is undefined', () => {
-      const fieldPath: string = 'field1';
-      const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`;
-
-      const result: string = getFieldPath(fullPath, undefined);
+      const result: string = getFieldPath('field1', undefined);
 
-      expect(result).toBe(fieldPath);
+      expect(result).toBe('field1');
     });
 
     it('should return field from field when resource is undefined', () => {
@@ -309,9 +315,7 @@ describe('ValidationUtils', () => {
         ...invalidParam,
         reason: ValidationMessageCode.FIELD_INVALID,
       });
-      expect(message).toEqual(
-        VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label),
-      );
+      expect(message).toEqual(VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label));
     });
 
     it('should return message with placeholders', () => {
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
index b0cb78f8a1d59efdddeb25e26c4deda6a746d7fa..0701d11447b842b06de8d18e9798c117a61b6f1f 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
@@ -22,7 +22,7 @@
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
 import { AbstractControl, UntypedFormGroup } from '@angular/forms';
-import { isEmpty, isNil } from 'lodash-es';
+import { isEmpty, isNil, uniqueId } from 'lodash-es';
 import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../tech.model';
 import { replacePlaceholder } from '../tech.util';
 import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
@@ -31,28 +31,18 @@ export function isValidationError(issue: Issue): boolean {
   return issue.messageCode.includes('javax.validation.constraints');
 }
 
-export function setIssueValidationError(
-  form: UntypedFormGroup,
-  issue: Issue,
-  pathPrefix?: string,
-): void {
+export function setIssueValidationError(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): void {
   const control: AbstractControl = getControlForIssue(form, issue, pathPrefix);
 
   control.setErrors({ [issue.messageCode]: issue });
   control.markAsTouched();
 }
 
-export function getControlForIssue(
-  form: UntypedFormGroup,
-  issue: Issue,
-  pathPrefix?: string,
-): AbstractControl {
+export function getControlForIssue(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): AbstractControl {
   const fieldPath: string = getFieldPath(issue.field, pathPrefix);
 
   let curControl: AbstractControl = form;
-  fieldPath
-    .split('.')
-    .forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
+  fieldPath.split('.').forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
 
   return curControl;
 }
@@ -66,9 +56,7 @@ export function getMessageForIssue(label: string, issue: Issue): string {
   }
 
   msg = replacePlaceholder(msg, 'field', label);
-  issue.parameters.forEach(
-    (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
-  );
+  issue.parameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
   return msg;
 }
 
@@ -84,11 +72,7 @@ export function getMessageCode(apiError: ApiError): string {
   return apiError.issues[0].messageCode;
 }
 
-export function setInvalidParamValidationError(
-  form: UntypedFormGroup,
-  invalidParam: InvalidParam,
-  pathPrefix?: string,
-): void {
+export function setInvalidParamValidationError(form: UntypedFormGroup, invalidParam: InvalidParam, pathPrefix?: string): void {
   const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix);
 
   control.setErrors({ [invalidParam.reason]: invalidParam });
@@ -112,17 +96,25 @@ export function getMessageForInvalidParam(label: string, invalidParam: InvalidPa
   }
 
   msg = replacePlaceholder(msg, 'field', label);
-  invalidParam.constraintParameters.forEach(
-    (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
-  );
+  invalidParam.constraintParameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
   return msg;
 }
 
 export function getFieldPath(name: string, pathPrefix: string): string {
+  const path: string = _mapFormArrayElementNameToPath(name);
   if (isEmpty(pathPrefix)) {
-    return name.split('.').pop();
+    return path;
   }
 
-  const indexOfField = name.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
-  return name.slice(indexOfField);
+  const indexOfField = path.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
+  return path.slice(indexOfField);
+}
+
+export function _mapFormArrayElementNameToPath(name: string): string {
+  const formArrayControlIndexCaptureGroup: RegExp = /\[(\d+?)]\./g;
+  return name.replace(formArrayControlIndexCaptureGroup, '.$1.');
+}
+
+export function generateValidationErrorId(): string {
+  return `${uniqueId()}-validation-error`;
 }
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.spec.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..00ef101f7dd00dde3d7c6452d5dc771cc41d0c6f
--- /dev/null
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.spec.ts
@@ -0,0 +1,155 @@
+import {
+  checkBoxGroupsEmptyValidator,
+  fieldEmptyValidator,
+  fieldInvalidValidator,
+  fieldLengthValidator,
+  InvalidParam,
+} from '@alfa-client/tech-shared';
+import { AbstractControl, FormControl, UntypedFormGroup } from '@angular/forms';
+import { expect } from '@jest/globals';
+import { ValidationMessageCode } from './tech.validation.messages';
+
+describe('Tech Validators', () => {
+  describe('field empty validator', () => {
+    it('should return null', () => {
+      const control: AbstractControl = new FormControl('test');
+
+      expect(fieldEmptyValidator('')(control)).toBeNull();
+    });
+
+    it('should return invalid param', () => {
+      const control: AbstractControl = new FormControl(null);
+
+      expect(fieldEmptyValidator('')(control)).toEqual({
+        [ValidationMessageCode.FIELD_EMPTY]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_EMPTY,
+          constraintParameters: [],
+        } as InvalidParam,
+      });
+    });
+  });
+
+  describe('field invalid validator', () => {
+    it('should return null', () => {
+      const control: AbstractControl = new FormControl('test');
+
+      expect(fieldInvalidValidator('', /^test$/)(control)).toBeNull();
+    });
+
+    it('should return invalid param', () => {
+      const control: AbstractControl = new FormControl('test2');
+
+      expect(fieldInvalidValidator('', /^test$/)(control)).toEqual({
+        [ValidationMessageCode.FIELD_INVALID]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_INVALID,
+          constraintParameters: [],
+        } as InvalidParam,
+      });
+    });
+  });
+
+  describe('field length validator', () => {
+    it('should return null', () => {
+      const control: AbstractControl = new FormControl('test');
+
+      expect(fieldLengthValidator('', 1, 5)(control)).toBeNull();
+    });
+
+    it('should return invalid param with min length', () => {
+      const control: AbstractControl = new FormControl('t');
+
+      expect(fieldLengthValidator('', 2, 5)(control)).toEqual({
+        [ValidationMessageCode.FIELD_SIZE]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_SIZE,
+          constraintParameters: [
+            { name: 'min', value: '2' },
+            { name: 'max', value: '5' },
+          ],
+        } as InvalidParam,
+      });
+    });
+
+    it('should return invalid param with max length', () => {
+      const control: AbstractControl = new FormControl('test test test');
+
+      expect(fieldLengthValidator('', 2, 5)(control)).toEqual({
+        [ValidationMessageCode.FIELD_SIZE]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_SIZE,
+          constraintParameters: [
+            { name: 'min', value: '2' },
+            { name: 'max', value: '5' },
+          ],
+        } as InvalidParam,
+      });
+    });
+  });
+
+  describe('check box group empty validator', () => {
+    it('should return null', () => {
+      const control: UntypedFormGroup = new UntypedFormGroup({
+        group1: new UntypedFormGroup({
+          check1: new FormControl(true),
+          check2: new FormControl(false),
+        }),
+        group2: new UntypedFormGroup({
+          check1: new FormControl(false),
+          check2: new FormControl(false),
+        }),
+      });
+
+      expect(checkBoxGroupsEmptyValidator('', ['group1', 'group2'])(control)).toBeNull();
+    });
+
+    it('should return invalid param', () => {
+      const control: UntypedFormGroup = new UntypedFormGroup({
+        group1: new UntypedFormGroup({
+          check1: new FormControl(false),
+          check2: new FormControl(false),
+        }),
+        group2: new UntypedFormGroup({
+          check1: new FormControl(false),
+          check2: new FormControl(false),
+        }),
+      });
+
+      expect(checkBoxGroupsEmptyValidator('', ['group1', 'group2'])(control)).toEqual({
+        [ValidationMessageCode.FIELD_EMPTY]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_EMPTY,
+          constraintParameters: [],
+        } as InvalidParam,
+      });
+    });
+
+    it('should return invalid param and ignore group', () => {
+      const control: UntypedFormGroup = new UntypedFormGroup({
+        group1: new UntypedFormGroup({
+          check1: new FormControl(false),
+          check2: new FormControl(false),
+        }),
+        group2: new UntypedFormGroup({
+          check1: new FormControl(true),
+          check2: new FormControl(false),
+        }),
+      });
+
+      expect(checkBoxGroupsEmptyValidator('', ['group1'])(control)).toEqual({
+        [ValidationMessageCode.FIELD_EMPTY]: {
+          name: '',
+          value: null,
+          reason: ValidationMessageCode.FIELD_EMPTY,
+          constraintParameters: [],
+        } as InvalidParam,
+      });
+    });
+  });
+});
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.ts
new file mode 100644
index 0000000000000000000000000000000000000000..798a98d8ebe415a7d9e97cae7c6bd6000a79926b
--- /dev/null
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validators.ts
@@ -0,0 +1,81 @@
+import { InvalidParam, isNotEmpty, isNotNil } from '@alfa-client/tech-shared';
+import { AbstractControl, UntypedFormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
+import { ValidationMessageCode } from './tech.validation.messages';
+
+export function fieldEmptyValidator(controlName: string): ValidatorFn {
+  return (control: AbstractControl): ValidationErrors | null => {
+    if (isNotEmpty(control.value)) return null;
+    return {
+      [ValidationMessageCode.FIELD_EMPTY]: {
+        name: controlName,
+        value: null,
+        reason: ValidationMessageCode.FIELD_EMPTY,
+        constraintParameters: [],
+      } as InvalidParam,
+    };
+  };
+}
+
+export function fieldInvalidValidator(controlName: string, regex: RegExp): ValidatorFn {
+  return (control: AbstractControl): ValidationErrors | null => {
+    if (regex.test(control.value)) return null;
+    return {
+      [ValidationMessageCode.FIELD_INVALID]: {
+        name: controlName,
+        value: null,
+        reason: ValidationMessageCode.FIELD_INVALID,
+        constraintParameters: [],
+      } as InvalidParam,
+    };
+  };
+}
+
+export function fieldLengthValidator(controlName: string, min: number, max: number): ValidatorFn {
+  return (control: AbstractControl): ValidationErrors | null => {
+    const value: string = control.value;
+    if (isNotEmpty(control.value) && value.length >= min && value.length <= max) return null;
+    return {
+      [ValidationMessageCode.FIELD_SIZE]: {
+        name: controlName,
+        value: null,
+        reason: ValidationMessageCode.FIELD_SIZE,
+        constraintParameters: [
+          { name: 'min', value: min.toString() },
+          { name: 'max', value: max.toString() },
+        ],
+      } as InvalidParam,
+    };
+  };
+}
+
+/**
+ * A simplified version of validation subgroups of check boxes. Could be extended for more advanced forms.
+ * It only looks up direct children of check box groups.
+ */
+export function checkBoxGroupsEmptyValidator(controlName: string, groupNames: string[]): ValidatorFn {
+  return (control: AbstractControl<UntypedFormGroup>): ValidationErrors | null => {
+    const group: UntypedFormGroup = control as UntypedFormGroup;
+    const found: boolean = groupNames
+      .filter((groupName: string) => _existsUntypedFormSubGroup(group, groupName))
+      .map((groupName: string) => group.controls[groupName] as UntypedFormGroup)
+      .map(_isAtLeastOneChecked)
+      .reduce((a: boolean, b: boolean) => a || b);
+    if (found) return null;
+    return {
+      [ValidationMessageCode.FIELD_EMPTY]: {
+        name: controlName,
+        value: null,
+        reason: ValidationMessageCode.FIELD_EMPTY,
+        constraintParameters: [],
+      } as InvalidParam,
+    };
+  };
+}
+
+export function _existsUntypedFormSubGroup(group: UntypedFormGroup, subGroupName: string): boolean {
+  return isNotNil(group.controls[subGroupName]) && group.controls[subGroupName] instanceof UntypedFormGroup;
+}
+
+export function _isAtLeastOneChecked(group: UntypedFormGroup): boolean {
+  return Object.values(group.value).findIndex((isChecked: boolean) => isChecked) > -1;
+}
diff --git a/alfa-client/libs/test-utils/src/lib/jest.helper.ts b/alfa-client/libs/test-utils/src/lib/jest.helper.ts
index 60df8592afa840fd2f06b5f967e8f5cb295a9a2c..4d0c827161f6290c7db00fd85f2bc7745f38c38d 100644
--- a/alfa-client/libs/test-utils/src/lib/jest.helper.ts
+++ b/alfa-client/libs/test-utils/src/lib/jest.helper.ts
@@ -21,10 +21,11 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { Type } from '@angular/core';
 import { ComponentFixture } from '@angular/core/testing';
 import { expect } from '@jest/globals';
 import { TooltipDirective } from '@ods/system';
-import { getDebugElementFromFixtureByCss, getElementFromFixture } from './helper';
+import { getDebugElementFromFixtureByCss, getElementFromFixture, getElementFromFixtureByType } from './helper';
 
 export function notExistsAsHtmlElement(fixture: ComponentFixture<any>, domElement: string): void {
   expect(getElementFromFixture(fixture, domElement)).not.toBeInstanceOf(HTMLElement);
@@ -34,6 +35,10 @@ export function existsAsHtmlElement(fixture: ComponentFixture<any>, domElement:
   expect(getElementFromFixture(fixture, domElement)).toBeInstanceOf(HTMLElement);
 }
 
+export function expectComponentExistsInTemplate<T>(fixture: ComponentFixture<any>, component: Type<T>): void {
+  expect(getElementFromFixtureByType(fixture, component)).toBeInstanceOf(component);
+}
+
 export function tooltipExistsWithText(fixture: ComponentFixture<any>, domElement: string, tooltipText: string) {
   const tooltipInstance = getDebugElementFromFixtureByCss(fixture, domElement).injector.get(TooltipDirective);
   expect(tooltipInstance.componentRef.instance.text).toBe(tooltipText);
diff --git a/alfa-client/package.json b/alfa-client/package.json
index d6a7bffddc6984305f4bc77149fa653d2083dfd3..d4e51a736a56e6aebfb6e3c8943ca998363833d7 100644
--- a/alfa-client/package.json
+++ b/alfa-client/package.json
@@ -17,9 +17,9 @@
     "test:lib": "nx test ${npm_config_lib}",
     "test:debug:lib": "nx test ${npm_config_lib} --detectOpenHandles --watchAll",
     "ci-build": "nx run alfa:build --outputHashing=all",
-    "ci-build-admin": "nx container admin",
+    "ci-build-administration": "nx container admin",
     "ci-prodBuild": "nx run alfa:build --outputHashing=all --configuration production",
-    "ci-prodBuild-admin": "nx container admin",
+    "ci-prodBuild-administration": "nx container admin",
     "ci-test": "nx run-many --target=test --parallel 20 -- --runInBand",
     "ci-sonar": "nx run-many --target=test --parallel 20 -- --runInBand --codeCoverage --coverageReporters=lcov --testResultsProcessor=jest-sonar-reporter && pnpm exec sonar-scanner",
     "lint": "nx workspace-lint && nx lint",
diff --git a/alfa-client/tsconfig.base.json b/alfa-client/tsconfig.base.json
index 615131502b18ad4b0d693d9d2baeed734f62c4ce..210fd016c76a0f4c30f30f797af24cca6c9d11dd 100644
--- a/alfa-client/tsconfig.base.json
+++ b/alfa-client/tsconfig.base.json
@@ -25,7 +25,7 @@
       "@admin-client/reporting-shared": ["libs/admin/reporting-shared/src/index.ts"],
       "@admin-client/settings-shared": ["libs/admin/settings-shared/src/index.ts"],
       "@admin-client/shared": ["libs/admin/shared/src/index.ts"],
-      "@admin-client/statistik": ["libs/admin/statistik/src/index.ts"],
+      "@admin-client/aggregation-mapping": ["libs/admin/aggregation-mapping/src/index.ts"],
       "@admin-client/user": ["libs/admin/user/src/index.ts"],
       "@admin-client/user-shared": ["libs/admin/user-shared/src/index.ts"],
       "@admin/keycloak-shared": ["libs/admin/keycloak-shared/src/index.ts"],