add the ability to copy an configurable product

This commit is contained in:
Herbert Maschke 2020-08-05 10:52:13 +02:00
parent a9b79d7f4d
commit f91533f47b
3 changed files with 135 additions and 57 deletions

View File

@ -4,12 +4,15 @@
.table th.price, .table th.weight {
width: 100px;
}
.table th.actions {
width: 85px;
}
.table td.actions .icon {
margin-top: 8px;
}
.table td.actions .icon.pencil-lg-icon {
margin-right: 10px;
}
@ -48,17 +51,31 @@
@parent
<script type="text/x-template" id="variant-form-template">
<form method="POST" action="{{ route('admin.catalog.products.store') }}" data-vv-scope="add-variant-form" @submit.prevent="addVariant('add-variant-form')">
<form method="POST" action="{{ route('admin.catalog.products.store') }}"
data-vv-scope="add-variant-form" @submit.prevent="addVariant('add-variant-form')">
<div class="page-content">
<div class="form-container">
<div v-for='(attribute, index) in super_attributes' class="control-group" :class="[errors.has('add-variant-form.' + attribute.code) ? 'has-error' : '']">
<label :for="attribute.code" class="required">@{{ attribute.admin_name }}</label>
<select v-validate="'required'" v-model="variant[attribute.code]" class="control" :id="attribute.code" :name="attribute.code" :data-vv-as="'&quot;' + attribute.admin_name + '&quot;'">
<option v-for='(option, index) in attribute.options' :value="option.id">@{{ option.admin_name }}</option>
<div v-for='(attribute, index) in super_attributes' class="control-group"
:class="[errors.has('add-variant-form.' + attribute.code) ? 'has-error' : '']"
>
<label :for="attribute.code" class="required">@{{ attribute.admin_name
}}</label>
<select
v-validate="'required'"
v-model="variant[attribute.code]"
class="control"
:id="attribute.code"
:name="attribute.code"
:data-vv-as="'&quot;' + attribute.admin_name + '&quot;'"
>
<option v-for='(option, index) in attribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
<span class="control-error" v-if="errors.has('add-variant-form.' + attribute.code)">@{{ errors.first('add-variant-form.' + attribute.code) }}</span>
<span class="control-error"
v-if="errors.has('add-variant-form.' + attribute.code)">@{{ errors.first('add-variant-form.' + attribute.code) }}</span>
</div>
<button type="submit" class="btn btn-lg btn-primary">
@ -76,25 +93,28 @@
<table>
<thead>
<tr>
<th class="sku">{{ __('admin::app.catalog.products.sku') }}</th>
<th>{{ __('admin::app.catalog.products.name') }}</th>
<tr>
<th class="sku">{{ __('admin::app.catalog.products.sku') }}</th>
<th>{{ __('admin::app.catalog.products.name') }}</th>
@foreach ($product->super_attributes as $attribute)
<th class="{{ $attribute->code }}" style="width: 150px">{{ $attribute->admin_name }}</th>
@endforeach
@foreach ($product->super_attributes as $attribute)
<th class="{{ $attribute->code }}"
style="width: 150px">{{ $attribute->admin_name }}</th>
@endforeach
<th class="qty">{{ __('admin::app.catalog.products.qty') }}</th>
<th class="price">{{ __('admin::app.catalog.products.price') }}</th>
<th class="weight">{{ __('admin::app.catalog.products.weight') }}</th>
<th class="status">{{ __('admin::app.catalog.products.status') }}</th>
<th class="actions"></th>
</tr>
<th class="qty">{{ __('admin::app.catalog.products.qty') }}</th>
<th class="price">{{ __('admin::app.catalog.products.price') }}</th>
<th class="weight">{{ __('admin::app.catalog.products.weight') }}</th>
<th class="status">{{ __('admin::app.catalog.products.status') }}</th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<variant-item v-for='(variant, index) in variants' :variant="variant" :key="index" :index="variant.id" @onRemoveVariant="removeVariant($event)"></variant-item>
<variant-item v-for='(variant, index) in variants' :variant="variant" :key="index"
:index="variant.id"
@onRemoveVariant="removeVariant($event)"></variant-item>
</tbody>
@ -105,23 +125,32 @@
<script type="text/x-template" id="variant-item-template">
<tr>
<td>
<div class="control-group" :class="[errors.has(variantInputName + '[sku]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.sku" :name="[variantInputName + '[sku]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.sku') }}&quot;" v-slugify/>
<div class="control-group"
:class="[errors.has(variantInputName + '[sku]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.sku"
:name="[variantInputName + '[sku]']" class="control"
data-vv-as="&quot;{{ __('admin::app.catalog.products.sku') }}&quot;"
v-slugify/>
<span class="control-error" v-if="errors.has(variantInputName + '[sku]')">@{{ errors.first(variantInputName + '[sku]') }}</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(variantInputName + '[name]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.name" :name="[variantInputName + '[name]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.name') }}&quot;"/>
<div class="control-group"
:class="[errors.has(variantInputName + '[name]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.name"
:name="[variantInputName + '[name]']" class="control"
data-vv-as="&quot;{{ __('admin::app.catalog.products.name') }}&quot;"/>
<span class="control-error" v-if="errors.has(variantInputName + '[name]')">@{{ errors.first(variantInputName + '[name]') }}</span>
</div>
</td>
<td v-for='(attribute, index) in superAttributes'>
<div class="control-group">
<input type="hidden" :name="[variantInputName + '[' + attribute.code + ']']" :value="variant[attribute.code]"/>
<input type="text" class="control" :value="optionName(variant[attribute.code])" readonly/>
<input type="hidden" :name="[variantInputName + '[' + attribute.code + ']']"
:value="variant[attribute.code]"/>
<input type="text" class="control" :value="optionName(variant[attribute.code])"
readonly/>
</div>
</td>
@ -135,10 +164,16 @@
<div class="dropdown-container">
<ul>
<li v-for='(inventorySource, index) in inventorySources'>
<div class="control-group" :class="[errors.has(variantInputName + '[inventories][' + inventorySource.id + ']') ? 'has-error' : '']">
<div class="control-group"
:class="[errors.has(variantInputName + '[inventories][' + inventorySource.id + ']') ? 'has-error' : '']">
<label>@{{ inventorySource.name }}</label>
<input type="text" v-validate="'numeric|min:0'" :name="[variantInputName + '[inventories][' + inventorySource.id + ']']" v-model="inventories[inventorySource.id]" class="control" v-on:keyup="updateTotalQty()" :data-vv-as="'&quot;' + inventorySource.name + '&quot;'"/>
<span class="control-error" v-if="errors.has(variantInputName + '[inventories][' + inventorySource.id + ']')">@{{ errors.first(variantInputName + '[inventories][' + inventorySource.id + ']') }}</span>
<input type="text" v-validate="'numeric|min:0'"
:name="[variantInputName + '[inventories][' + inventorySource.id + ']']"
v-model="inventories[inventorySource.id]" class="control"
v-on:keyup="updateTotalQty()"
:data-vv-as="'&quot;' + inventorySource.name + '&quot;'"/>
<span class="control-error"
v-if="errors.has(variantInputName + '[inventories][' + inventorySource.id + ']')">@{{ errors.first(variantInputName + '[inventories][' + inventorySource.id + ']') }}</span>
</div>
</li>
</ul>
@ -147,30 +182,42 @@
</td>
<td>
<div class="control-group" :class="[errors.has(variantInputName + '[price]') ? 'has-error' : '']">
<input type="number" v-validate="'required'" v-model="variant.price" :name="[variantInputName + '[price]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.price') }}&quot;" step="any"/>
<div class="control-group"
:class="[errors.has(variantInputName + '[price]') ? 'has-error' : '']">
<input type="number" v-validate="'required'" v-model="variant.price"
:name="[variantInputName + '[price]']" class="control"
data-vv-as="&quot;{{ __('admin::app.catalog.products.price') }}&quot;"
step="any"/>
<span class="control-error" v-if="errors.has(variantInputName + '[price]')">@{{ errors.first(variantInputName + '[price]') }}</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(variantInputName + '[weight]') ? 'has-error' : '']">
<input type="number" v-validate="'required'" v-model="variant.weight" :name="[variantInputName + '[weight]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.weight') }}&quot;" step="any"/>
<div class="control-group"
:class="[errors.has(variantInputName + '[weight]') ? 'has-error' : '']">
<input type="number" v-validate="'required'" v-model="variant.weight"
:name="[variantInputName + '[weight]']" class="control"
data-vv-as="&quot;{{ __('admin::app.catalog.products.weight') }}&quot;"
step="any"/>
<span class="control-error" v-if="errors.has(variantInputName + '[weight]')">@{{ errors.first(variantInputName + '[weight]') }}</span>
</div>
</td>
<td>
<div class="control-group">
<select type="text" v-model="variant.status" :name="[variantInputName + '[status]']" class="control">
<option value="1" :selected="variant.status">{{ __('admin::app.catalog.products.enabled') }}</option>
<option value="0" :selected="!variant.status">{{ __('admin::app.catalog.products.disabled') }}</option>
<select type="text" v-model="variant.status"
:name="[variantInputName + '[status]']" class="control">
<option value="1"
:selected="variant.status">{{ __('admin::app.catalog.products.enabled') }}</option>
<option value="0"
:selected="!variant.status">{{ __('admin::app.catalog.products.disabled') }}</option>
</select>
</div>
</td>
<td class="actions">
<a :href="['{{ route('admin.catalog.products.index') }}/edit/' + variant.id]"><i class="icon pencil-lg-icon"></i></a>
<a :href="['{{ route('admin.catalog.products.index') }}/edit/' + variant.id]"><i
class="icon pencil-lg-icon"></i></a>
<i class="icon remove-icon" @click="removeVariant()"></i>
</td>
</tr>
@ -190,7 +237,7 @@
Vue.component('variant-form', {
data: function() {
data: function () {
return {
variant: {},
super_attributes: super_attributes
@ -209,7 +256,7 @@
if (result) {
var this_this = this;
var filteredVariants = variants.filter(function(variant) {
var filteredVariants = variants.filter(function (variant) {
var matchCount = 0;
for (var key in this_this.variant) {
@ -224,7 +271,10 @@
if (filteredVariants.length) {
this.$parent.closeModal();
window.flashMessages = [{'type': 'alert-error', 'message': "{{ __('admin::app.catalog.products.variant-already-exist-message') }}" }];
window.flashMessages = [{
'type': 'alert-error',
'message': "{{ __('admin::app.catalog.products.variant-already-exist-message') }}"
}];
this.$root.addFlashMessages()
} else {
@ -234,12 +284,12 @@
}
variants.push(Object.assign({
sku: '{{ $product->sku }}' + '-variant-' + optionIds.join('-'),
name: '',
price: 0,
weight: 0,
status: 1
}, this.variant));
sku: '{{ $product->sku }}' + '-variant-' + optionIds.join('-'),
name: '',
price: 0,
weight: 0,
status: 1
}, this.variant));
this.resetModel();
@ -252,7 +302,7 @@
resetModel: function () {
var this_this = this;
this.super_attributes.forEach(function(attribute) {
this.super_attributes.forEach(function (attribute) {
this_this.variant[attribute.code] = '';
})
}
@ -265,7 +315,7 @@
inject: ['$validator'],
data: function() {
data: function () {
return {
variants: variants,
@ -285,7 +335,10 @@
var inventories = [];
for (var inventorySourceId in variant['inventories']) {
inventories.push({'qty': variant['inventories'][inventorySourceId], 'inventory_source_id': inventorySourceId})
inventories.push({
'qty': variant['inventories'][inventorySourceId],
'inventory_source_id': inventorySourceId
})
}
variant['inventories'] = inventories;
@ -299,7 +352,10 @@
variants[index][code] = [];
for (var inventorySourceId in variant[code]) {
variants[index][code].push({'qty': variant[code][inventorySourceId], 'inventory_source_id': inventorySourceId})
variants[index][code].push({
'qty': variant[code][inventorySourceId],
'inventory_source_id': inventorySourceId
})
}
}
}
@ -310,7 +366,7 @@
},
methods: {
removeVariant: function(variant) {
removeVariant: function (variant) {
let index = this.variants.indexOf(variant)
this.variants.splice(index, 1)
@ -327,7 +383,7 @@
inject: ['$validator'],
data: function() {
data: function () {
return {
inventorySources: @json($inventorySources),
inventories: {},
@ -339,7 +395,7 @@
created: function () {
var this_this = this;
this.inventorySources.forEach(function(inventorySource) {
this.inventorySources.forEach(function (inventorySource) {
this_this.inventories[inventorySource.id] = this_this.sourceInventoryQty(inventorySource.id)
this_this.totalQty += parseInt(this_this.inventories[inventorySource.id]);
})
@ -362,8 +418,8 @@
optionName: function (optionId) {
var optionName = '';
this.superAttributes.forEach(function(attribute) {
attribute.options.forEach(function(option) {
this.superAttributes.forEach(function (attribute) {
attribute.options.forEach(function (option) {
if (optionId == option.id) {
optionName = option.admin_name;
}
@ -374,10 +430,10 @@
},
sourceInventoryQty: function (inventorySourceId) {
if (! Array.isArray(this.variant.inventories))
if (!Array.isArray(this.variant.inventories))
return 0;
var inventories = this.variant.inventories.filter(function(inventory) {
var inventories = this.variant.inventories.filter(function (inventory) {
return inventorySourceId === parseInt(inventory.inventory_source_id);
})

View File

@ -2,6 +2,8 @@
namespace Webkul\Product\Models;
use Exception;
use Webkul\Product\Type\AbstractType;
use Illuminate\Database\Eloquent\Model;
use Webkul\Category\Models\CategoryProxy;
use Webkul\Attribute\Models\AttributeProxy;
@ -240,6 +242,12 @@ class Product extends Model implements ProductContract
$this->typeInstance = app(config('product_types.' . $this->type . '.class'));
if (! $this->typeInstance instanceof AbstractType) {
throw new Exception(
"Please ensure the product type '{$this->type}' is configured in your application."
);
}
$this->typeInstance->setProduct($this);
return $this->typeInstance;

View File

@ -657,9 +657,23 @@ class ProductRepository extends Repository
}
}
if (! isset($attributesToSkip['super_attributes'])) {
foreach ($originalProduct->super_attributes as $super_attribute) {
$copiedProduct->super_attributes()->save($super_attribute);
}
}
if (! isset($attributesToSkip['bundle_options'])) {
foreach ($originalProduct->bundle_options as $bundle_option) {
$copiedProduct->bundle_options()->save($bundle_option);
}
}
if (! isset($attributesToSkip['variants'])) {
foreach ($originalProduct->variants as $variant) {
$this->copy($variant);
$variant = $this->copy($variant);
$variant->parent_id = $copiedProduct->id;
$variant->save();
}
}
}