Почему не удаляются объекты из расширения 1С:Предприятие 8.3 стандартными средствами?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с расширениями в 1С:Предприятии 8.3 разработчики часто сталкиваются с неочевидной проблемой: объекты, созданные в расширении и помеченные на удаление, отказываются удаляться стандартным механизмом "Удаление помеченных объектов". Этот процесс может сбивать с толку, поскольку, казалось бы, объекты видны в списке помеченных на удаление, но система утверждает, что удаление не требуется. Давайте вместе разберемся в причинах такого поведения и рассмотрим доступные решения.

Разбираемся в корневой причине: Ограничения Метаданных и ТипВсеСсылки

Чтобы понять, почему стандартный механизм удаления не работает с объектами из расширений, мы должны глубже погрузиться в то, как платформа 1С:Предприятие обрабатывает метаданные и ссылочную целостность. В основе большинства типовых механизмов, работающих со всеми ссылочными типами конфигурации (включая удаление помеченных объектов), лежит коллекция метаданных и функция ОписаниеТипаВсеСсылки.

Функция ОписаниеТипаВсеСсылки в типовых конфигурациях предназначена для формирования универсального описания типов всех ссылочных объектов, существующих в основной конфигурации. Это позволяет механизмам платформы, таким как проверка ссылочной целостности или поиск ссылок при удалении, работать с единым списком всех возможных ссылок.

Исторически, в версиях платформы 1С:Предприятие 8.3 до версии 8.3.20, объекты, созданные в расширениях, не включались автоматически в стандартные коллекции метаданных и, соответственно, в описание типов, формируемое функцией ОписаниеТипаВсеСсылки. Для основной конфигурации объекты расширения были, по сути, "прозрачными", когда речь шла о глобальных системных списках метаданных. Это создавало следующую коллизию:

  1. Мы создавали новый справочник или документ в расширении.
  2. Мы работали с этим объектом, создавали его экземпляры.
  3. Когда требовалось удалить эти объекты, мы помечали их на удаление.
  4. Стандартный механизм "Удаление помеченных объектов" начинал свою работу. Он обращался к ОписаниеТипаВсеСсылки для получения списка всех типов, которые могут содержать ссылки.
  5. Поскольку объекты из расширения не входили в этот список, механизм просто "не видел" их как часть общей ссылочной системы. Он мог отображать их в интерфейсе (поскольку они являются частью активной конфигурации базы данных), но на этапе финальной проверки и удаления они игнорировались, что приводило к сообщению "Удаление не требуется".

Именно это фундаментальное ограничение в работе с метаданными расширений до версии 8.3.20 являлось основной причиной описываемой проблемы.

Решение 1: Обновление платформы 1С:Предприятие до версии 8.3.20 и выше

Самым простым, надежным и рекомендованным способом решения проблемы с неудаляемыми объектами из расширений является обновление платформы 1С:Предприятие до версии 8.3.20 или более новой. Разработчики 1С осознали это ограничение и значительно улучшили работу с расширениями, начиная с этой версии. В платформе 8.3.20 и последующих релизах объекты, добавленные через расширения, стали автоматически включаться в универсальные коллекции метаданных, такие как ТипВсеСсылки. Это означает, что:

  1. Стандартная обработка "Удаление помеченных объектов" теперь корректно идентифицирует и обрабатывает ссылки на объекты из расширений.
  2. Не требуется никаких дополнительных модификаций или "костылей" в вашем коде расширения.
  3. Обновление платформы также приносит множество других улучшений стабильности, производительности и новые возможности.

Мы настоятельно рекомендуем рассмотреть возможность обновления вашей платформы до актуальной версии, если вы столкнулись с данной проблемой. Это позволит вам использовать стандартные механизмы платформы без необходимости дополнительных программных обходов.

Чтобы проверить текущую версию вашей платформы, откройте клиент 1С:Предприятия и перейдите в меню "Справка" -> "О программе...". Там будет указан полный номер версии платформы (например, 8.3.13.1513, как у автора вопроса).

Решение 2: Переопределение функции ОписаниеТипаВсеСсылки в расширении (для платформ ниже 8.3.20)

