diff --git a/src/app/app-products.module.ts b/src/app/app-products.module.ts index 7ac9468423b6ce9eba80f9217f8ea5fe95d7c3a2..d26ba40165e2953f0d7adc3282bd6d8430053b66 100644 --- a/src/app/app-products.module.ts +++ b/src/app/app-products.module.ts @@ -10,6 +10,8 @@ import { ListProductSpecsComponent } from './p_product/admin/productCatalogManag import { EditProductCatalogsComponent } from './p_product/admin/productCatalogManagement/edit-product-catalogs/edit-product-catalogs.component'; import { EditProductCategoriesComponent } from './p_product/admin/productCatalogManagement/edit-product-categories/edit-product-categories.component'; import { EditProductSpecsComponent } from './p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component'; +import { EditProductSpecCharacteristicsComponent } from './p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component'; +import { DeleteProductSpecCharacteristicsComponent } from './p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component'; import { DeleteProductCatalogsComponent } from './p_product/admin/productCatalogManagement/delete-product-catalogs/delete-product-catalogs.component'; import { DeleteProductCategoriesComponent } from './p_product/admin/productCatalogManagement/delete-product-categories/delete-product-categories.component'; import { DeleteProductSpecsComponent } from './p_product/admin/productCatalogManagement/delete-product-specs/delete-product-specs.component'; @@ -21,7 +23,7 @@ import { EditProductOfferingsComponent } from './p_product/admin/productCatalogM import { PreviewMarketPlaceItemComponent } from './p_product/marketplace/preview-market-place-item/preview-market-place-item.component'; import { AssignProductOfferingsComponent } from './p_product/admin/productCatalogManagement/edit-product-categories/assign-product-offerings/assign-product-offerings.component'; import { AssignSubcategoriesComponent } from './p_product/admin/productCatalogManagement/edit-product-categories/assign-subcategories/assign-subcategories.component'; - +import { ConfirmCharacteristicAssignmentComponent } from './p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component'; @NgModule({ @@ -34,10 +36,13 @@ import { AssignSubcategoriesComponent } from './p_product/admin/productCatalogMa EditProductCatalogsComponent, EditProductCategoriesComponent, EditProductSpecsComponent, + EditProductSpecCharacteristicsComponent, + DeleteProductSpecCharacteristicsComponent, DeleteProductCatalogsComponent, DeleteProductCategoriesComponent, DeleteProductSpecsComponent, AssignServiceSpecificationComponent, + ConfirmCharacteristicAssignmentComponent, ListProductOfferingsComponent, DeleteProductOfferingComponent, EditProductOfferingsComponent, diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html index 3e3dd66de889b9e7aa8944ce51e4f50613acf112..4eae4f5292bddf312c4ce30cb272a3727db9ccc7 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.html @@ -13,7 +13,10 @@ - {{spec.name}} + {{spec.name}} + + {{ spec['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (spec['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} + @@ -26,7 +29,11 @@ Assigned Service Specifications - {{element.name}} + {{element.name}} + + {{ element['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (element['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} + + diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts index ede201f82c0daac51201b7b30a4631eaff3ec863..4dda413e18881854c51c7c283d35bec8f8c3059d 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/assign-service-specification.component.ts @@ -1,15 +1,18 @@ 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', @@ -24,7 +27,10 @@ export class AssignServiceSpecificationComponent implements OnInit { }, private dialogRef: MatDialogRef, private productSpecService: ProductSpecificationService, - private serviceSpecService: ServiceSpecificationService + private serviceSpecService: ServiceSpecificationService, + private toast: ToastrService, + private dialog: MatDialog + ) { } @ViewChild('specInput') specInput: ElementRef; @@ -37,44 +43,78 @@ export class AssignServiceSpecificationComponent implements OnInit { @ViewChild(MatSort, {static: true}) sort: MatSort; specInputCtrl = new UntypedFormControl(); - nonSelectedSpecs: ProductSpecification[] + nonSelectedSpecs: ProductSpecification[] = []; filteredSpecs$: Observable selectedSpecs: ProductSpecification[] = [] + // the subject holds the data when it arrives from the api + private availableSpecsSubject = new BehaviorSubject([]); + 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)) - - this.selectedSpecs = this.data.productSpec.serviceSpecification.slice() - this.dataSource.data = this.selectedSpecs - this.dataSource.sort = this.sort - - this.filteredSpecs$ = this.specInputCtrl.valueChanges.pipe( - startWith(null), - map( (spec: string | ServiceSpecification) => typeof(spec) === 'string' ? this._filter(spec) : this.nonSelectedSpecs.slice() ) - ) - } - ) + 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)); + + // push data into subject + this.availableSpecsSubject.next(filteredData); + + 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) + ); } - 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) + 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 { + 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[] { @@ -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); } + } - this.productSpecService.patchProductSpecification({id: this.data.productSpec.id, productSpecification: updateRelationshipsObj}).subscribe( - data => {}, - error => console.error(error), - () => {this.dialogRef.close('updated')} - ) + 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)); + } + }); + + 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 => { + 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'); + } + ) + } } diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html new file mode 100644 index 0000000000000000000000000000000000000000..46b908977beefa537c4f7e00cb71852d1ba504cf --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.html @@ -0,0 +1,13 @@ +

Import Characteristics?

+ +

+ The selected CustomerFacingServiceSpecification(s) contain configurable characteristics. +

+

+ Do you want to automatically add these as Product Characteristics to the Product Specification? +

+
+ + + + \ No newline at end of file diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e8910086710cb54abef00d7d57d75378ec35f630 --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/assign-service-specification/confirm-characteristic-assignment/confirm-characteristic-assignment.component.ts @@ -0,0 +1,22 @@ +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 + ) {} + + onNo(): void { + this.dialogRef.close(false); + } + + onYes(): void { + this.dialogRef.close(true); + } +} \ No newline at end of file diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html new file mode 100644 index 0000000000000000000000000000000000000000..29077a3548386a5a16c9a8a8497a550dbd36f251 --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.html @@ -0,0 +1,12 @@ +

+ + Confirm deletion +

+
+ Are you sure you want to permanently delete characteristic {{data.specToBeDeleted.name}} ? +
+ +
+ + +
\ No newline at end of file diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.scss b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..29bafc126cbdd13e9fbe09ae2f7a8c550adf671f --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/delete-product-spec-characteristics/delete-product-spec-characteristics.component.ts @@ -0,0 +1,47 @@ +import { trigger } from "@angular/animations"; +import { Component, Inject, OnInit } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; +import { Toast, ToastrService } from "ngx-toastr"; +import { ProductSpecification, ProductSpecificationCharacteristicRes, ProductSpecificationUpdate } from "src/app/openApis/productCatalogManagement/models"; +import { ProductSpecificationService } from "src/app/openApis/productCatalogManagement/services"; +import { fadeIn, simpleFade } from "src/app/shared/animations/animations"; + + +@Component({ + selector: 'app-delete-product-spec-characteristics', + templateUrl: './delete-product-spec-characteristics.component.html', + styleUrls: ['./delete-product-spec-characteristics.component.scss'], + animations: [trigger('fadeIn', fadeIn()), trigger('simpleFade', simpleFade())] +}) +export class DeleteProductSpecCharacteristicsComponent implements OnInit { + constructor( + @Inject(MAT_DIALOG_DATA) public data: { + productSpec: ProductSpecification + productSpecCharacteristicArray:ProductSpecificationCharacteristicRes[], + specToBeDeleted: ProductSpecificationCharacteristicRes + }, + private dialogRef: MatDialogRef, + private specService: ProductSpecificationService, + private toast: ToastrService + ) { } + + ngOnInit() { + } + + confirmDelete() { + // this.dialogRef.close('deleted') + const updateSpecObj: ProductSpecificationUpdate = { + productSpecCharacteristic: this.data.productSpecCharacteristicArray + } + + this.specService.patchProductSpecification({ id: this.data.productSpec.id, productSpecification: updateSpecObj }).subscribe( + data => {}, + error => this.toast.error("An error occurred while deleting the Product Specification Characteristic"), + () => this.dialogRef.close('deleted') + ) + } + + closeDialog() { + this.dialogRef.close() + } +} diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.html new file mode 100644 index 0000000000000000000000000000000000000000..572842c071391be3fa6ea3cf928b6f3cf2fe393e --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.html @@ -0,0 +1,229 @@ +
+ +

+ + Design New Product Characteristic +

+ +

+ + Edit Product Characteristic +

+ +
+ + +
+ + Name + + + + + Description + + + +
+
+ + Valid From + + + + date_range + + + + + + Valid Until + + + date_range + + + +
+
+ + + Value Type + + + {{type}} + + + + + Of + + + {{type}} + + + + + + + Min Cardinality + + + + + Max Cardinality + + + +
+
+
+ Extensible +
+ +
+ Configurable +
+
+
+ +
+ +
+
+
+ + Product Characteristic Value +
+
+ +
+
+ +
+ +
+ + +
+
+ + Alias + + + + Value + + + +
+
+ + + Unit Of Measure + + + +
+ Is + Default + +
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ + + +
+ + + Alias + + +
+ + + Unit Of Measure + + + +
+ Is + Default + +
+ + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ + Value + + + +
+ +
+ +
+ +
+ +
+ +
+
+
+ + +
+
+
+
diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.scss b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..42ca63a3ceb08d362330fcc53601054553700687 --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.scss @@ -0,0 +1,15 @@ +.form-array-container { + max-height: 250px; + overflow-x: hidden; + overflow-y: auto; +} + +.characteristic-value-container { + // padding-top: 0.75rem; + border: 1px; + border-style: dashed; + border-color: #ccc; + margin-bottom: 0.5rem; + background-color: #fafaff; + border-radius: 5px; +} \ No newline at end of file diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2408ed24dd4cb077f8afa55c64212de19ecb193 --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-spec-characteristics/edit-product-spec-characteristics.component.ts @@ -0,0 +1,212 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { UntypedFormGroup, UntypedFormControl, UntypedFormArray } from '@angular/forms'; +import { ToastrService } from 'ngx-toastr'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { trigger } from '@angular/animations'; +import { fadeIn, simpleFade } from 'src/app/shared/animations/animations'; +import { ProductSpecification, ProductSpecificationCharacteristicRes, ProductSpecificationCharacteristicValue, ProductSpecificationUpdate } from 'src/app/openApis/productCatalogManagement/models'; +import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; + + + +@Component({ + selector: 'app-edit-product-spec-characteristics', + templateUrl: './edit-product-spec-characteristics.component.html', + styleUrls: ['./edit-product-spec-characteristics.component.scss'], + animations: [trigger('fadeIn', fadeIn()), trigger('simpleFade', simpleFade())] +}) +export class EditProductSpecCharacteristicsComponent implements OnInit { + + constructor( + @Inject(MAT_DIALOG_DATA) public data: { + productSpec: ProductSpecification, + specToBeUpdated: ProductSpecificationCharacteristicRes, + }, + private dialogRef: MatDialogRef, + private specService: ProductSpecificationService, + private toast: ToastrService + ) { } + + editFormCharacteristic = new UntypedFormGroup({ + name: new UntypedFormControl(), + description: new UntypedFormControl(), + configurable: new UntypedFormControl(), + extensible: new UntypedFormControl(), + maxCardinality: new UntypedFormControl(1), + minCardinality: new UntypedFormControl(0), + validFor: new UntypedFormGroup({ + endDateTime: new UntypedFormControl(new Date(new Date().setFullYear(new Date().getFullYear() + 20))), + startDateTime: new UntypedFormControl(new Date()) + }), + valueType: new UntypedFormControl(), + productSpecCharacteristicValue: new UntypedFormArray([]) + }) + + subValueTypeCtrl = new UntypedFormControl('INTEGER') + + valueTypes = ['INTEGER', 'SMALLINT', 'LONGINT', 'FLOAT', 'BINARY', 'BOOLEAN', 'ARRAY', 'SET', 'TEXT', 'LONGTEXT', 'ENUM', 'TIMESTAMP'] + subValueTypes = ['INTEGER', 'SMALLINT', 'LONGINT', 'FLOAT', 'BINARY', 'BOOLEAN', 'TEXT', 'LONGTEXT', 'TIMESTAMP'] + + // valueSubType = new FormControl() + subTypeSelection: boolean = false + + newSpec: boolean = false + + compDestroy$ = new Subject() + + isCharValueBlockExpanded: boolean[] = [] + + ngOnInit(): void { + if (this.data.specToBeUpdated) { + if (!this.data.specToBeUpdated.validFor) this.data.specToBeUpdated.validFor = { endDateTime: null, startDateTime: null } + this.editFormCharacteristic.patchValue(this.data.specToBeUpdated) + + const formArray = this.editFormCharacteristic.get('productSpecCharacteristicValue') as UntypedFormArray + this.data.specToBeUpdated.productSpecCharacteristicValue.forEach(val => { + formArray.push(this.updateFormArrayItem(val)) + this.isCharValueBlockExpanded.push(false) + }) + + this.subValueTypeCtrl.patchValue(this.data.specToBeUpdated.productSpecCharacteristicValue[0].valueType) + if (['SET', 'ARRAY', 'ENUM'].includes(this.data.specToBeUpdated.valueType)) { + this.subTypeSelection = true + } + } + + else { this.newSpec = true } + + this.editFormCharacteristic.get('valueType').valueChanges.pipe( + takeUntil(this.compDestroy$) + ) + .subscribe( + val => { + this.editFormCharacteristic.setControl('productSpecCharacteristicValue', new UntypedFormArray([])) + this.createFormArrayItem() + + + if (['SET', 'ARRAY', 'ENUM'].includes(val)) { + this.subTypeSelection = true + this.subValueTypeCtrl.valueChanges.pipe( + takeUntil(this.compDestroy$) + ) + .subscribe( + subVal => { + this.editFormCharacteristic.setControl('productSpecCharacteristicValue', new UntypedFormArray([])) + this.createFormArrayItem() + } + ) + } else { + this.subTypeSelection = false + } + // const formArray = this.editFormCharacteristic.get('serviceSpecCharacteristicValue') as FormArray + // if (val !== 'ARRAY' && val !=='ENUM' && val !=='SET') { + // this.subTypeSelection = false + // formArray.setControl(0, + // new FormGroup({ + // value: new FormGroup({ + // alias: new FormControl(), + // value: new FormControl(), + // }), + // unitOfMeasure: new FormControl(), + // isDefault: new FormControl(), + // valueType: new FormControl(this.editFormCharacteristic.get('valueType').value) + // })) + // } + } + ) + } + + + updateFormArrayItem(CharValue: ProductSpecificationCharacteristicValue): UntypedFormGroup { + return new UntypedFormGroup({ + value: new UntypedFormGroup({ + alias: new UntypedFormControl(CharValue.value.alias), + value: new UntypedFormControl(CharValue.value.value), + }), + unitOfMeasure: new UntypedFormControl(CharValue.unitOfMeasure), + isDefault: new UntypedFormControl({ value: (CharValue.isDefault || this.data.specToBeUpdated.valueType === 'ARRAY'), disabled: this.data.specToBeUpdated.valueType === 'ARRAY' }), + valueType: new UntypedFormControl(CharValue.valueType) + }) + } + + + createFormArrayItem() { + const formArray = this.editFormCharacteristic.get('productSpecCharacteristicValue') as UntypedFormArray + + // let isDisabled: boolean = true + let subType: string = this.editFormCharacteristic.get('valueType').value + + if (['SET', 'ARRAY', 'ENUM'].includes(this.editFormCharacteristic.get('valueType').value)) { + // isDisabled = false + subType = this.subValueTypeCtrl.value + } + + formArray.push( + new UntypedFormGroup({ + value: new UntypedFormGroup({ + alias: new UntypedFormControl(), + value: new UntypedFormControl(), + }), + unitOfMeasure: new UntypedFormControl(), + isDefault: new UntypedFormControl({ value: this.editFormCharacteristic.get('valueType').value === 'ARRAY', disabled: this.editFormCharacteristic.get('valueType').value === 'ARRAY' }), + valueType: new UntypedFormControl(subType) + }) + ) + this.isCharValueBlockExpanded.push(false) + } + + deleteFormArrayItem(index) { + const formArray = this.editFormCharacteristic.get('productSpecCharacteristicValue') as UntypedFormArray + formArray.removeAt(index) + this.isCharValueBlockExpanded.splice(index, 1) + } + + expandCharValueBlock(index) { + this.isCharValueBlockExpanded[index] = !this.isCharValueBlockExpanded[index] + } + + isDefaultCheckboxChanged(index, event: MatCheckboxChange) { + if (this.editFormCharacteristic.get('valueType').value === "ENUM" && event.checked) { + const formArray = this.editFormCharacteristic.get('productSpecCharacteristicValue') as UntypedFormArray + for (let i = 0; i < formArray.controls.length; i++) { + if (i !== index) formArray.controls[i].get('isDefault').setValue(false) + } + } + } + + + closeDialog() { + this.dialogRef.close() + } + + submitDialog() { + + + + if (this.newSpec) { + this.data.productSpec.productSpecCharacteristic.push(this.editFormCharacteristic.getRawValue()) + } else { + const updateCharacteristIndex = this.data.productSpec.productSpecCharacteristic.findIndex(char => char.uuid === this.data.specToBeUpdated.uuid) + this.data.productSpec.productSpecCharacteristic[updateCharacteristIndex] = this.editFormCharacteristic.getRawValue() + } + + const updateCharacteristicObj: ProductSpecificationUpdate = { + productSpecCharacteristic: this.data.productSpec.productSpecCharacteristic + } + + this.specService.patchProductSpecification({ id: this.data.productSpec.id, productSpecification: updateCharacteristicObj }).subscribe( + data => { }, + error => this.toast.error("An error occurred upon updating Spec Characteristics"), + () => { this.dialogRef.close('updated') } + ) + } + + ngOnDestroy(): void { + this.compDestroy$.next("destroyed") + } + + +} \ No newline at end of file diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.html index 29881af9047ac6fc0bef8413ff9fdddaf911fcd8..a942f958cc538a2de2ceb7e020ebd314768d2a3a 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.html @@ -168,7 +168,11 @@
- +
{{relatedServiceSpecification.name}} +
+ {{ relatedServiceSpecification['@type'] === 'CustomerFacingServiceSpecification' ? 'CFSS' : (relatedServiceSpecification['@type'] === 'ResourceFacingServiceSpecification' ? 'RFSS' : 'null') }} +
+
@@ -200,6 +204,120 @@
+ + +
+
+
+
+ + Apply Filter... + + + +
+ +
+
+
+ + {{tag}} + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name + + {{element.name}} Value + Type + {{element.valueType}} + Default Values + + {{val.value.value}} + ({{val.value.alias}}) + + +
+
+ {{val.unitOfMeasure}}
+ +
+
+ Configurable + {{element.configurable}} Actions +
+ + + +
+
+ + +
+
+ +
+ +
+ + diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.ts index b94127a7f94fbf827add89dc8735b82dff2cff40..77cb87e58eb1486be2a7c331c39baf05580c18ff 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-specs/edit-product-specs.component.ts @@ -6,11 +6,14 @@ import { ActivatedRoute, ActivationEnd, Router } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { Observable, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { ProductSpecification, ProductSpecificationCreate, ProductSpecificationUpdate, ServiceSpecificationRef } from 'src/app/openApis/productCatalogManagement/models'; +import { ProductSpecification, ProductSpecificationCreate, ProductSpecificationUpdate, ServiceSpecificationRef, ProductSpecificationCharacteristicRes } from 'src/app/openApis/productCatalogManagement/models'; import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; import { fadeIn } from 'src/app/shared/animations/animations'; import { AppService } from 'src/app/shared/services/app.service'; import { AssignServiceSpecificationComponent } from './assign-service-specification/assign-service-specification.component'; +import { MatTableDataSource } from '@angular/material/table'; +import { EditProductSpecCharacteristicsComponent } from './edit-product-spec-characteristics/edit-product-spec-characteristics.component'; +import { DeleteProductSpecCharacteristicsComponent } from './delete-product-spec-characteristics/delete-product-spec-characteristics.component'; @Component({ selector: 'app-edit-product-specs', @@ -20,11 +23,18 @@ import { AssignServiceSpecificationComponent } from './assign-service-specificat }) export class EditProductSpecsComponent implements OnInit { + // to change + displayedColumnsCharacteristics = ['name', 'type', 'defaultValues', 'configurable', 'actions']; + specCharacteristicsTags: string[] = ["All"] + tagFiltervalue:string = "All" + dataSource = new MatTableDataSource(); + + constructor( private activatedRoute: ActivatedRoute, private specService: ProductSpecificationService, private dialog: MatDialog, - private toastrService: ToastrService, + private toast: ToastrService, private router: Router, public appService: AppService ) { } @@ -35,7 +45,7 @@ export class EditProductSpecsComponent implements OnInit { finishedLoading: boolean = false newSpecification = false - listItems = ["Main Properties", "Service Specification Relationships", "Related Parties"] + listItems = ["Main Properties", "Service Specification Relationships", "Related Parties", "Product Specification Characteristics"] activeListItem = "Main Properties" editForm = new UntypedFormGroup({ @@ -100,21 +110,29 @@ export class EditProductSpecsComponent implements OnInit { retrieveProductSpec() { this.specService.retrieveProductSpecification({id: this.specID}).subscribe( data => this.spec = data, - error => {console.error(error), this.toastrService.error("An error occurred while loading Product Specification")}, + error => this.toast.error("An error occurred while loading Product Specification"), () => { if (this.spec) { this.finishedLoading = true - //populate General Panel Info if (!this.spec.validFor) this.spec.validFor = { endDateTime: new Date(new Date().setFullYear(new Date().getFullYear()+20)).toISOString(), startDateTime: new Date().toISOString() } this.editForm.patchValue(this.spec) this.editForm.markAsPristine() - + //populate Service Specification Relationship Panel Info this.filteredServiceSpecificationRel$ = this.serviceSpecificationRelationshipFilterCtrl.valueChanges.pipe( startWith(null), map( (value:null | string) => value ? this._filterOnRelatedSpecs(value) : this.spec.serviceSpecification.slice() ) ) + + //populate Specification Characteristic Panel Info + this.dataSource.data = this.spec.productSpecCharacteristic; + // this.dataSource.paginator = this.paginator; + + this.specCharacteristicsTags = ["All"] + this.tagFiltervalue = "All" + this.specCharacteristicsTags = this.retrieveSpecCharaceristicsTags(this.dataSource.data) + } else { @@ -124,6 +142,16 @@ export class EditProductSpecsComponent implements OnInit { ) } + retrieveSpecCharaceristicsTags(dataSource: ProductSpecificationCharacteristicRes[]) { + let tagsArray = this.specCharacteristicsTags + dataSource.forEach(char => { + char.productSpecCharRelationship.filter( e => e.relationshipType === "tag").forEach(rel => { + if (!tagsArray.includes(rel.name)) tagsArray.push(rel.name) + }) + }); + return tagsArray + } + private _filterOnRelatedSpecs(filterValue: string) { filterValue = filterValue.trim(); filterValue = filterValue.toLowerCase(); @@ -142,7 +170,7 @@ export class EditProductSpecsComponent implements OnInit { dialogRef.afterClosed().subscribe ( result => { if (result) { - this.toastrService.success("Service Specification Relationship list was successfully updated") + this.toast.success("Product Specification Relationship list was successfully updated") this.retrieveProductSpec() } } @@ -164,10 +192,10 @@ export class EditProductSpecsComponent implements OnInit { if (this.newSpecification) { this.specService.createProductSpecification(updateObj).subscribe( data => { updatedSpec = data }, - error => console.error(error), + error => this.toast.error("An error occurred while creating the Product Specification"), () => { this.newSpecification = false - this.toastrService.success("Product Specification was successfully created") + this.toast.success("Product Specification was successfully created") this.refreshProductSpecification(updatedSpec) } ) @@ -175,9 +203,9 @@ export class EditProductSpecsComponent implements OnInit { else { this.specService.patchProductSpecification({ id: this.specID, productSpecification: updateObj }).subscribe( data => { updatedSpec = data }, - error => console.error(error), + error => this.toast.error("An error occurred while updating the Product Specification"), () => { - this.toastrService.success("Product Specification was successfully updated") + this.toast.success("Product Specification was successfully updated") this.refreshProductSpecification(updatedSpec) } ) @@ -191,6 +219,97 @@ export class EditProductSpecsComponent implements OnInit { this.retrieveProductSpec() } + + cloneProductSpecCharacteristic(characteristic: ProductSpecificationCharacteristicRes) { + const cloneCharacteristic: ProductSpecificationCharacteristicRes = { + name: `Copy of ${characteristic.name}`, + description: characteristic.description, + configurable: characteristic.configurable, + extensible: characteristic.extensible, + minCardinality: characteristic.minCardinality, + maxCardinality: characteristic.maxCardinality, + productSpecCharRelationship: characteristic.productSpecCharRelationship, + productSpecCharacteristicValue: characteristic.productSpecCharacteristicValue, + validFor: characteristic.validFor, + valueType: characteristic.valueType + } + + this.spec.productSpecCharacteristic.push(cloneCharacteristic); + + const updateCharacteristicObj: ProductSpecificationUpdate = { + productSpecCharacteristic: this.spec.productSpecCharacteristic + } + + this.specService.patchProductSpecification({id: this.spec.id, productSpecification: updateCharacteristicObj}).subscribe( + data => {}, + error => this.toast.error("An error occurred while cloning the Product Specification Characteristic"), + () => { + this.toast.success("Product Specification Characteristics list was successfully updated"); + this.retrieveProductSpec(); + } + ) + } + + applySpecCharFilter(filterValue: string) { + filterValue = filterValue.trim(); + filterValue = filterValue.toLowerCase(); + this.dataSource.filter = filterValue; + } + + openCharacteristicDeleteDialog(characteristic: ProductSpecificationCharacteristicRes) { + const specToBeDeletedIndex = this.spec.productSpecCharacteristic.findIndex(char => char.uuid === characteristic.uuid) + + const newSpecCharacteristicArray: ProductSpecificationCharacteristicRes[] = this.spec.productSpecCharacteristic.slice() + + newSpecCharacteristicArray.splice(specToBeDeletedIndex, 1) + + const dialogRef = this.dialog.open(DeleteProductSpecCharacteristicsComponent, { + data: { + productSpec: this.spec, + productSpecCharacteristicArray: newSpecCharacteristicArray, + specToBeDeleted: this.spec.productSpecCharacteristic[specToBeDeletedIndex] + } + }) + + dialogRef.afterClosed().subscribe ( + result => { + if (result){ + this.toast.success("Product Specification Characteristics list was successfully updated"); + this.retrieveProductSpec() + } + } + ) + } + + openCharacteristicDesignDialog(characteristic: ProductSpecificationCharacteristicRes) { + const dialogRef = this.dialog.open(EditProductSpecCharacteristicsComponent, { + data: { + productSpec: this.spec, + specToBeUpdated: characteristic + }, + disableClose: true + }) + + dialogRef.afterClosed().subscribe ( + result => { + if (result) { + this.toast.success("Product Specification Characteristics list was successfully updated"); + this.retrieveProductSpec(); + } + } + ) + } + + filterCharacteristicsByTag(tagName) { + this.tagFiltervalue = tagName + if (tagName === "All") { + this.dataSource.data = this.spec.productSpecCharacteristic.filter(specCharacteristic => specCharacteristic.valueType); + } else { + this.dataSource.data = this.spec.productSpecCharacteristic.filter(specCharacteristic => specCharacteristic.valueType) + .filter(specChar => specChar.productSpecCharRelationship.some( rel => rel.name === tagName )); + } + } + ngOnDestroy() { this.subscriptions.unsubscribe() } diff --git a/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.html b/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.html index e5645e7ff0ad9aa5893d52374cadfb03ae629c0f..ccb2bacd4d8ab7783148d3af4c96390a8ce311ae 100644 --- a/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.html +++ b/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.html @@ -39,14 +39,14 @@ - + diff --git a/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.ts b/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.ts index 2d5fff5821ed29c4c9edc03a5705a982bd932da7..c4e2e85cdaf0ff224638f1883b80def43319e64b 100644 --- a/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.ts +++ b/src/app/p_product/marketplace/preview-market-place-item/preview-market-place-item.component.ts @@ -4,6 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ProductOffering, ProductSpecification } from 'src/app/openApis/productCatalogManagement/models'; import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; import { fadeIn } from 'src/app/shared/animations/animations'; +import { AuthService } from 'src/app/shared/services/auth.service'; import { ThemingService } from 'src/app/theming/theming.service'; @Component({ @@ -20,7 +21,8 @@ export class PreviewMarketPlaceItemComponent implements OnInit { }, private dialogRef: MatDialogRef, private specService: ProductSpecificationService, - private themingService: ThemingService + private themingService: ThemingService, + public authService: AuthService ) { } productOffering: ProductOffering @@ -54,6 +56,8 @@ export class PreviewMarketPlaceItemComponent implements OnInit { } + placeInOrderList() {} + closeDialog() { this.dialogRef.close() } diff --git a/src/app/shared/navbar/navbar.component.html b/src/app/shared/navbar/navbar.component.html index b607aac83e5ade4e1bed7403bb3cefbcd6fc40e7..61d2099e9c28400d4d20a0fece63654667e240ef 100644 --- a/src/app/shared/navbar/navbar.component.html +++ b/src/app/shared/navbar/navbar.component.html @@ -269,6 +269,14 @@ + +