56 $this->cacheProductProviderData = $this->builder->getSettingsContainer()->getItemValue(
'cacheProductProviderData');
61 $this->formData = $this->builder->getFormData();
62 $this->delegate = $this->
getDelegate($this->formData[
'ID']);
81 return $this->builder->getSettingsContainer()->getItemValue(
'needUpdateNewProductPrice');
88 if((
float)$productData[
'QUANTITY'] <= 0)
93 "SALE_HLP_ORDERBUILDER_QUANTITY_ERROR",
94 [
'#PRODUCT_NAME#' => $productData[
'NAME']]
100 if((
int)$productData[
'PRICE'] < 0)
105 "SALE_HLP_ORDERBUILDER_PRICE_ERROR",
106 [
'#PRODUCT_NAME#' => $productData[
'NAME']]
117 foreach($this->formData[
"PRODUCT"] as $basketCode => $productData)
124 if(self::isBasketItemNew($basketCode))
126 $basketInternalId = intval(mb_substr($basketCode, 1));
128 if($basketInternalId > $this->maxBasketCodeIdx)
129 $this->maxBasketCodeIdx = $basketInternalId;
134 $this->needDataUpdate[] = $basketCode;
135 unset($this->formData[
"PRODUCT"][$basketCode][
"PROVIDER_DATA"]);
136 unset($this->formData[
"PRODUCT"][$basketCode][
"SET_ITEMS_DATA"]);
147 sortByColumn($this->formData[
"PRODUCT"],
array(
"BASE_PRICE" => SORT_DESC,
"PRICE" => SORT_DESC),
'',
null,
true);
159 public function removeDeletedItems()
161 $deleteBasketItemsIfNotExists = $this->builder->getSettingsContainer()->getItemValue(
'deleteBasketItemsIfNotExists');
162 $itemsBasketCodes = [];
164 foreach($this->formData[
"PRODUCT"] as $basketCode => $productData)
166 if (!isset($productData[
"PROPS"]))
168 $productData[
"PROPS"] =
array();
174 $item = $this->
getBasket()->getItemByBasketCode($basketCode);
177 if ($item ==
null && $deleteBasketItemsIfNotExists)
182 if($item && $item->isBundleChild())
192 $itemsBasketCodes[] = $item->getBasketCode();
196 $basketItems = $this->
getBasket()->getBasketItems();
198 foreach($basketItems as $item)
200 if(!in_array($item->getBasketCode(), $itemsBasketCodes))
202 if ($deleteBasketItemsIfNotExists)
204 $itemDeleteResult = $item->delete();
206 if (!$itemDeleteResult->isSuccess())
208 $this->builder->getErrorsContainer()->addErrors($itemDeleteResult->getErrors());
209 throw new BuildingException();
214 $this->clearReservesForItem($item);
222 public function itemsDataPreparation()
224 foreach($this->formData[
"PRODUCT"] as $basketCode => $productData)
226 if (isset($productData[
"IS_SET_ITEM"]) && $productData[
"IS_SET_ITEM"] ===
"Y")
231 if(!isset($productData[
"PROPS"]) || !is_array($productData[
"PROPS"]))
232 $productData[
"PROPS"] =
array();
235 $item = $this->getItemFromBasket($basketCode, $productData);
239 $this->
setItemData($basketCode, $productData, $item);
243 $item = $this->
createItem($basketCode, $productData);
255 if($basketCode != $item->getBasketCode())
258 if(!empty($productData[
"PROPS"]) && is_array($productData[
"PROPS"]))
261 $property = $item->getPropertyCollection();
262 if(!$property->isPropertyAlreadyExists($productData[
"PROPS"]))
264 $property->setProperty($productData[
"PROPS"]);
278 $this->formData[
'PRODUCT'][
$new] = $this->formData[
'PRODUCT'][$old];
279 unset($this->formData[
'PRODUCT'][$old]);
287 protected function setItemsFieldsByFormData()
290 $basketItems = $this->
getBasket()->getBasketItems();
292 foreach($basketItems as $item)
294 $basketCode = $item->getBasketCode();
296 if(empty($this->formData[
'PRODUCT'][$basketCode]))
299 $productData = $this->formData[
'PRODUCT'][$basketCode];
301 isset($productData[
'MODULE'])
302 && $productData[
'MODULE'] ===
'catalog'
303 && empty($productData[
'PRODUCT_PROVIDER_CLASS'])
304 && Loader::includeModule(
'catalog')
307 $productData[
'PRODUCT_PROVIDER_CLASS'] =
'\\'.Product\CatalogProvider::class;
310 $isProductDataNeedUpdate = in_array($basketCode, $this->needDataUpdate);
312 if(isset($productData[
"PRODUCT_PROVIDER_CLASS"]) && $productData[
"PRODUCT_PROVIDER_CLASS"] <>
'')
314 $item->setField(
"PRODUCT_PROVIDER_CLASS", trim($productData[
"PRODUCT_PROVIDER_CLASS"]));
321 if($this->cacheProductProviderData && !$isProductDataNeedUpdate)
323 self::sendProductCachedDataToProvider($item, $this->
getOrder(), $productData);
326 if (isset($productData[
'NAME']))
328 $item->setField(
'NAME', $productData[
'NAME']);
331 $item->setField(
'TYPE', $productData[
'TYPE'] ??
null);
333 if (isset($productData[
'CUSTOM_PRICE']) && $productData[
'CUSTOM_PRICE'] ===
'Y')
335 $item->markFieldCustom(
'PRICE');
338 $res = $item->setField(
"QUANTITY", $productData[
"QUANTITY"]);
340 if(!
$res->isSuccess())
343 throw new BuildingException();
346 if (isset($productData[
'VAT_RATE']))
348 $item->markFieldCustom(
'VAT_RATE');
349 $item->setField(
'VAT_RATE', $productData[
'VAT_RATE']);
352 if (isset($productData[
'VAT_INCLUDED']))
354 $item->markFieldCustom(
'VAT_INCLUDED');
355 $item->setField(
'VAT_INCLUDED', $productData[
'VAT_INCLUDED']);
358 if(isset($productData[
"MODULE"]) && $productData[
"MODULE"] ==
"catalog")
360 $this->catalogProductsIds[] = $item->getField(
'PRODUCT_ID');
362 elseif(empty($productData[
"PRODUCT_PROVIDER_CLASS"]))
365 $availableFields = array_fill_keys($availableFields,
true);
366 $fillFields = array_intersect_key($productData, $availableFields);
368 $orderCurrency = $this->
getOrder()->getCurrency();
370 isset($fillFields[
'CURRENCY'])
371 && $fillFields[
'CURRENCY'] !== $orderCurrency
374 $fillFields[
'PRICE'] = \CCurrencyRates::ConvertCurrency(
375 (
float)$fillFields[
'PRICE'],
376 $fillFields[
'CURRENCY'],
379 $fillFields[
'BASE_PRICE'] = \CCurrencyRates::ConvertCurrency(
380 (
float)$fillFields[
'BASE_PRICE'],
381 $fillFields[
'CURRENCY'],
384 $fillFields[
'CURRENCY'] = $orderCurrency;
387 if (!empty($fillFields))
389 $r = $item->setFields($fillFields);
402 if(empty($this->catalogProductsIds))
408 foreach($this->catalogProductsIds as $id)
410 $details = OrderEdit::getProductDetails($id,
$order->getUserId(),
$order->getSiteId());
412 if($details !==
false)
413 $this->catalogProductsData[$id] = $details;
416 $noCachedProductIds = array_diff($this->catalogProductsIds, array_keys($this->catalogProductsData));
418 if(!empty($noCachedProductIds))
420 $noCachedData = \Bitrix\Sale\Helpers\Admin\Product::getData($noCachedProductIds,
$order->getSiteId(), array_keys(
$fields));
422 foreach($noCachedData as $productId => $productData)
424 $this->catalogProductsData[$productId] = $productData;
425 OrderEdit::setProductDetails($productId,
$order->getUserId(),
$order->getSiteId(), $this->catalogProductsData[$productId]);
428 $emptyData = array_diff($this->catalogProductsIds, array_keys($this->catalogProductsData));
430 foreach($emptyData as $productId)
431 $this->catalogProductsData[$productId] =
array();
435 protected function obtainProviderProductsData()
438 $basketItems = $this->
getBasket()->getBasketItems();
451 foreach($basketItems as $item)
453 $basketCode = $item->getBasketCode();
455 if(
$order->getId() <= 0 && !empty($this->providerData[$basketCode]) && empty($this->providerData[$basketCode][
'QUANTITY']))
461 "SALE_ORDEREDIT_PRODUCT_QUANTITY_IS_EMPTY",
463 "#NAME#" => $item->getField(
'NAME')
466 'SALE_ORDEREDIT_PRODUCT_QUANTITY_IS_EMPTY'
475 protected function isProductAvailable($basketCode, $productFormData, $productProviderData, $isProductDataNeedUpdate)
479 if($this->
getOrder()->getId() <= 0 && (empty($productProviderData[$basketCode]) || !$this->cacheProductProviderData || $isProductDataNeedUpdate))
481 if(empty($productProviderData[$basketCode]) && $productFormData[
"PRODUCT_PROVIDER_CLASS"] <>
'')
491 public function setItemsFields()
494 $basketItems = $this->
getBasket()->getBasketItems();
496 $this->setItemsFieldsByFormData();
498 $this->obtainProviderProductsData();
500 $productProviderData =
array();
503 $firstBasketItem = $basketItems[0] ??
null;
504 $vatIncludedFromFirstItem = $firstBasketItem?->getField(
'VAT_INCLUDED');
507 foreach($basketItems as $item)
509 $basketCode = $item->getBasketCode();
510 $productFormData = $this->formData[
'PRODUCT'][$basketCode] ?? [];
511 $isProductDataNeedUpdate = in_array($basketCode, $this->needDataUpdate);
512 $productProviderData[$basketCode] = $item->getFieldValues();
514 if(empty($productFormData))
517 if(!empty($this->providerData[$basketCode]))
519 if ($this->builder->getSettingsContainer()->getItemValue(
'isRefreshData') ===
true)
521 unset($this->providerData[$basketCode][
'QUANTITY']);
524 $productProviderData[$basketCode] = $this->providerData[$basketCode];
528 $productProviderData[$basketCode] =
$trustData[$basketCode];
534 if(is_array($productProviderData[$basketCode]) && !empty($productProviderData[$basketCode]))
535 OrderEdit::setProviderTrustData($item,
$order, $productProviderData[$basketCode]);
546 if(!$this->
isProductAvailable($basketCode, $productFormData, $productProviderData, $isProductDataNeedUpdate))
551 "SALE_HLP_ORDERBUILDER_PRODUCT_NOT_AVILABLE",
553 "#NAME#" => $productFormData[
"NAME"] . (!empty($productFormData[
"PRODUCT_ID"]) ?
" (".$productFormData[
'PRODUCT_ID'].
")" :
"")
563 if($item->getField(
"MODULE") ==
"catalog")
567 $product = array_merge($product,
$catalogData[$item->getProductId()]);
568 unset($productFormData[
"CURRENCY"]);
573 if(!$this->cacheProductProviderData || $isProductDataNeedUpdate)
575 $product = array_merge($productFormData, $product);
579 $basePrice = (float)($productFormData[
'BASE_PRICE'] ?? 0);
580 $price = (float)($productFormData[
'PRICE'] ?? 0);
583 $isPriceCustom = isset($productFormData[
'CUSTOM_PRICE']) && $productFormData[
'CUSTOM_PRICE'] ==
'Y';
587 $productFormData[
'DISCOUNT_PRICE'] = 0;
588 if ($basePrice > $price)
590 $productFormData[
'DISCOUNT_PRICE'] = $basePrice - $price;
594 if ((
$order->getId() === 0 && !$isPriceCustom) || $needUpdateItemPrice)
596 unset($productFormData[
'PRICE'], $productFormData[
'PRICE_BASE'], $productFormData[
'BASE_PRICE']);
599 $product = array_merge($product, $productFormData);
602 if (isset($product[
"OFFER_ID"]) && intval($product[
"OFFER_ID"]) > 0)
604 $product[
"PRODUCT_ID"] = $product[
"OFFER_ID"];
608 $product = array_intersect_key($product, array_flip($item::getAvailableFields()));
610 if (isset($product[
"MEASURE_CODE"]) && $product[
"MEASURE_CODE"] <>
'')
612 $measures = OrderBasket::getCatalogMeasures();
614 if (!empty($measures[$product[
"MEASURE_CODE"]]))
616 $product[
"MEASURE_NAME"] = $measures[$product[
"MEASURE_CODE"]];
620 if (empty($product[
"CURRENCY"]))
622 $product[
"CURRENCY"] =
$order->getCurrency();
626 isset($productFormData[
"IS_SET_PARENT"])
627 && $productFormData[
"IS_SET_PARENT"] ===
"Y"
633 OrderEdit::setProductDetails(
634 $productFormData[
"OFFER_ID"] ??
null,
637 array_merge($product, $productFormData)
640 if ($product[
"CURRENCY"] !==
$order->getCurrency())
642 $product[
"PRICE"] = \CCurrencyRates::ConvertCurrency($product[
"PRICE"], $product[
"CURRENCY"],
$order->getCurrency());
643 if ($product[
"BASE_PRICE"] > 0)
645 $product[
"BASE_PRICE"] = \CCurrencyRates::ConvertCurrency($product[
"BASE_PRICE"], $product[
"CURRENCY"],
$order->getCurrency());
647 if ($product[
"DISCOUNT_PRICE"] > 0)
649 $product[
"DISCOUNT_PRICE"] = \CCurrencyRates::ConvertCurrency($product[
"DISCOUNT_PRICE"], $product[
"CURRENCY"],
$order->getCurrency());
651 if ($product[
"VAT_RATE"] > 0)
653 $product[
"VAT_RATE"] = \CCurrencyRates::ConvertCurrency($product[
"VAT_RATE"], $product[
"CURRENCY"],
$order->getCurrency());
656 $product[
"CURRENCY"] =
$order->getCurrency();
659 if ($vatIncludedFromFirstItem)
666 if ($item->isReservableItem())
668 if (!empty($productFormData[
'RESERVE']) && is_array($productFormData[
'RESERVE']))
670 $reserveData = $productFormData[
'RESERVE'];
675 $this->clearReservesForItem($item);
683 protected function clearReservesForItem(BasketItem $item)
685 if (!$item->isReservableItem())
691 $reserveCollection = $item->getReserveQuantityCollection();
692 if ($reserveCollection)
694 $reserveCollection->clearCollection();
705 $reserveCollection = $item->getReserveQuantityCollection();
706 if (!$reserveCollection)
714 $this->clearReservesForItem($item);
717 foreach ($reserveData as $reserveCode => $reserve)
719 $isNewReserve = mb_strpos($reserveCode,
'n') === 0;
722 $reserveCollectionItem = $reserveCollection->create();
726 $reserveCollectionItem = $reserveCollection->getItemById($reserveCode);
727 if (!$reserveCollectionItem)
733 if (isset($reserve[
'STORE_ID']) && (
int)$reserve[
'STORE_ID'] !== $reserveCollectionItem->getStoreId())
738 $deleteResult = $reserveCollectionItem->delete();
739 if (!$deleteResult->isSuccess())
745 $reserveCollectionItem = $reserveCollection->create();
748 $reserveCollectionItem->setStoreId((
int)$reserve[
'STORE_ID']);
751 if (isset($reserve[
'QUANTITY']))
753 $quantity = (float)$reserve[
'QUANTITY'];
758 $reserveCollectionItem->setQuantity($quantity);
761 if (isset($reserve[
'DATE_RESERVE_END']) && $reserve[
'DATE_RESERVE_END'] !==
'')
763 $dateReserveEnd =
new Date($reserve[
'DATE_RESERVE_END']);
764 $reserveCollectionItem->setField(
'DATE_RESERVE_END', $dateReserveEnd);
767 if (isset($reserve[
'RESERVED_BY']))
769 $reserveCollectionItem->setField(
'RESERVED_BY', $reserve[
'RESERVED_BY']);
776 return $this->builder->getOrder();
781 return $this->builder->getSettingsContainer();
786 return $this->builder->getErrorsContainer();
796 return $this->builder->getOrder()->getBasket();
801 return (mb_strpos($basketCode,
'n') === 0) && ($basketCode != self::BASKET_CODE_NEW);
804 protected function getItemFromBasket($basketCode, $productData)
807 $item = $this->delegate->getItemFromBasket($basketCode, $productData);
809 if($item && $item->isBundleChild())
815 protected function setItemData($basketCode, &$productData, &$item)
817 return $this->delegate->setItemData($basketCode, $productData, $item);
823 if($basketCode != self::BASKET_CODE_NEW)
824 $setBasketCode = $basketCode;
825 elseif(intval($this->maxBasketCodeIdx) > 0)
826 $setBasketCode =
'n'.strval($this->maxBasketCodeIdx+1);
828 $setBasketCode =
null;
831 $productData[
"MODULE"] ??
'',
832 $productData[
"OFFER_ID"] ??
null,
837 !isset($productData[
'BASKET_CODE'])
838 || $basketCode != $productData[
'BASKET_CODE']
841 $productData[
"BASKET_CODE"] = $item->getBasketCode();
844 if($basketCode == self::BASKET_CODE_NEW)
847 $this->needDataUpdate[] = $item->getBasketCode();
850 if(!empty($productData[
'REPLACED']) && $productData[
'REPLACED'] ==
'Y')
851 $this->needDataUpdate[] = $item->getBasketCode();
859 $catalogIncluded =
null;
865 if ($catalogIncluded ===
null)
867 $catalogIncluded = Loader::includeModule(
'catalog');
870 if ($catalogIncluded)
875 $result[$measure[
"CODE"]] = ($measure[
"SYMBOL_RUS"] !=
'' ? $measure[
"SYMBOL_RUS"] : $measure[
"SYMBOL_INTL"]);
890 if(empty($productData[
"PROVIDER_DATA"]) || !
CheckSerializedData($productData[
"PROVIDER_DATA"]))
893 $trustData = unserialize($productData[
"PROVIDER_DATA"], [
'allowed_classes' =>
false]);
906 foreach ($bundle as $bundleItem)
908 $bundleItemData = $bundleItem->getFields()->getValues();
928 if(is_array($containerErrors) && !empty($containerErrors))
932 if(
$error->getMessage() !== $existError->getMessage())
950 $this->delegate->finalActions();
969 if ($basket && !$basket->getFUserId(
true))
974 if ($order &&
$order->getUserId())
995 empty($product[
'PRICE'])
997 empty($product[
'BASE_PRICE'])
999 empty($product[
'VAT_RATE'])
1002 $product[
'VAT_INCLUDED'] = $vatIncludedFromFirstItem;
1007 $vatIncludedFrom = $product[
'VAT_INCLUDED'];
1008 $price = (float)$product[
'PRICE'];
1009 $basePrice = (float)$product[
'BASE_PRICE'];
1011 if ($vatIncludedFrom ===
'Y' && $vatIncludedFromFirstItem ===
'N')
1013 $calculator = new \Bitrix\Sale\Tax\VatCalculator($product[
'VAT_RATE']);
1014 $price = $calculator->allocate($product[
'PRICE']);
1015 $basePrice = $calculator->allocate($product[
'BASE_PRICE']);
1018 $product[
'PRICE'] = $price;
1019 $product[
'BASE_PRICE'] = $basePrice;
1022 $product[
'VAT_INCLUDED'] = $vatIncludedFromFirstItem;