Если обновление платформы по каким-либо причинам невозможно или нежелательно (например, из-за строгих требований к стабильности или сложной инфраструктуры), существует программный способ решения проблемы. Он заключается в переопределении стандартной функции ОписаниеТипаВсеСсылки внутри вашего расширения. Это позволит нам принудительно добавить в список типов ссылки на объекты, созданные в расширении, тем самым "показав" их стандартным механизмам платформы.

Давайте разберем этот подход подробнее:

  1. Создание общего модуля в расширении: Нам потребуется создать новый или использовать существующий общий модуль в вашем расширении, который будет переопределять стандартную функцию. Часто для этих целей используется общий модуль, который содержит функции, переопределяющие поведение типовых подсистем, например, СтандартныеПодсистемыПовтИсп. Если такого модуля нет, мы можем создать его в расширении с любым именем (например, МоеРасширениеПереопределения) и установить для него соответствующие свойства (например, "Клиент (обычное приложение)", "Клиент (управляемое приложение)", "Сервер").
  2. Использование директивы &Вместо: Для переопределения существующей функции типовой конфигурации мы будем использовать директиву &Вместо. Эта директива позволяет выполнить наш код вместо оригинального кода функции.
  3. Добавление ссылочных типов из расширения: Внутри переопределенной функции мы должны получить оригинальный список типов, а затем дополнить его типами ссылок на все объекты нашего расширения (справочники, документы, планы видов характеристик и т.д.).

Рассмотрим пример кода, который мы разместим в общем модуле вашего расширения. Название модуля может быть любым, но часто для переопределений типовых функций используется модуль СтандартныеПодсистемыПовтИсп, если вы заимствуете его из основной конфигурации. Если вы создаете новый модуль, убедитесь, что он доступен на сервере.


// В общем модуле расширения (например, СтандартныеПодсистемыПовтИсп или новом модуле)

&Вместо("ОписаниеТипаВсеСсылки")
Функция СТК_ОписаниеТипаВсеСсылки()

    // Сначала вызываем оригинальную функцию, чтобы получить ее результат.
    // Это гарантирует, что мы не потеряем типы из основной конфигурации.
    Результат = ПродолжитьВызов();

    МассивДобавляемыхТипов = Новый Массив;

    // Перебираем все справочники в метаданных
    Для Каждого МД Из Метаданные.Справочники Цикл
        // Проверяем, принадлежит ли справочник нашему расширению
        Если МД.РасширениеКонфигурации = Неопределено Тогда
            Продолжить; // Это справочник основной конфигурации
        КонецЕсли;
        // Замените "СТК" на имя вашего расширения
        Если МД.РасширениеКонфигурации.Имя <> "СТК" Тогда
            Продолжить; // Это справочник другого расширения, если их несколько
        КонецЕсли;
        // Добавляем тип ссылки на справочник из нашего расширения
        МассивДобавляемыхТипов.Добавить(Тип("СправочникСсылка." + МД.Имя));
    КонецЦикла;

    // Перебираем все документы в метаданных
    Для Каждого МД Из Метаданные.Документы Цикл
        Если МД.РасширениеКонфигурации = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        // Замените "СТК" на имя вашего расширения
        Если МД.РасширениеКонфигурации.Имя <> "СТК" Тогда
            Продолжить;
        КонецЕсли;
        // Добавляем тип ссылки на документ из нашего расширения
        МассивДобавляемыхТипов.Добавить(Тип("ДокументСсылка." + МД.Имя));
    КонецЦикла;

    // Аналогично для ПлановВидовХарактеристик (если есть в расширении)
    Для Каждого МД Из Метаданные.ПланыВидовХарактеристик Цикл
        Если МД.РасширениеКонфигурации = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        // Замените "СТК" на имя вашего расширения
        Если МД.РасширениеКонфигурации.Имя <> "СТК" Тогда
            Продолжить;
        КонецЕсли;
        МассивДобавляемыхТипов.Добавить(Тип("ПланВидовХарактеристикСсылка." + МД.Имя));
    КонецЦикла;

    // И так далее для других ссылочных объектов, если они есть в расширении
    // Например: ПланыСчетов, ПланыВидовРасчета, РегистрыСведений, РегистрыНакопления и т.д.
    // Но обычно для удаления помеченных объектов достаточно Справочников и Документов.

    // Создаем новое описание типов, объединяя оригинальное и добавленные нами
    Результат = Новый ОписаниеТипов(Результат, МассивДобавляемыхТипов);

    Возврат Результат;

