Merge pull request #2006 from jitendra-webkul/1.0

Catalog rule and cart rule implemented for all product types
This commit is contained in:
Jitendra Singh 2020-01-06 17:52:30 +05:30 committed by GitHub
commit d438d1b4ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
187 changed files with 7104 additions and 8096 deletions

View File

@ -2,6 +2,70 @@
#### This changelog consists the bug & security fixes and new features being included in the releases listed below. #### This changelog consists the bug & security fixes and new features being included in the releases listed below.
## **v0.1.9 (20th of December, 2019)** - *Release*
* #1875 [fixed] - Discount gets applied on cart if condition is "Visible Individually" is set to yes and product added in cart is not visible individually.
* #1868 [fixed] - Getting exception on checkout if action Buy X Get Y free is selected in cart rule.
* #1838 [fixed] - Cart rule not working for condition sku(children only) and sku(parent only).
* #1835 [fixed] - Getting exception if condition value remains blank.
* #1831 [fixed] - Negative sign is not required in discount amount.
* #1830 [fixed] - If Coupon type is selected as "No Coupon" then coupon code should not generate for that particular cart rule.
* #1904 [fixed] - Existing tax should display as list on creating catalog rule if in condition "Tax Category" is selected.
* #1903 [fixed] - UI issue in condition field
* #1895 [fixed] - Translation issue Action Type field while creating catalog rule.
* #1883 [fixed] - Free shipping not applied on cart, if from action "Free Shipping" is selected and discount amount is given as zero.
* #1882 [fixed] - If from Action "Apply on shipping " is selected as yes then the discounted amount is applying on both product and shipping charge.
* #1861 [fixed] - Getting exception on checkout if applying invalid coupon code and proceed for checkout.
* #1857 [fixed] - Getting exception when creating cart rule with condition URL KEY(children only).
* #1856 [fixed] - Getting exception on creating cart rule with condition Name(children only).
* #1847 [fixed] - If cart rule condition does not match the cart then on applying coupon it should display message "Coupon cannot be applied".
* #1839 [fixed] - Getting exception on filtering cart rule through id.
* #1836 [fixed] - Back button on Edit cart rule page doesn't work.
* #1834 [fixed] - On editing any cart rule, its priority updates to 1.
* #1833 [fixed] - Discount not applied if "Payment Method", "Shipping Method", "Shipping State" or "Shipping Country" is selected in condition.
* #1832 [fixed] - Able to use coupon irrespective of the number given in "Uses per coupon" field.
* #1828 [fixed] - Updated changes are not saved on editing cart rule.
* #1825 [fixed] - Not able to generate coupon code while creating cart rule.
* #1823 [fixed] - Getting exception after clicking on developement.
* #1929 [fixed] - Actual product amount and discounted amount both should display on product page for configurable product
* #1928 [fixed] - Catalog rule should not apply if any of the condition doesn't match, if condition type"All Condition are true"
* #1931 [fixed] - Catalog rule not applying if in condition type"Any Condition is true" is selected, and some of the condition doesn't match the product.
* #1935 [fixed] - Categories are not displaying if in condition Categories(children only) or Categories(Parent Only) is selected.
* #1938 [fixed] - multiple cart rules are getting applied by using only single coupon.
* #1939 [fixed] - Invalid coupon gets applied, if clicking on apply coupon multiple time, although there is no discount amount but its displaying message that coupon code applied successfully.
* #1942 [fixed] - When editing the cart rule, selected category is not checked.
## **v0.1.8 (4th of October, 2019)** - *Release* ## **v0.1.8 (4th of October, 2019)** - *Release*
* [feature] - Refund is added for orders. * [feature] - Refund is added for orders.

17
CHANGELOG for v0.2.x.md Normal file
View File

@ -0,0 +1,17 @@
# CHANGELOG for v0.2.x
#### This changelog consists the bug & security fixes and new features being included in the releases listed below.
## **v0.2.0 (23th of December, 2019)** - *Release*
* #1955 [fixed] - Message need to be changed on mouse hover on cross symbol next to applied coupon.
* #1959 [fixed] - if admin has set the same condition twice, then catalog rule is not getting apply
* #1958 [fixed] - getting exception on front end, if action type is Buy x get y free in non couponable cart rule.
* #1957 [fixed] - if action type is Fixed Amount to Whole Cart, then apply to shipping option should get hide.
* #1954 [fixed] - If any different tax category has been assigned to variants in configurable product, then while using tax category condition in cart rule, rule is not working properly.
* #1950 [fixed] - multiple catalog rule should not get get applied, if 1st one has been created as End Other Rules = yes

View File

@ -69,33 +69,35 @@
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [
"database/seeds", "database/seeds",
"database/factories" "database/factories"
], ],
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/",
"Webkul\\User\\": "packages/Webkul/User/src", "Webkul\\User\\": "packages/Webkul/User/src",
"Webkul\\Admin\\": "packages/Webkul/Admin/src", "Webkul\\Admin\\": "packages/Webkul/Admin/src",
"Webkul\\Ui\\": "packages/Webkul/Ui/src", "Webkul\\Ui\\": "packages/Webkul/Ui/src",
"Webkul\\Category\\": "packages/Webkul/Category/src", "Webkul\\Category\\": "packages/Webkul/Category/src",
"Webkul\\Checkout\\": "packages/Webkul/Checkout/src", "Webkul\\Checkout\\": "packages/Webkul/Checkout/src",
"Webkul\\Attribute\\": "packages/Webkul/Attribute/src", "Webkul\\Attribute\\": "packages/Webkul/Attribute/src",
"Webkul\\Shop\\": "packages/Webkul/Shop/src", "Webkul\\Shop\\": "packages/Webkul/Shop/src",
"Webkul\\Core\\": "packages/Webkul/Core/src", "Webkul\\Core\\": "packages/Webkul/Core/src",
"Webkul\\Customer\\": "packages/Webkul/Customer/src", "Webkul\\Customer\\": "packages/Webkul/Customer/src",
"Webkul\\Inventory\\": "packages/Webkul/Inventory/src", "Webkul\\Inventory\\": "packages/Webkul/Inventory/src",
"Webkul\\Product\\": "packages/Webkul/Product/src", "Webkul\\Product\\": "packages/Webkul/Product/src",
"Webkul\\Theme\\": "packages/Webkul/Theme/src", "Webkul\\Theme\\": "packages/Webkul/Theme/src",
"Webkul\\Shipping\\": "packages/Webkul/Shipping/src", "Webkul\\Shipping\\": "packages/Webkul/Shipping/src",
"Webkul\\Payment\\": "packages/Webkul/Payment/src", "Webkul\\Payment\\": "packages/Webkul/Payment/src",
"Webkul\\Paypal\\": "packages/Webkul/Paypal/src", "Webkul\\Paypal\\": "packages/Webkul/Paypal/src",
"Webkul\\Sales\\": "packages/Webkul/Sales/src", "Webkul\\Sales\\": "packages/Webkul/Sales/src",
"Webkul\\Tax\\": "packages/Webkul/Tax/src", "Webkul\\Tax\\": "packages/Webkul/Tax/src",
"Webkul\\API\\": "packages/Webkul/API", "Webkul\\API\\": "packages/Webkul/API",
"Webkul\\Discount\\": "packages/Webkul/Discount/src", "Webkul\\CatalogRule\\": "packages/Webkul/CatalogRule/src",
"Webkul\\CMS\\": "packages/Webkul/CMS/src" "Webkul\\CartRule\\": "packages/Webkul/CartRule/src",
} "Webkul\\Rule\\": "packages/Webkul/Rule/src",
"Webkul\\CMS\\": "packages/Webkul/CMS/src"
}
}, },
"autoload-dev": { "autoload-dev": {

View File

@ -245,7 +245,9 @@ return [
Webkul\Sales\Providers\SalesServiceProvider::class, Webkul\Sales\Providers\SalesServiceProvider::class,
Webkul\Tax\Providers\TaxServiceProvider::class, Webkul\Tax\Providers\TaxServiceProvider::class,
Webkul\API\Providers\APIServiceProvider::class, Webkul\API\Providers\APIServiceProvider::class,
Webkul\Discount\Providers\DiscountServiceProvider::class, Webkul\CatalogRule\Providers\CatalogRuleServiceProvider::class,
Webkul\CartRule\Providers\CartRuleServiceProvider::class,
Webkul\Rule\Providers\RuleServiceProvider::class,
Webkul\CMS\Providers\CMSServiceProvider::class Webkul\CMS\Providers\CMSServiceProvider::class
], ],

View File

@ -19,7 +19,8 @@ return [
\Webkul\Sales\Providers\ModuleServiceProvider::class, \Webkul\Sales\Providers\ModuleServiceProvider::class,
\Webkul\Tax\Providers\ModuleServiceProvider::class, \Webkul\Tax\Providers\ModuleServiceProvider::class,
\Webkul\User\Providers\ModuleServiceProvider::class, \Webkul\User\Providers\ModuleServiceProvider::class,
\Webkul\Discount\Providers\ModuleServiceProvider::class, \Webkul\CatalogRule\Providers\ModuleServiceProvider::class,
\Webkul\CartRule\Providers\ModuleServiceProvider::class,
\Webkul\CMS\Providers\ModuleServiceProvider::class \Webkul\CMS\Providers\ModuleServiceProvider::class
] ]
]; ];

View File

@ -394,27 +394,27 @@ return [
], [ ], [
'key' => 'promotions', 'key' => 'promotions',
'name' => 'admin::app.acl.promotions', 'name' => 'admin::app.acl.promotions',
'route' => 'admin.cart-rule.index', 'route' => 'admin.cart-rules.index',
'sort' => 7 'sort' => 7
], [ ], [
'key' => 'promotions.cart-rule', 'key' => 'promotions.cart-rules',
'name' => 'admin::app.acl.cart-rules', 'name' => 'admin::app.acl.cart-rules',
'route' => 'admin.cart-rule.index', 'route' => 'admin.cart-rules.index',
'sort' => 1 'sort' => 1
], [ ], [
'key' => 'promotions.cart-rule.create', 'key' => 'promotions.cart-rules.create',
'name' => 'admin::app.acl.create', 'name' => 'admin::app.acl.create',
'route' => 'admin.cart-rule.create', 'route' => 'admin.cart-rules.create',
'sort' => 1 'sort' => 1
], [ ], [
'key' => 'promotions.cart-rule.edit', 'key' => 'promotions.cart-rules.edit',
'name' => 'admin::app.acl.edit', 'name' => 'admin::app.acl.edit',
'route' => 'admin.cart-rule.edit', 'route' => 'admin.cart-rules.edit',
'sort' => 2 'sort' => 2
], [ ], [
'key' => 'promotions.cart-rule.delete', 'key' => 'promotions.cart-rules.delete',
'name' => 'admin::app.acl.delete', 'name' => 'admin::app.acl.delete',
'route' => 'admin.cart-rule.delete', 'route' => 'admin.cart-rules.delete',
'sort' => 3 'sort' => 3
], ],
]; ];

View File

