How to programmatically delete an order in 1C-Bitrix on D7: complete guide and common mistakes
Working with orders in online stores using 1C-Bitrix often brings surprises to novice developers. One of the most common tasks is to write a script to bulk clean up old or test orders.
It would seem that it is enough to call one method. On the old kernel this was done via CSaleOrder::Delete($id). On the new D7 core, an attempt to execute Sale\Order::delete($id) often ends with an Exception and an error "The order has reserves" or "Order paid".
It's a matter of strict architecture: an order in D7 is a container. To remove a container, you must first correctly disassemble its contents.
Why can’t an order be deleted directly?
In D7 the essence of the Order (Bitrix\Sale\Order) is inextricably linked with Collections:
- Collection of payments (
PaymentCollection) - contains transactions. - Collection of shipments (
ShipmentCollection) - is responsible for logistics and reserving balances in warehouses. - Cart (
Basket) — goods inside the order.
If at least one payment has been made (status “Paid”) or the item has been reserved/shipped, the kernel will not allow the top-level object to be deleted. This is protection against violation of financial and warehouse consistency.
The secure removal algorithm consists of strictly sequential steps:
- Upload order object.
- Go through all payments and mark them as “returned” (cancel payment).
- Go through all shipments, remove the flag
DEDUCTED(shipped) and cancel reserves. - Save changes to collections.
- Call the method for deleting the order itself.
Working code example on D7
Below is a universal script for deleting an array of orders. It can be invoked through agents, cron scripts, or the 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);
}
}Alternative: Cancel rather than delete
In 90% of cases, online stores no need to physically delete orders at all. Deletion disrupts continuous numbering, spoils conversion statistics and interferes with analytics in dashboards.
The correct business scenario is order cancellation.
$order = \Bitrix\Sale\Order::load($orderId);
$order->setField('CANCELED', 'Y');
$order->setField('REASON_CANCELED', 'Тестовый заказ / Ошибка клиента');
$order->save();When changing status CANCELED on Y, Bitrix automatically will return reserved goods to the warehouse if this is configured in the parameters of the Online Store module. It is better to leave physical deletion only for cleaning the database by developers after the test operation stage.
Summary
Working with collections is a fundamental principle of D7. Remember the main rule: never change table data directly through SQL queries like DELETE FROM b_sale_order. The Bitrix database is deeply relational, and a direct SQL query will leave behind broken indexes and garbage in the bucket tables (b_sale_basket) and discrepancies in warehouse balances (b_catalog_store_product).
Need help with support and improvement of the online store on 1C-Bitrix? NBM-IT developers write clean code in D7, integrate complex APIs and optimize highload projects. Leave a request for a technical audit.
Free SEO audit of your website
Leave a request and our specialists will find areas of search traffic growth.
