uoa-repository-manager-service/app/features/administration/forms/verification-rule-form/verification-rule-form.comp...

528 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Right } from './../../../../shared/models/right.interface';
import { environment } from './../../../../../environments/environment';
import { USER_RIGHTS } from './../../../../shared/enums/USER_RIGHTS.enum';
import { AuthService } from 'src/app/shared/services/auth.service';
import { CreateVerificationRuleFormValue } from './create-verification-rule-form-value.interface';
import { JournalVerificationsService } from './../../../../shared/services/administration/journal-verifications.service';
import { CapturingVerificationsService } from './../../../../shared/services/administration/capturing-verifications.service';
import { DocumentSubclassificationsService } from 'src/app/shared/services/administration/document-subclassifications.service';
import { IpowerClientsService } from 'src/app/shared/services/administration/ipower-clients.service';
import { CategoriesService } from 'src/app/shared/services/administration/categories.service';
import { DocumentClassificationsService } from 'src/app/shared/services/administration/document-classifications.service';
import { FormBuilder, Validators } from '@angular/forms';
import { DocumentSubclassification } from 'src/app/shared/models/document-subclassification.interface';
import { Component, Input, OnInit } from '@angular/core';
import { CapturingVerification } from '../../../../shared/models/capturing-verification.interface';
import { Category } from '../../../../shared/models/category.interface';
import { IPowerClient } from '../../../../shared/models/ipower-client.interface';
import { JournalVerification } from '../../../../shared/models/journal-verification.interface';
import { VerificationRule } from './../../../../shared/models/verification-rule.interface';
import { DocumentClassification } from '../../../../shared/models/document-classification.interface';
import { ErrorHandlingService } from 'src/app/shared/services/error-handling/error-handling.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-verification-rule-form',
templateUrl: './verification-rule-form.component.html',
styleUrls: ['./verification-rule-form.component.scss']
})
export class VerificationRuleFormComponent implements OnInit {
/*
* Inputs
*/
@Input() dialogLayout: boolean = false; // Controls .scss classes to allow for better dispay in a dialog.
@Input() requiredFields: string[] | boolean = false; // True/False indicates that all/none are required. String[] specifies the form controls required.
@Input() displayValidationMessagesEvenIfPristine: boolean = false; // Serves for manually treating the controls as dirty.
@Input() excludedFormControls: string[]; // Specifies the form controls to be removed and NOT displayed.
/*
* Reactive Form
*/
verificationRuleForm = this.fb.group({
documentClassification: [null],
ipowerName: [null],
ipowerCode: [null],
categoryName: [null],
categoryCode: [null],
subCategoryCode: [null], // This actually represents the complete DocumentSubclassification object.
confidenceLevelMinThreshold: [null],
capturingVerification: [null],
journalVerification: [null],
alteryxRoutineId: [null],
verificationRuleStatus: [true],
template: [null]
});
initiallySetFormValue: VerificationRule;
editMode: boolean = false;
/*
* Other Variables
*/
documentClassificationsList: DocumentClassification[];
categoryNameSuggestions: string[];
categoryCodeSuggestions: string[];
categorySuggestions: Category[];
selectedCategory: Category;
iPowerClientNameSuggestions: string[];
iPowerClientCodeSuggestions: string[];
iPowerClientSuggestions: IPowerClient[];
selectedIPowerClient: IPowerClient;
capturingVerificationsList: CapturingVerification[];
journalVerificationsList: JournalVerification[];
documentSubclassificationsList: DocumentSubclassification[];
availableDocumentSubclassifications: DocumentSubclassification[];
subCategoryCodeDisabled: boolean = true;
/*
* Constructor & Initialisers
*/
constructor(
private fb: FormBuilder,
private documentClassificationsService: DocumentClassificationsService,
private categoriesService: CategoriesService,
private iPowerClientsService: IpowerClientsService,
private documentSubclassificationsService: DocumentSubclassificationsService,
private capturingVerificationsService: CapturingVerificationsService,
private journalVerificationsService: JournalVerificationsService,
private authService: AuthService,
private errorHandlingService: ErrorHandlingService
) { }
ngOnInit(): void {
this.initData();
this.initValidators()
this.excludeFields();
}
initData() {
this.documentClassificationsService.getAll().subscribe(
value => this.documentClassificationsList = value,
err => this.errorHandlingService.showHttpResponseError(err)
);
// This is NOT the list of Subclassifications used for the dropdown.
this.documentSubclassificationsService.getAll().subscribe(
value => this.documentSubclassificationsList = value,
err => this.errorHandlingService.showHttpResponseError(err)
);
this.journalVerificationsService.getJournalVerifications().subscribe(
values => this.journalVerificationsList = values,
err => this.errorHandlingService.showHttpResponseError(err)
);
this.capturingVerificationsService.getCapturingVerifications().subscribe(
values => this.capturingVerificationsList = values,
err => this.errorHandlingService.showHttpResponseError(err)
);
}
// TODO: Have this mechanism offer the use of custom validators too.
initValidators() {
if (!this.requiredFields) { return; }
// In a true/false case.
if (typeof this.requiredFields == 'boolean') {
// If true, enable the required validator for all controls.
if (this.requiredFields) {
Object.keys(this.verificationRuleForm.controls).forEach(key => this.verificationRuleForm.controls[key].setValidators(Validators.required));
}
// If false, do nothing.
return;
}
// If it was a string array, enable the validators for all provided field-names.
(<string[]>this.requiredFields).forEach(field => this.verificationRuleForm.controls[field].setValidators(Validators.required))
}
excludeFields() {
if (!this.excludedFormControls) { return; }
this.excludedFormControls.forEach(field => this.verificationRuleForm.removeControl(field));
}
/*
* Auto-suggest & Auto-complete Category
*/
autosuggestCategoryName(event) {
if (!event.query || event.query.length < 3) {
this.categoryNameSuggestions = [];
return;
}
let classId = this.verificationRuleForm.get('documentClassification').value ? this.verificationRuleForm.get('documentClassification').value.classificationId : null;
this.categoriesService.autosuggestCategoryName(event.query, classId).subscribe(
(values: Category[]) => {
let temp: string[] = [];
this.categorySuggestions = values;
values.map(val => temp.push(val.categoryName));
this.categoryNameSuggestions = temp
},
err => this.errorHandlingService.showHttpResponseError(err)
);
}
autosuggestCategoryCode(event) {
if (event.query.length < 3) {
this.categoryCodeSuggestions = [];
return;
}
let classId = this.verificationRuleForm.get('documentClassification').value ? this.verificationRuleForm.get('documentClassification').value.classificationId : null;
this.categoriesService.autosuggestCategoryCode(event.query, classId).subscribe(
(values: Category[]) => {
let temp: string[] = [];
this.categorySuggestions = values;
values.map(val => temp.push(val.categoryCode));
this.categoryCodeSuggestions = temp
},
err => this.errorHandlingService.showHttpResponseError(err)
);
}
categoryNameSelected(name: string) {
this.selectedCategory = this.categorySuggestions.find(cat => cat.categoryName == name);
this.verificationRuleForm.get('categoryCode').patchValue(this.selectedCategory.categoryCode);
this.verificationRuleForm.updateValueAndValidity();
}
categoryCodeSelected(code: string) {
this.selectedCategory = this.categorySuggestions.find(cat => cat.categoryCode == code);
this.verificationRuleForm.get('categoryName').patchValue(this.selectedCategory.categoryName);
this.verificationRuleForm.updateValueAndValidity();
}
// /*
// * Auto-suggest & Auto-complete IPower Client
// */
// /*
// * Rights-check Warning:
// * If we are NOT in editMode (and are thus on "addMode"), we should only suggest clients for which we actually CAN add a new rule.
// * Note:
// * I know that if on editMode the client-input is disabled and thus this check is redundant,
// * but I think it is better to work with as an abstract perspective as possible, not entailing business logic that may either be unrelated or prone to changes.
// */
// autosuggestIPowerClientName(event) {
// if (!event.query || event.query.length < 3) {
// this.iPowerClientNameSuggestions = [];
// return;
// }
// // If the user has the right to preview all rules (B01), we use the endpoint that returns all iPowerClients,
// // otherwise, the one that checks for the user's User_Access too. Whether the user can see ANY rule has already been handled by the 'search' button.
// let endpointToSubscribeTo: Observable<IPowerClient[]> = USER_RIGHTS.B01.isGrantedToUser(
// this.authService.userRights.find(rbc => rbc.client.id == environment.globalRightsClientID)?.rights
// )
// ? this.iPowerClientsService.getClientsByNameOnly(event.query)
// : this.iPowerClientsService.getClientsByNameDistinct(event.query);
// endpointToSubscribeTo.subscribe(
// (values: IPowerClient[]) => {
// // ***See comment at method level***
// if (!this.editMode) {
// values = values.filter(client => USER_RIGHTS.B03.isGrantedToUser(
// this.authService.userRights.find(rdc => rdc.client.id == client.id)?.rights
// ));
// }
// let temp: string[] = [];
// this.iPowerClientSuggestions = values;
// values.map(val => temp.push(val.name));
// this.iPowerClientNameSuggestions = temp
// },
// err => this.errorHandlingService.showHttpResponseError(err)
// );
// }
// /*
// * Rights-check Warning:
// * If we are NOT in editMode (and are thus on "addMode"), we should only suggest clients for which we actually CAN add a new rule.
// * Note:
// * I know that if on editMode the client-input is disabled and thus this check is redundant,
// * but I think it is better to work with as an abstract perspective as possible, not entailing business logic that may either be unrelated or prone to changes.
// */
// autosuggestIPowerClientCode(event) {
// if (event.query.length < 3) {
// this.iPowerClientCodeSuggestions = [];
// return;
// }
// // If the user has the right to preview all rules (B01), we use the endpoint that returns all iPowerClients,
// // otherwise, the one that checks for the user's User_Access too. Whether the user can see ANY rule has already been handled by the 'search' button.
// let endpointToSubscribeTo: Observable<IPowerClient[]> = USER_RIGHTS.B01.isGrantedToUser(
// this.authService.userRights.find(rbc => rbc.client.id == environment.globalRightsClientID)?.rights
// )
// ? this.iPowerClientsService.getClientsByCodeOnly(event.query): null;
// endpointToSubscribeTo.subscribe(
// (values: IPowerClient[]) => {
// // ***See comment at method level***
// if (!this.editMode) {
// values = values;
// }
// let temp: string[] = [];
// this.iPowerClientSuggestions = values;
// values.map(val => temp.push(val.clientCode));
// this.iPowerClientCodeSuggestions = temp
// },
// err => this.errorHandlingService.showHttpResponseError(err)
// );
// }
/*
* Auto-suggest & Auto-complete IPower Client
*/
autosuggestIPowerClientName(event): void {
if (!event.query || event.query.length < 3) {
this.iPowerClientNameSuggestions = [];
return;
}
this.iPowerClientsService.getClientsByNameOnly(event.query).subscribe(
(values: IPowerClient[]) => {
this.iPowerClientSuggestions = values;
const temp: string[] = [];
values.map(val => temp.push(val.name));
this.iPowerClientNameSuggestions = temp;
},
err => this.errorHandlingService.showHttpResponseError(err)
);
}
autosuggestIPowerClientCode(event): void {
if (event.query.length < 3) {
this.iPowerClientCodeSuggestions = [];
return;
}
this.iPowerClientsService.getClientsByCodeOnly(event.query).subscribe(
(values: IPowerClient[]) => {
this.iPowerClientSuggestions = values;
const temp: string[] = [];
values.map(val => temp.push(val.clientCode));
this.iPowerClientCodeSuggestions = temp;
},
err => this.errorHandlingService.showHttpResponseError(err)
);
}
iPowerClientNameSelected(name: string) {
this.selectedIPowerClient = this.iPowerClientSuggestions.find(client => client.name == name);
this.verificationRuleForm.get('ipowerCode').patchValue(this.selectedIPowerClient.clientCode);
this.verificationRuleForm.updateValueAndValidity();
}
iPowerClientCodeSelected(code: string) {
this.selectedIPowerClient = this.iPowerClientSuggestions.find(client => client.clientCode == code);
this.verificationRuleForm.get('ipowerName').patchValue(this.selectedIPowerClient.name);
this.verificationRuleForm.updateValueAndValidity();
}
/*
* Other Methods
*/
documentClassificationSelected(selection) {
this.availableDocumentSubclassifications = this.documentSubclassificationsList.filter(element => element.documentClassification.classificationId == selection.value.classificationId);
this.verificationRuleForm.get('subCategoryCode').setValue(this.availableDocumentSubclassifications[0]);
this.subCategoryCodeDisabled = false;
}
/*
* Utility Methods
*/
initiateEditMode() {
// let controlsToDisableInEditMode = ['documentClassification', 'ipowerName', 'ipowerCode', 'categoryName', 'categoryCode', 'subCategoryCode']
// controlsToDisableInEditMode.forEach(ctrl => this.verificationRuleForm.get(ctrl).disable());
}
// Auto-suggest inputs - We have to ensure our reactive form holds values that represent the selectedCategory, otherwise truncate it.
syncSelectedCategory() {
// Ιf our form has no value, truncate the selectedCategory either way.
if (!this.verificationRuleForm.get('categoryName').value && !this.verificationRuleForm.get('categoryCode').value) {
this.selectedCategory = null;
return;
}
// If both or either of our form's values match the selectedCategory's and the other one doesn't have a value, all is good.
// Just sync the values in case one is missing. Otherwise truncate the selectedCategory.
if (
this.verificationRuleForm.get('categoryName').value == this.selectedCategory.categoryName
|| this.verificationRuleForm.get('categoryCode').value == this.selectedCategory.categoryCode
) {
this.selectedCategory.categoryName = this.verificationRuleForm.get('categoryName').value;
this.selectedCategory.categoryCode = this.verificationRuleForm.get('categoryCode').value;
}
// If both our values were different from the selectedCategory's, truncate it. This is an extremely abnormal scenario.
else {
console.error('WARNING - syncSelectedCategory()', 'Both of our form\'s values were different from the selectedCategory\'s.');
this.selectedCategory = null;
this.verificationRuleForm.get('categoryName').setValue('');
this.verificationRuleForm.get('categoryCode').setValue('');
this.verificationRuleForm.updateValueAndValidity();
}
}
// Auto-suggest inputs - We have to ensure our reactive form holds values that represent the selectedIPowerClient, otherwise truncate it.
syncSelectedIPowerClient() {
// Ιf our form has no value, truncate the selectedIPowerClient either way.
if (!this.verificationRuleForm.get('ipowerName').value && !this.verificationRuleForm.get('ipowerCode').value) {
this.selectedIPowerClient = null;
return;
}
// If both or either of our form's values match the selectedIPowerClient's and the other one doesn't have a value, all is good.
// Just sync the values in case one is missing. Otherwise truncate the selectedIPowerClient.
if (
this.verificationRuleForm.get('ipowerName').value == this.selectedIPowerClient.name
|| this.verificationRuleForm.get('ipowerCode').value == this.selectedIPowerClient.clientCode
) {
this.selectedIPowerClient.name = this.verificationRuleForm.get('ipowerName').value;
this.selectedIPowerClient.clientCode = this.verificationRuleForm.get('ipowerCode').value;
}
// If both our values were different from the selectedIPowerClient's, truncate it. This is an extremely abnormal scenario.
else {
console.error('WARNING - syncSelectedIPowerClient()', 'Both of our form\'s values were different from the selectedIPowerClient\'s.');
this.selectedIPowerClient = null;
this.verificationRuleForm.get('ipowerName').setValue('');
this.verificationRuleForm.get('ipowerCode').setValue('');
this.verificationRuleForm.updateValueAndValidity();
}
}
/*
* Rights-check Methods
*/
canEditRuleStatus(): boolean {
if (this.initiallySetFormValue) {
return this.authService.userHasRightForClient(USER_RIGHTS.B06, environment.globalRightsClientID);
}
return false;
}
// canEditConfidenceLevel(): boolean {
// if (this.initiallySetFormValue) {
// return this.authService.userHasRightForClient(USER_RIGHTS.B07, this.initiallySetFormValue.client.id);
// }
// return false;
// }
/*
* API methods
*/
public resetForm(): void {
this.verificationRuleForm.reset();
this.selectedCategory = null;
this.selectedIPowerClient = null;
this.editMode = null;
}
public formValue(): CreateVerificationRuleFormValue {
// We keep track of those two using object-variables separate from our ReactiveForm.
// Thus, we now have to make sure the ReactiveForm really has values that match those objects, before we forward them.
// Also, keep in mind that our auto-suggest inputs limit the user in choosing one of their values.
this.syncSelectedCategory();
this.syncSelectedIPowerClient();
let formValue: CreateVerificationRuleFormValue = {
id: this.initiallySetFormValue ? this.initiallySetFormValue.id : null,
confidenceLevelMinThreshold: this.verificationRuleForm.get('confidenceLevelMinThreshold').value,
clientId: this.selectedIPowerClient ? this.selectedIPowerClient.id : null,
categoryId: this.selectedCategory ? this.selectedCategory.id : null,
// The backend only requires the subcategory's name, and not the whole object. Don't ask me.
subCategoryCode: this.verificationRuleForm.get('subCategoryCode').value ? this.verificationRuleForm.get('subCategoryCode').value['subclassificationName'] : '',
capturingVerification: this.verificationRuleForm.get('capturingVerification').value,
journalVerification: this.verificationRuleForm.get('journalVerification').value,
alteryxRoutineId: this.verificationRuleForm.get('alteryxRoutineId').value,
template: this.verificationRuleForm.get('template').value,
// Convert 'verificationRuleStatus' from boolean to string.
verificationRuleStatus: this.verificationRuleForm.get('verificationRuleStatus').value ? 'Enabled' : 'Disabled'
};
return formValue;
}
public setValue(value: VerificationRule): void {
if (!value) {
return;
}
this.editMode = true;
this.initiateEditMode();
this.initiallySetFormValue = value
// If a documentClassification is already selected, enable the subclassification dropdown.
this.subCategoryCodeDisabled = !value.docClassificationCategory;
this.verificationRuleForm.get('documentClassification').setValue(value.docClassificationCategory);
// Having set the documentClassification -and provided there was one- we must also set the availableDocumentSubclassifications.
this.availableDocumentSubclassifications = this.documentSubclassificationsList.filter(element =>
element.documentClassification.classificationId == value.docClassificationCategory.classificationId
);
this.verificationRuleForm.get('ipowerName').setValue(value.client.name);
this.verificationRuleForm.get('ipowerCode').setValue(value.client.clientCode);
this.selectedIPowerClient = value.client;
this.verificationRuleForm.get('categoryName').setValue(value.template.category.categoryName);
this.verificationRuleForm.get('categoryCode').setValue(value.template.category.categoryCode);
this.selectedCategory = value.template.category;
this.verificationRuleForm.get('confidenceLevelMinThreshold').setValue(value.confidenceLevelMinThreshold);
this.verificationRuleForm.get('capturingVerification').setValue(value.capturingVerification);
this.verificationRuleForm.get('journalVerification').setValue(value.journalVerification);
this.verificationRuleForm.get('alteryxRoutineId').setValue(value.alteryxRoutineId);
this.verificationRuleForm.get('template').setValue(value.template);
// Convert 'verificationRuleStatus' from string to boolean.
this.verificationRuleForm.get('verificationRuleStatus').setValue(value.verificationRuleStatus.trim().toLowerCase() == 'enabled');
// To set the subcategory/subclassification we also have to make sure the documentClassification is the same.
// WARNING: Since we have enabled the [forceSelection] option of the <p-autoComplete>,
// if the availableDocumentSubclassifications are not set, documentSubclassification won't be displayed.
this.verificationRuleForm.get('subCategoryCode').setValue(
this.documentSubclassificationsList.find(subClass =>
subClass.subclassificationName == value.template.subCategoryCode && subClass.documentClassification.classificationId == value.docClassificationCategory.classificationId
)
);
this.verificationRuleForm.updateValueAndValidity();
}
public isValid(): boolean {
return this.verificationRuleForm.valid;
}
public initForCreation() {
this.editMode = false;
this.verificationRuleForm.get('verificationRuleStatus').setValue(true); // The default value in creation mode.
this.verificationRuleForm.get('template').setValidators(null); // 'template' is not required during the creation.
}
}