@ -189,22 +189,22 @@ return [
'icon-class' => '' 'icon-class' => ''
],[ ],[
'key' => 'promotions', 'key' => 'promotions',
'name' => 'admin::app.layouts.promotion', 'name' => 'admin::app.layouts.promotions',
'route' => 'admin.cart-rule.index', 'route' => 'admin.catalog-rules.index',
'sort' => 5, 'sort' => 5,
'icon-class' => 'promotion-icon' 'icon-class' => 'promotion-icon'
], [ ], [
'key' => 'promotions.cart-rule', 'key' => 'promotions.catalog-rules',
'name' => 'admin::app.promotion.cart-rule', 'name' => 'admin::app.promotions.catalog-rules.title',
'route' => 'admin.cart-rule.index', 'route' => 'admin.catalog-rules.index',
'sort' => 1, 'sort' => 1,
'icon-class' => '' 'icon-class' => ''
], [ ], [
'key' => 'promotions.catalog-rule', 'key' => 'promotions.cart-rules',
'name' => 'admin::app.promotion.catalog-rule', 'name' => 'admin::app.promotions.cart-rules.title',
'route' => 'admin.catalog-rule.index', 'route' => 'admin.cart-rules.index',
'sort' => 1, 'sort' => 2,
'icon-class' => '', 'icon-class' => ''
], [ ], [
'key' => 'cms', 'key' => 'cms',
'name' => 'admin::app.layouts.cms', 'name' => 'admin::app.layouts.cms',

View File

@ -0,0 +1,86 @@
<?php
namespace Webkul\Admin\DataGrids;
use Webkul\Ui\DataGrid\DataGrid;
use DB;
/**
* CartRuleCouponDataGrid class
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleCouponDataGrid extends DataGrid
{
protected $index = 'id';
protected $sortOrder = 'desc';
public function prepareQueryBuilder()
{
$queryBuilder = DB::table('cart_rule_coupons')
->addSelect('id', 'code', 'created_at', 'expired_at', 'times_used')
->where('cart_rule_coupons.cart_rule_id', request('id'));
$this->setQueryBuilder($queryBuilder);
}
public function addColumns()
{
$this->addColumn([
'index' => 'id',
'label' => trans('admin::app.datagrid.id'),
'type' => 'number',
'searchable' => false,
'sortable' => true,
'filterable' => true
]);
$this->addColumn([
'index' => 'code',
'label' => trans('admin::app.datagrid.coupon-code'),
'type' => 'string',
'searchable' => true,
'sortable' => true,
'filterable' => true
]);
$this->addColumn([
'index' => 'created_at',
'label' => trans('admin::app.datagrid.created-date'),
'type' => 'datetime',
'sortable' => true,
'searchable' => false,
'filterable' => true
]);
$this->addColumn([
'index' => 'expired_at',
'label' => trans('admin::app.datagrid.expiration-date'),
'type' => 'datetime',
'sortable' => true,
'searchable' => false,
'filterable' => true
]);
$this->addColumn([
'index' => 'times_used',
'label' => trans('admin::app.datagrid.times-used'),
'type' => 'number',
'searchable' => true,
'sortable' => true,
'filterable' => true
]);
}
public function prepareMassActions()
{
$this->addMassAction([
'type' => 'delete',
'action' => route('admin.cart-rule-coupons.mass-delete'),
'label' => trans('admin::app.datagrid.delete'),
'method' => 'DELETE'
]);
}
}

View File

@ -8,20 +8,27 @@ use DB;
/** /**
* Cart Rule DataGrid class * Cart Rule DataGrid class
* *
* @author Prashant Singh <prashant.singh852@webkul.com> @prashant-webkul * @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/ */
class CartRuleDataGrid extends DataGrid class CartRuleDataGrid extends DataGrid
{ {
protected $index = 'id'; //the column that needs to be treated as index column protected $index = 'id';
protected $sortOrder = 'desc'; //asc or desc protected $sortOrder = 'desc';
public function prepareQueryBuilder() public function prepareQueryBuilder()
{ {
$queryBuilder = DB::table('cart_rules') $queryBuilder = DB::table('cart_rules')
->select('id') ->leftJoin('cart_rule_coupons', function($leftJoin) {
->addSelect('id', 'name', 'status', 'end_other_rules', 'action_type', 'disc_amount', 'use_coupon'); $leftJoin->on('cart_rule_coupons.cart_rule_id', '=', 'cart_rules.id')
->where('cart_rule_coupons.is_primary', 1);
})
->addSelect('cart_rules.id', 'name', 'cart_rule_coupons.code as coupon_code', 'status', 'starts_from', 'ends_till', 'sort_order');
$this->addFilter('id', 'cart_rules.id');
$this->addFilter('coupon_code', 'cart_rule_coupons.code');
$this->setQueryBuilder($queryBuilder); $this->setQueryBuilder($queryBuilder);
} }
@ -30,7 +37,7 @@ class CartRuleDataGrid extends DataGrid
{ {
$this->addColumn([ $this->addColumn([
'index' => 'id', 'index' => 'id',
'label' => trans('admin::app.id'), 'label' => trans('admin::app.datagrid.id'),
'type' => 'number', 'type' => 'number',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
@ -39,13 +46,40 @@ class CartRuleDataGrid extends DataGrid
$this->addColumn([ $this->addColumn([
'index' => 'name', 'index' => 'name',
'label' => trans('admin::app.name'), 'label' => trans('admin::app.datagrid.name'),
'type' => 'string', 'type' => 'string',
'searchable' => true, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'filterable' => true 'filterable' => true
]); ]);
$this->addColumn([
'index' => 'coupon_code',
'label' => trans('admin::app.datagrid.coupon-code'),
'type' => 'string',
'searchable' => true,
'sortable' => true,
'filterable' => true
]);
$this->addColumn([
'index' => 'starts_from',
'label' => trans('admin::app.datagrid.start'),
'type' => 'datetime',
'sortable' => true,
'searchable' => false,
'filterable' => true
]);
$this->addColumn([
'index' => 'ends_till',
'label' => trans('admin::app.datagrid.end'),
'type' => 'datetime',
'sortable' => true,
'searchable' => false,
'filterable' => true
]);
$this->addColumn([ $this->addColumn([
'index' => 'status', 'index' => 'status',
'label' => trans('admin::app.status'), 'label' => trans('admin::app.status'),
@ -55,89 +89,36 @@ class CartRuleDataGrid extends DataGrid
'filterable' => true, 'filterable' => true,
'wrapper' => function($value) { 'wrapper' => function($value) {
if ($value->status == 1) if ($value->status == 1)
return 'Active'; return trans('admin::app.datagrid.active');
else else
return 'In Active'; return trans('admin::app.datagrid.inactive');
} }
]); ]);
$this->addColumn([ $this->addColumn([
'index' => 'end_other_rules', 'index' => 'sort_order',
'label' => 'End Other Rules', 'label' => trans('admin::app.datagrid.priority'),
'type' => 'boolean',
'searchable' => false,
'sortable' => true,
'filterable' => true,
'wrapper' => function($value) {
if ($value->end_other_rules == 1)
return 'True';
else
return 'False';
}
]);
$this->addColumn([
'index' => 'action_type',
'label' => 'Action Type',
'type' => 'string',
'searchable' => true,
'sortable' => true,
'filterable' => true,
'wrapper' => function($value) {
return config('pricerules.cart.actions')[$value->action_type];
}
]);
$this->addColumn([
'index' => 'disc_amount',
'label' => 'Discount Amount',
'type' => 'number', 'type' => 'number',
'searchable' => false, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'filterable' => true 'filterable' => true
]); ]);
$this->addColumn([
'index' => 'use_coupon',
'label' => 'Use Coupon',
'type' => 'boolean',
'searchable' => true,
'sortable' => true,
'filterable' => true,
'wrapper' => function($value) {
if ($value->use_coupon == 1) {
return 'True';
} else {
return 'False';
}
}
]);
} }
public function prepareActions() public function prepareActions()
{ {
$this->addAction([ $this->addAction([
'title' => 'Edit CartRule', 'title' => 'Edit Cart Rule',
'method' => 'GET', //use post only for redirects only 'method' => 'GET',
'route' => 'admin.cart-rule.edit', 'route' => 'admin.cart-rules.edit',
'icon' => 'icon pencil-lg-icon' 'icon' => 'icon pencil-lg-icon'
]); ]);
$this->addAction([ $this->addAction([
'title' => 'Delete CartRule', 'title' => 'Delete Cart Rule',
'method' => 'POST', //use post only for requests other than redirects 'method' => 'POST',
'route' => 'admin.cart-rule.delete', 'route' => 'admin.cart-rules.delete',
'icon' => 'icon trash-icon' 'icon' => 'icon trash-icon'
]); ]);
} }
public function prepareMassActions()
{
// $this->addMassAction([
// 'type' => 'delete',
// 'action' => route('admin.catalog.attributes.massdelete'),
// 'label' => 'Delete',
// 'method' => 'DELETE'
// ]);
}
} }

View File

@ -8,20 +8,19 @@ use DB;
/** /**
* Catalog Rule DataGrid class * Catalog Rule DataGrid class
* *
* @author Prashant Singh <prashant.singh852@webkul.com> @prashant-webkul * @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/ */
class CatalogRuleDataGrid extends DataGrid class CatalogRuleDataGrid extends DataGrid
{ {
protected $index = 'id'; //the column that needs to be treated as index column protected $index = 'id';
protected $sortOrder = 'desc'; //asc or desc protected $sortOrder = 'desc';
public function prepareQueryBuilder() public function prepareQueryBuilder()
{ {
$queryBuilder = DB::table('catalog_rules') $queryBuilder = DB::table('catalog_rules')
->select('id') ->addSelect('catalog_rules.id', 'name', 'status', 'starts_from', 'ends_till', 'sort_order');
->addSelect('id', 'name', 'starts_from', 'ends_till', 'status', 'end_other_rules', 'action_code');
$this->setQueryBuilder($queryBuilder); $this->setQueryBuilder($queryBuilder);
} }
@ -48,56 +47,41 @@ class CatalogRuleDataGrid extends DataGrid
$this->addColumn([ $this->addColumn([
'index' => 'starts_from', 'index' => 'starts_from',
'label' => trans('admin::app.datagrid.starts-from'), 'label' => trans('admin::app.datagrid.start'),
'type' => 'datetime', 'type' => 'datetime',
'searchable' => false,
'sortable' => true, 'sortable' => true,
'searchable' => false,
'filterable' => true 'filterable' => true
]); ]);
$this->addColumn([ $this->addColumn([
'index' => 'ends_till', 'index' => 'ends_till',
'label' => trans('admin::app.datagrid.ends-till'), 'label' => trans('admin::app.datagrid.end'),
'type' => 'datetime', 'type' => 'datetime',
'searchable' => false,
'sortable' => true, 'sortable' => true,
'searchable' => false,
'filterable' => true 'filterable' => true
]); ]);
$this->addColumn([ $this->addColumn([
'index' => 'status', 'index' => 'status',
'label' => trans('admin::app.datagrid.status'), 'label' => trans('admin::app.status'),
'type' => 'boolean', 'type' => 'boolean',
'searchable' => false, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'filterable' => true, 'filterable' => true,
'wrapper' => function ($value) { 'wrapper' => function($value) {
if ($value->status == 1) if ($value->status == 1)
return 'True'; return trans('admin::app.datagrid.active');
else else
return 'False'; return trans('admin::app.datagrid.inactive');
} }
]); ]);
$this->addColumn([ $this->addColumn([
'index' => 'end_other_rules', 'index' => 'sort_order',
'label' => 'End Other Rules', 'label' => trans('admin::app.datagrid.priority'),
'type' => 'boolean', 'type' => 'number',
'searchable' => false,
'sortable' => true,
'filterable' => true,
'wrapper' => function ($value) {
if ($value->end_other_rules == 1)
return 'True';
else
return 'False';
}
]);
$this->addColumn([
'index' => 'action_code',
'label' => 'Action Type',
'type' => 'string',
'searchable' => true, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'filterable' => true 'filterable' => true
@ -107,27 +91,17 @@ class CatalogRuleDataGrid extends DataGrid
public function prepareActions() public function prepareActions()
{ {
$this->addAction([ $this->addAction([
'title' => 'Edit CatalogRule', 'title' => 'Edit Catalog Rule',
'method' => 'GET', //use post only for redirects only 'method' => 'GET',
'route' => 'admin.catalog-rule.edit', 'route' => 'admin.catalog-rules.edit',
'icon' => 'icon pencil-lg-icon' 'icon' => 'icon pencil-lg-icon'
]); ]);
$this->addAction([ $this->addAction([
'title' => 'Delete CatalogRule', 'title' => 'Delete Catalog Rule',
'method' => 'POST', //use post only for requests other than redirects 'method' => 'POST',
'route' => 'admin.catalog-rule.delete', 'route' => 'admin.catalog-rules.delete',
'icon' => 'icon trash-icon' 'icon' => 'icon trash-icon'
]); ]);
} }
public function prepareMassActions()
{
// $this->addMassAction([
// 'type' => 'delete',
// 'action' => route('admin.catalog.attributes.massdelete'),
// 'label' => 'Delete',
// 'method' => 'DELETE'
// ]);
}
} }

View File

@ -669,60 +669,56 @@ Route::group(['middleware' => ['web']], function () {
//DataGrid Export //DataGrid Export
Route::post('admin/export', 'Webkul\Admin\Http\Controllers\ExportController@export')->name('admin.datagrid.export'); Route::post('admin/export', 'Webkul\Admin\Http\Controllers\ExportController@export')->name('admin.datagrid.export');
Route::prefix('promotion')->group(function () { Route::prefix('promotions')->group(function () {
Route::get('/catalog-rules', 'Webkul\Discount\Http\Controllers\CatalogRuleController@index')->defaults('_config', [ Route::get('cart-rules', 'Webkul\CartRule\Http\Controllers\CartRuleController@index')->defaults('_config', [
'view' => 'admin::promotions.catalog-rule.index' 'view' => 'admin::promotions.cart-rules.index'
])->name('admin.catalog-rule.index'); ])->name('admin.cart-rules.index');
Route::get('/catalog-rules/create', 'Webkul\Discount\Http\Controllers\CatalogRuleController@create')->defaults('_config', [ Route::get('cart-rules/create', 'Webkul\CartRule\Http\Controllers\CartRuleController@create')->defaults('_config', [
'view' => 'admin::promotions.catalog-rule.create' 'view' => 'admin::promotions.cart-rules.create'
])->name('admin.catalog-rule.create'); ])->name('admin.cart-rules.create');
Route::post('/catalog-rules/create', 'Webkul\Discount\Http\Controllers\CatalogRuleController@store')->defaults('_config', [ Route::post('cart-rules/create', 'Webkul\CartRule\Http\Controllers\CartRuleController@store')->defaults('_config', [
'redirect' => 'admin.catalog-rule.index' 'redirect' => 'admin.cart-rules.index'
])->name('admin.catalog-rule.store'); ])->name('admin.cart-rules.store');
Route::get('/catalog-rules/edit/{id}', 'Webkul\Discount\Http\Controllers\CatalogRuleController@edit')->defaults('_config', [ Route::get('cart-rules/edit/{id}', 'Webkul\CartRule\Http\Controllers\CartRuleController@edit')->defaults('_config', [
'view' => 'admin::promotions.catalog-rule.edit' 'view' => 'admin::promotions.cart-rules.edit'
])->name('admin.catalog-rule.edit'); ])->name('admin.cart-rules.edit');
Route::post('/catalog-rules/edit/{id}', 'Webkul\Discount\Http\Controllers\CatalogRuleController@update')->defaults('_config', [ Route::post('cart-rules/edit/{id}', 'Webkul\CartRule\Http\Controllers\CartRuleController@update')->defaults('_config', [
'redirect' => 'admin.catalog-rule.index' 'redirect' => 'admin.cart-rules.index'
])->name('admin.catalog-rule.update'); ])->name('admin.cart-rules.update');
Route::get('/catalog-rules/apply', 'Webkul\Discount\Http\Controllers\CatalogRuleController@applyRules')->defaults('_config', [ Route::post('cart-rules/delete/{id}', 'Webkul\CartRule\Http\Controllers\CartRuleController@destroy')->name('admin.cart-rules.delete');
'view' => 'admin::promotions.catalog-rule.index'
])->name('admin.catalog-rule.apply');
Route::post('/catalog-rules/delete/{id}', 'Webkul\Discount\Http\Controllers\CatalogRuleController@destroy')->name('admin.catalog-rule.delete'); Route::post('cart-rules/generate-coupons/{id?}', 'Webkul\CartRule\Http\Controllers\CartRuleController@generateCoupons')->name('admin.cart-rules.generate-coupons');
Route::get('/catalog-rules/declutter', 'Webkul\Discount\Http\Controllers\CatalogRuleController@deClutter')->defaults('_config', [ Route::post('/massdelete', 'Webkul\CartRule\Http\Controllers\CartRuleCouponController@massDelete')->name('admin.cart-rule-coupons.mass-delete');
'redirect' => 'admin.catalog-rule.index'
])->name('admin.catalog-rule.declut');
Route::post('fetch/options', 'Webkul\Discount\Http\Controllers\CatalogRuleController@fetchAttributeOptions')->name('admin.catalog-rule.options');
Route::get('cart-rules', 'Webkul\Discount\Http\Controllers\CartRuleController@index')->defaults('_config', [ //Catalog rules
'view' => 'admin::promotions.cart-rule.index' Route::get('catalog-rules', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@index')->defaults('_config', [
])->name('admin.cart-rule.index'); 'view' => 'admin::promotions.catalog-rules.index'
])->name('admin.catalog-rules.index');
Route::get('cart-rules/create', 'Webkul\Discount\Http\Controllers\CartRuleController@create')->defaults('_config', [ Route::get('catalog-rules/create', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@create')->defaults('_config', [
'view' => 'admin::promotions.cart-rule.create' 'view' => 'admin::promotions.catalog-rules.create'
])->name('admin.cart-rule.create'); ])->name('admin.catalog-rules.create');
Route::post('cart-rules/store', 'Webkul\Discount\Http\Controllers\CartRuleController@store')->defaults('_config', [ Route::post('catalog-rules/create', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@store')->defaults('_config', [
'redirect' => 'admin.cart-rule.index' 'redirect' => 'admin.catalog-rules.index'
])->name('admin.cart-rule.store'); ])->name('admin.catalog-rules.store');
Route::get('cart-rules/edit/{id}', 'Webkul\Discount\Http\Controllers\CartRuleController@edit')->defaults('_config', [ Route::get('catalog-rules/edit/{id}', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@edit')->defaults('_config', [
'view' => 'admin::promotions.cart-rule.edit' 'view' => 'admin::promotions.catalog-rules.edit'
])->name('admin.cart-rule.edit'); ])->name('admin.catalog-rules.edit');
Route::post('cart-rules/update/{id}', 'Webkul\Discount\Http\Controllers\CartRuleController@update')->defaults('_config', [ Route::post('catalog-rules/edit/{id}', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@update')->defaults('_config', [
'redirect' => 'admin.cart-rule.index' 'redirect' => 'admin.catalog-rules.index'
])->name('admin.cart-rule.update'); ])->name('admin.catalog-rules.update');
Route::post('cart-rules/delete/{id}', 'Webkul\Discount\Http\Controllers\CartRuleController@destroy')->name('admin.cart-rule.delete'); Route::post('catalog-rules/delete/{id}', 'Webkul\CatalogRule\Http\Controllers\CatalogRuleController@destroy')->name('admin.catalog-rules.delete');
}); });
Route::prefix('cms')->group(function () { Route::prefix('cms')->group(function () {

View File

@ -48,4 +48,29 @@
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8;
cursor: pointer; cursor: pointer;
} }
}
.cart-rule-conditions,
.catalog-rule-conditions {
table {
td {
&.attribute {
width: 250px;
padding-left: 0;
}
&.operator,
&.value {
width: 260px;
}
&.actions {
width: 44px;
.icon {
margin-top: 6px;
}
}
}
}
} }

View File

@ -73,7 +73,7 @@ return [
'taxes' => 'Taxes', 'taxes' => 'Taxes',
'tax-categories' => 'Tax Categories', 'tax-categories' => 'Tax Categories',
'tax-rates' => 'Tax Rates', 'tax-rates' => 'Tax Rates',
'promotion' => 'Promotions', 'promotions' => 'Promotions',
'discount' => 'Discount', 'discount' => 'Discount',
'cms' => 'CMS' 'cms' => 'CMS'
], ],
@ -110,7 +110,8 @@ return [
'create' => 'Add', 'create' => 'Add',
'delete' => 'Delete', 'delete' => 'Delete',
'promotions' => 'Promotions', 'promotions' => 'Promotions',
'cart-rules' => 'Cart Rules' 'cart-rules' => 'Cart Rules',
'catalog-rules' => 'Catalog Rules',
], ],
'dashboard' => [ 'dashboard' => [
@ -203,7 +204,16 @@ return [
'for-guest' => 'For Guest', 'for-guest' => 'For Guest',
'order_number' => 'Order Number', 'order_number' => 'Order Number',
'refund-date' => 'Refund Date', 'refund-date' => 'Refund Date',
'refunded' => 'Refunded' 'refunded' => 'Refunded',
'start' => 'Start',
'end' => 'End',
'active' => 'Active',
'inactive' => 'Inactive',
'coupon-code' => 'Coupon Code',
'times-used' => 'Times Used',
'created-date' => 'Created Date',
'expiration-date' => 'Expiration Date',
'delete' => 'Delete'
], ],
'account' => [ 'account' => [
@ -944,125 +954,138 @@ return [
] ]
], ],
'promotion' => [ 'promotions' => [
'information' => 'Information', 'cart-rules' => [
'conditions' => 'Conditions', 'title' => 'Cart Rules',
'actions' => 'Actions', 'add-title' => 'Add Cart Rule',
'coupons' => 'Coupons', 'edit-title' => 'Edit Cart Rule',
'zero-unlimited' => 'Zero denotes unlimited usage here', 'save-btn-title' => 'Save Cart Rule',
'catalog-rule' => 'Catalog Rules', 'rule-information' => 'Rule Information',
'cart-rule' => 'Cart Rules',
'add-catalog-rule' => 'Add Rule',
'add-cart-rule' => 'Add Cart Rule',
'edit-cart-rule' => 'Edit Cart Rule',
'edit-catalog-rule' => 'Edit Catalog Rule',
'create-catalog-rule' => 'Create Catalog Rule',
'create-cart-rule' => 'Create Cart Rule',
'save-btn-title' => 'Create',
'edit-btn-title' => 'Save',
'save' => 'Save',
'select-cart-attr' => 'Select Cart Attribute',
'select-products' => 'How to choose products ?',
'select-category' => 'Select by Categories',
'select-attribute' => 'Select by Attributes',
'cannot-activate-catalog-rule' => 'Cannot activate catalog rule',
'catalog-rule-already-activated' => 'Catalog rule is already activated',
'catalog-rule-activated' => 'Catalog rule is activated',
'select-attribute' => 'Select :attribute',
'enter-attribute' => 'Enter :attribute',
'add-condition' => 'Add Condition',
'cart-properties' => 'Cart Properties',
'yes' => 'Yes',
'no' => 'No',
'note' => 'Note',
'apply' => 'Apply',
'rule-name' => 'Enter Rule Name',
'rule-desc' => 'Enter Rule Description',
'convert-x-note' => 'If this section is left empty, then rule will get applied to all the products in the cart.',
'declut' => 'Declutter',
'processing-done' => 'Rules processing done',
'declut-success' => 'Catalog rules decluttering successful',
'declut-failure' => 'Catalog rules decluttering failed',
'add-attr-condition' => 'Add Attribute Condition',
'general-info' => [
'sku-like' => 'SKU Like',
'on-shipping' => 'If applied on shipping',
'discount' => 'Discount',
'shipping-apply-info' => 'It signifies how rule behaves when it is used for shipping',
'general-info' => 'General Info',
'options' => 'Options',
'name' => 'Rule Name',
'description' => 'Description',
'starts-from' => 'Start',
'ends-till' => 'End',
'channels' => 'Channels',
'channels-req' => 'Channels',
'cust-groups' => 'Customer Groups',
'priority' => 'Priority',
'add-condition' => 'Add Conditions',
'disc_amt' => 'Discount Amount',
'disc_percent' => 'Discount Percentage',
'is-coupon' => 'Use Coupon',
'is-coupon-yes' => 'Yes',
'is-coupon-no' => 'No',
'uses-per-cust' => 'Uses per customer',
'all' => 'All',
'any' => 'Any',
'end-other-rules' => 'End other rules',
'status' => 'Is Active',
'all-conditions-true' => 'Assuming all conditions are true',
'assuming' => 'Assuming',
'any' => 'Any',
'all' => 'All',
'conditions' => 'Conditions',
'apply' => 'Apply',
'are' => 'are',
'true' => 'true',
'false' => 'false',
'limit' => 'Usage Limit',
'specific-coupon' => 'Specific Coupon(Check) / Auto Generated(Unheck)',
'free-shipping' => 'Free Shipping',
'is-guest' => 'For Guests',
'disc_qty' => 'Max. Quantity Allowed To Be Discounted',
'test-mode' => 'Choose how to test conditions',
'labels' => 'Labels',
'prefix' => 'Prefix',
'suffix' => 'Suffix',
'code' => 'Code',
'global-label' => 'Global Label',
'label' => 'Label'
],
'status' => [
'success' => 'Success! rule created',
'success-coupon' => 'Success! rule created along with coupon',
'failed' => 'Error! failed to create rule',
'update-success' => 'Success! rule updated',
'update-coupon-success' => 'Success! rule updated along with coupon',
'update-failed' => 'Error! cannot update',
'delete-success' => 'Success! rule deleted',
'delete-failed' => 'Error! cannot delete',
'coupon-applied' => 'Coupon used',
'coupon-failed' => 'Coupon failed to apply',
'no-coupon' => '* Coupon not applicable',
'coupon-removed' => 'Coupon removed successfully',
'coupon-remove-failed' => 'Coupon removal failed',
'duplicate-coupon' => 'Coupon already exists, please try again with a different coupon'
],
'catalog' => [
'name' => 'Name', 'name' => 'Name',
'description' => 'Description', 'description' => 'Description',
'apply-percent' => 'Apply as percentage', 'status' => 'Status',
'apply-fixed' => 'Apply as fixed amount', 'is-active' => 'Cart Rule is Active',
'adjust-to-percent' => 'Adjust to percentage', 'channels' => 'Channels',
'adjust-to-value' => 'Adjust to discount value', 'customer-groups' => 'Customer Groups',
'condition-missing' => 'Please check conditons, some values might be missing' 'coupon-type' => 'Coupon Type',
'no-coupon' => 'No Coupon',
'specific-coupon' => 'Specific Coupon',
'auto-generate-coupon' => 'Auto Generate Coupon',
'no' => 'No',
'yes' => 'Yes',
'coupon-code' => 'Coupon Code',
'uses-per-coupon' => 'Uses Per Coupon',
'uses-per-customer' => 'Uses Per Customer',
'uses-per-customer-control-info' => 'Will be used for logged in customers only.',
'from' => 'From',
'to' => 'To',
'priority' => 'Priority',
'conditions' => 'Conditions',
'condition-type' => 'Condition Type',
'all-conditions-true' => 'All Conditions are True',
'any-condition-true' => 'Any Condition is True',
'add-condition' => 'Add Condition',
'choose-condition-to-add' => 'Choose a condition to add',
'cart-attribute' => 'Cart Attribute',
'subtotal' => 'Subtotal',
'total-items-qty' => 'Total Items Qty',
'total-weight' => 'Total Weight',
'payment-method' => 'Payment Method',
'shipping-method' => 'Shipping Method',
'shipping-postcode' => 'Shipping Zip/Postcode',
'shipping-state' => 'Shipping State',
'shipping-country' => 'Shipping Country',
'cart-item-attribute' => 'Cart Item Attribute',
'price-in-cart' => 'Price in Cart',
'qty-in-cart' => 'Qty in Cart',
'product-attribute' => 'Product Attribute',
'attribute-name-children-only' => ':attribute_name (Children Only)',
'attribute-name-parent-only' => ':attribute_name (Parent Only)',
'is-equal-to' => 'Is equal to',
'is-not-equal-to' => 'Is not equal to',
'equals-or-greater-than' => 'Equals or greater than',
'equals-or-less-than' => 'Equals or less than',
'greater-than' => 'Greater than',
'less-than' => 'Less than',
'contain' => 'Contain',
'contains' => 'Contains',
'does-not-contain' => 'Does not contain',
'actions' => 'Actions',
'action-type' => 'Action Type',
'percentage-product-price' => 'Percentage of Product Price',
'fixed-amount' => 'Fixed Amount',
'fixed-amount-whole-cart' => 'Fixed Amount to Whole Cart',
'buy-x-get-y-free' => 'Buy X Get Y Free',
'discount-amount' => 'Discount Amount',
'discount-quantity' => 'Maximum Quantity Allowed to be Discounted',
'discount-step' => 'Buy X Quantity',
'free-shipping' => 'Free Shipping',
'apply-to-shipping' => 'Apply to Shipping',
'coupon-codes' => 'Coupon Codes',
'coupon-qty' => 'Coupon Qty',
'code-length' => 'Code Length',
'code-format' => 'Code Format',
'alphanumeric' => 'Alphanumeric',
'alphabetical' => 'Alphabetical',
'numeric' => 'Numeric',
'code-prefix' => 'Code Prefix',
'code-suffix' => 'Code Suffix',
'generate' => 'Generate',
'cart-rule-not-defind-error' => 'Cart rule is not defined',
'mass-delete-success' => 'All the selected index of coupons have been deleted successfully.',
'end-other-rules' => 'End Other Rules',
'children-categories' => 'Categories (Children Only)',
'parent-categories' => 'Categories (Parent Only)',
'categories' => 'Categories',
'attribute_family' => 'Attribute Family'
], ],
'cart' => [ 'catalog-rules' => [
'buy-atleast' => 'Buy Atleast', 'title' => 'Catalog Rules',
'apply-to-shipping' => 'Apply to shipping' 'add-title' => 'Add Catalog Rule',
'edit-title' => 'Edit Catalog Rule',
'save-btn-title' => 'Save Catalog Rule',
'rule-information' => 'Rule Information',
'name' => 'Name',
'description' => 'Description',
'status' => 'Status',
'is-active' => 'Catalog Rule is Active',
'channels' => 'Channels',
'customer-groups' => 'Customer Groups',
'no' => 'No',
'yes' => 'Yes',
'from' => 'From',
'to' => 'To',
'priority' => 'Priority',
'conditions' => 'Conditions',
'condition-type' => 'Condition Type',
'all-conditions-true' => 'All Conditions are True',
'any-condition-true' => 'Any Condition is True',
'add-condition' => 'Add Condition',
'choose-condition-to-add' => 'Choose a condition to add',
'product-attribute' => 'Product Attribute',
'attribute-name-children-only' => ':attribute_name (Children Only)',
'attribute-name-parent-only' => ':attribute_name (Parent Only)',
'is-equal-to' => 'Is equal to',
'is-not-equal-to' => 'Is not equal to',
'equals-or-greater-than' => 'Equals or greater than',
'equals-or-less-than' => 'Equals or less than',
'greater-than' => 'Greater than',
'less-than' => 'Less than',
'contain' => 'Contain',
'contains' => 'Contains',
'does-not-contain' => 'Does not contain',
'actions' => 'Actions',
'action-type' => 'Action Type',
'percentage-product-price' => 'Percentage of Product Price',
'fixed-amount' => 'Fixed Amount',
'fixed-amount-whole-cart' => 'Fixed Amount to Whole Catalog',
'buy-x-get-y-free' => 'Buy X Get Y Free',
'discount-amount' => 'Discount Amount',
'mass-delete-success' => 'All the selected index of coupons have been deleted successfully.',
'end-other-rules' => 'End Other Rules',
'categories' => 'Categories',
'attribute_family' => 'Attribute Family'
] ]
], ],

View File

@ -1,630 +0,0 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotion.add-cart-rule') }}
@stop
@section('content')
<div class="content">
<cart-rule></cart-rule>
</div>
@push('scripts')
<script type="text/x-template" id="cart-rule-form-template">
<form method="POST" action="{{ route('admin.cart-rule.store') }}" @submit.prevent="onSubmit" autocomplete="off">
@csrf
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" onclick="history.length > 1 ? history.go(-1) : window.location = '{{ url('/admin/dashboard') }}';"></i>
{{ __('admin::app.promotion.add-cart-rule') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
<div>
@csrf()
<accordian :active="true" title="{{ __('admin::app.promotion.information') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotion.general-info.name') }}</label>
<input type="text" class="control" name="name" v-model="name" v-validate="'required'" value="{{ old('name') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.name') }}&quot;">
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group" :class="[errors.has('description') ? 'has-error' : '']">
<label for="description">{{ __('admin::app.promotion.general-info.description') }}</label>
<textarea class="control" name="description" v-model="description" value="{{ old('description') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.description') }}&quot;"></textarea>
<span class="control-error" v-if="errors.has('description')">@{{ errors.first('description') }}</span>
</div>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('starts_from') ? 'has-error' : '']">
<label for="starts_from">{{ __('admin::app.promotion.general-info.starts-from') }}</label>
<input type="text" class="control" v-model="starts_from" name="starts_from" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.starts-from') }}&quot;">
<span class="control-error" v-if="errors.has('starts_from')">@{{ errors.first('starts_from') }}</span>
</div>
</datetime>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('ends_till') ? 'has-error' : '']">
<label for="ends_till">{{ __('admin::app.promotion.general-info.ends-till') }}</label>
<input type="text" class="control" v-model="ends_till" name="ends_till" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.ends-till') }}&quot;">
<span class="control-error" v-if="errors.has('ends_till')">@{{ errors.first('ends_till') }}</span>
</div>
</datetime>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.cust-groups') }}</label>
<select type="text" class="control" name="customer_groups[]" v-model="customer_groups" v-validate="'required'" value="{{ old('customer_groups[]') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.cust-groups') }}&quot;" multiple="multiple">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Customer Group']) }}</option>
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotion.general-info.channels') }}</label>
<select type="text" class="control" name="channels[]" v-model="channels" v-validate="'required'" value="{{ old('channels[]') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.channels-req') }}&quot;" multiple="multiple">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Channels']) }}</option>
@foreach(app('Webkul\Core\Repositories\ChannelRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.first('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('status') ? 'has-error' : '']">
<label for="status" class="required">{{ __('admin::app.promotion.general-info.status') }}</label>
<select type="text" class="control" name="status" v-model="status" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.status') }}&quot;">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
<span class="control-error" v-if="errors.has('status')">@{{ errors.first('status') }}</span>
</div>
<div class="control-group" :class="[errors.has('use_coupon') ? 'has-error' :
'']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.is-coupon') }}</label>
<select type="text" class="control" name="use_coupon" v-model="use_coupon" v-validate="'required'" value="{{ old('use_coupon')}}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.is-coupon') }}&quot;">
<option value="1" :selected="use_coupon == 1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0" :selected="use_coupon == 0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('use_coupon')">@{{ errors.first('use_coupon') }}</span>
</div>
{{-- <div class="control-group" :class="[errors.has('auto_generation') ? 'has-error' : '']" v-if="use_coupon == 1">
<label for="auto_generation" class="required">{{ __('admin::app.promotion.general-info.specific-coupon') }}</label>
<input type="checkbox" class="control" name="auto_generation" v-model="auto_generation" value="{{ old('auto_generation') }}" data-vv-as="&quot;Specific Coupon&quot;" v-on:change="checkAutogen">
<span class="control-error" v-if="errors.has('auto_generation')">@{{ errors.first('auto_generation') }}</span>
</div> --}}
{{-- <input type="hidden" name="auto_generation" v-model="auto_generation"> --}}
{{-- <div class="control-group" :class="[errors.has('per_customer') ? 'has-error' : '']">
<label for="per_customer" class="required">{{ __('admin::app.promotion.general-info.uses-per-cust') }}</label>
<input type="number" step="1" class="control" name="per_customer" v-model="per_customer" v-validate="'required|numeric|min_value:0'" value="{{ old('per_customer') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.uses-per-cust') }}&quot;">
<span class="control-error" v-if="errors.has('per_customer')">@{{ errors.first('per_customer') }}</span>
</div>
<div class="control-group" :class="[errors.has('usage_limit') ? 'has-error' : '']">
<label for="usage_limit" class="required">{{ __('admin::app.promotion.general-info.limit') }}</label>
<input type="number" step="1" class="control" name="usage_limit" v-model="usage_limit" v-validate="'required|numeric|min_value:0'" value="{{ old('usage_limit') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.uses-per-cust') }}&quot;">
<span class="control-error" v-if="errors.has('usage_limit')">@{{ errors.first('usage_limit') }}</span>
</div> --}}
<div class="control-group" :class="[errors.has('priority') ? 'has-error' : '']">
<label for="priority" class="required">{{ __('admin::app.promotion.general-info.priority') }}</label>
<input type="number" class="control" step="1" name="priority" v-model="priority" v-validate="'required|numeric|min_value:1'" value="{{ old('priority') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.priority') }}&quot;">
<span class="control-error" v-if="errors.has('priority')">@{{ errors.first('priority') }}</span>
</div>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.conditions') }}">
<div slot="body">
<input type="hidden" name="all_conditions" v-model="all_conditions">
{{-- <div class="add-condition">
<div class="control-group">
<label for="criteria" class="required">{{ __('admin::app.promotion.general-info.add-condition') }}</label>
<select type="text" class="control" v-model="criteria">
<option value="cart">{{ __('admin::app.promotion.cart-properties') }}</option>
</select>
</div>
</div> --}}
<div class="control-group">
{{ __('admin::app.promotion.general-info.test-mode') }}
<select class="control" v-model="match_criteria" style="margin-right: 15px;">
{{ $i = 0 }}
@foreach(config('pricerules.test_mode') as $key => $value)
<option value="{{ $key }}">{{ $value }}</option>
{{ $i++ }}
@endforeach
</select>
</div>
<div class="condition-set">
<!-- Cart Attributes -->
<div class="control-container mt-20" v-for="(condition, index) in conditions_list" :key="index">
<select class="control" name="cart_attributes[]" v-model="conditions_list[index].attribute" title="You Can Make Multiple Selections Here" style="margin-right: 15px; width: 30%;" v-on:change="enableCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.select-option') }}</option>
<option v-for="(cart_ip, index1) in cart_input" :value="cart_ip.code" :key="index1">@{{ cart_ip.name }}</option>
</select>
<div v-if='conditions_list[index].type == "string"' style="display: flex">
<select class="control" name="cart_attributes[]" v-model="conditions_list[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-if='conditions_list[index].attribute == "shipping_state"'>
<select class="control" v-model="conditions_list[index].value">
<option disabled="disabled">{{ __('admin::app.select-option') }}</option>
<optgroup v-for='(state, code) in country_and_states.states' :label="code">
<option v-for="(stateObj, index) in state" :value="stateObj.code">@{{ stateObj.default_name }}</option>
</optgroup>
</select>
</div>
<div v-if='conditions_list[index].attribute == "shipping_country"'>
<select class="control" v-model="conditions_list[index].value">
<option disabled="disabled">{{ __('admin::app.select-option') }}</option>
<option v-for="(country, index) in country_and_states.countries" :value="country.code">@{{ country.name }}</option>
</select>
</div>
<input class="control" type="text" name="cart_attributes[]" v-model="conditions_list[index].value" placeholder="Enter Value" v-if='conditions_list[index].attribute != "shipping_state" && conditions_list[index].attribute != "shipping_country"'>
</div>
<div v-if='conditions_list[index].type == "numeric"' style="display: flex">
<select class="control" name="attributes[]" v-model="conditions_list[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.numeric" :value="index" :key="index">@{{ condition }}</option>
</select>
<input class="control" type="number" step="0.1000" name="cart_attributes[]" v-model="conditions_list[index].value" placeholder="Enter Value">
</div>
<span class="icon trash-icon" v-on:click="removeCartAttr(index)"></span>
</div>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addCondition">{{ __('admin::app.promotion.add-condition') }}</span>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.actions') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.action') }}</label>
<select class="control" name="action_type" v-model="action_type" v-validate="'required'" value="{{ old('action_type') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.apply') }}&quot;" v-on:change="detectApply">
<option v-for="(action, index) in actions" :value="index">@{{ action }}</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('disc_amount') ? 'has-error' : '']">
<label for="disc_amount" class="required">{{ __('admin::app.promotion.general-info.disc_amt') }}</label>
<input type="number" step="0.0001" class="control" name="disc_amount" v-model="disc_amount" v-validate="'required|min_value:0.0001'" value="{{ old('disc_amount') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_amt') }}&quot;">
<span class="control-error" v-if="errors.has('disc_amount')">@{{ errors.first('disc_amount') }}</span>
</div>
{{-- <div class="control-group" :class="[errors.has('disc_threshold') ? 'has-error' : '']">
<label for="disc_threshold" class="required">{{ __('admin::app.promotion.cart.buy-atleast') }}</label>
<input type="number" step="1" class="control" name="disc_threshold" v-model="disc_threshold" v-validate="'required|numeric|min_value:1'" value="{{ old('disc_threshold') }}" data-vv-as="&quot;{{ __('admin::app.promotion.cart.buy-atleast') }}&quot;">
<span class="control-error" v-if="errors.has('disc_threshold')">@{{ errors.first('disc_threshold') }}</span>
</div> --}}
<div class="control-group" :class="[errors.has('disc_quantity') ? 'has-error' : '']">
<label for="disc_quantity" class="required">{{ __('admin::app.promotion.general-info.disc_qty') }}</label>
<input type="number" step="1" class="control" name="disc_quantity" v-model="disc_quantity" v-validate="'required|decimal|min_value:1'" value="{{ old('disc_quantity') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_qty') }}&quot;">
<span class="control-error" v-if="errors.has('disc_quantity')">@{{ errors.first('disc_quantity') }}</span>
</div>
<div class="boolean-control-container">
<div class="control-group" :class="[errors.has('free_shipping') ? 'has-error' : '']">
<label for="free_shipping" class="required">{{ __('admin::app.promotion.general-info.free-shipping') }}</label>
<select type="text" class="control" name="free_shipping" v-model="free_shipping" v-validate="'required'" value="{{ old('free_shipping') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.free-shipping') }}&quot;">
<option value="1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('free_shipping')">@{{ errors.first('free_shipping') }}</span>
</div>
<div class="control-group" :class="[errors.has('apply_to_shipping') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.cart.apply-to-shipping') }}</label>
<select type="text" class="control" name="apply_to_shipping" v-model="apply_to_shipping" v-validate="'required'" value="{{ old('apply_to_shipping') }}" data-vv-as="&quot;{{ __('admin::app.promotion.cart.apply-to-shipping') }}&quot;">
<option value="1" :selected="apply_to_shipping == 0">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0" :selected="apply_to_shipping == 1">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('apply_to_shipping')">@{{ errors.first('apply_to_shipping') }}</span>
</div>
<div class="control-group" :class="[errors.has('end_other_rules') ? 'has-error' : '']">
<label for="end_other_rules" class="required">{{ __('admin::app.promotion.general-info.end-other-rules') }}</label>
<select type="text" class="control" name="end_other_rules" v-model="end_other_rules" v-validate="'required'" value="{{ old('end_other_rules')}}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.end-other-rules') }}&quot;">
<option value="1" :selected="end_other_rules == 1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0" :selected="end_other_rules == 0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('end_other_rules')">@{{ errors.first('end_other_rules') }}</span>
</div>
</div>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.coupons') }}" v-if="use_coupon == 1">
<div slot="body">
{{-- <div v-if="!auto_generation">
<div class="control-group" :class="[errors.has('prefix') ? 'has-error' : '']">
<label for="prefix" class="required">{{ __('admin::app.promotion.general-info.prefix') }}</label>
<input type="text" class="control" name="prefix" v-model="prefix" v-validate="'alpha'" value="{{ old('prefix') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.prefix') }}&quot;">
<span class="control-error" v-if="errors.has('prefix')">@{{ errors.first('prefix') }}</span>
</div>
<div class="control-group" :class="[errors.has('suffix') ? 'has-error' : '']"">
<label for="suffix" class="required">{{ __('admin::app.promotion.general-info.suffix') }}</label>
<input type="text" class="control" name="suffix" v-model="suffix" v-validate="'alpha'" value="{{ old('suffix') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.suffix') }}&quot;">
<span class="control-error" v-if="errors.has('suffix')">@{{ errors.first('suffix') }}</span>
</div>
</div> --}}
<div>
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.promotion.general-info.code') }}</label>
<input type="text" class="control" name="code" v-model="code" v-validate="'required'" value="{{ old('code') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.code') }}&quot;">
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
</div>
</div>
</accordian>
<accordian :active="true" title="{{ __('admin::app.promotion.select-products') }}">
<div slot="body">
<input type="hidden" name="all_attributes" v-model="all_attributes">
<div class="control-group" :class="[errors.has('category_values') ? 'has-error' : '']">
<label class="mb-10" for="categories">{{ __('admin::app.promotion.select-category') }}</label>
<multiselect v-model="category_values" :close-on-select="false" :options="category_options" :searchable="false" :custom-label="categoryLabel" :show-labels="true" placeholder="Select Categories" track-by="slug" :multiple="true"></multiselect>
</div>
<label class="mb-10" for="attributes">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Attribute']) }}</label>
<br/>
<div class="control-container mt-20" v-for="(condition, index) in attribute_values" :key="index">
<select class="control" v-model="attribute_values[index].attribute" title="You Can Make Multiple Selections Here" style="margin-right: 15px; width: 30%;" v-on:change="enableAttributeCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option v-for="(attr_ip, index1) in attribute_input" :value="attr_ip.code" :key="index1">@{{ attr_ip.name }}</option>
</select>
<select class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' style="display: flex;">
<select class="control" v-model="attribute_values[index].value" style="margin-right: 15px; height: 100px" :multiple="true">
<option :disabled="true">
{{ __('ui::form.select-attribute', ['attribute' => 'Values']) }}
</option>
<option v-for="(label, index2) in attribute_values[index].options" :value="label.id" :key="index2">@{{ label.admin_name }}</option>
</select>
{{-- <multiselect v-model="attribute_values[index].value" :close-on-select="false" :options="attribute_values[index].options" :searchable="false" :track-by="admin_name" :custom-label="attributeListLabel" :multiple="true" ></multiselect> --}}
</div>
<div v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price" || attribute_values[index].type == "textarea"' style="display: flex">
<input class="control" v-model="attribute_values[index].value" type="text" placeholder="{{ __('ui::form.enter-attribute', ['attribute' => 'Text']) }}">
</div>
<span class="icon trash-icon" v-on:click="removeAttr(index)"></span>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addAttributeCondition">{{ __('admin::app.promotion.add-attr-condition') }}</span>
</div>
</accordian>
<accordian :active="false" :title="'{{ __('admin::app.promotion.general-info.labels') }}'">
<div slot="body">
<div class="control-group" :class="[errors.has('label') ? 'has-error' : '']" v-if="dedicated_label">
<label for="label">{{ __('admin::app.promotion.general-info.global-label') }}</label>
<input type="text" class="control" name="label[global]" v-model="label.global" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.label') }}&quot;">
<span class="control-error" v-if="errors.has('label')">@{{ errors.first('label') }}</span>
</div>
<div v-if="label.global == null || label.global == ''">
@foreach(core()->getAllChannels() as $channel)
@foreach($channel->locales as $locale)
<div class="control-group" :class="[errors.has('label') ? 'has-error' : '']">
<label for="code">{{ __('admin::app.label') }}<span class="locale">[{{ $channel->code }} - {{ $locale->code }}]</span></label>
<input type="text" class="control" name="label[{{ $channel->code }}][{{ $locale->code }}]" v-model="label.{{ $channel->code }}.{{ $locale->code }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.label') }}&quot;">
<span class="control-error" v-if="errors.has('label')">@{{ errors.first('label') }}</span>
</div>
@endforeach
@endforeach
</div>
</div>
</accordian>
</div>
</div>
</div>
</form>
</script>
<script>
Vue.component('cart-rule', {
template: '#cart-rule-form-template',
inject: ['$validator'],
data () {
return {
name: null,
description: null,
conditions_list: [],
channels: ['{{ core()->getCurrentChannel()->id }}'],
customer_groups: ['{{ app('Webkul\Customer\Repositories\CustomerGroupRepository')->findOneWhere(["code" => "general"])->id }}'],
ends_till: null,
starts_from: null,
priority: 0,
per_customer: 0,
status: 0,
use_coupon: 0,
auto_generation: false,
usage_limit: 0,
action_type: null,
apply: null,
apply_to_shipping: 0,
disc_amount: 1,
// disc_threshold: null,
disc_quantity: 1,
end_other_rules: 0,
coupon_type: null,
free_shipping: 0,
all_conditions: [],
match_criteria: 'all_are_true',
all_attributes: {
'categories' : [],
'attributes' : []
},
code: null,
suffix: null,
prefix: null,
dedicated_label: true,
label: {
global: null,
@foreach(core()->getAllChannels() as $channel)
@foreach($channel->locales as $locale)
{{ trim($channel->code) }} : {
{{ trim($locale->code) }}: ''
},
@endforeach
@endforeach
},
criteria: 'cart',
conditions: @json($cart_rule[0]).conditions,
cart_input: @json($cart_rule[0]).attributes,
actions: @json($cart_rule[0]).actions,
cart_object: {
attribute: [],
condition: [],
value: []
},
country_and_states: @json($cart_rule[2]),
category_options: @json($cart_rule[1]),
category_values: [],
attribute_values: [],
attr_object: {
attribute: null,
condition: null,
value: [],
options: []
},
attribute_input: @json($cart_rule[3]),
}
},
methods: {
categoryLabel (option) {
return option.name + ' [ ' + option.slug + ' ]';
},
attributeListLabel(option) {
return option.label;
},
addCondition () {
if (this.criteria == 'product_subselection' || this.criteria == 'cart') {
this.condition_on = this.criteria;
} else {
alert('Please select type of condition');
return false;
}
if (this.condition_on == 'cart') {
this.conditions_list.push(this.cart_object);
this.cart_object = {
attribute: null,
condition: null,
value: []
};
}
},
addAttributeCondition() {
this.attribute_values.push(this.attr_object);
this.attr_object = {
attribute: null,
condition: null,
value: [],
options: []
};
},
checkAutogen() {
},
detectApply() {
return;
},
enableCondition(event, index) {
selectedIndex = event.target.selectedIndex - 1;
for (i in this.cart_input) {
if (i == selectedIndex) {
this.conditions_list[index].type = this.cart_input[i].type;
}
}
},
enableAttributeCondition (event, index) {
selectedIndex = event.target.selectedIndex - 1;
for(i in this.attribute_input) {
if (i == selectedIndex) {
if (this.attribute_input[i].has_options == true) {
this.attribute_values[index].options = this.attribute_input[i].options;
}
this.attribute_values[index].type = this.attribute_input[i].type;
}
}
},
// useCoupon() {
// if (this.use_coupon == 0) {
// this.auto_generation = null;
// } else {
// this.auto_generation = true;
// }
// },
removeCartAttr(index) {
this.conditions_list.splice(index, 1);
},
removeAttr(index) {
this.attribute_values.splice(index, 1);
},
onSubmit: function (e) {
if (this.attribute_values.length != 0 || this.category_values.length != 0) {
for (i in this.attribute_values) {
delete this.attribute_values[i].options;
}
this.all_attributes.categories = this.category_values;
this.all_attributes.attributes = this.attribute_values;
}
this.all_attributes = JSON.stringify(this.all_attributes);
if (this.conditions_list != null && this.conditions_list.length != 0) {
this.conditions_list.push({'criteria': this.match_criteria});
this.all_conditions = JSON.stringify(this.conditions_list);
this.conditions_list.pop();
}
this.$validator.validateAll().then(result => {
if (result) {
e.target.submit();
}
});
},
addFlashMessages() {
const flashes = this.$refs.flashes;
flashMessages.forEach(function(flash) {
flashes.addFlash(flash);
}, this);
}
}
});
</script>
@endpush
@stop

View File

@ -1,740 +0,0 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotion.edit-cart-rule') }}
@stop
@section('content')
<div class="content">
<cart-rule></cart-rule>
</div>
@push('scripts')
<script type="text/x-template" id="cart-rule-form-template">
<form method="POST" action="{{ route('admin.cart-rule.update', $cart_rule[3]->id) }}" @submit.prevent="onSubmit">
@csrf
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" onclick="history.length > 1 ? history.go(-1) : window.location = '{{ url('/admin/dashboard') }}';"></i>
{{ __('admin::app.promotion.edit-cart-rule') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.save') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
<div>
@csrf()
<accordian :active="true" title="{{ __('admin::app.promotion.information') }}">
<div slot="body">
<input type="hidden" name="all_conditions" v-model="all_conditions">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotion.general-info.name') }}</label>
<input type="text" class="control" name="name" v-model="name" v-validate="'required'" value="{{ old('name') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.name') }}&quot;">
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group" :class="[errors.has('description') ? 'has-error' : '']">
<label for="description">{{ __('admin::app.promotion.general-info.description') }}</label>
<textarea class="control" name="description" v-model="description" value="{{ old('description') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.description') }}&quot;"></textarea>
<span class="control-error" v-if="errors.has('description')">@{{ errors.first('description') }}</span>
</div>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('starts_from') ? 'has-error' : '']">
<label for="starts_from">{{ __('admin::app.promotion.general-info.starts-from') }}</label>
<input type="text" class="control" v-model="starts_from" name="starts_from" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.starts-from') }}&quot;">
<span class="control-error" v-if="errors.has('starts_from')">@{{ errors.first('starts_from') }}</span>
</div>
</datetime>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('ends_till') ? 'has-error' : '']">
<label for="ends_till">{{ __('admin::app.promotion.general-info.ends-till') }}</label>
<input type="text" class="control" v-model="ends_till" name="ends_till" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.ends-till') }}&quot;">
<span class="control-error" v-if="errors.has('ends_till')">@{{ errors.first('ends_till') }}</span>
</div>
</datetime>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.cust-groups') }}</label>
<select type="text" class="control" name="customer_groups[]" v-model="customer_groups" v-validate="'required'" value="{{ old('customer_groups[]') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.cust-groups') }}&quot;" multiple="multiple">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Customer Group']) }}</option>
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $customerGroup)
<option value="{{ $customerGroup->id }}">{{ $customerGroup->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotion.general-info.channels') }}</label>
<select type="text" class="control" name="channels[]" v-model="channels" v-validate="'required'" value="{{ old('channels[]') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.channels-req') }}&quot;" multiple="multiple">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Channels']) }}</option>
@foreach(app('Webkul\Core\Repositories\ChannelRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.first('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('status') ? 'has-error' : '']">
<label for="status" class="required">{{ __('admin::app.promotion.general-info.status') }}</label>
<select type="text" class="control" name="status" v-model="status" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.status') }}&quot;">
{{-- <option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Status']) }}</option> --}}
<option value="1">{{ __('admin::app.promotion.yes') }}</option>
<option value="0">{{ __('admin::app.promotion.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('status')">@{{ errors.first('status') }}</span>
</div>
<div class="control-group" :class="[errors.has('use_coupon') ? 'has-error' :
'']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.is-coupon') }}</label>
<select type="text" class="control" name="use_coupon" v-model="use_coupon" v-validate="'required'" value="{{ old('use_coupon')}}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.is-coupon') }}&quot;" v-on:change="useCoupon">
<option value="1" :selected="use_coupon == 1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0" :selected="use_coupon == 0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('use_coupon')">@{{ errors.first('use_coupon') }}</span>
</div>
{{-- <div class="control-group" :class="[errors.has('auto_generation') ? 'has-error' : '']" v-if="use_coupon == 1">
<label for="auto_generation" class="required">{{ __('admin::app.promotion.general-info.specific-coupon') }}</label>
<input type="checkbox" class="control" name="auto_generation" v-model="auto_generation" value="{{ old('auto_generation') }}" data-vv-as="&quot;Specific Coupon&quot;" v-on:change="checkAutogen">
<span class="control-error" v-if="errors.has('auto_generation')">@{{ errors.first('auto_generation') }}</span>
</div> --}}
{{-- <input type="hidden" name="auto_generation" v-model="auto_generation"> --}}
{{-- <div class="control-group" :class="[errors.has('per_customer') ? 'has-error' : '']">
<label for="per_customer" class="required">{{ __('admin::app.promotion.general-info.uses-per-cust') }}</label>
<input type="number" step="1" class="control" name="per_customer" v-model="per_customer" v-validate="'required|numeric|min_value:0'" value="{{ old('per_customer') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.uses-per-cust') }}&quot;">
<figcaption class="required">* {{ __('admin::app.promotion.zero-unlimited') }}</figcaption>
<span class="control-error" v-if="errors.has('per_customer')">@{{ errors.first('per_customer') }}</span>
</div>
<div class="control-group" :class="[errors.has('usage_limit') ? 'has-error' : '']">
<label for="usage_limit" class="required">{{ __('admin::app.promotion.general-info.limit') }}</label>
<input type="number" step="1" class="control" name="usage_limit" v-model="usage_limit" v-validate="'required|numeric|min_value:0'" value="{{ old('usage_limit') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.uses-per-cust') }}&quot;">
<figcaption class="required">* {{ __('admin::app.promotion.zero-unlimited') }}</figcaption>
<span class="control-error" v-if="errors.has('usage_limit')">@{{ errors.first('usage_limit') }}</span>
</div> --}}
<div class="control-group" :class="[errors.has('priority') ? 'has-error' : '']">
<label for="priority" class="required">{{ __('admin::app.promotion.general-info.priority') }}</label>
<input type="number" class="control" step="1" name="priority" v-model="priority" v-validate="'required|numeric|min_value:1'" value="{{ old('priority') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.priority') }}&quot;">
<span class="control-error" v-if="errors.has('priority')">@{{ errors.first('priority') }}</span>
</div>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.conditions') }}">
<div slot="body">
<input type="hidden" name="all_conditions" v-model="all_conditions">
{{-- <div class="add-condition">
<div class="control-group">
<label for="criteria" class="required">{{ __('admin::app.promotion.general-info.add-condition') }}</label>
<select type="text" class="control" v-model="criteria">
<option value="cart">Cart Properties</option>
</select>
</div>
</div> --}}
<div class="control-group">
{{ __('admin::app.promotion.general-info.test-mode') }}
<select class="control" v-model="match_criteria" style="margin-right: 15px;">
{{ $i = 0 }}
@foreach(config('pricerules.test_mode') as $key => $value)
<option value="{{ $key }}">{{ $value }}</option>
{{ $i++ }}
@endforeach
</select>
</div>
<div class="condition-set">
<!-- Cart Attributes -->
<div class="control-container mt-20" v-for="(condition, index) in conditions_list" :key="index">
<select class="control" name="cart_attributes[]" v-model="conditions_list[index].attribute" title="You Can Make Multiple Selections Here" style="margin-right: 15px; width: 30%;" v-on:change="enableCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option v-for="(cart_ip, index1) in cart_input" :value="cart_ip.code" :key="index1">@{{ cart_ip.name }}</option>
</select>
<div v-if='conditions_list[index].type == "string"' style="display: flex">
<select class="control" name="cart_attributes[]" v-model="conditions_list[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-if='conditions_list[index].attribute == "shipping_state"'>
<select class="control" v-model="conditions_list[index].value">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'State']) }}</option>
<optgroup v-for='(state, code) in country_and_states.states' :label="code">
<option v-for="(stateObj, index) in state" :value="stateObj.code">@{{ stateObj.default_name }}</option>
</optgroup>
</select>
</div>
<div v-if='conditions_list[index].attribute == "shipping_country"'>
<select class="control" v-model="conditions_list[index].value">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Country']) }}</option>
<option v-for="(country, index) in country_and_states.countries" :value="country.code">@{{ country.name }}</option>
</select>
</div>
<input class="control" type="text" name="cart_attributes[]" v-model="conditions_list[index].value" placeholder="{{ __('admin::app.promotion.enter-attribtue', ['attrbibute' => 'Value']) }}" v-if='conditions_list[index].attribute != "shipping_state" && conditions_list[index].attribute != "shipping_country"'>
</div>
<div v-if='conditions_list[index].type == "numeric"' style="display: flex">
<select class="control" name="attributes[]" v-model="conditions_list[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.numeric" :value="index" :key="index">@{{ condition }}</option>
</select>
<input class="control" type="number" step="0.1000" name="cart_attributes[]" v-model="conditions_list[index].value">
</div>
<span class="icon trash-icon" v-on:click="removeCartAttr(index)"></span>
</div>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addCondition">{{ __('admin::app.promotion.add-condition') }}</span>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.actions') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.action') }}</label>
<select class="control" name="action_type" v-model="action_type" v-validate="'required'" value="{{ old('action_type') }}" data-vv-as="&quot;Apply As&quot;" v-on:change="detectApply">
<option v-for="(action, index) in actions" :value="index">@{{ action }}</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('disc_amount') ? 'has-error' : '']">
<label for="disc_amount" class="required">{{ __('admin::app.promotion.general-info.disc_amt') }}</label>
<input type="number" step="0.0001" class="control" name="disc_amount" v-model="disc_amount" v-validate="'required|decimal|min_value:0.0001'" value="{{ old('disc_amount') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_amt') }}&quot;">
<span class="control-error" v-if="errors.has('disc_amount')">@{{ errors.first('disc_amount') }}</span>
</div>
<div class="control-group" :class="[errors.has('disc_quantity') ? 'has-error' : '']">
<label for="disc_quantity" class="required">{{ __('admin::app.promotion.general-info.disc_qty') }}</label>
<input type="number" step="1" class="control" name="disc_quantity" v-model="disc_quantity" v-validate="'required|decimal|min_value:1'" value="{{ old('disc_quantity') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_qty') }}&quot;">
<span class="control-error" v-if="errors.has('disc_quantity')">@{{ errors.first('disc_quantity') }}</span>
</div>
<div class="boolean-control-container">
<div class="control-group" :class="[errors.has('free_shipping') ? 'has-error' : '']">
<label for="free_shipping" class="required">{{ __('admin::app.promotion.general-info.free-shipping') }}</label>
<select type="text" class="control" name="free_shipping" v-model="free_shipping" v-validate="'required'" value="{{ old('free_shipping') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.free-shipping') }}&quot;">
<option value="1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('free_shipping')">@{{ errors.first('free_shipping') }}</span>
</div>
<div class="control-group" :class="[errors.has('apply_to_shipping') ? 'has-error' : '']">
<label for="apply_to_shipping" class="required">{{ __('admin::app.promotion.cart.apply-to-shipping') }}</label>
<select type="text" class="control" name="apply_to_shipping" v-model="apply_to_shipping" v-validate="'required'" value="{{ old('apply_to_shipping') }}" data-vv-as="&quot;{{ __('admin::app.promotion.cart.apply-to-shipping') }}&quot;">
<option value="1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('apply_to_shipping')">@{{ errors.first('apply_to_shipping') }}</span>
</div>
<div class="control-group" :class="[errors.has('end_other_rules') ? 'has-error' : '']">
<label for="end_other_rules" class="required">{{ __('admin::app.promotion.general-info.end-other-rules') }}</label>
<select type="text" class="control" name="end_other_rules" v-model="end_other_rules" v-validate="'required'" value="{{ old('end_other_rules')}}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.end-other-rules') }}&quot;">
<option value="1" :selected="end_other_rules == 1">{{ __('admin::app.promotion.general-info.is-coupon-yes') }}</option>
<option value="0" :selected="end_other_rules == 0">{{ __('admin::app.promotion.general-info.is-coupon-no') }}</option>
</select>
<span class="control-error" v-if="errors.has('end_other_rules')">@{{ errors.first('end_other_rules') }}</span>
</div>
</div>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.coupons') }}" v-if="use_coupon == 1">
<div slot="body">
{{-- <div v-if="!auto_generation">
<div class="control-group" :class="[errors.has('prefix') ? 'has-error' : '']">
<label for="prefix" class="required">{{ __('admin::app.promotion.general-info.prefix') }}</label>
<input type="text" class="control" name="prefix" v-model="prefix" value="{{ old('prefix') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.prefix') }}&quot;">
<span class="control-error" v-if="errors.has('prefix')">@{{ errors.first('prefix') }}</span>
</div>
<div class="control-group" :class="[errors.has('suffix') ? 'has-error' : '']"">
<label for="suffix" class="required">{{ __('admin::app.promotion.general-info.suffix') }}</label>
<input type="text" class="control" name="suffix" v-model="suffix" value="{{ old('suffix') }}" data-vv-as="&quot;suffix&quot;">
<span class="control-error" v-if="errors.has('suffix')">@{{ errors.first('suffix') }}</span>
</div>
</div> --}}
{{-- <div v-if="auto_generation != 0"> --}}
<div class="control-group" :class="[errors.has('code') ? 'has-error' : '']">
<label for="code" class="required">{{ __('admin::app.promotion.general-info.code') }}</label>
<input type="text" class="control" name="code" v-model="code" v-validate="'required'" value="{{ old('code') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.code') }}&quot;">
<span class="control-error" v-if="errors.has('code')">@{{ errors.first('code') }}</span>
</div>
{{-- </div> --}}
</div>
</accordian>
<accordian :active="true" title="{{ __('admin::app.promotion.select-products') }}">
<div slot="body">
<input type="hidden" name="all_attributes" v-model="all_attributes">
<span class="info mb-20" style="display: block;">
<b>{{ __('admin::app.promotion.note') }}:</b>
{{ __('admin::app.promotion.convert-x-note') }}
</span>
<div class="control-group" :class="[errors.has('category_values') ? 'has-error' : '']">
<label class="mb-10" for="categories">{{ __('admin::app.promotion.select-category') }}</label>
<multiselect v-model="category_values" :close-on-select="false" :options="category_options" :searchable="false" :custom-label="categoryLabel" :show-labels="true" placeholder="{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Categories']) }}" track-by="slug" :multiple="true"></multiselect>
</div>
<label class="mb-10" for="attributes">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</label>
<br/>
<div class="control-container mt-20" v-for="(condition, index) in attribute_values" :key="index">
<select class="control" v-model="attribute_values[index].attribute" title="You Can Make Multiple Selections Here" style="margin-right: 15px; width: 30%;" v-on:change="enableAttributeCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option v-for="(attr_ip, index1) in attribute_input" :value="attr_ip.code" :key="index1">@{{ attr_ip.name }}</option>
</select>
<select class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' style="display: flex;">
<select class="control" v-model="attribute_values[index].value" style="margin-right: 15px; height: 100px" :multiple="true">
<option :disabled="true">
{{ __('ui::form.select-attribute', ['attribute' => 'Values']) }}
</option>
<option v-for="(label, index2) in attribute_values[index].options" :value="label.id" :key="index2">@{{ label.admin_name }}</option>
</select>
{{-- <multiselect v-model="attribute_values[index].value" :close-on-select="false" :options="attribute_values[index].options" :searchable="false" :track-by="admin_name" :custom-label="attributeListLabel" :multiple="true" ></multiselect> --}}
</div>
<div v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price" || attribute_values[index].type == "textarea"' style="display: flex">
<input class="control" v-model="attribute_values[index].value" type="text" placeholder="{{ __('ui::form.enter-attribute', ['attribute' => 'Text']) }}">
</div>
<span class="icon trash-icon" v-on:click="removeAttr(index)"></span>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addAttributeCondition">{{ __('admin::app.promotion.add-attr-condition') }}</span>
</div>
</accordian>
<accordian :active="false" :title="'{{ __('admin::app.promotion.general-info.labels') }}'">
<div slot="body">
@foreach($cart_rule[3]->labels as $label)
<div class="control-group" :class="[errors.has('label') ? 'has-error' : '']">
<label for="code">{{ __('admin::app.label') }}
<span class="locale">[{{ $label->channel->code }} - {{ $label->locale->code }}]</span>
</label>
<input type="text" class="control" name="label[{{ $label->channel->code }}][{{ $label->locale->code }}]" value="{{ $label->label }}" data-vv-as="&quot;Label&quot;">
<span class="control-error" v-if="errors.has('label')">@{{ errors.first('label') }}</span>
</div>
@endforeach
</div>
</accordian>
</div>
</div>
</div>
</form>
</script>
<script>
Vue.component('cart-rule', {
template: '#cart-rule-form-template',
inject: ['$validator'],
data () {
return {
name: '{{ __('admin::app.promotion.rule-name') }}',
description: '{{ __('admin::app.promotion.rule-desc') }}',
channels: [],
customer_groups: [],
ends_till: null,
starts_from: null,
priority: 0,
per_customer: 0,
status: null,
use_coupon: null,
// auto_generation: 0,
usage_limit: 0,
action_type: null,
apply: null,
apply_to_shipping: null,
disc_amount: null,
disc_quantity: null,
end_other_rules: null,
coupon_type: null,
free_shipping: null,
all_conditions: null,
match_criteria: 'all_are_true',
all_attributes: {
'categories' : null,
'attributes' : null
},
code: null,
suffix: null,
prefix: null,
dedicated_label: true,
global_label: null,
labels: [],
// label: {
// @foreach(core()->getAllChannels() as $channel)
// @foreach($channel->locales as $locale)
// {{ trim($channel->code) }} : null,
// @endforeach
// @endforeach
// },
criteria: 'cart',
conditions: @json($cart_rule[0]).conditions,
cart_input: @json($cart_rule[0]).attributes,
actions: @json($cart_rule[0]).actions,
conditions_list:[],
cart_object: {
attribute: null,
condition: null,
value: []
},
country_and_states: @json($cart_rule[2]),
category_options: @json($cart_rule[1]),
category_values: [],
attribute_values: [],
attr_object: {
attribute: null,
condition: null,
value: [],
options: []
},
attribute_input: @json($cart_rule[4]),
}
},
mounted () {
data = @json($cart_rule[3]);
this.name = data.name;
this.description = data.description;
this.channels = [];
for (i in data.channels) {
this.channels.push(data.channels[i].channel_id);
}
this.customer_groups = data.customer_groups;
for (i in data.customer_groups) {
this.customer_groups.push(data.customer_groups[i].customer_group_id);
}
this.ends_till = data.ends_till;
this.starts_from = data.starts_from;
this.priority = data.priority;
this.per_customer = data.per_customer;
this.status = data.status;
if (data.use_coupon == 0) {
// this.auto_generation = null;
this.use_coupon = 0;
} else {
this.use_coupon = 1;
// this.auto_generation = data.auto_generation;
this.code = data.coupons.code;
// this.suffix = data.coupons.suffix;
// this.prefix = data.coupons.prefix;
// if (data.auto_generation == 0)
// this.auto_generation = true;
// else
// this.auto_generation = false;
}
this.usage_limit = data.usage_limit;
this.is_guest = data.is_guest;
this.action_type = data.action_type;
this.apply = null;
this.apply_to_shipping = data.apply_to_shipping;
this.disc_amount = data.disc_amount;
// this.disc_threshold = data.disc_threshold;
this.disc_quantity = data.disc_quantity;
this.end_other_rules = data.end_other_rules;
this.coupon_type = data.coupon_type;
this.free_shipping = data.free_shipping;
this.all_conditions = null;
if (data.hasOwnProperty('conditions') && data.conditions != null) {
this.conditions_list = JSON.parse(JSON.parse(data.conditions));
this.match_criteria = this.conditions_list.pop().criteria;
}
hasSubSelectionConditions = JSON.parse(data.actions).hasOwnProperty('attribute_conditions');
if (hasSubSelectionConditions) {
if (JSON.parse(data.actions).attribute_conditions.hasOwnProperty('categories')) {
this.category_values = JSON.parse(data.actions).attribute_conditions.categories;
} else {
this.category_values = [];
}
if (JSON.parse(data.actions).attribute_conditions.hasOwnProperty('attributes')) {
this.attribute_values = JSON.parse(data.actions).attribute_conditions.attributes;
} else {
this.attribute_values = [];
}
// creating options and has option param on the frontend
for (i in this.attribute_values) {
for (j in this.attribute_input) {
if (this.attribute_input[j].code == this.attribute_values[i].attribute) {
if (this.attribute_input[j].has_options == true) {
this.attribute_values[i].has_options = true;
this.attribute_values[i].options = this.attribute_input[j].options;
} else {
this.attribute_values[i].has_options = false;
this.attribute_values[i].options = null;
}
}
}
}
}
criteria = null;
},
methods: {
categoryLabel (option) {
return option.name + ' [ ' + option.slug + ' ]';
},
attributeListLabel(option) {
return option.label;
},
addCondition () {
if (this.criteria == 'product_subselection' || this.criteria == 'cart') {
this.condition_on = this.criteria;
} else {
alert('please select type of condition');
return false;
}
if (this.condition_on == 'cart') {
this.conditions_list.push(this.cart_object);
this.cart_object = {
attribute: null,
condition: null,
value: []
};
}
},
addAttributeCondition() {
this.attribute_values.push(this.attr_object);
this.attr_object = {
attribute: null,
condition: null,
value: [],
options: []
};
},
checkAutogen() {
},
detectApply() {
return;
},
enableCondition(event, index) {
selectedIndex = event.target.selectedIndex - 1;
for (i in this.cart_input) {
if (i == selectedIndex) {
this.conditions_list[index].type = this.cart_input[i].type;
}
}
},
enableAttributeCondition (event, index) {
selectedIndex = event.target.selectedIndex - 1;
for(i in this.attribute_input) {
if (i == selectedIndex) {
if (this.attribute_input[i].has_options == true) {
this.attribute_values[index].options = this.attribute_input[i].options;
}
this.attribute_values[index].type = this.attribute_input[i].type;
}
}
},
useCoupon() {
if (this.use_coupon == 0) {
this.auto_generation = null;
} else {
this.auto_generation = true;
}
},
removeCartAttr(index) {
this.conditions_list.splice(index, 1);
},
removeCat(index) {
this.cats.splice(index, 1);
},
removeAttr(index) {
this.attribute_values.splice(index, 1);
},
onSubmit: function (e) {
if (this.attribute_values.length != 0 || this.category_values.length != 0) {
for (i in this.attribute_values) {
delete this.attribute_values[i].options;
}
this.all_attributes.categories = this.category_values;
this.all_attributes.attributes = this.attribute_values;
this.all_attributes = JSON.stringify(this.all_attributes);
} else {
this.all_attributes = null;
}
if (this.conditions_list.length != 0) {
this.conditions_list.push({'criteria': this.match_criteria});
this.all_conditions = JSON.stringify(this.conditions_list);
this.conditions_list.pop();
}
this.$validator.validateAll().then(result => {
if (result) {
e.target.submit();
}
});
},
addFlashMessages() {
const flashes = this.$refs.flashes;
flashMessages.forEach(function(flash) {
flashes.addFlash(flash);
}, this);
}
}
});
</script>
@endpush
@stop

View File

@ -0,0 +1,614 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotions.cart-rules.add-title') }}
@stop
@section('content')
<div class="content">
<cart-rule></cart-rule>
</div>
@stop
@push('scripts')
@parent
<script type="text/x-template" id="cart-rule-template">
<div>
<form method="POST" action="{{ route('admin.cart-rules.store') }}" @submit.prevent="onSubmit">
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" @click="redirectBack('{{ url('/admin/dashboard') }}')"></i>
{{ __('admin::app.promotions.cart-rules.add-title') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotions.cart-rules.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
{!! view_render_event('bagisto.admin.promotions.cart-rules.create.before') !!}
<accordian :title="'{{ __('admin::app.promotions.cart-rules.rule-information') }}'" :active="true">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotions.cart-rules.name') }}</label>
<input v-validate="'required'" class="control" id="name" name="name" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.name') }}&quot;" value="{{ old('name') }}"/>
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group">
<label for="description">{{ __('admin::app.promotions.cart-rules.description') }}</label>
<textarea class="control" id="description" name="description">{{ old('description') }}</textarea>
</div>
<div class="control-group">
<label for="status">{{ __('admin::app.promotions.cart-rules.status') }}</label>
<span class="checkbox">
<input type="checkbox" id="status" name="status" value="1">
<label class="checkbox-view" for="status"></label>
{{ __('admin::app.promotions.cart-rules.is-active') }}
</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotions.cart-rules.channels') }}</label>
<select class="control" id="channels" name="channels[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.channels') }}&quot;" multiple="multiple">
@foreach(core()->getAllChannels() as $channel)
<option value="{{ $channel->id }}" {{ old('channels') && in_array($channel->id, old('channels')) ? 'selected' : '' }}>
{{ $channel->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotions.cart-rules.customer-groups') }}</label>
<select class="control" id="customer_groups" name="customer_groups[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.customer-groups') }}&quot;" multiple="multiple">
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $customerGroup)
<option value="{{ $customerGroup->id }}" {{ old('customer_groups') && in_array($customerGroup->id, old('customer_groups')) ? 'selected' : '' }}>
{{ $customerGroup->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('coupon_type') ? 'has-error' : '']">
<label for="coupon_type" class="required">{{ __('admin::app.promotions.cart-rules.coupon-type') }}</label>
<select class="control" id="coupon_type" name="coupon_type" v-model="coupon_type" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.coupon-type') }}&quot;">
<option value="0" {{ old('coupon_type') == 0 ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.no-coupon') }}</option>
<option value="1" {{ old('coupon_type') == 1 ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.specific-coupon') }}</option>
</select>
<span class="control-error" v-if="errors.has('coupon_type')">@{{ errors.first('coupon_type') }}</span>
</div>
<div v-if="parseInt(coupon_type)">
<div class="control-group" :class="[errors.has('use_auto_generation') ? 'has-error' : '']">
<label for="use_auto_generation" class="required">{{ __('admin::app.promotions.cart-rules.auto-generate-coupon') }}</label>
<select class="control" id="use_auto_generation" name="use_auto_generation" v-model="use_auto_generation" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.auto-generate-coupon') }}&quot;">
<option value="0">{{ __('admin::app.promotions.cart-rules.no') }}</option>
<option value="1">{{ __('admin::app.promotions.cart-rules.yes') }}</option>
</select>
<span class="control-error" v-if="errors.has('use_auto_generation')">@{{ errors.first('use_auto_generation') }}</span>
</div>
<div v-if="! parseInt(use_auto_generation)">
<div class="control-group" :class="[errors.has('coupon_code') ? 'has-error' : '']">
<label for="coupon_code" class="required">{{ __('admin::app.promotions.cart-rules.coupon-code') }}</label>
<input v-validate="'required'" class="control" id="coupon_code" name="coupon_code" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.coupon-code') }}&quot;" value="{{ old('coupon_code') }}"/>
<span class="control-error" v-if="errors.has('coupon_code')">@{{ errors.first('coupon_code') }}</span>
</div>
</div>
<div class="control-group">
<label for="uses_per_coupon">{{ __('admin::app.promotions.cart-rules.uses-per-coupon') }}</label>
<input class="control" id="uses_per_coupon" name="uses_per_coupon" value="{{ old('uses_per_coupon') }}"/>
</div>
</div>
<div class="control-group">
<label for="usage_per_customer">{{ __('admin::app.promotions.cart-rules.uses-per-customer') }}</label>
<input class="control" id="usage_per_customer" name="usage_per_customer" value="{{ old('usage_per_customer') }}"/>
<span class="control-info">{{ __('admin::app.promotions.cart-rules.uses-per-customer-control-info') }}</span>
</div>
<div class="control-group date">
<label for="starts_from">{{ __('admin::app.promotions.cart-rules.from') }}</label>
<date>
<input type="text" name="starts_from" class="control" value="{{ old('starts_from') }}"/>
</date>
</div>
<div class="control-group date">
<label for="ends_till">{{ __('admin::app.promotions.cart-rules.to') }}</label>
<date>
<input type="text" name="ends_till" class="control" value="{{ old('ends_till') }}"/>
</date>
</div>
<div class="control-group">
<label for="sort_order">{{ __('admin::app.promotions.cart-rules.priority') }}</label>
<input type="text" class="control" id="sort_order" name="sort_order" value="{{ old('sort_order') ?? 0 }}"/>
</div>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.cart-rules.conditions') }}'" :active="false">
<div slot="body">
<div class="control-group">
<label for="condition_type">{{ __('admin::app.promotions.cart-rules.condition-type') }}</label>
<select class="control" id="condition_type" name="condition_type" v-model="condition_type">
<option value="1">{{ __('admin::app.promotions.cart-rules.all-conditions-true') }}</option>
<option value="2">{{ __('admin::app.promotions.cart-rules.any-condition-true') }}</option>
</select>
</div>
<div class="table cart-rule-conditions" style="margin-top: 20px; overflow-x: unset;">
<table>
<tbody>
<cart-rule-condition-item
v-for='(condition, index) in conditions'
:condition="condition"
:key="index"
:index="index"
@onRemoveCondition="removeCondition($event)">
</cart-rule-condition-item>
</tbody>
</table>
</div>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px;" @click="addCondition">
{{ __('admin::app.promotions.cart-rules.add-condition') }}
</button>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.cart-rules.actions') }}'" :active="false">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotions.cart-rules.action-type') }}</label>
<select class="control" id="action_type" name="action_type" v-validate="'required'" v-model="action_type" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.action-type') }}&quot;">
<option value="by_percent" {{ old('action_type') == 'by_percent' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.percentage-product-price') }}
</option>
<option value="by_fixed" {{ old('action_type') == 'by_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.fixed-amount') }}
</option>
<option value="cart_fixed" {{ old('action_type') == 'cart_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.fixed-amount-whole-cart') }}
</option>
<option value="buy_x_get_y" {{ old('action_type') == 'buy_x_get_y' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.buy-x-get-y-free') }}
</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('discount_amount') ? 'has-error' : '']">
<label for="discount_amount" class="required">{{ __('admin::app.promotions.cart-rules.discount-amount') }}</label>
<input v-validate="'required'" class="control" id="discount_amount" name="discount_amount" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.discount-amount') }}&quot;" value="{{ old('discount_amount') ?? 0 }}"/>
<span class="control-error" v-if="errors.has('discount_amount')">@{{ errors.first('discount_amount') }}</span>
</div>
<div class="control-group">
<label for="discount_quantity">{{ __('admin::app.promotions.cart-rules.discount-quantity') }}</label>
<input class="control" id="discount_quantity" name="discount_quantity" value="{{ old('discount_quantity') ?? 0 }}"/>
</div>
<div class="control-group">
<label for="discount_step">{{ __('admin::app.promotions.cart-rules.discount-step') }}</label>
<input class="control" id="discount_step" name="discount_step" value="{{ old('discount_step') ?? 0 }}"/>
</div>
<div class="control-group">
<label for="apply_to_shipping">{{ __('admin::app.promotions.cart-rules.apply-to-shipping') }}</label>
<select class="control" id="apply_to_shipping" name="apply_to_shipping" :disabled="action_type == 'cart_fixed'">
<option value="0" {{ ! old('apply_to_shipping') ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.no') }}</option>
<option value="1" {{ old('apply_to_shipping') ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.yes') }}</option>
</select>
</div>
<div class="control-group">
<label for="free_shipping">{{ __('admin::app.promotions.cart-rules.free-shipping') }}</label>
<select class="control" id="free_shipping" name="free_shipping">
<option value="0" {{ ! old('free_shipping') ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.no') }}</option>
<option value="1" {{ old('free_shipping') ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.yes') }}</option>
</select>
</div>
<div class="control-group">
<label for="end_other_rules">{{ __('admin::app.promotions.cart-rules.end-other-rules') }}</label>
<select class="control" id="end_other_rules" name="end_other_rules">
<option value="0" {{ ! old('end_other_rules') ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.no') }}
</option>
<option value="1" {{ old('end_other_rules') ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.yes') }}
</option>
</select>
</div>
</div>
</accordian>
{!! view_render_event('bagisto.admin.promotions.cart-rules.create.after') !!}
</div>
</div>
</form>
</div>
</script>
<script type="text/x-template" id="cart-rule-condition-item-template">
<tr>
<td class="attribute">
<div class="control-group">
<select :name="['conditions[' + index + '][attribute]']" class="control" v-model="condition.attribute">
<option value="">{{ __('admin::app.promotions.cart-rules.choose-condition-to-add') }}</option>
<optgroup v-for='(conditionAttribute, index) in condition_attributes' :label="conditionAttribute.label">
<option v-for='(childAttribute, index) in conditionAttribute.children' :value="childAttribute.key">
@{{ childAttribute.label }}
</option>
</optgroup>
</select>
</div>
</td>
<td class="operator">
<div class="control-group" v-if="matchedAttribute">
<select :name="['conditions[' + index + '][operator]']" class="control" v-model="condition.operator">
<option v-for='operator in condition_operators[matchedAttribute.type]' :value="operator.operator">
@{{ operator.label }}
</option>
</select>
</div>
</td>
<td class="value">
<div v-if="matchedAttribute">
<input type="hidden" :name="['conditions[' + index + '][attribute_type]']" v-model="matchedAttribute.type">
<div v-if="matchedAttribute.key == 'product|category_ids' || matchedAttribute.key == 'product|category_ids' || matchedAttribute.key == 'product|parent::category_ids'">
<tree-view value-field="id" id-field="id" :name-field="'conditions[' + index + '][value]'" input-type="checkbox" :items='matchedAttribute.options' :behavior="'no'"></tree-view>
</div>
<div v-else>
<div class="control-group" v-if="matchedAttribute.type == 'text' || matchedAttribute.type == 'price' || matchedAttribute.type == 'decimal' || matchedAttribute.type == 'integer'">
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'date'">
<date>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</date>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'datetime'">
<datetime>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</datetime>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'boolean'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value">
<option value="1">{{ __('admin::app.promotions.cart-rules.yes') }}</option>
<option value="0">{{ __('admin::app.promotions.cart-rules.no') }}</option>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'select' || matchedAttribute.type == 'radio'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-if="matchedAttribute.key != 'cart|state'">
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-else>
<optgroup v-for='option in matchedAttribute.options' :label="option.admin_name">
<option v-for='state in option.states' :value="state.code">
@{{ state.admin_name }}
</option>
</optgroup>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'multiselect' || matchedAttribute.type == 'checkbox'">
<select :name="['conditions[' + index + '][value][]']" class="control" v-model="condition.value" multiple>
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
</div>
</div>
</div>
</td>
<td class="actions">
<i class="icon trash-icon" @click="removeCondition"></i>
</td>
</tr>
</script>
<script>
Vue.component('cart-rule', {
template: '#cart-rule-template',
inject: ['$validator'],
data: function() {
return {
coupon_type: 0,
use_auto_generation: 0,
condition_type: 1,
conditions: [],
action_type: "{{ old('action_type') ?: 'by_percent' }}"
}
},
methods: {
addCondition: function() {
this.conditions.push({
'attribute': '',
'operator': '==',
'value': '',
});
},
removeCondition: function(condition) {
let index = this.conditions.indexOf(condition)
this.conditions.splice(index, 1)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
redirectBack: function(fallbackUrl) {
this.$root.redirectBack(fallbackUrl)
}
}
});
Vue.component('cart-rule-condition-item', {
template: '#cart-rule-condition-item-template',
props: ['index', 'condition'],
data: function() {
return {
condition_attributes: @json(app('\Webkul\CartRule\Repositories\CartRuleRepository')->getConditionAttributes()),
attribute_type_indexes: {
'cart': 0,
'cart_item': 1,
'product': 2
},
condition_operators: {
'price': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'decimal': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'integer': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'text': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contain') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}],
'boolean': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'date': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'datetime': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'select': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'radio': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'multiselect': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}],
'checkbox': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}]
}
}
},
computed: {
matchedAttribute: function () {
if (this.condition.attribute == '')
return;
var this_this = this;
var attributeIndex = this.attribute_type_indexes[this.condition.attribute.split("|")[0]];
matchedAttribute = this.condition_attributes[attributeIndex]['children'].filter(function (attribute) {
return attribute.key == this_this.condition.attribute;
});
if (matchedAttribute[0]['type'] == 'multiselect' || matchedAttribute[0]['type'] == 'checkbox') {
this.condition.operator = '{}';
this.condition.value = [];
}
return matchedAttribute[0];
}
},
methods: {
removeCondition: function() {
this.$emit('onRemoveCondition', this.condition)
}
}
});
</script>
@endpush

View File

@ -0,0 +1,753 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotions.cart-rules.edit-title') }}
@stop
@section('content')
<div class="content">
<cart-rule></cart-rule>
</div>
@stop
@push('scripts')
@parent
<script type="text/x-template" id="cart-rule-template">
<div>
<form method="POST" action="{{ route('admin.cart-rules.update', $cartRule->id) }}" @submit.prevent="onSubmit">
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" @click="redirectBack('{{ url('/admin/dashboard') }}')"></i>
{{ __('admin::app.promotions.cart-rules.edit-title') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotions.cart-rules.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
{!! view_render_event('bagisto.admin.promotions.cart-rules.create.before') !!}
<accordian :title="'{{ __('admin::app.promotions.cart-rules.rule-information') }}'" :active="true">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotions.cart-rules.name') }}</label>
<input v-validate="'required'" class="control" id="name" name="name" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.name') }}&quot;" value="{{ old('name') ?: $cartRule->name }}"/>
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group">
<label for="description">{{ __('admin::app.promotions.cart-rules.description') }}</label>
<textarea class="control" id="description" name="description">{{ old('description') ?: $cartRule->description }}</textarea>
</div>
<div class="control-group">
<label for="status">{{ __('admin::app.promotions.cart-rules.status') }}</label>
<span class="checkbox">
<input type="checkbox" id="status" name="status" value="{{ $cartRule->status }}" {{ $cartRule->status ? 'checked' : '' }}>
<label class="checkbox-view" for="status"></label>
{{ __('admin::app.promotions.cart-rules.is-active') }}
</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotions.cart-rules.channels') }}</label>
<?php $selectedOptionIds = old('channels') ?: $cartRule->channels->pluck('id')->toArray() ?>
<select class="control" id="channels" name="channels[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.channels') }}&quot;" multiple="multiple">
@foreach(core()->getAllChannels() as $channel)
<option value="{{ $channel->id }}" {{ in_array($channel->id, $selectedOptionIds) ? 'selected' : '' }}>
{{ $channel->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotions.cart-rules.customer-groups') }}</label>
<?php $selectedOptionIds = old('customer_groups') ?: $cartRule->customer_groups->pluck('id')->toArray() ?>
<select class="control" id="customer_groups" name="customer_groups[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.customer-groups') }}&quot;" multiple="multiple">
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $customerGroup)
<option value="{{ $customerGroup->id }}" {{ in_array($customerGroup->id, $selectedOptionIds) ? 'selected' : '' }}>
{{ $customerGroup->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('coupon_type') ? 'has-error' : '']">
<label for="coupon_type" class="required">{{ __('admin::app.promotions.cart-rules.coupon-type') }}</label>
<select class="control" id="coupon_type" name="coupon_type" v-model="coupon_type" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.coupon-type') }}&quot;">
<option value="0" {{ old('coupon_type') == 0 ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.no-coupon') }}</option>
<option value="1" {{ old('coupon_type') == 1 ? 'selected' : '' }}>{{ __('admin::app.promotions.cart-rules.specific-coupon') }}</option>
</select>
<span class="control-error" v-if="errors.has('coupon_type')">@{{ errors.first('coupon_type') }}</span>
</div>
<div v-if="parseInt(coupon_type)">
<div class="control-group" :class="[errors.has('use_auto_generation') ? 'has-error' : '']">
<label for="use_auto_generation" class="required">{{ __('admin::app.promotions.cart-rules.auto-generate-coupon') }}</label>
<select class="control" id="use_auto_generation" name="use_auto_generation" v-model="use_auto_generation" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.auto-generate-coupon') }}&quot;">
<option value="0">{{ __('admin::app.promotions.cart-rules.no') }}</option>
<option value="1">{{ __('admin::app.promotions.cart-rules.yes') }}</option>
</select>
<span class="control-error" v-if="errors.has('use_auto_generation')">@{{ errors.first('use_auto_generation') }}</span>
</div>
<div v-if="! parseInt(use_auto_generation)">
<div class="control-group" :class="[errors.has('coupon_code') ? 'has-error' : '']">
<label for="coupon_code" class="required">{{ __('admin::app.promotions.cart-rules.coupon-code') }}</label>
<input v-validate="'required'" class="control" id="coupon_code" name="coupon_code" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.coupon-code') }}&quot;" value="{{ old('coupon_code') ?: $cartRule->coupon_code }}"/>
<span class="control-error" v-if="errors.has('coupon_code')">@{{ errors.first('coupon_code') }}</span>
</div>
</div>
<div class="control-group">
<label for="uses_per_coupon">{{ __('admin::app.promotions.cart-rules.uses-per-coupon') }}</label>
<input class="control" id="uses_per_coupon" name="uses_per_coupon" value="{{ old('uses_per_coupon') ?: $cartRule->uses_per_coupon }}"/>
</div>
</div>
<div class="control-group">
<label for="usage_per_customer">{{ __('admin::app.promotions.cart-rules.uses-per-customer') }}</label>
<input class="control" id="usage_per_customer" name="usage_per_customer" value="{{ old('usage_per_customer') ?: $cartRule->usage_per_customer }}"/>
<span class="control-info">{{ __('admin::app.promotions.cart-rules.uses-per-customer-control-info') }}</span>
</div>
<div class="control-group date">
<label for="starts_from">{{ __('admin::app.promotions.cart-rules.from') }}</label>
<date>
<input type="text" name="starts_from" class="control" value="{{ old('starts_from') ?: $cartRule->starts_from }}"/>
</date>
</div>
<div class="control-group date">
<label for="ends_till">{{ __('admin::app.promotions.cart-rules.to') }}</label>
<date>
<input type="text" name="ends_till" class="control" value="{{ old('ends_till') ?: $cartRule->ends_till }}"/>
</date>
</div>
<div class="control-group">
<label for="sort_order">{{ __('admin::app.promotions.cart-rules.priority') }}</label>
<input type="text" class="control" id="sort_order" name="sort_order" value="{{ $cartRule->sort_order }}" {{ $cartRule->sort_order ? 'checked' : '' }}/>
</div>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.cart-rules.conditions') }}'" :active="false">
<div slot="body">
<div class="control-group">
<label for="condition_type">{{ __('admin::app.promotions.cart-rules.condition-type') }}</label>
<select class="control" id="condition_type" name="condition_type" v-model="condition_type">
<option value="1">{{ __('admin::app.promotions.cart-rules.all-conditions-true') }}</option>
<option value="2">{{ __('admin::app.promotions.cart-rules.any-condition-true') }}</option>
</select>
</div>
<div class="table cart-rule-conditions" style="margin-top: 20px; overflow-x: unset;">
<table>
<tbody>
<cart-rule-condition-item
v-for='(condition, index) in conditions'
:condition="condition"
:key="index"
:index="index"
@onRemoveCondition="removeCondition($event)">
</cart-rule-condition-item>
</tbody>
</table>
</div>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px;" @click="addCondition">
{{ __('admin::app.promotions.cart-rules.add-condition') }}
</button>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.cart-rules.actions') }}'" :active="false">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotions.cart-rules.action-type') }}</label>
<?php $selectedOption = old('action_type') ?: $cartRule->action_type ?>
<select class="control" id="action_type" name="action_type" v-validate="'required'" v-model="action_type" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.action-type') }}&quot;">
<option value="by_percent" {{ $selectedOption == 'by_percent' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.percentage-product-price') }}
</option>
<option value="by_fixed" {{ $selectedOption == 'by_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.fixed-amount') }}
</option>
<option value="cart_fixed" {{ $selectedOption == 'cart_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.fixed-amount-whole-cart') }}
</option>
<option value="buy_x_get_y" {{ $selectedOption == 'buy_x_get_y' ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.buy-x-get-y-free') }}
</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('discount_amount') ? 'has-error' : '']">
<label for="discount_amount" class="required">{{ __('admin::app.promotions.cart-rules.discount-amount') }}</label>
<input v-validate="'required'" class="control" id="discount_amount" name="discount_amount" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.discount-amount') }}&quot;" value="{{ old('discount_amount') ?: $cartRule->discount_amount }}"/>
<span class="control-error" v-if="errors.has('discount_amount')">@{{ errors.first('discount_amount') }}</span>
</div>
<div class="control-group">
<label for="discount_quantity">{{ __('admin::app.promotions.cart-rules.discount-quantity') }}</label>
<input class="control" id="discount_quantity" name="discount_quantity" value="{{ old('discount_quantity') ?: $cartRule->discount_quantity }}"/>
</div>
<div class="control-group">
<label for="discount_step">{{ __('admin::app.promotions.cart-rules.discount-step') }}</label>
<input class="control" id="discount_step" name="discount_step" value="{{ old('discount_step') ?: $cartRule->discount_step }}"/>
</div>
<div class="control-group">
<label for="apply_to_shipping">{{ __('admin::app.promotions.cart-rules.apply-to-shipping') }}</label>
<?php $selectedOption = old('apply_to_shipping') ?: $cartRule->apply_to_shipping ?>
<select class="control" id="apply_to_shipping" name="apply_to_shipping" :disabled="action_type == 'cart_fixed'">
<option value="0" {{ ! $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.no') }}
</option>
<option value="1" {{ $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.yes') }}
</option>
</select>
</div>
<div class="control-group">
<label for="free_shipping">{{ __('admin::app.promotions.cart-rules.free-shipping') }}</label>
<?php $selectedOption = old('free_shipping') ?: $cartRule->free_shipping ?>
<select class="control" id="free_shipping" name="free_shipping">
<option value="0" {{ ! $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.no') }}
</option>
<option value="1" {{ $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.yes') }}
</option>
</select>
</div>
<div class="control-group">
<label for="end_other_rules">{{ __('admin::app.promotions.cart-rules.end-other-rules') }}</label>
<?php $selectedOption = old('end_other_rules') ?: $cartRule->end_other_rules ?>
<select class="control" id="end_other_rules" name="end_other_rules">
<option value="0" {{ ! $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.no') }}
</option>
<option value="1" {{ $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.cart-rules.yes') }}
</option>
</select>
</div>
</div>
</accordian>
{!! view_render_event('bagisto.admin.promotions.cart-rules.create.after') !!}
</div>
</div>
</form>
<accordian :title="'{{ __('admin::app.promotions.cart-rules.coupon-codes') }}'" :active="false" v-if="coupon_type && use_auto_generation">
<div slot="body">
<create-coupon-form></create-coupon-form>
</div>
</accordian>
</div>
</script>
<script type="text/x-template" id="cart-rule-condition-item-template">
<tr>
<td class="attribute">
<div class="control-group">
<select :name="['conditions[' + index + '][attribute]']" class="control" v-model="condition.attribute">
<option value="">{{ __('admin::app.promotions.cart-rules.choose-condition-to-add') }}</option>
<optgroup v-for='conditionAttribute in condition_attributes' :label="conditionAttribute.label">
<option v-for='childAttribute in conditionAttribute.children' :value="childAttribute.key">
@{{ childAttribute.label }}
</option>
</optgroup>
</select>
</div>
</td>
<td class="operator">
<div class="control-group" v-if="matchedAttribute">
<select :name="['conditions[' + index + '][operator]']" class="control" v-model="condition.operator">
<option v-for='operator in condition_operators[matchedAttribute.type]' :value="operator.operator">
@{{ operator.label }}
</option>
</select>
</div>
</td>
<td class="value">
<div v-if="matchedAttribute">
<input type="hidden" :name="['conditions[' + index + '][attribute_type]']" v-model="matchedAttribute.type">
<div v-if="matchedAttribute.key == 'product|children::category_ids' || matchedAttribute.key == 'product|category_ids' || matchedAttribute.key == 'product|parent::category_ids'">
<tree-view value-field="id" id-field="id" :name-field="'conditions[' + index + '][value]'" input-type="checkbox" :items='matchedAttribute.options' :value='condition.value' :behavior="'no'"></tree-view>
</div>
<div v-else>
<div class="control-group" v-if="matchedAttribute.type == 'text' || matchedAttribute.type == 'price' || matchedAttribute.type == 'decimal' || matchedAttribute.type == 'integer'">
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'date'">
<date>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</date>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'datetime'">
<datetime>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</datetime>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'boolean'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value">
<option value="1">{{ __('admin::app.promotions.cart-rules.yes') }}</option>
<option value="0">{{ __('admin::app.promotions.cart-rules.no') }}</option>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'select' || matchedAttribute.type == 'radio'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-if="matchedAttribute.key != 'cart|state'">
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-else>
<optgroup v-for='option in matchedAttribute.options' :label="option.admin_name">
<option v-for='state in option.states' :value="state.code">
@{{ state.admin_name }}
</option>
</optgroup>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'multiselect' || matchedAttribute.type == 'checkbox'">
<select :name="['conditions[' + index + '][value][]']" class="control" v-model="condition.value" multiple>
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
</div>
</div>
</div>
</td>
<td class="actions">
<i class="icon trash-icon" @click="removeCondition"></i>
</td>
</tr>
</script>
<script type="text/x-template" id="create-coupon-form-template">
<div class="">
<form method="POST" data-vv-scope="create-coupun-form" @submit.prevent="generateCopuns('create-coupun-form')">
<div class="control-group" :class="[errors.has('create-coupun-form.coupon_qty') ? 'has-error' : '']">
<label for="coupon_qty" class="required">{{ __('admin::app.promotions.cart-rules.coupon-qty') }}</label>
<input v-validate="'required|min_value:1'" class="control" id="coupon_qty" name="coupon_qty" v-model="coupon_format.coupon_qty" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.coupon-qty') }}&quot;"/>
<span class="control-error" v-if="errors.has('create-coupun-form.coupon_qty')">
@{{ errors.first('create-coupun-form.coupon_qty') }}
</span>
</div>
<div class="control-group" :class="[errors.has('create-coupun-form.code_length') ? 'has-error' : '']">
<label for="code_length" class="required">{{ __('admin::app.promotions.cart-rules.code-length') }}</label>
<input v-validate="'required|min_value:10'" class="control" id="code_length" name="code_length" v-model="coupon_format.code_length" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.code-length') }}&quot;"/>
<span class="control-error" v-if="errors.has('create-coupun-form.code_length')">
@{{ errors.first('create-coupun-form.code_length') }}
</span>
</div>
<div class="control-group" :class="[errors.has('create-coupun-form.code_format') ? 'has-error' : '']">
<label for="code_format" class="required">{{ __('admin::app.promotions.cart-rules.code-format') }}</label>
<select class="control" id="code_format" name="code_format" v-model="coupon_format.code_format" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.cart-rules.code-format') }}&quot;">
<option value="alphanumeric">{{ __('admin::app.promotions.cart-rules.alphanumeric') }}</option>
<option value="alphabetical">{{ __('admin::app.promotions.cart-rules.alphabetical') }}</option>
<option value="numeric">{{ __('admin::app.promotions.cart-rules.numeric') }}</option>
</select>
<span class="control-error" v-if="errors.has('create-coupun-form.code_format')">
@{{ errors.first('create-coupun-form.code_format') }}
</span>
</div>
<div class="control-group">
<label for="code_prefix">{{ __('admin::app.promotions.cart-rules.code-prefix') }}</label>
<input class="control" id="code_prefix" name="code_prefix" v-model="coupon_format.code_prefix"/>
</div>
<div class="control-group">
<label for="code_suffix">{{ __('admin::app.promotions.cart-rules.code-suffix') }}</label>
<input class="control" id="code_suffix" name="code_suffix" v-model="coupon_format.code_suffix"/>
</div>
<div class="button-group">
<button class="btn btn-xl btn-primary">{{ __('admin::app.promotions.cart-rules.generate') }}</button>
</div>
</form>
@inject('cartRuleCouponGrid','Webkul\Admin\DataGrids\CartRuleCouponDataGrid')
{!! $cartRuleCouponGrid->render() !!}
</div>
</script>
<script>
Vue.component('cart-rule', {
template: '#cart-rule-template',
inject: ['$validator'],
data: function() {
return {
coupon_type: {{ old('coupon_type') ?: $cartRule->coupon_type }},
use_auto_generation: {{ old('use_auto_generation') ?: $cartRule->use_auto_generation }},
condition_type: {{ old('condition_type') ?: $cartRule->condition_type }},
conditions: @json($cartRule->conditions ?: []),
action_type: "{{ old('action_type') ?: $cartRule->action_type }}"
}
},
methods: {
addCondition: function() {
this.conditions.push({
'attribute': '',
'operator': '==',
'value': '',
});
},
removeCondition: function(condition) {
let index = this.conditions.indexOf(condition)
this.conditions.splice(index, 1)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
redirectBack: function(fallbackUrl) {
this.$root.redirectBack(fallbackUrl)
}
}
});
Vue.component('cart-rule-condition-item', {
template: '#cart-rule-condition-item-template',
props: ['index', 'condition'],
data: function() {
return {
condition_attributes: @json(app('\Webkul\CartRule\Repositories\CartRuleRepository')->getConditionAttributes()),
attribute_type_indexes: {
'cart': 0,
'cart_item': 1,
'product': 2
},
condition_operators: {
'price': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'decimal': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'integer': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'text': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contain') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}],
'boolean': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'date': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'datetime': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.cart-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.cart-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.cart-rules.less-than') }}'
}],
'select': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'radio': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.cart-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.cart-rules.is-not-equal-to') }}'
}],
'multiselect': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}],
'checkbox': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.cart-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.cart-rules.does-not-contain') }}'
}]
}
}
},
computed: {
matchedAttribute: function () {
if (this.condition.attribute == '')
return;
var this_this = this;
var attributeIndex = this.attribute_type_indexes[this.condition.attribute.split("|")[0]];
matchedAttribute = this.condition_attributes[attributeIndex]['children'].filter(function (attribute) {
return attribute.key == this_this.condition.attribute;
});
if (matchedAttribute[0]['type'] == 'multiselect' || matchedAttribute[0]['type'] == 'checkbox') {
this.condition.operator = '{}';
this.condition.value = this.condition.value == '' && this.condition.value != undefined ? [] : this.condition.value;
}
return matchedAttribute[0];
}
},
methods: {
removeCondition: function() {
this.$emit('onRemoveCondition', this.condition)
}
}
});
Vue.component('create-coupon-form', {
template: '#create-coupon-form-template',
inject: ['$validator'],
data: function() {
return {
coupon_format: {
coupon_qty: '',
code_length: 12,
code_format: 'alphanumeric',
code_prefix: '',
code_suffix: ''
}
}
},
methods: {
generateCopuns: function(formScope) {
var this_this = this;
this.$validator.validateAll(formScope).then(function (result) {
if (result) {
this_this.$http.post("{{ route('admin.cart-rules.generate-coupons', $cartRule->id) }}", this_this.coupon_format)
.then(function(response) {
window.flashMessages = [{
'type': 'alert-success',
'message': response.data.message
}];
this_this.$root.addFlashMessages()
})
.catch(function (error) {
window.flashMessages = [{
'type': 'alert-error',
'message': error.response.data.message
}];
this_this.$root.addFlashMessages()
})
}
});
}
}
});
</script>
@endpush

View File

@ -1,7 +1,7 @@
@extends('admin::layouts.content') @extends('admin::layouts.content')
@section('page_title') @section('page_title')
{{ __('admin::app.promotion.cart-rule') }} {{ __('admin::app.promotions.cart-rules.title') }}
@stop @stop
@section('content') @section('content')
@ -9,12 +9,12 @@
<div class="content"> <div class="content">
<div class="page-header"> <div class="page-header">
<div class="page-title"> <div class="page-title">
<h1>{{ __('admin::app.promotion.cart-rule') }}</h1> <h1>{{ __('admin::app.promotions.cart-rules.title') }}</h1>
</div> </div>
<div class="page-action"> <div class="page-action">
<a href="{{ route('admin.cart-rule.create') }}" class="btn btn-lg btn-primary"> <a href="{{ route('admin.cart-rules.create') }}" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.add-cart-rule') }} {{ __('admin::app.promotions.cart-rules.add-title') }}
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,415 +0,0 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotion.add-catalog-rule') }}
@stop
@section('content')
<div class="content">
<catalog-rule></catalog-rule>
</div>
@push('scripts')
<script type="text/x-template" id="catalog-rule-form-template">
<div>
<form method="POST" action="{{ route('admin.catalog-rule.store') }}" @submit.prevent="onSubmit">
@csrf
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" onclick="history.length > 1 ? history.go(-1) : window.location = '{{ url('/admin/dashboard') }}';"></i>
{{ __('admin::app.promotion.add-catalog-rule') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
<accordian :active="true" title="{{ __('admin::app.promotion.information') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotion.general-info.name') }}</label>
<input type="text" class="control" name="name" v-model="name" v-validate="'required'" value="{{ old('name') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.name') }}&quot;">
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group" :class="[errors.has('description') ? 'has-error' : '']">
<label for="description" class="required">{{ __('admin::app.promotion.general-info.description') }}</label>
<textarea class="control" name="description" v-model="description" v-validate="'required'" value="{{ old('description') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.description') }}&quot;"></textarea>
<span class="control-error" v-if="errors.has('description')">@{{ errors.first('description') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.cust-groups') }}</label>
<select type="text" class="control" name="customer_groups[]" v-model="customer_groups" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.cust-groups') }}&quot;" multiple="multiple">
<option disabled="disabled">Select Customer Groups</option>
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotion.general-info.channels') }}</label>
<select type="text" class="control" name="channels[]" v-model="channels" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.channels') }}&quot;" multiple="multiple">
<option disabled="disabled">Select Channels</option>
@foreach(app('Webkul\Core\Repositories\ChannelRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('status') }}</span>
</div>
<div class="control-group" :class="[errors.has('status') ? 'has-error' : '']">
<label for="status" class="required">{{ __('admin::app.promotion.general-info.status') }}</label>
<select type="text" class="control" name="status" v-model="status" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.status') }}&quot;">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option value="1">{{ __('admin::app.promotion.yes') }}</option>
<option value="0">{{ __('admin::app.promotion.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('status')">@{{ errors.first('status') }}</span>
</div>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('starts_from') ? 'has-error' : '']">
<label for="starts_from">{{ __('admin::app.promotion.general-info.starts-from') }}</label>
<input type="text" class="control" v-model="starts_from" name="starts_from" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.starts-from') }}&quot;">
<span class="control-error" v-if="errors.has('starts_from')">@{{ errors.first('starts_from') }}</span>
</div>
</datetime>
<datetime :name="starts_from">
<div class="control-group" :class="[errors.has('ends_till') ? 'has-error' : '']">
<label for="ends_till">{{ __('admin::app.promotion.general-info.ends-till') }}</label>
<input type="text" class="control" v-model="ends_till" name="ends_till" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.ends-till') }}&quot;">
<span class="control-error" v-if="errors.has('ends_till')">@{{ errors.first('ends_till') }}</span>
</div>
</datetime>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.conditions') }}">
<div slot="body">
<input type="hidden" name="all_conditions" v-model="all_conditions">
<!-- Categories selection input block -->
<div class="control-group" :class="[errors.has('category_values') ? 'has-error' : '']">
<label class="mb-10" for="categories">{{ __('admin::app.promotion.select-category') }}</label>
<multiselect v-model="category_values" :close-on-select="false" :options="category_options" :searchable="false" :custom-label="categoryLabel" :show-labels="true" placeholder="Select Categories" track-by="slug" :multiple="true"></multiselect>
</div>
<label class="mb-10" for="attributes">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Attribute']) }}</label>
<br/>
<!-- Product attributes conditions block -->
<div class="control-container mt-20" v-for="(condition, index) in attribute_values" :key="index">
<select class="control" v-model="attribute_values[index].attribute" style="margin-right: 15px; width: 30%;" v-on:change="enableAttributeCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option v-for="(attr_ip, index1) in attribute_input" :value="attr_ip.code" :key="index1">@{{ attr_ip.name }}</option>
</select>
<select v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.select" :value="index" :key="index">@{{ condition }}</option>
</select>
<select v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price"' class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' style="display: flex;">
<select class="control" v-model="attribute_values[index].value" style="margin-right: 15px; height: 100px" :multiple="true">
<option :disabled="true">
{{ __('ui::form.select-attribute', ['attribute' => 'Values']) }}
</option>
<option v-for="(label, index2) in attribute_values[index].options" :value="index2" :key="index2">@{{ label.admin_name }}</option>
</select>
{{-- <multiselect v-model="attribute_values[index].value" :close-on-select="false" :options="attribute_values[index].options" :searchable="false" :track-by="admin_name" :custom-label="attributeListLabel" :multiple="true" ></multiselect> --}}
</div>
<div v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price" || attribute_values[index].type == "textarea"' style="display: flex">
<input class="control" v-model="attribute_values[index].value" type="text" placeholder="{{ __('ui::form.enter-attribute', ['attribute' => 'Text']) }}">
</div>
<span class="icon trash-icon" v-on:click="removeAttr(index)"></span>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addAttributeCondition">{{ __('admin::app.promotion.add-attr-condition') }}</span>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.actions') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotion.general-info.apply') }}</label>
<select class="control" name="action_type" v-model="action_type" v-validate="'required'" value="{{ old('action_type') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.apply') }}&quot;" v-on:change="detectApply">
<option v-for="(action, index) in actions" :value="index">@{{ action }}</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('disc_amount') ? 'has-error' : '']">
<label for="disc_amount" class="required">{{ __('admin::app.promotion.general-info.disc_amt') }}</label>
<input type="number" step="0.5000" class="control" name="disc_amount" v-model="disc_amount" v-validate="'required|min_value:0.0001'" value="{{ old('disc_amount') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_amt') }}&quot;">
<span class="control-error" v-if="errors.has('disc_amount')">@{{ errors.first('disc_amount') }}</span>
</div>
<div class="control-group" :class="[errors.has('end_other_rules') ? 'has-error' : '']">
<label for="end_other_rules" class="required">{{ __('admin::app.promotion.general-info.end-other-rules') }}</label>
<select type="text" class="control" name="end_other_rules" v-model="end_other_rules" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.end-other-rules') }}&quot;">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option value="1">{{ __('admin::app.promotion.yes') }}</option>
<option value="0">{{ __('admin::app.promotion.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('end_other_rules')">@{{ errors.first('end_other_rules') }}</span>
</div>
</div>
</accordian>
</div>
</div>
</form>
</div>
</script>
<script>
Vue.component('catalog-rule', {
template: '#catalog-rule-form-template',
inject: ['$validator'],
data () {
return {
name: null,
description: null,
conditions_list: [],
channels: [],
customer_groups: [],
ends_till: null,
starts_from: null,
per_customer: 0,
status: 1,
action_type: null,
apply: null,
apply_amt: false,
disc_amount: null,
end_other_rules: 0,
all_conditions: [],
all_attributes: {
'categories' : [],
'attributes' : []
},
criteria: 'cart',
actions: @json($catalog_rule[0]).actions,
category_options: @json($catalog_rule[1]),
category_values: [],
conditions: @json($catalog_rule[0]).conditions,
attribute_values: [],
attr_object: {
attribute: null,
condition: null,
value: [],
options: []
},
attribute_input: @json($catalog_rule[3])
}
},
mounted () {
},
methods: {
categoryLabel (option) {
return option.name + ' [ ' + option.slug + ' ]';
},
attributeListLabel(option) {
return option.label;
},
addCondition () {
if (this.criteria == 'attribute' || this.criteria == 'attribute_family' || this.criteria == 'category') {
this.condition_on = this.criteria;
} else {
alert('please try again');
return false;
}
if (this.condition_on == 'attribute') {
this.conditions_list.push(this.attr_object);
this.attr_object = {
criteria: this.condition_on,
attribute: null,
condition: null,
value: null,
type: null,
options: null
};
} else if (this.condition_on == 'category') {
this.conditions_list.push(this.cat_object);
this.cat_object = {
criteria: this.condition_on,
category: 'category',
condition: null,
value: []
};
} else if (this.condition_on == 'attribute_family') {
this.conditions_list.push(this.fam_object);
this.fam_object = {
criteria: this.condition_on,
family: 'attribute_family',
condition: null,
value: null
};
}
},
addAttributeCondition() {
this.attribute_values.push(this.attr_object);
this.attr_object = {
attribute: null,
condition: null,
value: [],
options: []
};
},
enableCondition(event, index) {
this.conditions_list[index].type = this.attrs_input[event.target.selectedIndex - 1].type;
var this_this = this;
if (this.attrs_input[event.target.selectedIndex - 1].type == 'select' || this.attrs_input[event.target.selectedIndex - 1].type == 'multiselect') {
this.conditions_list[index].options = this.attrs_options[this.attrs_input[event.target.selectedIndex - 1].code];
this.conditions_list[index].value = [];
}
},
enableAttributeCondition (event, index) {
selectedIndex = event.target.selectedIndex - 1;
for(i in this.attribute_input) {
if (i == selectedIndex) {
if (this.attribute_input[i].has_options == true) {
this.selectedIndex
this.attribute_values[index].options = this.attribute_input[i].options;
}
this.attribute_values[index].type = this.attribute_input[i].type;
}
}
},
detectApply() {
if (this.apply == 0 || this.apply == 2) {
this.apply_prct = true;
this.apply_amt = false;
} else if (this.apply == 1 || this.apply == 3) {
this.apply_prct = false;
this.apply_amt = true;
}
},
removeAttr(index) {
this.attribute_values.splice(index, 1);
},
removeCat(index) {
this.cats.splice(index, 1);
},
onSubmit: function (e) {
if (this.attribute_values.length > 0 || this.category_values.length > 0) {
for (i in this.attribute_values) {
delete this.attribute_values[i].options;
}
if (this.category_values.length > 0) {
this.all_attributes.categories = this.category_values;
}
if (this.attribute_values.length > 0) {
this.all_attributes.attributes = this.attribute_values;
}
}
this.all_conditions = JSON.stringify(this.all_attributes);
// this.all_conditions = JSON.stringify(this.conditions_list);
// if (this.conditions_list.length != 0) {
// this.conditions_list.push({'criteria': this.match_criteria});
// this.all_conditions = JSON.stringify(this.conditions_list);
// }
// return false;
this.$validator.validateAll().then(result => {
if (result) {
e.target.submit();
}
});
},
genericGroupCondition() {
this.generic_condition = false;
},
addFlashMessages() {
const flashes = this.$refs.flashes;
flashMessages.forEach(function(flash) {
flashes.addFlash(flash);
}, this);
}
}
});
</script>
@endpush
@stop

View File

@ -1,414 +0,0 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotion.edit-catalog-rule') }}
@stop
@section('content')
<div class="content">
<catalog-rule></catalog-rule>
</div>
@push('scripts')
<script type="text/x-template" id="catalog-rule-form-template">
<div>
<form method="POST" action="{{ route('admin.catalog-rule.update', $catalog_rule[5]->id) }}" @submit.prevent="onSubmit">
@csrf
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" onclick="history.length > 1 ? history.go(-1) : window.location = '{{ url('/admin/dashboard') }}';"></i>
{{ __('admin::app.promotion.edit-catalog-rule') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.edit-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
<accordian :active="true" title="{{ __('admin::app.promotion.information') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotion.general-info.name') }}</label>
<input type="text" class="control" name="name" v-model="name" v-validate="'required'" value="{{ old('name') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.name') }}&quot;">
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group" :class="[errors.has('description') ? 'has-error' : '']">
<label for="description" class="required">{{ __('admin::app.promotion.general-info.description') }}</label>
<textarea class="control" name="description" v-model="description" v-validate="'required'" value="{{ old('description') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.description') }}&quot;"></textarea>
<span class="control-error" v-if="errors.has('description')">@{{ errors.first('description') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotion.general-info.cust-groups') }}</label>
<select type="text" class="control" name="customer_groups[]" v-model="customer_groups" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.cust-groups') }}&quot;" multiple="multiple">
<option disabled="disabled">Select Customer Groups</option>
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotion.general-info.channels') }}</label>
<select type="text" class="control" name="channels[]" v-model="channels" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.channels') }}&quot;" multiple="multiple">
<option disabled="disabled">Select Channels</option>
@foreach(app('Webkul\Core\Repositories\ChannelRepository')->all() as $channel)
<option value="{{ $channel->id }}">{{ $channel->name }}</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('status') }}</span>
</div>
<div class="control-group" :class="[errors.has('status') ? 'has-error' : '']">
<label for="status" class="required">{{ __('admin::app.promotion.general-info.status') }}</label>
<select type="text" class="control" name="status" v-model="status" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.status') }}&quot;">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option value="1">{{ __('admin::app.promotion.yes') }}</option>
<option value="0">{{ __('admin::app.promotion.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('status')">@{{ errors.first('status') }}</span>
</div>
@php
$now = new \Carbon\Carbon();
@endphp
<date :name="starts_from">
<div class="control-group" :class="[errors.has('starts_from') ? 'has-error' : '']">
<label for="starts_from">{{ __('admin::app.promotion.general-info.starts-from') }}</label>
<input type="text" class="control" v-model="starts_from" name="starts_from" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.starts-from') }}&quot;">
<span class="control-error" v-if="errors.has('starts_from')">@{{ errors.first('starts_from') }}</span>
</div>
</date>
<date :name="starts_from">
<div class="control-group" :class="[errors.has('ends_till') ? 'has-error' : '']">
<label for="ends_till">{{ __('admin::app.promotion.general-info.ends-till') }}</label>
<input type="text" class="control" v-model="ends_till" name="ends_till" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.ends-till') }}&quot;">
<span class="control-error" v-if="errors.has('ends_till')">@{{ errors.first('ends_till') }}</span>
</div>
</date>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.conditions') }}">
<div slot="body">
<input type="hidden" name="all_conditions" v-model="all_conditions">
<!-- Categories selection input block -->
<div class="control-group" :class="[errors.has('category_values') ? 'has-error' : '']">
<label class="mb-10" for="categories">{{ __('admin::app.promotion.select-category') }}</label>
<multiselect v-model="category_values" :close-on-select="false" :options="category_options" :searchable="false" :custom-label="categoryLabel" :show-labels="true" placeholder="Select Categories" track-by="slug" :multiple="true"></multiselect>
</div>
<label class="mb-10" for="attributes">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Attribute']) }}</label>
<br/>
<!-- Product attributes conditions block -->
<div class="control-container mt-20" v-for="(condition, index) in attribute_values" :key="index">
<select class="control" v-model="attribute_values[index].attribute" style="margin-right: 15px; width: 30%;" v-on:change="enableAttributeCondition($event, index)">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option v-for="(attr_ip, index1) in attribute_input" :value="attr_ip.code" :key="index1">@{{ attr_ip.name }}</option>
</select>
<select v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.select" :value="index" :key="index">@{{ condition }}</option>
</select>
<select v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price"' class="control" v-model="attribute_values[index].condition" style="margin-right: 15px;">
<option v-for="(condition, index) in conditions.string" :value="index" :key="index">@{{ condition }}</option>
</select>
<div v-show='attribute_values[index].type == "select" || attribute_values[index].type == "multiselect"' style="display: flex;">
<select class="control" v-model="attribute_values[index].value" style="margin-right: 15px; height: 100px" :multiple="true">
<option :disabled="true">
{{ __('ui::form.select-attribute', ['attribute' => 'Values']) }}
</option>
<option v-for="(label, index2) in attribute_values[index].options" :value="index2" :key="index2">@{{ label.admin_name }}</option>
</select>
{{-- <multiselect v-model="attribute_values[index].value" :close-on-select="false" :options="attribute_values[index].options" :searchable="false" :track-by="admin_name" :custom-label="attributeListLabel" :multiple="true" ></multiselect> --}}
</div>
<div v-show='attribute_values[index].type == "text" || attribute_values[index].type == "textarea" || attribute_values[index].type == "price" || attribute_values[index].type == "textarea"' style="display: flex">
<input class="control" v-model="attribute_values[index].value" type="text" placeholder="{{ __('ui::form.enter-attribute', ['attribute' => 'Text']) }}">
</div>
<span class="icon trash-icon" v-on:click="removeAttr(index)"></span>
</div>
<span class="btn btn-primary btn-lg mt-20" v-on:click="addAttributeCondition">{{ __('admin::app.promotion.add-attr-condition') }}</span>
</div>
</accordian>
<accordian :active="false" title="{{ __('admin::app.promotion.actions') }}">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotion.general-info.apply') }}</label>
<select class="control" name="action_type" v-model="action_type" v-validate="'required'" value="{{ old('action_type') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.apply') }}&quot;" v-on:change="detectApply">
<option v-for="(action, index) in actions" :value="index">@{{ action }}</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('disc_amount') ? 'has-error' : '']">
<label for="disc_amount" class="required">{{ __('admin::app.promotion.general-info.disc_amt') }}</label>
<input type="number" step="0.5000" class="control" name="disc_amount" v-model="disc_amount" v-validate="'required|min_value:0.0001'" value="{{ old('disc_amount') }}" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.disc_amt') }}&quot;">
<span class="control-error" v-if="errors.has('disc_amount')">@{{ errors.first('disc_amount') }}</span>
</div>
<div class="control-group" :class="[errors.has('end_other_rules') ? 'has-error' : '']">
<label for="end_other_rules" class="required">{{ __('admin::app.promotion.general-info.end-other-rules') }}</label>
<select type="text" class="control" name="end_other_rules" v-model="end_other_rules" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotion.general-info.end-other-rules') }}&quot;">
<option disabled="disabled">{{ __('admin::app.promotion.select-attribute', ['attribute' => 'Option']) }}</option>
<option value="1">{{ __('admin::app.promotion.yes') }}</option>
<option value="0">{{ __('admin::app.promotion.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('end_other_rules')">@{{ errors.first('end_other_rules') }}</span>
</div>
</div>
</accordian>
</div>
</div>
</form>
</div>
</script>
<script>
Vue.component('catalog-rule', {
template: '#catalog-rule-form-template',
inject: ['$validator'],
data () {
return {
name: '{{ $catalog_rule[5]->name }}',
description: '{{ $catalog_rule[5]->description }}',
conditions_list: [],
channels: [],
customer_groups: [],
ends_till: '{{ $catalog_rule[5]->ends_till }}',
starts_from: '{{ $catalog_rule[5]->starts_from }}',
status: '{{ $catalog_rule[5]->status }}',
actions: @json($catalog_rule[3]).actions,
action_type: '{{ $catalog_rule[5]->action_code }}',
disc_amount: null,
end_other_rules: '{{ $catalog_rule[5]->end_other_rules }}',
all_conditions: [],
all_attributes: {
'categories' : [],
'attributes' : []
},
criteria: 'cart',
category_options: @json($catalog_rule[1]),
category_values: [],
conditions: @json($catalog_rule[3]).conditions,
attribute_values: [],
attr_object: {
attribute: null,
condition: null,
value: [],
options: []
},
attribute_input: @json($catalog_rule[0])
}
},
mounted () {
catalog_rule = @json($catalog_rule[5]);
channels = @json($catalog_rule[5]->channels);
this.channels = [];
for (i in channels) {
this.channels.push(channels[i].channel_id);
}
customer_groups = @json($catalog_rule[5]->customer_groups);
for (i in customer_groups) {
this.customer_groups.push(customer_groups[i].customer_group_id);
}
data = @json($catalog_rule[5]->conditions);
if (JSON.parse(JSON.parse(data))) {
this.category_values = JSON.parse(JSON.parse(data)).categories;
this.attribute_values = JSON.parse(JSON.parse(data)).attributes;
// creating options and has option param on the frontend
for (i in this.attribute_values) {
for (j in this.attribute_input) {
if (this.attribute_input[j].code == this.attribute_values[i].attribute) {
if (this.attribute_input[j].has_options == true) {
this.attribute_values[i].has_options = true;
this.attribute_values[i].options = this.attribute_input[j].options;
} else {
this.attribute_values[i].has_options = false;
this.attribute_values[i].options = null;
}
}
}
}
}
this.action_type = '{{ $catalog_rule[5]->action_code }}';
this.disc_amount = catalog_rule.discount_amount;
this.end_other_rules = '{{ $catalog_rule[5]->end_other_rules }}';
},
methods: {
created() {
VeeValidate.Validator.extend('is_time', {
getMessage: field => `The format must be HH:MM:SS`,
validate: (value) => new Promise(resolve => {
let regex = new RegExp("([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])");
resolve({
valid: value && regex.test(value)
});
})
});
},
categoryLabel (option) {
return option.name + ' [ ' + option.slug + ' ]';
},
attributeListLabel(option) {
return option.label;
},
addAttributeCondition() {
this.attribute_values.push(this.attr_object);
this.attr_object = {
attribute: null,
condition: null,
value: [],
options: []
};
},
enableAttributeCondition (event, index) {
selectedIndex = event.target.selectedIndex - 1;
for(i in this.attribute_input) {
if (i == selectedIndex) {
if (this.attribute_input[i].has_options == true) {
this.attribute_values[index].options = this.attribute_input[i].options;
}
this.attribute_values[index].type = this.attribute_input[i].type;
}
}
},
detectApply() {
if (this.apply == 0 || this.apply == 2) {
this.apply_prct = true;
this.apply_amt = false;
} else if (this.apply == 1 || this.apply == 3) {
this.apply_prct = false;
this.apply_amt = true;
}
},
removeAttr(index) {
this.attribute_values.splice(index, 1);
},
removeCat(index) {
this.cats.splice(index, 1);
},
onSubmit: function (e) {
if (this.attribute_values.length > 0 || this.category_values.length > 0) {
for (i in this.attribute_values) {
delete this.attribute_values[i].options;
}
if (this.category_values.length > 0) {
this.all_attributes.categories = this.category_values;
}
this.all_attributes.attributes = this.attribute_values;
}
this.all_conditions = JSON.stringify(this.all_attributes);
// this.all_conditions = JSON.stringify(this.conditions_list);
// if (this.conditions_list.length != 0) {
// this.conditions_list.push({'criteria': this.match_criteria});
// this.all_conditions = JSON.stringify(this.conditions_list);
// }
// return false;
this.$validator.validateAll().then(result => {
if (result) {
e.target.submit();
}
});
},
genericGroupCondition() {
this.generic_condition = false;
},
addFlashMessages() {
const flashes = this.$refs.flashes;
flashMessages.forEach(function(flash) {
flashes.addFlash(flash);
}, this);
}
}
});
</script>
@endpush
@stop

View File

@ -1,35 +0,0 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotion.catalog-rule') }}
@stop
@section('content')
<div class="content">
<div class="page-header">
<div class="page-title">
<h1>{{ __('admin::app.promotion.catalog-rule') }}</h1>
</div>
<div class="page-action">
<a href="{{ route('admin.catalog-rule.create') }}" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.add-catalog-rule') }}
</a>
<a href="{{ route('admin.catalog-rule.apply') }}" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.apply') }}
</a>
<a href="{{ route('admin.catalog-rule.declut') }}" class="btn btn-lg btn-primary">
{{ __('admin::app.promotion.declut') }}
</a>
</div>
</div>
<div class="page-content">
@inject('catalogRuleGrid','Webkul\Admin\DataGrids\CatalogRuleDataGrid')
{!! $catalogRuleGrid->render() !!}
</div>
</div>
@endsection

View File

@ -0,0 +1,528 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotions.catalog-rules.add-title') }}
@stop
@section('content')
<div class="content">
<catalog-rule></catalog-rule>
</div>
@stop
@push('scripts')
@parent
<script type="text/x-template" id="catalog-rule-template">
<div>
<form method="POST" action="{{ route('admin.catalog-rules.store') }}" @submit.prevent="onSubmit">
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" @click="redirectBack('{{ url('/admin/dashboard') }}')"></i>
{{ __('admin::app.promotions.catalog-rules.add-title') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotions.catalog-rules.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
{!! view_render_event('bagisto.admin.promotions.catalog-rules.create.before') !!}
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.rule-information') }}'" :active="true">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotions.catalog-rules.name') }}</label>
<input v-validate="'required'" class="control" id="name" name="name" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.name') }}&quot;" value="{{ old('name') }}"/>
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group">
<label for="description">{{ __('admin::app.promotions.catalog-rules.description') }}</label>
<textarea class="control" id="description" name="description">{{ old('description') }}</textarea>
</div>
<div class="control-group">
<label for="status">{{ __('admin::app.promotions.catalog-rules.status') }}</label>
<span class="checkbox">
<input type="checkbox" id="status" name="status" value="1">
<label class="checkbox-view" for="status"></label>
{{ __('admin::app.promotions.catalog-rules.is-active') }}
</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotions.catalog-rules.channels') }}</label>
<select class="control" id="channels" name="channels[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.channels') }}&quot;" multiple="multiple">
@foreach(core()->getAllChannels() as $channel)
<option value="{{ $channel->id }}" {{ old('channels') && in_array($channel->id, old('channels')) ? 'selected' : '' }}>
{{ $channel->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotions.catalog-rules.customer-groups') }}</label>
<select class="control" id="customer_groups" name="customer_groups[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.customer-groups') }}&quot;" multiple="multiple">
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $customerGroup)
<option value="{{ $customerGroup->id }}" {{ old('customer_groups') && in_array($customerGroup->id, old('customer_groups')) ? 'selected' : '' }}>
{{ $customerGroup->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group date">
<label for="starts_from">{{ __('admin::app.promotions.catalog-rules.from') }}</label>
<date>
<input type="text" name="starts_from" class="control" value="{{ old('starts_from') }}"/>
</date>
</div>
<div class="control-group date">
<label for="ends_till">{{ __('admin::app.promotions.catalog-rules.to') }}</label>
<date>
<input type="text" name="ends_till" class="control" value="{{ old('ends_till') }}"/>
</date>
</div>
<div class="control-group">
<label for="sort_order">{{ __('admin::app.promotions.catalog-rules.priority') }}</label>
<input type="text" class="control" id="sort_order" name="sort_order" value="{{ old('sort_order') ?? 0 }}"/>
</div>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.conditions') }}'" :active="false">
<div slot="body">
<div class="control-group">
<label for="condition_type">{{ __('admin::app.promotions.catalog-rules.condition-type') }}</label>
<select class="control" id="condition_type" name="condition_type" v-model="condition_type">
<option value="1">{{ __('admin::app.promotions.catalog-rules.all-conditions-true') }}</option>
<option value="2">{{ __('admin::app.promotions.catalog-rules.any-condition-true') }}</option>
</select>
</div>
<div class="table catalog-rule-conditions" style="margin-top: 20px; overflow-x: unset;">
<table>
<tbody>
<catalog-rule-condition-item
v-for='(condition, index) in conditions'
:condition="condition"
:key="index"
:index="index"
@onRemoveCondition="removeCondition($event)">
</catalog-rule-condition-item>
</tbody>
</table>
</div>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px;" @click="addCondition">
{{ __('admin::app.promotions.catalog-rules.add-condition') }}
</button>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.actions') }}'" :active="false">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotions.catalog-rules.action-type') }}</label>
<select class="control" id="action_type" name="action_type" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.action-type') }}&quot;">
<option value="by_percent" {{ old('action_type') == 'by_percent' ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.percentage-product-price') }}
</option>
<option value="by_fixed" {{ old('action_type') == 'by_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.fixed-amount') }}
</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('discount_amount') ? 'has-error' : '']">
<label for="discount_amount" class="required">{{ __('admin::app.promotions.catalog-rules.discount-amount') }}</label>
<input v-validate="'required'" class="control" id="discount_amount" name="discount_amount" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.discount-amount') }}&quot;" value="{{ old('discount_amount') ?? 0 }}"/>
<span class="control-error" v-if="errors.has('discount_amount')">@{{ errors.first('discount_amount') }}</span>
</div>
<div class="control-group">
<label for="end_other_rules">{{ __('admin::app.promotions.catalog-rules.end-other-rules') }}</label>
<select class="control" id="end_other_rules" name="end_other_rules">
<option value="0" {{ ! old('end_other_rules') ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.no') }}
</option>
<option value="1" {{ old('end_other_rules') ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.yes') }}
</option>
</select>
</div>
</div>
</accordian>
{!! view_render_event('bagisto.admin.promotions.catalog-rules.create.after') !!}
</div>
</div>
</form>
</div>
</script>
<script type="text/x-template" id="catalog-rule-condition-item-template">
<tr>
<td class="attribute">
<div class="control-group">
<select :name="['conditions[' + index + '][attribute]']" class="control" v-model="condition.attribute">
<option value="">{{ __('admin::app.promotions.catalog-rules.choose-condition-to-add') }}</option>
<optgroup v-for='(conditionAttribute, index) in condition_attributes' :label="conditionAttribute.label">
<option v-for='(childAttribute, index) in conditionAttribute.children' :value="childAttribute.key">
@{{ childAttribute.label }}
</option>
</optgroup>
</select>
</div>
</td>
<td class="operator">
<div class="control-group" v-if="matchedAttribute">
<select :name="['conditions[' + index + '][operator]']" class="control" v-model="condition.operator">
<option v-for='operator in condition_operators[matchedAttribute.type]' :value="operator.operator">
@{{ operator.label }}
</option>
</select>
</div>
</td>
<td class="value">
<div v-if="matchedAttribute">
<input type="hidden" :name="['conditions[' + index + '][attribute_type]']" v-model="matchedAttribute.type">
<div v-if="matchedAttribute.key == 'product|category_ids'">
<tree-view value-field="id" id-field="id" :name-field="'conditions[' + index + '][value]'" input-type="checkbox" :items='matchedAttribute.options' :behavior="'no'"></tree-view>
</div>
<div v-else>
<div class="control-group" v-if="matchedAttribute.type == 'text' || matchedAttribute.type == 'price' || matchedAttribute.type == 'decimal' || matchedAttribute.type == 'integer'">
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'date'">
<date>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</date>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'datetime'">
<datetime>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</datetime>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'boolean'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value">
<option value="1">{{ __('admin::app.promotions.catalog-rules.yes') }}</option>
<option value="0">{{ __('admin::app.promotions.catalog-rules.no') }}</option>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'select' || matchedAttribute.type == 'radio'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-if="matchedAttribute.key != 'catalog|state'">
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-else>
<optgroup v-for='option in matchedAttribute.options' :label="option.admin_name">
<option v-for='state in option.states' :value="state.code">
@{{ state.admin_name }}
</option>
</optgroup>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'multiselect' || matchedAttribute.type == 'checkbox'">
<select :name="['conditions[' + index + '][value][]']" class="control" v-model="condition.value" multiple>
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
</div>
</div>
</div>
</td>
<td class="actions">
<i class="icon trash-icon" @click="removeCondition"></i>
</td>
</tr>
</script>
<script>
Vue.component('catalog-rule', {
template: '#catalog-rule-template',
inject: ['$validator'],
data: function() {
return {
condition_type: 1,
conditions: []
}
},
methods: {
addCondition: function() {
this.conditions.push({
'attribute': '',
'operator': '==',
'value': '',
});
},
removeCondition: function(condition) {
let index = this.conditions.indexOf(condition)
this.conditions.splice(index, 1)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
redirectBack: function(fallbackUrl) {
this.$root.redirectBack(fallbackUrl)
}
}
});
Vue.component('catalog-rule-condition-item', {
template: '#catalog-rule-condition-item-template',
props: ['index', 'condition'],
data: function() {
return {
condition_attributes: @json(app('\Webkul\CatalogRule\Repositories\CatalogRuleRepository')->getConditionAttributes()),
attribute_type_indexes: {
'product': 0
},
condition_operators: {
'price': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'decimal': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'integer': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'text': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contain') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}],
'boolean': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'date': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'datetime': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'select': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'radio': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'multiselect': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}],
'checkbox': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}]
}
}
},
computed: {
matchedAttribute: function () {
if (this.condition.attribute == '')
return;
var this_this = this;
var attributeIndex = this.attribute_type_indexes[this.condition.attribute.split("|")[0]];
matchedAttribute = this.condition_attributes[attributeIndex]['children'].filter(function (attribute) {
return attribute.key == this_this.condition.attribute;
});
if (matchedAttribute[0]['type'] == 'multiselect' || matchedAttribute[0]['type'] == 'checkbox') {
this.condition.operator = '{}';
this.condition.value = [];
}
return matchedAttribute[0];
}
},
methods: {
removeCondition: function() {
this.$emit('onRemoveCondition', this.condition)
}
}
});
</script>
@endpush

View File

@ -0,0 +1,533 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotions.catalog-rules.edit-title') }}
@stop
@section('content')
<div class="content">
<catalog-rule></catalog-rule>
</div>
@stop
@push('scripts')
@parent
<script type="text/x-template" id="catalog-rule-template">
<div>
<form method="POST" action="{{ route('admin.catalog-rules.update', $catalogRule->id) }}" @submit.prevent="onSubmit">
<div class="page-header">
<div class="page-title">
<h1>
<i class="icon angle-left-icon back-link" @click="redirectBack('{{ url('/admin/dashboard') }}')"></i>
{{ __('admin::app.promotions.catalog-rules.edit-title') }}
</h1>
</div>
<div class="page-action">
<button type="submit" class="btn btn-lg btn-primary">
{{ __('admin::app.promotions.catalog-rules.save-btn-title') }}
</button>
</div>
</div>
<div class="page-content">
<div class="form-container">
@csrf()
{!! view_render_event('bagisto.admin.promotions.catalog-rules.create.before') !!}
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.rule-information') }}'" :active="true">
<div slot="body">
<div class="control-group" :class="[errors.has('name') ? 'has-error' : '']">
<label for="name" class="required">{{ __('admin::app.promotions.catalog-rules.name') }}</label>
<input v-validate="'required'" class="control" id="name" name="name" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.name') }}&quot;" value="{{ old('name') ?: $catalogRule->name }}"/>
<span class="control-error" v-if="errors.has('name')">@{{ errors.first('name') }}</span>
</div>
<div class="control-group">
<label for="description">{{ __('admin::app.promotions.catalog-rules.description') }}</label>
<textarea class="control" id="description" name="description">{{ old('description') ?: $catalogRule->description }}</textarea>
</div>
<div class="control-group">
<label for="status">{{ __('admin::app.promotions.catalog-rules.status') }}</label>
<span class="checkbox">
<input type="checkbox" id="status" name="status" value="{{ $catalogRule->status }}" {{ $catalogRule->status ? 'checked' : '' }}>
<label class="checkbox-view" for="status"></label>
{{ __('admin::app.promotions.catalog-rules.is-active') }}
</span>
</div>
<div class="control-group" :class="[errors.has('channels[]') ? 'has-error' : '']">
<label for="channels" class="required">{{ __('admin::app.promotions.catalog-rules.channels') }}</label>
<?php $selectedOptionIds = old('channels') ?: $catalogRule->channels->pluck('id')->toArray() ?>
<select class="control" id="channels" name="channels[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.channels') }}&quot;" multiple="multiple">
@foreach(core()->getAllChannels() as $channel)
<option value="{{ $channel->id }}" {{ in_array($channel->id, $selectedOptionIds) ? 'selected' : '' }}>
{{ $channel->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('channels[]')">@{{ errors.first('channels[]') }}</span>
</div>
<div class="control-group" :class="[errors.has('customer_groups[]') ? 'has-error' : '']">
<label for="customer_groups" class="required">{{ __('admin::app.promotions.catalog-rules.customer-groups') }}</label>
<?php $selectedOptionIds = old('customer_groups') ?: $catalogRule->customer_groups->pluck('id')->toArray() ?>
<select class="control" id="customer_groups" name="customer_groups[]" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.customer-groups') }}&quot;" multiple="multiple">
@foreach(app('Webkul\Customer\Repositories\CustomerGroupRepository')->all() as $customerGroup)
<option value="{{ $customerGroup->id }}" {{ in_array($customerGroup->id, $selectedOptionIds) ? 'selected' : '' }}>
{{ $customerGroup->name }}
</option>
@endforeach
</select>
<span class="control-error" v-if="errors.has('customer_groups[]')">@{{ errors.first('customer_groups[]') }}</span>
</div>
<div class="control-group date">
<label for="starts_from">{{ __('admin::app.promotions.catalog-rules.from') }}</label>
<date>
<input type="text" name="starts_from" class="control" value="{{ old('starts_from') ?: $catalogRule->starts_from }}"/>
</date>
</div>
<div class="control-group date">
<label for="ends_till">{{ __('admin::app.promotions.catalog-rules.to') }}</label>
<date>
<input type="text" name="ends_till" class="control" value="{{ old('ends_till') ?: $catalogRule->ends_till }}"/>
</date>
</div>
<div class="control-group">
<label for="sort_order">{{ __('admin::app.promotions.catalog-rules.priority') }}</label>
<input type="text" class="control" id="sort_order" name="sort_order" value="{{ $catalogRule->sort_order }}" {{ $catalogRule->status ? 'checked' : '' }}/>
</div>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.conditions') }}'" :active="false">
<div slot="body">
<div class="control-group">
<label for="condition_type">{{ __('admin::app.promotions.catalog-rules.condition-type') }}</label>
<select class="control" id="condition_type" name="condition_type" v-model="condition_type">
<option value="1">{{ __('admin::app.promotions.catalog-rules.all-conditions-true') }}</option>
<option value="2">{{ __('admin::app.promotions.catalog-rules.any-condition-true') }}</option>
</select>
</div>
<div class="table catalog-rule-conditions" style="margin-top: 20px; overflow-x: unset;">
<table>
<tbody>
<catalog-rule-condition-item
v-for='(condition, index) in conditions'
:condition="condition"
:key="index"
:index="index"
@onRemoveCondition="removeCondition($event)">
</catalog-rule-condition-item>
</tbody>
</table>
</div>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px;" @click="addCondition">
{{ __('admin::app.promotions.catalog-rules.add-condition') }}
</button>
</div>
</accordian>
<accordian :title="'{{ __('admin::app.promotions.catalog-rules.actions') }}'" :active="false">
<div slot="body">
<div class="control-group" :class="[errors.has('action_type') ? 'has-error' : '']">
<label for="action_type" class="required">{{ __('admin::app.promotions.catalog-rules.action-type') }}</label>
<?php $selectedOption = old('action_type') ?: $catalogRule->action_type ?>
<select class="control" id="action_type" name="action_type" v-validate="'required'" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.action-type') }}&quot;">
<option value="by_percent" {{ $selectedOption == 'by_percent' ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.percentage-product-price') }}
</option>
<option value="by_fixed" {{ $selectedOption == 'by_fixed' ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.fixed-amount') }}
</option>
</select>
<span class="control-error" v-if="errors.has('action_type')">@{{ errors.first('action_type') }}</span>
</div>
<div class="control-group" :class="[errors.has('discount_amount') ? 'has-error' : '']">
<label for="discount_amount" class="required">{{ __('admin::app.promotions.catalog-rules.discount-amount') }}</label>
<input v-validate="'required'" class="control" id="discount_amount" name="discount_amount" data-vv-as="&quot;{{ __('admin::app.promotions.catalog-rules.discount-amount') }}&quot;" value="{{ old('discount_amount') ?: $catalogRule->discount_amount }}"/>
<span class="control-error" v-if="errors.has('discount_amount')">@{{ errors.first('discount_amount') }}</span>
</div>
<div class="control-group">
<label for="end_other_rules">{{ __('admin::app.promotions.catalog-rules.end-other-rules') }}</label>
<?php $selectedOption = old('end_other_rules') ?: $catalogRule->end_other_rules ?>
<select class="control" id="end_other_rules" name="end_other_rules">
<option value="0" {{ ! $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.no') }}
</option>
<option value="1" {{ $selectedOption ? 'selected' : '' }}>
{{ __('admin::app.promotions.catalog-rules.yes') }}
</option>
</select>
</div>
</div>
</accordian>
{!! view_render_event('bagisto.admin.promotions.catalog-rules.create.after') !!}
</div>
</div>
</form>
</div>
</script>
<script type="text/x-template" id="catalog-rule-condition-item-template">
<tr>
<td class="attribute">
<div class="control-group">
<select :name="['conditions[' + index + '][attribute]']" class="control" v-model="condition.attribute">
<option value="">{{ __('admin::app.promotions.catalog-rules.choose-condition-to-add') }}</option>
<optgroup v-for='(conditionAttribute, index) in condition_attributes' :label="conditionAttribute.label">
<option v-for='(childAttribute, index) in conditionAttribute.children' :value="childAttribute.key">
@{{ childAttribute.label }}
</option>
</optgroup>
</select>
</div>
</td>
<td class="operator">
<div class="control-group" v-if="matchedAttribute">
<select :name="['conditions[' + index + '][operator]']" class="control" v-model="condition.operator">
<option v-for='operator in condition_operators[matchedAttribute.type]' :value="operator.operator">
@{{ operator.label }}
</option>
</select>
</div>
</td>
<td class="value">
<div v-if="matchedAttribute">
<input type="hidden" :name="['conditions[' + index + '][attribute_type]']" v-model="matchedAttribute.type">
<div v-if="matchedAttribute.key == 'product|category_ids'">
<tree-view value-field="id" id-field="id" :name-field="'conditions[' + index + '][value]'" input-type="checkbox" :items='matchedAttribute.options' :value='condition.value' :behavior="'no'"></tree-view>
</div>
<div v-else>
<div class="control-group" v-if="matchedAttribute.type == 'text' || matchedAttribute.type == 'price' || matchedAttribute.type == 'decimal' || matchedAttribute.type == 'integer'">
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'date'">
<date>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</date>
</div>
<div class="control-group date" v-if="matchedAttribute.type == 'datetime'">
<datetime>
<input class="control" :name="['conditions[' + index + '][value]']" v-model="condition.value"/>
</datetime>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'boolean'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value">
<option value="1">{{ __('admin::app.promotions.catalog-rules.yes') }}</option>
<option value="0">{{ __('admin::app.promotions.catalog-rules.no') }}</option>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'select' || matchedAttribute.type == 'radio'">
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-if="matchedAttribute.key != 'catalog|state'">
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
<select :name="['conditions[' + index + '][value]']" class="control" v-model="condition.value" v-else>
<optgroup v-for='option in matchedAttribute.options' :label="option.admin_name">
<option v-for='state in option.states' :value="state.code">
@{{ state.admin_name }}
</option>
</optgroup>
</select>
</div>
<div class="control-group" v-if="matchedAttribute.type == 'multiselect' || matchedAttribute.type == 'checkbox'">
<select :name="['conditions[' + index + '][value][]']" class="control" v-model="condition.value" multiple>
<option v-for='option in matchedAttribute.options' :value="option.id">
@{{ option.admin_name }}
</option>
</select>
</div>
</div>
</div>
</td>
<td class="actions">
<i class="icon trash-icon" @click="removeCondition"></i>
</td>
</tr>
</script>
<script>
Vue.component('catalog-rule', {
template: '#catalog-rule-template',
inject: ['$validator'],
data: function() {
return {
condition_type: {{ old('condition_type') ?: $catalogRule->condition_type }},
conditions: @json($catalogRule->conditions ?: [])
}
},
methods: {
addCondition: function() {
this.conditions.push({
'attribute': '',
'operator': '==',
'value': '',
});
},
removeCondition: function(condition) {
let index = this.conditions.indexOf(condition)
this.conditions.splice(index, 1)
},
onSubmit: function(e) {
this.$root.onSubmit(e)
},
redirectBack: function(fallbackUrl) {
this.$root.redirectBack(fallbackUrl)
}
}
});
Vue.component('catalog-rule-condition-item', {
template: '#catalog-rule-condition-item-template',
props: ['index', 'condition'],
data: function() {
return {
condition_attributes: @json(app('\Webkul\CatalogRule\Repositories\CatalogRuleRepository')->getConditionAttributes()),
attribute_type_indexes: {
'product': 0
},
condition_operators: {
'price': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'decimal': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'integer': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'text': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contain') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}],
'boolean': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'date': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'datetime': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}, {
'operator': '>=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-greater-than') }}'
}, {
'operator': '<=',
'label': '{{ __('admin::app.promotions.catalog-rules.equals-or-less-than') }}'
}, {
'operator': '>',
'label': '{{ __('admin::app.promotions.catalog-rules.greater-than') }}'
}, {
'operator': '<',
'label': '{{ __('admin::app.promotions.catalog-rules.less-than') }}'
}],
'select': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'radio': [{
'operator': '==',
'label': '{{ __('admin::app.promotions.catalog-rules.is-equal-to') }}'
}, {
'operator': '!=',
'label': '{{ __('admin::app.promotions.catalog-rules.is-not-equal-to') }}'
}],
'multiselect': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}],
'checkbox': [{
'operator': '{}',
'label': '{{ __('admin::app.promotions.catalog-rules.contains') }}'
}, {
'operator': '!{}',
'label': '{{ __('admin::app.promotions.catalog-rules.does-not-contain') }}'
}]
}
}
},
computed: {
matchedAttribute: function () {
if (this.condition.attribute == '')
return;
var this_this = this;
var attributeIndex = this.attribute_type_indexes[this.condition.attribute.split("|")[0]];
matchedAttribute = this.condition_attributes[attributeIndex]['children'].filter(function (attribute) {
return attribute.key == this_this.condition.attribute;
});
if (matchedAttribute[0]['type'] == 'multiselect' || matchedAttribute[0]['type'] == 'checkbox') {
this.condition.operator = '{}';
this.condition.value = this.condition.value == '' && this.condition.value != undefined ? [] : this.condition.value;
}
return matchedAttribute[0];
}
},
methods: {
removeCondition: function() {
this.$emit('onRemoveCondition', this.condition)
}
}
});
</script>
@endpush

View File

@ -0,0 +1,27 @@
@extends('admin::layouts.content')
@section('page_title')
{{ __('admin::app.promotions.catalog-rules.title') }}
@stop
@section('content')
<div class="content">
<div class="page-header">
<div class="page-title">
<h1>{{ __('admin::app.promotions.catalog-rules.title') }}</h1>
</div>
<div class="page-action">
<a href="{{ route('admin.catalog-rules.create') }}" class="btn btn-lg btn-primary">
{{ __('admin::app.promotions.catalog-rules.add-title') }}
</a>
</div>
</div>
<div class="page-content">
@inject('catalogRuleGrid','Webkul\Admin\DataGrids\CatalogRuleDataGrid')
{!! $catalogRuleGrid->render() !!}
</div>
</div>
@endsection

View File

@ -332,7 +332,13 @@
@if ($order->base_discount_amount > 0) @if ($order->base_discount_amount > 0)
<tr> <tr>
<td>{{ __('admin::app.sales.orders.discount') }}</td> <td>
{{ __('admin::app.sales.orders.discount') }}
@if ($order->coupon_code)
({{ $order->coupon_code }})
@endif
</td>
<td>-</td> <td>-</td>
<td>{{ core()->formatBasePrice($order->base_discount_amount) }}</td> <td>{{ core()->formatBasePrice($order->base_discount_amount) }}</td>
</tr> </tr>

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CartRule\Contracts;
interface CartRule
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CartRule\Contracts;
interface CartRuleCoupon
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CartRule\Contracts;
interface CartRuleCouponUsage
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CartRule\Contracts;
interface CartRuleCustomer
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CartRule\Contracts;
interface CartRuleTranslation
{
}

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RemoveTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('cart_rule_cart');
Schema::dropIfExists('cart_rule_labels');
Schema::dropIfExists('cart_rule_channels');
Schema::dropIfExists('cart_rule_customer_groups');
Schema::dropIfExists('cart_rule_customers');
Schema::dropIfExists('cart_rule_coupons');
Schema::dropIfExists('cart_rule_coupon_usage');
Schema::dropIfExists('cart_rule_coupons_usage');
Schema::dropIfExists('cart_rules');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class CreateCartRuleTable extends Migration class CreateCartRulesTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,38 +13,29 @@ class CreateCartRuleTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::dropIfExists('discount_customer_group');
Schema::dropIfExists('discount_channels');
Schema::dropIfExists('discount_rules');
Schema::dropIfExists('discounts');
Schema::dropIfExists('products_grid');
Schema::create('cart_rules', function (Blueprint $table) { Schema::create('cart_rules', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->string('name')->nullable(); $table->string('name')->nullable();
$table->string('description')->nullable(); $table->string('description')->nullable();
$table->datetime('starts_from')->nullable(); $table->date('starts_from')->nullable();
$table->datetime('ends_till')->nullable(); $table->date('ends_till')->nullable();
$table->boolean('status')->default(0); $table->boolean('status')->default(0);
$table->integer('per_customer')->unsigned()->default(0);
$table->boolean('is_guest')->default(0);
$table->boolean('use_coupon')->default(0);
$table->integer('usage_limit')->unsigned()->default(0);
$table->json('conditions')->nullable();
$table->json('actions')->nullable();
$table->boolean('end_other_rules')->default(0);
$table->integer('priority')->unsigned()->default(0);
$table->boolean('uses_attribute_conditions')->default(0);
$table->longtext('product_ids')->nullable();
$table->integer('sort_order')->unsigned()->default(0);
$table->string('action_type')->nullable();
$table->decimal('disc_amount', 12, 4)->default(0);
$table->decimal('disc_quantity', 12, 4)->default(1);
$table->string('disc_threshold')->default(1);
$table->integer('coupon_type')->default(1); $table->integer('coupon_type')->default(1);
$table->boolean('auto_generation')->default(0); $table->boolean('use_auto_generation')->default(0);
$table->integer('usage_per_customer')->default(0);
$table->integer('uses_per_coupon')->default(0);
$table->integer('times_used')->unsigned()->default(0);
$table->boolean('condition_type')->default(1);
$table->json('conditions')->nullable();
$table->boolean('end_other_rules')->default(0);
$table->boolean('uses_attribute_conditions')->default(0);
$table->string('action_type')->nullable();
$table->decimal('discount_amount', 12, 4)->default(0);
$table->integer('discount_quantity')->default(1);
$table->string('discount_step')->default(1);
$table->boolean('apply_to_shipping')->default(0); $table->boolean('apply_to_shipping')->default(0);
$table->boolean('free_shipping')->default(0); $table->boolean('free_shipping')->default(0);
$table->integer('sort_order')->unsigned()->default(0);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -14,12 +14,13 @@ class CreateCartRuleChannelsTable extends Migration
public function up() public function up()
{ {
Schema::create('cart_rule_channels', function (Blueprint $table) { Schema::create('cart_rule_channels', function (Blueprint $table) {
$table->increments('id');
$table->integer('cart_rule_id')->unsigned(); $table->integer('cart_rule_id')->unsigned();
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->integer('channel_id')->unsigned(); $table->integer('channel_id')->unsigned();
$table->primary(['cart_rule_id', 'channel_id']);
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade'); $table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
$table->timestamps();
}); });
} }

View File

@ -14,12 +14,14 @@ class CreateCartRuleCustomerGroupsTable extends Migration
public function up() public function up()
{ {
Schema::create('cart_rule_customer_groups', function (Blueprint $table) { Schema::create('cart_rule_customer_groups', function (Blueprint $table) {
$table->increments('id');
$table->integer('cart_rule_id')->unsigned(); $table->integer('cart_rule_id')->unsigned();
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->integer('customer_group_id')->unsigned(); $table->integer('customer_group_id')->unsigned();
$table->primary(['cart_rule_id', 'customer_group_id']);
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade'); $table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
$table->timestamps();
}); });
} }

View File

@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class CreateCartRuleCartTable extends Migration class CreateCartRuleTranslationsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,13 +13,13 @@ class CreateCartRuleCartTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::create('cart_rule_cart', function (Blueprint $table) { Schema::create('cart_rule_translations', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->integer('cart_rule_id')->unsigned()->nullable(); $table->string('locale');
$table->text('label')->nullable();
$table->integer('cart_rule_id')->unsigned();
$table->unique(['cart_rule_id', 'locale']);
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade'); $table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->integer('cart_id')->unsigned()->nullable();
$table->foreign('cart_id')->references('id')->on('cart')->onDelete('cascade');
$table->timestamps();
}); });
} }
@ -30,6 +30,6 @@ class CreateCartRuleCartTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('cart_rule_cart'); Schema::dropIfExists('cart_rule_translations');
} }
} }

View File

@ -15,12 +15,13 @@ class CreateCartruleCustomersTable extends Migration
{ {
Schema::create('cart_rule_customers', function (Blueprint $table) { Schema::create('cart_rule_customers', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->bigInteger('times_used')->unsigned()->default(0);
$table->integer('cart_rule_id')->unsigned(); $table->integer('cart_rule_id')->unsigned();
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade'); $table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->integer('customer_id')->unsigned(); $table->integer('customer_id')->unsigned();
$table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade'); $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
$table->bigInteger('usage_throttle')->unsigned()->default(0);
$table->timestamps();
}); });
} }
@ -31,6 +32,6 @@ class CreateCartruleCustomersTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('cart_rule_customers'); Schema::dropIfExists('cartrule_customers');
} }
} }

View File

@ -15,15 +15,16 @@ class CreateCartruleCouponsTable extends Migration
{ {
Schema::create('cart_rule_coupons', function (Blueprint $table) { Schema::create('cart_rule_coupons', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->integer('cart_rule_id')->unsigned();
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->string('prefix')->nullable();
$table->string('suffix')->nullable();
$table->string('code')->nullable(); $table->string('code')->nullable();
$table->integer('usage_limit')->unsigned()->default(0); $table->integer('usage_limit')->unsigned()->default(0);
$table->integer('usage_per_customer')->unsigned()->default(0); $table->integer('usage_per_customer')->unsigned()->default(0);
$table->integer('usage_throttle')->unsigned()->default(0); $table->integer('times_used')->unsigned()->default(0);
$table->integer('type')->unsigned()->default(0); $table->integer('type')->unsigned()->default(0);
$table->boolean('is_primary')->default(0);
$table->date('expired_at')->nullable();
$table->integer('cart_rule_id')->unsigned();
$table->foreign('cart_rule_id')->references('id')->on('cart_rules')->onDelete('cascade');
$table->timestamps(); $table->timestamps();
}); });
} }
@ -35,6 +36,6 @@ class CreateCartruleCouponsTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('cart_rule_coupons'); Schema::dropIfExists('cartrule_coupons');
} }
} }

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCartRuleCouponUsageTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cart_rule_coupon_usage', function (Blueprint $table) {
$table->increments('id');
$table->integer('times_used')->default(0);
$table->integer('cart_rule_coupon_id')->unsigned();
$table->foreign('cart_rule_coupon_id')->references('id')->on('cart_rule_coupons')->onDelete('cascade');
$table->integer('customer_id')->unsigned();
$table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('cart_rule_coupon_usage');
}
}

View File

@ -0,0 +1,523 @@
<?php
namespace Webkul\CartRule\Helpers;
use Carbon\Carbon;
use Webkul\CartRule\Repositories\CartRuleRepository;
use Webkul\CartRule\Repositories\CartRuleCouponRepository;
use Webkul\CartRule\Repositories\CartRuleCouponUsageRepository;
use Webkul\CartRule\Repositories\CartRuleCustomerRepository;
use Webkul\Customer\Repositories\CustomerGroupRepository;
use Webkul\Rule\Helpers\Validator;
use Webkul\Checkout\Facades\Cart;
class CartRule
{
/**
* CartRuleRepository object
*
* @var CartRuleRepository
*/
protected $cartRuleRepository;
/**
* CartRuleCouponRepository object
*
* @var CartRuleCouponRepository
*/
protected $cartRuleCouponRepository;
/**
* CartRuleCouponUsageRepository object
*
* @var CartRuleCouponUsageRepository
*/
protected $cartRuleCouponUsageRepository;
/**
* CartRuleCustomerRepository object
*
* @var CartRuleCustomerRepository
*/
protected $cartRuleCustomerRepository;
/**
* CustomerGroupRepository object
*
* @var CustomerGroupRepository
*/
protected $customerGroupRepository;
/**
* Validator object
*
* @var Validator
*/
protected $validator;
/**
* @var array
*/
protected $itemTotals = [];
/**
* Create a new helper instance.
*
* @param Webkul\CartRule\Repositories\CartRuleRepository $cartRuleRepository
* @param Webkul\CartRule\Repositories\CartRuleCouponRepository $cartRuleCouponRepository
* @param Webkul\CartRule\Repositories\CartRuleCouponUsageRepository $cartRuleCouponUsageRepository
* @param Webkul\CartRule\Repositories\CartRuleCustomerRepository $cartRuleCustomerRepository
* @param Webkul\Customer\Repositories\CustomerGroupRepository $customerGroupRepository
* @param Webkul\Rule\Helpers\Validator $validator
* @return void
*/
public function __construct(
CartRuleRepository $cartRuleRepository,
CartRuleCouponRepository $cartRuleCouponRepository,
CartRuleCouponUsageRepository $cartRuleCouponUsageRepository,
CartRuleCustomerRepository $cartRuleCustomerRepository,
CustomerGroupRepository $customerGroupRepository,
Validator $validator
)
{
$this->cartRuleRepository = $cartRuleRepository;
$this->cartRuleCouponRepository = $cartRuleCouponRepository;
$this->cartRuleCouponUsageRepository = $cartRuleCouponUsageRepository;
$this->cartRuleCustomerRepository = $cartRuleCustomerRepository;
$this->customerGroupRepository = $customerGroupRepository;
$this->validator = $validator;
}
/**
* Collect discount on cart
*
* @return void
*/
public function collect()
{
$cart = Cart::getCart();
$this->calculateCartItemTotals($cart->items()->get());
foreach ($cart->items()->get() as $item) {
$this->process($item);
if ($item->children()->count() && $item->product->getTypeInstance()->isChildrenCalculated())
$this->devideDiscount($item);
}
$this->processShippingDiscount($cart);
$this->processFreeShippingDiscount($cart);
$this->validateCouponCode();
}
/**
* Returns cart rules
*
* @return Collection
*/
public function getCartRules()
{
static $cartRules;
if ($cartRules)
return $cartRules;
$customerGroupId = null;
if (Cart::getCurrentCustomer()->check()) {
$customerGroupId = Cart::getCurrentCustomer()->user()->customer_group_id;
} else {
if ($customerGuestGroup = $this->customerGroupRepository->findOneByField('code', 'guest'))
$customerGroupId = $customerGuestGroup->id;
}
$cartRules = $this->cartRuleRepository->scopeQuery(function($query) use ($customerGroupId) {
return $query->leftJoin('cart_rule_customer_groups', 'cart_rules.id', '=', 'cart_rule_customer_groups.cart_rule_id')
->leftJoin('cart_rule_channels', 'cart_rules.id', '=', 'cart_rule_channels.cart_rule_id')
->where('cart_rule_customer_groups.customer_group_id', $customerGroupId)
->where('cart_rule_channels.channel_id', core()->getCurrentChannel()->id)
->where(function ($query1) {
$query1->where('cart_rules.starts_from', '<=', Carbon::now()->format('Y-m-d'))->orWhereNull('cart_rules.starts_from');
})
->where(function ($query2) {
$query2->where('cart_rules.ends_till', '>=', Carbon::now()->format('Y-m-d'))->orWhereNull('cart_rules.ends_till');
})
->orderBy('sort_order', 'asc');
})->findWhere(['status' => 1]);
return $cartRules;
}
/**
* Check if cart rule can be applied
*
* @param CartRule $rule
* @return boolean
*/
public function canProcessRule($rule)
{
$cart = Cart::getCart();
if ($rule->coupon_type) {
if (strlen($cart->coupon_code)) {
$coupon = $this->cartRuleCouponRepository->findOneWhere([
'cart_rule_id' => $rule->id,
'code' => $cart->coupon_code,
]);
if ($coupon) {
if ($coupon->usage_limit && $coupon->times_used >= $coupon->usage_limit)
return false;
if ($cart->customer_id && $coupon->usage_per_customer) {
$couponUsage = $this->cartRuleCouponUsageRepository->findOneWhere([
'cart_rule_coupon_id' => $coupon->id,
'customer_id' => $cart->customer_id
]);
if ($couponUsage && $couponUsage->times_used >= $coupon->usage_per_customer)
return false;
}
} else {
return false;
}
} else {
return false;
}
}
if ($rule->usage_per_customer) {
$ruleCustomer = $this->cartRuleCustomerRepository->findOneWhere([
'cart_rule_id' => $rule->id,
'customer_id' => $cart->customer_id,
]);
if ($ruleCustomer && $ruleCustomer->times_used >= $rule->usage_per_customer)
return false;
}
return true;
}
/**
* Cart item discount calculation process
*
* @param CartItem $item
* @return void
*/
public function process($item)
{
$item->discount_percent = 0;
$item->discount_amount = 0;
$item->base_discount_amount = 0;
$cart = $item->cart;
$cart->applied_cart_rule_ids = null;
$appliedRuleIds = [];
foreach ($this->getCartRules() as $rule) {
if (! $this->canProcessRule($rule))
continue;
if (! $this->validator->validate($rule, $item))
continue;
$quantity = $rule->discount_quantity ? min($item->quantity, $rule->discount_quantity) : $item->quantity;
$discountAmount = $baseDiscountAmount = 0;
switch ($rule->action_type) {
case 'by_percent':
$rulePercent = min(100, $rule->discount_amount);
$discountAmount = ($quantity * $item->price - $item->discount_amount) * ($rulePercent / 100);
$baseDiscountAmount = ($quantity * $item->base_price - $item->base_discount_amount) * ($rulePercent / 100);
if (! $rule->discount_quantity || $rule->discount_quantity > $quantity) {
$discountPercent = min(100, $item->discount_percent + $rulePercent);
$item->discount_percent = $discountPercent;
}
break;
case 'by_fixed':
$discountAmount = $quantity * core()->convertPrice($rule->discount_amount);
$baseDiscountAmount = $quantity * $rule->discount_amount;
break;
case 'cart_fixed':
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'];
$maxDiscount = $rule->discount_amount * $discountRate;
$discountAmount = core()->convertPrice($maxDiscount);
$baseDiscountAmount = min($item->base_price * $quantity, $maxDiscount);
}
$discountAmount = min($item->price * $quantity, $discountAmount);
break;
case 'buy_x_get_y':
if (! $rule->discount_step || $rule->discount_amount > $rule->discount_step)
break;
$buyAndDiscountQty = $rule->discount_step + $rule->discount_amount;
$qtyPeriod = floor($quantity / $buyAndDiscountQty);
$freeQty = $quantity - $qtyPeriod * $buyAndDiscountQty;
$discountQty = $qtyPeriod * $rule->discount_amount;
if ($freeQty > $rule->discount_step)
$discountQty += $freeQty - $rule->discount_step;
$discountAmount = $discountQty * $item->price;
$baseDiscountAmount = $discountQty * $item->base_price;
break;
}
$item->discount_amount = min($item->discount_amount + $discountAmount, $item->price * $quantity);
$item->base_discount_amount = min($item->base_discount_amount + $baseDiscountAmount, $item->base_price * $quantity);
$appliedRuleIds[$rule->id] = $rule->id;
if ($rule->end_other_rules)
break;
}
$item->applied_cart_rule_ids = join(',', $appliedRuleIds);
$item->save();
$cartAppliedCartRuleIds = array_merge(explode(',', $cart->applied_cart_rule_ids), $appliedRuleIds);
$cartAppliedCartRuleIds = array_filter($cartAppliedCartRuleIds);
$cartAppliedCartRuleIds = array_unique($cartAppliedCartRuleIds);
$cart->applied_cart_rule_ids = join(',', $cartAppliedCartRuleIds);
$cart->save();
}
/**
* Cart shipping discount calculation process
*
* @param Cart $cart
* @return void
*/
public function processShippingDiscount($cart)
{
if (! $selectedShipping = $cart->selected_shipping_rate)
return;
$selectedShipping->discount_amount = 0;
$selectedShipping->base_discount_amount = 0;
$appliedRuleIds = [];
foreach ($this->getCartRules() as $rule) {
if (! $this->canProcessRule($rule))
continue;
if (! $this->validator->validate($rule, $cart))
continue;
if (! $rule || ! $rule->apply_to_shipping)
continue;
$discountAmount = $baseDiscountAmount = 0;
switch ($rule->action_type) {
case 'by_percent':
$rulePercent = min(100, $rule->discount_amount);
$discountAmount = ($selectedShipping->price - $selectedShipping->discount_amount) * $rulePercent / 100;
$baseDiscountAmount = ($selectedShipping->base_price - $selectedShipping->base_discount_amount) * $rulePercent / 100;
break;
case 'by_fixed':
$discountAmount = core()->convertPrice($rule->discount_amount);
$baseDiscountAmount = $rule->discount_amount;
break;
}
$selectedShipping->discount_amount = min($selectedShipping->discount_amount + $discountAmount, $selectedShipping->price);
$selectedShipping->base_discount_amount = min(
$selectedShipping->base_discount_amount + $baseDiscountAmount,
$selectedShipping->base_price
);
$selectedShipping->save();
$appliedRuleIds[$rule->id] = $rule->id;
if ($rule->end_other_rules)
break;
}
$selectedShipping->save();
$cartAppliedCartRuleIds = array_merge(explode(',', $cart->applied_cart_rule_ids), $appliedRuleIds);
$cartAppliedCartRuleIds = array_filter($cartAppliedCartRuleIds);
$cartAppliedCartRuleIds = array_unique($cartAppliedCartRuleIds);
$cart->applied_cart_rule_ids = join(',', $cartAppliedCartRuleIds);
$cart->save();
return $this;
}
/**
* Cart free shipping discount calculation process
*
* @param Cart $cart
* @return void
*/
public function processFreeShippingDiscount($cart)
{
if (! $selectedShipping = $cart->selected_shipping_rate)
return;
$selectedShipping->discount_amount = 0;
$selectedShipping->base_discount_amount = 0;
$appliedRuleIds = [];
foreach ($this->getCartRules() as $rule) {
if (! $this->canProcessRule($rule))
continue;
if (! $this->validator->validate($rule, $cart))
continue;
if (! $rule || ! $rule->free_shipping)
continue;
$selectedShipping->price = 0;
$selectedShipping->base_price = 0;
$selectedShipping->save();
$appliedRuleIds[$rule->id] = $rule->id;
if ($rule->end_other_rules)
break;
}
$cartAppliedCartRuleIds = array_merge(explode(',', $cart->applied_cart_rule_ids), $appliedRuleIds);
$cartAppliedCartRuleIds = array_filter($cartAppliedCartRuleIds);
$cartAppliedCartRuleIds = array_unique($cartAppliedCartRuleIds);
$cart->applied_cart_rule_ids = join(',', $cartAppliedCartRuleIds);
$cart->save();
}
/**
* Calculate cart item totals for each rule
*
* @param mixed $items
* @return Validator
*/
public function calculateCartItemTotals($items)
{
foreach ($this->getCartRules() as $rule) {
if ($rule->action_type == 'cart_fixed') {
$totalPrice = $totalBasePrice = $validCount = 0;
foreach ($items as $item) {
if (! $this->canProcessRule($rule, $item))
continue;
$quantity = $rule->discount_quantity ? min($item->quantity, $rule->discount_quantity) : $item->quantity;
$totalBasePrice += $item->base_price * $quantity;
$validCount++;
}
$this->itemTotals[$rule->id] = [
'base_total_price' => $totalBasePrice,
'total_items' => $validCount,
];
}
}
}
/**
* Check if coupon code is valid or not, if not remove from cart
*
* @return void
*/
public function validateCouponCode()
{
$cart = Cart::getCart();
if (! $cart->coupon_code)
return;
$coupon = $this->cartRuleCouponRepository->findOneByField('code', $cart->coupon_code);
if (! $coupon || ! in_array($coupon->cart_rule_id, explode(',', $cart->applied_cart_rule_ids)))
Cart::removeCouponCode();
}
/**
* Devide discount amount to children
*
* @param CartItem $item
* @return void
*/
protected function devideDiscount($item)
{
foreach ($item->children as $child) {
$ratio = $item->base_total != 0 ? $child->base_total / $item->base_total : 0;
foreach (['discount_amount', 'base_discount_amount'] as $column) {
if (! $item->{$column})
continue;
$child->{$column} = round(($item->{$column} * $ratio), 4);
$child->save();
}
}
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace Webkul\CartRule\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Webkul\CartRule\Repositories\CartRuleRepository;
use Webkul\CartRule\Repositories\CartRuleCouponRepository;
/**
* Cart Rule controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleController extends Controller
{
/**
* Initialize _config, a default request parameter with route
*
* @param array
*/
protected $_config;
/**
* To hold Cart repository instance
*
* @var CartRuleRepository
*/
protected $cartRuleRepository;
/**
* To hold CartRuleCouponRepository repository instance
*
* @var CartRuleCouponRepository
*/
protected $cartRuleCouponRepository;
/**
* Create a new controller instance.
*
* @param \Webkul\CartRule\Repositories\CartRuleRepository $cartRuleRepository
* @param \Webkul\CartRule\Repositories\CartRuleCouponRepository $cartRuleCouponRepository
* @return void
*/
public function __construct(
CartRuleRepository $cartRuleRepository,
CartRuleCouponRepository $cartRuleCouponRepository
)
{
$this->_config = request('_config');
$this->cartRuleRepository = $cartRuleRepository;
$this->cartRuleCouponRepository = $cartRuleCouponRepository;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view($this->_config['view']);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view($this->_config['view']);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store()
{
$this->validate(request(), [
'name' => 'required',
'channels' => 'required|array|min:1',
'customer_groups' => 'required|array|min:1',
'coupon_type' => 'required',
'use_auto_generation' => 'required_if:coupon_type,==,1',
'coupon_code' => 'required_if:use_auto_generation,==,0',
'starts_from' => 'nullable|date',
'ends_till' => 'nullable|date|after_or_equal:starts_from',
'action_type' => 'required',
'discount_amount' => 'required|numeric'
]);
$data = request()->all();
Event::fire('promotions.cart_rule.create.before');
$cartRule = $this->cartRuleRepository->create($data);
Event::fire('promotions.cart_rule.create.after', $cartRule);
session()->flash('success', trans('admin::app.response.create-success', ['name' => 'Cart Rule']));
return redirect()->route($this->_config['redirect']);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$cartRule = $this->cartRuleRepository->findOrFail($id);
return view($this->_config['view'], compact('cartRule'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->validate(request(), [
'name' => 'required',
'channels' => 'required|array|min:1',
'customer_groups' => 'required|array|min:1',
'coupon_type' => 'required',
'use_auto_generation' => 'required_if:coupon_type,==,1',
'coupon_code' => 'required_if:use_auto_generation,==,0',
'starts_from' => 'nullable|date',
'ends_till' => 'nullable|date|after_or_equal:starts_from',
'action_type' => 'required',
'discount_amount' => 'required|numeric'
]);
$cartRule = $this->cartRuleRepository->findOrFail($id);
Event::fire('promotions.cart_rule.update.before', $cartRule);
$cartRule = $this->cartRuleRepository->update(request()->all(), $id);
Event::fire('promotions.cart_rule.update.after', $cartRule);
session()->flash('success', trans('admin::app.response.update-success', ['name' => 'Cart Rule']));
return redirect()->route($this->_config['redirect']);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$cartRule = $this->cartRuleRepository->findOrFail($id);
try {
Event::fire('promotions.cart_rule.delete.before', $id);
$this->cartRuleRepository->delete($id);
Event::fire('promotions.cart_rule.delete.after', $id);
session()->flash('success', trans('admin::app.response.delete-success', ['name' => 'Cart Rule']));
return response()->json(['message' => true], 200);
} catch(\Exception $e) {
session()->flash('error', trans('admin::app.response.delete-failed', ['name' => 'Cart Rule']));
}
return response()->json(['message' => false], 400);
}
/**
* Generate coupon code for cart rule
*
* @return \Illuminate\Http\JsonResponse
*/
public function generateCoupons()
{
$this->validate(request(), [
'coupon_qty' => 'required|integer|min:1',
'code_length' => 'required|integer|min:10',
'code_format' => 'required'
]);
if (! request('id'))
return response()->json(['message' => trans('admin::app.promotions.cart-rules.cart-rule-not-defind-error')], 400);
$this->cartRuleCouponRepository->generateCoupons(request()->all(), request('id'));
return response()->json(['message' => trans('admin::app.response.create-success', ['name' => 'Cart rule coupons'])]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Webkul\CartRule\Http\Controllers;
use Illuminate\Http\Request;
use Webkul\CartRule\Repositories\CartRuleCouponRepository;
/**
* Cart Rule Coupon controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleCouponController extends Controller
{
/**
* To hold CartRuleCouponRepository repository instance
*
* @var CartRuleCouponRepository
*/
protected $cartRuleCouponRepository;
/**
* Create a new controller instance.
*
* @param \Webkul\CartRule\Repositories\CartRuleCouponRepository $cartRuleCouponRepository
* @return void
*/
public function __construct(CartRuleCouponRepository $cartRuleCouponRepository)
{
$this->cartRuleCouponRepository = $cartRuleCouponRepository;
}
/**
* Mass Delete the coupons
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function massDelete()
{
$couponIds = explode(',', request()->input('indexes'));
foreach ($couponIds as $couponId) {
$coupon = $this->cartRuleCouponRepository->find($couponId);
if ($coupon)
$this->cartRuleCouponRepository->delete($couponId);
}
session()->flash('success', trans('admin::app.promotions.cart-rules.mass-delete-success'));
return redirect()->back();
}
}

View File

@ -1,9 +1,9 @@
<?php <?php
namespace Webkul\Discount\Http\Controllers; namespace Webkul\CartRule\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

View File

@ -0,0 +1,43 @@
<?php
namespace Webkul\CartRule\Listeners;
use Webkul\CartRule\Helpers\CartRule;
/**
* Cart event handler
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Cart
{
/**
* CartRule object
*
* @var CartRule
*/
protected $cartRuleHepler;
/**
* Create a new listener instance.
*
* @param Webkul\CartRule\Repositories\CartRule $cartRuleHepler
* @return void
*/
public function __construct(CartRule $cartRuleHepler)
{
$this->cartRuleHepler = $cartRuleHepler;
}
/**
* Aplly valid cart rules to cart
*
* @param Cart $cart
* @return void
*/
public function applyCartRules($cart)
{
$this->cartRuleHepler->collect();
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace Webkul\CartRule\Listeners;
use Webkul\CartRule\Repositories\CartRuleRepository;
use Webkul\CartRule\Repositories\CartRuleCustomerRepository;
use Webkul\CartRule\Repositories\CartRuleCouponRepository;
use Webkul\CartRule\Repositories\CartRuleCouponUsageRepository;
/**
* Order event handler
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Order
{
/**
* CartRuleRepository object
*
* @var CartRuleRepository
*/
protected $cartRuleRepository;
/**
* CartRuleCustomerRepository object
*
* @var CartRuleCustomerRepository
*/
protected $cartRuleCustomerRepository;
/**
* CartRuleCouponRepository object
*
* @var CartRuleCouponRepository
*/
protected $cartRuleCouponRepository;
/**
* CartRuleCouponUsageRepository object
*
* @var CartRuleCouponUsageRepository
*/
protected $cartRuleCouponUsageRepository;
/**
* Create a new listener instance.
*
* @param Webkul\CartRule\Repositories\CartRuleRepository $cartRuleRepository
* @param Webkul\CartRule\Repositories\CartRuleCustomerRepository $cartRuleCustomerRepository
* @param Webkul\CartRule\Repositories\CartRuleCouponRepository $cartRuleCouponRepository
* @param Webkul\CartRule\Repositories\CartRuleCouponUsageRepository $cartRuleCouponUsageRepository
* @return void
*/
public function __construct(
CartRuleRepository $cartRuleRepository,
CartRuleCustomerRepository $cartRuleCustomerRepository,
CartRuleCouponRepository $cartRuleCouponRepository,
CartRuleCouponUsageRepository $cartRuleCouponUsageRepository
)
{
$this->cartRuleRepository = $cartRuleRepository;
$this->cartRuleCustomerRepository = $cartRuleCustomerRepository;
$this->cartRuleCouponRepository = $cartRuleCouponRepository;
$this->cartRuleCouponUsageRepository = $cartRuleCouponUsageRepository;
}
/**
* Save cart rule and cart rule coupon properties after place order
*
* @param Order $order
* @return void
*/
public function manageCartRule($order)
{
if (! $order->discount_amount)
return;
$cartRuleIds = explode(',', $order->applied_cart_rule_ids);
$cartRuleIds = array_unique($cartRuleIds);
foreach ($cartRuleIds as $ruleId) {
$rule = $this->cartRuleRepository->find($ruleId);
if (! $rule)
continue;
$rule->update(['times_used' => $rule->times_used + 1]);
if (! $order->customer_id)
continue;
$ruleCustomer = $this->cartRuleCustomerRepository->findOneWhere([
'customer_id' => $order->customer_id,
'cart_rule_id' => $ruleId
]);
if ($ruleCustomer) {
$this->cartRuleCustomerRepository->update(['times_used' => $ruleCustomer->times_used + 1], $ruleCustomer->id);
} else {
$this->cartRuleCustomerRepository->create([
'customer_id' => $order->customer_id,
'cart_rule_id' => $ruleId,
'times_used' => 1
]);
}
}
if (! $order->coupon_code)
return;
$coupon = $this->cartRuleCouponRepository->findOneByField('code', $order->coupon_code);
if ($coupon) {
$this->cartRuleCouponRepository->update(['times_used' => $coupon->times_used + 1], $coupon->id);
if ($order->customer_id) {
$couponUsage = $this->cartRuleCouponUsageRepository->findOneWhere([
'customer_id' => $order->customer_id,
'cart_rule_coupon_id' => $coupon->id
]);
if ($couponUsage) {
$this->cartRuleCouponUsageRepository->update(['times_used' => $couponUsage->times_used + 1], $couponUsage->id);
} else {
$this->cartRuleCouponUsageRepository->create([
'customer_id' => $order->customer_id,
'cart_rule_coupon_id' => $coupon->id,
'times_used' => 1
]);
}
}
}
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Webkul\CartRule\Models;
// use Webkul\Core\Eloquent\TranslatableModel;
use Illuminate\Database\Eloquent\Model;
use Webkul\CartRule\Contracts\CartRule as CartRuleContract;
use Webkul\Core\Models\ChannelProxy;
use Webkul\Customer\Models\CustomerGroupProxy;
// class CartRule extends TranslatableModel implements CartRuleContract
class CartRule extends Model implements CartRuleContract
{
protected $fillable = ['name', 'description', 'starts_from', 'ends_till', 'status', 'coupon_type', 'use_auto_generation', 'usage_per_customer', 'uses_per_coupon', 'times_used', 'condition_type', 'conditions', 'actions', 'end_other_rules', 'uses_attribute_conditions', 'action_type', 'discount_amount', 'discount_quantity', 'discount_step', 'apply_to_shipping', 'free_shipping', 'sort_order'];
protected $casts = [
'conditions' => 'array',
];
// public $translatedAttributes = ['name'];
/**
* Get the channels that owns the cart rule.
*/
public function channels()
{
return $this->belongsToMany(ChannelProxy::modelClass(), 'cart_rule_channels');
}
/**
* Get the customer groups that owns the cart rule.
*/
public function customer_groups()
{
return $this->belongsToMany(CustomerGroupProxy::modelClass(), 'cart_rule_customer_groups');
}
/**
* Get the coupons that owns the cart rule.
*/
public function coupons()
{
return $this->hasOne(CartRuleCouponProxy::modelClass());
}
/**
* Get primary coupon code for cart rule.
*/
public function coupon_code()
{
return $this->coupons()->where('is_primary', 1);
}
/**
* Get primary coupon code for cart rule.
*/
public function getCouponCodeAttribute()
{
$coupon = $this->coupon_code()->first();
if (! $coupon)
return;
return $coupon->code;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Webkul\CartRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CartRule\Contracts\CartRuleCoupon as CartRuleCouponContract;
class CartRuleCoupon extends Model implements CartRuleCouponContract
{
protected $fillable = ['code', 'usage_limit', 'usage_per_customer', 'times_used', 'type', 'cart_rule_id', 'expired_at', 'is_primary'];
/**
* Get the cart rule that owns the cart rule coupon.
*/
public function cart_rule()
{
return $this->belongsTo(CartRuleProxy::modelClass());
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\CartRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CartRuleCouponProxy extends ModelProxy
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace Webkul\CartRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CartRule\Contracts\CartRuleCouponUsage as CartRuleCouponUsageContract;
class CartRuleCouponUsage extends Model implements CartRuleCouponUsageContract
{
public $timestamps = false;
protected $table = 'cart_rule_coupon_usage';
protected $guarded = ['created_at', 'updated_at'];
}

View File

@ -0,0 +1,9 @@
<?php
namespace Webkul\CartRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CartRuleCouponUsageProxy extends ModelProxy
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\CartRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CartRule\Contracts\CartRuleCustomer as CartRuleCustomerContract;
class CartRuleCustomer extends Model implements CartRuleCustomerContract
{
public $timestamps = false;
protected $fillable = ['times_used', 'cart_rule_id', 'customer_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\CartRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CartRuleCustomerProxy extends ModelProxy
{
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Webkul\Discount\Models; namespace Webkul\CartRule\Models;
use Konekt\Concord\Proxies\ModelProxy; use Konekt\Concord\Proxies\ModelProxy;

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\CartRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CartRule\Contracts\CartRuleTranslation as CartRuleTranslationContract;
class CartRuleTranslation extends Model implements CartRuleTranslationContract
{
public $timestamps = false;
protected $fillable = ['label'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\CartRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CartRuleTranslationProxy extends ModelProxy
{
}

View File

@ -0,0 +1,30 @@
<?php
namespace Webkul\CartRule\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\Router;
class CartRuleServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot(Router $router)
{
$this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
$this->app->register(EventServiceProvider::class);
}
/**
* Register services.
*
* @return void
*/
public function register()
{
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Webkul\CartRule\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Event::listen('checkout.order.save.after', 'Webkul\CartRule\Listeners\Order@manageCartRule');
Event::listen('checkout.cart.collect.totals.before', 'Webkul\CartRule\Listeners\Cart@applyCartRules');
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Webkul\CartRule\Providers;
use Konekt\Concord\BaseModuleServiceProvider;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\CartRule\Models\CartRule::class,
\Webkul\CartRule\Models\CartRuleTranslation::class,
\Webkul\CartRule\Models\CartRuleCustomer::class,
\Webkul\CartRule\Models\CartRuleCoupon::class,
\Webkul\CartRule\Models\CartRuleCouponUsage::class
];
}

View File

@ -0,0 +1,74 @@
<?php
namespace Webkul\CartRule\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* CartRuleCoupon Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleCouponRepository extends Repository
{
/**
* @var array
*/
protected $charsets = [
'alphanumeric' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
'alphabetical' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'numeric' => '0123456789'
];
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CartRule\Contracts\CartRuleCoupon';
}
/**
* Creates coupons for cart rule
*
* @param array $data
* @param integer $cartRuleId
* @return void
*/
public function generateCoupons($data, $cartRuleId)
{
$cartRule = app('Webkul\CartRule\Repositories\CartRuleRepository')->findOrFail($cartRuleId);
for ($i = 0; $i < $data['coupon_qty']; $i++) {
parent::create([
'cart_rule_id' => $cartRuleId,
'code' => $data['code_prefix'] . $this->getRandomString($data['code_format'], $data['code_length']) . $data['code_suffix'],
'usage_limit' => $cartRule->uses_per_coupon ?? 0,
'usage_per_customer' => $cartRule->usage_per_customer ?? 0,
'is_primary' => 0,
'expired_at' => $cartRule->ends_till ?: null
]);
}
}
/**
* Creates coupons for cart rule
*
* @param string $format
* @param integer $length
* @return string
*/
public function getRandomString($format, $length)
{
$couponCode = '';
for ($i = 0; $i < $length; $i++) {
$couponCode .= $this->charsets[$format][rand(0, strlen($this->charsets[$format]) - 1)];
}
return $couponCode;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\CartRule\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* CartRuleCouponUsage Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleCouponUsageRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CartRule\Contracts\CartRuleCouponUsage';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\CartRule\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* CartRuleCustomer Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleCustomerRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CartRule\Contracts\CartRuleCustomer';
}
}

View File

@ -0,0 +1,478 @@
<?php
namespace Webkul\CartRule\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Core\Eloquent\Repository;
use Webkul\Attribute\Repositories\AttributeFamilyRepository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Category\Repositories\CategoryRepository;
use Webkul\Tax\Repositories\TaxCategoryRepository;
use Webkul\Core\Repositories\CountryRepository;
use Webkul\Core\Repositories\CountryStateRepository;
/**
* CartRule Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CartRuleRepository extends Repository
{
/**
* AttributeFamilyRepository object
*
* @var AttributeFamilyRepository
*/
protected $attributeFamilyRepository;
/**
* AttributeRepository object
*
* @var AttributeRepository
*/
protected $attributeRepository;
/**
* CategoryRepository class
*
* @var CategoryRepository
*/
protected $categoryRepository;
/**
* CartRuleCouponRepository object
*
* @var CartRuleCouponRepository
*/
protected $cartRuleCouponRepository;
/**
* TaxCategoryRepository class
*
* @var TaxCategoryRepository
*/
protected $taxCategoryRepository;
/**
* CountryRepository class
*
* @var CountryRepository
*/
protected $countryRepository;
/**
* CountryStateRepository class
*
* @var CountryStateRepository
*/
protected $countryStateRepository;
/**
* Create a new repository instance.
*
* @param Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
* @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param Webkul\Category\Repositories\CategoryRepository $categoryRepository
* @param Webkul\CartRule\Repositories\CartRuleCouponRepository $cartRuleCouponRepository
* @param Webkul\Tax\Repositories\TaxCategoryRepository $taxCategoryRepository
* @param Webkul\Core\Repositories\CountryRepository $countryRepository
* @param Webkul\Core\Repositories\CountryStateRepository $countryStateRepository
* @param Illuminate\Container\Container $app
* @return void
*/
public function __construct(
AttributeFamilyRepository $attributeFamilyRepository,
AttributeRepository $attributeRepository,
CategoryRepository $categoryRepository,
CartRuleCouponRepository $cartRuleCouponRepository,
TaxCategoryRepository $taxCategoryRepository,
CountryRepository $countryRepository,
CountryStateRepository $countryStateRepository,
App $app
)
{
$this->attributeFamilyRepository = $attributeFamilyRepository;
$this->attributeRepository = $attributeRepository;
$this->categoryRepository = $categoryRepository;
$this->cartRuleCouponRepository = $cartRuleCouponRepository;
$this->taxCategoryRepository = $taxCategoryRepository;
$this->countryRepository = $countryRepository;
$this->countryStateRepository = $countryStateRepository;
parent::__construct($app);
}
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CartRule\Contracts\CartRule';
}
/**
* @param array $data
* @return mixed
*/
public function create(array $data)
{
$data['starts_from'] = $data['starts_from'] ?: null;
$data['ends_till'] = $data['ends_till'] ?: null;
$data['status'] = ! isset($data['status']) ? 0 : 1;
$cartRule = parent::create($data);
$cartRule->channels()->sync($data['channels']);
$cartRule->customer_groups()->sync($data['customer_groups']);
if ($data['coupon_type'] && ! $data['use_auto_generation']) {
$this->cartRuleCouponRepository->create([
'cart_rule_id' => $cartRule->id,
'code' => $data['coupon_code'],
'usage_limit' => $data['usage_per_customer'] ?? 0,
'usage_per_customer' => $data['usage_per_customer'] ?? 0,
'is_primary' => 1,
'expired_at' => $data['ends_till'] ?: null
]);
}
return $cartRule;
}
/**
* @param array $data
* @param array $id
* @param string $attribute
* @return mixed
*/
public function update(array $data, $id, $attribute = "id")
{
$data['starts_from'] = $data['starts_from'] ?: null;
$data['ends_till'] = $data['ends_till'] ?: null;
$data['status'] = ! isset($data['status']) ? 0 : 1;
$data['conditions'] = $data['conditions'] ?? [];
$cartRule = $this->find($id);
parent::update($data, $id, $attribute);
$cartRule->channels()->sync($data['channels']);
$cartRule->customer_groups()->sync($data['customer_groups']);
if ($data['coupon_type']) {
if (! $data['use_auto_generation']) {
$cartRuleCoupon = $this->cartRuleCouponRepository->findOneWhere(['is_primary' => 1, 'cart_rule_id' => $cartRule->id]);
if ($cartRuleCoupon) {
$this->cartRuleCouponRepository->update([
'code' => $data['coupon_code'],
'usage_limit' => $data['uses_per_coupon'] ?? 0,
'usage_per_customer' => $data['usage_per_customer'] ?? 0,
'expired_at' => $data['ends_till'] ?: null
], $cartRuleCoupon->id);
} else {
$this->cartRuleCouponRepository->create([
'cart_rule_id' => $cartRule->id,
'code' => $data['coupon_code'],
'usage_limit' => $data['uses_per_coupon'] ?? 0,
'usage_per_customer' => $data['usage_per_customer'] ?? 0,
'is_primary' => 1,
'expired_at' => $data['ends_till'] ?: null
]);
}
} else {
$this->cartRuleCouponRepository->deleteWhere(['is_primary' => 1, 'cart_rule_id' => $cartRule->id]);
$this->cartRuleCouponRepository->getModel()->where('cart_rule_id', $cartRule->id)->update([
'usage_limit' => $data['uses_per_coupon'] ?? 0,
'usage_per_customer' => $data['usage_per_customer'] ?? 0,
'expired_at' => $data['ends_till'] ?: null
]);
}
} else {
$cartRuleCoupon = $this->cartRuleCouponRepository->deleteWhere(['is_primary' => 1, 'cart_rule_id' => $cartRule->id]);
}
return $cartRule;
}
/**
* Returns attributes for cart rule conditions
*
* @return array
*/
public function getConditionAttributes()
{
$attributes = [
[
'key' => 'cart',
'label' => trans('admin::app.promotions.cart-rules.cart-attribute'),
'children' => [
[
'key' => 'cart|base_sub_total',
'type' => 'price',
'label' => trans('admin::app.promotions.cart-rules.subtotal')
], [
'key' => 'cart|items_qty',
'type' => 'integer',
'label' => trans('admin::app.promotions.cart-rules.total-items-qty')
], [
'key' => 'cart|payment_method',
'type' => 'select',
'options' => $this->getPaymentMethods(),
'label' => trans('admin::app.promotions.cart-rules.payment-method')
], [
'key' => 'cart|shipping_method',
'type' => 'select',
'options' => $this->getShippingMethods(),
'label' => trans('admin::app.promotions.cart-rules.shipping-method')
], [
'key' => 'cart|postcode',
'type' => 'text',
'label' => trans('admin::app.promotions.cart-rules.shipping-postcode')
], [
'key' => 'cart|state',
'type' => 'select',
'options' => $this->groupedStatesByCountries(),
'label' => trans('admin::app.promotions.cart-rules.shipping-state')
], [
'key' => 'cart|country',
'type' => 'select',
'options' => $this->getCountries(),
'label' => trans('admin::app.promotions.cart-rules.shipping-country')
]
]
], [
'key' => 'cart_item',
'label' => trans('admin::app.promotions.cart-rules.cart-item-attribute'),
'children' => [
[
'key' => 'cart_item|base_price',
'type' => 'price',
'label' => trans('admin::app.promotions.cart-rules.price-in-cart')
], [
'key' => 'cart_item|quantity',
'type' => 'integer',
'label' => trans('admin::app.promotions.cart-rules.qty-in-cart')
], [
'key' => 'cart_item|base_total_weight',
'type' => 'decimal',
'label' => trans('admin::app.promotions.cart-rules.total-weight')
], [
'key' => 'cart_item|base_total',
'type' => 'price',
'label' => trans('admin::app.promotions.cart-rules.subtotal')
]
]
], [
'key' => 'product',
'label' => trans('admin::app.promotions.cart-rules.product-attribute'),
'children' => [
[
'key' => 'product|category_ids',
'type' => 'multiselect',
'label' => trans('admin::app.promotions.cart-rules.categories'),
'options' => $categories = $this->categoryRepository->getCategoryTree()
], [
'key' => 'product|children::category_ids',
'type' => 'multiselect',
'label' => trans('admin::app.promotions.cart-rules.children-categories'),
'options' => $categories
], [
'key' => 'product|parent::category_ids',
'type' => 'multiselect',
'label' => trans('admin::app.promotions.cart-rules.parent-categories'),
'options' => $categories
], [
'key' => 'product|attribute_family_id',
'type' => 'select',
'label' => trans('admin::app.promotions.cart-rules.attribute_family'),
'options' => $this->getAttributeFamilies()
]
]
]
];
foreach ($this->attributeRepository->findWhereNotIn('type', ['textarea', 'image', 'file']) as $attribute) {
$attributeType = $attribute->type;
if ($attribute->code == 'tax_category_id') {
$options = $this->getTaxCategories();
} else {
$options = $attribute->options;
}
if ($attribute->validation == 'decimal')
$attributeType = 'decimal';
if ($attribute->validation == 'numeric')
$attributeType = 'integer';
$attributes[2]['children'][] = [
'key' => 'product|' . $attribute->code,
'type' => $attribute->type,
'label' => $attribute->name,
'options' => $options
];
$attributes[2]['children'][] = [
'key' => 'product|children::' . $attribute->code,
'type' => $attribute->type,
'label' => trans('admin::app.promotions.cart-rules.attribute-name-children-only', ['attribute_name' => $attribute->name]),
'options' => $options
];
$attributes[2]['children'][] = [
'key' => 'product|parent::' . $attribute->code,
'type' => $attribute->type,
'label' => trans('admin::app.promotions.cart-rules.attribute-name-parent-only', ['attribute_name' => $attribute->name]),
'options' => $options
];
}
return $attributes;
}
/**
* Returns all payment methods
*
* @return array
*/
public function getPaymentMethods()
{
$methods = [];
foreach (config('paymentmethods') as $paymentMethod) {
$object = app($paymentMethod['class']);
$methods[] = [
'id' => $object->getCode(),
'admin_name' => $object->getTitle()
];
}
return $methods;
}
/**
* Returns all shipping methods
*
* @return array
*/
public function getShippingMethods()
{
$methods = [];
foreach (config('carriers') as $shippingMethod) {
$object = app($shippingMethod['class']);
$methods[] = [
'id' => $object->getCode(),
'admin_name' => $object->getTitle()
];
}
return $methods;
}
/**
* Returns all countries
*
* @return array
*/
public function getTaxCategories()
{
$taxCategories = [];
foreach ($this->taxCategoryRepository->all() as $taxCategory) {
$taxCategories[] = [
'id' => $taxCategory->id,
'admin_name' => $taxCategory->name,
];
}
return $taxCategories;
}
/**
* Returns all attribute families
*
* @return array
*/
public function getAttributeFamilies()
{
$attributeFamilies = [];
foreach ($this->attributeFamilyRepository->all() as $attributeFamily) {
$attributeFamilies[] = [
'id' => $attributeFamily->id,
'admin_name' => $attributeFamily->name,
];
}
return $attributeFamilies;
}
/**
* Returns all countries
*
* @return array
*/
public function getCountries()
{
$countries = [];
foreach ($this->countryRepository->all() as $country) {
$countries[] = [
'id' => $country->code,
'admin_name' => $country->name,
];
}
return $countries;
}
/**
* Retrieve all grouped states by country code
*
* @return array
*/
public function groupedStatesByCountries()
{
$collection = [];
foreach ($this->countryRepository->all() as $country) {
$countryStates = $this->countryStateRepository->findWhere(
['country_id' => $country->id],
['code', 'default_name as admin_name']
)->toArray();
if (! count($countryStates))
continue;
$collection[] = [
'id' => $country->code,
'admin_name' => $country->name,
'states' => $countryStates
];
}
return $collection;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Webkul\CatalogRule\Console\Commands;
use Illuminate\Console\Command;
use Webkul\CatalogRule\Helpers\CatalogRuleIndex;
class PriceRuleIndex extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'product:price-rule:index';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Automatically updates catalog rule price index information (eg. rule_price)';
/**
* CatalogRuleIndex object
*
* @var Object
*/
protected $catalogRuleIndexHelper;
/**
* Create a new command instance.
*
* @param Webkul\CatalogRuleProduct\Helpers\CatalogRuleIndex $catalogRuleIndexHelper
* @return void
*/
public function __construct(CatalogRuleIndex $catalogRuleIndexHelper)
{
$this->catalogRuleIndexHelper = $catalogRuleIndexHelper;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// $this->catalogRuleIndexHelper->reindexComplete();
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CatalogRule\Contracts;
interface CatalogRule
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CatalogRule\Contracts;
interface CatalogRuleProduct
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\CatalogRule\Contracts;
interface CatalogRuleProductPrice
{
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRemoveCatalogRuleTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('catalog_rule_customer_groups');
Schema::dropIfExists('catalog_rule_channels');
Schema::dropIfExists('catalog_rule_products');
Schema::dropIfExists('catalog_rule_products_price');
Schema::dropIfExists('catalog_rules');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -15,16 +15,17 @@ class CreateCatalogRulesTable extends Migration
{ {
Schema::create('catalog_rules', function (Blueprint $table) { Schema::create('catalog_rules', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->string('name'); $table->string('name')->nullable();
$table->text('description'); $table->string('description')->nullable();
$table->datetime('starts_from')->nullable(); $table->date('starts_from')->nullable();
$table->datetime('ends_till')->nullable(); $table->date('ends_till')->nullable();
$table->boolean('status')->default(0); $table->boolean('status')->default(0);
$table->boolean('condition_type')->default(1);
$table->json('conditions')->nullable(); $table->json('conditions')->nullable();
$table->json('actions')->nullable();
$table->boolean('end_other_rules')->default(0); $table->boolean('end_other_rules')->default(0);
$table->string('action_code')->nullable(); $table->string('action_type')->nullable();
$table->decimal('discount_amount', 20, 4)->default(0.0000); $table->decimal('discount_amount', 12, 4)->default(0);
$table->integer('sort_order')->unsigned()->default(0);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -14,12 +14,13 @@ class CreateCatalogRuleChannelsTable extends Migration
public function up() public function up()
{ {
Schema::create('catalog_rule_channels', function (Blueprint $table) { Schema::create('catalog_rule_channels', function (Blueprint $table) {
$table->increments('id');
$table->integer('channel_id')->unsigned();
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
$table->integer('catalog_rule_id')->unsigned(); $table->integer('catalog_rule_id')->unsigned();
$table->integer('channel_id')->unsigned();
$table->primary(['catalog_rule_id', 'channel_id']);
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade'); $table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->timestamps(); $table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
}); });
} }

View File

@ -14,12 +14,14 @@ class CreateCatalogRuleCustomerGroupsTable extends Migration
public function up() public function up()
{ {
Schema::create('catalog_rule_customer_groups', function (Blueprint $table) { Schema::create('catalog_rule_customer_groups', function (Blueprint $table) {
$table->increments('id');
$table->integer('customer_group_id')->unsigned();
$table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
$table->integer('catalog_rule_id')->unsigned(); $table->integer('catalog_rule_id')->unsigned();
$table->integer('customer_group_id')->unsigned();
$table->primary(['catalog_rule_id', 'customer_group_id'], 'catalog_rule_id_customer_group_id_primary');
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade'); $table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->timestamps(); $table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
}); });
} }

View File

@ -14,20 +14,25 @@ class CreateCatalogRuleProductsTable extends Migration
public function up() public function up()
{ {
Schema::create('catalog_rule_products', function (Blueprint $table) { Schema::create('catalog_rule_products', function (Blueprint $table) {
$table->bigIncrements('id'); $table->increments('id');
$table->integer('catalog_rule_id')->unsigned();
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->datetime('starts_from')->nullable(); $table->datetime('starts_from')->nullable();
$table->datetime('ends_till')->nullable(); $table->datetime('ends_till')->nullable();
$table->integer('customer_group_id')->unsigned(); $table->boolean('end_other_rules')->default(0);
$table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade'); $table->string('action_type')->nullable();
$table->integer('channel_id')->unsigned(); $table->decimal('discount_amount', 12, 4)->default(0);
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade'); $table->integer('sort_order')->unsigned()->default(0);
$table->integer('product_id')->unsigned(); $table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->string('action_code')->nullable();
$table->decimal('action_amount', 20, 4)->default(0.0000); $table->integer('customer_group_id')->unsigned();
$table->timestamps(); $table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
$table->integer('catalog_rule_id')->unsigned();
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->integer('channel_id')->unsigned();
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
}); });
} }
@ -40,4 +45,4 @@ class CreateCatalogRuleProductsTable extends Migration
{ {
Schema::dropIfExists('catalog_rule_products'); Schema::dropIfExists('catalog_rule_products');
} }
} }

View File

@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class CreateCatalogRuleProductsPriceTable extends Migration class CreateCatalogRuleProductPricesTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,20 +13,24 @@ class CreateCatalogRuleProductsPriceTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::create('catalog_rule_products_price', function (Blueprint $table) { Schema::create('catalog_rule_product_prices', function (Blueprint $table) {
$table->bigIncrements('id'); $table->increments('id');
$table->integer('channel_id')->unsigned(); $table->decimal('price', 12, 4)->default(0);
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade'); $table->date('rule_date');
$table->integer('customer_group_id')->unsigned();
$table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('catalog_rule_id')->unsigned();
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->decimal('rule_price', 20, 4)->default(0.0000);
$table->datetime('starts_from')->nullable(); $table->datetime('starts_from')->nullable();
$table->datetime('ends_till')->nullable(); $table->datetime('ends_till')->nullable();
$table->timestamps();
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('customer_group_id')->unsigned();
$table->foreign('customer_group_id')->references('id')->on('customer_groups')->onDelete('cascade');
$table->integer('catalog_rule_id')->unsigned();
$table->foreign('catalog_rule_id')->references('id')->on('catalog_rules')->onDelete('cascade');
$table->integer('channel_id')->unsigned();
$table->foreign('channel_id')->references('id')->on('channels')->onDelete('cascade');
}); });
} }
@ -37,6 +41,6 @@ class CreateCatalogRuleProductsPriceTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('catalog_rule_products_price'); Schema::dropIfExists('catalog_rule_product_prices');
} }
} }

View File

@ -0,0 +1,137 @@
<?php
namespace Webkul\CatalogRule\Helpers;
use Carbon\Carbon;
use Webkul\CatalogRule\Repositories\CatalogRuleRepository;
class CatalogRuleIndex
{
/**
* CatalogRuleRepository object
*
* @var Object
*/
protected $catalogRuleRepository;
/**
* CatalogRuleProduct object
*
* @var Object
*/
protected $catalogRuleHelper;
/**
* CatalogRuleProductPrice object
*
* @var Object
*/
protected $catalogRuleProductPriceHelper;
/**
* Create a new helper instance.
*
* @param Webkul\CatalogRule\Repositories\CatalogRuleRepository $catalogRuleRepository
* @param Webkul\CatalogRuleProduct\Helpers\CatalogRuleProduct $catalogRuleProductHelper
* @param Webkul\CatalogRuleProduct\Helpers\CatalogRuleProductPrice $catalogRuleProductPriceHelper
* @return void
*/
public function __construct(
CatalogRuleRepository $catalogRuleRepository,
CatalogRuleProduct $catalogRuleProductHelper,
CatalogRuleProductPrice $catalogRuleProductPriceHelper
)
{
$this->catalogRuleRepository = $catalogRuleRepository;
$this->catalogRuleProductHelper = $catalogRuleProductHelper;
$this->catalogRuleProductPriceHelper = $catalogRuleProductPriceHelper;
}
/**
* Full reindex
*
* @return void
*/
public function reindexComplete()
{
try {
$this->cleanIndexes();
foreach ($this->getCatalogRules() as $rule) {
$this->catalogRuleProductHelper->insertRuleProduct($rule);
}
$this->catalogRuleProductPriceHelper->indexRuleProductPrice(1000);
} catch (\Exception $e) {
}
}
/**
* Full reindex
*
* @param Product $product
* @return void
*/
public function reindexProduct($product)
{
try {
if (! $product->getTypeInstance()->priceRuleCanBeApplied())
return;
$productIds = $product->getTypeInstance()->isComposite()
? $product->getTypeInstance()->getChildrenIds()
: [$product->id];
$this->cleanIndexes($productIds);
foreach ($this->getCatalogRules() as $rule) {
$this->catalogRuleProductHelper->insertRuleProduct($rule, 1000, $product);
}
$this->catalogRuleProductPriceHelper->indexRuleProductPrice(1000, $product);
} catch (\Exception $e) {
}
}
/**
* Deletes catalog rule product and catalog rule product price indexes
*
* @param array $productIds
* @return void
*/
public function cleanIndexes($productIds = [])
{
$this->catalogRuleProductHelper->cleanProductIndex($productIds);
$this->catalogRuleProductPriceHelper->cleanProductPriceIndex($productIds);
}
/**
* Returns catalog rules
*
* @return Collection
*/
public function getCatalogRules()
{
static $catalogRules;
if ($catalogRules)
return $catalogRules;
$catalogRules = $this->catalogRuleRepository->scopeQuery(function($query) {
return $query->where(function ($query1) {
$query1->where('catalog_rules.starts_from', '<=', Carbon::now()->format('Y-m-d'))->orWhereNull('catalog_rules.starts_from');
})
->where(function ($query2) {
$query2->where('catalog_rules.ends_till', '>=', Carbon::now()->format('Y-m-d'))->orWhereNull('catalog_rules.ends_till');
})
->orderBy('sort_order', 'asc');
})->findWhere(['status' => 1]);
return $catalogRules;
}
}

View File

@ -0,0 +1,253 @@
<?php
namespace Webkul\CatalogRule\Helpers;
use Carbon\Carbon;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\CatalogRule\Repositories\CatalogRuleProductRepository;
use Webkul\Rule\Helpers\Validator;
class CatalogRuleProduct
{
/**
* AttributeRepository object
*
* @var AttributeRepository
*/
protected $attributeRepository;
/**
* ProductRepository object
*
* @var ProductRepository
*/
protected $productRepository;
/**
* CatalogRuleProductRepository object
*
* @var CatalogRuleProductRepository
*/
protected $catalogRuleProductRepository;
/**
* Validator object
*
* @var Validator
*/
protected $validator;
/**
* Create a new helper instance.
*
* @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param Webkul\Product\Repositories\ProductRepository $productRepository
* @param Webkul\CatalogRule\Repositories\CatalogRuleProductRepository $catalogRuleProductRepository
* @param Webkul\Rule\Helpers\Validator $validator
* @return void
*/
public function __construct(
AttributeRepository $attributeRepository,
ProductRepository $productRepository,
CatalogRuleProductRepository $catalogRuleProductRepository,
Validator $validator
)
{
$this->attributeRepository = $attributeRepository;
$this->productRepository = $productRepository;
$this->catalogRuleProductRepository = $catalogRuleProductRepository;
$this->validator = $validator;
}
/**
* Collect discount on cart
*
* @param CatalogRule $rule
* @param integer $batchCount
* @return void
*/
public function insertRuleProduct($rule, $batchCount = 1000, $product = null)
{
$productIds = $this->getMatchingProductIds($rule, $product);
$rows = [];
$startsFrom = $rule->starts_from ? Carbon::createFromTimeString($rule->starts_from . " 00:00:01") : null;
$endsTill = $rule->ends_till ? Carbon::createFromTimeString($rule->ends_till . " 23:59:59") : null;
foreach ($productIds as $productId) {
foreach ($rule->channels()->pluck('id') as $channelId) {
foreach ($rule->customer_groups()->pluck('id') as $customerGroupId) {
$rows[] = [
'starts_from' => $startsFrom,
'ends_till' => $endsTill,
'catalog_rule_id' => $rule->id,
'channel_id' => $channelId,
'customer_group_id' => $customerGroupId,
'product_id' => $productId,
'discount_amount' => $rule->discount_amount,
'action_type' => $rule->action_type,
'end_other_rules' => $rule->end_other_rules,
'sort_order' => $rule->sort_order,
];
if (count($rows) == $batchCount) {
$this->catalogRuleProductRepository->getModel()->insert($rows);
$rows = [];
}
}
}
}
if (! empty($rows))
$this->catalogRuleProductRepository->getModel()->insert($rows);
}
/**
* Get array of product ids which are matched by rule
*
* @param CatalogRule $rule
* @param Product $product
* @return array
*/
public function getMatchingProductIds($rule, $product = null)
{
$qb = $this->productRepository->scopeQuery(function($query) use($rule, $product) {
$qb = $query->distinct()
->addSelect('products.*')
->leftJoin('product_flat', 'products.id', '=', 'product_flat.product_id')
->leftJoin('channels', 'product_flat.channel', '=', 'channels.code')
->whereIn('channels.id', $rule->channels()->pluck('id')->toArray());
if ($product)
$qb->where('products.id', $product->id);
if (! $rule->conditions)
return $qb;
$appliedAttributes = [];
foreach ($rule->conditions as $condition) {
if (! $condition['attribute']
|| ! isset($condition['value'])
|| is_null($condition['value'])
|| $condition['value'] == ''
|| in_array($condition['attribute'], $appliedAttributes))
continue;
$appliedAttributes[] = $condition['attribute'];
$chunks = explode('|', $condition['attribute']);
$qb = $this->addAttributeToSelect(end($chunks), $qb);
}
return $qb;
});
$validatedProductIds = [];
foreach ($qb->get() as $product) {
if (! $product->getTypeInstance()->priceRuleCanBeApplied())
continue;
if ($this->validator->validate($rule, $product)) {
if ($product->getTypeInstance()->isComposite()) {
$validatedProductIds = array_merge($validatedProductIds, $product->getTypeInstance()->getChildrenIds());
} else {
$validatedProductIds[] = $product->id;
}
}
}
return array_unique($validatedProductIds);
}
/**
* Add product attribute condition to query
*
* @param string $attributeCode
* @param QueryBuilder $query
* @return QueryBuilder
*/
public function addAttributeToSelect($attributeCode, $query)
{
$attribute = $this->attributeRepository->findOneByField('code', $attributeCode);
if (! $attribute)
return $query;
$query = $query->leftJoin('product_attribute_values as ' . 'pav_' . $attribute->code, function($qb) use($attribute) {
$qb = $qb->where('pav_' . $attribute->code . '.channel', $attribute->value_per_channel ? core()->getDefaultChannelCode() : null)
->where('pav_' . $attribute->code . '.locale', $attribute->value_per_locale ? app()->getLocale() : null);
$qb->on('products.id', 'pav_' . $attribute->code . '.product_id')
->where('pav_' . $attribute->code . '.attribute_id', $attribute->id);
});
$query = $query->addSelect('pav_' . $attribute->code . '.' . ProductAttributeValue::$attributeTypeFields[$attribute->type] . ' as ' . $attribute->code);
return $query;
}
/**
* Returns catalog rule products
*
* @param Product $product
* @return Collection
*/
public function getCatalogRuleProducts($product = null)
{
$results = $this->catalogRuleProductRepository->scopeQuery(function($query) use($product) {
$qb = $query->distinct()
->select('catalog_rule_products.*')
->leftJoin('products', 'catalog_rule_products.product_id', '=', 'products.id')
->orderBy('channel_id', 'asc')
->orderBy('customer_group_id', 'asc')
->orderBy('product_id', 'asc')
->orderBy('sort_order', 'asc')
->orderBy('catalog_rule_id', 'asc');
$qb = $this->addAttributeToSelect('price', $qb);
if ($product) {
if (! $product->getTypeInstance()->priceRuleCanBeApplied())
return $qb;
if ($product->getTypeInstance()->isComposite()) {
$qb->whereIn('catalog_rule_products.product_id', $product->getTypeInstance()->getChildrenIds());
} else {
$qb->where('catalog_rule_products.product_id', $product->id);
}
}
return $qb;
})->get();
return $results;
}
/**
* Returns catalog rules
*
* @param CatalogRule $rule
* @return void
*/
public function cleanProductIndex($productIds = [])
{
if (count($productIds)) {
$this->catalogRuleProductRepository->getModel()->whereIn('product_id', $productIds)->delete();
} else {
$this->catalogRuleProductRepository->deleteWhere([
['product_id', 'like', '%%']
]);
}
}
}

View File

@ -0,0 +1,217 @@
<?php
namespace Webkul\CatalogRule\Helpers;
use Carbon\Carbon;
use Webkul\CatalogRule\Repositories\CatalogRuleProductPriceRepository;
use Webkul\Customer\Repositories\CustomerGroupRepository;
class CatalogRuleProductPrice
{
/**
* CatalogRuleProductPriceRepository object
*
* @var CatalogRuleProductPriceRepository
*/
protected $catalogRuleProductPriceRepository;
/**
* CatalogRuleProduct object
*
* @var CatalogRuleProduct
*/
protected $catalogRuleProductHelper;
/**
* CustomerGroupRepository object
*
* @var CustomerGroupRepository
*/
protected $customerGroupRepository;
/**
* Create a new helper instance.
*
* @param Webkul\Attribute\Repositories\CatalogRuleProductPriceRepository $catalogRuleProductPriceRepository
* @param Webkul\CatalogRule\Repositories\CatalogRuleProduct $catalogRuleProductHelper
* @param Webkul\Customer\Repositories\CustomerGroupRepository $customerGroupRepository
* @return void
*/
public function __construct(
CatalogRuleProductPriceRepository $catalogRuleProductPriceRepository,
CatalogRuleProduct $catalogRuleProductHelper,
CustomerGroupRepository $customerGroupRepository
)
{
$this->catalogRuleProductPriceRepository = $catalogRuleProductPriceRepository;
$this->catalogRuleProductHelper = $catalogRuleProductHelper;
$this->customerGroupRepository = $customerGroupRepository;
}
/**
* Return current logged in customer
*
* @return Customer | Boolean
*/
public function getCurrentCustomer()
{
$guard = request()->has('token') ? 'api' : 'customer';
return auth()->guard($guard);
}
/**
* Collect discount on cart
*
* @param integer $batchCount
* @param Product $product
* @return void
*/
public function indexRuleProductPrice($batchCount, $product = null)
{
$dates = [
'current' => $currentDate = Carbon::now(),
'previous' => (clone $currentDate)->subDays('1')->setTime(23, 59, 59),
'next' => (clone $currentDate)->addDays('1')->setTime(0, 0, 0),
];
$prices = $endRuleFlags = [];
$previousKey = null;
$catalogRuleProducts = $this->catalogRuleProductHelper->getCatalogRuleProducts($product);
foreach ($catalogRuleProducts as $row) {
$productKey = $row->product_id . '-' . $row->channel_id . '-' . $row->customer_group_id;
if ($previousKey && $previousKey != $productKey) {
$endRuleFlags = [];
if (count($prices) > $batchCount) {
$this->catalogRuleProductPriceRepository->getModel()->insert($prices);
$prices = [];
}
}
foreach ($dates as $key => $date) {
if ((! $row->starts_from || $date >= $row->starts_from)
&& (! $row->ends_till || $date <= $row->ends_till))
{
$priceKey = $date->getTimestamp() . '-' . $productKey;
if (isset($endRuleFlags[$priceKey]))
continue;
if (! isset($prices[$priceKey])) {
$prices[$priceKey] = [
'rule_date' => $date,
'catalog_rule_id' => $row->catalog_rule_id,
'channel_id' => $row->channel_id,
'customer_group_id' => $row->customer_group_id,
'product_id' => $row->product_id,
'price' => $this->calculate($row),
'starts_from' => $row->starts_from,
'ends_till' => $row->ends_till,
];
} else {
$prices[$priceKey]['price'] = $this->calculate($row, $prices[$priceKey]);
$prices[$priceKey]['starts_from'] = max($prices[$priceKey]['starts_from'], $row->starts_from);
$prices[$priceKey]['ends_till'] = min($prices[$priceKey]['ends_till'], $row->ends_till);
}
if ($row->end_other_rules)
$endRuleFlags[$priceKey] = true;
}
}
$previousKey = $productKey;
}
$this->catalogRuleProductPriceRepository->getModel()->insert($prices);
}
/**
* Calculates product price based on rule
*
* @param array $rule
* @param Product|null $productData
* @return float
*/
public function calculate($rule, $productData = null)
{
$price = $productData && isset($productData['price']) ? $productData['price'] : $rule->price;
switch ($rule->action_type) {
case 'to_fixed':
$price = min($rule->discount_amount, $price);
break;
case 'to_percent':
$price = $price * $rule->discount_amount / 100;
break;
case 'by_fixed':
$price = max(0, $price - $rule->discount_amount);
break;
case 'by_percent':
$price = $price * (1 - $rule->discount_amount / 100);
break;
}
return $price;
}
/**
* Clean product price index
*
* @param array $productIds
* @return void
*/
public function cleanProductPriceIndex($productIds = [])
{
if (count($productIds)) {
$this->catalogRuleProductPriceRepository->getModel()->whereIn('product_id', $productIds)->delete();
} else {
$this->catalogRuleProductPriceRepository->deleteWhere([
['product_id', 'like', '%%']
]);
}
}
/**
* Get catalog rules product price for specific date, channel and customer group
*
* @param Product $product
* @return void
*/
public function getRulePrice($product)
{
if ($this->getCurrentCustomer()->check()) {
$customerGroupId = $this->getCurrentCustomer()->user()->customer_group_id;
} else {
$customerGroup = $this->customerGroupRepository->findOneByField('code', 'guest');
if (! $customerGroup)
return;
$customerGroupId = $customerGroup->id;
}
return $this->catalogRuleProductPriceRepository->findOneWhere([
'product_id' => $product->id,
'channel_id' => core()->getCurrentChannel()->id,
'customer_group_id' => $customerGroupId,
'rule_date' => Carbon::now()->format('Y-m-d'),
]);
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace Webkul\CatalogRule\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Webkul\CatalogRule\Repositories\CatalogRuleRepository;
use Webkul\CatalogRule\Helpers\CatalogRuleIndex;
/**
* Catalog Rule controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CatalogRuleController extends Controller
{
/**
* Initialize _config, a default request parameter with route
*
* @param array
*/
protected $_config;
/**
* To hold Catalog repository instance
*
* @var CatalogRuleRepository
*/
protected $catalogRuleRepository;
/**
* CatalogRuleIndex
*
* @var CatalogRuleIndex
*/
protected $catalogRuleIndexHelper;
/**
* Create a new controller instance.
*
* @param \Webkul\CatalogRule\Repositories\CatalogRuleRepository $catalogRuleRepository
* @param \Webkul\CatalogRule\Helpers\CatalogRuleIndex $catalogRuleIndexHelper
* @return void
*/
public function __construct(
CatalogRuleRepository $catalogRuleRepository,
CatalogRuleIndex $catalogRuleIndexHelper
)
{
$this->_config = request('_config');
$this->catalogRuleRepository = $catalogRuleRepository;
$this->catalogRuleIndexHelper = $catalogRuleIndexHelper;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view($this->_config['view']);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view($this->_config['view']);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store()
{
$this->validate(request(), [
'name' => 'required',
'channels' => 'required|array|min:1',
'customer_groups' => 'required|array|min:1',
'starts_from' => 'nullable|date',
'ends_till' => 'nullable|date|after_or_equal:starts_from',
'action_type' => 'required',
'discount_amount' => 'required|numeric'
]);
$data = request()->all();
Event::fire('promotions.catalog_rule.create.before');
$catalogRule = $this->catalogRuleRepository->create($data);
Event::fire('promotions.catalog_rule.create.after', $catalogRule);
$this->catalogRuleIndexHelper->reindexComplete();
session()->flash('success', trans('admin::app.response.create-success', ['name' => 'Catalog Rule']));
return redirect()->route($this->_config['redirect']);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$catalogRule = $this->catalogRuleRepository->findOrFail($id);
return view($this->_config['view'], compact('catalogRule'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->validate(request(), [
'name' => 'required',
'channels' => 'required|array|min:1',
'customer_groups' => 'required|array|min:1',
'starts_from' => 'nullable|date',
'ends_till' => 'nullable|date|after_or_equal:starts_from',
'action_type' => 'required',
'discount_amount' => 'required|numeric'
]);
$catalogRule = $this->catalogRuleRepository->findOrFail($id);
Event::fire('promotions.catalog_rule.update.before', $catalogRule);
$catalogRule = $this->catalogRuleRepository->update(request()->all(), $id);
Event::fire('promotions.catalog_rule.update.after', $catalogRule);
$this->catalogRuleIndexHelper->reindexComplete();
session()->flash('success', trans('admin::app.response.update-success', ['name' => 'Catalog Rule']));
return redirect()->route($this->_config['redirect']);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$catalogRule = $this->catalogRuleRepository->findOrFail($id);
try {
Event::fire('promotions.catalog_rule.delete.before', $id);
$this->catalogRuleRepository->delete($id);
Event::fire('promotions.catalog_rule.delete.after', $id);
session()->flash('success', trans('admin::app.response.delete-success', ['name' => 'Catalog Rule']));
return response()->json(['message' => true], 200);
} catch(\Exception $e) {
session()->flash('error', trans('admin::app.response.delete-failed', ['name' => 'Catalog Rule']));
}
return response()->json(['message' => false], 400);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\CatalogRule\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View File

@ -0,0 +1,41 @@
<?php
namespace Webkul\CatalogRule\Listeners;
use Webkul\CatalogRule\Helpers\CatalogRuleIndex;
/**
* Products Event handler
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Product
{
/**
* Product Repository Object
*
* @var Object
*/
protected $catalogRuleIndexHelper;
/**
* Create a new listener instance.
*
* @param Webkul\CatalogRule\Helpers\CatalogRuleIndex $catalogRuleIndexHelper
* @return void
*/
public function __construct(CatalogRuleIndex $catalogRuleIndexHelper)
{
$this->catalogRuleIndexHelper = $catalogRuleIndexHelper;
}
/**
* @param Product $product
* @return void
*/
public function createProductRuleIndex($product)
{
$this->catalogRuleIndexHelper->reindexProduct($product);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Webkul\CatalogRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CatalogRule\Contracts\CatalogRule as CatalogRuleContract;
use Webkul\Core\Models\ChannelProxy;
use Webkul\Customer\Models\CustomerGroupProxy;
class CatalogRule extends Model implements CatalogRuleContract
{
protected $fillable = ['name', 'description', 'starts_from', 'ends_till', 'status', 'condition_type', 'conditions', 'end_other_rules', 'action_type', 'discount_amount', 'sort_order'];
protected $casts = [
'conditions' => 'array'
];
/**
* Get the channels that owns the catalog rule.
*/
public function channels()
{
return $this->belongsToMany(ChannelProxy::modelClass(), 'catalog_rule_channels');
}
/**
* Get the customer groups that owns the catalog rule.
*/
public function customer_groups()
{
return $this->belongsToMany(CustomerGroupProxy::modelClass(), 'catalog_rule_customer_groups');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\CatalogRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CatalogRule\Contracts\CatalogRuleProduct as CatalogRuleProductContract;
class CatalogRuleProduct extends Model implements CatalogRuleProductContract
{
public $timestamps = false;
protected $fillable = ['starts_from', 'ends_till', 'discount_amount', 'action_type', 'end_other_rules', 'sort_order', 'catalog_rule_id', 'channel_id', 'customer_group_id', 'product_id'];
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\CatalogRule\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\CatalogRule\Contracts\CatalogRuleProductPrice as CatalogRuleProductPriceContract;
class CatalogRuleProductPrice extends Model implements CatalogRuleProductPriceContract
{
public $timestamps = false;
protected $fillable = ['price', 'rule_date', 'starts_from', 'ends_till', 'catalog_rule_id', 'channel_id', 'customer_group_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\CatalogRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CatalogRuleProductPriceProxy extends ModelProxy
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\CatalogRule\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CatalogRuleProductProxy extends ModelProxy
{
}

View File

@ -1,10 +1,10 @@
<?php <?php
namespace Webkul\Discount\Models; namespace Webkul\CatalogRule\Models;
use Konekt\Concord\Proxies\ModelProxy; use Konekt\Concord\Proxies\ModelProxy;
class CatalogRuleProxy extends ModelProxy class CatalogRuleProxy extends ModelProxy
{ {
} }

View File

@ -0,0 +1,42 @@
<?php
namespace Webkul\CatalogRule\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Event;
use Webkul\CatalogRule\Console\Commands\PriceRuleIndex;
class CatalogRuleServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot(Router $router)
{
$this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
Event::listen('catalog.product.update.after', 'Webkul\CatalogRule\Listeners\Product@createProductRuleIndex');
}
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->registerCommands();
}
/**
* Register the console commands of this package
*/
protected function registerCommands()
{
if ($this->app->runningInConsole())
$this->commands([PriceRuleIndex::class]);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Webkul\CatalogRule\Providers;
use Konekt\Concord\BaseModuleServiceProvider;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\CatalogRule\Models\CatalogRule::class,
\Webkul\CatalogRule\Models\CatalogRuleProduct::class,
\Webkul\CatalogRule\Models\CatalogRuleProductPrice::class
];
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\CatalogRule\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* CatalogRuleProductPrice Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CatalogRuleProductPriceRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CatalogRule\Contracts\CatalogRuleProductPrice';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\CatalogRule\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* CatalogRuleProductPrice Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CatalogRuleProductRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CatalogRule\Contracts\CatalogRuleProduct';
}
}

View File

@ -0,0 +1,225 @@
<?php
namespace Webkul\CatalogRule\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Core\Eloquent\Repository;
use Webkul\Attribute\Repositories\AttributeFamilyRepository;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Category\Repositories\CategoryRepository;
use Webkul\Tax\Repositories\TaxCategoryRepository;
/**
* CatalogRule Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class CatalogRuleRepository extends Repository
{
/**
* AttributeFamilyRepository object
*
* @var AttributeFamilyRepository
*/
protected $attributeFamilyRepository;
/**
* AttributeRepository object
*
* @var AttributeRepository
*/
protected $attributeRepository;
/**
* CategoryRepository class
*
* @var CategoryRepository
*/
protected $categoryRepository;
/**
* TaxCategoryRepository class
*
* @var TaxCategoryRepository
*/
protected $taxCategoryRepository;
/**
* Create a new repository instance.
*
* @param Webkul\Attribute\Repositories\AttributeFamilyRepository $attributeFamilyRepository
* @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param Webkul\Category\Repositories\CategoryRepository $categoryRepository
* @param Webkul\Tax\Repositories\TaxCategoryRepository $taxCategoryRepository
* @param Illuminate\Container\Container $app
* @return void
*/
public function __construct(
AttributeFamilyRepository $attributeFamilyRepository,
AttributeRepository $attributeRepository,
CategoryRepository $categoryRepository,
TaxCategoryRepository $taxCategoryRepository,
App $app
)
{
$this->attributeFamilyRepository = $attributeFamilyRepository;
$this->attributeRepository = $attributeRepository;
$this->categoryRepository = $categoryRepository;
$this->taxCategoryRepository = $taxCategoryRepository;
parent::__construct($app);
}
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\CatalogRule\Contracts\CatalogRule';
}
/**
* @param array $data
* @return mixed
*/
public function create(array $data)
{
$data['starts_from'] = $data['starts_from'] ?: null;
$data['ends_till'] = $data['ends_till'] ?: null;
$data['status'] = ! isset($data['status']) ? 0 : 1;
$catalogRule = parent::create($data);
$catalogRule->channels()->sync($data['channels']);
$catalogRule->customer_groups()->sync($data['customer_groups']);
return $catalogRule;
}
/**
* @param array $data
* @param array $id
* @param string $attribute
* @return mixed
*/
public function update(array $data, $id, $attribute = "id")
{
$data['starts_from'] = $data['starts_from'] ?: null;
$data['ends_till'] = $data['ends_till'] ?: null;
$data['status'] = ! isset($data['status']) ? 0 : 1;
$data['conditions'] = $data['conditions'] ?? [];
$catalogRule = $this->find($id);
parent::update($data, $id, $attribute);
$catalogRule->channels()->sync($data['channels']);
$catalogRule->customer_groups()->sync($data['customer_groups']);
return $catalogRule;
}
/**
* Returns attributes for catalog rule conditions
*
* @return array
*/
public function getConditionAttributes()
{
$attributes = [
[
'key' => 'product',
'label' => trans('admin::app.promotions.catalog-rules.product-attribute'),
'children' => [
[
'key' => 'product|category_ids',
'type' => 'multiselect',
'label' => trans('admin::app.promotions.catalog-rules.categories'),
'options' => $this->categoryRepository->getCategoryTree()
], [
'key' => 'product|attribute_family_id',
'type' => 'select',
'label' => trans('admin::app.promotions.catalog-rules.attribute_family'),
'options' => $this->getAttributeFamilies()
]
]
]
];
foreach ($this->attributeRepository->findWhereNotIn('type', ['textarea', 'image', 'file']) as $attribute) {
$attributeType = $attribute->type;
if ($attribute->code == 'tax_category_id') {
$options = $this->getTaxCategories();
} else {
$options = $attribute->options;
}
if ($attribute->validation == 'decimal')
$attributeType = 'decimal';
if ($attribute->validation == 'numeric')
$attributeType = 'integer';
$attributes[0]['children'][] = [
'key' => 'product|' . $attribute->code,
'type' => $attribute->type,
'label' => $attribute->name,
'options' => $options
];
}
return $attributes;
}
/**
* Returns all tax categories
*
* @return array
*/
public function getTaxCategories()
{
$taxCategories = [];
foreach ($this->taxCategoryRepository->all() as $taxCategory) {
$taxCategories[] = [
'id' => $taxCategory->id,
'admin_name' => $taxCategory->name,
];
}
return $taxCategories;
}
/**
* Returns all attribute families
*
* @return array
*/
public function getAttributeFamilies()
{
$attributeFamilies = [];
foreach ($this->attributeFamilyRepository->all() as $attributeFamily) {
$attributeFamilies[] = [
'id' => $attributeFamily->id,
'admin_name' => $attributeFamily->name,
];
}
return $attributeFamilies;
}
}

View File

@ -614,6 +614,8 @@ class Cart {
if (! $cart = $this->getCart()) if (! $cart = $this->getCart())
return false; return false;
Event::fire('checkout.cart.collect.totals.before', $cart);
$this->calculateItemsTax(); $this->calculateItemsTax();
$cart->grand_total = $cart->base_grand_total = 0; $cart->grand_total = $cart->base_grand_total = 0;
@ -636,11 +638,15 @@ class Cart {
} }
if ($shipping = $cart->selected_shipping_rate) { if ($shipping = $cart->selected_shipping_rate) {
$cart->grand_total = (float) $cart->grand_total + $shipping->price; $cart->grand_total = (float) $cart->grand_total + $shipping->price - $shipping->discount_amount;
$cart->base_grand_total = (float) $cart->base_grand_total + $shipping->base_price; $cart->base_grand_total = (float) $cart->base_grand_total + $shipping->base_price - $shipping->base_discount_amount;
$cart->discount_amount += $shipping->discount_amount;
$cart->base_discount_amount += $shipping->base_discount_amount;
} }
$quantities = 0; $quantities = 0;
foreach ($cart->items as $item) { foreach ($cart->items as $item) {
$quantities = $quantities + $item->quantity; $quantities = $quantities + $item->quantity;
} }
@ -650,6 +656,8 @@ class Cart {
$cart->items_qty = $quantities; $cart->items_qty = $quantities;
$cart->save(); $cart->save();
Event::fire('checkout.cart.collect.totals.after', $cart);
} }
/** /**
@ -835,6 +843,8 @@ class Cart {
'base_sub_total' => $data['base_sub_total'], 'base_sub_total' => $data['base_sub_total'],
'tax_amount' => $data['tax_total'], 'tax_amount' => $data['tax_total'],
'base_tax_amount' => $data['base_tax_total'], 'base_tax_amount' => $data['base_tax_total'],
'coupon_code' => $data['coupon_code'],
'applied_cart_rule_ids' => $data['applied_cart_rule_ids'],
'discount_amount' => $data['discount_amount'], 'discount_amount' => $data['discount_amount'],
'base_discount_amount' => $data['base_discount_amount'], 'base_discount_amount' => $data['base_discount_amount'],
'billing_address' => array_except($data['billing_address'], ['id', 'cart_id']), 'billing_address' => array_except($data['billing_address'], ['id', 'cart_id']),
@ -972,4 +982,37 @@ class Cart {
return true; return true;
} }
/**
* Set coupon code to the cart
*
* @param string $code
* @return Cart
*/
public function setCouponCode($code)
{
$cart = $this->getCart();
$cart->coupon_code = $code;
$cart->save();
return $this;
}
/**
* Remove coupon code from cart
*
* @return Cart
*/
public function removeCouponCode()
{
$cart = $this->getCart();
$cart->coupon_code = null;
$cart->save();
return $this;
}
} }

View File

@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class AddProductsSelectionColumnInCartRulesTable extends Migration class AddAppliedCartRuleIdsColumnInCartTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,8 +13,8 @@ class AddProductsSelectionColumnInCartRulesTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('cart_rules', function (Blueprint $table) { Schema::table('cart', function (Blueprint $table) {
$table->json('products_selection')->nullable(); $table->string('applied_cart_rule_ids')->nullable();
}); });
} }
@ -25,8 +25,8 @@ class AddProductsSelectionColumnInCartRulesTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('cart_rules', function (Blueprint $table) { Schema::table('cart', function (Blueprint $table) {
$table->dropColumn('products_selection'); //
}); });
} }
} }

View File

@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class RemoveIsGuestFromCartRulesTable extends Migration class AddAppliedCartRuleIdsColumnInCartItemsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,8 +13,8 @@ class RemoveIsGuestFromCartRulesTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('cart_rules', function (Blueprint $table) { Schema::table('cart_items', function (Blueprint $table) {
$table->dropColumn('is_guest'); $table->string('applied_cart_rule_ids')->nullable();
}); });
} }
@ -25,8 +25,8 @@ class RemoveIsGuestFromCartRulesTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('cart_rules', function (Blueprint $table) { Schema::table('cart_items', function (Blueprint $table) {
$table->boolean('is_guest')->default(0); //
}); });
} }
} }

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddDiscountColumnsInCartShippingRatesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('cart_shipping_rates', function (Blueprint $table) {
$table->decimal('discount_amount', 12, 4)->default(0);
$table->decimal('base_discount_amount', 12, 4)->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('cart_shipping_rates', function (Blueprint $table) {
//
});
}
}

View File

@ -12,8 +12,6 @@ class Cart extends Model implements CartContract
protected $guarded = ['id', 'created_at', 'updated_at']; protected $guarded = ['id', 'created_at', 'updated_at'];
protected $hidden = ['coupon_code'];
protected $with = ['items', 'items.children']; protected $with = ['items', 'items.children'];
/** /**

View File

@ -7,7 +7,7 @@ use Webkul\Checkout\Contracts\CartShippingRate as CartShippingRateContract;
class CartShippingRate extends Model implements CartShippingRateContract class CartShippingRate extends Model implements CartShippingRateContract
{ {
protected $fillable = ['carrier', 'carrier_title', 'method', 'method_title', 'method_description', 'price', 'base_price']; protected $fillable = ['carrier', 'carrier_title', 'method', 'method_title', 'method_description', 'price', 'base_price', 'discount_amount', 'base_discount_amount'];
/** /**
* Get the post that owns the comment. * Get the post that owns the comment.

View File

@ -19,7 +19,8 @@ return [
\Webkul\Sales\Providers\ModuleServiceProvider::class, \Webkul\Sales\Providers\ModuleServiceProvider::class,
\Webkul\Tax\Providers\ModuleServiceProvider::class, \Webkul\Tax\Providers\ModuleServiceProvider::class,
\Webkul\User\Providers\ModuleServiceProvider::class, \Webkul\User\Providers\ModuleServiceProvider::class,
\Webkul\Discount\Providers\ModuleServiceProvider::class, \Webkul\CatalogRule\Providers\ModuleServiceProvider::class,
\Webkul\CartRule\Providers\ModuleServiceProvider::class,
\Webkul\CMS\Providers\ModuleServiceProvider::class \Webkul\CMS\Providers\ModuleServiceProvider::class
] ]
]; ];

View File

@ -1,27 +0,0 @@
{
"name": "bagisto/laravel-discount",
"license": "MIT",
"authors": [
{
"name": "Prashant Singh",
"email": "prashant.singh852@webkul.com"
}
],
"require": {
"propaganistas/laravel-intl": "^2.0"
},
"autoload": {
"psr-4": {
"Webkul\\Discount\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Webkul\\Discount\\DiscountServiceProvider"
],
"aliases": {}
}
},
"minimum-stability": "dev"
}

View File

@ -1,88 +0,0 @@
<?php
namespace Webkul\Discount\Actions\Cart;
abstract class Action
{
/**
* To hold the current rule
*/
protected $rule;
abstract public function calculate($rule);
/**
* To find the eligble items for the current rule,
*
* @param CartRule $rule
* @return Collection $eligibleItems
*/
public function getEligibleItems()
{
$rule = $this->rule;
$cart = \Cart::getCart();
$items = $cart->items()->get();
$eligibleItems = collect();
if ($this->rule->action_type == 'whole_cart_to_percent' || $this->rule->action_type == 'whole_cart_to_fixed_amount')
$this->eligibleItems = $items;
if (! $rule->uses_attribute_conditions) {
return $items;
} else {
$productIDs = explode(',', $rule->product_ids);
foreach ($items as $item) {
foreach ($productIDs as $productID) {
$childrens = $item->children;
foreach ($childrens as $children) {
if ($children->product_id == $productID)
$eligibleItems->push($item);
}
if ($item->product_id == $productID)
$eligibleItems->push($item);
}
}
return $eligibleItems;
}
}
/**
* To check the items applicability
*/
public function checkApplicability()
{
$rule = $this->rule;
$eligibleItems = $this->getEligibleItems($rule);
$apply = function () use($rule, $eligibleItems) {
if ($rule->action_type == 'percent_of_product') {
return true;
} else {
if ($rule->action_type == 'whole_cart_to_percent' && $rule->uses_attribute_condition) {
$matchingIds = explode(',', $rule->product_ids);
foreach ($matchingIds as $matchingId) {
foreach ($eligibleItems as $item) {
if (($item->child ? $item->child->product_id : $item->product_id) == $matchingId)
return true;
}
}
return false;
} else {
return true;
}
}
};
return $apply();
}
}

View File

@ -1,88 +0,0 @@
<?php
namespace Webkul\Discount\Actions\Cart;
use Webkul\Discount\Actions\Cart\Action;
class FixedAmount extends Action
{
public function __construct($rule)
{
$this->rule = $rule;
}
public function calculate($rule)
{
$impact = collect();
$totalDiscount = 0;
if ($this->checkApplicability()) {
$eligibleItems = $this->getEligibleItems();
foreach ($eligibleItems as $item) {
$report = array();
$report['item_id'] = $item->id;
$report['child_items'] = collect();
$perItemDiscount = $rule->disc_amount / $eligibleItems->count();
$itemPrice = $item->base_price;
$itemQuantity = $item->quantity;
$discQuantity = $rule->disc_quantity;
$discQuantity = $itemQuantity <= $discQuantity ? $itemQuantity : $discQuantity;
if ($item->product->getTypeInstance()->isComposite()) {
$isQtyZero = true;
foreach ($item->children as $children) {
if ($children->quantity > 0)
$isQtyZero = false;
}
if ($isQtyZero) {
// case for configurable products
$report['product_id'] = $item->children->first()->product_id;
} else {
// composites other than configurable
$report['product_id'] = $item->product_id;
foreach ($item->children as $children) {
$childBaseTotal = $children->base_total;
$itemDiscount = $childBaseTotal / ($item->base_total / 100);
$children->discount = ($itemDiscount / 100) * $perItemDiscount;
$children->discount = $children->base_total > $children->discount ? $children->discount : $children->base_total;
$report['child_items']->push($children);
}
}
} else {
$report['product_id'] = $item->product_id;
}
$discount = round($perItemDiscount, 4) * $discQuantity;
$discount = $discount <= $itemPrice * $discQuantity ? $discount : $itemPrice * $discQuantity;
$report['discount'] = $discount;
$report['formatted_discount'] = core()->currency($discount);
$impact->push($report);
$totalDiscount = $totalDiscount + $discount;
unset($report);
}
}
$impact->discount = $totalDiscount;
$impact->formatted_discount = core()->currency($impact->discount);
return $impact;
}
}

View File

@ -1,110 +0,0 @@
<?php
namespace Webkul\Discount\Actions\Cart;
use Webkul\Discount\Actions\Cart\Action;
class PercentOfProduct extends Action
{
public function __construct($rule)
{
/**
* Setting the rule getting applied
*/
$this->rule = $rule;
}
/**
* To calculate impact of cart rule's action of current items of cart instance
*
* @param CartRule $rule
*
* @return boolean
*/
public function calculate($rule)
{
$impact = collect();
$totalDiscount = 0;
$applicability = $this->checkApplicability();
if ($applicability) {
$eligibleItems = $this->getEligibleItems();
$applicableDiscount = function () use ($eligibleItems) {
$total = 0;
foreach ($eligibleItems as $item) {
$total = $total + $item->base_total;
}
return $total;
};
foreach ($eligibleItems as $item) {
$report = array();
$report['item_id'] = $item->id;
$report['child_items'] = collect();
$perItemDiscount = $applicableDiscount() * ($rule->disc_amount / 100) / $eligibleItems->count();
$itemPrice = $item->base_price;
$itemQuantity = $item->quantity;
$discQuantity = $rule->disc_quantity;
$discQuantity = $itemQuantity <= $discQuantity ? $itemQuantity : $discQuantity;
if ($item->product->getTypeInstance()->isComposite()) {
$isQtyZero = true;
foreach ($item->children as $children) {
if ($children->quantity > 0)
$isQtyZero = false;
}
if ($isQtyZero) {
// case for configurable products
$report['product_id'] = $item->children->first()->product_id;
} else {
// composites other than configurable
$report['product_id'] = $item->product_id;
foreach ($item->children as $children) {
$childBaseTotal = $children->base_total;
$itemDiscount = $childBaseTotal / ($item->base_total / 100);
$children->discount = ($itemDiscount / 100) * $perItemDiscount;
$children->discount = $children->base_total > $children->discount ? $children->discount : $children->base_total;
$report['child_items']->push($children);
}
}
} else {
$report['product_id'] = $item->product_id;
}
$discount = round($perItemDiscount, 4) * $discQuantity;
$discount = $discount <= $itemPrice * $discQuantity ? $discount : $itemPrice * $discQuantity;
$report['discount'] = $discount;
$report['formatted_discount'] = core()->currency($discount);
$impact->push($report);
$totalDiscount = $totalDiscount + $discount;
unset($report);
}
$impact->discount = $totalDiscount;
$impact->formatted_discount = core()->currency($impact->discount);
return $impact;
}
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Webkul\Discount\Actions\Cart;
use Webkul\Discount\Actions\Cart\FixedAmount;
class WholeCartToFixed
{
/**
* To calculate impact of cart rule's action of current items of cart instance
*
* @param CartRule $rule
*
* @return boolean
*/
public function calculate($rule)
{
$actualInstance = new FixedAmount($rule);
$result = $actualInstance->calculate($rule);
return $result;
}
}

Some files were not shown because too many files have changed in this diff Show More