[BP-841] Add cyclic reporting form component

Signed-off-by: Christopher Keim <keim@develop-group.de>
diff --git a/src/app/cyclic-reporting/components/form/cyclic-report-form.component.html b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.html
new file mode 100644
index 0000000..f0fd69b
--- /dev/null
+++ b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.html
@@ -0,0 +1,135 @@
+<!--
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+-->
+
+<div class="container-fluid">
+  <div class="row">
+    <div class="col-md-12">
+      <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2" id="overviewHeader">
+          Zyklischen Report verwalten
+        </h1>
+        <div class="btn-toolbar mb-2 mb-md-0">
+          <div class="btn-group mr-2">
+            <button class="btn btn-primary mr-1" routerLink=".." >
+              Zurück
+            </button>
+            <ng-container *ngIf="report">
+              <button *ngIf="report?.id != null"
+                      [disabled]="form?.disabled"
+                      (click)="delete()"
+                      class="btn btn-danger mr-1">
+                Löschen
+              </button>
+              <button class="btn btn-success mr-1"
+                      [disabled]="form?.disabled"
+                      (click)="submit()">
+                Speichern
+              </button>
+            </ng-container>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div class="row" *ngIf="report == null">
+    <div class="col-md-12">
+      Daten konnten nicht geladen werden. Bitte kehren Sie zur Übersicht zurück oder betreten Sie die Seite erneut.
+    </div>
+  </div>
+
+  <div class="row" *ngIf="report != null">
+    <div class="col-md-12 cyclic-report-form">
+
+      <div class="cyclic-report-form-column">
+        <ok-cyclic-report-form-input [form]="form" [key]="'name'">
+          <span class="cyclic-report-form-label">Name:</span>
+        </ok-cyclic-report-form-input>
+
+        <div></div>
+
+        <ok-cyclic-report-form-select [form]="form" [key]="'reportName'" [options]="reportNameOptions">
+          <span class="cyclic-report-form-label">Planart:</span>
+        </ok-cyclic-report-form-select>
+
+        <ok-cyclic-report-form-select [form]="form" [key]="'standByListId'"
+                                      [options]="standByListOptions">
+          <span class="cyclic-report-form-label">Liste:</span>
+        </ok-cyclic-report-form-select>
+
+        <ok-cyclic-report-form-select [form]="form" [key]="'statusId'" [options]="planStatusOptions">
+          <span class="cyclic-report-form-label">Ebene:</span>
+        </ok-cyclic-report-form-select>
+
+        <ok-cyclic-report-form-select [form]="form" [key]="'printFormat'" [options]="printFormats">
+          <span class="cyclic-report-form-label">Format:</span>
+        </ok-cyclic-report-form-select>
+
+        <div></div>
+
+        <ng-container *ngFor="let entry of toFormArray?.controls; let i = index; let first = first;">
+          <ok-cyclic-report-form-input [form]="toFormArray" [key]="i"
+                                       [displayAddButton]="first" [displayCancelButton]="!first"
+                                       (add)="addEmailControl()" (cancel)="removeEmailControlAt(i)">
+            <span class="cyclic-report-form-label">
+              <ng-container *ngIf="first">Empfänger:</ng-container>
+            </span>
+          </ok-cyclic-report-form-input>
+        </ng-container>
+
+        <ok-cyclic-report-form-input [form]="form" [key]="'subject'">
+          <span class="cyclic-report-form-label">Betreff:</span>
+        </ok-cyclic-report-form-input>
+
+        <ok-cyclic-report-form-textarea [form]="form" [key]="'emailText'" class="cyclic-report-form-textarea">
+          <span class="cyclic-report-form-label">Emailtext:</span>
+        </ok-cyclic-report-form-textarea>
+
+        <ok-cyclic-report-form-input [form]="form" [key]="'fileNamePattern'">
+          <span class="cyclic-report-form-label">Dateiname:</span>
+        </ok-cyclic-report-form-input>
+
+      </div>
+
+      <div class="cyclic-report-form-column">
+
+        <ok-cyclic-report-form-date-controls [form]="form"
+                                             [options]="weekDayOptions"
+                                             [triggerMinuteStep]="triggerMinuteStep"
+                                             [key]="'trigger'">
+          <span>Auslöse&shy;zeitpunkt:</span>
+        </ok-cyclic-report-form-date-controls>
+
+        <ok-cyclic-report-form-date-controls [form]="form"
+                                             [key]="'validFrom'" [forDayOffset]="true">
+          <span>Gültig&shy;keit von:</span>
+        </ok-cyclic-report-form-date-controls>
+
+        <ok-cyclic-report-form-date-controls [form]="form" [key]="'validTo'"
+                                             [forDayOffset]="true">
+          <span>Gültig&shy;keit bis:</span>
+        </ok-cyclic-report-form-date-controls>
+
+        <ok-error class="cyclic-report-form-error" [control]="form"></ok-error>
+
+        <ok-cyclic-report-form-info
+          [data]="form?.value"
+          [dateReplacementTokens]="dateTokens"
+          class="cyclic-report-form-info">
+        </ok-cyclic-report-form-info>
+
+      </div>
+
+    </div>
+  </div>
+
+</div>
diff --git a/src/app/cyclic-reporting/components/form/cyclic-report-form.component.scss b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.scss
new file mode 100644
index 0000000..973cd15
--- /dev/null
+++ b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.scss
@@ -0,0 +1,52 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+
+.cyclic-report-form {
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: space-around;
+  padding: 0;
+  width: 100%;
+}
+
+.cyclic-report-form-column {
+  flex: 1 1 25em;
+  width: 25em;
+  margin: 0 15px;
+
+  display: flex;
+  flex-flow: column;
+
+  & > * {
+    margin-bottom: 0.75em;
+  }
+}
+
+.cyclic-report-form-error {
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.cyclic-report-form-label {
+  display: inline-block;
+  padding: calc(.375rem + 1px) 1rem calc(.375rem + 1px) 0;
+  line-height: 1.5;
+  margin: 0;
+  min-width: 10rem;
+}
+
+.cyclic-report-form-textarea {
+  min-height: calc(5 * 1.5em + 1em);
+}
+
+.cyclic-report-form-info {
+  padding: 0 1em 0 1em;
+}
diff --git a/src/app/cyclic-reporting/components/form/cyclic-report-form.component.spec.ts b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.spec.ts
new file mode 100644
index 0000000..f675a09
--- /dev/null
+++ b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.spec.ts
@@ -0,0 +1,270 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {CommonModule} from '@angular/common';
+import {HttpClientTestingModule} from '@angular/common/http/testing';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {ActivatedRoute, Router} from '@angular/router';
+import {RouterTestingModule} from '@angular/router/testing';
+import {SharedModule} from '@shared/shared.module';
+import {CyclicReportObject} from '@shared/model/CyclicReportObject';
+import {ReportObject} from '@shared/model/ReportObject';
+import {StandbylistObject} from '@shared/model/StandbylistObject';
+import {FormUtil} from '@shared/utils/form.util';
+import {MessageService} from 'primeng/api';
+import {defer, of, throwError} from 'rxjs';
+import {CyclicReportFormDateControlsComponent} from '../form-date-controls/cyclic-report-form-date-controls.component';
+import {CyclicReportFormInfoComponent} from '../form-info/cyclic-report-form-info.component';
+import {CyclicReportFormInputComponent} from '../form-input/cyclic-report-form-input.component';
+import {CyclicReportFormSelectComponent} from '../form-select/cyclic-report-form-select.component';
+import {CyclicReportFormComponent} from './cyclic-report-form.component';
+import {CyclicReportFormTextareaComponent} from '@cyclic-reporting/components/form-textarea/cyclic-report-form-textarea.component';
+
+function createMockData<T extends object>(data: Partial<T>): T {
+  return (data == null ? {} : data) as T;
+}
+
+describe('CyclicReportFormComponent', () => {
+
+  let component: CyclicReportFormComponent;
+  let fixture: ComponentFixture<CyclicReportFormComponent>;
+  let idParam: number | 'new';
+  let report: CyclicReportObject;
+  let router: Router;
+  let activatedRoute: ActivatedRoute;
+
+  const standByListData: StandbylistObject[] = Array(42).fill(0)
+    .map((_, id) => createMockData<StandbylistObject>({ id: id, title: 'QVL ' + id }));
+
+  const reportObjects: ReportObject[] = Array(42).fill(0)
+    .map((_, id) => createMockData<ReportObject>({ reportName: 'ReportName' + id }));
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        CyclicReportFormComponent,
+        CyclicReportFormDateControlsComponent,
+        CyclicReportFormInfoComponent,
+        CyclicReportFormInputComponent,
+        CyclicReportFormSelectComponent,
+        CyclicReportFormTextareaComponent
+      ],
+      imports: [
+        CommonModule,
+        SharedModule,
+        RouterTestingModule,
+        HttpClientTestingModule
+      ],
+      providers: [
+        MessageService,
+        {
+          provide: ActivatedRoute,
+          useValue: { params: defer(() => of({id: idParam == null ? undefined : '' + idParam}))}
+        }
+      ]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CyclicReportFormComponent);
+    component = fixture.componentInstance;
+    component.excludedReportNames = reportObjects.slice(-1).map((reportObject) => reportObject.reportName);
+    router = TestBed.get(Router);
+    activatedRoute = TestBed.get(ActivatedRoute);
+    idParam = 19;
+    report = null;
+    spyOn(component.reportingService, 'getReportData')
+      .and.returnValue(defer(() => of(reportObjects)));
+    spyOn(component.masterdataService, 'getStandbyListSelection')
+      .and.returnValue(defer(() => of(standByListData)));
+    spyOn(component.cyclicReportingService, 'getCyclicReports')
+      .and.returnValue(defer(() => of(report == null ? [] : [report])));
+  });
+
+  describe('should create', () => {
+    it('for new reports', () => {
+      idParam = 'new';
+      fixture.detectChanges();
+      expect(component).toBeDefined();
+    });
+
+    it('for non-existing reports', () => {
+      idParam = -19;
+      fixture.detectChanges();
+      expect(component).toBeDefined();
+    });
+
+    it('for existing report', () => {
+      idParam = 19;
+      report = createMockData<CyclicReportObject>({ id: 19, to: [ 'a@b.c', 'x@y.z' ], standByListId: 19 });
+      fixture.detectChanges();
+      expect(component).toBeDefined();
+    });
+  });
+
+  it('should add and remove email controls', () => {
+    idParam = 'new';
+    fixture.detectChanges();
+    expect(component.form.value.to).toEqual(['']);
+    component.addEmailControl();
+    expect(component.form.value.to).toEqual(['', '']);
+    component.removeEmailControlAt(1);
+    expect(component.form.value.to).toEqual(['']);
+  });
+
+  describe('should submit', () => {
+
+    it('for new reports', () => {
+      const validateSpy = spyOn(FormUtil, 'validate').and.returnValue(true);
+      const putFormSpy = spyOn(component.cyclicReportingService, 'putCyclicReport').and.returnValue(of(report));
+      const navigateSpy = spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
+
+      report = {
+        id: null,
+        name: 'Name',
+        fileNamePattern: '{Date}_{Time}_{Week}',
+        subject: 'Test Subject',
+        to: ['test@tld.org'],
+        emailText: '',
+        reportName: reportObjects[0].reportName,
+        printFormat: 'pdf',
+        standByListId: standByListData[0].id,
+        statusId: 2,
+        triggerWeekDay: 1,
+        triggerHour: 8,
+        triggerMinute: 0,
+        validFromDayOffset: 0,
+        validFromHour: 8,
+        validFromMinute: 0,
+        validToDayOffset: 1,
+        validToHour: 8,
+        validToMinute: 0
+      };
+      idParam = 'new';
+      fixture.detectChanges();
+
+      component.form.patchValue(report);
+      component.submit();
+      expect(validateSpy).toHaveBeenCalled();
+      expect(putFormSpy).toHaveBeenCalledWith(report);
+      expect(navigateSpy).toHaveBeenCalledWith(['..'], { relativeTo: activatedRoute });
+    });
+
+    it('for existing reports', () => {
+      const validateSpy = spyOn(FormUtil, 'validate').and.returnValue(true);
+      const postFormSpy = spyOn(component.cyclicReportingService, 'postCyclicReport').and.returnValue(of(report));
+      const navigateSpy = spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
+
+      report = {
+        id: 19,
+        name: 'Name',
+        fileNamePattern: '{Date}_{Time}_{Week}',
+        subject: 'Test Subject',
+        to: ['test@tld.org'],
+        emailText: '',
+        reportName: reportObjects[0].reportName,
+        printFormat: 'pdf',
+        standByListId: standByListData[0].id,
+        statusId: 2,
+        triggerWeekDay: 1,
+        triggerHour: 8,
+        triggerMinute: 0,
+        validFromDayOffset: 0,
+        validFromHour: 8,
+        validFromMinute: 0,
+        validToDayOffset: 1,
+        validToHour: 8,
+        validToMinute: 0
+      };
+      idParam = 19;
+      fixture.detectChanges();
+
+      component.addEmailControl();
+      component.submit();
+      expect(validateSpy).toHaveBeenCalled();
+      expect(postFormSpy).toHaveBeenCalledWith(report);
+      expect(navigateSpy).toHaveBeenCalledWith(['..'], { relativeTo: activatedRoute });
+    });
+
+    it('and handle errors correctly', () => {
+      const validateSpy = spyOn(FormUtil, 'validate').and.returnValue(false);
+      const postFormSpy = spyOn(component.cyclicReportingService, 'postCyclicReport').and.returnValue(of(report));
+      const navigateSpy = spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
+
+      report = {
+        id: 19,
+        name: 'Name',
+        fileNamePattern: '{Date}_{Time}_{Week}',
+        subject: 'Test Subject',
+        to: ['test@tld.org'],
+        emailText: '',
+        reportName: reportObjects[0].reportName,
+        printFormat: 'pdf',
+        standByListId: standByListData[0].id,
+        statusId: 2,
+        triggerWeekDay: 1,
+        triggerHour: 8,
+        triggerMinute: 0,
+        validFromDayOffset: 0,
+        validFromHour: 8,
+        validFromMinute: 0,
+        validToDayOffset: 1,
+        validToHour: 8,
+        validToMinute: 0
+      };
+      idParam = 19;
+      fixture.detectChanges();
+
+      component.submit();
+      expect(validateSpy).toHaveBeenCalled();
+      expect(postFormSpy).not.toHaveBeenCalled();
+
+      validateSpy.and.returnValue(true);
+      postFormSpy.and.returnValue(throwError('TestError'));
+      component.submit();
+      expect(validateSpy).toHaveBeenCalled();
+      expect(postFormSpy).toHaveBeenCalledWith(report);
+      expect(navigateSpy).not.toHaveBeenCalled();
+      expect(component.form.enabled).toBe(true);
+    });
+  });
+
+  describe('should delete', () => {
+    it('existing reports', () => {
+      const deleteSpy = spyOn(component.cyclicReportingService, 'deleteCyclicReport')
+        .and.returnValue(of(null));
+      const navigateSpy = spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
+
+      idParam = 19;
+      report = createMockData<CyclicReportObject>({ id: 19 });
+      fixture.detectChanges();
+
+      component.delete();
+      expect(deleteSpy).toHaveBeenCalledWith(19);
+      expect(navigateSpy).toHaveBeenCalledWith(['..'], { relativeTo: activatedRoute });
+    });
+
+    it('and handle errors correctly', () => {
+      const deleteSpy = spyOn(component.cyclicReportingService, 'deleteCyclicReport')
+        .and.returnValue(throwError('TestError'));
+      const navigateSpy = spyOn(router, 'navigate');
+
+      idParam = 19;
+      report = createMockData<CyclicReportObject>({ id: 19 });
+      fixture.detectChanges();
+
+      component.delete();
+      expect(deleteSpy).toHaveBeenCalledWith(19);
+      expect(navigateSpy).not.toHaveBeenCalled();
+      expect(component.form.disabled).toBe(false);
+    });
+  });
+
+});
diff --git a/src/app/cyclic-reporting/components/form/cyclic-report-form.component.ts b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.ts
new file mode 100644
index 0000000..0b54c5b
--- /dev/null
+++ b/src/app/cyclic-reporting/components/form/cyclic-report-form.component.ts
@@ -0,0 +1,241 @@
+/********************************************************************************
+ * Copyright © 2020 Basys GmbH.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+import {Component, Injector, OnDestroy, OnInit} from '@angular/core';
+import {FormArray, FormControl, Validators} from '@angular/forms';
+import {UtilService} from '@core/services/util.service';
+import {MasterdataService} from '@masterdata/services/masterdata.service';
+import {ReportingService} from '@reporting/services/reporting.service';
+import {AbstractFormComponent} from '@shared/abstract/abstract-form/abstract-form.component';
+import {CyclicReportObject} from '@shared/model/CyclicReportObject';
+import {SelectOptionObject} from '@shared/model/SelectOptionObject';
+import {FormUtil} from '@shared/utils/form.util';
+import {combineLatest, defer, EMPTY, Observable, of, Subscription} from 'rxjs';
+import {catchError, map, switchMap, tap} from 'rxjs/operators';
+import {CyclicReportingService} from '../../services/cyclic-reporting.service';
+import {CyclicReportingUtilService} from '../../services/cyclic-reporting-util.service';
+import {CyclicReportValidators} from '../../validators/cyclic-report.validators';
+
+@Component({
+  selector: 'ok-cyclic-report-form',
+  styleUrls: ['cyclic-report-form.component.scss'],
+  templateUrl: 'cyclic-report-form.component.html'
+})
+export class CyclicReportFormComponent extends AbstractFormComponent implements OnInit, OnDestroy {
+
+  public dateTokens = this.cyclicReportingUtilService.dateReplacementTokens;
+
+  public defaultValue: CyclicReportObject = {
+    id: undefined,
+    name: '',
+    fileNamePattern: '{Date}_{Time}_{Week}',
+    subject: '',
+    to: [],
+    emailText: '',
+    reportName: '',
+    printFormat: this.cyclicReportingUtilService.printFormatOptions[0].value,
+    standByListId: Number.NEGATIVE_INFINITY,
+    statusId: this.cyclicReportingUtilService.planStatusOptions[0].value,
+    triggerWeekDay: 1,
+    triggerHour: 8,
+    triggerMinute: 0,
+    validFromDayOffset: 0,
+    validFromHour: 8,
+    validFromMinute: 0,
+    validToDayOffset: 1,
+    validToHour: 8,
+    validToMinute: 0
+  };
+
+  public excludedReportNames: string[] = this.cyclicReportingUtilService.excludedReportNames;
+
+  public planStatusOptions = this.cyclicReportingUtilService.planStatusOptions;
+
+  public printFormats = this.cyclicReportingUtilService.printFormatOptions;
+
+  public report: CyclicReportObject;
+
+  public reportNameOptions: SelectOptionObject[] = [];
+
+  public standByListOptions: SelectOptionObject[] = [];
+
+  public toFormArray: FormArray;
+
+  public triggerMinuteStep = 5;
+
+  public weekDayOptions = this.cyclicReportingUtilService.weekDayOptions;
+
+  private subscriptions: Subscription[] = [];
+
+  public constructor(
+    injector: Injector,
+    public utilService: UtilService,
+    public masterdataService: MasterdataService,
+    public reportingService: ReportingService,
+    public cyclicReportingService: CyclicReportingService,
+    public cyclicReportingUtilService: CyclicReportingUtilService
+  ) {
+    super(injector);
+  }
+
+  public ngOnInit() {
+    this.subscriptions.push(this.initializeForm().subscribe());
+  }
+
+  public ngOnDestroy() {
+    this.subscriptions.forEach((subscription) => subscription.unsubscribe());
+  }
+
+  public submit() {
+    this.form.markAsTouched();
+    if (!FormUtil.validate(this.form)) {
+      return;
+    }
+
+    this.subscriptions.push(...[
+      of(this.form.value).pipe(
+        switchMap((value) => {
+          this.form.disable();
+          value = {
+            ...value,
+            to: value.to.filter((_) => _ !== '')
+          };
+          return value.id == null ?
+            this.cyclicReportingService.putCyclicReport(value)
+            : this.cyclicReportingService.postCyclicReport(value);
+        }),
+        switchMap(() => this.navigateToOverview()),
+        catchError(() => {
+          this.form.enable();
+          return EMPTY;
+        })
+      ).subscribe()
+    ]);
+  }
+
+  public delete() {
+    this.form.disable();
+    this.subscriptions.push(...[
+      this.cyclicReportingService.deleteCyclicReport(this.report.id).pipe(
+        switchMap(() => this.navigateToOverview()),
+        catchError(() => {
+          this.form.enable();
+          return EMPTY;
+        })
+      ).subscribe()
+    ]);
+  }
+
+  public addEmailControl() {
+    this.toFormArray.push(this.createEmailControl(''));
+  }
+
+  public removeEmailControlAt(index: number) {
+    this.toFormArray.removeAt(index);
+  }
+
+  public navigateToOverview() {
+    return this.router.navigate(['..'], { relativeTo: this.route });
+  }
+
+  private initializeForm() {
+    return defer(() => {
+      this.createForm({...this.defaultValue});
+      this.form.disable();
+      return combineLatest(this.fetchReport(), this.fetchReportNameOptions(), this.fetchStandByListOptions());
+    }).pipe(
+      tap(([report, reportNameOptions, standByListOptions]) => {
+        this.reportNameOptions = reportNameOptions.filter((option) => !this.excludedReportNames.includes(option.value));
+        this.standByListOptions = standByListOptions;
+        this.form.enable();
+        this.createForm({
+          ...this.defaultValue,
+          ...report,
+          standByListId: extractValueFromOption(
+            report == null ? this.standByListOptions[0] : findInOptions(report.standByListId, this.standByListOptions)
+          ),
+          reportName: extractValueFromOption(
+            report == null ? this.reportNameOptions[0] : findInOptions(report.reportName, this.reportNameOptions)
+          )
+        });
+      }),
+      catchError(() => {
+        this.report = undefined;
+        return EMPTY;
+      })
+    );
+  }
+
+  private fetchReport(): Observable<CyclicReportObject> {
+     return this.route.params.pipe(
+      map((params) => params.id),
+      switchMap((id) => {
+        return id === 'new' ? of(null) : this.cyclicReportingService.getCyclicReports().pipe(
+          map((reports) => {
+            id = parseInt(id, 10);
+            const result = reports.find((entry) => entry.id === id);
+            if (result == null) {
+              throw new Error('Entry not found');
+            }
+            return result;
+          }));
+      })
+    );
+  }
+
+  private fetchReportNameOptions(): Observable<SelectOptionObject<string>[]> {
+    return this.reportingService.getReportData().pipe(
+      map((data) => data.map((reportObject) => ({
+        ...reportObject,
+        value: reportObject.reportName,
+        label: reportObject.reportName
+      })))
+    );
+  }
+
+  private fetchStandByListOptions(): Observable<SelectOptionObject<number>[]> {
+    return this.masterdataService.getStandbyListSelection().pipe(
+      map((data) => data.map((reportObject) => ({
+        ...reportObject,
+        value: reportObject.id,
+        label: reportObject.title
+      })))
+    );
+  }
+
+  private createForm(report: CyclicReportObject) {
+    this.report = report;
+    const to = Array.isArray(report.to) && report.to.length > 0 ? report.to : [''];
+    this.toFormArray = this.fb.array(to.map((email) => this.createEmailControl(email)));
+    this.form = this.fb.group({...report, to: this.toFormArray});
+    Object.values(this.form.controls)
+      .forEach((control) => control.setValidators(Validators.required));
+    this.form.get('id').clearValidators();
+    this.form.get('emailText').clearValidators();
+    this.form.get('name').setValidators([Validators.required, Validators.maxLength(256)]);
+    this.form.get('fileNamePattern').setValidators([Validators.required, Validators.maxLength(128)]);
+    this.form.get('subject').setValidators([Validators.required, Validators.maxLength(128)]);
+    this.form.setValidators([CyclicReportValidators.validationDate]);
+  }
+
+  private createEmailControl(value: string): FormControl {
+    return this.fb.control(value, [Validators.email]);
+  }
+
+}
+
+function extractValueFromOption<T>(option: SelectOptionObject<T>) {
+  return option == null ? undefined : option.value;
+}
+
+function findInOptions<T>(value: T, options: SelectOptionObject<T>[]) {
+  const selected = options.find((option) => option.value === value);
+  return selected == null ? undefined : selected;
+}