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