feat(catalog): consume global availability decisions
This commit is contained in:
@@ -1,45 +1,80 @@
|
||||
import { CatalogAvailability } from './catalog.interface';
|
||||
import { CatalogAction, CatalogAvailability } from './catalog.interface';
|
||||
|
||||
const ALL_CATALOG_ACTIONS: CatalogAction[] = [
|
||||
'select_variant',
|
||||
'change_quantity',
|
||||
'add_to_cart',
|
||||
'buy_now',
|
||||
];
|
||||
|
||||
export const AVAILABLE_CATALOG_AVAILABILITY: CatalogAvailability = {
|
||||
subject: 'product',
|
||||
state: 'visible',
|
||||
maximum_quantity: null,
|
||||
restrictions: [],
|
||||
capabilities: {
|
||||
display: true,
|
||||
select_variant: true,
|
||||
change_quantity: true,
|
||||
add_to_cart: true,
|
||||
buy_now: true,
|
||||
},
|
||||
allowed_actions: ALL_CATALOG_ACTIONS,
|
||||
reasons: [],
|
||||
};
|
||||
|
||||
export function createCatalogAvailability(
|
||||
maximumQuantity: number | null,
|
||||
subject: CatalogAvailability['subject'] = 'product',
|
||||
): CatalogAvailability {
|
||||
export function createCatalogAvailability(maximumQuantity: number | null): CatalogAvailability {
|
||||
const unavailable = maximumQuantity === 0;
|
||||
|
||||
if (unavailable) {
|
||||
return {
|
||||
state: 'hidden',
|
||||
reasons: [
|
||||
{
|
||||
code: 'out_of_stock',
|
||||
message: 'Este producto no tiene stock disponible.',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
subject,
|
||||
state: 'visible',
|
||||
maximum_quantity: maximumQuantity,
|
||||
restrictions: unavailable
|
||||
? [
|
||||
{
|
||||
code: 'out_of_stock',
|
||||
message: 'Este producto no tiene stock disponible.',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
capabilities: {
|
||||
display: !unavailable,
|
||||
select_variant: !unavailable,
|
||||
change_quantity: !unavailable,
|
||||
add_to_cart: !unavailable,
|
||||
buy_now: !unavailable,
|
||||
},
|
||||
allowed_actions: [...ALL_CATALOG_ACTIONS],
|
||||
reasons: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function primaryAvailabilityMessage(availability: CatalogAvailability): string | null {
|
||||
return availability.restrictions[0]?.message ?? null;
|
||||
return availability.reasons[0]?.message ?? null;
|
||||
}
|
||||
|
||||
export function allowsCatalogAction(
|
||||
availability: CatalogAvailability,
|
||||
action: CatalogAction,
|
||||
): boolean {
|
||||
return availability.state === 'visible' && availability.allowed_actions.includes(action);
|
||||
}
|
||||
|
||||
export function maximumCatalogQuantity(availability: CatalogAvailability): number | null {
|
||||
return availability.state === 'visible' ? availability.maximum_quantity : 0;
|
||||
}
|
||||
|
||||
export function combineCatalogAvailability(
|
||||
product: CatalogAvailability,
|
||||
variant?: CatalogAvailability | null,
|
||||
): CatalogAvailability {
|
||||
if (!variant) return product;
|
||||
|
||||
const reasons = [...product.reasons, ...variant.reasons];
|
||||
if (product.state === 'hidden' || variant.state === 'hidden') {
|
||||
return { state: 'hidden', reasons };
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'visible',
|
||||
maximum_quantity: minimumNullable(product.maximum_quantity, variant.maximum_quantity),
|
||||
allowed_actions: product.allowed_actions.filter((action) =>
|
||||
variant.allowed_actions.includes(action),
|
||||
),
|
||||
reasons,
|
||||
};
|
||||
}
|
||||
|
||||
function minimumNullable(left: number | null, right: number | null): number | null {
|
||||
if (left === null) return right;
|
||||
if (right === null) return left;
|
||||
return Math.min(left, right);
|
||||
}
|
||||
|
||||
@@ -54,20 +54,19 @@ export interface CatalogRestriction {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface CatalogCapabilities {
|
||||
display: boolean;
|
||||
select_variant: boolean;
|
||||
change_quantity: boolean;
|
||||
add_to_cart: boolean;
|
||||
buy_now: boolean;
|
||||
}
|
||||
export type CatalogAction = 'select_variant' | 'change_quantity' | 'add_to_cart' | 'buy_now';
|
||||
|
||||
export interface CatalogAvailability {
|
||||
subject: 'product' | 'variant';
|
||||
maximum_quantity: number | null;
|
||||
restrictions: CatalogRestriction[];
|
||||
capabilities: CatalogCapabilities;
|
||||
}
|
||||
export type CatalogAvailability =
|
||||
| {
|
||||
state: 'hidden';
|
||||
reasons: CatalogRestriction[];
|
||||
}
|
||||
| {
|
||||
state: 'visible';
|
||||
maximum_quantity: number | null;
|
||||
allowed_actions: CatalogAction[];
|
||||
reasons: CatalogRestriction[];
|
||||
};
|
||||
|
||||
export interface CatalogVariantOption {
|
||||
value: string;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ProductAttribute,
|
||||
ProductAttributeOption,
|
||||
} from '../../../../core/services/catalog/catalog.interface';
|
||||
import { allowsCatalogAction } from '../../../../core/services/catalog/catalog-availability';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-attribute-selector',
|
||||
@@ -242,7 +243,7 @@ export class ProductAttributeSelectorComponent {
|
||||
}
|
||||
|
||||
private isVariantAvailable(variant: CatalogItemVariant): boolean {
|
||||
return variant.availability.capabilities.select_variant;
|
||||
return allowsCatalogAction(variant.availability, 'select_variant');
|
||||
}
|
||||
|
||||
private findFirstHexValue(value: unknown): string | null {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
[variants]="prod.variants"
|
||||
[selectedVariant]="prod.selected_variant ?? null"
|
||||
[inventoryPolicy]="prod.inventory_policy"
|
||||
[disabled]="!prod.availability.capabilities.select_variant"
|
||||
[disabled]="!allows(prod.availability, 'select_variant')"
|
||||
(variantChange)="onVariantChange($event)"
|
||||
/>
|
||||
</section>
|
||||
@@ -61,7 +61,7 @@
|
||||
<app-quantity-selector
|
||||
[(quantity)]="quantity"
|
||||
[max]="selectedVariantMax()"
|
||||
[disabled]="!effectiveAvailability().capabilities.change_quantity"
|
||||
[disabled]="!allows(effectiveAvailability(), 'change_quantity')"
|
||||
/>
|
||||
|
||||
<div class="product-detail__actions">
|
||||
|
||||
@@ -34,6 +34,9 @@ import { TenantService } from '../../../../core/services/tenant.service';
|
||||
import { AuthService } from '../../../../core/services/auth/auth.service';
|
||||
import {
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
allowsCatalogAction,
|
||||
combineCatalogAvailability,
|
||||
maximumCatalogQuantity,
|
||||
primaryAvailabilityMessage,
|
||||
} from '../../../../core/services/catalog/catalog-availability';
|
||||
|
||||
@@ -105,7 +108,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
const variant = this.selectedVariant();
|
||||
if (!prod) return AVAILABLE_CATALOG_AVAILABILITY;
|
||||
|
||||
return variant?.availability ?? prod.availability;
|
||||
return combineCatalogAvailability(prod.availability, variant?.availability);
|
||||
});
|
||||
protected readonly selectedVariantMax = computed<number | null>(() => {
|
||||
const prod = this.product();
|
||||
@@ -113,7 +116,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
if (!prod) return 0;
|
||||
if (!variant && prod.variants.length > 0) return 0;
|
||||
|
||||
return this.effectiveAvailability().maximum_quantity;
|
||||
return maximumCatalogQuantity(this.effectiveAvailability());
|
||||
});
|
||||
protected readonly hasPurchasableSelection = computed(() => {
|
||||
const prod = this.product();
|
||||
@@ -122,14 +125,19 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
return prod.variants.length === 0 || this.selectedVariant() !== null;
|
||||
});
|
||||
protected readonly canAddToCart = computed(
|
||||
() => this.hasPurchasableSelection() && this.effectiveAvailability().capabilities.add_to_cart,
|
||||
() =>
|
||||
this.hasPurchasableSelection() &&
|
||||
allowsCatalogAction(this.effectiveAvailability(), 'add_to_cart'),
|
||||
);
|
||||
protected readonly canBuyNow = computed(
|
||||
() => this.hasPurchasableSelection() && this.effectiveAvailability().capabilities.buy_now,
|
||||
() =>
|
||||
this.hasPurchasableSelection() &&
|
||||
allowsCatalogAction(this.effectiveAvailability(), 'buy_now'),
|
||||
);
|
||||
protected readonly restrictionMessage = computed(() =>
|
||||
primaryAvailabilityMessage(this.effectiveAvailability()),
|
||||
);
|
||||
protected readonly allows = allowsCatalogAction;
|
||||
protected readonly descriptionExpanded = signal(false);
|
||||
protected readonly descriptionMaxHeight = signal(0);
|
||||
protected readonly descriptionHasOverflow = signal(false);
|
||||
@@ -235,8 +243,12 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
this.quantity.set(Math.max(1, maximum));
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// Keep the last known availability if the silent refresh fails.
|
||||
error: (error: HttpErrorResponse) => {
|
||||
if (error.status === 404) {
|
||||
this.product.set(null);
|
||||
this.selectedVariant.set(null);
|
||||
this.error.set('Este producto ya no está disponible.');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -308,8 +320,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
const variant = this.selectedVariant();
|
||||
if (!currentProduct || !this.canAddToCart()) {
|
||||
this.toastService.danger(
|
||||
this.effectiveAvailability().restrictions[0]?.message ??
|
||||
'Por favor, selecciona una variante.',
|
||||
this.effectiveAvailability().reasons[0]?.message ?? 'Por favor, selecciona una variante.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -342,8 +353,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (!currentProduct || !this.canBuyNow()) {
|
||||
this.toastService.danger(
|
||||
this.effectiveAvailability().restrictions[0]?.message ??
|
||||
'Por favor, selecciona una variante.',
|
||||
this.effectiveAvailability().reasons[0]?.message ?? 'Por favor, selecciona una variante.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
[price]="price(item)"
|
||||
[imageUrl]="loadImages() ? (item.image ?? null) : null"
|
||||
[unavailableMessage]="availabilityMessage(item)"
|
||||
[disabled]="loading() || !itemAvailability(item).capabilities.buy_now"
|
||||
[disabled]="loading() || !allows(itemAvailability(item), 'buy_now')"
|
||||
(buy)="emitTicketBuy(item, $event)"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from '../../../core/services/catalog/catalog.interface';
|
||||
import {
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
allowsCatalogAction,
|
||||
primaryAvailabilityMessage,
|
||||
} from '../../../core/services/catalog/catalog-availability';
|
||||
import { CarouselComponent } from '../carousel/carousel.component';
|
||||
@@ -78,6 +79,7 @@ export class ProductListComponent {
|
||||
readonly buy = output<ProductListBuyEvent>();
|
||||
readonly addToCart = output<ProductListCartEvent>();
|
||||
readonly pageChange = output<number>();
|
||||
protected readonly allows = allowsCatalogAction;
|
||||
|
||||
protected readonly effectiveLayout = computed<ProductListLayout>(() =>
|
||||
this.mobile() && this.layout() === 'row' ? 'column_with_cart' : this.layout(),
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
<app-variant-selector
|
||||
class="product-row-card__selectors"
|
||||
[variants]="variants()"
|
||||
[disabled]="!availability().capabilities.select_variant"
|
||||
[disabled]="!allows(availability(), 'select_variant')"
|
||||
[(selectedVariant)]="selectedVariant"
|
||||
/>
|
||||
|
||||
<app-quantity-selector
|
||||
[(quantity)]="quantity"
|
||||
[max]="effectiveMaximum()"
|
||||
[disabled]="!effectiveAvailability().capabilities.change_quantity"
|
||||
[disabled]="!allows(effectiveAvailability(), 'change_quantity')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="product-row-card__btn-wrapper">
|
||||
<app-button
|
||||
variant="primary"
|
||||
[disabled]="!hasPurchasableSelection() || !effectiveAvailability().capabilities.buy_now"
|
||||
[disabled]="!hasPurchasableSelection() || !allows(effectiveAvailability(), 'buy_now')"
|
||||
(click)="onBuy()"
|
||||
>
|
||||
Comprar
|
||||
@@ -53,7 +53,7 @@
|
||||
[disabled]="
|
||||
saving() ||
|
||||
!hasPurchasableSelection() ||
|
||||
!effectiveAvailability().capabilities.add_to_cart
|
||||
!allows(effectiveAvailability(), 'add_to_cart')
|
||||
"
|
||||
(click)="onAddToCart()"
|
||||
>
|
||||
|
||||
@@ -17,6 +17,9 @@ import {
|
||||
import { CatalogAvailability } from '../../../core/services/catalog/catalog.interface';
|
||||
import {
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
allowsCatalogAction,
|
||||
combineCatalogAvailability,
|
||||
maximumCatalogQuantity,
|
||||
primaryAvailabilityMessage,
|
||||
} from '../../../core/services/catalog/catalog-availability';
|
||||
|
||||
@@ -63,24 +66,22 @@ export class ProductRowCardComponent {
|
||||
|
||||
return Number.isFinite(variantPrice) ? variantPrice : this.price();
|
||||
});
|
||||
protected readonly effectiveAvailability = computed(
|
||||
() =>
|
||||
this.selectedVariantData()?.availability ??
|
||||
this.availability() ??
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
protected readonly effectiveAvailability = computed(() =>
|
||||
combineCatalogAvailability(this.availability(), this.selectedVariantData()?.availability),
|
||||
);
|
||||
protected readonly hasVariants = computed(() => this.variants().length > 0);
|
||||
protected readonly hasPurchasableSelection = computed(
|
||||
() => !this.hasVariants() || this.selectedVariantData() !== undefined,
|
||||
);
|
||||
protected readonly effectiveMaximum = computed(
|
||||
() => this.effectiveAvailability().maximum_quantity,
|
||||
protected readonly effectiveMaximum = computed(() =>
|
||||
maximumCatalogQuantity(this.effectiveAvailability()),
|
||||
);
|
||||
protected readonly effectiveUnavailableMessage = computed(() =>
|
||||
primaryAvailabilityMessage(this.effectiveAvailability()),
|
||||
);
|
||||
|
||||
readonly formattedPrice = computed(() => this.formatCurrency(this.effectivePrice()));
|
||||
protected readonly allows = allowsCatalogAction;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
@@ -96,7 +97,7 @@ export class ProductRowCardComponent {
|
||||
if (
|
||||
this.saving() ||
|
||||
!this.hasPurchasableSelection() ||
|
||||
!this.effectiveAvailability().capabilities.add_to_cart
|
||||
!this.allows(this.effectiveAvailability(), 'add_to_cart')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +109,7 @@ export class ProductRowCardComponent {
|
||||
}
|
||||
|
||||
protected onBuy(): void {
|
||||
if (!this.hasPurchasableSelection() || !this.effectiveAvailability().capabilities.buy_now)
|
||||
if (!this.hasPurchasableSelection() || !this.allows(this.effectiveAvailability(), 'buy_now'))
|
||||
return;
|
||||
|
||||
this.buy.emit({
|
||||
|
||||
@@ -23,7 +23,10 @@ import {
|
||||
CatalogVariantSelector,
|
||||
CatalogVariantValue,
|
||||
} from '../../../core/services/catalog/catalog.interface';
|
||||
import { createCatalogAvailability } from '../../../core/services/catalog/catalog-availability';
|
||||
import {
|
||||
allowsCatalogAction,
|
||||
createCatalogAvailability,
|
||||
} from '../../../core/services/catalog/catalog-availability';
|
||||
import { CatalogService } from '../../../core/services/catalog/catalog.service';
|
||||
import { ModalService } from '../../../core/services/modal.service';
|
||||
import { ToastService } from '../../../core/services/toast.service';
|
||||
@@ -356,7 +359,8 @@ export class ProductTicketSelectorComponent {
|
||||
}
|
||||
return variants.filter(
|
||||
({ id, availability }) =>
|
||||
availability?.capabilities.select_variant !== false && !reservedByOtherRows.has(id),
|
||||
(availability === undefined || allowsCatalogAction(availability, 'select_variant')) &&
|
||||
!reservedByOtherRows.has(id),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -367,7 +371,9 @@ export class ProductTicketSelectorComponent {
|
||||
return {
|
||||
id: item.variant.id,
|
||||
precio: item.variant.precio,
|
||||
availability: createCatalogAvailability(item.variant.stock_tecnico),
|
||||
availability: createCatalogAvailability(
|
||||
item.variant.stock_tecnico === null ? null : item.variant.stock_tecnico + item.cantidad,
|
||||
),
|
||||
values: item.variant.values,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<app-quantity-selector
|
||||
[(quantity)]="quantity"
|
||||
[max]="effectiveMaximum()"
|
||||
[disabled]="!effectiveAvailability().capabilities.change_quantity"
|
||||
[disabled]="!allows(effectiveAvailability(), 'change_quantity')"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -29,14 +29,14 @@
|
||||
<app-quantity-selector
|
||||
[(quantity)]="quantity"
|
||||
[max]="effectiveMaximum()"
|
||||
[disabled]="!effectiveAvailability().capabilities.change_quantity"
|
||||
[disabled]="!allows(effectiveAvailability(), 'change_quantity')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="product-vertical-with-cart-card__variant-selectors">
|
||||
<app-variant-selector
|
||||
[variants]="variants()"
|
||||
[disabled]="!availability().capabilities.select_variant"
|
||||
[disabled]="!allows(availability(), 'select_variant')"
|
||||
[(selectedVariant)]="selectedVariant"
|
||||
/>
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@
|
||||
<app-button
|
||||
variant="primary"
|
||||
[disabled]="
|
||||
!effectiveAvailability().capabilities.buy_now ||
|
||||
!allows(effectiveAvailability(), 'buy_now') ||
|
||||
(hasVariants() && selectedVariant() === null)
|
||||
"
|
||||
(click)="onBuy()"
|
||||
@@ -58,7 +58,7 @@
|
||||
variant="secondary"
|
||||
[disabled]="
|
||||
saving() ||
|
||||
!effectiveAvailability().capabilities.add_to_cart ||
|
||||
!allows(effectiveAvailability(), 'add_to_cart') ||
|
||||
(hasVariants() && selectedVariant() === null)
|
||||
"
|
||||
(click)="onAddToCart()"
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
import { CatalogAvailability } from '../../../core/services/catalog/catalog.interface';
|
||||
import {
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
allowsCatalogAction,
|
||||
combineCatalogAvailability,
|
||||
maximumCatalogQuantity,
|
||||
primaryAvailabilityMessage,
|
||||
} from '../../../core/services/catalog/catalog-availability';
|
||||
|
||||
@@ -61,18 +64,16 @@ export class ProductVerticalWithCartCardComponent {
|
||||
});
|
||||
protected readonly formattedPrice = computed(() => this.formatCurrency(this.effectivePrice()));
|
||||
protected readonly hasVariants = computed(() => this.variants().length > 0);
|
||||
protected readonly effectiveAvailability = computed(
|
||||
() =>
|
||||
this.selectedVariantData()?.availability ??
|
||||
this.availability() ??
|
||||
AVAILABLE_CATALOG_AVAILABILITY,
|
||||
protected readonly effectiveAvailability = computed(() =>
|
||||
combineCatalogAvailability(this.availability(), this.selectedVariantData()?.availability),
|
||||
);
|
||||
protected readonly effectiveMaximum = computed(
|
||||
() => this.effectiveAvailability().maximum_quantity,
|
||||
protected readonly effectiveMaximum = computed(() =>
|
||||
maximumCatalogQuantity(this.effectiveAvailability()),
|
||||
);
|
||||
protected readonly effectiveUnavailableMessage = computed(() =>
|
||||
primaryAvailabilityMessage(this.effectiveAvailability()),
|
||||
);
|
||||
protected readonly allows = allowsCatalogAction;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
@@ -85,7 +86,7 @@ export class ProductVerticalWithCartCardComponent {
|
||||
}
|
||||
|
||||
protected onAddToCart(): void {
|
||||
if (this.saving() || !this.effectiveAvailability().capabilities.add_to_cart) {
|
||||
if (this.saving() || !this.allows(this.effectiveAvailability(), 'add_to_cart')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ export class ProductVerticalWithCartCardComponent {
|
||||
}
|
||||
|
||||
protected onBuy(): void {
|
||||
if (!this.effectiveAvailability().capabilities.buy_now) return;
|
||||
if (!this.allows(this.effectiveAvailability(), 'buy_now')) return;
|
||||
|
||||
this.buy.emit({ quantity: this.quantity(), variant: this.selectedVariant() });
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CatalogAvailability } from '../../../core/services/catalog/catalog.interface';
|
||||
import { allowsCatalogAction } from '../../../core/services/catalog/catalog-availability';
|
||||
|
||||
export interface VariantAttributeOption {
|
||||
value: string;
|
||||
@@ -22,12 +24,7 @@ export type VariantAttributeValue = VariantAttributeScalar | VariantAttributeSca
|
||||
export interface VariantSelectorVariant {
|
||||
id: unknown;
|
||||
values: Record<string, VariantAttributeValue>;
|
||||
availability?: {
|
||||
capabilities: {
|
||||
display: boolean;
|
||||
select_variant: boolean;
|
||||
};
|
||||
};
|
||||
availability?: CatalogAvailability;
|
||||
}
|
||||
|
||||
export interface VariantSelectorSelectionChange {
|
||||
@@ -208,7 +205,9 @@ export class VariantSelectorComponent {
|
||||
|
||||
private getSelectableVariants(): VariantSelectorVariant[] {
|
||||
return this.variants().filter(
|
||||
(variant) => variant.availability?.capabilities.select_variant !== false,
|
||||
(variant) =>
|
||||
variant.availability === undefined ||
|
||||
allowsCatalogAction(variant.availability, 'select_variant'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user