This commit is contained in:
2026-02-03 15:31:29 +05:00
commit 326c677e8d
2800 changed files with 1489388 additions and 0 deletions

View File

@@ -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');
}
}

View 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 [];
}
}

View File

@@ -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 [];
}
}

View 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()
);
}
}

View 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(),
]);
}
}

View File

@@ -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();
}
}