feat(catalog): refactor product and variant interfaces for improved structure and clarity
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user