Как правильно получить и проверить статус Заказа клиента при проведении в УТ 11.4

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке кастомной логики в конфигурации «Управление торговлей», редакция 11.4 (а также в ERP 2 и Комплексной автоматизации 2) программисты часто сталкиваются с необходимостью проверять реквизиты проводимого документа. Типичный сценарий: если заказ клиента находится в определенном статусе (например, «На согласовании» или «К обеспечению»), то система должна выполнить некоторые действия — проверить остатки, изменить дополнительные реквизиты или сделать записи в регистры.

Однако попытка получить статус документа через классический SQL-запрос в обработчике проведения или в подписке на событие часто приводит к неожиданному результату — запрос возвращает пустое или устаревшее значение. Разберем подробно, почему так происходит, как правильно обращаться к статусам и какие критические ошибки производительности часто допускают разработчики в подобных алгоритмах.

Почему запрос к базе данных возвращает пустой или старый статус?

Посмотрим на код, который изначально написал автор вопроса. В обработчике события проведения ОбработкаПроведения он пытается выполнить запрос к таблице документов:


Запрос2 = Новый Запрос;
Запрос2.Текст = 
    "ВЫБРАТЬ
    |	ЗаказКлиента.Статус КАК Статусы
    |ИЗ
    |	Документ.ЗаказКлиента КАК ЗаказКлиента
    |ГДЕ
    |	ЗаказКлиента.Ссылка = &ТекущийДокумент";
Запрос2.УстановитьПараметр("ТекущийДокумент", Источник.Ссылка);
Результат2 = Запрос2.Выполнить();

Почему этот запрос не возвращает актуальное значение статуса, выбранное пользователем на форме перед проведением?

Дело в особенностях работы транзакционной модели платформы 1С:Предприятие. Когда пользователь нажимает кнопку «Провести», данные на форме записываются во временный объект в оперативной памяти (переменная Источник или ЭтотОбъект). Процедура ОбработкаПроведения выполняется внутри транзакции, но до того, как новые данные будут окончательно записаны в таблицы базы данных на сервере СУБД.

Если мы выполняем запрос к таблице Документ.ЗаказКлиента по ссылке документа:

  1. Если документ проводится впервые (новый документ), в базе данных физически еще нет записи с такой ссылкой. Запрос вернет пустой результат (пустую выборку).
  2. Если документ перепроводится (уже существовал в базе), запрос вернет те данные, которые были записаны в базу данных до начала транзакции проведения. То есть вы получите старый статус заказа, который был у него до того, как пользователь его изменил и нажал кнопку проведения.

Правильное решение: Для получения любых реквизитов проводимого документа в обработчиках проведения или в подписках на события (например, ПередЗаписью, ПриЗаписи, ОбработкаПроведения) нужно обращаться напрямую к свойствам объекта-источника, который уже находится в оперативной памяти. Никаких запросов к самому документу делать не нужно!

Мы можем получить статус простым обращением к свойству:

ТекущийСтатус = Источник.Статус; ---

Как правильно использовать статус заказа в условии «Если»

Вторая распространенная ошибка начинающих разработчиков — попытка сравнить статус с текстовой строкой. В сообщении на форуме был предложен следующий ошибочный вариант кода:


// ОШИБОЧНЫЙ КОД! Сравнение со строкой всегда будет возвращать Ложь
Если Выборка.Статус = "Нужный тебе статус" Тогда
    // Действие...
КонецЕсли;

В платформе 1С реквизит Статус документа «Заказ клиента» имеет тип ПеречислениеСсылка.СтатусыЗаказовКлиентов. Ссылочные типы данных (включая перечисления) нельзя напрямую сравнивать с примитивным строковым типом. Такое сравнение всегда будет возвращать Ложь (False), и условие никогда не выполнится.

Рассмотрим, как правильно написать условие проверки статуса с использованием метаданных перечисления:


// Правильный вариант сравнения с перечислением
Если Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.НеСогласован Тогда
    // Действие, если статус "Не согласован"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.КОбеспечению Тогда
    // Действие, если статус "К обеспечению"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.КОтгрузке Тогда
    // Действие, если статус "К отгрузке"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.Закрыт Тогда
    // Действие, если статус "Закрыт"
КонецЕсли;

