refactor(backend): reorganize domains into Core, Commerce, Ticketing and Shared
This commit is contained in:
67
app/Domains/Commerce/Catalog/Models/Attribute.php
Normal file
67
app/Domains/Commerce/Catalog/Models/Attribute.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_codigo',
|
||||
'codigo',
|
||||
'nombre',
|
||||
'is_required',
|
||||
'metadata_schema',
|
||||
'type',
|
||||
])]
|
||||
class Attribute extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'attribute';
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_required' => 'boolean',
|
||||
'metadata_schema' => 'array',
|
||||
'type' => FieldType::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Tenant, $this>
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<AttributeOption, $this>
|
||||
*/
|
||||
public function options(): HasMany
|
||||
{
|
||||
return $this->hasMany(AttributeOption::class, 'attribute_id')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<EventDate, $this>
|
||||
*/
|
||||
public function eventDates(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventDate::class, 'tenant_code', 'tenant_codigo')
|
||||
->whereNull('rescheduled_to_event_date_id')
|
||||
->whereNull('suspended_at')
|
||||
->orderBy('date')
|
||||
->orderBy('time_start');
|
||||
}
|
||||
}
|
||||
50
app/Domains/Commerce/Catalog/Models/AttributeOption.php
Normal file
50
app/Domains/Commerce/Catalog/Models/AttributeOption.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Ticket\Models\ValidityTime;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'attribute_id',
|
||||
'validity_time_id',
|
||||
'value',
|
||||
'label',
|
||||
'sort_order',
|
||||
'metadata',
|
||||
])]
|
||||
class AttributeOption extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'attribute_options';
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'validity_time_id' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Attribute, $this>
|
||||
*/
|
||||
public function attribute(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Attribute::class, 'attribute_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<ValidityTime, $this> */
|
||||
public function validityTime(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ValidityTime::class);
|
||||
}
|
||||
}
|
||||
36
app/Domains/Commerce/Catalog/Models/Brand.php
Normal file
36
app/Domains/Commerce/Catalog/Models/Brand.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_codigo',
|
||||
'nombre',
|
||||
'descripcion',
|
||||
])]
|
||||
class Brand extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'brands';
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Tenant, $this>
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
/** @return HasMany<CatalogItem, $this> */
|
||||
public function catalogItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(CatalogItem::class);
|
||||
}
|
||||
}
|
||||
49
app/Domains/Commerce/Catalog/Models/BundleComponent.php
Normal file
49
app/Domains/Commerce/Catalog/Models/BundleComponent.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'bundle_catalog_item_id',
|
||||
'component_catalog_item_id',
|
||||
'component_variant_id',
|
||||
'quantity',
|
||||
])]
|
||||
class BundleComponent extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'bundle_catalog_item_id' => 'integer',
|
||||
'component_catalog_item_id' => 'integer',
|
||||
'component_variant_id' => 'integer',
|
||||
'quantity' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function bundle(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class, 'bundle_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function catalogItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class, 'component_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Variant, $this> */
|
||||
public function variant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Variant::class, 'component_variant_id');
|
||||
}
|
||||
}
|
||||
257
app/Domains/Commerce/Catalog/Models/CatalogItem.php
Normal file
257
app/Domains/Commerce/Catalog/Models/CatalogItem.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Catalog\Enums\CatalogItemType;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Enums\InventorySubject;
|
||||
use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_code',
|
||||
'category_id',
|
||||
'brand_id',
|
||||
'inventory_id',
|
||||
'type',
|
||||
'slug',
|
||||
'nombre',
|
||||
'group_order',
|
||||
'descripcion',
|
||||
'precio',
|
||||
'inventory_policy',
|
||||
'inventory_subject',
|
||||
'max_units_per_user',
|
||||
'has_tickets',
|
||||
])]
|
||||
class CatalogItem extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'catalog_items';
|
||||
|
||||
protected $attributes = [
|
||||
'type' => CatalogItemType::Standard->value,
|
||||
'inventory_policy' => InventoryPolicy::Tracked->value,
|
||||
'inventory_subject' => InventorySubject::Product->value,
|
||||
'has_tickets' => false,
|
||||
'group_order' => 0,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'integer',
|
||||
'brand_id' => 'integer',
|
||||
'inventory_id' => 'integer',
|
||||
'type' => CatalogItemType::class,
|
||||
'group_order' => 'integer',
|
||||
'precio' => 'decimal:2',
|
||||
'inventory_policy' => InventoryPolicy::class,
|
||||
'inventory_subject' => InventorySubject::class,
|
||||
'max_units_per_user' => 'integer',
|
||||
'has_tickets' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Tenant, $this> */
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Category, $this> */
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Brand, $this> */
|
||||
public function brand(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Brand::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Inventory, $this> */
|
||||
public function inventory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inventory::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<BundleComponent, $this> */
|
||||
public function bundleComponents(): HasMany
|
||||
{
|
||||
return $this->hasMany(BundleComponent::class, 'bundle_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<BundleComponent, $this> */
|
||||
public function bundleComponentUsages(): HasMany
|
||||
{
|
||||
return $this->hasMany(BundleComponent::class, 'component_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Variant, $this> */
|
||||
public function variants(): HasMany
|
||||
{
|
||||
return $this->hasMany(Variant::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<Ticket, $this> */
|
||||
public function sourceTickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'source_catalog_item_id');
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attribute, $this> */
|
||||
public function attributes(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Attribute::class, 'item_attributes')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/** @return HasMany<ItemAttribute, $this> */
|
||||
public function itemAttributes(): HasMany
|
||||
{
|
||||
return $this->hasMany(ItemAttribute::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<FeaturedItem, $this> */
|
||||
public function featuredItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(FeaturedItem::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attachment, $this> */
|
||||
public function attachments(): BelongsToMany
|
||||
{
|
||||
return $this->allAttachments()->wherePivot('is_enabled', true);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attachment, $this> */
|
||||
public function allAttachments(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
Attachment::class,
|
||||
'catalog_items_attachments',
|
||||
'catalog_item_id',
|
||||
'attachment_id'
|
||||
)
|
||||
->withPivot(['orden', 'is_enabled'])
|
||||
->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<CatalogItem> $query */
|
||||
public function scopeWhereAvailable(Builder $query): Builder
|
||||
{
|
||||
return $query->where(function (Builder $query): void {
|
||||
$query
|
||||
->where(function (Builder $unlimitedQuery): void {
|
||||
$unlimitedQuery
|
||||
->where('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value)
|
||||
->where(function (Builder $selectionQuery): void {
|
||||
$selectionQuery
|
||||
->whereDoesntHave('variants')
|
||||
->orWhereHas('variants', fn (Builder $variantQuery): Builder => $variantQuery
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id'));
|
||||
});
|
||||
})
|
||||
->orWhereHas(
|
||||
'variants',
|
||||
fn (Builder $variantQuery): Builder => $variantQuery
|
||||
->whereNull('sales_disabled_at')
|
||||
->whereNull('replaced_by_variant_id')
|
||||
->whereHas(
|
||||
'inventory',
|
||||
fn (Builder $inventoryQuery): Builder => $inventoryQuery
|
||||
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||
)
|
||||
)
|
||||
->orWhere(function (Builder $directItemQuery): void {
|
||||
$directItemQuery
|
||||
->whereDoesntHave('variants')
|
||||
->where(function (Builder $inventoryQuery): void {
|
||||
$inventoryQuery
|
||||
->whereNull('catalog_items.inventory_id')
|
||||
->orWhereHas(
|
||||
'inventory',
|
||||
fn (Builder $availableInventoryQuery): Builder => $availableInventoryQuery
|
||||
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @return Collection<int, Variant> */
|
||||
public function visibleVariants(?int $includedVariantId = null): Collection
|
||||
{
|
||||
return $this->variants
|
||||
->filter(fn (Variant $variant): bool => $variant->hasOnlyActiveEventDates()
|
||||
&& (($includedVariantId !== null && $variant->id === $includedVariantId)
|
||||
|| ($variant->isSellable() && (
|
||||
$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 getSelectionLabel(): string
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->descripcion;
|
||||
}
|
||||
|
||||
public function isBundle(): bool
|
||||
{
|
||||
return $this->type === CatalogItemType::Bundle;
|
||||
}
|
||||
}
|
||||
86
app/Domains/Commerce/Catalog/Models/Category.php
Normal file
86
app/Domains/Commerce/Catalog/Models/Category.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_code',
|
||||
'categoria_id',
|
||||
'nombre',
|
||||
'is_enabled',
|
||||
])]
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'categorias';
|
||||
|
||||
protected $attributes = [
|
||||
'is_enabled' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'categoria_id' => 'integer',
|
||||
'is_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function isGlobal(): bool
|
||||
{
|
||||
return $this->tenant_code === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Tenant, $this>
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Category, $this>
|
||||
*/
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'categoria_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Category, $this>
|
||||
*/
|
||||
public function subCategories(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'categoria_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<CatalogItem, $this> */
|
||||
public function catalogItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(CatalogItem::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<User, $this> */
|
||||
public function scanners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
User::class,
|
||||
'category_scanners',
|
||||
'categoria_id',
|
||||
'user_id',
|
||||
)->withTimestamps();
|
||||
}
|
||||
}
|
||||
89
app/Domains/Commerce/Catalog/Models/FeaturedGroup.php
Normal file
89
app/Domains/Commerce/Catalog/Models/FeaturedGroup.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Catalog\Enums\FeaturedGroupSource;
|
||||
use App\Domains\Catalog\Enums\GroupLayout;
|
||||
use App\Domains\Catalog\Enums\ProductLayout;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_code',
|
||||
'code',
|
||||
'source_type',
|
||||
'category_id',
|
||||
'product_layout',
|
||||
'group_layout',
|
||||
'group_name',
|
||||
'group_order',
|
||||
])]
|
||||
class FeaturedGroup extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'featured_groups';
|
||||
|
||||
protected $attributes = [
|
||||
'source_type' => FeaturedGroupSource::Manual->value,
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (FeaturedGroup $featuredGroup): void {
|
||||
if (filled($featuredGroup->code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$baseCode = Str::slug($featuredGroup->group_name) ?: 'group';
|
||||
$code = $baseCode;
|
||||
$suffix = 2;
|
||||
|
||||
while (static::query()
|
||||
->where('tenant_code', $featuredGroup->tenant_code)
|
||||
->where('code', $code)
|
||||
->exists()) {
|
||||
$code = "{$baseCode}-{$suffix}";
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
$featuredGroup->code = $code;
|
||||
});
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'source_type' => FeaturedGroupSource::class,
|
||||
'category_id' => 'integer',
|
||||
'product_layout' => ProductLayout::class,
|
||||
'group_layout' => GroupLayout::class,
|
||||
'group_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Tenant, $this> */
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Category, $this> */
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<FeaturedItem, $this> */
|
||||
public function featuredItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(FeaturedItem::class)->orderBy('order');
|
||||
}
|
||||
}
|
||||
43
app/Domains/Commerce/Catalog/Models/FeaturedItem.php
Normal file
43
app/Domains/Commerce/Catalog/Models/FeaturedItem.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'featured_group_id',
|
||||
'catalog_item_id',
|
||||
'order',
|
||||
])]
|
||||
class FeaturedItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'featured_items';
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'featured_group_id' => 'integer',
|
||||
'catalog_item_id' => 'integer',
|
||||
'order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<FeaturedGroup, $this> */
|
||||
public function featuredGroup(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FeaturedGroup::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function catalogItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class);
|
||||
}
|
||||
}
|
||||
107
app/Domains/Commerce/Catalog/Models/Inventory.php
Normal file
107
app/Domains/Commerce/Catalog/Models/Inventory.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
#[Fillable([
|
||||
'sold_units',
|
||||
'refunded_units',
|
||||
'reserved_stock',
|
||||
'real_stock',
|
||||
])]
|
||||
class Inventory extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'inventories';
|
||||
|
||||
protected $attributes = [
|
||||
'sold_units' => 0,
|
||||
'refunded_units' => 0,
|
||||
'reserved_stock' => 0,
|
||||
'real_stock' => 0,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'sold_units' => 'integer',
|
||||
'refunded_units' => 'integer',
|
||||
'reserved_stock' => 'integer',
|
||||
'real_stock' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return HasOne<CatalogItem, $this> */
|
||||
public function catalogItem(): HasOne
|
||||
{
|
||||
return $this->hasOne(CatalogItem::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<Variant, $this> */
|
||||
public function variants(): HasMany
|
||||
{
|
||||
return $this->hasMany(Variant::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<StockReservationLine, $this> */
|
||||
public function stockReservationLines(): HasMany
|
||||
{
|
||||
return $this->hasMany(StockReservationLine::class);
|
||||
}
|
||||
|
||||
public function availableStock(): int
|
||||
{
|
||||
return max(0, $this->real_stock - $this->reserved_stock);
|
||||
}
|
||||
|
||||
public function reserve(int $amount, bool $tracksInventory): void
|
||||
{
|
||||
if ($amount < 0) {
|
||||
throw new \InvalidArgumentException('La cantidad a reservar debe ser positiva.');
|
||||
}
|
||||
|
||||
if ($tracksInventory && $this->availableStock() < $amount) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock disponible para reservar.');
|
||||
}
|
||||
|
||||
$this->reserved_stock += $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function release(int $amount): void
|
||||
{
|
||||
if ($amount < 0 || $this->reserved_stock < $amount) {
|
||||
throw new \InvalidArgumentException('La cantidad reservada no es válida.');
|
||||
}
|
||||
|
||||
$this->reserved_stock -= $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function buy(int $amount, bool $tracksInventory): void
|
||||
{
|
||||
if ($amount < 0 || $this->reserved_stock < $amount) {
|
||||
throw new \InvalidArgumentException('La cantidad reservada no alcanza para confirmar la compra.');
|
||||
}
|
||||
|
||||
if ($tracksInventory && $this->real_stock < $amount) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock real para confirmar la compra.');
|
||||
}
|
||||
|
||||
if ($tracksInventory) {
|
||||
$this->real_stock -= $amount;
|
||||
}
|
||||
|
||||
$this->reserved_stock -= $amount;
|
||||
$this->sold_units += $amount;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
57
app/Domains/Commerce/Catalog/Models/ItemAttribute.php
Normal file
57
app/Domains/Commerce/Catalog/Models/ItemAttribute.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'catalog_item_id',
|
||||
'attribute_id',
|
||||
'allow_multi_select',
|
||||
'sort_order',
|
||||
'show_in_selector',
|
||||
'ticket_label',
|
||||
])]
|
||||
class ItemAttribute extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'item_attributes';
|
||||
|
||||
protected $attributes = [
|
||||
'show_in_selector' => true,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'catalog_item_id' => 'integer',
|
||||
'attribute_id' => 'integer',
|
||||
'allow_multi_select' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
'show_in_selector' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function catalogItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Attribute, $this> */
|
||||
public function attribute(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Attribute::class);
|
||||
}
|
||||
|
||||
/** @return HasMany<VariantDefinition, $this> */
|
||||
public function variantDefinitions(): HasMany
|
||||
{
|
||||
return $this->hasMany(VariantDefinition::class);
|
||||
}
|
||||
}
|
||||
57
app/Domains/Commerce/Catalog/Models/StockReservation.php
Normal file
57
app/Domains/Commerce/Catalog/Models/StockReservation.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
#[Fillable([
|
||||
'status',
|
||||
'expires_at',
|
||||
'committed_at',
|
||||
'released_at',
|
||||
'expired_at',
|
||||
'release_reason',
|
||||
])]
|
||||
class StockReservation extends Model
|
||||
{
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
|
||||
public const STATUS_COMMITTED = 'committed';
|
||||
|
||||
public const STATUS_RELEASED = 'released';
|
||||
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'expires_at' => 'datetime',
|
||||
'committed_at' => 'datetime',
|
||||
'released_at' => 'datetime',
|
||||
'expired_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return HasMany<StockReservationLine, $this> */
|
||||
public function lines(): HasMany
|
||||
{
|
||||
return $this->hasMany(StockReservationLine::class);
|
||||
}
|
||||
|
||||
/** @return HasOne<Cart, $this> */
|
||||
public function currentCart(): HasOne
|
||||
{
|
||||
return $this->hasOne(Cart::class, 'current_stock_reservation_id');
|
||||
}
|
||||
|
||||
/** @return HasOne<Purchase, $this> */
|
||||
public function purchase(): HasOne
|
||||
{
|
||||
return $this->hasOne(Purchase::class);
|
||||
}
|
||||
}
|
||||
38
app/Domains/Commerce/Catalog/Models/StockReservationLine.php
Normal file
38
app/Domains/Commerce/Catalog/Models/StockReservationLine.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'stock_reservation_id',
|
||||
'inventory_id',
|
||||
'quantity',
|
||||
'tracks_inventory',
|
||||
])]
|
||||
class StockReservationLine extends Model
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'stock_reservation_id' => 'integer',
|
||||
'inventory_id' => 'integer',
|
||||
'quantity' => 'integer',
|
||||
'tracks_inventory' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<StockReservation, $this> */
|
||||
public function reservation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StockReservation::class, 'stock_reservation_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Inventory, $this> */
|
||||
public function inventory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inventory::class);
|
||||
}
|
||||
}
|
||||
345
app/Domains/Commerce/Catalog/Models/Variant.php
Normal file
345
app/Domains/Commerce/Catalog/Models/Variant.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Fillable([
|
||||
'catalog_item_id',
|
||||
'event_date_id',
|
||||
'inventory_id',
|
||||
'replaced_by_variant_id',
|
||||
'sales_disabled_at',
|
||||
'descripcion',
|
||||
'precio',
|
||||
])]
|
||||
class Variant extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'variantes';
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'catalog_item_id' => 'integer',
|
||||
'event_date_id' => 'integer',
|
||||
'inventory_id' => 'integer',
|
||||
'replaced_by_variant_id' => 'integer',
|
||||
'sales_disabled_at' => 'datetime',
|
||||
'precio' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return BelongsTo<CatalogItem, $this> */
|
||||
public function catalogItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CatalogItem::class)->withTrashed();
|
||||
}
|
||||
|
||||
/** @return BelongsTo<EventDate, $this> */
|
||||
public function eventDate(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EventDate::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<EventDate, $this> */
|
||||
public function eventDates(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
EventDate::class,
|
||||
'variant_event_dates',
|
||||
'variant_id',
|
||||
'event_date_id',
|
||||
)->orderBy('date')->orderBy('time_start');
|
||||
}
|
||||
|
||||
/** @return HasMany<Ticket, $this> */
|
||||
public function sourceTickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'source_variant_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Inventory, $this> */
|
||||
public function inventory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inventory::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Variant, $this> */
|
||||
public function replacement(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'replaced_by_variant_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Variant, $this> */
|
||||
public function replacedVariants(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'replaced_by_variant_id');
|
||||
}
|
||||
|
||||
public function isSellable(): bool
|
||||
{
|
||||
return $this->sales_disabled_at === null
|
||||
&& $this->replaced_by_variant_id === null
|
||||
&& $this->hasOnlyActiveEventDates();
|
||||
}
|
||||
|
||||
public function hasOnlyActiveEventDates(): bool
|
||||
{
|
||||
if (! $this->exists
|
||||
&& $this->event_date_id === null
|
||||
&& ! $this->relationLoaded('eventDates')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->selectedEventDates()->every(
|
||||
fn (EventDate $eventDate): bool => $eventDate->rescheduled_to_event_date_id === null
|
||||
&& $eventDate->suspended_at === null,
|
||||
);
|
||||
}
|
||||
|
||||
/** @return HasMany<VariantDefinition, $this> */
|
||||
public function definitions(): HasMany
|
||||
{
|
||||
return $this->hasMany(VariantDefinition::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attachment, $this> */
|
||||
public function attachments(): BelongsToMany
|
||||
{
|
||||
return $this->allAttachments()->wherePivot('is_enabled', true);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Attachment, $this> */
|
||||
public function allAttachments(): BelongsToMany
|
||||
{
|
||||
$relation = $this->belongsToMany(
|
||||
Attachment::class,
|
||||
'catalog_items_attachments',
|
||||
'variant_id',
|
||||
'attachment_id'
|
||||
)
|
||||
->withPivot(['orden', 'is_enabled'])
|
||||
->orderByPivot('orden');
|
||||
|
||||
if ($this->catalog_item_id !== null) {
|
||||
$relation->withPivotValue('catalog_item_id', $this->catalog_item_id);
|
||||
}
|
||||
|
||||
return $relation;
|
||||
}
|
||||
|
||||
public function getPrice(): float
|
||||
{
|
||||
return (float) ($this->precio ?? $this->catalogItem->precio);
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->descripcion ?? $this->catalogItem->descripcion;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->catalogItem->nombre;
|
||||
}
|
||||
|
||||
public function getSelectionLabel(): string
|
||||
{
|
||||
$this->loadMissing([
|
||||
'catalogItem.itemAttributes.attribute.options',
|
||||
'definitions.itemAttribute.attribute.options',
|
||||
'eventDates',
|
||||
'eventDate',
|
||||
]);
|
||||
|
||||
$itemAttributes = $this->catalogItem->itemAttributes;
|
||||
|
||||
$label = $this->selectionOptions($itemAttributes)
|
||||
->map(function (array $option, string $attributeCode) use ($itemAttributes): ?string {
|
||||
$itemAttribute = $itemAttributes->first(
|
||||
fn (ItemAttribute $candidate): bool => $candidate->attribute?->codigo === $attributeCode,
|
||||
);
|
||||
$translationKey = "api.catalog.attribute_labels.{$attributeCode}";
|
||||
$attributeName = Lang::has($translationKey)
|
||||
? __($translationKey)
|
||||
: ($itemAttribute?->attribute?->nombre ?? Str::headline($attributeCode));
|
||||
$selectedOptions = array_is_list($option) ? $option : [$option];
|
||||
$selectedLabels = collect($selectedOptions)
|
||||
->pluck('label')
|
||||
->filter()
|
||||
->implode(', ');
|
||||
|
||||
return $selectedLabels === ''
|
||||
? null
|
||||
: "{$attributeName} {$selectedLabels}";
|
||||
})
|
||||
->filter()
|
||||
->implode(' · ');
|
||||
|
||||
return $label !== '' ? $label : $this->getName();
|
||||
}
|
||||
|
||||
/** @return Collection<string, string|array<int, string>> */
|
||||
public function selectionValues(): Collection
|
||||
{
|
||||
$values = $this->definitions
|
||||
->groupBy('item_attribute_id')
|
||||
->mapWithKeys(function (Collection $definitions): array {
|
||||
$itemAttribute = $definitions->first()?->itemAttribute;
|
||||
$attributeCode = $itemAttribute?->attribute?->codigo;
|
||||
|
||||
if ($attributeCode === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definitionValues = $definitions->pluck('value')->values();
|
||||
|
||||
return [
|
||||
$attributeCode => $itemAttribute->allow_multi_select
|
||||
? $definitionValues->all()
|
||||
: $definitionValues->first(),
|
||||
];
|
||||
});
|
||||
|
||||
$eventDateIds = $this->selectedEventDates()
|
||||
->pluck('id')
|
||||
->map(fn ($id): string => (string) $id)
|
||||
->values();
|
||||
|
||||
if ($eventDateIds->count() === 1) {
|
||||
$values->put('event_date', $eventDateIds->first());
|
||||
} elseif ($eventDateIds->isNotEmpty()) {
|
||||
$values->put('event_date', $eventDateIds->all());
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<string, array{value: string, label: string}|array<int, array{value: string, label: string}>>
|
||||
*/
|
||||
public function selectionOptions(?Collection $itemAttributes = null): Collection
|
||||
{
|
||||
$options = $this->definitions
|
||||
->groupBy('item_attribute_id')
|
||||
->mapWithKeys(function (Collection $definitions): array {
|
||||
$itemAttribute = $definitions->first()?->itemAttribute;
|
||||
$attribute = $itemAttribute?->attribute;
|
||||
$attributeCode = $attribute?->codigo;
|
||||
|
||||
if ($attributeCode === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$values = $definitions
|
||||
->pluck('value')
|
||||
->values()
|
||||
->map(function (string $value) use ($attribute): array {
|
||||
$attributeOption = $attribute->options->firstWhere('value', $value);
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'label' => $attributeOption?->label ?? $value,
|
||||
];
|
||||
});
|
||||
|
||||
return [
|
||||
$attributeCode => $itemAttribute->allow_multi_select
|
||||
? $values->all()
|
||||
: $values->first(),
|
||||
];
|
||||
});
|
||||
|
||||
$eventDateOptions = $this->selectedEventDates()
|
||||
->map(fn (EventDate $eventDate): array => [
|
||||
'value' => (string) $eventDate->id,
|
||||
'label' => $eventDate->date->format('d/m/Y'),
|
||||
])
|
||||
->values();
|
||||
|
||||
if ($eventDateOptions->count() === 1) {
|
||||
$options->put('event_date', $eventDateOptions->first());
|
||||
} elseif ($eventDateOptions->isNotEmpty()) {
|
||||
$options->put('event_date', $eventDateOptions->all());
|
||||
}
|
||||
|
||||
if ($itemAttributes === null) {
|
||||
$itemAttributes = $this->definitions
|
||||
->map(fn (VariantDefinition $definition) => $definition->itemAttribute)
|
||||
->filter()
|
||||
->unique('id')
|
||||
->values();
|
||||
|
||||
if ($this->catalogItem !== null) {
|
||||
$itemAttributes = $itemAttributes
|
||||
->merge($this->catalogItem->itemAttributes)
|
||||
->unique('id')
|
||||
->values();
|
||||
}
|
||||
}
|
||||
|
||||
$ordering = $itemAttributes->mapWithKeys(function (ItemAttribute $itemAttribute): array {
|
||||
$attribute = $itemAttribute->attribute;
|
||||
|
||||
return $attribute === null
|
||||
? []
|
||||
: [$attribute->codigo => [$itemAttribute->sort_order, mb_strtolower($attribute->nombre)]];
|
||||
});
|
||||
|
||||
return $options->sortKeysUsing(function (string $left, string $right) use ($ordering): int {
|
||||
[$leftOrder, $leftLabel] = $ordering->get($left, [0, mb_strtolower($left)]);
|
||||
[$rightOrder, $rightLabel] = $ordering->get($right, [0, mb_strtolower($right)]);
|
||||
|
||||
return $leftOrder <=> $rightOrder
|
||||
?: $leftLabel <=> $rightLabel
|
||||
?: $left <=> $right;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, ItemAttribute> $itemAttributes
|
||||
* @return Collection<string, array{value: string, label: string}|array<int, array{value: string, label: string}>>
|
||||
*/
|
||||
public function selectorOptions(Collection $itemAttributes): Collection
|
||||
{
|
||||
$visibleAttributeCodes = $itemAttributes
|
||||
->filter(fn (ItemAttribute $itemAttribute): bool => $itemAttribute->show_in_selector)
|
||||
->map(fn (ItemAttribute $itemAttribute): ?string => $itemAttribute->attribute?->codigo)
|
||||
->filter()
|
||||
->values();
|
||||
|
||||
return $this->selectionOptions($itemAttributes)
|
||||
->filter(
|
||||
fn (array $option, string $attributeCode): bool => $visibleAttributeCodes
|
||||
->contains($attributeCode)
|
||||
);
|
||||
}
|
||||
|
||||
/** @return Collection<int, EventDate> */
|
||||
public function selectedEventDates(): Collection
|
||||
{
|
||||
$eventDates = $this->eventDates;
|
||||
|
||||
if ($eventDates->isEmpty() && $this->event_date_id !== null && $this->eventDate !== null) {
|
||||
return collect([$this->eventDate]);
|
||||
}
|
||||
|
||||
return $eventDates;
|
||||
}
|
||||
}
|
||||
32
app/Domains/Commerce/Catalog/Models/VariantDefinition.php
Normal file
32
app/Domains/Commerce/Catalog/Models/VariantDefinition.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'variant_id',
|
||||
'item_attribute_id',
|
||||
'value',
|
||||
])]
|
||||
class VariantDefinition extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'variant_values';
|
||||
|
||||
/** @return BelongsTo<Variant, $this> */
|
||||
public function variant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Variant::class);
|
||||
}
|
||||
|
||||
/** @return BelongsTo<ItemAttribute, $this> */
|
||||
public function itemAttribute(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ItemAttribute::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user