¨4.0.1¨
This commit is contained in:
94
Modules/Product/Entities/Concerns/EloquentRelations.php
Normal file
94
Modules/Product/Entities/Concerns/EloquentRelations.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
use Modules\Tag\Entities\Tag;
|
||||
use Modules\Brand\Entities\Brand;
|
||||
use Modules\Tax\Entities\TaxClass;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Review\Entities\Review;
|
||||
use Modules\Category\Entities\Category;
|
||||
use Modules\Variation\Entities\Variation;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
use Modules\Attribute\Entities\ProductAttribute;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
trait EloquentRelations
|
||||
{
|
||||
public function brand(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Brand::class)->withDefault();
|
||||
}
|
||||
|
||||
|
||||
public function tags(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Tag::class, 'product_tags');
|
||||
}
|
||||
|
||||
|
||||
public function categories(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Category::class, 'product_categories');
|
||||
}
|
||||
|
||||
|
||||
public function taxClass(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TaxClass::class)->withDefault();
|
||||
}
|
||||
|
||||
|
||||
public function reviews(): HasMany
|
||||
{
|
||||
return $this->hasMany(Review::class);
|
||||
}
|
||||
|
||||
|
||||
public function attributes(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProductAttribute::class);
|
||||
}
|
||||
|
||||
|
||||
public function variations(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Variation::class, 'product_variations')
|
||||
->orderBy('position')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
public function variants(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProductVariant::class, 'product_id');
|
||||
}
|
||||
|
||||
|
||||
public function options(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Option::class, 'product_options')
|
||||
->orderBy('position')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
public function relatedProducts(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(static::class, 'related_products', 'product_id', 'related_product_id');
|
||||
}
|
||||
|
||||
|
||||
public function upSellProducts(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(static::class, 'up_sell_products', 'product_id', 'up_sell_product_id');
|
||||
}
|
||||
|
||||
|
||||
public function crossSellProducts(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(static::class, 'cross_sell_products', 'product_id', 'cross_sell_product_id');
|
||||
}
|
||||
}
|
||||
11
Modules/Product/Entities/Concerns/Filterable.php
Normal file
11
Modules/Product/Entities/Concerns/Filterable.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
trait Filterable
|
||||
{
|
||||
public function filter($filter)
|
||||
{
|
||||
return $filter->apply($this);
|
||||
}
|
||||
}
|
||||
78
Modules/Product/Entities/Concerns/HasSpecialPrice.php
Normal file
78
Modules/Product/Entities/Concerns/HasSpecialPrice.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
use Modules\Support\Money;
|
||||
|
||||
trait HasSpecialPrice
|
||||
{
|
||||
public function getSpecialPrice(): Money
|
||||
{
|
||||
$specialPrice = $this->attributes['special_price'];
|
||||
|
||||
if ($this->special_price_type === 'percent') {
|
||||
$discountedPrice = ($specialPrice / 100) * $this->attributes['price'];
|
||||
|
||||
$specialPrice = $this->attributes['price'] - $discountedPrice;
|
||||
}
|
||||
|
||||
if ($specialPrice < 0) {
|
||||
$specialPrice = 0;
|
||||
}
|
||||
|
||||
return Money::inDefaultCurrency($specialPrice);
|
||||
}
|
||||
|
||||
|
||||
public function hasSpecialPrice(): bool
|
||||
{
|
||||
if (is_null($this->special_price)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasSpecialPriceStartDate() && $this->hasSpecialPriceEndDate()) {
|
||||
return $this->specialPriceStartDateIsValid() && $this->specialPriceEndDateIsValid();
|
||||
}
|
||||
|
||||
if ($this->hasSpecialPriceStartDate()) {
|
||||
return $this->specialPriceStartDateIsValid();
|
||||
}
|
||||
|
||||
if ($this->hasSpecialPriceEndDate()) {
|
||||
return $this->specialPriceEndDateIsValid();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function hasPercentageSpecialPrice(): bool
|
||||
{
|
||||
return $this->hasSpecialPrice() && $this->special_price_type === 'percent';
|
||||
}
|
||||
|
||||
|
||||
private function hasSpecialPriceStartDate(): bool
|
||||
{
|
||||
return !is_null($this->special_price_start);
|
||||
}
|
||||
|
||||
|
||||
private function hasSpecialPriceEndDate(): bool
|
||||
{
|
||||
return !is_null($this->special_price_end);
|
||||
}
|
||||
|
||||
|
||||
private function specialPriceStartDateIsValid(): bool
|
||||
{
|
||||
return today() >= $this->special_price_start;
|
||||
}
|
||||
|
||||
|
||||
private function specialPriceEndDateIsValid(): bool
|
||||
{
|
||||
return today() <= $this->special_price_end;
|
||||
}
|
||||
|
||||
}
|
||||
59
Modules/Product/Entities/Concerns/HasStock.php
Normal file
59
Modules/Product/Entities/Concerns/HasStock.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
use Modules\FlashSale\Entities\FlashSale;
|
||||
|
||||
trait HasStock
|
||||
{
|
||||
public function isOutOfStock(): bool
|
||||
{
|
||||
return !$this->isInStock();
|
||||
}
|
||||
|
||||
|
||||
public function isInStock()
|
||||
{
|
||||
if (FlashSale::contains($this)) {
|
||||
return FlashSale::remainingQty($this) > 0;
|
||||
}
|
||||
if ($this->hasAnyVariants()) {
|
||||
$productWithStock = $this->variants()
|
||||
->where(function ($query) {
|
||||
$query->where(
|
||||
[
|
||||
['manage_stock', true],
|
||||
['qty', '>', 0],
|
||||
]);
|
||||
|
||||
$query->orWhere('manage_stock', false);
|
||||
})
|
||||
->where('in_stock', true)
|
||||
->first();
|
||||
|
||||
return (bool)$productWithStock;
|
||||
} else {
|
||||
if ($this->manage_stock && $this->qty === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->in_stock;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function markAsInStock(): void
|
||||
{
|
||||
$this->withoutEvents(function () {
|
||||
$this->update(['in_stock' => true]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function markAsOutOfStock(): void
|
||||
{
|
||||
$this->withoutEvents(function () {
|
||||
$this->update(['in_stock' => false]);
|
||||
});
|
||||
}
|
||||
}
|
||||
47
Modules/Product/Entities/Concerns/IsNew.php
Normal file
47
Modules/Product/Entities/Concerns/IsNew.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
trait IsNew
|
||||
{
|
||||
public function isNew(): bool
|
||||
{
|
||||
if ($this->hasNewFromDate() && $this->hasNewToDate()) {
|
||||
return $this->newFromDateIsValid() && $this->newToDateIsValid();
|
||||
}
|
||||
|
||||
if ($this->hasNewFromDate()) {
|
||||
return $this->newFromDateIsValid();
|
||||
}
|
||||
|
||||
if ($this->hasNewToDate()) {
|
||||
return $this->newToDateIsValid();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function hasNewFromDate(): bool
|
||||
{
|
||||
return !is_null($this->new_from);
|
||||
}
|
||||
|
||||
|
||||
private function hasNewToDate(): bool
|
||||
{
|
||||
return !is_null($this->new_to);
|
||||
}
|
||||
|
||||
|
||||
private function newFromDateIsValid(): bool
|
||||
{
|
||||
return today() >= $this->new_from;
|
||||
}
|
||||
|
||||
|
||||
private function newToDateIsValid(): bool
|
||||
{
|
||||
return today() <= $this->new_to;
|
||||
}
|
||||
}
|
||||
190
Modules/Product/Entities/Concerns/ModelAccessors.php
Normal file
190
Modules/Product/Entities/Concerns/ModelAccessors.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Modules\Media\Entities\File;
|
||||
use Modules\FlashSale\Entities\FlashSale;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Modules\FlashSale\Entities\FlashSaleProduct;
|
||||
|
||||
trait ModelAccessors
|
||||
{
|
||||
public function getVariantAttribute()
|
||||
{
|
||||
if (request()->query('variant')) {
|
||||
return $this->variants()->where('uid', request()->query('variant'))->first();
|
||||
}
|
||||
|
||||
return $this->variants()->default()->first();
|
||||
}
|
||||
|
||||
|
||||
public function getIsInFlashSaleAttribute()
|
||||
{
|
||||
return FlashSale::contains($this);
|
||||
}
|
||||
|
||||
|
||||
public function getFlashSaleEndDateAttribute()
|
||||
{
|
||||
if (FlashSale::contains($this)) {
|
||||
return FlashSaleProduct::where('product_id', $this->id)->first()?->end_date;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getPriceAttribute($price): Money
|
||||
{
|
||||
return Money::inDefaultCurrency($price);
|
||||
}
|
||||
|
||||
|
||||
public function getFormattedPriceAttribute(): string
|
||||
{
|
||||
return product_price_formatted($this);
|
||||
}
|
||||
|
||||
|
||||
public function getFormattedPriceRangeAttribute(): ?string
|
||||
{
|
||||
if ($this->variants()->exists()) {
|
||||
$minPrice = $this->variants()->min('price');
|
||||
$maxPrice = $this->variants()->max('price');
|
||||
|
||||
if ($minPrice !== $maxPrice) {
|
||||
$formattedMinPriceInCurrentCurrency = Money::inDefaultCurrency($minPrice)->convertToCurrentCurrency()->format();
|
||||
$formattedMaxPriceInCurrentCurrency = Money::inDefaultCurrency($maxPrice)->convertToCurrentCurrency()->format();
|
||||
|
||||
return "$formattedMinPriceInCurrentCurrency - $formattedMaxPriceInCurrentCurrency";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function getSpecialPriceAttribute($specialPrice)
|
||||
{
|
||||
if (!is_null($specialPrice)) {
|
||||
return Money::inDefaultCurrency($specialPrice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getHasPercentageSpecialPriceAttribute(): bool
|
||||
{
|
||||
return $this->hasPercentageSpecialPrice();
|
||||
}
|
||||
|
||||
|
||||
public function getSpecialPricePercentAttribute()
|
||||
{
|
||||
if ($this->hasPercentageSpecialPrice()) {
|
||||
return round($this->special_price->amount(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getSellingPriceAttribute($sellingPrice): Money
|
||||
{
|
||||
if (FlashSale::contains($this)) {
|
||||
$sellingPrice = FlashSale::pivot($this)->price->amount();
|
||||
}
|
||||
|
||||
return Money::inDefaultCurrency($sellingPrice);
|
||||
}
|
||||
|
||||
|
||||
public function getTotalAttribute($total): Money
|
||||
{
|
||||
return Money::inDefaultCurrency($total);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the product's base image.
|
||||
*
|
||||
* @return File
|
||||
*/
|
||||
public function getBaseImageAttribute(): File
|
||||
{
|
||||
return $this->files->where('pivot.zone', 'base_image')->first() ?: new File();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get product's additional images.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAdditionalImagesAttribute(): Collection
|
||||
{
|
||||
return $this->files->where('pivot.zone', 'additional_images')
|
||||
->sortBy('pivot.id');
|
||||
}
|
||||
|
||||
|
||||
public function getMediaAttribute()
|
||||
{
|
||||
return $this->filterFiles(['base_image', 'additional_images'])->get();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get product's downloadable files.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getDownloadsAttribute()
|
||||
{
|
||||
return $this->files
|
||||
->where('pivot.zone', 'downloads')
|
||||
->sortBy('pivot.id')
|
||||
->flatten();
|
||||
}
|
||||
|
||||
|
||||
public function getDoesManageStockAttribute(): bool
|
||||
{
|
||||
return (bool)$this->manage_stock;
|
||||
}
|
||||
|
||||
|
||||
public function getQtyAttribute($qty)
|
||||
{
|
||||
return $qty;
|
||||
}
|
||||
|
||||
|
||||
public function getIsInStockAttribute(): bool
|
||||
{
|
||||
return (bool)$this->isInStock();
|
||||
}
|
||||
|
||||
|
||||
public function getIsOutOfStockAttribute(): bool
|
||||
{
|
||||
return $this->isOutOfStock();
|
||||
}
|
||||
|
||||
|
||||
public function getIsNewAttribute(): bool
|
||||
{
|
||||
return $this->isNew();
|
||||
}
|
||||
|
||||
|
||||
public function getAttributeSetsAttribute()
|
||||
{
|
||||
return $this->getAttribute('attributes')->groupBy('attributeSet');
|
||||
}
|
||||
|
||||
|
||||
public function getRatingPercentAttribute()
|
||||
{
|
||||
if ($this->relationLoaded('reviews')) {
|
||||
return ($this->reviews->avg->rating / 5) * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Modules/Product/Entities/Concerns/Predicates.php
Normal file
40
Modules/Product/Entities/Concerns/Predicates.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
trait Predicates
|
||||
{
|
||||
/**
|
||||
* Is this Product purchased by the current user?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function purchasedByUser(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function hasAnyVariation()
|
||||
{
|
||||
return $this->getAttribute('variations')->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
public function hasAnyVariants(): bool
|
||||
{
|
||||
return $this->getAttribute('variants')->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
public function hasAnyOption(): bool
|
||||
{
|
||||
return $this->getAttribute('options')->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
public function hasAnyAttribute(): bool
|
||||
{
|
||||
return $this->getAttribute('attributes')->isNotEmpty();
|
||||
}
|
||||
}
|
||||
78
Modules/Product/Entities/Concerns/QueryScopes.php
Normal file
78
Modules/Product/Entities/Concerns/QueryScopes.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Product\Entities\Concerns;
|
||||
|
||||
trait QueryScopes
|
||||
{
|
||||
public function scopeForCard($query): void
|
||||
{
|
||||
$query
|
||||
->withName()
|
||||
->withBaseImage()
|
||||
->withPrice()
|
||||
->withCount('options')
|
||||
->with('reviews')
|
||||
->withStock()
|
||||
->withNew()
|
||||
->addSelect(
|
||||
[
|
||||
'products.id',
|
||||
'products.slug',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function scopeWithName($query): void
|
||||
{
|
||||
$query->with('translations:id,product_id,locale,name');
|
||||
}
|
||||
|
||||
|
||||
public function scopeWithStock($query): void
|
||||
{
|
||||
$query->addSelect(
|
||||
[
|
||||
'products.in_stock',
|
||||
'products.manage_stock',
|
||||
'products.qty',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function scopeWithNew($query): void
|
||||
{
|
||||
$query->addSelect(
|
||||
[
|
||||
'products.new_from',
|
||||
'products.new_to',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function scopeWithPrice($query): void
|
||||
{
|
||||
$query->addSelect(
|
||||
[
|
||||
'products.price',
|
||||
'products.special_price',
|
||||
'products.special_price_type',
|
||||
'products.selling_price',
|
||||
'products.special_price_start',
|
||||
'products.special_price_end',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function scopeWithBaseImage($query): void
|
||||
{
|
||||
$query->with([
|
||||
'files' => function ($q) {
|
||||
$q->wherePivot('zone', 'base_image');
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user