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. (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 = 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 = 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 , // 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. } }