При сопровождении и доработке конфигураций 1С разработчики нередко сталкиваются с ситуацией, когда документ неожиданно перезаписывается из неизвестного места программы. Типичным последствием такого поведения становится ошибка «Данные были изменены или удалены другим пользователем»: пока пользователь работает с открытой формой, фоновый процесс, внешняя обработка или сторонний модуль перезаписывают объект в базе, изменяя системный реквизит ВерсияДанных. При последующей попытке пользователя нажать кнопку «Записать» платформа справедливо блокирует операцию из-за несовпадения версий.
Для поиска проблемного участка кода необходимо понять, кто именно и откуда инициировал запись в процедуре ПередЗаписью модуля объекта. Разберем основные практические способы определения источника вызова — от штатной передачи параметров до программного перехвата полного стека вызовов.
Если конфигурация разрабатывается под вашим контролем и вы можете модифицировать известные процедуры записи, самым простым и производительным решением является использование структуры ДополнительныеСвойства объекта.
Разберем реализацию по шагам:
ПередЗаписьюНаСервере).Записать() добавлять в свойство объекта специальный маркер с указанием источника.ПередЗаписью модуля объекта анализировать наличие этого свойства и фиксировать событие в Журнале регистрации.Посмотрим на пример установки признака в обработке или внешнем алгоритме:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
ДокументОбъект.ДополнительныеСвойства.Вставить("ИсточникЗаписи", "ОбработкаАвтозаполнения");
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Теперь проанализируем обработку переданного маркера внутри модуля объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Источник = "Интерактивно/Неизвестный источник";
Если ДополнительныеСвойства.Свойство("ИсточникЗаписи") Тогда
Источник = ДополнительныеСвойства.ИсточникЗаписи;
КонецЕсли;
// Запись события в Журнал регистрации для анализа
ЗаписьЖурналаРегистрации(
"КонтрольЗаписиДокумента",
УровеньЖурналаРегистрации.Информация,
Метаданные(),
ЭтотОбъект,
СтрШаблон("Документ %1 записан. Источник: %2", ЭтотОбъект, Источник)
);
КонецПроцедуры
Когда система уже содержит множество неизвестных внешних обработок, модулей и интеграций, расставить свойства вручную во всех местах невозможно. В таком случае выясним точный путь выполнения с помощью программного получения стека вызовов.
В платформе 1С получить текущий стек исполнения в процедуре модуля объекта можно с помощью искусственной генерации исключения внутри блока Попытка ... Исключение с последующим получением данных через метод ПодробноеПредставлениеОшибки().
Рассмотрим пример реализации такого логирования:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Получаем текущий стек выполнения
ТекстСтека = "";
Попытка
ВызватьИсключение "";
Исключение
ИнфоОбОшибке = ИнформацияОбОшибке();
ТекстСтека = ПодробноеПредставлениеОшибки(ИнфоОбОшибке);
КонецПопытки;
// Получаем контекст сеанса
ТекущийСеанс = ПолучитьТекущийСеансИнформационнойБазы();
ИмяПриложения = ТекущийСеанс.ИмяПриложения;
ПользовательСеанса = ТекущийСеанс.Пользователь;
ОписаниеСобытия = СтрШаблон(
"Приложение: %1; Пользователь: %2;%3Стек вызовов:%3%4",
ИмяПриложения,
ПользовательСеанса,
Символы.ПС,
ТекстСтека
);
ЗаписьЖурналаРегистрации(
"ДиагностикаЗаписи",
УровеньЖурналаРегистрации.Предупреждение,
Метаданные(),
ЭтотОбъект,
ОписаниеСобытия
);
КонецПроцедуры
Полученный ТекстСтека покажет полную цепочку вызовов процедур и функций с номерами строк, что позволит мгновенно вычислить конкретную внешнюю обработку или общий модуль, выполняющий запись.
Нередко запись объекта выполняется не пользователем в явном виде, а фоновыми потоками или веб-сервисами, запущенными от имени того же пользователя. Проанализируем, как определить среду выполнения:
ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения возвращает тип подключения: 1CV8C (Тонкий клиент), 1CV8 (Толстый клиент), BackgroundJob (Фоновое задание), WSConnection (Веб-сервис), HTTPService (HTTP-сервис) или COMConnection (COM-соединение).ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание() позволяет проверить, выполняется ли код в рамках регламентного или фонового задания, и получить его наименование.Посмотрим на пример кода для проверки фонового задания:
ТекущееФЗ = ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание();
Если ТекущееФЗ <> Неопределено Тогда
ИмяЗадания = ТекущееФЗ.Ключ;
Если ПустаяСтрока(ИмяЗадания) Тогда
ИмяЗадания = ТекущееФЗ.Наименование;
КонецЕсли;
// Логируем факт модификации объекта регламентным фоновым заданием
КонецЕсли;
Если нежелательно изменять сам модуль объекта (например, чтобы не усложнять последующее обновление конфигурации), разместим отладочный код в отдельной подписке на события:
ПодпискаНаСобытие с именем ДиагностикаЗаписиДокументов.ДокументОбъект.ПриемВрача) или составной тип.ПередЗаписью.После локализации и устранения проблемы подписку можно легко отключить или удалить из конфигурации в один клик без риска повредить рабочую логику документа.