Merge pull request #5154 from devansh-webkul/inventory-enhancement
Added ability to edit inventory from the product listing page #5005
This commit is contained in:
commit
87d88d3a0a
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"/js/admin.js": "/js/admin.js?id=8bcdcf3b86191d1739a5",
|
||||
"/css/admin.css": "/css/admin.css?id=b24aa6544b82f674840f"
|
||||
"/css/admin.css": "/css/admin.css?id=59ebf5021195a90c8760"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,85 @@
|
|||
|
||||
namespace Webkul\Admin\DataGrids;
|
||||
|
||||
use Webkul\Core\Models\Locale;
|
||||
use Webkul\Core\Models\Channel;
|
||||
use Webkul\Ui\DataGrid\DataGrid;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Webkul\Core\Models\Channel;
|
||||
use Webkul\Core\Models\Locale;
|
||||
use Webkul\Inventory\Repositories\InventorySourceRepository;
|
||||
use Webkul\Product\Repositories\ProductRepository;
|
||||
use Webkul\Ui\DataGrid\DataGrid;
|
||||
|
||||
class ProductDataGrid extends DataGrid
|
||||
{
|
||||
/**
|
||||
* Default sort order of datagrid.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sortOrder = 'desc';
|
||||
|
||||
/**
|
||||
* Set index columns, ex: id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $index = 'product_id';
|
||||
|
||||
/**
|
||||
* If paginated then value of pagination.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemsPerPage = 10;
|
||||
|
||||
/**
|
||||
* Locale.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $locale = 'all';
|
||||
|
||||
/**
|
||||
* Channel.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $channel = 'all';
|
||||
|
||||
/** @var string[] contains the keys for which extra filters to render */
|
||||
/**
|
||||
* Contains the keys for which extra filters to show.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $extraFilters = [
|
||||
'channels',
|
||||
'locales',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/**
|
||||
* Product repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductRepository
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* Inventory source repository instance.
|
||||
*
|
||||
* @var \Webkul\Inventory\Repositories\InventorySourceRepository
|
||||
*/
|
||||
protected $inventorySourceRepository;
|
||||
|
||||
/**
|
||||
* Create datagrid instance.
|
||||
*
|
||||
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
|
||||
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
ProductRepository $productRepository,
|
||||
InventorySourceRepository $inventorySourceRepository
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
/* locale */
|
||||
|
|
@ -40,8 +94,17 @@ class ProductDataGrid extends DataGrid
|
|||
$this->channel = Channel::query()->find($this->channel);
|
||||
$this->channel = $this->channel ? $this->channel->code : 'all';
|
||||
}
|
||||
|
||||
$this->productRepository = $productRepository;
|
||||
|
||||
$this->inventorySourceRepository = $inventorySourceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare query builder.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareQueryBuilder()
|
||||
{
|
||||
if ($this->channel === 'all') {
|
||||
|
|
@ -72,7 +135,7 @@ class ProductDataGrid extends DataGrid
|
|||
'product_flat.status',
|
||||
'product_flat.price',
|
||||
'attribute_families.name as attribute_family',
|
||||
DB::raw('SUM(DISTINCT ' . DB::getTablePrefix() . 'product_inventories.qty) as quantity')
|
||||
DB::raw('SUM(' . DB::getTablePrefix() . 'product_inventories.qty) as quantity')
|
||||
);
|
||||
|
||||
$queryBuilder->groupBy('product_flat.product_id', 'product_flat.locale', 'product_flat.channel');
|
||||
|
|
@ -91,6 +154,11 @@ class ProductDataGrid extends DataGrid
|
|||
$this->setQueryBuilder($queryBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add columns.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addColumns()
|
||||
{
|
||||
$this->addColumn([
|
||||
|
|
@ -179,16 +247,22 @@ class ProductDataGrid extends DataGrid
|
|||
'sortable' => true,
|
||||
'searchable' => false,
|
||||
'filterable' => false,
|
||||
'wrapper' => function ($value) {
|
||||
if (is_null($value->quantity)) {
|
||||
'closure' => true,
|
||||
'wrapper' => function ($row) {
|
||||
if (is_null($row->quantity)) {
|
||||
return 0;
|
||||
} else {
|
||||
return $value->quantity;
|
||||
return $this->renderQuantityView($row);
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareActions()
|
||||
{
|
||||
$this->addAction([
|
||||
|
|
@ -210,6 +284,11 @@ class ProductDataGrid extends DataGrid
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare mass actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareMassActions()
|
||||
{
|
||||
$this->addAction([
|
||||
|
|
@ -237,4 +316,21 @@ class ProductDataGrid extends DataGrid
|
|||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render quantity view.
|
||||
*
|
||||
* @parma object $row
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
private function renderQuantityView($row)
|
||||
{
|
||||
$product = $this->productRepository->find($row->product_id);
|
||||
|
||||
$inventorySources = $this->inventorySourceRepository->findWhere(['status' => 1]);
|
||||
|
||||
$totalQuantity = $row->quantity;
|
||||
|
||||
return view('admin::catalog.products.datagrid.quantity', compact('product', 'inventorySources', 'totalQuantity'))->render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,6 +304,10 @@ Route::group(['middleware' => ['web', 'admin_locale']], function () {
|
|||
'redirect' => 'admin.catalog.products.index',
|
||||
])->name('admin.catalog.products.update');
|
||||
|
||||
Route::put('/products/edit/{id}/inventories', 'Webkul\Product\Http\Controllers\ProductController@updateInventories')->defaults('_config', [
|
||||
'redirect' => 'admin.catalog.products.index',
|
||||
])->name('admin.catalog.products.update-inventories');
|
||||
|
||||
Route::post('/products/upload-file/{id}', 'Webkul\Product\Http\Controllers\ProductController@uploadLink')->name('admin.catalog.products.upload_link');
|
||||
|
||||
Route::post('/products/upload-sample/{id}', 'Webkul\Product\Http\Controllers\ProductController@uploadSample')->name('admin.catalog.products.upload_sample');
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ $accordian-header: #fbfbfb;
|
|||
// Buttons
|
||||
$btn-primary: $white;
|
||||
$btn-primary-bg: #0041FF;
|
||||
$btn-danger-bg: $red;
|
||||
|
||||
// Cards
|
||||
$card-title: #a2a2a2;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
// Accordion
|
||||
.accordian-header {
|
||||
background-color: $accordian-header;
|
||||
background-color: $accordian-header;
|
||||
}
|
||||
|
||||
// buttons
|
||||
.btn.btn-primary{
|
||||
// Buttons
|
||||
.btn.btn-primary {
|
||||
background: $btn-primary-bg;
|
||||
}
|
||||
|
||||
.btn.btn-danger {
|
||||
background: $btn-danger-bg;
|
||||
}
|
||||
|
||||
.fixed-action {
|
||||
position: fixed;
|
||||
top: 108px;
|
||||
|
|
@ -24,4 +28,4 @@
|
|||
|
||||
.pagination {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -580,6 +580,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -498,31 +498,31 @@ return [
|
|||
[
|
||||
'products' =>
|
||||
[
|
||||
'title' => 'Produkte',
|
||||
'add-product-btn-title' => 'Produkt hinzufügen',
|
||||
'add-title' => 'Produkt hinzufügen',
|
||||
'edit-title' => 'Produkt bearbeiten',
|
||||
'save-btn-title' => 'Produkt speichern',
|
||||
'general' => 'Allgemein',
|
||||
'product-type' => 'Produkttyp',
|
||||
'simple' => 'Einfach',
|
||||
'configurable' => 'Konfigurierbar',
|
||||
'familiy' => 'Attributgruppe',
|
||||
'sku' => 'SKU',
|
||||
'configurable-attributes' => 'Konfigurierbare Attribute',
|
||||
'attribute-header' => 'Attribut(s)',
|
||||
'attribute-option-header' => 'Attribut Option(s)',
|
||||
'no' => 'Nein',
|
||||
'yes' => 'Ja',
|
||||
'disabled' => 'Deaktiviert',
|
||||
'enabled' => 'Aktiviert',
|
||||
'add-variant-btn-title' => 'Variante hinzufügen',
|
||||
'name' => 'Name',
|
||||
'qty' => 'Menge',
|
||||
'price' => 'Preis',
|
||||
'weight' => 'Gewicht',
|
||||
'status' => 'Status',
|
||||
'add-variant-title' => 'Variante hinzufügen',
|
||||
'title' => 'Produkte',
|
||||
'add-product-btn-title' => 'Produkt hinzufügen',
|
||||
'add-title' => 'Produkt hinzufügen',
|
||||
'edit-title' => 'Produkt bearbeiten',
|
||||
'save-btn-title' => 'Produkt speichern',
|
||||
'general' => 'Allgemein',
|
||||
'product-type' => 'Produkttyp',
|
||||
'simple' => 'Einfach',
|
||||
'configurable' => 'Konfigurierbar',
|
||||
'familiy' => 'Attributgruppe',
|
||||
'sku' => 'SKU',
|
||||
'configurable-attributes' => 'Konfigurierbare Attribute',
|
||||
'attribute-header' => 'Attribut(s)',
|
||||
'attribute-option-header' => 'Attribut Option(s)',
|
||||
'no' => 'Nein',
|
||||
'yes' => 'Ja',
|
||||
'disabled' => 'Deaktiviert',
|
||||
'enabled' => 'Aktiviert',
|
||||
'add-variant-btn-title' => 'Variante hinzufügen',
|
||||
'name' => 'Name',
|
||||
'qty' => 'Menge',
|
||||
'price' => 'Preis',
|
||||
'weight' => 'Gewicht',
|
||||
'status' => 'Status',
|
||||
'add-variant-title' => 'Variante hinzufügen',
|
||||
'variant-already-exist-message' => 'Eine Variante mit denselben Attributoptionen ist bereits vorhanden.',
|
||||
'add-image-btn-title' => 'Bild hinzufügen',
|
||||
'mass-delete-success' => 'Alle ausgewählten Produkte wurden erfolgreich gelöscht',
|
||||
|
|
@ -565,12 +565,15 @@ return [
|
|||
'multiselect' => 'Multiselect',
|
||||
'new-option' => 'Neue Option',
|
||||
'is-default' => 'Ist Standard',
|
||||
'remove-image-btn-title' => 'Remove Image',
|
||||
'videos' => 'Videos',
|
||||
'video' => 'Video',
|
||||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'remove-image-btn-title' => 'Remove Image',
|
||||
'videos' => 'Videos',
|
||||
'video' => 'Video',
|
||||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
'attributes' =>
|
||||
[
|
||||
|
|
|
|||
|
|
@ -514,84 +514,87 @@ return [
|
|||
|
||||
'catalog' => [
|
||||
'products' => [
|
||||
'title' => 'Products',
|
||||
'add-product-btn-title' => 'Add Product',
|
||||
'add-title' => 'Add Product',
|
||||
'edit-title' => 'Edit Product',
|
||||
'save-btn-title' => 'Save Product',
|
||||
'general' => 'General',
|
||||
'product-type' => 'Product Type',
|
||||
'simple' => 'Simple',
|
||||
'configurable' => 'Configurable',
|
||||
'familiy' => 'Attribute Family',
|
||||
'sku' => 'SKU',
|
||||
'configurable-attributes' => 'Configurable Attributes',
|
||||
'attribute-header' => 'Attribute(s)',
|
||||
'attribute-option-header' => 'Attribute Option(s)',
|
||||
'no' => 'No',
|
||||
'yes' => 'Yes',
|
||||
'disabled' => 'Disabled',
|
||||
'enabled' => 'Enabled',
|
||||
'add-variant-btn-title' => 'Add Variant',
|
||||
'name' => 'Name',
|
||||
'qty' => 'Qty',
|
||||
'price' => 'Price',
|
||||
'weight' => 'Weight',
|
||||
'status' => 'Status',
|
||||
'add-variant-title' => 'Add Variant',
|
||||
'add-image-btn-title' => 'Add Image',
|
||||
'mass-delete-success' => 'All the selected products have been deleted successfully',
|
||||
'mass-update-success' => 'All the selected products have been updated successfully',
|
||||
'configurable-error' => 'Please select atleast one configurable attribute.',
|
||||
'categories' => 'Categories',
|
||||
'images' => 'Images',
|
||||
'inventories' => 'Inventories',
|
||||
'variations' => 'Variations',
|
||||
'downloadable' => 'Downloadable Information',
|
||||
'links' => 'Links',
|
||||
'add-link-btn-title' => 'Add Link',
|
||||
'samples' => 'Samples',
|
||||
'add-sample-btn-title' => 'Add Sample',
|
||||
'downloads' => 'Download Allowed',
|
||||
'file' => 'File',
|
||||
'sample' => 'Sample',
|
||||
'upload-file' => 'Upload File',
|
||||
'url' => 'Url',
|
||||
'sort-order' => 'Sort Order',
|
||||
'browse-file' => 'Browse File',
|
||||
'product-link' => 'Linked Products',
|
||||
'cross-selling' => 'Cross Selling',
|
||||
'up-selling' => 'Up Selling',
|
||||
'related-products' => 'Related Products',
|
||||
'product-search-hint' => 'Start typing product name',
|
||||
'no-result-found' => 'Products not found with same name.',
|
||||
'searching' => 'Searching ...',
|
||||
'grouped-products' => 'Grouped Products',
|
||||
'search-products' => 'Search Products',
|
||||
'channel' => 'Channels',
|
||||
'bundle-items' => 'Bundle Items',
|
||||
'add-option-btn-title' => 'Add Option',
|
||||
'option-title' => 'Option Title',
|
||||
'input-type' => 'Input Type',
|
||||
'is-required' => 'Is Required',
|
||||
'select' => 'Select',
|
||||
'radio' => 'Radio',
|
||||
'checkbox' => 'Checkbox',
|
||||
'multiselect' => 'Multiselect',
|
||||
'new-option' => 'New Option',
|
||||
'is-default' => 'Is Default',
|
||||
'customer-group' => 'Customer Group',
|
||||
'add-group-price' => 'Add Customer Group Price',
|
||||
'all-group' => 'All Groups',
|
||||
'fixed' => 'Fixed',
|
||||
'discount' => 'Discount',
|
||||
'remove-image-btn-title' => 'Remove Image',
|
||||
'videos' => 'Videos',
|
||||
'video' => 'Video',
|
||||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'title' => 'Products',
|
||||
'add-product-btn-title' => 'Add Product',
|
||||
'add-title' => 'Add Product',
|
||||
'edit-title' => 'Edit Product',
|
||||
'save-btn-title' => 'Save Product',
|
||||
'general' => 'General',
|
||||
'product-type' => 'Product Type',
|
||||
'simple' => 'Simple',
|
||||
'configurable' => 'Configurable',
|
||||
'familiy' => 'Attribute Family',
|
||||
'sku' => 'SKU',
|
||||
'configurable-attributes' => 'Configurable Attributes',
|
||||
'attribute-header' => 'Attribute(s)',
|
||||
'attribute-option-header' => 'Attribute Option(s)',
|
||||
'no' => 'No',
|
||||
'yes' => 'Yes',
|
||||
'disabled' => 'Disabled',
|
||||
'enabled' => 'Enabled',
|
||||
'add-variant-btn-title' => 'Add Variant',
|
||||
'name' => 'Name',
|
||||
'qty' => 'Qty',
|
||||
'price' => 'Price',
|
||||
'weight' => 'Weight',
|
||||
'status' => 'Status',
|
||||
'add-variant-title' => 'Add Variant',
|
||||
'add-image-btn-title' => 'Add Image',
|
||||
'mass-delete-success' => 'All the selected products have been deleted successfully',
|
||||
'mass-update-success' => 'All the selected products have been updated successfully',
|
||||
'configurable-error' => 'Please select atleast one configurable attribute.',
|
||||
'categories' => 'Categories',
|
||||
'images' => 'Images',
|
||||
'inventories' => 'Inventories',
|
||||
'variations' => 'Variations',
|
||||
'downloadable' => 'Downloadable Information',
|
||||
'links' => 'Links',
|
||||
'add-link-btn-title' => 'Add Link',
|
||||
'samples' => 'Samples',
|
||||
'add-sample-btn-title' => 'Add Sample',
|
||||
'downloads' => 'Download Allowed',
|
||||
'file' => 'File',
|
||||
'sample' => 'Sample',
|
||||
'upload-file' => 'Upload File',
|
||||
'url' => 'Url',
|
||||
'sort-order' => 'Sort Order',
|
||||
'browse-file' => 'Browse File',
|
||||
'product-link' => 'Linked Products',
|
||||
'cross-selling' => 'Cross Selling',
|
||||
'up-selling' => 'Up Selling',
|
||||
'related-products' => 'Related Products',
|
||||
'product-search-hint' => 'Start typing product name',
|
||||
'no-result-found' => 'Products not found with same name.',
|
||||
'searching' => 'Searching ...',
|
||||
'grouped-products' => 'Grouped Products',
|
||||
'search-products' => 'Search Products',
|
||||
'channel' => 'Channels',
|
||||
'bundle-items' => 'Bundle Items',
|
||||
'add-option-btn-title' => 'Add Option',
|
||||
'option-title' => 'Option Title',
|
||||
'input-type' => 'Input Type',
|
||||
'is-required' => 'Is Required',
|
||||
'select' => 'Select',
|
||||
'radio' => 'Radio',
|
||||
'checkbox' => 'Checkbox',
|
||||
'multiselect' => 'Multiselect',
|
||||
'new-option' => 'New Option',
|
||||
'is-default' => 'Is Default',
|
||||
'customer-group' => 'Customer Group',
|
||||
'add-group-price' => 'Add Customer Group Price',
|
||||
'all-group' => 'All Groups',
|
||||
'fixed' => 'Fixed',
|
||||
'discount' => 'Discount',
|
||||
'remove-image-btn-title' => 'Remove Image',
|
||||
'videos' => 'Videos',
|
||||
'video' => 'Video',
|
||||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'variant-already-exist-message' => 'Variant with same attribute options already exists.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -592,6 +592,9 @@ return [
|
|||
'add-video-btn-title' => 'Agregar Video',
|
||||
'remove-video-btn-title' => 'Remover Video',
|
||||
'not-support-video' => 'Su navegador no soporta la etiqueta video.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -581,6 +581,9 @@ return [
|
|||
'add-video-btn-title' => 'اضافه کردن فیلم',
|
||||
'remove-video-btn-title' => 'حذف فیلم',
|
||||
'not-support-video' => 'مرورگر شما تگ ویدیو را پشتیبانی نمی کند',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -591,6 +591,9 @@ return [
|
|||
'remove-video-btn-title' => 'Supprimer la vidéo',
|
||||
'not-support-video' => 'Votre navigateur ne prend pas en charge la balise vidéo.',
|
||||
'variant-already-exist-message' => 'Une variante avec les mêmes options d\'attribut existe déjà.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -581,6 +581,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -577,6 +577,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -579,6 +579,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -578,6 +578,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -574,6 +574,9 @@ return [
|
|||
'add-video-btn-title' => 'Add Video',
|
||||
'remove-video-btn-title' => 'Remove Video',
|
||||
'not-support-video' => 'Your browser does not support the video tag.',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'saved-inventory-message' => 'Product inventory saved successfully.',
|
||||
],
|
||||
|
||||
'attributes' => [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<span id="product-{{ $product->id }}-quantity">
|
||||
<a id="product-{{ $product->id }}-quantity-anchor" href="javascript:void(0);" onclick="showEditQuantityForm('{{ $product->id }}')">{{ $totalQuantity }}</a>
|
||||
</span>
|
||||
|
||||
<span id="edit-product-{{ $product->id }}-quantity-form-block" style="display: none;">
|
||||
<form id="edit-product-{{ $product->id }}-quantity-form" action="javascript:void(0);">
|
||||
@csrf
|
||||
|
||||
@method('PUT')
|
||||
|
||||
@foreach ($inventorySources as $inventorySource)
|
||||
@php
|
||||
$qty = 0;
|
||||
|
||||
foreach ($product->inventories as $inventory) {
|
||||
if ($inventory->inventory_source_id == $inventorySource->id) {
|
||||
$qty = $inventory->qty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$qty = old('inventories[' . $inventorySource->id . ']') ?: $qty;
|
||||
@endphp
|
||||
|
||||
<div class="control-group" :class="[errors.has('inventories[{{ $inventorySource->id }}]') ? 'has-error' : '']">
|
||||
<label>{{ $inventorySource->name }}</label>
|
||||
|
||||
<input type="text" v-validate="'numeric|min:0'" name="inventories[{{ $inventorySource->id }}]" class="control" value="{{ $qty }}" data-vv-as=""{{ $inventorySource->name }}""/>
|
||||
|
||||
<span class="control-error" v-if="errors.has('inventories[{{ $inventorySource->id }}]')">@{{ errors.first('inventories[{!! $inventorySource->id !!}]') }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<button class="btn btn-primary" onclick="saveEditQuantityForm('{{ route('admin.catalog.products.update-inventories', $product->id) }}', '{{ $product->id }}')">{{ __('admin::app.catalog.products.save') }}</button>
|
||||
<button class="btn btn-danger" onclick="cancelEditQuantityForm('{{ $product->id }}')">{{ __('admin::app.catalog.products.cancel') }}</button>
|
||||
</form>
|
||||
</span>
|
||||
|
|
@ -29,11 +29,11 @@
|
|||
|
||||
<div class="page-content">
|
||||
@inject('products', 'Webkul\Admin\DataGrids\ProductDataGrid')
|
||||
|
||||
{!! $products->render() !!}
|
||||
</div>
|
||||
|
||||
{!! view_render_event('bagisto.admin.catalog.products.list.after') !!}
|
||||
|
||||
</div>
|
||||
|
||||
<modal id="downloadDataGrid" :is-open="modalIds.downloadDataGrid">
|
||||
|
|
@ -46,14 +46,42 @@
|
|||
|
||||
@push('scripts')
|
||||
@include('admin::export.export', ['gridName' => $products])
|
||||
<script>
|
||||
|
||||
<script>
|
||||
function reloadPage(getVar, getVal) {
|
||||
let url = new URL(window.location.href);
|
||||
|
||||
url.searchParams.set(getVar, getVal);
|
||||
|
||||
window.location.href = url.href;
|
||||
}
|
||||
|
||||
function showEditQuantityForm(productId) {
|
||||
$(`#product-${productId}-quantity`).hide();
|
||||
|
||||
$(`#edit-product-${productId}-quantity-form-block`).show();
|
||||
}
|
||||
|
||||
function cancelEditQuantityForm(productId) {
|
||||
$(`#edit-product-${productId}-quantity-form-block`).hide();
|
||||
|
||||
$(`#product-${productId}-quantity`).show();
|
||||
}
|
||||
|
||||
function saveEditQuantityForm(updateSource, productId) {
|
||||
let quantityFormData = $(`#edit-product-${productId}-quantity-form`).serialize();
|
||||
|
||||
axios
|
||||
.post(updateSource, quantityFormData)
|
||||
.then(function (response) {
|
||||
let data = response.data;
|
||||
|
||||
$(`#edit-product-${productId}-quantity-form-block`).hide();
|
||||
|
||||
$(`#product-${productId}-quantity-anchor`).text(data.updatedTotal);
|
||||
|
||||
$(`#product-${productId}-quantity`).show();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -3,89 +3,96 @@
|
|||
namespace Webkul\Product\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Webkul\Product\Models\Product;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webkul\Product\Helpers\ProductType;
|
||||
use Webkul\Core\Contracts\Validations\Slug;
|
||||
use Webkul\Product\Http\Requests\ProductForm;
|
||||
use Webkul\Product\Repositories\ProductRepository;
|
||||
use Webkul\Category\Repositories\CategoryRepository;
|
||||
use Webkul\Attribute\Repositories\AttributeFamilyRepository;
|
||||
use Webkul\Category\Repositories\CategoryRepository;
|
||||
use Webkul\Core\Contracts\Validations\Slug;
|
||||
use Webkul\Inventory\Repositories\InventorySourceRepository;
|
||||
use Webkul\Product\Helpers\ProductType;
|
||||
use Webkul\Product\Http\Requests\ProductForm;
|
||||
use Webkul\Product\Models\Product;
|
||||
use Webkul\Product\Repositories\ProductAttributeValueRepository;
|
||||
use Webkul\Product\Repositories\ProductDownloadableLinkRepository;
|
||||
use Webkul\Product\Repositories\ProductDownloadableSampleRepository;
|
||||
use Webkul\Product\Repositories\ProductAttributeValueRepository;
|
||||
use Webkul\Product\Repositories\ProductInventoryRepository;
|
||||
use Webkul\Product\Repositories\ProductRepository;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
/**
|
||||
* Contains route related configuration
|
||||
* Contains route related configuration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* CategoryRepository object
|
||||
* Category repository instance.
|
||||
*
|
||||
* @var \Webkul\Category\Repositories\CategoryRepository
|
||||
*/
|
||||
protected $categoryRepository;
|
||||
|
||||
/**
|
||||
* ProductRepository object
|
||||
* Product repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductRepository
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* ProductDownloadableLinkRepository object
|
||||
* Product downloadable link repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductDownloadableLinkRepository
|
||||
*/
|
||||
protected $productDownloadableLinkRepository;
|
||||
|
||||
/**
|
||||
* ProductDownloadableSampleRepository object
|
||||
* Product downloadable sample repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductDownloadableSampleRepository
|
||||
*/
|
||||
protected $productDownloadableSampleRepository;
|
||||
|
||||
/**
|
||||
* AttributeFamilyRepository object
|
||||
* Attribute family repository instance.
|
||||
*
|
||||
* @var \Webkul\Attribute\Repositories\AttributeFamilyRepository
|
||||
*/
|
||||
protected $attributeFamilyRepository;
|
||||
|
||||
/**
|
||||
* InventorySourceRepository object
|
||||
* Inventory source repository instance.
|
||||
*
|
||||
* @var \Webkul\Inventory\Repositories\InventorySourceRepository
|
||||
*/
|
||||
protected $inventorySourceRepository;
|
||||
|
||||
/**
|
||||
* ProductAttributeValueRepository object
|
||||
* Product attribute value repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductAttributeValueRepository
|
||||
*/
|
||||
protected $productAttributeValueRepository;
|
||||
|
||||
/**
|
||||
* Product inventory repository instance.
|
||||
*
|
||||
* @var \Webkul\Product\Repositories\ProductInventoryRepository
|
||||
*/
|
||||
protected $productInventoryRepository;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @param \Webkul\Category\Repositories\CategoryRepository $categoryRepository
|
||||
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
|
||||
* @param \Webkul\Product\Repositories\ProductDownloadableLinkRepository $productDownloadableLinkRepository
|
||||
* @param \Webkul\Product\Repositories\ProductDownloadableSampleRepository $productDownloadableSampleRepository
|
||||
* @param \Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
|
||||
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
|
||||
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $productAttributeValueRepository
|
||||
*
|
||||
* @param \Webkul\Category\Repositories\CategoryRepository $categoryRepository
|
||||
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
|
||||
* @param \Webkul\Product\Repositories\ProductDownloadableLinkRepository $productDownloadableLinkRepository
|
||||
* @param \Webkul\Product\Repositories\ProductDownloadableSampleRepository $productDownloadableSampleRepository
|
||||
* @param \Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
|
||||
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
|
||||
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $productAttributeValueRepository
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
|
|
@ -95,9 +102,9 @@ class ProductController extends Controller
|
|||
ProductDownloadableSampleRepository $productDownloadableSampleRepository,
|
||||
AttributeFamilyRepository $attributeFamilyRepository,
|
||||
InventorySourceRepository $inventorySourceRepository,
|
||||
ProductAttributeValueRepository $productAttributeValueRepository
|
||||
)
|
||||
{
|
||||
ProductAttributeValueRepository $productAttributeValueRepository,
|
||||
ProductInventoryRepository $productInventoryRepository
|
||||
) {
|
||||
$this->_config = request('_config');
|
||||
|
||||
$this->categoryRepository = $categoryRepository;
|
||||
|
|
@ -113,6 +120,8 @@ class ProductController extends Controller
|
|||
$this->inventorySourceRepository = $inventorySourceRepository;
|
||||
|
||||
$this->productAttributeValueRepository = $productAttributeValueRepository;
|
||||
|
||||
$this->productInventoryRepository = $productInventoryRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -150,14 +159,16 @@ class ProductController extends Controller
|
|||
*/
|
||||
public function store()
|
||||
{
|
||||
if (! request()->get('family')
|
||||
if (
|
||||
! request()->get('family')
|
||||
&& ProductType::hasVariants(request()->input('type'))
|
||||
&& request()->input('sku') != ''
|
||||
) {
|
||||
return redirect(url()->current() . '?type=' . request()->input('type') . '&family=' . request()->input('attribute_family_id') . '&sku=' . request()->input('sku'));
|
||||
}
|
||||
|
||||
if (ProductType::hasVariants(request()->input('type'))
|
||||
if (
|
||||
ProductType::hasVariants(request()->input('type'))
|
||||
&& (! request()->has('super_attributes')
|
||||
|| ! count(request()->get('super_attributes')))
|
||||
) {
|
||||
|
|
@ -182,8 +193,7 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function edit($id)
|
||||
|
|
@ -200,9 +210,8 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Webkul\Product\Http\Requests\ProductForm $request
|
||||
* @param int $id
|
||||
*
|
||||
* @param \Webkul\Product\Http\Requests\ProductForm $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(ProductForm $request, $id)
|
||||
|
|
@ -233,7 +242,7 @@ class ProductController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$product = $this->productRepository->update($data, $id);
|
||||
$this->productRepository->update($data, $id);
|
||||
|
||||
session()->flash('success', trans('admin::app.response.update-success', ['name' => 'Product']));
|
||||
|
||||
|
|
@ -241,10 +250,27 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Uploads downloadable file
|
||||
* Update inventories.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateInventories($id)
|
||||
{
|
||||
$product = $this->productRepository->findOrFail($id);
|
||||
|
||||
$this->productInventoryRepository->saveInventories(request()->all(), $product);
|
||||
|
||||
return response()->json([
|
||||
'message' => __('admin::app.catalog.products.saved-inventory-message'),
|
||||
'updatedTotal' => $this->productInventoryRepository->where('product_id', $product->id)->sum('qty')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads downloadable file.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function uploadLink($id)
|
||||
|
|
@ -256,23 +282,30 @@ class ProductController extends Controller
|
|||
|
||||
/**
|
||||
* Copy a given Product.
|
||||
*
|
||||
* @param int $productId
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function copy(int $productId)
|
||||
{
|
||||
$originalProduct = $this->productRepository->findOrFail($productId);
|
||||
|
||||
if (! $originalProduct->getTypeInstance()->canBeCopied()) {
|
||||
session()->flash('error',
|
||||
session()->flash(
|
||||
'error',
|
||||
trans('admin::app.response.product-can-not-be-copied', [
|
||||
'type' => $originalProduct->type,
|
||||
]));
|
||||
])
|
||||
);
|
||||
|
||||
return redirect()->to(route('admin.catalog.products.index'));
|
||||
}
|
||||
|
||||
if ($originalProduct->parent_id) {
|
||||
session()->flash('error',
|
||||
trans('admin::app.catalog.products.variant-already-exist-message'));
|
||||
session()->flash(
|
||||
'error',
|
||||
trans('admin::app.catalog.products.variant-already-exist-message')
|
||||
);
|
||||
|
||||
return redirect()->to(route('admin.catalog.products.index'));
|
||||
}
|
||||
|
|
@ -289,10 +322,9 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Uploads downloadable sample file
|
||||
*
|
||||
* @param int $id
|
||||
* Uploads downloadable sample file.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function uploadSample($id)
|
||||
|
|
@ -305,8 +337,7 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
|
|
@ -329,7 +360,7 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Mass Delete the products
|
||||
* Mass Delete the products.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
|
|
@ -351,7 +382,7 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Mass updates the products
|
||||
* Mass updates the products.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
|
|
@ -383,7 +414,7 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* To be manually invoked when data is seeded into products
|
||||
* To be manually invoked when data is seeded into products.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
|
|
@ -419,11 +450,10 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Download image or file
|
||||
*
|
||||
* @param int $productId
|
||||
* @param int $attributeId
|
||||
* Download image or file.
|
||||
*
|
||||
* @param int $productId
|
||||
* @param int $attributeId
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download($productId, $attributeId)
|
||||
|
|
@ -437,7 +467,7 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Search simple products
|
||||
* Search simple products.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
|
|
@ -447,4 +477,4 @@ class ProductController extends Controller
|
|||
$this->productRepository->searchSimpleProducts(request()->input('query'))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Product;
|
||||
|
||||
use UnitTester;
|
||||
use Webkul\Product\Repositories\ProductInventoryRepository;
|
||||
|
||||
class ProductCest
|
||||
{
|
||||
/**
|
||||
* Test product inventory updation.
|
||||
*
|
||||
* @param UnitTester $I
|
||||
* @return void
|
||||
*/
|
||||
public function testProductInventoryUpdation(UnitTester $I): void
|
||||
{
|
||||
$product = $I->haveProduct(\Webkul\Core\Helpers\Laravel5Helper::SIMPLE_PRODUCT, [], ['simple']);
|
||||
|
||||
$updatedInventoriesQty = $this->getRandomUpdatedInventoriesQty($I, $product);
|
||||
|
||||
app(ProductInventoryRepository::class)->saveInventories([
|
||||
'inventories' => $updatedInventoriesQty
|
||||
], $product);
|
||||
|
||||
$product->refresh();
|
||||
|
||||
$I->assertEquals(array_sum($updatedInventoriesQty), $product->inventories->sum('qty'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test old quantities.
|
||||
*
|
||||
* @param UnitTester $I
|
||||
* @return void
|
||||
*/
|
||||
public function testProductInventoriesQty(UnitTester $I): void
|
||||
{
|
||||
$product = $I->haveProduct(\Webkul\Core\Helpers\Laravel5Helper::SIMPLE_PRODUCT, [], ['simple']);
|
||||
|
||||
$oldInventoriesQty = $product->inventories->pluck('qty', 'inventory_source_id')->toArray();
|
||||
|
||||
$oldTotalQuantity = $product->inventories->sum('qty');
|
||||
|
||||
$I->assertEquals($oldTotalQuantity, array_sum($oldInventoriesQty));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random inventories qty for product.
|
||||
*
|
||||
* @param UnitTester $I
|
||||
* @param \Webkul\Product\Models\Product $product
|
||||
* @return array
|
||||
*/
|
||||
private function getRandomUpdatedInventoriesQty(UnitTester $I, $product): array
|
||||
{
|
||||
$oldInventoriesQty = $product->inventories->pluck('qty', 'inventory_source_id');
|
||||
|
||||
$updatedInventoriesQty = [];
|
||||
|
||||
foreach ($oldInventoriesQty as $id => $oldInventoryQty) {
|
||||
$updatedInventoriesQty[$id] = $I->fake()->numberBetween(500, 2000);
|
||||
}
|
||||
|
||||
return $updatedInventoriesQty;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue