Added product creation page

This commit is contained in:
jitendra 2018-08-09 10:05:13 +05:30
parent 8a6f670a4f
commit ff50784aa8
74 changed files with 1648 additions and 427 deletions

View File

@ -128,7 +128,15 @@ class EventServiceProvider extends ServiceProvider
});
Event::listen('admin.catalog.products.accordian.build', function($accordian) {
$accordian->add('categories', 'Categories', 'admin::catalog.products.accordians.categories', 1);
$accordian->add('images', 'Inventories', 'admin::catalog.products.accordians.inventories', 1);
$accordian->add('images', 'Images', 'admin::catalog.products.accordians.images', 2);
$accordian->add('categories', 'Categories', 'admin::catalog.products.accordians.categories', 3);
$accordian->add('variations', 'Variations', 'admin::catalog.products.accordians.variations', 4);
// $accordian->add('product-links', 'Linked Products', 'admin::catalog.products.accordians.product-links', 4);
});
}
}

View File

@ -39,10 +39,16 @@ $(document).ready(function () {
addServerErrors() {
var scope = null;
for (var key in serverErrors) {
var inputName = key;
if (key.indexOf(".") !== -1) {
inputName = key.replace(".", "[") + "]";
}
var inputNames = [];
key.split('.').forEach(function(chunk, index) {
if(index) {
inputNames.push('[' + chunk + ']')
} else {
inputNames.push(chunk)
}
})
var inputName = inputNames.join('');
const field = this.$validator.fields.find({
name: inputName,

View File

@ -1,53 +0,0 @@
<template>
<div class="control-group" :class="[errors.has(name) ? 'has-error' : '']">
<label :for="name" :class="[is_required ? 'required' : '']">
{{ label }}
</label>
<flat-pickr v-model="finalvalue" class="control" v-validate="'required'" :config="config" :name="name" @on-open="open()"></flat-pickr>
<span class="control-error" v-if="errors.has(name)">{{ errors.first(name) }}</span>
</div>
</template>
<script>
import flatPickr from 'vue-flatpickr-component';
// Vue.use(flatPickr);
export default {
props: {
label: String,
name: String,
required: String,
value: String,
},
computed: {
is_required () {
return Number(this.required)
}
},
methods: {
open () {
console.log(this.$validator)
}
},
data () {
return {
finalvalue: this.value,
date: new Date(),
config: {
allowInput: true,
altFormat: 'Y-m-d H:i:s',
dateFormat: 'Y-m-d H:i:s',
enableTime: true
},
}
}
};
</script>

View File

@ -202,6 +202,17 @@ body {
}
}
.control-group {
label {
width: 70%;
.locale {
float: right;
color: #8E8E8E;
}
}
}
//style for dummy datagrid
// .page-content {
// .table-container {

View File

@ -40,6 +40,7 @@ return [
'all' => 'All'
],
'users' => [
'title' => 'Users',
'add-user-title' => 'Add User',
'edit-user-title' => 'Edit User',
'save-btn-title' => 'Save User',
@ -64,7 +65,7 @@ return [
],
'catalog' => [
'products' => [
'products' => 'products',
'products' => 'Products',
'add-product-btn-title' => 'Add Product',
'add-title' => 'Add Product',
'edit-title' => 'Edit Product',
@ -79,7 +80,20 @@ return [
'attribute-header' => 'Attribute(s)',
'attribute-option-header' => 'Attribute Option(s)',
'no' => 'No',
'yes' => 'Yes'
'yes' => 'Yes',
'disabled' => 'Disabled',
'enabled' => 'Enabled',
'add-variant-btn-title' => 'Add Variant',
'name' => 'Name',
'qty' => 'Qty',
'price' => 'Price',
'weight' => 'Weight',
'status' => 'Status',
'enabled' => 'Enabled',
'disabled' => 'Disabled',
'add-variant-title' => 'Add Variant',
'variant-already-exist-message' => 'Variant with same attribute options already exists.',
'add-image-btn-title' => 'Add Image'
],
'attributes' => [
'add-title' => 'Add Attribute',

View File

@ -29,7 +29,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code">{{ __('admin::app.catalog.attributes.code') }}</label>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}"/>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -131,13 +131,13 @@
</select>
</div>
<div class="control-group">
<!--<div class="control-group">
<label for="value_per_channel">{{ __('admin::app.catalog.attributes.value_per_channel') }}</label>
<select class="control" id="value_per_channel" name="value_per_channel">
<option value="0">{{ __('admin::app.catalog.attributes.no') }}</option>
<option value="1">{{ __('admin::app.catalog.attributes.yes') }}</option>
</select>
</div>
</div>-->
<div class="control-group">
<label for="is_filterable">{{ __('admin::app.catalog.attributes.is_filterable') }}</label>

View File

@ -4,7 +4,6 @@
{{ __('admin::app.catalog.attributes.edit-title') }}
@stop
@section('content')
<div class="content">
<form method="POST" action="{{ route('admin.catalog.attributes.update', $attribute->id) }}" @submit.prevent="onSubmit">
@ -30,7 +29,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.catalog.attributes.code') }}</label>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ $attribute->code }}" disabled="disabled"/>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ $attribute->code }}" disabled="disabled" v-code/>
<input type="hidden" name="code" value="{{ $attribute->code }}"/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -175,7 +174,7 @@
<input type="hidden" name="value_per_locale" value="{{ $attribute->value_per_locale }}"/>
</div>
<div class="control-group">
<!--<div class="control-group">
<label for="value_per_channel">{{ __('admin::app.catalog.attributes.value_per_channel') }}</label>
<select class="control" id="value_per_channel" name="value_per_channel" disabled>
<option value="0" {{ $attribute->value_per_channel ? '' : 'selected' }}>
@ -186,7 +185,7 @@
</option>
</select>
<input type="hidden" name="value_per_channel" value="{{ $attribute->value_per_channel }}"/>
</div>
</div>-->
<div class="control-group">
<label for="is_filterable">{{ __('admin::app.catalog.attributes.is_filterable') }}</label>

View File

@ -16,7 +16,7 @@
<div class="control-group">
<select class="control" id="locale-switcher" onChange="window.location.href = this.value">
@foreach(core()->allLocales() as $localeModel)
@foreach(core()->getAllLocales() as $localeModel)
<option value="{{ route('admin.catalog.categories.update', $category->id) . '?locale=' . $localeModel->code }}" {{ ($localeModel->code) == $locale ? 'selected' : '' }}>
{{ $localeModel->name }}

View File

@ -30,7 +30,7 @@
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.catalog.families.code') }}</label>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}"/>
<input type="text" v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -101,7 +101,7 @@
<script type="text/x-template" id="group-list-template">
<div>
<group-item v-for='(group, index) in groups' :group="group" :attributes="attributes" :key="index" :index="index" @onRemoveGroup="removeGroup($event)" @onAttributeAdd="addAttributes(index, $event)" @onAttributeRemove="removeAttribute(index, $event)"></group-item>
<group-item v-for='(group, index) in groups' :group="group" :custom_attributes="custom_attributes" :key="index" :index="index" @onRemoveGroup="removeGroup($event)" @onAttributeAdd="addAttributes(index, $event)" @onAttributeRemove="removeAttribute(index, $event)"></group-item>
</div>
</script>
@ -118,7 +118,7 @@
<input type="hidden" :name="[groupInputName + '[position]']" :value="group.position"/>
<input type="hidden" :name="[groupInputName + '[is_user_defined]']" :value="group.is_user_defined"/>
<div class="table" v-if="group.attributes.length" style="margin-bottom: 20px;">
<div class="table" v-if="group.custom_attributes.length" style="margin-bottom: 20px;">
<table>
<thead>
<tr>
@ -130,9 +130,9 @@
</thead>
<tbody>
<tr v-for='(attribute, index) in group.attributes'>
<tr v-for='(attribute, index) in group.custom_attributes'>
<td>
<input type="hidden" :name="[groupInputName + '[attributes][][id]']" :value="attribute.id"/>
<input type="hidden" :name="[groupInputName + '[custom_attributes][][id]']" :value="attribute.id"/>
@{{ attribute.code }}
</td>
<td>@{{ attribute.admin_name }}</td>
@ -156,7 +156,7 @@
<div class="dropdown-container">
<ul>
<li v-for='(attribute, index) in attributes' :data-id="attribute.id">
<li v-for='(attribute, index) in custom_attributes' :data-id="attribute.id">
<span class="checkbox">
<input type="checkbox" :id="attribute.id" :value="attribute.id"/>
<label class="checkbox-view" :for="attribute.id"></label>
@ -176,7 +176,7 @@
<script>
var groups = @json($attributeFamily ? $attributeFamily->attribute_groups : []);
var attributes = @json($attributes);
var custom_attributes = @json($custom_attributes);
Vue.component('group-form', {
@ -185,7 +185,7 @@
'groupName': '',
'position': '',
'is_user_defined': 1,
'attributes': []
'custom_attributes': []
}
}),
@ -217,7 +217,7 @@
groups = this.sortGroups();
this.group = {'groupName': '', 'position': '', 'is_user_defined': 1, 'attributes': []};
this.group = {'groupName': '', 'position': '', 'is_user_defined': 1, 'custom_attributes': []};
this.$parent.closeModal();
}
@ -239,18 +239,18 @@
data: () => ({
groups: groups,
attributes: attributes
custom_attributes: custom_attributes
}),
created () {
this.groups.forEach(function(group) {
group.attributes.forEach(function(attribute) {
var attribute = this.attributes.filter(attributeTemp => attributeTemp.id == attribute.id)
group.custom_attributes.forEach(function(attribute) {
var attribute = this.custom_attributes.filter(attributeTemp => attributeTemp.id == attribute.id)
if(attribute.length) {
let index = this.attributes.indexOf(attribute[0])
let index = this.custom_attributes.indexOf(attribute[0])
this.attributes.splice(index, 1)
this.custom_attributes.splice(index, 1)
}
});
@ -259,11 +259,11 @@
methods: {
removeGroup (group) {
group.attributes.forEach(function(attribute) {
this.attributes.push(attribute);
group.custom_attributes.forEach(function(attribute) {
this.custom_attributes.push(attribute);
})
this.attributes = this.sortAttributes();
this.custom_attributes = this.sortAttributes();
let index = groups.indexOf(group)
@ -272,28 +272,28 @@
addAttributes (groupIndex, attributeIds) {
attributeIds.forEach(function(attributeId) {
var attribute = this.attributes.filter(attribute => attribute.id == attributeId)
var attribute = this.custom_attributes.filter(attribute => attribute.id == attributeId)
this.groups[groupIndex].attributes.push(attribute[0]);
this.groups[groupIndex].custom_attributes.push(attribute[0]);
let index = this.attributes.indexOf(attribute[0])
let index = this.custom_attributes.indexOf(attribute[0])
this.attributes.splice(index, 1)
this.custom_attributes.splice(index, 1)
})
},
removeAttribute (groupIndex, attribute) {
let index = this.groups[groupIndex].attributes.indexOf(attribute)
let index = this.groups[groupIndex].custom_attributes.indexOf(attribute)
this.groups[groupIndex].attributes.splice(index, 1)
this.groups[groupIndex].custom_attributes.splice(index, 1)
this.attributes.push(attribute);
this.custom_attributes.push(attribute);
this.attributes = this.sortAttributes();
this.custom_attributes = this.sortAttributes();
},
sortAttributes () {
return this.attributes.sort(function(a, b) {
return this.custom_attributes.sort(function(a, b) {
return a.id - b.id;
});
}
@ -301,7 +301,7 @@
})
Vue.component('group-item', {
props: ['index', 'group', 'attributes'],
props: ['index', 'group', 'custom_attributes'],
template: "#group-item-template",

View File

@ -30,7 +30,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<input type="text" v-validate="'required'" name="code" class="control" id="code" value="{{ $attributeFamily->code }}" disabled="disabled"/>
<input type="text" v-validate="'required'" name="code" class="control" id="code" value="{{ $attributeFamily->code }}" disabled="disabled" v-code/>
<input type="hidden" name="code" value="{{ $attributeFamily->code }}"/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -102,7 +102,7 @@
<script type="text/x-template" id="group-list-template">
<div style="margin-top: 20px">
<group-item v-for='(group, index) in groups' :group="group" :attributes="attributes" :key="index" :index="index" @onRemoveGroup="removeGroup($event)" @onAttributeAdd="addAttributes(index, $event)" @onAttributeRemove="removeAttribute(index, $event)"></group-item>
<group-item v-for='(group, index) in groups' :group="group" :custom_attributes="custom_attributes" :key="index" :index="index" @onRemoveGroup="removeGroup($event)" @onAttributeAdd="addAttributes(index, $event)" @onAttributeRemove="removeAttribute(index, $event)"></group-item>
</div>
</script>
@ -118,7 +118,7 @@
<input type="hidden" :name="[groupInputName + '[name]']" :value="group.name ? group.name : group.groupName"/>
<input type="hidden":name="[groupInputName + '[position]']" :value="group.position"/>
<div class="table" v-if="group.attributes.length" style="margin-bottom: 20px;">
<div class="table" v-if="group.custom_attributes.length" style="margin-bottom: 20px;">
<table>
<thead>
<tr>
@ -130,9 +130,9 @@
</thead>
<tbody>
<tr v-for='(attribute, index) in group.attributes'>
<tr v-for='(attribute, index) in group.custom_attributes'>
<td>
<input type="hidden" :name="[groupInputName + '[attributes][][id]']" :value="attribute.id"/>
<input type="hidden" :name="[groupInputName + '[custom_attributes][][id]']" :value="attribute.id"/>
@{{ attribute.code }}
</td>
<td>@{{ attribute.admin_name }}</td>
@ -156,7 +156,7 @@
<div class="dropdown-container">
<ul>
<li v-for='(attribute, index) in attributes' :data-id="attribute.id">
<li v-for='(attribute, index) in custom_attributes' :data-id="attribute.id">
<span class="checkbox">
<input type="checkbox" :id="attribute.id" :value="attribute.id"/>
<label class="checkbox-view" :for="attribute.id"></label>
@ -176,7 +176,7 @@
<script>
var groups = @json($attributeFamily->attribute_groups);
var attributes = @json($attributes);
var custom_attributes = @json($custom_attributes);
Vue.component('group-form', {
@ -184,7 +184,7 @@
group: {
'groupName': '',
'position': '',
'attributes': []
'custom_attributes': []
}
}),
@ -216,7 +216,7 @@
groups = this.sortGroups();
this.group = {'groupName': '', 'position': '', 'attributes': []};
this.group = {'groupName': '', 'position': '', 'custom_attributes': []};
this.$parent.closeModal();
}
@ -238,18 +238,18 @@
data: () => ({
groups: groups,
attributes: attributes
custom_attributes: custom_attributes
}),
created () {
this.groups.forEach(function(group) {
group.attributes.forEach(function(attribute) {
var attribute = this.attributes.filter(attributeTemp => attributeTemp.id == attribute.id)
group.custom_attributes.forEach(function(attribute) {
var attribute = this.custom_attributes.filter(attributeTemp => attributeTemp.id == attribute.id)
if(attribute.length) {
let index = this.attributes.indexOf(attribute[0])
let index = this.custom_attributes.indexOf(attribute[0])
this.attributes.splice(index, 1)
this.custom_attributes.splice(index, 1)
}
});
@ -258,11 +258,11 @@
methods: {
removeGroup (group) {
group.attributes.forEach(function(attribute) {
this.attributes.push(attribute);
group.custom_attributes.forEach(function(attribute) {
this.custom_attributes.push(attribute);
})
this.attributes = this.sortAttributes();
this.custom_attributes = this.sortAttributes();
let index = groups.indexOf(group)
@ -271,28 +271,28 @@
addAttributes (groupIndex, attributeIds) {
attributeIds.forEach(function(attributeId) {
var attribute = this.attributes.filter(attribute => attribute.id == attributeId)
var attribute = this.custom_attributes.filter(attribute => attribute.id == attributeId)
this.groups[groupIndex].attributes.push(attribute[0]);
this.groups[groupIndex].custom_attributes.push(attribute[0]);
let index = this.attributes.indexOf(attribute[0])
let index = this.custom_attributes.indexOf(attribute[0])
this.attributes.splice(index, 1)
this.custom_attributes.splice(index, 1)
})
},
removeAttribute (groupIndex, attribute) {
let index = this.groups[groupIndex].attributes.indexOf(attribute)
let index = this.groups[groupIndex].custom_attributes.indexOf(attribute)
this.groups[groupIndex].attributes.splice(index, 1)
this.groups[groupIndex].custom_attributes.splice(index, 1)
this.attributes.push(attribute);
this.custom_attributes.push(attribute);
this.attributes = this.sortAttributes();
this.custom_attributes = this.sortAttributes();
},
sortAttributes () {
return this.attributes.sort(function(a, b) {
return this.custom_attributes.sort(function(a, b) {
return a.id - b.id;
});
}
@ -300,7 +300,7 @@
})
Vue.component('group-item', {
props: ['index', 'group', 'attributes'],
props: ['index', 'group', 'custom_attributes'],
template: "#group-item-template",

View File

@ -1,6 +1,9 @@
@if($categories->count())
<accordian :title="'{{ __($accordian['name']) }}'" :active="true">
<div slot="body">
<tree-view behavior="normal" value-field="id" name-field="categories" input-type="checkbox" items='@json($categories)' value='@json($product->categories->pluck("id"))'></tree-view>
</div>
</accordian>
</accordian>
@endif

View File

@ -0,0 +1,7 @@
<accordian :title="'{{ __($accordian['name']) }}'" :active="true">
<div slot="body">
<image-wrapper :button-label="'{{ __('admin::app.catalog.products.add-image-btn-title') }}'" input-name="images" multiple="true"></image-wrapper>
</div>
</accordian>

View File

@ -0,0 +1,29 @@
@if ($product->type != 'configurable')
<accordian :title="'{{ __($accordian['name']) }}'" :active="true">
<div slot="body">
@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;
?>
<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 }}"/>
<span class="control-error" v-if="errors.has('inventories[{{ $inventorySource->id }}]')">@{{ errors.first('inventories[{!! $inventorySource->id !!}]') }}</span>
</div>
@endforeach
</div>
</accordian>
@endif

View File

@ -0,0 +1,6 @@
<accordian :title="'{{ __($accordian['name']) }}'" :active="true">
<div slot="body">
</div>
</accordian>

View File

@ -0,0 +1,347 @@
@if ($product->type == 'configurable')
@section('css')
@parent
<style>
.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;
}
</style>
@stop
<accordian :title="'{{ __($accordian['name']) }}'" :active="true">
<div slot="body">
<button type="button" class="btn btn-md btn-primary" @click="showModal('addVariant')">
{{ __('admin::app.catalog.products.add-variant-btn-title') }}
</button>
<variant-list></variant-list>
</div>
</accordian>
<modal id="addVariant" :is-open="modalIds.addVariant">
<h3 slot="header">{{ __('admin::app.catalog.products.add-variant-title') }}</h3>
<div slot="body">
<variant-form></variant-form>
</div>
</modal>
@section('javascript')
@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')">
<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">
<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>
</div>
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.catalog.products.add-variant-title') }}
</button>
</div>
</div>
</form>
</script>
<script type="text/x-template" id="variant-list-template">
<div class="table" style="margin-top: 20px; overflow-x: unset;">
<table>
<thead>
<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
<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="index" @onRemoveVariant="removeVariant($event)"></variant-item>
</tbody>
</table>
</div>
</script>
<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" 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"/>
<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/>
</div>
</td>
<td>
<button style="width: 100%;" type="button" class="dropdown-btn dropdown-toggle">
@{{ totalQty }}
<i class="icon arrow-down-icon"></i>
</button>
<div class="dropdown-list">
<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' : '']">
<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()"/>
<span class="control-error" v-if="errors.has(variantInputName + '[inventories][' + inventorySource.id + ']')">@{{ errors.first(variantInputName + '[inventories][' + inventorySource.id + ']') }}</span>
</div>
</li>
</ul>
</div>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(variantInputName + '[price]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.price" :name="[variantInputName + '[price]']" class="control"/>
<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 + '[price]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" v-model="variant.weight" :name="[variantInputName + '[weight]']" class="control"/>
<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>
</div>
</td>
<td class="actions">
<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>
</script>
<script>
$(document).ready(function () {
Vue.config.ignoredElements = [
'variant-form',
'variant-list',
'variant-item'
];
});
var super_attributes = @json($product->super_attributes);
var variants = @json($product->variants);
Vue.component('variant-form', {
data: () => ({
variant: {},
super_attributes: super_attributes
}),
template: '#variant-form-template',
created () {
this.resetModel();
},
methods: {
addVariant (formScope) {
this.$validator.validateAll(formScope).then((result) => {
if (result) {
var this_this = this;
var filteredVariants = variants.filter(function(variant) {
var matchCount = 0;
for (var key in this_this.variant) {
if(variant[key] == this_this.variant[key]) {
matchCount++;
}
}
return matchCount == this_this.super_attributes.length;
})
if(filteredVariants.length) {
this.$parent.closeModal();
window.flashMessages = [{'type': 'alert-error', 'message': "{{ __('admin::app.catalog.products.variant-already-exist-message') }}" }];
this.$root.addFlashMessages()
} else {
var optionIds = [];
for (var key in this_this.variant) {
optionIds.push(this_this.variant[key]);
}
variants.push(Object.assign({
sku: '{{ $product->sku }}' + '-variant-' + optionIds.join('-'),
name: '',
price: 0,
weight: 0,
status: 1
}, this.variant));
this.resetModel();
this.$parent.closeModal();
}
}
});
},
resetModel () {
var this_this = this;
this.super_attributes.forEach(function(attribute) {
this_this.variant[attribute.code] = '';
})
}
}
});
Vue.component('variant-list', {
template: '#variant-list-template',
inject: ['$validator'],
data: () => ({
variants: variants,
superAttributes: super_attributes
}),
methods: {
removeVariant(variant) {
let index = this.variants.indexOf(variant)
this.variants.splice(index, 1)
},
}
});
Vue.component('variant-item', {
template: '#variant-item-template',
props: ['index', 'variant'],
inject: ['$validator'],
data: () => ({
inventorySources: @json($inventorySources),
inventories: {},
totalQty: 0,
superAttributes: super_attributes
}),
created () {
var this_this = this;
this.inventorySources.forEach(function(inventorySource) {
this_this.inventories[inventorySource.id] = this_this.sourceInventoryQty(inventorySource.id)
this_this.totalQty += parseInt(this_this.inventories[inventorySource.id]);
})
},
computed: {
variantInputName () {
if(this.variant.id)
return "variants[" + this.variant.id + "]";
return "variants[variant_" + this.index + "]";
}
},
methods: {
removeVariant () {
this.$emit('onRemoveVariant', this.variant)
},
optionName (optionId) {
var optionName = '';
this.superAttributes.forEach(function(attribute) {
attribute.options.forEach(function(option) {
if(optionId == option.id) {
optionName = option.admin_name;
}
});
})
return optionName;
},
sourceInventoryQty (inventorySourceId) {
var inventories = this.variant.inventories.filter(function(inventory) {
return inventorySourceId === inventory.inventory_source_id;
})
if(inventories.length)
return inventories[0]['qty'];
return 0;
},
updateTotalQty () {
this.totalQty = 0;
for (var key in this.inventories) {
this.totalQty += parseInt(this.inventories[key]);
}
}
}
});
</script>
@stop
@endif

View File

@ -1,5 +1,9 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.catalog.products.add-title') }}
@stop
@section('css')
<style>
.table td .label {

View File

@ -1,12 +1,44 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.catalog.products.edit-title') }}
@stop
@section('content')
<div class="content">
<form method="POST" action="{{ route('admin.catalog.products.update', $product->id) }}" @submit.prevent="onSubmit">
<?php $locale = request()->get('locale') ?: app()->getLocale(); ?>
<?php $channel = request()->get('channel') ?: channel()->getChannel(); ?>
<form method="POST" action="" @submit.prevent="onSubmit">
<div class="page-header">
<div class="page-title">
<h1>{{ __('admin::app.catalog.products.edit-title') }}</h1>
<div class="control-group">
<select class="control" id="channel-switcher" name="channel">
@foreach(channel()->getAllChannels() as $channelModel)
<option value="{{ $channelModel->code }}" {{ ($channelModel->code) == $channel ? 'selected' : '' }}>
{{ $channelModel->name }}
</option>
@endforeach
</select>
</div>
<div class="control-group">
<select class="control" id="locale-switcher" name="locale">
@foreach(core()->getAllLocales() as $localeModel)
<option value="{{ $localeModel->code }}" {{ ($localeModel->code) == $locale ? 'selected' : '' }}>
{{ $localeModel->name }}
</option>
@endforeach
</select>
</div>
</div>
<div class="page-action">
@ -19,29 +51,62 @@
<div class="page-content">
@csrf()
<input name="_method" type="hidden" value="PUT">
@foreach($product->attribute_family->attribute_groups as $attributeGroup)
@if(count($attributeGroup->attributes))
@if(count($attributeGroup->custom_attributes))
<accordian :title="'{{ __($attributeGroup->name) }}'" :active="true">
<div slot="body">
@foreach($attributeGroup->attributes as $attribute)
@foreach($attributeGroup->custom_attributes as $attribute)
@if(!$product->super_attributes->contains($attribute))
<?php
$validations = [];
if($attribute->is_required) {
array_push($validations, 'required');
}
$disabled = false;
if($product->type == 'configurable' && in_array($attribute->code, ['price', 'cost', 'special_price', 'special_price_from', 'special_price_to', 'width', 'height', 'depth', 'weight'])) {
if(!$attribute->is_required)
continue;
array_push($validations, $attribute->validation);
$disabled = true;
} else {
if($attribute->is_required) {
array_push($validations, 'required');
}
array_push($validations, $attribute->validation);
}
$validations = implode('|', array_filter($validations));
?>
@if(view()->exists($typeView = 'admin::catalog.products.field-types.' . $attribute->type))
@include ($typeView)
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
<?php
$channel_locale = [];
if($attribute->value_per_channel) {
array_push($channel_locale, $channel);
}
if($attribute->value_per_locale) {
array_push($channel_locale, $locale);
}
?>
@if(count($channel_locale))
<span class="locale">[{{ implode(' - ', $channel_locale) }}]</span>
@endif
</label>
@include ($typeView)
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
@endif
@ -59,8 +124,22 @@
@include ($accordian['view'])
@endforeach
</div>
</form>
</div>
@stop
@section('javascript')
<script>
$(document).ready(function () {
$('#channel-switcher, #locale-switcher').on('change', function (e) {
$('#channel-switcher').val()
var query = '?channel=' + $('#channel-switcher').val() + '&locale=' + $('#locale-switcher').val();
window.location.href = "{{ route('admin.catalog.products.edit', $product->id) }}" + query;
})
});
</script>
@stop

View File

@ -1,16 +1,12 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" {{ $disabled ? 'disabled' : '' }}>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}">
<option value="0" {{ !$product[$attribute->code] ? 'selected' : ''}}>
{{ __('admin::app.catalog.products.no') }}
</option>
<option value="1" {{ $product[$attribute->code] ? 'selected' : ''}}>
{{ __('admin::app.catalog.products.yes') }}
</option>
</select>
<?php $selectedOption = old($attribute->code) ?: $product[$attribute->code] ?>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<option value="0" {{ $selectedOption ? '' : 'selected'}}>
{{ $attribute->code == 'status' ? __('admin::app.catalog.products.disabled') : __('admin::app.catalog.products.no') }}
</option>
<option value="1" {{ $selectedOption ? 'selected' : ''}}>
{{ $attribute->code == 'status' ? __('admin::app.catalog.products.enabled') : __('admin::app.catalog.products.yes') }}
</option>
</select>

View File

@ -1,11 +1,3 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<date>
<input type="text" name="{{ $attribute->code }}" class="control" v-validate="'required'" value="{{ $product[$attribute->code]}}" data-input>
</date>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<date>
<input type="text" name="{{ $attribute->code }}" class="control" {{ $attribute->is_required ? "v-validate='required'" : '' }} value="{{ old($attribute->code) ?: $product[$attribute->code] }}" {{ $disabled ? 'disabled' : '' }}/>
</date>

View File

@ -1,11 +1,3 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<datetime>
<input type="text" name="{{ $attribute->code }}" class="control" v-validate="'required'" value="{{ $product[$attribute->code]}}" data-input>
</datetime>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<datetime>
<input type="text" name="{{ $attribute->code }}" class="control" {{ $attribute->is_required ? "v-validate='required'" : '' }} value="{{ old($attribute->code) ?: $product[$attribute->code]}}" {{ $disabled ? 'disabled' : '' }}>
</datetime>

View File

@ -1,17 +1,9 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" multiple {{ $disabled ? 'disabled' : '' }}>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" multiple>
@foreach($attribute->options as $option)
<option value="{{ $option->id }}" {{ in_array($option->id, explode(',', $attribute[$attribute->code])) ? 'selected' : ''}}>
{{ $option->admin_name }}
</option>
@endforeach
@foreach($attribute->options as $option)
<option value="{{ $option->id }}" {{ in_array($option->id, explode(',', $attribute[$attribute->code])) ? 'selected' : ''}}>
{{ $option->admin_name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
</select>

View File

@ -1,9 +1 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<input type="text" v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" value="{{ $product[$attribute->code]}}"/>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<input type="text" v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" value="{{ old($attribute->code) ?: $product[$attribute->code]}}" {{ $disabled ? 'disabled' : '' }}/>

View File

@ -1,17 +1,11 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" {{ $disabled ? 'disabled' : '' }}>
<select v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}">
<?php $selectedOption = old($attribute->code) ?: $product[$attribute->code] ?>
@foreach($attribute->options as $option)
<option value="{{ $option->id }}" {{ $option->id == $attribute[$attribute->code] ? 'selected' : ''}}>
{{ $option->admin_name }}
</option>
@endforeach
@foreach($attribute->options as $option)
<option value="{{ $option->id }}" {{ $option->id == $selectedOption ? 'selected' : ''}}>
{{ $option->admin_name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
</select>

View File

@ -1,9 +1 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<input type="text" v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" value="{{ $product[$attribute->code]}}"/>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<input type="text" v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" value="{{ old($attribute->code) ?: $product[$attribute->code] }}" {{ $disabled ? 'disabled' : '' }} {{ in_array($attribute->code, ['sku', 'url_key']) ? 'v-slugify' : '' }}/>

View File

@ -1,11 +1 @@
<div class="control-group" :class="[errors.has('{{ $attribute->code }}') ? 'has-error' : '']">
<label for="{{ $attribute->code }}" {{ $attribute->is_required ? 'class=required' : '' }}>
{{ $attribute->admin_name }}
</label>
<textarea v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}">
{{ $product[$attribute->code]}}
</textarea>
<span class="control-error" v-if="errors.has('{{ $attribute->code }}')">@{{ errors.first('{!! $attribute->code !!}') }}</span>
</div>
<textarea v-validate="'{{$validations}}'" class="control" id="{{ $attribute->code }}" name="{{ $attribute->code }}" {{ $disabled ? 'disabled' : '' }}>{{ old($attribute->code) ?: $product[$attribute->code]}}</textarea>

View File

@ -29,7 +29,7 @@
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.channels.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -54,7 +54,7 @@
<div class="control-group" :class="[errors.has('locales[]') ? 'has-error' : '']">
<label for="locales" class="required">{{ __('admin::app.settings.channels.locales') }}</label>
<select v-validate="'required'" class="control" id="locales" name="locales[]" multiple>
@foreach(core()->allLocales() as $locale)
@foreach(core()->getAllLocales() as $locale)
<option value="{{ $locale->id }}" {{ old('locales') && in_array($locale->id, old('locales')) ? 'selected' : '' }}>
{{ $locale->name }}
</option>
@ -66,7 +66,7 @@
<div class="control-group" :class="[errors.has('default_locale') ? 'has-error' : '']">
<label for="default_locale" class="required">{{ __('admin::app.settings.channels.default-locale') }}</label>
<select v-validate="'required'" class="control" id="default_locale" name="default_locale">
@foreach(core()->allLocales() as $locale)
@foreach(core()->getAllLocales() as $locale)
<option value="{{ $locale->id }}" {{ old('default_locale') == $locale->id ? 'selected' : '' }}>
{{ $locale->name }}
</option>

View File

@ -30,7 +30,7 @@
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.channels.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $channel->code }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $channel->code }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
@ -56,7 +56,7 @@
<label for="locales" class="required">{{ __('admin::app.settings.channels.locales') }}</label>
<?php $selectedOptionIds = old('locales') ?: $channel->locales->pluck('id')->toArray() ?>
<select v-validate="'required'" class="control" id="locales" name="locales[]" multiple>
@foreach(core()->allLocales() as $locale)
@foreach(core()->getAllLocales() as $locale)
<option value="{{ $locale->id }}" {{ in_array($locale->id, $selectedOptionIds) ? 'selected' : '' }}>
{{ $locale->name }}
</option>
@ -69,7 +69,7 @@
<label for="default_locale" class="required">{{ __('admin::app.settings.channels.default-locale') }}</label>
<?php $selectedOption = old('default_locale') ?: $channel->default_locale ?>
<select v-validate="'required'" class="control" id="default_locale" name="default_locale">
@foreach(core()->allLocales() as $locale)
@foreach(core()->getAllLocales() as $locale)
<option value="{{ $locale->id }}" {{ $selectedOption == $locale->id ? 'selected' : '' }}>
{{ $locale->name }}
</option>

View File

@ -24,7 +24,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.countries.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code"/>
<input v-validate="'required'" class="control" id="code" name="code" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -24,7 +24,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.currencies.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -25,7 +25,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.currencies.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $currency->code }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $currency->code }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -29,7 +29,7 @@
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.inventory_sources.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -30,7 +30,7 @@
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.settings.inventory_sources.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $inventorySource->code }}"/>
<input v-validate="'required'" class="control" id="code" name="code" value="{{ old('code') ?: $inventorySource->code }}" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -28,7 +28,7 @@
<div slot="body">
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code">{{ __('admin::app.settings.locales.code') }}</label>
<input v-validate="'required'" class="control" id="code" name="code"/>
<input v-validate="'required'" class="control" id="code" name="code" v-code/>
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>

View File

@ -37,8 +37,11 @@ class AttributeFamilyTableSeeder extends Seeder
'code' => 'new_to',
'position' => 5
], [
'code' => 'status',
'code' => 'visible_individually',
'position' => 6
], [
'code' => 'status',
'position' => 7
]
]
], [

View File

@ -83,6 +83,20 @@ class AttributeTableSeeder extends Seeder
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 0
], [
'code' => 'visible_individually',
'admin_name' => 'Visible Individually',
'en' => [
'name' => 'Visible Individually'
],
'type' => 'boolean',
'position' => 6,
'is_required' => 1,
'value_per_locale' => 0,
'value_per_channel' => 0,
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 0
], [
'code' => 'status',
'admin_name' => 'Status',
@ -90,7 +104,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Status'
],
'type' => 'boolean',
'position' => 6,
'position' => 7,
'is_required' => 1,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -104,7 +118,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Short Description'
],
'type' => 'textarea',
'position' => 7,
'position' => 8,
'is_required' => 1,
'value_per_locale' => 1,
'value_per_channel' => 1,
@ -118,7 +132,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Description'
],
'type' => 'textarea',
'position' => 8,
'position' => 9,
'is_required' => 1,
'value_per_locale' => 1,
'value_per_channel' => 1,
@ -132,10 +146,10 @@ class AttributeTableSeeder extends Seeder
'name' => 'Price'
],
'type' => 'price',
'position' => 9,
'position' => 10,
'is_required' => 1,
'value_per_locale' => 0,
'value_per_channel' => 0,
'value_per_channel' => 1,
'is_filterable' => 1,
'is_configurable' => 0,
'is_user_defined' => 0
@ -146,10 +160,10 @@ class AttributeTableSeeder extends Seeder
'name' => 'Cost'
],
'type' => 'price',
'position' => 10,
'position' => 11,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
'value_per_channel' => 1,
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 1
@ -160,10 +174,10 @@ class AttributeTableSeeder extends Seeder
'name' => 'Special Price'
],
'type' => 'price',
'position' => 11,
'position' => 12,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
'value_per_channel' => 1,
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 0
@ -174,10 +188,10 @@ class AttributeTableSeeder extends Seeder
'name' => 'Special Price From'
],
'type' => 'datetime',
'position' => 12,
'position' => 13,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
'value_per_channel' => 1,
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 0
@ -188,10 +202,10 @@ class AttributeTableSeeder extends Seeder
'name' => 'Special Price To'
],
'type' => 'datetime',
'position' => 13,
'position' => 14,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
'value_per_channel' => 1,
'is_filterable' => 0,
'is_configurable' => 0,
'is_user_defined' => 0
@ -202,7 +216,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Meta Description'
],
'type' => 'textarea',
'position' => 14,
'position' => 15,
'is_required' => 0,
'value_per_locale' => 1,
'value_per_channel' => 1,
@ -216,7 +230,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Meta Keywords'
],
'type' => 'textarea',
'position' => 15,
'position' => 16,
'is_required' => 0,
'value_per_locale' => 1,
'value_per_channel' => 1,
@ -230,7 +244,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Meta Description'
],
'type' => 'textarea',
'position' => 16,
'position' => 17,
'is_required' => 0,
'value_per_locale' => 1,
'value_per_channel' => 1,
@ -245,7 +259,7 @@ class AttributeTableSeeder extends Seeder
],
'type' => 'text',
'validation' => 'number',
'position' => 17,
'position' => 18,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -260,7 +274,7 @@ class AttributeTableSeeder extends Seeder
],
'type' => 'text',
'validation' => 'number',
'position' => 18,
'position' => 19,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -275,7 +289,7 @@ class AttributeTableSeeder extends Seeder
],
'type' => 'text',
'validation' => 'number',
'position' => 19,
'position' => 20,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -290,7 +304,7 @@ class AttributeTableSeeder extends Seeder
],
'type' => 'text',
'validation' => 'number',
'position' => 20,
'position' => 21,
'is_required' => 1,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -304,7 +318,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Color'
],
'type' => 'select',
'position' => 21,
'position' => 22,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,
@ -346,7 +360,7 @@ class AttributeTableSeeder extends Seeder
'name' => 'Size'
],
'type' => 'select',
'position' => 22,
'position' => 23,
'is_required' => 0,
'value_per_locale' => 0,
'value_per_channel' => 0,

View File

@ -70,7 +70,7 @@ class AttributeController extends Controller
public function store()
{
$this->validate(request(), [
'code' => ['required', 'unique:attributes,code', new \Webkul\Core\Contracts\Validations\Slug],
'code' => ['required', 'unique:attributes,code', new \Webkul\Core\Contracts\Validations\Code],
'admin_name' => 'required',
'type' => 'required'
]);
@ -105,7 +105,7 @@ class AttributeController extends Controller
public function update(Request $request, $id)
{
$this->validate(request(), [
'code' => ['required', 'unique:attributes,code,' . $id, new \Webkul\Core\Contracts\Validations\Slug],
'code' => ['required', 'unique:attributes,code,' . $id, new \Webkul\Core\Contracts\Validations\Code],
'admin_name' => 'required',
'type' => 'required'
]);

View File

@ -61,11 +61,11 @@ class AttributeFamilyController extends Controller
*/
public function create(Attribute $attribute)
{
$attributeFamily = $this->attributeFamily->findBy('code', 'default', ['*'], ['attribute_groups.attributes']);
$attributeFamily = $this->attributeFamily->findBy('code', 'default', ['*'], ['attribute_groups.custom_attributes']);
$attributes = $attribute->all(['id', 'code', 'admin_name', 'type']);
$custom_attributes = $attribute->all(['id', 'code', 'admin_name', 'type']);
return view($this->_config['view'], compact('attributes', 'attributeFamily'));
return view($this->_config['view'], compact('custom_attributes', 'attributeFamily'));
}
/**
@ -76,7 +76,7 @@ class AttributeFamilyController extends Controller
public function store()
{
$this->validate(request(), [
'code' => ['required', 'unique:attribute_families,code', new \Webkul\Core\Contracts\Validations\Slug],
'code' => ['required', 'unique:attribute_families,code', new \Webkul\Core\Contracts\Validations\Code],
'name' => 'required'
]);
@ -96,11 +96,11 @@ class AttributeFamilyController extends Controller
*/
public function edit(Attribute $attribute, $id)
{
$attributeFamily = $this->attributeFamily->findOrFail($id, ['*'], ['attribute_groups.attributes']);
$attributeFamily = $this->attributeFamily->findOrFail($id, ['*'], ['attribute_groups.custom_attributes']);
$attributes = $attribute->all(['id', 'code', 'admin_name', 'type']);
$custom_attributes = $attribute->all(['id', 'code', 'admin_name', 'type']);
return view($this->_config['view'], compact('attributeFamily', 'attributes'));
return view($this->_config['view'], compact('attributeFamily', 'custom_attributes'));
}
/**
@ -113,7 +113,7 @@ class AttributeFamilyController extends Controller
public function update(Request $request, $id)
{
$this->validate(request(), [
'code' => ['required', 'unique:attribute_families,code,' . $id, new \Webkul\Core\Contracts\Validations\Slug],
'code' => ['required', 'unique:attribute_families,code,' . $id, new \Webkul\Core\Contracts\Validations\Code],
'name' => 'required'
]);

View File

@ -14,7 +14,7 @@ class AttributeFamily extends Model
/**
* Get all of the attributes for the attribute groups.
*/
public function attributes()
public function custom_attributes()
{
return Attribute::join('attribute_group_mappings', 'attributes.id', '=', 'attribute_group_mappings.attribute_id')
->join('attribute_groups', 'attribute_group_mappings.attribute_group_id', '=', 'attribute_groups.id')
@ -26,9 +26,9 @@ class AttributeFamily extends Model
/**
* Get all of the attributes for the attribute groups.
*/
public function getAttributesAttribute()
public function getCustomAttributesAttribute()
{
return $this->attributes()->get();
return $this->custom_attributes()->get();
}
/**
@ -44,6 +44,6 @@ class AttributeFamily extends Model
*/
public function getConfigurableAttributesAttribute()
{
return $this->attributes()->where('attributes.is_configurable', 1)->where('attributes.type', 'select')->get();
return $this->custom_attributes()->where('attributes.is_configurable', 1)->where('attributes.type', 'select')->get();
}
}

View File

@ -14,7 +14,7 @@ class AttributeGroup extends Model
/**
* Get the attributes that owns the attribute group.
*/
public function attributes()
public function custom_attributes()
{
return $this->belongsToMany(Attribute::class, 'attribute_group_mappings')
->withPivot('position')

View File

@ -12,7 +12,7 @@ class AttributeOption extends TranslatableModel
public $translatedAttributes = ['label'];
protected $fillable = ['sort_order'];
protected $fillable = ['admin_name', 'sort_order'];
/**
* Get the attribute that owns the attribute option.

View File

@ -66,16 +66,16 @@ class AttributeFamilyRepository extends Repository
$family = $this->model->create($data);
foreach ($attributeGroups as $group) {
$attributes = isset($group['attributes']) ? $group['attributes'] : [];
unset($group['attributes']);
$custom_attributes = isset($group['custom_attributes']) ? $group['custom_attributes'] : [];
unset($group['custom_attributes']);
$attributeGroup = $family->attribute_groups()->create($group);
foreach ($attributes as $attribute) {
foreach ($custom_attributes as $attribute) {
if(isset($attribute['id'])) {
$attributeGroup->attributes()->attach($attribute['id']);
$attributeGroup->custom_attributes()->attach($attribute['id']);
} else {
$attributeModel = $this->attribute->findBy('code', $attribute['code']);
$attributeGroup->attributes()->save($attributeModel, ['position' => $attribute['position']]);
$attributeGroup->custom_attributes()->save($attributeModel, ['position' => $attribute['position']]);
}
}
}
@ -102,9 +102,9 @@ class AttributeFamilyRepository extends Repository
if (str_contains($attributeGroupId, 'group_')) {
$attributeGroup = $family->attribute_groups()->create($attributeGroupInputs);
if(isset($attributeGroupInputs['attributes'])) {
foreach ($attributeGroupInputs['attributes'] as $attribute) {
$attributeGroup->attributes()->attach($attribute['id']);
if(isset($attributeGroupInputs['custom_attributes'])) {
foreach ($attributeGroupInputs['custom_attributes'] as $attribute) {
$attributeGroup->custom_attributes()->attach($attribute['id']);
}
}
} else {
@ -115,20 +115,20 @@ class AttributeFamilyRepository extends Repository
$attributeGroup = $this->attributeGroup->findOrFail($attributeGroupId);
$attributeGroup->update($attributeGroupInputs);
$attributeIds = $attributeGroup->attributes()->get()->pluck('id');
$attributeIds = $attributeGroup->custom_attributes()->get()->pluck('id');
if(isset($attributeGroupInputs['attributes'])) {
foreach ($attributeGroupInputs['attributes'] as $attribute) {
if(isset($attributeGroupInputs['custom_attributes'])) {
foreach ($attributeGroupInputs['custom_attributes'] as $attribute) {
if(is_numeric($index = $attributeIds->search($attribute['id']))) {
$attributeIds->forget($index);
} else {
$attributeGroup->attributes()->attach($attribute['id']);
$attributeGroup->custom_attributes()->attach($attribute['id']);
}
}
}
if($attributeIds->count()) {
$attributeGroup->attributes()->detach($attributeIds);
$attributeGroup->custom_attributes()->detach($attributeIds);
}
}
}

View File

@ -77,7 +77,7 @@ class AttributeRepository extends Repository
$previousOptionIds = $attribute->options()->pluck('id');
if(in_array($attribute->code, ['select', 'multiselect', 'checkbox'])) {
if(in_array($attribute->type, ['select', 'multiselect', 'checkbox'])) {
if(isset($data['options'])) {
foreach ($data['options'] as $optionId => $optionInputs) {
if (str_contains($optionId, 'option_')) {

View File

@ -71,7 +71,7 @@ class CategoryController extends Controller
public function store()
{
$this->validate(request(), [
'slug' => ['required', 'unique:category_translations,slug', new \Webkul\Core\Contracts\Validations\Slug],
'slug' => ['required', 'unique:category_translations,slug', new \Webkul\Core\Contracts\Validations\Code],
'name' => 'required'
]);

View File

@ -44,7 +44,7 @@ class CategoryRepository extends Repository
if(isset($data['locale']) && $data['locale'] == 'all') {
$model = app()->make($this->model());
foreach(core()->allLocales() as $locale) {
foreach(core()->getAllLocales() as $locale) {
foreach ($model->translatedAttributes as $attribute) {
if(isset($data[$attribute])) {
$data[$locale->code][$attribute] = $data[$attribute];

View File

@ -6,22 +6,16 @@ use Webkul\Channel\Models\Channel as ChannelModel;
class Channel
{
public function getDefaultChannelLocaleCode() {
public function getAllChannels() {
return ChannelModel::all();
}
public function getChannel() {
$channel = ChannelModel::first();
if(!$channel || !$channel->locales()->count())
if(!$channel)
return;
return $channel->code . '-' . $channel->locales()->first()->code;
}
public function getDefaultChannelLocale() {
$channel = ChannelModel::first();
return $channel->locales()->first();
}
public function getChannelWithLocales() {
return ChannelModel::with('locales')->get();
return $channel->code;
}
}

View File

@ -15,7 +15,7 @@ class Code implements Rule
*/
public function passes($attribute, $value)
{
return preg_match('/^[a-zA-Z0-9_]*$/', $value);
return preg_match('/^[a-z]+[a-z0-9_]+$/', $value);
}
/**

View File

@ -7,7 +7,7 @@ use Webkul\Core\Models\Currency as CurrencyModel;
class Core
{
public function allLocales() {
public function getAllLocales() {
return LocaleModel::all();
}

View File

@ -123,6 +123,28 @@ abstract class Repository implements RepositoryInterface {
return $this->resetScope()->model->with($with)->where($attribute, '=', $value)->first($columns);
}
/**
* @param $conditions
* @param array $columns
* @return mixed
*/
public function findWhere($conditions, $columns = ['*'], $with = [])
{
$model = $this->resetScope()->model;
foreach ($conditions as $column => $value) {
if(is_array($value)) {
list($column, $condition, $val) = $value;
$this->model = $this->model->where($column, $condition, $val);
} else {
$model->where($column, $value);
}
}
return $model->with($with)->get($columns);
}
/**
* @return \Illuminate\Database\Eloquent\Builder
* @throws RepositoryException

View File

@ -28,8 +28,8 @@ class CreateInventorySourcesTable extends Migration
$table->string('street')->nullable();
$table->string('postcode');
$table->integer('priority')->default(0);
$table->decimal('latitude', 10, 5);
$table->decimal('longitude', 10, 5);
$table->decimal('latitude', 10, 5)->nullable();
$table->decimal('longitude', 10, 5)->nullable();
$table->boolean('status')->default(0);
$table->timestamps();
});

View File

@ -27,13 +27,6 @@ class CreateProductsTable extends Migration
$table->foreign('parent_id')->references('id')->on('products')->onDelete('cascade');
});
Schema::create('product_inventories', function (Blueprint $table) {
$table->integer('product_id')->unsigned();
$table->integer('inventory_source_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('inventory_source_id')->references('id')->on('inventory_sources')->onDelete('cascade');
});
Schema::create('product_categories', function (Blueprint $table) {
$table->integer('product_id')->unsigned();
$table->integer('category_id')->unsigned();

View File

@ -16,6 +16,7 @@ class CreateProductAttributeValuesTable extends Migration
Schema::create('product_attribute_values', function (Blueprint $table) {
$table->increments('id');
$table->string('locale')->nullable();
$table->string('channel')->nullable();
$table->text('text_value')->nullable();
$table->boolean('boolean_value')->nullable();
$table->integer('integer_value')->nullable();
@ -25,11 +26,9 @@ class CreateProductAttributeValuesTable extends Migration
$table->json('json_value')->nullable();
$table->integer('product_id')->unsigned();
$table->integer('attribute_id')->unsigned();
$table->integer('channel_id')->nullable()->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade');
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
$table->unique(['channel_id', 'locale', 'attribute_id', 'product_id'], 'chanel_locale_attribute_value_index_unique');
$table->unique(['channel', 'locale', 'attribute_id', 'product_id'], 'chanel_locale_attribute_value_index_unique');
});
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProductInventoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_inventories', function (Blueprint $table) {
$table->increments('id');
$table->integer('qty')->default(0);
$table->integer('product_id')->unsigned();
$table->integer('inventory_source_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('inventory_source_id')->references('id')->on('inventory_sources')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_inventories');
}
}

View File

@ -4,8 +4,11 @@ namespace Webkul\Product\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Webkul\Product\Http\Requests\ProductForm;
use Webkul\Product\Repositories\ProductRepository as Product;
use Webkul\Attribute\Repositories\AttributeFamilyRepository as AttributeFamily;
use Webkul\Category\Repositories\CategoryRepository as Category;
use Webkul\Inventory\Repositories\InventorySourceRepository as InventorySource;
/**
* Product controller
@ -29,6 +32,20 @@ class ProductController extends Controller
*/
protected $attributeFamily;
/**
* CategoryRepository object
*
* @var array
*/
protected $category;
/**
* InventorySourceRepository object
*
* @var array
*/
protected $inventorySource;
/**
* ProductRepository object
*
@ -40,13 +57,23 @@ class ProductController extends Controller
* Create a new controller instance.
*
* @param Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamily
* @param Webkul\Category\Repositories\CategoryRepository $category
* @param Webkul\Inventory\Repositories\InventorySourceRepository $inventorySource
* @param Webkul\Product\Repositories\ProductRepository $product
* @return void
*/
public function __construct(AttributeFamily $attributeFamily, Product $product)
public function __construct(
AttributeFamily $attributeFamily,
Category $category,
InventorySource $inventorySource,
Product $product)
{
$this->attributeFamily = $attributeFamily;
$this->category = $category;
$this->inventorySource = $inventorySource;
$this->product = $product;
$this->_config = request('_config');
@ -98,7 +125,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 \Webkul\Core\Contracts\Validations\Code]
]);
$product = $this->product->create(request()->all());
@ -118,22 +145,24 @@ class ProductController extends Controller
{
$product = $this->product->findOrFail($id);
return view($this->_config['view'], compact('product'));
$categories = $this->category->getCategoryTree();
$inventorySources = $this->inventorySource->all();
return view($this->_config['view'], compact('product', 'categories', 'inventorySources'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \Webkul\Product\Http\Requests\ProductForm $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
public function update(ProductForm $request, $id)
{
$this->validate(request(), [
'name' => 'required',
]);
dd(request()->all());
$this->product->update(request()->all(), $id);
session()->flash('success', 'Product updated successfully.');

View File

@ -0,0 +1,118 @@
<?php
namespace Webkul\Product\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Webkul\Attribute\Repositories\AttributeFamilyRepository as AttributeFamily;
use Webkul\Product\Repositories\ProductRepository as Product;
use Webkul\Product\Repositories\ProductAttributeValueRepository as AttributeValue;
use Webkul\Product\Models\ProductAttributeValue;
class ProductForm extends FormRequest
{
/**
* AttributeFamilyRepository object
*
* @var array
*/
protected $attributeFamily;
/**
* ProductRepository object
*
* @var array
*/
protected $product;
/**
* ProductAttributeValueRepository object
*
* @var array
*/
protected $attributeValue;
/**
* Create a new controller instance.
*
* @param Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamily
* @param Webkul\Product\Repositories\ProductRepository $product
* @param Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValue
* @return void
*/
public function __construct(AttributeFamily $attributeFamily, Product $product, AttributeValue $attributeValue)
{
$this->attributeFamily = $attributeFamily;
$this->product = $product;
$this->attributeValue = $attributeValue;
}
protected $rules;
/**
* Determine if the product is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$this->rules = [
'sku' => ['required', 'unique:products,sku,' . $this->id, new \Webkul\Core\Contracts\Validations\Slug],
'variants.*.name' => 'required',
'variants.*.sku' => 'required',
'variants.*.price' => 'required',
'variants.*.weight' => 'required',
];
$inputs = $this->all();
$product = $this->product->find($this->id);
$attributes = $product->attribute_family->custom_attributes;
foreach ($attributes as $attribute) {
if($attribute->code == 'sku')
continue;
if($product->type == 'configurable' && in_array($attribute->code, ['price', 'cost', 'special_price', 'special_price_from', 'special_price_to', 'width', 'height', 'depth', 'weight']))
continue;
$validations = [];
if($attribute->is_required) {
array_push($validations, 'required');
} else {
array_push($validations, 'nullable');
}
if($attribute->type == 'text' && $attribute->validation) {
array_push($validations, $attribute->validation);
}
if($attribute->is_unique) {
array_push($validations, function ($field, $value, $fail) use ($inputs, $attribute) {
$column = ProductAttributeValue::$attributeTypeFields[$attribute->type];
if (!$this->attributeValue->isValueUnique($this->id, $attribute->id, $column, $inputs[$attribute->code])) {
$fail('The :attribute has already been taken.');
}
});
}
$this->rules[$attribute->code] = $validations;
}
return $this->rules;
}
}

View File

@ -7,27 +7,15 @@ use Webkul\Attribute\Models\AttributeFamily;
use Webkul\Category\Models\Category;
use Webkul\Attribute\Models\Attribute;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Product\Models\ProductInventory;
use Webkul\Product\Models\ProductImage;
use Webkul\Inventory\Models\InventorySource;
class Product extends Model
{
protected $fillable = ['type', 'attribute_family_id', 'sku', 'parent_id'];
protected $with = ['attribute_values', 'varients'];
/**
* @var array
*/
protected $attributeTypeFields = [
'text' => 'text_value',
'textarea' => 'text_value',
'price' => 'float_value',
'boolean' => 'boolean_value',
'select' => 'integer_value',
'multiselect' => 'text_value',
'datetime' => 'datetime_time',
'date' => 'date_value',
];
protected $with = ['attribute_family', 'attribute_values', 'variants', 'inventories'];
/**
* Get the product attribute family that owns the product.
@ -46,13 +34,21 @@ class Product extends Model
}
/**
* Get the product varients that owns the product.
* Get the product variants that owns the product.
*/
public function varients()
public function variants()
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the product that owns the product.
*/
public function parent()
{
return $this->belongsTo(self::class);
}
/**
* The categories that belong to the product.
*/
@ -66,7 +62,15 @@ class Product extends Model
*/
public function inventories()
{
return $this->belongsToMany(InventorySource::class, 'product_inventories');
return $this->hasMany(ProductInventory::class, 'product_id');
}
/**
* The inventory sources that belong to the product.
*/
public function inventory_sources()
{
return $this->belongsToMany(InventorySource::class, 'product_inventories')->withPivot('id', 'qty');
}
/**
@ -77,6 +81,14 @@ class Product extends Model
return $this->belongsToMany(Attribute::class, 'product_super_attributes');
}
/**
* The images that belong to the product.
*/
public function images()
{
return $this->hasMany(ProductImage::class, 'product_id');
}
/**
* The related products that belong to the product.
*/
@ -100,6 +112,64 @@ class Product extends Model
{
return $this->belongsToMany(self::class, 'product_cross_sells');
}
/**
* @param string $key
*
* @return bool
*/
public function isCustomAttribute($attribute)
{
return $this->attribute_family->custom_attributes->pluck('code')->contains($attribute);
}
/**
* Get an attribute from the model.
*
* @param string $key
* @return mixed
*/
public function getAttribute($key)
{
if (!method_exists(self::class, $key) && !isset($this->attributes[$key])) {
if ($this->isCustomAttribute($key)) {
$attributeModel = $this->attribute_family->custom_attributes()->where('attributes.code', $key)->first();
if($attributeModel) {
if($attributeModel->value_per_channel) {
$channel = request()->get('channel') ?: channel()->getChannel();
if($attributeModel->value_per_locale) {
$locale = request()->get('locale') ?: app()->getLocale();
$attributeValue = $this->attribute_values()->where('channel', $channel)->where('locale', $locale)->where('attribute_id', $attributeModel->id)->first();
} else {
$attributeValue = $this->attribute_values()->where('channel', $channel)->where('attribute_id', $attributeModel->id)->first();
}
} else {
if($attributeModel->value_per_locale) {
$locale = request()->get('locale') ?: app()->getLocale();
$attributeValue = $this->attribute_values()->where('locale', $locale)->where('attribute_id', $attributeModel->id)->first();
} else {
$attributeValue = $this->attribute_values()->where('attribute_id', $attributeModel->id)->first();
}
}
if ($this->hasGetMutator($key)) {
$this->attributes[$key] = $attributeValue[ProductAttributeValue::$attributeTypeFields[$attributeModel->type]];
return $this->getAttributeValue($key);
}
return $attributeValue[ProductAttributeValue::$attributeTypeFields[$attributeModel->type]];
}
return $this->getAttributeValue($key);
}
return $this->getAttributeValue($key);
}
return parent::getAttribute($key);
}
/**
* @return array
@ -110,15 +180,29 @@ class Product extends Model
$hiddenAttributes = $this->getHidden();
foreach ($this->attribute_values as $attributeValue) {
$attribute = $attributeValue->attribute;
foreach ($this->attribute_family->custom_attributes as $attribute) {
if (in_array($attribute->code, $hiddenAttributes)) {
continue;
}
if ($value = $attributeValue[$this->attributeTypeFields[$attribute->type]]) {
if($attribute->value_per_channel) {
$channel = request()->get('channel') ?: channel()->getChannel();
if($attribute->value_per_locale) {
$locale = request()->get('locale') ?: app()->getLocale();
$attributeValue = $this->attribute_values()->where('channel', $channel)->where('locale', $locale)->where('attribute_id', $attribute->id)->first();
} else {
$attributeValue = $this->attribute_values()->where('channel', $channel)->where('attribute_id', $attribute->id)->first();
}
} else {
if($attribute->value_per_locale) {
$locale = request()->get('locale') ?: app()->getLocale();
$attributeValue = $this->attribute_values()->where('locale', $locale)->where('attribute_id', $attribute->id)->first();
} else {
$attributeValue = $this->attribute_values()->where('attribute_id', $attribute->id)->first();
}
}
if (!is_null($value = $attributeValue[ProductAttributeValue::$attributeTypeFields[$attribute->type]])) {
$attributes[$attribute->code] = $value;
}
}

View File

@ -13,11 +13,26 @@ class ProductAttributeValue extends Model
protected $with = ['attribute'];
/**
* @var array
*/
public static $attributeTypeFields = [
'text' => 'text_value',
'textarea' => 'text_value',
'price' => 'float_value',
'boolean' => 'boolean_value',
'select' => 'integer_value',
'multiselect' => 'text_value',
'datetime' => 'datetime_time',
'date' => 'date_value',
];
protected $fillable = [
'product_id',
'attribute_id',
'channel_id',
'locale',
'channel',
'text_value',
'boolean_value',
'integer_value',

View File

@ -0,0 +1,21 @@
<?php
namespace Webkul\Product\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Product\Models\Product;
class ProductImage extends Model
{
public $timestamps = false;
protected $fillable = [];
/**
* Get the product that owns the image.
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Webkul\Product\Models;
use Illuminate\Database\Eloquent\Model;
class ProductInventory extends Model
{
public $timestamps = false;
protected $fillable = ['qty', 'product_id', 'inventory_source_id'];
}

View File

@ -5,6 +5,7 @@ namespace Webkul\Product\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Core\Eloquent\Repository;
use Webkul\Product\Models\ProductAttributeValue;
/**
* Product Attribute Value Reposotory
@ -21,20 +22,6 @@ class ProductAttributeValueRepository extends Repository
*/
protected $attribute;
/**
* @var array
*/
protected $attributeTypeFields = [
'text' => 'text_value',
'textarea' => 'text_value',
'price' => 'float_value',
'boolean' => 'boolean_value',
'select' => 'integer_value',
'multiselect' => 'text_value',
'datetime' => 'datetime_time',
'date' => 'date_value',
];
/**
* Create a new controller instance.
*
@ -64,10 +51,31 @@ class ProductAttributeValueRepository extends Repository
*/
public function create(array $data)
{
$attribute = $this->attribute->find($data['attribute_id']);
if(isset($data['attribute_id'])) {
$attribute = $this->attribute->find($data['attribute_id']);
} else {
$attribute = $this->attribute->findBy('code', $data['attribute_code']);
}
$data[$this->attributeTypeFields[$attribute->type]] = $data['value'];
if(!$attribute)
return;
$data[ProductAttributeValue::$attributeTypeFields[$attribute->type]] = $data['value'];
return $this->model->create($data);
}
/**
* @param string $column
* @param int $attributeId
* @param int $productId
* @param string $value
* @return boolean
*/
public function isValueUnique($productId, $attributeId, $column, $value)
{
$result = $this->resetScope()->model->where($column, $value)->where('attribute_id', '!=', $attributeId)->where('product_id', '!=', $productId)->get();
return $result->count() ? false : true;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Webkul\Product\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Core\Eloquent\Repository;
/**
* Product Inventory Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class ProductInventoryRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\Product\Models\ProductInventory';
}
/**
* @param array $inventories
* @param mixed $product
* @return mixed
*/
public function saveInventories(array $data, $product)
{
$inventorySourceIds = $product->inventory_sources->pluck('id');
if(isset($data['inventories'])) {
foreach($data['inventories'] as $inventorySourceId => $qty) {
if(is_null($qty))
continue;
$productInventory = $this->findWhere([
'product_id' => $product->id,
'inventory_source_id' => $inventorySourceId,
])->first();
if($productInventory) {
if(is_numeric($index = $inventorySourceIds->search($inventorySourceId))) {
$inventorySourceIds->forget($index);
}
$this->update(['qty' => $qty], $productInventory->id);
} else {
$this->create([
'qty' => $qty,
'product_id' => $product->id,
'inventory_source_id' => $inventorySourceId,
]);
}
}
}
if($inventorySourceIds->count()) {
$product->inventory_sources()->detach($inventorySourceIds);
}
}
}

View File

@ -5,10 +5,13 @@ namespace Webkul\Product\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Core\Eloquent\Repository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Attribute\Repositories\AttributeOptionRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Models\ProductAttributeValue;
/**
* Product Reposotory
* Product Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
@ -22,6 +25,13 @@ class ProductRepository extends Repository
*/
protected $attribute;
/**
* AttributeOptionRepository object
*
* @var array
*/
protected $attributeOption;
/**
* ProductAttributeValueRepository object
*
@ -29,22 +39,37 @@ class ProductRepository extends Repository
*/
protected $attributeValue;
/**
* ProductInventoryRepository object
*
* @var array
*/
protected $productInventory;
/**
* Create a new controller instance.
*
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @param Webkul\Attribute\Repositories\ProductAttributeValueRepository $attributeValue
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @param Webkul\Attribute\Repositories\AttributeOptionRepository $attributeOption
* @param Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValue
* @param Webkul\Product\Repositories\ProductInventoryRepository $productInventory
* @return void
*/
public function __construct(
AttributeRepository $attribute,
AttributeOptionRepository $attributeOption,
ProductAttributeValueRepository $attributeValue,
ProductInventoryRepository $productInventory,
App $app)
{
$this->attribute = $attribute;
$this->attributeOption = $attributeOption;
$this->attributeValue = $attributeValue;
$this->productInventory = $productInventory;
parent::__construct($app);
}
@ -66,6 +91,13 @@ class ProductRepository extends Repository
{
$product = $this->model->create($data);
$nameAttribute = $this->attribute->findBy('code', 'status');
$this->attributeValue->create([
'product_id' => $product->id,
'attribute_id' => $nameAttribute->id,
'value' => 1
]);
if(isset($data['super_attributes'])) {
$super_attributes = [];
@ -79,7 +111,7 @@ class ProductRepository extends Repository
}
foreach (array_permutation($super_attributes) as $permutation) {
$this->createVarient($product, $permutation);
$this->createVariant($product, $permutation);
}
}
@ -88,25 +120,228 @@ class ProductRepository extends Repository
/**
* @param array $data
* @param $id
* @param string $attribute
* @return mixed
*/
public function createVarient($product, $permutation)
public function update(array $data, $id, $attribute = "id")
{
$varient = $this->model->create([
$product = $this->findOrFail($id);
if($product->parent_id && $this->checkVariantOptionAvailabiliy($data, $product)) {
$data['parent_id'] = null;
}
$product->update($data);
if(isset($data['categories']))
$product->categories()->sync($data['categories']);
$attributes = $product->attribute_family->custom_attributes;
foreach ($attributes as $attribute) {
if(!isset($data[$attribute->code]) || !$data[$attribute->code])
continue;
$attributeValue = $this->attributeValue->findWhere([
'product_id' => $product->id,
'attribute_id' => $attribute->id,
'channel' => $attribute->value_per_channel ? $data['channel'] : null,
'locale' => $attribute->value_per_locale ? $data['locale'] : null
])->first();
if(!$attributeValue) {
$this->attributeValue->create([
'product_id' => $product->id,
'attribute_id' => $attribute->id,
'value' => $data[$attribute->code],
'channel' => $attribute->value_per_channel ? $data['channel'] : null,
'locale' => $attribute->value_per_locale ? $data['locale'] : null
]);
} else {
$this->attributeValue->update([
ProductAttributeValue::$attributeTypeFields[$attribute->type] => $data[$attribute->code]
], $attributeValue->id);
}
}
if(isset($data['variants'])) {
foreach ($data['variants'] as $variantId => $variantData) {
if (str_contains($variantId, 'variant_')) {
$permutation = [];
foreach ($product->super_attributes as $superAttribute) {
$permutation[$superAttribute->id] = $variantData[$superAttribute->code];
}
$this->createVariant($product, $permutation, $variantData);
} else {
$variantData['channel'] = $data['channel'];
$variantData['locale'] = $data['locale'];
$this->updateVariant($variantData, $variantId);
}
}
}
$this->productInventory->saveInventories($data, $product);
return $product;
}
/**
* @param mixed $product
* @param array $permutation
* @param array $data
* @return mixed
*/
public function createVariant($product, $permutation, $data = [])
{
if(!count($data)) {
$data = [
"sku" => $product->sku . '-variant-' . implode('-', $permutation),
"name" => "",
"inventories" => [],
"price" => 0,
"weight" => 0,
"status" => 1
];
}
$variant = $this->model->create([
'parent_id' => $product->id,
'type' => 'simple',
'attribute_family_id' => $product->attribute_family_id,
'sku' => $product->sku . '-varient-' . implode('-', $permutation),
'sku' => $data['sku'],
]);
foreach (['sku', 'name', 'price', 'weight', 'status'] as $attributeCode) {
$attribute = $this->attribute->findBy('code', $attributeCode);
if($attribute->value_per_channel) {
if($attribute->value_per_locale) {
foreach(channel()->getAllChannels() as $channel) {
foreach(core()->getAllLocales() as $locale) {
$this->attributeValue->create([
'product_id' => $variant->id,
'attribute_id' => $attribute->id,
'channel' => $channel->code,
'locale' => $locale->code,
'value' => $data[$attributeCode]
]);
}
}
} else {
foreach(channel()->getAllChannels() as $channel) {
$this->attributeValue->create([
'product_id' => $variant->id,
'attribute_id' => $attribute->id,
'channel' => $channel->code,
'value' => $data[$attributeCode]
]);
}
}
} else {
if($attribute->value_per_locale) {
foreach(core()->getAllLocales() as $locale) {
$this->attributeValue->create([
'product_id' => $variant->id,
'attribute_id' => $attribute->id,
'locale' => $locale->code,
'value' => $data[$attributeCode]
]);
}
} else {
$this->attributeValue->create([
'product_id' => $variant->id,
'attribute_id' => $attribute->id,
'value' => $data[$attributeCode]
]);
}
}
}
foreach($permutation as $attributeId => $optionId) {
$this->attributeValue->create([
'product_id' => $varient->id,
'product_id' => $variant->id,
'attribute_id' => $attributeId,
'value' => $optionId
]);
}
return $varient;
$this->productInventory->saveInventories($data, $variant);
return $variant;
}
/**
* @param array $data
* @param $id
* @return mixed
*/
public function updateVariant(array $data, $id)
{
$variant = $this->findOrFail($id);
$variant->update(['sku' => $data['sku']]);
foreach (['sku', 'name', 'price', 'weight', 'status'] as $attributeCode) {
$attribute = $this->attribute->findBy('code', $attributeCode);
$attributeValue = $this->attributeValue->findWhere([
'product_id' => $id,
'attribute_id' => $attribute->id,
'channel' => $attribute->value_per_channel ? $data['channel'] : null,
'locale' => $attribute->value_per_locale ? $data['locale'] : null
])->first();
if(!$attributeValue) {
$this->attributeValue->create([
'product_id' => $id,
'attribute_id' => $attribute->id,
'value' => $data[$attribute->code],
'channel' => $attribute->value_per_channel ? $data['channel'] : null,
'locale' => $attribute->value_per_locale ? $data['locale'] : null
]);
} else {
$this->attributeValue->update([
ProductAttributeValue::$attributeTypeFields[$attribute->type] => $data[$attribute->code]
], $attributeValue->id);
}
}
$this->productInventory->saveInventories($data, $variant);
return $variant;
}
/**
* @param array $data
* @param mixed $product
* @return mixed
*/
public function checkVariantOptionAvailabiliy($data, $product)
{
$parent = $product->parent;
$superAttributeCodes = $parent->super_attributes->pluck('code');
$isAlreadyExist = false;
foreach ($parent->variants as $variant) {
if($variant->id == $product->id)
continue;
$matchCount = 0;
foreach ($superAttributeCodes as $attributeCode) {
if($data[$attributeCode] == $variant->{$attributeCode})
$matchCount++;
}
if($matchCount == $superAttributeCodes->count()) {
return true;
}
}
return false;
}
}

View File

@ -6,3 +6,7 @@ Vue.component("tree-item", require("./components/tree-view/tree-item"));
Vue.component("tree-checkbox", require("./components/tree-view/tree-checkbox"));
Vue.component("tree-radio", require("./components/tree-view/tree-radio"));
Vue.component("modal", require("./components/modal"));
Vue.component("image-wrapper", require("./components/image/image-wrapper"));
Vue.component("image-item", require("./components/image/image-item"));
Vue.directive("slugify", require("./directives/slugify"));
Vue.directive("code", require("./directives/code"));

View File

@ -22,6 +22,8 @@
active: Boolean
},
inject: ['$validator'],
data: function() {
return {
isActive: false

View File

@ -0,0 +1,47 @@
<template>
<div>
<input type="file" :name="finalInputName" ref="imageInput"/>
</div>
</template>
<script>
export default {
props: {
inputName: {
type: String,
required: false,
default: 'attachments'
},
multiple: {
type: [Boolean, String],
required: false,
default: true
},
image: {
type: Object,
required: false,
default: null
}
},
mounted () {
if(!this.image.id) {
var element = this.$refs.imageInput;
element.dispatchEvent(new Event("click"));
element.click();
}
},
computed: {
finalInputName () {
if(this.multiple)
return this.inputName + '[]';
return this.inputName;
}
}
}
</script>

View File

@ -0,0 +1,46 @@
<template>
<div>
<div class="image-wrapper">
<image-item v-for='(image, index) in images' :key='image.uid' :image="image" :input-name="inputName" :multiple="multiple" @onRemoveImage="removImage($event)"></image-item>
</div>
<label class="btn btn-lg btn-primary" style="display: inline-block" @click="createFileType">{{ buttonLabel }}</label>
</div>
</template>
<script>
export default {
props: {
buttonLabel: {
type: String,
required: false,
default: 'Add Image'
},
inputName: {
type: String,
required: false,
default: 'attachments'
},
multiple: {
type: [Boolean, String],
required: false,
default: true
},
images: {
type: Array,
required: false,
default: () => ([])
}
},
methods: {
createFileType () {
this.images.push({});
}
}
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<span class="checkbox">
<input type="checkbox" :id="id" :name="[nameField, '[]']" :value="modelValue" @change="inputChanged()" :checked="isActive">
<input type="checkbox" :id="id" :name="[nameField + '[]']" :value="modelValue" @change="inputChanged()" :checked="isActive">
<label class="checkbox-view" :for="id"></label>
<span class="" :for="id">{{ label }}</span>
</span>

View File

@ -30,6 +30,12 @@
default: null
},
behavior: {
type: String,
required: false,
default: 'reactive'
},
savedValues: {
type: Array,
required: false,
@ -104,45 +110,58 @@
generateRoot () {
if(this.inputType == 'checkbox') {
return this.$createElement('tree-checkbox', {
props: {
id: this.items[this.idField],
label: this.caption,
nameField: this.nameField,
modelValue: this.items[this.valueField],
inputValue: this.hasChildren ? this.isSomeChildrenSelected : this.value,
value: this.hasChildren ? this.isAllChildrenSelected : this.items
},
on: {
change: selection => {
if(this.hasChildren) {
if(this.isAllChildrenSelected) {
this.allChildren.forEach(leaf => {
let index = this.value.indexOf(leaf)
this.value.splice(index, 1)
})
} else {
this.allChildren.forEach(leaf => {
let exists = false;
this.value.forEach(item => {
if(item['key'] == leaf['key']) {
exists = true;
if(this.behavior == 'reactive') {
return this.$createElement('tree-checkbox', {
props: {
id: this.items[this.idField],
label: this.caption,
nameField: this.nameField,
modelValue: this.items[this.valueField],
inputValue: this.hasChildren ? this.isSomeChildrenSelected : this.value,
value: this.hasChildren ? this.isAllChildrenSelected : this.items
},
on: {
change: selection => {
if(this.hasChildren) {
if(this.isAllChildrenSelected) {
this.allChildren.forEach(leaf => {
let index = this.value.indexOf(leaf)
this.value.splice(index, 1)
})
} else {
this.allChildren.forEach(leaf => {
let exists = false;
this.value.forEach(item => {
if(item['key'] == leaf['key']) {
exists = true;
}
})
if(!exists) {
this.value.push(leaf);
}
})
}
if(!exists) {
this.value.push(leaf);
}
})
this.$emit('input', this.value)
} else {
this.$emit('input', selection);
}
this.$emit('input', this.value)
} else {
this.$emit('input', selection);
}
}
}
})
})
} else {
return this.$createElement('tree-checkbox', {
props: {
id: this.items[this.idField],
label: this.caption,
nameField: this.nameField,
modelValue: this.items[this.valueField],
inputValue: this.value,
value: this.items
}
})
}
} else if(this.inputType == 'radio') {
return this.$createElement('tree-radio', {
props: {
@ -172,7 +191,8 @@
captionField: this.captionField,
childrenField: this.childrenField,
valueField: this.valueField,
idField: this.idField
idField: this.idField,
behavior: this.behavior
}
})
},

View File

@ -48,6 +48,12 @@
default: () => ([])
},
behavior: {
type: String,
required: false,
default: 'reactive'
},
value: {
type: [Array, String, Object],
required: false,
@ -96,7 +102,8 @@
captionField: this.captionField,
childrenField: this.childrenField,
valueField: this.valueField,
idField: this.idField
idField: this.idField,
behavior: this.behavior
},
on: {
input: selection => {

View File

@ -0,0 +1,16 @@
<script>
export default {
bind(el, binding, vnode) {
let handler = function(e) {
setTimeout(function(){
e.target.value = e.target.value.toString().toLowerCase()
.replace(/[^\w ]+/g,'')
.trim()
.replace(/ +/g,'-');
}, 100);
}
el.addEventListener('input', handler);
}
}
</script>

View File

@ -0,0 +1,16 @@
<script>
export default {
bind(el, binding, vnode) {
let handler = function(e) {
setTimeout(function(){
e.target.value = e.target.value.toString().toLowerCase()
.replace(/[^\w- ]+/g,'')
.trim()
.replace(/ +/g,'-');
}, 100);
}
el.addEventListener('input', handler);
}
}
</script>

View File

@ -1,3 +1,4 @@
$font-color: #3a3a3a;
$brand-color: #0041FF;
$danger-color: #FC6868;
$success-color: #4CAF50;

View File

@ -32,13 +32,13 @@ ul {
h1 {
font-size: 28px;
color: #3a3a3a;
color: $font-color;
margin-top: 0;
}
h2 {
font-size: 18px;
color: #3a3a3a;
color: $font-color;
}
.hide {
@ -168,6 +168,7 @@ h2 {
padding: 0px;
li {
padding: 5px 0px;
// &:hover {
// color: $brand-color;
// }
@ -186,6 +187,14 @@ h2 {
.checkbox {
margin: 0;
}
.control-group label {
color: $font-color;
font-size: 15px;
font-weight: 500;
text-transform: capitalize;
width: 100%;
}
}
}
@ -209,13 +218,13 @@ h2 {
font-weight: 700;
padding: 12px 10px;
background: #f8f9fa;
color: #3a3a3a;
color: $font-color;
}
tbody td {
padding: 12px 10px;
border-bottom: solid 1px #d3d3d3;
color: #3a3a3a;
color: $font-color;
vertical-align: top;
&.actions {
@ -227,6 +236,10 @@ h2 {
}
}
}
tbody tr:last-child td {
border-bottom: none;
}
}
.control-group {
@ -247,7 +260,6 @@ h2 {
border: 2px solid $control-border-color;
@include border-radius(3px);
font-size: 14px;
color: #8e8e8e;
padding: 8px 35px 8px 10px;
cursor: pointer;
position: relative;
@ -376,7 +388,14 @@ h2 {
label {
display: block;
color: #3a3a3a;
color: $font-color;
&.required::after {
content: "*";
color: $danger-color;
font-weight: 700;
display: inline-block;
}
}
textarea.control {
@ -511,7 +530,7 @@ h2 {
.accordian {
.accordian-header {
font-size: 18px;
color: #3a3a3a;
color: $font-color;
border-bottom: solid 1px $border-color;
padding: 20px 15px;
cursor: pointer;
@ -913,7 +932,7 @@ h2 {
h3 {
display: inline-block;
font-size: 20px;
color: #3a3a3a;
color: $font-color;
margin: 0;
}

View File

@ -175,13 +175,13 @@
background-image: url("../images/Icon-Configure-Active.svg");
}
.arrow-down-icon {
> .arrow-down-icon {
background-image: url("../images/Arrow-Down.svg");
width: 14px;
height: 8px;
}
.expand-icon {
> .expand-icon {
background-image: url("../images/Expand-Light-On.svg");
}

View File

@ -49,10 +49,6 @@ class UserServiceProvider extends ServiceProvider
public function register()
{
$this->registerBouncer();
Event::listen('admin.acl.build', function ($acl) {
$acl->add('settings.users.roles1', 'Roles1', 'admin.roles.index1', 3);
});
}
/**