При разработке кастомной логики в конфигурации «Управление торговлей», редакция 11.4 (а также в ERP 2 и Комплексной автоматизации 2) программисты часто сталкиваются с необходимостью проверять реквизиты проводимого документа. Типичный сценарий: если заказ клиента находится в определенном статусе (например, «На согласовании» или «К обеспечению»), то система должна выполнить некоторые действия — проверить остатки, изменить дополнительные реквизиты или сделать записи в регистры.
Однако попытка получить статус документа через классический SQL-запрос в обработчике проведения или в подписке на событие часто приводит к неожиданному результату — запрос возвращает пустое или устаревшее значение. Разберем подробно, почему так происходит, как правильно обращаться к статусам и какие критические ошибки производительности часто допускают разработчики в подобных алгоритмах.
Посмотрим на код, который изначально написал автор вопроса. В обработчике события проведения ОбработкаПроведения он пытается выполнить запрос к таблице документов:
Запрос2 = Новый Запрос;
Запрос2.Текст =
"ВЫБРАТЬ
| ЗаказКлиента.Статус КАК Статусы
|ИЗ
| Документ.ЗаказКлиента КАК ЗаказКлиента
|ГДЕ
| ЗаказКлиента.Ссылка = &ТекущийДокумент";
Запрос2.УстановитьПараметр("ТекущийДокумент", Источник.Ссылка);
Результат2 = Запрос2.Выполнить();
Почему этот запрос не возвращает актуальное значение статуса, выбранное пользователем на форме перед проведением?
Дело в особенностях работы транзакционной модели платформы 1С:Предприятие. Когда пользователь нажимает кнопку «Провести», данные на форме записываются во временный объект в оперативной памяти (переменная Источник или ЭтотОбъект). Процедура ОбработкаПроведения выполняется внутри транзакции, но до того, как новые данные будут окончательно записаны в таблицы базы данных на сервере СУБД.
Если мы выполняем запрос к таблице Документ.ЗаказКлиента по ссылке документа:
Правильное решение: Для получения любых реквизитов проводимого документа в обработчиках проведения или в подписках на события (например, ПередЗаписью, ПриЗаписи, ОбработкаПроведения) нужно обращаться напрямую к свойствам объекта-источника, который уже находится в оперативной памяти. Никаких запросов к самому документу делать не нужно!
Мы можем получить статус простым обращением к свойству:
ТекущийСтатус = Источник.Статус;
---
Вторая распространенная ошибка начинающих разработчиков — попытка сравнить статус с текстовой строкой. В сообщении на форуме был предложен следующий ошибочный вариант кода:
// ОШИБОЧНЫЙ КОД! Сравнение со строкой всегда будет возвращать Ложь
Если Выборка.Статус = "Нужный тебе статус" Тогда
// Действие...
КонецЕсли;
В платформе 1С реквизит Статус документа «Заказ клиента» имеет тип ПеречислениеСсылка.СтатусыЗаказовКлиентов. Ссылочные типы данных (включая перечисления) нельзя напрямую сравнивать с примитивным строковым типом. Такое сравнение всегда будет возвращать Ложь (False), и условие никогда не выполнится.
Рассмотрим, как правильно написать условие проверки статуса с использованием метаданных перечисления:
// Правильный вариант сравнения с перечислением
Если Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.НеСогласован Тогда
// Действие, если статус "Не согласован"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.КОбеспечению Тогда
// Действие, если статус "К обеспечению"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.КОтгрузке Тогда
// Действие, если статус "К отгрузке"
ИначеЕсли Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.Закрыт Тогда
// Действие, если статус "Закрыт"
КонецЕсли;
Важное примечание для конфигурации УТ 11.4: Состав доступных статусов в системе может динамически меняться в зависимости от функциональных опций. Перейдите в раздел НСИ и администрирование -> Настройка НСИ и разделов -> Продажи -> Оптовые продажи -> Использование заказов. В зависимости от выбранной настройки («Заказ как счет», «Заказ только со склада» или «Заказ со склада и под заказ») поведение статусов и их состав в документах могут различаться.
---
Разработчики часто путают эти два понятия в конфигурациях на базе ERP-платформы (УТ 11, ERP 2, КА 2):
ПеречислениеСсылка), который жестко определяет стадию работы с заказом. Его выбирает пользователь вручную или устанавливает бизнес-процесс.СостоянияЗаказовКлиентов.Если вашей бизнес-логике требуется проверять именно комплексное состояние заказа, вам придется обратиться к регистру сведений СостоянияЗаказовКлиентов с отбором по вашему документу-заказу.
---
Проанализируем исходный код из первого сообщения темы. Помимо проблемы со статусом, этот алгоритм содержит грубейшие нарушения стандартов разработки фирмы «1С», которые гарантированно приведут к падению производительности системы и зависанию базы данных (Deadlocks) при многопользовательской работе.
Посмотрим на этот фрагмент кода:
Пока Выборка.Следующий() Цикл
ИмяТовара = Выборка.Ном;
Запрос1 = Новый Запрос;
Запрос1.Текст = "ВЫБРАТЬ ... ИЗ РегистрНакопления.СвободныеОстатки.Остатки ...";
// выполнение запроса ...
КонецЦикла;
В чем проблема: Выполнение SQL-запросов внутри цикла обхода строк — одна из самых серьезных ошибок в 1С. Если в табличной части проводимого документа будет 150 строк товаров, система сделает 150 отдельных запросов к СУБД. Это создает огромную паразитную нагрузку на сервер.
Решение: Всегда извлекайте данные одним пакетным запросом. Сначала соберите все товары из табличной части в массив или временную таблицу, передайте этот массив в качестве параметра в один-единственный запрос и получите остатки сразу по всем товарам за один шаг.
Строка кода автора:
Товар = Справочники.Номенклатура.НайтиПоНаименованию(ИмяТовара).ПолучитьОбъект();
В чем проблема: Переменная ИмяТовара получается из поля запроса Выборка.Ном, которое уже содержит прямую ссылку (тип СправочникСсылка.Номенклатура). Поиск элемента, у которого у вас уже есть ссылка, по его строковому представлению — это абсолютно бессмысленная операция, которая сильно замедляет работу системы и может привести к ошибкам, если в базе есть дубли товаров с одинаковыми наименованиями.
Решение: Получайте объект напрямую из ссылки:
ТоварОбъект = Выборка.Ном.ПолучитьОбъект();
Самая опасная ошибка в коде автора:
Товар.Записать(); // Запись элемента справочника Номенклатура при проведении заказа!
В чем проблема: Проведение любого документа всегда выполняется внутри неделимой транзакции базы данных. Блокировка и модификация элемента справочника Номенклатура (особенно если это популярный товар, который одновременно продают несколько менеджеров) приведет к тому, что транзакция одного пользователя заблокирует карточку товара для всех остальных.
Другие пользователи, пытающиеся выписать или провести документы с этим же товаром, будут получать ошибку «Конфликт блокировок» или система будет зависать в ожидании освобождения ресурса. Более того, запись тяжелых объектов (справочников) внутри транзакции проведения документов категорически запрещена стандартами разработки 1С.
Решение: Не храните оперативную информацию (например, текущие свободные остатки) в дополнительных реквизитах справочника Номенклатура. Справочники предназначены для хранения условно-постоянной нормативно-справочной информации (НСИ). Для оперативного хранения количественных показателей используйте Регистры сведений или Регистры накопления.
---
Проанализируем задачу автора: ему нужно при проведении заказа клиента проверить свободные остатки товаров на складах и записать информацию. Давайте перепишем этот код в соответствии со стандартами разработки 1С. Мы избавимся от запросов в цикле, уберем лишние поиски по наименованию и оптимизируем работу со свойствами.
Рассмотрим пример качественной реализации процедуры проведения:
Процедура ПродажаТовараОбработкаПроведения(Источник, Отказ, РежимПроведения) Экспорт
// 1. Проверяем статус заказа напрямую из объекта Источник (без запросов к базе)
Если Источник.Статус = Перечисления.СтатусыЗаказовКлиентов.НеСогласован Тогда
// Если заказ не согласован, не выполняем никаких действий по распределению
Возврат;
КонецЕсли;
// 2. Выгружаем уникальные товары из табличной части проводимого документа
ТаблицаТоваров = Источник.Товары.ВыгрузитьКолонку("Номенклатура");
// Очистим массив от пустых ссылок и дублей
МассивТоваров = Новый Массив;
Для Каждого СсылкаНаТовар Из ТаблицаТоваров Цикл
Если ЗначениеЗаполнено(СсылкаНаТовар) И МассивТоваров.Найти(СсылкаНаТовар) = Неопределено Тогда
МассивТоваров.Добавить(СсылкаНаТовар);
КонецЕсли;
КонецЦикла;
Если МассивТоваров.Количество() = 0 Тогда
Возврат; // В документе нет товаров, обрабатывать нечего
КонецЕсли;
// 3. Выполняем ОДИН запрос для получения остатков по ВСЕМ товарам из списка
ЗапросОстатков = Новый Запрос;
ЗапросОстатков.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК ТоварСсылка,
| ЕСТЬNULL(СвободныеОстатки.ВНаличииОстаток, 0) - ЕСТЬNULL(СвободныеОстатки.ВРезервеСоСкладаОстаток, 0) КАК ДоступныйОстаток
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстатки.Остатки(, Номенклатура В (&МассивТоваров)) КАК СвободныеОстатки
| ПО Номенклатура.Ссылка = СвободныеОстатки.Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивТоваров)";
ЗапросОстатков.УстановитьПараметр("МассивТоваров", МассивТоваров);
РезультатЗапроса = ЗапросОстатков.Выполнить();
ВыборкаОстатков = РезультатЗапроса.Выбрать();
// Имя доп. реквизита, который мы ищем в ПВХ
ИмяСвойства = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("КоличествоТоваров");
Если Не ЗначениеЗаполнено(ИмяСвойства) Тогда
ВызватьИсключение "Не найден дополнительный реквизит ""КоличествоТоваров"" в системе!";
КонецЕсли;
// 4. Обходим результаты запроса и обновляем данные
Пока ВыборкаОстатков.Следующий() Цикл
// Получаем объект номенклатуры напрямую по ссылке без поиска по имени!
ТоварОбъект = ВыборкаОстатков.ТоварСсылка.ПолучитьОбъект();
ТоварОбъект.Заблокировать();
// Работаем с табличной частью дополнительных реквизитов объекта
ПараметрыОтбора = Новый Структура("Свойство", ИмяСвойства);
НайденныеСтроки = ТоварОбъект.ДополнительныеРеквизиты.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() = 0 Тогда
НоваяСтрока = ТоварОбъект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = ИмяСвойства;
НоваяСтрока.Значение = ВыборкаОстатков.ДоступныйОстаток;
Иначе
НайденныеСтроки[0].Значение = ВыборкаОстатков.ДоступныйОстаток;
КонецЕсли;
// Внимание! Этот метод записи справочника в транзакции проведения документа оставлен
// исключительно для демонстрации исправления исходного кода автора.
// На рабочих базах настоятельно рекомендуется перенести хранение остатков в регистр сведений!
ТоварОбъект.Записать();
КонецЦикла;
КонецПроцедуры
Подведем итоги. При работе в транзакциях проведения всегда используйте данные из параметра Источник, так как именно там хранятся актуальные, еще не записанные в базу данных изменения документа. Сравнивайте ссылочные типы статусов только с аналогичными элементами перечисления метаданных. Не пишите запросы в циклах и избегайте необоснованных блокировок справочников при проведении документов.