Plan Workflow > add plan workflow editor to tenant configuration

This commit is contained in:
mchouliara 2024-08-26 16:49:30 +03:00
parent d8959d4180
commit e29912a354
26 changed files with 840 additions and 14 deletions

View File

@ -217,6 +217,10 @@ export enum AppPermission {
EditUsageLimit = "EditUsageLimit",
DeleteUsageLimit = "DeleteUsageLimit",
//PlanWorkflow
EditPlanWorkflow = "EditPlanWorkflow",
DeletePlanWorkflow = "DeletePlanWorkflow",
// UI Pages
ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage",
@ -245,5 +249,6 @@ export enum AppPermission {
ViewUsageLimitPage = "ViewUsageLimitPage",
ViewPlanStatusPage = "ViewPlanStatusPage",
ViewDescriptionStatusPage = "ViewDescriptionStatusPage",
ViewPlanWorkflowPage = "ViewPlanWorkflowPage"
}

View File

@ -50,6 +50,7 @@ import { RouterUtilsService } from './services/router/router-utils.service';
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';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -119,7 +120,8 @@ export class CoreServiceModule {
RouterUtilsService,
UsageLimitService,
PlanStatusService,
DescriptionStatusService
DescriptionStatusService,
PlanWorkflowService
],
};
}

View File

@ -13,6 +13,6 @@ export interface PlanWorkflowDefinitionPersist {
}
export interface PlanWorkflowDefinitionTransitionPersist {
fromStatus: Guid;
toStatus: Guid;
fromStatusId: Guid;
toStatusId: Guid;
}

View File

@ -3,19 +3,12 @@ import { Guid } from "@common/types/guid";
import { IsActive } from "../common/enum/is-active.enum";
export class PlanWorkflowLookup extends Lookup implements PlanWorkflowFilter{
ids: Guid[];
excludedIds: Guid[];
like: string;
isActive: IsActive[];
tenantId: Guid;
constructor() {
super();
}
}
export interface PlanWorkflowFilter {
ids: Guid[];
excludedIds: Guid[];
like: string;
isActive: IsActive[];
tenantId: Guid;
}

View File

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

View File

@ -8,6 +8,10 @@ import { QueryResult } from "@common/model/query-result";
import { catchError, Observable, throwError } from "rxjs";
import { PlanStatusPersist } from "@app/core/model/plan-status/plan-status-persist";
import { Guid } from "@common/types/guid";
import { PlanLookup } from "@app/core/query/plan.lookup";
import { FilterService } from "@common/modules/text-filter/filter-service";
import { IsActive } from "@notification-service/core/enum/is-active.enum";
import { nameof } from "ts-simple-nameof";
@Injectable()
export class PlanStatusService {
@ -16,6 +20,7 @@ export class PlanStatusService {
constructor(
private http: BaseHttpV2Service,
private configurationService: ConfigurationService,
private filterService: FilterService
) {
}
@ -50,4 +55,30 @@ export class PlanStatusService {
.delete<PlanStatus>(url).pipe(
catchError((error: any) => throwError(() => error)));
}
buildLookup(params: {
like?: string,
excludedIds?: Guid[],
ids?: Guid[],
lookupFields?: string[],
size?: number,
order?: string[]
}): PlanStatusLookup {
const {like, excludedIds, ids, lookupFields, size = 100, order} = params;
const lookup = new PlanLookup();
lookup.isActive = [IsActive.Active];
lookup.order = { items: order ?? [nameof<PlanStatus>(x => x.name)] };
lookup.page = { size, offset: 0 };
if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; }
if (ids && ids.length > 0) { lookup.ids = ids; }
if (like) { lookup.like = this.filterService.transformLike(like); }
if(lookupFields){
lookup.project = {
fields: lookupFields
}
};
return lookup;
}
}

View File

