From 8941b3ed5864b0a879ead834ce9106cd0172ff37 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Tue, 16 Dec 2025 13:44:02 +0200 Subject: [PATCH 1/8] fixed product spec linking in product offering update/creation --- .../edit-product-offerings.component.html | 46 ++++++ .../edit-product-offerings.component.ts | 142 +++++++++++++----- 2 files changed, 149 insertions(+), 39 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index e62fdea..74f4008 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -151,6 +151,52 @@ + +
+
+
+
+
+ +
+ +
+
Characteristics from linked Product + Specification: + {{offering?.productSpecification?.name}} +
+
+ + + +
+ +
+ +
+ +
+
+ +
+
+
+ + + diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index a7e5b6f..c3f4ace 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -1,13 +1,10 @@ import { trigger } from '@angular/animations'; -import { SelectionChange } from '@angular/cdk/collections'; import { Component, OnInit } from '@angular/core'; import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; -import { MatOptionSelectionChange } from '@angular/material/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatSelectChange } from '@angular/material/select'; import { ActivatedRoute, ActivationEnd, Router } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; -import { relative } from 'path'; import { Subscription } from 'rxjs'; import { ProductOffering, ProductOfferingCreate, ProductOfferingUpdate, ProductSpecification } from 'src/app/openApis/productCatalogManagement/models'; import { ProductOfferingService, ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; @@ -40,7 +37,7 @@ export class EditProductOfferingsComponent implements OnInit { productSpecifications: ProductSpecification[] - listItems = ["Main Properties"] + listItems = ["Main Properties", "Linked Product Specification Characteristics"] activeListItem = "Main Properties" editForm = new UntypedFormGroup({ @@ -160,45 +157,112 @@ export class EditProductOfferingsComponent implements OnInit { updateOfferingGeneral() { if (this.editForm.valid) { - const updateObj: ProductOfferingCreate | ProductOfferingUpdate = { - description: this.editForm.value.description, - lifecycleStatus: this.editForm.value.lifecycleStatus, - name: this.editForm.value.name, - validFor: this.editForm.value.validFor, - version: this.editForm.value.version - } - - if (this.editForm.value.productSpecification) { - updateObj.productSpecification = {name: this.editForm.value.productSpecification.name, id: this.editForm.value.productSpecification.id} - } - - let updatedOffering: ProductOffering - - if (this.newOffering) { - this.offeringService.createProductOffering(updateObj).subscribe( - data => { updatedOffering = data }, - error => console.error(error), - () => { - this.newOffering = false - this.router.navigate([updatedOffering.id], {relativeTo: this.activatedRoute}) - this.toastrService.success("Product Offering was successfully created") - this.refreshProductOffering(updatedOffering) + const formValue = this.editForm.value; + const specRef = formValue.productSpecification; + + if (specRef && specRef.id) { + // retrieve full specification details + this.specService.retrieveProductSpecification({ id: specRef.id }).subscribe( + (fullSpec: ProductSpecification) => { + const payload = this.constructPayload(formValue, fullSpec); + this.submitOfferingToApi(payload); + }, + (error) => { + console.error(error); + this.toastrService.error("Could not retrieve full Product Specification details."); } - ) - } - else { - this.offeringService.patchProductOffering({ id: this.offeringID, productOffering: updateObj }).subscribe( - data => { updatedOffering = data }, - error => console.error(error), - () => { - this.toastrService.success("Product Offering was successfully updated") - this.refreshProductOffering(updatedOffering) - } - ) + ); + } else { + // no spec selected + const payload = this.constructPayload(formValue, null); + this.submitOfferingToApi(payload); } } + } + + private constructPayload(formValue: any, linkedSpec: ProductSpecification | null): ProductOfferingCreate | ProductOfferingUpdate { - } + let productSpecRef: any = undefined; + if (linkedSpec) { + productSpecRef = { id: linkedSpec.id, name: linkedSpec.name }; + if (linkedSpec.version) productSpecRef.version = linkedSpec.version; + } + + return { + name: formValue.name, + description: formValue.description, + lifecycleStatus: formValue.lifecycleStatus, + version: formValue.version, + validFor: formValue.validFor, + isSellable: true, + statusReason: '', + + productSpecification: productSpecRef, + + + isBundle: linkedSpec?.isBundle || false, + attachment: linkedSpec?.attachment || [], + + prodSpecCharValueUse: [], + + serviceCandidate: (linkedSpec && (linkedSpec as any).serviceSpecification && (linkedSpec as any).serviceSpecification.length > 0) + ? { + id: (linkedSpec as any).serviceSpecification[0].id, + name: (linkedSpec as any).serviceSpecification[0].name, + version: (linkedSpec as any).serviceSpecification[0].version, + '@type': 'ServiceCandidateRef' + } + : undefined, + + resourceCandidate: (linkedSpec && (linkedSpec as any).resourceSpecification && (linkedSpec as any).resourceSpecification.length > 0) + ? { + id: (linkedSpec as any).resourceSpecification[0].id, + name: (linkedSpec as any).resourceSpecification[0].name, + version: (linkedSpec as any).resourceSpecification[0].version, + '@type': 'ResourceCandidateRef' + } + : undefined, + + serviceLevelAgreement: (linkedSpec as any).serviceLevelAgreement || undefined, + + agreement: [], + bundledProductOffering: [], + category: [], + channel: [], + marketSegment: [], + place: [], + productOfferingPrice: [], + productOfferingTerm: [], + + '@type': 'ProductOffering' + }; + } + + private submitOfferingToApi(updateObj: ProductOfferingCreate | ProductOfferingUpdate) { + let updatedOffering: ProductOffering; + + if (this.newOffering) { + this.offeringService.createProductOffering(updateObj).subscribe( + data => { updatedOffering = data }, + error => { console.error(error); this.toastrService.error("Failed to create offering"); }, + () => { + this.newOffering = false; + this.router.navigate([updatedOffering.id], {relativeTo: this.activatedRoute}); + this.toastrService.success("Product Offering was successfully created"); + this.refreshProductOffering(updatedOffering); + } + ); + } else { + this.offeringService.patchProductOffering({ id: this.offeringID, productOffering: updateObj }).subscribe( + data => { updatedOffering = data }, + error => { console.error(error); this.toastrService.error("Failed to update offering"); }, + () => { + this.toastrService.success("Product Offering was successfully updated"); + this.refreshProductOffering(updatedOffering); + } + ); + } + } refreshProductOffering(updatedOffering : ProductOffering) { this.offeringID = updatedOffering.id -- GitLab From 4cc2116499954f1d37f1b436673b32c498b9bc46 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Wed, 17 Dec 2025 15:02:57 +0200 Subject: [PATCH 2/8] enhance product offering management with characteristic and characteristic values exposure --- .../edit-product-offerings.component.html | 130 ++++- .../edit-product-offerings.component.ts | 514 +++++++++++++----- 2 files changed, 476 insertions(+), 168 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index 74f4008..7a361cc 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -152,45 +152,129 @@ -
+ role="tabpanel">
-
+
+
+
Please select a Linked Product Specification in the "Main Properties" tab first.
+
+
+ +
-
-
Characteristics from linked Product - Specification: - {{offering?.productSpecification?.name}} +
+
Choose Characteristics from: + {{editForm.value.productSpecification.name}}
+ Select the characteristics to include. Expand to select specific values.
- +
+ No characteristics found in the selected Product Specification. +
-
+ + + + +
+ + + +
+ {{char.name}} + {{char.description}} +
+ +
+ {{ getSelectedValuesCount(char) }} / {{ char.productSpecCharacteristicValue.length }} values +
+
+
+ + +
+
+

