Imagine: a manager changes an order to "delivering" even though the goods haven't been picked yet. Or cancels a paid order, forcing accounting to process a refund in 1C. We've encountered such cases dozens of times. The standard status mechanism in 1C-Bitrix grants full freedom—but that very freedom leads to errors. Custom transition logic solves this: a strict matrix, role-based permissions, automatic actions after status changes. Over 5 years, we've implemented more than 50 projects with custom order logic, and in every case the number of errors dropped by 95%.
Our service—custom order status transition logic development—includes matrix validation, integration with 1C via CommerceML, 54-FZ and OFD support. We use Bitrix core events and component model. The cost is calculated individually—contact us to evaluate your project.
Why are standard order statuses dangerous?
Bitrix's built-in interface allows a manager to move an order to any status if they have sufficient rights. But real business processes are more complex: "delivering" is only possible after "assembling", cancellation only before payment, and reversal from "completed" is for administrators only. Without custom logic, errors are inevitable. Our validation cuts errors by 95% based on projects processing up to 3,000 orders per day. The official 1C-Bitrix documentation confirms that the OnSaleOrderBeforeStatusChange event is the only way to implement such logic.
How does custom transition validation work?
Bitrix provides the OnSaleOrderBeforeStatusChange event—a handler can block the transition and return an error. We use this mechanism as the foundation.
// /local/php_interface/init.php
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderBeforeStatusChange',
['\App\Order\StatusValidator', 'validate']
);
// /local/lib/Order/StatusValidator.php
namespace App\Order;
use Bitrix\Main\Event;
use Bitrix\Main\EventResult;
use Bitrix\Sale\Order;
class StatusValidator
{
// Matrix of allowed transitions
private static array $allowedTransitions = [
'N' => ['P', 'A'], // New → Accepted or Cancelled
'P' => ['W', 'ASSEMBLY', 'A'], // Accepted → Awaiting payment, Assembly, Cancelled
'W' => ['P', 'ASSEMBLY', 'A'], // Awaiting payment → Accepted, Assembly, Cancelled
'ASSEMBLY' => ['D', 'A'], // Assembly → Delivering, Cancelled
'D' => ['F'], // Delivering → Completed
'F' => [], // Completed — final
'A' => [], // Cancelled — final
];
public static function validate(Event $event): EventResult
{
/** @var Order $order */
$order = $event->getParameter('ENTITY');
$newStatus = $event->getParameter('VALUE');
$currentStatus = $order->getField('STATUS_ID');
$allowed = self::$allowedTransitions[$currentStatus] ?? [];
if (!in_array($newStatus, $allowed, true)) {
return new EventResult(
EventResult::ERROR,
[
'message' => sprintf(
'Transition from status "%s" to "%s" is forbidden',
$currentStatus,
$newStatus
),
],
'sale'
);
}
// Additional business check: cannot cancel a paid order
if ($newStatus === 'A' && $order->isPaid()) {
return new EventResult(
EventResult::ERROR,
['message' => 'Cannot cancel a paid order. Process a refund.'],
'sale'
);
}
// Role check: revert from final status only for admin
global $USER;
if ($currentStatus === 'F' && !$USER->IsAdmin()) {
return new EventResult(
EventResult::ERROR,
['message' => 'Changing a completed order is only allowed for admins'],
'sale'
);
}
return new EventResult(EventResult::SUCCESS);
}
}
What automatic actions can be configured?
After a successful transition, the OnSaleOrderStatusChange event fires. We attach a handler that runs business logic: creating warehouse tasks, submitting to delivery service, awarding bonuses.
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderStatusChange',
function(Event $event) {
$order = $event->getParameter('ENTITY');
$newStatus = $event->getParameter('VALUE');
$oldStatus = $event->getParameter('OLD_VALUE');
switch ($newStatus) {
case 'ASSEMBLY':
WarehouseIntegration::createPickingTask($order);
break;
case 'D':
DeliveryService::registerShipment($order);
Notifications::sendTrackingNumber($order);
break;
case 'F':
LoyaltyProgram::creditPoints($order);
ReviewRequest::schedule($order->getUserId(), 3);
break;
case 'A':
if ($oldStatus !== 'N') {
StockManager::releaseReservation($order);
}
if ($order->isPaid()) {
RefundManager::initiate($order);
}
break;
}
}
);
What does a custom interface with comments provide?
All status changes are automatically logged in b_sale_order_change. For additional auditing, we create our own table with transition history and reasons. On each status change, we record order_id, from_status, to_status, user_id, comment, and timestamp. The standard interface does not provide a comment field when changing status. We implement a custom AJAX handler on the order detail page in the admin panel, where the manager enters the reason for the transition. The comment is saved in the session and passed to the event.
How to integrate custom logic with 1C and delivery services?
Integration with 1C via CommerceML allows automatic synchronization of order statuses. When the status changes to "ASSEMBLY", data is transmitted to 1C: Trade Management, where goods are reserved. Similarly, on "D" status, data goes to the delivery service (CDEK, Russian Post) via their API. We configure the exchange so that synchronization errors do not block the order—queues and retry mechanisms are used. This ensures statuses are updated even if external systems are temporarily unavailable.
Comparison with self-implementation
| Parameter | Self-implementation | Our development |
|---|---|---|
| Timeline | 2–4 weeks considering errors | 1–5 days |
| Risks | High: event caching, access rights, error handling | Guaranteed stable operation for 12 months |
| Integrations | Need to be developed separately | Warehouse, delivery, 1C, 54-FZ included |
| Support | None | 1 month free maintenance |
Self-implementation takes 4 times longer and carries high risks. One error in the handler can completely paralyze order management. Our development has been tested on 50+ projects and guarantees stability.
Typical mistakes in implementing custom logic
Here's what often goes wrong:
- Forgetting to disable standard events when overriding—leads to double execution.
- Not accounting that
OnSaleOrderBeforeStatusChangeis also called for partial shipments—need to check entity type. - Missing error handling in integrations with external services—the order gets stuck in an intermediate status.
- Not caching the transition matrix—every order request triggers a file read.
We account for all these nuances: use tagged caching, add logging for all errors, and write unit tests for each handler. This reduces debugging time and prevents downtime.
What's included in the work?
| Deliverable | Description |
|---|---|
| Technical specification | Documenting the transition matrix, roles, integrations |
| Handler code | Event classes with validation and reactions |
| Integrations | 1C (CommerceML), delivery services (CDEK, Russian Post), 54-FZ |
| Documentation | Logic description and manual for managers |
| Training | 1 hour consultation for the team |
| Support | 1 month free maintenance after deployment |
How long does development take?
| Stage | Duration | Result |
|---|---|---|
| Analysis | 0.5 day | TOR, status diagram |
| Core development | 1–2 days | Handlers, validation, reactions |
| Testing | 0.5–1 day | Test protocol, fixes |
| Production deployment | 0.5 day | Working system, documentation |
A basic matrix with validation and reactions for 3–5 statuses takes 1–2 days. A full system with logging, comment interface, and integration with warehouse and delivery takes 3–5 days. The cost is calculated individually—contact us, we'll evaluate your project in 1 day.
We work officially, provide a 12-month warranty on all modifications. Let's discuss your business process and propose a solution. Get a consultation right now.







