add description workflow editor to tenant configuration, move plan workflow files

This commit is contained in:
mchouliara 2024-08-27 10:15:06 +03:00
parent 0a0bc0ee1b
commit 34490e268a
22 changed files with 630 additions and 31 deletions

View File

@ -221,6 +221,9 @@ export enum AppPermission {
EditPlanWorkflow = "EditPlanWorkflow",
DeletePlanWorkflow = "DeletePlanWorkflow",
//DescriptionWorkflow
EditDescriptionWorkflow = "EditDescriptionWorkflow",
DeleteDescriptionWorkflow = "DeleteDescriptionWorkflow",
// UI Pages
ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage",
@ -249,6 +252,7 @@ export enum AppPermission {
ViewUsageLimitPage = "ViewUsageLimitPage",
ViewPlanStatusPage = "ViewPlanStatusPage",
ViewDescriptionStatusPage = "ViewDescriptionStatusPage",
ViewPlanWorkflowPage = "ViewPlanWorkflowPage"
ViewPlanWorkflowPage = "ViewPlanWorkflowPage",//TODO remove if workflows remain in tenant config view
ViewDescriptionWorkflowPage = "ViewDescriptionWorkflowPage"//TODO remove if workflows remain in tenant config view
}

View File

@ -51,6 +51,7 @@ import { UsageLimitService } from './services/usage-limit/usage.service';
import { PlanStatusService } from './services/plan/plan-status.service';
import { DescriptionStatusService } from './services/description-status/description-status.service';
import { PlanWorkflowService } from './services/plan/plan-workflow.service';
import { DescriptionWorkflowService } from './services/description-workflow/description-workflow.service';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -121,7 +122,8 @@ export class CoreServiceModule {
UsageLimitService,
PlanStatusService,
DescriptionStatusService,
PlanWorkflowService
PlanWorkflowService,
DescriptionWorkflowService
],
};
}

View File

@ -0,0 +1,18 @@
import { BaseEntityPersist } from "@common/base/base-entity.model";
import { Guid } from "@common/types/guid";
export interface DescriptionWorkflowPersist extends BaseEntityPersist {
name: string;
description: string;
definition: DescriptionWorkflowDefinitionPersist;
}
export interface DescriptionWorkflowDefinitionPersist {
startingStatusId: Guid;
statusTransitions: DescriptionWorkflowDefinitionTransitionPersist[];
}
export interface DescriptionWorkflowDefinitionTransitionPersist {
fromStatusId: Guid;
toStatusId: Guid;
}

View File

@ -0,0 +1,18 @@
import { BaseEntity } from "@common/base/base-entity.model";
import { DescriptionStatus } from "../description-status/description-status";
export interface DescriptionWorkflow extends BaseEntity {
name: string;
description: string;
definition: DescriptionWorkflowDefinition;
}
export interface DescriptionWorkflowDefinition {
startingStatus: DescriptionStatus;
statusTransitions: DescriptionWorkflowDefinitionTransition[];
}
export interface DescriptionWorkflowDefinitionTransition {
fromStatus: DescriptionStatus;
toStatus: DescriptionStatus;
}

View File

@ -0,0 +1,7 @@
import { Lookup } from "@common/model/lookup";
export class DescriptionWorkflowLookup extends Lookup{
constructor() {
super();
}
}

View File

