Приветствуем вас! В практике разработки на платформе 1С:Предприятие часто возникает задача жесткого контроля за тем, кто именно внес последние изменения в документ. Для этого программисты нередко используют подписки на события. Однако стандартные механизмы типовых конфигураций (например, в «1С:Бухгалтерия 2.0» или «3.0») могут преподносить сюрпризы: например, при изменении суммы в документе РеализацияТоваровУслуг связанный с ней подчиненный документ СчетФактураВыданный перезаписывается автоматически, но установленный в подписке алгоритм смены ответственного в нем не срабатывает. В этой статье мы подробно разберем, почему так происходит, какие механизмы платформы влияют на это поведение и как правильно реализовать контроль изменений без нарушения бизнес-логики системы.
Рассмотрим конкретную техническую ситуацию. В конфигурации создана подписка на событие ПередЗаписью для документов. Цель подписки — принудительно перезаписывать реквизит Ответственный значением текущего пользователя при каждом сохранении объекта. Ручное редактирование реквизита на форме заблокировано.
Сценарий сбоя выглядит следующим образом:
Ответственный в самом счете-фактуре остается старым, несмотря на наличие общей подписки на событие ПередЗаписью.Давайте подробно проанализируем причины этого явления.
Самая распространенная причина, по которой кастомные подписки на события «пролетают» мимо выполнения при программной записи подчиненных документов, заключается в использовании режима обмена данными. При программном обновлении реквизитов связанных объектов разработчики типовых решений часто отключают лишние обработчики, чтобы ускорить транзакцию и избежать коллизий.
Для этого используется свойство объекта ОбменДанными.Загрузка. Посмотрим на типичный шаблон написания подписки на событие:
Процедура ПередЗаписьюДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Если документ записывается в режиме загрузки данных (например, при обмене или групповой обработке)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Алгоритм заполнения ответственного
Источник.Ответственный = Пользователи.ТекущийПользователь();
КонецПроцедуры
Если в коде вашей подписки установлена проверка на Источник.ОбменДанными.Загрузка = Истина, то при любом программном вызове, который устанавливает этот флаг для обхода проверок, ваш алгоритм обновления ответственного будет полностью проигнорирован. Программные групповые обработки перепроведения или синхронизации практически всегда работают с этим флагом.
Выясним, как именно типовая конфигурация обновляет подчиненные счета-фактуры. При проведении документа продажи вызывается специализированный механизм соответствия реквизитов (например, процедура УчетНДС.ПроверитьСоответствиеРеквизитовСчетаФактуры). Внутри этого механизма выполняются следующие шаги:
СчетФактураВыданный.Объект.Записать(РежимЗаписиДокумента.Проведение) или Объект.Записать(РежимЗаписиДокумента.Запись).Важный момент: во многих версиях БСП и типовых конфигураций при программной синхронизации связанных документов в их структуру ДополнительныеСвойства передаются специальные маркеры (например, указывающие системе, что документ записывается «в связке» и проверять права или интерактивные ограничения не нужно). Если в подписке на события не учтены эти дополнительные свойства, поведение системы может отличаться от интерактивного ввода.
Кроме того, если документ «Счет-фактура выданный» по какой-то причине был случайно или намеренно исключен из списка источников вашей подписки (в метаданных не установлена галочка напротив этого типа документа), то код подписки для него не выполнится никогда.
Когда перезапись подчиненного документа происходит внутри транзакции проведения родительского документа, изменяется контекст выполнения кода:
Справочники.Пользователи.ПустаяСсылка().Ответственный пустую ссылку, либо оставлять значение без изменений.Попытка использовать стандартный реквизит Ответственный в качестве технического лога («кто последний изменил документ») ведет к ряду серьезных проблем в учете и правах доступа:
Ответственный. Если главный бухгалтер или администратор базы запустит групповое перепроведение документов за прошлый период (например, для восстановления последовательности), то подписка автоматически перезапишет ответственного во всех документах на администратора/бухгалтера. В результате обычные менеджеры полностью потеряют видимость и доступ к своим документам.Ответственный часто выводится в печатные формы документов (в поля «Отпустил», «Ответственный за оформление» и т.д.). Если документ перепроведет другой сотрудник спустя месяц, печатная форма исказится — на ней будет фигурировать имя человека, который просто перепровел документ в конце месяца, а не того, кто реально совершил хозяйственную операцию.Проанализируем три надежных способа решения этой задачи без вмешательства в типовую логику реквизита Ответственный.
Это классический и самый надежный способ разделения ответственности. Вместо одного реквизита создаются два дополнительных реквизита (или используются дополнительные свойства БСП):
Автор — заполняется строго один раз при создании документа. Значение больше никогда не меняется.Редактор (или ПоследнийИзменивший) — обновляется при каждой интерактивной записи.Рассмотрим пример кода подписки на событие ПередЗаписью, реализующей эту схему:
Процедура ПередЗаписьюДокументаРегистрацияИзменений(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Игнорируем запись при загрузке данных (обмены, загрузки из внешних файлов)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
ТекущийПользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
НайденныйПользователь = Справочники.Пользователи.НайтиПоИмени(ТекущийПользователь.Имя);
Если Не ЗначениеЗаполнено(НайденныйПользователь) Тогда
Возврат; // Если пользователь не определен (например, это фоновое задание без привязки к справочнику)
КонецЕсли;
// Заполняем автора, только если документ абсолютно новый
Если Источник.ЭтоНовый() Тогда
Если Источник.Метаданные().Реквизиты.Найти("Автор") <> Неопределено Тогда
Источник.Автор = НайденныйПользователь;
КонецЕсли;
КонецЕсли;
// Заполняем редактора при каждом изменении
Если Источник.Метаданные().Реквизиты.Найти("ПоследнийРедактор") <> Неопределено Тогда
Источник.ПоследнийРедактор = НайденныйПользователь;
КонецЕсли;
КонецПроцедуры
Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), вам не нужно изобретать велосипед и писать собственные подписки. Достаточно включить и настроить штатное версионирование объектов:
Этот механизм полностью решает проблему: система сама автоматически и абсолютно надежно запишет в специальный регистр сведений историю всех версий объекта — кто, когда и какие именно реквизиты изменил. Вы сможете наглядно сравнить версию «было» и «стало» прямо из формы документа.
Если вам нужно расследовать редкие случаи несанкционированного изменения документов (например, «кто поменял сумму в СФ»), нет необходимости усложнять структуру метаданных базы данных. Все события записи объектов фиксируются платформой в Журнале регистрации по умолчанию.
Для поиска виновного достаточно открыть Журнал регистрации, установить отбор по метаданным конкретного документа (или по его уникальному идентификатору / представлению) и отфильтровать события по действиям «Данные.Изменение» или «Данные.Проведение». Журнал регистрации зафиксирует реальную учетную запись операционной системы или пользователя 1С, под которой была выполнена транзакция, независимо от того, какие значения записаны внутри самого документа.