При разработке механизмов интеграции, особенно в задачах обмена данными через EDI (например, при формировании ответов на заказы ORDRSP), часто возникает необходимость проверять наличие определенных записей в регистре перед тем, как создавать новые. Одной из типичных проблем является необходимость выполнять одну и ту же проверку в разных процедурах модуля объекта: ПередЗаписью и ОбработкаПроведения. Разберем, как сделать это правильно, эффективно и с соблюдением стандартов разработки на платформе 1С.
Рассмотрим ситуацию: нам нужно записывать данные в регистр сведений УведомленияORDRSP только в том случае, если в нем еще нет записи по текущему заказу, которая была бы помечена флагом ОтправленВРучную = Истина. Проанализируем исходный код функции, предложенный автором темы, и выясним причину его некорректной работы.
Функция НетЗаписейУведомленияORDRSP()
Запрос = новый Запрос();
Запрос.УстановитьПараметр("ТекущийЗаказ", Ссылка);
Запрос.Текст = "ВЫБРАТЬ
| УведомленияORDRSP.ЗаказПокупателя
|ИЗ
| РегистрСведений.УведомленияORDRSP КАК УведомленияORDRSP
|ГДЕ
| УведомленияORDRSP.ЗаказПокупателя <> &ТекущийЗаказ
| И УведомленияORDRSP.ОтправленВРучную <> ИСТИНА";
Выборка = Запрос.Выполнить().Пустой();
Возврат Выборка;
КонецФункции
В данном коде допущена критическая логическая ошибка в блоке ГДЕ. Условие УведомленияORDRSP.ЗаказПокупателя <> &ТекущийЗаказ заставляет систему искать все записи, которые не относятся к нашему документу. Нам же, наоборот, нужно найти запись именно по текущему заказу. Также использование <> ИСТИНА является избыточным и менее производительным, чем прямое сравнение.
Для того чтобы проверка работала быстро и возвращала верный результат, перепишем запрос. Мы будем использовать конструкцию ВЫБРАТЬ ПЕРВЫЕ 1, так как нам не нужно знать количество записей — достаточно факта наличия хотя бы одной. Это существенно снижает нагрузку на СУБД.
Функция ЕстьРучнаяОтправкаORDRSP(СсылкаНаЗаказ) Экспорт
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТекущийЗаказ", СсылкаНаЗаказ);
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле1
|ИЗ
| РегистрСведений.УведомленияORDRSP КАК УведомленияORDRSP
|ГДЕ
| УведомленияORDRSP.ЗаказПокупателя = &ТекущийЗаказ
| И УведомленияORDRSP.ОтправленВРучную = ИСТИНА";
РезультатЗапроса = Запрос.Выполнить();
Возврат НЕ РезультатЗапроса.Пустой();
КонецФункции
Обратим внимание: мы передаем СсылкаНаЗаказ в качестве параметра. Это делает функцию универсальной, позволяя вызывать ее из любого места конфигурации.
Чтобы не загромождать процедуры ПередЗаписью и ОбработкаПроведения повторяющимися проверками, логично вынести условие непосредственно в процедуру, которая отвечает за создание записи в регистре. Выясним, как это реализовать.
Проанализируем процедуру РегистрУведомленияORDRSP(). Мы добавим в ее начало вызов нашей функции проверки. Если условие (наличие ручной отправки) выполняется, мы просто прерываем выполнение процедуры, не создавая новую запись.
Процедура СоздатьЗаписьВРегистреУведомлений()
// Проверяем, не была ли уже выполнена ручная отправка
Если ЕстьРучнаяОтправкаORDRSP(ЭтотОбъект.Ссылка) Тогда
Возврат; // Запись уже существует, ничего делать не нужно
КонецЕсли;
// Если проверки пройдены, создаем запись
МенеджерЗаписи = РегистрыСведений.УведомленияORDRSP.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ЗаказПокупателя = ЭтотОбъект.Ссылка;
МенеджерЗаписи.ЗаказEDI = ЭтотОбъект.ДокументОснование;
МенеджерЗаписи.УникальныйИдентификатор = СформироватьГУИД();
МенеджерЗаписи.ОтправленВРучную = Ложь;
МенеджерЗаписи.ВариантЗаполнения = Перечисления.ВариантыЗаполненияORDRSP.ПовкладкеМТСС;
МенеджерЗаписи.Состояние = Перечисления.СостоянияORDRSP.Ожидает;
МенеджерЗаписи.ДатаПостановки = ТекущаяДата();
// Используем попытку для исключения блокировок или ошибок записи
Попытка
МенеджерЗаписи.Записать();
Исключение
ЗаписьЖурналаРегистрации("Ошибка записи EDI", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Теперь, когда у нас есть единая процедура с встроенной проверкой, мы можем вызывать ее в нужных местах модуля документа. Посмотрим на пример вызова в ПередЗаписью. Важно помнить, что в ПередЗаписью у нового (еще не записанного в БД) объекта ссылка может быть пустой, поэтому такие проверки обычно актуальны для уже существующих заказов.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Пропускаем проверку для новых, еще не записанных документов
Если ЭтотОбъект.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Пример бизнес-логики: запись в регистр при изменении состава товаров
Если НужнаОтправкаORDRSP() Тогда
СоздатьЗаписьВРегистреУведомлений();
КонецЕсли;
КонецПроцедуры
Аналогичным образом вызов добавляется в ОбработкаПроведения. Поскольку вся "тяжелая" логика проверки регистра скрыта внутри СоздатьЗаписьВРегистреУведомлений, код событий документа остается чистым и легко читаемым.
Разберем несколько важных моментов, которые помогут избежать проблем в высоконагруженных системах:
ПередЗаписью, и в ПриЗаписи), имеет смысл закэшировать результат. Для этого используйте ЭтотОбъект.ДополнительныеСвойства. Один раз выполните запрос, сохраните результат в структуру и при следующем вызове просто прочитайте его.МенеджерЗаписи.Записать() внутри транзакции проведения документа накладывает блокировки. Убедитесь, что измерения регистра (особенно ЗаказПокупателя) индексированы. Это предотвратит сканирование всей таблицы при выполнении запроса.ЗаказПокупателя, рассмотрите возможность выноса этой логики в Подписку на событие. Это значительно упростит процесс обновления конфигурации в будущем.Таким образом, мы проанализировали ситуацию, исправили ошибки в запросе и создали масштабируемую архитектуру для проверки и записи данных в регистр сведений. Этот подход минимизирует дублирование кода и делает систему более устойчивой к ошибкам.