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

@@ -3,14 +3,12 @@ export interface CartItemProduct {
imagen: string | null;
}
export type BuyableType = 'variant' | 'bundle';
export interface CartItem {
id: number;
cantidad: number;
precio_unitario: string;
buyable_type: BuyableType;
buyable_id: number;
catalog_item_id: number;
variant_id: number | null;
product: CartItemProduct | null;
}

View File

@@ -21,8 +21,8 @@ describe('CartService', () => {
id: 1,
cantidad: 2,
precio_unitario: '10.00',
buyable_type: 'variant',
buyable_id: 10,
catalog_item_id: 5,
variant_id: 10,
product: {
nombre: 'Test Product (Size: M)',
imagen: null,
@@ -88,7 +88,7 @@ describe('CartService', () => {
});
it('should add item and update signal', () => {
service.addItem('variant', 10, 2).subscribe((res) => {
service.addItem(5, 10, 2).subscribe((res) => {
expect(res.data).toEqual(mockCart);
expect(service.cart()).toEqual(mockCart);
});
@@ -96,8 +96,8 @@ describe('CartService', () => {
const req = httpMock.expectOne('http://api.test/tenants/acme/cart/items');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual({
buyable_type: 'variant',
buyable_id: 10,
catalog_item_id: 5,
variant_id: 10,
cantidad: 2,
});
expect(req.request.withCredentials).toBe(true);

View File

@@ -4,7 +4,7 @@ import { catchError, map, Observable, tap } from 'rxjs';
import { ApiResponse } from '../api-response.interface';
import { TenantService } from '../tenant.service';
import { BuyableType, Cart } from './cart.interface';
import { Cart } from './cart.interface';
@Injectable({
providedIn: 'root',
@@ -45,15 +45,15 @@ export class CartService {
}
addItem(
buyableType: BuyableType,
buyableId: number,
catalogItemId: number,
variantId: number | null,
cantidad: number,
): Observable<ApiResponse<Cart>> {
this.isUpdatingState.set(true);
return this.http
.post<
ApiResponse<Cart>
>(`${this.tenantApiUrl}/cart/items`, { buyable_type: buyableType, buyable_id: buyableId, cantidad }, { withCredentials: true })
>(`${this.tenantApiUrl}/cart/items`, { catalog_item_id: catalogItemId, variant_id: variantId, cantidad }, { withCredentials: true })
.pipe(
tap((response) => {
this.cartState.set(response.data);

View File

@@ -33,27 +33,35 @@ export interface ProductAttribute {
export type InventoryPolicy = 'tracked' | 'unlimited';
export interface ProductVariant {
export interface CatalogItemVariant {
id: number;
inventory_policy: InventoryPolicy;
cantidad_maxima: number | null;
cantidad_vendida: number;
definitions: Record<string, string>;
stock_tecnico: number | null;
values: Record<string, string>;
}
export interface SelectedCatalogItemVariant extends CatalogItemVariant {
images: string[];
}
export interface ProductVariantMap {
variant_id: number;
export interface CatalogItemDetail {
id: number;
category_id: number | null;
brand_id: number | null;
slug: string;
nombre: string;
descripcion: string | null;
precio: string;
category: string | null;
brand: string | null;
inventory_policy: InventoryPolicy;
cantidad_maxima: number | null;
cantidad_vendida: number;
attributes: Record<string, string>;
}
export interface ProductDetail extends Product {
has_tickets: boolean;
minimum_use_date: string | null;
maximum_use_date: string | null;
attributes: ProductAttribute[];
variants_map: ProductVariantMap[];
variant: ProductVariant | null;
variants: CatalogItemVariant[];
selected_variant?: SelectedCatalogItemVariant;
stock_tecnico?: number | null;
images?: string[];
}
export type CatalogProductLayout = 'row' | 'column_with_image' | 'column_with_cart';

View File

@@ -9,8 +9,8 @@ import { TenantService } from '../tenant.service';
import {
CatalogFeaturedGroup,
CatalogFeaturedItem,
CatalogItemDetail,
Product,
ProductDetail,
} from './catalog.interface';
type HttpParamValue = string | number | boolean | readonly (string | number | boolean)[];
@@ -46,14 +46,14 @@ export class CatalogService {
);
}
getProducto(id: number, variantId?: number): Observable<ProductDetail> {
getCatalogItem(id: number, variantId?: number): Observable<CatalogItemDetail> {
let params = new HttpParams();
if (variantId) {
params = params.set('variant_id', variantId);
}
return this.http
.get<ApiResponse<ProductDetail>>(`${this.tenantApiUrl}/productos/${id}`, { params })
.get<ApiResponse<CatalogItemDetail>>(`${this.tenantApiUrl}/catalog-items/${id}`, { params })
.pipe(map((response) => response.data));
}