wip
This commit is contained in:
23
app/Repositories/Ecommerce/Channel/ChannelRepository.php
Normal file
23
app/Repositories/Ecommerce/Channel/ChannelRepository.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Channel;
|
||||
|
||||
use App\Models\Ecommerce\Channel\Channel;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ChannelRepository
|
||||
{
|
||||
public const CHANNEL_CACHE_NAME_FOR_NOVA = 'cs-nova-models-channels';
|
||||
|
||||
/**
|
||||
* Channels
|
||||
*/
|
||||
public static function values(): array|Collection
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: self::CHANNEL_CACHE_NAME_FOR_NOVA,
|
||||
value: fn () => Channel::where('is_visible', true)->pluck('name', 'id')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Collection;
|
||||
|
||||
use App\Models\Ecommerce\Product\Collection\Collection as CollectionModel;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CollectionRepository
|
||||
{
|
||||
/**
|
||||
* Channels
|
||||
*/
|
||||
public static function values(): array|Collection
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: 'cs-nova-models-collections',
|
||||
value: fn () => CollectionModel::where('is_visible', true)->pluck('name', 'id'),
|
||||
);
|
||||
}
|
||||
}
|
||||
57
app/Repositories/Ecommerce/Filter/FilterParamRepository.php
Normal file
57
app/Repositories/Ecommerce/Filter/FilterParamRepository.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Filter;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Makeable;
|
||||
|
||||
class FilterParamRepository
|
||||
{
|
||||
use Makeable;
|
||||
|
||||
/**
|
||||
* Validate
|
||||
*/
|
||||
protected bool $validationPassed = true;
|
||||
|
||||
/**
|
||||
* Request object to work with
|
||||
*/
|
||||
protected Request $request;
|
||||
|
||||
/**
|
||||
* Filter Repo
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
if ($this->validateRequest()->fails()) {
|
||||
$this->validationPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response should be filtered by collection
|
||||
*/
|
||||
public function shouldFilterByCategory(): bool
|
||||
{
|
||||
return $this->validationPassed && $this->request->filled('category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response should be filtered by collection
|
||||
*/
|
||||
public function shouldFilterByCollection(): bool
|
||||
{
|
||||
return $this->validationPassed && $this->request->filled('collection_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response should be filtered by collection
|
||||
*/
|
||||
public function shouldFilterByBrand(): bool
|
||||
{
|
||||
return $this->validationPassed && $this->request->filled('brand_id');
|
||||
}
|
||||
}
|
||||
40
app/Repositories/Ecommerce/Order/NovaOrderRepository.php
Normal file
40
app/Repositories/Ecommerce/Order/NovaOrderRepository.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Order;
|
||||
|
||||
use Closure;
|
||||
|
||||
class NovaOrderRepository
|
||||
{
|
||||
/**
|
||||
* Mask value
|
||||
*
|
||||
* @param string $attribute
|
||||
*/
|
||||
public static function mask($resource, $attribute): Closure
|
||||
{
|
||||
return fn () => $resource->{$attribute} ? mask_phone($resource->{$attribute}) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill form with masking value mask
|
||||
*/
|
||||
public static function fillUnmasked(): Closure
|
||||
{
|
||||
return function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = unmask_phone($request->input($attribute));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on
|
||||
*/
|
||||
public static function dependsOnWhere(string $attribute, string $model): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) use ($attribute, $model) {
|
||||
if ($formData->{$attribute}) {
|
||||
$field->options($model::where('region', $formData->{$attribute})->pluck('name', 'id'));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
99
app/Repositories/Ecommerce/Order/OrderRepository.php
Normal file
99
app/Repositories/Ecommerce/Order/OrderRepository.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Order;
|
||||
|
||||
use App\Events\Ecommerce\Product\Order\OrderCreated;
|
||||
use App\Models\Ecommerce\Product\Order\Order;
|
||||
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OrderRepository
|
||||
{
|
||||
/**
|
||||
* Order repo
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $data = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create new order
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$order = Order::create($this->data);
|
||||
|
||||
auth()->user()->carts()->with(['product' => [
|
||||
'media',
|
||||
'channels',
|
||||
]])->get()->each(function ($cart) use ($order) {
|
||||
DB::table('order_items')->insert([
|
||||
'product_name' => $cart->product->name,
|
||||
'product_id' => $cart->product_id,
|
||||
'order_id' => $order->id,
|
||||
'channel_id' => $cart->product->channels->first()?->id ?? tmpostChannel()->id,
|
||||
'quantity' => $cart->product_quantity,
|
||||
'unit_price_amount' => $cart->product->price_amount,
|
||||
'unit_cost_amount' => $cart->product->cost_amount,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$cart->product->update([
|
||||
'stock' => $cart->product->stock - $cart->product_quantity,
|
||||
]);
|
||||
});
|
||||
|
||||
auth()->user()->carts()->delete();
|
||||
|
||||
OrderCreated::dispatch($order);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available times
|
||||
*/
|
||||
public static function availableTimes(): array
|
||||
{
|
||||
$today = now()->format('d.m.Y');
|
||||
$tomorrow = now()->addHour(24)->format('d.m.Y');
|
||||
$times = [
|
||||
'today' => [],
|
||||
'tomorrow' => [],
|
||||
];
|
||||
|
||||
if (strtotime('now') <= strtotime('09:00')) {
|
||||
$times['today'][] = [
|
||||
'date' => $today,
|
||||
'hour' => OrderShipping::MORNING,
|
||||
];
|
||||
}
|
||||
|
||||
if (strtotime('now') <= strtotime('13:00')) {
|
||||
$times['today'][] = [
|
||||
'date' => $today,
|
||||
'hour' => OrderShipping::EVENING,
|
||||
];
|
||||
}
|
||||
|
||||
$times['tomorrow'] = [
|
||||
[
|
||||
'date' => $tomorrow,
|
||||
'hour' => OrderShipping::MORNING,
|
||||
],
|
||||
[
|
||||
'date' => $tomorrow,
|
||||
'hour' => OrderShipping::EVENING,
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
'dates' => [
|
||||
'today' => $today,
|
||||
'tomorrow' => $tomorrow,
|
||||
],
|
||||
'hours' => $times,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product\Barcode;
|
||||
|
||||
class BarcodeRepository
|
||||
{
|
||||
/**
|
||||
* Default barcode generator
|
||||
*/
|
||||
public static function generateBarcode(): int|string|float
|
||||
{
|
||||
return rand(1000000000, 9999999999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random 12-digit number.
|
||||
*/
|
||||
public static function generateRandomNumber(int $length = 12): string
|
||||
{
|
||||
return collect(range(1, $length))
|
||||
->map(fn () => random_int(0, 9))
|
||||
->join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate EAN-13 checksum for a given number.
|
||||
*/
|
||||
public static function calculateEAN13Checksum(string $number): int
|
||||
{
|
||||
$sum = collect(str_split($number))
|
||||
->take(12)
|
||||
->reduce(
|
||||
callback: fn ($carry, $item, $key) => $carry + ($key % 2 === 0 ? $item : $item * 3),
|
||||
initial: 0
|
||||
);
|
||||
|
||||
return (10 - ($sum % 10)) % 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a complete EAN-13 barcode number.
|
||||
*/
|
||||
public static function generateEan13Number(): string
|
||||
{
|
||||
$randomNumber = static::generateRandomNumber();
|
||||
|
||||
return $randomNumber.static::calculateEAN13Checksum($randomNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generator route
|
||||
*/
|
||||
public static function imageGeneratorRoute(): string
|
||||
{
|
||||
return route('barcode-generator');
|
||||
}
|
||||
}
|
||||
76
app/Repositories/Ecommerce/Product/Brand/BrandRepository.php
Normal file
76
app/Repositories/Ecommerce/Product/Brand/BrandRepository.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product\Brand;
|
||||
|
||||
use App\Models\Ecommerce\Product\Brand\Brand;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BrandRepository
|
||||
{
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* Query Builder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* Related to nova ?
|
||||
*/
|
||||
private bool $nova;
|
||||
|
||||
/**
|
||||
* @param bool|bool $nova
|
||||
*/
|
||||
public function __construct(Brand $model, bool $nova = false)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->nova = $nova;
|
||||
|
||||
$this->queryBuilder = Brand::query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Related to Nova
|
||||
*/
|
||||
public static function nova(): self
|
||||
{
|
||||
return new self(model: (new Brand), nova: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels
|
||||
*/
|
||||
public static function values(): array|Collection
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: 'cs-nova-models-brands',
|
||||
value: fn () => Brand::where('is_visible', true)
|
||||
->pluck('name', 'id')
|
||||
->prepend(sprintf('------- %s -------', __('No')), '')
|
||||
);
|
||||
}
|
||||
|
||||
public function filterBy($resource)
|
||||
{
|
||||
if (get_class($resource) === config('ecommerce.models.brand')) {
|
||||
return [$resource];
|
||||
}
|
||||
|
||||
if (in_array(get_class($resource), [
|
||||
config('ecommerce.models.brand.collection'),
|
||||
config('ecommerce.models.brand.category'),
|
||||
])) {
|
||||
return $this->queryBuilder->whereIntegerInRaw(
|
||||
column: 'id',
|
||||
values: $resource->products()->distinct('brand_id')->pluck('brand_id')
|
||||
)->get(['id', 'name']);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product\Category;
|
||||
|
||||
use App\Models\Ecommerce\Product\Category\Category;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CategoryRepository
|
||||
{
|
||||
/**
|
||||
* $queryBuilder
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected mixed $queryBuilder;
|
||||
|
||||
/**
|
||||
* $request
|
||||
*/
|
||||
protected Request $request;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
public static function model(): string
|
||||
{
|
||||
return config('ecommerce.models.category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Product repository
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->queryBuilder = self::model()::query();
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new class via static method "make"
|
||||
*/
|
||||
public static function make(Request $request): self
|
||||
{
|
||||
return new self($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Basic queries
|
||||
*/
|
||||
public function applyBasicQueries(): self
|
||||
{
|
||||
$this->queryBuilder->with('media')->enabled()->ordered();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters
|
||||
*/
|
||||
public function applyFilters(): self
|
||||
{
|
||||
if ($this->request->filled('type')) {
|
||||
$this->filterByType();
|
||||
}
|
||||
|
||||
if ($this->request->filled('group')) {
|
||||
$this->queryBuilder->where('type', $this->request->group);
|
||||
} else {
|
||||
$this->queryBuilder->where('type', '!=', 'market')->whereNot('slug', 'market');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by type
|
||||
*/
|
||||
public function filterByType(): self
|
||||
{
|
||||
match ($this->request->type) {
|
||||
'root' => $this->queryBuilder->where('parent_id', null),
|
||||
'tree' => $this->queryBuilder->tree(),
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$data = $this->queryBuilder->get();
|
||||
|
||||
if ($this->request->type === 'tree') {
|
||||
$data = $data->toTree();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names for laravel nova
|
||||
*/
|
||||
public static function getNamesForNova(): array
|
||||
{
|
||||
$names = [];
|
||||
$categories = static::model()::with('children')->where('parent_id', null)->get(['id', 'slug', 'name', 'parent_id', 'is_visible']);
|
||||
|
||||
// Empty space for optional
|
||||
$names[null] = __('Select');
|
||||
foreach ($categories as $category) {
|
||||
$names[$category->id] = $category->name;
|
||||
|
||||
foreach ($category->children as $child) {
|
||||
$names[$child->id] = $category->name.' / '.$child->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Names with taxes (usefull for nova)
|
||||
*/
|
||||
public static function namesWithTaxes(): array
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: 'cs-nova-models-categories',
|
||||
value: fn () => static::maskParentName(
|
||||
Category::tree()
|
||||
->where('is_visible', true)
|
||||
->get(['id', 'slug', 'name', 'tax_percentage', 'parent_id', 'is_visible'])
|
||||
->toTree()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Names with taxes
|
||||
*/
|
||||
public static function maskParentName($categories): array
|
||||
{
|
||||
$names = [];
|
||||
|
||||
foreach ($categories as $category) {
|
||||
// Ignore if has parent
|
||||
if ($category->parent_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names[$category->id] = sprintf('%s - %s%%', $category->name, $category->tax_percentage);
|
||||
|
||||
foreach ($category->children as $child) {
|
||||
$names[$child->id] = sprintf('%s / %s - %s%%', $category->name, $child->name, $child->tax_percentage);
|
||||
|
||||
foreach ($child->children as $grandChild) {
|
||||
$names[$grandChild->id] = sprintf('%s / %s / %s - %s%%', $category->name, $child->name, $grandChild->name, $child->tax_percentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by given "resource"
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $resource
|
||||
*/
|
||||
public function filterBy($resource)
|
||||
{
|
||||
if (get_class($resource) === config('ecommerce.models.category')) {
|
||||
return $resource->children()->get(['id', 'parent_id', 'slug', 'name']);
|
||||
}
|
||||
|
||||
if (in_array(get_class($resource), [
|
||||
config('ecommerce.models.collection'),
|
||||
config('ecommerce.models.brand'),
|
||||
])) {
|
||||
$products = $resource->products()->distinct('products.id')->pluck('products.id');
|
||||
|
||||
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
|
||||
->where('product_has_relations.productable_type', '=', 'category')
|
||||
->whereIntegerInRaw('product_has_relations.product_id', $products)
|
||||
->distinct('categories.id')
|
||||
->get(['id', 'slug', 'name']);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
253
app/Repositories/Ecommerce/Product/NovaProductRepository.php
Normal file
253
app/Repositories/Ecommerce/Product/NovaProductRepository.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product;
|
||||
|
||||
use App\Models\Ecommerce\Product\Inventory\Inventory;
|
||||
use App\Models\Ecommerce\Product\Product\Product;
|
||||
use App\Models\Ecommerce\Product\Property\Attribute;
|
||||
use App\Models\Ecommerce\Product\Property\ProductAttributeValue;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NovaProductRepository
|
||||
{
|
||||
/**
|
||||
* inventory dependsOn
|
||||
*/
|
||||
public static function inventoryDependsOn(string $resource_attribute): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) {
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isEntrepreneur()) {
|
||||
$field->options(Inventory::where(function ($query) use ($user) {
|
||||
$query
|
||||
->where('channel_id', $user->channel()->id)
|
||||
->orWhere('shareable', true);
|
||||
})->pluck('name', 'id'));
|
||||
}
|
||||
|
||||
if ($user->isAdmin()) {
|
||||
$field->options(Inventory::pluck('name', 'id'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Price amount dependsOn
|
||||
*/
|
||||
public static function priceAmountDependsOn($categories = []): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) use ($categories) {
|
||||
if (! $formData->cost_amount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($categories) > 0) {
|
||||
static::setPriceAmount($field, $categories, $formData->cost_amount);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($formData->categories && is_array($formData->categories)) {
|
||||
static::setPriceAmount($field, $formData->categories, $formData->cost_amount);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request->isNotFilled('categories')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For update request, if there are already categories, tackle it
|
||||
$categories = is_string($request->input('categories'))
|
||||
? json_decode($request->input('categories'))
|
||||
: $request->input('categories');
|
||||
|
||||
if (! empty($categories)) {
|
||||
static::setPriceAmount($field, $categories, $formData->cost_amount);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes Depends On (create request)
|
||||
*/
|
||||
public static function createRequestAttributesDependsOn($resource): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) {
|
||||
$categories = is_string($formData->categories)
|
||||
? json_decode($formData->categories)
|
||||
: $formData->categories;
|
||||
|
||||
if (! $categories) {
|
||||
return;
|
||||
}
|
||||
|
||||
session(['product_categories' => $categories]);
|
||||
|
||||
$field->fields(static::attributeFields($categories));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes Depends On (update request)
|
||||
*/
|
||||
public static function updateRequestAttributesDependsOn($resource): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) use ($resource) {
|
||||
$categories = is_string($formData->categories)
|
||||
? json_decode($formData->categories)
|
||||
: $formData->categories;
|
||||
|
||||
$field->fields(
|
||||
static::attributeFieldsWithValues($categories, $resource)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create request variations depends on
|
||||
*/
|
||||
public static function createRequestVariationsDependsOn(): Closure
|
||||
{
|
||||
return function ($field, $request, $formData) {
|
||||
$categories = is_string($formData->categories)
|
||||
? json_decode($formData->categories)
|
||||
: $formData->categories;
|
||||
|
||||
if (! $categories) {
|
||||
return;
|
||||
}
|
||||
|
||||
$field->show();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field's price amount
|
||||
*
|
||||
* @param array $categories
|
||||
* @param int|float|string $cost_amount
|
||||
*/
|
||||
public static function setPriceAmount($field, array|Collection $categories, $cost_amount): void
|
||||
{
|
||||
$tax = productTaxForCategory($categories);
|
||||
|
||||
$field->value = ProductRepository::calculatePrice(
|
||||
floatval($cost_amount),
|
||||
$tax
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute fields
|
||||
*/
|
||||
public static function attributeFields($categories): array
|
||||
{
|
||||
$attribute_categories = DB::table('attribute_category')
|
||||
->whereIntegerInRaw('category_id', $categories)
|
||||
->distinct('attribute_id')
|
||||
->pluck('attribute_id');
|
||||
|
||||
$attributes = Attribute::with('values')
|
||||
->whereIntegerInRaw('id', $attribute_categories)
|
||||
->get()
|
||||
->map(
|
||||
fn ($attribute) => [
|
||||
'label' => $attribute->name,
|
||||
'name' => $attribute->slug,
|
||||
'type' => $attribute->type,
|
||||
'required' => $attribute->is_required,
|
||||
'options' => $attribute->values
|
||||
->map(
|
||||
fn ($value) => [
|
||||
'label' => $value->value,
|
||||
'value' => $value->id,
|
||||
]
|
||||
)
|
||||
->toArray(),
|
||||
]
|
||||
);
|
||||
|
||||
return $attributes->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute fields with values
|
||||
*/
|
||||
public static function attributeFieldsWithValues(
|
||||
$categories,
|
||||
$resource
|
||||
): array {
|
||||
if (is_null($categories) || is_null($resource)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attribute_categories = DB::table('attribute_category')
|
||||
->whereIntegerInRaw('category_id', $categories)
|
||||
->distinct('attribute_id')
|
||||
->pluck('attribute_id');
|
||||
|
||||
$product_attributes = DB::table('product_attributes')
|
||||
->where('product_id', $resource->id)
|
||||
->whereIntegerInRaw('attribute_id', $attribute_categories)
|
||||
->get(['id', 'attribute_id']);
|
||||
|
||||
$product_attribute_values = ProductAttributeValue::whereIntegerInRaw(
|
||||
column: 'product_attribute_id',
|
||||
values: $product_attributes->pluck('id')
|
||||
)->get();
|
||||
|
||||
$attributes = Attribute::with('values')
|
||||
->whereIntegerInRaw('id', $attribute_categories)
|
||||
->get()
|
||||
->map(
|
||||
fn ($attribute) => [
|
||||
'label' => $attribute->name,
|
||||
'name' => $attribute->slug,
|
||||
'type' => $attribute->type,
|
||||
'required' => false,
|
||||
'default' => $product_attribute_values
|
||||
->firstWhere(
|
||||
key: 'product_attribute_id',
|
||||
operator: '=',
|
||||
value: $product_attributes->firstWhere(
|
||||
'attribute_id',
|
||||
'=',
|
||||
$attribute->id
|
||||
)?->id
|
||||
)
|
||||
?->valueForForms(),
|
||||
'options' => $attribute->values
|
||||
->map(
|
||||
fn ($value) => [
|
||||
'label' => $value->value,
|
||||
'value' => $value->id,
|
||||
]
|
||||
)
|
||||
->toArray(),
|
||||
]
|
||||
);
|
||||
|
||||
return $attributes->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: 'cs-nova-models-products',
|
||||
value: fn () => Product::where('products.is_visible', true)
|
||||
->where('products.stock', '>', 0)
|
||||
->pluck('name', 'id')
|
||||
->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
270
app/Repositories/Ecommerce/Product/ProductRepository.php
Normal file
270
app/Repositories/Ecommerce/Product/ProductRepository.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product;
|
||||
|
||||
use App\Helpers\Ecommerce\Product\Filter\ProductFilterer;
|
||||
use App\Helpers\Ecommerce\Product\Sort\ProductSorter;
|
||||
use App\Models\Ecommerce\Product\Product\Product;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductRepository
|
||||
{
|
||||
/**
|
||||
* Product pagination count
|
||||
*/
|
||||
public const PERPAGE = 32;
|
||||
|
||||
/**
|
||||
* Query builder
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected mixed $queryBuilder;
|
||||
|
||||
/**
|
||||
* Application request
|
||||
*/
|
||||
protected Request $request;
|
||||
|
||||
/**
|
||||
* Relationships to eager load
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $with = [];
|
||||
|
||||
/**
|
||||
* Product repository
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->queryBuilder = Product::query();
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new class via static method "make"
|
||||
*/
|
||||
public static function make(Request $request): self
|
||||
{
|
||||
return new self($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update query builder with resource relationship
|
||||
*/
|
||||
public function queryAsFromResource($resource): self
|
||||
{
|
||||
$this->queryBuilder = $resource->products();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach relationships to eager load
|
||||
*/
|
||||
public function attachEagerLoadingRelationship(array $relationships): self
|
||||
{
|
||||
$this->with = array_merge($this->with, $relationships);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load relationships
|
||||
*/
|
||||
public function eagerLoadRelationships(): self
|
||||
{
|
||||
$this->queryBuilder->with($this->with);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Basic queries
|
||||
*/
|
||||
public function applyBasicQueries(): self
|
||||
{
|
||||
$this->attachEagerLoadingRelationship([
|
||||
'brand',
|
||||
'reviews',
|
||||
'media' => function ($query) {
|
||||
$query->orderBy('order_column', 'asc');
|
||||
},
|
||||
]);
|
||||
|
||||
$this->queryBuilder
|
||||
->where('products.is_visible', true)
|
||||
->where('products.parent_id', null)
|
||||
->where('products.stock', '>', 0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters
|
||||
*/
|
||||
public function applyFilters(): self
|
||||
{
|
||||
$this->queryBuilder = ProductFilterer::make($this->queryBuilder, $this->request)->filter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply search queries
|
||||
*/
|
||||
public function applySearchQueries(): self
|
||||
{
|
||||
if (request()->filled('q')) {
|
||||
$searcQuery = str_replace([
|
||||
'\\',
|
||||
'(',
|
||||
')',
|
||||
'[',
|
||||
']',
|
||||
], '', request('q'));
|
||||
|
||||
// Search by name
|
||||
$this->queryBuilder->where('products.name', '~*', $searcQuery);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply sorting
|
||||
*/
|
||||
public function applySorting(): self
|
||||
{
|
||||
$this->queryBuilder = ProductSorter::make($this->queryBuilder, $this->request)->sort();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Where IN" clouse
|
||||
*/
|
||||
public function whereIn(string $attribute, array $value): self
|
||||
{
|
||||
$this->queryBuilder->whereIn($attribute, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* "where integer in raw" clouse
|
||||
*/
|
||||
public function whereIntegerInRaw(string $attribute, array $value): self
|
||||
{
|
||||
$this->queryBuilder->whereIn($attribute, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple pagination
|
||||
*/
|
||||
public function simplePaginate(): Paginator
|
||||
{
|
||||
$this->eagerLoadRelationships();
|
||||
|
||||
return $this->queryBuilder->simplePaginate(
|
||||
perPage: $this->defaultPaginationPerPage(),
|
||||
page: $this->paginationPage(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$this->eagerLoadRelationships();
|
||||
|
||||
return $this->queryBuilder->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Pagination Per Page
|
||||
*/
|
||||
public function defaultPaginationPerPage(): int
|
||||
{
|
||||
return intval(request('perPage')) ?: self::PERPAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination page
|
||||
*/
|
||||
public function paginationPage(): int
|
||||
{
|
||||
return intval($this->request->page) ?: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to manage products on panel
|
||||
*/
|
||||
public static function panelPath(): string
|
||||
{
|
||||
return sprintf('%s/resources/products', config('nova.path'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate price
|
||||
*/
|
||||
public static function calculatePrice(int|float $price, int $tax): float
|
||||
{
|
||||
return round_up(($price / (100 - $tax)) * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shipping attributes
|
||||
*/
|
||||
public static function shippingAttributes(): array
|
||||
{
|
||||
return [
|
||||
'back_order' => false,
|
||||
'weight_value' => null,
|
||||
'weight_unit' => null,
|
||||
'height_value' => null,
|
||||
'height_unit' => null,
|
||||
'width_value' => null,
|
||||
'width_unit' => null,
|
||||
'depth_value' => null,
|
||||
'depth_unit' => null,
|
||||
'volume_value' => null,
|
||||
'volume_unit' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Nova repository
|
||||
*/
|
||||
public static function nova(): NovaProductRepository
|
||||
{
|
||||
return new NovaProductRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanatize File Name
|
||||
*/
|
||||
public static function sanitizeFileName(string $filename): string
|
||||
{
|
||||
return strtolower(str_replace(['#', '/', '\\', ' ', ','], '-', $filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax paginate
|
||||
*/
|
||||
public static function ajaxPaginate($products): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'pagination' => $products,
|
||||
'products' => view('web.themes.trendyol.components.products.collection.just-render', [
|
||||
'products' => $products,
|
||||
])->render(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories\Ecommerce\Product\Property;
|
||||
|
||||
use App\Models\Ecommerce\Product\Property\Attribute;
|
||||
use App\Repositories\System\Cache\CacheRepository;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PropertyRepository
|
||||
{
|
||||
public static function properties(): array|Collection
|
||||
{
|
||||
return CacheRepository::make(
|
||||
name: 'product-properties-with-values',
|
||||
value: fn () => Attribute::with('values')->get(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function getRealValue(string $slug, string|int|float|null $value)
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$attribute = static::properties()->firstWhere('slug', $slug);
|
||||
|
||||
if (! $attribute) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($attribute->type !== 'select') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $attribute->values?->firstWhere('id', $value)?->value;
|
||||
}
|
||||
|
||||
public static function getAvailableColors($product): array
|
||||
{
|
||||
$colors = collect();
|
||||
|
||||
// Add current product color
|
||||
if ($product->colour) {
|
||||
$colors->push([
|
||||
'product_id' => $product->id,
|
||||
'colour' => PropertyRepository::getRealValue('colour', $product->colour),
|
||||
'availabel_sizes' => static::getAvailableSizes($product),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $product->relationLoaded('variations')) {
|
||||
return $colors->toArray();
|
||||
}
|
||||
|
||||
$product->variations->each(function ($variation) use (&$colors, $product) {
|
||||
if ($variation->colour) {
|
||||
$colors->push([
|
||||
'product_id' => $variation->id,
|
||||
'colour' => PropertyRepository::getRealValue('colour', $variation->colour),
|
||||
'availabel_sizes' => static::getAvailableSizes($variation, $product),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return $colors->uniqueStrict('colour')->toArray();
|
||||
}
|
||||
|
||||
public static function getAvailableSizes($product, $parent = null): array
|
||||
{
|
||||
$sizes = collect();
|
||||
|
||||
// Add current product size
|
||||
if ($product->size) {
|
||||
$sizes->push([
|
||||
'product_id' => $product->id,
|
||||
'size' => PropertyRepository::getRealValue('size', $product->size),
|
||||
]);
|
||||
}
|
||||
|
||||
$product = $parent ?: $product;
|
||||
|
||||
if (! $product->relationLoaded('variations')) {
|
||||
return $sizes->toArray();
|
||||
}
|
||||
|
||||
$product->variations->each(function ($variation) use (&$sizes) {
|
||||
if ($variation->size) {
|
||||
$sizes->push([
|
||||
'product_id' => $variation->id,
|
||||
'size' => PropertyRepository::getRealValue('size', $variation->size),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return $sizes->uniqueStrict('size')->toArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user