@ -1,14 +1,7 @@
import { Lookup } from "@common/model/lookup";
import { Guid } from "@common/types/guid";
import { IsActive } from "../common/enum/is-active.enum";
export class PlanWorkflowLookup extends Lookup implements PlanWorkflowFilter{
tenantId: Guid;
export class PlanWorkflowLookup extends Lookup{
constructor() {
super();
}
}
export interface PlanWorkflowFilter {
tenantId: Guid;
}

View File

@ -0,0 +1,55 @@
import { HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { DescriptionWorkflow } from "@app/core/model/workflow/description-workflow";
import { DescriptionWorkflowPersist } from "@app/core/model/workflow/description-workflow-persist";
import { QueryResult } from "@common/model/query-result";
import { Guid } from "@common/types/guid";
import { Observable, catchError, throwError } from "rxjs";
import { ConfigurationService } from "../configuration/configuration.service";
import { BaseHttpV2Service } from "../http/base-http-v2.service";
import { DescriptionWorkflowLookup } from "@app/core/query/description-workflow.lookup";
@Injectable()
export class DescriptionWorkflowService {
private headers = new HttpHeaders();
constructor(
private http: BaseHttpV2Service,
private configurationService: ConfigurationService,
) {
}
private get apiBase(): string { return `${this.configurationService.server}description-workflow`; }
getCurrent(reqFields: string[] = []): Observable<DescriptionWorkflow> {
const url = `${this.apiBase}/current-tenant`;
const options = { params: { f: reqFields } };
return this.http
.get<DescriptionWorkflow>(url, options).pipe(
catchError((error: any) => throwError(() => error)));
}
query(q: DescriptionWorkflowLookup): Observable<QueryResult<DescriptionWorkflow>> {
const url = `${this.apiBase}/query`;
return this.http.post<QueryResult<DescriptionWorkflow>>(url, q).pipe(
catchError((error: any) => throwError(() => error))
);
}
persist(item: DescriptionWorkflowPersist): Observable<DescriptionWorkflow> {
const url = `${this.apiBase}/persist`;
return this.http
.post<DescriptionWorkflow>(url, item).pipe(
catchError((error: any) => throwError(() => error)));
}
delete(id: Guid): Observable<void> {
const url = `${this.apiBase}/${id}`;
return this.http
.delete<void>(url).pipe(
catchError((error: any) => throwError(() => error)));
}
}

View File

@ -23,6 +23,7 @@ export class AnalyticsService {
public static FileTransformerEditor: string = 'Admin: TenantConfigurations';
public static LogoEditor: string = 'Admin: TenantConfigurations';
public static PlanWorkflowEditor: string = 'Admin: TenantConfigurations';
public static DescriptionWorkflowEditor: string = 'Admin: TenantConfigurations';
public static ContactContent: string = 'Contact Content';
public static RecentEditedActivity: string = 'Recent DMP Activity';
public static DescriptionEditor: string = 'Description Editor';

View File

@ -2,8 +2,8 @@ import { HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { BaseHttpV2Service } from "../http/base-http-v2.service";
import { ConfigurationService } from "../configuration/configuration.service";
import { PlanWorkflowPersist } from "@app/core/model/plan-workflow/plan-workflow-persist";
import { PlanWorkflow } from "@app/core/model/plan-workflow/plan-workflow";
import { PlanWorkflowPersist } from "@app/core/model/workflow/plan-workflow-persist";
import { PlanWorkflow } from "@app/core/model/workflow/plan-workflow";
import { catchError, map, Observable, of, throwError } from "rxjs";
import { PlanWorkflowLookup } from "@app/core/query/plan-workflow.lookup";
import { QueryResult } from "@common/model/query-result";

View File

@ -0,0 +1,125 @@
import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from "@angular/forms";
import { DescriptionWorkflow } from "@app/core/model/workflow/description-workflow";
import { DescriptionWorkflowDefinitionPersist, DescriptionWorkflowPersist } from "@app/core/model/workflow/description-workflow-persist";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";
import { Guid } from "@common/types/guid";
export class DescriptionWorkflowEditorModel extends BaseEditorModel implements DescriptionWorkflowPersist {
name: string = 'default';
description: string;
definition: DescriptionWorkflowDefinitionPersist;
public fromModel(item: DescriptionWorkflow): DescriptionWorkflowEditorModel {
if(item){
super.fromModel(item);
this.description = item.description;
this.definition = {
startingStatusId: item.definition?.startingStatus?.id ?? null,
statusTransitions: []
};
item.definition?.statusTransitions?.forEach((st) =>
this.definition.statusTransitions.push({
fromStatusId: st.fromStatus?.id,
toStatusId: st.toStatus?.id
})
)
}
return this;
}
buildForm(params: {context?: ValidationContext, disabled?: boolean}): FormGroup<DescriptionWorkflowForm> {
const {context, disabled = false} = params;
const mainContext = context ?? this.createValidationContext();
const formGroup = new FormGroup<DescriptionWorkflowForm>({
id: new FormControl({value: this.id, disabled }, mainContext.getValidation('id').validators),
hash: new FormControl({value: this.hash, disabled }, mainContext.getValidation('hash').validators),
name: new FormControl({value: this.name, disabled}, mainContext.getValidation('name').validators),
description: new FormControl({value: this.description, disabled}, mainContext.getValidation('description').validators),
definition: new FormGroup<DescriptionWorkflowDefinitionForm>({
startingStatusId: new FormControl({value: this.definition?.startingStatusId, disabled}, mainContext.getValidation('startingStatusId').validators),
statusTransitions: new FormArray<FormGroup<DescriptionWorkflowDefinitionTransitionForm>>([], mainContext.getValidation('statusTransitions').validators)
}, mainContext.getValidation('definition').validators)
})
this.definition?.statusTransitions?.forEach((st, index) => {
const itemContext = context ?? this.createValidationContext(`StatusTransition[${index}]`)
formGroup.controls.definition.controls.statusTransitions.push(new FormGroup({
fromStatusId: new FormControl({value: st.fromStatusId,disabled}, itemContext.getValidation('fromStatusId').validators),
toStatusId: new FormControl({value: st.toStatusId,disabled}, itemContext.getValidation('fromStatusId').validators)
}, this.differentStatusId()))
})
return formGroup;
}
buildStatusTransitionForm(index: number): FormGroup<DescriptionWorkflowDefinitionTransitionForm> {
const itemContext = this.createValidationContext(`StatusTransition[${index}]`)
return new FormGroup({
fromStatusId: new FormControl(null, itemContext.getValidation('fromStatusId').validators),
toStatusId: new FormControl(null, itemContext.getValidation('toStatusId').validators)
}, this.differentStatusId())
}
reApplyDefinitionValidators(formGroup: FormGroup<DescriptionWorkflowDefinitionForm>) {
if(!formGroup?.controls?.statusTransitions?.length) { return; }
formGroup.controls.statusTransitions.controls.forEach((st, index) => {
const context = this.createValidationContext(`StatusTransition[${index}]`);
st.clearValidators();
st.addValidators(this.differentStatusId());
st.controls.fromStatusId.clearValidators();
st.controls.fromStatusId.addValidators(context.getValidation('fromStatusId').validators);
st.controls.toStatusId.clearValidators();
st.controls.toStatusId.addValidators(context.getValidation('toStatusId').validators);
})
}
createValidationContext(rootPath?: string): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}id`)] });
baseValidationArray.push({ key: 'name', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}name`)] });
baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}description`)] });
baseValidationArray.push({ key: 'hash', validators: [] });
baseValidationArray.push({ key: 'definition', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}definition`)] });
baseValidationArray.push({ key: 'statusTransitions', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}statusTransitions`)] });
baseValidationArray.push({ key: 'startingStatusId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}startingStatusId`)] });
baseValidationArray.push({ key: 'fromStatusId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}fromStatusId`)] });
baseValidationArray.push({ key: 'toStatusId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}toStatusId`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
differentStatusId(): ValidatorFn {
return (formGroup: FormGroup<DescriptionWorkflowDefinitionTransitionForm>): { [key: string]: any } => {
const fromStatusId = formGroup?.controls?.fromStatusId?.value;
const toStatusId = formGroup?.controls?.toStatusId?.value;
if(!fromStatusId || !toStatusId){
return null;
}
return fromStatusId?.toString().toLowerCase() == toStatusId?.toString()?.toLowerCase() ?
{'differentStatusId': true} : null
}
}
}
export interface DescriptionWorkflowForm {
id: FormControl<Guid>;
hash: FormControl<string>;
name: FormControl<string>;
description: FormControl<string>;
definition: FormGroup<DescriptionWorkflowDefinitionForm>;
}
export interface DescriptionWorkflowDefinitionForm {
startingStatusId: FormControl<Guid>;
statusTransitions: FormArray<FormGroup<DescriptionWorkflowDefinitionTransitionForm>>
}
export interface DescriptionWorkflowDefinitionTransitionForm {
fromStatusId: FormControl<Guid>;
toStatusId: FormControl<Guid>;
}

View File

@ -0,0 +1,40 @@
import { Injectable } from "@angular/core";
import { DescriptionWorkflow, DescriptionWorkflowDefinition, DescriptionWorkflowDefinitionTransition } from "@app/core/model/workflow/description-workflow";
import { DescriptionWorkflowService } from "@app/core/services/description-workflow/description-workflow.service";
import { BaseEditorResolver } from "@common/base/base-editor.resolver";
import { takeUntil } from "rxjs";
import { nameof } from "ts-simple-nameof";
@Injectable()
export class DescriptionWorkflowEditorResolver extends BaseEditorResolver {
constructor(private descriptionWorkflowService: DescriptionWorkflowService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<DescriptionWorkflow>(x => x.name),
nameof<DescriptionWorkflow>(x => x.description),
nameof<DescriptionWorkflow>(x => x.definition),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.startingStatus.id)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.startingStatus.name)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.statusTransitions)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.statusTransitions), nameof<DescriptionWorkflowDefinitionTransition>(x => x.fromStatus.id)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.statusTransitions), nameof<DescriptionWorkflowDefinitionTransition>(x => x.fromStatus.name)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.statusTransitions), nameof<DescriptionWorkflowDefinitionTransition>(x => x.toStatus.id)].join('.'),
[nameof<DescriptionWorkflow>(x => x.definition), nameof<DescriptionWorkflowDefinition>(x => x.statusTransitions), nameof<DescriptionWorkflowDefinitionTransition>(x => x.toStatus.name)].join('.'),
]
}
resolve() {
const fields = [
...DescriptionWorkflowEditorResolver.lookupFields()
];
return this.descriptionWorkflowService.getCurrent(fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,86 @@
<div *ngIf="formGroup" class="container-fluid description-workflow">
<div class="row">
<div class="col-12">
<mat-form-field class="w-100">
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.STARTING-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="definitionForm?.controls?.startingStatusId"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-DESCRIPTION-STATUS' | translate}}"
[configuration]="descriptionStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="definitionForm?.controls?.startingStatusId.hasError('backendError')">{{definitionForm?.controls?.startingStatusId.getError('backendError').message}}</mat-error>
<mat-error *ngIf="definitionForm?.controls.startingStatusId.hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-12">
<button mat-button class="action-btn row" type="button" (click)="addStatusTransition()" [disabled]="formGroup.disabled">{{'WORKFLOW-EDITOR.ACTIONS.ADD-STATUS-TRANSITION' | translate}}</button>
</div>
<div class="row mb-3">
<mat-error class="pl-3 pt-2" *ngIf="definitionForm?.touched && definitionForm?.controls?.statusTransitions?.hasError('required')">
{{'WORKFLOW-EDITOR.ERRORS.STATUS-TRANSITION-REQUIRED' | translate}}
</mat-error>
@for(transitionForm of definitionForm?.controls?.statusTransitions?.controls; track transitionForm; let index = $index){
<div class="col-12">
<div class="row mb-3 d-flex align-items-center">
<div class="col-auto d-flex">
<mat-card-title>{{'WORKFLOW-EDITOR.FIELDS.STATUS-TRANSITION' | translate}} {{index + 1}}</mat-card-title>
</div>
<div class="col-auto d-flex">
<button mat-icon-button class="action-list-icon" matTooltip="{{'WORKFLOW-EDITOR.ACTIONS.REMOVE-STATUS-TRANSITION' | translate}}" (click)="removeStatusTransition(index)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<mat-form-field class="w-100">
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.FROM-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="transitionForm?.controls?.fromStatusId"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-DESCRIPTION-STATUS' | translate}}"
[configuration]="descriptionStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="transitionForm?.controls?.fromStatusId.hasError('backendError')">{{transitionForm?.controls?.fromStatusId.getError('backendError').message}}</mat-error>
<mat-error *ngIf="transitionForm?.controls.fromStatusId.hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-12 col-lg-6">
<mat-form-field class="w-100">
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.TO-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="transitionForm?.controls?.toStatusId"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-DESCRIPTION-STATUS' | translate}}"
[configuration]="descriptionStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="transitionForm?.controls?.toStatusId.hasError('backendError')">{{transitionForm?.controls?.toStatusId.getError('backendError').message}}</mat-error>
<mat-error *ngIf="transitionForm?.controls.toStatusId.hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<mat-error class="pl-3" *ngIf="transitionForm.hasError('differentStatusId')">{{'WORKFLOW-EDITOR.ERRORS.DIFFERENT-STATUS' | translate}}</mat-error>
}
</div>
<div class="col-12">
<div class="row actions-row">
<div class="col"></div>
@if(canDelete){
<div class="col-auto">
<button class="normal-btn-sm" (click)="delete()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.RESET-TO-DEFAULT' | translate}}
</button>
</div>
}
@if(canSave){
<div class="col-auto">
<button class="normal-btn-sm" (click)="formSubmit()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
.description-workflow {
.action-btn {
border-radius: 30px;
background-color: var(--secondary-color);
border: 1px solid transparent;
padding-left: 2em;
padding-right: 2em;
box-shadow: 0px 3px 6px #1E202029;
transition-property: background-color, color;
transition-duration: 200ms;
transition-delay: 50ms;
transition-timing-function: ease-in-out;
&:disabled{
background-color: #CBCBCB;
color: #FFF;
border: 0px;
}
}
}
::ng-deep label {
margin: 0;
}

View File

@ -0,0 +1,213 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionStatus } from '@app/core/model/description-status/description-status';
import { DescriptionWorkflow } from '@app/core/model/workflow/description-workflow';
import { DescriptionWorkflowPersist } from '@app/core/model/workflow/description-workflow-persist';
import { AuthService } from '@app/core/services/auth/auth.service';
import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service';
import { DescriptionWorkflowService } from '@app/core/services/description-workflow/description-workflow.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { BasePendingChangesComponent } from '@common/base/base-pending-changes.component';
import { FormService } from '@common/forms/form-service';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { HttpErrorHandlingService, HttpError } from '@common/modules/errors/error-handling/http-error-handling.service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil, Observable, map } from 'rxjs';
import { nameof } from 'ts-simple-nameof';
import { DescriptionWorkflowForm, DescriptionWorkflowEditorModel, DescriptionWorkflowDefinitionForm } from '../description-workflow-editor.model';
import { DescriptionWorkflowEditorResolver } from '../description-workflow-editor.resolver';
@Component({
selector: 'app-description-workflow-editor',
templateUrl: './description-workflow-editor.component.html',
styleUrl: './description-workflow-editor.component.scss'
})
export class DescriptionWorkflowEditorComponent extends BasePendingChangesComponent implements OnInit{
formGroup: FormGroup<DescriptionWorkflowForm>;
editorModel: DescriptionWorkflowEditorModel;
constructor(
protected dialog: MatDialog,
protected language: TranslateService,
protected formService: FormService,
protected uiNotificationService: UiNotificationService,
protected httpErrorHandlingService: HttpErrorHandlingService,
protected authService: AuthService,
protected enumUtils: EnumUtils,
private logger: LoggingService,
private descriptionWorkflowService: DescriptionWorkflowService,
private analyticsService: AnalyticsService,
private descriptionStatusService: DescriptionStatusService
) {
super();
}
ngOnInit(): void {
this.analyticsService.trackPageView(AnalyticsService.DescriptionWorkflowEditor);
this.getItem((entity) => {
this.prepareForm(entity);
if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) {
this.formGroup.disable();
}
});
}
protected getItem(successFunction: (item: DescriptionWorkflow) => void) {
this.descriptionWorkflowService.getCurrent(DescriptionWorkflowEditorResolver.lookupFields())
.pipe(takeUntil(this._destroyed))
.subscribe({
next: (data) => successFunction(data),
error: (error) => this.onCallbackError(error)
});
}
protected prepareForm(data: DescriptionWorkflow) {
try {
this.editorModel = data ? new DescriptionWorkflowEditorModel().fromModel(data) : new DescriptionWorkflowEditorModel();
this.formGroup = this.editorModel.buildForm({disabled: !this.authService.hasPermission(AppPermission.EditDescriptionWorkflow)});
} catch (error) {
this.logger.error('Could not parse DescriptionWorkflow item: ' + data + error);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
}
protected refreshData(): void {
this.getItem((entity) => {
this.prepareForm(entity);
if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) {
this.formGroup.disable();
}
});
}
protected get canDelete(): boolean {
return this.editorModel?.id && this.authService.hasPermission(this.authService.permissionEnum.DeleteDescriptionWorkflow);
}
protected get canSave(): boolean {
return this.formGroup.touched && this.authService.hasPermission(this.authService.permissionEnum.EditDescriptionWorkflow);
}
protected get definitionForm(): FormGroup<DescriptionWorkflowDefinitionForm> {
return this.formGroup?.controls?.definition;
}
protected onCallbackError(errorResponse: HttpErrorResponse) {
this.httpErrorHandlingService.handleBackedRequestError(errorResponse)
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
if (error.statusCode === 400) {
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
this.formService.validateAllFormFields(this.formGroup);
}
}
protected onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.refreshData();
}
formSubmit(): void {
this.editorModel.validationErrorModel.clear();
this.formService.removeAllBackEndErrors(this.formGroup);
this.formService.touchAllFormFields(this.formGroup);
this.formService.validateAllFormFields(this.formGroup);
if (!this.formGroup?.valid) {
return;
}
this.persistEntity();
}
persistEntity(): void {
const formData = JSON.parse(JSON.stringify(this.formGroup.value)) as DescriptionWorkflowPersist;
this.descriptionWorkflowService.persist(formData)
.pipe(takeUntil(this._destroyed)).subscribe({
complete: () => this.onCallbackSuccess(),
error: (error) => this.onCallbackError(error)
});
}
delete() {
const value = this.formGroup.value;
if (value.id) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
data: {
message: this.language.instant('TENANT-CONFIGURATION-EDITOR.RESET-TO-DEFAULT-DIALOG.RESET-TO-DEFAULT'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this.descriptionWorkflowService.delete(value.id).pipe(takeUntil(this._destroyed))
.subscribe({
complete: () => {
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-RESET'), SnackBarNotificationLevel.Success);
this.prepareForm(null);
},
error: (error) => this.onCallbackError(error)
});
}
});
}
}
addStatusTransition(){
const index = this.formGroup.controls.definition.controls.statusTransitions.length;
this.formGroup.controls.definition.controls.statusTransitions.push(this.editorModel.buildStatusTransitionForm(index));
this.formGroup.markAsTouched();
}
removeStatusTransition(index: number){
this.formGroup.controls.definition.controls.statusTransitions.removeAt(index);
this.editorModel.reApplyDefinitionValidators(this.formGroup.controls.definition);
this.formGroup.markAsTouched();
}
canDeactivate(): boolean | Observable<boolean> {
return this.formGroup ? !this.formGroup.dirty : true;
}
private descriptionStatusLookupFields = [
nameof<DescriptionStatus>(x => x.id),
nameof<DescriptionStatus>(x => x.name),
]
descriptionStatusAutoCompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (excludedItems: any[], data?: any) => this.descriptionStatusService.query(
this.descriptionStatusService.buildLookup({
size: 20,
lookupFields: this.descriptionStatusLookupFields,
excludedIds: excludedItems
})).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.descriptionStatusService.query(
this.descriptionStatusService.buildLookup({
size: 20,
like: searchQuery,
lookupFields: this.descriptionStatusLookupFields
})
).pipe(map(x => x.items)),
getSelectedItem: (id: Guid) => this.descriptionStatusService.getSingle(id, this.descriptionStatusLookupFields),
displayFn: (item: DescriptionStatus) => item.name,
titleFn: (item: DescriptionStatus) => item.name,
valueAssign: (item: DescriptionStatus) => {this.formGroup.markAsTouched(); return item.id},
};
}

View File

@ -1,6 +1,6 @@
import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from "@angular/forms";
import { PlanWorkflow } from "@app/core/model/plan-workflow/plan-workflow";
import { PlanWorkflowDefinitionPersist, PlanWorkflowPersist } from "@app/core/model/plan-workflow/plan-workflow-persist";
import { PlanWorkflow } from "@app/core/model/workflow/plan-workflow";
import { PlanWorkflowDefinitionPersist, PlanWorkflowPersist } from "@app/core/model/workflow/plan-workflow-persist";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";

View File

@ -1,5 +1,5 @@
import { Injectable } from "@angular/core";
import { PlanWorkflow, PlanWorkflowDefinition, PlanWorkflowDefinitionTransition } from "@app/core/model/plan-workflow/plan-workflow";
import { PlanWorkflow, PlanWorkflowDefinition, PlanWorkflowDefinitionTransition } from "@app/core/model/workflow/plan-workflow";
import { PlanWorkflowService } from "@app/core/services/plan/plan-workflow.service";
import { BaseEditorResolver } from "@common/base/base-editor.resolver";
import { takeUntil } from "rxjs";

View File

@ -2,11 +2,11 @@
<div class="row">
<div class="col-12">
<mat-form-field class="w-100">
<mat-label>{{'PLAN-WORKFLOW-EDITOR.FIELDS.STARTING-STATUS' | translate}}*</mat-label>
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.STARTING-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="definitionForm?.controls?.startingStatusId"
placeholder="{{'PLAN-WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
[configuration]="planStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="definitionForm?.controls?.startingStatusId.hasError('backendError')">{{definitionForm?.controls?.startingStatusId.getError('backendError').message}}</mat-error>
@ -14,20 +14,20 @@
</mat-form-field>
</div>
<div class="col-12">
<button mat-button class="action-btn row" type="button" (click)="addStatusTransition()" [disabled]="formGroup.disabled">{{'PLAN-WORKFLOW-EDITOR.ACTIONS.ADD-STATUS-TRANSITION' | translate}}</button>
<button mat-button class="action-btn row" type="button" (click)="addStatusTransition()" [disabled]="formGroup.disabled">{{'WORKFLOW-EDITOR.ACTIONS.ADD-STATUS-TRANSITION' | translate}}</button>
</div>
<div class="row mb-3">
<mat-error class="pl-3 pt-2" *ngIf="definitionForm?.touched && definitionForm?.controls?.statusTransitions?.hasError('required')">
{{'PLAN-WORKFLOW-EDITOR.ERRORS.STATUS-TRANSITION-REQUIRED' | translate}}
{{'WORKFLOW-EDITOR.ERRORS.STATUS-TRANSITION-REQUIRED' | translate}}
</mat-error>
@for(transitionForm of definitionForm?.controls?.statusTransitions?.controls; track transitionForm; let index = $index){
<div class="col-12">
<div class="row mb-3 d-flex align-items-center">
<div class="col-auto d-flex">
<mat-card-title>{{'PLAN-WORKFLOW-EDITOR.FIELDS.STATUS-TRANSITION' | translate}} {{index + 1}}</mat-card-title>
<mat-card-title>{{'WORKFLOW-EDITOR.FIELDS.STATUS-TRANSITION' | translate}} {{index + 1}}</mat-card-title>
</div>
<div class="col-auto d-flex">
<button mat-icon-button class="action-list-icon" matTooltip="{{'PLAN-WORKFLOW-EDITOR.ACTIONS.REMOVE-STATUS-TRANSITION' | translate}}" (click)="removeStatusTransition(index)" [disabled]="formGroup.disabled">
<button mat-icon-button class="action-list-icon" matTooltip="{{'WORKFLOW-EDITOR.ACTIONS.REMOVE-STATUS-TRANSITION' | translate}}" (click)="removeStatusTransition(index)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
@ -35,11 +35,11 @@
</div>
<div class="col-12 col-lg-6">
<mat-form-field class="w-100">
<mat-label>{{'PLAN-WORKFLOW-EDITOR.FIELDS.FROM-STATUS' | translate}}*</mat-label>
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.FROM-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="transitionForm?.controls?.fromStatusId"
placeholder="{{'PLAN-WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
[configuration]="planStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="transitionForm?.controls?.fromStatusId.hasError('backendError')">{{transitionForm?.controls?.fromStatusId.getError('backendError').message}}</mat-error>
@ -48,18 +48,18 @@
</div>
<div class="col-12 col-lg-6">
<mat-form-field class="w-100">
<mat-label>{{'PLAN-WORKFLOW-EDITOR.FIELDS.TO-STATUS' | translate}}*</mat-label>
<mat-label>{{'WORKFLOW-EDITOR.FIELDS.TO-STATUS' | translate}}*</mat-label>
<app-single-auto-complete
[required]="true"
[formControl]="transitionForm?.controls?.toStatusId"
placeholder="{{'PLAN-WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
placeholder="{{'WORKFLOW-EDITOR.ACTIONS.SELECT-PLAN-STATUS' | translate}}"
[configuration]="planStatusAutoCompleteConfiguration"
/>
<mat-error *ngIf="transitionForm?.controls?.toStatusId.hasError('backendError')">{{transitionForm?.controls?.toStatusId.getError('backendError').message}}</mat-error>
<mat-error *ngIf="transitionForm?.controls.toStatusId.hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<mat-error class="pl-3" *ngIf="transitionForm.hasError('differentStatusId')">{{'PLAN-WORKFLOW-EDITOR.ERRORS.DIFFERENT-STATUS' | translate}}</mat-error>
<mat-error class="pl-3" *ngIf="transitionForm.hasError('differentStatusId')">{{'WORKFLOW-EDITOR.ERRORS.DIFFERENT-STATUS' | translate}}</mat-error>
}
</div>
<div class="col-12">

