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:
Glenn Hermans 2021-09-16 16:09:28 +02:00 committed by GitHub
commit 87d88d3a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 481 additions and 181 deletions

File diff suppressed because one or more lines are too long

View File

@ -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"
}

View File

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

View File

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

View File

@ -9,6 +9,7 @@ $accordian-header: #fbfbfb;
// Buttons
$btn-primary: $white;
$btn-primary-bg: #0041FF;
$btn-danger-bg: $red;
// Cards
$card-title: #a2a2a2;

View File

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

View File

@ -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' => [

View File

@ -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' =>
[

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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' => [

View File

@ -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="&quot;{{ $inventorySource->name }}&quot;"/>
<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>

View File

@ -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

View File

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

View File

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