Loading src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html 0 → 100644 +13 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html: 13 added lines, 0 removed lines. Original line number Diff line number Diff line <h2 mat-dialog-title>Import Characteristics?</h2> <mat-dialog-content class="mat-typography"> <p> The selected <b>CustomerFacingServiceSpecification</b>(s) contain configurable characteristics. </p> <p> Do you want to automatically add these as <b>Product Characteristics</b> to the Product Specification? </p> </mat-dialog-content> <mat-dialog-actions align="end"> <button (click)="onYes()" class="btn btn-success ml-2">Yes, Import Characteristics</button> <button (click)="onNo()" class="btn btn-primary ml-2">No, just link Service Specification</button> </mat-dialog-actions> No newline at end of file src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts 0 → 100644 +22 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts: 22 added lines, 0 removed lines. Original line number Diff line number Diff line import { Component } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-confirm-characteristic-assignment', templateUrl: './confirm-characteristic-assignment.component.html', styles: [] }) export class ConfirmCharacteristicAssignmentComponent { constructor( public dialogRef: MatDialogRef<ConfirmCharacteristicAssignmentComponent> ) {} onNo(): void { this.dialogRef.close(false); } onYes(): void { this.dialogRef.close(true); } } No newline at end of file src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html +8 −1 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -14,6 +14,9 @@ <mat-autocomplete #autoComplete="matAutocomplete" (optionSelected)="selected($event)" autoActiveFirstOption> <mat-option *ngFor="let spec of filteredSpecs$ | async" [value]="spec"> {{spec.name}} <b class="badge ml-1 {{ spec['@type'] === 'CustomerFacingServiceSpecification' ? 'badge-warning' : (spec['@type'] === 'ResourceFacingServiceSpecification' ? 'badge-success' : 'badge-danger') }}"> {{ spec['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (spec['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} </b> </mat-option> </mat-autocomplete> </mat-form-field> Loading @@ -26,7 +29,11 @@ <th mat-header-cell *matHeaderCellDef mat-sort-header> Assigned Service Specifications </th> <td mat-cell *matCellDef="let element" matTooltipClass="universal-tooltip" [matTooltip]="element.description" matTooltipPosition="above"> <b>{{element.name}}</b> </td> <b>{{element.name}}</b> <b class="badge ml-1 {{ element['@type'] === 'CustomerFacingServiceSpecification' ? 'badge-warning' : (element['@type'] === 'ResourceFacingServiceSpecification' ? 'badge-success' : 'badge-danger') }}"> {{ element['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (element['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} </b> </td> </ng-container> <ng-container matColumnDef="actions"> Loading src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts +228 −30 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts: 228 added lines, 30 removed lines. Original line number Diff line number Diff line import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core'; import { UntypedFormControl } from '@angular/forms'; import { MatAutocomplete, MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Observable } from 'rxjs'; import { ToastrService } from 'ngx-toastr'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators' import { ProductSpecification, ProductSpecificationUpdate } from 'src/app/openApis/productCatalogManagement/models'; import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; import { ServiceSpecification } from 'src/app/openApis/serviceCatalogManagement/models'; import { ServiceSpecificationService } from 'src/app/openApis/serviceCatalogManagement/services'; import { ConfirmCharacteristicAssignmentComponent } from './confirm-characteristic-assignment/confirm-characteristic-assignment.component'; @Component({ selector: 'app-assign-service-specification', Loading @@ -24,7 +27,10 @@ export class AssignServiceSpecificationComponent implements OnInit { }, private dialogRef: MatDialogRef<AssignServiceSpecificationComponent>, private productSpecService: ProductSpecificationService, private serviceSpecService: ServiceSpecificationService private serviceSpecService: ServiceSpecificationService, private toast: ToastrService, private dialog: MatDialog ) { } @ViewChild('specInput') specInput: ElementRef<HTMLInputElement>; Loading @@ -37,44 +43,78 @@ export class AssignServiceSpecificationComponent implements OnInit { @ViewChild(MatSort, {static: true}) sort: MatSort; specInputCtrl = new UntypedFormControl(); nonSelectedSpecs: ProductSpecification[] nonSelectedSpecs: ProductSpecification[] = []; filteredSpecs$: Observable<ProductSpecification[]> selectedSpecs: ProductSpecification[] = [] // the subject holds the data when it arrives from the api private availableSpecsSubject = new BehaviorSubject<ProductSpecification[]>([]); ngOnInit(): void { this.listServiceSpecs() this.listServiceSpecs(); // for the filtering to work when user starts typing in the input this.filteredSpecs$ = combineLatest([ this.specInputCtrl.valueChanges.pipe(startWith('')), this.availableSpecsSubject.asObservable() ]).pipe( map(([inputValue, specs]) => { const filterValue = typeof inputValue === 'string' ? inputValue.toLowerCase() : ''; return this.performFilter(filterValue, specs); }) ); } listServiceSpecs() { this.serviceSpecService.listServiceSpecification({}).subscribe( data => this.nonSelectedSpecs = data, error => console.error(error), () => { //remove self from available spec list as well as the allready assigned specs const initiallyAssignedSpecIDs: string[] = this.data.productSpec.serviceSpecification.map(el => el.id) this.nonSelectedSpecs = this.nonSelectedSpecs.filter(spec => !initiallyAssignedSpecIDs.includes(spec.id)) data => { // remove the already assigned specs const initiallyAssignedIDs = this.data.productSpec.serviceSpecification.map(el => el.id); const filteredData = data.filter(spec => !initiallyAssignedIDs.includes(spec.id)); this.selectedSpecs = this.data.productSpec.serviceSpecification.slice() this.dataSource.data = this.selectedSpecs this.dataSource.sort = this.sort // push data into subject this.availableSpecsSubject.next(filteredData); this.filteredSpecs$ = this.specInputCtrl.valueChanges.pipe( startWith(null), map( (spec: string | ServiceSpecification) => typeof(spec) === 'string' ? this._filter(spec) : this.nonSelectedSpecs.slice() ) ) this.selectedSpecs = this.data.productSpec.serviceSpecification.slice(); this.dataSource.data = this.selectedSpecs; this.dataSource.sort = this.sort; }, error => this.toast.error('Failed to fetch Service Specifications', error) ); } ) private performFilter(filterValue: string, specs: ProductSpecification[]): ProductSpecification[] { if (!filterValue) { return specs; } return specs.filter(spec => { const name = spec.name ? spec.name.toLowerCase() : ''; return name.indexOf(filterValue) !== -1; }); } selected(event: MatAutocompleteSelectedEvent): void { this.selectedSpecs.push(event.option.value); this.dataSource.data = this.selectedSpecs this.nonSelectedSpecs = this.nonSelectedSpecs.filter(el => el.name != event.option.value.name) const selectedLiteSpec = event.option.value; //retrieve full service specs details, @type and spec characteristics once a service spec is selected this.specInput.nativeElement.value = ''; this.specInputCtrl.setValue(null); this.serviceSpecService.retrieveServiceSpecification({ id: selectedLiteSpec.id }).subscribe( (fullSpec: ServiceSpecification) => { this.selectedSpecs.push(fullSpec); this.dataSource.data = this.selectedSpecs; this.nonSelectedSpecs = this.nonSelectedSpecs.filter( el => el.name != selectedLiteSpec.name ); }, (error) => { this.toast.error('Failed to fetch full service specification details', error); } ); } private _filter(value: string): ProductSpecification[] { Loading Loading @@ -103,16 +143,174 @@ export class AssignServiceSpecificationComponent implements OnInit { confirmAssignment() { const updateRelationshipsObj: ProductSpecificationUpdate = { serviceSpecification: this.selectedSpecs.map(spec =>{ return {id: spec.id, name: spec.name}}) const hasConfigurableCFSS = this.selectedSpecs.some(spec => { const s = spec as any; const type = s['@type'] || 'ServiceSpecification'; const chars = s.serviceSpecCharacteristic || []; const hasConfigurable = chars.some(c => c.configurable === true); return type === 'CustomerFacingServiceSpecification' && hasConfigurable; }); if (hasConfigurableCFSS) { // Open the dialog to ask the user const dialogRef = this.dialog.open(ConfirmCharacteristicAssignmentComponent, { width: '600px', disableClose: true }); dialogRef.afterClosed().subscribe(result => { this.executeAssignment(result); }); } else { this.executeAssignment(null); } } executeAssignment(importCharacteristics: boolean) { const serviceRefs: any[] = []; let tempCharacteristics = [...(this.data.productSpec.productSpecCharacteristic || [])]; this.selectedSpecs.forEach(spec => { const sourceSpec = spec as any; const type = sourceSpec['@type'] || 'ServiceSpecification'; serviceRefs.push({ id: sourceSpec.id, name: sourceSpec.name, '@type': type }); // If user said YES, we manually prepare the chars to add if (importCharacteristics) { const serviceChars = sourceSpec.serviceSpecCharacteristic || []; const configurableChars = serviceChars.filter(char => char.configurable === true); configurableChars.forEach(sourceChar => { const cleanValues = (sourceChar.serviceSpecCharacteristicValue || []).map(val => ({ valueType: val.valueType, isDefault: val.isDefault, value: val.value, unitOfMeasure: val.unitOfMeasure, valueFrom: val.valueFrom, valueTo: val.valueTo, rangeInterval: val.rangeInterval, alias: val.alias })); const newCharObj = { name: sourceChar.name, description: sourceChar.description, valueType: sourceChar.valueType, configurable: sourceChar.configurable, extensible: sourceChar.extensible, regex: sourceChar.regex, uiType: sourceChar.uiType, unitOfMeasure: sourceChar.unitOfMeasure, defaultValue: sourceChar.defaultValue, minCardinality: sourceChar.minCardinality, maxCardinality: sourceChar.maxCardinality, productSpecCharacteristicValue: cleanValues, productSpecCharRelationship: [], validFor: sourceChar.validFor || { startDateTime: new Date().toISOString(), endDateTime: null }, lifecycleStatus: "In design", '@type': 'ProductSpecCharacteristic', '@baseType': 'BaseEntity', '@schemaLocation': null }; // if characteristic already exists by name, overwrite it anyway const existingCharIndex = tempCharacteristics.findIndex(c => c.name === sourceChar.name); if (existingCharIndex > -1) { tempCharacteristics[existingCharIndex] = { ...newCharObj, uuid: tempCharacteristics[existingCharIndex].uuid }; } else { tempCharacteristics.push(newCharObj); } }); } }); const payloadStep1: ProductSpecificationUpdate = { serviceSpecification: serviceRefs, productSpecCharacteristic: tempCharacteristics }; this.productSpecService.patchProductSpecification({ id: this.data.productSpec.id, productSpecification: payloadStep1 }).subscribe( (updatedSpec) => { // If user said NO, and we have CFSS, we need to check if backend added them // and remove them (patching the list without them) if (!importCharacteristics) { this.removeUnwantedCharacteristics(updatedSpec); } else { this.toast.success('Product Specification updated successfully'); this.dialogRef.close('updated'); } }, error => { this.toast.error('Failed to update Product Specification'); } ); } removeUnwantedCharacteristics(spec: ProductSpecification) { const unwantedNames: string[] = []; this.selectedSpecs.forEach(s => { const sourceSpec = s as any; if (sourceSpec['@type'] === 'CustomerFacingServiceSpecification') { const chars = sourceSpec.serviceSpecCharacteristic || []; chars.filter(c => c.configurable).forEach(c => unwantedNames.push(c.name)); } }); this.productSpecService.patchProductSpecification({id: this.data.productSpec.id, productSpecification: updateRelationshipsObj}).subscribe( const originalNames = (this.data.productSpec.productSpecCharacteristic || []).map(c => c.name); const currentChars = spec.productSpecCharacteristic || []; const newSpecCharacteristicArray = currentChars.filter(char => { const isUnwanted = unwantedNames.includes(char.name); const wasPreExisting = originalNames.includes(char.name); // If it is NOT in the unwanted list, keep it. if (!isUnwanted) return true; // If it IS in the unwanted list, BUT it existed before we started, keep it. if (wasPreExisting) return true; return false; }); // If there is nothing to change (lengths match), just close if (newSpecCharacteristicArray.length === currentChars.length) { this.toast.success('Product Specification updated successfully'); this.dialogRef.close('updated'); return; } const updateCharacteristicObj: ProductSpecificationUpdate = { productSpecCharacteristic: newSpecCharacteristicArray } this.productSpecService.patchProductSpecification({ id: spec.id, productSpecification: updateCharacteristicObj }).subscribe( data => {}, error => console.error(error), () => {this.dialogRef.close('updated')} error => { this.toast.warning('Service assigned, but failed to clean up auto-assigned characteristics.'); this.dialogRef.close('updated'); }, () => { this.toast.success("Product Specification Characteristics list was successfully updated"); this.dialogRef.close('updated'); } ) } } src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html 0 → 100644 +12 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html: 12 added lines, 0 removed lines. Original line number Diff line number Diff line <h4 mat-dialog-title class="shadowed mb-3"> <i class="far fa-trash-alt mr-2"></i> <span>Confirm deletion</span> </h4> <div mat-dialog-content> Are you sure you want to permanently delete characteristic <b><dfn>{{data.specToBeDeleted.name}}</dfn></b> ? </div> <div mat-dialog-actions class="d-flex justify-content-end"> <button type="button" class="btn btn-outline-danger m-2" (click)="closeDialog()">Cancel</button> <button type="button" class="btn btn-danger m-2" (click)="confirmDelete()">Delete</button> </div> No newline at end of file Loading
src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html 0 → 100644 +13 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html: 13 added lines, 0 removed lines. Original line number Diff line number Diff line <h2 mat-dialog-title>Import Characteristics?</h2> <mat-dialog-content class="mat-typography"> <p> The selected <b>CustomerFacingServiceSpecification</b>(s) contain configurable characteristics. </p> <p> Do you want to automatically add these as <b>Product Characteristics</b> to the Product Specification? </p> </mat-dialog-content> <mat-dialog-actions align="end"> <button (click)="onYes()" class="btn btn-success ml-2">Yes, Import Characteristics</button> <button (click)="onNo()" class="btn btn-primary ml-2">No, just link Service Specification</button> </mat-dialog-actions> No newline at end of file
src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts 0 → 100644 +22 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts: 22 added lines, 0 removed lines. Original line number Diff line number Diff line import { Component } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-confirm-characteristic-assignment', templateUrl: './confirm-characteristic-assignment.component.html', styles: [] }) export class ConfirmCharacteristicAssignmentComponent { constructor( public dialogRef: MatDialogRef<ConfirmCharacteristicAssignmentComponent> ) {} onNo(): void { this.dialogRef.close(false); } onYes(): void { this.dialogRef.close(true); } } No newline at end of file
src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html +8 −1 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -14,6 +14,9 @@ <mat-autocomplete #autoComplete="matAutocomplete" (optionSelected)="selected($event)" autoActiveFirstOption> <mat-option *ngFor="let spec of filteredSpecs$ | async" [value]="spec"> {{spec.name}} <b class="badge ml-1 {{ spec['@type'] === 'CustomerFacingServiceSpecification' ? 'badge-warning' : (spec['@type'] === 'ResourceFacingServiceSpecification' ? 'badge-success' : 'badge-danger') }}"> {{ spec['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (spec['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} </b> </mat-option> </mat-autocomplete> </mat-form-field> Loading @@ -26,7 +29,11 @@ <th mat-header-cell *matHeaderCellDef mat-sort-header> Assigned Service Specifications </th> <td mat-cell *matCellDef="let element" matTooltipClass="universal-tooltip" [matTooltip]="element.description" matTooltipPosition="above"> <b>{{element.name}}</b> </td> <b>{{element.name}}</b> <b class="badge ml-1 {{ element['@type'] === 'CustomerFacingServiceSpecification' ? 'badge-warning' : (element['@type'] === 'ResourceFacingServiceSpecification' ? 'badge-success' : 'badge-danger') }}"> {{ element['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (element['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} </b> </td> </ng-container> <ng-container matColumnDef="actions"> Loading
src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts +228 −30 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts: 228 added lines, 30 removed lines. Original line number Diff line number Diff line import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core'; import { UntypedFormControl } from '@angular/forms'; import { MatAutocomplete, MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Observable } from 'rxjs'; import { ToastrService } from 'ngx-toastr'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators' import { ProductSpecification, ProductSpecificationUpdate } from 'src/app/openApis/productCatalogManagement/models'; import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; import { ServiceSpecification } from 'src/app/openApis/serviceCatalogManagement/models'; import { ServiceSpecificationService } from 'src/app/openApis/serviceCatalogManagement/services'; import { ConfirmCharacteristicAssignmentComponent } from './confirm-characteristic-assignment/confirm-characteristic-assignment.component'; @Component({ selector: 'app-assign-service-specification', Loading @@ -24,7 +27,10 @@ export class AssignServiceSpecificationComponent implements OnInit { }, private dialogRef: MatDialogRef<AssignServiceSpecificationComponent>, private productSpecService: ProductSpecificationService, private serviceSpecService: ServiceSpecificationService private serviceSpecService: ServiceSpecificationService, private toast: ToastrService, private dialog: MatDialog ) { } @ViewChild('specInput') specInput: ElementRef<HTMLInputElement>; Loading @@ -37,44 +43,78 @@ export class AssignServiceSpecificationComponent implements OnInit { @ViewChild(MatSort, {static: true}) sort: MatSort; specInputCtrl = new UntypedFormControl(); nonSelectedSpecs: ProductSpecification[] nonSelectedSpecs: ProductSpecification[] = []; filteredSpecs$: Observable<ProductSpecification[]> selectedSpecs: ProductSpecification[] = [] // the subject holds the data when it arrives from the api private availableSpecsSubject = new BehaviorSubject<ProductSpecification[]>([]); ngOnInit(): void { this.listServiceSpecs() this.listServiceSpecs(); // for the filtering to work when user starts typing in the input this.filteredSpecs$ = combineLatest([ this.specInputCtrl.valueChanges.pipe(startWith('')), this.availableSpecsSubject.asObservable() ]).pipe( map(([inputValue, specs]) => { const filterValue = typeof inputValue === 'string' ? inputValue.toLowerCase() : ''; return this.performFilter(filterValue, specs); }) ); } listServiceSpecs() { this.serviceSpecService.listServiceSpecification({}).subscribe( data => this.nonSelectedSpecs = data, error => console.error(error), () => { //remove self from available spec list as well as the allready assigned specs const initiallyAssignedSpecIDs: string[] = this.data.productSpec.serviceSpecification.map(el => el.id) this.nonSelectedSpecs = this.nonSelectedSpecs.filter(spec => !initiallyAssignedSpecIDs.includes(spec.id)) data => { // remove the already assigned specs const initiallyAssignedIDs = this.data.productSpec.serviceSpecification.map(el => el.id); const filteredData = data.filter(spec => !initiallyAssignedIDs.includes(spec.id)); this.selectedSpecs = this.data.productSpec.serviceSpecification.slice() this.dataSource.data = this.selectedSpecs this.dataSource.sort = this.sort // push data into subject this.availableSpecsSubject.next(filteredData); this.filteredSpecs$ = this.specInputCtrl.valueChanges.pipe( startWith(null), map( (spec: string | ServiceSpecification) => typeof(spec) === 'string' ? this._filter(spec) : this.nonSelectedSpecs.slice() ) ) this.selectedSpecs = this.data.productSpec.serviceSpecification.slice(); this.dataSource.data = this.selectedSpecs; this.dataSource.sort = this.sort; }, error => this.toast.error('Failed to fetch Service Specifications', error) ); } ) private performFilter(filterValue: string, specs: ProductSpecification[]): ProductSpecification[] { if (!filterValue) { return specs; } return specs.filter(spec => { const name = spec.name ? spec.name.toLowerCase() : ''; return name.indexOf(filterValue) !== -1; }); } selected(event: MatAutocompleteSelectedEvent): void { this.selectedSpecs.push(event.option.value); this.dataSource.data = this.selectedSpecs this.nonSelectedSpecs = this.nonSelectedSpecs.filter(el => el.name != event.option.value.name) const selectedLiteSpec = event.option.value; //retrieve full service specs details, @type and spec characteristics once a service spec is selected this.specInput.nativeElement.value = ''; this.specInputCtrl.setValue(null); this.serviceSpecService.retrieveServiceSpecification({ id: selectedLiteSpec.id }).subscribe( (fullSpec: ServiceSpecification) => { this.selectedSpecs.push(fullSpec); this.dataSource.data = this.selectedSpecs; this.nonSelectedSpecs = this.nonSelectedSpecs.filter( el => el.name != selectedLiteSpec.name ); }, (error) => { this.toast.error('Failed to fetch full service specification details', error); } ); } private _filter(value: string): ProductSpecification[] { Loading Loading @@ -103,16 +143,174 @@ export class AssignServiceSpecificationComponent implements OnInit { confirmAssignment() { const updateRelationshipsObj: ProductSpecificationUpdate = { serviceSpecification: this.selectedSpecs.map(spec =>{ return {id: spec.id, name: spec.name}}) const hasConfigurableCFSS = this.selectedSpecs.some(spec => { const s = spec as any; const type = s['@type'] || 'ServiceSpecification'; const chars = s.serviceSpecCharacteristic || []; const hasConfigurable = chars.some(c => c.configurable === true); return type === 'CustomerFacingServiceSpecification' && hasConfigurable; }); if (hasConfigurableCFSS) { // Open the dialog to ask the user const dialogRef = this.dialog.open(ConfirmCharacteristicAssignmentComponent, { width: '600px', disableClose: true }); dialogRef.afterClosed().subscribe(result => { this.executeAssignment(result); }); } else { this.executeAssignment(null); } } executeAssignment(importCharacteristics: boolean) { const serviceRefs: any[] = []; let tempCharacteristics = [...(this.data.productSpec.productSpecCharacteristic || [])]; this.selectedSpecs.forEach(spec => { const sourceSpec = spec as any; const type = sourceSpec['@type'] || 'ServiceSpecification'; serviceRefs.push({ id: sourceSpec.id, name: sourceSpec.name, '@type': type }); // If user said YES, we manually prepare the chars to add if (importCharacteristics) { const serviceChars = sourceSpec.serviceSpecCharacteristic || []; const configurableChars = serviceChars.filter(char => char.configurable === true); configurableChars.forEach(sourceChar => { const cleanValues = (sourceChar.serviceSpecCharacteristicValue || []).map(val => ({ valueType: val.valueType, isDefault: val.isDefault, value: val.value, unitOfMeasure: val.unitOfMeasure, valueFrom: val.valueFrom, valueTo: val.valueTo, rangeInterval: val.rangeInterval, alias: val.alias })); const newCharObj = { name: sourceChar.name, description: sourceChar.description, valueType: sourceChar.valueType, configurable: sourceChar.configurable, extensible: sourceChar.extensible, regex: sourceChar.regex, uiType: sourceChar.uiType, unitOfMeasure: sourceChar.unitOfMeasure, defaultValue: sourceChar.defaultValue, minCardinality: sourceChar.minCardinality, maxCardinality: sourceChar.maxCardinality, productSpecCharacteristicValue: cleanValues, productSpecCharRelationship: [], validFor: sourceChar.validFor || { startDateTime: new Date().toISOString(), endDateTime: null }, lifecycleStatus: "In design", '@type': 'ProductSpecCharacteristic', '@baseType': 'BaseEntity', '@schemaLocation': null }; // if characteristic already exists by name, overwrite it anyway const existingCharIndex = tempCharacteristics.findIndex(c => c.name === sourceChar.name); if (existingCharIndex > -1) { tempCharacteristics[existingCharIndex] = { ...newCharObj, uuid: tempCharacteristics[existingCharIndex].uuid }; } else { tempCharacteristics.push(newCharObj); } }); } }); const payloadStep1: ProductSpecificationUpdate = { serviceSpecification: serviceRefs, productSpecCharacteristic: tempCharacteristics }; this.productSpecService.patchProductSpecification({ id: this.data.productSpec.id, productSpecification: payloadStep1 }).subscribe( (updatedSpec) => { // If user said NO, and we have CFSS, we need to check if backend added them // and remove them (patching the list without them) if (!importCharacteristics) { this.removeUnwantedCharacteristics(updatedSpec); } else { this.toast.success('Product Specification updated successfully'); this.dialogRef.close('updated'); } }, error => { this.toast.error('Failed to update Product Specification'); } ); } removeUnwantedCharacteristics(spec: ProductSpecification) { const unwantedNames: string[] = []; this.selectedSpecs.forEach(s => { const sourceSpec = s as any; if (sourceSpec['@type'] === 'CustomerFacingServiceSpecification') { const chars = sourceSpec.serviceSpecCharacteristic || []; chars.filter(c => c.configurable).forEach(c => unwantedNames.push(c.name)); } }); this.productSpecService.patchProductSpecification({id: this.data.productSpec.id, productSpecification: updateRelationshipsObj}).subscribe( const originalNames = (this.data.productSpec.productSpecCharacteristic || []).map(c => c.name); const currentChars = spec.productSpecCharacteristic || []; const newSpecCharacteristicArray = currentChars.filter(char => { const isUnwanted = unwantedNames.includes(char.name); const wasPreExisting = originalNames.includes(char.name); // If it is NOT in the unwanted list, keep it. if (!isUnwanted) return true; // If it IS in the unwanted list, BUT it existed before we started, keep it. if (wasPreExisting) return true; return false; }); // If there is nothing to change (lengths match), just close if (newSpecCharacteristicArray.length === currentChars.length) { this.toast.success('Product Specification updated successfully'); this.dialogRef.close('updated'); return; } const updateCharacteristicObj: ProductSpecificationUpdate = { productSpecCharacteristic: newSpecCharacteristicArray } this.productSpecService.patchProductSpecification({ id: spec.id, productSpecification: updateCharacteristicObj }).subscribe( data => {}, error => console.error(error), () => {this.dialogRef.close('updated')} error => { this.toast.warning('Service assigned, but failed to clean up auto-assigned characteristics.'); this.dialogRef.close('updated'); }, () => { this.toast.success("Product Specification Characteristics list was successfully updated"); this.dialogRef.close('updated'); } ) } }
src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html 0 → 100644 +12 −0 Viewed Changes for src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html: 12 added lines, 0 removed lines. Original line number Diff line number Diff line <h4 mat-dialog-title class="shadowed mb-3"> <i class="far fa-trash-alt mr-2"></i> <span>Confirm deletion</span> </h4> <div mat-dialog-content> Are you sure you want to permanently delete characteristic <b><dfn>{{data.specToBeDeleted.name}}</dfn></b> ? </div> <div mat-dialog-actions class="d-flex justify-content-end"> <button type="button" class="btn btn-outline-danger m-2" (click)="closeDialog()">Cancel</button> <button type="button" class="btn btn-danger m-2" (click)="confirmDelete()">Delete</button> </div> No newline at end of file