Your IP : 18.220.156.180
<?php
namespace Bitrix\Sale\Discount;
use Bitrix\Main,
Bitrix\Main\Localization\Loc,
Bitrix\Sale;
Loc::loadMessages(__FILE__);
class Actions
{
const VALUE_TYPE_FIX = 'F';
const VALUE_TYPE_PERCENT = 'P';
const VALUE_TYPE_SUMM = 'S';
const GIFT_SELECT_TYPE_ONE = 'one';
const GIFT_SELECT_TYPE_ALL = 'all';
const BASKET_APPLIED_FIELD = 'DISCOUNT_APPLIED';
const VALUE_EPS = 1E-5;
const MODE_CALCULATE = 0x0001;
const MODE_MANUAL = 0x0002;
const MODE_MIXED = 0x0004;
const APPLY_COUNTER_START = -1;
const PERCENT_FROM_CURRENT_PRICE = 0x0001;
const PERCENT_FROM_BASE_PRICE = 0x0002;
const RESULT_ENTITY_BASKET = 0x0001;
const RESULT_ENTITY_DELIVERY = 0x0002;
const APPLY_RESULT_MODE_COUNTER = 0x0001;
const APPLY_RESULT_MODE_DESCR = 0x0002;
const APPLY_RESULT_MODE_SIMPLE = 0x0004;
protected static $useMode = self::MODE_CALCULATE;
protected static $applyCounter = self::APPLY_COUNTER_START;
protected static $actionResult = array();
protected static $actionDescription = array();
protected static $applyResult = array();
protected static $applyResultMode = self::APPLY_RESULT_MODE_COUNTER;
protected static $storedData = array();
protected static $useBasketFilter = true;
protected static $percentValueMode = self::PERCENT_FROM_CURRENT_PRICE;
protected static $currencyId = '';
protected static $siteId = '';
private static $compatibleBasketFields = array('DISCOUNT_PRICE', 'PRICE', 'VAT_VALUE', 'PRICE_DEFAULT');
/**
* Check for zero value.
*
* @param float|int $value Price or discount value.
* @return float|int
*/
public static function roundZeroValue($value)
{
return (abs($value) <= self::VALUE_EPS ? 0 : $value);
}
/**
* Rounded value with sale rules.
*
* @param float|int $value Value.
* @param string $currency Currency.
* @return float
*/
public static function roundValue($value, /** @noinspection PhpUnusedParameterInspection */ $currency)
{
/** @noinspection PhpInternalEntityUsedInspection */
return Sale\PriceMaths::roundPrecision($value);
}
/**
* Set use actions mode.
*
* @param int $mode Use mode.
* @param array $config Config.
* @return void
*/
public static function setUseMode($mode, array $config = array())
{
$mode = (int)$mode;
if ($mode !== self::MODE_CALCULATE && $mode !== self::MODE_MANUAL && $mode !== self::MODE_MIXED)
return;
self::$useMode = $mode;
switch (self::$useMode)
{
case self::MODE_CALCULATE:
$percentOption = (string)Main\Config\Option::get('sale', 'get_discount_percent_from_base_price');
self::$percentValueMode = ($percentOption == 'Y' ? self::PERCENT_FROM_BASE_PRICE : self::PERCENT_FROM_CURRENT_PRICE);
unset($percentOption);
if (isset($config['CURRENCY']))
self::$currencyId = $config['CURRENCY'];
if (isset($config['SITE_ID']))
{
self::$siteId = $config['SITE_ID'];
if (self::$currencyId == '')
self::$currencyId = Sale\Internals\SiteCurrencyTable::getSiteCurrency(self::$siteId);
}
break;
case self::MODE_MANUAL:
case self::MODE_MIXED:
$percentOption = '';
if (isset($config['USE_BASE_PRICE']))
$percentOption = $config['USE_BASE_PRICE'];
if ($percentOption == '')
$percentOption = (string)Main\Config\Option::get('sale', 'get_discount_percent_from_base_price');
self::$percentValueMode = ($percentOption == 'Y' ? self::PERCENT_FROM_BASE_PRICE : self::PERCENT_FROM_CURRENT_PRICE);
unset($percentOption);
if (isset($config['CURRENCY']))
self::$currencyId = $config['CURRENCY'];
if (isset($config['SITE_ID']))
{
self::$siteId = $config['SITE_ID'];
if (self::$currencyId == '')
self::$currencyId = Sale\Internals\SiteCurrencyTable::getSiteCurrency(self::$siteId);
}
break;
}
static::clearApplyCounter();
static::enableBasketFilter();
}
/**
* Return current use actions mode.
*
* @return int
*/
public static function getUseMode()
{
return self::$useMode;
}
/**
* Check calculate mode.
*
* @return bool
*/
public static function isCalculateMode()
{
return self::$useMode === self::MODE_CALCULATE;
}
/**
* Check manual mode.
*
* @return bool
*/
public static function isManualMode()
{
return self::$useMode === self::MODE_MANUAL;
}
/**
* Check mixed mode.
*
* @return bool
*/
public static function isMixedMode()
{
return self::$useMode === self::MODE_MIXED;
}
/**
* Return calculate mode for percent discount.
*
* @return int
*/
public static function getPercentMode()
{
return self::$percentValueMode;
}
/**
* Return calculate currency.
*
* @return string
*/
public static function getCurrency()
{
return self::$currencyId;
}
/**
* Clear apply counter.
*
* @return void
*/
public static function clearApplyCounter()
{
self::$applyCounter = self::APPLY_COUNTER_START;
}
/**
* Return current apply counter.
*
* @return int
*/
public static function getApplyCounter()
{
return self::$applyCounter;
}
/**
* Increment current apply counter. Use BEFORE discount action apply.
*
* @return void
*/
public static function increaseApplyCounter()
{
self::$applyCounter++;
}
/**
* Disable basket filter for mixed apply mode.
*
* @return void
*/
public static function disableBasketFilter()
{
if (!static::isMixedMode())
return;
self::$useBasketFilter = false;
}
/**
* Enable basket filter for mixed apply mode.
*
* @return void
*/
public static function enableBasketFilter()
{
if (!static::isMixedMode())
return;
self::$useBasketFilter = true;
}
/**
* Return is enabled basket filter mixed apply mode.
*
* @return bool
*/
public static function usedBasketFilter()
{
return self::$useBasketFilter;
}
/**
* Fill compatible fields for old public api.
*
* @param array &$order Order data.
* @return void
*/
public static function fillCompatibleFields(array &$order)
{
$adminSection = Main\Context::getCurrent()->getRequest()->isAdminSection();
if (empty($order) || !is_array($order))
return;
if (!empty($order['BASKET_ITEMS']) && is_array($order['BASKET_ITEMS']))
{
foreach ($order['BASKET_ITEMS'] as &$item)
{
if (isset($item['PRICE_DEFAULT']))
$item['PRICE_DEFAULT'] = $item['PRICE'];
if ($adminSection)
continue;
foreach (self::$compatibleBasketFields as &$fieldName)
{
if (array_key_exists($fieldName, $item) && !is_array($item[$fieldName]))
$item['~'.$fieldName] = $item[$fieldName];
}
unset($fieldName);
}
unset($item);
}
}
/**
* Basket filter.
*
* @param array $item Basket item.
* @return bool
*/
public static function filterBasketForAction(array $item)
{
return (
(!isset($item['CUSTOM_PRICE']) || $item['CUSTOM_PRICE'] != 'Y') &&
(
(isset($item['TYPE']) && (int)$item['TYPE'] == Sale\BasketItem::TYPE_SET) ||
(!isset($item['SET_PARENT_ID']) || (int)$item['SET_PARENT_ID'] <= 0)
) &&
(!isset($item['ITEM_FIX']) || $item['ITEM_FIX'] != 'Y') &&
(!isset($item['LAST_DISCOUNT']) || $item['LAST_DISCOUNT'] != 'Y') &&
(!isset($item['IN_SET']) || $item['IN_SET'] != 'Y')
);
}
/**
* Return all actions description.
*
* @return array
*/
public static function getActionDescription()
{
return self::$actionDescription;
}
/**
* Return all actions results.
*
* @return array
*/
public static function getActionResult()
{
return self::$actionResult;
}
/**
* Set apply result format mode.
*
* @param int $mode Apply result mode.
* @return void
*/
public static function setApplyResultMode($mode)
{
$mode = (int)$mode;
if ($mode != self::APPLY_RESULT_MODE_COUNTER && $mode != self::APPLY_RESULT_MODE_DESCR && $mode != self::APPLY_RESULT_MODE_SIMPLE)
return;
self::$applyResultMode = $mode;
self::$applyResult = array();
}
/**
* Return apply result format mode.
*
* @return int
*/
public static function getApplyResultMode()
{
return self::$applyResultMode;
}
/**
* Set apply result list.
*
* @param array $applyResult Apply data.
* @return void
*/
public static function setApplyResult(array $applyResult)
{
self::$applyResult = $applyResult;
}
/**
* Fill data to store for discount.
*
* @param array $data Data.
* @return void
*/
public static function setStoredData(array $data)
{
self::$storedData = $data;
}
/**
* Return stored data after discount calculation.
*
* @return array
*/
public static function getStoredData()
{
return self::$storedData;
}
/**
* Fill action data to store.
*
* @param array $data Action data to store.
* @return void
*/
public static function setActionStoredData(array $data)
{
if (!static::isCalculateMode())
return;
if (empty($data))
return;
self::$storedData[static::getApplyCounter()] = $data;
}
/**
* Return stored action.
*
* @return array|null
*/
public static function getActionStoredData()
{
$counter = static::getApplyCounter();
if (isset(self::$storedData[$counter]))
return self::$storedData[$counter];
return null;
}
/**
* Clear actions description and result.
*
* @return void
*/
public static function clearAction()
{
self::clearApplyCounter();
self::$applyResult = array();
self::$actionResult = array();
self::$actionDescription = array();
self::$storedData = array();
}
/**
* Basket action.
*
* @param array &$order Order data.
* @param array $action Action detail
* keys are case sensitive:
* <ul>
* <li>float|int VALUE Discount value.
* <li>char UNIT Discount type.
* <li>string CURRENCY Currency discount (optional).
* <li>char MAX_BOUND Max bound (optional).
* </ul>.
* @param callable $filter Filter for basket items.
* @return void
*/
public static function applyToBasket(array &$order, array $action, $filter)
{
static::increaseApplyCounter();
if (!isset($action['VALUE']) || !isset($action['UNIT']))
return;
$orderCurrency = static::getCurrency();
$value = (float)$action['VALUE'];
$limitValue = (int)$action['LIMIT_VALUE'];
$unit = (string)$action['UNIT'];
$currency = (isset($action['CURRENCY']) ? $action['CURRENCY'] : $orderCurrency);
$maxBound = false;
if ($unit == self::VALUE_TYPE_FIX && $value < 0)
$maxBound = (isset($action['MAX_BOUND']) && $action['MAX_BOUND'] == 'Y');
$valueAction = (
$value < 0
? Sale\OrderDiscountManager::DESCR_VALUE_ACTION_DISCOUNT
: Sale\OrderDiscountManager::DESCR_VALUE_ACTION_EXTRA
);
$actionDescription = array(
'ACTION_TYPE' => Sale\OrderDiscountManager::DESCR_TYPE_VALUE,
'VALUE' => abs($value),
'VALUE_ACTION' => $valueAction
);
switch ($unit)
{
case self::VALUE_TYPE_SUMM:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_SUMM;
$actionDescription['VALUE_UNIT'] = $currency;
break;
case self::VALUE_TYPE_PERCENT:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_PERCENT;
break;
case self::VALUE_TYPE_FIX:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_CURRENCY;
$actionDescription['VALUE_UNIT'] = $currency;
if ($maxBound)
$actionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_MAX_BOUND;
break;
default:
return;
break;
}
if(!empty($limitValue))
{
$actionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_LIMIT_VALUE;
$actionDescription['LIMIT_TYPE'] = Sale\OrderDiscountManager::DESCR_LIMIT_MAX;
$actionDescription['LIMIT_UNIT'] = $orderCurrency;
$actionDescription['LIMIT_VALUE'] = $limitValue;
}
static::setActionDescription(self::RESULT_ENTITY_BASKET, $actionDescription);
if (empty($order['BASKET_ITEMS']) || !is_array($order['BASKET_ITEMS']))
return;
static::enableBasketFilter();
$filteredBasket = static::getBasketForApply($order['BASKET_ITEMS'], $filter, $action);
if (empty($filteredBasket))
return;
$applyBasket = array_filter($filteredBasket, '\Bitrix\Sale\Discount\Actions::filterBasketForAction');
unset($filteredBasket);
if (empty($applyBasket))
return;
if ($unit == self::VALUE_TYPE_SUMM || $unit == self::VALUE_TYPE_FIX)
{
if ($currency != $orderCurrency)
$value = \CCurrencyRates::ConvertCurrency($value, $currency, $orderCurrency);
if ($unit == self::VALUE_TYPE_SUMM)
{
$value = static::getPercentByValue($applyBasket, $value);
if (
($valueAction == Sale\OrderDiscountManager::DESCR_VALUE_ACTION_DISCOUNT && ($value >= 0 || $value < -100))
||
($valueAction == Sale\OrderDiscountManager::DESCR_VALUE_ACTION_EXTRA && $value <= 0)
)
return;
$unit = self::VALUE_TYPE_PERCENT;
}
}
$value = static::roundZeroValue($value);
if ($value == 0)
return;
foreach ($applyBasket as $basketCode => $basketRow)
{
list($calculateValue, $result) = self::calculateDiscountPrice(
$value,
$unit,
$basketRow,
$limitValue,
$maxBound
);
if ($result >= 0)
{
self::fillDiscountPrice($basketRow, $result, -$calculateValue);
$order['BASKET_ITEMS'][$basketCode] = $basketRow;
$rowActionDescription = $actionDescription;
$rowActionDescription['BASKET_CODE'] = $basketCode;
$rowActionDescription['RESULT_VALUE'] = abs($calculateValue);
$rowActionDescription['RESULT_UNIT'] = $orderCurrency;
if(!empty($limitValue))
{
$rowActionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_LIMIT_VALUE;
$rowActionDescription['LIMIT_TYPE'] = Sale\OrderDiscountManager::DESCR_LIMIT_MAX;
$rowActionDescription['LIMIT_UNIT'] = $orderCurrency;
$rowActionDescription['LIMIT_VALUE'] = $limitValue;
}
static::setActionResult(self::RESULT_ENTITY_BASKET, $rowActionDescription);
unset($rowActionDescription);
}
unset($result);
}
unset($basketCode, $basketRow);
}
/**
* Cumulative action.
*
* @param array &$order Order data.
* @param array $ranges
* @param array $configuration
* @param callable|null $filter
* @return void
* @throws Main\ArgumentOutOfRangeException
*/
public static function applyCumulativeToBasket(array &$order, array $ranges, array $configuration = array(), $filter = null)
{
static::increaseApplyCounter();
Main\Type\Collection::sortByColumn($ranges, 'sum');
$sumConfiguration = $configuration['sum']?: array();
$applyIfMoreProfitable = $configuration['apply_if_more_profitable'] === 'Y';
if (in_array(self::getUseMode(), array(self::MODE_MANUAL, self::MODE_MIXED)))
{
$actionStoredData = self::getActionStoredData();
$cumulativeOrderUserValue = $actionStoredData['cumulative_value'];
}
else
{
$cumulativeCalculator = new CumulativeCalculator((int)$order['USER_ID'], $order['SITE_ID']);
$cumulativeCalculator->setSumConfiguration(
array(
'type_sum_period' => $sumConfiguration['type_sum_period'],
'sum_period_data' => array(
'order_start' => $sumConfiguration['sum_period_data']['discount_sum_order_start'],
'order_end' => $sumConfiguration['sum_period_data']['discount_sum_order_end'],
'period_value' => $sumConfiguration['sum_period_data']['discount_sum_period_value'],
'period_type' => $sumConfiguration['sum_period_data']['discount_sum_period_type'],
),
)
);
$cumulativeOrderUserValue = $cumulativeCalculator->calculate();
}
$rangeToApply = null;
foreach ($ranges as $range)
{
if ($cumulativeOrderUserValue >= $range['sum'])
{
$rangeToApply = $range;
}
}
if (!$rangeToApply)
{
return;
}
$action = array(
'VALUE' => -$rangeToApply['value'],
'UNIT' => $rangeToApply['type'],
);
if (!isset($action['VALUE']) || !isset($action['UNIT']))
return;
$orderCurrency = static::getCurrency();
$value = (float)$action['VALUE'];
$limitValue = (int)$action['LIMIT_VALUE'];
$unit = (string)$action['UNIT'];
$currency = (isset($action['CURRENCY']) ? $action['CURRENCY'] : $orderCurrency);
$maxBound = false;
if ($unit == self::VALUE_TYPE_FIX && $value < 0)
$maxBound = (isset($action['MAX_BOUND']) && $action['MAX_BOUND'] == 'Y');
$valueAction = Sale\OrderDiscountManager::DESCR_VALUE_ACTION_CUMULATIVE;
$actionDescription = array(
'ACTION_TYPE' => Sale\OrderDiscountManager::DESCR_TYPE_VALUE,
'VALUE' => abs($value),
'VALUE_ACTION' => $valueAction
);
switch ($unit)
{
case self::VALUE_TYPE_PERCENT:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_PERCENT;
break;
case self::VALUE_TYPE_FIX:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_CURRENCY;
$actionDescription['VALUE_UNIT'] = $currency;
if ($maxBound)
$actionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_MAX_BOUND;
break;
default:
return;
}
if ($unit == self::VALUE_TYPE_FIX && $currency != $orderCurrency)
{
$value = \CCurrencyRates::ConvertCurrency($value, $currency, $orderCurrency);
}
$value = static::roundZeroValue($value);
if ($value == 0)
{
return;
}
if(!empty($limitValue))
{
$actionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_LIMIT_VALUE;
$actionDescription['LIMIT_TYPE'] = Sale\OrderDiscountManager::DESCR_LIMIT_MAX;
$actionDescription['LIMIT_UNIT'] = $orderCurrency;
$actionDescription['LIMIT_VALUE'] = $limitValue;
}
static::setActionDescription(self::RESULT_ENTITY_BASKET, $actionDescription);
if (empty($order['BASKET_ITEMS']) || !is_array($order['BASKET_ITEMS']))
return;
static::enableBasketFilter();
if ($applyIfMoreProfitable)
{
if ($filter === null)
{
$filter = function(){
return true;
};
}
$filter = self::wrapFilterToFindMoreProfitableForCumulative($filter, $unit, $value, $limitValue, $maxBound);
}
$filteredBasket = static::getBasketForApply($order['BASKET_ITEMS'], $filter, $action);
if (empty($filteredBasket))
return;
$applyBasket = array_filter($filteredBasket, '\Bitrix\Sale\Discount\Actions::filterBasketForAction');
unset($filteredBasket);
if (empty($applyBasket))
return;
foreach ($applyBasket as $basketCode => $basketRow)
{
if ($applyIfMoreProfitable)
{
$basketRow['PRICE'] = $basketRow['BASE_PRICE'];
$basketRow['DISCOUNT_PRICE'] = 0;
}
list($calculateValue, $result) = self::calculateDiscountPrice(
$value,
$unit,
$basketRow,
$limitValue,
$maxBound
);
if ($result >= 0)
{
self::fillDiscountPrice($basketRow, $result, -$calculateValue);
$order['BASKET_ITEMS'][$basketCode] = $basketRow;
$rowActionDescription = $actionDescription;
$rowActionDescription['BASKET_CODE'] = $basketCode;
$rowActionDescription['RESULT_VALUE'] = abs($calculateValue);
$rowActionDescription['RESULT_UNIT'] = $orderCurrency;
if(!empty($limitValue))
{
$rowActionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_LIMIT_VALUE;
$rowActionDescription['LIMIT_TYPE'] = Sale\OrderDiscountManager::DESCR_LIMIT_MAX;
$rowActionDescription['LIMIT_UNIT'] = $orderCurrency;
$rowActionDescription['LIMIT_VALUE'] = $limitValue;
}
if ($applyIfMoreProfitable)
{
//revert apply on affected basket items
$rowActionDescription['REVERT_APPLY'] = true;
}
static::setActionResult(self::RESULT_ENTITY_BASKET, $rowActionDescription);
unset($rowActionDescription);
}
unset($result);
}
unset($basketCode, $basketRow);
if (self::getUseMode() == self::MODE_CALCULATE)
{
self::setActionStoredData(
array(
'cumulative_value' => $cumulativeOrderUserValue,
)
);
}
}
private static function wrapFilterToFindMoreProfitableForCumulative($filter, $unit, $value, $limitValue, $maxBound)
{
if (!is_callable($filter))
{
return null;
}
return function($basketItem) use ($filter, $unit, $value, $limitValue, $maxBound) {
if (empty($basketItem['BASE_PRICE']))
{
return false;
}
if (empty($basketItem['DISCOUNT_PRICE']))
{
return true;
}
if (!$filter($basketItem))
{
return false;
}
$prevPrice = $basketItem['PRICE'];
$basketItem['PRICE'] = $basketItem['BASE_PRICE'];
list(, $newPrice) = self::calculateDiscountPrice(
$value,
$unit,
$basketItem,
$limitValue,
$maxBound
);
return $newPrice < $prevPrice;
};
}
/**
* Delivery action.
*
* @param array &$order Order data.
* @param array $action Action detail
* keys are case sensitive:
* <ul>
* <li>float|int VALUE Discount value.
* <li>char UNIT Discount type.
* <li>string CURRENCY Currency discount (optional).
* <li>char MAX_BOUND Max bound.
* </ul>.
* @return void
*/
public static function applyToDelivery(array &$order, array $action)
{
static::increaseApplyCounter();
if (!isset($action['VALUE']) || !isset($action['UNIT']))
return;
if ($action['UNIT'] != self::VALUE_TYPE_PERCENT && $action['UNIT'] != self::VALUE_TYPE_FIX)
return;
$orderCurrency = static::getCurrency();
$unit = (string)$action['UNIT'];
$value = (float)$action['VALUE'];
$currency = (isset($action['CURRENCY']) ? $action['CURRENCY'] : $orderCurrency);
$maxBound = false;
if ($unit == self::VALUE_TYPE_FIX && $value < 0)
$maxBound = (isset($action['MAX_BOUND']) && $action['MAX_BOUND'] == 'Y');
$actionDescription = array(
'ACTION_TYPE' => Sale\OrderDiscountManager::DESCR_TYPE_VALUE,
'VALUE' => abs($value),
'VALUE_ACTION' => (
$value < 0
? Sale\OrderDiscountManager::DESCR_VALUE_ACTION_DISCOUNT
: Sale\OrderDiscountManager::DESCR_VALUE_ACTION_EXTRA
)
);
if ($maxBound)
$actionDescription['ACTION_TYPE'] = Sale\OrderDiscountManager::DESCR_TYPE_MAX_BOUND;
switch ($unit)
{
case self::VALUE_TYPE_PERCENT:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_PERCENT;
$value = ($order['PRICE_DELIVERY'] * $value) / 100;
break;
case self::VALUE_TYPE_FIX:
$actionDescription['VALUE_TYPE'] = Sale\OrderDiscountManager::DESCR_VALUE_TYPE_CURRENCY;
$actionDescription['VALUE_UNIT'] = $currency;
if ($currency != $orderCurrency)
$value = \CCurrencyRates::ConvertCurrency($value, $currency, $orderCurrency);
break;
}
static::setActionDescription(self::RESULT_ENTITY_DELIVERY, $actionDescription);
if (isset($order['CUSTOM_PRICE_DELIVERY']) && $order['CUSTOM_PRICE_DELIVERY'] == 'Y')
return;
if (
!isset($order['PRICE_DELIVERY'])
|| (
static::roundZeroValue($order['PRICE_DELIVERY']) == 0
&& $actionDescription['VALUE_ACTION'] == Sale\OrderDiscountManager::DESCR_VALUE_ACTION_DISCOUNT
)
)
return;
$value = static::roundValue($value, $order['CURRENCY']);
$value = static::roundZeroValue($value);
if ($value == 0)
return;
$resultValue = static::roundZeroValue($order['PRICE_DELIVERY'] + $value);
if ($maxBound && $resultValue < 0)
{
$resultValue = 0;
$value = -$order['PRICE_DELIVERY'];
}
if ($resultValue < 0)
return;
if (!isset($order['PRICE_DELIVERY_DIFF']))
$order['PRICE_DELIVERY_DIFF'] = 0;
$order['PRICE_DELIVERY_DIFF'] -= $value;
$order['PRICE_DELIVERY'] = $resultValue;
$actionDescription['RESULT_VALUE'] = abs($value);
$actionDescription['RESULT_UNIT'] = $orderCurrency;
static::setActionResult(self::RESULT_ENTITY_DELIVERY, $actionDescription);
unset($actionDescription);
}
/**
* Simple gift action.
*
* @param array &$order Order data.
* @param callable $filter Filter.
* @throws Main\ArgumentOutOfRangeException
* @return void
*/
public static function applySimpleGift(array &$order, $filter)
{
static::increaseApplyCounter();
$actionDescription = array(
'ACTION_TYPE' => Sale\OrderDiscountManager::DESCR_TYPE_SIMPLE,
'ACTION_DESCRIPTION' => Loc::getMessage('BX_SALE_DISCOUNT_ACTIONS_SIMPLE_GIFT_DESCR')
);
static::setActionDescription(self::RESULT_ENTITY_BASKET, $actionDescription);
if (!is_callable($filter))
return;
if (empty($order['BASKET_ITEMS']) || !is_array($order['BASKET_ITEMS']))
return;
static::disableBasketFilter();
$itemsCopy = $order['BASKET_ITEMS'];
Main\Type\Collection::sortByColumn($itemsCopy, 'PRICE', null, null, true);
$filteredBasket = static::getBasketForApply(
$itemsCopy,
$filter,
array(
'GIFT_TITLE' => Loc::getMessage('BX_SALE_DISCOUNT_ACTIONS_SIMPLE_GIFT_DESCR')
)
);
unset($itemsCopy);
static::enableBasketFilter();
if (empty($filteredBasket))
return;
$applyBasket = array_filter($filteredBasket, '\Bitrix\Sale\Discount\Actions::filterBasketForAction');
unset($filteredBasket);
if (empty($applyBasket))
return;
foreach ($applyBasket as $basketCode => $basketRow)
{
$basketRow['DISCOUNT_PRICE'] = $basketRow['BASE_PRICE'];
$basketRow['PRICE'] = 0;
$order['BASKET_ITEMS'][$basketCode] = $basketRow;
$rowActionDescription = $actionDescription;
$rowActionDescription['BASKET_CODE'] = $basketCode;
static::setActionResult(self::RESULT_ENTITY_BASKET, $rowActionDescription);
unset($rowActionDescription);
}
unset($basketCode, $basketRow);
}
/**
* Return basket item for action apply.
*
* @param array $basket Basket.
* @param mixed $filter Filter.
* @param array $action Prepare data.
* @return mixed
*/
public static function getBasketForApply(array $basket, $filter, $action = array())
{
$result = array();
switch (static::getUseMode())
{
case self::MODE_CALCULATE:
$result = (is_callable($filter) ? array_filter($basket, $filter) : $basket);
break;
case self::MODE_MANUAL:
case self::MODE_MIXED:
switch (static::getApplyResultMode())
{
case self::APPLY_RESULT_MODE_COUNTER:
$currentCounter = static::getApplyCounter();
$basketCodeList = array_keys($basket);
foreach ($basketCodeList as &$code)
{
if (empty(self::$applyResult['BASKET'][$code]) || !is_array(self::$applyResult['BASKET'][$code]))
continue;
if (!in_array($currentCounter, self::$applyResult['BASKET'][$code]))
continue;
$result[$code] = $basket[$code];
}
unset($code, $basketCodeList, $currentCounter);
break;
case self::APPLY_RESULT_MODE_DESCR:
$basketCodeList = array_keys($basket);
foreach ($basketCodeList as &$code)
{
if (empty(self::$applyResult['BASKET'][$code]) || !is_array(self::$applyResult['BASKET'][$code]))
continue;
foreach (self::$applyResult['BASKET'][$code] as $descr)
{
if (static::compareBasketResultDescr($action, $descr))
{
$result[$code] = $basket[$code];
break;
}
}
unset($descr);
// only for old format simple gifts
if (!isset($result[$code]))
{
if (isset($action['GIFT_TITLE']))
{
end(self::$applyResult['BASKET'][$code]);
$descr = current(self::$applyResult['BASKET'][$code]);
if (
$descr['TYPE'] == Sale\OrderDiscountManager::DESCR_TYPE_SIMPLE
&& $descr['DESCR'] == $action['GIFT_TITLE']
)
$result[$code] = $basket[$code];
unset($descr);
}
}
}
unset($code, $basketCodeList);
break;
case self::APPLY_RESULT_MODE_SIMPLE:
$basketCodeList = array_keys($basket);
foreach ($basketCodeList as &$code)
{
if (isset(self::$applyResult['BASKET'][$code]))
$result[$code] = $basket[$code];
}
unset($code, $basketCodeList);
break;
}
break;
}
return $result;
}
/**
* Save action description.
*
* @param int $type Action object type.
* @param array $description Description.
* @return void
*/
public static function setActionDescription($type, $description)
{
if (!static::isCalculateMode())
return;
if (empty($description) || !is_array($description) || !isset($description['ACTION_TYPE']))
return;
$actionType = $description['ACTION_TYPE'];
if ($actionType == Sale\OrderDiscountManager::DESCR_TYPE_SIMPLE)
$description = (isset($description['ACTION_DESCRIPTION']) ? $description['ACTION_DESCRIPTION'] : '');
$prepareResult = Sale\OrderDiscountManager::prepareDiscountDescription($actionType, $description);
unset($actionType);
if ($prepareResult->isSuccess())
{
switch ($type)
{
case self::RESULT_ENTITY_BASKET:
if (!isset(self::$actionDescription['BASKET']))
self::$actionDescription['BASKET'] = array();
self::$actionDescription['BASKET'][static::getApplyCounter()] = $prepareResult->getData();
break;
case self::RESULT_ENTITY_DELIVERY:
if (!isset(self::$actionDescription['DELIVERY']))
self::$actionDescription['DELIVERY'] = array();
self::$actionDescription['DELIVERY'][static::getApplyCounter()] = $prepareResult->getData();
break;
}
}
unset($prepareResult);
}
/**
* Save result.
*
* @param int $entity Action object type.
* @param array $actionResult Result description.
* @return void
*/
public static function setActionResult($entity, array $actionResult)
{
if (empty($actionResult) || !isset($actionResult['ACTION_TYPE']))
return;
$actionType = $actionResult['ACTION_TYPE'];
if ($actionType == Sale\OrderDiscountManager::DESCR_TYPE_SIMPLE)
$actionDescription = (isset($actionResult['ACTION_DESCRIPTION']) ? $actionResult['ACTION_DESCRIPTION'] : '');
else
$actionDescription = $actionResult;
$prepareResult = Sale\OrderDiscountManager::prepareDiscountDescription($actionType, $actionDescription);
unset($actionDescription, $actionType);
if ($prepareResult->isSuccess())
{
switch ($entity)
{
case self::RESULT_ENTITY_BASKET:
if (!isset(self::$actionResult['BASKET']))
self::$actionResult['BASKET'] = array();
$basketCode = $actionResult['BASKET_CODE'];
if (!isset(self::$actionResult['BASKET'][$basketCode]))
self::$actionResult['BASKET'][$basketCode] = array();
self::$actionResult['BASKET'][$basketCode][static::getApplyCounter()] = $prepareResult->getData();
unset($basketCode);
break;
case self::RESULT_ENTITY_DELIVERY:
if (!isset(self::$actionResult['DELIVERY']))
self::$actionResult['DELIVERY'] = array();
self::$actionResult['DELIVERY'][static::getApplyCounter()] = $prepareResult->getData();
break;
}
}
unset($prepareResult);
}
/**
* @param int $entity Entity id.
* @param array $entityParams Entity params (optional).
* @return void
*/
public static function clearEntityActionResult($entity, array $entityParams = array())
{
switch ($entity)
{
case self::RESULT_ENTITY_BASKET:
if (empty($entityParams))
{
if (array_key_exists('BASKET', self::$actionResult))
unset(self::$actionResult['BASKET']);
}
else
{
if (isset($entityParams['BASKET_CODE']) && array_key_exists($entityParams['BASKET_CODE'], self::$actionResult['BASKET']))
unset(self::$actionResult['BASKET'][$entityParams['BASKET_CODE']]);
}
break;
case self::RESULT_ENTITY_DELIVERY:
if (array_key_exists('DELIVERY', self::$actionResult))
unset(self::$actionResult['DELIVERY']);
break;
}
}
/**
* Return percent value.
*
* @param array $basket Basket.
* @param int|float $value Value.
* @return float
*/
public static function getPercentByValue($basket, $value)
{
$summ = 0;
switch (static::getPercentMode())
{
case self::PERCENT_FROM_BASE_PRICE:
foreach ($basket as $basketRow)
$summ += (float)$basketRow['BASE_PRICE'] * (float)$basketRow['QUANTITY'];
unset($basketRow);
break;
case self::PERCENT_FROM_CURRENT_PRICE:
foreach ($basket as $basketRow)
$summ += (float)$basketRow['PRICE'] * (float)$basketRow['QUANTITY'];
unset($basketRow);
break;
}
return static::roundZeroValue($summ > 0 ? ($value * 100) / $summ : 0);
}
/**
* Calculate percent price.
*
* @param array $basketRow Basket item.
* @param float $percent Percent value.
* @return float
*/
public static function percentToValue($basketRow, $percent)
{
$value = 0.0;
switch (static::getPercentMode())
{
case self::PERCENT_FROM_BASE_PRICE:
$value = ((float)$basketRow['BASE_PRICE'] * $percent) / 100;
break;
case self::PERCENT_FROM_CURRENT_PRICE:
$value = ((float)$basketRow['PRICE'] * $percent) / 100;
break;
}
return $value;
}
public static function getActionConfiguration(array $discount)
{
$actionStructure = self::getActionStructure($discount);
if(!$actionStructure || !is_array($actionStructure))
{
return null;
}
if($actionStructure['CLASS_ID'] != 'CondGroup')
{
return null;
}
if(count($actionStructure['CHILDREN']) > 1)
{
return null;
}
$action = reset($actionStructure['CHILDREN']);
if($action['CLASS_ID'] != 'ActSaleBsktGrp')
{
return null;
}
$actionData = $action['DATA'];
$configuration = array(
'TYPE' => $actionData['Type'],
'VALUE' => $actionData['Value'],
'LIMIT_VALUE' => $actionData['Max']?: 0,
);
switch ($actionData['Unit'])
{
case 'CurEach':
$configuration['VALUE_TYPE'] = Sale\Discount\Actions::VALUE_TYPE_FIX;
break;
case 'CurAll':
$configuration['VALUE_TYPE'] = Sale\Discount\Actions::VALUE_TYPE_SUMM;
break;
default:
$configuration['VALUE_TYPE'] = Sale\Discount\Actions::VALUE_TYPE_PERCENT;
break;
}
return $configuration;
}
protected static function getActionStructure(array $discount)
{
$actionStructure = null;
if (isset($discount['ACTIONS']) && !empty($discount['ACTIONS']))
{
$actionStructure = false;
if (!is_array($discount['ACTIONS']))
{
if (CheckSerializedData($discount['ACTIONS']))
{
$actionStructure = unserialize($discount['ACTIONS']);
}
}
else
{
$actionStructure = $discount['ACTIONS'];
}
}
elseif(isset($discount['ACTIONS_LIST']) && is_array($discount['ACTIONS_LIST']))
{
$actionStructure = $discount['ACTIONS_LIST'];
}
return $actionStructure;
}
/**
* Return check result for error mode.
*
* @param array $action Action description.
* @param array $resultDescr Result description.
* @return bool
*/
protected static function compareBasketResultDescr(array $action, $resultDescr)
{
$result = false;
if (empty($action))
return $result;
if (!is_array($resultDescr) || !isset($resultDescr['TYPE']))
return $result;
$currency = (isset($action['CURRENCY']) ? $action['CURRENCY'] : static::getCurrency());
$value = abs($action['VALUE']);
$valueAction = (
$action['VALUE'] < 0
? Sale\OrderDiscountManager::DESCR_VALUE_ACTION_DISCOUNT
: Sale\OrderDiscountManager::DESCR_VALUE_ACTION_EXTRA
);
switch ($resultDescr['TYPE'])
{
case Sale\OrderDiscountManager::DESCR_TYPE_VALUE:
if (
$resultDescr['VALUE'] == $value
&& $resultDescr['VALUE_ACTION'] = $valueAction
)
{
switch($action['UNIT'])
{
case self::VALUE_TYPE_SUMM:
$result = (
(
$resultDescr['VALUE_TYPE'] == Sale\OrderDiscountManager::DESCR_VALUE_TYPE_SUMM_BASKET
|| $resultDescr['VALUE_TYPE'] == Sale\OrderDiscountManager::DESCR_VALUE_TYPE_SUMM
)
&& $resultDescr['VALUE_UNIT'] == $currency
);
break;
case self::VALUE_TYPE_PERCENT:
$result = ($resultDescr['VALUE_TYPE'] == Sale\OrderDiscountManager::DESCR_VALUE_TYPE_PERCENT);
break;
case self::VALUE_TYPE_FIX:
$result = (
$resultDescr['VALUE_TYPE'] == Sale\OrderDiscountManager::DESCR_VALUE_TYPE_CURRENCY
&& $resultDescr['VALUE_UNIT'] == $currency
);
break;
}
}
break;
case Sale\OrderDiscountManager::DESCR_TYPE_MAX_BOUND:
$result = (
$resultDescr['VALUE'] == $value
&& $resultDescr['VALUE_ACTION'] == $valueAction
&& $resultDescr['VALUE_TYPE'] == Sale\OrderDiscountManager::DESCR_VALUE_TYPE_CURRENCY
&& $resultDescr['VALUE_UNIT'] == $currency
);
break;
}
unset($valueAction, $value, $currency);
return $result;
}
/**
* Calculate simple discount result.
*
* @param int|float $value Discount value.
* @param string $unit Discount value type.
* @param array $basketRow Basket item.
* @param int|float|null $limitValue Max discount value.
* @param bool $maxBound Allow set price to 0, if discount more than price.
*
* @return array
*/
protected static function calculateDiscountPrice($value, $unit, array $basketRow, $limitValue, $maxBound)
{
$calculateValue = $value;
if ($unit == self::VALUE_TYPE_PERCENT)
$calculateValue = static::percentToValue($basketRow, $calculateValue);
$calculateValue = static::roundValue($calculateValue, $basketRow['CURRENCY']);
if (!empty($limitValue) && $limitValue + $calculateValue <= 0)
$calculateValue = -$limitValue;
$result = static::roundZeroValue($basketRow['PRICE'] + $calculateValue);
if ($maxBound && $result < 0)
{
$result = 0;
$calculateValue = -$basketRow['PRICE'];
}
return [$calculateValue, $result];
}
/**
* Fill price fields in basket item.
*
* @param array &$basketRow Basket item fields.
* @param int|float $price New price.
* @param int|float $discount Value of the discount change.
* @return void
*/
protected static function fillDiscountPrice(array &$basketRow, $price, $discount)
{
if (!isset($basketRow['DISCOUNT_PRICE']))
$basketRow['DISCOUNT_PRICE'] = 0;
$basketRow['PRICE'] = $price;
$basketRow['DISCOUNT_PRICE'] += $discount;
}
}