При активной разработке в экосистеме 1С программисты часто используют расширения для добавления новых объектов метаданных (справочников, документов), чтобы не снимать основную конфигурацию с поддержки. Однако в процессе эксплуатации таких решений можно столкнуться с неприятной проблемой: документы или справочники, созданные в расширении, отказываются удаляться стандартным механизмом БСП (Библиотеки стандартных подсистем) "Удаление помеченных объектов".
Симптомы выглядят следующим образом. Мы помечаем объект из расширения на удаление, запускаем стандартную обработку удаления. На первом этапе система успешно находит наш документ в списке помеченных. Но при попытке выполнить непосредственное удаление процесс завершается сообщением: "Удаление не требуется. Нет объектов помеченных на удаление". При этом сам объект так и остается в базе данных.
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем внутренние механизмы платформы 1С и БСП, а также рассмотрим несколько способов решения — от официального до программных обходов без изменения режима совместимости базы данных.
Для того чтобы понять суть проблемы, нам необходимо заглянуть "под капот" платформы 1С и типовой обработки удаления БСП. Разберем ситуацию по шагам.
В составе БСП есть обработка, которая отвечает за поиск и удаление помеченных объектов. В процессе своей работы она строит дерево объектов, предназначенных к удалению, которое выводится на форму. Это дерево связано с реквизитом формы ДеревоПомеченныхНаУдаление. У этого дерева есть колонка Значение, в которую записывается ссылка на конкретный удаляемый объект.
Разработчики БСП спроектировали эту колонку как составной тип данных, включающий в себя:
ЛюбаяСсылка (ссылочный тип для всех объектов базы данных);Строка (для вывода текстовой информации);Число.Именно здесь и кроется главная проблема. В версиях платформы 1С:Предприятие ниже 8.3.20 обобщенные ссылочные типы данных (такие как ЛюбаяСсылка, СправочникСсылка или ДокументСсылка) жестко привязаны к метаданным основной конфигурации. Платформа на этих версиях физически "не знает", что в типе ЛюбаяСсылка могут находиться ссылки на объекты, созданные в расширениях.
Когда алгоритм БСП пытается поместить ссылку на ваш документ из расширения в колонку дерева, имеющую тип ЛюбаяСсылка, срабатывает защитный механизм платформы. Так как тип документа из расширения не входит в составной тип ЛюбаяСсылка основной конфигурации, платформа выполняет автоматическое приведение типов и преобразует ссылку в обычную Строку (содержащую текстовое представление ссылки или её УИД).
В результате, когда обработка переходит к финальной стадии удаления, она обходит дерево и анализирует тип данных в колонке Значение. Вместо ожидания ссылочного типа (например, ДокументСсылка.МойДокументИзРасширения) алгоритм видит тип Строка. Обработка БСП игнорирует строковые значения, поскольку строку невозможно удалить из базы данных как объект. Система считает, что реальных объектов для удаления нет, и выдает пользователю ложное сообщение об успешном завершении процесса.
---
Начиная с релиза платформы 8.3.20.1549, разработчики фирмы "1С" исправили это ограничение. Теперь обобщенные типы данных динамически расширяются типами из подключенных расширений конфигурации. Системные методы (например, Документы.ТипВсеСсылки().Типы()) теперь корректно возвращают типы метаданных расширений.
Однако простого обновления платформы на сервере или локальном компьютере недостаточно. Чтобы новые правила работы с типами вступили в силу, необходимо соблюдение важного условия:
Режим совместимости основной конфигурации должен быть установлен в значение 8.3.20 или выше.
Если ваша конфигурация (например, старая редакция "Управление торговлей 11.4" или "ERP 2.4") работает в режиме совместимости 8.3.12, 8.3.14 или 8.3.16, платформа будет продолжать эмулировать старое поведение для обеспечения обратной совместимости, и ошибка со строковым представлением ссылки сохранится даже на самой современной платформе.
Если у вас есть возможность обновить конфигурацию и поднять режим совместимости до 8.3.20+, обязательно сделайте это — проблема решится сама собой без написания кода.
---
Что делать, если конфигурация находится на поддержке, ее режим совместимости заблокирован поставщиком (например, вендором 1С), и менять его вручную крайне нежелательно?
Мы можем решить проблему с помощью расширения, доработав форму удаления помеченных объектов программным путем. Напрямую изменить тип колонки дерева Значение в визуальном редакторе конфигуратора внутри расширения не удастся — платформа делает свойства подчиненных реквизитов заимствованных объектов доступными только для чтения. Но мы можем обойти это ограничение через встроенный язык.
Рассмотрим по шагам, как реализовать программное преобразование типа реквизита формы в расширении.
Во-первых, заимствуем форму обработки удаления помеченных объектов в наше расширение. В типовых конфигурациях на БСП эта форма обычно называется _ДемоУдалениеПомеченныхОбъектов, УдалениеПомеченныхОбъектов или имеет схожее имя в зависимости от версии библиотеки.
Во-вторых, создадим обработчик события ПриСозданииНаСервере для этой формы в расширении с методом выполнения После. Вставим туда код, который программно переопределит тип колонки дерева на Произвольный, что позволит хранить там любые ссылки без искажения типа.
Посмотрим на пример реализации такого кода:
&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
// Определим путь к реквизиту дерева. Обычно это "ДеревоПомеченныхНаУдаление"
ИмяРеквизитаДерева = "ДеревоПомеченныхНаУдаление";
ИмяКолонкиЗначение = "Значение";
ПолныйПутьКолонки = ИмяРеквизитаДерева + "." + ИмяКолонкиЗначение;
// Создаем массив удаляемых реквизитов (нам нужно удалить старое описание колонки)
УдаляемыеРеквизиты = Новый Массив;
УдаляемыеРеквизиты.Добавить(ПолныйПутьКолонки);
// Создаем массив новых реквизитов для добавления с типом "Произвольный"
ДобавляемыеРеквизиты = Новый Массив;
// Описание типов "Произвольный" получается созданием пустого ОписанияТипов
ОписаниеТипаПроизвольный = Новый ОписаниеТипов;
НоваяКолонка = Новый РеквизитФормы(ИмяКолонкиЗначение, ОписаниеТипаПроизвольный, ИмяРеквизитаДерева);
ДобавляемыеРеквизиты.Добавить(НоваяКолонка);
// Выполняем реструктуризацию реквизитов формы на сервере
ЭтаФорма.ИзменитьРеквизиты(ДобавляемыеРеквизиты, УдаляемыеРеквизиты);
КонецПроцедуры
Этот метод полностью решает проблему "на лету". При заполнении дерева платформа больше не будет пытаться привести ссылку на ваш документ к типу ЛюбаяСсылка основной конфигурации, так как теперь тип колонки стал полностью произвольным. Ссылки сохранят свой первозданный вид, и алгоритмы удаления отработают штатно.
---
Если динамическое изменение схемы реквизитов формы кажется вам слишком радикальным или вызывает нестабильность интерфейса на вашей версии платформы, мы можем применить другой изящный способ — перехват данных перед непосредственным удалением.
Идея заключается в том, чтобы перед началом процедуры удаления программно обойти дерево помеченных объектов в форме, найти все строковые значения, которые по своей сути являются УИД-ами (уникальными идентификаторами) документов из нашего расширения, и заменить эти строки обратно на валидные ссылки.
Для этого мы можем использовать расширение модуля формы обработки удаления, перехватив процедуру, которая запускает процесс удаления (например, ПередУдалениемНаСервере или аналогичную процедуру БСП, обрабатывающую коллекцию помеченных объектов).
Проанализируем пример кода для такого восстановления ссылок:
&Перед("ВыполнитьУдалениеНаСервере")
Процедура Расш1_ВыполнитьУдалениеНаСервереПеред(ПараметрыВыполнения)
// Перебираем строки дерева на форме перед тем, как БСП начнет их обрабатывать.
// Нам нужно обойти все дерево рекурсивно.
ВосстановитьСсылкиВДереве(ДеревоПомеченныхНаУдаление.ПолучитьЭлементы());
КонецПроцедуры
&НаСервере
Процедура ВосстановитьСсылкиВДереве(СтрокиДерева)
Для Каждого СтрокаДерева Из СтрокиДерева Цикл
// Проверяем, если тип значения - Строка, пробуем проверить, не наш ли это документ
Если ТипЗнч(СтрокаДерева.Значение) = Тип("Строка") Тогда
СтроковоеЗначение = СтрокаДерева.Значение;
// Пытаемся распознать в строке Уникальный Идентификатор и восстановить ссылку
// Например, если имя нашего документа из расширения "Расш1_МойДокумент"
Если Найти(СтроковоеЗначение, "Расш1_МойДокумент") > 0 Тогда
// Извлекаем УИД из строкового представления (зависит от того, как БСП преобразует ссылку в строку)
УИДСтрокой = Сред(СтроковоеЗначение, Найти(СтроковоеЗначение, "Ref=") + 4, 36);
Попытка
СсылкаУИД = Новый УникальныйИдентификатор(УИДСтрокой);
ВосстановленнаяСсылка = Документы.Расш1_МойДокумент.ПолучитьСсылку(СсылкаУИД);
// Записываем восстановленную ссылку обратно в дерево
СтрокаДерева.Значение = ВосстановленнаяСсылка;
Исключение
// Обработка исключения, если строка оказалась не УИД-ом
КонецПопытки;
КонецЕсли;
КонецЕсли;
// Рекурсивный обход подчиненных элементов дерева
Если СтрокаДерева.ПолучитьЭлементы().Количество() > 0 Тогда
ВосстановитьСсылкиВДереве(СтрокаДерева.ПолучитьЭлементы());
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот способ абсолютно безопасен для основной конфигурации, не вмешивается в структуру метаданных формы и гарантирует, что к моменту непосредственного удаления все объекты из расширения будут переданы платформе в виде полноценных ссылок, а не строк.
---
Если вам нужно срочно очистить базу от накопившихся помеченных документов расширения, а времени на написание и отладку расширений для БСП нет, можно воспользоваться непосредственным программным удалением. Этот метод подходит для администраторов баз данных.
Создайте простую внешнюю обработку со следующим кодом для принудительного удаления конкретного типа документов из расширения:
Процедура ВыполнитьПринудительноеУдаление() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка
|ИЗ
| Документ.Расш1_МойДокумент КАК Т
|ГДЕ
| Т.ПометкаУдаления = ИСТИНА";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
СсылкаНаОбъект = ВыборкаДетальныеЗаписи.Ссылка;
// Получаем объект для редактирования
ДокументОбъект = СсылкаНаОбъект.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
Попытка
// Внимание: метод Удалить() удаляет объект физически без контроля ссылочной целостности!
// Перед использованием убедитесь, что на этот документ нет ссылок в других объектах.
ДокументОбъект.Удалить();
Сообщить("Успешно удален документ: " + СсылкаНаОбъект);
Исключение
Сообщить("Ошибка удаления документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важное предупреждение: Метод Удалить() осуществляет непосредственное физическое удаление записи из таблицы базы данных. Платформа при этом не проверяет, сослался ли кто-то на этот документ в других таблицах (например, в движениях регистров или реквизитах других документов). Пользуйтесь этим методом с осторожностью и только после предварительного анализа связей удаляемого объекта.
---
Мы подробно проанализировали проблему удаления документов из расширений 1С и выяснили, что ее причиной является старое платформенное ограничение работы обобщенного типа ЛюбаяСсылка, которое приводило к потере типа данных и конвертации ссылок в строки внутри дерева БСП.
Для решения этой проблемы вы можете использовать любой из предложенных вариантов в зависимости от ваших возможностей по изменению конфигурации: