Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/app/pages/dashboard/dashboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
)
.subscribe((picked) => {
this.productService.getProductsDetails(picked).then((data) => {
this.productOfferings = data as ProductOffering[];
this.productOfferings = (data as ProductOffering[]).filter((offering) =>
offering.attachment?.some(
(a) => a.attachmentType === 'Picture' || a.name === 'Profile Picture'
)
);
})
});
}
Expand Down
1 change: 1 addition & 0 deletions src/app/services/app-init.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class AppInitService {
environment.TENDER_ENABLED = config.tenderingEnabled ?? false
environment.DATA_SPACE_ENABLED = config.dataSpaceEnabled ?? false
environment.LEAR_URL = config.learUrl ?? ''
environment.LAUNCH_VALIDATION_ENABLED = config.launchValidationEnabled ?? false;
environment.AI_SEARCH_ENABLED = aiConfig.aiEnabled ?? config.aiEnabled ?? false;
environment.AI_SEARCH_API_KEY = aiConfig.aiApiKey ?? config.aiApiKey ?? '';
environment.AI_SEARCH_API_URL = aiConfig.aiApiUrl ?? config.aiApiUrl ?? '';
Expand Down
5 changes: 5 additions & 0 deletions src/app/services/product-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ export class ApiServiceService {
return lastValueFrom(this.http.get<any>(url));
}

checkOfferingLaunch(id: string): Promise<{ canBeLaunched: boolean }> {
const url = `${ApiServiceService.BASE_URL}/offering/${id}/launch`;
return lastValueFrom(this.http.get<{ canBeLaunched: boolean }>(url));
}

getProductPrice(id: any) {
let url = `${ApiServiceService.BASE_URL}${ApiServiceService.API_PRODUCT}/productOfferingPrice/${id}`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,9 @@
<input data-cy="offerVersion" id="prod-version" type="text" [formControl]="versionControl"
class="mb-2 bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-white text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
}
@if (extBillingEnabledControl) {
<div class="mb-2 flex items-center gap-3">
<label class="font-bold text-lg dark:text-white">{{ 'CREATE_OFFER._ext_billing' | translate }}</label>
<input data-cy="extBillingToggle" type="checkbox" [formControl]="extBillingEnabledControl"
class="w-4 h-4 text-primary-100 bg-gray-100 border-gray-300 rounded focus:ring-primary-100 dark:focus:ring-primary-50 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600"/>
</div>
}
@if (extBillingEnabledControl?.value && plaSpecIdControl) {
<label for="plaSpecId" class="font-bold text-lg dark:text-white">{{ 'CREATE_OFFER._pla_spec_id' | translate }}</label>
<input data-cy="plaSpecId" id="plaSpecId" type="url" [formControl]="plaSpecIdControl"
class="bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-white text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
[class.border-red-500]="plaSpecIdControl.invalid && plaSpecIdControl.touched"
placeholder="https://external-billing-engine.example.com"/>
@if (plaSpecIdControl.invalid && plaSpecIdControl.touched) {
<p class="text-red-500 text-sm mt-1 mb-2">{{ 'CREATE_OFFER._pla_spec_id_required' | translate }}</p>
}
}
@if (statusControl && formType === 'update') {
<label class="font-bold text-lg col-span-2 dark:text-white">{{ 'CREATE_OFFER._status' | translate }}</label>
<app-status-selector [formControl]="statusControl"></app-status-selector>
<app-status-selector [formControl]="statusControl" [disabledStatuses]="disabledStatuses"></app-status-selector>
}
@if (descControl) {
<label class="font-bold text-lg col-span-2 dark:text-white">{{ 'CREATE_OFFER._description' | translate }}</label>
Expand Down
55 changes: 15 additions & 40 deletions src/app/shared/forms/offer/general-info/general-info.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import {SharedModule} from "../../../shared.module";
import {MarkdownTextareaComponent} from "../../markdown-textarea/markdown-textarea.component";
import {StatusSelectorComponent} from "../../status-selector/status-selector.component";
import {EventMessageService} from "../../../../services/event-message.service";
import {ApiServiceService} from "../../../../services/product-service.service";
import {FormChangeState} from "../../../../models/interfaces";
import {Subscription} from "rxjs";
import {debounceTime} from "rxjs/operators";
import { noWhitespaceValidator } from 'src/app/validators/validators';
import {Subject} from "rxjs";
import { takeUntil } from 'rxjs/operators';
import { environment } from 'src/environments/environment';

interface GeneralInfo {
name: string;
status: string;
description: string;
version: string;
extBillingEnabled: boolean;
plaSpecId: string;
}

@Component({
Expand All @@ -43,7 +43,9 @@ export class GeneralInfoComponent implements OnInit, OnDestroy {
private isEditMode: boolean = false;
private destroy$ = new Subject<void>();

constructor(private eventMessage: EventMessageService) {
disabledStatuses: string[] = [];

constructor(private eventMessage: EventMessageService, private apiService: ApiServiceService) {
console.log('🔄 Initializing GeneralInfoComponent');
}

Expand Down Expand Up @@ -71,68 +73,41 @@ export class GeneralInfoComponent implements OnInit, OnDestroy {
return control instanceof FormControl ? control : null;
}

get extBillingEnabledControl(): FormControl | null {
const control = this.formGroup.get('extBillingEnabled');
return control instanceof FormControl ? control : null;
}

get plaSpecIdControl(): FormControl | null {
const control = this.formGroup.get('plaSpecId');
return control instanceof FormControl ? control : null;
}

ngOnInit() {
console.log('📝 Initializing form in', this.formType, 'mode');
this.isEditMode = this.formType === 'update';

if (this.isEditMode && this.data) {
console.log('Initializing form in update mode with data:', this.data);
const existingPlaSpecId = this.data.pricingLogicAlgorithm?.[0]?.plaSpecId ?? '';
this.formGroup.addControl('name', new FormControl<string>(this.data.name, [Validators.required, Validators.maxLength(100), noWhitespaceValidator]));
this.formGroup.addControl('status', new FormControl<string>(this.data.lifecycleStatus));
this.formGroup.addControl('description', new FormControl<string>(this.data.description, Validators.maxLength(100000)));
this.formGroup.addControl('version', new FormControl<string>(this.data.version, [Validators.required,Validators.pattern('^-?[0-9]\\d*(\\.\\d*)?$'), noWhitespaceValidator]));
this.formGroup.addControl('extBillingEnabled', new FormControl<boolean>(!!existingPlaSpecId));
this.formGroup.addControl('plaSpecId', new FormControl<string>(existingPlaSpecId, !!existingPlaSpecId ? [Validators.required] : []));

this.formGroup.get('extBillingEnabled')!.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
const plaControl = this.formGroup.get('plaSpecId')!;
if (enabled) {
plaControl.setValidators([Validators.required]);
} else {
plaControl.clearValidators();
}
plaControl.updateValueAndValidity();
});

// Store original value only in edit mode
this.originalValue = {
name: this.data.name,
status: this.data.lifecycleStatus,
description: this.data.description,
version: this.data.version,
extBillingEnabled: !!existingPlaSpecId,
plaSpecId: existingPlaSpecId
};
console.log('📝 Original value stored:', this.originalValue);

if (environment.LAUNCH_VALIDATION_ENABLED && this.data.id) {
this.apiService.checkOfferingLaunch(this.data.id).then((result) => {
if (!result.canBeLaunched) {
this.disabledStatuses = ['Launched'];
}
}).catch(() => {
this.disabledStatuses = ['Launched'];
});
}
} else {
console.log('Initializing form in create mode');
this.formGroup.addControl('name', new FormControl<string>('', [Validators.required, Validators.maxLength(100), noWhitespaceValidator]));
this.formGroup.addControl('status', new FormControl<string>('Active', [Validators.required]));
this.formGroup.addControl('description', new FormControl<string>(''));
this.formGroup.addControl('version', new FormControl<string>('0.1', [Validators.required,Validators.pattern('^-?[0-9]\\d*(\\.\\d*)?$'), noWhitespaceValidator]));
this.formGroup.addControl('extBillingEnabled', new FormControl<boolean>(false));
this.formGroup.addControl('plaSpecId', new FormControl<string>(''));

this.formGroup.get('extBillingEnabled')!.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
const plaControl = this.formGroup.get('plaSpecId')!;
if (enabled) {
plaControl.setValidators([Validators.required]);
} else {
plaControl.clearValidators();
}
plaControl.updateValueAndValidity();
});
}

// Subscribe to form changes only in edit mode
Expand Down
14 changes: 7 additions & 7 deletions src/app/shared/forms/offer/offer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,8 +703,8 @@ export class OfferComponent implements OnInit, OnDestroy{
bundledProductOffering: this.offersBundle,
place: [],
version: generalInfo.version,
...(generalInfo.extBillingEnabled && generalInfo.plaSpecId ? {
pricingLogicAlgorithm: [{ name: 'external billing', plaSpecId: generalInfo.plaSpecId }]
...(formValue.procurementMode.extBillingEnabled && formValue.procurementMode.plaSpecId ? {
pricingLogicAlgorithm: [{ name: 'external billing', plaSpecId: formValue.procurementMode.plaSpecId }]
} : {}),

category: categories,
Expand Down Expand Up @@ -820,11 +820,6 @@ export class OfferComponent implements OnInit, OnDestroy{
basePayload.description = change.currentValue.description;
basePayload.version = change.currentValue.version;
basePayload.lifecycleStatus = change.currentValue.status;
if (change.currentValue.extBillingEnabled && change.currentValue.plaSpecId) {
basePayload.pricingLogicAlgorithm = [{ name: 'external billing', plaSpecId: change.currentValue.plaSpecId }];
} else if (change.originalValue.extBillingEnabled && !change.currentValue.extBillingEnabled) {
basePayload.pricingLogicAlgorithm = [];
}
break;

case 'productSpecification':
Expand Down Expand Up @@ -932,6 +927,11 @@ export class OfferComponent implements OnInit, OnDestroy{
description: change.currentValue.id
});
}
if (change.currentValue.extBillingEnabled && change.currentValue.plaSpecId) {
basePayload.pricingLogicAlgorithm = [{ name: 'external billing', plaSpecId: change.currentValue.plaSpecId }];
} else if (change.originalValue.extBillingEnabled && !change.currentValue.extBillingEnabled) {
basePayload.pricingLogicAlgorithm = [];
}
break;

case 'replication':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
</div>
</div>

@if (extBillingEnabledControl) {
<div class="mt-4 flex items-center gap-3">
<label class="font-bold text-lg dark:text-white">{{ 'CREATE_OFFER._ext_billing' | translate }}</label>
<input data-cy="extBillingToggle" type="checkbox" [formControl]="extBillingEnabledControl"
class="w-4 h-4 text-primary-100 bg-gray-100 border-gray-300 rounded focus:ring-primary-100 dark:focus:ring-primary-50 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600"/>
</div>
}
@if (extBillingEnabledControl?.value && plaSpecIdControl) {
<label for="plaSpecId" class="font-bold text-lg dark:text-white">{{ 'CREATE_OFFER._pla_spec_id' | translate }}</label>
<input data-cy="plaSpecId" id="plaSpecId" type="url" [formControl]="plaSpecIdControl"
class="bg-gray-50 dark:bg-secondary-300 border border-gray-300 dark:border-secondary-200 dark:text-white text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
[class.border-red-500]="plaSpecIdControl.invalid && plaSpecIdControl.touched"
placeholder="https://external-billing-engine.example.com"/>
@if (plaSpecIdControl.invalid && plaSpecIdControl.touched) {
<p class="text-red-500 text-sm mt-1 mb-2">{{ 'CREATE_OFFER._pla_spec_id_required' | translate }}</p>
}
}

@if(showProcurementError){
<div
class="flex justify-center items-center fixed w-fit top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { takeUntil } from 'rxjs/operators';
interface ProcurementMode {
id: string;
name: string;
extBillingEnabled?: boolean;
plaSpecId?: string;
}

@Component({
Expand Down Expand Up @@ -71,7 +73,9 @@ export class ProcurementModeComponent implements ControlValueAccessor, AfterView
if (this.isEditMode && this.hasBeenModified && this.originalValue) {
const currentValue = {
id: this.procurementMode,
name: this.procurementModes.find(m => m.id === this.procurementMode)?.name || 'Manual'
name: this.procurementModes.find(m => m.id === this.procurementMode)?.name || 'Manual',
extBillingEnabled: this.formGroup.get('extBillingEnabled')?.value ?? false,
plaSpecId: this.formGroup.get('plaSpecId')?.value ?? ''
};

// Solo emitir si el valor es diferente al original
Expand Down Expand Up @@ -125,6 +129,16 @@ export class ProcurementModeComponent implements ControlValueAccessor, AfterView
return control instanceof FormControl ? control : null;
}

get extBillingEnabledControl(): FormControl | null {
const control = this.formGroup.get('extBillingEnabled');
return control instanceof FormControl ? control : null;
}

get plaSpecIdControl(): FormControl | null {
const control = this.formGroup.get('plaSpecId');
return control instanceof FormControl ? control : null;
}

registerOnChange(fn: any): void {
this.onChange = fn;
}
Expand Down Expand Up @@ -154,11 +168,27 @@ export class ProcurementModeComponent implements ControlValueAccessor, AfterView
// Inicializar el control del formulario
this.formGroup.addControl('mode', new FormControl<string>(initialValue, [Validators.required]));

const existingPlaSpecId = this.data?.pricingLogicAlgorithm?.[0]?.plaSpecId ?? '';
this.formGroup.addControl('extBillingEnabled', new FormControl<boolean>(!!existingPlaSpecId));
this.formGroup.addControl('plaSpecId', new FormControl<string>(existingPlaSpecId, !!existingPlaSpecId ? [Validators.required] : []));

this.formGroup.get('extBillingEnabled')!.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
const plaControl = this.formGroup.get('plaSpecId')!;
if (enabled) {
plaControl.setValidators([Validators.required]);
} else {
plaControl.clearValidators();
}
plaControl.updateValueAndValidity();
});

// Guardar el valor original solo en modo edición
if (this.isEditMode) {
this.originalValue = {
id: initialValue,
name: this.procurementModes.find(m => m.id === initialValue)?.name || 'Manual'
name: this.procurementModes.find(m => m.id === initialValue)?.name || 'Manual',
extBillingEnabled: !!existingPlaSpecId,
plaSpecId: existingPlaSpecId
};
console.log('📝 Original value stored:', this.originalValue);
}
Expand All @@ -169,26 +199,29 @@ export class ProcurementModeComponent implements ControlValueAccessor, AfterView
.subscribe(value => {
console.log('📝 Form value changed in subscription:', value);

if (value && value.mode) {
if (value.mode != 'manual' && this.gatewayCount == 0) {
this.errorMessage = "You can't select this procurement mode as you are not registered on the payment gateway.";
this.showProcurementError = true;
this.form.setErrors({ invalidProcurement: true });
this.formGroup.patchValue({
mode: 'manual'
}, { emitEvent: false });
return;
}
if (value) {
if (value.mode) {
if (value.mode != 'manual' && this.gatewayCount == 0) {
this.errorMessage = "You can't select this procurement mode as you are not registered on the payment gateway.";
this.showProcurementError = true;
this.form.setErrors({ invalidProcurement: true });
this.formGroup.patchValue({
mode: 'manual'
}, { emitEvent: false });
return;
}

this.errorMessage = "";
this.showProcurementError = false;
this.form.setErrors(null);

this.errorMessage = "";
this.showProcurementError = false;
this.form.setErrors(null)
const mode = this.procurementModes.find(m => m.id === value.mode) || this.procurementModes[0];
console.log('📝 Found mode:', mode);

const mode = this.procurementModes.find(m => m.id === value.mode) || this.procurementModes[0];
console.log('📝 Found mode:', mode);
this.procurementMode = mode.id;
console.log('📝 Current procurementMode:', this.procurementMode);
}

this.procurementMode = mode.id;
console.log('📝 Current procurementMode:', this.procurementMode);
this.hasBeenModified = true;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {SharedModule} from "../../shared.module";
styleUrl: './status-selector.component.css'
})
export class StatusSelectorComponent implements ControlValueAccessor {
@Input() statuses: string[] = ['Active', 'Launched', 'Retired', 'Obsolete']; // Estados disponibles
@Input() statuses: string[] = ['Active', 'Launched', 'Retired', 'Obsolete'];
@Input() disabledStatuses: string[] = [];
selectedStatus: string = '';

onChange = (status: string) => {};
Expand All @@ -38,8 +39,9 @@ export class StatusSelectorComponent implements ControlValueAccessor {
}

selectStatus(status: string): void {
if (this.disabledStatuses.includes(status)) return;
this.selectedStatus = status;
this.onChange(status); // Notifica al formulario padre
this.onChange(status);
this.onTouched();
}

Expand All @@ -51,6 +53,10 @@ export class StatusSelectorComponent implements ControlValueAccessor {
"Obsolete": 'text-gray-800'
};

if (this.disabledStatuses.includes(status)) {
return "opacity-40 cursor-not-allowed flex items-center justify-center p-4 rounded-lg space-x-4 transition-all text-gray-500 dark:text-gray-200";
}

const baseClasses = "cursor-pointer flex items-center justify-center p-4 rounded-lg space-x-4 transition-all";

return this.selectedStatus === status
Expand Down
3 changes: 2 additions & 1 deletion src/environments/environment.development.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,6 @@ export const environment = {
AI_SEARCH_SCORE_THRESHOLD: 0.3,
AI_SEARCH_ANSWER_MAX_ITEMS: 5,
AI_SEARCH_PROFILE: 'dome_prod',
LEAR_URL: ''
LEAR_URL: '',
LAUNCH_VALIDATION_ENABLED: false
};
3 changes: 2 additions & 1 deletion src/environments/environment.production.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,6 @@ export const environment = {
AI_SEARCH_SCORE_THRESHOLD: 0.3,
AI_SEARCH_ANSWER_MAX_ITEMS: 5,
AI_SEARCH_PROFILE: 'dome_dev2',
LEAR_URL: ''
LEAR_URL: '',
LAUNCH_VALIDATION_ENABLED: false
};
Loading
Loading