diff --git a/CHANGELOG for v0.1.x.md b/CHANGELOG for v0.1.x.md index 1359bdd27..be4c3b3c1 100755 --- a/CHANGELOG for v0.1.x.md +++ b/CHANGELOG for v0.1.x.md @@ -2,6 +2,70 @@ #### 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* * [feature] - Refund is added for orders. diff --git a/CHANGELOG for v0.2.x.md b/CHANGELOG for v0.2.x.md new file mode 100644 index 000000000..bf693debb --- /dev/null +++ b/CHANGELOG for v0.2.x.md @@ -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 \ No newline at end of file diff --git a/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/create.blade.php b/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/create.blade.php index c687601ac..6ced9889a 100644 --- a/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/create.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/create.blade.php @@ -199,7 +199,7 @@
- @@ -236,7 +236,7 @@
- @@ -380,7 +380,9 @@ condition_type: 1, - conditions: [] + conditions: [], + + action_type: "{{ old('action_type') ?: 'by_percent' }}" } }, diff --git a/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/edit.blade.php b/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/edit.blade.php index 4af87ddf2..cdc051a59 100644 --- a/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/edit.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/promotions/cart-rules/edit.blade.php @@ -164,7 +164,7 @@
- +
@@ -205,7 +205,7 @@ action_type ?> - @@ -246,7 +246,7 @@ apply_to_shipping ?> - @@ -472,7 +472,9 @@ condition_type: {{ old('condition_type') ?: $cartRule->condition_type }}, - conditions: @json($cartRule->conditions ?: []) + conditions: @json($cartRule->conditions ?: []), + + action_type: "{{ old('action_type') ?: $cartRule->action_type }}" } }, diff --git a/packages/Webkul/CartRule/src/Helpers/CartRule.php b/packages/Webkul/CartRule/src/Helpers/CartRule.php index 942fe77ea..8ad0c4dac 100644 --- a/packages/Webkul/CartRule/src/Helpers/CartRule.php +++ b/packages/Webkul/CartRule/src/Helpers/CartRule.php @@ -106,6 +106,9 @@ class CartRule foreach ($cart->items()->get() as $item) { $this->process($item); + + if ($item->children()->count() && $item->product->getTypeInstance()->isChildrenCalculated()) + $this->devideDiscount($item); } $this->processShippingDiscount($cart); @@ -231,6 +234,8 @@ class CartRule $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); @@ -493,4 +498,26 @@ class CartRule 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(); + } + } + } } \ No newline at end of file diff --git a/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleIndex.php b/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleIndex.php index 3d8336caf..fd9b6cc5f 100644 --- a/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleIndex.php +++ b/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleIndex.php @@ -78,13 +78,12 @@ class CatalogRuleIndex public function reindexProduct($product) { try { - $productIds = []; - - if ($product->type == 'configurable') { - $productIds = $product->variants()->pluck('id')->toArray(); - } else { - $productIds[] = $product->id; - } + if (! $product->getTypeInstance()->priceRuleCanBeApplied()) + return; + + $productIds = $product->getTypeInstance()->isComposite() + ? $product->getTypeInstance()->getChildrenIds() + : [$product->id]; $this->cleanIndexes($productIds); diff --git a/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleProduct.php b/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleProduct.php index 5be4b124f..38eb6e439 100644 --- a/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleProduct.php +++ b/packages/Webkul/CatalogRule/src/Helpers/CatalogRuleProduct.php @@ -133,9 +133,17 @@ class CatalogRuleProduct if (! $rule->conditions) return $qb; + $appliedAttributes = []; + foreach ($rule->conditions as $condition) { - if (! $condition['attribute'] || ! isset($condition['value']) || is_null($condition['value']) || $condition['value'] == '') + 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']); @@ -148,9 +156,12 @@ class CatalogRuleProduct $validatedProductIds = []; foreach ($qb->get() as $product) { + if (! $product->getTypeInstance()->priceRuleCanBeApplied()) + continue; + if ($this->validator->validate($rule, $product)) { - if ($product->type == 'configurable') { - $validatedProductIds = array_merge($validatedProductIds, $product->variants()->pluck('id')->toArray()); + if ($product->getTypeInstance()->isComposite()) { + $validatedProductIds = array_merge($validatedProductIds, $product->getTypeInstance()->getChildrenIds()); } else { $validatedProductIds[] = $product->id; } @@ -208,8 +219,12 @@ class CatalogRuleProduct ->orderBy('catalog_rule_id', 'asc'); if ($product) { - if ($product->type == 'configurable') { - $qb->whereIn('catalog_rule_products.product_id', $product->variants()->pluck('id')->toArray()); + + 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); } diff --git a/packages/Webkul/Product/src/Type/AbstractType.php b/packages/Webkul/Product/src/Type/AbstractType.php index 39568653d..a761d5b2a 100644 --- a/packages/Webkul/Product/src/Type/AbstractType.php +++ b/packages/Webkul/Product/src/Type/AbstractType.php @@ -112,6 +112,13 @@ abstract class AbstractType */ protected $hasVariants = false; + /** + * Product children price can be calculated or not + * + * @var boolean + */ + protected $isChildrenCalculated = false; + /** * Create a new product type instance. * @@ -250,6 +257,26 @@ abstract class AbstractType return $this; } + /** + * Returns children ids + * + * @return array + */ + public function getChildrenIds() + { + return []; + } + + /** + * Check if catalog rule can be applied + * + * @return bool + */ + public function priceRuleCanBeApplied() + { + return true; + } + /** * Return true if this product type is saleable * @@ -289,11 +316,21 @@ abstract class AbstractType * * @return bool */ - public function hasVariants(): bool + public function hasVariants() { return $this->hasVariants; } + /** + * Product children price can be calculated or not + * + * @return bool + */ + public function isChildrenCalculated() + { + return $this->isChildrenCalculated; + } + /** * @param integer $qty * @return bool diff --git a/packages/Webkul/Product/src/Type/Bundle.php b/packages/Webkul/Product/src/Type/Bundle.php index dad347cec..52a1345da 100644 --- a/packages/Webkul/Product/src/Type/Bundle.php +++ b/packages/Webkul/Product/src/Type/Bundle.php @@ -68,6 +68,13 @@ class Bundle extends AbstractType */ protected $isComposite = true; + /** + * Product children price can be calculated or not + * + * @var boolean + */ + protected $isChildrenCalculated = true; + /** * Create a new product type instance. * @@ -126,6 +133,26 @@ class Bundle extends AbstractType return $product; } + /** + * Returns children ids + * + * @return array + */ + public function getChildrenIds() + { + return array_unique($this->product->bundle_options()->pluck('product_id')->toArray()); + } + + /** + * Check if catalog rule can be applied + * + * @return bool + */ + public function priceRuleCanBeApplied() + { + return false; + } + /** * Get product minimal price * diff --git a/packages/Webkul/Product/src/Type/Configurable.php b/packages/Webkul/Product/src/Type/Configurable.php index e0f1c3eea..db2890f1d 100644 --- a/packages/Webkul/Product/src/Type/Configurable.php +++ b/packages/Webkul/Product/src/Type/Configurable.php @@ -286,6 +286,16 @@ class Configurable extends AbstractType return false; } + /** + * Returns children ids + * + * @return array + */ + public function getChildrenIds() + { + return $this->product->variants()->pluck('id')->toArray(); + } + /** * @param CartItem $cartItem * @return bool diff --git a/packages/Webkul/Product/src/Type/Grouped.php b/packages/Webkul/Product/src/Type/Grouped.php index 2af33ccb6..ec2fc4c39 100644 --- a/packages/Webkul/Product/src/Type/Grouped.php +++ b/packages/Webkul/Product/src/Type/Grouped.php @@ -104,6 +104,16 @@ class Grouped extends AbstractType return $product; } + /** + * Returns children ids + * + * @return array + */ + public function getChildrenIds() + { + return array_unique($this->product->grouped_products()->pluck('product_id')->toArray()); + } + /** * Get product minimal price * diff --git a/packages/Webkul/Rule/src/Helpers/Validator.php b/packages/Webkul/Rule/src/Helpers/Validator.php index 06a623f43..ac510f314 100644 --- a/packages/Webkul/Rule/src/Helpers/Validator.php +++ b/packages/Webkul/Rule/src/Helpers/Validator.php @@ -29,16 +29,14 @@ class Validator $totalConditionCount++; - $attributeValue = $this->getAttributeValue($condition, $entity); - if ($rule->condition_type == 1) { - if (! $this->validateAttribute($condition['operator'], $attributeValue, $condition['value'])) { + if (! $this->validateObject($condition, $entity)) { return false; } else { $validConditionCount++; } } else { - if ($this->validateAttribute($condition['operator'], $attributeValue, $condition['value'])) + if ($this->validateObject($condition, $entity)) return true; } } @@ -49,8 +47,8 @@ class Validator /** * Return value for the attribute * - * @param array $condition - * @param Cart|CartItem|Product $entity + * @param array $condition + * @param CartItem|Product $entity * @return boolean */ public function getAttributeValue($condition, $entity) @@ -61,8 +59,6 @@ class Validator $attributeCode = $attributeNameChunks[count($attributeNameChunks) - 1]; - $attributeScope = count($attributeNameChunks) == 2 ? $attributeNameChunks[0] : null; - switch (current($chunks)) { case 'cart': $cart = $entity instanceof \Webkul\Checkout\Contracts\Cart ? $entity : $entity->cart; @@ -93,18 +89,15 @@ class Validator case 'product': if ($attributeCode == 'category_ids') { - $value = $attributeScope == 'children' - ? ($entity->child ? $entity->child->product->categories()->pluck('id')->toArray() : []) - : ($entity->product + $value = $entity->product ? $entity->product->categories()->pluck('id')->toArray() - : $entity->categories()->pluck('id')->toArray() - ); + : $entity->categories()->pluck('id')->toArray(); return $value; } else { - $value = $attributeScope == 'children' - ? ($entity->child ? $entity->child->product->{$attributeCode} : null) - : ($entity->product ? $entity->product->{$attributeCode} : $entity->{$attributeCode}); + $value = $entity->product + ? $entity->product->{$attributeCode} + : $entity->{$attributeCode}; if (! in_array($condition['attribute_type'], ['multiselect', 'checkbox'])) return $value; @@ -114,28 +107,85 @@ class Validator } } + /** + * Validate object + * + * @param array $condition + * @param CartItem $entity + * @return bool + */ + private function validateObject($condition, $entity) + { + $validated = false; + + foreach ($this->getAllItems($this->getAttributeScope($condition), $entity) as $item) { + $attributeValue = $this->getAttributeValue($condition, $item); + + if ($validated = $this->validateAttribute($condition, $attributeValue)) + break; + } + + return $validated; + } + + /** + * Return all cart items + * + * @param string $attributeScope + * @param Cart|CartItem|Product $item + * @return array + */ + private function getAllItems($attributeScope, $item) + { + if ($attributeScope === 'parent') { + return [$item]; + } elseif ($attributeScope === 'children') { + return $item->children ?: [$item]; + } else { + $items = $item->children ?: []; + + $items[] = $item; + } + + return $items; + } + + /** + * Validate object + * + * @param array $condition + * @return string + */ + private function getAttributeScope($condition) + { + $chunks = explode('|', $condition['attribute']); + + $attributeNameChunks = explode('::', $chunks[1]); + + return count($attributeNameChunks) == 2 ? $attributeNameChunks[0] : null; + } + /** * Validate attribute value for condition * - * @param string $operator - * @param mixed $attributeValue - * @param mixed $conditionValue + * @param array $condition + * @param mixed $attributeValue * @return boolean */ - public function validateAttribute($operator, $attributeValue, $conditionValue) + public function validateAttribute($condition, $attributeValue) { - switch ($operator) { + switch ($condition['operator']) { case '==': case '!=': - if (is_array($conditionValue)) { + if (is_array($condition['value'])) { if (! is_array($attributeValue)) return false; - $result = ! empty(array_intersect($conditionValue, $attributeValue)); + $result = ! empty(array_intersect($condition['value'], $attributeValue)); } else { if (is_array($attributeValue)) { - $result = count($attributeValue) == 1 && array_shift($attributeValue) == $conditionValue; + $result = count($attributeValue) == 1 && array_shift($attributeValue) == $condition['value']; } else { - $result = $attributeValue == $conditionValue; + $result = $attributeValue == $condition['value']; } } @@ -145,7 +195,7 @@ class Validator if (! is_scalar($attributeValue)) return false; - $result = $attributeValue <= $conditionValue; + $result = $attributeValue <= $condition['value']; break; @@ -153,36 +203,36 @@ class Validator if (! is_scalar($attributeValue)) return false; - $result = $attributeValue >= $conditionValue; + $result = $attributeValue >= $condition['value']; break; case '{}': case '!{}': - if (is_scalar($attributeValue) && is_array($conditionValue)) { - foreach ($conditionValue as $item) { + if (is_scalar($attributeValue) && is_array($condition['value'])) { + foreach ($condition['value'] as $item) { if (stripos($attributeValue, $item) !== false) { $result = true; break; } } - } else if (is_array($conditionValue)) { + } else if (is_array($condition['value'])) { if (! is_array($attributeValue)) return false; - $result = ! empty(array_intersect($conditionValue, $attributeValue)); + $result = ! empty(array_intersect($condition['value'], $attributeValue)); } else { if (is_array($attributeValue)) { - $result = in_array($conditionValue, $attributeValue); + $result = in_array($condition['value'], $attributeValue); } else { - $result = (strpos($attributeValue, $conditionValue) !== false) ? true : false; + $result = (strpos($attributeValue, $condition['value']) !== false) ? true : false; } } break; } - if (in_array($operator, ['!=', '>', '<', '!{}'])) + if (in_array($condition['operator'], ['!=', '>', '<', '!{}'])) $result = ! $result; return $result; diff --git a/packages/Webkul/Shop/src/Resources/lang/en/app.php b/packages/Webkul/Shop/src/Resources/lang/en/app.php index c538d53b8..27547f8c4 100755 --- a/packages/Webkul/Shop/src/Resources/lang/en/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/en/app.php @@ -519,7 +519,6 @@ return [ 'cannot-apply-coupon' => 'Cannot Apply Coupon', 'invalid-coupon' => 'Coupon code is invalid.', 'success-coupon' => 'Coupon code applied successfully.', - 'remove-coupon' => 'Coupon code removed successfully.', 'coupon-apply-issue' => 'Coupon code can\'t be applied.' ],