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');
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
return array(
|
||||
'save' => 'Speichern',
|
||||
'copy-of' => 'Kopie von',
|
||||
'copy-of-slug' => 'kopie-von',
|
||||
'create' => 'Erstellen',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Löschen',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
return [
|
||||
'save' => 'Save',
|
||||
'copy-of' => 'Copy of',
|
||||
'copy-of-slug' => 'copy-of',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -73,6 +77,7 @@ class ProductController extends Controller
|
|||
* @param \Webkul\Product\Repositories\ProductDownloadableSampleRepository $productDownloadableSampleRepository
|
||||
* @param \Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
|
||||
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
|
|
@ -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());
|
||||
|
|
@ -167,6 +172,7 @@ class ProductController extends Controller
|
|||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function edit($id)
|
||||
|
|
@ -185,6 +191,7 @@ class ProductController extends Controller
|
|||
*
|
||||
* @param \Webkul\Product\Http\Requests\ProductForm $request
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(ProductForm $request, $id)
|
||||
|
|
@ -226,6 +233,7 @@ class ProductController extends Controller
|
|||
* Uploads downloadable file
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function uploadSample($id)
|
||||
|
|
@ -252,6 +342,7 @@ class ProductController extends Controller
|
|||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @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']));
|
||||
|
|
@ -368,6 +459,7 @@ class ProductController extends Controller
|
|||
*
|
||||
* @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
|
||||
|
|
@ -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)
|
||||
|
|
@ -294,6 +297,7 @@ class Product extends Model implements ProductContract
|
|||
* Get an attribute from the model.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($key)
|
||||
|
|
@ -305,7 +309,7 @@ class Product extends Model implements ProductContract
|
|||
if (isset($this->id)) {
|
||||
$this->attributes[$key] = '';
|
||||
|
||||
$attribute = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
|
||||
$attribute = core()->getSingletonInstance(AttributeRepository::class)
|
||||
->getAttributeByCode($key);
|
||||
|
||||
$this->attributes[$key] = $this->getCustomAttributeValue($attribute);
|
||||
|
|
@ -327,7 +331,8 @@ class Product extends Model implements ProductContract
|
|||
$hiddenAttributes = $this->getHidden();
|
||||
|
||||
if (isset($this->id)) {
|
||||
$familyAttributes = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
|
||||
$familyAttributes = core()
|
||||
->getSingletonInstance(AttributeRepository::class)
|
||||
->getFamilyAttributes($this->attribute_family);
|
||||
|
||||
foreach ($familyAttributes as $attribute) {
|
||||
|
|
@ -378,11 +383,12 @@ class Product extends Model implements ProductContract
|
|||
* Overrides the default Eloquent query builder
|
||||
*
|
||||
* @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);
|
||||
|
||||
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