View File

@ -5,7 +5,7 @@ import { PlanWorkflowDefinitionForm, PlanWorkflowEditorModel, PlanWorkflowForm }
import { FormGroup } from '@angular/forms';
import { PlanWorkflowService } from '@app/core/services/plan/plan-workflow.service';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { PlanWorkflow } from '@app/core/model/plan-workflow/plan-workflow';
import { PlanWorkflow } from '@app/core/model/workflow/plan-workflow';
import { PlanWorkflowEditorResolver } from '../plan-workflow-editor.resolver';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
@ -15,7 +15,7 @@ import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/serv
import { LoggingService } from '@app/core/services/logging/logging-service';
import { FormService } from '@common/forms/form-service';
import { TranslateService } from '@ngx-translate/core';
import { PlanWorkflowPersist } from '@app/core/model/plan-workflow/plan-workflow-persist';
import { PlanWorkflowPersist } from '@app/core/model/workflow/plan-workflow-persist';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
@ -192,9 +192,11 @@ export class PlanWorkflowEditorComponent extends BasePendingChangesComponent imp
]
planStatusAutoCompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.planStatusService.query(
initialItems: (excludedItems: any[], data?: any) => this.planStatusService.query(
this.planStatusService.buildLookup({
size: 20, lookupFields: this.planStatusLookupFields
size: 20,
lookupFields: this.planStatusLookupFields,
excludedIds: excludedItems
})).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.planStatusService.query(
this.planStatusService.buildLookup({

View File

@ -81,6 +81,15 @@
<ng-template matExpansionPanelContent>
<app-plan-workflow-editor></app-plan-workflow-editor>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.DESCRIPTION-WORKFLOW.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.DESCRIPTION-WORKFLOW.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-description-workflow-editor></app-description-workflow-editor>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>

View File

@ -21,6 +21,7 @@ import { LogoEditorComponent } from './editor/logo/logo-editor.component';
import { NgxColorsModule } from 'ngx-colors';
import { NotifierListModule } from '@notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module';
import { PlanWorkflowEditorComponent } from './editor/plan-workflow/plan-workflow-editor/plan-workflow-editor.component';
import { DescriptionWorkflowEditorComponent } from './editor/description-workflow/description-workflow-editor/description-workflow-editor.component';
@NgModule({
imports: [
@ -47,7 +48,8 @@ import { PlanWorkflowEditorComponent } from './editor/plan-workflow/plan-workflo
DepositEditorComponent,
FileTransformerEditorComponent,
LogoEditorComponent,
PlanWorkflowEditorComponent
PlanWorkflowEditorComponent,
DescriptionWorkflowEditorComponent
]
})
export class TenantConfigurationModule { }