This commit is contained in:
2025-09-25 03:03:31 +05:00
commit ae480cf2f6
2768 changed files with 1485826 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Inventory\Inventory as InventoryModel;
use App\Models\System\Settings\Location\Province;
use App\Models\System\Settings\Location\Region;
use App\Nova\Permissions\NovaPermission;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Channel\Channel;
use App\Nova\Resources\Ecommerce\Product\Inventory\Product\ProductResource;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\BelongsToMany;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\Currency;
use Laravel\Nova\Fields\HasMany;
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;
class Inventory extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Shop\Inventory\Inventory>
*/
public static $model = InventoryModel::class;
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['channel'];
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'name';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = ['name', 'code', 'email', 'phone_number'];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Inventories');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Inventory');
}
/**
* Build an "index" query for the given resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
$user = $request->user();
if ($user->hasRole('vendor')) {
$query->where('channel_id', $user->channel()->id);
}
return $query;
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make(__('Channel'), 'channel', Channel::class)
->rules('required')
->canSeeWhen('systemUser', $this)
->sortable(),
Hidden::make('channel_id')
->default(fn () => $request->isCreateOrAttachRequest() ? $request->user()->channel()?->id : null)
->canSee(NovaPermission::canSeeIfUserIsEntrepreneur()),
Text::make(__('Name'), 'name')
->rules('required', 'string', 'max:255')
->sortable(),
Text::make(__('Code'), 'code')->exceptOnForms()->sortable(),
Select::make(__('Region'), 'region')
->displayUsingLabels()
->searchable()
->options(Region::values())
->default(Region::AG)
->rules('required')
->sortable(),
Select::make(__('Province'), 'province_id')
->displayUsingLabels()
->searchable()
->dependsOn(['region'], function ($field, $request, $formData) {
if ($formData->region != 'ag') {
$field->rules('required');
}
$field->options(
Province::where('region', $formData->region)->pluck(
'name',
'id'
)
);
})
->hideFromIndex()
->sortable(),
Text::make(__('Address'), 'street_address')
->rules('nullable', 'string', 'max:255')
->hideFromIndex(),
Text::make(__('Description'), 'description')->hideFromIndex(),
Text::make(__('Email'), 'email')
->rules('nullable', 'string', 'max:255')
->hideFromIndex()
->sortable(),
Currency::make(__('Phone'), 'phone_number')
->symbol('+993')
->displayUsing(fn ($value) => $value ? '+(993)-'.$value : '—')
->textAlign('left')
->rules('nullable', 'string', 'max:255')
->sortable(),
Number::make(__('Zipcode'), 'zipcode')
->rules('nullable', 'integer')
->hideFromIndex()
->sortable(),
Text::make(__('Longitude'), 'longitude')->hideFromIndex(),
Text::make(__('Latitude'), 'latitude')->hideFromIndex(),
Boolean::make(__('Sharable'), 'shareable')
->canSeeWhen('isAdmin', $this)
->sortable(),
HasMany::make(__('Inventory entry'), 'history', InventoryHistoryResource::class),
HasMany::make(__('Inventory exit'), 'historyRemoved', InventoryHistoryRemovedResource::class),
BelongsToMany::make(__('Products'), 'products', ProductResource::class),
];
}
/**
* Get the cards available for the request.
*
* @return array
*/
public function cards(NovaRequest $request)
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array
*/
public function filters(NovaRequest $request)
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array
*/
public function lenses(NovaRequest $request)
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array
*/
public function actions(NovaRequest $request)
{
return [];
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Inventory\InventoryHistoryItem;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Product\Product\Product;
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;
class InventoryHistoryItemResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Ecommerce\Product\Inventory\InventoryHistory>
*/
public static $model = InventoryHistoryItem::class;
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['inventoryHistory', 'product'];
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Items');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Item');
}
/**
* Build an "index" query for the given resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
$user = $request->user();
return $query;
}
/**
* Get the fields displayed by the resource on detail page.
*/
public function fieldsForUpdate(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Number::make(__('Quantity'), 'quantity')
->rules('required', 'min:1', 'numeric'),
Text::make(__('Cost amount'), 'cost_amount'),
Text::make(__('Total'), 'total'),
];
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make(__('Inventory history'), 'inventoryHistory', InventoryHistoryResource::class),
BelongsTo::make(__('Product'), 'product', Product::class),
Number::make(__('Quantity'), 'quantity')
->rules('required', 'min:1', 'numeric'),
Number::make(__('Cost amount'), 'cost_amount'),
Text::make(__('Total'), 'total'),
];
}
/**
* Return the location to redirect the user after creation.
*
* @param \Laravel\Nova\Resource $resource
* @return \Laravel\Nova\URL|string
*/
public static function redirectAfterUpdate(NovaRequest $request, $resource): string
{
return sprintf('/resources/inventory-history-resources/%s', $resource->inventory_history_id);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Inventory\InventoryHistoryRemovedItem;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Product\Product\Product;
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;
class InventoryHistoryRemovedItemResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Ecommerce\Product\Inventory\InventoryHistory>
*/
public static $model = InventoryHistoryRemovedItem::class;
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['inventoryHistoryRemoved', 'product'];
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Items');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Item');
}
/**
* Build an "index" query for the given resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
$user = $request->user();
return $query;
}
/**
* Get the fields displayed by the resource on detail page.
*/
public function fieldsForUpdate(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Number::make(__('Quantity'), 'quantity')
->rules('required', 'min:1', 'numeric'),
Text::make(__('Cost amount'), 'cost_amount'),
Text::make(__('Total'), 'total'),
];
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
BelongsTo::make(__('Inventory history'), 'inventoryHistoryRemoved', InventoryHistoryRemovedResource::class),
BelongsTo::make(__('Product'), 'product', Product::class),
Number::make(__('Quantity'), 'quantity')
->rules('required', 'min:1', 'numeric'),
Number::make(__('Cost amount'), 'cost_amount'),
Text::make(__('Total'), 'total'),
];
}
/**
* Return the location to redirect the user after creation.
*
* @param \Laravel\Nova\Resource $resource
* @return \Laravel\Nova\URL|string
*/
public static function redirectAfterUpdate(NovaRequest $request, $resource): string
{
return sprintf('/resources/inventory-history-resources/%s', $resource->inventory_history_id);
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Inventory\InventoryHistoryRemoved;
use App\Models\Ecommerce\Product\Product\Product;
use App\Nova\Repeater\InventoryHistoryItemRepeater;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Channel\Channel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Date;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Repeater;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
class InventoryHistoryRemovedResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Ecommerce\Product\Inventory\InventoryHistoryRemoved>
*/
public static $model = InventoryHistoryRemoved::class;
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['channel', 'inventory'];
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'code';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Inventory exits');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Inventory exit');
}
/**
* Build an "index" query for the given resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
$user = $request->user();
if ($user->hasRole('vendor')) {
$query->where('channel_id', $user->channel()->id);
}
return $query;
}
/**
* 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): string
{
return 'resources/inventories/'.$resource->inventory_id;
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Date::make(__('Date'), 'date')
->rules('required')
->displayUsing(fn ($value) => $value->format('d.m.Y'))
->readonly(fn ($request) => $request->isUpdateOrUpdateAttachedRequest())
->default(now()->toDate())
->sortable(),
Text::make(__('R. Number'), 'number')
->rules('required')
->readonly(fn ($request) => $request->isUpdateOrUpdateAttachedRequest())
->sortable(),
BelongsTo::make(__('Inventory'), 'inventory', Inventory::class),
BelongsTo::make(__('Channel'), 'channel', Channel::class),
Text::make(__('Notes'), 'notes')
->rules('nullable', 'string', 'max:255')
->hideFromIndex(),
Text::make(__('Total'), fn () => $this->total),
Text::make(__('Total'), 'readonly_total')
->onlyOnForms()
->readonly(),
Repeater::make('Options', 'options')
->repeatables([
InventoryHistoryItemRepeater::make(),
])
->asJson()
->fullWidth()
->hideWhenUpdating(),
HasMany::make(__('Items'), 'items', InventoryHistoryRemovedItemResource::class),
];
}
/**
* Register a callback to be called after the resource is created.
*/
public static function afterCreate(NovaRequest $request, Model $model): void
{
$total = 0;
$request->collect('options')->each(function ($item) use ($model, &$total) {
$total = floatval($total) + floatval($item['fields']['total']);
$model->items()->create([
'product_id' => $item['fields']['product_id'],
'quantity' => $item['fields']['quantity'],
'total' => floatval($item['fields']['total']),
'cost_amount' => $item['fields']['cost_amount'],
]);
$product = Product::find($item['fields']['product_id']);
$inventoryRecordQuery = DB::table('inventory_product')
->where('product_id', $product->id)
->where('inventory_id', $model->inventory_id);
$inventoryRecord = $inventoryRecordQuery->first();
$inventoryRecord
? $inventoryRecordQuery->update([
'stock' => intval($inventoryRecord->stock) - intval($item['fields']['quantity']),
]) : DB::table('inventory_product')->insert([
'product_id' => $product->id,
'inventory_id' => $model->inventory_id,
'stock' => -intval($item['fields']['quantity']),
]);
$product->updateQuietly([
'stock' => $product->inventories()->sum('inventory_product.stock'),
]);
});
$model->updateQuietly(['total' => $total]);
}
/**
* Determine if the current user can update the given resource.
*/
public function authorizedToUpdate(Request $request): bool
{
return false;
}
/**
* Determine if the current user can delete the given resource.
*/
public function authorizedToDelete(Request $request): bool
{
if (auth()->user()->isMe()) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory;
use App\Models\Ecommerce\Product\Inventory\InventoryHistory;
use App\Models\Ecommerce\Product\Product\Product;
use App\Nova\Repeater\InventoryHistoryItemRepeater;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Channel\Channel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Date;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Repeater;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
class InventoryHistoryResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<\App\Models\Ecommerce\Product\Inventory\InventoryHistory>
*/
public static $model = InventoryHistory::class;
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['channel', 'inventory'];
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'code';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Inventory entries');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Inventory entry');
}
/**
* Build an "index" query for the given resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
$user = $request->user();
if ($user->hasRole('vendor')) {
$query->where('channel_id', $user->channel()->id);
}
return $query;
}
/**
* 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): string
{
return 'resources/inventories/'.$resource->inventory_id;
}
/**
* Get the fields displayed by the resource.
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Date::make(__('Date'), 'date')
->rules('required')
->displayUsing(fn ($value) => $value->format('d.m.Y'))
->readonly(fn ($request) => $request->isUpdateOrUpdateAttachedRequest())
->default(now()->toDate())
->sortable(),
Text::make(__('R. Number'), 'number')
->rules('required')
->readonly(fn ($request) => $request->isUpdateOrUpdateAttachedRequest())
->sortable(),
BelongsTo::make(__('Inventory'), 'inventory', Inventory::class),
BelongsTo::make(__('Channel'), 'channel', Channel::class),
Text::make(__('Notes'), 'notes')
->rules('nullable', 'string', 'max:255')
->hideFromIndex(),
Text::make(__('Total'), fn () => $this->total),
Text::make(__('Total'), 'readonly_total')
->onlyOnForms()
->readonly(),
Repeater::make('Options', 'options')
->repeatables([
InventoryHistoryItemRepeater::make(),
])
->asJson()
->fullWidth()
->hideWhenUpdating(),
HasMany::make(__('Items'), 'items', InventoryHistoryItemResource::class),
];
}
/**
* Register a callback to be called after the resource is created.
*/
public static function afterCreate(NovaRequest $request, Model $model): void
{
$total = 0;
$request->collect('options')->each(function ($item) use ($model, &$total) {
$total = floatval($total) + floatval($item['fields']['total']);
$model->items()->create([
'product_id' => $item['fields']['product_id'],
'quantity' => $item['fields']['quantity'],
'total' => floatval($item['fields']['total']),
'cost_amount' => $item['fields']['cost_amount'],
]);
$product = Product::find($item['fields']['product_id']);
$inventoryRecordQuery = DB::table('inventory_product')
->where('product_id', $product->id)
->where('inventory_id', $model->inventory_id);
$inventoryRecord = $inventoryRecordQuery->first();
$inventoryRecord
? $inventoryRecordQuery->update([
'stock' => intval($inventoryRecord->stock) + intval($item['fields']['quantity']),
]) : DB::table('inventory_product')->insert([
'product_id' => $product->id,
'inventory_id' => $model->inventory_id,
'stock' => intval($item['fields']['quantity']),
]);
$product->updateQuietly([
'stock' => $product->inventories()->sum('inventory_product.stock'),
]);
});
$model->updateQuietly(['total' => $total]);
}
/**
* Determine if the current user can update the given resource.
*/
public function authorizedToUpdate(Request $request): bool
{
return false;
}
/**
* Determine if the current user can delete the given resource.
*/
public function authorizedToDelete(Request $request): bool
{
if (auth()->user()->isMe()) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Inventory\Product;
use App\Models\Ecommerce\Product\Product\Product;
use App\Nova\Fields\FieldHelpers;
use App\Nova\Resource;
use App\Nova\Resources\Ecommerce\Product\Brand\Brand;
use Ebess\AdvancedNovaMediaLibrary\Fields\Images;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
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 ProductResource extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Product>
*/
public static $model = Product::class;
/**
* The number of resources to show per page via relationships.
*
* @var int
*/
public static $perPageViaRelationship = 20;
/**
* The pagination per-page options configured for this resource.
*/
public static function perPageOptions(): array
{
return [50, 100, 150];
}
/**
* The relationships that should be eager loaded on index queries.
*
* @var array
*/
public static $with = ['brand', 'media'];
/**
* 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
{
return $query->where('parent_id', null);
}
/**
* Fields
*/
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb200x200'),
Text::make(__('Name'), fn () => $this->novaDetailPage())
->displayUsing(FieldHelpers::asLink(
link: $this->novaDetailPage(),
title: $this->name
))
->asHtml(),
BelongsTo::make(__('Brand'), 'brand', Brand::class)->sortable(),
Number::make(__('Price'), 'cost_amount')->sortable(),
Text::make(__('SKU'), 'sku')->sortable(),
Text::make(__('Total count'), FieldHelpers::formatQuantity())->asHtml(),
Text::make(__('Giriş çykyş boýunça'), FieldHelpers::formatQuantity(
intval(DB::table('inventory_history_items')->where('product_id', $this->id)->sum('quantity'))
-
intval(DB::table('inventory_history_removed_items')->where('product_id', $this->id)->sum('quantity'))
))->asHtml(),
Boolean::make(__('Active'), 'is_visible')
->sortable()
->canSeeWhen('isVendor', $this),
NovaSwitcher::make(__('Is visible'), 'is_visible')
->sortable()
->canSeeWhen('isAdmin', $this),
];
}
}