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