Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/catalog/lib/model/ |
Upload File : |
<?php namespace Bitrix\Catalog\Model; use Bitrix\Catalog; use Bitrix\Currency; use Bitrix\Main; use Bitrix\Main\Localization\Loc; use Bitrix\Main\ORM; use Bitrix\Iblock; Loc::loadMessages(__FILE__); class Price extends Entity { /** @var bool Enable offers automation */ private static $separateSkuMode = null; private static $productPrices = []; private static $basePriceType = null; private static $priceTypes = null; private static $extraList = null; private static $productList = []; /** @var string Query for update element timestamp */ private static $queryElementDate; /** * Returns price tablet name. * * @return string */ public static function getTabletClassName(): string { return '\Bitrix\Catalog\PriceTable'; } /** * Returns product price default fields list for caching. * * @return array */ protected static function getDefaultCachedFieldList(): array { return [ 'ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY' ]; } public static function recountPricesFromBase($id): bool { $id = (int)$id; if ($id <= 0) return false; if (self::$separateSkuMode === null) { self::loadSettings(); } if (empty(self::$extraList) || self::$basePriceType == 0) { return false; } $iterator = Catalog\PriceTable::getList([ 'select' => ['ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'PRICE', 'CURRENCY', 'QUANTITY_FROM', 'QUANTITY_TO'], 'filter' => ['=ID' => $id] ]); $price = $iterator->fetch(); unset($iterator); if (empty($price)) return false; $price['CATALOG_GROUP_ID'] = (int)$price['CATALOG_GROUP_ID']; if ($price['CATALOG_GROUP_ID'] != self::$basePriceType) return false; $productId = (int)$price['PRODUCT_ID']; $product = Product::getCacheItem($productId, true); if (empty($product)) return false; if ( !self::$separateSkuMode && ($product['TYPE'] == Catalog\ProductTable::TYPE_SKU || $product['TYPE'] == Catalog\ProductTable::TYPE_EMPTY_SKU) ) return false; //TODO: replace CCurrency::GetByID to d7 cached method $currency = \CCurrency::GetByID($price['CURRENCY']); if (empty($currency)) return false; $filter = ORM\Query\Query::filter(); $filter->where('PRODUCT_ID', '=', $productId); $filter->where('CATALOG_GROUP_ID', '!=', self::$basePriceType); $filter->whereIn('EXTRA_ID', array_keys(self::$extraList)); if ($price['QUANTITY_FROM'] === null) $filter->whereNull('QUANTITY_FROM'); else $filter->where('QUANTITY_FROM', '=', (int)$price['QUANTITY_FROM']); if ($price['QUANTITY_TO'] === null) $filter->whereNull('QUANTITY_TO'); else $filter->where('QUANTITY_TO', '=', (int)$price['QUANTITY_TO']); $datetime = new Main\Type\DateTime(); $updatePriceTypes = []; $iterator = Catalog\PriceTable::getList([ 'select' => ['ID', 'EXTRA_ID', 'CATALOG_GROUP_ID', 'QUANTITY_FROM', 'QUANTITY_TO'], 'filter' => $filter ]); while ($row = $iterator->fetch()) { $fields = [ 'PRICE' => $price['PRICE']*self::$extraList[$row['EXTRA_ID']], 'CURRENCY' => $price['CURRENCY'], 'TIMESTAMP_X' => $datetime ]; $fields['PRICE_SCALE'] = $fields['PRICE']*$currency['CURRENT_BASE_RATE']; $result = Catalog\PriceTable::update($row['ID'], $fields); if ($result->isSuccess()) $updatePriceTypes[$row['CATALOG_TYPE_ID']] = $row['CATALOG_TYPE_ID']; } unset($result, $fields, $currency, $index); unset($row, $iterator); if (!empty($updatePriceTypes) && $product['TYPE'] == Catalog\ProductTable::TYPE_OFFER) Catalog\Product\Sku::calculatePrice($productId, null, Catalog\ProductTable::TYPE_OFFER, $updatePriceTypes); return true; } /** * Check and modify fields before add product price. Need for entity automation. * * @param ORM\Data\AddResult $result * @param int|null $id * @param array &$data * @return void */ protected static function prepareForAdd(ORM\Data\AddResult $result, $id, array &$data): void { $fields = $data['fields']; parent::prepareForAdd($result, $id, $fields); if (!$result->isSuccess()) return; if (self::$separateSkuMode === null) { self::loadSettings(); } static $defaultValues = null, $blackList = null; if ($defaultValues === null) { $defaultValues = [ 'PRODUCT_ID' => 0, 'CATALOG_GROUP_ID' => 0, 'EXTRA_ID' => null, 'PRICE' => null, 'CURRENCY' => null, 'QUANTITY_FROM' => null, 'QUANTITY_TO' => null, 'TMP_ID' => null ]; $blackList = [ 'ID' => true ]; } $fields = array_merge($defaultValues, array_diff_key($fields, $blackList)); $fields['PRODUCT_ID'] = (int)$fields['PRODUCT_ID']; if ($fields['PRODUCT_ID'] <= 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRODUCT_ID') )); return; } $fields['CATALOG_GROUP_ID'] = (int)$fields['CATALOG_GROUP_ID']; if (!isset(self::$priceTypes[$fields['CATALOG_GROUP_ID']])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_CATALOG_GROUP_ID') )); return; } else { if ($fields['CATALOG_GROUP_ID'] == self::$basePriceType) { $fields['EXTRA_ID'] = null; if (isset($data['actions']['OLD_RECOUNT'])) { if ($data['actions']['OLD_RECOUNT'] === true) $data['actions']['PARENT_PRICE'] = true; } } } if ($fields['TMP_ID'] !== null) $fields['TMP_ID'] = mb_substr($fields['TMP_ID'], 0, 40); static::checkQuantityRange($result, $fields); if ($fields['EXTRA_ID'] !== null) { $fields['EXTRA_ID'] = (int)$fields['EXTRA_ID']; if (!isset(self::$extraList[$fields['EXTRA_ID']])) { unset($fields['EXTRA_ID']); } else { if ( (!isset($fields['PRICE']) && !isset($fields['CURRENCY'])) || (isset($data['actions']['OLD_RECOUNT']) && $data['actions']['OLD_RECOUNT'] === true) ) self::calculatePriceFromBase(null, $fields); } } if ($fields['PRICE'] === null) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRICE') )); } else { $fields['PRICE'] = self::checkPriceValue($fields['PRICE']); if ($fields['PRICE'] === null || $fields['PRICE'] < 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRICE') )); } } $fields['CURRENCY'] = (string)$fields['CURRENCY']; if (!Currency\CurrencyManager::isCurrencyExist($fields['CURRENCY'])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_CURRENCY') )); } if ($result->isSuccess()) { $fields['TIMESTAMP_X'] = new Main\Type\DateTime(); if (isset($fields['PRICE_SCALE'])) $fields['PRICE_SCALE'] = self::checkPriceValue($fields['PRICE_SCALE']); // self::checkPriceValue return float or null if (!isset($fields['PRICE_SCALE'])) { //TODO: replace CCurrency::GetByID to d7 cached method $currency = \CCurrency::GetByID($fields['CURRENCY']); $fields['PRICE_SCALE'] = $fields['PRICE']*$currency['CURRENT_BASE_RATE']; unset($currency); } if (isset($data['actions']['PARENT_PRICE'])) unset($data['actions']['PARENT_PRICE']); if (!self::$separateSkuMode) { $product = Product::getCacheItem($fields['PRODUCT_ID'], true); if (!empty($product) && $product['TYPE'] == Catalog\ProductTable::TYPE_OFFER) $data['actions']['PARENT_PRICE'] = true; unset($product); } if (isset($data['actions']['RECOUNT_PRICES'])) { if ($fields['CATALOG_GROUP_ID'] != self::$basePriceType) unset($data['actions']['RECOUNT_PRICES']); else $data['actions']['RECOUNT_PRICES'] = true; } $data['fields'] = $fields; } unset($fields); } /** * Check and modify fields before update product price. Need for entity automation. * * @param ORM\Data\UpdateResult $result * @param int $id * @param array &$data * @return void */ protected static function prepareForUpdate(ORM\Data\UpdateResult $result, $id, array &$data): void { $id = (int)$id; if ($id <= 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRICE_ID') )); return; } if (self::$separateSkuMode === null) { self::loadSettings(); } $fields = $data['fields']; parent::prepareForUpdate($result, $id, $fields); if (!$result->isSuccess()) return; $blackList = [ 'ID' => true ]; $fields = array_diff_key($fields, $blackList); if (array_key_exists('PRODUCT_ID', $fields)) { $fields['PRODUCT_ID'] = (int)$fields['PRODUCT_ID']; if ($fields['PRODUCT_ID'] <= 0) $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRODUCT_ID') )); } if (array_key_exists('CATALOG_GROUP_ID', $fields)) { $fields['CATALOG_GROUP_ID'] = (int)$fields['CATALOG_GROUP_ID']; if (!isset(self::$priceTypes[$fields['CATALOG_GROUP_ID']])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_CATALOG_GROUP_ID') )); } else { if ($fields['CATALOG_GROUP_ID'] == self::$basePriceType) { $fields['EXTRA_ID'] = null; if (isset($data['actions']['OLD_RECOUNT'])) { if ($data['actions']['OLD_RECOUNT'] === true) $data['actions']['PARENT_PRICE'] = true; } } } } if (isset($fields['TMP_ID'])) $fields['TMP_ID'] = mb_substr($fields['TMP_ID'], 0, 40); $existQuantityFrom = array_key_exists('QUANTITY_FROM', $fields); $existQuantityTo = array_key_exists('QUANTITY_TO', $fields); if ($existQuantityFrom != $existQuantityTo) { if ($existQuantityFrom) $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_QUANTITY_RANGE_LEFT_BORDER_ONLY') )); else $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_QUANTITY_RANGE_RIGHT_BORDER_ONLY') )); } else { if ($existQuantityFrom) static::checkQuantityRange($result, $fields); } unset($existQuantityTo, $existQuantityFrom); if (isset($fields['EXTRA_ID'])) { $fields['EXTRA_ID'] = (int)$fields['EXTRA_ID']; if (!isset(self::$extraList[$fields['EXTRA_ID']])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_EXTRA_ID') )); } else { if ( (!isset($fields['PRICE']) && !isset($fields['CURRENCY'])) || (isset($data['actions']['OLD_RECOUNT']) && $data['actions']['OLD_RECOUNT'] === true) ) self::calculatePriceFromBase($id, $fields); } } if (array_key_exists('PRICE', $fields)) { $fields['PRICE'] = self::checkPriceValue($fields['PRICE']); if ($fields['PRICE'] === null || $fields['PRICE'] < 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_PRICE') )); } } if (array_key_exists('CURRENCY', $fields)) { $fields['CURRENCY'] = (string)$fields['CURRENCY']; if (!Currency\CurrencyManager::isCurrencyExist($fields['CURRENCY'])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_CURRENCY') )); } } if ($result->isSuccess()) { if (isset($data['actions']['PARENT_PRICE'])) unset($data['actions']['PARENT_PRICE']); $priceScale = !isset($fields['PRICE_SCALE']) && (isset($fields['PRICE']) || isset($fields['CURRENCY'])); $needCalculatePrice = ( !self::$separateSkuMode && ( isset($fields['PRODUCT_ID']) || isset($fields['CATALOG_GROUP_ID']) || isset($fields['PRICE']) || isset($fields['CURRENCY']) ) ); $recountPrices = isset($data['actions']['RECOUNT_PRICES']); $copyFields = []; if ($priceScale || $needCalculatePrice || $recountPrices) $copyFields = array_merge(static::getCacheItem($id, true), $fields); if (isset($fields['PRICE_SCALE'])) { $fields['PRICE_SCALE'] = self::checkPriceValue($fields['PRICE_SCALE']); if ($fields['PRICE_SCALE'] === null || $fields['PRICE_SCALE'] < 0) unset($fields['PRICE_SCALE']); } if (!isset($fields['PRICE_SCALE'])) { if (isset($fields['PRICE']) || isset($fields['CURRENCY'])) { //TODO: replace CCurrency::GetByID to d7 cached method $currency = \CCurrency::GetByID($copyFields['CURRENCY']); $fields['PRICE_SCALE'] = $copyFields['PRICE']*$currency['CURRENT_BASE_RATE']; unset($currency); } } if ($needCalculatePrice) { $product = Product::getCacheItem($copyFields['PRODUCT_ID'], true); if (!empty($product) && $product['TYPE'] == Catalog\ProductTable::TYPE_OFFER) $data['actions']['PARENT_PRICE'] = true; unset($product); } if (isset($data['actions']['RECOUNT_PRICES'])) { if ($copyFields['CATALOG_GROUP_ID'] != self::$basePriceType) unset($data['actions']['RECOUNT_PRICES']); else $data['actions']['RECOUNT_PRICES'] = true; } unset($copyFields); $fields['TIMESTAMP_X'] = new Main\Type\DateTime(); $data['fields'] = $fields; } unset($fields); } /** * Run core automation after add product price. * * @param int $id * @param array $data * @return void */ protected static function runAddExternalActions($id, array $data): void { if ((int)$data['fields']['CATALOG_GROUP_ID'] === self::$basePriceType) { if (isset(self::$productPrices[$data['fields']['PRODUCT_ID']])) { unset(self::$productPrices[$data['fields']['PRODUCT_ID']]); } } if (isset($data['actions']['RECOUNT_PRICES'])) { self::recountPricesFromBase($id); } if (isset($data['actions']['PARENT_PRICE'])) { Catalog\Product\Sku::calculatePrice( $data['fields']['PRODUCT_ID'], null, Catalog\ProductTable::TYPE_OFFER, [0 => $data['fields']['CATALOG_GROUP_ID']] ); } self::updateProductModificationTime($data['fields']['PRODUCT_ID']); } /** * Run core automation after update product price. * * @param int $id * @param array $data * @return void */ protected static function runUpdateExternalActions($id, array $data): void { $price = self::getCacheItem($id); if ((int)$price['CATALOG_GROUP_ID'] === self::$basePriceType) { if (isset(self::$productPrices[$price['PRODUCT_ID']])) { unset(self::$productPrices[$price['PRODUCT_ID']]); } } if (isset($data['actions']['RECOUNT_PRICES'])) { self::recountPricesFromBase($id); } if (isset($data['actions']['PARENT_PRICE'])) { $priceTypes = [0 => $price['CATALOG_GROUP_ID']]; if ( isset($price[self::PREFIX_OLD.'CATALOG_GROUP_ID']) && $price[self::PREFIX_OLD.'CATALOG_GROUP_ID'] != $price['CATALOG_GROUP_ID'] ) $priceTypes[] = $price[self::PREFIX_OLD.'CATALOG_GROUP_ID']; Catalog\Product\Sku::calculatePrice( $price['PRODUCT_ID'], null, Catalog\ProductTable::TYPE_OFFER, $priceTypes); if ( isset($price[self::PREFIX_OLD.'PRODUCT_ID']) && $price[self::PREFIX_OLD.'PRODUCT_ID'] != $price['PRODUCT_ID'] ) Catalog\Product\Sku::calculatePrice($price[self::PREFIX_OLD.'PRODUCT_ID'], null, null, $priceTypes); unset($priceTypes); } self::updateProductModificationTime($price['PRODUCT_ID']); if ( isset($price[self::PREFIX_OLD.'PRODUCT_ID']) && $price[self::PREFIX_OLD.'PRODUCT_ID'] != $price['PRODUCT_ID'] ) { self::updateProductModificationTime($price[self::PREFIX_OLD.'PRODUCT_ID']); } unset($price); } /** * Run core automation after delete product price. * * @param int $id * @return void */ protected static function runDeleteExternalActions($id): void { $price = self::getCacheItem($id); $product = Product::getCacheItem($price[self::PREFIX_OLD.'PRODUCT_ID']); if (!empty($product) && $product['TYPE'] == Catalog\ProductTable::TYPE_OFFER) { Catalog\Product\Sku::calculatePrice( $price[self::PREFIX_OLD.'PRODUCT_ID'], null, Catalog\ProductTable::TYPE_OFFER, [0 => $price[self::PREFIX_OLD.'CATALOG_GROUP_ID']] ); } if (!empty($product)) { self::updateProductModificationTime($price[self::PREFIX_OLD.'PRODUCT_ID']); } unset($product, $price); } /** * Check and correct quantity range. * @internal * * @param ORM\Data\Result $result for errors. * @param array &$fields price data. * @return void */ private static function checkQuantityRange(ORM\Data\Result $result, array &$fields) { if ($fields['QUANTITY_FROM'] !== null) { $fields['QUANTITY_FROM'] = (int)$fields['QUANTITY_FROM']; if ($fields['QUANTITY_FROM'] <= 0) $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_QUANTITY_FROM') )); } if ($fields['QUANTITY_TO'] != null) { $fields['QUANTITY_TO'] = (int)$fields['QUANTITY_TO']; if ($fields['QUANTITY_TO'] <= 0) $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_QUANTITY_TO') )); } if ($fields['QUANTITY_FROM'] !== null && $fields['QUANTITY_TO'] != null) { if ($fields['QUANTITY_FROM'] == 0 && $fields['QUANTITY_TO'] == 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRICE_ERR_WRONG_QUANTITY_RANGE_ZERO') )); } elseif ($fields['QUANTITY_FROM'] > $fields['QUANTITY_TO']) { $result->addError(new ORM\EntityError( Loc::getMessage( 'BX_CATALOG_MODEL_PRICE_ERR_WRONG_QUANTITY_RANGE_INVERT', ['#LEFT#' => $fields['QUANTITY_FROM'], '#RIGHT#' => $fields['QUANTITY_TO']] ) )); } } } /** * Check price value. * @internal * * @param string|int|float|null $price Price value. * @return float|int|null */ private static function checkPriceValue($price) { $result = null; if ($price !== null) { if (is_string($price)) { if ($price !== '' && is_numeric($price)) { $price = (float)$price; if (is_finite($price)) $result = $price; } } elseif ( is_int($price) || (is_float($price) && is_finite($price)) ) { $result = $price; } } return $result; } private static function loadSettings() { self::$separateSkuMode = Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y'; self::$extraList = []; foreach (Catalog\ExtraTable::getExtraList() as $row) { self::$extraList[$row['ID']] = (100 + (float)$row['PERCENTAGE']) / 100; } unset($row); self::$basePriceType = (int)Catalog\GroupTable::getBasePriceTypeId(); self::$priceTypes = Catalog\GroupTable::getTypeList();; } private static function calculatePriceFromBase($id, array &$fields) { $correct = false; $copyFields = $fields; if ( !isset($fields['PRODUCT_ID']) || !isset($fields['CATALOG_GROUP_ID']) || !array_key_exists('QUANTITY_FROM', $fields) || !array_key_exists('QUANTITY_TO', $fields) ) { if ($id !== null) { $iterator = self::getList([ 'select' => ['ID', 'PRODUCT_ID', 'CATALOG_GROUP_ID', 'QUANTITY_FROM', 'QUANTITY_TO'], 'filter' => ['=ID' => $id] ]); $data = $iterator->fetch(); unset($iterator); if (!empty($data)) { $copyFields = array_merge($data, $copyFields); $correct = true; } unset($data); } } else { $correct = true; } if (!$correct) return; $productId = $copyFields['PRODUCT_ID']; if (!isset(self::$productPrices[$productId])) self::loadProductBasePrices($productId); $index = self::getPriceIndex($copyFields); if (!isset(self::$productPrices[$productId][$index])) return; $fields['PRICE'] = self::$productPrices[$productId][$index]['PRICE']*self::$extraList[$copyFields['EXTRA_ID']]; $fields['CURRENCY'] = self::$productPrices[$productId][$index]['CURRENCY']; } private static function getPriceIndex(array $row): string { return ($row['QUANTITY_FROM'] === null ? 'ZERO' : $row['QUANTITY_FROM']). '-'.($row['QUANTITY_TO'] === null ? 'INF' : $row['QUANTITY_TO']); } private static function loadProductBasePrices($productId) { self::$productPrices = [ $productId => [] ]; $iterator = Catalog\PriceTable::getList([ 'select' => ['ID', 'PRICE', 'CURRENCY', 'QUANTITY_FROM', 'QUANTITY_TO'], 'filter' => ['=PRODUCT_ID' => $productId, '=CATALOG_GROUP_ID' => self::$basePriceType] ]); while ($row = $iterator->fetch()) self::$productPrices[$productId][self::getPriceIndex($row)] = $row; unset($row, $iterator); } public static function clearSettings(): void { parent::clearSettings(); self::$separateSkuMode = null; self::$basePriceType = null; self::$priceTypes = null; self::$extraList = null; } private static function updateProductModificationTime(int $productId): void { if (!isset(self::$productList[$productId])) { self::$productList[$productId] = true; $conn = Main\Application::getConnection(); if (self::$queryElementDate === null) { $helper = $conn->getSqlHelper(); self::$queryElementDate = 'update ' . $helper->quote(Iblock\ElementTable::getTableName()) . ' set '.$helper->quote('TIMESTAMP_X') . ' = '.$helper->getCurrentDateTimeFunction() . ' where '.$helper->quote('ID') . '='; } $conn->queryExecute(self::$queryElementDate . $productId); } } }