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

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

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

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

Реализация логирования изменений реквизитов (старого и нового значений)

Рассмотрим ситуацию, когда в документе Заявка есть реквизиты Этап и Сотрудник. Нам необходимо при записи документа проверять, изменились ли эти поля. Если изменения произошли, мы должны записать в регистр сведений запись вида: "Реквизит изменен с значения А на значение Б".

Для того чтобы узнать старое значение реквизита, которое еще хранится в базе данных, нам нужно обратиться к базе через ссылку текущего объекта. В памяти компьютера в момент записи находится объект ЭтотОбъект (с новыми, еще не записанными данными), а по адресу ЭтотОбъект.Ссылка в базе данных всё еще лежат старые данные.

Проанализируем пример кода, который необходимо разместить в модуле объекта документа в обработчике события ПередЗаписью:


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

Метод сравнения ЭтотОбъект.Реквизит <> Ссылка.Реквизит является классическим для платформы 1С. Однако помните, что обращение к реквизитам через точку от ссылки (Ссылка.Этап) вызывает неявный SQL-запрос к базе данных. Если реквизитов много, это может снизить производительность. В высоконагруженных системах старые значения рекомендуется получать одним явным запросом.

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

Устранение ошибки «Процедура или функция с указанным именем не определена (Оповестить)»

Рассмотрим ошибку, с которой часто сталкиваются новички при попытке автоматизировать обновление списков на формах. Пытаясь вызвать метод Оповестить() внутри модуля объекта в процедуре ПриЗаписи, разработчик получает отказ системы компиляции.

Выясним причину этой ошибки. В платформе 1С:Предприятие реализовано жесткое разделение контекстов исполнения кода:

  1. Серверный контекст (база данных) — здесь выполняются модули объектов, модули менеджеров, транзакции записи и проведения. На сервере нет интерфейса пользователя.
  2. Клиентский контекст (интерфейс) — здесь выполняются клиентские процедуры форм. Только на клиенте доступны интерактивные методы, такие как предупреждения, вопросы пользователю и метод Оповестить(), отправляющий сигналы другим открытым формам.

Процедура ПриЗаписи в модуле объекта выполняется строго на сервере в транзакции базы данных. Метод Оповестить является клиентским. Попытка вызвать его на сервере и приводит к ошибке компиляции.

Чтобы правильно обновить форму списка регистров при записи документа, нам нужно перенести отправку оповещения на клиентский уровень — в модуль формы документа. Разберем этот алгоритм по шагам:

Шаг 1. В модуле формы документа найдем или создадим предопределенный обработчик события ПослеЗаписиНаСервере или ПослеЗаписи. Обработчик ПослеЗаписи выполняется на клиенте, поэтому мы можем использовать нужные нам методы:


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    // Этот метод вызывается на клиенте, когда транзакция записи на сервере успешно завершена
    Оповестить("ЗаписанДокументЗаявка", ЭтотОбъект.Ссылка);
КонецПроцедуры

Шаг 2. В форме, где у вас отображается список регистра сведений (например, на главной рабочей панели), создаем обработчик события формы ОбработкаОповещения:


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

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

Как получить ссылку нового документа до его фактической записи

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

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

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

Способ 1. Использование обработчика «ПриЗаписи»

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


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

Способ 2. Предварительная генерация ссылки через «УстановитьСсылкуНового()»

Если вам критически важно знать точную ссылку на документ еще в процедуре ПередЗаписью (например, для передачи ее в сложные алгоритмы или внешние системы), вы можете сгенерировать ее программно.


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

Передача Таблицы значений из внешней обработки в модуль объекта документа

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

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

Свойство ДополнительныеСвойства представляет собой структуру, которая существует в памяти объекта «на лету» во время его жизненного цикла в текущем сеансе (пока объект не записан и не закрыт). Она не хранится в базе данных, не требует создания новых реквизитов документа, но доступна во всех серверных обработчиках модуля объекта.

Рассмотрим по шагам реализацию этого механизма:

Шаг 1. В коде вашей внешней обработки (или формы обработки), где вы создаете или открываете объект документа для записи, заполните структуру дополнительных свойств:


// Получаем объект документа из ссылки (или создаем новый)
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();

// Наша загруженная Таблица Значений из Excel
ТаблицаИзЭксель = ПолучитьТаблицуДанных(); 

// Помещаем ТЗ в ДополнительныеСвойства документа под произвольным ключом
ДокументОбъект.ДополнительныеСвойства.Вставить("ТаблицаИмпорта", ТаблицаИзЭксель);

// Записываем документ. Это вызовет стандартные процедуры модуля объекта на сервере
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Шаг 2. В модуле объекта документа (например, в процедуре ПередЗаписью или ПриЗаписи) перехватываем переданную таблицу и обрабатываем ее:


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

Преимущество этого метода заключается в абсолютной безопасности и чистоте архитектуры. Если пользователь вручную открывает документ из интерфейса программы и нажимает кнопку «Записать», структура ДополнительныеСвойства окажется пустой. Проверка метода Свойство("ТаблицаИмпорта") вернет значение Ложь, и код импорта гарантированно не будет выполнен, защищая систему от ложных срабатываний.

← На главную