Важное примечание для конфигурации УТ 11.4: Состав доступных статусов в системе может динамически меняться в зависимости от функциональных опций. Перейдите в раздел НСИ и администрирование -> Настройка НСИ и разделов -> Продажи -> Оптовые продажи -> Использование заказов. В зависимости от выбранной настройки («Заказ как счет», «Заказ только со склада» или «Заказ со склада и под заказ») поведение статусов и их состав в документах могут различаться.

---

В чем разница между «Статусом» и «Состоянием» заказа?

Разработчики часто путают эти два понятия в конфигурациях на базе ERP-платформы (УТ 11, ERP 2, КА 2):

Если вашей бизнес-логике требуется проверять именно комплексное состояние заказа, вам придется обратиться к регистру сведений СостоянияЗаказовКлиентов с отбором по вашему документу-заказу.

---

Разбор и исправление критических архитектурных ошибок в коде автора

Проанализируем исходный код из первого сообщения темы. Помимо проблемы со статусом, этот алгоритм содержит грубейшие нарушения стандартов разработки фирмы «1С», которые гарантированно приведут к падению производительности системы и зависанию базы данных (Deadlocks) при многопользовательской работе.

Архитектурная ошибка №1: Запросы в цикле (Антипаттерн)

Посмотрим на этот фрагмент кода:


Пока Выборка.Следующий() Цикл
    ИмяТовара = Выборка.Ном;
    Запрос1 = Новый Запрос;
    Запрос1.Текст = "ВЫБРАТЬ ... ИЗ РегистрНакопления.СвободныеОстатки.Остатки ...";
    // выполнение запроса ...
КонецЦикла;

В чем проблема: Выполнение SQL-запросов внутри цикла обхода строк — одна из самых серьезных ошибок в 1С. Если в табличной части проводимого документа будет 150 строк товаров, система сделает 150 отдельных запросов к СУБД. Это создает огромную паразитную нагрузку на сервер.

Решение: Всегда извлекайте данные одним пакетным запросом. Сначала соберите все товары из табличной части в массив или временную таблицу, передайте этот массив в качестве параметра в один-единственный запрос и получите остатки сразу по всем товарам за один шаг.

Архитектурная ошибка №2: Поиск элемента по наименованию при наличии ссылки

Строка кода автора:


Товар = Справочники.Номенклатура.НайтиПоНаименованию(ИмяТовара).ПолучитьОбъект();

В чем проблема: Переменная ИмяТовара получается из поля запроса Выборка.Ном, которое уже содержит прямую ссылку (тип СправочникСсылка.Номенклатура). Поиск элемента, у которого у вас уже есть ссылка, по его строковому представлению — это абсолютно бессмысленная операция, которая сильно замедляет работу системы и может привести к ошибкам, если в базе есть дубли товаров с одинаковыми наименованиями.

Решение: Получайте объект напрямую из ссылки:

ТоварОбъект = Выборка.Ном.ПолучитьОбъект();

Архитектурная ошибка №3: Запись элементов справочника в транзакции проведения документа

Самая опасная ошибка в коде автора:


Товар.Записать(); // Запись элемента справочника Номенклатура при проведении заказа!

В чем проблема: Проведение любого документа всегда выполняется внутри неделимой транзакции базы данных. Блокировка и модификация элемента справочника Номенклатура (особенно если это популярный товар, который одновременно продают несколько менеджеров) приведет к тому, что транзакция одного пользователя заблокирует карточку товара для всех остальных.

Другие пользователи, пытающиеся выписать или провести документы с этим же товаром, будут получать ошибку «Конфликт блокировок» или система будет зависать в ожидании освобождения ресурса. Более того, запись тяжелых объектов (справочников) внутри транзакции проведения документов категорически запрещена стандартами разработки 1С.

Решение: Не храните оперативную информацию (например, текущие свободные остатки) в дополнительных реквизитах справочника Номенклатура. Справочники предназначены для хранения условно-постоянной нормативно-справочной информации (НСИ). Для оперативного хранения количественных показателей используйте Регистры сведений или Регистры накопления.

---

Как должен выглядеть правильный, оптимизированный код

Проанализируем задачу автора: ему нужно при проведении заказа клиента проверить свободные остатки товаров на складах и записать информацию. Давайте перепишем этот код в соответствии со стандартами разработки 1С. Мы избавимся от запросов в цикле, уберем лишние поиски по наименованию и оптимизируем работу со свойствами.

