#резервирование

Советы с тегом "резервирование"

1 совет

Резерв корзины в Sale — не складской документ

Резерв корзины в Sale — не складской документ

Проблема/контекст

Резерв позиции заказа и складской резерв — разные сущности. \Bitrix\Catalog\StoreProductTable::QUANTITY_RESERVED показывает итоговое число по паре товар-склад, а снимается оно документом TYPE_UNDO_RESERVE. Строку резерва конкретной позиции корзины ведёт модуль sale — через \Bitrix\Sale\Reservation\BasketReservationService. Правка складского остатка мимо него оставит заказ и склад рассогласованными.

Решение с кодом

Сервис зарегистрирован в sale/.settings.php под ключом sale.basketReservation, зависимость BasketReservationHistoryService собирается в constructorParams. Есть и статический ярлык BasketReservationService::getInstance().

Данные лежат в b_sale_basket_reservation (BasketReservationTable): QUANTITY, DATE_RESERVE, DATE_RESERVE_END, BASKET_ID — обязательные, плюс STORE_ID и RESERVED_BY. Все три метода записи возвращают Bitrix\Main\Result:

        <?php
declare(strict_types=1);

use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
use Bitrix\Main\Type\DateTime;
use Bitrix\Sale\Reservation\BasketReservationService;

Loader::includeModule('sale');

/** @var BasketReservationService $service */
$service = ServiceLocator::getInstance()->get('sale.basketReservation');

$result = $service->add([
    'BASKET_ID' => $basketId,
    'STORE_ID' => $storeId,
    'QUANTITY' => 3.0,
    'DATE_RESERVE' => new DateTime(),
    'DATE_RESERVE_END' => (new DateTime())->add('7 days'),
]);

if ($result->isSuccess())
{
    $service->update((int)$result->getId(), ['QUANTITY' => 5.0]);
}

    

Ключевое отличие от прямой работы с таблицей — история. add() после успешной вставки вызывает addByReservation(), update(int $id, array $fields)updateByReservation(), delete(int $id)deleteByReservation(). История в BasketReservationHistoryTable хранит не снимки, а дельты: при изменении количества с 80 на 90 добавляется строка на 10. По этим дельтам и их датам считается, сколько позиция реально может списать со склада:

        <?php
declare(strict_types=1);

// $ret[$productId][$storeId] => доступное количество
$byOrder = $service->getAvailableCountForOrder($orderId);

$available = $service->getAvailableCountForBasketItem($basketId, $storeId); // float

    

Логика такая: если первым зарезервировали 80 из 100, а вторым — 40, то первая сделка спишет 80, вторая — 20. Порядок резервирования учитывается.

В прикладном коде заказа отдельный вызов сервиса обычно не нужен: ReserveQuantity::addInternal(), updateInternal() и ReserveQuantityCollection::deleteInternal() уже ходят в sale.basketReservation, поэтому $basketItem->getReserveQuantityCollection() пишет историю автоматически. Сервис нужен для диагностики, миграций и внешних интеграций.

Условие резерва и срок хранения приходят из ReservationSettingsService (ключ sale.reservation.settings) — метод get() читает опции sale/product_reserve_condition и sale/product_reserve_clear_period, собирает ReservationSettings и рассылает ReservationSettingsBuildEvent с именем OnReservationSettingsBuild. В обработчике можно вызвать $event->getSettings()->setReserveCondition(ReserveCondition::ON_CREATE) и включить автоматический резерв, не меняя настройки модуля. Допустимые значения — ON_CREATE, ON_PAY, ON_FULL_PAY, ON_ALLOW_DELIVERY, ON_SHIP; setReserveCondition() валидирует их и бросает SystemException.

Итог

Резерв позиции — это строка b_sale_basket_reservation плюс история дельт, а не складской документ. Меняйте её через sale.basketReservation, а условие резервирования переопределяйте событием OnReservationSettingsBuild.

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти