При разработке на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с задачей логирования действий пользователей. Одна из типичных задач — фиксация даты и времени удаления элемента справочника непосредственно в его реквизите. Однако при попытке реализовать это стандартным способом в предопределенной процедуре ПриУдаленииЭлемента возникают ошибки "Не выбран элемент" или "Объект заблокирован". Это происходит потому, что в момент выполнения процедуры объект уже находится в состоянии транзакции удаления, и платформа блокирует любые попытки изменения этого же объекта через метод Записать().
В данной статье мы подробно разберем, почему возникает эта проблема, и рассмотрим несколько эффективных способов её решения: от использования штатных механизмов до применения внешних компонент.
Проанализируем ситуацию. Когда пользователь нажимает клавишу Del или выбирает пункт меню "Удалить", срабатывает системное событие, которое вызывает процедуру ПриУдаленииЭлемента(Элемент, Режим) в глобальном модуле или модуле справочника. В этот момент 1С уже открыла транзакцию на изменение состояния объекта (установку пометки на удаление или физическое удаление).
Если мы попытаемся внутри этой процедуры изменить реквизит этого же элемента и вызвать Элемент.Записать(), система выдаст ошибку. Платформа 7.7 не позволяет рекурсивно изменять объект, который уже находится в процессе удаления. Попытка создать новый объект через СоздатьОбъект("Справочник.Имя") и найти его по ссылке также не увенчается успехом, так как блокировка устанавливается на уровне записи в таблице базы данных, а не на уровне конкретной программной переменной.
Разберем самый надежный и правильный с точки зрения архитектуры 1С способ. Вместо того чтобы перегружать справочник лишними реквизитами, которые все равно будут потеряны при физическом удалении объекта, логично использовать штатный Журнал Регистрации.
Посмотрим на пример кода, который можно разместить в глобальном модуле:
Процедура ПриУдаленииЭлемента(Элемент, Режим)
Если Режим = 1 Тогда
// Режим 1 - это пометка на удаление
ЗаписьЖурналаРегистрации("Удаление: " + Элемент.Наименование, "Справочники", "Пометка на удаление", Элемент, 2);
ИначеЕсли Режим = 2 Тогда
// Режим 2 - непосредственное удаление
ЗаписьЖурналаРегистрации("ФИЗИЧЕСКОЕ Удаление: " + Элемент.Наименование, "Справочники", "Удаление", , 3);
КонецЕсли;
КонецПроцедуры
Для последующего анализа данных и построения отчетов можно использовать специализированные инструменты. Например, компонента 1С++ предоставляет объект ПоставщикДанныхЖурналРегистрации, который позволяет программно считывать записи журнала с высокой скоростью, что решает проблему построения отчетов по удаленным объектам.
Если бизнес-задача требует наличия даты именно в реквизите элемента (например, для отображения в форме списка), мы можем применить "хитрость" с отложенной записью. Суть метода заключается в том, чтобы запомнить объект, который нужно изменить, и выполнить запись сразу после того, как основная транзакция 1С завершится.
Рассмотрим пошагово реализацию этого механизма:
СписокЗначений для хранения очереди объектов на обработку.ПриУдаленииЭлемента будем добавлять ссылку на элемент в этот список.ОбработкаОжидания, которая будет периодически проверять список и записывать данные.Пример кода в Глобальном модуле:
Перем СписокОбъектовДляЗаписиДаты Экспорт;
Процедура ЗаписатьДатуУдаления()
Перем ТекЭлем, Индекс;
Для Индекс = 1 По СписокОбъектовДляЗаписиДаты.РазмерСписка() Цикл
ТекЭлем = СписокОбъектовДляЗаписиДаты.ПолучитьЗначение(Индекс);
Если ТекЭлем.Выбран() = 1 Тогда
Если ТекЭлем.ПометкаУдаления() = 1 Тогда
// Пытаемся записать. Если объект все еще заблокирован,
// он останется в списке до следующего такта.
Попытка
ТекЭлем.ДатаУдаления = РабочаяДата();
ТекЭлем.Записать();
СписокОбъектовДляЗаписиДаты.УдалитьЗначение(Индекс);
Индекс = Индекс - 1;
Исключение
// Объект еще занят системой
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Процедура ПриНачалеРаботыСистемы()
СписокОбъектовДляЗаписиДаты = СоздатьОбъект("СписокЗначений");
Форма.ОбработкаОжидания("ЗаписатьДатуУдаления", 5); // Проверка каждые 5 секунд
КонецПроцедуры
Для продвинутых разработчиков существуют более элегантные способы, требующие наличия внешних компонент. Разберем использование FormEx для генерации внешнего события.
Проанализируем ситуацию: нам нужно выйти из контекста текущей процедуры. С помощью FormEx мы можем сгенерировать событие, которое выполнится в "свободное" время системы, когда блокировки уже сняты.
// В процедуре ПриУдаленииЭлемента
Сервис = СоздатьОбъект("Сервис");
Сервис.ВнешнееСобытие("СистемаУдаления", "УстановитьДату", Элемент.ТекущийЭлемент());
Затем в процедуре ПриВнешнемСобытии глобального модуля мы просто ловим этот источник и спокойно записываем реквизит, так как основная транзакция пометки на удаление уже завершена.
Если же ваша база работает на SQL-версии, самым мощным методом будет прямой SQL-запрос через компоненту 1С++. Прямая запись в таблицу БД (команда UPDATE) игнорирует объектные блокировки 1С. Выглядит это примерно так:
ТекстЗапроса = "
|UPDATE $Справочник.Номенклатура
|SET $Справочник.Номенклатура.ДатаУдаления = :ТекДата
|WHERE ID = :ВыбЭлемент
|";
ЗапросSQL.УстановитьПараметр("ТекДата", ТекущаяДата());
ЗапросSQL.УстановитьПараметр("ВыбЭлемент", Элемент);
ЗапросSQL.Выполнить(ТекстЗапроса);
Важный нюанс: Прямые запросы следует использовать с осторожностью, понимая структуру таблиц 1С (имена таблиц начинаются на SC), но это единственный способ гарантированно внести изменения в момент удаления без возникновения ошибок блокировки.
Выясним причину, почему иногда лучше не трогать основной справочник. Если пользователь выполнит непосредственное удаление (физическое стирание записи из базы), ваш реквизит ДатаУдаления исчезнет вместе с самим элементом. В этом случае никакая запись в этот же объект не поможет.
Рассмотрим альтернативу: создание отдельного справочника ЛогУдаления. В него мы будем записывать:
Поскольку это другой объект (другая таблица БД), блокировка основного справочника на него не распространяется. Запись в справочник ЛогУдаления внутри процедуры ПриУдаленииЭлемента пройдет без ошибок:
Процедура ПриУдаленииЭлемента(Элемент, Режим)
Лог = СоздатьОбъект("Справочник.ЛогУдаления");
Лог.Новый();
Лог.Объект = Элемент.ТекущийЭлемент();
Лог.Наименование = Элемент.Наименование;
Лог.ДатаСобытия = ТекущаяДата();
Лог.Пользователь = ИмяПользователя();
Лог.Записать();
КонецПроцедуры
Мы рассмотрели несколько подходов к решению проблемы записи данных при удалении в 1С 7.7. Выбор метода зависит от ваших технических условий:
ОбработкуОжидания для отложенной записи.1С++ и прямых запросов.Помните, что работа с транзакциями в 1С 7.7 требует четкого понимания того, в какой момент объект доступен для записи, а в какой — монопольно захвачен ядром системы для выполнения системных операций.