diff --git a/src/app/core/services/catalog/catalog.interface.ts b/src/app/core/services/catalog/catalog.interface.ts index 18977a2..8676b93 100644 --- a/src/app/core/services/catalog/catalog.interface.ts +++ b/src/app/core/services/catalog/catalog.interface.ts @@ -36,6 +36,10 @@ export type InventoryPolicy = 'tracked' | 'unlimited'; export interface CatalogItemVariant { id: number; stock_tecnico: number | null; + minimum_use_date?: string | null; + maximum_use_date?: string | null; + effective_minimum_use_date?: string | null; + effective_maximum_use_date?: string | null; values: Record; } diff --git a/src/app/features/store/pages/search-page/search-page.component.ts b/src/app/features/store/pages/search-page/search-page.component.ts index 1af42af..17c83c0 100644 --- a/src/app/features/store/pages/search-page/search-page.component.ts +++ b/src/app/features/store/pages/search-page/search-page.component.ts @@ -12,6 +12,8 @@ import { catchError, distinctUntilChanged, map, of, switchMap, tap } from 'rxjs' import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CartService } from '../../../../core/services/cart/cart.service'; +import { AuthService } from '../../../../core/services/auth/auth.service'; +import { CheckoutService } from '../../../../core/services/checkout.service'; import { CatalogFeaturedItem, CatalogFeaturedItems, @@ -24,6 +26,7 @@ import { TenantService } from '../../../../core/services/tenant.service'; import { ToastService } from '../../../../core/services/toast.service'; import { ProductListCartEvent, + ProductListBuyEvent, ProductListComponent, ProductListItem, } from '../../../../shared/components/product-list/product-list.component'; @@ -43,7 +46,9 @@ interface SearchRouteState { export class SearchPageComponent { private readonly minSearchLength = 3; private readonly cartService = inject(CartService); + private readonly authService = inject(AuthService); private readonly catalogService = inject(CatalogService); + private readonly checkoutService = inject(CheckoutService); private readonly destroyRef = inject(DestroyRef); private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); @@ -54,6 +59,7 @@ export class SearchPageComponent { protected readonly results = signal | null>(null); protected readonly loading = signal(false); protected readonly error = signal(null); + protected readonly creatingDirectPurchase = signal(false); protected readonly productLayout = computed( () => this.tenantService.tenant()?.search_product_layout ?? 'column_with_image', @@ -131,8 +137,45 @@ export class SearchPageComponent { }); } - protected onBuyProduct(product: ProductListItem): void { - void this.router.navigate(['/producto', product.id]); + protected async onBuyProduct(event: ProductListBuyEvent): Promise { + if (!event.directPurchase) { + await this.router.navigate(['/producto', event.product.id]); + return; + } + + if (this.creatingDirectPurchase()) { + return; + } + + if (!this.authService.user()) { + await this.router.navigate(['/login'], { + queryParams: { returnUrl: `/producto/${event.product.id}` }, + }); + return; + } + + const tenant = this.tenantService.tenant(); + if (!tenant) { + this.toastService.danger('No se pudo identificar la tienda.'); + return; + } + + this.creatingDirectPurchase.set(true); + try { + const purchase = await this.checkoutService.startCheckout(tenant.codigo, { + direct_item: { + catalog_item_id: event.product.id, + variant_id: event.variant ?? null, + cantidad: event.quantity, + }, + }); + await this.router.navigate(['/checkout'], { queryParams: { purchase: purchase.id } }); + } catch (error) { + console.error('Failed to create direct purchase:', error); + this.toastService.danger('No se pudo iniciar la compra directa.'); + } finally { + this.creatingDirectPurchase.set(false); + } } protected onAddToCart(event: ProductListCartEvent): void { diff --git a/src/app/features/store/pages/store-home-page/store-home-page.component.ts b/src/app/features/store/pages/store-home-page/store-home-page.component.ts index 02cc320..d981436 100644 --- a/src/app/features/store/pages/store-home-page/store-home-page.component.ts +++ b/src/app/features/store/pages/store-home-page/store-home-page.component.ts @@ -12,13 +12,16 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { CartService } from '../../../../core/services/cart/cart.service'; +import { AuthService } from '../../../../core/services/auth/auth.service'; import { CatalogFeaturedGroup } from '../../../../core/services/catalog/catalog.interface'; import { CatalogService } from '../../../../core/services/catalog/catalog.service'; import { TenantService } from '../../../../core/services/tenant.service'; import { ToastService } from '../../../../core/services/toast.service'; +import { CheckoutService } from '../../../../core/services/checkout.service'; import { ProductListComponent, ProductListCartEvent, + ProductListBuyEvent, ProductListItem, } from '../../../../shared/components/product-list/product-list.component'; import { HeroBannerComponent } from '../../../../shared/components/hero-banner/hero-banner.component'; @@ -43,7 +46,9 @@ import { }) export class StoreHomePageComponent implements OnInit, OnDestroy { private readonly cartService = inject(CartService); + private readonly authService = inject(AuthService); private readonly catalogService = inject(CatalogService); + private readonly checkoutService = inject(CheckoutService); private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly tenantService = inject(TenantService); @@ -55,6 +60,7 @@ export class StoreHomePageComponent implements OnInit, OnDestroy { protected readonly loadingGroupIds = signal>(new Set()); protected readonly error = signal(null); protected readonly mainCarouselReady = signal(false); + protected readonly creatingDirectPurchase = signal(false); protected readonly hasMainCarouselImages = computed( () => (this.tenant()?.main_carousel_images?.length ?? 0) > 0, ); @@ -118,8 +124,45 @@ export class StoreHomePageComponent implements OnInit, OnDestroy { this.groupRequestSubscriptions.set(groupId, subscription); } - protected onBuyProduct(product: ProductListItem): void { - this.router.navigate(['/producto', product.id]); + protected async onBuyProduct(event: ProductListBuyEvent): Promise { + if (!event.directPurchase) { + await this.router.navigate(['/producto', event.product.id]); + return; + } + + if (this.creatingDirectPurchase()) { + return; + } + + if (!this.authService.user()) { + await this.router.navigate(['/login'], { + queryParams: { returnUrl: `/producto/${event.product.id}` }, + }); + return; + } + + const tenant = this.tenantService.tenant(); + if (!tenant) { + this.toastService.danger('No se pudo identificar la tienda.'); + return; + } + + this.creatingDirectPurchase.set(true); + try { + const purchase = await this.checkoutService.startCheckout(tenant.codigo, { + direct_item: { + catalog_item_id: event.product.id, + variant_id: event.variant ?? null, + cantidad: event.quantity, + }, + }); + await this.router.navigate(['/checkout'], { queryParams: { purchase: purchase.id } }); + } catch (error) { + console.error('Failed to create direct purchase:', error); + this.toastService.danger('No se pudo iniciar la compra directa.'); + } finally { + this.creatingDirectPurchase.set(false); + } } protected onAddToCart(event: ProductListCartEvent): void { diff --git a/src/app/shared/components/product-list/product-list.component.html b/src/app/shared/components/product-list/product-list.component.html index 1c09a32..b92b18f 100644 --- a/src/app/shared/components/product-list/product-list.component.html +++ b/src/app/shared/components/product-list/product-list.component.html @@ -13,7 +13,7 @@ [description]="item.descripcion ?? ''" [price]="price(item)" [variants]="variantsFor(item)" - (buy)="buy.emit(item)" + (buy)="emitRowBuy(item, $event)" (addToCart)="emitRowCart(item, $event)" /> } @@ -22,7 +22,7 @@ [title]="item.nombre" [description]="item.descripcion ?? ''" [price]="price(item)" - (buy)="buy.emit(item)" + (buy)="emitColumnBuy(item, $event)" (addToCart)="emitColumnCart(item, $event)" /> } @@ -32,7 +32,7 @@ [title]="item.nombre" [originalPrice]="price(item)" [imagePriority]="loadImages() && index < 4" - (buy)="buy.emit(item)" + (buy)="emitProductDetailBuy(item)" /> } } diff --git a/src/app/shared/components/product-list/product-list.component.ts b/src/app/shared/components/product-list/product-list.component.ts index 1a79478..34382a4 100644 --- a/src/app/shared/components/product-list/product-list.component.ts +++ b/src/app/shared/components/product-list/product-list.component.ts @@ -37,6 +37,13 @@ export interface ProductListCartEvent { variant?: number | null; } +export interface ProductListBuyEvent { + product: ProductListItem; + quantity: number; + variant?: number | null; + directPurchase: boolean; +} + @Component({ selector: 'app-product-list', imports: [ @@ -61,7 +68,7 @@ export class ProductListComponent { readonly loading = input(false); readonly loadImages = input(true); - readonly buy = output(); + readonly buy = output(); readonly addToCart = output(); readonly pageChange = output(); @@ -119,4 +126,24 @@ export class ProductListComponent { protected emitColumnCart(product: ProductListItem, event: { quantity: number }): void { this.addToCart.emit({ product, quantity: event.quantity }); } + + protected emitRowBuy( + product: ProductListItem, + event: { quantity: number; variant: unknown }, + ): void { + this.buy.emit({ + product, + quantity: event.quantity, + variant: typeof event.variant === 'number' ? event.variant : null, + directPurchase: true, + }); + } + + protected emitColumnBuy(product: ProductListItem, event: { quantity: number }): void { + this.buy.emit({ product, quantity: event.quantity, variant: null, directPurchase: true }); + } + + protected emitProductDetailBuy(product: ProductListItem): void { + this.buy.emit({ product, quantity: 1, variant: null, directPurchase: false }); + } } diff --git a/src/app/shared/components/product-row-card/product-row-card.component.html b/src/app/shared/components/product-row-card/product-row-card.component.html index 4604ae6..3010393 100644 --- a/src/app/shared/components/product-row-card/product-row-card.component.html +++ b/src/app/shared/components/product-row-card/product-row-card.component.html @@ -19,7 +19,7 @@ {{ formattedPrice() }}
- + Comprar
diff --git a/src/app/shared/components/product-row-card/product-row-card.component.ts b/src/app/shared/components/product-row-card/product-row-card.component.ts index 456a414..0b7e31a 100644 --- a/src/app/shared/components/product-row-card/product-row-card.component.ts +++ b/src/app/shared/components/product-row-card/product-row-card.component.ts @@ -28,7 +28,7 @@ export class ProductRowCardComponent { readonly selectedVariant = model(null); // Interactive events - readonly buy = output(); + readonly buy = output<{ quantity: number; variant: any }>(); readonly addToCart = output<{ quantity: number; variant: any }>(); constructor() { @@ -57,6 +57,13 @@ export class ProductRowCardComponent { }); } + protected onBuy(): void { + this.buy.emit({ + quantity: this.quantity(), + variant: this.selectedVariant(), + }); + } + /** * Helper method to format currency numbers to Argentine Pesos style "$ XX.XXX". */ diff --git a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.html b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.html index caa9268..d6000cb 100644 --- a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.html +++ b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.html @@ -14,7 +14,7 @@
- Comprar + Comprar Agregar al carrito
diff --git a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.ts b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.ts index 277b8c5..7ef0c11 100644 --- a/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.ts +++ b/src/app/shared/components/product-vertical-with-cart-card/product-vertical-with-cart-card.component.ts @@ -17,7 +17,7 @@ export class ProductVerticalWithCartCardComponent { readonly quantity = model(1); - readonly buy = output(); + readonly buy = output<{ quantity: number }>(); readonly addToCart = output<{ quantity: number }>(); protected readonly formattedPrice = computed(() => this.formatCurrency(this.price())); @@ -26,6 +26,10 @@ export class ProductVerticalWithCartCardComponent { this.addToCart.emit({ quantity: this.quantity() }); } + protected onBuy(): void { + this.buy.emit({ quantity: this.quantity() }); + } + private formatCurrency(value: number): string { const rounded = Math.round(value); const parts = rounded.toString().split('.');