24 private static ?
bool $separateSkuMode =
null;
26 private static ?
bool $saleIncluded =
null;
28 private static ?
string $queryElementDate =
null;
37 return '\Bitrix\Catalog\ProductTable';
52 'QUANTITY_TRACE' =>
'QUANTITY_TRACE_ORIG',
53 'CAN_BUY_ZERO' =>
'CAN_BUY_ZERO_ORIG',
54 'SUBSCRIBE' =>
'SUBSCRIBE_ORIG'
66 return parent::deleteNoDemands($id);
79 if (isset(
$data[
'fields'][
'ID']))
80 $id =
$data[
'fields'][
'ID'];
85 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PRODUCT_ID')
91 if (isset(
$data[
'external_fields'][
'IBLOCK_ID']))
94 $iblockId = \CIBlockElement::GetIBlockByID($id);
98 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_ELEMENT_NOT_EXISTS')
102 $iblockData = \CCatalogSku::GetInfoByIBlock(
$iblockId);
103 if (empty($iblockData))
106 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_SIMPLE_IBLOCK')
112 $allowedTypes = self::getProductTypes($iblockData[
'CATALOG_TYPE']);
114 $fields =
$data[
'fields'];
115 parent::prepareForAdd($result, $id, $fields);
116 if (!$result->isSuccess())
119 if (self::$separateSkuMode ===
null)
126 $paymentPeriods =
null,
127 $tripleFields =
null,
128 $booleanFields =
null,
135 'QUANTITY_RESERVED' => 0,
140 'RECUR_SCHEME_LENGTH' =>
null,
142 'TRIAL_PRICE_ID' =>
null,
150 'PURCHASING_PRICE' =>
null,
151 'PURCHASING_CURRENCY' =>
null,
160 'NEGATIVE_AMOUNT_TRACE' => true
164 $tripleFields = [
'QUANTITY_TRACE',
'CAN_BUY_ZERO',
'SUBSCRIBE'];
165 $booleanFields = [
'WITHOUT_ORDER',
'SELECT_BEST_PRICE',
'VAT_INCLUDED',
'BARCODE_MULTI',
'BUNDLE'];
166 $nullFields = [
'MEASURE',
'TRIAL_PRICE_ID',
'VAT_ID',
'RECUR_SCHEME_LENGTH'];
167 $sizeFields = [
'WIDTH',
'LENGTH',
'HEIGHT'];
169 $defaultValues[
'TYPE'] = self::getDefaultProductType($iblockData[
'CATALOG_TYPE']);
171 $fields = array_merge(
$defaultValues, array_diff_key($fields, $blackList));
173 $fields[
'TYPE'] = (int)$fields[
'TYPE'];
174 if (!isset($allowedTypes[$fields[
'TYPE']]))
177 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_PRODUCT_TYPE')
189 $fields[
'QUANTITY'] = 0;
190 $fields[
'QUANTITY_RESERVED'] = 0;
197 $fields[
'QUANTITY_RESERVED'] = 0;
202 if (is_string($fields[
'QUANTITY']) && !is_numeric($fields[
'QUANTITY']))
206 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD',
207 [
'#FIELD#' =>
'QUANTITY']
214 $fields[
'QUANTITY'] = (int)$fields[
'QUANTITY'];
215 if ($fields[
'QUANTITY'] !== 1)
217 $fields[
'QUANTITY'] = 0;
222 $fields[
'QUANTITY'] = (float)$fields[
'QUANTITY'];
225 if (is_string($fields[
'QUANTITY_RESERVED']) && !is_numeric($fields[
'QUANTITY_RESERVED']))
229 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD',
230 [
'#FIELD#' =>
'QUANTITY_RESERVED']
234 $fields[
'QUANTITY_RESERVED'] = (float)$fields[
'QUANTITY_RESERVED'];
236 if ($fields[
'QUANTITY_RESERVED'] < 0)
239 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'),
240 'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'
244 foreach ($tripleFields as $fieldName)
252 foreach ($booleanFields as $fieldName)
257 foreach ($nullFields as $fieldName)
259 if ($fields[$fieldName] !==
null)
261 $fields[$fieldName] = (int)$fields[$fieldName];
262 if ($fields[$fieldName] <= 0)
263 $fields[$fieldName] =
null;
266 foreach ($sizeFields as $fieldName)
268 if ($fields[$fieldName] !==
null)
270 $fields[$fieldName] = (float)$fields[$fieldName];
271 if ($fields[$fieldName] <= 0)
272 $fields[$fieldName] =
null;
282 if (!in_array($fields[
'RECUR_SCHEME_TYPE'], $paymentPeriods,
true))
283 $fields[
'RECUR_SCHEME_TYPE'] =
$defaultValues[
'RECUR_SCHEME_TYPE'];
285 if (is_string($fields[
'WEIGHT']) && !is_numeric($fields[
'WEIGHT']))
289 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD',
290 [
'#FIELD#' =>
'WEIGHT']
294 $fields[
'WEIGHT'] = (float)$fields[
'WEIGHT'];
295 if ($fields[
'TMP_ID'] !==
null)
296 $fields[
'TMP_ID'] = mb_substr($fields[
'TMP_ID'], 0, 40);
299 $purchasingCurrency =
null;
300 $purchasingPrice = static::checkPriceValue($fields[
'PURCHASING_PRICE']);
301 if ($purchasingPrice !==
null)
303 $purchasingCurrency = static::checkPriceCurrency($fields[
'PURCHASING_CURRENCY']);
304 if ($purchasingCurrency ===
null)
307 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PURCHASING_CURRENCY')
309 $purchasingPrice =
null;
312 $fields[
'PURCHASING_PRICE'] = $purchasingPrice;
313 $fields[
'PURCHASING_CURRENCY'] = $purchasingCurrency;
314 unset($purchasingCurrency, $purchasingPrice);
317 if (array_key_exists(
'AVAILABLE', $fields))
324 unset($fields[
'AVAILABLE']);
337 $data[
'actions'][self::ACTION_CHANGE_PARENT_AVAILABLE] =
true;
343 if ($result->isSuccess())
346 $fields[
'NEGATIVE_AMOUNT_TRACE'] = $fields[
'CAN_BUY_ZERO'];
349 if (!isset($fields[
'AVAILABLE']))
351 self::calculateAvailable($fields,
$data[
'actions']);
352 if ($fields[
'AVAILABLE'] ===
null)
357 $data[
'actions'][self::ACTION_CHANGE_PARENT_TYPE] =
true;
361 if ($result->isSuccess())
362 $data[
'fields'] = $fields;
380 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PRODUCT_ID')
386 if (isset(
$data[
'external_fields'][
'IBLOCK_ID']))
389 $iblockId = \CIBlockElement::GetIBlockByID($id);
393 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_ELEMENT_NOT_EXISTS')
397 $iblockData = \CCatalogSku::GetInfoByIBlock(
$iblockId);
398 if (empty($iblockData))
401 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_SIMPLE_IBLOCK')
407 $fields =
$data[
'fields'];
408 parent::prepareForUpdate($result, $id, $fields);
409 if (!$result->isSuccess())
412 if (self::$separateSkuMode ===
null)
417 static $quantityFields =
null,
418 $paymentPeriods =
null,
419 $tripleFields =
null,
420 $booleanFields =
null,
425 if ($quantityFields ===
null)
427 $quantityFields = [
'QUANTITY',
'QUANTITY_RESERVED'];
429 $tripleFields = [
'QUANTITY_TRACE',
'CAN_BUY_ZERO',
'SUBSCRIBE'];
430 $booleanFields = [
'WITHOUT_ORDER',
'SELECT_BEST_PRICE',
'VAT_INCLUDED',
'BARCODE_MULTI',
'BUNDLE',
'AVAILABLE'];
431 $nullFields = [
'MEASURE',
'TRIAL_PRICE_ID',
'VAT_ID',
'RECUR_SCHEME_LENGTH'];
432 $sizeFields = [
'WIDTH',
'LENGTH',
'HEIGHT'];
436 'NEGATIVE_AMOUNT_TRACE' => true
439 $fields = array_diff_key($fields, $blackList);
441 $allowedTypes = self::getProductTypes($iblockData[
'CATALOG_TYPE']);
443 if (array_key_exists(
'TYPE', $fields))
445 $fields[
'TYPE'] = (int)$fields[
'TYPE'];
446 if (!isset($allowedTypes[$fields[
'TYPE']]))
449 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_PRODUCT_TYPE')
455 foreach ($quantityFields as $fieldName)
457 if (array_key_exists($fieldName, $fields))
459 if ($fields[$fieldName] ===
null)
460 unset($fields[$fieldName]);
462 $fields[$fieldName] = (float)$fields[$fieldName];
465 foreach ($tripleFields as $fieldName)
467 if (array_key_exists($fieldName, $fields))
474 unset($fields[$fieldName]);
477 if (isset($fields[
'SUBSCRIBE']))
478 $data[
'actions'][self::ACTION_SEND_NOTIFICATIONS] =
true;
479 foreach ($booleanFields as $fieldName)
481 if (array_key_exists($fieldName, $fields))
487 unset($fields[$fieldName]);
490 foreach ($nullFields as $fieldName)
492 if (isset($fields[$fieldName]))
494 $fields[$fieldName] = (int)$fields[$fieldName];
495 if ($fields[$fieldName] <= 0)
497 $fields[$fieldName] =
null;
501 foreach ($sizeFields as $fieldName)
503 if (isset($fields[$fieldName]))
505 $fields[$fieldName] = (float)$fields[$fieldName];
506 if ($fields[$fieldName] <= 0)
508 $fields[$fieldName] =
null;
514 if (array_key_exists(
'PRICE_TYPE', $fields))
521 unset($fields[
'PRICE_TYPE']);
523 if (array_key_exists(
'RECUR_SCHEME_TYPE', $fields))
525 if (!in_array($fields[
'RECUR_SCHEME_TYPE'], $paymentPeriods,
true))
526 unset($fields[
'RECUR_SCHEME_TYPE']);
529 if (array_key_exists(
'WEIGHT', $fields))
531 if ($fields[
'WEIGHT'] ===
null)
533 unset($fields[
'WEIGHT']);
537 $fields[
'WEIGHT'] = (float)$fields[
'WEIGHT'];
538 $data[
'actions'][self::ACTION_RECALCULATE_SETS] =
true;
541 if (isset($fields[
'TMP_ID']))
543 $fields[
'TMP_ID'] = mb_substr($fields[
'TMP_ID'], 0, 40);
546 if (array_key_exists(
'QUANTITY_RESERVED', $fields) && (
float)$fields[
'QUANTITY_RESERVED'] < 0)
549 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'),
550 'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'
555 $existPurchasingPrice = array_key_exists(
'PURCHASING_PRICE', $fields);
556 $existPurchasingCurrency = array_key_exists(
'PURCHASING_CURRENCY', $fields);
557 if ($existPurchasingPrice)
559 $fields[
'PURCHASING_PRICE'] = static::checkPriceValue($fields[
'PURCHASING_PRICE']);
560 if ($fields[
'PURCHASING_PRICE'] ===
null)
562 $fields[
'PURCHASING_CURRENCY'] =
null;
563 $existPurchasingCurrency =
false;
566 if ($existPurchasingCurrency)
568 $fields[
'PURCHASING_CURRENCY'] = static::checkPriceCurrency($fields[
'PURCHASING_CURRENCY']);
569 if ($fields[
'PURCHASING_CURRENCY'] ===
null)
572 Loc::getMessage(
'BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PURCHASING_CURRENCY')
578 if ($result->isSuccess())
580 if (isset($fields[
'CAN_BUY_ZERO']))
581 $fields[
'NEGATIVE_AMOUNT_TRACE'] = $fields[
'CAN_BUY_ZERO'];
584 if (isset($fields[
'AVAILABLE']))
587 isset($fields[
'TYPE'])
589 : array_merge(static::getCacheItem($id,
true), $fields)
591 $copyFields[
'TYPE'] = (int)$copyFields[
'TYPE'];
600 $data[
'actions'][self::ACTION_CHANGE_PARENT_AVAILABLE] =
true;
602 $data[
'actions'][self::ACTION_RECALCULATE_SETS] =
true;
603 $data[
'actions'][self::ACTION_SEND_NOTIFICATIONS] =
true;
607 $needCalculateAvailable = (isset($fields[
'TYPE'])
608 || isset($fields[
'QUANTITY'])
609 || isset($fields[
'QUANTITY_TRACE'])
610 || isset($fields[
'CAN_BUY_ZERO'])
612 if ($needCalculateAvailable)
614 $needCache = (!isset($fields[
'TYPE'])
615 || !isset($fields[
'QUANTITY'])
616 || !isset($fields[
'QUANTITY_TRACE'])
617 || !isset($fields[
'CAN_BUY_ZERO'])
621 ? array_merge(static::getCacheItem($id,
true), $fields)
624 $copyFields[
'TYPE'] = (int)$copyFields[
'TYPE'];
625 self::calculateAvailable($copyFields,
$data[
'actions']);
626 if ($copyFields[
'AVAILABLE'] !==
null)
627 $fields[
'AVAILABLE'] = $copyFields[
'AVAILABLE'];
628 unset($copyFields, $needCache);
630 unset($needCalculateAvailable);
632 $data[
'fields'] = $fields;
647 switch (
$data[
'fields'][
'TYPE'])
651 isset(
$data[
'actions'][self::ACTION_CHANGE_PARENT_AVAILABLE])
652 || isset(
$data[
'actions'][self::ACTION_CHANGE_PARENT_TYPE])
657 $data[
'external_fields'][
'IBLOCK_ID'],
663 if (isset(
$data[
'actions'][self::ACTION_CHANGE_PARENT_AVAILABLE]))
667 $data[
'external_fields'][
'IBLOCK_ID'],
684 $product = self::getCacheItem($id);
685 if (isset(
$data[
'actions'][self::ACTION_CHANGE_PARENT_AVAILABLE]))
687 switch ($product[
'TYPE'])
690 if (isset(
$data[
'actions'][
'SKU_AVAILABLE']))
694 $data[
'external_fields'][
'IBLOCK_ID'],
698 self::updateElementModificationTime($id);
701 if (isset(
$data[
'actions'][
'SKU_AVAILABLE']))
705 $data[
'external_fields'][
'IBLOCK_ID'],
712 self::updateElementModificationTime($id);
716 if (isset(
$data[
'actions'][self::ACTION_SEND_NOTIFICATIONS]))
718 self::checkSubscription($id, $product);
720 if (isset(
$data[
'actions'][self::ACTION_RECALCULATE_SETS]))
726 isset($product[
'AVAILABLE'])
727 && isset($product[self::PREFIX_OLD.
'AVAILABLE'])
728 && $product[self::PREFIX_OLD.
'AVAILABLE'] != $product[
'AVAILABLE']
730 if ($changeAvailable)
733 \CIBlock::clearIblockTagCache(
$data[
'external_fields'][
'IBLOCK_ID']);
735 $eventId =
'OnProductQuantityTrace';
737 Main\
Config\Option::get(
'catalog',
'enable_processing_deprecated_events') ===
'Y'
742 'ID' => $product[
'ID'],
743 'ELEMENT_IBLOCK_ID' =>
$data[
'external_fields'][
'IBLOCK_ID'],
744 'IBLOCK_ID' =>
$data[
'external_fields'][
'IBLOCK_ID'],
745 'TYPE' => $product[
'TYPE'],
746 'AVAILABLE' => $product[
'AVAILABLE'],
747 'CAN_BUY_ZERO' => $product[
'CAN_BUY_ZERO'],
748 'NEGATIVE_AMOUNT_TRACE' => $product[
'CAN_BUY_ZERO'],
749 'QUANTITY_TRACE' => $product[
'QUANTITY_TRACE'],
750 'QUANTITY' => $product[
'QUANTITY'],
751 'OLD_QUANTITY' => $product[self::PREFIX_OLD.
'QUANTITY'] ?? $product[
'QUANTITY'],
761 $handlerList =
$eventManager->findEventHandlers(
'catalog', $eventId);
762 foreach ($handlerList as $handler)
764 $handler[
'FROM_MODULE_ID'] =
'catalog';
765 $handler[
'MESSAGE_ID'] = $eventId;
768 unset($handler, $handlerList);
792 $helper = $conn->getSqlHelper();
794 'delete from '.$helper->quote(
'b_catalog_product_sets').
795 ' where '.$helper->quote(
'ITEM_ID').
' = '.$id.
' or '.$helper->quote(
'OWNER_ID').
' = '.$id
797 unset($helper, $conn);
808 private static function checkSubscription($id,
array $product): void
811 isset($product[self::PREFIX_OLD.
'AVAILABLE'])
830 $product[self::PREFIX_OLD.
'QUANTITY'] <= 0
831 && $product[
'QUANTITY'] > 0
834 if (self::$saleIncluded ===
null)
835 self::$saleIncluded = Loader::includeModule(
'sale');
836 if (self::$saleIncluded)
837 \CSaleBasket::ProductSubscribe($id,
'catalog');
842 private static function checkPriceValue($price): ?float
848 if (is_string($price))
850 if ($price !==
'' && is_numeric($price))
852 $price = (float)$price;
853 if (is_finite($price))
859 || (is_float($price) && is_finite($price))
869 private static function checkPriceCurrency(
$currency): ?string
880 private static function calculateAvailable(
array &$fields,
array &$actions)
884 switch ($fields[
'TYPE'])
886 case Catalog\ProductTable::TYPE_PRODUCT:
887 case Catalog\ProductTable::TYPE_FREE_OFFER:
888 $result = Catalog\ProductTable::calculateAvailable($fields);
889 $actions[self::ACTION_RECALCULATE_SETS] =
true;
890 $actions[self::ACTION_SEND_NOTIFICATIONS] =
true;
892 case Catalog\ProductTable::TYPE_OFFER:
893 $result = Catalog\ProductTable::calculateAvailable($fields);
894 if (!self::$separateSkuMode)
895 $actions[self::ACTION_CHANGE_PARENT_AVAILABLE] =
true;
896 $actions[self::ACTION_RECALCULATE_SETS] =
true;
897 $actions[self::ACTION_SEND_NOTIFICATIONS] =
true;
899 case Catalog\ProductTable::TYPE_SKU:
900 if (self::$separateSkuMode)
901 $result = Catalog\ProductTable::calculateAvailable($fields);
903 $actions[self::ACTION_CHANGE_PARENT_AVAILABLE] =
true;
905 case Catalog\ProductTable::TYPE_SET:
906 $result = Catalog\ProductTable::calculateAvailable($fields);
908 case Catalog\ProductTable::TYPE_EMPTY_SKU:
909 $result = Catalog\ProductTable::STATUS_NO;
911 case Catalog\ProductTable::TYPE_SERVICE:
912 if (isset($fields[
'QUANTITY']))
914 $result = ($fields[
'QUANTITY'] > 0 ? Catalog\ProductTable::STATUS_YES : Catalog\ProductTable::STATUS_NO);
915 $actions[self::ACTION_SEND_NOTIFICATIONS] =
true;
919 $fields[
'AVAILABLE'] = $result;
923 private static function getProductTypes($catalogType):
array
927 switch ($catalogType)
929 case \CCatalogSku::TYPE_CATALOG:
931 Catalog\ProductTable::TYPE_PRODUCT =>
true,
932 Catalog\ProductTable::TYPE_SET =>
true,
933 Catalog\ProductTable::TYPE_SERVICE =>
true,
936 case \CCatalogSku::TYPE_OFFERS:
938 Catalog\ProductTable::TYPE_OFFER =>
true,
939 Catalog\ProductTable::TYPE_FREE_OFFER =>
true
942 case \CCatalogSku::TYPE_FULL:
944 Catalog\ProductTable::TYPE_PRODUCT =>
true,
945 Catalog\ProductTable::TYPE_SET =>
true,
946 Catalog\ProductTable::TYPE_SKU =>
true,
947 Catalog\ProductTable::TYPE_EMPTY_SKU =>
true,
948 Catalog\ProductTable::TYPE_SERVICE =>
true,
951 case \CCatalogSku::TYPE_PRODUCT:
953 Catalog\ProductTable::TYPE_SKU =>
true,
954 Catalog\ProductTable::TYPE_EMPTY_SKU =>
true
963 private static function getDefaultProductType($catalogType): ?int
967 switch ($catalogType)
969 case \CCatalogSku::TYPE_CATALOG:
970 case \CCatalogSku::TYPE_FULL:
971 $result = Catalog\ProductTable::TYPE_PRODUCT;
973 case \CCatalogSku::TYPE_OFFERS:
974 $result = Catalog\ProductTable::TYPE_OFFER;
976 case \CCatalogSku::TYPE_PRODUCT:
977 $result = Catalog\ProductTable::TYPE_SKU;
984 private static function updateElementModificationTime(
int $elementId): void
986 $conn = Main\Application::getConnection();
987 if (self::$queryElementDate ===
null)
989 $helper = $conn->getSqlHelper();
991 .
' set ' . $helper->quote(
'TIMESTAMP_X') .
' = ' . $helper->getCurrentDateTimeFunction()
992 .
' where ' . $helper->quote(
'ID') .
'=';
994 $conn->queryExecute(self::$queryElementDate . $elementId);
static getConnection($name="")
static get($moduleId, $name, $default="", $siteId=false)
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)