Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ошибочное резервирование товара

Ошибочное резервирование товара

Пример решения возникшей проблемы с помощью логирования в Битриксе

Evgeny E. Neverov

March 02, 2023
Tweet

More Decks by Evgeny E. Neverov

Other Decks in Programming

Transcript

  1. Проблема На сайте клиента можно покупать коробы с обувью. Один

    короб - одно предложение. Каждый короб продается в единственном экземпляре (доступное кол-во = 1) Резервирование товаров происходит при оформлении заказа После обновления Битрикса в разных заказах стали появляться одни и те же коробы.
  2. Поиск причины Изучаем заказы с одинаковыми товарами. Подробные данные и

    даты смотрим в таблицах • b_sale_order • b_sale_basket • b_sale_basket_reservation Оба заказа зарезервировали товар. Но у одного из заказов время резервирования не совпадало ни со временем оформления заказа, ни со временем добавления товара в корзину. Чтобы выяснить, когда и кем товар резервируется логируем таблицу резервов
  3. Код логгера use Techdir\PhpInterface\Event\Regular; use Bitrix\Main\Diag; use Bitrix\Main\Entity; class Reservation

    extends Regular { public static function getHandlers() { return [ [ "module" => "sale", 'event' => '\Bitrix\Sale\Reservation\Internals\BasketReservation::OnAfterAdd', 'method' => 'logReservation' ], ]; } public static function logReservation(Entity\Event $event) { $path = __DIR__ . '/log-reservation.txt'; $message = (new Diag\LogFormatter())->format("{args}\n{trace}\n{delimiter}\n", [ 'args' => print_r($event->getParameter('fields'), true), 'trace' => Diag\Helper::getBackTrace(20, DEBUG_BACKTRACE_IGNORE_ARGS, 2), ]); (new Diag\FileLogger($path))->info($message); } }
  4. Разбираем логи ... #7: Bitrix\Sale\Reservation\BasketReservationService->add /home/bitrix/www/bitrix/modules/sale/lib/reservequantity.php:303 #8: Bitrix\Sale\ReserveQuantity->addInternal /home/bitrix/www/bitrix/modules/sale/lib/reservequantity.php:247 #9:

    Bitrix\Sale\ReserveQuantity->save /home/bitrix/www/bitrix/modules/sale/lib/reservequantitycollection.php #10: Bitrix\Sale\ReserveQuantityCollection->save /home/bitrix/www/bitrix/modules/sale/lib/basketitem.php:62 #11: Bitrix\Sale\BasketItem->save /home/bitrix/www/bitrix/modules/sale/lib/basketbase.php:489 #12: Bitrix\Sale\BasketBase->save /home/bitrix/www/bitrix/modules/sale/lib/basket.php:228 #13: Bitrix\Sale\Basket->save /home/bitrix/www/local/lib/Rieker/Util/BasketHelper.php:76 #14: Rieker\Util\BasketHelper::clearBasket /home/bitrix/www/local/lib/Rieker/Util/OrderHelper.php:35 #15: Rieker\Util\OrderHelper::getPublicData /home/bitrix/www/local/components/rieker/order/class.php:196 #16: OrderComponent->executeComponent /home/bitrix/www/bitrix/modules/main/classes/general/component.php:660 #17: CBitrixComponent->includeComponent /home/bitrix/www/bitrix/modules/main/classes/general/main.php:1055 #18: CAllMain->IncludeComponent /home/bitrix/www/basket/index.php:26 В логе видим, что: • Резервирование происходит на странице корзины • Удаляются недоступные товары и корзина сохраняется • Резервы сохраняются вместе с корзиной
  5. Смотрим в коде class BasketHelper { // ... public static

    function clearBasket(BasketBase &$basket, $save = false) { if (!SiteHelper::isSewnContextSite()) { $changed = false; foreach ($basket as $basketItem) { if (!$basketItem->canBuy()) { $basketItem->delete(); $changed = true; } } if ($changed && $save) { $basket->save(); } } } // ... } Здесь видно, что при загрузке корзины из нее удаляются недоступные товары. Если корзина была изменена, она сохраняется. И при этом создаются резервы. • Таким образом мы понимаем, как нам повторить ошибку: добавляем несколько товаров в корзину • Делаем часть товаров недоступными • Обновляем страницу корзины
  6. Отладка С помощью xdebug находим место, где резервы создаются $order

    = $this->order = Order::create(Context::getCurrent()->getSite(), $USER->GetID()); $basket = Basket::loadItemsForFUser(Fuser::getId(), Context::getCurrent()->getSite()); // резервы создаются при добавлении корзины к заказу $order->setBasket($basket); // ... // внутри getPublicData вызывается BasketHelper->clearBasket() // и созданные резервы сохраняются вместе с корзиной $arResult += OrderHelper::getPublicData($order, $page === 'edit');
  7. Решение Чистим и сохраняем корзину, пока к ней не привязан

    заказ $order = $this->order = Order::create(Context::getCurrent()->getSite(), $USER->GetID()); $basket = Basket::loadItemsForFUser(Fuser::getId(), Context::getCurrent()->getSite()); $basket->refresh(); BasketHelper::clearBasket($basket, true); $order->setBasket($basket); // ... // из getPublicData убираем вызов BasketHelper::clearBasket() $arResult += OrderHelper::getPublicData($order, $page === 'edit');