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

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

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

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

Постановка задачи и выбор инструмента

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

Разберем, почему это удобно:

  1. Централизация: Весь код сосредоточен в одном общем модуле.
  2. Масштабируемость: При добавлении нового регистра достаточно просто добавить его в список объектов подписки.
  3. Чистота конфигурации: Мы не меняем типовые модули объектов, что упрощает поддержку.

Шаг 1: Создание подписки на событие

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

В дереве метаданных выполним следующие действия:

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

Шаг 2: Реализация программного кода в общем модуле

Главная ошибка начинающих разработчиков при работе с регистрами заключается в попытке обратиться к Источник.Т напрямую, как это делается в документах или справочниках. Однако Источник в данном случае — это объект типа РегистрСведенийНаборЗаписей. Он представляет собой коллекцию записей, даже если вы записываете всего одну строку.

Проанализируем правильный алгоритм обхода набора записей. Рассмотрим оптимизированный вариант кода:


Процедура УстановитьРеквизитТПередЗаписьюРегистра(Источник, Отказ, Замещение) Экспорт

    // 1. Проверяем режим загрузки данных
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // 2. Получаем дату один раз до начала цикла для оптимизации
    ТекДата = ТекущаяДатаСеанса();

    // 3. Перебираем все записи в наборе
    Для Каждого Запись Из Источник Цикл
        // Обращаемся к реквизиту каждой конкретной записи
        Запись.Т = ТекДата;
    КонецЦикла;

КонецПроцедуры

Разберем по шагам, почему код написан именно так:

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

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

Оптимизация цикла: Мы вынесли получение даты за пределы цикла Для Каждого. Если в наборе записей будет 10 000 строк, функция времени вызовется один раз, а не 10 000 раз, что положительно скажется на производительности системы.

Шаг 3: Обеспечение универсальности (Проверка метаданных)

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

Проанализируем расширенный вариант функции:


Процедура УстановитьРеквизитТПередЗаписьюРегистра(Источник, Отказ, Замещение) Экспорт

    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Проверяем, есть ли вообще у данного регистра реквизит "Т"
    МетаданныеНабора = Источник.Метаданные();
    Если МетаданныеНабора.Реквизиты.Найти("Т") = Неопределено Тогда
        // Если это общий реквизит, проверяем его вхождение в состав
        Если Метаданные.ОбщиеРеквизиты.Т.Состав.Найти(МетаданныеНабора) = Неопределено 
           ИЛИ Метаданные.ОбщиеРеквизиты.Т.Состав[МетаданныеНабора].Использование = Метаданные.ВариантыИспользованияОбщегоРеквизита.НеИспользовать Тогда
            Возврат;
        КонецЕсли;
    КонецЕсли;

    ТекДата = ТекущаяДатаСеанса();

    Для Каждого Запись Из Источник Цикл
        Запись.Т = ТекДата;
    КонецЦикла;

КонецПроцедуры

Нюансы работы с Общими реквизитами

Если ваш реквизит Т настроен как Общий реквизит в дереве метаданных, обратите внимание на его свойство "Автоматическое использование". Если там установлено "Использовать", то платформа позволяет программно изменять его значение. Если же реквизит используется как Разделитель данных (актуально для облачных сервисов), программное изменение его значения может быть запрещено платформой на уровне прав доступа или логики разделения.

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

Рекомендации по использованию в высоконагруженных системах

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

Тем не менее, предложенный метод через Для Каждого Запись Из Источник является стандартным и наиболее простым способом решения задачи для большинства бизнес-сценариев в 1С:Предприятии 8.3.

← На главную