Рассмотрим пример качественной реализации процедуры проведения:


Процедура ПродажаТовараОбработкаПроведения(Источник, Отказ, РежимПроведения) Экспорт
    
    // 1. Проверяем статус заказа напрямую из объекта Источник (без запросов к базе)
    Если Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.НеСогласован Тогда
        // Если заказ не согласован, не выполняем никаких действий по распределению
        Возврат;
    КонецЕсли;
    
    // 2. Выгружаем уникальные товары из табличной части проводимого документа
    ТаблицаТоваров = Источник.Товары.ВыгрузитьКолонку("Номенклатура");
    
    // Очистим массив от пустых ссылок и дублей
    МассивТоваров = Новый Массив;
    Для Каждого СсылкаНаТовар Из ТаблицаТоваров Цикл
        Если ЗначениеЗаполнено(СсылкаНаТовар) И МассивТоваров.Найти(СсылкаНаТовар) = Неопределено Тогда
            МассивТоваров.Добавить(СсылкаНаТовар);
        КонецЕсли;
    КонецЦикла;
    
     Если МассивТоваров.Количество() = 0 Тогда
         Возврат; // В документе нет товаров, обрабатывать нечего
     КонецЕсли;
    
    // 3. Выполняем ОДИН запрос для получения остатков по ВСЕМ товарам из списка
    ЗапросОстатков = Новый Запрос;
    ЗапросОстатков.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Ссылка КАК ТоварСсылка,
        |	ЕСТЬNULL(СвободныеОстатки.ВНаличииОстаток, 0) - ЕСТЬNULL(СвободныеОстатки.ВРезервеСоСкладаОстаток, 0) КАК ДоступныйОстаток
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки.Остатки(, Номенклатура В (&МассивТоваров)) КАК СвободныеОстатки
        |		ПО Номенклатура.Ссылка = СвободныеОстатки.Номенклатура
        |ГДЕ
        |	Номенклатура.Ссылка В(&МассивТоваров)";
        
    ЗапросОстатков.УстановитьПараметр("МассивТоваров", МассивТоваров);
    РезультатЗапроса = ЗапросОстатков.Выполнить();
    ВыборкаОстатков = РезультатЗапроса.Выбрать();
    
    // Имя доп. реквизита, который мы ищем в ПВХ
    ИмяСвойства = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("КоличествоТоваров");
    Если Не ЗначениеЗаполнено(ИмяСвойства) Тогда
        ВызватьИсключение "Не найден дополнительный реквизит ""КоличествоТоваров"" в системе!";
    КонецЕсли;
    
    // 4. Обходим результаты запроса и обновляем данные
    Пока ВыборкаОстатков.Следующий() Цикл
        
        // Получаем объект номенклатуры напрямую по ссылке без поиска по имени!
        ТоварОбъект = ВыборкаОстатков.ТоварСсылка.ПолучитьОбъект();
        ТоварОбъект.Заблокировать();
        
        // Работаем с табличной частью дополнительных реквизитов объекта
        ПараметрыОтбора = Новый Структура("Свойство", ИмяСвойства);
        НайденныеСтроки = ТоварОбъект.ДополнительныеРеквизиты.НайтиСтроки(ПараметрыОтбора);
        
        Если НайденныеСтроки.Количество() = 0 Тогда
            НоваяСтрока = ТоварОбъект.ДополнительныеРеквизиты.Добавить();
            НоваяСтрока.Свойство = ИмяСвойства;
            НоваяСтрока.Значение = ВыборкаОстатков.ДоступныйОстаток;
        Иначе
            НайденныеСтроки[0].Значение = ВыборкаОстатков.ДоступныйОстаток;
        КонецЕсли;
        
        // Внимание! Этот метод записи справочника в транзакции проведения документа оставлен 
        // исключительно для демонстрации исправления исходного кода автора. 
        // На рабочих базах настоятельно рекомендуется перенести хранение остатков в регистр сведений!
        ТоварОбъект.Записать();
        
    КонецЦикла;

КонецПроцедуры

Подведем итоги. При работе в транзакциях проведения всегда используйте данные из параметра Источник, так как именно там хранятся актуальные, еще не записанные в базу данных изменения документа. Сравнивайте ссылочные типы статусов только с аналогичными элементами перечисления метаданных. Не пишите запросы в циклах и избегайте необоснованных блокировок справочников при проведении документов.

← На главную