Как определить источник вызова процедуры ПередЗаписью в модуле объекта 1С?

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

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

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

Способ 1. Использование свойства ДополнительныеСвойства для маркировки вызовов

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

Разберем реализацию по шагам:

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

Посмотрим на пример установки признака в обработке или внешнем алгоритме:


ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
ДокументОбъект.ДополнительныеСвойства.Вставить("ИсточникЗаписи", "ОбработкаАвтозаполнения");
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Теперь проанализируем обработку переданного маркера внутри модуля объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Источник = "Интерактивно/Неизвестный источник";
    
    Если ДополнительныеСвойства.Свойство("ИсточникЗаписи") Тогда
        Источник = ДополнительныеСвойства.ИсточникЗаписи;
    КонецЕсли;
    
    // Запись события в Журнал регистрации для анализа
    ЗаписьЖурналаРегистрации(
        "КонтрольЗаписиДокумента",
        УровеньЖурналаРегистрации.Информация,
        Метаданные(),
        ЭтотОбъект,
        СтрШаблон("Документ %1 записан. Источник: %2", ЭтотОбъект, Источник)
    );
    
КонецПроцедуры

Способ 2. Получение полного стека вызовов через перехват исключения

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

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

Рассмотрим пример реализации такого логирования:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Получаем текущий стек выполнения
    ТекстСтека = "";
    Попытка
        ВызватьИсключение "";
    Исключение
        ИнфоОбОшибке = ИнформацияОбОшибке();
        ТекстСтека = ПодробноеПредставлениеОшибки(ИнфоОбОшибке);
    КонецПопытки;
    
    // Получаем контекст сеанса
    ТекущийСеанс = ПолучитьТекущийСеансИнформационнойБазы();
    ИмяПриложения = ТекущийСеанс.ИмяПриложения;
    ПользовательСеанса = ТекущийСеанс.Пользователь;
    
    ОписаниеСобытия = СтрШаблон(
        "Приложение: %1; Пользователь: %2;%3Стек вызовов:%3%4",
        ИмяПриложения,
        ПользовательСеанса,
        Символы.ПС,
        ТекстСтека
    );
    
    ЗаписьЖурналаРегистрации(
        "ДиагностикаЗаписи",
        УровеньЖурналаРегистрации.Предупреждение,
        Метаданные(),
        ЭтотОбъект,
        ОписаниеСобытия
    );
    
КонецПроцедуры

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

Способ 3. Определение типа клиента и фоновых заданий

Нередко запись объекта выполняется не пользователем в явном виде, а фоновыми потоками или веб-сервисами, запущенными от имени того же пользователя. Проанализируем, как определить среду выполнения:

  1. Свойство ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения возвращает тип подключения: 1CV8C (Тонкий клиент), 1CV8 (Толстый клиент), BackgroundJob (Фоновое задание), WSConnection (Веб-сервис), HTTPService (HTTP-сервис) или COMConnection (COM-соединение).
  2. Метод ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание() позволяет проверить, выполняется ли код в рамках регламентного или фонового задания, и получить его наименование.

Посмотрим на пример кода для проверки фонового задания:


ТекущееФЗ = ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание();
Если ТекущееФЗ <> Неопределено Тогда
    ИмяЗадания = ТекущееФЗ.Ключ;
    Если ПустаяСтрока(ИмяЗадания) Тогда
        ИмяЗадания = ТекущееФЗ.Наименование;
    КонецЕсли;
    // Логируем факт модификации объекта регламентным фоновым заданием
КонецЕсли;

Способ 4. Использование подписки на событие без изменения типовой конфигурации

Если нежелательно изменять сам модуль объекта (например, чтобы не усложнять последующее обновление конфигурации), разместим отладочный код в отдельной подписке на события:

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

После локализации и устранения проблемы подписку можно легко отключить или удалить из конфигурации в один клик без риска повредить рабочую логику документа.

← На главную