Как выполнить код в событиях ПередЗаписью и ПослеЗаписи при программном сохранении регистра сведений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Выясняем причину: почему код не работает в форме?

Для понимания сути проблемы рассмотрим, как платформа «1С:Предприятие» разделяет контексты выполнения кода. Форма — это исключительно интерактивный элемент интерфейса. Все обработчики событий формы, такие как ПередЗаписьюНаСервере, ПриЗаписиНаСервере и ПослеЗаписиНаСервере, созданы для управления поведением элементов отображения на клиенте и сервере во время работы пользователя с экраном.

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

Чтобы реализовать сквозную бизнес-логику, которая гарантированно будет срабатывать как при ручном вводе, так и при автоматической загрузке или программной генерации движений, нам необходимо перенести обработчики на уровень СУБД — в Модуль набора записей регистра сведений.

Решение по шагам: перенос логики в Модуль набора записей

Для того чтобы перехватить запись данных на уровне СУБД, нам доступны события модуля набора записей. Основными из них являются ПередЗаписью и ПриЗаписи. Разберем по шагам, как правильно перенести код.

Шаг 1. Откроем конфигуратор, найдем наш регистр сведений и перейдем в его свойства. На вкладке «Прочее» нажмем кнопку «Модуль набора записей».

Шаг 2. В открывшемся модуле создадим предопределенную процедуру-обработчик ПередЗаписью:


Процедура ПередЗаписью(Отказ, Замещение)
    
    // Здесь будет располагаться наша бизнес-логика
    
КонецПроцедуры

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

Ловушка «двойного вызова» (эффект Менеджера записи)

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

  1. Очистка старых данных: Сначала платформа записывает пустой набор записей с установленным отбором по ключевым полям, чтобы удалить старую версию строки из базы данных. При этом вызывается обработчик ПередЗаписью, но количество элементов в наборе равно нулю (ЭтотОбъект.Количество() = 0).
  2. Запись новых данных: Следом платформа производит запись набора, содержащего одну заполненную строку с новыми значениями ресурсов и реквизитов. Обработчик ПередЗаписью вызывается второй раз.

Если внутри обработчика выполняется тяжелый запрос или сложный расчет без предварительной проверки, система выполнит этот код дважды, причем на первом шаге (при очистке) он может завершиться ошибкой из-за отсутствия данных в наборе.

Чтобы избежать этой ловушки, в самом начале обработчика всегда следует делать проверку на количество строк в наборе:


Процедура ПередЗаписью(Отказ, Замещение)
    
    // Игнорируем вызов обработчика при очистке (удалении) записи
    Если ЭтотОбъект.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Основной цикл обработки записей
    Для Каждого Запись Из ЭтотОбъект Цикл
        // Выполняем полезное действие, например, забираем данные
        ИмяПользователя = ПользователиИнформационнойБазы.ТекущийПользователь().Имя;
        Запись.Ответственный = ИмяПользователя;
    КонецЦикла;
    
КонецПроцедуры

Передача параметров через ДополнительныеСвойства

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

Рассмотрим пример, когда при программной записи из внешней обработки нам нужно передать специальный флаг в модуль набора:

В вызывающем коде (где мы программно формируем набор записей):


Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Номенклатура.Установить(ВыбНоменклатура);

// Заполняем строки набора...
НоваяСтрока = Набор.Добавить();
НоваяСтрока.Номенклатура = ВыбНоменклатура;
НоваяСтрока.Цена = 1500;

// Передаем параметр через ДополнительныеСвойства
Набор.ДополнительныеСвойства.Вставить("ВыполнитьСложныйПерерасчет", Истина);

Набор.Записать();

В модуле набора записей регистра сведений:


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

Важное ограничение: Помните, что у объекта типа РегистрСведенийМенеджерЗаписи отсутствует свойство ДополнительныеСвойства. Если запись в вашей системе ведется через менеджеры записи, то передать параметры напрямую в набор не получится. В таких случаях рекомендуется переписать код инициализации записи с использования менеджера на использование набора записей.

Использование подписок на события

Если вы работаете в типовой конфигурации, находящейся на поддержке (например, «1С:ERP», «1С:Комплексная автоматизация» или «1С:Управление торговлей»), вносить изменения непосредственно в модуль набора записей регистра не рекомендуется, чтобы не усложнять последующие обновления системы.

Вместо этого мы можем использовать механизм подписок на события. Давайте разберем этот процесс по шагам:

  1. В дереве конфигурации перейдем в ветку Общие -> Подписки на события и создадим новую подписку.
  2. В качестве источника выберем тип РегистрСведенийНаборЗаписей.[ИмяВашегоРегистра].
  3. В поле «Событие» выберем значение ПередЗаписью или ПриЗаписи.
  4. В качестве обработчика укажем процедуру в неглобальном общем модуле (модуль должен иметь включенные флажки «Сервер» и «Вызов сервера»).

Пример кода обработчика в общем модуле будет выглядеть аналогично коду в модуле набора, за исключением того, что сам набор будет передан в качестве первого параметра Источник:


Процедура ПередЗаписьюРегистраПодписка(Источник, Отказ, Замещение) Экспорт
    
    // Источник — это и есть наш РегистрСведенийНаборЗаписей
    Если Источник.ОбменДанными.Загрузка Или Источник.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    Для Каждого Запись Из Источник Цикл
        // Логика обработки...
    КонецЦикла;
    
КонецПроцедуры

Учет режима обмена данными (Синхронизация)

При проектировании логики в модуле набора записей или в подписках на события критически важно учитывать работу механизмов обмена данными. Если ваша база данных синхронизируется с другими информационными системами, то при выгрузке/загрузке данных в регистр могут попадать уже готовые и проверенные записи.

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


Процедура ПередЗаписью(Отказ, Замещение)
    
    // Если идет процесс загрузки данных при обмене, пропускаем логику
    Если ЭтотОбъект.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Проверка заполненности набора
    Если ЭтотОбъект.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Ваша бизнес-логика...
    
КонецПроцедуры

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

← На главную