Как организовать проверку регистра сведений в модуле документа без дублирования кода

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

При разработке механизмов интеграции, особенно в задачах обмена данными через EDI (например, при формировании ответов на заказы ORDRSP), часто возникает необходимость проверять наличие определенных записей в регистре перед тем, как создавать новые. Одной из типичных проблем является необходимость выполнять одну и ту же проверку в разных процедурах модуля объекта: ПередЗаписью и ОбработкаПроведения. Разберем, как сделать это правильно, эффективно и с соблюдением стандартов разработки на платформе 1С.

Анализ исходной задачи и поиск логических ошибок

Рассмотрим ситуацию: нам нужно записывать данные в регистр сведений УведомленияORDRSP только в том случае, если в нем еще нет записи по текущему заказу, которая была бы помечена флагом ОтправленВРучную = Истина. Проанализируем исходный код функции, предложенный автором темы, и выясним причину его некорректной работы.


Функция НетЗаписейУведомленияORDRSP()
    Запрос = новый Запрос();
    Запрос.УстановитьПараметр("ТекущийЗаказ", Ссылка);
    Запрос.Текст = "ВЫБРАТЬ
                   |    УведомленияORDRSP.ЗаказПокупателя
                   |ИЗ
                   |    РегистрСведений.УведомленияORDRSP КАК УведомленияORDRSP
                   |ГДЕ
                   |    УведомленияORDRSP.ЗаказПокупателя <> &ТекущийЗаказ
                   |    И УведомленияORDRSP.ОтправленВРучную <> ИСТИНА";
    Выборка = Запрос.Выполнить().Пустой();
    Возврат Выборка;
КонецФункции

В данном коде допущена критическая логическая ошибка в блоке ГДЕ. Условие УведомленияORDRSP.ЗаказПокупателя <> &ТекущийЗаказ заставляет систему искать все записи, которые не относятся к нашему документу. Нам же, наоборот, нужно найти запись именно по текущему заказу. Также использование <> ИСТИНА является избыточным и менее производительным, чем прямое сравнение.

Шаг 1: Оптимизация функции проверки

Для того чтобы проверка работала быстро и возвращала верный результат, перепишем запрос. Мы будем использовать конструкцию ВЫБРАТЬ ПЕРВЫЕ 1, так как нам не нужно знать количество записей — достаточно факта наличия хотя бы одной. Это существенно снижает нагрузку на СУБД.


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

Обратим внимание: мы передаем СсылкаНаЗаказ в качестве параметра. Это делает функцию универсальной, позволяя вызывать ее из любого места конфигурации.

Шаг 2: Централизация логики в процедуре записи регистра

Чтобы не загромождать процедуры ПередЗаписью и ОбработкаПроведения повторяющимися проверками, логично вынести условие непосредственно в процедуру, которая отвечает за создание записи в регистре. Выясним, как это реализовать.

Проанализируем процедуру РегистрУведомленияORDRSP(). Мы добавим в ее начало вызов нашей функции проверки. Если условие (наличие ручной отправки) выполняется, мы просто прерываем выполнение процедуры, не создавая новую запись.


Процедура СоздатьЗаписьВРегистреУведомлений()
    // Проверяем, не была ли уже выполнена ручная отправка
    Если ЕстьРучнаяОтправкаORDRSP(ЭтотОбъект.Ссылка) Тогда
        Возврат; // Запись уже существует, ничего делать не нужно
    КонецЕсли;

    // Если проверки пройдены, создаем запись
    МенеджерЗаписи = РегистрыСведений.УведомленияORDRSP.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.ЗаказПокупателя = ЭтотОбъект.Ссылка;
    МенеджерЗаписи.ЗаказEDI        = ЭтотОбъект.ДокументОснование;
    МенеджерЗаписи.УникальныйИдентификатор = СформироватьГУИД();
    МенеджерЗаписи.ОтправленВРучную = Ложь;
    МенеджерЗаписи.ВариантЗаполнения = Перечисления.ВариантыЗаполненияORDRSP.ПовкладкеМТСС;
    МенеджерЗаписи.Состояние         = Перечисления.СостоянияORDRSP.Ожидает;
    МенеджерЗаписи.ДатаПостановки    = ТекущаяДата();
    
    // Используем попытку для исключения блокировок или ошибок записи
    Попытка
        МенеджерЗаписи.Записать();
    Исключение
        ЗаписьЖурналаРегистрации("Ошибка записи EDI", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

Шаг 3: Интеграция в события документа

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Пропускаем проверку для новых, еще не записанных документов
    Если ЭтотОбъект.ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;

    // Пример бизнес-логики: запись в регистр при изменении состава товаров
    Если НужнаОтправкаORDRSP() Тогда
        СоздатьЗаписьВРегистреУведомлений();
    КонецЕсли;
КонецПроцедуры

Аналогичным образом вызов добавляется в ОбработкаПроведения. Поскольку вся "тяжелая" логика проверки регистра скрыта внутри СоздатьЗаписьВРегистреУведомлений, код событий документа остается чистым и легко читаемым.

Дополнительные рекомендации по производительности

Разберем несколько важных моментов, которые помогут избежать проблем в высоконагруженных системах:

  1. Использование ДополнительныхСвойств: Если вы вызываете проверку регистра несколько раз в рамках одной транзакции (и в ПередЗаписью, и в ПриЗаписи), имеет смысл закэшировать результат. Для этого используйте ЭтотОбъект.ДополнительныеСвойства. Один раз выполните запрос, сохраните результат в структуру и при следующем вызове просто прочитайте его.
  2. Транзакционность: Помните, что МенеджерЗаписи.Записать() внутри транзакции проведения документа накладывает блокировки. Убедитесь, что измерения регистра (особенно ЗаказПокупателя) индексированы. Это предотвратит сканирование всей таблицы при выполнении запроса.
  3. Использование Подписок на события: Вместо изменения типового модуля ЗаказПокупателя, рассмотрите возможность выноса этой логики в Подписку на событие. Это значительно упростит процесс обновления конфигурации в будущем.

Таким образом, мы проанализировали ситуацию, исправили ошибки в запросе и создали масштабируемую архитектуру для проверки и записи данных в регистр сведений. Этот подход минимизирует дублирование кода и делает систему более устойчивой к ошибкам.

← На главную