КонецФункции

Подробное объяснение кода:

  1. &Вместо("ОписаниеТипаВсеСсылки"): Это ключевая директива. Она указывает платформе, что наша функция СТК_ОписаниеТипаВсеСсылки должна быть вызвана вместо оригинальной функции с именем ОписаниеТипаВсеСсылки.
  2. Результат = ПродолжитьВызов();: Внутри переопределенной функции мы сначала вызываем оригинальную функцию с помощью специального оператора ПродолжитьВызов(). Это очень важно, так как позволяет нам получить то описание типов, которое было бы сформировано без нашего переопределения (то есть, все типы из основной конфигурации). Таким образом, мы не ломаем функциональность для объектов основной конфигурации.
  3. МассивДобавляемыхТипов = Новый Массив;: Мы создаем пустой массив, в который будем собирать типы ссылок на объекты нашего расширения.
  4. Циклы по Метаданные.Справочники, Метаданные.Документы и т.д.: Мы последовательно перебираем коллекции метаданных для различных видов ссылочных объектов (справочники, документы, планы видов характеристик и, при необходимости, другие).
  5. Если МД.РасширениеКонфигурации = Неопределено Тогда Продолжить;: Это условие позволяет нам отфильтровать объекты основной конфигурации. Если свойство РасширениеКонфигурации у объекта метаданных равно Неопределено, это означает, что объект принадлежит основной конфигурации, и мы его пропускаем, так как его тип уже есть в Результат от ПродолжитьВызов().
  6. Если МД.РасширениеКонфигурации.Имя <> "СТК" Тогда Продолжить;: Здесь мы фильтруем объекты по имени нашего расширения. Очень важно заменить "СТК" на актуальное имя вашего расширения (то имя, которое вы задали для расширения в конфигураторе). Это гарантирует, что мы добавляем только объекты из *нашего* расширения, а не из каких-либо других, если их несколько.
  7. МассивДобавляемыхТипов.Добавить(Тип("СправочникСсылка." + МД.Имя));: Если объект принадлежит нашему расширению, мы формируем его тип ссылки (например, Тип("СправочникСсылка.МойНовыйСправочник")) и добавляем его в наш массив.
  8. Результат = Новый ОписаниеТипов(Результат, МассивДобавляемыхТипов);: В конце мы создаем новое описание типов, используя конструктор Новый ОписаниеТипов(). Мы передаем ему два параметра: первое описание типов (полученное от оригинальной функции) и наш массив с добавляемыми типами. Это объединяет все типы в одно результирующее описание.
  9. Возврат Результат;: Возвращаем объединенное описание типов.

После реализации этого кода и обновления расширения в базе данных, стандартный механизм "Удаление помеченных объектов" начнет видеть объекты вашего расширения и сможет корректно их удалять.

Решение 3: Программное удаление объектов из расширения

Как мы уже выяснили, прямой вызов метода удаления для объекта работает даже на старых платформах, поскольку он не зависит от глобальных коллекций метаданных, таких как ТипВсеСсылки. Если вы не хотите или не можете обновлять платформу или переопределять ОписаниеТипаВсеСсылки, вы всегда можете написать собственную обработку или регламентное задание для программного удаления помеченных объектов из вашего расширения.

Принцип такого решения достаточно прост:

  1. Мы должны получить ссылки на все объекты определенного типа из нашего расширения, которые помечены на удаление.
  2. Для каждого такого объекта мы получаем его объектную форму и вызываем метод Удалить().

Вот пример кода, который демонстрирует этот подход для справочника "МойСправочникРасширения":


// Пример программного удаления для справочника из расширения
// Этот код можно разместить в обработке или регламентном задании.

