Как программно удалить заказ в 1С-Битрикс на D7: полный гайд и частые ошибки

26.09.20255 мин чтения
Макар Кучеренко
Python-разработчикМакар Кучеренко

Работа с заказами в интернет-магазинах на 1С-Битрикс часто преподносит сюрпризы начинающим разработчикам. Одна из самых частых задач — написать скрипт для массовой очистки старых или тестовых заказов.

Казалось бы, достаточно вызвать один метод. На старом ядре это делали через CSaleOrder::Delete($id). На новом ядре D7 попытка выполнить Sale\Order::delete($id) часто заканчивается выбросом Exception и ошибкой "У заказа есть резервы" или "Заказ оплачен".

Дело в строгой архитектуре: заказ в D7 — это контейнер. Чтобы удалить контейнер, нужно сначала корректно разобрать его содержимое.


Почему заказ нельзя удалить «в лоб»

В D7 сущность Заказа (Bitrix\Sale\Order) неразрывно связана с коллекциями (Collections):

  1. Коллекция оплат (PaymentCollection) — содержит транзакции.
  2. Коллекция отгрузок (ShipmentCollection) — отвечает за логистику и резервирование остатков на складах.
  3. Корзина (Basket) — товары внутри заказа.

Если хотя бы одна оплата проведена (статус «Оплачено») или товар зарезервирован/отгружен, ядро не даст удалить объект верхнего уровня. Это защита от нарушения финансовой и складской консистентности.

Алгоритм безопасного удаления состоит из строго последовательных шагов:

  1. Загрузить объект заказа.
  2. Перебрать все оплаты и отметить их как «возвращенные» (отменить оплату).
  3. Перебрать все отгрузки, снять признак DEDUCTED (отгружено) и отменить резервы.
  4. Сохранить изменения в коллекциях.
  5. Вызвать метод удаления самого заказа.

Рабочий пример кода на D7

Ниже представлен универсальный скрипт для удаления массива заказов. Его можно вызывать через агентов, крон-скрипты или CLI.

use Bitrix\Main\Loader;
use Bitrix\Sale;

// Проверяем подключение модуля интернет-магазина
if (!Loader::includeModule('sale')) {
    die("Модуль sale не установлен");
}

// Массив ID заказов, которые нужно удалить
$orderIds = [1001, 1002, 1003]; 
$logFile = $_SERVER["DOCUMENT_ROOT"] . "/local/logs/order_delete.log";

foreach ($orderIds as $orderId) {
    // 1. Загружаем объект заказа
    $order = Sale\Order::load($orderId);
    
    if (!$order) {
        file_put_contents($logFile, "[$orderId] Заказ не найден\n", FILE_APPEND);
        continue;
    }

    try {
        // 2. Отменяем оплаты
        $paymentCollection = $order->getPaymentCollection();
        foreach ($paymentCollection as $payment) {
            if ($payment->isPaid()) {
                // Ставим флаг возврата
                $payment->setReturn("Y"); 
            }
        }

        // 3. Отменяем отгрузки и системную отгрузку (System shipment)
        $shipmentCollection = $order->getShipmentCollection();
        foreach ($shipmentCollection as $shipment) {
            if (!$shipment->isSystem()) {
                if ($shipment->isShipped()) {
                    // Снимаем флаг отгрузки (возвращает остатки)
                    $shipment->setField("DEDUCTED", "N"); 
                }
                // На всякий случай снимаем резервирование
                $shipment->setField("RESERVED", "N");
            }
        }

        // 4. Обязательно сохраняем сброшенные статусы до удаления!
        $saveResult = $order->save();
        if (!$saveResult->isSuccess()) {
            throw new \Exception(implode(", ", $saveResult->getErrorMessages()));
        }

        // 5. Удаляем сам заказ
        $deleteResult = Sale\Order::delete($orderId);
        
        if ($deleteResult->isSuccess()) {
            file_put_contents($logFile, "[$orderId] Успешно удален\n", FILE_APPEND);
        } else {
            throw new \Exception(implode(", ", $deleteResult->getErrorMessages()));
        }

    } catch (\Exception $e) {
        file_put_contents($logFile, "[$orderId] Ошибка: " . $e->getMessage() . "\n", FILE_APPEND);
    }
}

Альтернатива: отмена, а не удаление

В 90% случаев интернет-магазинам вообще не нужно физически удалять заказы. Удаление нарушает сквозную нумерацию, портит статистику конверсий и мешает аналитике в дашбордах.

Правильный бизнес-сценарий — это отмена заказа.

$order = \Bitrix\Sale\Order::load($orderId);
$order->setField('CANCELED', 'Y');
$order->setField('REASON_CANCELED', 'Тестовый заказ / Ошибка клиента');
$order->save();

При смене статуса CANCELED на Y, Битрикс автоматически вернет зарезервированные товары на склад, если это настроено в параметрах модуля Интернет-магазин. Физическое удаление лучше оставить только для очистки базы разработчиками после этапа тестовой эксплуатации.


Резюме

Работа с коллекциями — основополагающий принцип D7. Запомните главное правило: никогда не меняйте данные таблиц напрямую через SQL запросы типа DELETE FROM b_sale_order. База Битрикса глубоко реляционна, и прямой SQL-запрос оставит после себя битые индексы, мусор в таблицах корзины (b_sale_basket) и расхождение в складских остатках (b_catalog_store_product).

Нужна помощь с сопровождением и доработкой интернет-магазина на 1С-Битрикс? Разработчики NBM-IT пишут чистый код на D7, интегрируют сложные API и оптимизируют highload-проекты. Оставьте заявку на технический аудит.

Бесплатный SEO-аудит вашего сайта

Оставьте заявку, и наши специалисты найдут точки роста поискового трафика.

Вам также может быть интересно

Оставьте свои контакты — мы перезвоним, разберёмся в задаче и предложим оптимальный путь. За плечами более 350 проектов, каждый из которых мы запускали с индивидуального подхода. Гарантируем экспертную консультацию в рабочее время.