wip
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
trait HandlesProductResourceEvents
|
||||
{
|
||||
public static function afterCreate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
ProductFieldsForCreate::afterCreate($request, $model);
|
||||
}
|
||||
|
||||
public static function afterUpdate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
ProductFieldsForUpdate::afterUpdate($request, $model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns;
|
||||
|
||||
use App\Models\Ecommerce\Product\Property\ProductAttributeValue;
|
||||
use App\Models\Ecommerce\Product\Property\ProductProperty;
|
||||
use App\Nova\Forms\NovaForm;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Product as ProductResource;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\ProductVariant as ProductVariantResource;
|
||||
use App\Repositories\Ecommerce\Channel\ChannelRepository;
|
||||
use App\Repositories\Ecommerce\Collection\CollectionRepository;
|
||||
use App\Repositories\Ecommerce\Product\Barcode\BarcodeRepository;
|
||||
use App\Repositories\Ecommerce\Product\Brand\BrandRepository;
|
||||
use App\Repositories\Ecommerce\Product\Category\CategoryRepository;
|
||||
use App\Repositories\Ecommerce\Product\NovaProductRepository;
|
||||
use App\Repositories\Ecommerce\Product\ProductRepository;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\Hidden;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Fields\Trix;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Panel;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
use Nurmuhammet\InlineRelationship\InlineRelationship;
|
||||
use Nurmuhammet\ProductInventory\ProductInventory;
|
||||
use Outl1ne\MultiselectField\Multiselect;
|
||||
|
||||
class ProductFieldsForCreate
|
||||
{
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public static function make(ProductResource $resource, NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
|
||||
new Panel(__('Product relations'), [
|
||||
Hidden::make('is_visible')
|
||||
->default(function () {
|
||||
if (auth()->user()->isEntrepreneur()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
Select::make(__('Channel'), 'channel')
|
||||
->fullWidth()
|
||||
->displayUsingLabels()
|
||||
->searchable()
|
||||
->options(ChannelRepository::values())
|
||||
->default(tmpostChannel()->id)
|
||||
->rules('required')
|
||||
->fillUsing(NovaForm::fillEmpty())
|
||||
->canSeeWhen('isAdmin', $resource),
|
||||
|
||||
Select::make(__('Brand'), 'brand_id')
|
||||
->fullWidth()
|
||||
->displayUsingLabels()
|
||||
->searchable()
|
||||
->options(BrandRepository::values())
|
||||
->rules('nullable'),
|
||||
|
||||
Multiselect::make(__('Category'), 'categories')
|
||||
->fullWidth()
|
||||
->options(CategoryRepository::namesWithTaxes())
|
||||
->help(__('Biggest tax is chosen from categories'))
|
||||
->rules('required')
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Multiselect::make(__('Collection'), 'collections')
|
||||
->fullWidth()
|
||||
->options(CollectionRepository::values())
|
||||
->rules('nullable')
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
]),
|
||||
|
||||
new Panel(__('General information'), [
|
||||
Text::make(__('Ady'), 'name')
|
||||
->fullWidth()
|
||||
->rules('required', 'string', 'max:255'),
|
||||
|
||||
Trix::make(__('Description'), 'description')
|
||||
->fullWidth()
|
||||
->withFiles('public')
|
||||
->rules('nullable', 'string'),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->conversionOnIndexView('thumb200x200')
|
||||
->rules('required')
|
||||
->setFileName(NovaForm::fillMediaFileName())
|
||||
->required(),
|
||||
]),
|
||||
|
||||
new Panel(__('Prices'), [
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->fullWidth()
|
||||
->rules('required', 'numeric'),
|
||||
|
||||
Text::make(__('Price without discount'), 'old_price_amount')
|
||||
->fullWidth()
|
||||
->rules('nullable', 'numeric', 'gt:readonly_price_amount'),
|
||||
|
||||
Text::make(__('Purchase price'), 'readonly_price_amount')
|
||||
->fullWidth()
|
||||
->readonly()
|
||||
->dependsOn(['cost_amount', 'categories'], NovaProductRepository::priceAmountDependsOn()),
|
||||
|
||||
Hidden::make('price_amount')
|
||||
->fillUsing(NovaForm::fillAttribute('price_amount', 'readonly_price_amount')),
|
||||
]),
|
||||
|
||||
new Panel(__('Inventory'), [
|
||||
Text::make('SKU', 'sku')
|
||||
->fullWidth()
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
Boolean::make(__('Generate new barcode'), 'generate_new_barcode')
|
||||
->onlyOnForms()
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Text::make(__('Barcode'), 'barcode')
|
||||
->fullWidth()
|
||||
->rules('nullable', 'string', 'max:255', 'unique:products,barcode')
|
||||
->dependsOn(
|
||||
attributes: 'generate_new_barcode',
|
||||
mixin: fn ($field, $request, $formData) => boolval($formData->generate_new_barcode)
|
||||
? $field->setValue(BarcodeRepository::generateBarcode())
|
||||
: null
|
||||
),
|
||||
|
||||
ProductInventory::make(__('Inventory'), 'inventories')
|
||||
->fullWidth()
|
||||
->rules('required')
|
||||
->dependsOn('channel', NovaProductRepository::inventoryDependsOn('channel'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Hidden::make('stock')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = array_sum(array_values($request->input('inventories')));
|
||||
}),
|
||||
|
||||
Hidden::make('options')->default(json_encode(ProductRepository::shippingAttributes())),
|
||||
]),
|
||||
|
||||
new Panel(__('Attributes'), [
|
||||
DynamicFields::make(__('Attributes'), 'properties')
|
||||
->fullWidth()
|
||||
->fillWithArrayName('properties')
|
||||
->dependsOn(['categories'], NovaProductRepository::createRequestAttributesDependsOn($resource))
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$properties = $request->input('properties') ?? [];
|
||||
|
||||
foreach ($properties as $key => $value) {
|
||||
try {
|
||||
if (in_array($key, ['size', 'colour'])) {
|
||||
$model->{$key} = $value;
|
||||
}
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
}),
|
||||
]),
|
||||
|
||||
new Panel(__('Variations'), [
|
||||
// InlineRelationship::make(__('Variations'), 'variations', ProductVariantResource::class)
|
||||
// ->hide()
|
||||
// ->dependsOn(['categories'], NovaProductRepository::createRequestVariationsDependsOn()),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be called after the resource is created.
|
||||
*/
|
||||
public static function afterCreate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
$model::withoutEvents(function () use ($request, $model) {
|
||||
$user = $request->user();
|
||||
|
||||
$model->categories()->attach($request->input('categories'));
|
||||
$model->collections()->attach($request->input('collections'));
|
||||
|
||||
if ($user->isEntrepreneur()) {
|
||||
$model->channels()->attach($user->channel()->id);
|
||||
} else {
|
||||
$model->channels()->attach($request->input('channel'));
|
||||
}
|
||||
|
||||
$request->collect('inventories')->each(
|
||||
fn ($stockValue, $inventoryID) => $model->inventories()->attach($inventoryID, ['stock' => $stockValue])
|
||||
);
|
||||
|
||||
$properties = $request->input('properties');
|
||||
$attribute_ids = $properties
|
||||
? DB::table('attributes')->whereIn('slug', array_keys($properties))->get(['id', 'type', 'slug'])
|
||||
: collect();
|
||||
|
||||
$attribute_ids->each(function ($attribute) use ($model, $properties) {
|
||||
$productAttribute = ProductProperty::create([
|
||||
'product_id' => $model->id,
|
||||
'attribute_id' => $attribute->id,
|
||||
]);
|
||||
|
||||
ProductAttributeValue::create([
|
||||
'product_attribute_id' => $productAttribute->id,
|
||||
'attribute_value_id' => in_array($attribute->type, ['text', 'number']) ? null : $properties[$attribute->slug],
|
||||
'product_custom_value' => in_array($attribute->type, ['text', 'number']) ? $properties[$attribute->slug] : null,
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns;
|
||||
|
||||
use App\Nova\Resources\Ecommerce\Channel\Channel;
|
||||
use App\Nova\Resources\Ecommerce\Product\Brand\Brand;
|
||||
use App\Nova\Resources\Ecommerce\Product\Category\Category;
|
||||
use App\Nova\Resources\Ecommerce\Product\Collection\Collection;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Product as ProductResource;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\ProductVariant;
|
||||
use App\Nova\Resources\Ecommerce\Product\Review\Review;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\HasMany;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Fields\Trix;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Milon\Barcode\Facades\DNS1DFacade;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
use Nurmuhammet\ProductInventory\ProductInventory;
|
||||
use Outl1ne\MultiselectField\Multiselect;
|
||||
use ShuvroRoy\NovaTabs\Tab;
|
||||
use ShuvroRoy\NovaTabs\Tabs;
|
||||
use Trin4ik\NovaSwitcher\NovaSwitcher;
|
||||
|
||||
class ProductFieldsForDetail
|
||||
{
|
||||
/**
|
||||
* Get the fields displayed by the resource on detail page.
|
||||
*/
|
||||
public static function make(ProductResource $resource, NovaRequest $request): array
|
||||
{
|
||||
// nova has a wierd bug, can load detail fields while on Index
|
||||
if (! $request->isResourceDetailRequest()) {
|
||||
if ($request->viaResource && $request->viaRelationship && $request->relationshipType) {
|
||||
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($request->viaResource === 'inventory-history-resources') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
ID::make(),
|
||||
Text::make(__('Name'), 'name'),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->conversionOnIndexView('thumb200x200'),
|
||||
|
||||
Trix::make(__('Description'), 'description')->withFiles('public')->alwaysShow(),
|
||||
Text::make(__('Price'), 'cost_amount'),
|
||||
Text::make(__('Price For Sale'), 'price_amount'),
|
||||
Text::make(__('Price without discount'), 'old_price_amount'),
|
||||
NovaSwitcher::make(__('Active'), 'is_visible')->canSeeWhen('isAdmin', $resource),
|
||||
|
||||
Tabs::make('Main', [
|
||||
Tab::make(__('Product relations'), [
|
||||
BelongsTo::make(__('Brand'), 'brand', Brand::class),
|
||||
|
||||
Multiselect::make(__('Channel'), 'channels')
|
||||
->belongsToMany(Channel::class)
|
||||
->canSeeWhen('isAdmin', $resource),
|
||||
|
||||
Multiselect::make(__('Category'), 'categories')->belongsToMany(Category::class),
|
||||
Multiselect::make(__('Collection'), 'collections')->belongsToMany(Collection::class),
|
||||
]),
|
||||
Tab::make(__('Inventory'), [
|
||||
ProductInventory::make(
|
||||
name: __('Inventory'),
|
||||
attribute: fn () => $resource->inventories()
|
||||
->get(['inventories.name', 'inventories.id'])
|
||||
->map(fn ($inventory) => [
|
||||
'name' => Str::title($inventory->name),
|
||||
'value' => $inventory->pivot?->stock,
|
||||
])->toArray()
|
||||
)->fullWidth(),
|
||||
|
||||
Text::make('SKU', 'sku'),
|
||||
|
||||
Text::make(__('Barcode'), function () use ($resource) {
|
||||
if (! is_numeric($resource->barcode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return view('vendor.nova.products.barcode', [
|
||||
'barcode' => $resource->barcode,
|
||||
'image' => DNS1DFacade::getBarcodeHTML(
|
||||
code: $resource->barcode,
|
||||
type: config('barcode.default_type'),
|
||||
),
|
||||
])->render();
|
||||
})->asHtml()->showWhen(boolval($resource->barcode)),
|
||||
|
||||
Text::make(__('Stock'), 'stock'),
|
||||
Text::make(__('Security stock'), 'security_stock'),
|
||||
]),
|
||||
Tab::make(__('Shipping'), [
|
||||
Boolean::make(__('Back order'), 'back_order'),
|
||||
|
||||
Number::make(__('Weight value'), 'weight_value'),
|
||||
Select::make(__('Weight unit'), 'weight_unit'),
|
||||
|
||||
Number::make(__('Height value'), 'height_value'),
|
||||
Select::make(__('Height unit'), 'height_unit'),
|
||||
|
||||
Number::make(__('Width value'), 'width_value'),
|
||||
Select::make(__('Width unit'), 'width_unit'),
|
||||
|
||||
Number::make(__('Depth value'), 'depth_value'),
|
||||
Select::make(__('Depth unit'), 'depth_unit'),
|
||||
|
||||
Number::make(__('Volume value'), 'volume_value'),
|
||||
Select::make(__('Volume unit'), 'volume_unit'),
|
||||
]),
|
||||
Tab::make(__('SEO'), [
|
||||
Text::make(__('Seo title'), 'seo_title'),
|
||||
Text::make(__('Seo description'), 'seo_description'),
|
||||
]),
|
||||
]),
|
||||
|
||||
Tabs::make(__('Properties'), [
|
||||
DynamicFields::make('Attributes', 'attributes')
|
||||
->fields(function () use ($resource) {
|
||||
$attributes = $resource->properties()->with(['attribute.values', 'values.value'])->get()->map(fn ($property) => [
|
||||
'label' => $property->attribute->name,
|
||||
'name' => $property->attribute->slug,
|
||||
'type' => $property->attribute->type,
|
||||
'default' => $property->attribute->type === 'select'
|
||||
? $property->values->first()->value?->id
|
||||
: $property->values->first()->product_custom_value,
|
||||
'options' => $property->attribute->values->map(fn ($value) => [
|
||||
'label' => $value->value,
|
||||
'value' => $value->id,
|
||||
])->toArray(),
|
||||
]);
|
||||
|
||||
return $attributes->toArray();
|
||||
}),
|
||||
]),
|
||||
|
||||
HasMany::make(__('Variations'), 'variations', ProductVariant::class),
|
||||
HasMany::make(__('Reviews'), 'reviews', Review::class),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns;
|
||||
|
||||
use App\Nova\Fields\FieldHelpers;
|
||||
use App\Nova\Resources\Ecommerce\Product\Brand\Brand;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Trin4ik\NovaSwitcher\NovaSwitcher;
|
||||
|
||||
class ProductFieldsForIndex
|
||||
{
|
||||
/**
|
||||
* Get the fields for index.
|
||||
*/
|
||||
public static function make(NovaRequest $request, $resource): array
|
||||
{
|
||||
if ($request->filled('search') && is_null($request->filters)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb200x200'),
|
||||
Text::make(__('Name'), 'name')->sortable(),
|
||||
|
||||
BelongsTo::make(__('Brand'), 'brand', Brand::class)
|
||||
->sortable()
|
||||
->searchable()
|
||||
->filterable(),
|
||||
|
||||
Number::make(__('Price'), 'cost_amount')
|
||||
->sortable()
|
||||
->filterable(),
|
||||
|
||||
Text::make(__('SKU'), 'sku')->sortable(),
|
||||
|
||||
Text::make(__('Count'), FieldHelpers::formatQuantity())->asHtml(),
|
||||
|
||||
// For filter
|
||||
Number::make(__('Count'), 'stock')
|
||||
->hideFromIndex()
|
||||
->filterable(),
|
||||
|
||||
Boolean::make(__('Active'), 'is_visible')
|
||||
->sortable()
|
||||
->canSeeWhen('isVendor', $resource),
|
||||
|
||||
NovaSwitcher::make(__('Is visible'), 'is_visible')
|
||||
->sortable()
|
||||
->canSeeWhen('isAdmin', $resource),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns;
|
||||
|
||||
use App\Jobs\Ecommerce\Product\UpdateProductRelations;
|
||||
use App\Nova\Forms\NovaForm;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Product as ProductResource;
|
||||
use App\Repositories\Ecommerce\Channel\ChannelRepository;
|
||||
use App\Repositories\Ecommerce\Collection\CollectionRepository;
|
||||
use App\Repositories\Ecommerce\Product\Barcode\BarcodeRepository;
|
||||
use App\Repositories\Ecommerce\Product\Brand\BrandRepository;
|
||||
use App\Repositories\Ecommerce\Product\Category\CategoryRepository;
|
||||
use App\Repositories\Ecommerce\Product\NovaProductRepository;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\Hidden;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Fields\Trix;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
use Nurmuhammet\ProductInventory\ProductInventory;
|
||||
use Outl1ne\MultiselectField\Multiselect;
|
||||
use ShuvroRoy\NovaTabs\Tab;
|
||||
use ShuvroRoy\NovaTabs\Tabs;
|
||||
|
||||
class ProductFieldsForUpdate
|
||||
{
|
||||
/**
|
||||
* Get the fields displayed by the resource on detail page.
|
||||
*/
|
||||
public static function make(ProductResource $resource, NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ID::make(),
|
||||
|
||||
Text::make(__('Name'), 'name')
|
||||
->rules('required', 'string', 'max:255'),
|
||||
|
||||
Trix::make(__('Description'), 'description')
|
||||
->withFiles('public')
|
||||
->rules('nullable', 'string'),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->required()
|
||||
->rules('required')
|
||||
->showStatistics()
|
||||
->setFileName(fn ($originalFilename, $extension, $model) => sprintf('%s.%s', Str::random(70), $extension)),
|
||||
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->rules('required', 'numeric'),
|
||||
|
||||
Text::make(__('Purchase price'), 'readonly_price_amount')
|
||||
->readonly()
|
||||
->dependsOn(['cost_amount', 'categories'], NovaProductRepository::priceAmountDependsOn()),
|
||||
|
||||
Hidden::make('price_amount')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = $request->input('readonly_price_amount');
|
||||
}),
|
||||
|
||||
Text::make(__('Price without discount'), 'old_price_amount')
|
||||
->rules('nullable', 'gt:readonly_price_amount'),
|
||||
|
||||
Boolean::make(__('Active'), 'is_visible')
|
||||
->canSeeWhen('isAdmin', $resource),
|
||||
|
||||
Tabs::make('Main', [
|
||||
Tab::make(__('Product relations'), [
|
||||
Select::make(__('Brand'), 'brand_id')
|
||||
->displayUsingLabels()
|
||||
->searchable()
|
||||
->options(BrandRepository::values())
|
||||
->rules('nullable'),
|
||||
|
||||
Select::make(__('Channel'), 'channel')
|
||||
->displayUsingLabels()
|
||||
->searchable()
|
||||
->options(ChannelRepository::values())
|
||||
->rules('required')
|
||||
->resolveUsing(fn ($value, $resource, $attribute) => $resource->owner()?->id)
|
||||
->fillUsing(NovaForm::fillEmpty())
|
||||
->canSeeWhen('isAdmin', $resource),
|
||||
|
||||
Multiselect::make(__('Category'), 'categories')
|
||||
->options(CategoryRepository::namesWithTaxes())
|
||||
->help(__('Biggest tax is chosen from categories'))
|
||||
->rules('required')
|
||||
->resolveUsing(fn ($value, $resource, $attribute) => $value->pluck('id'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Multiselect::make(__('Collection'), 'collections')
|
||||
->options(CollectionRepository::values())
|
||||
->rules('nullable')
|
||||
->resolveUsing(fn ($value, $resource, $attribute) => $value->pluck('id'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
]),
|
||||
Tab::make(__('Inventory'), [
|
||||
ProductInventory::make(__('Inventory'), 'inventories')
|
||||
->rules('required')
|
||||
->dependsOn('channel', NovaProductRepository::inventoryDependsOn('channel'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Text::make('SKU', 'sku')
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
Boolean::make(__('Generate new barcode'), 'generate_new_barcode')
|
||||
->onlyOnForms()
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Text::make(__('Barcode'), 'barcode')
|
||||
->rules('nullable', 'string', 'max:255', 'unique:products,barcode,{{resourceId}}')
|
||||
->dependsOn(
|
||||
attributes: 'generate_new_barcode',
|
||||
mixin: function ($field, $request, $formData) use ($resource) {
|
||||
if (boolval($formData->generate_new_barcode)) {
|
||||
$field->setValue(BarcodeRepository::generateBarcode());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$field->setValue($resource->barcode);
|
||||
}
|
||||
),
|
||||
|
||||
Number::make(__('Security stock'), 'security_stock')
|
||||
->rules('nullable', 'integer'),
|
||||
|
||||
Hidden::make('stock')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = array_sum(array_values($request->input('inventories')));
|
||||
}),
|
||||
]),
|
||||
Tab::make(__('Attributes'), [
|
||||
DynamicFields::make('Attributes', 'workingpn')
|
||||
->fillWithArrayName('properties')
|
||||
->dependsOn(['categories'], NovaProductRepository::updateRequestAttributesDependsOn($resource))
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$properties = $request->input('properties');
|
||||
|
||||
if (! $properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($properties as $key => $value) {
|
||||
try {
|
||||
if (in_array($key, ['size', 'colour'])) {
|
||||
$model->{$key} = $value;
|
||||
}
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
}),
|
||||
]),
|
||||
Tab::make(__('Shipping'), [
|
||||
Boolean::make(__('Back order'), 'back_order')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Weight value'), 'weight_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Weight unit'), 'weight_unit')
|
||||
->options(['kg' => 'kg'])
|
||||
->default('kg')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Height value'), 'height_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Height unit'), 'height_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Width value'), 'width_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Width unit'), 'width_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Depth value'), 'depth_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Depth unit'), 'depth_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Volume value'), 'volume_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Volume unit'), 'volume_unit')
|
||||
->options(['kg' => 'kg'])
|
||||
->default('kg')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
]),
|
||||
Tab::make(__('SEO'), [
|
||||
Text::make(__('Seo title'), 'seo_title'),
|
||||
Text::make(__('Seo description'), 'seo_description'),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be called after the resource is updated.
|
||||
*/
|
||||
public static function afterUpdate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
UpdateProductRelations::dispatch(
|
||||
user: $request->user(),
|
||||
model: $model,
|
||||
categories: $request->input('categories'),
|
||||
collections: $request->input('collections'),
|
||||
channel: $request->input('channel'),
|
||||
properties: $request->input('properties'),
|
||||
inventories: $request->collect('inventories')
|
||||
->mapWithKeys(fn ($stockValue, $inventoryID) => [$inventoryID => ['stock' => $stockValue]])
|
||||
->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant;
|
||||
|
||||
use App\Models\Ecommerce\Product\Inventory\Inventory;
|
||||
use App\Models\Ecommerce\Product\Product\Product as ProductModel;
|
||||
use App\Models\Ecommerce\Product\Property\ProductAttributeValue;
|
||||
use App\Models\Ecommerce\Product\Property\ProductProperty;
|
||||
use App\Nova\Forms\NovaForm;
|
||||
use App\Repositories\Ecommerce\Product\NovaProductRepository;
|
||||
use App\Repositories\Ecommerce\Product\ProductRepository;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Nova\Fields\Heading;
|
||||
use Laravel\Nova\Fields\Hidden;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
use Nurmuhammet\ProductInventory\ProductInventory;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
|
||||
class VariantFieldsForCreate
|
||||
{
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public static function make(NovaRequest $request): array
|
||||
{
|
||||
if ($request->viaRelationship()) {
|
||||
$parent = ProductModel::find($request->viaResourceId);
|
||||
$categories = $parent->categories;
|
||||
} else {
|
||||
$parent = null;
|
||||
$categories = session('product_categories') ?? [];
|
||||
}
|
||||
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
|
||||
ProductInventory::make(__('Inventory'), 'inventories')
|
||||
->rules('required')
|
||||
->options(Inventory::pluck('name', 'id'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
DynamicFields::make('Attributes', 'properties')
|
||||
->fillWithArrayName('properties')
|
||||
->fields(fn () => NovaProductRepository::attributeFields($categories))
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$properties = $request->input('properties');
|
||||
|
||||
foreach ($properties as $key => $value) {
|
||||
try {
|
||||
if (in_array($key, ['size', 'colour'])) {
|
||||
$model->{$key} = $value;
|
||||
}
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
Heading::make(__('Leave empty to use original image')),
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->conversionOnIndexView('thumb150x150')
|
||||
->showStatistics()
|
||||
->help(__('Leave empty to use original image')),
|
||||
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->default(optional($parent)->cost_amount)
|
||||
->rules('nullable', 'numeric'),
|
||||
|
||||
Text::make(__('Price without discount'), 'old_price_amount')
|
||||
->default(optional($parent)->old_price_amount)
|
||||
->rules('nullable', 'numeric'),
|
||||
|
||||
Text::make(__('Purchase price'), 'readonly_price_amount')
|
||||
->readonly()
|
||||
->dependsOn(['cost_amount'], NovaProductRepository::priceAmountDependsOn($categories)),
|
||||
|
||||
Hidden::make('price_amount')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = $request->input('readonly_price_amount');
|
||||
}),
|
||||
|
||||
Text::make('SKU', 'sku')
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
Number::make(__('Security stock'), 'security_stock')
|
||||
->rules('nullable', 'integer'),
|
||||
|
||||
Hidden::make('name')
|
||||
->default($parent->name ?? 'inlineCreated'),
|
||||
|
||||
Hidden::make('options')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
if ($request->name === 'inlineCreated') {
|
||||
$model->options->set('inline_created', true);
|
||||
} else {
|
||||
json_encode(ProductRepository::shippingAttributes());
|
||||
}
|
||||
}),
|
||||
|
||||
Hidden::make('stock')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = array_sum(array_values($request->input('inventories')));
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* After resource has been created
|
||||
*/
|
||||
public static function afterCreate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
$model::withoutEvents(function () use ($request, $model) {
|
||||
if ($request->viaRelationship() && $request->missing('__media__')) {
|
||||
$parent = ProductModel::find($request->viaResourceId);
|
||||
|
||||
$parent->media->each(function (Media $media) use ($model) {
|
||||
$model->addMedia($media->getPath())
|
||||
->preservingOriginal()
|
||||
->toMediaCollection($media->collection_name);
|
||||
});
|
||||
}
|
||||
|
||||
$request->collect('inventories')->each(
|
||||
fn ($stockValue, $inventoryID) => $model->inventories()->attach($inventoryID, ['stock' => $stockValue])
|
||||
);
|
||||
|
||||
$properties = $request->input('properties');
|
||||
$attribute_ids = $properties
|
||||
? DB::table('attributes')->whereIn('slug', array_keys($properties))->get(['id', 'type', 'slug'])
|
||||
: collect();
|
||||
|
||||
$attribute_ids->each(function ($attribute) use ($model, $properties) {
|
||||
$productAttribute = ProductProperty::create([
|
||||
'product_id' => $model->id,
|
||||
'attribute_id' => $attribute->id,
|
||||
]);
|
||||
|
||||
ProductAttributeValue::create([
|
||||
'product_attribute_id' => $productAttribute->id,
|
||||
'attribute_value_id' => in_array($attribute->type, ['text', 'number']) ? null : $properties[$attribute->slug],
|
||||
'product_custom_value' => in_array($attribute->type, ['text', 'number']) ? $properties[$attribute->slug] : null,
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant;
|
||||
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Product;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
|
||||
class VariantFieldsForDetail
|
||||
{
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public static function make(NovaRequest $request, $resource): array
|
||||
{
|
||||
if (! $request->isResourceDetailRequest()) {
|
||||
if ($request->viaResource && $request->viaRelationship && $request->relationshipType) {
|
||||
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
|
||||
BelongsTo::make(__('Parent'), 'parent', Product::class),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->conversionOnIndexView('thumb200x200'),
|
||||
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->rules('required', 'numeric'),
|
||||
|
||||
Text::make('SKU', 'sku')
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
Number::make(__('Stock'), 'stock')
|
||||
->rules('required', 'integer'),
|
||||
|
||||
DynamicFields::make('Attributes', 'attributes')
|
||||
->fields(function () use ($resource) {
|
||||
$attributes = $resource->properties()->with(['attribute.values', 'values.value'])->get()->map(fn ($property) => [
|
||||
'label' => $property->attribute->name,
|
||||
'name' => $property->attribute->slug,
|
||||
'type' => $property->attribute->type,
|
||||
'default' => $property->attribute->type === 'select'
|
||||
? $property->values->first()->value?->id
|
||||
: $property->values->first()->product_custom_value,
|
||||
'options' => $property->attribute->values->map(fn ($value) => [
|
||||
'label' => $value->value,
|
||||
'value' => $value->id,
|
||||
])->toArray(),
|
||||
]);
|
||||
|
||||
return $attributes->toArray();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant;
|
||||
|
||||
use App\Repositories\Ecommerce\Product\Property\PropertyRepository;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
class VariantFieldsForIndex
|
||||
{
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public static function make(NovaRequest $request, $resource): array
|
||||
{
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->conversionOnIndexView('thumb200x200'),
|
||||
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->rules('required', 'numeric'),
|
||||
|
||||
Text::make(__('Color'), fn () => PropertyRepository::getRealValue('colour', $resource->colour)),
|
||||
Text::make(__('Size'), fn () => PropertyRepository::getRealValue('size', $resource->size)),
|
||||
|
||||
Number::make(__('Stock'), 'stock')
|
||||
->rules('required', 'integer'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant;
|
||||
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
|
||||
class VariantFieldsForPreview
|
||||
{
|
||||
/**
|
||||
* Fields For Preview
|
||||
*/
|
||||
public static function make(NovaRequest $request, $resource): array
|
||||
{
|
||||
if (! $request->isResourcePreviewRequest()) {
|
||||
return [
|
||||
DynamicFields::make('Attributes', 'attributes')
|
||||
->fields([]),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
DynamicFields::make('Attributes', 'attributes')
|
||||
->fields(function () use ($resource) {
|
||||
$attributes = $resource->properties()->with(['attribute.values', 'values.value'])->get()->map(fn ($property) => [
|
||||
'label' => $property->attribute->name,
|
||||
'name' => $property->attribute->slug,
|
||||
'type' => $property->attribute->type,
|
||||
'default' => $property->attribute->type === 'select'
|
||||
? $property->values->first()->value?->id
|
||||
: $property->values->first()->product_custom_value,
|
||||
'options' => $property->attribute->values->map(fn ($value) => [
|
||||
'label' => $value->value,
|
||||
'value' => $value->id,
|
||||
])->toArray(),
|
||||
]);
|
||||
|
||||
return $attributes->toArray();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant;
|
||||
|
||||
use App\Jobs\Ecommerce\Product\UpdateProductRelations;
|
||||
use App\Models\Ecommerce\Product\Inventory\Inventory;
|
||||
use App\Nova\Forms\NovaForm;
|
||||
use App\Repositories\Ecommerce\Product\NovaProductRepository;
|
||||
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Laravel\Nova\Fields\Boolean;
|
||||
use Laravel\Nova\Fields\Hidden;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Number;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Panel;
|
||||
use Nurmuhammet\DynamicFields\DynamicFields;
|
||||
use Nurmuhammet\ProductInventory\ProductInventory;
|
||||
|
||||
class VariantFieldsForUpdate
|
||||
{
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public static function make(NovaRequest $request, $resource): array
|
||||
{
|
||||
$categories = $resource->parent->categories;
|
||||
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
|
||||
Images::make(__('Image'), 'uploads')
|
||||
->showStatistics()
|
||||
->setFileName(fn ($originalFilename, $extension, $model) => sprintf('%s.%s', md5($originalFilename), $extension)),
|
||||
|
||||
Text::make(__('Price'), 'cost_amount')
|
||||
->rules('required', 'numeric'),
|
||||
|
||||
Text::make(__('Price without discount'), 'old_price_amount')
|
||||
->rules('nullable', 'numeric'),
|
||||
|
||||
Text::make(__('Purchase price'), 'readonly_price_amount')
|
||||
->readonly()
|
||||
->dependsOn(['cost_amount'], NovaProductRepository::priceAmountDependsOn($categories)),
|
||||
|
||||
Hidden::make('price_amount')
|
||||
->fillUsing(NovaForm::fillAttribute('price_amount', 'readonly_price_amount')),
|
||||
|
||||
new Panel(__('Inventory'), [
|
||||
Text::make('SKU', 'sku')
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
Text::make(__('Barcode'), 'barcode')
|
||||
->rules('nullable', 'string', 'max:255'),
|
||||
|
||||
ProductInventory::make(__('Inventory'), 'inventories')
|
||||
->rules('required')
|
||||
->options(Inventory::pluck('name', 'id'))
|
||||
->fillUsing(NovaForm::fillEmpty()),
|
||||
|
||||
Hidden::make('stock')
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$model->{$attribute} = array_sum(array_values($request->input('inventories')));
|
||||
}),
|
||||
|
||||
Number::make(__('Security stock'), 'security_stock')
|
||||
->rules('nullable', 'integer'),
|
||||
]),
|
||||
|
||||
new Panel(__('Attributes'), [
|
||||
DynamicFields::make('Attributes', 'workingpn')
|
||||
->fillWithArrayName('properties')
|
||||
->fields(fn () => NovaProductRepository::attributeFieldsWithValues($categories, $resource))
|
||||
->fillUsing(function ($request, $model, $attribute, $requestAttribute) {
|
||||
$properties = $request->input('properties');
|
||||
|
||||
foreach ($properties as $key => $value) {
|
||||
try {
|
||||
if (in_array($key, ['size', 'colour'])) {
|
||||
$model->{$key} = $value;
|
||||
}
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
}),
|
||||
]),
|
||||
|
||||
new Panel(__('Shipping'), [
|
||||
Boolean::make(__('Back order'), 'back_order')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Weight value'), 'weight_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Weight unit'), 'weight_unit')
|
||||
->options(['kg' => 'kg'])
|
||||
->default('kg')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Height value'), 'height_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Height unit'), 'height_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Width value'), 'width_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Width unit'), 'width_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Depth value'), 'depth_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Depth unit'), 'depth_unit')
|
||||
->options(['cm' => 'cm'])
|
||||
->default('cm')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Number::make(__('Volume value'), 'volume_value')
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
|
||||
Select::make(__('Volume unit'), 'volume_unit')
|
||||
->options(['kg' => 'kg'])
|
||||
->default('kg')
|
||||
->searchable()
|
||||
->fillUsing(NovaForm::fillSchemalessField()),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be called after the resource is updated.
|
||||
*/
|
||||
public static function afterUpdate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
UpdateProductRelations::dispatch(
|
||||
model: $model,
|
||||
properties: $request->input('properties'),
|
||||
inventories: $request->collect('inventories')
|
||||
->mapWithKeys(fn ($stockValue, $inventoryID) => [$inventoryID => ['stock' => $stockValue]])
|
||||
->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product\Filters;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Nova\Filters\Filter;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
class ProductEntrepreneurFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* The filter's component.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $component = 'select-filter';
|
||||
|
||||
/**
|
||||
* Get the displayable name of the filter.
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return __('Entrepreneur');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter to the given query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function apply(NovaRequest $request, $query, $value)
|
||||
{
|
||||
$vendorProducts = DB::table('product_has_relations')
|
||||
->where('productable_type', 'channel')
|
||||
->where('productable_id', $value)
|
||||
->pluck('product_id');
|
||||
|
||||
$query->whereIntegerInRaw('id', $vendorProducts);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter's available options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function options(NovaRequest $request)
|
||||
{
|
||||
if (! Cache::has('channels')) {
|
||||
Cache::remember('channels', 60 * 5, fn () => DB::table('channels')->pluck('id', 'name'));
|
||||
}
|
||||
|
||||
return Cache::get('channels');
|
||||
}
|
||||
}
|
||||
216
app/Nova/Resources/Ecommerce/Product/Product/Product.php
Normal file
216
app/Nova/Resources/Ecommerce/Product/Product/Product.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product;
|
||||
|
||||
use App\Models\Ecommerce\Product\Product\Product as ProductModel;
|
||||
use App\Models\User;
|
||||
use App\Nova\Actions\Ecommerce\Product\ProductImportAction;
|
||||
use App\Nova\Filters\ResourceLimitFilter;
|
||||
use App\Nova\Filters\VisableFilter;
|
||||
use App\Nova\Lenses\MostSoldProducts;
|
||||
use App\Nova\Resource;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\HandlesProductResourceEvents;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\ProductFieldsForCreate;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\ProductFieldsForDetail;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\ProductFieldsForIndex;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\ProductFieldsForUpdate;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Filters\ProductEntrepreneurFilter;
|
||||
use App\Repositories\CMS\Icon\IconRepository;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Nova\Fields\HasMany;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use ShuvroRoy\NovaTabs\Traits\HasTabs;
|
||||
|
||||
class Product extends Resource
|
||||
{
|
||||
/**
|
||||
* Handles product resource events
|
||||
*/
|
||||
use HandlesProductResourceEvents;
|
||||
|
||||
/**
|
||||
* Supports tabs
|
||||
*/
|
||||
use HasTabs;
|
||||
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var class-string<ProductModel>
|
||||
*/
|
||||
public static $model = ProductModel::class;
|
||||
|
||||
/**
|
||||
* The number of resources to show per page via relationships.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $perPageViaRelationship = 5;
|
||||
|
||||
/**
|
||||
* The pagination per-page options configured for this resource.
|
||||
*/
|
||||
public static function perPageOptions(): array
|
||||
{
|
||||
return [50, 100, 150];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value that should be displayed to represent the resource.
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
if (request()->filled('search') && is_null(request('filters'))) {
|
||||
return sprintf('%s (%s)', $this->name, $this->barcode);
|
||||
}
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationships that should be eager loaded on index queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
// public static $with = ['brand', 'media'];
|
||||
|
||||
/**
|
||||
* The columns that should be searched.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $search = [
|
||||
'id', 'name', 'sku', 'barcode',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the displayable label of the resource.
|
||||
*/
|
||||
public static function label(): string
|
||||
{
|
||||
return __('Product');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable singular label of the resource.
|
||||
*/
|
||||
public static function singularLabel(): string
|
||||
{
|
||||
return __('Products');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an "index" query for the given resource.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*/
|
||||
public static function indexQuery(NovaRequest $request, $query): Builder
|
||||
{
|
||||
if ($request->filled('search') && is_null($request->filters)) {
|
||||
|
||||
} else {
|
||||
$query->with(['brand', 'media']);
|
||||
}
|
||||
|
||||
if ($request->viaResource() != Product::class) {
|
||||
$query->where('parent_id', null);
|
||||
// ->whereNot('name', 'inlineCreated');
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->hasRole('vendor')) {
|
||||
$vendorProducts = DB::table('product_has_relations')
|
||||
->where('productable_type', 'channel')
|
||||
->where('productable_id', $user->channel()->id)
|
||||
->pluck('product_id');
|
||||
|
||||
$query->whereIntegerInRaw('id', $vendorProducts);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for index.
|
||||
*/
|
||||
public function fieldsForIndex(NovaRequest $request): array
|
||||
{
|
||||
return ProductFieldsForIndex::make($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public function fieldsForCreate(NovaRequest $request): array
|
||||
{
|
||||
return ProductFieldsForCreate::make($this, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields displayed by the resource on detail page.
|
||||
*/
|
||||
public function fieldsForDetail(NovaRequest $request): array
|
||||
{
|
||||
return ProductFieldsForDetail::make($this, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields displayed by the resource on detail page.
|
||||
*/
|
||||
public function fieldsForUpdate(NovaRequest $request): array
|
||||
{
|
||||
return ProductFieldsForUpdate::make($this, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields.
|
||||
*/
|
||||
public function fields(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
Text::make(__('Ady'), 'name'),
|
||||
HasMany::make(__('Variations'), 'variations', ProductVariant::class),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filters available for the resource.
|
||||
*/
|
||||
public function filters(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ProductEntrepreneurFilter::make(),
|
||||
VisableFilter::make(),
|
||||
// ResourceLimitFilter::make($this),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lenses available for the resource.
|
||||
*/
|
||||
public function lenses(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
MostSoldProducts::make()
|
||||
->canSeeWhen('isAdmin', User::class),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions available for the resource.
|
||||
*/
|
||||
public function actions(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ProductImportAction::make()
|
||||
->standalone()
|
||||
->onlyOnIndex()
|
||||
->icon(IconRepository::make()->upload()),
|
||||
];
|
||||
}
|
||||
}
|
||||
177
app/Nova/Resources/Ecommerce/Product/Product/ProductVariant.php
Normal file
177
app/Nova/Resources/Ecommerce/Product/Product/ProductVariant.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Nova\Resources\Ecommerce\Product\Product;
|
||||
|
||||
use App\Models\Ecommerce\Product\Product\Product as ProductModel;
|
||||
use App\Nova\Resource;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant\VariantFieldsForCreate;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant\VariantFieldsForDetail;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant\VariantFieldsForIndex;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant\VariantFieldsForPreview;
|
||||
use App\Nova\Resources\Ecommerce\Product\Product\Concerns\Variant\VariantFieldsForUpdate;
|
||||
use App\Rules\CommaSeparatedIntegers;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use ShuvroRoy\NovaTabs\Traits\HasTabs;
|
||||
|
||||
class ProductVariant extends Resource
|
||||
{
|
||||
/**
|
||||
* Supports tabs
|
||||
*/
|
||||
use HasTabs;
|
||||
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var class-string<ProductModel>
|
||||
*/
|
||||
public static $model = ProductModel::class;
|
||||
|
||||
/**
|
||||
* The single value that should be used to represent the resource when being displayed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $title = 'name';
|
||||
|
||||
/**
|
||||
* The relationships that should be eager loaded on index queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $with = ['media'];
|
||||
|
||||
/**
|
||||
* The columns that should be searched.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $search = [
|
||||
'id', 'name', 'sku', 'barcode',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the displayable label of the resource.
|
||||
*/
|
||||
public static function label(): string
|
||||
{
|
||||
return __('Product Variations');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable singular label of the resource.
|
||||
*/
|
||||
public static function singularLabel(): string
|
||||
{
|
||||
return __('Product Variation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an "index" query for the given resource.
|
||||
*/
|
||||
public static function indexQuery(NovaRequest $request, $query)
|
||||
{
|
||||
if (Validator::make($request->all(), [
|
||||
'ids' => ['required', 'string', new CommaSeparatedIntegers()],
|
||||
])->passes()) {
|
||||
$query->whereIntegerInRaw('id', explode(',', $request->ids));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->whereNotNull('parent_id');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for index.
|
||||
*/
|
||||
public function fieldsForIndex(NovaRequest $request): array
|
||||
{
|
||||
return VariantFieldsForIndex::make($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields for preview
|
||||
*/
|
||||
public function fieldsForPreview(NovaRequest $request): array
|
||||
{
|
||||
return VariantFieldsForPreview::make($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public function fieldsForDetail(NovaRequest $request): array
|
||||
{
|
||||
return VariantFieldsForDetail::make($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for create.
|
||||
*/
|
||||
public function fieldsForCreate(NovaRequest $request): array
|
||||
{
|
||||
return VariantFieldsForCreate::make($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields displayed by the resource on detail page.
|
||||
*/
|
||||
public function fieldsForUpdate(NovaRequest $request): array
|
||||
{
|
||||
return VariantFieldsForUpdate::make($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields.
|
||||
*/
|
||||
public function fields(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ID::make()->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* After resource has been created
|
||||
*/
|
||||
public static function afterCreate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
VariantFieldsForCreate::afterCreate($request, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* After resource has been updated
|
||||
*/
|
||||
public static function afterUpdate(NovaRequest $request, Model $model): void
|
||||
{
|
||||
VariantFieldsForUpdate::afterUpdate($request, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location to redirect the user after creation.
|
||||
*
|
||||
* @param \Laravel\Nova\Resource $resource
|
||||
* @return \Laravel\Nova\URL|string
|
||||
*/
|
||||
public static function redirectAfterCreate(NovaRequest $request, $resource)
|
||||
{
|
||||
return sprintf('/resources/products/%s', $resource->parent_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location to redirect the user after update.
|
||||
*
|
||||
* @param \Laravel\Nova\Resource $resource
|
||||
* @return \Laravel\Nova\URL|string
|
||||
*/
|
||||
public static function redirectAfterUpdate(NovaRequest $request, $resource)
|
||||
{
|
||||
return sprintf('/resources/products/%s', $resource->parent_id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user