This commit is contained in:
2025-09-25 03:03:31 +05:00
commit ae480cf2f6
2768 changed files with 1485826 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
<?php
namespace App\Models\Ecommerce\Product\Brand;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
class Brand extends Model implements HasMedia, Sortable
{
use HasFactory;
use HasSlug;
use HasTranslations;
use InteractsWithMedia;
use SortableTrait;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'name',
'slug',
'type',
'website',
'description',
'seo_title',
'seo_description',
'is_visible',
'sort_order',
];
/**
* Translatable fields
*
* @var array<string>
*/
public $translatable = ['description'];
/**
* Sortable attributes
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
/**
* Register media collections
*/
public function registerMediaCollections(): void
{
$this->addMediaCollection('uploads')
->singleFile()
->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png'])
->useFallbackUrl(
sprintf('%s/logo-space.png', config('app.url'))
);
}
/**
* Register Media Conversions
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
* Brand thumbnail
*/
public function thumbnail(string $size = '200x200'): string
{
return $this->getFirstMediaUrl('uploads', 'thumb'.$size);
}
/**
* Original image
*/
public function image(): string
{
return $this->getFirstMediaUrl('uploads');
}
/**
* Brand Products
*/
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
/**
* Scope for visable models
*/
public function scopeEnabled(Builder $query): void
{
$query->where('is_visible', true);
}
/**
* Brand Types
*/
public static function types(): array
{
return [
'market',
];
}
/**
* Check if brand belongs to market
*
* @return bool
*/
public function isMarketCategory(): string
{
return $this->type === 'market';
}
/**
* Brand's Products web page
*/
public function productsPage(): string
{
return route('web.brands.products', ['brand' => $this->slug]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Models\Ecommerce\Product\Cart;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CartItem extends Model
{
use HasFactory;
/**
* The table associated with the model.
*/
protected $table = 'cart_items';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'product_id',
'product_quantity',
];
/**
* Cart Item user
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Cart Item product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace App\Models\Ecommerce\Product\Category;
use App\Events\Ecommerce\Product\Category\CategoryTaxChanged;
use App\Models\Concerns\HasSchemalessAttributes;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
use Staudenmeir\EloquentEagerLimitXLaravelAdjacencyList\Eloquent\HasEagerLimitAndRecursiveRelationships;
class Category extends Model implements HasMedia, Sortable
{
use HasEagerLimitAndRecursiveRelationships;
use HasFactory;
use HasSchemalessAttributes;
use HasSlug;
use HasTranslations;
use InteractsWithMedia;
use SortableTrait;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'parent_id',
'name',
'slug',
'type',
'description',
'seo_title',
'seo_description',
'is_visible',
'tax_percentage',
'sort_order',
'options',
];
/**
* Translatable fields
*
* @var array<string>
*/
public $translatable = ['name', 'description'];
/**
* Sortable attributes
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
/**
* Media Collections
*/
public function registerMediaCollections(): void
{
$this->addMediaCollection('uploads')
->singleFile()
->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png'])
->useFallbackUrl(
sprintf('%s/logo-space.png', config('app.url'))
);
}
/**
* Register Media Conversions
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
$this->addMediaConversion('thumb657x230')
->fit(Manipulations::FIT_CROP, 657, 230);
}
/**
* Brand thumbnail
*/
public function thumbnail(string $size = '200x200'): string
{
return $this->getFirstMediaUrl('uploads', 'thumb'.$size);
}
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::updated(function ($category) {
if ($category->wasChanged('tax_percentage')) {
CategoryTaxChanged::dispatch($category);
}
});
}
/**
* Attributes (properties) of Category
*/
public function properties(): BelongsToMany
{
return $this->belongsToMany(Attribute::class, 'attributes');
}
/**
* Category Products
*/
public function products(): MorphToMany
{
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
}
/**
* Scope for visable models
*/
public function scopeEnabled(Builder $query): void
{
$query->where('is_visible', true);
}
/**
* Color attribute
*/
public function color(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('color'),
);
}
/**
* Category's Parent name
*
* @return ?string
*/
public function parentName(): Attribute
{
return Attribute::make(
get: fn () => $this->parent?->name
);
}
/**
* Check if model belongs to Market
*/
public function isMarketCategory(): bool
{
return $this->type === 'market';
}
/**
* Category's Products webpage
*/
public function productsPage(): string
{
return route('web.categories.products', ['category' => $this->slug]);
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace App\Models\Ecommerce\Product\Collection;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
use Staudenmeir\EloquentEagerLimitXLaravelAdjacencyList\Eloquent\HasEagerLimitAndRecursiveRelationships;
class Collection extends Model implements HasMedia, Sortable
{
use HasEagerLimitAndRecursiveRelationships;
use HasFactory;
use HasSlug;
use HasTranslations;
use InteractsWithMedia;
use SortableTrait;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'name',
'slug',
'description',
'sort_order',
'seo_title',
'seo_description',
'is_visible',
];
/**
* Translatable fields
*
* @var array<string>
*/
public $translatable = ['name', 'description'];
/**
* Translatable fields
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
/**
* Register Media Collections
*/
public function registerMediaCollections(): void
{
$this->addMediaCollection('uploads')
->singleFile()
->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png'])
->useFallbackUrl(
sprintf('%s/logo-space.png', config('app.url'))
);
}
/**
* Register Media Conversions
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
* Products
*/
public function products(): MorphToMany
{
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
}
/**
* Brand's Products web page
*/
public function productsPage(): string
{
return route('web.collections.products', ['collection' => $this->slug]);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Models\Ecommerce\Product\Coupon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Coupon extends Model
{
use HasFactory;
/**
* Discount by percentage (%)
*/
public const DISCOUNT_PERCENTAGE = 'percentage';
/**
* Discount by fixed amount (ex: -10 TMT)
*/
public const DISCOUNT_FIXED = 'fixed_amount';
/**
* @var array<int, string>
*/
protected $fillable = [
'code',
'discount_type',
'discount_value',
'notes',
'is_active',
];
/**
* Discount types
*/
public static function discountTypes(): array
{
return [
self::DISCOUNT_PERCENTAGE => __('Percentage'),
self::DISCOUNT_FIXED => __('Fixed Amount'),
];
}
/**
* Default discount type
*/
public static function defaultDiscountType(): string
{
return self::DISCOUNT_PERCENTAGE;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models\Ecommerce\Product\Favorite;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Favorite extends Model
{
use HasFactory;
/**
* The table associated with the model.
*/
protected $table = 'favorites';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'product_id',
];
/**
* Favourite Product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class, 'product_id', 'id');
}
/**
* Favourite model User (Owner)
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Models\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Inventory extends Model
{
use HasFactory;
use HasSlug;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'name',
'code',
'description',
'email',
'phone_number',
'street_address',
'zipcode',
'region',
'province_id',
'longitude',
'latitude',
'is_default',
'channel_id',
'shareable',
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('code')
->doNotGenerateSlugsOnUpdate();
}
/**
* User Channel
*/
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class);
}
/**
* Products
*/
public function products()
{
return $this->belongsToMany(Product::class)->withPivot('stock');
}
/**
* Tmpost default inventory
*/
public static function tmpostDefault(): Inventory
{
return static::where('code', 'tmpost')->first();
}
/**
* History
*/
public function history(): HasMany
{
return $this->hasMany(InventoryHistory::class, 'inventory_id');
}
/**
* History removed
*/
public function historyRemoved(): HasMany
{
return $this->hasMany(InventoryHistoryRemoved::class, 'inventory_id');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models\Ecommerce\Product\Inventory;
use App\Models\Concerns\HasSchemalessAttributes;
use App\Models\Ecommerce\Channel\Channel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class InventoryHistory extends Model
{
use HasFactory;
use HasSchemalessAttributes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'date',
'inventory_id',
'channel_id',
'notes',
'total',
'options',
];
/**
* Attributes that are casted
*/
protected $casts = [
'date' => 'date',
];
/**
* Channel
*/
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class);
}
/**
* Inventory
*/
public function inventory(): BelongsTo
{
return $this->belongsTo(Inventory::class);
}
/**
* Items
*/
public function items(): HasMany
{
return $this->hasMany(InventoryHistoryItem::class, 'inventory_history_id');
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Models\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InventoryHistoryItem extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'inventory_history_id',
'product_id',
'quantity',
'total',
'cost_amount',
];
/**
* Inventory history
*/
public function inventoryHistory(): BelongsTo
{
return $this->belongsTo(InventoryHistory::class, 'inventory_history_id');
}
/**
* Product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models\Ecommerce\Product\Inventory;
use App\Models\Concerns\HasSchemalessAttributes;
use App\Models\Ecommerce\Channel\Channel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class InventoryHistoryRemoved extends Model
{
use HasFactory;
use HasSchemalessAttributes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'date',
'inventory_id',
'channel_id',
'notes',
'total',
'options',
];
/**
* Attributes that are casted
*/
protected $casts = [
'date' => 'date',
];
/**
* Channel
*/
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class);
}
/**
* Inventory
*/
public function inventory(): BelongsTo
{
return $this->belongsTo(Inventory::class);
}
/**
* Items
*/
public function items(): HasMany
{
return $this->hasMany(InventoryHistoryRemovedItem::class);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Models\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InventoryHistoryRemovedItem extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'inventory_history_removed_id',
'product_id',
'quantity',
'total',
'cost_amount',
];
/**
* Inventory history
*/
public function inventoryHistoryRemoved(): BelongsTo
{
return $this->belongsTo(InventoryHistoryRemoved::class, 'inventory_history_removed_id');
}
/**
* Product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Concerns;
trait HasPayments
{
/**
* Total price for order
*/
public function total(): float
{
return $this->items->sum('total');
}
/**
* Return total order with shipping.
*/
public function fullPriceWithShipping(): string
{
return floatval($this->total()) + floatval($this->shippingPrice()) + floatval($this->additional_tax);
}
/**
* Formatted payment type
*/
public function formattedPaymentType(): string
{
return $this->paymentType?->name;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Concerns;
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
trait HasShipping
{
/**
* Shipping price
*/
public function shippingPrice(): int
{
return intval($this->shipping_price) ?: OrderShipping::priceFor($this->shipping_method);
}
/**
* Formatted shipping method
*/
public function formattedShippingMethod(): string
{
return OrderShipping::formattedShippingMethod($this->shipping_method);
}
/**
* Full address for the order
*/
public function fullAddress(): string
{
return OrderShipping::fullAddress($this->region, $this->province_id, $this->customer_address);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Concerns;
use App\Models\Ecommerce\Product\Order\Status\OrderStatus;
trait HasStatus
{
/**
* Return the correct order status formatted.
*/
public function formatted_status(): string
{
return OrderStatus::formattedStatusFor($this->status);
}
/**
* Can order be cancelled?
*/
public function canBeCancelled(): bool
{
return $this->isPending();
}
/**
* Check if order is pending
*/
public function isPending(): bool
{
return $this->status === OrderStatus::PENDING;
}
/**
* Check if order is registered
*/
public function isRegistered(): bool
{
return $this->status === OrderStatus::REGISTER;
}
/**
* Check if order is paid
*/
public function isPaid(): bool
{
return $this->status === OrderStatus::PAID;
}
/**
* Check if order is completed
*/
public function isCompleted(): bool
{
return $this->status === OrderStatus::COMPLETED;
}
/**
* Check if order is completed
*/
public function isCancelled(): bool
{
return $this->status === OrderStatus::CANCELLED;
}
/**
* Mark order as paid
*/
public function markAsPaid(): void
{
$this->update(['status' => OrderStatus::PAID]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Models\Ecommerce\Product\Order;
use App\Models\Ecommerce\Product\Order\Concerns\HasPayments;
use App\Models\Ecommerce\Product\Order\Concerns\HasShipping;
use App\Models\Ecommerce\Product\Order\Concerns\HasStatus;
use App\Models\System\Settings\Location\Province;
use App\Models\System\Settings\Payments\PaymentType;
use App\Models\User;
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\Database\Eloquent\SoftDeletes;
class Order extends Model
{
use HasFactory;
use HasPayments;
use HasShipping;
use HasStatus;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'number',
'status',
'shipping_method',
'shipping_price',
'payment_type_id',
'notes',
'customer_name',
'customer_phone',
'customer_address',
'delivery_time',
'delivery_at',
'region',
'user_id',
'additional_tax',
'province_id',
'source_app',
];
/**
* The attributes that should be cast.
*/
protected $casts = [
'delivery_at' => 'date',
];
/**
* User (customer)
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Related Province
*/
public function province(): BelongsTo
{
return $this->belongsTo(Province::class);
}
/**
* Order items
*/
public function items(): HasMany
{
return $this->hasMany(OrderItem::class);
}
/**
* Payment types
*/
public function paymentType(): BelongsTo
{
return $this->belongsTo(PaymentType::class, 'payment_type_id');
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Models\Ecommerce\Product\Order;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class OrderItem extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'product_name',
'product_id',
'order_id',
'channel_id',
'quantity',
'unit_price_amount',
'unit_cost_amount',
];
/**
* Related order (parent)
*/
public function order(): BelongsTo
{
return $this->belongsTo(Order::class, 'order_id');
}
/**
* Related product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class, 'product_id', 'id');
}
/**
* Related Channel
*/
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class, 'channel_id', 'id');
}
/**
* Total price
*/
public function total(): Attribute
{
return Attribute::make(
get: fn () => (float) $this->unit_price_amount * (int) $this->quantity
);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Payment;
use App\Models\System\Settings\Payments\PaymentType;
use App\Repositories\System\Cache\CacheRepository;
class OrderPayment
{
/**
* Cash
*/
public const CASH = 'cash';
/**
* ATM
*/
public const ATM = 'atm';
/**
* Online (halkbank)
*/
public const ONLINE = 'online';
/**
* Online (halkbank)
*/
public const HALK_BANK = 'halk_bank';
/**
* Online (senagat)
*/
public const SENAGAT_BANK = 'senegat_bank';
/**
* Online (rysgal)
*/
public const RYSGAL_BANK = 'rysgal_bank';
/**
* Online (wneshka)
*/
public const WNESHKA_BANK = 'wneshka_bank';
/**
* Default payment value
*/
public static function default(): ?string
{
return CacheRepository::make(
name: 'cs-nova-models-default-order-payment',
value: fn () => PaymentType::where('code', 'cash')->pluck('id')->first(),
time: 60 * 60 * 24
);
}
/**
* Payment values
*/
public static function values(): array
{
return CacheRepository::make(
name: 'cs-nova-models-order-payments',
value: fn () => PaymentType::pluck('name', 'id')->toArray()
);
}
/**
* Old but good types
*
* @return array<int, string>
*/
public static function oldButGoodTypes(): array
{
return [
static::ATM => __('ATM'),
static::CASH => __('Cash'),
];
}
/**
* Formatted payment type for given "type"
*/
public static function formattedPaymentTypeFor(string $type): string
{
return static::values()[$type] ?? self::CASH;
}
}

View File

@@ -0,0 +1,201 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Shipping;
use App\Models\Ecommerce\Product\Order\Order;
use App\Models\System\Settings\Location\Province;
use App\Models\System\Settings\Location\Region;
class OrderShipping
{
/*
* Shipping methods
*
* standart - basic
* express - fast
* self_pickup - self pickup
* region - deliver to regions (mr, ah, dz, lb, bn)
*/
/**
* Standart shipping
*/
public const STANDART = 'standart';
/**
* Express shipping
*/
public const EXPRESS = 'express';
/**
* On own - self pickup
*/
public const SELF_PICKUP = 'self_pickup';
/**
* Region - shipping to regions (mr, ah, ...)
*/
public const REGION = 'region';
/*
* Shipping method prices
*
* standart - 20
* express - 30
* self pickup - 0
* region - 40
*/
/**
* Standart shipping price
*/
public const STANDART_PRICE = 20;
/**
* Express shipping price
*/
public const EXPRESS_PRICE = 30;
/**
* Self pickup shipping price
*/
public const SELF_PICKUP_PRICE = 0;
/**
* Region shipping price
*/
public const REGION_PRICE = 40;
/**
* Order delivery times
*
* morning
* evening
*/
/**
* Morning time 09 - 12:00
*/
public const MORNING = '09:00 - 12:00';
/**
* Evening time 13:00 - 18:00
*/
public const EVENING = '13:00 - 18:00';
/**
* Default shipping value
*/
public static function default(): string
{
return static::STANDART;
}
/**
* Default shipping time value
*/
public static function defaultTime(): string
{
return static::EVENING;
}
/**
* Shipping values
*/
public static function values(): array
{
return [
self::STANDART => __('Standart'),
self::EXPRESS => __('Express'),
self::SELF_PICKUP => __('Self Pickup'),
self::REGION => __('Region'),
];
}
/**
* Prices
*/
public static function prices(): array
{
return [
self::STANDART => self::STANDART_PRICE,
self::EXPRESS => self::EXPRESS_PRICE,
self::SELF_PICKUP => self::SELF_PICKUP_PRICE,
self::REGION => self::REGION_PRICE,
];
}
/**
* Shipping times
*/
public static function times(): array
{
return [
self::MORNING => self::MORNING,
self::EVENING => self::EVENING,
];
}
/**
* Get price for specific shipping
*/
public static function priceFor(string $type): int
{
return static::prices()[$type] ?? self::STANDART_PRICE;
}
/**
* Order shipping price
*
* @param Order $order
*/
public static function orderShippingPrice($order): int
{
if ($order->region === Region::AG) {
return static::priceFor($order->shipping_method);
}
if (in_array($order->province_id, [
12,
43,
45,
83,
56,
22,
])) {
return 30;
}
return static::REGION_PRICE;
}
/**
* Formatted shipping method
*/
public static function formattedShippingMethod(string $shippingMethod): string
{
return static::values()[$shippingMethod] ?? __('Standart');
}
/**
* Get full address
*/
public static function fullAddress($region, $province, $customer_address): string
{
$address = '';
if ($region) {
$address .= Region::labelFor($region);
}
if ($province = Province::where('id', $province)->first('name')) {
$address .= ', '.$province->name;
}
if ($customer_address) {
$address .= ','.$customer_address;
}
return $address;
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Models\Ecommerce\Product\Order\Status;
class OrderStatus
{
/**
* Pending orders are brand new orders that have not been processed yet.
*/
public const PENDING = 'pending';
/**
* Orders that has been registered..
*/
public const REGISTER = 'register';
/**
* Orders that has been paid..
*/
public const PAID = 'paid';
/**
* Orders fulfilled completely.
*/
public const COMPLETED = 'completed';
/**
* Order that has been cancelled.
*/
public const CANCELLED = 'cancelled';
/**
* Default status value
*/
public static function default(): string
{
return static::PENDING;
}
/**
* Status Values
*/
public static function values(): array
{
return [
self::PENDING => __('Pending'),
self::REGISTER => __('Registered'),
self::PAID => __('Paid'),
self::COMPLETED => __('Completed'),
self::CANCELLED => __('Cancelled'),
];
}
/**
* Formatted status for given "status"
*/
public static function formattedStatusFor(string $status): string
{
return static::values()[$status] ?? __('None');
}
/**
* Tailwind
*/
public static function classes(): array
{
return [
self::PENDING => 'warning',
self::REGISTER => 'info',
self::PAID => 'primary',
self::COMPLETED => 'success',
self::CANCELLED => 'danger',
];
}
/**
* HEX Colors
*/
public static function colors(): array
{
return [
self::PENDING => '#F5573B',
self::REGISTER => '#F2CB22',
self::PAID => '#098F56',
self::COMPLETED => '#8FC15D',
self::CANCELLED => '#d70206',
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use Laravel\Nova\Nova;
trait ProductFrontEndHelpers
{
/**
* Show page
*/
public function showPage(): string
{
return route('web.products.show', ['product' => $this->slug]);
}
/**
* Nova page
*/
public function novaPage(): string
{
return Nova::url('/resources/products');
}
/**
* Nova detail page
*/
public function novaDetailPage(): string
{
return Nova::url('/resources/products/'.$this->id);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
trait ProductMedia
{
/**
* Media collections
*/
public function registerMediaCollections(): void
{
$this->addMediaCollection('uploads')
->useFallbackUrl(url('/assets/web/images/05.jpg'));
}
/**
* Media Conversions
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
* Thumbnail
*/
public function thumbnail(string $size = '200x200'): string
{
return $this->getFirstMediaUrl('uploads', 'thumb'.$size);
}
/**
* Get image when hovered (returns second image)
*/
public function getHoverImage(string $size = '270x350'): string
{
$media = $this->getMedia('uploads');
$image_count = $media->count();
if ($image_count == 0) {
return '';
}
return ($image_count == 1)
? $media[0]->getUrl('thumb'.$size)
: $media[1]->getUrl('thumb'.$size);
}
}

View File

@@ -0,0 +1,436 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use App\Models\Ecommerce\Product\Property\ProductProperty;
use Illuminate\Support\Facades\Blade;
trait ProductProperties
{
/**
* Check if given product has given property
*/
public function containsProperty(string $property): bool
{
return $this->properties->pluck('attribute.slug')->contains($property);
}
/**
* Get product property
*/
public function getProperty(string $name): ?ProductProperty
{
return $this->properties->first(fn ($property) => $property->attribute->slug === $name);
}
/**
* Check if product has color variants
*/
public function hasColorVariants(): bool
{
return $this->containsProperty('colour');
}
/**
* Get product color property
*/
public function getColorProperty(): ?ProductProperty
{
return $this->getProperty('colour');
}
/**
* Get product color property
*/
public function getSizeProperty(): ?ProductProperty
{
return $this->getProperty('size');
}
/**
* Get color value of product
*/
public function getColorValue(): ?string
{
$colorProperty = $this->getColorProperty();
return $colorProperty ? $colorProperty->values->first()?->real_value : null;
}
/**
* Get size value
*/
public function getSizeValue(): ?string
{
$sizeProperty = $this->getSizeProperty();
return $sizeProperty ? $sizeProperty->values->first()?->real_value : null;
}
/**
* Check if product has a color.
*/
public function hasColor(): bool
{
return $this->hasPropertyWithSlug('colour');
}
/**
* Check if product has a size.
*/
public function hasSize(): bool
{
return $this->hasPropertyWithSlug('size');
}
/**
* Color property
*/
public function getColor(): string
{
$product = 'productColor'.$this->id;
if (temp_cache($product)) {
return temp_cache($product);
}
cache()->put(
key: 'productColor'.$this->id,
value: $this->getPropertyValueBySlug('colour'),
);
return temp_cache($product);
}
/**
* Size property
*/
public function getSize(): string
{
$product = 'productSize'.$this->id;
if (temp_cache($product)) {
return temp_cache($product);
}
cache()->put(
'productSize'.$this->id,
$this->getPropertyValueBySlug('size'),
);
return temp_cache($product);
}
/**
* Color variants
*/
public function colorVariants(): array
{
if (temp_cache('productColorVariants')) {
return temp_cache('productColorVariants');
}
$this->loadMissing(['media', 'properties', 'variations' => ['media', 'properties']]);
$colorVariants = collect();
// Add parent product
$colorVariants->push([
'slug' => $this->slug,
'id' => $this->id,
'image' => $this->thumbnail(),
'color' => $this->getColor(),
]);
$this->variations->each(function ($variation) use ($colorVariants) {
$colorVariants->push([
'slug' => $variation->slug,
'id' => $variation->id,
'image' => $variation->thumbnail(),
'color' => $variation->getColor(),
]);
});
cache()->put(
'productColorVariants',
$colorVariants->uniqueStrict('color')->toArray(),
);
return $colorVariants->toArray();
}
/**
* Check if a property with the given attribute slug exists
*/
protected function hasPropertyWithSlug(string $slug): bool
{
if ($this->relationLoaded('properties')) {
return $this->checkLoadedPropertiesForSlug($slug);
}
return $this->checkDatabaseForPropertySlug($slug);
}
/**
* Check loaded properties for attribute slug
*/
protected function checkLoadedPropertiesForSlug(string $slug): bool
{
foreach ($this->properties as $property) {
if ($property->attribute->slug === $slug) {
return true;
}
}
return false;
}
/**
* Check database for attribute slug
*/
protected function checkDatabaseForPropertySlug(string $slug): bool
{
return $this->properties()
->where('attribute.slug', $slug)
->exists();
}
/**
* Get property value by slug
*/
public function getPropertyValueBySlug(string $slug): string
{
return $this->properties->firstWhere('attribute.slug', 'colour')->values->first()->real_value ?? '';
}
/**
* Get size variants available for given color
*/
public function getSizeVariantsForColor(): array
{
if (temp_cache('productSizeVariantsForColor')) {
return temp_cache('productSizeVariantsForColor');
}
if ($this->parent_id) {
return $this->childSizeVariantsForColor();
}
$this->loadMissing(['properties', 'variations', 'variations.properties']);
$properties = $this->properties;
$variations = $this->variations;
$productSize = [];
$matchingProducts = [];
foreach ($properties as $property) {
if ($property->attribute->slug === 'size') {
$productSize = $property->values->first()->real_value;
$productSize = [
'slug' => $this->slug,
'id' => $this->id,
'size' => $productSize,
'parent_id' => null,
];
}
if ($property->attribute->slug === 'colour') {
// Color found
$productColor = $property->values->first()->real_value;
// Get All Sizes
$variationAllSizes = [];
// Iterate over to find same colors in variants
foreach ($variations as $variation) {
foreach ($variation->properties as $variationProperty) {
if ($variationProperty->attribute->slug === 'size') {
$productSize = $variationProperty->values->first()->real_value;
$variationAllSizes[] = [
'slug' => $variation->slug,
'id' => $variation->id,
'parent_id' => $variation->parent_id,
'size' => $productSize,
];
}
if ($variationProperty->attribute->slug === 'colour') {
// If colors match, append to array
$variationColor = $variationProperty->values->first()->real_value;
if ($productColor === $variationColor) {
$matchingProducts[] = [
'slug' => $variation->slug,
'id' => $variation->id,
'color' => $productColor,
];
}
}
}
}
} /* Color end */
}
$result = collect();
$result->push($productSize);
collect($variationAllSizes)->whereIn('id', collect($matchingProducts)->pluck('id'))
->each(function ($variationSize) use ($result) {
$result->push($variationSize);
});
cache()->put(
'productSizeVariantsForColor',
$result->toArray(),
);
return $result->toArray();
}
public function childSizeVariantsForColor(): array
{
$this->loadMissing(['properties']);
$properties = $this->properties;
$parent = $this->parent;
$parent->load(['properties', 'variations', 'variations.properties']);
$variations = $parent->variations;
$productSize = [];
$matchingProducts = [];
foreach ($properties as $property) {
if ($property->attribute->slug === 'size') {
$productSize = $property->values->first()->real_value;
$productSize = [
'slug' => $this->slug,
'id' => $this->id,
'size' => $productSize,
'parent_id' => $this->parent_id,
];
}
if ($property->attribute->slug === 'colour') {
// Color found
$productColor = $property->values->first()->real_value;
// Get All Sizes
$variationAllSizes = [];
// Iterate over to find same colors in variants
foreach ($variations as $variation) {
if ($variation->id === $this->id) {
continue;
}
foreach ($variation->properties as $variationProperty) {
if ($variationProperty->attribute->slug === 'size') {
$productSize = $variationProperty->values->first()->real_value;
$variationAllSizes[] = [
'slug' => $variation->slug,
'id' => $variation->id,
'parent_id' => $variation->parent_id,
'size' => $productSize,
];
}
if ($variationProperty->attribute->slug === 'colour') {
// If colors match, append to array
$variationColor = $variationProperty->values->first()->real_value;
if ($productColor === $variationColor) {
$matchingProducts[] = [
'slug' => $variation->slug,
'id' => $variation->id,
'color' => $productColor,
];
}
}
}
}
} /* Color end */
}
$result = collect();
$result->push($productSize);
collect($variationAllSizes)->whereIn('id', collect($matchingProducts)->pluck('id'))
->each(function ($variationSize) use ($result) {
$result->push($variationSize);
});
return $result->toArray();
}
/**
* All values of product properties
*/
public function allValuesOfProperties(): array
{
return $this->properties->flatMap(function ($property) {
return $property->values->map(function ($value) {
return $value->real_value;
})->filter();
})->all();
}
/**
* Name with properties
*/
public function nameWithProperties(): string
{
$properties = $this->allValuesOfProperties();
return sprintf(
'%s %s %s',
$this->name,
count($properties) > 0 ? '-' : '',
Blade::render('
@foreach($datas as $data)
<strong>{{ $data }}{{ $loop->last ? "" : "," }}</strong>
@endforeach
', ['datas' => $properties])
);
}
public function getSizeVariants(): array
{
$sizeVariants = [];
// Add parent product
$sizeVariants[] = [
'slug' => $this->slug,
'id' => $this->id,
'image' => $this->thumbnail(),
'images_hd' => $this->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb720x720')),
'size' => $this->getSizeValue(),
];
$this->variations->each(function ($variation) use (&$sizeVariants) {
$variationSize = $variation->getSizeValue();
if ($variationSize && ! keyValueExistsInArray(datas: $sizeVariants, key: 'size', value: $variationSize)) {
$sizeVariants[] = [
'slug' => $variation->slug,
'id' => $variation->id,
'image' => $variation->thumbnail(),
'images_hd' => $variation->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb720x720')),
'size' => $variationSize,
];
}
});
return $sizeVariants;
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use App\Models\Common\Comment;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Models\Ecommerce\Product\Cart\Cart;
use App\Models\Ecommerce\Product\Category\Category;
use App\Models\Ecommerce\Product\Collection\Collection;
use App\Models\Ecommerce\Product\Inventory\Inventory;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\Ecommerce\Product\Property\ProductProperty;
use App\Models\Ecommerce\Product\Review\Review;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
trait ProductRelationships
{
/**
* Product variations
*/
public function variations(): HasMany
{
return $this->hasMany(Product::class, 'parent_id');
}
/**
+ * Parent
+ */
public function parent(): BelongsTo
{
return $this->belongsTo(Product::class, 'parent_id');
}
/**
* Related Channels
*/
public function channels(): MorphToMany
{
return $this->morphedByMany(Channel::class, 'productable', 'product_has_relations');
}
/**
* Firt channel
*/
public function owner(): ?Channel
{
return $this->relationLoaded('channels')
? $this->channels->first()
: $this->channels()->first();
}
/**
* Related products (similar)
*/
public function relatedProducts(): MorphToMany
{
return $this->morphedByMany(Product::class, 'productable', 'product_has_relations');
}
/**
* Related categories
*/
public function categories(): MorphToMany
{
return $this->morphedByMany(Category::class, 'productable', 'product_has_relations');
}
/**
* Related Collections
*/
public function collections(): MorphToMany
{
return $this->morphedByMany(Collection::class, 'productable', 'product_has_relations');
}
/**
* Related Brand
*/
public function brand(): BelongsTo
{
return $this->belongsTo(Brand::class, 'brand_id');
}
/**
* Product Properties, same as attribtues
*
* (size, color, material, ..)
*/
public function properties(): HasMany
{
return $this->hasMany(ProductProperty::class);
}
/**
* Related inventories
*/
public function inventories(): BelongsToMany
{
return $this->belongsToMany(Inventory::class)
->withPivot('stock');
}
/**
* Carts
*/
public function carts(): BelongsTo
{
return $this->belongsTo(Cart::class);
}
/**
* Product's review
*/
public function reviews(): HasMany
{
return $this->hasMany(Review::class);
}
/**
* Product's comments
*/
public function comments(): BelongsToMany
{
return $this->belongsToMany(Comment::class);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
trait ProductScopeQueries
{
/**
* Visable products scope
*
* @param mixed $query
*/
public function scopeVisable($query): void
{
$query->where('is_visible', true);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
trait ProductStates
{
/**
* Check if product is new (one week range)
*/
public function isNew(): bool
{
return $this->created_at->greaterThan(now()->subWeek());
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use Illuminate\Database\Eloquent\Relations\MorphMany;
trait ProductStock
{
/**
* Inventory history
*/
public function inventoryHistories(): MorphMany
{
return $this->morphMany(InventoryHistory::class, 'stockable')->orderBy('created_at', 'desc');
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Models\Ecommerce\Product\Product\Concerns;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Spatie\Sluggable\SlugOptions;
trait ProductTransformers
{
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
/**
* Get discount difference
*
* @return int|float
*/
public function discountDifference(): Attribute
{
return Attribute::make(get: fn () => $this->caluculateDiscountDifference());
}
/**
* Discount percentage
*/
public function discountPercentage()
{
return number_format(($this->caluculateDiscountDifference() * 100) / $this->old_price_amount);
}
/**
* Caluculate Discount Difference
*/
public function caluculateDiscountDifference(int|float|null $price_amount = null): float
{
return $this->old_price_amount ? $this->old_price_amount - ($price_amount ?: $this->price_amount) : 0;
}
/**
* Full name
*/
public function fullName(): Attribute
{
return Attribute::make(
get: function () {
$brand = $this->brand ? "({$this->brand->name})" : '';
return $this->name.$brand.$this->colour.$this->size;
}
);
}
/**
* @param bool backorder
*/
public function backOrder(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('back_order')
);
}
/**
* @param string weight_value
*/
public function weightValue(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('weight_value')
);
}
/**
* @param string weight_unit
*/
public function weightUnit(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('weight_unit')
);
}
/**
* @param string height_value
*/
public function heightValue(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('height_value')
);
}
/**
* @param string height_unit
*/
public function heightUnit(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('height_unit')
);
}
/**
* @param string width_value
*/
public function widthValue(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('width_value')
);
}
/**
* @param string width_unit
*/
public function widthUnit(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('width_unit')
);
}
/**
* @param string depth_value
*/
public function depthValue(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('depth_value')
);
}
/**
* @param string depth_unit
*/
public function depthUnit(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('depth_unit')
);
}
/**
* @param string volume_value
*/
public function volumeValue(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('volume_value')
);
}
/**
* @param string volume_unit
*/
public function volumeUnit(): Attribute
{
return Attribute::make(
get: fn () => $this->options->get('volume_unit')
);
}
/**
* Flexible Content
*/
// public function flexibleContent(): Attribute
// {
// return Attribute::make(
// get: fn () => $this->flexible('flexible-content')
// );
// }
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Models\Ecommerce\Product\Product;
use App\Models\Concerns\HasSchemalessAttributes;
use App\Models\Ecommerce\Product\Product\Concerns\ProductFrontEndHelpers;
use App\Models\Ecommerce\Product\Product\Concerns\ProductMedia;
use App\Models\Ecommerce\Product\Product\Concerns\ProductProperties;
use App\Models\Ecommerce\Product\Product\Concerns\ProductRelationships;
use App\Models\Ecommerce\Product\Product\Concerns\ProductScopeQueries;
use App\Models\Ecommerce\Product\Product\Concerns\ProductStates;
use App\Models\Ecommerce\Product\Product\Concerns\ProductStock;
use App\Models\Ecommerce\Product\Product\Concerns\ProductTransformers;
use CyrildeWit\EloquentViewable\Contracts\Viewable;
use CyrildeWit\EloquentViewable\InteractsWithViews;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\Sluggable\HasSlug;
class Product extends Model implements HasMedia, Viewable
{
/**
* Has factory (laravel default)
*/
use HasFactory;
/**
* Has Schemaless Attributes (spatie/laravel-schemaless-attributes)
*/
use HasSchemalessAttributes;
/**
* Has Slug (spatie/laravel-sluggable)
*/
use HasSlug;
/**
* Interacts with media (spatie/laravel-medialibrary)
*/
use InteractsWithMedia;
/**
* Interacts with views (cyrildewit/eloquent-viewable)
*/
use InteractsWithViews;
/**
* Product helpers for front end
*/
use ProductFrontEndHelpers;
/**
* Media interactions
*/
use ProductMedia {
ProductMedia::registerMediaCollections insteadof InteractsWithMedia;
ProductMedia::registerMediaConversions insteadof InteractsWithMedia;
}
/**
* Product properties (attributes[EAV])
*/
use ProductProperties;
/**
* Product relationships
*/
use ProductRelationships;
/**
* Product scope queries
*/
use ProductScopeQueries;
/**
* Product states (new, featured, ...)
*/
use ProductStates;
/**
* Products stocks
*/
use ProductStock;
/**
* Interacts with media (whitecube/nova-flexible-content)
*/
// use HasFlexible;
/**
* Product Data Transformers
*/
use ProductTransformers;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'id',
'parent_id',
'brand_id',
'name',
'slug',
'description',
'sku',
'barcode',
'stock',
'security_stock',
'cost_amount',
'price_amount',
'old_price_amount',
'seo_title',
'seo_description',
'options',
'is_visible',
'colour',
'size',
];
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Models\Ecommerce\Product\Property;
use App\Models\Ecommerce\Product\Category\Category;
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 Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
class Attribute extends Model implements Sortable
{
use HasFactory;
use HasSlug;
use HasTranslations;
use SortableTrait;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'name',
'slug',
'description',
'type',
'is_visible',
'is_required',
'is_searchable',
'is_filterable',
'sort_order',
];
/**
* Translatable fields
*
* @var array<string>
*/
public $translatable = ['name', 'description'];
/**
* Translatable fields
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug');
}
/**
* Attribute values (color: red, black, ...)
*/
public function values(): HasMany
{
return $this->hasMany(AttributeValue::class);
}
/**
* Attribute Category
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
/**
* Attributes categories as belongs-to-many
*/
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
/**
* Return available fields types.
*
* @return array<string, string>
*/
public static function typesFields(): array
{
return [
'text' => __('Text'),
'number' => __('Number'),
'select' => __('Select option'),
];
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Models\Ecommerce\Product\Property;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;
class AttributeValue extends Model implements Sortable
{
use HasFactory;
use HasSlug;
use HasTranslations;
use SortableTrait;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'value',
'key',
'attribute_id',
];
/**
* Translatable fields
*
* @var array<string>
*/
public $translatable = ['value'];
/**
* Translatable fields
*
* @var array<string, mixed>
*/
public $sortable = [
'order_column_name' => 'sort_order',
'sort_when_creating' => true,
];
/**
* Get the options for generating the slug.
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->usingSeparator('_')
->saveSlugsTo('key');
}
/**
* Attribute
*/
public function attribute(): BelongsTo
{
return $this->belongsTo(Attribute::class, 'attribute_id');
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Models\Ecommerce\Product\Property;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductAttributeValue extends Model
{
use HasFactory;
/**
* Table name
*/
protected $table = 'attribute_value_product_attribute';
/**
* Indicates if the model should be timestamped.
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'product_attribute_id',
'attribute_value_id',
'product_custom_value',
];
/**
* The relations to eager load on every query.
*/
protected $with = ['value'];
/**
* The accessors to append to the model's array form.
*/
protected $appends = ['real_value'];
/**
* Return exact product attribute value.
*
* @return string|int
*/
public function realValue(): Attribute
{
return Attribute::make(
get: fn () => $this->product_custom_value ?: ($this->value?->value ?? '')
);
}
/**
* Attribute Value for forms
*/
public function valueForForms(): null|int|string
{
return $this->product_custom_value ?: $this->attribute_value_id;
}
/**
* Value
*/
public function value(): BelongsTo
{
return $this->belongsTo(AttributeValue::class, 'attribute_value_id');
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Models\Ecommerce\Product\Property;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class ProductProperty extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'product_attributes';
/**
* Indicates if the model should be timestamped.
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'product_id',
'attribute_id',
];
/**
* The relations to eager load on every query.
*/
protected $with = [
'attribute',
'values',
];
/**
* Attribute
*/
public function attribute(): BelongsTo
{
return $this->belongsTo(Attribute::class, 'attribute_id');
}
/**
* Product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class, 'product_id');
}
/**
* Property values
*/
public function values(): HasMany
{
return $this->hasMany(ProductAttributeValue::class, 'product_attribute_id');
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Models\Ecommerce\Product\Review;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Review extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'user_id',
'product_id',
'rating',
'title',
'content',
'is_visible',
'is_recommended',
'source',
];
/**
* Author
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Product
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}