Resolve conflicts

This commit is contained in:
jitendra 2020-08-12 13:19:13 +05:30
commit 694a10d06f
47 changed files with 2808 additions and 1976 deletions

14
config/products.php Normal file
View File

@ -0,0 +1,14 @@
<?php
return [
// use the 'code' of the 'attributes' table here to be able to control which attributes
// should be skipped when doing a copy (admin->catalog->products->copy product).
// you can also add every relation that should not be copied here to skip them.
// defaults to none (which means everything is copied).
'skipAttributesOnCopy' => [
],
// Make the original and source product 'related' via the 'product_relations' table
'linkProductsOnCopy' => false,
];

View File

@ -51,7 +51,7 @@ class ProductDataGrid extends DataGrid
->select(
'product_flat.locale',
'product_flat.channel',
'product_flat.product_id as product_id',
'product_flat.product_id',
'products.sku as product_sku',
'product_flat.name as product_name',
'products.type as product_type',
@ -61,17 +61,9 @@ class ProductDataGrid extends DataGrid
DB::raw('SUM(DISTINCT ' . DB::getTablePrefix() . 'product_inventories.qty) as quantity')
);
if ($this->locale !== 'all') {
$queryBuilder->where('product_flat.locale', $this->locale);
}
if ($this->channel !== 'all') {
$queryBuilder->where('product_flat.channel', $this->channel);
}
$queryBuilder->groupBy('product_flat.product_id');
$queryBuilder->having('locale', $this->locale !== 'all' ? $this->locale : 'en');
$queryBuilder->having('channel', $this->channel !== 'all' ? $this->channel : 'default');
$queryBuilder->groupBy('product_flat.product_id', 'product_flat.locale', 'product_flat.channel');
$queryBuilder->where('locale', $this->locale !== 'all' ? $this->locale : 'en');
$queryBuilder->where('channel', $this->channel !== 'all' ? $this->channel : 'default');
$this->addFilter('product_id', 'product_flat.product_id');
$this->addFilter('product_name', 'product_flat.name');
@ -195,6 +187,13 @@ class ProductDataGrid extends DataGrid
public function prepareMassActions()
{
$this->addAction([
'title' => trans('admin::app.datagrid.copy'),
'method' => 'GET',
'route' => 'admin.catalog.products.copy',
'icon' => 'icon note-icon',
]);
$this->addMassAction([
'type' => 'delete',
'label' => trans('admin::app.datagrid.delete'),

View File

@ -206,6 +206,7 @@ class CustomerController extends Controller
$this->customerRepository->delete($id);
} else {
session()->flash('error', trans('admin::app.response.order-pending', ['name' => 'Customer']));
return response()->json(['message' => false], 400);
}

View File

@ -236,9 +236,10 @@ class DashboardController extends Controller
public function getCustomerWithMostSales()
{
return $this->orderRepository->getModel()
->select(DB::raw('SUM(base_grand_total) as total_base_grand_total'))
->addSelect(DB::raw('COUNT(id) as total_orders'))
->addSelect('id', 'customer_id', 'customer_email', 'customer_first_name', 'customer_last_name')
->leftJoin('refunds', 'orders.id', 'refunds.order_id')
->select(DB::raw('(SUM(orders.base_grand_total) - SUM(IFNULL(refunds.base_grand_total, 0))) as total_base_grand_total'))
->addSelect(DB::raw('COUNT(orders.id) as total_orders'))
->addSelect('orders.id', 'customer_id', 'customer_email', 'customer_first_name', 'customer_last_name')
->where('orders.created_at', '>=', $this->startDate)
->where('orders.created_at', '<=', $this->endDate)
->where('orders.status', '<>', 'closed')

View File

@ -273,6 +273,10 @@ Route::group(['middleware' => ['web']], function () {
'redirect' => 'admin.catalog.products.edit',
])->name('admin.catalog.products.store');
Route::get('products/copy/{id}', 'Webkul\Product\Http\Controllers\ProductController@copy')->defaults('_config', [
'view' => 'admin::catalog.products.edit',
])->name('admin.catalog.products.copy');
Route::get('/products/edit/{id}', 'Webkul\Product\Http\Controllers\ProductController@edit')->defaults('_config', [
'view' => 'admin::catalog.products.edit',
])->name('admin.catalog.products.edit');

View File

@ -1,20 +1,22 @@
<?php
return [
'save' => 'حفظ',
'create' => 'خلق',
'update' => 'تحديث',
'delete' => 'حذف',
'failed' => 'فشل',
'store' => 'متجر',
'image' => 'صورة',
'no result' => 'لا نتيجة',
'product' => 'المنتج',
'attribute' => 'ينسب',
'actions' => 'أجراءات',
'id' => 'ID',
'action' => 'عمل',
'yes' => 'نعم',
'save' => 'حفظ',
'create' => 'خلق',
'update' => 'تحديث',
'delete' => 'حذف',
'copy-of' => 'نسخة من ',
'copy-of-slug' => 'نسخة-من-',
'failed' => 'فشل',
'store' => 'متجر',
'image' => 'صورة',
'no result' => 'لا نتيجة',
'product' => 'المنتج',
'attribute' => 'ينسب',
'actions' => 'أجراءات',
'id' => 'ID',
'action' => 'عمل',
'yes' => 'نعم',
'no' => 'لا',
'true' => 'صحيح',
'false' => 'خاطئة',
@ -138,27 +140,28 @@ return [
'datagrid' => [
'mass-ops' => [
'method-error' => 'خطأ! تم اكتشاف طريقة خاطئة ، الرجاء التحقق من تشكيل حركة الكتلة',
'method-error' => 'خطأ! تم اكتشاف طريقة خاطئة ، الرجاء التحقق من تشكيل حركة الكتلة',
'delete-success' => "تم حذف المورد بنجاح :Selected",
'partial-action' => 'ولم تنفذ بعض الإجراءات بسبب القيود المفروضة على النظام :resource',
'update-success' => "تم تحديث المورد بنجاح :Selected",
'no-resource' => 'المورد المقدم غير كاف للعمل'
'no-resource' => 'المورد المقدم غير كاف للعمل',
],
'id' => 'ID',
'status' => 'الحالة',
'code' => 'رمز',
'admin-name' => 'اسم',
'name' => 'اسم',
'direction' => 'اتجاه',
'fullname' => 'الاسم الكامل',
'type' => 'النوع',
'required' => 'مطلوب',
'unique' => 'فريد',
'per-locale' => 'على أساس اللغة',
'per-channel' => 'قائم على القناة',
'position' => 'موضع',
'locale' => 'لغة',
'id' => 'ID',
'status' => 'الحالة',
'code' => 'رمز',
'admin-name' => 'اسم',
'copy' => 'نسخ',
'name' => 'اسم',
'direction' => 'اتجاه',
'fullname' => 'الاسم الكامل',
'type' => 'النوع',
'required' => 'مطلوب',
'unique' => 'فريد',
'per-locale' => 'على أساس اللغة',
'per-channel' => 'قائم على القناة',
'position' => 'موضع',
'locale' => 'لغة',
'hostname' => 'اسم المضيف',
'email' => 'البريد الإلكتروني',
'group' => 'المجموعة',
@ -1201,17 +1204,20 @@ return [
],
'response' => [
'being-used' => ':source في :name يتم استخدام هذا المورد',
'cannot-delete-default' => 'لا يمكن حذف القناة الافتراضية',
'create-success' => 'إنشاء الاسم بنجاح:name',
'update-success' => 'تحديث الاسم بنجاح :name ',
'delete-success' => 'حذف الاسم بنجاح :name',
'delete-failed' => ':name حدث خطأ أثناء حذف',
'last-delete-error' => 'مطلوب name: واحد على الأقل',
'user-define-error' => 'لا يستطيع حذف نظام :name',
'attribute-error' => 'في المنتجات القابلة للتكوين :name يستخدم ' ,
'attribute-product-error' => 'في المنتجات :name يستخدم ' ,
'customer-associate' => 'لا يمكن حذف :name لأن العميل مرتبط بهذه المجموعة.',
'being-used' => ':source في :name يتم استخدام هذا المورد',
'product-copied' => 'تم نسخ المنتج',
'error-while-copying' => 'خطأ في نسخ المنتج',
'product-can-not-be-copied' => 'لا يمكن نسخ منتجات الحجز',
'cannot-delete-default' => 'لا يمكن حذف القناة الافتراضية',
'create-success' => 'إنشاء الاسم بنجاح:name',
'update-success' => 'تحديث الاسم بنجاح :name ',
'delete-success' => 'حذف الاسم بنجاح :name',
'delete-failed' => ':name حدث خطأ أثناء حذف',
'last-delete-error' => 'مطلوب name: واحد على الأقل',
'user-define-error' => 'لا يستطيع حذف نظام :name',
'attribute-error' => 'في المنتجات القابلة للتكوين :name يستخدم ',
'attribute-product-error' => 'في المنتجات :name يستخدم ',
'customer-associate' => 'لا يمكن حذف :name لأن العميل مرتبط بهذه المجموعة.',
'currency-delete-error' => 'يتم تعيين هذه العملة كعملة أساسية القناة لذلك لا يمكن حذفها.',
'upload-success' => 'بنجاح :name تم تحميل',
'delete-category-root' => 'لا يستطيع حذف الجذر الفئة',

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,19 @@
<?php
return [
'save' => 'Save',
'copy-of' => 'Copy of',
'create' => 'Create',
'update' => 'Update',
'delete' => 'Delete',
'failed' => 'Failed',
'store' => 'Store',
'image' => 'Image',
'no result' => 'No result',
'product' => 'Product',
'attribute' => 'Attribute',
'actions' => 'Actions',
'save' => 'Save',
'copy-of' => 'Copy of ',
'copy-of-slug' => 'copy-of-',
'create' => 'Create',
'update' => 'Update',
'delete' => 'Delete',
'failed' => 'Failed',
'store' => 'Store',
'image' => 'Image',
'no result' => 'No result',
'product' => 'Product',
'attribute' => 'Attribute',
'actions' => 'Actions',
'id' => 'ID',
'action' => 'action',
'yes' => 'Yes',
@ -1209,17 +1210,20 @@ return [
],
'response' => [
'being-used' => 'This resource :name is getting used in :source',
'cannot-delete-default' => 'Cannot delete the default channel',
'create-success' => ':name created successfully.',
'update-success' => ':name updated successfully.',
'delete-success' => ':name deleted successfully.',
'delete-failed' => 'Error encountered while deleting :name.',
'last-delete-error' => 'At least one :name is required.',
'user-define-error' => 'Can not delete system :name',
'attribute-error' => ':name is used in configurable products.',
'attribute-product-error' => ':name is used in products.',
'customer-associate' => ':name can not be deleted because customer is associated with this group.',
'being-used' => 'This resource :name is getting used in :source',
'product-copied' => 'The Product has been copied',
'error-while-copying' => 'Something went wrong while trying to copy the product',
'product-can-not-be-copied' => 'Products of type :type can not be copied',
'cannot-delete-default' => 'Cannot delete the default channel',
'create-success' => ':name created successfully.',
'update-success' => ':name updated successfully.',
'delete-success' => ':name deleted successfully.',
'delete-failed' => 'Error encountered while deleting :name.',
'last-delete-error' => 'At least one :name is required.',
'user-define-error' => 'Can not delete system :name',
'attribute-error' => ':name is used in configurable products.',
'attribute-product-error' => ':name is used in products.',
'customer-associate' => ':name can not be deleted because customer is associated with this group.',
'currency-delete-error' => 'This currency is set as channel base currency so it can not be deleted.',
'upload-success' => ':name uploaded successfully.',
'delete-category-root' => 'Cannot delete the root category',

View File

@ -146,24 +146,25 @@ return [
'no-resource' => 'The resource provided for insufficient for the action'
],
'id' => 'ID',
'status' => 'Stato',
'code' => 'Codice',
'admin-name' => 'Nome',
'name' => 'Nome',
'direction' => 'Direzione',
'fullname' => 'Nome completo',
'type' => 'Tipo',
'required' => 'Richiesto',
'unique' => 'Unico',
'per-locale' => 'Basato su localizzazione',
'per-channel' => 'Basato sul canale',
'position' => 'Posizione',
'locale' => 'Locale',
'hostname' => 'Hostname',
'email' => 'Email',
'group' => 'Gruppo',
'phone' => 'Telefono',
'id' => 'ID',
'status' => 'Stato',
'code' => 'Codice',
'admin-name' => 'Nome',
'name' => 'Nome',
'direction' => 'Direzione',
'fullname' => 'Nome completo',
'type' => 'Tipo',
'copy' => 'Copia',
'required' => 'Richiesto',
'unique' => 'Unico',
'per-locale' => 'Basato su localizzazione',
'per-channel' => 'Basato sul canale',
'position' => 'Posizione',
'locale' => 'Locale',
'hostname' => 'Hostname',
'email' => 'Email',
'group' => 'Gruppo',
'phone' => 'Telefono',
'gender' => 'Sesso',
'title' => 'Titolo',
'layout' => 'Layout',

View File

@ -146,24 +146,25 @@ return [
'no-resource' => 'The resource provided for insufficient for the action'
],
'id' => 'ID',
'status' => 'Status',
'code' => 'Code',
'admin-name' => 'Naam',
'name' => 'Naam',
'direction' => 'Richting',
'fullname' => 'Volledige naam',
'type' => 'Type',
'required' => 'Verplicht',
'unique' => 'Uniek',
'per-locale' => 'Lokaal gebaseerd',
'per-channel' => 'Kanaal gebaseerd',
'position' => 'Position',
'locale' => 'Locale',
'hostname' => 'Hostnaam',
'email' => 'Email',
'group' => 'Groep',
'phone' => 'Telefoon',
'id' => 'ID',
'status' => 'Status',
'code' => 'Code',
'admin-name' => 'Naam',
'name' => 'Naam',
'direction' => 'Richting',
'fullname' => 'Volledige naam',
'type' => 'Type',
'copy' => 'kopiëren',
'required' => 'Verplicht',
'unique' => 'Uniek',
'per-locale' => 'Lokaal gebaseerd',
'per-channel' => 'Kanaal gebaseerd',
'position' => 'Position',
'locale' => 'Locale',
'hostname' => 'Hostnaam',
'email' => 'Email',
'group' => 'Groep',
'phone' => 'Telefoon',
'gender' => 'Geslacht',
'title' => 'Titel',
'layout' => 'Layout',

View File

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

View File

@ -24,6 +24,16 @@ class AttributeFamily extends Model implements AttributeFamilyContract
->select('attributes.*');
}
/**
* Get all of the comparable attributes which belongs to attribute family.
*/
public function getComparableAttributesBelongsToFamily()
{
return (AttributeProxy::modelClass())::join('attribute_group_mappings', 'attribute_group_mappings.attribute_id', '=', 'attributes.id')
->select('attributes.*')->where('attributes.is_comparable', 1)->get();
}
/**
* Get all of the attributes for the attribute groups.
*/

View File

@ -2,17 +2,17 @@
namespace Webkul\BookingProduct\Type;
use Illuminate\Support\Arr;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Helpers\ProductImage;
use Webkul\BookingProduct\Repositories\BookingProductRepository;
use Webkul\BookingProduct\Helpers\Booking as BookingHelper;
use Webkul\Product\Type\Virtual;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Webkul\Product\Type\Virtual;
use Webkul\Product\Helpers\ProductImage;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\BookingProduct\Helpers\Booking as BookingHelper;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\BookingProduct\Repositories\BookingProductRepository;
class Booking extends Virtual
{
@ -30,6 +30,9 @@ class Booking extends Virtual
*/
protected $bookingHelper;
/** @var bool do not allow booking products to be copied, it would be too complicated. */
protected $canBeCopied = false;
/**
* @var array
*/
@ -44,11 +47,11 @@ class Booking extends Virtual
/**
* Create a new product type instance.
*
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository
* @param \Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository
* @param \Webkul\Product\Repositories\ProductImageRepository $productImageRepository
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository
* @param \Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository
* @param \Webkul\Product\Repositories\ProductImageRepository $productImageRepository
* @param \Webkul\Product\Helpers\ProductImage $productImageHelper
* @param \Webkul\BookingProduct\Repositories\BookingProductRepository $bookingProductRepository
* @param \Webkul\BookingProduct\Helpers\BookingHelper $bookingHelper
@ -133,7 +136,7 @@ class Booking extends Virtual
if (! $bookingProduct) {
return false;
}
if (in_array($bookingProduct->type, ['default', 'rental', 'table'])) {
return true;
}
@ -180,7 +183,7 @@ class Booking extends Virtual
if ($bookingProduct->type == 'event') {
if (Carbon::now() > $bookingProduct->available_from && Carbon::now() > $bookingProduct->available_to) {
return trans('shop::app.checkout.cart.event.expired');
}
}
$filtered = Arr::where($data['booking']['qty'], function ($qty, $key) {
return $qty != 0;
@ -206,7 +209,7 @@ class Booking extends Virtual
if (is_string($cartProducts)) {
return $cartProducts;
}
$products = array_merge($products, $cartProducts);
}
} else {

View File

@ -275,19 +275,23 @@ class CartRule
break;
case 'cart_fixed':
if ($this->itemTotals[$rule->id]['total_items'] <= 1) {
$discountAmount = core()->convertPrice($rule->discount_amount);
// if ($this->itemTotals[$rule->id]['total_items'] <= 1) {
// $discountAmount = core()->convertPrice($rule->discount_amount);
$baseDiscountAmount = min($item->base_price * $quantity, $rule->discount_amount);
} else {
$discountRate = $item->base_price * $quantity / $this->itemTotals[$rule->id]['base_total_price'];
// $baseDiscountAmount = min($item->base_price * $quantity, $rule->discount_amount);
// } else {
// $discountRate = $item->base_price * $quantity / $this->itemTotals[$rule->id]['base_total_price'];
$maxDiscount = $rule->discount_amount * $discountRate;
// $maxDiscount = $rule->discount_amount * $discountRate;
$discountAmount = core()->convertPrice($maxDiscount);
// $discountAmount = core()->convertPrice($maxDiscount);
$baseDiscountAmount = min($item->base_price * $quantity, $maxDiscount);
}
// $baseDiscountAmount = min($item->base_price * $quantity, $maxDiscount);
// }
$discountAmount = core()->convertPrice($rule->discount_amount);
$baseDiscountAmount = min($item->base_price * $quantity, $rule->discount_amount);
$discountAmount = min($item->price * $quantity, $discountAmount);

View File

@ -2,6 +2,7 @@
namespace Webkul\CartRule\Http\Controllers;
use Exception;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
@ -87,7 +88,7 @@ class CartRuleController extends Controller
->replicate()
->fill([
'status' => 0,
'name' => __('admin::app.copy-of') . ' ' . $originalCartRule->name,
'name' => __('admin::app.copy-of') . $originalCartRule->name,
]);
$copiedCartRule->save();
@ -209,7 +210,7 @@ class CartRuleController extends Controller
session()->flash('success', trans('admin::app.response.delete-success', ['name' => 'Cart Rule']));
return response()->json(['message' => true], 200);
} catch (\Exception $e) {
} catch (Exception $e) {
session()->flash('error', trans('admin::app.response.delete-failed', ['name' => 'Cart Rule']));
}

View File

@ -2,16 +2,20 @@
namespace Webkul\Product\Http\Controllers;
use Exception;
use Webkul\Product\Models\Product;
use Illuminate\Support\Facades\Event;
use Webkul\Product\Http\Requests\ProductForm;
use Illuminate\Support\Facades\Storage;
use Webkul\Product\Helpers\ProductType;
use Webkul\Category\Repositories\CategoryRepository;
use Webkul\Core\Contracts\Validations\Slug;
use Webkul\Product\Http\Requests\ProductForm;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Product\Repositories\ProductDownloadableLinkRepository;
use Webkul\Product\Repositories\ProductDownloadableSampleRepository;
use Webkul\Category\Repositories\CategoryRepository;
use Webkul\Attribute\Repositories\AttributeFamilyRepository;
use Webkul\Inventory\Repositories\InventorySourceRepository;
use Illuminate\Support\Facades\Storage;
use Webkul\Product\Repositories\ProductDownloadableLinkRepository;
use Webkul\Product\Repositories\ProductDownloadableSampleRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
class ProductController extends Controller
{
@ -64,15 +68,24 @@ class ProductController extends Controller
*/
protected $inventorySourceRepository;
/**
* ProductAttributeValueRepository object
*
* @var \Webkul\Product\Repositories\ProductAttributeValueRepository
*/
protected $productAttributeValueRepository;
/**
* Create a new controller instance.
*
* @param \Webkul\Category\Repositories\CategoryRepository $categoryRepository
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
* @param \Webkul\Product\Repositories\ProductDownloadableLinkRepository $productDownloadableLinkRepository
* @param \Webkul\Product\Repositories\ProductDownloadableSampleRepository $productDownloadableSampleRepository
* @param \Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
* @param \Webkul\Category\Repositories\CategoryRepository $categoryRepository
* @param \Webkul\Product\Repositories\ProductRepository $productRepository
* @param \Webkul\Product\Repositories\ProductDownloadableLinkRepository $productDownloadableLinkRepository
* @param \Webkul\Product\Repositories\ProductDownloadableSampleRepository $productDownloadableSampleRepository
* @param \Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
* @param \Webkul\Inventory\Repositories\InventorySourceRepository $inventorySourceRepository
* @param \Webkul\Product\Repositories\ProductAttributeValueRepository $productAttributeValueRepository
*
* @return void
*/
public function __construct(
@ -81,7 +94,8 @@ class ProductController extends Controller
ProductDownloadableLinkRepository $productDownloadableLinkRepository,
ProductDownloadableSampleRepository $productDownloadableSampleRepository,
AttributeFamilyRepository $attributeFamilyRepository,
InventorySourceRepository $inventorySourceRepository
InventorySourceRepository $inventorySourceRepository,
ProductAttributeValueRepository $productAttributeValueRepository
)
{
$this->_config = request('_config');
@ -97,6 +111,8 @@ class ProductController extends Controller
$this->attributeFamilyRepository = $attributeFamilyRepository;
$this->inventorySourceRepository = $inventorySourceRepository;
$this->productAttributeValueRepository = $productAttributeValueRepository;
}
/**
@ -143,7 +159,7 @@ class ProductController extends Controller
if (ProductType::hasVariants(request()->input('type'))
&& (! request()->has('super_attributes')
|| ! count(request()->get('super_attributes')))
|| ! count(request()->get('super_attributes')))
) {
session()->flash('error', trans('admin::app.catalog.products.configurable-error'));
@ -153,7 +169,7 @@ class ProductController extends Controller
$this->validate(request(), [
'type' => 'required',
'attribute_family_id' => 'required',
'sku' => ['required', 'unique:products,sku', new \Webkul\Core\Contracts\Validations\Slug],
'sku' => ['required', 'unique:products,sku', new Slug],
]);
$product = $this->productRepository->create(request()->all());
@ -166,7 +182,8 @@ class ProductController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @param int $id
*
* @return \Illuminate\View\View
*/
public function edit($id)
@ -183,8 +200,9 @@ class ProductController extends Controller
/**
* Update the specified resource in storage.
*
* @param \Webkul\Product\Http\Requests\ProductForm $request
* @param int $id
* @param \Webkul\Product\Http\Requests\ProductForm $request
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function update(ProductForm $request, $id)
@ -201,20 +219,20 @@ class ProductController extends Controller
if (count($customAttributes)) {
foreach ($customAttributes as $attribute) {
if ($attribute->type == 'multiselect') {
array_push($multiselectAttributeCodes,$attribute->code);
}
array_push($multiselectAttributeCodes, $attribute->code);
}
}
}
}
if (count($multiselectAttributeCodes)) {
foreach ($multiselectAttributeCodes as $multiselectAttributeCode) {
if(! isset($data[$multiselectAttributeCode])){
if (! isset($data[$multiselectAttributeCode])) {
$data[$multiselectAttributeCode] = array();
}
}
}
}
$product = $this->productRepository->update($data, $id);
session()->flash('success', trans('admin::app.response.update-success', ['name' => 'Product']));
@ -225,7 +243,8 @@ class ProductController extends Controller
/**
* Uploads downloadable file
*
* @param int $id
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function uploadLink($id)
@ -235,10 +254,45 @@ class ProductController extends Controller
);
}
/**
* Copy a given Product.
*/
public function copy(int $productId)
{
$originalProduct = $this->productRepository->findOrFail($productId);
if (! $originalProduct->getTypeInstance()->canBeCopied()) {
session()->flash('error',
trans('admin::app.response.product-can-not-be-copied', [
'type' => $originalProduct->type,
]));
return redirect()->to(route('admin.catalog.products.index'));
}
if ($originalProduct->parent_id) {
session()->flash('error',
trans('admin::app.catalog.products.variant-already-exist-message'));
return redirect()->to(route('admin.catalog.products.index'));
}
$copiedProduct = $this->productRepository->copy($originalProduct);
if ($copiedProduct instanceof Product && $copiedProduct->id) {
session()->flash('success', trans('admin::app.response.product-copied'));
} else {
session()->flash('error', trans('admin::app.response.error-while-copying'));
}
return redirect()->to(route('admin.catalog.products.edit', ['id' => $copiedProduct->id]));
}
/**
* Uploads downloadable sample file
*
* @param int $id
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function uploadSample($id)
@ -251,7 +305,8 @@ class ProductController extends Controller
/**
* Remove the specified resource from storage.
*
* @param int $id
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function destroy($id)
@ -264,7 +319,7 @@ class ProductController extends Controller
session()->flash('success', trans('admin::app.response.delete-success', ['name' => 'Product']));
return response()->json(['message' => true], 200);
} catch (\Exception $e) {
} catch (Exception $e) {
report($e);
session()->flash('error', trans('admin::app.response.delete-failed', ['name' => 'Product']));
@ -363,16 +418,17 @@ class ProductController extends Controller
}
}
/**
/**
* Download image or file
*
* @param int $productId
* @param int $attributeId
* @param int $productId
* @param int $attributeId
*
* @return \Illuminate\Http\Response
*/
public function download($productId, $attributeId)
{
$productAttribute = $this->productAttributeValue->findOneWhere([
$productAttribute = $this->productAttributeValueRepository->findOneWhere([
'product_id' => $productId,
'attribute_id' => $attributeId,
]);

View File

@ -2,11 +2,15 @@
namespace Webkul\Product\Models;
use Exception;
use Webkul\Product\Type\AbstractType;
use Illuminate\Database\Eloquent\Model;
use Webkul\Attribute\Models\AttributeFamilyProxy;
use Webkul\Category\Models\CategoryProxy;
use Webkul\Attribute\Models\AttributeProxy;
use Webkul\Product\Database\Eloquent\Builder;
use Webkul\Attribute\Models\AttributeFamilyProxy;
use Webkul\Inventory\Models\InventorySourceProxy;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Contracts\Product as ProductContract;
class Product extends Model implements ProductContract
@ -59,7 +63,8 @@ class Product extends Model implements ProductContract
}
/**
* Get the product variants that owns the product.
* Get the product flat entries that are associated with product.
* May be one for each locale and each channel.
*/
public function product_flats()
{
@ -220,8 +225,8 @@ class Product extends Model implements ProductContract
public function inventory_source_qty($inventorySourceId)
{
return $this->inventories()
->where('inventory_source_id', $inventorySourceId)
->sum('qty');
->where('inventory_source_id', $inventorySourceId)
->sum('qty');
}
/**
@ -237,6 +242,12 @@ class Product extends Model implements ProductContract
$this->typeInstance = app(config('product_types.' . $this->type . '.class'));
if (! $this->typeInstance instanceof AbstractType) {
throw new Exception(
"Please ensure the product type '{$this->type}' is configured in your application."
);
}
$this->typeInstance->setProduct($this);
return $this->typeInstance;
@ -283,6 +294,7 @@ class Product extends Model implements ProductContract
*
* @param Group $group
* @param bool $skipSuperAttribute
*
* @return Collection
*/
public function getEditableAttributes($group = null, $skipSuperAttribute = true)
@ -293,7 +305,8 @@ class Product extends Model implements ProductContract
/**
* Get an attribute from the model.
*
* @param string $key
* @param string $key
*
* @return mixed
*/
public function getAttribute($key)
@ -305,8 +318,8 @@ class Product extends Model implements ProductContract
if (isset($this->id)) {
$this->attributes[$key] = '';
$attribute = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
->getAttributeByCode($key);
$attribute = core()->getSingletonInstance(AttributeRepository::class)
->getAttributeByCode($key);
$this->attributes[$key] = $this->getCustomAttributeValue($attribute);
@ -327,8 +340,9 @@ class Product extends Model implements ProductContract
$hiddenAttributes = $this->getHidden();
if (isset($this->id)) {
$familyAttributes = core()->getSingletonInstance(\Webkul\Attribute\Repositories\AttributeRepository::class)
->getFamilyAttributes($this->attribute_family);
$familyAttributes = core()
->getSingletonInstance(AttributeRepository::class)
->getFamilyAttributes($this->attribute_family);
foreach ($familyAttributes as $attribute) {
if (in_array($attribute->code, $hiddenAttributes)) {
@ -377,12 +391,13 @@ class Product extends Model implements ProductContract
/**
* Overrides the default Eloquent query builder
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $query
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newEloquentBuilder($query)
{
return new \Webkul\Product\Database\Eloquent\Builder($query);
return new Builder($query);
}
/**

View File

@ -2,14 +2,20 @@
namespace Webkul\Product\Repositories;
use Illuminate\Pagination\Paginator;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Container\Container as App;
use Illuminate\Support\Facades\Event;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Models\Product;
use Illuminate\Pagination\Paginator;
use Webkul\Core\Eloquent\Repository;
use Illuminate\Support\Facades\Event;
use Webkul\Attribute\Models\Attribute;
use Webkul\Product\Models\ProductFlat;
use Illuminate\Container\Container as App;
use Illuminate\Pagination\LengthAwarePaginator;
use Webkul\Product\Models\ProductAttributeValueProxy;
use Webkul\Attribute\Repositories\AttributeRepository;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ProductRepository extends Repository
{
@ -23,8 +29,9 @@ class ProductRepository extends Repository
/**
* Create a new repository instance.
*
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param \Illuminate\Container\Container $app
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param \Illuminate\Container\Container $app
*
* @return void
*/
public function __construct(
@ -48,7 +55,8 @@ class ProductRepository extends Repository
}
/**
* @param array $data
* @param array $data
*
* @return \Webkul\Product\Contracts\Product
*/
public function create(array $data)
@ -65,9 +73,10 @@ class ProductRepository extends Repository
}
/**
* @param array $data
* @param int $id
* @param string $attribute
* @param array $data
* @param int $id
* @param string $attribute
*
* @return \Webkul\Product\Contracts\Product
*/
public function update(array $data, $id, $attribute = "id")
@ -88,7 +97,8 @@ class ProductRepository extends Repository
}
/**
* @param int $id
* @param int $id
*
* @return void
*/
public function delete($id)
@ -101,7 +111,8 @@ class ProductRepository extends Repository
}
/**
* @param int $categoryId
* @param int $categoryId
*
* @return \Illuminate\Support\Collection
*/
public function getAll($categoryId = null)
@ -111,21 +122,21 @@ class ProductRepository extends Repository
if (core()->getConfigData('catalog.products.storefront.products_per_page')) {
$pages = explode(',', core()->getConfigData('catalog.products.storefront.products_per_page'));
$perPage = isset($params['limit']) ? (!empty($params['limit']) ? $params['limit'] : 9) : current($pages);
$perPage = isset($params['limit']) ? (! empty($params['limit']) ? $params['limit'] : 9) : current($pages);
} else {
$perPage = isset($params['limit']) && !empty($params['limit']) ? $params['limit'] : 9;
$perPage = isset($params['limit']) && ! empty($params['limit']) ? $params['limit'] : 9;
}
$page = Paginator::resolveCurrentPage('page');
$repository = app(ProductFlatRepository::class)->scopeQuery(function($query) use($params, $categoryId) {
$repository = app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($params, $categoryId) {
$channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode());
$locale = request()->get('locale') ?: app()->getLocale();
$qb = $query->distinct()
->select('product_flat.*')
->join('product_flat as variants', 'product_flat.id', '=', DB::raw('COALESCE('.DB::getTablePrefix().'variants.parent_id, '.DB::getTablePrefix().'variants.id)'))
->join('product_flat as variants', 'product_flat.id', '=', DB::raw('COALESCE(' . DB::getTablePrefix() . 'variants.parent_id, ' . DB::getTablePrefix() . 'variants.id)'))
->leftJoin('product_categories', 'product_categories.product_id', '=', 'product_flat.product_id')
->leftJoin('product_attribute_values', 'product_attribute_values.product_id', '=', 'variants.product_id')
->where('product_flat.channel', $channel)
@ -149,25 +160,25 @@ class ProductRepository extends Repository
# sort direction
$orderDirection = 'asc';
if( isset($params['order']) && in_array($params['order'], ['desc', 'asc']) ){
if (isset($params['order']) && in_array($params['order'], ['desc', 'asc'])) {
$orderDirection = $params['order'];
} else {
$sortOptions = $this->getDefaultSortByOption();
$orderDirection = !empty($sortOptions) ? $sortOptions[1] : 'asc';
$orderDirection = ! empty($sortOptions) ? $sortOptions[1] : 'asc';
}
if (isset($params['sort'])) {
$this->checkSortAttributeAndGenerateQuery($qb, $params['sort'], $orderDirection);
} else {
$sortOptions = $this->getDefaultSortByOption();
if (!empty($sortOptions)) {
if (! empty($sortOptions)) {
$this->checkSortAttributeAndGenerateQuery($qb, $sortOptions[0], $orderDirection);
}
}
if ( $priceFilter = request('price') ){
if ($priceFilter = request('price')) {
$priceRange = explode(',', $priceFilter);
if( count($priceRange) > 0 ) {
if (count($priceRange) > 0) {
$qb->where('variants.min_price', '>=', core()->convertToBasePrice($priceRange[0]));
$qb->where('variants.min_price', '<=', core()->convertToBasePrice(end($priceRange)));
}
@ -178,8 +189,8 @@ class ProductRepository extends Repository
request()->except(['price'])
));
if ( count($attributeFilters) > 0 ) {
$qb->where(function ($filterQuery) use($attributeFilters){
if (count($attributeFilters) > 0) {
$qb->where(function ($filterQuery) use ($attributeFilters) {
foreach ($attributeFilters as $attribute) {
$filterQuery->orWhere(function ($attributeQuery) use ($attribute) {
@ -196,7 +207,7 @@ class ProductRepository extends Repository
$attributeQuery->where(function ($attributeValueQuery) use ($column, $filterInputValues) {
foreach ($filterInputValues as $filterValue) {
if (!is_numeric($filterValue)) {
if (! is_numeric($filterValue)) {
continue;
}
$attributeValueQuery->orWhereRaw("find_in_set(?, {$column})", [$filterValue]);
@ -239,9 +250,9 @@ class ProductRepository extends Repository
$items = [];
}
$results = new \Illuminate\Pagination\LengthAwarePaginator($items, $count, $perPage, $page, [
$results = new LengthAwarePaginator($items, $count, $perPage, $page, [
'path' => request()->url(),
'query' => request()->query()
'query' => request()->query(),
]);
return $results;
@ -250,8 +261,9 @@ class ProductRepository extends Repository
/**
* Retrive product from slug
*
* @param string $slug
* @param string $columns
* @param string $slug
* @param string $columns
*
* @return \Webkul\Product\Contracts\Product
*/
public function findBySlugOrFail($slug, $columns = null)
@ -274,7 +286,8 @@ class ProductRepository extends Repository
/**
* Retrieve product from slug without throwing an exception (might return null)
*
* @param string $slug
* @param string $slug
*
* @return \Webkul\Product\Contracts\ProductFlat
*/
public function findBySlug($slug)
@ -293,19 +306,19 @@ class ProductRepository extends Repository
*/
public function getNewProducts()
{
$results = app(ProductFlatRepository::class)->scopeQuery(function($query) {
$results = app(ProductFlatRepository::class)->scopeQuery(function ($query) {
$channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode());
$locale = request()->get('locale') ?: app()->getLocale();
return $query->distinct()
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.new', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->inRandomOrder();
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.new', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->inRandomOrder();
})->paginate(4);
return $results;
@ -318,19 +331,19 @@ class ProductRepository extends Repository
*/
public function getFeaturedProducts()
{
$results = app(ProductFlatRepository::class)->scopeQuery(function($query) {
$results = app(ProductFlatRepository::class)->scopeQuery(function ($query) {
$channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode());
$locale = request()->get('locale') ?: app()->getLocale();
return $query->distinct()
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.featured', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->inRandomOrder();
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.featured', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->inRandomOrder();
})->paginate(4);
return $results;
@ -339,7 +352,8 @@ class ProductRepository extends Repository
/**
* Search Product by Attribute
*
* @param string $term
* @param string $term
*
* @return \Illuminate\Support\Collection
*/
public function searchProductByAttribute($term)
@ -349,27 +363,27 @@ class ProductRepository extends Repository
$locale = request()->get('locale') ?: app()->getLocale();
if (config('scout.driver') == 'algolia') {
$results = app(ProductFlatRepository::class)->getModel()::search('query', function ($searchDriver, string $query, array $options) use($term, $channel, $locale) {
$results = app(ProductFlatRepository::class)->getModel()::search('query', function ($searchDriver, string $query, array $options) use ($term, $channel, $locale) {
$queries = explode('_', $term);
$options['similarQuery'] = array_map('trim', $queries);
$searchDriver->setSettings([
'attributesForFaceting' => [
"searchable(locale)",
"searchable(channel)"
]
"searchable(locale)",
"searchable(channel)",
],
]);
$options['facetFilters'] = ['locale:' . $locale, 'channel:' . $channel];
$options['facetFilters'] = ['locale:' . $locale, 'channel:' . $channel];
return $searchDriver->search($query, $options);
})
->where('status', 1)
->where('visible_individually', 1)
->orderBy('product_id', 'desc')
->paginate(16);
} else if(config('scout.driver') == 'elastic') {
->where('status', 1)
->where('visible_individually', 1)
->orderBy('product_id', 'desc')
->paginate(16);
} else if (config('scout.driver') == 'elastic') {
$queries = explode('_', $term);
$results = app(ProductFlatRepository::class)->getModel()::search(implode(' OR ', $queries))
@ -380,23 +394,23 @@ class ProductRepository extends Repository
->orderBy('product_id', 'desc')
->paginate(16);
} else {
$results = app(ProductFlatRepository::class)->scopeQuery(function($query) use($term, $channel, $locale) {
$results = app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($term, $channel, $locale) {
return $query->distinct()
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->whereNotNull('product_flat.url_key')
->where(function($subQuery) use ($term) {
$queries = explode('_', $term);
->addSelect('product_flat.*')
->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->whereNotNull('product_flat.url_key')
->where(function ($subQuery) use ($term) {
$queries = explode('_', $term);
foreach (array_map('trim', $queries) as $value) {
$subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%')
->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%');
}
})
->orderBy('product_id', 'desc');
foreach (array_map('trim', $queries) as $value) {
$subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%')
->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%');
}
})
->orderBy('product_id', 'desc');
})->paginate(16);
}
@ -406,7 +420,8 @@ class ProductRepository extends Repository
/**
* Returns product's super attribute with options
*
* @param \Webkul\Product\Contracts\Product $product
* @param \Webkul\Product\Contracts\Product $product
*
* @return \Illuminate\Support\Collection
*/
public function getSuperAttributes($product)
@ -432,35 +447,72 @@ class ProductRepository extends Repository
/**
* Search simple products for grouped product association
*
* @param string $term
* @param string $term
*
* @return \Illuminate\Support\Collection
*/
public function searchSimpleProducts($term)
{
return app(ProductFlatRepository::class)->scopeQuery(function($query) use($term) {
return app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($term) {
$channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode());
$locale = request()->get('locale') ?: app()->getLocale();
return $query->distinct()
->addSelect('product_flat.*')
->addSelect('product_flat.product_id as id')
->leftJoin('products', 'product_flat.product_id', '=', 'products.id')
->where('products.type', 'simple')
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->where('product_flat.name', 'like', '%' . urldecode($term) . '%')
->orderBy('product_id', 'desc');
->addSelect('product_flat.*')
->addSelect('product_flat.product_id as id')
->leftJoin('products', 'product_flat.product_id', '=', 'products.id')
->where('products.type', 'simple')
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->where('product_flat.name', 'like', '%' . urldecode($term) . '%')
->orderBy('product_id', 'desc');
})->get();
}
/**
* Copy a product. Is usually called by the copy() function of the ProductController.
*
* Always make the copy is inactive so the admin is able to configure it before setting it live.
*
* @param int $sourceProductId the id of the product that should be copied
*/
public function copy(Product $originalProduct): Product
{
$this->fillOriginalProduct($originalProduct);
if (! $originalProduct->getTypeInstance()->canBeCopied()) {
throw new Exception(trans('admin::app.response.product-can-not-be-copied', ['type' => $originalProduct->type]));
}
DB::beginTransaction();
try {
$copiedProduct = $this->persistCopiedProduct($originalProduct);
$this->persistAttributeValues($originalProduct, $copiedProduct);
$this->persistRelations($originalProduct, $copiedProduct);
} catch (Exception $e) {
DB::rollBack();
report($e);
throw $e;
}
DB::commit();
return $copiedProduct;
}
/**
* Get default sort by option
*
* @return array
*/
private function getDefaultSortByOption()
{
{
$value = core()->getConfigData('catalog.products.storefront.sort_by');
$config = $value ? $value : 'name-desc';
@ -471,9 +523,10 @@ class ProductRepository extends Repository
/**
* Check sort attribute and generate query
*
* @param object $query
* @param string $sort
* @param string $direction
* @param object $query
* @param string $sort
* @param string $direction
*
* @return object
*/
private function checkSortAttributeAndGenerateQuery($query, $sort, $direction)
@ -481,13 +534,188 @@ class ProductRepository extends Repository
$attribute = $this->attributeRepository->findOneByField('code', $sort);
if ($attribute) {
if ($attribute->code == 'price') {
if ($attribute->code === 'price') {
$query->orderBy('min_price', $direction);
} else {
$query->orderBy($sort == 'created_at' ? 'product_flat.created_at' : $attribute->code, $direction);
$query->orderBy($sort === 'created_at' ? 'product_flat.created_at' : $attribute->code, $direction);
}
}
return $query;
}
private function fillOriginalProduct(Product &$sourceProduct): void
{
$sourceProduct
->load('attribute_family')
->load('categories')
->load('customer_group_prices')
->load('inventories')
->load('inventory_sources');
}
/**
* @param $originalProduct
*
* @return mixed
*/
private function persistCopiedProduct($originalProduct): Product
{
$copiedProduct = $originalProduct
->replicate()
->fill([
// the sku and url_key needs to be unique and should be entered again newly by the admin:
'sku' => 'temporary-sku-' . substr(md5(microtime()), 0, 6),
]);
$copiedProduct->save();
return $copiedProduct;
}
/**
* Gather the ids of the necessary product attributes.
* Throw an Exception if one of these 'basic' attributes are missing for some reason.
*/
private function gatherAttributeIds(): array
{
$ids = [];
foreach (['name', 'sku', 'status', 'url_key'] as $code) {
$ids[$code] = Attribute::query()->where(['code' => $code])->firstOrFail()->id;
}
return $ids;
}
private function persistAttributeValues(Product $originalProduct, Product $copiedProduct): void
{
$attributeIds = $this->gatherAttributeIds();
$newProductFlat = new ProductFlat();
// only obey copied locale and channel:
if (isset($originalProduct->product_flats[0])) {
$newProductFlat = $originalProduct->product_flats[0]->replicate();
}
$newProductFlat->product_id = $copiedProduct->id;
$attributesToSkip = config('products.skipAttributesOnCopy') ?? [];
$randomSuffix = substr(md5(microtime()), 0, 6);
foreach ($originalProduct->attribute_values as $oldValue) {
if (in_array($oldValue->attribute->code, $attributesToSkip)) {
continue;
}
$newValue = $oldValue->replicate();
// change name of copied product
if ($oldValue->attribute_id === $attributeIds['name']) {
$copyOf = trans('admin::app.copy-of');
$copiedName = sprintf('%s%s (%s)',
Str::startsWith($originalProduct->name, $copyOf) ? '' : $copyOf,
$originalProduct->name,
$randomSuffix
);
$newValue->text_value = $copiedName;
$newProductFlat->name = $copiedName;
}
// change url_key of copied product
if ($oldValue->attribute_id === $attributeIds['url_key']) {
$copyOfSlug = trans('admin::app.copy-of-slug');
$copiedSlug = sprintf('%s%s-%s',
Str::startsWith($originalProduct->url_key, $copyOfSlug) ? '' : $copyOfSlug,
$originalProduct->url_key,
$randomSuffix
);
$newValue->text_value = $copiedSlug;
$newProductFlat->url_key = $copiedSlug;
}
// change sku of copied product
if ($oldValue->attribute_id === $attributeIds['sku']) {
$newValue->text_value = $copiedProduct->sku;
$newProductFlat->sku = $copiedProduct->sku;
}
// force the copied product to be inactive so the admin can adjust it before release
if ($oldValue->attribute_id === $attributeIds['status']) {
$newValue->boolean_value = 0;
$newProductFlat->status = 0;
}
$copiedProduct->attribute_values()->save($newValue);
}
$newProductFlat->save();
}
/**
* @param $originalProduct
* @param $copiedProduct
*/
private function persistRelations($originalProduct, $copiedProduct): void
{
$attributesToSkip = config('products.skipAttributesOnCopy') ?? [];
if (! in_array('categories', $attributesToSkip)) {
foreach ($originalProduct->categories as $category) {
DB::table('product_categories')->insert([
'product_id' => $copiedProduct->id,
'category_id' => $category->id,
]);
}
}
if (! in_array('inventories', $attributesToSkip)) {
foreach ($originalProduct->inventories as $inventory) {
$copiedProduct->inventories()->save($inventory->replicate());
}
}
if (! in_array('customer_group_pricces', $attributesToSkip)) {
foreach ($originalProduct->customer_group_prices as $customer_group_price) {
$copiedProduct->customer_group_prices()->save($customer_group_price->replicate());
}
}
if (! in_array('images', $attributesToSkip)) {
foreach ($originalProduct->images as $image) {
$copiedProduct->images()->save($image->replicate());
}
}
if (! in_array('super_attributes', $attributesToSkip)) {
foreach ($originalProduct->super_attributes as $super_attribute) {
$copiedProduct->super_attributes()->save($super_attribute);
}
}
if (! in_array('bundle_options', $attributesToSkip)) {
foreach ($originalProduct->bundle_options as $bundle_option) {
$copiedProduct->bundle_options()->save($bundle_option->replicate());
}
}
if (! in_array('variants', $attributesToSkip)) {
foreach ($originalProduct->variants as $variant) {
$variant = $this->copy($variant);
$variant->parent_id = $copiedProduct->id;
$variant->save();
}
}
if (config('products.linkProductsOnCopy')) {
DB::table('product_relations')->insert([
'parent_id' => $originalProduct->id,
'child_id' => $copiedProduct->id,
]);
}
}
}

View File

@ -2,16 +2,15 @@
namespace Webkul\Product\Type;
use Illuminate\Support\Facades\Storage;
use phpDocumentor\Reflection\Types\Boolean;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Product\Helpers\ProductImage;
use Webkul\Checkout\Facades\Cart;
use Illuminate\Support\Facades\Storage;
use Webkul\Product\Helpers\ProductImage;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
abstract class AbstractType
{
@ -54,7 +53,7 @@ abstract class AbstractType
* Product Image helper instance
*
* @var \Webkul\Product\Helpers\ProductImage
*/
*/
protected $productImageHelper;
/**
@ -99,6 +98,13 @@ abstract class AbstractType
*/
protected $canBeMovedFromWishlistToCart = true;
/**
* Products of this type can be copied in the admin backend?
*
* @var bool
*/
protected $canBeCopied = true;
/**
* Has child products aka variants
*
@ -152,7 +158,16 @@ abstract class AbstractType
}
/**
* @param array $data
* Is the administrator able to copy products of this type in the admin backend?
*/
public function canBeCopied(): bool
{
return $this->canBeCopied;
}
/**
* @param array $data
*
* @return \Webkul\Product\Contracts\Product
*/
public function create(array $data)
@ -162,8 +177,8 @@ abstract class AbstractType
/**
* @param array $data
* @param int $id
* @param string $attribute
* @param int $id
* @param string $attribute
* @return \Webkul\Product\Contracts\Product
*/
public function update(array $data, $id, $attribute = "id")
@ -175,7 +190,7 @@ abstract class AbstractType
foreach ($product->attribute_family->custom_attributes as $attribute) {
$route = request()->route() ? request()->route()->getName() : "";
if ($attribute->type == 'boolean' && $route != 'admin.catalog.products.massupdate') {
if ($attribute->type === 'boolean' && $route !== 'admin.catalog.products.massupdate') {
$data[$attribute->code] = isset($data[$attribute->code]) && $data[$attribute->code] ? 1 : 0;
}
@ -197,8 +212,8 @@ abstract class AbstractType
if ($attribute->type == 'image' || $attribute->type == 'file') {
$data[$attribute->code] = gettype($data[$attribute->code]) == 'object'
? request()->file($attribute->code)->store('product/' . $product->id)
: NULL;
? request()->file($attribute->code)->store('product/' . $product->id)
: NULL;
}
$attributeValue = $this->attributeValueRepository->findOneWhere([
@ -219,7 +234,7 @@ abstract class AbstractType
} else {
$this->attributeValueRepository->update([
ProductAttributeValue::$attributeTypeFields[$attribute->type] => $data[$attribute->code]
], $attributeValue->id
], $attributeValue->id
);
if ($attribute->type == 'image' || $attribute->type == 'file') {
@ -230,8 +245,8 @@ abstract class AbstractType
$route = request()->route() ? request()->route()->getName() : "";
if ($route != 'admin.catalog.products.massupdate') {
if (! isset($data['categories'])) {
if ($route !== 'admin.catalog.products.massupdate') {
if (! isset($data['categories'])) {
$data['categories'] = [];
}
@ -376,9 +391,9 @@ abstract class AbstractType
$total = 0;
$channelInventorySourceIds = core()->getCurrentChannel()
->inventory_sources()
->where('status', 1)
->pluck('id');
->inventory_sources()
->where('status', 1)
->pluck('id');
foreach ($this->product->inventories as $inventory) {
if (is_numeric($index = $channelInventorySourceIds->search($inventory->inventory_source_id))) {
@ -387,8 +402,8 @@ abstract class AbstractType
}
$orderedInventory = $this->product->ordered_inventories()
->where('channel_id', core()->getCurrentChannel()->id)
->first();
->where('channel_id', core()->getCurrentChannel()->id)
->first();
if ($orderedInventory) {
$total -= $orderedInventory->qty;
@ -542,7 +557,7 @@ abstract class AbstractType
if ($customerGroupPrice != $this->product->price) {
$haveSpecialPrice = true;
$this->product->special_price = $customerGroupPrice;
}
}
}
return $haveSpecialPrice;
@ -573,8 +588,8 @@ abstract class AbstractType
$customerGroupPrices = $product->customer_group_prices()->where(function ($query) use ($customerGroupId) {
$query->where('customer_group_id', $customerGroupId)
->orWhereNull('customer_group_id');
}
->orWhereNull('customer_group_id');
}
)->get();
if (! $customerGroupPrices->count()) {

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
{
"/js/shop.js": "/js/shop.js?id=d64fdfe9e3fe3e4b9ee4",
"/css/shop.css": "/css/shop.css?id=72d87fe2508fdcd9f397"
"/css/shop.css": "/css/shop.css?id=8ef1aa65dda3180b66ee"
}

View File

@ -28,7 +28,7 @@ return [
'sort' => 4,
], [
'key' => 'account.compare',
'name' => 'velocity::app.customer.compare.text',
'name' => 'shop::app.customer.compare.text',
'route' =>'velocity.customer.product.compare',
'sort' => 5,
], [

View File

@ -4286,10 +4286,6 @@ section.review {
}
}
.checkbox {
margin: 10px 0px 5px 5px;
}
.radio {
margin: 10px 0px 5px 5px;
@ -4304,11 +4300,22 @@ section.review {
}
}
.radio-container .checkmark {
top: 2px;
left: 4px;
}
.mt-5 {
margin-top: 5px;
margin-right: 28px;
}
@media only screen and (max-width: 770px) {
.checkout-process .col-main {
padding-left: 0px;
}
}
//checkout process page end here
//customer page start here

View File

@ -176,6 +176,11 @@ return [
'dashboard' => 'الملف الشخصي',
'menu' => 'القائمة',
'general' => [
'no' => 'لا',
'yes' => 'نعم',
],
'profile' => [
'index' => [
'page-title' => 'الملف الشخصي',

View File

@ -176,6 +176,11 @@ return [
'dashboard' => 'Profil bearbeiten',
'menu' => 'Menu',
'general' => [
'no' => 'Nein',
'yes' => 'Ja',
],
'profile' => [
'index' => [
'page-title' => 'Profil',

View File

@ -176,6 +176,11 @@ return [
'dashboard' => 'Edit Profile',
'menu' => 'Menu',
'general' => [
'no' => 'No',
'yes' => 'Yes',
],
'profile' => [
'index' => [
'page-title' => 'Profile',

View File

@ -173,6 +173,11 @@ return [
'dashboard' => 'Cliente - Editar perfil',
'menu' => 'Menu',
'general' => [
'no' => 'No',
'yes' => 'si',
],
'profile' => [
'index' => [
'page-title' => 'Cliente - Perfil',

View File

@ -176,6 +176,11 @@ return [
'dashboard' => 'مشتری - ویرایش نمایه',
'menu' => 'فهرست',
'general' => [
'no' => 'نه',
'yes' => 'آره',
],
'profile' => [
'index' => [
'page-title' => 'مشتری - پروفایل',

View File

@ -174,6 +174,11 @@ return [
'dashboard' => 'Modifica Profilo',
'menu' => 'Menu',
'general' => [
'no' => 'No',
'yes' => 'sì',
],
'profile' => [
'index' => [
'page-title' => 'Profilo',

View File

@ -173,6 +173,11 @@ return [
'dashboard' => 'プロフィールを編集',
'menu' => 'メニュー',
'general' => [
'no' => '番号',
'yes' => 'はい',
],
'profile' => [
'index' => [
'page-title' => 'プロフィール',

View File

@ -181,6 +181,11 @@ return [
'dashboard' => 'Edit Profile',
'menu' => 'Menu',
'general' => [
'no' => 'Nee',
'yes' => 'Ja',
],
'profile' => [
'index' => [
'page-title' => 'Profiel',

View File

@ -174,6 +174,11 @@ return [
'dashboard' => 'Edytuj profil',
'menu' => 'Menu',
'general' => [
'no' => 'Nie',
'yes' => 'tak',
],
'profile' => [
'index' => [
'page-title' => 'Profil',

View File

@ -174,6 +174,11 @@ return [
'dashboard' => 'Cliente - Perfil',
'menu' => 'Menu',
'general' => [
'no' => 'Não',
'yes' => 'sim',
],
'profile' => [
'index' => [
'page-title' => 'Cliente - Perfil',

View File

@ -173,6 +173,11 @@ return [
'dashboard' => 'Profil Düzenle',
'menu' => 'Menü',
'general' => [
'no' => 'Hayır',
'yes' => 'Evet',
],
'profile' => [
'index' => [
'page-title' => 'Profil',

View File

@ -16,13 +16,11 @@
<div class="line-one">
<label class="radio-container">
<input v-validate="'required'" type="radio" id="{{ $payment['method'] }}" name="payment[method]" value="{{ $payment['method'] }}" v-model="payment.method" @change="methodSelected()" data-vv-as="&quot;{{ __('shop::app.checkout.onepage.payment-method') }}&quot;">
<span class="checkmark"></span>
<span class="payment-method method-label">
<b>{{ $payment['method_title'] }}</b>
</span>
</label>
<span class="payment-method method-label">
<b>{{ $payment['method_title'] }}</b>
</span>
</div>
<div class="line-two mt-5">

View File

@ -3,7 +3,7 @@
@include('shop::guest.compare.compare-products')
@section('page_title')
{{ __('velocity::app.customer.compare.compare_similar_items') }}
{{ __('shop::app.customer.compare.compare_similar_items') }}
@endsection
@section('content-wrapper')

View File

@ -21,7 +21,7 @@
@if ($order->canCancel())
<a href="{{ route('customer.orders.cancel', $order->id) }}" class="btn btn-lg btn-primary" v-alert:message="'{{ __('shop::app.customer.account.order.view.cancel-confirm-msg') }}'">
<a href="{{ route('customer.orders.cancel', $order->id) }}" class="btn btn-lg btn-primary" v-alert:message="'{{ __('shop::app.customer.account.order.view.cancel-confirm-msg') }}'" style="float: right;">
{{ __('shop::app.customer.account.order.view.cancel-btn-title') }}
</a>
@endif

View File

@ -1,13 +1,17 @@
@php
$attributeRepository = app('\Webkul\Attribute\Repositories\AttributeRepository');
$comparableAttributes = $attributeRepository->findByField('is_comparable', 1);
$attributeRepository = app('\Webkul\Attribute\Repositories\AttributeFamilyRepository');
$comparableAttributes = $attributeRepository->getComparableAttributesBelongsToFamily();
$locale = request()->get('locale') ?: app()->getLocale();
$attributeOptionTranslations = DB::table('attribute_option_translations')->where('locale', $locale)->get()->toJson();
@endphp
@push('scripts')
<script type="text/x-template" id="compare-product-template">
<section class="comparison-component">
<h1>
{{ __('velocity::app.customer.compare.compare_similar_items') }}
{{ __('shop::app.customer.compare.compare_similar_items') }}
</h1>
<button
@ -25,13 +29,13 @@
$comparableAttributes = $comparableAttributes->toArray();
array_splice($comparableAttributes, 1, 0, [[
'admin_name' => 'Product Image',
'type' => 'product_image'
'code' => 'product_image',
'admin_name' => __('velocity::app.customer.compare.product_image'),
]]);
array_splice($comparableAttributes, 2, 0, [[
'admin_name' => 'Actions',
'type' => 'action'
'code' => 'addToCartHtml',
'admin_name' => __('velocity::app.customer.compare.actions'),
]]);
@endphp
@ -42,74 +46,90 @@
</td>
<td :key="`title-${index}`" v-for="(product, index) in products">
@switch ($attribute['type'])
@case('text')
@switch ($attribute['code'])
@case('name')
<a :href="`${baseUrl}/${product.url_key}`" class="unset remove-decoration active-hover">
<h3 class="fw6 fs18" v-text="product['{{ $attribute['code'] }}']"></h3>
</a>
@break;
@case('textarea')
<span v-html="product.product['{{ $attribute['code'] }}']"></span>
@break;
@case('price')
<span v-html="product.product['{{ $attribute['code'] }}']"></span>
@break;
@case('boolean')
<span
v-text="product.product['{{ $attribute['code'] }}']
? '{{ __('velocity::app.shop.general.yes') }}'
: '{{ __('velocity::app.shop.general.no') }}'"
></span>
@break;
@case('select')
<span v-html="product.product['{{ $attribute['code'] }}']" class="fs16"></span>
@break;
@case('multiselect')
<span v-html="product.product['{{ $attribute['code'] }}']" class="fs16"></span>
@break
@case('file')
<a v-if="product.product['{{ $attribute['code'] }}']" :href="`${baseUrl}/storage/${product.product['{{ $attribute['code'] }}']}`">
<span v-text="product.product['{{ $attribute['code'] }}'].substr(product.product['{{ $attribute['code'] }}'].lastIndexOf('/') + 1)" class="fs16"></span>
<i class='icon sort-down-icon download'></i>
</a>
<span v-else class="fs16">__</span>
@break;
@case('image')
<img v-if="product.product['{{ $attribute['code'] }}']" :src="`${baseUrl}/storage/${product.product['{{ $attribute['code'] }}']}`">
@break;
@case('product_image')
<a :href="`${baseUrl}/${product.url_key}`" class="unset">
<img
class="image-wrapper"
:src="product['product_image']"
:src="product['{{ $attribute['code'] }}']"
:onerror="`this.src='${baseUrl}/vendor/webkul/ui/assets/images/product/large-product-placeholder.png'`" />
</a>
@break
@break
@case('action')
@case('price')
<span v-html="product['priceHTML']"></span>
@break
@case('addToCartHtml')
<div class="action">
<div v-html="product.defaultAddToCart"></div>
<span class="icon white-cross-sm-icon remove-product" @click="removeProductCompare(product.id)"></span>
</div>
@break;
@break
@endswitch
@case('color')
<span v-html="product.color_label" class="fs16"></span>
@break
@case('size')
<span v-html="product.size_label" class="fs16"></span>
@break
@case('description')
<span v-html="product.description"></span>
@break
@default
@switch ($attribute['type'])
@case('boolean')
<span
v-text="product.product['{{ $attribute['code'] }}']
? '{{ __('velocity::app.shop.general.yes') }}'
: '{{ __('velocity::app.shop.general.no') }}'"
></span>
@break;
@case('checkbox')
<span v-if="product.product['{{ $attribute['code'] }}']" v-html="getAttributeOptions(product['{{ $attribute['code'] }}'] ? product : product.product['{{ $attribute['code'] }}'] ? product.product : null, '{{ $attribute['code'] }}', 'multiple')" class="fs16"></span>
<span v-else class="fs16">__</span>
@break;
@case('select')
<span v-if="product.product['{{ $attribute['code'] }}']" v-html="getAttributeOptions(product['{{ $attribute['code'] }}'] ? product : product.product['{{ $attribute['code'] }}'] ? product.product : null, '{{ $attribute['code'] }}', 'single')" class="fs16"></span>
<span v-else class="fs16">__</span>
@break;
@case ('file')
@case ('image')
<a :href="`${baseUrl}/${product.url_key}`" class="unset">
<img
class="image-wrapper"
:src="'storage/' + product.product['{{ $attribute['code'] }}']"
:onerror="`this.src='${baseUrl}/vendor/webkul/ui/assets/images/product/large-product-placeholder.png'`" />
</a>
@break;
@default
<span v-html="product['{{ $attribute['code'] }}'] ? product['{{ $attribute['code'] }}'] : product.product['{{ $attribute['code'] }}'] ? product.product['{{ $attribute['code'] }}'] : '__'" class="fs16"></span>
@break;
@endswitch
@break
@endswitch
</td>
</tr>
@endforeach
</template>
<span v-else-if="isProductListLoaded && products.length == 0">
{{ __('velocity::app.customer.compare.empty-text') }}
{{ __('shop::app.customer.compare.empty-text') }}
</span>
</table>
@ -126,6 +146,7 @@
'products': [],
'isProductListLoaded': false,
'baseUrl': "{{ url()->to('/') }}",
'attributeOptions': JSON.parse(@json($attributeOptionTranslations)),
'isCustomer': '{{ auth()->guard('customer')->user() ? "true" : "false" }}' == "true",
}
},
@ -167,7 +188,7 @@
})
.catch(error => {
this.isProductListLoaded = true;
console.log("{{ __('velocity::app.error.something_went_wrong') }}");
console.log("{{ __('shop::app.common.error') }}");
});
} else {
this.isProductListLoaded = true;
@ -190,7 +211,7 @@
this.$root.addFlashMessages();
})
.catch(error => {
console.log("{{ __('velocity::app.error.something_went_wrong') }}");
console.log("{{ __('shop::app.common.error') }}");
});
} else {
let existingItems = this.getStorageValue('compared_product');
@ -198,17 +219,19 @@
if (productId == "all") {
updatedItems = [];
this.$set(this, 'products', []);
window.flashMessages = [{'type': 'alert-success', 'message': '{{ __('velocity::app.customer.compare.removed-all') }}' }];
window.flashMessages = [{'type': 'alert-success', 'message': '{{ __('shop::app.customer.compare.removed-all') }}' }];
} else {
updatedItems = existingItems.filter(item => item != productId);
this.$set(this, 'products', this.products.filter(product => product.id != productId));
window.flashMessages = [{'type': 'alert-success', 'message': '{{ __('velocity::app.customer.compare.removed') }}' }];
window.flashMessages = [{'type': 'alert-success', 'message': '{{ __('shop::app.customer.compare.removed') }}' }];
}
this.setStorageValue('compared_product', updatedItems);
this.$root.addFlashMessages();
}
this.updateCompareCount();
},
'getDynamicHTML': function (input) {
@ -255,6 +278,64 @@
return true;
},
'getAttributeOptions': function (productDetails, attributeValues, type) {
var attributeOptions = '__';
if (productDetails && attributeValues) {
var attributeItems;
if (type == "multiple") {
attributeItems = productDetails[attributeValues].split(',');
} else if (type == "single") {
attributeItems = productDetails[attributeValues];
}
attributeOptions = this.attributeOptions.filter(option => {
if (type == "multiple") {
if (attributeItems.indexOf(option.attribute_option_id.toString()) > -1) {
return true;
}
} else if (type == "single") {
if (attributeItems == option.attribute_option_id.toString()) {
return true;
}
}
return false;
});
attributeOptions = attributeOptions.map(option => {
return option.label;
});
attributeOptions = attributeOptions.join(', ');
}
return attributeOptions;
},
'updateCompareCount': function () {
if (this.isCustomer == "true" || this.isCustomer == true) {
this.$http.get(`${this.baseUrl}/items-count`)
.then(response => {
$('#compare-items-count').html(response.data.compareProductsCount);
})
.catch(exception => {
window.flashMessages = [{
'type': `alert-error`,
'message': "{{ __('shop::app.common.error') }}"
}];
this.$root.addFlashMessages();
});
} else {
let comparedItems = JSON.parse(localStorage.getItem('compared_product'));
comparedItemsCount = comparedItems ? comparedItems.length : 0;
$('#compare-items-count').html(comparedItemsCount);
}
}
}
});
</script>

View File

@ -3,7 +3,7 @@
@include('shop::guest.compare.compare-products')
@section('page_title')
{{ __('velocity::app.customer.compare.compare_similar_items') }}
{{ __('shop::app.customer.compare.compare_similar_items') }}
@endsection
@section('content-wrapper')

View File

@ -55,7 +55,7 @@
{!! view_render_event('bagisto.shop.layout.header.comppare-item.before') !!}
@php
$showCompare = core()->getConfigData('general.content.shop.compare_option') == "1" ? true : false
$showCompare = core()->getConfigData('general.content.shop.compare_option') == "1" ? true : false
@endphp
@if ($showCompare)
@ -70,8 +70,8 @@
@endguest
style="color: #242424;"
>
<span class="name">{{ __('velocity::app.customer.compare.text') }}</span>
<span class="name">{{ __('shop::app.customer.compare.text') }}</span>
(<span id="compare-items-count"></span>)
</a>
</li>
@endif
@ -202,7 +202,7 @@
<button style="background: none; border: none; padding: 0px;">
<i class="icon icon-search"></i>
</button>
<image-search-component></image-search-component>
<input type="search" name="term" class="search">
@ -297,7 +297,7 @@
localStorage.searched_image_url = self.uploaded_image_url;
queryString = localStorage.searched_terms = analysedResult.join('_');
self.$root.hideLoader();
window.location.href = "{{ route('shop.search.index') }}" + '?term=' + queryString + '&image-search=1';
@ -336,6 +336,23 @@
toggleDropdown(e);
});
@auth('customer')
@php
$compareCount = app('Webkul\Velocity\Repositories\VelocityCustomerCompareProductRepository')
->count([
'customer_id' => auth()->guard('customer')->user()->id,
]);
@endphp
let comparedItems = JSON.parse(localStorage.getItem('compared_product'));
$('#compare-items-count').html({{ $compareCount }});
@endauth
@guest('customer')
let comparedItems = JSON.parse(localStorage.getItem('compared_product'));
$('#compare-items-count').html(comparedItems ? comparedItems.length : 0);
@endguest
function toggleDropdown(e) {
var currentElement = $(e.currentTarget);

View File

@ -18,7 +18,7 @@
template: '#compare-component-template',
data: function () {
data: function () {
return {
'baseUrl': "{{ url()->to('/') }}",
'customer': '{{ auth()->guard('customer')->user() ? "true" : "false" }}' == "true",
@ -37,12 +37,12 @@
'type': `alert-${response.data.status}`,
'message': response.data.message
}];
this.$root.addFlashMessages()
}).catch(error => {
window.flashMessages = [{
'type': `alert-danger`,
'message': "{{ __('velocity::app.error.something_went_wrong') }}"
'message': "{{ __('shop::app.common.error') }}"
}];
this.$root.addFlashMessages()
@ -59,14 +59,14 @@
window.flashMessages = [{
'type': `alert-success`,
'message': "{{ __('velocity::app.customer.compare.added') }}"
'message': "{{ __('shop::app.customer.compare.added') }}"
}];
this.$root.addFlashMessages()
} else {
window.flashMessages = [{
'type': `alert-success`,
'message': "{{ __('velocity::app.customer.compare.already_added') }}"
'message': "{{ __('shop::app.customer.compare.already_added') }}"
}];
this.$root.addFlashMessages()
@ -76,12 +76,14 @@
window.flashMessages = [{
'type': `alert-success`,
'message': "{{ __('velocity::app.customer.compare.added') }}"
'message': "{{ __('shop::app.customer.compare.added') }}"
}];
this.$root.addFlashMessages()
}
}
this.updateCompareCount();
},
'getStorageValue': function (key) {
@ -99,6 +101,28 @@
return true;
},
'updateCompareCount': function () {
if (this.customer == "true" || this.customer == true) {
this.$http.get(`${this.baseUrl}/items-count`)
.then(response => {
$('#compare-items-count').html(response.data.compareProductsCount);
})
.catch(exception => {
window.flashMessages = [{
'type': `alert-error`,
'message': "{{ __('shop::app.common.error') }}"
}];
this.$root.addFlashMessages();
});
} else {
let comparedItems = JSON.parse(localStorage.getItem('compared_product'));
comparedItemsCount = comparedItems ? comparedItems.length : 0;
$('#compare-items-count').html(comparedItemsCount);
}
}
}
});
</script>

View File

@ -66,6 +66,32 @@ class Themes
return $this->themes;
}
/**
* Return list of registered themes
*
* @return array
*/
public function getChannelThemes()
{
$themes = config('themes.themes', []);
$channelThemes = [];
foreach ($themes as $code => $data) {
$channelThemes[] = new Theme(
$code,
isset($data['name']) ? $data['name'] : '',
isset($data['assets_path']) ? $data['assets_path'] : '',
isset($data['views_path']) ? $data['views_path'] : ''
);
if (isset($data['parent']) && $data['parent']) {
$parentThemes[$code] = $data['parent'];
}
}
return $channelThemes;
}
/**
* Check if specified exists
*

View File

@ -93,7 +93,6 @@ class Controller extends BaseController
* @param \Webkul\Category\Repositories\CategoryRepository $categoryRepository
* @param \Webkul\Velocity\Repositories\Product\ProductRepository $velocityProductRepository
* @param \Webkul\Velocity\Repositories\VelocityCustomerCompareProductRepository $compareProductsRepository
* @param \Webkul\Velocity\Repositories\VelocityCustomerCompareProductRepository $compareProductsRepository
*
* @return void
*/

View File

@ -1,197 +1,199 @@
<?php
return [
'admin' => [
'admin' => [
'system' => [
'velocity' => [
'general' => 'General',
'category' => 'Category',
'settings' => 'Settings',
'extension_name' => 'Velocity Theme',
'error-module-inactive' => 'Warning: Velocity theme status is inactive',
'general' => '一般的な',
'category' => 'カテゴリー',
'settings' => '設定',
'extension_name' => '速度のテーマ',
'error-module-inactive' => '警告Velocityテーマのステータスは非アクティブです',
],
'settings' => [
'channels'=> [
'subscription_bar' => 'Subscription bar content'
'channels' => [
'subscription_bar' => 'サブスクリプションバーのコンテンツ'
],
],
'general' => [
'status' => 'Status',
'active' => 'Active',
'inactive' => 'Inactive',
'general' => [
'status' => '状態',
'active' => 'アクティブ',
'inactive' => '非活性',
],
'category' => [
'all' => 'All',
'left' => 'Left',
'right' => 'Right',
'active' => 'Active',
'custom' => 'Custom',
'inactive' => 'Inactive',
'image-alignment' => 'Image Alignment',
'icon-status' => 'Category Icon Status',
'image-status' => 'Category Image Status',
'sub-category-show' => 'Show Sub Category',
'image-height' => 'Image\'s Height [in Pixel]',
'image-width' => 'Image\'s Width [in Pixel]',
'show-tooltip' => 'Show Category\'s Tooltip',
'num-sub-category' => 'Number Of Sub Category',
'all' => 'すべて',
'left' => '左',
'right' => '正しい',
'active' => 'アクティブ',
'custom' => 'カスタム',
'inactive' => '非活性',
'image-alignment' => '画像の配置',
'icon-status' => 'カテゴリアイコンステータス',
'image-status' => 'カテゴリー画像ステータス',
'sub-category-show' => 'サブカテゴリーを表示',
'image-height' => '画像の高さ(ピクセル単位)',
'image-width' => '画像の幅[ピクセル単位]',
'show-tooltip' => 'カテゴリのツールチップを表示',
'num-sub-category' => 'サブカテゴリーの数',
]
],
'layouts' => [
'velocity' => 'Velocity',
'cms-pages' => 'CMS Pages',
'meta-data' => 'Meta Data',
'category-menu' => 'Category Menu',
'header-content' => 'Header Content',
'velocity' => '速度',
'cms-pages' => 'CMSページ',
'meta-data' => 'メタデータ',
'category-menu' => 'カテゴリーメニュー',
'header-content' => 'ヘッダーコンテンツ',
],
'contents' => [
'self' => 'Self',
'active' => 'Active',
'new-tab' => 'New Tab',
'inactive' => 'Inactive',
'title' => 'Content List',
'select' => '-- Select --',
'add-title' => 'Add Content',
'btn-add-content' => 'Add Content',
'save-btn-title' => 'Save Content',
'autocomplete' => '[Autocomplete]',
'no-result-found' => 'No record found.',
'search-hint' => 'Search product here...',
'mass-delete-success' => 'Selected content deleted successfully.',
'tab' => [
'page' => 'Page Setting',
'content' => 'Content Setting',
'meta_content' => 'Meta Data',
'self' => '自己',
'active' => 'アクティブ',
'new-tab' => '新しいタブ',
'inactive' => '非活性',
'title' => 'コンテンツリスト',
'select' => '- 選択する -',
'add-title' => 'コンテンツを追加',
'btn-add-content' => 'コンテンツを追加',
'save-btn-title' => 'コンテンツを保存',
'autocomplete' => '[オートコンプリート]',
'no-result-found' => 'レコードが見つかりません。',
'search-hint' => 'ここで製品を検索...',
'mass-delete-success' => '選択したコンテンツは正常に削除されました。',
'tab' => [
'page' => 'ページ設定',
'content' => 'コンテンツ設定',
'meta_content' => 'メタデータ',
],
'page' => [
'title' => 'Title',
'status' => 'Status',
'position' => 'Position',
'page' => [
'title' => '題名',
'status' => '状態',
'position' => 'ポジション',
],
'content' => [
'content-type' => 'Content Type',
'custom-title' => 'Custom Title',
'category-slug' => 'Category Slug',
'link-target' => 'Page Link Target',
'custom-product' => 'Store Products',
'custom-heading' => 'Custom Heading',
'catalog-type' => 'Product Catalog Type',
'static-description' => 'Content Description',
'page-link' => 'Page Link [e.g. http://example.com/../../]',
'content' => [
'content-type' => 'コンテンツタイプ',
'custom-title' => 'カスタムタイトル',
'category-slug' => 'カテゴリースラッグ',
'link-target' => 'ページリンクターゲット',
'custom-product' => 'ストア製品',
'custom-heading' => 'カスタム見出し',
'catalog-type' => '製品カタログのタイプ',
'static-description' => 'コンテンツの説明',
'page-link' => 'ページリンク[例: http://example.com/../../]',
],
'datagrid' => [
'id' => 'Id',
'title' => 'Title',
'status' => 'Status',
'position' => 'Position',
'content-type' => 'Content Type',
'datagrid' => [
'id' => 'Id',
'title' => '題名',
'status' => '状態',
'position' => 'ポジション',
'content-type' => 'コンテンツタイプ',
]
],
'meta-data' => [
'footer' => 'Footer',
'title' => 'Velocity meta data',
'activate-slider' => 'Activate Slider',
'home-page-content' => 'Home Page Content',
'footer-left-content' => 'Footer Left Content',
'subscription-content' => 'Subscription bar Content',
'sidebar-categories' => 'Sidebar Categories',
'header_content_count' => 'Header Content Count',
'footer-left-raw-content' => '<p>We love to craft softwares and solve the real world problems with the binaries. We are highly committed to our goals. We invest our resources to create world class easy to use softwares and applications for the enterprise business with the top notch, on the edge technology expertise.</p>',
'slider-path' => 'Slider Path',
'category-logo' => 'Category logo',
'product-policy' => 'Product Policy',
'update-meta-data' => 'Update Meta Data',
'product-view-image' => 'Product View Image',
'advertisement-two' => 'Advertisement Two Images',
'advertisement-one' => 'Advertisement One Images',
'footer-middle-content' => 'Footer Middle Content',
'advertisement-four' => 'Advertisement Four Images',
'advertisement-three' => 'Advertisement Three Images',
'images' => 'Images',
'general' => 'General',
'add-image-btn-title' => 'Add Image'
'footer' => 'フッター',
'title' => '速度メタデータ',
'activate-slider' => 'スライダーをアクティブにする',
'home-page-content' => 'ホームページコンテンツ',
'footer-left-content' => 'フッター左コンテンツ',
'subscription-content' => 'サブスクリプションバーのコンテンツ',
'sidebar-categories' => 'サイドバーのカテゴリ',
'header_content_count' => 'ヘッダーコンテンツ数',
'footer-left-raw-content' => '<p>私たちはソフトウェアを作成し、バイナリで現実世界の問題を解決するのが大好きです。私達は私達の目標に非常にコミットしています。私たちはリソースを投資して、最先端のテクノロジーの専門知識を活用し、一流のエンタープライズビジネス向けの使いやすいソフトウェアとアプリケーションを作成します。</p>',
'slider-path' => 'スライダーパス',
'category-logo' => 'カテゴリーロゴ',
'product-policy' => '製品ポリシー',
'update-meta-data' => 'メタデータを更新',
'product-view-image' => '製品ビュー画像',
'advertisement-two' => '広告2つの画像',
'advertisement-one' => '広告1つの画像',
'footer-middle-content' => 'フッターミドルコンテンツ',
'advertisement-four' => '広告4つの画像',
'advertisement-three' => '広告3つの画像',
'images' => '画像',
'general' => '一般的な',
'add-image-btn-title' => '画像を追加'
],
'category' => [
'save-btn-title' => 'Save Menu',
'title' => 'Category Menu List',
'add-title' => 'Add Menu Content',
'edit-title' => 'Edit Menu Content',
'btn-add-category' => 'Add Category Content',
'datagrid' => [
'category-id' => 'Category Id',
'category-name' => 'Category Name',
'category-icon' => 'Category Icon',
'category-status' => 'Status',
'save-btn-title' => '保存メニュー',
'title' => 'カテゴリーメニュー一覧',
'add-title' => 'メニューコンテンツを追加',
'edit-title' => 'メニューコンテンツの編集',
'btn-add-category' => 'カテゴリコンテンツを追加',
'datagrid' => [
'category-id' => 'カテゴリID',
'category-name' => '種別名',
'category-icon' => 'カテゴリーアイコン',
'category-status' => '状態',
],
'tab' => [
'general' => 'General',
'tab' => [
'general' => '一般的な',
],
'status' => 'Status',
'active' => 'Active',
'inactive' => 'Inactive',
'select' => '-- Select --',
'icon-class' => 'Icon Class',
'select-category' => 'Choose Category',
'tooltip-content' => 'Tooltip Content',
'mass-delete-success' => 'Selected categories menu deleted successfully.',
'status' => '状態',
'active' => 'アクティブ',
'inactive' => '非活性',
'select' => '- 選択する -',
'icon-class' => 'アイコンクラス',
'select-category' => 'カテゴリーを選択',
'tooltip-content' => 'ツールチップの内容',
'mass-delete-success' => '選択したカテゴリメニューを削除しました。',
],
'general' => [
'locale_logo' => 'Locale Logo',
'locale_logo' => 'ロケールロゴ',
],
],
'home' => [
'view-all' => 'View All',
'add-to-cart' => 'Add To Cart',
'hot-categories' => 'Hot Categories',
'payment-methods' => 'Payment Methods',
'customer-reviews' => 'Customer Reviews',
'shipping-methods' => 'Shipping Methods',
'popular-categories' => 'Popular Categories',
'home' => [
'view-all' => 'すべてを見る',
'add-to-cart' => 'カートに追加',
'hot-categories' => '人気のカテゴリ',
'payment-methods' => 'お支払い方法',
'customer-reviews' => 'カスタマーレビュー',
'shipping-methods' => '輸送方法',
'popular-categories' => '人気のカテゴリー',
],
'header' => [
'cart' => 'Cart',
'cart' => 'Cart',
'guest' => 'Guest',
'logout' => 'Logout',
'title' => 'Account',
'account' => 'Account',
'profile' => 'Profile',
'wishlist' => 'Wishlist',
'all-categories' => 'All Categories',
'search-text' => 'Search products here',
'welcome-message' => 'Welcome, :customer_name',
'dropdown-text' => 'Manage Cart, Orders & Wishlist',
'header' => [
'cart' => 'カート',
'guest' => 'ゲスト',
'logout' => 'ログアウト',
'title' => 'アカウント',
'account' => 'アカウント',
'profile' => 'プロフィール',
'wishlist' => 'ウィッシュリスト',
'all-categories' => 'すべてのカテゴリ',
'search-text' => 'ここで製品を検索',
'welcome-message' => 'ようこそ、:customer_name',
'dropdown-text' => 'カート、注文、ウィッシュリストを管理する',
],
'menu-navbar' => [
'text-more' => 'More',
'text-category' => 'Shop by Category',
'menu-navbar' => [
'text-more' => 'もっと',
'text-category' => 'カテゴリーで選ぶ',
],
'minicart' => [
'cart' => 'Cart',
'view-cart' => 'View Cart',
'minicart' => [
'cart' => 'カート',
'view-cart' => 'カート',
],
'checkout' => [
'qty' => 'Qty',
'checkout' => 'Checkout',
'checkout' => [
'qty' => '数量',
'checkout' => 'チェックアウト',
'cart' => [
'view-cart' => 'View Cart',
'cart-summary' => 'Cart Summary',
'view-cart' => 'かごの中身を見る',
'cart-summary' => 'カートの概要',
],
'qty' => 'Qty',
'items' => 'Items',
'subtotal' => 'Subtotal',
'sub-total' => 'Sub Total',
'proceed' => 'Proceed to checkout',
'qty' => '数量',
'items' => 'アイテム',
'subtotal' => '小計',
'sub-total' => '小計',
'proceed' => 'チェックアウトに進む',
],
'customer' => [
'customer' => [
'compare' => [
'text' => '比較する',
'compare_similar_items' => '類似アイテムを比較する',
@ -200,85 +202,84 @@ return [
'already_added' => 'アイテムは比較リストに既に追加されています',
'removed' => '比較リストからアイテムを削除しました',
'empty-text' => "比較リストにアイテムがありません",
'product_image' => 'Product Image',
'actions' => 'Actions',
'product_image' => '商品画像',
'actions' => '行動',
],
'login-form' => [
'sign-up' => 'Sign up',
'new-customer' => 'New Customer',
'customer-login' => 'Customer Login',
'registered-user' => 'Registered User',
'your-email-address' => 'Your email address',
'form-login-text' => 'If you have an account, sign in with your email address.',
'sign-up' => 'サインアップ',
'new-customer' => '新規のお客様',
'customer-login' => 'お客様ログイン',
'registered-user' => '登録ユーザー',
'your-email-address' => 'メールアドレス',
'form-login-text' => 'アカウントをお持ちの場合は、メールアドレスでログインしてください。',
],
'signup-form' => [
'login' => 'Login',
'become-user' => 'Become User',
'user-registration' => 'User Registration',
'form-sginup-text' => 'If you are new to our store, we glad to have you as member.',
'login' => 'ログインする',
'become-user' => 'ユーザーになる',
'user-registration' => 'ユーザー登録',
'form-sginup-text' => 'あなたが私たちの店に初めている場合は、メンバーとして喜んでいます。',
],
'forget-password' => [
'login' => 'Login',
'forgot-password' => 'Forgot Password',
'recover-password' => 'Recover Password',
'recover-password-text' => 'If you forgot your password, recover it by entering your email address.',
'login' => 'ログインする',
'forgot-password' => 'パスワードをお忘れですか',
'recover-password' => 'パスワードを回復',
'recover-password-text' => 'パスワードを忘れた場合は、メールアドレスを入力してパスワードを回復してください。',
]
],
'error' => [
'go-to-home' => 'Go to home',
'page-lost-short' => 'Page lost content',
'something_went_wrong' => 'something went wrong',
'page-lost-description' => "The page you're looking for isn't available. Try to search again or use the Go Back button below.",
'error' => [
'go-to-home' => '家に帰る',
'page-lost-short' => 'ページが失われたコンテンツ',
'something_went_wrong' => '問題が発生しました',
'page-lost-description' => "お探しのページはご利用いただけません。もう一度検索するか、下の[戻る]ボタンを使用してください。",
],
'products' => [
'text' => 'Products',
'details' => 'Details',
'reviews-title' => 'Reviews',
'reviewed' => 'Reviewed',
'review-by' => 'Review by',
'quick-view' => 'Quick View',
'not-available' => 'Not Available',
'submit-review' => 'Submit Review',
'ratings' => ':totalRatings Ratings',
'reviews-count' => ':totalReviews Reviews',
'customer-rating' => 'Customer Rating',
'more-infomation' => 'More Information',
'view-all-reviews' => 'View All Reviews',
'write-your-review' => 'Write Your Review',
'short-description' => 'Short Descriptions',
'recently-viewed' => 'Recently Viewed Products',
'be-first-review' => 'Be the first to write a review',
'products' => [
'text' => '製品',
'details' => '細部',
'reviews-title' => 'レビュー',
'reviewed' => '審査',
'review-by' => 'によるレビュー',
'quick-view' => 'クイックビュー',
'not-available' => '利用不可',
'submit-review' => 'レビュー送信',
'ratings' => ':totalRatings 評価',
'reviews-count' => ':totalReviews レビュー',
'customer-rating' => 'お客様の評価',
'more-infomation' => '詳しくは',
'view-all-reviews' => 'すべてのレビューを表示',
'write-your-review' => 'レビューを書く',
'short-description' => '短い説明',
'recently-viewed' => '最近見た製品',
'be-first-review' => '最初のレビューを書く',
],
'shop' => [
'shop' => [
'gender' => [
'male' => 'Male',
'other' => 'Other',
'female' => 'Female',
'male' => '男性',
'other' => 'その他の',
'female' => '女性',
],
'general' => [
'no' => 'No',
'yes' => 'Yes',
'view' => 'View',
'filter' => 'Filter',
'orders' => 'Orders',
'update' => 'Update',
'reviews' => 'Reviews',
'currencies' => 'Currencies',
'addresses' => 'Addresses',
'top-brands' => 'Top Brands',
'new-password' => 'New password',
'downloadables' => 'Downloadable Products',
'confirm-new-password' => 'Confirm new password',
'enter-current-password' => 'Enter your current password',
'no' => '番号',
'yes' => 'はい',
'view' => '見る',
'filter' => 'フィルタ',
'orders' => '注文',
'update' => '更新',
'reviews' => 'レビュー',
'currencies' => '通貨',
'addresses' => '住所',
'top-brands' => 'トップブランド',
'new-password' => '新しいパスワード',
'downloadables' => 'ダウンロード可能な製品',
'confirm-new-password' => '新しいパスワードを確認',
'enter-current-password' => '現在のパスワードを入力してください',
'alert' => [
'info' => 'Info',
'error' => 'Error',
'success' => 'Success',
'warning' => 'Warning',
'info' => '情報',
'error' => 'エラー',
'success' => '成功',
'warning' => '警告',
],
],
'wishlist' => [
@ -287,13 +288,11 @@ return [
]
],
'responsive' => [
'responsive' => [
'header' => [
'done' => 'Done',
'languages' => 'Languages',
'greeting' => 'Welcome, :customer !',
'done' => 'できた',
'languages' => '言語',
'greeting' => 'ようこそ、:customer !',
]
],
]
?>
];

View File

@ -1,6 +1,10 @@
@php
$attributeRepository = app('\Webkul\Attribute\Repositories\AttributeRepository');
$comparableAttributes = $attributeRepository->findByField('is_comparable', 1);
$attributeRepository = app('\Webkul\Attribute\Repositories\AttributeFamilyRepository');
$comparableAttributes = $attributeRepository->getComparableAttributesBelongsToFamily();
$locale = request()->get('locale') ?: app()->getLocale();
$attributeOptionTranslations = DB::table('attribute_option_translations')->where('locale', $locale)->get()->toJson();
@endphp
@push('css')
@ -39,13 +43,13 @@
$comparableAttributes = $comparableAttributes->toArray();
array_splice($comparableAttributes, 1, 0, [[
'admin_name' => 'Product Image',
'type' => 'product_image'
'code' => 'product_image',
'admin_name' => __('velocity::app.customer.compare.product_image'),
]]);
array_splice($comparableAttributes, 2, 0, [[
'admin_name' => 'Actions',
'type' => 'action'
'code' => 'addToCartHtml',
'admin_name' => __('velocity::app.customer.compare.actions'),
]]);
@endphp
@ -56,67 +60,91 @@
</td>
<td :key="`title-${index}`" v-for="(product, index) in products">
@switch ($attribute['type'])
@case('text')
<a :href="`${baseUrl}/${product.url_key}`" class="unset remove-decoration active-hover">
<h3 class="fw6 fs18" v-text="product['{{ $attribute['code'] }}']"></h3>
@switch ($attribute['code'])
@case('name')
<a :href="`${$root.baseUrl}/${product.url_key}`" class="unset remove-decoration active-hover">
<h1 class="fw6 fs18" v-text="product['{{ $attribute['code'] }}']"></h1>
</a>
@break;
@case('textarea')
<span v-html="product.product['{{ $attribute['code'] }}']"></span>
@break;
@case('price')
<span v-html="product.product['{{ $attribute['code'] }}']"></span>
@break;
@case('boolean')
<span
v-text="product.product['{{ $attribute['code'] }}']
? '{{ __('velocity::app.shop.general.yes') }}'
: '{{ __('velocity::app.shop.general.no') }}'"
></span>
@break;
@case('select')
<span v-html="product.product['{{ $attribute['code'] }}']" class="fs16"></span>
@break;
@case('multiselect')
<span v-html="product.product['{{ $attribute['code'] }}']" class="fs16"></span>
@break
@case('file')
<a v-if="product.product['{{ $attribute['code'] }}']" :href="`${baseUrl}/storage/${product.product['{{ $attribute['code'] }}']}`">
<span v-text="product.product['{{ $attribute['code'] }}'].substr(product.product['{{ $attribute['code'] }}'].lastIndexOf('/') + 1)" class="fs16"></span>
<i class='icon sort-down-icon download'></i>
</a>
<span v-else class="fs16">__</span>
@break;
@case('image')
<img v-if="product.product['{{ $attribute['code'] }}']" :src="`${baseUrl}/storage/${product.product['{{ $attribute['code'] }}']}`">
@break;
@case('product_image')
<a :href="`${baseUrl}/${product.url_key}`" class="unset">
<a :href="`${$root.baseUrl}/${product.url_key}`" class="unset">
<img
class="image-wrapper"
:src="product['product_image']"
:onerror="`this.src='${baseUrl}/vendor/webkul/ui/assets/images/product/large-product-placeholder.png'`" />
:src="product['{{ $attribute['code'] }}']"
onload="window.updateHeight ? window.updateHeight() : ''"
:onerror="`this.src='${$root.baseUrl}/vendor/webkul/ui/assets/images/product/large-product-placeholder.png'`" />
</a>
@break
@break
@case('action')
@case('price')
<span v-html="product['priceHTML']"></span>
@break
@case('addToCartHtml')
<div class="action">
<div v-html="product.defaultAddToCart"></div>
<vnode-injector :nodes="getDynamicHTML(product.addToCartHtml)"></vnode-injector>
<span class="icon white-cross-sm-icon remove-product" @click="removeProductCompare(product.id)"></span>
<i
class="material-icons cross fs16"
@click="removeProductCompare(product.id)">
close
</i>
</div>
@break;
@break
@endswitch
@case('color')
<span v-html="product.color_label" class="fs16"></span>
@break
@case('size')
<span v-html="product.size_label" class="fs16"></span>
@break
@case('description')
<span v-html="product.description"></span>
@break
@default
@switch ($attribute['type'])
@case('boolean')
<span
v-text="product.product['{{ $attribute['code'] }}']
? '{{ __('velocity::app.shop.general.yes') }}'
: '{{ __('velocity::app.shop.general.no') }}'"
></span>
@break;
@case('checkbox')
<span v-if="product.product['{{ $attribute['code'] }}']" v-html="getAttributeOptions(product['{{ $attribute['code'] }}'] ? product : product.product['{{ $attribute['code'] }}'] ? product.product : null, '{{ $attribute['code'] }}', 'multiple')" class="fs16"></span>
<span v-else class="fs16">__</span>
@break;
@case('select')
<span v-if="product.product['{{ $attribute['code'] }}']" v-html="getAttributeOptions(product['{{ $attribute['code'] }}'] ? product : product.product['{{ $attribute['code'] }}'] ? product.product : null, '{{ $attribute['code'] }}', 'single')" class="fs16"></span>
<span v-else class="fs16">__</span>
@break;
@case ('file')
@case ('image')
<a :href="`${$root.baseUrl}/${product.url_key}`" class="unset">
<img
class="image-wrapper"
onload="window.updateHeight ? window.updateHeight() : ''"
:src="'storage/' + product.product['{{ $attribute['code'] }}']"
:onerror="`this.src='${$root.baseUrl}/vendor/webkul/ui/assets/images/product/large-product-placeholder.png'`" />
</a>
@break;
@default
<span v-html="product['{{ $attribute['code'] }}'] ? product['{{ $attribute['code'] }}'] : product.product['{{ $attribute['code'] }}'] ? product.product['{{ $attribute['code'] }}'] : '__'" class="fs16"></span>
@break;
@endswitch
@break
@endswitch
</td>
</tr>
@endforeach
@ -139,6 +167,7 @@
return {
'products': [],
'isProductListLoaded': false,
'attributeOptions': JSON.parse(@json($attributeOptionTranslations)),
'isCustomer': '{{ auth()->guard('customer')->user() ? "true" : "false" }}' == "true",
}
},
@ -231,6 +260,42 @@
this.$root.headerItemsCount++;
},
'getAttributeOptions': function (productDetails, attributeValues, type) {
var attributeOptions = '__';
if (productDetails && attributeValues) {
var attributeItems;
if (type == "multiple") {
attributeItems = productDetails[attributeValues].split(',');
} else if (type == "single") {
attributeItems = productDetails[attributeValues];
}
attributeOptions = this.attributeOptions.filter(option => {
if (type == "multiple") {
if (attributeItems.indexOf(option.attribute_option_id.toString()) > -1) {
return true;
}
} else if (type == "single") {
if (attributeItems == option.attribute_option_id.toString()) {
return true;
}
}
return false;
});
attributeOptions = attributeOptions.map(option => {
return option.label;
});
attributeOptions = attributeOptions.join(', ');
}
return attributeOptions;
}
}
});
</script>

View File

@ -1,5 +1,6 @@
<?php
use Codeception\Actor;
use Webkul\User\Models\Admin;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
@ -22,7 +23,7 @@ use Illuminate\Routing\RouteCollection;
*
* @SuppressWarnings(PHPMD)
*/
class FunctionalTester extends \Codeception\Actor
class FunctionalTester extends Actor
{
use _generated\FunctionalTesterActions;
@ -47,7 +48,7 @@ class FunctionalTester extends \Codeception\Actor
}
if (! $admin) {
throw new \Exception(
throw new Exception(
'Admin user not found in database. Please ensure Seeders are executed');
}
@ -85,12 +86,17 @@ class FunctionalTester extends \Codeception\Actor
/**
* @param string $name
* @param array $params
* @param bool $routeCheck set this to false if the action is doing a redirection
*/
public function amOnAdminRoute(string $name, array $params = [])
public function amOnAdminRoute(string $name, array $params = [], bool $routeCheck = true)
{
$I = $this;
$I->amOnRoute($name, $params);
$I->seeCurrentRouteIs($name);
if ($routeCheck) {
$I->seeCurrentRouteIs($name);
}
/** @var RouteCollection $routes */
$routes = Route::getRoutes();

View File

@ -0,0 +1,134 @@
<?php
namespace Tests\Functional\Product;
use FunctionalTester;
use Webkul\Product\Models\Product;
use Webkul\Product\Models\ProductFlat;
use Webkul\Core\Helpers\Laravel5Helper;
use Webkul\Product\Models\ProductInventory;
use Webkul\Product\Models\ProductAttributeValue;
class ProductCopyCest
{
public function _before(FunctionalTester $I)
{
$I->loginAsAdmin();
}
public function testSkipAttributes(FunctionalTester $I)
{
config(['products.skipAttributesOnCopy' => ['name', 'inventories']]);
$original = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, [
'productInventory' => [
'qty' => 10,
],
'attributeValues' => [
'name' => 'Original',
],
]);
$I->amOnAdminRoute('admin.catalog.products.copy', ['id' => $original->id], false);
// test attribute is skipped:
$attr = $I->dontSeeRecord(ProductAttributeValue::class, [
'attribute_id' => 2, // name
'product_id' => $original->id + 1,
]);
// test relation is skipped:
$I->dontSeeRecord(ProductInventory::class, [
'product_id' => $original->id + 1,
'qty' => 10,
]);
}
public function testBlockProductCopy(FunctionalTester $I)
{
$original = $I->haveProduct(Laravel5Helper::BOOKING_EVENT_PRODUCT, []);
$I->amOnAdminRoute('admin.catalog.products.copy', ['id' => $original->id], false);
$I->seeInSource('Products of type booking can not be copied');
}
public function testProductCopy(FunctionalTester $I)
{
// set this config value to true to make it testable. It defaults to false.
config(['products.linkProductsOnCopy' => true]);
$originalName = $I->fake()->name;
$original = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, [
'productInventory' => [
'qty' => 10,
],
'attributeValues' => [
'name' => $originalName,
],
]);
$count = count(Product::all());
$I->amOnAdminRoute('admin.catalog.products.copy', ['id' => $original->id], false);
$copiedProduct = $I->grabRecord(Product::class, [
'id' => $original->id + 1,
'parent_id' => $original->parent_id,
'attribute_family_id' => $original->attribute_family_id,
]);
$attr = $I->grabRecord(ProductAttributeValue::class, [
'attribute_id' => 2,
'product_id' => $copiedProduct->id,
]);
$I->assertStringStartsWith('Copy of ' . $originalName, $attr->text_value);
// url_key
$attr = $I->grabRecord(ProductAttributeValue::class, [
'attribute_id' => 3,
'product_id' => $copiedProduct->id,
]);
$I->assertStringStartsWith('copy-of-' . $original->url_key, $attr->text_value);
// sku
$I->seeRecord(ProductAttributeValue::class, [
'attribute_id' => 1,
'product_id' => $copiedProduct->id,
]);
// sku
$I->dontSeeRecord(ProductAttributeValue::class, [
'attribute_id' => 1,
'product_id' => $copiedProduct->id,
'text_value' => $original->sku,
]);
// status
$I->seeRecord(ProductAttributeValue::class, [
'attribute_id' => 8,
'boolean_value' => 0,
]);
$I->seeRecord(ProductInventory::class, [
'product_id' => $copiedProduct->id,
'qty' => 10,
]);
$I->seeRecord('product_relations', [
'parent_id' => $original->id,
'child_id' => $copiedProduct->id,
]);
$flat = $I->grabRecord(ProductFlat::class, [
'product_id' => $copiedProduct->id,
]);
$I->assertStringStartsWith('Copy of ' . $originalName, $flat->name);
$I->assertCount($count + 1, Product::all());
$I->seeResponseCodeIsSuccessful();
}
}