CatalogItemType::Standard->value, 'inventory_policy' => InventoryPolicy::Tracked->value, 'has_tickets' => false, 'ticket_generation_policy' => TicketGenerationPolicy::PerEventDate->value, ]; protected function casts(): array { return [ 'category_id' => 'integer', 'brand_id' => 'integer', 'inventory_id' => 'integer', 'type' => CatalogItemType::class, 'precio' => 'decimal:2', 'inventory_policy' => InventoryPolicy::class, 'max_units_per_user' => 'integer', 'has_tickets' => 'boolean', 'ticket_generation_policy' => TicketGenerationPolicy::class, 'validity_time_id' => 'integer', ]; } /** @return BelongsTo */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo'); } /** @return BelongsTo */ public function category(): BelongsTo { return $this->belongsTo(Category::class); } /** @return BelongsTo */ public function brand(): BelongsTo { return $this->belongsTo(Brand::class); } /** @return BelongsTo */ public function inventory(): BelongsTo { return $this->belongsTo(Inventory::class); } /** @return HasMany */ public function bundleComponents(): HasMany { return $this->hasMany(BundleComponent::class, 'bundle_catalog_item_id'); } /** @return HasMany */ public function bundleComponentUsages(): HasMany { return $this->hasMany(BundleComponent::class, 'component_catalog_item_id'); } /** @return HasMany */ public function variants(): HasMany { return $this->hasMany(Variant::class); } /** @return HasMany */ public function sourceTickets(): HasMany { return $this->hasMany(Ticket::class, 'source_catalog_item_id'); } /** @return BelongsTo */ public function validityTime(): BelongsTo { return $this->belongsTo(ValidityTime::class); } /** @return BelongsToMany */ public function attributes(): BelongsToMany { return $this->belongsToMany(Attribute::class, 'item_attributes') ->withTimestamps(); } /** @return HasMany */ public function itemAttributes(): HasMany { return $this->hasMany(ItemAttribute::class); } /** @return HasMany */ public function featuredItems(): HasMany { return $this->hasMany(FeaturedItem::class); } /** @return BelongsToMany */ public function attachments(): BelongsToMany { return $this->belongsToMany( Attachment::class, 'catalog_items_attachments', 'catalog_item_id', 'attachment_id' ) ->withPivot('orden') ->wherePivotNull('variant_id') ->orderByPivot('orden'); } public function availableStock(): ?int { return app(CatalogInventoryService::class)->availableQuantity($this); } public function isAvailable(): bool { if ($this->type === CatalogItemType::Bundle) { $availableStock = $this->availableStock(); return $availableStock === null || $availableStock > 0; } if ($this->inventory_policy === InventoryPolicy::Unlimited) { return true; } return ($this->availableStock() ?? 0) > 0; } /** @param Builder $query */ public function scopeWhereVariantsAvailable(Builder $query): Builder { return $query->where(function (Builder $query): void { $query ->whereDoesntHave('variants') ->orWhere('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value) ->orWhereHas( 'variants.inventory', fn (Builder $inventoryQuery): Builder => $inventoryQuery ->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock') ); }); } /** @return Collection */ public function visibleVariants(?int $includedVariantId = null): Collection { return $this->variants ->filter(fn (Variant $variant): bool => ($includedVariantId !== null && $variant->id === $includedVariantId) || $this->inventory_policy === InventoryPolicy::Unlimited || ($variant->inventory?->availableStock() ?? 0) > 0) ->values(); } public function getPrice(): float { return (float) $this->precio; } public function getName(): string { return $this->nombre; } public function getDescription(): ?string { return $this->descripcion; } public function isBundle(): bool { return $this->type === CatalogItemType::Bundle; } }