+ This characteristic has no pre-defined values. +

+
+ +
+

Select the characteristic values for this offering:

+ +
+
+ + + + + + {{val.value.alias}}: {{val.value.value}} + - ({{val.unitOfMeasure}}) + + + + {{val.value.value}} + ({{val.unitOfMeasure}}) + + + + + + Empty Value + + +
+
+ +
+ + +
+
+
+
+ + +
+
+ +
-
- +
+
+ + +
- + +
diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index c3f4ace..f476f53 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -1,12 +1,19 @@ -import { trigger } from '@angular/animations'; import { Component, OnInit } from '@angular/core'; +import { trigger } from '@angular/animations'; import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatSelectChange } from '@angular/material/select'; import { ActivatedRoute, ActivationEnd, Router } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { Subscription } from 'rxjs'; -import { ProductOffering, ProductOfferingCreate, ProductOfferingUpdate, ProductSpecification } from 'src/app/openApis/productCatalogManagement/models'; +import { + ProductOffering, + ProductOfferingCreate, + ProductOfferingUpdate, + ProductSpecification, + ProductSpecificationCharacteristicRes +} from 'src/app/openApis/productCatalogManagement/models'; import { ProductOfferingService, ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services'; import { fadeIn } from 'src/app/shared/animations/animations'; import { AppService } from 'src/app/shared/services/app.service'; @@ -34,17 +41,22 @@ export class EditProductOfferingsComponent implements OnInit { offeringNotFound: boolean = false finishedLoading: boolean = false newOffering = false - productSpecifications: ProductSpecification[] + + currentLinkedSpec: ProductSpecification; + availableSpecCharacteristics: ProductSpecificationCharacteristicRes[] = []; + + selectedCharacteristics: Set = new Set(); + selectedCharValues: Map> = new Map(); listItems = ["Main Properties", "Linked Product Specification Characteristics"] activeListItem = "Main Properties" + lifecycleStatuses = ["In study", "In design", "In test", "Active", "Launched", "Retired", "Obsolete", "Rejected"] - editForm = new UntypedFormGroup({ + editForm = new UntypedFormGroup({ description: new UntypedFormControl(), lifecycleStatus: new UntypedFormControl("In design", Validators.required), name: new UntypedFormControl(null, Validators.required), - // isBundle: new FormControl(), validFor: new UntypedFormGroup({ endDateTime: new UntypedFormControl(new Date(new Date().setFullYear(new Date().getFullYear()+20)), Validators.required), startDateTime: new UntypedFormControl(new Date(), Validators.required) @@ -52,26 +64,20 @@ export class EditProductOfferingsComponent implements OnInit { version: new UntypedFormControl("0.1.0", Validators.required), productSpecification: new UntypedFormControl() }) - - lifecycleStatuses = ["In study", "In design", "In test", "Active", "Launched", "Retired", "Obsolete", "Rejected"] subscriptions = new Subscription() - // valueChangeSubscription$: any - ngOnInit(): void { this.initSubscriptions() this.listProductSpecifications() - if (this.activatedRoute.snapshot.params.id) - { + if (this.activatedRoute.snapshot.params.id) { this.offeringID = this.activatedRoute.snapshot.params.id this.retrieveOffering() } else { this.newOffering = true this.finishedLoading = true } - } initSubscriptions() { @@ -85,38 +91,6 @@ export class EditProductOfferingsComponent implements OnInit { )) } - - onSpecificationChange(event: MatSelectChange) { - const dialogRef = this.dialog.open(CopySpecificationPropertiesComponent, { autoFocus: true }) - - dialogRef.afterClosed().subscribe(confirmCopy => { - if (confirmCopy) { - this.editForm.patchValue({ - name: event.value.name, - description: event.value.description, - }) - } - }) - } - - - - selectListitem(item: string) { - if (this.editForm.pristine) { - this.activeListItem = item - } else { - const dialogRef = this.dialog.open(DiscardChangesComponent, {autoFocus: true}) - - dialogRef.afterClosed().subscribe (discardChanges => { - if (discardChanges) { - this.editForm.patchValue(this.offering) - this.editForm.markAsPristine() - this.activeListItem = item - } - }) - } - } - listProductSpecifications() { this.specService.listProductSpecification({}).subscribe( data => this.productSpecifications = data, @@ -132,21 +106,17 @@ export class EditProductOfferingsComponent implements OnInit { if (this.offering) { this.finishedLoading = true this.editForm.reset() - //populate General Panel Info if (!this.offering.validFor) this.offering.validFor = { endDateTime: new Date(new Date().setFullYear(new Date().getFullYear()+20)).toISOString(), startDateTime: new Date().toISOString() } + this.editForm.patchValue(this.offering) this.editForm.markAsPristine() if (this.offering.productSpecification) { - this.editForm.patchValue({productSpecification: this.productSpecifications.find(el => el.id === this.offering.productSpecification.id) }) + const specRef = this.productSpecifications.find(el => el.id === this.offering.productSpecification.id); + this.editForm.patchValue({ productSpecification: specRef }); + + this.loadLinkedSpecDetails(this.offering.productSpecification.id, true); } - - //populate Product Specification Relationship - // this.filteredServiceSpecificationRel$ = this.serviceSpecificationRelationshipFilterCtrl.valueChanges.pipe( - // startWith(null), - // map( (value:null | string) => value ? this._filterOnRelatedSpecs(value) : this.spec.serviceSpecification.slice() ) - // ) - } else { this.offeringNotFound = true @@ -155,113 +125,328 @@ export class EditProductOfferingsComponent implements OnInit { ) } + + loadLinkedSpecDetails(specId: string, isInitialLoad: boolean = false) { + this.specService.retrieveProductSpecification({ id: specId }).subscribe( + fullSpec => { + this.currentLinkedSpec = fullSpec; + this.availableSpecCharacteristics = fullSpec.productSpecCharacteristic || []; + + if (!isInitialLoad) { + this.selectedCharacteristics.clear(); + this.selectedCharValues.clear(); + this.selectAllCharacteristics(); + } + + if (isInitialLoad && this.offering.prodSpecCharValueUse) { + this.offering.prodSpecCharValueUse.forEach(charUse => { + this.selectedCharacteristics.add(charUse.name); + + const valueSet = new Set(); + if (charUse.productSpecCharacteristicValue) { + charUse.productSpecCharacteristicValue.forEach(v => valueSet.add(v.value.value)); + } + this.selectedCharValues.set(charUse.name, valueSet); + }); + } + }, + error => console.error("Failed to load full spec details", error) + ); + } + + // ------------------------------------------------------------------ + + onSpecificationChange(event: MatSelectChange) { + + const dialogRef = this.dialog.open(CopySpecificationPropertiesComponent, { autoFocus: true }); + + dialogRef.afterClosed().subscribe(confirmCopy => { + if (confirmCopy) { + this.editForm.patchValue({ + name: event.value.name, + description: event.value.description, + }); + + } + }) + + this.selectedCharacteristics.clear(); + this.availableSpecCharacteristics = []; + if (event.value && event.value.id) { + this.loadLinkedSpecDetails(event.value.id, false); + } + + } + + isCharacteristicSelected(char: ProductSpecificationCharacteristicRes): boolean { + return this.selectedCharacteristics.has(char.name); + } + + onCharacteristicToggle(char: ProductSpecificationCharacteristicRes, event: MatCheckboxChange) { + if (event.checked) { + this.selectedCharacteristics.add(char.name); + if (!this.selectedCharValues.has(char.name)) { + this.selectAllValues(char); + } + } else { + this.selectedCharacteristics.delete(char.name); + } + this.editForm.markAsDirty(); + } + + isValueSelected(char: ProductSpecificationCharacteristicRes, val: any): boolean { + if (!val || !val.value) return false; // Guard + const selectedSet = this.selectedCharValues.get(char.name); + return selectedSet ? selectedSet.has(val.value.value) : false; + } + + getSelectedValuesCount(char: ProductSpecificationCharacteristicRes): number { + const selectedSet = this.selectedCharValues.get(char.name); + return selectedSet ? selectedSet.size : 0; + } + + onValueToggle(char: ProductSpecificationCharacteristicRes, val: any, event: MatCheckboxChange) { + // FIX: Only guard against missing wrapper. Allow empty strings inside val.value.value. + if (!val || !val.value) return; + + let selectedSet = this.selectedCharValues.get(char.name); + if (!selectedSet) { + selectedSet = new Set(); + this.selectedCharValues.set(char.name, selectedSet); + } + + const valueToStore = val.value.value; + + if (event.checked) { + selectedSet.add(valueToStore); + } else { + selectedSet.delete(valueToStore); + } + this.editForm.markAsDirty(); + } + + selectAllCharacteristics() { + this.availableSpecCharacteristics.forEach(c => { + this.selectedCharacteristics.add(c.name); + this.selectAllValues(c); + }); + this.editForm.markAsDirty(); + } + + deselectAllCharacteristics() { + this.selectedCharacteristics.clear(); + this.selectedCharValues.clear(); + this.editForm.markAsDirty(); + } + + selectAllValues(char: ProductSpecificationCharacteristicRes) { + const allValues = new Set(); + + if (char.productSpecCharacteristicValue && Array.isArray(char.productSpecCharacteristicValue)) { + char.productSpecCharacteristicValue.forEach(v => { + if (v && v.value) { + allValues.add(v.value.value); + } + }); + } + + this.selectedCharValues.set(char.name, allValues); + this.editForm.markAsDirty(); + } + + deselectAllValues(char: ProductSpecificationCharacteristicRes) { + const selectedSet = this.selectedCharValues.get(char.name); + if (selectedSet) { + selectedSet.clear(); + } else { + // Create empty set if none existed to ensure state is clean + this.selectedCharValues.set(char.name, new Set()); + } + this.editForm.markAsDirty(); + } + + // ------------------------------------------------------------------ + updateOfferingGeneral() { if (this.editForm.valid) { const formValue = this.editForm.value; - const specRef = formValue.productSpecification; - - if (specRef && specRef.id) { - // retrieve full specification details - this.specService.retrieveProductSpecification({ id: specRef.id }).subscribe( - (fullSpec: ProductSpecification) => { - const payload = this.constructPayload(formValue, fullSpec); - this.submitOfferingToApi(payload); - }, - (error) => { - console.error(error); - this.toastrService.error("Could not retrieve full Product Specification details."); + const linkedSpec = this.currentLinkedSpec || formValue.productSpecification; + + let productSpecRef: any = undefined; + if (linkedSpec) { + productSpecRef = { id: linkedSpec.id, name: linkedSpec.name }; + if (linkedSpec.version) productSpecRef.version = linkedSpec.version; + } + + + const updateObj: ProductOfferingCreate | ProductOfferingUpdate = { + name: formValue.name, + description: formValue.description, + lifecycleStatus: formValue.lifecycleStatus, + version: formValue.version, + validFor: formValue.validFor, + isSellable: true, + statusReason: '', + productSpecification: productSpecRef, + + // Auto-mappings + isBundle: linkedSpec?.isBundle || false, + attachment: linkedSpec?.attachment || [], + + prodSpecCharValueUse: [], + + serviceCandidate: (linkedSpec && (linkedSpec as any).serviceSpecification && (linkedSpec as any).serviceSpecification.length > 0) + ? { + id: (linkedSpec as any).serviceSpecification[0].id, + name: (linkedSpec as any).serviceSpecification[0].name, + version: (linkedSpec as any).serviceSpecification[0].version, + '@type': 'ServiceCandidateRef' + } + : undefined, + + resourceCandidate: (linkedSpec && (linkedSpec as any).resourceSpecification && (linkedSpec as any).resourceSpecification.length > 0) + ? { + id: (linkedSpec as any).resourceSpecification[0].id, + name: (linkedSpec as any).resourceSpecification[0].name, + version: (linkedSpec as any).resourceSpecification[0].version, + '@type': 'ResourceCandidateRef' + } + : undefined, + + serviceLevelAgreement: (linkedSpec as any).serviceLevelAgreement || undefined, + + agreement: [], + bundledProductOffering: [], + category: [], + channel: [], + marketSegment: [], + place: [], + productOfferingPrice: [], + productOfferingTerm: [], + '@type': 'ProductOffering' + }; + + let updatedOffering: ProductOffering; + + if (this.newOffering) { + this.offeringService.createProductOffering(updateObj).subscribe( + data => { updatedOffering = data }, + error => { console.error(error); this.toastrService.error("Failed to create offering"); }, + () => { + this.newOffering = false; + this.router.navigate([updatedOffering.id], {relativeTo: this.activatedRoute}); + this.toastrService.success("Product Offering created. Please configure characteristics in the next tab."); + this.refreshProductOffering(updatedOffering); } ); } else { - // no spec selected - const payload = this.constructPayload(formValue, null); - this.submitOfferingToApi(payload); + this.offeringService.patchProductOffering({ id: this.offeringID, productOffering: updateObj }).subscribe( + data => { updatedOffering = data }, + error => { console.error(error); this.toastrService.error("Failed to update offering"); }, + () => { + this.toastrService.success("Product Offering General Info updated"); + this.refreshProductOffering(updatedOffering); + } + ); } } } - private constructPayload(formValue: any, linkedSpec: ProductSpecification | null): ProductOfferingCreate | ProductOfferingUpdate { - - let productSpecRef: any = undefined; - if (linkedSpec) { - productSpecRef = { id: linkedSpec.id, name: linkedSpec.name }; - if (linkedSpec.version) productSpecRef.version = linkedSpec.version; + // ------------------------------------------------------------------ + + updateOfferingCharacteristics() { + if (!this.offeringID) { + this.toastrService.error("Please save the General Properties first to create the Offering."); + return; } - return { - name: formValue.name, - description: formValue.description, - lifecycleStatus: formValue.lifecycleStatus, - version: formValue.version, - validFor: formValue.validFor, - isSellable: true, - statusReason: '', + if (!this.currentLinkedSpec) { + this.toastrService.warning("No Product Specification linked."); + return; + } - productSpecification: productSpecRef, + const charsToMap = this.currentLinkedSpec.productSpecCharacteristic?.filter( + char => this.selectedCharacteristics.has(char.name) + ) || []; - - isBundle: linkedSpec?.isBundle || false, - attachment: linkedSpec?.attachment || [], - - prodSpecCharValueUse: [], - - serviceCandidate: (linkedSpec && (linkedSpec as any).serviceSpecification && (linkedSpec as any).serviceSpecification.length > 0) - ? { - id: (linkedSpec as any).serviceSpecification[0].id, - name: (linkedSpec as any).serviceSpecification[0].name, - version: (linkedSpec as any).serviceSpecification[0].version, - '@type': 'ServiceCandidateRef' - } - : undefined, - - resourceCandidate: (linkedSpec && (linkedSpec as any).resourceSpecification && (linkedSpec as any).resourceSpecification.length > 0) - ? { - id: (linkedSpec as any).resourceSpecification[0].id, - name: (linkedSpec as any).resourceSpecification[0].name, - version: (linkedSpec as any).resourceSpecification[0].version, - '@type': 'ResourceCandidateRef' - } - : undefined, - - serviceLevelAgreement: (linkedSpec as any).serviceLevelAgreement || undefined, - - agreement: [], - bundledProductOffering: [], - category: [], - channel: [], - marketSegment: [], - place: [], - productOfferingPrice: [], - productOfferingTerm: [], - - '@type': 'ProductOffering' + const mappedChars = this.mapSpecCharacteristicsToOffering(charsToMap, this.currentLinkedSpec); + + const patchObj: ProductOfferingUpdate = { + prodSpecCharValueUse: mappedChars }; - } - private submitOfferingToApi(updateObj: ProductOfferingCreate | ProductOfferingUpdate) { let updatedOffering: ProductOffering; - - if (this.newOffering) { - this.offeringService.createProductOffering(updateObj).subscribe( - data => { updatedOffering = data }, - error => { console.error(error); this.toastrService.error("Failed to create offering"); }, - () => { - this.newOffering = false; - this.router.navigate([updatedOffering.id], {relativeTo: this.activatedRoute}); - this.toastrService.success("Product Offering was successfully created"); - this.refreshProductOffering(updatedOffering); - } - ); - } else { - this.offeringService.patchProductOffering({ id: this.offeringID, productOffering: updateObj }).subscribe( - data => { updatedOffering = data }, - error => { console.error(error); this.toastrService.error("Failed to update offering"); }, - () => { - this.toastrService.success("Product Offering was successfully updated"); - this.refreshProductOffering(updatedOffering); - } - ); - } + + this.offeringService.patchProductOffering({ id: this.offeringID, productOffering: patchObj }).subscribe( + data => { updatedOffering = data }, + error => { + console.error(error); + this.toastrService.error("Failed to update characteristics: " + (error.error?.message || error.message)); + }, + () => { + this.toastrService.success("Product Offering Characteristics updated successfully"); + this.refreshProductOffering(updatedOffering); + } + ); + } + + + private mapSpecCharacteristicsToOffering(specChars: ProductSpecificationCharacteristicRes[], linkedSpec: ProductSpecification | null): any[] { + if (!specChars) return []; + + return specChars.map(char => { + + let mappedValues = []; + if (char.productSpecCharacteristicValue && char.productSpecCharacteristicValue.length > 0) { + const selectedSet = this.selectedCharValues.get(char.name); + + mappedValues = char.productSpecCharacteristicValue + .filter(val => { + return val.value && selectedSet && selectedSet.has(val.value.value); + }) + .map(val => { + const { uuid, ...rest } = val as any; + return rest; + }); + } + + const charId = char.uuid; + const charUuid = char.uuid; + let charVersion = char.version; + if (!charVersion && linkedSpec) charVersion = linkedSpec.version; + + const charRef: any = { + id: charId, + name: char.name, + '@type': 'ProductSpecificationCharacteristic', + '@referredType': 'ProductSpecificationCharacteristic' + }; + if (charUuid) charRef.uuid = charUuid; + if (charVersion) charRef.version = charVersion; + + let parentSpecRef: any = undefined; + if (linkedSpec) { + parentSpecRef = { + id: linkedSpec.id, + name: linkedSpec.name, + version: linkedSpec.version, + '@type': 'ProductSpecificationRef', + '@referredType': 'ProductSpecification' + }; + } + + return { + name: char.name, + description: char.description, + minCardinality: char.minCardinality, + maxCardinality: char.maxCardinality, + valueType: char.valueType, + validFor: char.validFor, + productSpecCharacteristicValue: mappedValues, + productSpecificationCharacteristic: charRef, + productSpecification: parentSpecRef + }; + }); } refreshProductOffering(updatedOffering : ProductOffering) { @@ -269,12 +454,51 @@ export class EditProductOfferingsComponent implements OnInit { this.retrieveOffering() } + selectListitem(item: string) { + if (this.editForm.pristine) { + this.activeListItem = item; + } else { + const dialogRef = this.dialog.open(DiscardChangesComponent, { autoFocus: true }); + + dialogRef.afterClosed().subscribe(discardChanges => { + if (discardChanges) { + this.editForm.reset(); + + this.selectedCharacteristics.clear(); + this.selectedCharValues.clear(); + this.availableSpecCharacteristics = []; + this.currentLinkedSpec = null; + + if (this.offering) { + if (!this.offering.validFor) { + this.offering.validFor = { + endDateTime: new Date(new Date().setFullYear(new Date().getFullYear()+20)).toISOString(), + startDateTime: new Date().toISOString() + }; + } + this.editForm.patchValue(this.offering); + + if (this.offering.productSpecification) { + const specRef = this.productSpecifications.find(el => el.id === this.offering.productSpecification.id); + this.editForm.patchValue({ productSpecification: specRef }); + + this.loadLinkedSpecDetails(this.offering.productSpecification.id, true); + } + } + + this.editForm.markAsPristine(); + this.activeListItem = item; + } + }); + } + } + ngOnDestroy() { this.subscriptions.unsubscribe() } - } + @Component({ selector: 'app-discard-changes', templateUrl: 'discard-changes.component.html', @@ -313,4 +537,4 @@ export class CopySpecificationPropertiesComponent { onYesClick(): void { this.dialogRef.close(true) } -} +} \ No newline at end of file -- GitLab From 4606e4a0339af638205b98e1e99fc3c034dee423 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Mon, 22 Dec 2025 15:15:42 +0200 Subject: [PATCH 3/8] update selected characteristic values fixes --- .../edit-product-offerings.component.html | 2 +- .../edit-product-offerings.component.ts | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index 7a361cc..8ae8433 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -185,7 +185,7 @@
diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index f476f53..bc32eca 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -47,7 +47,7 @@ export class EditProductOfferingsComponent implements OnInit { availableSpecCharacteristics: ProductSpecificationCharacteristicRes[] = []; selectedCharacteristics: Set = new Set(); - selectedCharValues: Map> = new Map(); + selectedCharValues: Map> = new Map(); listItems = ["Main Properties", "Linked Product Specification Characteristics"] activeListItem = "Main Properties" @@ -142,9 +142,9 @@ export class EditProductOfferingsComponent implements OnInit { this.offering.prodSpecCharValueUse.forEach(charUse => { this.selectedCharacteristics.add(charUse.name); - const valueSet = new Set(); + const valueSet = new Set(); if (charUse.productSpecCharacteristicValue) { - charUse.productSpecCharacteristicValue.forEach(v => valueSet.add(v.value.value)); + charUse.productSpecCharacteristicValue.forEach(v => valueSet.add(v.value)); } this.selectedCharValues.set(charUse.name, valueSet); }); @@ -195,9 +195,20 @@ export class EditProductOfferingsComponent implements OnInit { } isValueSelected(char: ProductSpecificationCharacteristicRes, val: any): boolean { - if (!val || !val.value) return false; // Guard + if (!val || !val.value) return false; + + // Get the Set of selected objects for this characteristic name const selectedSet = this.selectedCharValues.get(char.name); - return selectedSet ? selectedSet.has(val.value.value) : false; + + if (!selectedSet) return false; + + for (const storedObj of selectedSet) { + if (storedObj.alias === val.value.alias && storedObj.value === val.value.value) { + return true; + } + } + + return false; } getSelectedValuesCount(char: ProductSpecificationCharacteristicRes): number { @@ -206,16 +217,17 @@ export class EditProductOfferingsComponent implements OnInit { } onValueToggle(char: ProductSpecificationCharacteristicRes, val: any, event: MatCheckboxChange) { - // FIX: Only guard against missing wrapper. Allow empty strings inside val.value.value. if (!val || !val.value) return; let selectedSet = this.selectedCharValues.get(char.name); if (!selectedSet) { - selectedSet = new Set(); + selectedSet = new Set(); this.selectedCharValues.set(char.name, selectedSet); } - const valueToStore = val.value.value; + console.log(selectedSet); + + const valueToStore = val.value; if (event.checked) { selectedSet.add(valueToStore); @@ -240,16 +252,15 @@ export class EditProductOfferingsComponent implements OnInit { } selectAllValues(char: ProductSpecificationCharacteristicRes) { - const allValues = new Set(); + const allValues = new Set(); if (char.productSpecCharacteristicValue && Array.isArray(char.productSpecCharacteristicValue)) { char.productSpecCharacteristicValue.forEach(v => { if (v && v.value) { - allValues.add(v.value.value); + allValues.add(v.value); } }); } - this.selectedCharValues.set(char.name, allValues); this.editForm.markAsDirty(); } @@ -259,8 +270,7 @@ export class EditProductOfferingsComponent implements OnInit { if (selectedSet) { selectedSet.clear(); } else { - // Create empty set if none existed to ensure state is clean - this.selectedCharValues.set(char.name, new Set()); + this.selectedCharValues.set(char.name, new Set()); } this.editForm.markAsDirty(); } @@ -278,7 +288,6 @@ export class EditProductOfferingsComponent implements OnInit { if (linkedSpec.version) productSpecRef.version = linkedSpec.version; } - const updateObj: ProductOfferingCreate | ProductOfferingUpdate = { name: formValue.name, description: formValue.description, @@ -402,7 +411,7 @@ export class EditProductOfferingsComponent implements OnInit { mappedValues = char.productSpecCharacteristicValue .filter(val => { - return val.value && selectedSet && selectedSet.has(val.value.value); + return val.value && selectedSet && selectedSet.has(val.value); }) .map(val => { const { uuid, ...rest } = val as any; @@ -411,7 +420,7 @@ export class EditProductOfferingsComponent implements OnInit { } const charId = char.uuid; - const charUuid = char.uuid; + // const charUuid = char.uuid; let charVersion = char.version; if (!charVersion && linkedSpec) charVersion = linkedSpec.version; @@ -421,7 +430,7 @@ export class EditProductOfferingsComponent implements OnInit { '@type': 'ProductSpecificationCharacteristic', '@referredType': 'ProductSpecificationCharacteristic' }; - if (charUuid) charRef.uuid = charUuid; + // if (charUuid) charRef.uuid = charUuid; if (charVersion) charRef.version = charVersion; let parentSpecRef: any = undefined; @@ -436,14 +445,23 @@ export class EditProductOfferingsComponent implements OnInit { } return { + uuid: char.uuid, + lastUpdate: char.lastUpdate, + lifecyclestatusEnum: 'In Study', + '@baseType': 'ProductSpecCharacteristicUse', + '@type': 'ProductSpecCharacteristicUse', + '@schemaLocation': char['@schemaLocation'], + href: char.href, name: char.name, description: char.description, + lifecycleStatus: char.lifecycleStatus, + version: char.version, minCardinality: char.minCardinality, maxCardinality: char.maxCardinality, valueType: char.valueType, validFor: char.validFor, productSpecCharacteristicValue: mappedValues, - productSpecificationCharacteristic: charRef, + // productSpecificationCharacteristic: charRef, productSpecification: parentSpecRef }; }); -- GitLab From f044509bc1a1226577d82d00b138c01aa8000725 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Mon, 22 Dec 2025 15:39:43 +0200 Subject: [PATCH 4/8] checkbox bug fix --- .../edit-product-offerings.component.ts | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index bc32eca..8df246f 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -47,7 +47,7 @@ export class EditProductOfferingsComponent implements OnInit { availableSpecCharacteristics: ProductSpecificationCharacteristicRes[] = []; selectedCharacteristics: Set = new Set(); - selectedCharValues: Map> = new Map(); + selectedCharValues: Map = new Map(); listItems = ["Main Properties", "Linked Product Specification Characteristics"] activeListItem = "Main Properties" @@ -139,14 +139,27 @@ export class EditProductOfferingsComponent implements OnInit { } if (isInitialLoad && this.offering.prodSpecCharValueUse) { - this.offering.prodSpecCharValueUse.forEach(charUse => { - this.selectedCharacteristics.add(charUse.name); + this.offering.prodSpecCharValueUse.forEach(offeringChar => { + this.selectedCharacteristics.add(offeringChar.name); - const valueSet = new Set(); - if (charUse.productSpecCharacteristicValue) { - charUse.productSpecCharacteristicValue.forEach(v => valueSet.add(v.value)); + const specCharDef = this.availableSpecCharacteristics.find(c => c.name === offeringChar.name); + + const selectedUUIDs: string[] = []; + + if (offeringChar.productSpecCharacteristicValue && specCharDef) { + offeringChar.productSpecCharacteristicValue.forEach(savedVal => { + const matchingSpecVal = specCharDef.productSpecCharacteristicValue?.find(specVal => { + const sameValue = specVal.value?.value === savedVal.value?.value; + const sameAlias = specVal.value?.alias === savedVal.value?.alias; + return sameValue && sameAlias; + }); + + if (matchingSpecVal && matchingSpecVal.uuid) { + selectedUUIDs.push(matchingSpecVal.uuid); + } + }); } - this.selectedCharValues.set(charUse.name, valueSet); + this.selectedCharValues.set(offeringChar.name, selectedUUIDs); }); } }, @@ -195,44 +208,37 @@ export class EditProductOfferingsComponent implements OnInit { } isValueSelected(char: ProductSpecificationCharacteristicRes, val: any): boolean { - if (!val || !val.value) return false; - - // Get the Set of selected objects for this characteristic name - const selectedSet = this.selectedCharValues.get(char.name); + if (!val || !val.uuid) return false; - if (!selectedSet) return false; - - for (const storedObj of selectedSet) { - if (storedObj.alias === val.value.alias && storedObj.value === val.value.value) { - return true; - } - } - - return false; + const selectedUUIDs = this.selectedCharValues.get(char.name); + return selectedUUIDs ? selectedUUIDs.includes(val.uuid) : false; } getSelectedValuesCount(char: ProductSpecificationCharacteristicRes): number { - const selectedSet = this.selectedCharValues.get(char.name); - return selectedSet ? selectedSet.size : 0; + const selectedArray = this.selectedCharValues.get(char.name); + return selectedArray ? selectedArray.length : 0; } onValueToggle(char: ProductSpecificationCharacteristicRes, val: any, event: MatCheckboxChange) { - if (!val || !val.value) return; + if (!val || !val.uuid) return; - let selectedSet = this.selectedCharValues.get(char.name); - if (!selectedSet) { - selectedSet = new Set(); - this.selectedCharValues.set(char.name, selectedSet); + let selectedUUIDs = this.selectedCharValues.get(char.name); + if (!selectedUUIDs) { + selectedUUIDs = []; + this.selectedCharValues.set(char.name, selectedUUIDs); } - console.log(selectedSet); - - const valueToStore = val.value; + const uuid = val.uuid; if (event.checked) { - selectedSet.add(valueToStore); + if (!selectedUUIDs.includes(uuid)) { + selectedUUIDs.push(uuid); + } } else { - selectedSet.delete(valueToStore); + const index = selectedUUIDs.indexOf(uuid); + if (index > -1) { + selectedUUIDs.splice(index, 1); + } } this.editForm.markAsDirty(); } @@ -252,27 +258,24 @@ export class EditProductOfferingsComponent implements OnInit { } selectAllValues(char: ProductSpecificationCharacteristicRes) { - const allValues = new Set(); + const allUUIDs: string[] = []; - if (char.productSpecCharacteristicValue && Array.isArray(char.productSpecCharacteristicValue)) { + if (char.productSpecCharacteristicValue) { char.productSpecCharacteristicValue.forEach(v => { - if (v && v.value) { - allValues.add(v.value); + if (v.uuid) { + allUUIDs.push(v.uuid); } }); } - this.selectedCharValues.set(char.name, allValues); + this.selectedCharValues.set(char.name, allUUIDs); this.editForm.markAsDirty(); } deselectAllValues(char: ProductSpecificationCharacteristicRes) { - const selectedSet = this.selectedCharValues.get(char.name); - if (selectedSet) { - selectedSet.clear(); - } else { - this.selectedCharValues.set(char.name, new Set()); - } + + this.selectedCharValues.set(char.name, []); this.editForm.markAsDirty(); + } // ------------------------------------------------------------------ @@ -298,7 +301,6 @@ export class EditProductOfferingsComponent implements OnInit { statusReason: '', productSpecification: productSpecRef, - // Auto-mappings isBundle: linkedSpec?.isBundle || false, attachment: linkedSpec?.attachment || [], @@ -407,11 +409,11 @@ export class EditProductOfferingsComponent implements OnInit { let mappedValues = []; if (char.productSpecCharacteristicValue && char.productSpecCharacteristicValue.length > 0) { - const selectedSet = this.selectedCharValues.get(char.name); + const selectedUUIDs = this.selectedCharValues.get(char.name); mappedValues = char.productSpecCharacteristicValue .filter(val => { - return val.value && selectedSet && selectedSet.has(val.value); + return val.uuid && selectedUUIDs && selectedUUIDs.includes(val.uuid); }) .map(val => { const { uuid, ...rest } = val as any; -- GitLab From 43a8ffe1c1a767a6ff5586390231a24345fb2551 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Mon, 22 Dec 2025 16:17:38 +0200 Subject: [PATCH 5/8] add SortByValuePipe and integrate it into product offerings component for sorting characteristic values --- src/app/app-products.module.ts | 5 +++-- .../edit-product-offerings.component.html | 7 ++----- .../edit-product-offerings.component.ts | 5 +++-- .../sort-by-value.pipe.ts | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 src/app/p_product/admin/productCatalogManagement/edit-product-offerings/sort-by-value.pipe.ts diff --git a/src/app/app-products.module.ts b/src/app/app-products.module.ts index d26ba40..a83a0f9 100644 --- a/src/app/app-products.module.ts +++ b/src/app/app-products.module.ts @@ -24,7 +24,7 @@ import { PreviewMarketPlaceItemComponent } from './p_product/marketplace/preview 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'; - +import { SortByValuePipe } from './p_product/admin/productCatalogManagement/edit-product-offerings/sort-by-value.pipe'; @NgModule({ declarations: [ @@ -48,7 +48,8 @@ import { ConfirmCharacteristicAssignmentComponent } from './p_product/admin/prod EditProductOfferingsComponent, PreviewMarketPlaceItemComponent, AssignProductOfferingsComponent, - AssignSubcategoriesComponent + AssignSubcategoriesComponent, + SortByValuePipe ], imports: [ CommonModule, diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index 8ae8433..b5d8bad 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -214,26 +214,23 @@

Select the characteristic values for this offering:

-
+
+ - {{val.value.alias}}: {{val.value.value}} - ({{val.unitOfMeasure}}) - {{val.value.value}} ({{val.unitOfMeasure}}) - - Empty Value diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index 8df246f..471f247 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -162,8 +162,9 @@ export class EditProductOfferingsComponent implements OnInit { this.selectedCharValues.set(offeringChar.name, selectedUUIDs); }); } + }, - error => console.error("Failed to load full spec details", error) + error => this.toastrService.error("Failed to load full spec details", error) ); } @@ -296,7 +297,7 @@ export class EditProductOfferingsComponent implements OnInit { description: formValue.description, lifecycleStatus: formValue.lifecycleStatus, version: formValue.version, - validFor: formValue.validFor, + validFor: {"startDateTime": formValue.validFor.startDateTime, "endDateTime": formValue.validFor.endDateTime}, isSellable: true, statusReason: '', productSpecification: productSpecRef, diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/sort-by-value.pipe.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/sort-by-value.pipe.ts new file mode 100644 index 0000000..282ce9b --- /dev/null +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/sort-by-value.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'sortByValue' +}) +export class SortByValuePipe implements PipeTransform { + + transform(values: any[]): any[] { + if (!values || !Array.isArray(values)) { + return []; + } + + return [...values].sort((a, b) => { + const labelA = (a.value?.alias || a.value?.value || '').toString().toLowerCase(); + const labelB = (b.value?.alias || b.value?.value || '').toString().toLowerCase(); + + return labelA.localeCompare(labelB, undefined, { numeric: true }); + }); + } + +} \ No newline at end of file -- GitLab From b5f28b1b1191d3eca1ecf2e970b344f92043baf5 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Tue, 23 Dec 2025 10:27:52 +0200 Subject: [PATCH 6/8] clean up characteristic selection logic and ensure proper handling of product specification values --- .../edit-product-offerings.component.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index 471f247..bab7b76 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -131,11 +131,10 @@ export class EditProductOfferingsComponent implements OnInit { fullSpec => { this.currentLinkedSpec = fullSpec; this.availableSpecCharacteristics = fullSpec.productSpecCharacteristic || []; - if (!isInitialLoad) { this.selectedCharacteristics.clear(); this.selectedCharValues.clear(); - this.selectAllCharacteristics(); + // this.selectAllCharacteristics(); } if (isInitialLoad && this.offering.prodSpecCharValueUse) { @@ -199,9 +198,9 @@ export class EditProductOfferingsComponent implements OnInit { onCharacteristicToggle(char: ProductSpecificationCharacteristicRes, event: MatCheckboxChange) { if (event.checked) { this.selectedCharacteristics.add(char.name); - if (!this.selectedCharValues.has(char.name)) { - this.selectAllValues(char); - } + // if (!this.selectedCharValues.has(char.name)) { + // this.selectAllValues(char); + // } } else { this.selectedCharacteristics.delete(char.name); } @@ -247,7 +246,7 @@ export class EditProductOfferingsComponent implements OnInit { selectAllCharacteristics() { this.availableSpecCharacteristics.forEach(c => { this.selectedCharacteristics.add(c.name); - this.selectAllValues(c); + // this.selectAllValues(c); }); this.editForm.markAsDirty(); } @@ -291,10 +290,23 @@ export class EditProductOfferingsComponent implements OnInit { productSpecRef = { id: linkedSpec.id, name: linkedSpec.name }; if (linkedSpec.version) productSpecRef.version = linkedSpec.version; } + + let characteristicsToSave: any[] = []; + + if (!this.newOffering && this.offering && this.offering.prodSpecCharValueUse) { + const originalSpecId = this.offering.productSpecification?.id; + const currentSpecId = linkedSpec?.id; + + if (originalSpecId === currentSpecId) { + characteristicsToSave = this.offering.prodSpecCharValueUse; + } else { + characteristicsToSave = []; + } + } const updateObj: ProductOfferingCreate | ProductOfferingUpdate = { name: formValue.name, - description: formValue.description, + description: formValue.description || '', lifecycleStatus: formValue.lifecycleStatus, version: formValue.version, validFor: {"startDateTime": formValue.validFor.startDateTime, "endDateTime": formValue.validFor.endDateTime}, @@ -305,7 +317,7 @@ export class EditProductOfferingsComponent implements OnInit { isBundle: linkedSpec?.isBundle || false, attachment: linkedSpec?.attachment || [], - prodSpecCharValueUse: [], + prodSpecCharValueUse: characteristicsToSave, serviceCandidate: (linkedSpec && (linkedSpec as any).serviceSpecification && (linkedSpec as any).serviceSpecification.length > 0) ? { -- GitLab From e2d3e85b5e6e4ab1f5cef3d9c86a3244fc4ce122 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Tue, 23 Dec 2025 10:52:11 +0200 Subject: [PATCH 7/8] update product offering characteristics display --- .../edit-product-offerings.component.html | 3 ++- .../edit-product-offerings/edit-product-offerings.component.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index b5d8bad..7e42044 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -193,7 +193,7 @@
{{char.name}} - {{char.description}} + {{char.valueType}}
@@ -204,6 +204,7 @@
+

{{char.description}}

This characteristic has no pre-defined values. diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index bab7b76..837bc8d 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -203,6 +203,7 @@ export class EditProductOfferingsComponent implements OnInit { // } } else { this.selectedCharacteristics.delete(char.name); + this.selectedCharValues.set(char.name, []); } this.editForm.markAsDirty(); } -- GitLab From 4a2bdd9af81d849869f6e4ce82d2dacfd2cf0f07 Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Wed, 7 Jan 2026 13:34:58 +0200 Subject: [PATCH 8/8] minor text changes --- .../edit-product-offerings.component.html | 25 ++++++++++--------- .../edit-product-offerings.component.scss | 3 +++ .../edit-product-offerings.component.ts | 2 +- .../list-product-offerings.component.ts | 2 ++ 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html index 7e42044..f431895 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.html @@ -151,9 +151,9 @@

- +
@@ -169,10 +169,11 @@
-
Choose Characteristics from: - {{editForm.value.productSpecification.name}} +
Choose Configurable Characteristics to expose from: + {{editForm.value.productSpecification.name}} +
- Select the characteristics to include. Expand to select specific values. + Select the configurable characteristics to include in the product offering. Expand to select specific characteristic values.
@@ -217,7 +218,7 @@
- @@ -241,10 +242,10 @@
- -
@@ -259,10 +260,10 @@
-
+
- - + +
@@ -277,7 +278,7 @@
- +
diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.scss b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.scss index e69de29..c6dda67 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.scss +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.scss @@ -0,0 +1,3 @@ +.nav-link:hover { + cursor: pointer; +} diff --git a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts index 837bc8d..90aed37 100644 --- a/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/edit-product-offerings/edit-product-offerings.component.ts @@ -49,7 +49,7 @@ export class EditProductOfferingsComponent implements OnInit { selectedCharacteristics: Set = new Set(); selectedCharValues: Map = new Map(); - listItems = ["Main Properties", "Linked Product Specification Characteristics"] + listItems = ["Main Properties", "Product Offering Characteristics"] activeListItem = "Main Properties" lifecycleStatuses = ["In study", "In design", "In test", "Active", "Launched", "Retired", "Obsolete", "Rejected"] diff --git a/src/app/p_product/admin/productCatalogManagement/list-product-offerings/list-product-offerings.component.ts b/src/app/p_product/admin/productCatalogManagement/list-product-offerings/list-product-offerings.component.ts index 102cb45..505335e 100644 --- a/src/app/p_product/admin/productCatalogManagement/list-product-offerings/list-product-offerings.component.ts +++ b/src/app/p_product/admin/productCatalogManagement/list-product-offerings/list-product-offerings.component.ts @@ -45,6 +45,8 @@ export class ListProductOfferingsComponent implements OnInit { error => { console.error(error) }, () => { this.dataSource.data = this.productOfferings + this.sort.active = 'lastUpdate' + this.sort.direction = 'desc' this.dataSource.sort = this.sort this.dataSource.paginator = this.paginator; this.dataSource.sortingDataAccessor = (item, property): string | number => { -- GitLab