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