introduce ability to copy an product
This commit is contained in:
parent
e6d11ed809
commit
0509f45762
|
|
@ -181,6 +181,13 @@ class ProductDataGrid extends DataGrid
|
|||
|
||||
public function prepareMassActions()
|
||||
{
|
||||
$this->addAction([
|
||||
'title' => trans('admin::app.datagrid.copy'),
|
||||
'method' => 'GET',
|
||||
'route' => 'admin.catalog.products.copy',
|
||||
'icon' => 'icon note-icon',
|
||||
]);
|
||||
|
||||
$this->addMassAction([
|
||||
'type' => 'delete',
|
||||
'label' => trans('admin::app.datagrid.delete'),
|
||||
|
|
|
|||
|
|
@ -269,6 +269,10 @@ Route::group(['middleware' => ['web']], function () {
|
|||
'redirect' => 'admin.catalog.products.edit',
|
||||
])->name('admin.catalog.products.store');
|
||||
|
||||
Route::get('products/copy/{id}', 'Webkul\Product\Http\Controllers\ProductController@copy')->defaults('_config', [
|
||||
'view' => 'admin::catalog.products.edit',
|
||||
])->name('admin.catalog.products.copy');
|
||||
|
||||
Route::get('/products/edit/{id}', 'Webkul\Product\Http\Controllers\ProductController@edit')->defaults('_config', [
|
||||
'view' => 'admin::catalog.products.edit',
|
||||
])->name('admin.catalog.products.edit');
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
<?php
|
||||
|
||||
return array (
|
||||
'save' => 'Speichern',
|
||||
'copy-of' => 'Kopie von',
|
||||
'create' => 'Erstellen',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Löschen',
|
||||
'failed' => 'Fehlgeschlagen',
|
||||
'store' => 'Speichern',
|
||||
'image' => 'Bild',
|
||||
'no result' => 'Kein Ergebnis',
|
||||
'product' => 'Produkt',
|
||||
'attribute' => 'Attribut',
|
||||
'actions' => 'Aktionen',
|
||||
return array(
|
||||
'save' => 'Speichern',
|
||||
'copy-of' => 'Kopie von',
|
||||
'copy-of-slug' => 'kopie-von',
|
||||
'create' => 'Erstellen',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Löschen',
|
||||
'failed' => 'Fehlgeschlagen',
|
||||
'store' => 'Speichern',
|
||||
'image' => 'Bild',
|
||||
'no result' => 'Kein Ergebnis',
|
||||
'product' => 'Produkt',
|
||||
'attribute' => 'Attribut',
|
||||
'actions' => 'Aktionen',
|
||||
'id' => 'Id',
|
||||
'action' => 'Aktion',
|
||||
'yes' => 'Ja',
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'save' => 'Save',
|
||||
'copy-of' => 'Copy of',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'failed' => 'Failed',
|
||||
'store' => 'Store',
|
||||
'image' => 'Image',
|
||||
'no result' => 'No result',
|
||||
'product' => 'Product',
|
||||
'attribute' => 'Attribute',
|
||||
'actions' => 'Actions',
|
||||
'save' => 'Save',
|
||||
'copy-of' => 'Copy of',
|
||||
'copy-of-slug' => 'copy-of',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'failed' => 'Failed',
|
||||
'store' => 'Store',
|
||||
'image' => 'Image',
|
||||
'no result' => 'No result',
|
||||
'product' => 'Product',
|
||||
'attribute' => 'Attribute',
|
||||
'actions' => 'Actions',
|
||||
'id' => 'ID',
|
||||
'action' => 'action',
|
||||
'yes' => 'Yes',
|
||||
|
|
|
|||
|
|
@ -2,16 +2,20 @@
|
|||
|
||||
namespace Webkul\Product\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Webkul\Product\Http\Requests\ProductForm;
|
||||
use Webkul\Attribute\Models\Attribute;
|
||||
use Webkul\Product\Models\ProductFlat;
|
||||
use Webkul\Product\Helpers\ProductType;
|
||||
use Webkul\Category\Repositories\CategoryRepository;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webkul\Core\Contracts\Validations\Slug;
|
||||
use Webkul\Product\Http\Requests\ProductForm;
|
||||
use Webkul\Product\Repositories\ProductRepository;
|
||||
use Webkul\Product\Repositories\ProductDownloadableLinkRepository;
|
||||
use Webkul\Product\Repositories\ProductDownloadableSampleRepository;
|
||||
use Webkul\Category\Repositories\CategoryRepository;
|
||||
use Webkul\Attribute\Repositories\AttributeFamilyRepository;
|
||||
use Webkul\Inventory\Repositories\InventorySourceRepository;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webkul\Product\Repositories\ProductDownloadableLinkRepository;
|
||||
use Webkul\Product\Repositories\ProductDownloadableSampleRepository;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
|
|
@ -67,12 +71,13 @@ class ProductController extends Controller
|
|||
/**
|
||||
* 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\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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
|
|
@ -143,7 +148,7 @@ class ProductController extends Controller
|
|||
|
||||
if (ProductType::hasVariants(request()->input('type'))
|
||||
&& (! request()->has('super_attributes')
|
||||
|| ! count(request()->get('super_attributes')))
|
||||
|| ! count(request()->get('super_attributes')))
|
||||
) {
|
||||
session()->flash('error', trans('admin::app.catalog.products.configurable-error'));
|
||||
|
||||
|
|
@ -153,7 +158,7 @@ class ProductController extends Controller
|
|||
$this->validate(request(), [
|
||||
'type' => 'required',
|
||||
'attribute_family_id' => 'required',
|
||||
'sku' => ['required', 'unique:products,sku', new \Webkul\Core\Contracts\Validations\Slug],
|
||||
'sku' => ['required', 'unique:products,sku', new Slug],
|
||||
]);
|
||||
|
||||
$product = $this->productRepository->create(request()->all());
|
||||
|
|
@ -166,7 +171,8 @@ 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)
|
||||
|
|
@ -183,8 +189,9 @@ 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)
|
||||
|
|
@ -201,20 +208,20 @@ class ProductController extends Controller
|
|||
if (count($customAttributes)) {
|
||||
foreach ($customAttributes as $attribute) {
|
||||
if ($attribute->type == 'multiselect') {
|
||||
array_push($multiselectAttributeCodes,$attribute->code);
|
||||
}
|
||||
array_push($multiselectAttributeCodes, $attribute->code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($multiselectAttributeCodes)) {
|
||||
foreach ($multiselectAttributeCodes as $multiselectAttributeCode) {
|
||||
if(! isset($data[$multiselectAttributeCode])){
|
||||
if (! isset($data[$multiselectAttributeCode])) {
|
||||
$data[$multiselectAttributeCode] = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$product = $this->productRepository->update($data, $id);
|
||||
|
||||
session()->flash('success', trans('admin::app.response.update-success', ['name' => 'Product']));
|
||||
|
|
@ -225,7 +232,8 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Uploads downloadable file
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function uploadLink($id)
|
||||
|
|
@ -235,10 +243,92 @@ class ProductController extends Controller
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a given Product. Do not copy the images.
|
||||
* Always make the copy is inactive so the admin is able to configure it before setting it live.
|
||||
*/
|
||||
public function copy(int $productId)
|
||||
{
|
||||
$originalProduct = $this->productRepository
|
||||
->findOrFail($productId)
|
||||
->load('attribute_family')
|
||||
->load('categories')
|
||||
->load('customer_group_prices')
|
||||
->load('inventories')
|
||||
->load('inventory_sources');
|
||||
|
||||
$copiedProduct = $originalProduct
|
||||
->replicate()
|
||||
->fill([
|
||||
// the sku and url_key needs to be unique and should be entered again newly by the admin:
|
||||
'sku' => 'temporary-sku-' . substr(md5(microtime()), 0, 6),
|
||||
]);
|
||||
|
||||
$copiedProduct->save();
|
||||
|
||||
$attributeName = Attribute::query()->where(['code' => 'name'])->firstOrFail();
|
||||
$attributeSku = Attribute::query()->where(['code' => 'sku'])->firstOrFail();
|
||||
$attributeStatus = Attribute::query()->where(['code' => 'status'])->firstOrFail();
|
||||
$attributeUrlKey = Attribute::query()->where(['code' => 'url_key'])->firstOrFail();
|
||||
|
||||
$newProductFlat = new ProductFlat();
|
||||
|
||||
// only obey copied locale and channel:
|
||||
if (isset($originalProduct->product_flats[0])) {
|
||||
$newProductFlat = $originalProduct->product_flats[0]->replicate();
|
||||
}
|
||||
|
||||
$newProductFlat->product_id = $copiedProduct->id;
|
||||
|
||||
foreach ($originalProduct->attribute_values as $oldValue) {
|
||||
$newValue = $oldValue->replicate();
|
||||
|
||||
if ($oldValue->attribute_id === $attributeName->id) {
|
||||
$newValue->text_value = __('admin::app.copy-of') . ' ' . $originalProduct->name;
|
||||
$newProductFlat->name = __('admin::app.copy-of') . ' ' . $originalProduct->name;
|
||||
}
|
||||
|
||||
if ($oldValue->attribute_id === $attributeUrlKey->id) {
|
||||
$newValue->text_value = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key;
|
||||
$newProductFlat->url_key = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key;
|
||||
}
|
||||
|
||||
if ($oldValue->attribute_id === $attributeSku->id) {
|
||||
$newValue->text_value = $copiedProduct->sku;
|
||||
$newProductFlat->sku = $copiedProduct->sku;
|
||||
}
|
||||
|
||||
if ($oldValue->attribute_id === $attributeStatus->id) {
|
||||
$newValue->boolean_value = 0;
|
||||
$newProductFlat->status = 0;
|
||||
}
|
||||
|
||||
$copiedProduct->attribute_values()->save($newValue);
|
||||
|
||||
}
|
||||
|
||||
$newProductFlat->save();
|
||||
|
||||
foreach ($originalProduct->categories as $category) {
|
||||
$copiedProduct->categories()->save($category);
|
||||
}
|
||||
|
||||
foreach ($originalProduct->inventories as $inventory) {
|
||||
$copiedProduct->inventories()->save($inventory);
|
||||
}
|
||||
|
||||
foreach ($originalProduct->customer_group_prices as $customer_group_price) {
|
||||
$copiedProduct->customer_group_prices()->save($customer_group_price);
|
||||
}
|
||||
|
||||
return redirect()->to(route('admin.catalog.products.edit', ['id' => $copiedProduct->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads downloadable sample file
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function uploadSample($id)
|
||||
|
|
@ -251,7 +341,8 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
|
|
@ -264,7 +355,7 @@ class ProductController extends Controller
|
|||
session()->flash('success', trans('admin::app.response.delete-success', ['name' => 'Product']));
|
||||
|
||||
return response()->json(['message' => true], 200);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
report($e);
|
||||
|
||||
session()->flash('error', trans('admin::app.response.delete-failed', ['name' => 'Product']));
|
||||
|
|
@ -363,11 +454,12 @@ class ProductController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Download image or file
|
||||
*
|
||||
* @param int $productId
|
||||
* @param int $attributeId
|
||||
* @param int $productId
|
||||
* @param int $attributeId
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download($productId, $attributeId)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
namespace Webkul\Product\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Webkul\Attribute\Models\AttributeFamilyProxy;
|
||||
use Webkul\Category\Models\CategoryProxy;
|
||||
use Webkul\Attribute\Models\AttributeProxy;
|
||||
use Webkul\Product\Database\Eloquent\Builder;
|
||||
use Webkul\Attribute\Models\AttributeFamilyProxy;
|
||||
use Webkul\Inventory\Models\InventorySourceProxy;
|
||||
use Webkul\Attribute\Repositories\AttributeRepository;
|
||||
use Webkul\Product\Contracts\Product as ProductContract;
|
||||
|
||||
class Product extends Model implements ProductContract
|
||||
|
|
@ -220,8 +222,8 @@ class Product extends Model implements ProductContract
|
|||
public function inventory_source_qty($inventorySourceId)
|
||||
{
|
||||
return $this->inventories()
|
||||
->where('inventory_source_id', $inventorySourceId)
|
||||
->sum('qty');
|
||||
->where('inventory_source_id', $inventorySourceId)
|
||||
->sum('qty');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -283,6 +285,7 @@ class Product extends Model implements ProductContract
|
|||
*
|
||||
* @param Group $group
|
||||
* @param bool $skipSuperAttribute
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEditableAttributes($group = null, $skipSuperAttribute = true)
|
||||
|
|
@ -293,7 +296,8 @@ class Product extends Model implements ProductContract
|
|||
/**
|
||||
* Get an attribute from the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($key)
|
||||
|
|
@ -305,8 +309,8 @@ class Product extends Model implements ProductContract
|
|||
if (isset($this->id)) {
|
||||
$this->attributes[$key] = '';
|
||||
|
||||
$attribute = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
|
||||
->getAttributeByCode($key);
|
||||
$attribute = core()->getSingletonInstance(AttributeRepository::class)
|
||||
->getAttributeByCode($key);
|
||||
|
||||
$this->attributes[$key] = $this->getCustomAttributeValue($attribute);
|
||||
|
||||
|
|
@ -327,8 +331,9 @@ class Product extends Model implements ProductContract
|
|||
$hiddenAttributes = $this->getHidden();
|
||||
|
||||
if (isset($this->id)) {
|
||||
$familyAttributes = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
|
||||
->getFamilyAttributes($this->attribute_family);
|
||||
$familyAttributes = core()
|
||||
->getSingletonInstance(AttributeRepository::class)
|
||||
->getFamilyAttributes($this->attribute_family);
|
||||
|
||||
foreach ($familyAttributes as $attribute) {
|
||||
if (in_array($attribute->code, $hiddenAttributes)) {
|
||||
|
|
@ -377,12 +382,13 @@ class Product extends Model implements ProductContract
|
|||
/**
|
||||
* Overrides the default Eloquent query builder
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newEloquentBuilder($query)
|
||||
{
|
||||
return new \Webkul\Product\Database\Eloquent\Builder($query);
|
||||
return new Builder($query);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Codeception\Actor;
|
||||
use Webkul\User\Models\Admin;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -22,7 +23,7 @@ use Illuminate\Routing\RouteCollection;
|
|||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class FunctionalTester extends \Codeception\Actor
|
||||
class FunctionalTester extends Actor
|
||||
{
|
||||
use _generated\FunctionalTesterActions;
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ class FunctionalTester extends \Codeception\Actor
|
|||
}
|
||||
|
||||
if (! $admin) {
|
||||
throw new \Exception(
|
||||
throw new Exception(
|
||||
'Admin user not found in database. Please ensure Seeders are executed');
|
||||
}
|
||||
|
||||
|
|
@ -85,12 +86,17 @@ class FunctionalTester extends \Codeception\Actor
|
|||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $params
|
||||
* @param bool $routeCheck set this to false if the action is doing a redirection
|
||||
*/
|
||||
public function amOnAdminRoute(string $name, array $params = [])
|
||||
public function amOnAdminRoute(string $name, array $params = [], bool $routeCheck = true)
|
||||
{
|
||||
$I = $this;
|
||||
$I->amOnRoute($name, $params);
|
||||
$I->seeCurrentRouteIs($name);
|
||||
|
||||
if ($routeCheck) {
|
||||
$I->seeCurrentRouteIs($name);
|
||||
}
|
||||
|
||||
/** @var RouteCollection $routes */
|
||||
$routes = Route::getRoutes();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Functional\Product;
|
||||
|
||||
use FunctionalTester;
|
||||
use Webkul\Product\Models\Product;
|
||||
use Webkul\Product\Models\ProductFlat;
|
||||
use Webkul\Core\Helpers\Laravel5Helper;
|
||||
use Webkul\Product\Models\ProductInventory;
|
||||
use Webkul\Product\Models\ProductAttributeValue;
|
||||
|
||||
class ProductCopyCest
|
||||
{
|
||||
|
||||
public function testProductCopy(FunctionalTester $I)
|
||||
{
|
||||
$I->loginAsAdmin();
|
||||
|
||||
$originalName = $I->fake()->name;
|
||||
|
||||
$original = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, [
|
||||
'productInventory' => [
|
||||
'qty' => 10,
|
||||
],
|
||||
'attributeValues' => [
|
||||
'name' => $originalName,
|
||||
],
|
||||
]);
|
||||
|
||||
$count = count(Product::all());
|
||||
|
||||
$I->amOnAdminRoute('admin.catalog.products.copy', ['id' => $original->id], false);
|
||||
|
||||
$copiedProduct = $I->grabRecord(Product::class, [
|
||||
'id' => $original->id + 1,
|
||||
'parent_id' => $original->parent_id,
|
||||
'attribute_family_id' => $original->attribute_family_id,
|
||||
]);
|
||||
|
||||
$I->seeRecord(ProductAttributeValue::class, [
|
||||
'attribute_id' => 2,
|
||||
'product_id' => $copiedProduct->id,
|
||||
'text_value' => 'Copy of ' . $originalName,
|
||||
]);
|
||||
|
||||
// url_key
|
||||
$I->seeRecord(ProductAttributeValue::class, [
|
||||
'attribute_id' => 3,
|
||||
'product_id' => $copiedProduct->id,
|
||||
'text_value' => 'copy-of-' . $original->url_key,
|
||||
]);
|
||||
|
||||
// sku
|
||||
$I->seeRecord(ProductAttributeValue::class, [
|
||||
'attribute_id' => 1,
|
||||
'product_id' => $copiedProduct->id,
|
||||
]);
|
||||
|
||||
// sku
|
||||
$I->dontSeeRecord(ProductAttributeValue::class, [
|
||||
'attribute_id' => 1,
|
||||
'product_id' => $copiedProduct->id,
|
||||
'text_value' => $original->sku,
|
||||
]);
|
||||
|
||||
// status
|
||||
$I->seeRecord(ProductAttributeValue::class, [
|
||||
'attribute_id' => 8,
|
||||
'boolean_value' => 0,
|
||||
]);
|
||||
|
||||
$I->seeRecord(ProductInventory::class, [
|
||||
'product_id' => $copiedProduct->id,
|
||||
'qty' => 10,
|
||||
]);
|
||||
|
||||
$I->seeRecord(ProductFlat::class, [
|
||||
'product_id' => $copiedProduct->id,
|
||||
'name' => 'Copy of ' . $originalName,
|
||||
]);
|
||||
|
||||
$I->assertCount($count + 1, Product::all());
|
||||
|
||||
$I->seeResponseCodeIsSuccessful();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue