feat(catalog): refactor product and variant interfaces for improved structure and clarity

This commit is contained in:
2026-07-20 16:46:17 -03:00
parent a233a93d16
commit 0c0d2b559d
12 changed files with 216 additions and 197 deletions

View File

@@ -27,13 +27,12 @@ describe('ProductAttributeSelectorComponent', () => {
it('keeps an unlimited option available when maximum quantity is null', () => {
const fixture = TestBed.createComponent(ProductAttributeSelectorComponent);
fixture.componentRef.setInput('attributes', [sizeAttribute]);
fixture.componentRef.setInput('variantsMap', [
fixture.componentRef.setInput('inventoryPolicy', 'unlimited');
fixture.componentRef.setInput('variants', [
{
variant_id: 1,
inventory_policy: 'unlimited',
cantidad_maxima: null,
cantidad_vendida: 0,
attributes: { size: 'S' },
id: 1,
stock_tecnico: null,
values: { size: 'S' },
},
]);
fixture.detectChanges();
@@ -48,20 +47,17 @@ describe('ProductAttributeSelectorComponent', () => {
it('disables tracked options without available stock', () => {
const fixture = TestBed.createComponent(ProductAttributeSelectorComponent);
fixture.componentRef.setInput('attributes', [sizeAttribute]);
fixture.componentRef.setInput('variantsMap', [
fixture.componentRef.setInput('inventoryPolicy', 'tracked');
fixture.componentRef.setInput('variants', [
{
variant_id: 1,
inventory_policy: 'tracked',
cantidad_maxima: 0,
cantidad_vendida: 0,
attributes: { size: 'S' },
id: 1,
stock_tecnico: 0,
values: { size: 'S' },
},
{
variant_id: 2,
inventory_policy: 'tracked',
cantidad_maxima: 2,
cantidad_vendida: 0,
attributes: { size: 'M' },
id: 2,
stock_tecnico: 2,
values: { size: 'M' },
},
]);
fixture.detectChanges();

View File

@@ -10,10 +10,10 @@ import {
} from '@angular/core';
import { CommonModule } from '@angular/common';
import {
CatalogItemVariant,
InventoryPolicy,
ProductAttribute,
ProductAttributeOption,
ProductVariant,
ProductVariantMap,
} from '../../../../core/services/catalog/catalog.interface';
@Component({
@@ -26,16 +26,17 @@ import {
})
export class ProductAttributeSelectorComponent {
public attributes = input<ProductAttribute[]>([]);
public variantsMap = input<ProductVariantMap[]>([]);
public defaultVariant = input<ProductVariant | null>(null);
public variants = input<CatalogItemVariant[]>([]);
public selectedVariant = input<CatalogItemVariant | null>(null);
public inventoryPolicy = input.required<InventoryPolicy>();
public variantChange = output<ProductVariantMap | null>();
public variantChange = output<CatalogItemVariant | null>();
protected readonly selectedAttributeOptions = signal<Record<number, number>>({});
protected readonly availableOptions = computed(() => {
const selections = this.selectedAttributeOptions();
const variants = this.variantsMap();
const variants = this.variants();
const attributes = this.attributes();
const availability: Record<number, Record<number, boolean>> = {};
@@ -48,10 +49,7 @@ export class ProductAttributeSelectorComponent {
const isAvailable = variants.some((variant) => {
if (!this.isVariantAvailable(variant)) return false;
const variantAttrValue = this.getDefaultVariantAttributeValue(
attribute,
variant.attributes,
);
const variantAttrValue = this.getDefaultVariantAttributeValue(attribute, variant.values);
if (!variantAttrValue || this.normalizeText(variantAttrValue) !== optionNormalized) {
return false;
}
@@ -65,10 +63,7 @@ export class ProductAttributeSelectorComponent {
const selectedNormalized = this.normalizeText(
selectedOption.value || selectedOption.label,
);
const vAttrValue = this.getDefaultVariantAttributeValue(
otherAttr,
variant.attributes,
);
const vAttrValue = this.getDefaultVariantAttributeValue(otherAttr, variant.values);
if (!vAttrValue || this.normalizeText(vAttrValue) !== selectedNormalized) {
return false;
}
@@ -86,13 +81,13 @@ export class ProductAttributeSelectorComponent {
});
public reset(): void {
this.initializeSelections(this.attributes(), this.defaultVariant());
this.initializeSelections(this.attributes(), this.selectedVariant());
}
constructor() {
effect(() => {
const attributes = this.attributes();
const defaultVariant = this.defaultVariant();
const defaultVariant = this.selectedVariant();
untracked(() => {
this.initializeSelections(attributes, defaultVariant);
@@ -101,11 +96,11 @@ export class ProductAttributeSelectorComponent {
effect(() => {
const selections = this.selectedAttributeOptions();
const variantsMap = this.variantsMap();
const variants = this.variants();
const attributes = this.attributes();
untracked(() => {
this.emitMatchingVariant(selections, variantsMap, attributes);
this.emitMatchingVariant(selections, variants, attributes);
});
});
}
@@ -137,7 +132,7 @@ export class ProductAttributeSelectorComponent {
private initializeSelections(
attributes: ProductAttribute[],
defaultVariant: ProductVariant | null,
defaultVariant: CatalogItemVariant | null,
): void {
const selections: Record<number, number> = {};
@@ -153,13 +148,13 @@ export class ProductAttributeSelectorComponent {
private findDefaultOptionId(
attribute: ProductAttribute,
variant: ProductVariant | null,
variant: CatalogItemVariant | null,
): number | null {
if (!variant) {
return null;
}
const defaultValue = this.getDefaultVariantAttributeValue(attribute, variant.definitions);
const defaultValue = this.getDefaultVariantAttributeValue(attribute, variant.values);
if (!defaultValue) {
return null;
}
@@ -206,8 +201,8 @@ export class ProductAttributeSelectorComponent {
.toLowerCase();
}
private isVariantAvailable(variant: ProductVariantMap): boolean {
return variant.inventory_policy === 'unlimited' || (variant.cantidad_maxima ?? 0) > 0;
private isVariantAvailable(variant: CatalogItemVariant): boolean {
return this.inventoryPolicy() === 'unlimited' || (variant.stock_tecnico ?? 0) > 0;
}
private findFirstHexValue(value: unknown): string | null {
@@ -240,10 +235,10 @@ export class ProductAttributeSelectorComponent {
private emitMatchingVariant(
selections: Record<number, number>,
variantsMap: ProductVariantMap[],
variants: CatalogItemVariant[],
attributes: ProductAttribute[],
): void {
if (attributes.length === 0 || variantsMap.length === 0) {
if (attributes.length === 0 || variants.length === 0) {
this.variantChange.emit(null);
return;
}
@@ -267,12 +262,12 @@ export class ProductAttributeSelectorComponent {
return;
}
const matchingVariant = variantsMap.find((vMap) => {
const matchingVariant = variants.find((variant) => {
return attributes.every((attr) => {
const selectedValue = selectedValuesById[attr.id];
const vMapValue = this.getDefaultVariantAttributeValue(attr, vMap.attributes);
if (!vMapValue) return false;
return this.normalizeText(vMapValue) === selectedValue;
const variantValue = this.getDefaultVariantAttributeValue(attr, variant.values);
if (!variantValue) return false;
return this.normalizeText(variantValue) === selectedValue;
});
});