&НаСервере
Процедура УдалитьПомеченныеОбъектыРасширения()

    Сообщить("Начинаем удаление помеченных объектов расширения...");

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   СправочникМойСправочникРасширения.Ссылка
        |ИЗ
        |   Справочник.МойСправочникРасширения КАК СправочникМойСправочникРасширения
        |ГДЕ
        |   СправочникМойСправочникРасширения.ПометкаУдаления = ИСТИНА";

    Выборка = Запрос.Выполнить().Выбрать();

    Пока Выборка.Следующий() Цикл
        Попытка
            Объект = Выборка.Ссылка.ПолучитьОбъект();
            Если Объект <> Неопределено Тогда
                Объект.Удалить();
                Сообщить("Удален объект: " + Выборка.Ссылка);
            Иначе
                Сообщить("Объект не найден для удаления: " + Выборка.Ссылка);
            КонецЕсли;
        Исключение
            Сообщить("Ошибка при удалении объекта " + Выборка.Ссылка + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЦикла;

    Сообщить("Удаление помеченных объектов расширения завершено.");

КонецПроцедуры

Этот подход требует, чтобы вы для каждого типа объектов расширения (справочники, документы и т.д.) написали похожий код. Это может быть трудоемко, если у вас много различных типов объектов в расширении. Однако, как временное решение или в случае небольшого количества объектов, это вполне рабочий вариант.

Влияние ролей и прав доступа

В ходе обсуждения часто возникает вопрос о влиянии ролей и прав доступа на процесс удаления. Важно понимать, что в большинстве случаев, когда объекты из расширения не удаляются стандартным механизмом, проблема кроется не в правах пользователя, а в механизме работы платформы с метаданными. Даже пользователь с "Полными правами" может столкнуться с описанной проблемой, если платформа ниже 8.3.20 и функция ОписаниеТипаВсеСсылки не переопределена.

Тем не менее, права доступа играют свою роль в других аспектах:

  1. Права на пометку удаления: У пользователя должны быть права на "Пометку удаления" для соответствующих объектов в расширении, чтобы он мог вообще пометить их.
  2. Права на удаление: Для окончательного удаления объектов пользователь должен иметь право на "Удаление" этих объектов.
  3. Роли в расширении: Если вы заимствуете роли из основной конфигурации в расширение, а затем пытаетесь дать их на новые объекты расширения, это может привести к путанице. Заимствованные роли сами по себе "не знают" о новых объектах расширения. Если вы добавляете новую роль в расширение, убедитесь, что вы явно включаете в нее права на ваши новые объекты расширения и назначаете эту роль пользователю.

В рассматриваемом случае, когда автор вопроса утверждал, что программное удаление работает (сообщение 13), это прямо указывает на то, что права доступа на удаление у пользователя были достаточными, и проблема была именно в том, как стандартный механизм "видел" или "не видел" объекты расширения.

Дополнительные рекомендации и советы

В процессе работы с расширениями и отладки подобных проблем мы рекомендуем учитывать следующие моменты:

  1. Очистка кэша: После внесения изменений в расширение (особенно в части метаданных или общего кода) всегда рекомендуется очищать кэш клиента и сервера 1С. Это помогает избежать ситуаций, когда система работает со старыми метаданными или кодом.
  2. Тестирование и исправление базы: Регулярно выполняйте "Тестирование и исправление" информационной базы через конфигуратор. Это может помочь выявить и устранить логические ошибки и нарушения ссылочной целостности, которые могли возникнуть по другим причинам.
  3. Отладка: Если проблема сохраняется, используйте отладчик 1С. Поставьте точку останова в функции ОписаниеТипаВсеСсылки (если вы ее переопределяете) и проследите, какие типы объектов она возвращает. Это поможет убедиться, что объекты вашего расширения действительно попадают в результирующий список.
  4. Имя расширения: Внимательно проверяйте имя вашего расширения, которое используется в коде для фильтрации объектов (например, "СТК" в примере). Оно должно точно соответствовать имени, заданному в свойствах расширения в конфигураторе.

Мы видим, что проблема с неудаляемыми объектами из расширения 1С:Предприятие, хотя и может показаться мистической на первый взгляд, имеет четкое техническое объяснение и несколько эффективных решений. Выбор конкретного решения будет зависеть от вашей версии платформы и готовности к ее обновлению или реализации программных обходов.

← На главную