@ -0,0 +1,55 @@
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 { catchError, map, Observable, of, throwError } from "rxjs";
import { PlanWorkflowLookup } from "@app/core/query/plan-workflow.lookup";
import { QueryResult } from "@common/model/query-result";
import { Guid } from "@common/types/guid";
@Injectable()
export class PlanWorkflowService {
private headers = new HttpHeaders();
constructor(
private http: BaseHttpV2Service,
private configurationService: ConfigurationService,
) {
}
private get apiBase(): string { return `${this.configurationService.server}plan-workflow`; }
getCurrent(reqFields: string[] = []): Observable<PlanWorkflow> {
const url = `${this.apiBase}/current-tenant`;
const options = { params: { f: reqFields } };
return this.http
.get<PlanWorkflow>(url, options).pipe(
catchError((error: any) => throwError(() => error)));
}
query(q: PlanWorkflowLookup): Observable<QueryResult<PlanWorkflow>> {
const url = `${this.apiBase}/query`;
return this.http.post<QueryResult<PlanWorkflow>>(url, q).pipe(
catchError((error: any) => throwError(() => error))
);
}
persist(item: PlanWorkflowPersist): Observable<PlanWorkflow> {
const url = `${this.apiBase}/persist`;
return this.http
.post<PlanWorkflow>(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

@ -95,6 +95,7 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus
export interface PlanStatusForm {
id: FormControl<Guid>;
hash: FormControl<string>;
name: FormControl<string>;
description: FormControl<string>;
internalStatus: FormControl<PlanStatusEnum>;

View File

@ -0,0 +1,125 @@
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 { 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 PlanWorkflowEditorModel extends BaseEditorModel implements PlanWorkflowPersist {
name: string = 'default';
description: string;
definition: PlanWorkflowDefinitionPersist;
public fromModel(item: PlanWorkflow): PlanWorkflowEditorModel {
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<PlanWorkflowForm> {
const {context, disabled = false} = params;
const mainContext = context ?? this.createValidationContext();
const formGroup = new FormGroup<PlanWorkflowForm>({
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<PlanWorkflowDefinitionForm>({
startingStatusId: new FormControl({value: this.definition?.startingStatusId, disabled}, mainContext.getValidation('startingStatusId').validators),
statusTransitions: new FormArray<FormGroup<PlanWorkflowDefinitionTransitionForm>>([], 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<PlanWorkflowDefinitionTransitionForm> {
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<PlanWorkflowDefinitionForm>) {
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<PlanWorkflowDefinitionTransitionForm>): { [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 PlanWorkflowForm {
id: FormControl<Guid>;
hash: FormControl<string>;
name: FormControl<string>;
description: FormControl<string>;
definition: FormGroup<PlanWorkflowDefinitionForm>;
}
export interface PlanWorkflowDefinitionForm {
startingStatusId: FormControl<Guid>;
statusTransitions: FormArray<FormGroup<PlanWorkflowDefinitionTransitionForm>>
}
export interface PlanWorkflowDefinitionTransitionForm {
fromStatusId: FormControl<Guid>;
toStatusId: FormControl<Guid>;
}

View File

@ -0,0 +1,40 @@
import { Injectable } from "@angular/core";
import { PlanWorkflow, PlanWorkflowDefinition, PlanWorkflowDefinitionTransition } from "@app/core/model/plan-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";
import { nameof } from "ts-simple-nameof";
@Injectable()
export class PlanWorkflowEditorResolver extends BaseEditorResolver {
constructor(private planWorkflowService: PlanWorkflowService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<PlanWorkflow>(x => x.name),
nameof<PlanWorkflow>(x => x.description),
nameof<PlanWorkflow>(x => x.definition),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.startingStatus.id)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.startingStatus.name)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.statusTransitions)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.statusTransitions), nameof<PlanWorkflowDefinitionTransition>(x => x.fromStatus.id)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.statusTransitions), nameof<PlanWorkflowDefinitionTransition>(x => x.fromStatus.name)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.statusTransitions), nameof<PlanWorkflowDefinitionTransition>(x => x.toStatus.id)].join('.'),
[nameof<PlanWorkflow>(x => x.definition), nameof<PlanWorkflowDefinition>(x => x.statusTransitions), nameof<PlanWorkflowDefinitionTransition>(x => x.toStatus.name)].join('.'),
]
}
resolve() {
const fields = [
...PlanWorkflowEditorResolver.lookupFields()
];
return this.planWorkflowService.getCurrent(fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,86 @@
<div *ngIf="formGroup" class="container-fluid plan-workflow">
<div class="row">
<div class="col-12">
<mat-form-field class="w-100">
<mat-label>{{'PLAN-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}}"
[configuration]="planStatusAutoCompleteConfiguration"
/>
<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">{{'PLAN-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}}
</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>
</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">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</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>
<app-single-auto-complete
[required]="true"
[formControl]="transitionForm?.controls?.fromStatusId"
placeholder="{{'PLAN-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>
<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>{{'PLAN-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}}"
[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>
}
</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 @@
.plan-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,211 @@
import { Component, OnInit } from '@angular/core';
import { BasePendingChangesComponent } from '@common/base/base-pending-changes.component';
import { map, Observable, takeUntil } from 'rxjs';
import { PlanWorkflowDefinitionForm, PlanWorkflowEditorModel, PlanWorkflowForm } from '../plan-workflow-editor.model';
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 { 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';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { AuthService } from '@app/core/services/auth/auth.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
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 { 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';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanStatus } from '@app/core/model/plan-status/plan-status';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { PlanStatusService } from '@app/core/services/plan/plan-status.service';
import { nameof } from 'ts-simple-nameof';
import { Guid } from '@common/types/guid';
@Component({
selector: 'app-plan-workflow-editor',
templateUrl: './plan-workflow-editor.component.html',
styleUrl: './plan-workflow-editor.component.scss'
})
export class PlanWorkflowEditorComponent extends BasePendingChangesComponent implements OnInit{
formGroup: FormGroup<PlanWorkflowForm>;
editorModel: PlanWorkflowEditorModel;
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 planWorkflowService: PlanWorkflowService,
private analyticsService: AnalyticsService,
private planStatusService: PlanStatusService
) {
super();
}
ngOnInit(): void {
this.analyticsService.trackPageView(AnalyticsService.PlanWorkflowEditor);
this.getItem((entity) => {
this.prepareForm(entity);
if (this.formGroup && this.editorModel.belongsToCurrentTenant == false) {
this.formGroup.disable();
}
});
}
protected getItem(successFunction: (item: PlanWorkflow) => void) {
this.planWorkflowService.getCurrent(PlanWorkflowEditorResolver.lookupFields())
.pipe(takeUntil(this._destroyed))
.subscribe({
next: (data) => successFunction(data),
error: (error) => this.onCallbackError(error)
});
}
protected prepareForm(data: PlanWorkflow) {
try {
this.editorModel = data ? new PlanWorkflowEditorModel().fromModel(data) : new PlanWorkflowEditorModel();
this.formGroup = this.editorModel.buildForm({disabled: !this.authService.hasPermission(AppPermission.EditPlanWorkflow)});
} catch (error) {
this.logger.error('Could not parse PlanWorkflow 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.DeletePlanWorkflow);
}
protected get canSave(): boolean {
return this.formGroup.touched && this.authService.hasPermission(this.authService.permissionEnum.EditPlanWorkflow);
}
protected get definitionForm(): FormGroup<PlanWorkflowDefinitionForm> {
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 PlanWorkflowPersist;
this.planWorkflowService.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.planWorkflowService.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 planStatusLookupFields = [
nameof<PlanStatus>(x => x.id),
nameof<PlanStatus>(x => x.name),
]
planStatusAutoCompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.planStatusService.query(
this.planStatusService.buildLookup({
size: 20, lookupFields: this.planStatusLookupFields
})).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.planStatusService.query(
this.planStatusService.buildLookup({
size: 20,
like: searchQuery,
lookupFields: this.planStatusLookupFields
})
).pipe(map(x => x.items)),
getSelectedItem: (id: Guid) => this.planStatusService.getSingle(id, this.planStatusLookupFields),
displayFn: (item: PlanStatus) => item.name,
titleFn: (item: PlanStatus) => item.name,
valueAssign: (item: PlanStatus) => {this.formGroup.markAsTouched(); return item.id},
};
}

View File

@ -73,7 +73,15 @@
<app-tenant-configuration-notifier-list-editor></app-tenant-configuration-notifier-list-editor>
</ng-template>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.PLAN-WORKFLOW.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.PLAN-WORKFLOW.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-plan-workflow-editor></app-plan-workflow-editor>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>

View File

@ -20,6 +20,7 @@ import { FileTransformerEditorComponent } from './editor/file-transformer/file-t
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';
@NgModule({
imports: [
@ -45,7 +46,8 @@ import { NotifierListModule } from '@notification-service/ui/admin/tenant-config
DefaultUserLocaleEditorComponent,
DepositEditorComponent,
FileTransformerEditorComponent,
LogoEditorComponent
LogoEditorComponent,
PlanWorkflowEditorComponent
]
})
export class TenantConfigurationModule { }

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -450,6 +450,10 @@
"TITLE": "File Transformer Plugins",
"HINT": "Change file transformer plugins"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"FIELDS": {
"TIMEZONE": "Timezone",
"CULTURE": "Format",
@ -479,6 +483,7 @@
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"

View File

@ -476,11 +476,16 @@
"RESET-TO-DEFAULT-DIALOG": {
"RESET-TO-DEFAULT": "Reset To Default"
},
"PLAN-WORKFLOW": {
"TITLE": "Plan Workflow",
"HINT": "Configure the workflow for this tenant's plans"
},
"ACTIONS": {
"SAVE": "Save",
"UPLOAD": "Upload",
"DOWNLOAD": "Download",
"ADD-SOURCE": "Add Source",
"REMOVE-SOURCE": "Remove Source",
"RESET-TO-DEFAULT": "Reset To Default"
}
},
@ -2522,6 +2527,23 @@
"CONFIRM": "Confirm"
}
},
"PLAN-WORKFLOW-EDITOR": {
"ACTIONS": {
"SELECT-PLAN-STATUS": "Select plan status",
"ADD-STATUS-TRANSITION": "Add status transition",
"REMOVE-STATUS-TRANSITION": "Remove status transition"
},
"FIELDS": {
"STARTING-STATUS": "Starting Status",
"STATUS-TRANSITION": "Status Transition",
"FROM-STATUS": "From Status",
"TO-STATUS": "To Status"
},
"ERRORS": {
"DIFFERENT-STATUS": "Transition statuses must be different",
"STATUS-TRANSITION-REQUIRED": "At least one Status transition pair must be defined"
}
},
"copy": "Copy",
"clone": "Clone",
"new-version": "New Version"