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