wip
This commit is contained in:
169
app/Models/Ecommerce/Product/Brand/Brand.php
Normal file
169
app/Models/Ecommerce/Product/Brand/Brand.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
46
app/Models/Ecommerce/Product/Cart/CartItem.php
Normal file
46
app/Models/Ecommerce/Product/Cart/CartItem.php
Normal 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);
|
||||
}
|
||||
}
|
||||
205
app/Models/Ecommerce/Product/Category/Category.php
Normal file
205
app/Models/Ecommerce/Product/Category/Category.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
124
app/Models/Ecommerce/Product/Collection/Collection.php
Normal file
124
app/Models/Ecommerce/Product/Collection/Collection.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
51
app/Models/Ecommerce/Product/Coupon/Coupon.php
Normal file
51
app/Models/Ecommerce/Product/Coupon/Coupon.php
Normal 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;
|
||||
}
|
||||
}
|
||||
45
app/Models/Ecommerce/Product/Favorite/Favorite.php
Normal file
45
app/Models/Ecommerce/Product/Favorite/Favorite.php
Normal 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);
|
||||
}
|
||||
}
|
||||
89
app/Models/Ecommerce/Product/Inventory/Inventory.php
Normal file
89
app/Models/Ecommerce/Product/Inventory/Inventory.php
Normal 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');
|
||||
}
|
||||
}
|
||||
61
app/Models/Ecommerce/Product/Inventory/InventoryHistory.php
Normal file
61
app/Models/Ecommerce/Product/Inventory/InventoryHistory.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
30
app/Models/Ecommerce/Product/Order/Concerns/HasPayments.php
Normal file
30
app/Models/Ecommerce/Product/Order/Concerns/HasPayments.php
Normal 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;
|
||||
}
|
||||
}
|
||||
32
app/Models/Ecommerce/Product/Order/Concerns/HasShipping.php
Normal file
32
app/Models/Ecommerce/Product/Order/Concerns/HasShipping.php
Normal 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);
|
||||
}
|
||||
}
|
||||
72
app/Models/Ecommerce/Product/Order/Concerns/HasStatus.php
Normal file
72
app/Models/Ecommerce/Product/Order/Concerns/HasStatus.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
87
app/Models/Ecommerce/Product/Order/Order.php
Normal file
87
app/Models/Ecommerce/Product/Order/Order.php
Normal 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');
|
||||
}
|
||||
}
|
||||
64
app/Models/Ecommerce/Product/Order/OrderItem.php
Normal file
64
app/Models/Ecommerce/Product/Order/OrderItem.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
88
app/Models/Ecommerce/Product/Order/Payment/OrderPayment.php
Normal file
88
app/Models/Ecommerce/Product/Order/Payment/OrderPayment.php
Normal 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;
|
||||
}
|
||||
}
|
||||
201
app/Models/Ecommerce/Product/Order/Shipping/OrderShipping.php
Normal file
201
app/Models/Ecommerce/Product/Order/Shipping/OrderShipping.php
Normal 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;
|
||||
}
|
||||
}
|
||||
89
app/Models/Ecommerce/Product/Order/Status/OrderStatus.php
Normal file
89
app/Models/Ecommerce/Product/Order/Status/OrderStatus.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
// );
|
||||
// }
|
||||
}
|
||||
121
app/Models/Ecommerce/Product/Product/Product.php
Normal file
121
app/Models/Ecommerce/Product/Product/Product.php
Normal 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',
|
||||
];
|
||||
}
|
||||
103
app/Models/Ecommerce/Product/Property/Attribute.php
Normal file
103
app/Models/Ecommerce/Product/Property/Attribute.php
Normal 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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
65
app/Models/Ecommerce/Product/Property/AttributeValue.php
Normal file
65
app/Models/Ecommerce/Product/Property/AttributeValue.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
65
app/Models/Ecommerce/Product/Property/ProductProperty.php
Normal file
65
app/Models/Ecommerce/Product/Property/ProductProperty.php
Normal 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');
|
||||
}
|
||||
}
|
||||
44
app/Models/Ecommerce/Product/Review/Review.php
Normal file
44
app/Models/Ecommerce/Product/Review/Review.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user