sarga/packages/Webkul/Discount/src/Helpers/Catalog/Apply.php

547 lines
19 KiB
PHP

<?php
namespace Webkul\Discount\Helpers\Catalog;
use Webkul\Discount\Repositories\CatalogRuleRepository as CatalogRule;
use Webkul\Discount\Repositories\CatalogRuleProductsRepository as CatalogRuleProducts;
use Webkul\Discount\Repositories\CatalogRuleProductsPriceRepository as CatalogRuleProductsPrice;
use Webkul\Discount\Helpers\Catalog\ConvertXToProductId as ConvertX;
use Webkul\Product\Repositories\ProductRepository as Product;
use Webkul\Discount\Helpers\Catalog\Sale;
/**
* Apply - Applies catalog rule to products intended in the rules
*
* @author Prashant Singh <prashant.singh852@webkul.com> @prashant-webkul
* @copyright 2019 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Apply extends Sale
{
/**
* To hold the catalog rule repository instance
*/
protected $catalogRule;
/**
* To maintain the list of deceased rules and remove their records if present
*/
protected $activeRules;
/**
* To maintain the list of deceased rules and remove their records if present
*/
protected $deceased;
/**
* Holds convertXToProductId instance
*/
protected $convertX;
/**
* Hold catalog rule products repository instance
*/
protected $catalogRuleProduct;
/**
* Hold catalog rule products price repository instance
*/
protected $catalogRuleProductPrice;
/**
* Hold ProductRepository instance
*/
protected $product;
/**
* To hold the rule classes
*/
protected $rules;
/**
* @param CatalogRule $catalogRule
*/
public function __construct(
CatalogRule $catalogRule,
ConvertX $convertX,
CatalogRuleProducts $catalogRuleProduct,
CatalogRuleProductsPrice $catalogRuleProductPrice,
Product $product
) {
$this->catalogRule = $catalogRule;
$this->convertX = $convertX;
$this->catalogRuleProduct = $catalogRuleProduct;
$this->catalogRuleProductPrice = $catalogRuleProductPrice;
$this->product = $product;
$this->active = collect();
$this->activeRules = collect();
$this->deceased = collect();
$this->rules = config('discount-rules.catalog');
}
/**
* To apply the new catalog rules and sync previous rules changes,
*/
public function apply()
{
$rules = $this->catalogRule->all();
foreach ($rules as $rule) {
$validated = $this->checkApplicability($rule);
if ($validated) {
$this->active->push($rule->id);
$this->activeRules->push($rule);
} else {
$this->deceased->push($rule->id);
// Job execution for deceased rules
}
}
if ($this->active->count()) {
$productIDs = array();
foreach ($this->activeRules as $rule) {
$productIDs = $this->getProductIds($rule);
if ($productIDs) {
$productIDs = $this->expandProducts($productIDs);
$this->setSale($rule, $productIDs);
}
}
} else {
// handle the deceased rules here or call the declutter handler over here
// dd($this->deceased);
}
}
/**
* To set the sale price for products falling catalog rule criteria
*
* @param CatalogRule $catalogRule
*
* @return Void
*/
public function setSale($rule, $productIDs)
{
if (is_array($productIDs)) {
// apply on selected products
foreach ($productIDs as $productID) {
// catalog rule product resource is updated here
$this->createOrUpdateCatalogRuleProduct($rule, $productID);
// catalog rule product price resource is updated here
$this->createOrUpdateCatalogRuleProductPrice($rule, $productID);
}
} else if ($productIDs == '*') {
$this->catalogRuleProduct->createOrUpdateCatalogRuleProduct($rule, $productIDs);
$this->catalogRuleProductPrice->createOrUpdateCatalogRuleProductPrice($rule, $productIDs);
}
}
/**
* Create or update catalog rule product resource
*
* @param CatalogRule $rule
* @param Integer $productID
*
* @return Void
*/
public function createOrUpdateCatalogRuleProduct($rule, $productID)
{
$channels = $rule->channels;
$customerGroups = $rule->customer_groups;
$channelsGroupsCross = $channels->crossJoin($customerGroups);
if ($productID == '*') {
$products = $this->product->all('id');
foreach ($channelsGroupsCross as $channelGroup) {
$channelId = $channelGroup[0]->channel_id;
$groupId = $channelGroup[1]->customer_group_id;
foreach ($products as $product) {
$productID = $product->id;
$catalogRuleProduct = $this->catalogRuleProduct->findWhere([
'channel_id' => $channelId,
'customer_group_id' => $groupId,
'product_id' => $productID
]);
if ($catalogRuleProduct->count()) {
// check for tie breaker rules and then update
$product = $this->product->find($productID);
$productPrice = $product->price;
// check for tie breaker rules and then update
$previousRuleID = $catalogRuleProduct->first()->catalog_rule_id;
$newRuleID = $rule->id;
$winnerRuleId = $this->breakTie($previousRuleID, $newRuleID, $product);
$discountAmount = $this->getDiscountAmount($product, $rule);
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $discountAmount
];
if ($rule->id == $winnerRuleId) {
$this->catalogRuleProduct->create($data);
} else {
$this->catalogRuleProduct->update($data, $catalogRuleProduct->first()->id);
}
} else {
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $rule->discount_amount
];
$this->catalogRuleProduct->create($data);
}
}
}
} else {
foreach ($channelsGroupsCross as $channelGroup) {
$channelId = $channelGroup[0]->channel_id;
$groupId = $channelGroup[1]->customer_group_id;
$catalogRuleProduct = $this->catalogRuleProduct->findWhere([
'channel_id' => $channelId,
'customer_group_id' => $groupId,
'product_id' => $productID
]);
if ($catalogRuleProduct->count()) {
$product = $this->product->find($productID);
$productPrice = $product->price;
// check for tie breaker rules and then update
$previousRuleID = $catalogRuleProduct->first()->catalog_rule_id;
$newRuleID = $rule->id;
$winnerRuleId = $this->breakTie($previousRuleID, $newRuleID, $product);
// update
if ($winnerRuleId != $rule->id) {
$discountAmount = $this->getDiscountAmount($product, $rule);
$catalogRuleProduct->first()->update([
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $discountAmount
]);
} else {
//do reassessment
}
} else if ($catalogRuleProduct->count() == 0) {
$product = $this->product->find($productID);
$discountAmount = $this->getDiscountAmount($product, $rule);
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $discountAmount
];
$this->catalogRuleProduct->create($data);
} else {
// do the reassessment updation if the cart rule action changes the new action and its amount needs to be updated
}
}
}
}
/**
* Create or update catalog rule product resource
*
* @param CatalogRule $rule
* @param Integer $productID
*
* @return Void
*/
public function createOrUpdateCatalogRuleProductPrice($rule, $productID)
{
$channels = $rule->channels;
$customerGroups = $rule->customer_groups;
$channelsGroupsCross = $channels->crossJoin($customerGroups);
if ($productID == '*') {
$products = $this->product->all('id');
foreach ($channelsGroupsCross as $channelGroup) {
$channelId = $channelGroup[0]->channel_id;
$groupId = $channelGroup[1]->customer_group_id;
foreach ($products as $product) {
$productID = $product->id;
$catalogRuleProductPrice = $this->catalogRuleProductPrice->findWhere([
'channel_id' => $channelId,
'customer_group_id' => $groupId,
'product_id' => $productID
]);
if ($catalogRuleProductPrice->count()) {
// check for tie breaker rules and then update
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $rule->discount_amount
];
$this->catalogRuleProductPrice->update($data, $catalogRuleProductPrice->first()->id);
} else {
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'action_code' => $rule->action_code,
'action_amount' => $rule->discount_amount
];
$this->catalogRuleProductPrice->create($data);
}
}
}
} else {
foreach ($channelsGroupsCross as $channelGroup) {
$channelId = $channelGroup[0]->channel_id;
$groupId = $channelGroup[1]->customer_group_id;
$catalogRuleProduct = $this->catalogRuleProduct->findWhere([
'channel_id' => $channelId,
'customer_group_id' => $groupId,
'product_id' => $productID
]);
$catalogRuleProductPrice = $this->catalogRuleProductPrice->findWhere([
'channel_id' => $channelId,
'customer_group_id' => $groupId,
'product_id' => $productID
]);
if ($catalogRuleProductPrice->count()) {
$catalogRuleProduct = $catalogRuleProduct->first();
$product = $this->product->find($productID);
$productPrice = $product->price - $catalogRuleProduct->action_amount;
$winnerRuleId = $this->breakTie($catalogRuleProduct->catalog_rule_id, $rule->id, $product);
if ($winnerRuleId != $rule->id) {
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'rule_price' => $productPrice
];
$catalogRuleProductPrice->first()->update($data);
} else {
// do reassement
}
} else if ($catalogRuleProductPrice->count() == 0) {
$catalogRuleProduct = $catalogRuleProduct->first();
$discountAmount = $catalogRuleProduct->action_amount;
$product = $this->product->find($productID);
$productPrice = $product->price - $discountAmount;
$data = [
'catalog_rule_id' => $rule->id,
'starts_from' => $rule->starts_from,
'ends_till' => $rule->ends_till,
'customer_group_id' => $groupId,
'channel_id' => $channelId,
'product_id' => $productID,
'rule_price' => $productPrice
];
$this->catalogRuleProductPrice->create($data);
} else {
// do the reassessment updation if the cart rule action changes the new action and its amount needs to be updated
}
}
}
}
/**
* Get discount amount for the rule and product
*
* @return Decimal $discountAmount
*/
public function getDiscountAmount($product, $rule)
{
$actionClass = config('discount-rules.catalog')[$rule->action_code];
$actionInstance = new $actionClass();
$discountAmount = $actionInstance->calculate($rule, $product);
return $discountAmount;
}
/**
* To expand the productIDs of configurable products
*
* @param Array $productIDs
*
* @return Array
*/
protected function expandProducts($productIDs)
{
$products = app('Webkul\Product\Repositories\ProductRepository');
$newProductIDs = collect();
foreach ($productIDs as $productID) {
$product = $products->find($productID);
if ($product->type == 'configurable') {
$variants = $product->variants;
foreach($variants as $variant) {
$newProductIDs->push($variant->id);
}
} else {
$newProductIDs->push($productID);
}
}
if ($newProductIDs->count()) {
return $newProductIDs->toArray();
} else {
return [];
}
}
/**
* To break tie between two rules
*
* @param Integer $previousRuleID
* @param Integer $newRuleID
*
* @return String $id
*/
public function breakTie($previousRuleID, $newRuleID, $product)
{
$oldRule = $this->catalogRule->find($previousRuleID);
$newRule = $this->catalogRule->find($newRuleID);
if ($oldRule->ends_other_rules) {
return $oldRule->id;
} else {
$oldRuleDiscount = $this->getDiscountAmount($product, $oldRule);
$newRuleDiscount = $this->getDiscountAmount($product, $newRule);
if ($newRuleDiscount > $oldRuleDiscount) {
return $newRule->id;
} else {
return $oldRule->id;
}
}
}
/**
* To declutter the catalog rules that are either inactive or deleted
*
* @return Boolean
*/
public function deClutter()
{
$rules = $this->catalogRule->all();
foreach ($rules as $rule) {
$validated = $this->checkApplicability($rule);
if (! $validated) {
$this->deceased->push($rule->id);
}
}
if (count($this->deceased)) {
$count = 0;
foreach ($this->deceased as $deceased) {
$cartRuleProducts = $this->catalogRuleProduct->findWhere([
'catalog_rule_id' => $deceased
]);
$cartRuleProductsPrice = $this->catalogRuleProductPrice->findWhere([
'catalog_rule_id' => $deceased
]);
// obvious logic for removing entries as entries in both storage needs to be exactly equal for a product
foreach ($cartRuleProducts as $cartRuleProduct) {
// deletes cartRuleProducts resource
$cartRuleProduct->delete();
// deletes cartRuleProductsPrice resource
$cartRuleProductsPrice->delete();
}
}
return true;
} else {
return false;
}
}
}