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