Как правильно организовать запись в регистр накопления из подписки на событие в 1С?

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

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

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

Главное архитектурное заблуждение: можно ли писать в регистр, если он не выбран в «Движениях» документа?

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

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

  1. Документ должен быть указан в качестве регистратора: В свойствах самого регистра накопления на вкладке «Регистраторы» обязательно должен быть выбран ваш документ. Без этого платформа физически не позволит записать движения, так как тип значения поля Регистратор жестко контролируется СУБД на уровне ссылочной целостности.
  2. Запись производится через набор записей: Если регистр не выбран в движениях документа, вы не сможете обратиться к нему через коллекцию Источник.Движения.ИмяРегистра. Запись необходимо выполнять напрямую через менеджер регистра, создавая объект НаборЗаписей.

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


Вариант А. Регистр выбран в «Движениях» документов (Рекомендуемый способ)

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

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

В ветке конфигурации «Общие -> Подписки на события» создаем новую подписку:

Шаг 2. Написание программного кода

В процедуре-обработчике общего модуля мы используем стандартную коллекцию Движения объекта-источника. Проанализируем пример кода:


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

Плюсы данного подхода:


Вариант Б. Регистр НЕ выбран в «Движениях» документов (Ручной способ)

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

Программный код в обработчике подписки на проведение:


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

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


Важнейшие технические нюансы ручной записи (Вариант Б)

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

1. Проблема отмены проведения (Разпроведения) документа

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

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


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

2. Проблема транзакционных блокировок (Data Locks)

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

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

Решение: Перед записью набора данных в коде проведения необходимо явно объявлять объект БлокировкаДанных, накладывая исключительную блокировку на регистр по нашему регистратору или ключевым измерениям (номенклатуре, складу).

3. Наличие ссылки на документ при записи

Обратите внимание, что в обработчике события ОбработкаПроведения у объекта Источник гарантированно заполнена ссылка Источник.Ссылка, так как документ уже сохранен в базе данных перед началом транзакции проведения.

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


Резюме и рекомендации

Подведем итог нашему анализу:

← На главную